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