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 "components/paint_preview/player/player_compositor_delegate.h"
6
7 #include <string>
8 #include <utility>
9 #include <vector>
10
11 #include "base/containers/flat_map.h"
12 #include "base/files/file_path.h"
13 #include "base/memory/memory_pressure_monitor.h"
14 #include "base/memory/read_only_shared_memory_region.h"
15 #include "base/memory/weak_ptr.h"
16 #include "base/notreached.h"
17 #include "base/optional.h"
18 #include "base/strings/string_piece.h"
19 #include "base/strings/utf_string_conversions.h"
20 #include "base/task/post_task.h"
21 #include "base/task/thread_pool.h"
22 #include "base/trace_event/common/trace_event_common.h"
23 #include "base/trace_event/trace_event.h"
24 #include "base/unguessable_token.h"
25 #include "components/paint_preview/browser/paint_preview_base_service.h"
26 #include "components/paint_preview/browser/warm_compositor.h"
27 #include "components/paint_preview/common/proto/paint_preview.pb.h"
28 #include "components/paint_preview/common/recording_map.h"
29 #include "components/paint_preview/common/serialized_recording.h"
30 #include "components/paint_preview/common/version.h"
31 #include "components/paint_preview/public/paint_preview_compositor_client.h"
32 #include "components/paint_preview/public/paint_preview_compositor_service.h"
33 #include "components/services/paint_preview_compositor/public/mojom/paint_preview_compositor.mojom.h"
34 #include "mojo/public/cpp/bindings/remote.h"
35 #include "third_party/skia/include/core/SkBitmap.h"
36 #include "ui/gfx/geometry/rect.h"
37
38 namespace paint_preview {
39
40 namespace {
41
BuildHitTester(const PaintPreviewFrameProto & proto)42 std::pair<base::UnguessableToken, std::unique_ptr<HitTester>> BuildHitTester(
43 const PaintPreviewFrameProto& proto) {
44 std::pair<base::UnguessableToken, std::unique_ptr<HitTester>> out(
45 base::UnguessableToken::Deserialize(proto.embedding_token_high(),
46 proto.embedding_token_low()),
47 std::make_unique<HitTester>());
48 out.second->Build(proto);
49 return out;
50 }
51
52 base::flat_map<base::UnguessableToken, std::unique_ptr<HitTester>>
BuildHitTesters(const PaintPreviewProto & proto)53 BuildHitTesters(const PaintPreviewProto& proto) {
54 std::vector<std::pair<base::UnguessableToken, std::unique_ptr<HitTester>>>
55 hit_testers;
56 hit_testers.reserve(proto.subframes_size() + 1);
57 hit_testers.push_back(BuildHitTester(proto.root_frame()));
58 for (const auto& frame_proto : proto.subframes())
59 hit_testers.push_back(BuildHitTester(frame_proto));
60
61 return base::flat_map<base::UnguessableToken, std::unique_ptr<HitTester>>(
62 std::move(hit_testers));
63 }
64
ToReadOnlySharedMemory(const paint_preview::PaintPreviewProto & proto)65 base::Optional<base::ReadOnlySharedMemoryRegion> ToReadOnlySharedMemory(
66 const paint_preview::PaintPreviewProto& proto) {
67 auto region = base::WritableSharedMemoryRegion::Create(proto.ByteSizeLong());
68 if (!region.IsValid())
69 return base::nullopt;
70
71 auto mapping = region.Map();
72 if (!mapping.IsValid())
73 return base::nullopt;
74
75 proto.SerializeToArray(mapping.memory(), mapping.size());
76 return base::WritableSharedMemoryRegion::ConvertToReadOnly(std::move(region));
77 }
78
79 paint_preview::mojom::PaintPreviewBeginCompositeRequestPtr
PrepareCompositeRequest(const paint_preview::PaintPreviewProto & proto)80 PrepareCompositeRequest(const paint_preview::PaintPreviewProto& proto) {
81 paint_preview::mojom::PaintPreviewBeginCompositeRequestPtr
82 begin_composite_request =
83 paint_preview::mojom::PaintPreviewBeginCompositeRequest::New();
84 begin_composite_request->recording_map =
85 RecordingMapFromPaintPreviewProto(proto);
86 if (begin_composite_request->recording_map.empty())
87 return nullptr;
88
89 auto read_only_proto = ToReadOnlySharedMemory(proto);
90 if (!read_only_proto) {
91 DVLOG(1) << "Failed to read proto to read-only shared memory.";
92 return nullptr;
93 }
94 begin_composite_request->proto = std::move(read_only_proto.value());
95 return begin_composite_request;
96 }
97
98 } // namespace
99
PlayerCompositorDelegate()100 PlayerCompositorDelegate::PlayerCompositorDelegate()
101 : paint_preview_compositor_service_(nullptr,
102 base::OnTaskRunnerDeleter(nullptr)),
103 paint_preview_compositor_client_(nullptr,
104 base::OnTaskRunnerDeleter(nullptr)) {}
105
~PlayerCompositorDelegate()106 PlayerCompositorDelegate::~PlayerCompositorDelegate() {
107 if (compress_on_close_ && paint_preview_service_) {
108 paint_preview_service_->GetTaskRunner()->PostTask(
109 FROM_HERE,
110 base::BindOnce(base::IgnoreResult(&FileManager::CompressDirectory),
111 paint_preview_service_->GetFileManager(), key_));
112 }
113 }
114
Initialize(PaintPreviewBaseService * paint_preview_service,const GURL & expected_url,const DirectoryKey & key,base::OnceCallback<void (int)> compositor_error,base::TimeDelta timeout_duration,size_t max_requests)115 void PlayerCompositorDelegate::Initialize(
116 PaintPreviewBaseService* paint_preview_service,
117 const GURL& expected_url,
118 const DirectoryKey& key,
119 base::OnceCallback<void(int)> compositor_error,
120 base::TimeDelta timeout_duration,
121 size_t max_requests) {
122 TRACE_EVENT_NESTABLE_ASYNC_BEGIN0("paint_preview",
123 "PlayerCompositorDelegate CreateCompositor",
124 TRACE_ID_LOCAL(this));
125 auto* memory_monitor = memory_pressure_monitor();
126 // If the device is already under moderate memory pressure abort right away.
127 if (memory_monitor &&
128 memory_monitor->GetCurrentPressureLevel() >=
129 base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_MODERATE) {
130 base::SequencedTaskRunnerHandle::Get()->PostTask(
131 FROM_HERE,
132 base::BindOnce(std::move(compositor_error),
133 static_cast<int>(
134 CompositorStatus::SKIPPED_DUE_TO_MEMORY_PRESSURE)));
135 return;
136 }
137
138 paint_preview_compositor_service_ =
139 WarmCompositor::GetInstance()->GetOrStartCompositorService(base::BindOnce(
140 &PlayerCompositorDelegate::OnCompositorServiceDisconnected,
141 weak_factory_.GetWeakPtr()));
142
143 InitializeInternal(paint_preview_service, expected_url, key,
144 std::move(compositor_error), timeout_duration,
145 max_requests);
146 }
147
InitializeWithFakeServiceForTest(PaintPreviewBaseService * paint_preview_service,const GURL & expected_url,const DirectoryKey & key,base::OnceCallback<void (int)> compositor_error,base::TimeDelta timeout_duration,size_t max_requests,std::unique_ptr<PaintPreviewCompositorService,base::OnTaskRunnerDeleter> fake_compositor_service)148 void PlayerCompositorDelegate::InitializeWithFakeServiceForTest(
149 PaintPreviewBaseService* paint_preview_service,
150 const GURL& expected_url,
151 const DirectoryKey& key,
152 base::OnceCallback<void(int)> compositor_error,
153 base::TimeDelta timeout_duration,
154 size_t max_requests,
155 std::unique_ptr<PaintPreviewCompositorService, base::OnTaskRunnerDeleter>
156 fake_compositor_service) {
157 paint_preview_compositor_service_ = std::move(fake_compositor_service);
158 paint_preview_compositor_service_->SetDisconnectHandler(
159 base::BindOnce(&PlayerCompositorDelegate::OnCompositorServiceDisconnected,
160 weak_factory_.GetWeakPtr()));
161
162 InitializeInternal(paint_preview_service, expected_url, key,
163 std::move(compositor_error), timeout_duration,
164 max_requests);
165 }
166
InitializeInternal(PaintPreviewBaseService * paint_preview_service,const GURL & expected_url,const DirectoryKey & key,base::OnceCallback<void (int)> compositor_error,base::TimeDelta timeout_duration,size_t max_requests)167 void PlayerCompositorDelegate::InitializeInternal(
168 PaintPreviewBaseService* paint_preview_service,
169 const GURL& expected_url,
170 const DirectoryKey& key,
171 base::OnceCallback<void(int)> compositor_error,
172 base::TimeDelta timeout_duration,
173 size_t max_requests) {
174 max_requests_ = max_requests;
175 compositor_error_ = std::move(compositor_error);
176 paint_preview_service_ = paint_preview_service;
177 key_ = key;
178 memory_pressure_ = std::make_unique<base::MemoryPressureListener>(
179 FROM_HERE,
180 base::BindRepeating(&PlayerCompositorDelegate::OnMemoryPressure,
181 weak_factory_.GetWeakPtr()));
182
183 paint_preview_compositor_client_ =
184 paint_preview_compositor_service_->CreateCompositor(
185 base::BindOnce(&PlayerCompositorDelegate::OnCompositorClientCreated,
186 weak_factory_.GetWeakPtr(), expected_url, key));
187 paint_preview_compositor_client_->SetDisconnectHandler(
188 base::BindOnce(&PlayerCompositorDelegate::OnCompositorClientDisconnected,
189 weak_factory_.GetWeakPtr()));
190
191 if (!timeout_duration.is_inf() && !timeout_duration.is_zero()) {
192 timeout_.Reset(
193 base::BindOnce(&PlayerCompositorDelegate::OnCompositorTimeout,
194 weak_factory_.GetWeakPtr()));
195 base::SequencedTaskRunnerHandle::Get()->PostDelayedTask(
196 FROM_HERE, timeout_.callback(), timeout_duration);
197 }
198 }
199
RequestBitmap(const base::UnguessableToken & frame_guid,const gfx::Rect & clip_rect,float scale_factor,base::OnceCallback<void (mojom::PaintPreviewCompositor::BitmapStatus,const SkBitmap &)> callback)200 int32_t PlayerCompositorDelegate::RequestBitmap(
201 const base::UnguessableToken& frame_guid,
202 const gfx::Rect& clip_rect,
203 float scale_factor,
204 base::OnceCallback<void(mojom::PaintPreviewCompositor::BitmapStatus,
205 const SkBitmap&)> callback) {
206 DCHECK(IsInitialized());
207 const int32_t request_id = next_request_id_;
208 next_request_id_++;
209 if (!paint_preview_compositor_client_) {
210 std::move(callback).Run(
211 mojom::PaintPreviewCompositor::BitmapStatus::kMissingFrame, SkBitmap());
212 return request_id;
213 }
214
215 bitmap_request_queue_.push(request_id);
216 pending_bitmap_requests_.emplace(
217 request_id,
218 BitmapRequest(frame_guid, clip_rect, scale_factor,
219 base::BindOnce(
220 &PlayerCompositorDelegate::BitmapRequestCallbackAdapter,
221 weak_factory_.GetWeakPtr(), std::move(callback))));
222 ProcessBitmapRequestsFromQueue();
223 return request_id;
224 }
225
CancelBitmapRequest(int32_t request_id)226 bool PlayerCompositorDelegate::CancelBitmapRequest(int32_t request_id) {
227 auto it = pending_bitmap_requests_.find(request_id);
228 if (it == pending_bitmap_requests_.end())
229 return false;
230
231 pending_bitmap_requests_.erase(it);
232 return true;
233 }
234
CancelAllBitmapRequests()235 void PlayerCompositorDelegate::CancelAllBitmapRequests() {
236 while (bitmap_request_queue_.size())
237 bitmap_request_queue_.pop();
238
239 pending_bitmap_requests_.clear();
240 }
241
OnClick(const base::UnguessableToken & frame_guid,const gfx::Rect & rect)242 std::vector<const GURL*> PlayerCompositorDelegate::OnClick(
243 const base::UnguessableToken& frame_guid,
244 const gfx::Rect& rect) {
245 DCHECK(IsInitialized());
246 std::vector<const GURL*> urls;
247 auto it = hit_testers_.find(frame_guid);
248 if (it != hit_testers_.end())
249 it->second->HitTest(rect, &urls);
250
251 return urls;
252 }
253
OnMemoryPressure(base::MemoryPressureListener::MemoryPressureLevel memory_pressure_level)254 void PlayerCompositorDelegate::OnMemoryPressure(
255 base::MemoryPressureListener::MemoryPressureLevel memory_pressure_level) {
256 if (memory_pressure_level ==
257 base::MemoryPressureListener::MEMORY_PRESSURE_LEVEL_CRITICAL) {
258 if (paint_preview_compositor_client_)
259 paint_preview_compositor_client_.reset();
260
261 if (paint_preview_compositor_service_)
262 paint_preview_compositor_service_.reset();
263
264 if (compositor_error_) {
265 std::move(compositor_error_)
266 .Run(static_cast<int>(
267 CompositorStatus::STOPPED_DUE_TO_MEMORY_PRESSURE));
268 }
269 }
270 }
271
272 base::MemoryPressureMonitor*
memory_pressure_monitor()273 PlayerCompositorDelegate::memory_pressure_monitor() {
274 return base::MemoryPressureMonitor::Get();
275 }
276
OnCompositorReadyStatusAdapter(mojom::PaintPreviewCompositor::BeginCompositeStatus status,mojom::PaintPreviewBeginCompositeResponsePtr composite_response)277 void PlayerCompositorDelegate::OnCompositorReadyStatusAdapter(
278 mojom::PaintPreviewCompositor::BeginCompositeStatus status,
279 mojom::PaintPreviewBeginCompositeResponsePtr composite_response) {
280 timeout_.Cancel();
281 CompositorStatus new_status;
282 switch (status) {
283 // fallthrough
284 case mojom::PaintPreviewCompositor::BeginCompositeStatus::kSuccess:
285 case mojom::PaintPreviewCompositor::BeginCompositeStatus::kPartialSuccess:
286 new_status = CompositorStatus::OK;
287 break;
288 case mojom::PaintPreviewCompositor::BeginCompositeStatus::
289 kDeserializingFailure:
290 new_status = CompositorStatus::COMPOSITOR_DESERIALIZATION_ERROR;
291 break;
292 case mojom::PaintPreviewCompositor::BeginCompositeStatus::
293 kCompositingFailure:
294 new_status = CompositorStatus::INVALID_ROOT_FRAME_SKP;
295 break;
296 default:
297 NOTREACHED();
298 }
299 OnCompositorReady(new_status, std::move(composite_response));
300 }
301
OnCompositorServiceDisconnected()302 void PlayerCompositorDelegate::OnCompositorServiceDisconnected() {
303 DLOG(ERROR) << "Compositor service disconnected.";
304 if (compositor_error_) {
305 std::move(compositor_error_)
306 .Run(static_cast<int>(CompositorStatus::COMPOSITOR_SERVICE_DISCONNECT));
307 }
308 }
309
OnCompositorClientCreated(const GURL & expected_url,const DirectoryKey & key)310 void PlayerCompositorDelegate::OnCompositorClientCreated(
311 const GURL& expected_url,
312 const DirectoryKey& key) {
313 TRACE_EVENT_NESTABLE_ASYNC_END0("paint_preview",
314 "PlayerCompositorDelegate CreateCompositor",
315 TRACE_ID_LOCAL(this));
316 paint_preview_service_->GetCapturedPaintPreviewProto(
317 key, base::nullopt,
318 base::BindOnce(&PlayerCompositorDelegate::OnProtoAvailable,
319 weak_factory_.GetWeakPtr(), expected_url));
320 }
321
OnProtoAvailable(const GURL & expected_url,PaintPreviewBaseService::ProtoReadStatus proto_status,std::unique_ptr<PaintPreviewProto> proto)322 void PlayerCompositorDelegate::OnProtoAvailable(
323 const GURL& expected_url,
324 PaintPreviewBaseService::ProtoReadStatus proto_status,
325 std::unique_ptr<PaintPreviewProto> proto) {
326 if (proto_status == PaintPreviewBaseService::ProtoReadStatus::kExpired) {
327 OnCompositorReady(CompositorStatus::CAPTURE_EXPIRED, nullptr);
328 return;
329 }
330
331 if (proto_status == PaintPreviewBaseService::ProtoReadStatus::kNoProto) {
332 OnCompositorReady(CompositorStatus::NO_CAPTURE, nullptr);
333 return;
334 }
335
336 if (proto_status ==
337 PaintPreviewBaseService::ProtoReadStatus::kDeserializationError ||
338 !proto || !proto->IsInitialized()) {
339 OnCompositorReady(CompositorStatus::PROTOBUF_DESERIALIZATION_ERROR,
340 nullptr);
341 return;
342 }
343
344 const uint32_t version = proto->metadata().version();
345 if (version < kPaintPreviewVersion) {
346 // If the version is old there was a breaking change to either;
347 // - The SkPicture encoding format
348 // - The storage structure
349 // In either case, the new code is likely unable to deserialize the result
350 // so we should early abort.
351 OnCompositorReady(CompositorStatus::OLD_VERSION, nullptr);
352 return;
353 } else if (version > kPaintPreviewVersion) {
354 // This shouldn't happen hence NOTREACHED(). However, in release we should
355 // treat this as a new failure type to catch any possible regressions.
356 OnCompositorReady(CompositorStatus::UNEXPECTED_VERSION, nullptr);
357 NOTREACHED();
358 return;
359 }
360
361 auto proto_url = GURL(proto->metadata().url());
362 if (expected_url != proto_url) {
363 OnCompositorReady(CompositorStatus::URL_MISMATCH, nullptr);
364 return;
365 }
366
367 if (!paint_preview_compositor_client_) {
368 OnCompositorReady(CompositorStatus::COMPOSITOR_CLIENT_DISCONNECT, nullptr);
369 return;
370 }
371
372 paint_preview_compositor_client_->SetRootFrameUrl(proto_url);
373
374 proto_ = std::move(proto);
375 base::ThreadPool::PostTaskAndReplyWithResult(
376 FROM_HERE, {base::MayBlock(), base::TaskPriority::USER_VISIBLE},
377 base::BindOnce(&PrepareCompositeRequest, *proto_),
378 base::BindOnce(&PlayerCompositorDelegate::SendCompositeRequest,
379 weak_factory_.GetWeakPtr()));
380 }
381
SendCompositeRequest(mojom::PaintPreviewBeginCompositeRequestPtr begin_composite_request)382 void PlayerCompositorDelegate::SendCompositeRequest(
383 mojom::PaintPreviewBeginCompositeRequestPtr begin_composite_request) {
384 // TODO(crbug.com/1021590): Handle initialization errors.
385 if (!begin_composite_request) {
386 OnCompositorReady(CompositorStatus::INVALID_REQUEST, nullptr);
387 return;
388 }
389
390 // It is possible the client was disconnected while loading the proto.
391 if (!paint_preview_compositor_client_) {
392 OnCompositorReady(CompositorStatus::COMPOSITOR_CLIENT_DISCONNECT, nullptr);
393 return;
394 }
395
396 paint_preview_compositor_client_->BeginSeparatedFrameComposite(
397 std::move(begin_composite_request),
398 base::BindOnce(&PlayerCompositorDelegate::OnCompositorReadyStatusAdapter,
399 weak_factory_.GetWeakPtr()));
400
401 // Defer building hit testers so it happens in parallel with preparing the
402 // compositor.
403 hit_testers_ = BuildHitTesters(*proto_);
404 proto_.reset();
405 }
406
OnCompositorClientDisconnected()407 void PlayerCompositorDelegate::OnCompositorClientDisconnected() {
408 DLOG(ERROR) << "Compositor client disconnected.";
409 if (compositor_error_) {
410 std::move(compositor_error_)
411 .Run(static_cast<int>(CompositorStatus::COMPOSITOR_CLIENT_DISCONNECT));
412 }
413 }
414
OnCompositorTimeout()415 void PlayerCompositorDelegate::OnCompositorTimeout() {
416 DLOG(ERROR) << "Compositor process startup timed out.";
417 if (compositor_error_) {
418 std::move(compositor_error_)
419 .Run(static_cast<int>(CompositorStatus::TIMED_OUT));
420 }
421 }
422
ProcessBitmapRequestsFromQueue()423 void PlayerCompositorDelegate::ProcessBitmapRequestsFromQueue() {
424 while (active_requests_ < max_requests_ && bitmap_request_queue_.size()) {
425 int request_id = bitmap_request_queue_.front();
426 bitmap_request_queue_.pop();
427
428 auto it = pending_bitmap_requests_.find(request_id);
429 if (it == pending_bitmap_requests_.end())
430 continue;
431
432 BitmapRequest& request = it->second;
433 active_requests_++;
434 // If the client disconnects mid request, just give up as we should be
435 // exiting.
436 if (!paint_preview_compositor_client_)
437 return;
438
439 paint_preview_compositor_client_->BitmapForSeparatedFrame(
440 request.frame_guid, request.clip_rect, request.scale_factor,
441 std::move(request.callback));
442 pending_bitmap_requests_.erase(it);
443 }
444 }
445
BitmapRequestCallbackAdapter(base::OnceCallback<void (mojom::PaintPreviewCompositor::BitmapStatus,const SkBitmap &)> callback,mojom::PaintPreviewCompositor::BitmapStatus status,const SkBitmap & bitmap)446 void PlayerCompositorDelegate::BitmapRequestCallbackAdapter(
447 base::OnceCallback<void(mojom::PaintPreviewCompositor::BitmapStatus,
448 const SkBitmap&)> callback,
449 mojom::PaintPreviewCompositor::BitmapStatus status,
450 const SkBitmap& bitmap) {
451 std::move(callback).Run(status, bitmap);
452
453 active_requests_--;
454 ProcessBitmapRequestsFromQueue();
455 }
456
457 } // namespace paint_preview
458