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