1 // Copyright 2019 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 #include "third_party/blink/renderer/core/content_capture/content_capture_manager.h"
6 
7 #include "base/test/metrics/histogram_tester.h"
8 #include "third_party/blink/public/web/web_content_capture_client.h"
9 #include "third_party/blink/public/web/web_content_holder.h"
10 #include "third_party/blink/renderer/bindings/core/v8/v8_gc_controller.h"
11 #include "third_party/blink/renderer/core/dom/dom_node_ids.h"
12 #include "third_party/blink/renderer/core/dom/element.h"
13 #include "third_party/blink/renderer/core/dom/node.h"
14 #include "third_party/blink/renderer/core/frame/web_local_frame_impl.h"
15 #include "third_party/blink/renderer/core/html/html_iframe_element.h"
16 #include "third_party/blink/renderer/core/html_element_type_helpers.h"
17 #include "third_party/blink/renderer/core/layout/layout_object.h"
18 #include "third_party/blink/renderer/core/layout/layout_text.h"
19 #include "third_party/blink/renderer/core/loader/empty_clients.h"
20 #include "third_party/blink/renderer/core/testing/page_test_base.h"
21 #include "third_party/blink/renderer/core/testing/sim/sim_request.h"
22 #include "third_party/blink/renderer/core/testing/sim/sim_test.h"
23 #include "third_party/blink/renderer/platform/heap/heap.h"
24 
25 namespace blink {
26 
27 class WebContentCaptureClientTestHelper : public WebContentCaptureClient {
28  public:
29   ~WebContentCaptureClientTestHelper() override = default;
30 
GetTaskTimingParameters(base::TimeDelta & short_delay,base::TimeDelta & long_delay) const31   void GetTaskTimingParameters(base::TimeDelta& short_delay,
32                                base::TimeDelta& long_delay) const override {
33     short_delay = GetTaskShortDelay();
34     long_delay = GetTaskLongDelay();
35   }
36 
GetTaskLongDelay() const37   base::TimeDelta GetTaskLongDelay() const {
38     return base::TimeDelta::FromMilliseconds(5000);
39   }
40 
GetTaskShortDelay() const41   base::TimeDelta GetTaskShortDelay() const {
42     return base::TimeDelta::FromMilliseconds(500);
43   }
44 
DidCaptureContent(const WebVector<WebContentHolder> & data,bool first_data)45   void DidCaptureContent(const WebVector<WebContentHolder>& data,
46                          bool first_data) override {
47     data_ = data;
48     first_data_ = first_data;
49     for (auto& d : data)
50       all_text_.push_back(d.GetValue().Utf8());
51   }
52 
DidUpdateContent(const WebVector<WebContentHolder> & data)53   void DidUpdateContent(const WebVector<WebContentHolder>& data) override {
54     updated_data_ = data;
55     for (auto& d : data)
56       updated_text_.push_back(d.GetValue().Utf8());
57   }
58 
DidRemoveContent(WebVector<int64_t> data)59   void DidRemoveContent(WebVector<int64_t> data) override {
60     removed_data_ = data;
61   }
62 
FirstData() const63   bool FirstData() const { return first_data_; }
64 
Data() const65   const WebVector<WebContentHolder>& Data() const { return data_; }
66 
UpdatedData() const67   const WebVector<WebContentHolder>& UpdatedData() const {
68     return updated_data_;
69   }
70 
AllText() const71   const Vector<std::string>& AllText() const { return all_text_; }
72 
UpdatedText() const73   const Vector<std::string>& UpdatedText() const { return updated_text_; }
74 
RemovedData() const75   const WebVector<int64_t>& RemovedData() const { return removed_data_; }
76 
ResetResults()77   void ResetResults() {
78     first_data_ = false;
79     data_.Clear();
80     updated_data_.Clear();
81     removed_data_.Clear();
82   }
83 
84  private:
85   bool first_data_ = false;
86   WebVector<WebContentHolder> data_;
87   WebVector<WebContentHolder> updated_data_;
88   WebVector<int64_t> removed_data_;
89   Vector<std::string> all_text_;
90   Vector<std::string> updated_text_;
91 };
92 
93 class ContentCaptureTaskTestHelper : public ContentCaptureTask {
94  public:
ContentCaptureTaskTestHelper(LocalFrame & local_frame_root,TaskSession & task_session,WebContentCaptureClient & content_capture_client)95   ContentCaptureTaskTestHelper(LocalFrame& local_frame_root,
96                                TaskSession& task_session,
97                                WebContentCaptureClient& content_capture_client)
98       : ContentCaptureTask(local_frame_root, task_session),
99         content_capture_client_(&content_capture_client) {}
SetTaskStopState(TaskState state)100   void SetTaskStopState(TaskState state) { task_stop_state_ = state; }
101 
102  protected:
GetWebContentCaptureClient(const Document & document)103   WebContentCaptureClient* GetWebContentCaptureClient(
104       const Document& document) override {
105     return content_capture_client_;
106   }
107 
ShouldPause()108   bool ShouldPause() override {
109     return GetTaskStateForTesting() == task_stop_state_;
110   }
111 
112  private:
113   WebContentCaptureClient* content_capture_client_;
114   TaskState task_stop_state_ = TaskState::kStop;
115 };
116 
117 class ContentCaptureManagerTestHelper : public ContentCaptureManager {
118  public:
ContentCaptureManagerTestHelper(LocalFrame & local_frame_root,WebContentCaptureClientTestHelper & content_capture_client)119   ContentCaptureManagerTestHelper(
120       LocalFrame& local_frame_root,
121       WebContentCaptureClientTestHelper& content_capture_client)
122       : ContentCaptureManager(local_frame_root) {
123     content_capture_task_ = MakeGarbageCollected<ContentCaptureTaskTestHelper>(
124         local_frame_root, GetTaskSessionForTesting(), content_capture_client);
125   }
126 
GetContentCaptureTask()127   ContentCaptureTaskTestHelper* GetContentCaptureTask() {
128     return content_capture_task_;
129   }
130 
Trace(Visitor * visitor)131   void Trace(Visitor* visitor) override {
132     visitor->Trace(content_capture_task_);
133     ContentCaptureManager::Trace(visitor);
134   }
135 
136  protected:
CreateContentCaptureTask()137   ContentCaptureTask* CreateContentCaptureTask() override {
138     return content_capture_task_;
139   }
140 
141  private:
142   Member<ContentCaptureTaskTestHelper> content_capture_task_;
143 };
144 
145 class ContentCaptureLocalFrameClientHelper : public EmptyLocalFrameClient {
146  public:
ContentCaptureLocalFrameClientHelper(WebContentCaptureClient & client)147   ContentCaptureLocalFrameClientHelper(WebContentCaptureClient& client)
148       : client_(client) {}
149 
GetWebContentCaptureClient() const150   WebContentCaptureClient* GetWebContentCaptureClient() const override {
151     return &client_;
152   }
153 
154  private:
155   WebContentCaptureClient& client_;
156 };
157 
158 class ContentCaptureTest : public PageTestBase {
159  public:
ContentCaptureTest()160   ContentCaptureTest() { EnablePlatform(); }
161 
SetUp()162   void SetUp() override {
163     content_capture_client_ =
164         std::make_unique<WebContentCaptureClientTestHelper>();
165     local_frame_client_ =
166         MakeGarbageCollected<ContentCaptureLocalFrameClientHelper>(
167             *content_capture_client_);
168     SetupPageWithClients(nullptr, local_frame_client_);
169     SetHtmlInnerHTML(
170         "<!DOCTYPE HTML>"
171         "<p id='p1'>1</p>"
172         "<p id='p2'>2</p>"
173         "<p id='p3'>3</p>"
174         "<p id='p4'>4</p>"
175         "<p id='p5'>5</p>"
176         "<p id='p6'>6</p>"
177         "<p id='p7'>7</p>"
178         "<p id='p8'>8</p>"
179         "<div id='d1'></div>");
180     platform()->SetAutoAdvanceNowToPendingTasks(false);
181     // TODO(michaelbai): ContentCaptureManager should be get from LocalFrame.
182     content_capture_manager_ =
183         MakeGarbageCollected<ContentCaptureManagerTestHelper>(
184             GetFrame(), *content_capture_client_);
185 
186     InitNodeHolders();
187     // Setup captured content to ContentCaptureTask, it isn't necessary once
188     // ContentCaptureManager is created by LocalFrame.
189     content_capture_manager_->GetContentCaptureTask()
190         ->SetCapturedContentForTesting(node_ids_);
191   }
192 
CreateTextNodeAndNotifyManager()193   void CreateTextNodeAndNotifyManager() {
194     Document& doc = GetDocument();
195     Node* node = doc.createTextNode("New Text");
196     Element* element = MakeGarbageCollected<Element>(html_names::kPTag, &doc);
197     element->appendChild(node);
198     Element* div_element = GetElementById("d1");
199     div_element->appendChild(element);
200     UpdateAllLifecyclePhasesForTest();
201     GetContentCaptureManager()->ScheduleTaskIfNeeded();
202     created_node_id_ = DOMNodeIds::IdForNode(node);
203     Vector<DOMNodeId> captured_content{created_node_id_};
204     content_capture_manager_->GetContentCaptureTask()
205         ->SetCapturedContentForTesting(captured_content);
206   }
207 
GetContentCaptureManager() const208   ContentCaptureManagerTestHelper* GetContentCaptureManager() const {
209     return content_capture_manager_;
210   }
211 
GetWebContentCaptureClient() const212   WebContentCaptureClientTestHelper* GetWebContentCaptureClient() const {
213     return content_capture_client_.get();
214   }
215 
GetContentCaptureTask() const216   ContentCaptureTaskTestHelper* GetContentCaptureTask() const {
217     return GetContentCaptureManager()->GetContentCaptureTask();
218   }
219 
RunContentCaptureTask()220   void RunContentCaptureTask() {
221     ResetResult();
222     platform()->RunForPeriod(GetWebContentCaptureClient()->GetTaskShortDelay());
223   }
224 
RunLongDelayContentCaptureTask()225   void RunLongDelayContentCaptureTask() {
226     ResetResult();
227     platform()->RunForPeriod(GetWebContentCaptureClient()->GetTaskLongDelay());
228   }
229 
RemoveNode(Node * node)230   void RemoveNode(Node* node) {
231     // Remove the node.
232     node->remove();
233     GetContentCaptureManager()->OnLayoutTextWillBeDestroyed(*node);
234   }
235 
RemoveUnsentNode(const WebVector<WebContentHolder> & sent_nodes)236   void RemoveUnsentNode(const WebVector<WebContentHolder>& sent_nodes) {
237     // Find a node isn't in sent_nodes
238     for (auto node : nodes_) {
239       bool found_in_sent = false;
240       for (auto& sent : sent_nodes) {
241         found_in_sent = (node->nodeValue().Utf8().c_str() == sent.GetValue());
242         if (found_in_sent)
243           break;
244       }
245       if (!found_in_sent) {
246         RemoveNode(node);
247         return;
248       }
249     }
250     // Didn't find unsent nodes.
251     NOTREACHED();
252   }
253 
GetExpectedFirstResultSize()254   size_t GetExpectedFirstResultSize() { return ContentCaptureTask::kBatchSize; }
255 
GetExpectedSecondResultSize()256   size_t GetExpectedSecondResultSize() {
257     return node_ids_.size() - GetExpectedFirstResultSize();
258   }
259 
NodeIds() const260   const Vector<DOMNodeId>& NodeIds() const { return node_ids_; }
Nodes() const261   const Vector<Persistent<Node>> Nodes() const { return nodes_; }
262 
263  private:
ResetResult()264   void ResetResult() {
265     GetWebContentCaptureClient()->ResetResults();
266   }
267 
268   // TODO(michaelbai): Remove this once integrate with LayoutText.
InitNodeHolders()269   void InitNodeHolders() {
270     Vector<std::string> ids{"p1", "p2", "p3", "p4", "p5", "p6", "p7", "p8"};
271     for (auto id : ids) {
272       Node* node = GetElementById(id.c_str())->firstChild();
273       CHECK(node);
274       LayoutObject* layout_object = node->GetLayoutObject();
275       CHECK(layout_object);
276       CHECK(layout_object->IsText());
277       nodes_.push_back(node);
278       GetContentCaptureManager()->ScheduleTaskIfNeeded();
279       node_ids_.push_back(DOMNodeIds::IdForNode(node));
280     }
281   }
282 
283   Vector<Persistent<Node>> nodes_;
284   Vector<DOMNodeId> node_ids_;
285   std::unique_ptr<WebContentCaptureClientTestHelper> content_capture_client_;
286   Persistent<ContentCaptureManagerTestHelper> content_capture_manager_;
287   Persistent<ContentCaptureLocalFrameClientHelper> local_frame_client_;
288   DOMNodeId created_node_id_ = kInvalidDOMNodeId;
289 };
290 
TEST_F(ContentCaptureTest,Basic)291 TEST_F(ContentCaptureTest, Basic) {
292   RunContentCaptureTask();
293   EXPECT_EQ(ContentCaptureTask::TaskState::kStop,
294             GetContentCaptureTask()->GetTaskStateForTesting());
295   EXPECT_FALSE(GetWebContentCaptureClient()->Data().empty());
296   EXPECT_EQ(GetExpectedSecondResultSize(),
297             GetWebContentCaptureClient()->Data().size());
298 }
299 
TEST_F(ContentCaptureTest,PauseAndResume)300 TEST_F(ContentCaptureTest, PauseAndResume) {
301   // The task stops before captures content.
302   GetContentCaptureTask()->SetTaskStopState(
303       ContentCaptureTask::TaskState::kCaptureContent);
304   RunContentCaptureTask();
305   EXPECT_FALSE(GetWebContentCaptureClient()->FirstData());
306   EXPECT_TRUE(GetWebContentCaptureClient()->Data().empty());
307   EXPECT_TRUE(GetWebContentCaptureClient()->RemovedData().empty());
308 
309   // The task stops before sends the captured content out.
310   GetContentCaptureTask()->SetTaskStopState(
311       ContentCaptureTask::TaskState::kProcessCurrentSession);
312   RunContentCaptureTask();
313   EXPECT_FALSE(GetWebContentCaptureClient()->FirstData());
314   EXPECT_TRUE(GetWebContentCaptureClient()->Data().empty());
315   EXPECT_TRUE(GetWebContentCaptureClient()->RemovedData().empty());
316 
317   // The task should be stop at kProcessRetryTask because the captured content
318   // needs to be sent with 2 batch.
319   GetContentCaptureTask()->SetTaskStopState(
320       ContentCaptureTask::TaskState::kProcessRetryTask);
321   RunContentCaptureTask();
322   EXPECT_TRUE(GetWebContentCaptureClient()->FirstData());
323   EXPECT_FALSE(GetWebContentCaptureClient()->Data().empty());
324   EXPECT_TRUE(GetWebContentCaptureClient()->RemovedData().empty());
325   EXPECT_EQ(GetExpectedFirstResultSize(),
326             GetWebContentCaptureClient()->Data().size());
327 
328   // Run task until it stops, task will not capture content, because there is no
329   // content change, so we have 3 NodeHolders.
330   GetContentCaptureTask()->SetTaskStopState(
331       ContentCaptureTask::TaskState::kStop);
332   RunContentCaptureTask();
333   EXPECT_FALSE(GetWebContentCaptureClient()->FirstData());
334   EXPECT_FALSE(GetWebContentCaptureClient()->Data().empty());
335   EXPECT_TRUE(GetWebContentCaptureClient()->RemovedData().empty());
336   EXPECT_EQ(GetExpectedSecondResultSize(),
337             GetWebContentCaptureClient()->Data().size());
338 }
339 
TEST_F(ContentCaptureTest,NodeOnlySendOnce)340 TEST_F(ContentCaptureTest, NodeOnlySendOnce) {
341   // Send all nodes
342   RunContentCaptureTask();
343   EXPECT_FALSE(GetWebContentCaptureClient()->Data().empty());
344   EXPECT_EQ(GetExpectedSecondResultSize(),
345             GetWebContentCaptureClient()->Data().size());
346 
347   GetContentCaptureManager()->OnScrollPositionChanged();
348   RunContentCaptureTask();
349   EXPECT_TRUE(GetWebContentCaptureClient()->Data().empty());
350   EXPECT_TRUE(GetWebContentCaptureClient()->RemovedData().empty());
351 }
352 
TEST_F(ContentCaptureTest,RemoveNodeBeforeSendingOut)353 TEST_F(ContentCaptureTest, RemoveNodeBeforeSendingOut) {
354   // Capture the content, but didn't send them.
355   GetContentCaptureTask()->SetTaskStopState(
356       ContentCaptureTask::TaskState::kProcessCurrentSession);
357   RunContentCaptureTask();
358   EXPECT_TRUE(GetWebContentCaptureClient()->Data().empty());
359 
360   // Remove the node and sent the captured content out.
361   RemoveNode(Nodes().at(0));
362   GetContentCaptureTask()->SetTaskStopState(
363       ContentCaptureTask::TaskState::kProcessRetryTask);
364   RunContentCaptureTask();
365   EXPECT_EQ(GetExpectedFirstResultSize(),
366             GetWebContentCaptureClient()->Data().size());
367   EXPECT_EQ(0u, GetWebContentCaptureClient()->RemovedData().size());
368   RunContentCaptureTask();
369   // Total 7 content returned instead of 8.
370   EXPECT_EQ(GetExpectedSecondResultSize() - 1,
371             GetWebContentCaptureClient()->Data().size());
372   EXPECT_EQ(0u, GetWebContentCaptureClient()->RemovedData().size());
373   RunContentCaptureTask();
374   // No removed node because it hasn't been sent out.
375   EXPECT_EQ(0u, GetWebContentCaptureClient()->Data().size());
376   EXPECT_EQ(0u, GetWebContentCaptureClient()->RemovedData().size());
377 }
378 
TEST_F(ContentCaptureTest,RemoveNodeInBetweenSendingOut)379 TEST_F(ContentCaptureTest, RemoveNodeInBetweenSendingOut) {
380   // Capture the content, but didn't send them.
381   GetContentCaptureTask()->SetTaskStopState(
382       ContentCaptureTask::TaskState::kProcessCurrentSession);
383   RunContentCaptureTask();
384   EXPECT_TRUE(GetWebContentCaptureClient()->Data().empty());
385 
386   // Sends first batch.
387   GetContentCaptureTask()->SetTaskStopState(
388       ContentCaptureTask::TaskState::kProcessRetryTask);
389   RunContentCaptureTask();
390   EXPECT_EQ(GetExpectedFirstResultSize(),
391             GetWebContentCaptureClient()->Data().size());
392   EXPECT_EQ(0u, GetWebContentCaptureClient()->RemovedData().size());
393 
394   // This relies on each node to have different value.
395   RemoveUnsentNode(GetWebContentCaptureClient()->Data());
396   GetContentCaptureTask()->SetTaskStopState(
397       ContentCaptureTask::TaskState::kProcessRetryTask);
398   RunContentCaptureTask();
399   // Total 7 content returned instead of 8.
400   EXPECT_EQ(GetExpectedSecondResultSize() - 1,
401             GetWebContentCaptureClient()->Data().size());
402   EXPECT_EQ(0u, GetWebContentCaptureClient()->RemovedData().size());
403   RunContentCaptureTask();
404   // No removed node because it hasn't been sent out.
405   EXPECT_EQ(0u, GetWebContentCaptureClient()->Data().size());
406   EXPECT_EQ(0u, GetWebContentCaptureClient()->RemovedData().size());
407 }
408 
TEST_F(ContentCaptureTest,RemoveNodeAfterSendingOut)409 TEST_F(ContentCaptureTest, RemoveNodeAfterSendingOut) {
410   // Captures the content, but didn't send them.
411   GetContentCaptureTask()->SetTaskStopState(
412       ContentCaptureTask::TaskState::kProcessCurrentSession);
413   RunContentCaptureTask();
414   EXPECT_TRUE(GetWebContentCaptureClient()->Data().empty());
415 
416   // Sends first batch.
417   GetContentCaptureTask()->SetTaskStopState(
418       ContentCaptureTask::TaskState::kProcessRetryTask);
419   RunContentCaptureTask();
420   EXPECT_EQ(GetExpectedFirstResultSize(),
421             GetWebContentCaptureClient()->Data().size());
422   EXPECT_EQ(0u, GetWebContentCaptureClient()->RemovedData().size());
423 
424   // Sends second batch.
425   RunContentCaptureTask();
426   EXPECT_EQ(GetExpectedSecondResultSize(),
427             GetWebContentCaptureClient()->Data().size());
428   EXPECT_EQ(0u, GetWebContentCaptureClient()->RemovedData().size());
429 
430   // Remove the node.
431   RemoveNode(Nodes().at(0));
432   RunLongDelayContentCaptureTask();
433   EXPECT_EQ(0u, GetWebContentCaptureClient()->Data().size());
434   EXPECT_EQ(1u, GetWebContentCaptureClient()->RemovedData().size());
435 }
436 
TEST_F(ContentCaptureTest,TaskHistogramReporter)437 TEST_F(ContentCaptureTest, TaskHistogramReporter) {
438   // This performs gc for all DocumentSession, flushes the existing
439   // SentContentCount and give a clean baseline for histograms.
440   // We are not sure if it always work, maybe still be the source of flaky.
441   V8GCController::CollectAllGarbageForTesting(v8::Isolate::GetCurrent());
442   base::HistogramTester histograms;
443 
444   // The task stops before captures content.
445   GetContentCaptureTask()->SetTaskStopState(
446       ContentCaptureTask::TaskState::kCaptureContent);
447   RunContentCaptureTask();
448   // Verify no histogram reported yet.
449   histograms.ExpectTotalCount(
450       ContentCaptureTaskHistogramReporter::kCaptureContentTime, 0u);
451   histograms.ExpectTotalCount(
452       ContentCaptureTaskHistogramReporter::kSendContentTime, 0u);
453   histograms.ExpectTotalCount(
454       ContentCaptureTaskHistogramReporter::kCaptureContentDelayTime, 0u);
455   histograms.ExpectTotalCount(
456       ContentCaptureTaskHistogramReporter::kSentContentCount, 0u);
457 
458   // The task stops before sends the captured content out.
459   GetContentCaptureTask()->SetTaskStopState(
460       ContentCaptureTask::TaskState::kProcessCurrentSession);
461   RunContentCaptureTask();
462   // Verify has one CaptureContentTime record.
463   histograms.ExpectTotalCount(
464       ContentCaptureTaskHistogramReporter::kCaptureContentTime, 1u);
465   histograms.ExpectTotalCount(
466       ContentCaptureTaskHistogramReporter::kSendContentTime, 0u);
467   histograms.ExpectTotalCount(
468       ContentCaptureTaskHistogramReporter::kCaptureContentDelayTime, 0u);
469   histograms.ExpectTotalCount(
470       ContentCaptureTaskHistogramReporter::kSentContentCount, 0u);
471 
472   // The task stops at kProcessRetryTask because the captured content
473   // needs to be sent with 2 batch.
474   GetContentCaptureTask()->SetTaskStopState(
475       ContentCaptureTask::TaskState::kProcessRetryTask);
476   RunContentCaptureTask();
477   // Verify has one CaptureContentTime, one SendContentTime and one
478   // CaptureContentDelayTime record.
479   histograms.ExpectTotalCount(
480       ContentCaptureTaskHistogramReporter::kCaptureContentTime, 1u);
481   histograms.ExpectTotalCount(
482       ContentCaptureTaskHistogramReporter::kSendContentTime, 1u);
483   histograms.ExpectTotalCount(
484       ContentCaptureTaskHistogramReporter::kCaptureContentDelayTime, 1u);
485   histograms.ExpectTotalCount(
486       ContentCaptureTaskHistogramReporter::kSentContentCount, 0u);
487 
488   // Run task until it stops, task will not capture content, because there is no
489   // content change.
490   GetContentCaptureTask()->SetTaskStopState(
491       ContentCaptureTask::TaskState::kStop);
492   RunContentCaptureTask();
493   // Verify has two SendContentTime records.
494   histograms.ExpectTotalCount(
495       ContentCaptureTaskHistogramReporter::kCaptureContentTime, 1u);
496   histograms.ExpectTotalCount(
497       ContentCaptureTaskHistogramReporter::kSendContentTime, 2u);
498   histograms.ExpectTotalCount(
499       ContentCaptureTaskHistogramReporter::kCaptureContentDelayTime, 1u);
500   histograms.ExpectTotalCount(
501       ContentCaptureTaskHistogramReporter::kSentContentCount, 0u);
502 
503   // Create a node and run task until it stops.
504   CreateTextNodeAndNotifyManager();
505   GetContentCaptureTask()->SetTaskStopState(
506       ContentCaptureTask::TaskState::kStop);
507   RunLongDelayContentCaptureTask();
508   histograms.ExpectTotalCount(
509       ContentCaptureTaskHistogramReporter::kCaptureContentTime, 2u);
510   histograms.ExpectTotalCount(
511       ContentCaptureTaskHistogramReporter::kSendContentTime, 3u);
512   histograms.ExpectTotalCount(
513       ContentCaptureTaskHistogramReporter::kCaptureContentDelayTime, 2u);
514   histograms.ExpectTotalCount(
515       ContentCaptureTaskHistogramReporter::kSentContentCount, 0u);
516 
517   GetContentCaptureTask()->ClearDocumentSessionsForTesting();
518   V8GCController::CollectAllGarbageForTesting(v8::Isolate::GetCurrent());
519   histograms.ExpectTotalCount(
520       ContentCaptureTaskHistogramReporter::kCaptureContentTime, 2u);
521   histograms.ExpectTotalCount(
522       ContentCaptureTaskHistogramReporter::kSendContentTime, 3u);
523   histograms.ExpectTotalCount(
524       ContentCaptureTaskHistogramReporter::kCaptureContentDelayTime, 2u);
525   histograms.ExpectTotalCount(
526       ContentCaptureTaskHistogramReporter::kSentContentCount, 1u);
527   // Verify total content has been sent.
528   histograms.ExpectBucketCount(
529       ContentCaptureTaskHistogramReporter::kSentContentCount, 9u, 1u);
530 }
531 
TEST_F(ContentCaptureTest,RescheduleTask)532 TEST_F(ContentCaptureTest, RescheduleTask) {
533   // This test assumes test runs much faster than task's long delay which is 5s.
534   Persistent<ContentCaptureTaskTestHelper> task = GetContentCaptureTask();
535   task->CancelTaskForTesting();
536   EXPECT_TRUE(task->GetTaskNextFireIntervalForTesting().is_zero());
537   task->Schedule(ContentCaptureTask::ScheduleReason::kContentChange);
538   auto begin = base::TimeTicks::Now();
539   base::TimeDelta interval1 = task->GetTaskNextFireIntervalForTesting();
540   task->Schedule(ContentCaptureTask::ScheduleReason::kScrolling);
541   base::TimeDelta interval2 = task->GetTaskNextFireIntervalForTesting();
542   auto test_running_time = base::TimeTicks::Now() - begin;
543   // The interval1 will be greater than interval2 even the task wasn't
544   // rescheduled, removing the test_running_time from interval1 make sure
545   // task rescheduled.
546   EXPECT_GT(interval1 - test_running_time, interval2);
547 }
548 
TEST_F(ContentCaptureTest,NotRescheduleTask)549 TEST_F(ContentCaptureTest, NotRescheduleTask) {
550   // This test assumes test runs much faster than task's long delay which is 5s.
551   Persistent<ContentCaptureTaskTestHelper> task = GetContentCaptureTask();
552   task->CancelTaskForTesting();
553   EXPECT_TRUE(task->GetTaskNextFireIntervalForTesting().is_zero());
554   task->Schedule(ContentCaptureTask::ScheduleReason::kContentChange);
555   auto begin = base::TimeTicks::Now();
556   base::TimeDelta interval1 = task->GetTaskNextFireIntervalForTesting();
557   task->Schedule(ContentCaptureTask::ScheduleReason::kContentChange);
558   base::TimeDelta interval2 = task->GetTaskNextFireIntervalForTesting();
559   auto test_running_time = base::TimeTicks::Now() - begin;
560   EXPECT_GE(interval1, interval2);
561   EXPECT_LE(interval1 - test_running_time, interval2);
562 }
563 
564 // TODO(michaelbai): use RenderingTest instead of PageTestBase for multiple
565 // frame test.
566 class ContentCaptureSimTest : public SimTest {
567  public:
568   static const char* kEditableContent;
569 
ContentCaptureSimTest()570   ContentCaptureSimTest() : client_(), child_client_() {}
SetUp()571   void SetUp() override {
572     SimTest::SetUp();
573     MainFrame().SetContentCaptureClient(&client_);
574     SetupPage();
575   }
576 
RunContentCaptureTaskUntil(ContentCaptureTask::TaskState state)577   void RunContentCaptureTaskUntil(ContentCaptureTask::TaskState state) {
578     Client().ResetResults();
579     ChildClient().ResetResults();
580     GetDocument()
581         .GetFrame()
582         ->LocalFrameRoot()
583         .GetContentCaptureManager()
584         ->GetContentCaptureTaskForTesting()
585         ->RunTaskForTestingUntil(state);
586   }
587 
Client()588   WebContentCaptureClientTestHelper& Client() { return client_; }
ChildClient()589   WebContentCaptureClientTestHelper& ChildClient() { return child_client_; }
590 
591   enum class ContentType { kAll, kMainFrame, kChildFrame };
SetCapturedContent(ContentType type)592   void SetCapturedContent(ContentType type) {
593     if (type == ContentType::kMainFrame) {
594       SetCapturedContent(main_frame_content_);
595     } else if (type == ContentType::kChildFrame) {
596       SetCapturedContent(child_frame_content_);
597     } else if (type == ContentType::kAll) {
598       Vector<DOMNodeId> holders(main_frame_content_);
599       holders.AppendRange(child_frame_content_.begin(),
600                           child_frame_content_.end());
601       SetCapturedContent(holders);
602     }
603   }
604 
AddOneNodeToMainFrame()605   void AddOneNodeToMainFrame() {
606     AddNodeToDocument(GetDocument(), main_frame_content_);
607     main_frame_expected_text_.push_back("New Text");
608   }
609 
AddOneNodeToChildFrame()610   void AddOneNodeToChildFrame() {
611     AddNodeToDocument(*child_document_, child_frame_content_);
612     child_frame_expected_text_.push_back("New Text");
613   }
614 
InsertMainFrameEditableContent(const std::string & content,unsigned offset)615   void InsertMainFrameEditableContent(const std::string& content,
616                                       unsigned offset) {
617     InsertNodeContent(GetDocument(), "editable_id", content, offset);
618   }
619 
DeleteMainFrameEditableContent(unsigned offset,unsigned length)620   void DeleteMainFrameEditableContent(unsigned offset, unsigned length) {
621     DeleteNodeContent(GetDocument(), "editable_id", offset, length);
622   }
623 
MainFrameExpectedText() const624   const Vector<std::string>& MainFrameExpectedText() const {
625     return main_frame_expected_text_;
626   }
627 
ChildFrameExpectedText() const628   const Vector<std::string>& ChildFrameExpectedText() const {
629     return child_frame_expected_text_;
630   }
631 
ReplaceMainFrameExpectedText(const std::string & old_text,const std::string & new_text)632   void ReplaceMainFrameExpectedText(const std::string& old_text,
633                                     const std::string& new_text) {
634     std::replace(main_frame_expected_text_.begin(),
635                  main_frame_expected_text_.end(), old_text, new_text);
636   }
637 
638  private:
SetupPage()639   void SetupPage() {
640     SimRequest main_resource("https://example.com/test.html", "text/html");
641     SimRequest frame_resource("https://example.com/frame.html", "text/html");
642     LoadURL("https://example.com/test.html");
643     WebView().MainFrameWidget()->Resize(WebSize(800, 6000));
644     main_resource.Complete(R"HTML(
645       <!DOCTYPE html>
646       <body style='background: white'>
647       <iframe id=frame name='frame' src=frame.html></iframe>
648       <p id='p1'>Hello World1</p>
649       <p id='p2'>Hello World2</p>
650       <p id='p3'>Hello World3</p>
651       <p id='p4'>Hello World4</p>
652       <p id='p5'>Hello World5</p>
653       <p id='p6'>Hello World6</p>
654       <p id='p7'>Hello World7</p>
655       <div id='editable_id'>editable</div>
656       <svg>
657       <text id="s8">Hello World8</text>
658       </svg>
659       <div id='d1'></div>
660       )HTML");
661     auto frame1 = Compositor().BeginFrame();
662     frame_resource.Complete(R"HTML(
663       <!DOCTYPE html>
664       <p id='c1'>Hello World11</p>
665       <p id='c2'>Hello World12</p>
666       <div id='d1'></div>
667       )HTML");
668 
669     static_cast<WebLocalFrame*>(MainFrame().FindFrameByName("frame"))
670         ->SetContentCaptureClient(&child_client_);
671     auto* child_frame =
672         To<HTMLIFrameElement>(GetDocument().getElementById("frame"));
673     child_document_ = child_frame->contentDocument();
674     child_document_->UpdateStyleAndLayout(DocumentUpdateReason::kTest);
675     Compositor().BeginFrame();
676     InitMainFrameNodeHolders();
677     InitChildFrameNodeHolders(*child_document_);
678   }
679 
InitMainFrameNodeHolders()680   void InitMainFrameNodeHolders() {
681     Vector<std::string> ids = {"p1", "p2", "p3", "p4",         "p5",
682                                "p6", "p7", "s8", "editable_id"};
683     main_frame_expected_text_ = {
684         "Hello World1", "Hello World2", "Hello World3",
685         "Hello World4", "Hello World5", "Hello World6",
686         "Hello World7", "Hello World8", kEditableContent};
687     InitNodeHolders(main_frame_content_, ids, GetDocument());
688     EXPECT_EQ(9u, main_frame_content_.size());
689   }
690 
InitChildFrameNodeHolders(const Document & doc)691   void InitChildFrameNodeHolders(const Document& doc) {
692     Vector<std::string> ids = {"c1", "c2"};
693     child_frame_expected_text_ = {"Hello World11", "Hello World12"};
694     InitNodeHolders(child_frame_content_, ids, doc);
695     EXPECT_EQ(2u, child_frame_content_.size());
696   }
697 
InitNodeHolders(Vector<DOMNodeId> & buffer,const Vector<std::string> & ids,const Document & document)698   void InitNodeHolders(Vector<DOMNodeId>& buffer,
699                        const Vector<std::string>& ids,
700                        const Document& document) {
701     for (auto id : ids) {
702       LayoutText* layout_text = ToLayoutText(
703           document.getElementById(id.c_str())->firstChild()->GetLayoutObject());
704       EXPECT_TRUE(layout_text->HasNodeId());
705       buffer.push_back(layout_text->EnsureNodeId());
706     }
707   }
708 
AddNodeToDocument(Document & doc,Vector<DOMNodeId> & buffer)709   void AddNodeToDocument(Document& doc, Vector<DOMNodeId>& buffer) {
710     Node* node = doc.createTextNode("New Text");
711     auto* element = MakeGarbageCollected<Element>(html_names::kPTag, &doc);
712     element->appendChild(node);
713     Element* div_element = doc.getElementById("d1");
714     div_element->appendChild(element);
715     Compositor().BeginFrame();
716     LayoutText* layout_text = ToLayoutText(node->GetLayoutObject());
717     EXPECT_TRUE(layout_text->HasNodeId());
718     buffer.push_front(layout_text->EnsureNodeId());
719   }
720 
InsertNodeContent(Document & doc,const std::string & id,const std::string & content,unsigned offset)721   void InsertNodeContent(Document& doc,
722                          const std::string& id,
723                          const std::string& content,
724                          unsigned offset) {
725     To<Text>(doc.getElementById(id.c_str())->firstChild())
726         ->insertData(offset, String(content.c_str()),
727                      IGNORE_EXCEPTION_FOR_TESTING);
728     Compositor().BeginFrame();
729   }
730 
DeleteNodeContent(Document & doc,const std::string & id,unsigned offset,unsigned length)731   void DeleteNodeContent(Document& doc,
732                          const std::string& id,
733                          unsigned offset,
734                          unsigned length) {
735     To<Text>(doc.getElementById(id.c_str())->firstChild())
736         ->deleteData(offset, length, IGNORE_EXCEPTION_FOR_TESTING);
737     Compositor().BeginFrame();
738   }
739 
SetCapturedContent(const Vector<DOMNodeId> & captured_content)740   void SetCapturedContent(const Vector<DOMNodeId>& captured_content) {
741     GetDocument()
742         .GetFrame()
743         ->LocalFrameRoot()
744         .GetContentCaptureManager()
745         ->GetContentCaptureTaskForTesting()
746         ->SetCapturedContentForTesting(captured_content);
747   }
748 
749   Vector<std::string> main_frame_expected_text_;
750   Vector<std::string> child_frame_expected_text_;
751   Vector<DOMNodeId> main_frame_content_;
752   Vector<DOMNodeId> child_frame_content_;
753   WebContentCaptureClientTestHelper client_;
754   WebContentCaptureClientTestHelper child_client_;
755   Persistent<Document> child_document_;
756 };
757 
758 const char* ContentCaptureSimTest::kEditableContent = "editable";
759 
TEST_F(ContentCaptureSimTest,MultiFrame)760 TEST_F(ContentCaptureSimTest, MultiFrame) {
761   SetCapturedContent(ContentType::kAll);
762   RunContentCaptureTaskUntil(ContentCaptureTask::TaskState::kStop);
763   EXPECT_EQ(4u, Client().Data().size());
764   EXPECT_EQ(2u, ChildClient().Data().size());
765   EXPECT_THAT(Client().AllText(),
766               testing::UnorderedElementsAreArray(MainFrameExpectedText()));
767   EXPECT_THAT(ChildClient().AllText(),
768               testing::UnorderedElementsAreArray(ChildFrameExpectedText()));
769 }
770 
TEST_F(ContentCaptureSimTest,AddNodeToMultiFrame)771 TEST_F(ContentCaptureSimTest, AddNodeToMultiFrame) {
772   SetCapturedContent(ContentType::kMainFrame);
773   // Stops after capturing content.
774   RunContentCaptureTaskUntil(
775       ContentCaptureTask::TaskState::kProcessCurrentSession);
776   EXPECT_TRUE(Client().Data().empty());
777   EXPECT_FALSE(Client().FirstData());
778   EXPECT_TRUE(ChildClient().Data().empty());
779 
780   // Sends the first batch data.
781   RunContentCaptureTaskUntil(ContentCaptureTask::TaskState::kProcessRetryTask);
782   EXPECT_EQ(5u, Client().Data().size());
783   EXPECT_TRUE(Client().FirstData());
784   EXPECT_TRUE(ChildClient().Data().empty());
785 
786   // Sends the reset of data
787   RunContentCaptureTaskUntil(ContentCaptureTask::TaskState::kProcessRetryTask);
788   EXPECT_EQ(4u, Client().Data().size());
789   EXPECT_FALSE(Client().FirstData());
790   EXPECT_TRUE(ChildClient().Data().empty());
791   EXPECT_THAT(Client().AllText(),
792               testing::UnorderedElementsAreArray(MainFrameExpectedText()));
793 
794   AddOneNodeToMainFrame();
795   SetCapturedContent(ContentType::kMainFrame);
796   RunContentCaptureTaskUntil(ContentCaptureTask::TaskState::kStop);
797   // Though returns all main frame content, only new added node is unsent.
798   EXPECT_EQ(1u, Client().Data().size());
799   EXPECT_FALSE(Client().FirstData());
800   EXPECT_TRUE(ChildClient().Data().empty());
801   EXPECT_THAT(Client().AllText(),
802               testing::UnorderedElementsAreArray(MainFrameExpectedText()));
803 
804   AddOneNodeToChildFrame();
805   SetCapturedContent(ContentType::kChildFrame);
806   RunContentCaptureTaskUntil(ContentCaptureTask::TaskState::kStop);
807   EXPECT_EQ(3u, ChildClient().Data().size());
808   EXPECT_THAT(ChildClient().AllText(),
809               testing::UnorderedElementsAreArray(ChildFrameExpectedText()));
810   EXPECT_TRUE(ChildClient().FirstData());
811 }
812 
TEST_F(ContentCaptureSimTest,ChangeNode)813 TEST_F(ContentCaptureSimTest, ChangeNode) {
814   SetCapturedContent(ContentType::kMainFrame);
815   RunContentCaptureTaskUntil(ContentCaptureTask::TaskState::kStop);
816   EXPECT_EQ(4u, Client().Data().size());
817   EXPECT_FALSE(Client().FirstData());
818   EXPECT_TRUE(ChildClient().Data().empty());
819   EXPECT_THAT(Client().AllText(),
820               testing::UnorderedElementsAreArray(MainFrameExpectedText()));
821   Vector<std::string> expected_text_update;
822   std::string insert_text = "content ";
823 
824   // Changed content to 'content editable'.
825   InsertMainFrameEditableContent(insert_text, 0);
826   SetCapturedContent(ContentType::kMainFrame);
827   RunContentCaptureTaskUntil(ContentCaptureTask::TaskState::kStop);
828   EXPECT_EQ(1u, Client().UpdatedData().size());
829   EXPECT_FALSE(Client().FirstData());
830   EXPECT_TRUE(ChildClient().Data().empty());
831   expected_text_update.push_back(insert_text + kEditableContent);
832   EXPECT_THAT(Client().UpdatedText(),
833               testing::UnorderedElementsAreArray(expected_text_update));
834 
835   // Changing content multiple times before capturing.
836   std::string insert_text1 = "i";
837   // Changed content to 'content ieditable'.
838   InsertMainFrameEditableContent(insert_text1, insert_text.size());
839   std::string insert_text2 = "s ";
840   // Changed content to 'content is editable'.
841   InsertMainFrameEditableContent(insert_text2,
842                                  insert_text.size() + insert_text1.size());
843 
844   SetCapturedContent(ContentType::kMainFrame);
845   RunContentCaptureTaskUntil(ContentCaptureTask::TaskState::kStop);
846   EXPECT_EQ(1u, Client().UpdatedData().size());
847   EXPECT_FALSE(Client().FirstData());
848   EXPECT_TRUE(ChildClient().Data().empty());
849   expected_text_update.push_back(insert_text + insert_text1 + insert_text2 +
850                                  kEditableContent);
851   EXPECT_THAT(Client().UpdatedText(),
852               testing::UnorderedElementsAreArray(expected_text_update));
853 }
854 
TEST_F(ContentCaptureSimTest,ChangeNodeBeforeCapture)855 TEST_F(ContentCaptureSimTest, ChangeNodeBeforeCapture) {
856   // Changed content to 'content editable' before capture.
857   std::string insert_text = "content ";
858   InsertMainFrameEditableContent(insert_text, 0);
859   // Changing content multiple times before capturing.
860   std::string insert_text1 = "i";
861   // Changed content to 'content ieditable'.
862   InsertMainFrameEditableContent(insert_text1, insert_text.size());
863   std::string insert_text2 = "s ";
864   // Changed content to 'content is editable'.
865   InsertMainFrameEditableContent(insert_text2,
866                                  insert_text.size() + insert_text1.size());
867 
868   // The changed content shall be captured as new content.
869   ReplaceMainFrameExpectedText(
870       kEditableContent,
871       insert_text + insert_text1 + insert_text2 + kEditableContent);
872   SetCapturedContent(ContentType::kMainFrame);
873   RunContentCaptureTaskUntil(ContentCaptureTask::TaskState::kStop);
874   EXPECT_EQ(4u, Client().Data().size());
875   EXPECT_FALSE(Client().FirstData());
876   EXPECT_TRUE(ChildClient().Data().empty());
877   EXPECT_TRUE(ChildClient().UpdatedData().empty());
878   EXPECT_THAT(Client().AllText(),
879               testing::UnorderedElementsAreArray(MainFrameExpectedText()));
880 }
881 
TEST_F(ContentCaptureSimTest,DeleteNodeContent)882 TEST_F(ContentCaptureSimTest, DeleteNodeContent) {
883   SetCapturedContent(ContentType::kMainFrame);
884   RunContentCaptureTaskUntil(ContentCaptureTask::TaskState::kStop);
885   EXPECT_EQ(4u, Client().Data().size());
886   EXPECT_FALSE(Client().FirstData());
887   EXPECT_TRUE(ChildClient().Data().empty());
888   EXPECT_THAT(Client().AllText(),
889               testing::UnorderedElementsAreArray(MainFrameExpectedText()));
890 
891   // Deleted 4 char, changed content to 'edit'.
892   DeleteMainFrameEditableContent(4, 4);
893   SetCapturedContent(ContentType::kMainFrame);
894   RunContentCaptureTaskUntil(ContentCaptureTask::TaskState::kStop);
895   EXPECT_EQ(1u, Client().UpdatedData().size());
896   EXPECT_FALSE(Client().FirstData());
897   EXPECT_TRUE(ChildClient().Data().empty());
898   Vector<std::string> expected_text_update;
899   expected_text_update.push_back("edit");
900   EXPECT_THAT(Client().UpdatedText(),
901               testing::UnorderedElementsAreArray(expected_text_update));
902 
903   // Emptied content, the node shall be removed.
904   DeleteMainFrameEditableContent(0, 4);
905   SetCapturedContent(ContentType::kMainFrame);
906   RunContentCaptureTaskUntil(ContentCaptureTask::TaskState::kStop);
907   EXPECT_TRUE(Client().UpdatedData().empty());
908   EXPECT_FALSE(Client().FirstData());
909   EXPECT_TRUE(ChildClient().Data().empty());
910   EXPECT_EQ(1u, Client().RemovedData().size());
911 }
912 
913 }  // namespace blink
914