Commit 18a6b28c authored by dimich's avatar dimich Committed by Commit bot

SnapshotController implementation. It will be used in WebContentsObservers for...

SnapshotController implementation. It will be used in WebContentsObservers for Offline Pages - to detect the right moment to make a snapshot.
It will take input and generate a sequence of StartSnapshot calls (typically 1 or 2) to its Client.
This is initial implementation, in the future we might update the logic to rely not on a timer and onload event,
but rather on some more specific indicators (meaningful layout or proportion of resources loaded).

BUG=606106

Review URL: https://codereview.chromium.org/1920603002

Cr-Commit-Position: refs/heads/master@{#389843}
parent 70974524
......@@ -409,6 +409,7 @@
'offline_page_unittest_sources': [
'offline_pages/offline_page_metadata_store_impl_unittest.cc',
'offline_pages/offline_page_model_unittest.cc',
'offline_pages/snapshot_controller_unittest.cc',
],
'omnibox_unittest_sources': [
'omnibox/browser/answers_cache_unittest.cc',
......
......@@ -32,6 +32,8 @@
'offline_pages/offline_page_metadata_store.h',
'offline_pages/offline_page_metadata_store_impl.cc',
'offline_pages/offline_page_metadata_store_impl.h',
'offline_pages/snapshot_controller.cc',
'offline_pages/snapshot_controller.h',
],
},
{
......
......@@ -18,6 +18,8 @@ static_library("offline_pages") {
"offline_page_metadata_store_impl.h",
"offline_page_model.cc",
"offline_page_model.h",
"snapshot_controller.cc",
"snapshot_controller.h",
]
deps = [
......@@ -68,6 +70,7 @@ source_set("unit_tests") {
sources = [
"offline_page_metadata_store_impl_unittest.cc",
"offline_page_model_unittest.cc",
"snapshot_controller_unittest.cc",
]
deps = [
......
// Copyright 2016 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "components/offline_pages/snapshot_controller.h"
#include "base/bind.h"
#include "base/location.h"
#include "base/logging.h"
#include "base/time/time.h"
namespace {
// Delay, in milliseconds, between the main document parsed event and snapshot.
// Note if the "load" event fires before this delay is up, then the snapshot
// is taken immediately.
const size_t kDelayAfterDocumentAvailable = 7000;
} // namespace
namespace offline_pages {
SnapshotController::SnapshotController(
const scoped_refptr<base::SingleThreadTaskRunner>& task_runner,
SnapshotController::Client* client)
: task_runner_(task_runner),
client_(client),
state_(State::kReady),
weak_ptr_factory_(this) {
}
SnapshotController::~SnapshotController() {}
void SnapshotController::Reset() {
state_ = State::kReady;
}
void SnapshotController::Stop() {
state_ = State::kStopped;
}
void SnapshotController::PendingSnapshotCompleted() {
// Unless the controller is "stopped", enable the subsequent snapshots.
// Stopped state prevents any further snapshots form being started.
if (state_ == State::kStopped)
return;
DCHECK(state_ == State::kSnapshotPending);
state_ = State::kReady;
}
void SnapshotController::DocumentAvailableInMainFrame() {
// Post a delayed task. The snapshot will happen either when the delay
// is up, or if the "load" event is dispatched in the main frame.
task_runner_->PostDelayedTask(
FROM_HERE,
base::Bind(&SnapshotController::MaybeStartSnapshot,
weak_ptr_factory_.GetWeakPtr()),
base::TimeDelta::FromMilliseconds(kDelayAfterDocumentAvailable));
}
void SnapshotController::DocumentOnLoadCompletedInMainFrame() {
MaybeStartSnapshot();
// No more snapshots after onLoad (there still can be other events
// or delayed tasks that can try to start another snapshot)
Stop();
}
void SnapshotController::MaybeStartSnapshot() {
if (state_ != State::kReady)
return;
if (client_->StartSnapshot())
state_ = State::kSnapshotPending;
}
size_t SnapshotController::GetDelayAfterDocumentAvailableForTest() {
return kDelayAfterDocumentAvailable;
}
} // namespace offline_pages
// Copyright 2016 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef COMPONENTS_OFFLINE_PAGES_SNAPSHOT_CONTROLLER_H_
#define COMPONENTS_OFFLINE_PAGES_SNAPSHOT_CONTROLLER_H_
#include "base/memory/ref_counted.h"
#include "base/memory/weak_ptr.h"
#include "base/single_thread_task_runner.h"
namespace offline_pages {
// Takes various signals and produces StartSnapshot calls following a specific
// policy. Can request snapshots multiple times per 'session'. Session can be
// ended and another one started by calling Reset().
// Main invariants:
// - It never starts overlapping snapshots, Client reports when previous
// snapshot is done.
// - The currently worked on (pending) snapshot is always allowed to complete,
// the new attempts to start a snapshot are ignored until it does.
// - Some signals prevent more snapshots to be taken.
// OnLoad is currently such signal.
class SnapshotController {
public:
// kStateReady - listening to input, will start snapshot when needed.
// kStateSnapshotPending - snapshot is in progress, don't start another.
// kStateStopped - terminal state, no snapshots until reset.
enum class State { kReady, kSnapshotPending, kStopped };
// Client of the SnapshotController.
class Client {
public:
// Invoked at a good moment to start a snapshot. May be invoked multiple
// times, but not in overlapping manner - waits until
// PreviousSnapshotCompleted() before the next StatrSnapshot().
// Client should overwrite the result of previous snapshot with the new one,
// it is assumed that later snapshots are better then previous.
// Returns true if the snapshot actually started.
virtual bool StartSnapshot() = 0;
protected:
virtual ~Client() {}
};
SnapshotController(
const scoped_refptr<base::SingleThreadTaskRunner>& task_runner,
SnapshotController::Client* client);
virtual ~SnapshotController();
// Resets the 'session', returning controller to initial state.
void Reset();
// Stops current session, no more Client::StartSnapshot calls will be
// invoked from the SnapshotController until current session is Reset().
// Called by Client, for example when it encounters an error loading the page.
void Stop();
// The way for Client to report that previously started snapshot is
// now completed (so the next one can be started).
void PendingSnapshotCompleted();
// Invoked from WebContentObserver::DocumentAvailableInMainFrame
void DocumentAvailableInMainFrame();
// Invoked from WebContentObserver::DocumentOnLoadCompletedInMainFrame
void DocumentOnLoadCompletedInMainFrame();
size_t GetDelayAfterDocumentAvailableForTest();
private:
void MaybeStartSnapshot();
scoped_refptr<base::SingleThreadTaskRunner> task_runner_;
// Client owns this class.
SnapshotController::Client* client_;
SnapshotController::State state_;
base::WeakPtrFactory<SnapshotController> weak_ptr_factory_;
DISALLOW_COPY_AND_ASSIGN(SnapshotController);
};
} // namespace offline_pages
#endif // COMPONENTS_OFFLINE_PAGES_SNAPSHOT_CONTROLLER_H_
// Copyright 2016 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "components/offline_pages/snapshot_controller.h"
#include "base/bind.h"
#include "base/run_loop.h"
#include "base/single_thread_task_runner.h"
#include "base/test/test_mock_time_task_runner.h"
#include "base/thread_task_runner_handle.h"
#include "base/time/time.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace offline_pages {
class SnapshotControllerTest
: public testing::Test,
public SnapshotController::Client {
public:
SnapshotControllerTest();
~SnapshotControllerTest() override;
SnapshotController* controller() { return controller_.get(); }
void set_snapshot_started(bool started) { snapshot_started_ = started; }
int snapshot_count() { return snapshot_count_; }
// testing::Test
void SetUp() override;
void TearDown() override;
// SnapshotController::Client
bool StartSnapshot() override;
// Utility methods.
// Runs until all of the tasks that are not delayed are gone from the task
// queue.
void PumpLoop();
// Fast-forwards virtual time by |delta|, causing tasks with a remaining
// delay less than or equal to |delta| to be executed.
void FastForwardBy(base::TimeDelta delta);
private:
std::unique_ptr<SnapshotController> controller_;
scoped_refptr<base::TestMockTimeTaskRunner> task_runner_;
bool snapshot_started_;
int snapshot_count_;
};
SnapshotControllerTest::SnapshotControllerTest()
: task_runner_(new base::TestMockTimeTaskRunner),
snapshot_started_(true),
snapshot_count_(0) {
}
SnapshotControllerTest::~SnapshotControllerTest() {
}
void SnapshotControllerTest::SetUp() {
controller_.reset(new SnapshotController(task_runner_, this));
snapshot_started_ = true;
}
void SnapshotControllerTest::TearDown() {
controller_.reset();
}
bool SnapshotControllerTest::StartSnapshot() {
snapshot_count_++;
return snapshot_started_;
}
void SnapshotControllerTest::PumpLoop() {
task_runner_->RunUntilIdle();
}
void SnapshotControllerTest::FastForwardBy(base::TimeDelta delta) {
task_runner_->FastForwardBy(delta);
}
TEST_F(SnapshotControllerTest, OnLoad) {
// Onload should make snapshot right away.
EXPECT_EQ(0, snapshot_count());
controller()->DocumentOnLoadCompletedInMainFrame();
PumpLoop();
EXPECT_EQ(1, snapshot_count());
}
TEST_F(SnapshotControllerTest, OnDocumentAvailable) {
EXPECT_GT(controller()->GetDelayAfterDocumentAvailableForTest(), 0UL);
// OnDOM should make snapshot after a delay.
controller()->DocumentAvailableInMainFrame();
PumpLoop();
EXPECT_EQ(0, snapshot_count());
FastForwardBy(base::TimeDelta::FromMilliseconds(
controller()->GetDelayAfterDocumentAvailableForTest()));
EXPECT_EQ(1, snapshot_count());
}
TEST_F(SnapshotControllerTest, OnLoadSnapshotIsTheLastOne) {
// OnDOM should make snapshot after a delay.
controller()->DocumentAvailableInMainFrame();
PumpLoop();
EXPECT_EQ(0, snapshot_count());
// This should start snapshot immediately.
controller()->DocumentOnLoadCompletedInMainFrame();
EXPECT_EQ(1, snapshot_count());
// Report that snapshot is completed.
controller()->PendingSnapshotCompleted();
// Even though previous snapshot is completed, new one should not start
// when this delay expires.
FastForwardBy(base::TimeDelta::FromMilliseconds(
controller()->GetDelayAfterDocumentAvailableForTest()));
EXPECT_EQ(1, snapshot_count());
}
TEST_F(SnapshotControllerTest, OnLoadSnapshotAfterLongDelay) {
// OnDOM should make snapshot after a delay.
controller()->DocumentAvailableInMainFrame();
PumpLoop();
EXPECT_EQ(0, snapshot_count());
FastForwardBy(base::TimeDelta::FromMilliseconds(
controller()->GetDelayAfterDocumentAvailableForTest()));
EXPECT_EQ(1, snapshot_count());
// Report that snapshot is completed.
controller()->PendingSnapshotCompleted();
// This should start snapshot immediately.
controller()->DocumentOnLoadCompletedInMainFrame();
EXPECT_EQ(2, snapshot_count());
}
TEST_F(SnapshotControllerTest, Stop) {
// OnDOM should make snapshot after a delay.
controller()->DocumentAvailableInMainFrame();
PumpLoop();
EXPECT_EQ(0, snapshot_count());
controller()->Stop();
FastForwardBy(base::TimeDelta::FromMilliseconds(
controller()->GetDelayAfterDocumentAvailableForTest()));
// Should not start snapshots
EXPECT_EQ(0, snapshot_count());
// Also should not start snapshot.
controller()->DocumentOnLoadCompletedInMainFrame();
EXPECT_EQ(0, snapshot_count());
}
TEST_F(SnapshotControllerTest, ClientDidntStartSnapshot) {
// This will tell that Client did not start the snapshot
set_snapshot_started(false);
controller()->DocumentAvailableInMainFrame();
FastForwardBy(base::TimeDelta::FromMilliseconds(
controller()->GetDelayAfterDocumentAvailableForTest()));
// Should have one snapshot requested, but not reported started.
EXPECT_EQ(1, snapshot_count());
// Should start another snapshot since previous did not start
controller()->DocumentOnLoadCompletedInMainFrame();
EXPECT_EQ(2, snapshot_count());
}
} // namespace offline_pages
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment