blob: f32721b9bf9449c9f4d261e4e6603555c3b7e0b4 [file] [log] [blame]
// Copyright 2019 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 "third_party/blink/renderer/core/content_capture/content_capture_manager.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/scoped_feature_list.h"
#include "third_party/blink/public/common/features.h"
#include "third_party/blink/public/web/web_content_capture_client.h"
#include "third_party/blink/public/web/web_content_holder.h"
#include "third_party/blink/renderer/bindings/core/v8/v8_gc_controller.h"
#include "third_party/blink/renderer/core/dom/dom_node_ids.h"
#include "third_party/blink/renderer/core/dom/element.h"
#include "third_party/blink/renderer/core/dom/node.h"
#include "third_party/blink/renderer/core/frame/web_local_frame_impl.h"
#include "third_party/blink/renderer/core/html/html_iframe_element.h"
#include "third_party/blink/renderer/core/html_element_type_helpers.h"
#include "third_party/blink/renderer/core/layout/layout_object.h"
#include "third_party/blink/renderer/core/layout/layout_text.h"
#include "third_party/blink/renderer/core/loader/empty_clients.h"
#include "third_party/blink/renderer/core/testing/page_test_base.h"
#include "third_party/blink/renderer/core/testing/sim/sim_request.h"
#include "third_party/blink/renderer/core/testing/sim/sim_test.h"
#include "third_party/blink/renderer/platform/heap/heap.h"
#include "third_party/blink/renderer/platform/wtf/hash_set.h"
namespace blink {
namespace {
gfx::Rect GetRect(LayoutObject* layout_object) {
return gfx::Rect(EnclosingIntRect(layout_object->VisualRectInDocument()));
}
void FindNodeVectorsDiff(const Vector<Persistent<Node>>& a,
const Vector<Persistent<Node>>& b,
Vector<Persistent<Node>>& a_diff_b) {
for (auto& i : a) {
if (!b.Contains(i))
a_diff_b.push_back(i);
}
}
void FindNodeVectorsDiff(const Vector<Persistent<Node>>& a,
const Vector<Persistent<Node>>& b,
Vector<Persistent<Node>>& a_diff_b,
Vector<Persistent<Node>>& b_diff_a) {
FindNodeVectorsDiff(a, b, a_diff_b);
FindNodeVectorsDiff(b, a, b_diff_a);
}
void FindNodeVectorsUnion(const Vector<Persistent<Node>>& a,
const Vector<Persistent<Node>>& b,
HashSet<Persistent<Node>>& a_and_b) {
for (auto& n : a) {
a_and_b.insert(n);
}
for (auto& n : b) {
a_and_b.insert(n);
}
}
void ToNodeIds(const Vector<Persistent<Node>>& nodes,
Vector<int64_t>& node_ids) {
for (auto& v : nodes) {
node_ids.push_back(reinterpret_cast<int64_t>(static_cast<Node*>(v)));
}
}
void ToNodeTexts(const Vector<Persistent<Node>>& nodes,
Vector<std::string>& texts) {
for (auto& n : nodes)
texts.push_back(n->nodeValue().Utf8());
}
} // namespace
class WebContentCaptureClientTestHelper : public WebContentCaptureClient {
public:
~WebContentCaptureClientTestHelper() override = default;
void GetTaskTimingParameters(base::TimeDelta& short_delay,
base::TimeDelta& long_delay) const override {
short_delay = GetTaskShortDelay();
long_delay = GetTaskLongDelay();
}
base::TimeDelta GetTaskLongDelay() const {
return base::TimeDelta::FromMilliseconds(5000);
}
base::TimeDelta GetTaskShortDelay() const {
return base::TimeDelta::FromMilliseconds(500);
}
void DidCaptureContent(const WebVector<WebContentHolder>& data,
bool first_data) override {
data_ = data;
first_data_ = first_data;
for (auto& d : data) {
auto text = d.GetValue().Utf8();
all_text_.push_back(text);
captured_text_.push_back(text);
}
}
void DidUpdateContent(const WebVector<WebContentHolder>& data) override {
updated_data_ = data;
for (auto& d : data)
updated_text_.push_back(d.GetValue().Utf8());
}
void DidRemoveContent(WebVector<int64_t> data) override {
removed_data_ = data;
}
bool FirstData() const { return first_data_; }
const WebVector<WebContentHolder>& Data() const { return data_; }
const WebVector<WebContentHolder>& UpdatedData() const {
return updated_data_;
}
const Vector<std::string>& AllText() const { return all_text_; }
const Vector<std::string>& CapturedText() const { return captured_text_; }
const Vector<std::string>& UpdatedText() const { return updated_text_; }
const WebVector<int64_t>& RemovedData() const { return removed_data_; }
void ResetResults() {
first_data_ = false;
data_.Clear();
updated_data_.Clear();
removed_data_.Clear();
captured_text_.clear();
}
private:
bool first_data_ = false;
WebVector<WebContentHolder> data_;
WebVector<WebContentHolder> updated_data_;
WebVector<int64_t> removed_data_;
Vector<std::string> all_text_;
Vector<std::string> updated_text_;
Vector<std::string> captured_text_;
};
class ContentCaptureTaskTestHelper : public ContentCaptureTask {
public:
ContentCaptureTaskTestHelper(LocalFrame& local_frame_root,
TaskSession& task_session,
WebContentCaptureClient& content_capture_client)
: ContentCaptureTask(local_frame_root, task_session),
content_capture_client_(&content_capture_client) {}
void SetTaskStopState(TaskState state) { task_stop_state_ = state; }
protected:
WebContentCaptureClient* GetWebContentCaptureClient(
const Document& document) override {
return content_capture_client_;
}
bool ShouldPause() override {
return GetTaskStateForTesting() == task_stop_state_;
}
private:
WebContentCaptureClient* content_capture_client_;
TaskState task_stop_state_ = TaskState::kStop;
};
class ContentCaptureManagerTestHelper : public ContentCaptureManager {
public:
ContentCaptureManagerTestHelper(
LocalFrame& local_frame_root,
WebContentCaptureClientTestHelper& content_capture_client)
: ContentCaptureManager(local_frame_root) {
content_capture_task_ = MakeGarbageCollected<ContentCaptureTaskTestHelper>(
local_frame_root, GetTaskSessionForTesting(), content_capture_client);
}
ContentCaptureTaskTestHelper* GetContentCaptureTask() {
return content_capture_task_;
}
void Trace(Visitor* visitor) const override {
visitor->Trace(content_capture_task_);
ContentCaptureManager::Trace(visitor);
}
protected:
ContentCaptureTask* CreateContentCaptureTask() override {
return content_capture_task_;
}
private:
Member<ContentCaptureTaskTestHelper> content_capture_task_;
};
class ContentCaptureLocalFrameClientHelper : public EmptyLocalFrameClient {
public:
ContentCaptureLocalFrameClientHelper(WebContentCaptureClient& client)
: client_(client) {}
WebContentCaptureClient* GetWebContentCaptureClient() const override {
return &client_;
}
private:
WebContentCaptureClient& client_;
};
class ContentCaptureTest
: public PageTestBase,
public ::testing::WithParamInterface<std::vector<base::Feature>> {
public:
ContentCaptureTest() {
EnablePlatform();
feature_list_.InitWithFeatures(
GetParam(), /*disabled_features=*/std::vector<base::Feature>());
}
void SetUp() override {
content_capture_client_ =
std::make_unique<WebContentCaptureClientTestHelper>();
local_frame_client_ =
MakeGarbageCollected<ContentCaptureLocalFrameClientHelper>(
*content_capture_client_);
SetupPageWithClients(nullptr, local_frame_client_);
SetHtmlInnerHTML(
"<!DOCTYPE HTML>"
"<p id='p1'>1</p>"
"<p id='p2'>2</p>"
"<p id='p3'>3</p>"
"<p id='p4'>4</p>"
"<p id='p5'>5</p>"
"<p id='p6'>6</p>"
"<p id='p7'>7</p>"
"<p id='p8'>8</p>"
"<div id='d1'></div>"
"<p id='invisible'>invisible</p>");
platform()->SetAutoAdvanceNowToPendingTasks(false);
// TODO(michaelbai): ContentCaptureManager should be get from LocalFrame.
content_capture_manager_ =
MakeGarbageCollected<ContentCaptureManagerTestHelper>(
GetFrame(), *content_capture_client_);
InitNodeHolders();
// Setup captured content to ContentCaptureTask, it isn't necessary once
// ContentCaptureManager is created by LocalFrame.
content_capture_manager_->GetContentCaptureTask()
->SetCapturedContentForTesting(node_ids_);
InitScrollingTestData();
}
void SimulateScrolling(size_t step) {
CHECK_LT(step, 4u);
content_capture_manager_->GetContentCaptureTask()
->SetCapturedContentForTesting(scrolling_node_ids_[step]);
content_capture_manager_->OnScrollPositionChanged();
}
void CreateTextNodeAndNotifyManager() {
Document& doc = GetDocument();
Node* node = doc.createTextNode("New Text");
Element* element = MakeGarbageCollected<Element>(html_names::kPTag, &doc);
element->appendChild(node);
Element* div_element = GetElementById("d1");
div_element->appendChild(element);
UpdateAllLifecyclePhasesForTest();
GetContentCaptureManager()->ScheduleTaskIfNeeded(*node);
created_node_id_ = DOMNodeIds::IdForNode(node);
Vector<cc::NodeInfo> captured_content{
cc::NodeInfo(created_node_id_, GetRect(node->GetLayoutObject()))};
content_capture_manager_->GetContentCaptureTask()
->SetCapturedContentForTesting(captured_content);
}
ContentCaptureManagerTestHelper* GetContentCaptureManager() const {
return content_capture_manager_;
}
WebContentCaptureClientTestHelper* GetWebContentCaptureClient() const {
return content_capture_client_.get();
}
ContentCaptureTaskTestHelper* GetContentCaptureTask() const {
return GetContentCaptureManager()->GetContentCaptureTask();
}
void RunContentCaptureTask() {
ResetResult();
platform()->RunForPeriod(GetWebContentCaptureClient()->GetTaskShortDelay());
}
void RunLongDelayContentCaptureTask() {
ResetResult();
platform()->RunForPeriod(GetWebContentCaptureClient()->GetTaskLongDelay());
}
void RemoveNode(Node* node) {
// Remove the node.
node->remove();
GetContentCaptureManager()->OnLayoutTextWillBeDestroyed(*node);
}
void RemoveUnsentNode(const WebVector<WebContentHolder>& sent_nodes) {
// Find a node isn't in sent_nodes
for (auto node : nodes_) {
bool found_in_sent = false;
for (auto& sent : sent_nodes) {
found_in_sent = (node->nodeValue().Utf8().c_str() == sent.GetValue());
if (found_in_sent)
break;
}
if (!found_in_sent) {
RemoveNode(node);
return;
}
}
// Didn't find unsent nodes.
NOTREACHED();
}
size_t GetExpectedFirstResultSize() { return ContentCaptureTask::kBatchSize; }
size_t GetExpectedSecondResultSize() {
return node_ids_.size() - GetExpectedFirstResultSize();
}
const Vector<cc::NodeInfo>& NodeIds() const { return node_ids_; }
const Vector<Persistent<Node>> Nodes() const { return nodes_; }
Node& invisible_node() const { return *invisible_node_; }
const Vector<Vector<std::string>>& scrolling_expected_captured_nodes() {
return scrolling_expected_captured_nodes_;
}
const Vector<Vector<int64_t>>& scrolling_expected_removed_nodes() {
return scrolling_expected_removed_nodes_;
}
private:
void ResetResult() {
GetWebContentCaptureClient()->ResetResults();
}
void BuildNodesInfo(const Vector<std::string>& ids,
Vector<Persistent<Node>>& nodes,
Vector<cc::NodeInfo>& node_ids) {
for (auto id : ids) {
Node* node = GetElementById(id.c_str())->firstChild();
CHECK(node);
LayoutObject* layout_object = node->GetLayoutObject();
CHECK(layout_object);
CHECK(layout_object->IsText());
nodes.push_back(node);
GetContentCaptureManager()->ScheduleTaskIfNeeded(*node);
node_ids.push_back(
cc::NodeInfo(DOMNodeIds::IdForNode(node), GetRect(layout_object)));
}
}
// TODO(michaelbai): Remove this once integrate with LayoutText.
void InitNodeHolders() {
BuildNodesInfo(
Vector<std::string>{"p1", "p2", "p3", "p4", "p5", "p6", "p7", "p8"},
nodes_, node_ids_);
invisible_node_ = GetElementById("invisible")->firstChild();
DCHECK(invisible_node_.Get());
}
void InitScrollingTestData() {
Vector<Vector<Persistent<Node>>> nodes{4};
BuildNodesInfo(Vector<std::string>{"p1", "p2", "p3"}, nodes[0],
scrolling_node_ids_[0]);
BuildNodesInfo(Vector<std::string>{"p3", "p4", "p5"}, nodes[1],
scrolling_node_ids_[1]);
BuildNodesInfo(Vector<std::string>{"p6", "p7", "p8"}, nodes[2],
scrolling_node_ids_[2]);
BuildNodesInfo(Vector<std::string>{"p2", "p3"}, nodes[3],
scrolling_node_ids_[3]);
// Build expected result.
if (base::FeatureList::IsEnabled(
features::kContentCaptureConstantStreaming)) {
for (int i = 0; i < 4; ++i) {
Vector<Persistent<Node>> a_diff_b;
Vector<Persistent<Node>> b_diff_a;
FindNodeVectorsDiff(nodes[i],
i == 0 ? Vector<Persistent<Node>>() : nodes[i - 1],
a_diff_b, b_diff_a);
ToNodeTexts(a_diff_b, scrolling_expected_captured_nodes_[i]);
ToNodeIds(b_diff_a, scrolling_expected_removed_nodes_[i]);
}
} else {
HashSet<Persistent<Node>> sent;
for (int i = 0; i < 4; ++i) {
Vector<Persistent<Node>> a_diff_b;
Vector<Persistent<Node>> b;
CopyToVector(sent, b);
FindNodeVectorsDiff(nodes[i], b, a_diff_b);
ToNodeTexts(a_diff_b, scrolling_expected_captured_nodes_[i]);
sent.clear();
FindNodeVectorsUnion(b, nodes[i], sent);
}
}
}
Vector<Persistent<Node>> nodes_;
Vector<cc::NodeInfo> node_ids_;
Persistent<Node> invisible_node_;
Vector<Vector<std::string>> scrolling_expected_captured_nodes_{4};
Vector<Vector<int64_t>> scrolling_expected_removed_nodes_{4};
Vector<Vector<cc::NodeInfo>> scrolling_node_ids_{4};
std::unique_ptr<WebContentCaptureClientTestHelper> content_capture_client_;
Persistent<ContentCaptureManagerTestHelper> content_capture_manager_;
Persistent<ContentCaptureLocalFrameClientHelper> local_frame_client_;
DOMNodeId created_node_id_ = kInvalidDOMNodeId;
base::test::ScopedFeatureList feature_list_;
};
INSTANTIATE_TEST_SUITE_P(
,
ContentCaptureTest,
testing::Values(
std::vector<base::Feature>{},
std::vector<base::Feature>{features::kContentCaptureUserActivatedDelay},
std::vector<base::Feature>{features::kContentCaptureConstantStreaming},
std::vector<base::Feature>{
features::kContentCaptureUserActivatedDelay,
features::kContentCaptureConstantStreaming}));
TEST_P(ContentCaptureTest, Basic) {
RunContentCaptureTask();
EXPECT_EQ(ContentCaptureTask::TaskState::kStop,
GetContentCaptureTask()->GetTaskStateForTesting());
EXPECT_FALSE(GetWebContentCaptureClient()->Data().empty());
EXPECT_EQ(GetExpectedSecondResultSize(),
GetWebContentCaptureClient()->Data().size());
}
TEST_P(ContentCaptureTest, Scrolling) {
for (size_t step = 0; step < 4; ++step) {
SimulateScrolling(step);
RunContentCaptureTask();
EXPECT_EQ(ContentCaptureTask::TaskState::kStop,
GetContentCaptureTask()->GetTaskStateForTesting());
EXPECT_THAT(GetWebContentCaptureClient()->CapturedText(),
testing::UnorderedElementsAreArray(
scrolling_expected_captured_nodes()[step]))
<< "at step " << step;
EXPECT_THAT(GetWebContentCaptureClient()->RemovedData(),
testing::UnorderedElementsAreArray(
scrolling_expected_removed_nodes()[step]))
<< "at step " << step;
}
}
TEST_P(ContentCaptureTest, PauseAndResume) {
// The task stops before captures content.
GetContentCaptureTask()->SetTaskStopState(
ContentCaptureTask::TaskState::kCaptureContent);
RunContentCaptureTask();
EXPECT_FALSE(GetWebContentCaptureClient()->FirstData());
EXPECT_TRUE(GetWebContentCaptureClient()->Data().empty());
EXPECT_TRUE(GetWebContentCaptureClient()->RemovedData().empty());
// The task stops before sends the captured content out.
GetContentCaptureTask()->SetTaskStopState(
ContentCaptureTask::TaskState::kProcessCurrentSession);
RunContentCaptureTask();
EXPECT_FALSE(GetWebContentCaptureClient()->FirstData());
EXPECT_TRUE(GetWebContentCaptureClient()->Data().empty());
EXPECT_TRUE(GetWebContentCaptureClient()->RemovedData().empty());
// The task should be stop at kProcessRetryTask because the captured content
// needs to be sent with 2 batch.
GetContentCaptureTask()->SetTaskStopState(
ContentCaptureTask::TaskState::kProcessRetryTask);
RunContentCaptureTask();
EXPECT_TRUE(GetWebContentCaptureClient()->FirstData());
EXPECT_FALSE(GetWebContentCaptureClient()->Data().empty());
EXPECT_TRUE(GetWebContentCaptureClient()->RemovedData().empty());
EXPECT_EQ(GetExpectedFirstResultSize(),
GetWebContentCaptureClient()->Data().size());
// Run task until it stops, task will not capture content, because there is no
// content change, so we have 3 NodeHolders.
GetContentCaptureTask()->SetTaskStopState(
ContentCaptureTask::TaskState::kStop);
RunContentCaptureTask();
EXPECT_FALSE(GetWebContentCaptureClient()->FirstData());
EXPECT_FALSE(GetWebContentCaptureClient()->Data().empty());
EXPECT_TRUE(GetWebContentCaptureClient()->RemovedData().empty());
EXPECT_EQ(GetExpectedSecondResultSize(),
GetWebContentCaptureClient()->Data().size());
}
TEST_P(ContentCaptureTest, NodeOnlySendOnce) {
// Send all nodes
RunContentCaptureTask();
EXPECT_FALSE(GetWebContentCaptureClient()->Data().empty());
EXPECT_EQ(GetExpectedSecondResultSize(),
GetWebContentCaptureClient()->Data().size());
GetContentCaptureManager()->OnScrollPositionChanged();
RunContentCaptureTask();
EXPECT_TRUE(GetWebContentCaptureClient()->Data().empty());
EXPECT_TRUE(GetWebContentCaptureClient()->RemovedData().empty());
}
TEST_P(ContentCaptureTest, UnsentNode) {
// Send all nodes expect |invisible_node_|.
RunContentCaptureTask();
EXPECT_FALSE(GetWebContentCaptureClient()->Data().empty());
EXPECT_EQ(GetExpectedSecondResultSize(),
GetWebContentCaptureClient()->Data().size());
// Simulates the |invisible_node_| being changed, and verifies no content
// change because |invisible_node_| wasn't captured.
GetContentCaptureManager()->OnNodeTextChanged(invisible_node());
RunContentCaptureTask();
EXPECT_TRUE(GetWebContentCaptureClient()->Data().empty());
EXPECT_TRUE(GetWebContentCaptureClient()->UpdatedData().empty());
EXPECT_TRUE(GetWebContentCaptureClient()->RemovedData().empty());
// Simulates the |invisible_node_| being removed, and verifies no content
// change because |invisible_node_| wasn't captured.
GetContentCaptureManager()->OnLayoutTextWillBeDestroyed(invisible_node());
RunContentCaptureTask();
EXPECT_TRUE(GetWebContentCaptureClient()->Data().empty());
EXPECT_TRUE(GetWebContentCaptureClient()->UpdatedData().empty());
EXPECT_TRUE(GetWebContentCaptureClient()->RemovedData().empty());
}
TEST_P(ContentCaptureTest, RemoveNodeBeforeSendingOut) {
// Capture the content, but didn't send them.
GetContentCaptureTask()->SetTaskStopState(
ContentCaptureTask::TaskState::kProcessCurrentSession);
RunContentCaptureTask();
EXPECT_TRUE(GetWebContentCaptureClient()->Data().empty());
// Remove the node and sent the captured content out.
RemoveNode(Nodes().at(0));
GetContentCaptureTask()->SetTaskStopState(
ContentCaptureTask::TaskState::kProcessRetryTask);
RunContentCaptureTask();
EXPECT_EQ(GetExpectedFirstResultSize(),
GetWebContentCaptureClient()->Data().size());
EXPECT_EQ(0u, GetWebContentCaptureClient()->RemovedData().size());
RunContentCaptureTask();
// Total 7 content returned instead of 8.
EXPECT_EQ(GetExpectedSecondResultSize() - 1,
GetWebContentCaptureClient()->Data().size());
EXPECT_EQ(0u, GetWebContentCaptureClient()->RemovedData().size());
RunContentCaptureTask();
// No removed node because it hasn't been sent out.
EXPECT_EQ(0u, GetWebContentCaptureClient()->Data().size());
EXPECT_EQ(0u, GetWebContentCaptureClient()->RemovedData().size());
}
TEST_P(ContentCaptureTest, RemoveNodeInBetweenSendingOut) {
// Capture the content, but didn't send them.
GetContentCaptureTask()->SetTaskStopState(
ContentCaptureTask::TaskState::kProcessCurrentSession);
RunContentCaptureTask();
EXPECT_TRUE(GetWebContentCaptureClient()->Data().empty());
// Sends first batch.
GetContentCaptureTask()->SetTaskStopState(
ContentCaptureTask::TaskState::kProcessRetryTask);
RunContentCaptureTask();
EXPECT_EQ(GetExpectedFirstResultSize(),
GetWebContentCaptureClient()->Data().size());
EXPECT_EQ(0u, GetWebContentCaptureClient()->RemovedData().size());
// This relies on each node to have different value.
RemoveUnsentNode(GetWebContentCaptureClient()->Data());
GetContentCaptureTask()->SetTaskStopState(
ContentCaptureTask::TaskState::kProcessRetryTask);
RunContentCaptureTask();
// Total 7 content returned instead of 8.
EXPECT_EQ(GetExpectedSecondResultSize() - 1,
GetWebContentCaptureClient()->Data().size());
EXPECT_EQ(0u, GetWebContentCaptureClient()->RemovedData().size());
RunContentCaptureTask();
// No removed node because it hasn't been sent out.
EXPECT_EQ(0u, GetWebContentCaptureClient()->Data().size());
EXPECT_EQ(0u, GetWebContentCaptureClient()->RemovedData().size());
}
TEST_P(ContentCaptureTest, RemoveNodeAfterSendingOut) {
// Captures the content, but didn't send them.
GetContentCaptureTask()->SetTaskStopState(
ContentCaptureTask::TaskState::kProcessCurrentSession);
RunContentCaptureTask();
EXPECT_TRUE(GetWebContentCaptureClient()->Data().empty());
// Sends first batch.
GetContentCaptureTask()->SetTaskStopState(
ContentCaptureTask::TaskState::kProcessRetryTask);
RunContentCaptureTask();
EXPECT_EQ(GetExpectedFirstResultSize(),
GetWebContentCaptureClient()->Data().size());
EXPECT_EQ(0u, GetWebContentCaptureClient()->RemovedData().size());
// Sends second batch.
RunContentCaptureTask();
EXPECT_EQ(GetExpectedSecondResultSize(),
GetWebContentCaptureClient()->Data().size());
EXPECT_EQ(0u, GetWebContentCaptureClient()->RemovedData().size());
// Remove the node.
RemoveNode(Nodes().at(0));
RunLongDelayContentCaptureTask();
EXPECT_EQ(0u, GetWebContentCaptureClient()->Data().size());
EXPECT_EQ(1u, GetWebContentCaptureClient()->RemovedData().size());
}
TEST_P(ContentCaptureTest, TaskHistogramReporter) {
// This performs gc for all DocumentSession, flushes the existing
// SentContentCount and give a clean baseline for histograms.
// We are not sure if it always work, maybe still be the source of flaky.
ThreadState::Current()->CollectAllGarbageForTesting();
base::HistogramTester histograms;
// The task stops before captures content.
GetContentCaptureTask()->SetTaskStopState(
ContentCaptureTask::TaskState::kCaptureContent);
RunContentCaptureTask();
// Verify no histogram reported yet.
histograms.ExpectTotalCount(
ContentCaptureTaskHistogramReporter::kCaptureContentTime, 0u);
histograms.ExpectTotalCount(
ContentCaptureTaskHistogramReporter::kSendContentTime, 0u);
histograms.ExpectTotalCount(
ContentCaptureTaskHistogramReporter::kCaptureContentDelayTime, 0u);
histograms.ExpectTotalCount(
ContentCaptureTaskHistogramReporter::kSentContentCount, 0u);
histograms.ExpectTotalCount(
ContentCaptureTaskHistogramReporter::kTaskRunsPerCapture, 0u);
histograms.ExpectTotalCount(
ContentCaptureTaskHistogramReporter::kTaskDelayInMs, 1u);
// The task stops before sends the captured content out.
GetContentCaptureTask()->SetTaskStopState(
ContentCaptureTask::TaskState::kProcessCurrentSession);
RunContentCaptureTask();
// Verify has one CaptureContentTime record.
histograms.ExpectTotalCount(
ContentCaptureTaskHistogramReporter::kCaptureContentTime, 1u);
histograms.ExpectTotalCount(
ContentCaptureTaskHistogramReporter::kSendContentTime, 0u);
histograms.ExpectTotalCount(
ContentCaptureTaskHistogramReporter::kCaptureContentDelayTime, 0u);
histograms.ExpectTotalCount(
ContentCaptureTaskHistogramReporter::kSentContentCount, 0u);
// The task stops at kProcessRetryTask because the captured content
// needs to be sent with 2 batch.
GetContentCaptureTask()->SetTaskStopState(
ContentCaptureTask::TaskState::kProcessRetryTask);
RunContentCaptureTask();
// Verify has one CaptureContentTime, one SendContentTime and one
// CaptureContentDelayTime record.
histograms.ExpectTotalCount(
ContentCaptureTaskHistogramReporter::kCaptureContentTime, 1u);
histograms.ExpectTotalCount(
ContentCaptureTaskHistogramReporter::kSendContentTime, 1u);
histograms.ExpectTotalCount(
ContentCaptureTaskHistogramReporter::kCaptureContentDelayTime, 1u);
histograms.ExpectTotalCount(
ContentCaptureTaskHistogramReporter::kSentContentCount, 0u);
// Run task until it stops, task will not capture content, because there is no
// content change.
GetContentCaptureTask()->SetTaskStopState(
ContentCaptureTask::TaskState::kStop);
RunContentCaptureTask();
// Verify has two SendContentTime records.
histograms.ExpectTotalCount(
ContentCaptureTaskHistogramReporter::kCaptureContentTime, 1u);
histograms.ExpectTotalCount(
ContentCaptureTaskHistogramReporter::kSendContentTime, 2u);
histograms.ExpectTotalCount(
ContentCaptureTaskHistogramReporter::kCaptureContentDelayTime, 1u);
histograms.ExpectTotalCount(
ContentCaptureTaskHistogramReporter::kSentContentCount, 0u);
// Verify retry task won't count to TaskDelay metrics.
histograms.ExpectTotalCount(
ContentCaptureTaskHistogramReporter::kTaskDelayInMs, 1u);
histograms.ExpectTotalCount(
ContentCaptureTaskHistogramReporter::kTaskRunsPerCapture, 1u);
// Verify the task ran 4 times, first run stopped before capturing content
// and 2nd run captured content, 3rd and 4th run sent the content out.
histograms.ExpectBucketCount(
ContentCaptureTaskHistogramReporter::kTaskRunsPerCapture, 4u, 1u);
// Create a node and run task until it stops.
CreateTextNodeAndNotifyManager();
GetContentCaptureTask()->SetTaskStopState(
ContentCaptureTask::TaskState::kStop);
RunLongDelayContentCaptureTask();
histograms.ExpectTotalCount(
ContentCaptureTaskHistogramReporter::kCaptureContentTime, 2u);
histograms.ExpectTotalCount(
ContentCaptureTaskHistogramReporter::kSendContentTime, 3u);
histograms.ExpectTotalCount(
ContentCaptureTaskHistogramReporter::kCaptureContentDelayTime, 2u);
histograms.ExpectTotalCount(
ContentCaptureTaskHistogramReporter::kSentContentCount, 0u);
histograms.ExpectTotalCount(
ContentCaptureTaskHistogramReporter::kTaskRunsPerCapture, 2u);
// Verify the task ran 1 times for this session because we didn't explicitly
// stop it.
histograms.ExpectBucketCount(
ContentCaptureTaskHistogramReporter::kTaskRunsPerCapture, 1u, 1u);
GetContentCaptureTask()->ClearDocumentSessionsForTesting();
ThreadState::Current()->CollectAllGarbageForTesting();
histograms.ExpectTotalCount(
ContentCaptureTaskHistogramReporter::kCaptureContentTime, 2u);
histograms.ExpectTotalCount(
ContentCaptureTaskHistogramReporter::kSendContentTime, 3u);
histograms.ExpectTotalCount(
ContentCaptureTaskHistogramReporter::kCaptureContentDelayTime, 2u);
histograms.ExpectTotalCount(
ContentCaptureTaskHistogramReporter::kSentContentCount, 1u);
// Verify total content has been sent.
histograms.ExpectBucketCount(
ContentCaptureTaskHistogramReporter::kSentContentCount, 9u, 1u);
// Verify TaskDelay was recorded again for node change.
histograms.ExpectTotalCount(
ContentCaptureTaskHistogramReporter::kTaskDelayInMs, 2u);
}
TEST_P(ContentCaptureTest, RescheduleTask) {
// This test assumes test runs much faster than task's long delay which is 5s.
Persistent<ContentCaptureTaskTestHelper> task = GetContentCaptureTask();
task->CancelTaskForTesting();
EXPECT_TRUE(task->GetTaskNextFireIntervalForTesting().is_zero());
task->Schedule(
ContentCaptureTask::ScheduleReason::kNonUserActivatedContentChange);
auto begin = base::TimeTicks::Now();
base::TimeDelta interval1 = task->GetTaskNextFireIntervalForTesting();
task->Schedule(ContentCaptureTask::ScheduleReason::kScrolling);
base::TimeDelta interval2 = task->GetTaskNextFireIntervalForTesting();
auto test_running_time = base::TimeTicks::Now() - begin;
if (base::FeatureList::IsEnabled(
features::kContentCaptureUserActivatedDelay)) {
// The first scheduled task is always shortest even though caused by
// NonUserTriggered.
EXPECT_LE(interval1, GetWebContentCaptureClient()->GetTaskShortDelay());
EXPECT_LE(interval2, interval1);
} else {
// The interval1 will be greater than interval2 even the task wasn't
// rescheduled, removing the test_running_time from interval1 make sure
// task rescheduled.
EXPECT_GT(interval1 - test_running_time, interval2);
}
}
TEST_P(ContentCaptureTest, NotRescheduleTask) {
// This test assumes test runs much faster than task's long delay which is 5s.
Persistent<ContentCaptureTaskTestHelper> task = GetContentCaptureTask();
task->CancelTaskForTesting();
EXPECT_TRUE(task->GetTaskNextFireIntervalForTesting().is_zero());
task->Schedule(
ContentCaptureTask::ScheduleReason::kNonUserActivatedContentChange);
auto begin = base::TimeTicks::Now();
base::TimeDelta interval1 = task->GetTaskNextFireIntervalForTesting();
task->Schedule(
ContentCaptureTask::ScheduleReason::kNonUserActivatedContentChange);
base::TimeDelta interval2 = task->GetTaskNextFireIntervalForTesting();
auto test_running_time = base::TimeTicks::Now() - begin;
EXPECT_GE(interval1, interval2);
EXPECT_LE(interval1 - test_running_time, interval2);
}
// TODO(michaelbai): use RenderingTest instead of PageTestBase for multiple
// frame test.
class ContentCaptureSimTest : public SimTest {
public:
static const char* kEditableContent;
ContentCaptureSimTest() : client_(), child_client_() {}
void SetUp() override {
SimTest::SetUp();
MainFrame().SetContentCaptureClient(&client_);
SetupPage();
}
void RunContentCaptureTaskUntil(ContentCaptureTask::TaskState state) {
Client().ResetResults();
ChildClient().ResetResults();
GetDocument()
.GetFrame()
->LocalFrameRoot()
.GetContentCaptureManager()
->GetContentCaptureTaskForTesting()
->RunTaskForTestingUntil(state);
// Cancels the scheduled task to simulate that the task is running by
// scheduler.
GetContentCaptureManager()
->GetContentCaptureTaskForTesting()
->CancelTaskForTesting();
}
WebContentCaptureClientTestHelper& Client() { return client_; }
WebContentCaptureClientTestHelper& ChildClient() { return child_client_; }
enum class ContentType { kAll, kMainFrame, kChildFrame };
void SetCapturedContent(ContentType type) {
if (type == ContentType::kMainFrame) {
SetCapturedContent(main_frame_content_);
} else if (type == ContentType::kChildFrame) {
SetCapturedContent(child_frame_content_);
} else if (type == ContentType::kAll) {
Vector<cc::NodeInfo> holders(main_frame_content_);
holders.AppendRange(child_frame_content_.begin(),
child_frame_content_.end());
SetCapturedContent(holders);
}
}
void AddOneNodeToMainFrame() {
AddNodeToDocument(GetDocument(), main_frame_content_);
main_frame_expected_text_.push_back("New Text");
}
void AddOneNodeToChildFrame() {
AddNodeToDocument(*child_document_, child_frame_content_);
child_frame_expected_text_.push_back("New Text");
}
void InsertMainFrameEditableContent(const std::string& content,
unsigned offset) {
InsertNodeContent(GetDocument(), "editable_id", content, offset);
}
void DeleteMainFrameEditableContent(unsigned offset, unsigned length) {
DeleteNodeContent(GetDocument(), "editable_id", offset, length);
}
const Vector<std::string>& MainFrameExpectedText() const {
return main_frame_expected_text_;
}
const Vector<std::string>& ChildFrameExpectedText() const {
return child_frame_expected_text_;
}
void ReplaceMainFrameExpectedText(const std::string& old_text,
const std::string& new_text) {
std::replace(main_frame_expected_text_.begin(),
main_frame_expected_text_.end(), old_text, new_text);
}
ContentCaptureManager* GetContentCaptureManager() {
return DynamicTo<LocalFrame>(LocalFrameRoot().GetFrame())
->GetContentCaptureManager();
}
void SimulateUserInputOnMainFrame() {
GetContentCaptureManager()->NotifyInputEvent(
WebInputEvent::Type::kMouseDown,
*DynamicTo<LocalFrame>(MainFrame().GetFrame()));
}
void SimulateUserInputOnChildFrame() {
GetContentCaptureManager()->NotifyInputEvent(
WebInputEvent::Type::kMouseDown, *child_document_->GetFrame());
}
base::TimeDelta GetNextTaskDelay() {
return GetContentCaptureManager()
->GetContentCaptureTaskForTesting()
->GetTaskDelayForTesting()
.GetNextTaskDelay();
}
base::TimeDelta GetTaskNextFireInterval() {
return GetContentCaptureManager()
->GetContentCaptureTaskForTesting()
->GetTaskNextFireIntervalForTesting();
}
private:
void SetupPage() {
SimRequest main_resource("https://example.com/test.html", "text/html");
SimRequest frame_resource("https://example.com/frame.html", "text/html");
LoadURL("https://example.com/test.html");
WebView().MainFrameViewWidget()->Resize(gfx::Size(800, 6000));
main_resource.Complete(R"HTML(
<!DOCTYPE html>
<body style='background: white'>
<iframe id=frame name='frame' src=frame.html></iframe>
<p id='p1'>Hello World1</p>
<p id='p2'>Hello World2</p>
<p id='p3'>Hello World3</p>
<p id='p4'>Hello World4</p>
<p id='p5'>Hello World5</p>
<p id='p6'>Hello World6</p>
<p id='p7'>Hello World7</p>
<div id='editable_id'>editable</div>
<svg>
<text id="s8">Hello World8</text>
</svg>
<div id='d1'></div>
)HTML");
auto frame1 = Compositor().BeginFrame();
frame_resource.Complete(R"HTML(
<!DOCTYPE html>
<p id='c1'>Hello World11</p>
<p id='c2'>Hello World12</p>
<div id='d1'></div>
)HTML");
static_cast<WebLocalFrame*>(MainFrame().FindFrameByName("frame"))
->SetContentCaptureClient(&child_client_);
auto* child_frame_element =
To<HTMLIFrameElement>(GetDocument().getElementById("frame"));
child_document_ = child_frame_element->contentDocument();
child_document_->UpdateStyleAndLayout(DocumentUpdateReason::kTest);
Compositor().BeginFrame();
InitMainFrameNodeHolders();
InitChildFrameNodeHolders(*child_document_);
}
void InitMainFrameNodeHolders() {
Vector<std::string> ids = {"p1", "p2", "p3", "p4", "p5",
"p6", "p7", "s8", "editable_id"};
main_frame_expected_text_ = {
"Hello World1", "Hello World2", "Hello World3",
"Hello World4", "Hello World5", "Hello World6",
"Hello World7", "Hello World8", kEditableContent};
InitNodeHolders(main_frame_content_, ids, GetDocument());
EXPECT_EQ(9u, main_frame_content_.size());
}
void InitChildFrameNodeHolders(const Document& doc) {
Vector<std::string> ids = {"c1", "c2"};
child_frame_expected_text_ = {"Hello World11", "Hello World12"};
InitNodeHolders(child_frame_content_, ids, doc);
EXPECT_EQ(2u, child_frame_content_.size());
}
void InitNodeHolders(Vector<cc::NodeInfo>& buffer,
const Vector<std::string>& ids,
const Document& document) {
for (auto id : ids) {
auto* layout_object =
document.getElementById(id.c_str())->firstChild()->GetLayoutObject();
auto* layout_text = To<LayoutText>(layout_object);
EXPECT_TRUE(layout_text->HasNodeId());
buffer.push_back(
cc::NodeInfo(layout_text->EnsureNodeId(), GetRect(layout_object)));
}
}
void AddNodeToDocument(Document& doc, Vector<cc::NodeInfo>& buffer) {
Node* node = doc.createTextNode("New Text");
auto* element = MakeGarbageCollected<Element>(html_names::kPTag, &doc);
element->appendChild(node);
Element* div_element = doc.getElementById("d1");
div_element->appendChild(element);
Compositor().BeginFrame();
auto* layout_text = To<LayoutText>(node->GetLayoutObject());
EXPECT_TRUE(layout_text->HasNodeId());
buffer.push_back(cc::NodeInfo(layout_text->EnsureNodeId(),
GetRect(node->GetLayoutObject())));
}
void InsertNodeContent(Document& doc,
const std::string& id,
const std::string& content,
unsigned offset) {
To<Text>(doc.getElementById(id.c_str())->firstChild())
->insertData(offset, String(content.c_str()),
IGNORE_EXCEPTION_FOR_TESTING);
Compositor().BeginFrame();
}
void DeleteNodeContent(Document& doc,
const std::string& id,
unsigned offset,
unsigned length) {
To<Text>(doc.getElementById(id.c_str())->firstChild())
->deleteData(offset, length, IGNORE_EXCEPTION_FOR_TESTING);
Compositor().BeginFrame();
}
void SetCapturedContent(const Vector<cc::NodeInfo>& captured_content) {
GetDocument()
.GetFrame()
->LocalFrameRoot()
.GetContentCaptureManager()
->GetContentCaptureTaskForTesting()
->SetCapturedContentForTesting(captured_content);
}
Vector<std::string> main_frame_expected_text_;
Vector<std::string> child_frame_expected_text_;
Vector<cc::NodeInfo> main_frame_content_;
Vector<cc::NodeInfo> child_frame_content_;
WebContentCaptureClientTestHelper client_;
WebContentCaptureClientTestHelper child_client_;
Persistent<Document> child_document_;
};
const char* ContentCaptureSimTest::kEditableContent = "editable";
TEST_F(ContentCaptureSimTest, MultiFrame) {
SetCapturedContent(ContentType::kAll);
RunContentCaptureTaskUntil(ContentCaptureTask::TaskState::kStop);
EXPECT_EQ(4u, Client().Data().size());
EXPECT_EQ(2u, ChildClient().Data().size());
EXPECT_THAT(Client().AllText(),
testing::UnorderedElementsAreArray(MainFrameExpectedText()));
EXPECT_THAT(ChildClient().AllText(),
testing::UnorderedElementsAreArray(ChildFrameExpectedText()));
}
TEST_F(ContentCaptureSimTest, AddNodeToMultiFrame) {
SetCapturedContent(ContentType::kMainFrame);
// Stops after capturing content.
RunContentCaptureTaskUntil(
ContentCaptureTask::TaskState::kProcessCurrentSession);
EXPECT_TRUE(Client().Data().empty());
EXPECT_FALSE(Client().FirstData());
EXPECT_TRUE(ChildClient().Data().empty());
// Sends the first batch data.
RunContentCaptureTaskUntil(ContentCaptureTask::TaskState::kProcessRetryTask);
EXPECT_EQ(5u, Client().Data().size());
EXPECT_TRUE(Client().FirstData());
EXPECT_TRUE(ChildClient().Data().empty());
// Sends the reset of data
RunContentCaptureTaskUntil(ContentCaptureTask::TaskState::kProcessRetryTask);
EXPECT_EQ(4u, Client().Data().size());
EXPECT_FALSE(Client().FirstData());
EXPECT_TRUE(ChildClient().Data().empty());
EXPECT_THAT(Client().AllText(),
testing::UnorderedElementsAreArray(MainFrameExpectedText()));
AddOneNodeToMainFrame();
SetCapturedContent(ContentType::kMainFrame);
RunContentCaptureTaskUntil(ContentCaptureTask::TaskState::kStop);
// Though returns all main frame content, only new added node is unsent.
EXPECT_EQ(1u, Client().Data().size());
EXPECT_FALSE(Client().FirstData());
EXPECT_TRUE(ChildClient().Data().empty());
EXPECT_THAT(Client().AllText(),
testing::UnorderedElementsAreArray(MainFrameExpectedText()));
AddOneNodeToChildFrame();
SetCapturedContent(ContentType::kChildFrame);
RunContentCaptureTaskUntil(ContentCaptureTask::TaskState::kStop);
EXPECT_EQ(3u, ChildClient().Data().size());
EXPECT_THAT(ChildClient().AllText(),
testing::UnorderedElementsAreArray(ChildFrameExpectedText()));
EXPECT_TRUE(ChildClient().FirstData());
}
TEST_F(ContentCaptureSimTest, ChangeNode) {
SetCapturedContent(ContentType::kMainFrame);
RunContentCaptureTaskUntil(ContentCaptureTask::TaskState::kStop);
EXPECT_EQ(4u, Client().Data().size());
EXPECT_FALSE(Client().FirstData());
EXPECT_TRUE(ChildClient().Data().empty());
EXPECT_THAT(Client().AllText(),
testing::UnorderedElementsAreArray(MainFrameExpectedText()));
Vector<std::string> expected_text_update;
std::string insert_text = "content ";
// Changed content to 'content editable'.
InsertMainFrameEditableContent(insert_text, 0);
SetCapturedContent(ContentType::kMainFrame);
RunContentCaptureTaskUntil(ContentCaptureTask::TaskState::kStop);
EXPECT_EQ(1u, Client().UpdatedData().size());
EXPECT_FALSE(Client().FirstData());
EXPECT_TRUE(ChildClient().Data().empty());
expected_text_update.push_back(insert_text + kEditableContent);
EXPECT_THAT(Client().UpdatedText(),
testing::UnorderedElementsAreArray(expected_text_update));
// Changing content multiple times before capturing.
std::string insert_text1 = "i";
// Changed content to 'content ieditable'.
InsertMainFrameEditableContent(insert_text1, insert_text.size());
std::string insert_text2 = "s ";
// Changed content to 'content is editable'.
InsertMainFrameEditableContent(insert_text2,
insert_text.size() + insert_text1.size());
SetCapturedContent(ContentType::kMainFrame);
RunContentCaptureTaskUntil(ContentCaptureTask::TaskState::kStop);
EXPECT_EQ(1u, Client().UpdatedData().size());
EXPECT_FALSE(Client().FirstData());
EXPECT_TRUE(ChildClient().Data().empty());
expected_text_update.push_back(insert_text + insert_text1 + insert_text2 +
kEditableContent);
EXPECT_THAT(Client().UpdatedText(),
testing::UnorderedElementsAreArray(expected_text_update));
}
TEST_F(ContentCaptureSimTest, ChangeNodeBeforeCapture) {
// Changed content to 'content editable' before capture.
std::string insert_text = "content ";
InsertMainFrameEditableContent(insert_text, 0);
// Changing content multiple times before capturing.
std::string insert_text1 = "i";
// Changed content to 'content ieditable'.
InsertMainFrameEditableContent(insert_text1, insert_text.size());
std::string insert_text2 = "s ";
// Changed content to 'content is editable'.
InsertMainFrameEditableContent(insert_text2,
insert_text.size() + insert_text1.size());
// The changed content shall be captured as new content.
ReplaceMainFrameExpectedText(
kEditableContent,
insert_text + insert_text1 + insert_text2 + kEditableContent);
SetCapturedContent(ContentType::kMainFrame);
RunContentCaptureTaskUntil(ContentCaptureTask::TaskState::kStop);
EXPECT_EQ(4u, Client().Data().size());
EXPECT_FALSE(Client().FirstData());
EXPECT_TRUE(ChildClient().Data().empty());
EXPECT_TRUE(ChildClient().UpdatedData().empty());
EXPECT_THAT(Client().AllText(),
testing::UnorderedElementsAreArray(MainFrameExpectedText()));
}
TEST_F(ContentCaptureSimTest, DeleteNodeContent) {
SetCapturedContent(ContentType::kMainFrame);
RunContentCaptureTaskUntil(ContentCaptureTask::TaskState::kStop);
EXPECT_EQ(4u, Client().Data().size());
EXPECT_FALSE(Client().FirstData());
EXPECT_TRUE(ChildClient().Data().empty());
EXPECT_THAT(Client().AllText(),
testing::UnorderedElementsAreArray(MainFrameExpectedText()));
// Deleted 4 char, changed content to 'edit'.
DeleteMainFrameEditableContent(4, 4);
SetCapturedContent(ContentType::kMainFrame);
RunContentCaptureTaskUntil(ContentCaptureTask::TaskState::kStop);
EXPECT_EQ(1u, Client().UpdatedData().size());
EXPECT_FALSE(Client().FirstData());
EXPECT_TRUE(ChildClient().Data().empty());
Vector<std::string> expected_text_update;
expected_text_update.push_back("edit");
EXPECT_THAT(Client().UpdatedText(),
testing::UnorderedElementsAreArray(expected_text_update));
// Emptied content, the node shall be removed.
DeleteMainFrameEditableContent(0, 4);
SetCapturedContent(ContentType::kMainFrame);
RunContentCaptureTaskUntil(ContentCaptureTask::TaskState::kStop);
EXPECT_TRUE(Client().UpdatedData().empty());
EXPECT_FALSE(Client().FirstData());
EXPECT_TRUE(ChildClient().Data().empty());
EXPECT_EQ(1u, Client().RemovedData().size());
}
TEST_F(ContentCaptureSimTest, UserActivatedDelay) {
base::TimeDelta expected_delays[] = {
base::TimeDelta::FromMilliseconds(500), base::TimeDelta::FromSeconds(1),
base::TimeDelta::FromSeconds(2), base::TimeDelta::FromSeconds(4),
base::TimeDelta::FromSeconds(8), base::TimeDelta::FromSeconds(16),
base::TimeDelta::FromSeconds(32), base::TimeDelta::FromSeconds(64),
base::TimeDelta::FromSeconds(128)};
size_t expected_delays_size = base::size(expected_delays);
base::test::ScopedFeatureList user_activated_delay;
user_activated_delay.InitAndEnableFeature(
features::kContentCaptureUserActivatedDelay);
// The first task has been scheduled but not run yet, the delay will be
// increased until current task starts to run. Verifies the value is
// unchanged.
EXPECT_EQ(expected_delays[0], GetNextTaskDelay());
// Settles the initial task.
RunContentCaptureTaskUntil(ContentCaptureTask::TaskState::kStop);
for (size_t i = 1; i < expected_delays_size; ++i) {
EXPECT_EQ(expected_delays[i], GetNextTaskDelay());
// Add a node to schedule the task.
AddOneNodeToMainFrame();
auto scheduled_interval = GetTaskNextFireInterval();
EXPECT_GE(expected_delays[i], scheduled_interval);
EXPECT_LT(expected_delays[i - 1], scheduled_interval);
RunContentCaptureTaskUntil(ContentCaptureTask::TaskState::kStop);
}
// Verifies the delay is up to 128s.
AddOneNodeToMainFrame();
EXPECT_EQ(expected_delays[expected_delays_size - 1], GetNextTaskDelay());
auto scheduled_interval = GetTaskNextFireInterval();
EXPECT_GE(expected_delays[expected_delays_size - 1], scheduled_interval);
EXPECT_LT(expected_delays[expected_delays_size - 2], scheduled_interval);
RunContentCaptureTaskUntil(ContentCaptureTask::TaskState::kStop);
// Verifies the user activated change will reset the delay.
SimulateUserInputOnMainFrame();
AddOneNodeToMainFrame();
EXPECT_EQ(expected_delays[0], GetNextTaskDelay());
scheduled_interval = GetTaskNextFireInterval();
EXPECT_GE(expected_delays[0], scheduled_interval);
// Verifies the multiple changes won't reschedule the task.
AddOneNodeToMainFrame();
EXPECT_GE(scheduled_interval, GetTaskNextFireInterval());
RunContentCaptureTaskUntil(ContentCaptureTask::TaskState::kStop);
// Overrides the main frame's user activation, and verify the child one won't
// effect the main frame.
SimulateUserInputOnChildFrame();
AddOneNodeToMainFrame();
// Verifies the delay time become one step longer since no user activation was
// override by child frame's.
EXPECT_EQ(expected_delays[1], GetNextTaskDelay());
scheduled_interval = GetTaskNextFireInterval();
EXPECT_GE(expected_delays[1], scheduled_interval);
EXPECT_LT(expected_delays[0], scheduled_interval);
}
} // namespace blink