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/renderer/paint_preview_recorder_impl.h"
6
7 #include <utility>
8
9 #include "base/auto_reset.h"
10 #include "base/metrics/histogram_functions.h"
11 #include "base/task_runner.h"
12 #include "base/time/time.h"
13 #include "cc/paint/paint_record.h"
14 #include "cc/paint/paint_recorder.h"
15 #include "components/paint_preview/renderer/paint_preview_recorder_utils.h"
16 #include "content/public/renderer/render_frame.h"
17 #include "mojo/public/cpp/base/shared_memory_utils.h"
18 #include "third_party/blink/public/common/associated_interfaces/associated_interface_registry.h"
19 #include "third_party/blink/public/web/web_local_frame.h"
20
21 namespace paint_preview {
22
23 namespace {
24
FinishRecording(sk_sp<const cc::PaintRecord> recording,const gfx::Rect & bounds,PaintPreviewTracker * tracker,base::File skp_file,mojom::PaintPreviewCaptureResponse * response)25 mojom::PaintPreviewStatus FinishRecording(
26 sk_sp<const cc::PaintRecord> recording,
27 const gfx::Rect& bounds,
28 PaintPreviewTracker* tracker,
29 base::File skp_file,
30 mojom::PaintPreviewCaptureResponse* response) {
31 ParseGlyphs(recording.get(), tracker);
32 if (!SerializeAsSkPicture(recording, tracker, bounds, std::move(skp_file)))
33 return mojom::PaintPreviewStatus::kCaptureFailed;
34
35 BuildResponse(tracker, response);
36 return mojom::PaintPreviewStatus::kOk;
37 }
38
39 } // namespace
40
PaintPreviewRecorderImpl(content::RenderFrame * render_frame)41 PaintPreviewRecorderImpl::PaintPreviewRecorderImpl(
42 content::RenderFrame* render_frame)
43 : content::RenderFrameObserver(render_frame),
44 is_painting_preview_(false),
45 is_main_frame_(render_frame->IsMainFrame()) {
46 render_frame->GetAssociatedInterfaceRegistry()->AddInterface(
47 base::BindRepeating(&PaintPreviewRecorderImpl::BindPaintPreviewRecorder,
48 weak_ptr_factory_.GetWeakPtr()));
49 }
50
51 PaintPreviewRecorderImpl::~PaintPreviewRecorderImpl() = default;
52
CapturePaintPreview(mojom::PaintPreviewCaptureParamsPtr params,CapturePaintPreviewCallback callback)53 void PaintPreviewRecorderImpl::CapturePaintPreview(
54 mojom::PaintPreviewCaptureParamsPtr params,
55 CapturePaintPreviewCallback callback) {
56 mojom::PaintPreviewStatus status = mojom::PaintPreviewStatus::kOk;
57 base::ReadOnlySharedMemoryRegion region;
58 // This should not be called recursively or multiple times while unfinished
59 // (Blink can only run one capture per RenderFrame at a time).
60 DCHECK(!is_painting_preview_);
61 // DCHECK, but fallback safely as it is difficult to reason about whether this
62 // might happen due to it being tied to a RenderFrame rather than
63 // RenderWidget and we don't want to crash the renderer as this is
64 // recoverable.
65 auto response = mojom::PaintPreviewCaptureResponse::New();
66 if (is_painting_preview_) {
67 status = mojom::PaintPreviewStatus::kAlreadyCapturing;
68 std::move(callback).Run(status, std::move(response));
69 return;
70 }
71 base::AutoReset<bool>(&is_painting_preview_, true);
72
73 CapturePaintPreviewInternal(params, response.get(), &status);
74 std::move(callback).Run(status, std::move(response));
75 }
76
OnDestruct()77 void PaintPreviewRecorderImpl::OnDestruct() {
78 paint_preview_recorder_receiver_.reset();
79 base::ThreadTaskRunnerHandle::Get()->DeleteSoon(FROM_HERE, this);
80 }
81
BindPaintPreviewRecorder(mojo::PendingAssociatedReceiver<mojom::PaintPreviewRecorder> receiver)82 void PaintPreviewRecorderImpl::BindPaintPreviewRecorder(
83 mojo::PendingAssociatedReceiver<mojom::PaintPreviewRecorder> receiver) {
84 paint_preview_recorder_receiver_.Bind(std::move(receiver));
85 }
86
CapturePaintPreviewInternal(const mojom::PaintPreviewCaptureParamsPtr & params,mojom::PaintPreviewCaptureResponse * response,mojom::PaintPreviewStatus * status)87 void PaintPreviewRecorderImpl::CapturePaintPreviewInternal(
88 const mojom::PaintPreviewCaptureParamsPtr& params,
89 mojom::PaintPreviewCaptureResponse* response,
90 mojom::PaintPreviewStatus* status) {
91 blink::WebLocalFrame* frame = render_frame()->GetWebFrame();
92 // Ensure the a frame actually exists to avoid a possible crash.
93 if (!frame) {
94 DVLOG(1) << "Error: renderer has no frame yet!";
95 return;
96 }
97
98 // Warm up paint for an out-of-lifecycle paint phase.
99 frame->DispatchBeforePrintEvent();
100
101 DCHECK_EQ(is_main_frame_, params->is_main_frame);
102 gfx::Rect bounds;
103 if (is_main_frame_ || params->clip_rect == gfx::Rect(0, 0, 0, 0)) {
104 auto size = frame->DocumentSize();
105
106 // |size| may be 0 if a tab is captured prior to layout finishing. This
107 // shouldn't occur often, if at all, in normal usage. However, this may
108 // occur during tests. Capturing prior to layout is non-sensical as the
109 // canvas size cannot be deremined so just abort.
110 if (size.height == 0 || size.width == 0) {
111 *status = mojom::PaintPreviewStatus::kCaptureFailed;
112 return;
113 }
114 bounds = gfx::Rect(0, 0, size.width, size.height);
115 } else {
116 bounds = gfx::Rect(params->clip_rect.size());
117 }
118
119 cc::PaintRecorder recorder;
120 PaintPreviewTracker tracker(params->guid, frame->GetEmbeddingToken(),
121 is_main_frame_);
122 cc::PaintCanvas* canvas =
123 recorder.beginRecording(bounds.width(), bounds.height());
124 canvas->SetPaintPreviewTracker(&tracker);
125
126 // Use time ticks manually rather than a histogram macro so as to;
127 // 1. Account for main frames and subframes separately.
128 // 2. Mitigate binary size as this won't be used that often.
129 // 3. Record only on successes as failures are likely to be outliers (fast or
130 // slow).
131 base::TimeTicks start_time = base::TimeTicks::Now();
132 bool success = frame->CapturePaintPreview(bounds, canvas);
133 base::TimeDelta capture_time = base::TimeTicks::Now() - start_time;
134 response->blink_recording_time = capture_time;
135
136 if (is_main_frame_) {
137 base::UmaHistogramBoolean("Renderer.PaintPreview.Capture.MainFrameSuccess",
138 success);
139 if (success) {
140 // Main frame should generally be the largest cost and will always run so
141 // it is tracked separately.
142 base::UmaHistogramTimes(
143 "Renderer.PaintPreview.Capture.MainFrameBlinkCaptureDuration",
144 capture_time);
145 }
146 } else {
147 base::UmaHistogramBoolean("Renderer.PaintPreview.Capture.SubframeSuccess",
148 success);
149 if (success) {
150 base::UmaHistogramTimes(
151 "Renderer.PaintPreview.Capture.SubframeBlinkCaptureDuration",
152 capture_time);
153 }
154 }
155
156 // Restore to before out-of-lifecycle paint phase.
157 frame->DispatchAfterPrintEvent();
158 if (!success) {
159 *status = mojom::PaintPreviewStatus::kCaptureFailed;
160 return;
161 }
162
163 // TODO(crbug/1011896): Determine if making this async would be beneficial.
164 *status = FinishRecording(recorder.finishRecordingAsPicture(), bounds,
165 &tracker, std::move(params->file), response);
166 }
167
168 } // namespace paint_preview
169