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_task.h"
6 
7 #include "base/auto_reset.h"
8 #include "cc/trees/layer_tree_host.h"
9 #include "third_party/blink/public/web/web_content_capture_client.h"
10 #include "third_party/blink/public/web/web_content_holder.h"
11 #include "third_party/blink/renderer/core/dom/dom_node_ids.h"
12 #include "third_party/blink/renderer/core/frame/local_frame_client.h"
13 #include "third_party/blink/renderer/core/layout/layout_text.h"
14 #include "third_party/blink/renderer/core/paint/paint_layer.h"
15 #include "third_party/blink/renderer/platform/graphics/graphics_layer.h"
16 #include "third_party/blink/renderer/platform/scheduler/public/thread_scheduler.h"
17 #include "third_party/blink/renderer/platform/wtf/functional.h"
18 
19 namespace blink {
20 
ContentCaptureTask(LocalFrame & local_frame_root,TaskSession & task_session)21 ContentCaptureTask::ContentCaptureTask(LocalFrame& local_frame_root,
22                                        TaskSession& task_session)
23     : local_frame_root_(&local_frame_root), task_session_(&task_session) {
24   local_frame_root.Client()
25       ->GetWebContentCaptureClient()
26       ->GetTaskTimingParameters(task_short_delay_, task_long_delay_);
27   // The histogram is all about time, just disable it if high resolution isn't
28   // supported.
29   if (base::TimeTicks::IsHighResolution()) {
30     histogram_reporter_ =
31         base::MakeRefCounted<ContentCaptureTaskHistogramReporter>();
32     task_session_->SetSentNodeCountCallback(
33         WTF::BindRepeating(&ContentCaptureTaskHistogramReporter::
34                                RecordsSentContentCountPerDocument,
35                            histogram_reporter_));
36   }
37 }
38 
~ContentCaptureTask()39 ContentCaptureTask::~ContentCaptureTask() {}
40 
Shutdown()41 void ContentCaptureTask::Shutdown() {
42   DCHECK(local_frame_root_);
43   local_frame_root_ = nullptr;
44 }
45 
CaptureContent(Vector<cc::NodeId> & data)46 bool ContentCaptureTask::CaptureContent(Vector<cc::NodeId>& data) {
47   if (captured_content_for_testing_) {
48     data = captured_content_for_testing_.value();
49     return true;
50   }
51   // Because this is called from a different task, the frame may be in any
52   // lifecycle step so we need to early-out in many cases.
53   if (const auto* root_frame_view = local_frame_root_->View()) {
54     if (const auto* cc_layer = root_frame_view->RootCcLayer()) {
55       if (auto* layer_tree_host = cc_layer->layer_tree_host()) {
56         std::vector<cc::NodeId> content;
57         if (layer_tree_host->CaptureContent(&content)) {
58           for (auto c : content)
59             data.push_back(std::move(c));
60           return true;
61         }
62         return false;
63       }
64     }
65   }
66   return false;
67 }
68 
CaptureContent()69 bool ContentCaptureTask::CaptureContent() {
70   DCHECK(task_session_);
71   Vector<cc::NodeId> buffer;
72   if (histogram_reporter_)
73     histogram_reporter_->OnCaptureContentStarted();
74   bool result = CaptureContent(buffer);
75   if (histogram_reporter_)
76     histogram_reporter_->OnCaptureContentEnded(buffer.size());
77   if (!buffer.IsEmpty())
78     task_session_->SetCapturedContent(buffer);
79   return result;
80 }
81 
SendContent(TaskSession::DocumentSession & doc_session)82 void ContentCaptureTask::SendContent(
83     TaskSession::DocumentSession& doc_session) {
84   auto* document = doc_session.GetDocument();
85   DCHECK(document);
86   auto* client = GetWebContentCaptureClient(*document);
87   DCHECK(client);
88 
89   if (histogram_reporter_)
90     histogram_reporter_->OnSendContentStarted();
91   WebVector<WebContentHolder> content_batch;
92   content_batch.reserve(kBatchSize);
93   // Only send changed content after the new content was sent.
94   bool sending_changed_content = !doc_session.HasUnsentCapturedContent();
95   while (content_batch.size() < kBatchSize) {
96     Node* node;
97     if (sending_changed_content)
98       node = doc_session.GetNextChangedNode();
99     else
100       node = doc_session.GetNextUnsentNode();
101     if (!node)
102       break;
103     content_batch.emplace_back(WebContentHolder(*node));
104   }
105   if (!content_batch.empty()) {
106     if (sending_changed_content) {
107       client->DidUpdateContent(content_batch);
108     } else {
109       client->DidCaptureContent(content_batch, !doc_session.FirstDataHasSent());
110       doc_session.SetFirstDataHasSent();
111     }
112   }
113   if (histogram_reporter_)
114     histogram_reporter_->OnSendContentEnded(content_batch.size());
115 }
116 
GetWebContentCaptureClient(const Document & document)117 WebContentCaptureClient* ContentCaptureTask::GetWebContentCaptureClient(
118     const Document& document) {
119   if (auto* frame = document.GetFrame())
120     return frame->Client()->GetWebContentCaptureClient();
121   return nullptr;
122 }
123 
ProcessSession()124 bool ContentCaptureTask::ProcessSession() {
125   DCHECK(task_session_);
126   while (auto* document_session =
127              task_session_->GetNextUnsentDocumentSession()) {
128     if (!ProcessDocumentSession(*document_session))
129       return false;
130     if (ShouldPause())
131       return !task_session_->HasUnsentData();
132   }
133   return true;
134 }
135 
ProcessDocumentSession(TaskSession::DocumentSession & doc_session)136 bool ContentCaptureTask::ProcessDocumentSession(
137     TaskSession::DocumentSession& doc_session) {
138   // If no client, we don't need to send it at all.
139   auto* content_capture_client =
140       GetWebContentCaptureClient(*doc_session.GetDocument());
141   if (!content_capture_client) {
142     doc_session.Reset();
143     return true;
144   }
145 
146   while (doc_session.HasUnsentCapturedContent() ||
147          doc_session.HasUnsentChangedContent()) {
148     SendContent(doc_session);
149     if (ShouldPause()) {
150       return !doc_session.HasUnsentData();
151     }
152   }
153   // Sent the detached nodes.
154   if (doc_session.HasUnsentDetachedNodes())
155     content_capture_client->DidRemoveContent(doc_session.MoveDetachedNodes());
156   DCHECK(!doc_session.HasUnsentData());
157   return true;
158 }
159 
RunInternal()160 bool ContentCaptureTask::RunInternal() {
161   base::AutoReset<TaskState> state(&task_state_, TaskState::kProcessRetryTask);
162   // Already shutdown.
163   if (!local_frame_root_)
164     return true;
165 
166   do {
167     switch (task_state_) {
168       case TaskState::kProcessRetryTask:
169         if (task_session_->HasUnsentData()) {
170           if (!ProcessSession())
171             return false;
172         }
173         task_state_ = TaskState::kCaptureContent;
174         break;
175       case TaskState::kCaptureContent:
176         if (!has_content_change_)
177           return true;
178         if (!CaptureContent()) {
179           // Don't schedule task again in this case.
180           return true;
181         }
182         has_content_change_ = false;
183         if (!task_session_->HasUnsentData())
184           return true;
185 
186         task_state_ = TaskState::kProcessCurrentSession;
187         break;
188       case TaskState::kProcessCurrentSession:
189         return ProcessSession();
190         break;
191       default:
192         return true;
193     }
194   } while (!ShouldPause());
195   return false;
196 }
197 
Run(TimerBase *)198 void ContentCaptureTask::Run(TimerBase*) {
199   TRACE_EVENT0("content_capture", "RunTask");
200   if (!RunInternal()) {
201     ScheduleInternal(ScheduleReason::kRetryTask);
202   }
203 }
204 
ScheduleInternal(ScheduleReason reason)205 void ContentCaptureTask::ScheduleInternal(ScheduleReason reason) {
206   DCHECK(local_frame_root_);
207 
208   base::TimeDelta delay;
209   switch (reason) {
210     case ScheduleReason::kFirstContentChange:
211     case ScheduleReason::kScrolling:
212     case ScheduleReason::kRetryTask:
213       delay = task_short_delay_;
214       break;
215     case ScheduleReason::kContentChange:
216       delay = task_long_delay_;
217       break;
218   }
219 
220   // Return if the current task is about to run soon.
221   if (delay_task_ && delay_task_->IsActive() &&
222       delay_task_->NextFireInterval() < delay) {
223     return;
224   }
225 
226   if (!delay_task_) {
227     scoped_refptr<base::SingleThreadTaskRunner> task_runner =
228         local_frame_root_->GetTaskRunner(TaskType::kInternalContentCapture);
229     delay_task_ = std::make_unique<TaskRunnerTimer<ContentCaptureTask>>(
230         task_runner, this, &ContentCaptureTask::Run);
231   }
232 
233   if (delay_task_->IsActive())
234     delay_task_->Stop();
235 
236   delay_task_->StartOneShot(delay, FROM_HERE);
237   TRACE_EVENT_INSTANT1("content_capture", "ScheduleTask",
238                        TRACE_EVENT_SCOPE_THREAD, "reason", reason);
239 }
240 
Schedule(ScheduleReason reason)241 void ContentCaptureTask::Schedule(ScheduleReason reason) {
242   DCHECK(local_frame_root_);
243   has_content_change_ = true;
244   if (histogram_reporter_)
245     histogram_reporter_->OnContentChanged();
246   ScheduleInternal(reason);
247 }
248 
ShouldPause()249 bool ContentCaptureTask::ShouldPause() {
250   if (task_stop_for_testing_) {
251     return task_state_ == task_stop_for_testing_.value();
252   }
253   return ThreadScheduler::Current()->ShouldYieldForHighPriorityWork();
254 }
255 
ClearDocumentSessionsForTesting()256 void ContentCaptureTask::ClearDocumentSessionsForTesting() {
257   task_session_->ClearDocumentSessionsForTesting();
258 }
259 
GetTaskNextFireIntervalForTesting() const260 base::TimeDelta ContentCaptureTask::GetTaskNextFireIntervalForTesting() const {
261   return delay_task_ && delay_task_->IsActive()
262              ? delay_task_->NextFireInterval()
263              : base::TimeDelta();
264 }
265 
CancelTaskForTesting()266 void ContentCaptureTask::CancelTaskForTesting() {
267   if (delay_task_ && delay_task_->IsActive())
268     delay_task_->Stop();
269 }
270 
Trace(Visitor * visitor)271 void ContentCaptureTask::Trace(Visitor* visitor) {
272   visitor->Trace(local_frame_root_);
273   visitor->Trace(task_session_);
274 }
275 
276 }  // namespace blink
277