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/services/paint_preview_compositor/paint_preview_compositor_impl.h"
6
7 #include <stdint.h>
8
9 #include <memory>
10 #include <utility>
11
12 #include "base/callback_helpers.h"
13 #include "base/containers/flat_map.h"
14 #include "base/containers/span.h"
15 #include "base/files/file.h"
16 #include "base/files/file_path.h"
17 #include "base/files/scoped_temp_dir.h"
18 #include "base/notreached.h"
19 #include "base/optional.h"
20 #include "base/test/task_environment.h"
21 #include "base/unguessable_token.h"
22 #include "components/paint_preview/common/capture_result.h"
23 #include "components/paint_preview/common/file_stream.h"
24 #include "components/paint_preview/common/paint_preview_tracker.h"
25 #include "components/paint_preview/common/proto/paint_preview.pb.h"
26 #include "components/paint_preview/common/recording_map.h"
27 #include "components/paint_preview/common/serialized_recording.h"
28 #include "mojo/public/cpp/base/big_buffer.h"
29 #include "testing/gmock/include/gmock/gmock.h"
30 #include "testing/gtest/include/gtest/gtest.h"
31 #include "third_party/skia/include/core/SkCanvas.h"
32 #include "third_party/skia/include/core/SkColor.h"
33 #include "third_party/skia/include/core/SkMatrix.h"
34 #include "third_party/skia/include/core/SkPicture.h"
35 #include "third_party/skia/include/core/SkPictureRecorder.h"
36 #include "third_party/skia/include/core/SkRefCnt.h"
37 #include "third_party/skia/include/core/SkStream.h"
38 #include "ui/gfx/geometry/rect.h"
39
40 namespace paint_preview {
41
42 namespace {
43
44 // Checks that |status| == |expected_status|. If |expected_status| == kSuccess,
45 // then also checks that;
46 // - |response->root_frame_guid| == |expected_root_frame_guid|
47 // - |response->subframe_rect_hierarchy| == |expected_data|
BeginSeparatedFrameCompositeCallbackImpl(mojom::PaintPreviewCompositor::BeginCompositeStatus expected_status,const base::UnguessableToken & expected_root_frame_guid,const base::flat_map<base::UnguessableToken,mojom::FrameDataPtr> & expected_data,mojom::PaintPreviewCompositor::BeginCompositeStatus status,mojom::PaintPreviewBeginCompositeResponsePtr response)48 void BeginSeparatedFrameCompositeCallbackImpl(
49 mojom::PaintPreviewCompositor::BeginCompositeStatus expected_status,
50 const base::UnguessableToken& expected_root_frame_guid,
51 const base::flat_map<base::UnguessableToken, mojom::FrameDataPtr>&
52 expected_data,
53 mojom::PaintPreviewCompositor::BeginCompositeStatus status,
54 mojom::PaintPreviewBeginCompositeResponsePtr response) {
55 EXPECT_EQ(status, expected_status);
56 if (expected_status !=
57 mojom::PaintPreviewCompositor::BeginCompositeStatus::kSuccess)
58 return;
59 EXPECT_EQ(response->root_frame_guid, expected_root_frame_guid);
60 EXPECT_EQ(response->frames.size(), expected_data.size());
61 for (const auto& frame : expected_data) {
62 EXPECT_TRUE(response->frames.count(frame.first));
63 EXPECT_EQ(response->frames[frame.first]->scroll_extents,
64 frame.second->scroll_extents);
65 size_t size = response->frames[frame.first]->subframes.size();
66 EXPECT_EQ(size, frame.second->subframes.size());
67 std::vector<std::pair<base::UnguessableToken, gfx::Rect>>
68 response_subframes, expected_subframes;
69 for (size_t i = 0; i < size; ++i) {
70 response_subframes.push_back(
71 {response->frames[frame.first]->subframes[i]->frame_guid,
72 response->frames[frame.first]->subframes[i]->clip_rect});
73 expected_subframes.push_back({frame.second->subframes[i]->frame_guid,
74 frame.second->subframes[i]->clip_rect});
75 }
76 EXPECT_THAT(response_subframes,
77 ::testing::UnorderedElementsAreArray(expected_subframes));
78 }
79 }
80
BeginMainFrameCompositeCallbackImpl(mojom::PaintPreviewCompositor::BeginCompositeStatus expected_status,mojom::PaintPreviewCompositor::BeginCompositeStatus status)81 void BeginMainFrameCompositeCallbackImpl(
82 mojom::PaintPreviewCompositor::BeginCompositeStatus expected_status,
83 mojom::PaintPreviewCompositor::BeginCompositeStatus status) {
84 EXPECT_EQ(status, expected_status);
85 }
86
87 // Checks that |status| == |expected_status|. If |expected_status| == kSuccess,
88 // then it also checks that |bitmap| and |expected_bitmap| are pixel identical.
BitmapCallbackImpl(mojom::PaintPreviewCompositor::BitmapStatus expected_status,const SkBitmap & expected_bitmap,mojom::PaintPreviewCompositor::BitmapStatus status,const SkBitmap & bitmap)89 void BitmapCallbackImpl(
90 mojom::PaintPreviewCompositor::BitmapStatus expected_status,
91 const SkBitmap& expected_bitmap,
92 mojom::PaintPreviewCompositor::BitmapStatus status,
93 const SkBitmap& bitmap) {
94 EXPECT_EQ(status, expected_status);
95 if (expected_status != mojom::PaintPreviewCompositor::BitmapStatus::kSuccess)
96 return;
97 EXPECT_EQ(bitmap.width(), expected_bitmap.width());
98 EXPECT_EQ(bitmap.height(), expected_bitmap.height());
99 EXPECT_EQ(bitmap.bytesPerPixel(), expected_bitmap.bytesPerPixel());
100 // Assert that all the bytes of the backing memory are equal. This check is
101 // only safe if all of the width, height and bytesPerPixel are equal between
102 // the two bitmaps.
103 EXPECT_EQ(memcmp(bitmap.getPixels(), expected_bitmap.getPixels(),
104 expected_bitmap.bytesPerPixel() * expected_bitmap.width() *
105 expected_bitmap.height()),
106 0);
107 }
108
109 // Encodes |proto| a ReadOnlySharedMemoryRegion.
ToReadOnlySharedMemory(const PaintPreviewProto & proto)110 base::ReadOnlySharedMemoryRegion ToReadOnlySharedMemory(
111 const PaintPreviewProto& proto) {
112 auto region = base::WritableSharedMemoryRegion::Create(proto.ByteSizeLong());
113 EXPECT_TRUE(region.IsValid());
114 auto mapping = region.Map();
115 EXPECT_TRUE(mapping.IsValid());
116 proto.SerializeToArray(mapping.memory(), mapping.size());
117 return base::WritableSharedMemoryRegion::ConvertToReadOnly(std::move(region));
118 }
119
ToSkRect(const gfx::Size & size)120 SkRect ToSkRect(const gfx::Size& size) {
121 return SkRect::MakeWH(size.width(), size.height());
122 }
123
ToSkRect(const gfx::Rect & rect)124 SkRect ToSkRect(const gfx::Rect& rect) {
125 return SkRect::MakeXYWH(rect.x(), rect.y(), rect.width(), rect.height());
126 }
127
128 // Draw a dummy picture of size |scroll_extents|, whose origin is equal to
129 // |clip_rect| and clipped by |clip_rect|'s size. The dummy picture will by
130 // filled with |rect_fill_color| with a cyan border and will have an XY axis
131 // drawn at the origin in red and green that is 10 units in |canvas|'s local
132 // coordinate system.
DrawDummyTestPicture(SkCanvas * canvas,SkColor rect_fill_color,const gfx::Size & scroll_extents,base::Optional<gfx::Rect> clip_rect=base::nullopt,gfx::Size scroll_offsets=gfx::Size ())133 void DrawDummyTestPicture(SkCanvas* canvas,
134 SkColor rect_fill_color,
135 const gfx::Size& scroll_extents,
136 base::Optional<gfx::Rect> clip_rect = base::nullopt,
137 gfx::Size scroll_offsets = gfx::Size()) {
138 canvas->save();
139 if (clip_rect.has_value()) {
140 canvas->clipRect(ToSkRect(*clip_rect));
141 canvas->translate(clip_rect->x(), clip_rect->y());
142 }
143 canvas->translate(-scroll_offsets.width(), -scroll_offsets.height());
144
145 SkPaint paint;
146 paint.setStyle(SkPaint::kFill_Style);
147 paint.setColor(rect_fill_color);
148 canvas->drawRect(ToSkRect(scroll_extents), paint);
149
150 SkPaint border_paint;
151 border_paint.setStyle(SkPaint::kStroke_Style);
152 border_paint.setColor(SK_ColorCYAN);
153 border_paint.setStrokeWidth(2.0);
154 canvas->drawRect(ToSkRect(scroll_extents), border_paint);
155
156 const int kAxisLength = 25;
157 const int kAxisThickness = 1;
158
159 // Draw axis as rects instead of lines so when the canvas is scaled, the axis
160 // scale relative to the origin, rather than by their stroke center.
161 SkPaint x_axis_paint;
162 x_axis_paint.setStyle(SkPaint::kFill_Style);
163 x_axis_paint.setColor(SK_ColorRED);
164 canvas->drawRect(SkRect::MakeXYWH(1, 0, kAxisLength, kAxisThickness),
165 x_axis_paint);
166
167 SkPaint y_axis_paint;
168 y_axis_paint.setStyle(SkPaint::kFill_Style);
169 y_axis_paint.setColor(SK_ColorGREEN);
170 canvas->drawRect(SkRect::MakeXYWH(0, 1, kAxisThickness, kAxisLength),
171 y_axis_paint);
172
173 // Draw an additional diagonal line to help identify the origin if a subframe
174 // is scrolled.
175 SkPaint diagonal_line_paint;
176 border_paint.setStyle(SkPaint::kStroke_Style);
177 border_paint.setColor(SK_ColorBLUE);
178 border_paint.setStrokeWidth(2.0);
179 canvas->drawLine(0, 0, kAxisLength, kAxisLength, diagonal_line_paint);
180
181 canvas->restore();
182 }
183
184 // Fill |frame| and |expected_data| with the remaining parameters and draw and
185 // save a recording to |path|.
PopulateFrameProto(PaintPreviewFrameProto * frame_proto,const base::UnguessableToken & guid,bool set_is_main_frame,const base::FilePath & path,const gfx::Size & scroll_extents,std::vector<std::pair<base::UnguessableToken,gfx::Rect>> subframes,base::flat_map<base::UnguessableToken,mojom::FrameDataPtr> * expected_data,gfx::Size scroll_offsets=gfx::Size (),SkColor picture_fill_color=SK_ColorDKGRAY)186 void PopulateFrameProto(
187 PaintPreviewFrameProto* frame_proto,
188 const base::UnguessableToken& guid,
189 bool set_is_main_frame,
190 const base::FilePath& path,
191 const gfx::Size& scroll_extents,
192 std::vector<std::pair<base::UnguessableToken, gfx::Rect>> subframes,
193 base::flat_map<base::UnguessableToken, mojom::FrameDataPtr>* expected_data,
194 gfx::Size scroll_offsets = gfx::Size(),
195 SkColor picture_fill_color = SK_ColorDKGRAY) {
196 frame_proto->set_embedding_token_low(guid.GetLowForSerialization());
197 frame_proto->set_embedding_token_high(guid.GetHighForSerialization());
198 frame_proto->set_is_main_frame(set_is_main_frame);
199 frame_proto->set_file_path(path.AsUTF8Unsafe());
200 frame_proto->set_scroll_offset_x(scroll_offsets.width());
201 frame_proto->set_scroll_offset_y(scroll_offsets.height());
202
203 SkPictureRecorder recorder;
204 SkCanvas* canvas = recorder.beginRecording(ToSkRect(scroll_extents));
205 DrawDummyTestPicture(canvas, picture_fill_color, scroll_extents);
206
207 // It's okay to create a new document guid every time since we're not
208 // observing it.
209 PaintPreviewTracker tracker(base::UnguessableToken::Create(), guid,
210 set_is_main_frame);
211 mojom::FrameDataPtr expected_frame_data = mojom::FrameData::New();
212 expected_frame_data->scroll_extents = scroll_extents;
213 expected_frame_data->scroll_offsets = scroll_offsets;
214
215 for (const auto& subframe : subframes) {
216 const base::UnguessableToken& subframe_id = subframe.first;
217 gfx::Rect clip_rect = subframe.second;
218
219 // Record the subframe as custom data to |canvas|.
220 uint32_t content_id =
221 tracker.CreateContentForRemoteFrame(clip_rect, subframe_id);
222 tracker.CustomDataToSkPictureCallback(canvas, content_id);
223
224 auto* content_id_embedding_token_pair =
225 frame_proto->add_content_id_to_embedding_tokens();
226 content_id_embedding_token_pair->set_content_id(content_id);
227 content_id_embedding_token_pair->set_embedding_token_low(
228 subframe_id.GetLowForSerialization());
229 content_id_embedding_token_pair->set_embedding_token_high(
230 subframe_id.GetHighForSerialization());
231 expected_frame_data->subframes.push_back(
232 mojom::SubframeClipRect::New(subframe_id, clip_rect));
233 tracker.TransformClipForFrame(content_id);
234 }
235
236 sk_sp<SkPicture> pic = recorder.finishRecordingAsPicture();
237
238 size_t serialized_size = 0;
239 ASSERT_TRUE(RecordToFile(
240 base::File(path, base::File::FLAG_CREATE_ALWAYS | base::File::FLAG_WRITE),
241 pic, &tracker, base::nullopt, &serialized_size));
242 ASSERT_GE(serialized_size, 0u);
243
244 expected_data->insert({guid, std::move(expected_frame_data)});
245 }
246
247 // Enumeration to select between the |*SeparateFrame*| and |*MainFrame*|
248 // functions on |PaintPreviewCompositor|.
249 enum class CompositeType { kSeparateFrame, kMainFrame };
250
CompositeTypeParamToString(const::testing::TestParamInfo<CompositeType> & composite_type)251 std::string CompositeTypeParamToString(
252 const ::testing::TestParamInfo<CompositeType>& composite_type) {
253 switch (composite_type.param) {
254 case CompositeType::kSeparateFrame:
255 return "SeparateFrame";
256 case CompositeType::kMainFrame:
257 return "MainFrame";
258 }
259 }
260
261 } // namespace
262
263 class PaintPreviewCompositorBeginCompositeTest
264 : public testing::TestWithParam<CompositeType> {
265 protected:
SetUp()266 void SetUp() override {
267 ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
268 compositor_.SetRootFrameUrl(url_);
269 }
270
271 // Run |Begin*Composite| with |request| and compare the response with
272 // the |expected_*| parameters.
BeginCompositeAndValidate(mojom::PaintPreviewBeginCompositeRequestPtr request,mojom::PaintPreviewCompositor::BeginCompositeStatus expected_status,const base::UnguessableToken & expected_root_frame_guid,base::flat_map<base::UnguessableToken,mojom::FrameDataPtr> expected_data)273 void BeginCompositeAndValidate(
274 mojom::PaintPreviewBeginCompositeRequestPtr request,
275 mojom::PaintPreviewCompositor::BeginCompositeStatus expected_status,
276 const base::UnguessableToken& expected_root_frame_guid,
277 base::flat_map<base::UnguessableToken, mojom::FrameDataPtr>
278 expected_data) {
279 switch (GetParam()) {
280 case CompositeType::kSeparateFrame:
281 compositor_.BeginSeparatedFrameComposite(
282 std::move(request),
283 base::BindOnce(&BeginSeparatedFrameCompositeCallbackImpl,
284 expected_status, expected_root_frame_guid,
285 std::move(expected_data)));
286 break;
287 case CompositeType::kMainFrame:
288 compositor_.BeginMainFrameComposite(
289 std::move(request),
290 base::BindOnce(&BeginMainFrameCompositeCallbackImpl,
291 expected_status));
292 break;
293 default:
294 NOTREACHED();
295 break;
296 }
297 }
298
299 base::ScopedTempDir temp_dir_;
300
301 GURL url_{"https://www.chromium.org"};
302
303 private:
304 PaintPreviewCompositorImpl compositor_{mojo::NullReceiver(), nullptr,
305 base::DoNothing()};
306 };
307
TEST_P(PaintPreviewCompositorBeginCompositeTest,MissingSubFrameRecording)308 TEST_P(PaintPreviewCompositorBeginCompositeTest, MissingSubFrameRecording) {
309 const base::UnguessableToken kRootFrameID = base::UnguessableToken::Create();
310 gfx::Size root_frame_scroll_extent(100, 200);
311 const base::UnguessableToken kSubframe_0_ID =
312 base::UnguessableToken::Create();
313 gfx::Size subframe_0_scroll_extent(50, 75);
314 gfx::Rect subframe_0_clip_rect(10, 20, 30, 40);
315 const base::UnguessableToken kSubframe_0_0_ID =
316 base::UnguessableToken::Create();
317 gfx::Size subframe_0_0_scroll_extent(20, 20);
318 gfx::Rect subframe_0_0_clip_rect(10, 10, 20, 20);
319 const base::UnguessableToken kSubframe_0_1_ID =
320 base::UnguessableToken::Create();
321 gfx::Size subframe_0_1_scroll_extent(10, 5);
322 gfx::Rect subframe_0_1_clip_rect(10, 10, 30, 30);
323 const base::UnguessableToken kSubframe_1_ID =
324 base::UnguessableToken::Create();
325 gfx::Size subframe_1_scroll_extent(1, 1);
326 gfx::Rect subframe_1_clip_rect(0, 0, 1, 1);
327
328 PaintPreviewProto proto;
329 proto.mutable_metadata()->set_url(url_.spec());
330 base::flat_map<base::UnguessableToken, mojom::FrameDataPtr> expected_data;
331 PopulateFrameProto(proto.mutable_root_frame(), kRootFrameID, true,
332 temp_dir_.GetPath().AppendASCII("root.skp"),
333 root_frame_scroll_extent,
334 {{kSubframe_0_ID, subframe_0_clip_rect},
335 {kSubframe_1_ID, subframe_1_clip_rect}},
336 &expected_data);
337 PopulateFrameProto(proto.add_subframes(), kSubframe_0_ID, false,
338 temp_dir_.GetPath().AppendASCII("subframe_0.skp"),
339 subframe_0_scroll_extent,
340 {{kSubframe_0_0_ID, subframe_0_0_clip_rect},
341 {kSubframe_0_1_ID, subframe_0_1_clip_rect}},
342 &expected_data);
343 PopulateFrameProto(proto.add_subframes(), kSubframe_0_0_ID, false,
344 temp_dir_.GetPath().AppendASCII("subframe_0_0.skp"),
345 subframe_0_0_scroll_extent, {}, &expected_data);
346 PopulateFrameProto(proto.add_subframes(), kSubframe_0_1_ID, false,
347 temp_dir_.GetPath().AppendASCII("subframe_0_1.skp"),
348 subframe_0_1_scroll_extent, {}, &expected_data);
349 PopulateFrameProto(proto.add_subframes(), kSubframe_1_ID, false,
350 temp_dir_.GetPath().AppendASCII("subframe_1.skp"),
351 subframe_1_scroll_extent, {}, &expected_data);
352 auto recording_map = RecordingMapFromPaintPreviewProto(proto);
353 // Missing a subframe SKP is still valid. Compositing will ignore it in the
354 // results.
355 recording_map.erase(kSubframe_0_0_ID);
356 expected_data.erase(kSubframe_0_0_ID);
357 // Remove the kSubframe_0_0_ID from the subframe list since it isn't
358 // available.
359 auto& vec = expected_data[kSubframe_0_ID]->subframes;
360 vec.front() = std::move(vec.back());
361 vec.pop_back();
362
363 mojom::PaintPreviewBeginCompositeRequestPtr request =
364 mojom::PaintPreviewBeginCompositeRequest::New();
365 request->recording_map = std::move(recording_map);
366 request->proto = ToReadOnlySharedMemory(proto);
367
368 BeginCompositeAndValidate(
369 std::move(request),
370 mojom::PaintPreviewCompositor::BeginCompositeStatus::kPartialSuccess,
371 kRootFrameID, std::move(expected_data));
372 }
373
374 // Ensure that depending on a frame multiple times works.
TEST_P(PaintPreviewCompositorBeginCompositeTest,DuplicateFrame)375 TEST_P(PaintPreviewCompositorBeginCompositeTest, DuplicateFrame) {
376 const base::UnguessableToken kRootFrameID = base::UnguessableToken::Create();
377 gfx::Size root_frame_scroll_extent(100, 200);
378 const base::UnguessableToken kSubframe_0_ID =
379 base::UnguessableToken::Create();
380 gfx::Size subframe_0_scroll_extent(50, 75);
381 gfx::Rect subframe_0_clip_rect(10, 20, 30, 40);
382
383 PaintPreviewProto proto;
384 proto.mutable_metadata()->set_url(url_.spec());
385 base::flat_map<base::UnguessableToken, mojom::FrameDataPtr> expected_data;
386 PopulateFrameProto(proto.mutable_root_frame(), kRootFrameID, true,
387 temp_dir_.GetPath().AppendASCII("root.skp"),
388 root_frame_scroll_extent,
389 {{kSubframe_0_ID, subframe_0_clip_rect},
390 {kSubframe_0_ID, subframe_0_clip_rect}},
391 &expected_data);
392 PopulateFrameProto(proto.add_subframes(), kSubframe_0_ID, false,
393 temp_dir_.GetPath().AppendASCII("subframe_0.skp"),
394 subframe_0_scroll_extent, {}, &expected_data);
395
396 mojom::PaintPreviewBeginCompositeRequestPtr request =
397 mojom::PaintPreviewBeginCompositeRequest::New();
398 request->recording_map = RecordingMapFromPaintPreviewProto(proto);
399 request->proto = ToReadOnlySharedMemory(proto);
400
401 BeginCompositeAndValidate(
402 std::move(request),
403 mojom::PaintPreviewCompositor::BeginCompositeStatus::kSuccess,
404 kRootFrameID, std::move(expected_data));
405 }
406
407 // Ensure that a loop in frames works.
TEST_P(PaintPreviewCompositorBeginCompositeTest,FrameDependencyLoop)408 TEST_P(PaintPreviewCompositorBeginCompositeTest, FrameDependencyLoop) {
409 const base::UnguessableToken kRootFrameID = base::UnguessableToken::Create();
410 gfx::Size root_frame_scroll_extent(100, 200);
411 const base::UnguessableToken kSubframe_0_ID =
412 base::UnguessableToken::Create();
413 gfx::Size subframe_0_scroll_extent(50, 75);
414 gfx::Rect subframe_0_clip_rect(10, 20, 30, 40);
415
416 PaintPreviewProto proto;
417 proto.mutable_metadata()->set_url(url_.spec());
418 base::flat_map<base::UnguessableToken, mojom::FrameDataPtr> expected_data;
419 PopulateFrameProto(proto.mutable_root_frame(), kRootFrameID, true,
420 temp_dir_.GetPath().AppendASCII("root.skp"),
421 root_frame_scroll_extent,
422 {{kSubframe_0_ID, subframe_0_clip_rect}}, &expected_data);
423 PopulateFrameProto(proto.add_subframes(), kSubframe_0_ID, false,
424 temp_dir_.GetPath().AppendASCII("subframe_0.skp"),
425 subframe_0_scroll_extent,
426 {{kRootFrameID, subframe_0_clip_rect}}, &expected_data);
427
428 mojom::PaintPreviewBeginCompositeRequestPtr request =
429 mojom::PaintPreviewBeginCompositeRequest::New();
430 request->recording_map = RecordingMapFromPaintPreviewProto(proto);
431 request->proto = ToReadOnlySharedMemory(proto);
432
433 BeginCompositeAndValidate(
434 std::move(request),
435 GetParam() == CompositeType::kSeparateFrame
436 ? mojom::PaintPreviewCompositor::BeginCompositeStatus::kSuccess
437 // In the root frame case, subframes will load until the first cycle.
438 : mojom::PaintPreviewCompositor::BeginCompositeStatus::
439 kPartialSuccess,
440 kRootFrameID, std::move(expected_data));
441 }
442
443 // Ensure that a frame referencing itself works correctly.
TEST_P(PaintPreviewCompositorBeginCompositeTest,SelfReference)444 TEST_P(PaintPreviewCompositorBeginCompositeTest, SelfReference) {
445 const base::UnguessableToken kRootFrameID = base::UnguessableToken::Create();
446 gfx::Size root_frame_scroll_extent(100, 200);
447 gfx::Rect root_frame_clip_rect(10, 20, 30, 40);
448
449 PaintPreviewProto proto;
450 proto.mutable_metadata()->set_url(url_.spec());
451 base::flat_map<base::UnguessableToken, mojom::FrameDataPtr> expected_data;
452 PopulateFrameProto(proto.mutable_root_frame(), kRootFrameID, true,
453 temp_dir_.GetPath().AppendASCII("root.skp"),
454 root_frame_scroll_extent,
455 {{kRootFrameID, root_frame_clip_rect}}, &expected_data);
456
457 mojom::PaintPreviewBeginCompositeRequestPtr request =
458 mojom::PaintPreviewBeginCompositeRequest::New();
459 request->recording_map = RecordingMapFromPaintPreviewProto(proto);
460 request->proto = ToReadOnlySharedMemory(proto);
461
462 BeginCompositeAndValidate(
463 std::move(request),
464 GetParam() == CompositeType::kSeparateFrame
465 ? mojom::PaintPreviewCompositor::BeginCompositeStatus::kSuccess
466 // In the root frame case, a frame cannot be embedded in itself.
467 : mojom::PaintPreviewCompositor::BeginCompositeStatus::
468 kPartialSuccess,
469 kRootFrameID, std::move(expected_data));
470 }
471
TEST_P(PaintPreviewCompositorBeginCompositeTest,InvalidRegionHandling)472 TEST_P(PaintPreviewCompositorBeginCompositeTest, InvalidRegionHandling) {
473 mojom::PaintPreviewBeginCompositeRequestPtr request =
474 mojom::PaintPreviewBeginCompositeRequest::New();
475
476 BeginCompositeAndValidate(
477 std::move(request),
478 mojom::PaintPreviewCompositor::BeginCompositeStatus::
479 kDeserializingFailure,
480 base::UnguessableToken::Create(),
481 base::flat_map<base::UnguessableToken, mojom::FrameDataPtr>());
482 }
483
TEST_P(PaintPreviewCompositorBeginCompositeTest,InvalidProto)484 TEST_P(PaintPreviewCompositorBeginCompositeTest, InvalidProto) {
485 mojom::PaintPreviewBeginCompositeRequestPtr request =
486 mojom::PaintPreviewBeginCompositeRequest::New();
487 std::string test_data = "hello world";
488 auto region = base::WritableSharedMemoryRegion::Create(test_data.size());
489 ASSERT_TRUE(region.IsValid());
490 auto mapping = region.Map();
491 ASSERT_TRUE(mapping.IsValid());
492 memcpy(mapping.memory(), test_data.data(), mapping.size());
493
494 // These calls log errors without a newline (from the proto lib). As a
495 // result, the Android gtest parser fails to parse the test status. To work
496 // around this gobble the log message.
497 {
498 testing::internal::CaptureStdout();
499 request->proto =
500 base::WritableSharedMemoryRegion::ConvertToReadOnly(std::move(region));
501 BeginCompositeAndValidate(
502 std::move(request),
503 mojom::PaintPreviewCompositor::BeginCompositeStatus::
504 kDeserializingFailure,
505 base::UnguessableToken::Create(),
506 base::flat_map<base::UnguessableToken, mojom::FrameDataPtr>());
507 LOG(ERROR) << testing::internal::GetCapturedStdout();
508 }
509 }
510
TEST_P(PaintPreviewCompositorBeginCompositeTest,InvalidRootFrame)511 TEST_P(PaintPreviewCompositorBeginCompositeTest, InvalidRootFrame) {
512 mojom::PaintPreviewBeginCompositeRequestPtr request =
513 mojom::PaintPreviewBeginCompositeRequest::New();
514 const base::UnguessableToken kRootFrameID = base::UnguessableToken::Create();
515 PaintPreviewProto proto;
516 proto.mutable_metadata()->set_url(url_.spec());
517 base::flat_map<base::UnguessableToken, mojom::FrameDataPtr> expected_data;
518 PopulateFrameProto(proto.mutable_root_frame(), kRootFrameID, true,
519 temp_dir_.GetPath().AppendASCII("root.skp"),
520 gfx::Size(1, 1), {}, &expected_data);
521 auto recording_map = RecordingMapFromPaintPreviewProto(proto);
522 recording_map.erase(
523 kRootFrameID); // Missing a SKP for the root file is invalid.
524 request->recording_map = std::move(recording_map);
525 request->proto = ToReadOnlySharedMemory(proto);
526 BeginCompositeAndValidate(
527 std::move(request),
528 mojom::PaintPreviewCompositor::BeginCompositeStatus::kCompositingFailure,
529 base::UnguessableToken::Create(),
530 base::flat_map<base::UnguessableToken, mojom::FrameDataPtr>());
531 }
532
533 // Ensure that scroll offsets are correctly returned in the
534 // |BeginSeparatedFrameComposite| case.
TEST_P(PaintPreviewCompositorBeginCompositeTest,SubframeWithScrollOffsets)535 TEST_P(PaintPreviewCompositorBeginCompositeTest, SubframeWithScrollOffsets) {
536 const base::UnguessableToken kRootFrameID = base::UnguessableToken::Create();
537 gfx::Size root_frame_scroll_extent(100, 200);
538 const base::UnguessableToken kSubframe_0_ID =
539 base::UnguessableToken::Create();
540 gfx::Size subframe_0_scroll_extent(50, 75);
541 gfx::Rect subframe_0_clip_rect(10, 20, 30, 40);
542 gfx::Size subframe_0_scroll_offsets(34, 56);
543
544 PaintPreviewProto proto;
545 proto.mutable_metadata()->set_url(url_.spec());
546 base::flat_map<base::UnguessableToken, mojom::FrameDataPtr> expected_data;
547 PopulateFrameProto(proto.mutable_root_frame(), kRootFrameID, true,
548 temp_dir_.GetPath().AppendASCII("root.skp"),
549 root_frame_scroll_extent,
550 {{kSubframe_0_ID, subframe_0_clip_rect}}, &expected_data);
551 PopulateFrameProto(proto.add_subframes(), kSubframe_0_ID, false,
552 temp_dir_.GetPath().AppendASCII("subframe_0.skp"),
553 subframe_0_scroll_extent, {}, &expected_data,
554 subframe_0_scroll_offsets);
555
556 mojom::PaintPreviewBeginCompositeRequestPtr request =
557 mojom::PaintPreviewBeginCompositeRequest::New();
558 request->recording_map = RecordingMapFromPaintPreviewProto(proto);
559 request->proto = ToReadOnlySharedMemory(proto);
560
561 BeginCompositeAndValidate(
562 std::move(request),
563 mojom::PaintPreviewCompositor::BeginCompositeStatus::kSuccess,
564 kRootFrameID, std::move(expected_data));
565 }
566
567 INSTANTIATE_TEST_SUITE_P(All,
568 PaintPreviewCompositorBeginCompositeTest,
569 testing::Values(CompositeType::kSeparateFrame,
570 CompositeType::kMainFrame),
571 CompositeTypeParamToString);
572
TEST(PaintPreviewCompositorTest,TestComposite)573 TEST(PaintPreviewCompositorTest, TestComposite) {
574 base::test::TaskEnvironment task_environment;
575 base::ScopedTempDir temp_dir;
576 ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
577 PaintPreviewCompositorImpl compositor(mojo::NullReceiver(), nullptr,
578 base::BindOnce([]() {}));
579 GURL url("https://www.chromium.org");
580 const base::UnguessableToken kRootFrameID = base::UnguessableToken::Create();
581 gfx::Size root_frame_scroll_extent(100, 200);
582 PaintPreviewProto proto;
583 proto.mutable_metadata()->set_url(url.spec());
584 base::flat_map<base::UnguessableToken, mojom::FrameDataPtr> expected_data;
585 PopulateFrameProto(proto.mutable_root_frame(), kRootFrameID, true,
586 temp_dir.GetPath().AppendASCII("root.skp"),
587 root_frame_scroll_extent, {}, &expected_data);
588 mojom::PaintPreviewBeginCompositeRequestPtr request =
589 mojom::PaintPreviewBeginCompositeRequest::New();
590 request->recording_map = RecordingMapFromPaintPreviewProto(proto);
591 request->proto = ToReadOnlySharedMemory(proto);
592 compositor.BeginSeparatedFrameComposite(
593 std::move(request),
594 base::BindOnce(
595 &BeginSeparatedFrameCompositeCallbackImpl,
596 mojom::PaintPreviewCompositor::BeginCompositeStatus::kSuccess,
597 kRootFrameID, std::move(expected_data)));
598 float scale_factor = 2;
599 gfx::Rect rect = gfx::ScaleToEnclosingRect(
600 gfx::Rect(root_frame_scroll_extent), scale_factor);
601 SkBitmap bitmap;
602 bitmap.allocPixels(
603 SkImageInfo::MakeN32(rect.width(), rect.height(), kOpaque_SkAlphaType));
604 SkCanvas canvas(bitmap, SkSurfaceProps{});
605 canvas.scale(scale_factor, scale_factor);
606 DrawDummyTestPicture(&canvas, SK_ColorDKGRAY, root_frame_scroll_extent);
607 compositor.BitmapForSeparatedFrame(
608 kRootFrameID, rect, scale_factor,
609 base::BindOnce(&BitmapCallbackImpl,
610 mojom::PaintPreviewCompositor::BitmapStatus::kSuccess,
611 bitmap));
612 task_environment.RunUntilIdle();
613 compositor.BitmapForSeparatedFrame(
614 base::UnguessableToken::Create(), rect, scale_factor,
615 base::BindOnce(&BitmapCallbackImpl,
616 mojom::PaintPreviewCompositor::BitmapStatus::kMissingFrame,
617 bitmap));
618 task_environment.RunUntilIdle();
619 }
620
TEST(PaintPreviewCompositorTest,TestCompositeWithMemoryBuffer)621 TEST(PaintPreviewCompositorTest, TestCompositeWithMemoryBuffer) {
622 base::test::TaskEnvironment task_environment;
623 PaintPreviewCompositorImpl compositor(mojo::NullReceiver(), nullptr,
624 base::BindOnce([]() {}));
625 GURL url("https://www.chromium.org");
626 const base::UnguessableToken kRootFrameID = base::UnguessableToken::Create();
627 gfx::Size root_frame_scroll_extent(100, 200);
628 PaintPreviewProto proto;
629 proto.mutable_metadata()->set_url(url.spec());
630
631 PaintPreviewFrameProto* root_frame = proto.mutable_root_frame();
632 root_frame->set_embedding_token_low(kRootFrameID.GetLowForSerialization());
633 root_frame->set_embedding_token_high(kRootFrameID.GetHighForSerialization());
634 root_frame->set_is_main_frame(true);
635
636 mojo_base::BigBuffer buffer;
637 base::flat_map<base::UnguessableToken, mojom::FrameDataPtr> expected_data;
638 {
639 SkPictureRecorder recorder;
640 SkCanvas* canvas =
641 recorder.beginRecording(ToSkRect(root_frame_scroll_extent));
642 DrawDummyTestPicture(canvas, SK_ColorDKGRAY, root_frame_scroll_extent);
643 sk_sp<SkPicture> pic = recorder.finishRecordingAsPicture();
644
645 PaintPreviewTracker tracker(base::UnguessableToken::Create(), kRootFrameID,
646 /*is_main_frame=*/true);
647 size_t serialized_size = 0;
648 auto result =
649 RecordToBuffer(pic, &tracker, base::nullopt, &serialized_size);
650 ASSERT_TRUE(result.has_value());
651 buffer = std::move(result.value());
652
653 mojom::FrameDataPtr frame_data = mojom::FrameData::New();
654 frame_data->scroll_extents = root_frame_scroll_extent;
655 expected_data.insert({kRootFrameID, std::move(frame_data)});
656 }
657
658 mojom::PaintPreviewBeginCompositeRequestPtr request =
659 mojom::PaintPreviewBeginCompositeRequest::New();
660 request->recording_map.insert(
661 {kRootFrameID, SerializedRecording(std::move(buffer))});
662 request->proto = ToReadOnlySharedMemory(proto);
663
664 compositor.BeginSeparatedFrameComposite(
665 std::move(request),
666 base::BindOnce(
667 &BeginSeparatedFrameCompositeCallbackImpl,
668 mojom::PaintPreviewCompositor::BeginCompositeStatus::kSuccess,
669 kRootFrameID, std::move(expected_data)));
670 float scale_factor = 2;
671 gfx::Rect rect = gfx::ScaleToEnclosingRect(
672 gfx::Rect(root_frame_scroll_extent), scale_factor);
673 SkBitmap bitmap;
674 bitmap.allocPixels(
675 SkImageInfo::MakeN32(rect.width(), rect.height(), kOpaque_SkAlphaType));
676 SkCanvas canvas(bitmap, SkSurfaceProps{});
677 canvas.scale(scale_factor, scale_factor);
678 DrawDummyTestPicture(&canvas, SK_ColorDKGRAY, root_frame_scroll_extent);
679 compositor.BitmapForSeparatedFrame(
680 kRootFrameID, rect, scale_factor,
681 base::BindOnce(&BitmapCallbackImpl,
682 mojom::PaintPreviewCompositor::BitmapStatus::kSuccess,
683 bitmap));
684 task_environment.RunUntilIdle();
685 compositor.BitmapForSeparatedFrame(
686 base::UnguessableToken::Create(), rect, scale_factor,
687 base::BindOnce(&BitmapCallbackImpl,
688 mojom::PaintPreviewCompositor::BitmapStatus::kMissingFrame,
689 bitmap));
690 task_environment.RunUntilIdle();
691 }
692
TEST(PaintPreviewCompositorTest,TestCompositeMainFrameNoDependencies)693 TEST(PaintPreviewCompositorTest, TestCompositeMainFrameNoDependencies) {
694 base::test::TaskEnvironment task_environment;
695 base::ScopedTempDir temp_dir;
696 ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
697 PaintPreviewCompositorImpl compositor(mojo::NullReceiver(), nullptr,
698 base::BindOnce([]() {}));
699 GURL url("https://www.chromium.org");
700 const base::UnguessableToken kRootFrameID = base::UnguessableToken::Create();
701 gfx::Size root_frame_scroll_extent(100, 200);
702 PaintPreviewProto proto;
703 proto.mutable_metadata()->set_url(url.spec());
704 base::flat_map<base::UnguessableToken, mojom::FrameDataPtr> expected_data;
705 PopulateFrameProto(proto.mutable_root_frame(), kRootFrameID, true,
706 temp_dir.GetPath().AppendASCII("root.skp"),
707 root_frame_scroll_extent, {}, &expected_data);
708 mojom::PaintPreviewBeginCompositeRequestPtr request =
709 mojom::PaintPreviewBeginCompositeRequest::New();
710 request->recording_map = RecordingMapFromPaintPreviewProto(proto);
711 request->proto = ToReadOnlySharedMemory(proto);
712 compositor.BeginMainFrameComposite(
713 std::move(request),
714 base::BindOnce(
715 [](mojom::PaintPreviewCompositor::BeginCompositeStatus status) {
716 EXPECT_EQ(
717 status,
718 mojom::PaintPreviewCompositor::BeginCompositeStatus::kSuccess);
719 }));
720 float scale_factor = 2;
721 gfx::Rect rect = gfx::ScaleToEnclosingRect(
722 gfx::Rect(root_frame_scroll_extent), scale_factor);
723 SkBitmap bitmap;
724 bitmap.allocPixels(
725 SkImageInfo::MakeN32(rect.width(), rect.height(), kOpaque_SkAlphaType));
726 SkCanvas canvas(bitmap, SkSurfaceProps{});
727 canvas.scale(scale_factor, scale_factor);
728 DrawDummyTestPicture(&canvas, SK_ColorDKGRAY, root_frame_scroll_extent);
729 compositor.BitmapForMainFrame(
730 rect, scale_factor,
731 base::BindOnce(&BitmapCallbackImpl,
732 mojom::PaintPreviewCompositor::BitmapStatus::kSuccess,
733 bitmap));
734 task_environment.RunUntilIdle();
735 }
736
TEST(PaintPreviewCompositorTest,TestCompositeMainFrameOneDependency)737 TEST(PaintPreviewCompositorTest, TestCompositeMainFrameOneDependency) {
738 base::test::TaskEnvironment task_environment;
739 base::ScopedTempDir temp_dir;
740 ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
741 PaintPreviewCompositorImpl compositor(mojo::NullReceiver(), nullptr,
742 base::BindOnce([]() {}));
743 GURL url("https://www.chromium.org");
744 const base::UnguessableToken kRootFrameID = base::UnguessableToken::Create();
745 gfx::Size root_frame_scroll_extent(100, 200);
746 const base::UnguessableToken kSubframe_0_ID =
747 base::UnguessableToken::Create();
748 gfx::Size subframe_0_scroll_extent(50, 75);
749 gfx::Rect subframe_0_clip_rect(10, 20, 30, 40);
750
751 PaintPreviewProto proto;
752 proto.mutable_metadata()->set_url(url.spec());
753 base::flat_map<base::UnguessableToken, mojom::FrameDataPtr> expected_data;
754 PopulateFrameProto(proto.mutable_root_frame(), kRootFrameID, true,
755 temp_dir.GetPath().AppendASCII("root.skp"),
756 root_frame_scroll_extent,
757 {{kSubframe_0_ID, subframe_0_clip_rect}}, &expected_data);
758 PopulateFrameProto(proto.add_subframes(), kSubframe_0_ID, false,
759 temp_dir.GetPath().AppendASCII("subframe_0.skp"),
760 subframe_0_scroll_extent, {}, &expected_data, gfx::Size(),
761 SK_ColorLTGRAY);
762
763 mojom::PaintPreviewBeginCompositeRequestPtr request =
764 mojom::PaintPreviewBeginCompositeRequest::New();
765 request->recording_map = RecordingMapFromPaintPreviewProto(proto);
766 request->proto = ToReadOnlySharedMemory(proto);
767 compositor.BeginMainFrameComposite(
768 std::move(request),
769 base::BindOnce(
770 [](mojom::PaintPreviewCompositor::BeginCompositeStatus status) {
771 EXPECT_EQ(
772 status,
773 mojom::PaintPreviewCompositor::BeginCompositeStatus::kSuccess);
774 }));
775 float scale_factor = 1;
776 gfx::Rect rect = gfx::ScaleToEnclosingRect(
777 gfx::Rect(root_frame_scroll_extent), scale_factor);
778 SkBitmap bitmap;
779 bitmap.allocPixels(
780 SkImageInfo::MakeN32(rect.width(), rect.height(), kOpaque_SkAlphaType));
781 SkCanvas canvas(bitmap, SkSurfaceProps{});
782 canvas.scale(scale_factor, scale_factor);
783 DrawDummyTestPicture(&canvas, SK_ColorDKGRAY, root_frame_scroll_extent);
784 // Draw the subframe where we embedded it while populating the proto.
785 DrawDummyTestPicture(&canvas, SK_ColorLTGRAY, subframe_0_scroll_extent,
786 subframe_0_clip_rect);
787 compositor.BitmapForMainFrame(
788 rect, scale_factor,
789 base::BindOnce(&BitmapCallbackImpl,
790 mojom::PaintPreviewCompositor::BitmapStatus::kSuccess,
791 bitmap));
792 task_environment.RunUntilIdle();
793 }
794
TEST(PaintPreviewCompositorTest,TestCompositeMainFrameOneDependencyScrolled)795 TEST(PaintPreviewCompositorTest, TestCompositeMainFrameOneDependencyScrolled) {
796 base::test::TaskEnvironment task_environment;
797 base::ScopedTempDir temp_dir;
798 ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
799 PaintPreviewCompositorImpl compositor(mojo::NullReceiver(), nullptr,
800 base::BindOnce([]() {}));
801 GURL url("https://www.chromium.org");
802 const base::UnguessableToken kRootFrameID = base::UnguessableToken::Create();
803 gfx::Size root_frame_scroll_extent(100, 200);
804 const base::UnguessableToken kSubframe_0_ID =
805 base::UnguessableToken::Create();
806 gfx::Size subframe_0_scroll_extent(50, 75);
807 gfx::Rect subframe_0_clip_rect(10, 20, 30, 40);
808 gfx::Size subframe_0_scroll_offsets(0, 5);
809
810 PaintPreviewProto proto;
811 proto.mutable_metadata()->set_url(url.spec());
812 base::flat_map<base::UnguessableToken, mojom::FrameDataPtr> expected_data;
813 PopulateFrameProto(proto.mutable_root_frame(), kRootFrameID, true,
814 temp_dir.GetPath().AppendASCII("root.skp"),
815 root_frame_scroll_extent,
816 {{kSubframe_0_ID, subframe_0_clip_rect}}, &expected_data);
817 PopulateFrameProto(proto.add_subframes(), kSubframe_0_ID, false,
818 temp_dir.GetPath().AppendASCII("subframe_0.skp"),
819 subframe_0_scroll_extent, {}, &expected_data,
820 subframe_0_scroll_offsets, SK_ColorLTGRAY);
821
822 mojom::PaintPreviewBeginCompositeRequestPtr request =
823 mojom::PaintPreviewBeginCompositeRequest::New();
824 request->recording_map = RecordingMapFromPaintPreviewProto(proto);
825 request->proto = ToReadOnlySharedMemory(proto);
826 compositor.BeginMainFrameComposite(
827 std::move(request),
828 base::BindOnce(
829 [](mojom::PaintPreviewCompositor::BeginCompositeStatus status) {
830 EXPECT_EQ(
831 status,
832 mojom::PaintPreviewCompositor::BeginCompositeStatus::kSuccess);
833 }));
834 float scale_factor = 1;
835 gfx::Rect rect = gfx::ScaleToEnclosingRect(
836 gfx::Rect(root_frame_scroll_extent), scale_factor);
837 SkBitmap bitmap;
838 bitmap.allocPixels(
839 SkImageInfo::MakeN32(rect.width(), rect.height(), kOpaque_SkAlphaType));
840 SkCanvas canvas(bitmap, SkSurfaceProps{});
841 canvas.scale(scale_factor, scale_factor);
842 DrawDummyTestPicture(&canvas, SK_ColorDKGRAY, root_frame_scroll_extent);
843 // Draw the subframe where we embedded it while populating the proto.
844 DrawDummyTestPicture(&canvas, SK_ColorLTGRAY, subframe_0_scroll_extent,
845 subframe_0_clip_rect, subframe_0_scroll_offsets);
846 compositor.BitmapForMainFrame(
847 rect, scale_factor,
848 base::BindOnce(&BitmapCallbackImpl,
849 mojom::PaintPreviewCompositor::BitmapStatus::kSuccess,
850 bitmap));
851 task_environment.RunUntilIdle();
852 }
853
TEST(PaintPreviewCompositorTest,TestCompositeMainFrameOneDependencyWithRootFrameScrolled)854 TEST(PaintPreviewCompositorTest,
855 TestCompositeMainFrameOneDependencyWithRootFrameScrolled) {
856 base::test::TaskEnvironment task_environment;
857 base::ScopedTempDir temp_dir;
858 ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
859 PaintPreviewCompositorImpl compositor(mojo::NullReceiver(), nullptr,
860 base::BindOnce([]() {}));
861 GURL url("https://www.chromium.org");
862 const base::UnguessableToken kRootFrameID = base::UnguessableToken::Create();
863 gfx::Size root_frame_scroll_extent(110, 215);
864 gfx::Size root_frame_scroll_offsets(10, 15);
865 gfx::Rect root_frame_clip_rect(10, 15, 100, 200);
866 const base::UnguessableToken kSubframe_0_ID =
867 base::UnguessableToken::Create();
868 gfx::Size subframe_0_scroll_extent(50, 75);
869 gfx::Rect subframe_0_clip_rect(10, 20, 30, 40);
870
871 PaintPreviewProto proto;
872 proto.mutable_metadata()->set_url(url.spec());
873 base::flat_map<base::UnguessableToken, mojom::FrameDataPtr> expected_data;
874 PopulateFrameProto(proto.mutable_root_frame(), kRootFrameID, true,
875 temp_dir.GetPath().AppendASCII("root.skp"),
876 root_frame_scroll_extent,
877 {{kSubframe_0_ID, subframe_0_clip_rect}}, &expected_data,
878 root_frame_scroll_offsets);
879 PopulateFrameProto(proto.add_subframes(), kSubframe_0_ID, false,
880 temp_dir.GetPath().AppendASCII("subframe_0.skp"),
881 subframe_0_scroll_extent, {}, &expected_data, gfx::Size(),
882 SK_ColorLTGRAY);
883
884 mojom::PaintPreviewBeginCompositeRequestPtr request =
885 mojom::PaintPreviewBeginCompositeRequest::New();
886 request->recording_map = RecordingMapFromPaintPreviewProto(proto);
887 request->proto = ToReadOnlySharedMemory(proto);
888 compositor.BeginMainFrameComposite(
889 std::move(request),
890 base::BindOnce(
891 [](mojom::PaintPreviewCompositor::BeginCompositeStatus status) {
892 EXPECT_EQ(
893 status,
894 mojom::PaintPreviewCompositor::BeginCompositeStatus::kSuccess);
895 }));
896 float scale_factor = 1;
897 gfx::Rect rect =
898 gfx::ScaleToEnclosingRect(root_frame_clip_rect, scale_factor);
899 SkBitmap bitmap;
900 bitmap.allocPixels(
901 SkImageInfo::MakeN32(rect.width(), rect.height(), kOpaque_SkAlphaType));
902 SkCanvas canvas(bitmap, SkSurfaceProps{});
903 canvas.scale(scale_factor, scale_factor);
904 // Offset the canvas to simulate the root frame being scrolled.
905 canvas.translate(-root_frame_clip_rect.x(), -root_frame_clip_rect.y());
906 DrawDummyTestPicture(&canvas, SK_ColorDKGRAY, root_frame_scroll_extent,
907 root_frame_clip_rect, root_frame_scroll_offsets);
908 DrawDummyTestPicture(&canvas, SK_ColorLTGRAY, subframe_0_scroll_extent,
909 subframe_0_clip_rect);
910 compositor.BitmapForMainFrame(
911 rect, scale_factor,
912 base::BindOnce(&BitmapCallbackImpl,
913 mojom::PaintPreviewCompositor::BitmapStatus::kSuccess,
914 bitmap));
915 task_environment.RunUntilIdle();
916 }
917
918 } // namespace paint_preview
919