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