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 "base/files/file.h"
8 #include "base/files/file_path.h"
9 #include "base/files/scoped_temp_dir.h"
10 #include "base/test/scoped_feature_list.h"
11 #include "base/threading/thread_restrictions.h"
12 #include "build/build_config.h"
13 #include "components/paint_preview/common/file_stream.h"
14 #include "components/paint_preview/common/mojom/paint_preview_recorder.mojom.h"
15 #include "content/public/renderer/render_frame.h"
16 #include "content/public/renderer/render_view.h"
17 #include "content/public/test/render_view_test.h"
18 #include "content/public/test/test_utils.h"
19 #include "testing/gtest/include/gtest/gtest.h"
20 #include "third_party/blink/public/web/web_local_frame.h"
21 #include "third_party/skia/include/core/SkPicture.h"
22 #include "ui/native_theme/native_theme_features.h"
23
24 namespace paint_preview {
25
26 namespace {
27
28 // Checks that |status| == |expected_status| and loads |response| into
29 // |out_response| if |expected_status| == kOk. If |expected_status| != kOk
30 // |out_response| can safely be nullptr.
OnCaptureFinished(mojom::PaintPreviewStatus expected_status,mojom::PaintPreviewCaptureResponsePtr * out_response,mojom::PaintPreviewStatus status,mojom::PaintPreviewCaptureResponsePtr response)31 void OnCaptureFinished(mojom::PaintPreviewStatus expected_status,
32 mojom::PaintPreviewCaptureResponsePtr* out_response,
33 mojom::PaintPreviewStatus status,
34 mojom::PaintPreviewCaptureResponsePtr response) {
35 EXPECT_EQ(status, expected_status);
36 if (expected_status == mojom::PaintPreviewStatus::kOk)
37 *out_response = std::move(response);
38 }
39
40 } // namespace
41
42 class PaintPreviewRecorderRenderViewTest : public content::RenderViewTest {
43 public:
PaintPreviewRecorderRenderViewTest()44 PaintPreviewRecorderRenderViewTest() {}
~PaintPreviewRecorderRenderViewTest()45 ~PaintPreviewRecorderRenderViewTest() override {}
46
SetUp()47 void SetUp() override {
48 ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
49
50 // TODO(crbug/1022398): This is required to bypass a seemingly unrelated
51 // DCHECK for |use_overlay_scrollbars_| in NativeThemeAura on ChromeOS when
52 // painting scrollbars when first calling LoadHTML().
53 feature_list_.InitAndDisableFeature(features::kOverlayScrollbar);
54
55 RenderViewTest::SetUp();
56 }
57
GetFrame()58 content::RenderFrame* GetFrame() { return view_->GetMainRenderFrame(); }
59
MakeTestFilePath(const std::string & filename)60 base::FilePath MakeTestFilePath(const std::string& filename) {
61 return temp_dir_.GetPath().AppendASCII(filename);
62 }
63
RunCapture(content::RenderFrame * frame,mojom::PaintPreviewCaptureResponsePtr * out_response,bool is_main_frame=true,gfx::Rect clip_rect=gfx::Rect ())64 base::FilePath RunCapture(content::RenderFrame* frame,
65 mojom::PaintPreviewCaptureResponsePtr* out_response,
66 bool is_main_frame = true,
67 gfx::Rect clip_rect = gfx::Rect()) {
68 base::FilePath skp_path = MakeTestFilePath("test.skp");
69
70 mojom::PaintPreviewCaptureParamsPtr params =
71 mojom::PaintPreviewCaptureParams::New();
72 auto token = base::UnguessableToken::Create();
73 params->guid = token;
74 params->clip_rect = clip_rect;
75 params->clip_rect_is_hint = false;
76 params->is_main_frame = is_main_frame;
77 params->capture_links = true;
78 base::File skp_file(
79 skp_path, base::File::FLAG_CREATE_ALWAYS | base::File::FLAG_WRITE);
80 params->file = std::move(skp_file);
81
82 PaintPreviewRecorderImpl paint_preview_recorder(frame);
83 paint_preview_recorder.CapturePaintPreview(
84 std::move(params),
85 base::BindOnce(&OnCaptureFinished, mojom::PaintPreviewStatus::kOk,
86 out_response));
87 content::RunAllTasksUntilIdle();
88 return skp_path;
89 }
90
91 private:
92 base::ScopedTempDir temp_dir_;
93 base::test::ScopedFeatureList feature_list_;
94 };
95
TEST_F(PaintPreviewRecorderRenderViewTest,TestCaptureMainFrameAndClipping)96 TEST_F(PaintPreviewRecorderRenderViewTest, TestCaptureMainFrameAndClipping) {
97 LoadHTML(
98 "<!doctype html>"
99 "<body>"
100 " <div style='width: 600px; height: 80vh; "
101 " background-color: #ff0000'> </div>"
102 " <a style='display:inline-block' href='http://www.google.com'>Foo</a>"
103 " <div style='width: 100px; height: 600px; "
104 " background-color: #000000'> </div>"
105 " <div style='overflow: hidden; width: 100px; height: 100px;"
106 " background: orange;'>"
107 " <div style='width: 500px; height: 500px;"
108 " background: yellow;'></div>"
109 " </div>"
110 "</body>");
111
112 auto out_response = mojom::PaintPreviewCaptureResponse::New();
113 content::RenderFrame* frame = GetFrame();
114 base::FilePath skp_path = RunCapture(frame, &out_response);
115
116 EXPECT_TRUE(out_response->embedding_token.has_value());
117 EXPECT_EQ(frame->GetWebFrame()->GetEmbeddingToken(),
118 out_response->embedding_token.value());
119 EXPECT_EQ(out_response->content_id_to_embedding_token.size(), 0U);
120
121 EXPECT_EQ(out_response->links.size(), 1U);
122 EXPECT_EQ(out_response->links[0]->url, GURL("http://www.google.com/"));
123 // Relaxed checks on dimensions and no checks on positions. This is not
124 // intended to test the rendering behavior of the page only that a link
125 // was captured and has a bounding box.
126 EXPECT_GT(out_response->links[0]->rect.width(), 0);
127 EXPECT_GT(out_response->links[0]->rect.height(), 0);
128
129 sk_sp<SkPicture> pic;
130 {
131 base::ScopedAllowBlockingForTesting scope;
132 FileRStream rstream(base::File(
133 skp_path, base::File::FLAG_OPEN_ALWAYS | base::File::FLAG_READ));
134 pic = SkPicture::MakeFromStream(&rstream, nullptr);
135 }
136 // The min page height is the sum of the three top level divs of 800. The min
137 // width is that of the widest div at 600.
138 EXPECT_GE(pic->cullRect().height(), 800);
139 EXPECT_GE(pic->cullRect().width(), 600);
140 SkBitmap bitmap;
141 ASSERT_TRUE(bitmap.tryAllocN32Pixels(pic->cullRect().width(),
142 pic->cullRect().height()));
143 SkCanvas canvas(bitmap, SkSurfaceProps{});
144 canvas.drawPicture(pic);
145 // This should be inside the top right corner of the first top level div.
146 // Success means there was no horizontal clipping as this region is red,
147 // matching the div.
148 EXPECT_EQ(bitmap.getColor(600, 50), 0xFFFF0000U);
149 // This should be inside the bottom of the second top level div. Success means
150 // there was no vertical clipping as this region is black matching the div. If
151 // the yellow div within the orange div overflowed then this would be yellow
152 // and fail.
153 EXPECT_EQ(bitmap.getColor(50, pic->cullRect().height() - 150), 0xFF000000U);
154 // This should be for the white background in the bottom right. This checks
155 // that the background is not clipped.
156 EXPECT_EQ(bitmap.getColor(pic->cullRect().width() - 50,
157 pic->cullRect().height() - 50),
158 0xFFFFFFFFU);
159 }
160
TEST_F(PaintPreviewRecorderRenderViewTest,TestCaptureMainFrameWithScroll)161 TEST_F(PaintPreviewRecorderRenderViewTest, TestCaptureMainFrameWithScroll) {
162 LoadHTML(
163 "<!doctype html>"
164 "<body>"
165 " <div style='width: 600px; height: 80vh; "
166 " background-color: #ff0000'> </div>"
167 " <div style='width: 600px; height: 1200px; "
168 " background-color: #00ff00'> </div>"
169 "</body>");
170
171 // Scroll to bottom of page to ensure scroll position has no effect on
172 // capture.
173 ExecuteJavaScriptForTests("window.scrollTo(0,document.body.scrollHeight);");
174
175 auto out_response = mojom::PaintPreviewCaptureResponse::New();
176 content::RenderFrame* frame = GetFrame();
177 base::FilePath skp_path = RunCapture(frame, &out_response);
178
179 EXPECT_TRUE(out_response->embedding_token.has_value());
180 EXPECT_EQ(frame->GetWebFrame()->GetEmbeddingToken(),
181 out_response->embedding_token.value());
182 EXPECT_EQ(out_response->content_id_to_embedding_token.size(), 0U);
183
184 // Relaxed checks on dimensions and no checks on positions. This is not
185 // intended to intensively test the rendering behavior of the page.
186 sk_sp<SkPicture> pic;
187 {
188 base::ScopedAllowBlockingForTesting scope;
189 FileRStream rstream(base::File(
190 skp_path, base::File::FLAG_OPEN_ALWAYS | base::File::FLAG_READ));
191 pic = SkPicture::MakeFromStream(&rstream, nullptr);
192 }
193 SkBitmap bitmap;
194 ASSERT_TRUE(bitmap.tryAllocN32Pixels(pic->cullRect().width(),
195 pic->cullRect().height()));
196 SkCanvas canvas(bitmap, SkSurfaceProps{});
197 canvas.drawPicture(pic);
198 // This should be inside the top right corner of the top div. Success means
199 // there was no horizontal or vertical clipping as this region is red,
200 // matching the div.
201 EXPECT_EQ(bitmap.getColor(600, 50), 0xFFFF0000U);
202 // This should be inside the bottom of the bottom div. Success means there was
203 // no vertical clipping as this region is blue matching the div.
204 EXPECT_EQ(bitmap.getColor(50, pic->cullRect().height() - 100), 0xFF00FF00U);
205 }
206
TEST_F(PaintPreviewRecorderRenderViewTest,TestCaptureFragment)207 TEST_F(PaintPreviewRecorderRenderViewTest, TestCaptureFragment) {
208 // Use position absolute position to check that the captured link dimensions
209 // match what is specified.
210 LoadHTML(
211 "<!doctype html>"
212 "<body style='min-height:1000px;'>"
213 " <a style='position: absolute; left: -15px; top: 0px; width: 40px; "
214 " height: 30px;' href='#fragment'>Foo</a>"
215 " <h1 id='fragment'>I'm a fragment</h1>"
216 "</body>");
217 auto out_response = mojom::PaintPreviewCaptureResponse::New();
218 content::RenderFrame* frame = GetFrame();
219
220 RunCapture(frame, &out_response);
221
222 EXPECT_TRUE(out_response->embedding_token.has_value());
223 EXPECT_EQ(frame->GetWebFrame()->GetEmbeddingToken(),
224 out_response->embedding_token.value());
225 EXPECT_EQ(out_response->content_id_to_embedding_token.size(), 0U);
226
227 EXPECT_EQ(out_response->links.size(), 1U);
228 EXPECT_EQ(out_response->links[0]->url, GURL("fragment"));
229 EXPECT_EQ(out_response->links[0]->rect.x(), -15);
230 EXPECT_EQ(out_response->links[0]->rect.y(), 0);
231 EXPECT_EQ(out_response->links[0]->rect.width(), 40);
232 EXPECT_EQ(out_response->links[0]->rect.height(), 30);
233 }
234
TEST_F(PaintPreviewRecorderRenderViewTest,TestCaptureInvalidFile)235 TEST_F(PaintPreviewRecorderRenderViewTest, TestCaptureInvalidFile) {
236 LoadHTML("<body></body>");
237
238 mojom::PaintPreviewCaptureParamsPtr params =
239 mojom::PaintPreviewCaptureParams::New();
240 auto token = base::UnguessableToken::Create();
241 params->guid = token;
242 params->clip_rect = gfx::Rect();
243 params->is_main_frame = true;
244 params->capture_links = true;
245 params->max_capture_size = 0;
246 base::File skp_file; // Invalid file.
247 params->file = std::move(skp_file);
248
249 content::RenderFrame* frame = GetFrame();
250 PaintPreviewRecorderImpl paint_preview_recorder(frame);
251 paint_preview_recorder.CapturePaintPreview(
252 std::move(params),
253 base::BindOnce(&OnCaptureFinished,
254 mojom::PaintPreviewStatus::kCaptureFailed, nullptr));
255 content::RunAllTasksUntilIdle();
256 }
257
TEST_F(PaintPreviewRecorderRenderViewTest,TestCaptureMainFrameAndLocalFrame)258 TEST_F(PaintPreviewRecorderRenderViewTest, TestCaptureMainFrameAndLocalFrame) {
259 LoadHTML(
260 "<!doctype html>"
261 "<body style='min-height:1000px;'>"
262 " <iframe style='width: 500px, height: 500px'"
263 " srcdoc=\"<div style='width: 100px; height: 100px;"
264 " background-color: #000000'> </div>\"></iframe>"
265 "</body>");
266 auto out_response = mojom::PaintPreviewCaptureResponse::New();
267 content::RenderFrame* frame = GetFrame();
268
269 RunCapture(frame, &out_response);
270
271 EXPECT_TRUE(out_response->embedding_token.has_value());
272 EXPECT_EQ(frame->GetWebFrame()->GetEmbeddingToken(),
273 out_response->embedding_token.value());
274 EXPECT_EQ(out_response->content_id_to_embedding_token.size(), 0U);
275 }
276
TEST_F(PaintPreviewRecorderRenderViewTest,TestCaptureLocalFrame)277 TEST_F(PaintPreviewRecorderRenderViewTest, TestCaptureLocalFrame) {
278 LoadHTML(
279 "<!doctype html>"
280 "<body style='min-height:1000px;'>"
281 " <iframe style='width: 500px, height: 500px'"
282 " srcdoc=\"<div style='width: 100px; height: 100px;"
283 " background-color: #000000'> </div>\"></iframe>"
284 "</body>");
285 auto out_response = mojom::PaintPreviewCaptureResponse::New();
286 auto* child_frame = content::RenderFrame::FromWebFrame(
287 GetFrame()->GetWebFrame()->FirstChild()->ToWebLocalFrame());
288 ASSERT_TRUE(child_frame);
289
290 RunCapture(child_frame, &out_response, false);
291
292 EXPECT_TRUE(out_response->embedding_token.has_value());
293 EXPECT_EQ(out_response->content_id_to_embedding_token.size(), 0U);
294 }
295
TEST_F(PaintPreviewRecorderRenderViewTest,TestCaptureCustomClipRect)296 TEST_F(PaintPreviewRecorderRenderViewTest, TestCaptureCustomClipRect) {
297 LoadHTML(
298 "<!doctype html>"
299 "<body>"
300 " <div style='width: 600px; height: 600px; background-color: #0000ff;'>"
301 " <div style='width: 300px; height: 300px; background-color: "
302 " #ffff00; position: relative; left: 150px; top: 150px'></div>"
303 " </div>"
304 " <a style='position: absolute; left: 160px; top: 170px; width: 40px; "
305 " height: 30px;' href='http://www.example.com'>Foo</a>"
306 "</body>");
307
308 auto out_response = mojom::PaintPreviewCaptureResponse::New();
309 content::RenderFrame* frame = GetFrame();
310 gfx::Rect clip_rect = gfx::Rect(150, 150, 300, 300);
311 base::FilePath skp_path = RunCapture(frame, &out_response, true, clip_rect);
312
313 EXPECT_TRUE(out_response->embedding_token.has_value());
314 EXPECT_EQ(frame->GetWebFrame()->GetEmbeddingToken(),
315 out_response->embedding_token.value());
316 EXPECT_EQ(out_response->content_id_to_embedding_token.size(), 0U);
317
318 sk_sp<SkPicture> pic;
319 {
320 base::ScopedAllowBlockingForTesting scope;
321 FileRStream rstream(base::File(
322 skp_path, base::File::FLAG_OPEN_ALWAYS | base::File::FLAG_READ));
323 pic = SkPicture::MakeFromStream(&rstream, nullptr);
324 }
325 EXPECT_EQ(pic->cullRect().height(), 300);
326 EXPECT_EQ(pic->cullRect().width(), 300);
327 SkBitmap bitmap;
328 ASSERT_TRUE(bitmap.tryAllocN32Pixels(pic->cullRect().width(),
329 pic->cullRect().height()));
330 SkCanvas canvas(bitmap);
331 canvas.drawPicture(pic);
332 EXPECT_EQ(bitmap.getColor(100, 100), 0xFFFFFF00U);
333
334 ASSERT_EQ(out_response->links.size(), 1U);
335 EXPECT_EQ(out_response->links[0]->url, GURL("http://www.example.com"));
336 EXPECT_EQ(out_response->links[0]->rect.x(), 10);
337 EXPECT_EQ(out_response->links[0]->rect.y(), 20);
338 EXPECT_EQ(out_response->links[0]->rect.width(), 40);
339 EXPECT_EQ(out_response->links[0]->rect.height(), 30);
340 }
341
TEST_F(PaintPreviewRecorderRenderViewTest,CaptureWithTranslate)342 TEST_F(PaintPreviewRecorderRenderViewTest, CaptureWithTranslate) {
343 // URLs should be annotated correctly when a CSS transform is applied.
344 LoadHTML(
345 R"(
346 <!doctype html>
347 <body>
348 <div style="display: inline-block;
349 padding: 16px;
350 font-size: 16px;">
351 <div style="padding: 16px;
352 transform: translate(10px, 20px);
353 margin-bottom: 30px;">
354 <div>
355 <a href="http://www.example.com" style="display: block;
356 width: 70px;
357 height: 20px;">
358 <div>Example</div>
359 </a>
360 </div>
361 </div>
362 </div>
363 </body>)");
364 auto out_response = mojom::PaintPreviewCaptureResponse::New();
365 content::RenderFrame* frame = GetFrame();
366
367 RunCapture(frame, &out_response);
368
369 EXPECT_TRUE(out_response->embedding_token.has_value());
370 EXPECT_EQ(frame->GetWebFrame()->GetEmbeddingToken(),
371 out_response->embedding_token.value());
372 EXPECT_EQ(out_response->content_id_to_embedding_token.size(), 0U);
373
374 ASSERT_EQ(out_response->links.size(), 1U);
375 EXPECT_EQ(out_response->links[0]->url, GURL("http://www.example.com"));
376 EXPECT_NEAR(out_response->links[0]->rect.x(), 50, 3);
377 EXPECT_NEAR(out_response->links[0]->rect.y(), 60, 3);
378 EXPECT_NEAR(out_response->links[0]->rect.width(), 70, 3);
379 EXPECT_NEAR(out_response->links[0]->rect.height(), 20, 3);
380 }
381
TEST_F(PaintPreviewRecorderRenderViewTest,CaptureWithTranslateThenRotate)382 TEST_F(PaintPreviewRecorderRenderViewTest, CaptureWithTranslateThenRotate) {
383 // URLs should be annotated correctly when a CSS transform is applied.
384 LoadHTML(
385 R"(
386 <!doctype html>
387 <body>
388 <div style="display: inline-block;
389 padding: 16px;
390 font-size: 16px;">
391 <div style="padding: 16px;
392 transform: translate(100px, 0) rotate(45deg);
393 margin-bottom: 30px;">
394 <div>
395 <a href="http://www.example.com" style="display: block;
396 width: 70px;
397 height: 20px;">
398 <div>Example</div>
399 </a>
400 </div>
401 </div>
402 </div>
403 </body>)");
404 auto out_response = mojom::PaintPreviewCaptureResponse::New();
405 content::RenderFrame* frame = GetFrame();
406
407 RunCapture(frame, &out_response);
408
409 EXPECT_TRUE(out_response->embedding_token.has_value());
410 EXPECT_EQ(frame->GetWebFrame()->GetEmbeddingToken(),
411 out_response->embedding_token.value());
412 EXPECT_EQ(out_response->content_id_to_embedding_token.size(), 0U);
413
414 ASSERT_EQ(out_response->links.size(), 1U);
415 EXPECT_EQ(out_response->links[0]->url, GURL("http://www.example.com"));
416 EXPECT_NEAR(out_response->links[0]->rect.x(), 141, 5);
417 EXPECT_NEAR(out_response->links[0]->rect.y(), 18, 5);
418 #if !defined(OS_ANDROID)
419 EXPECT_NEAR(out_response->links[0]->rect.width(), 58, 10);
420 EXPECT_NEAR(out_response->links[0]->rect.height(), 58, 10);
421 #endif
422 }
423
TEST_F(PaintPreviewRecorderRenderViewTest,CaptureWithRotateThenTranslate)424 TEST_F(PaintPreviewRecorderRenderViewTest, CaptureWithRotateThenTranslate) {
425 // URLs should be annotated correctly when a CSS transform is applied.
426 LoadHTML(
427 R"(
428 <!doctype html>
429 <body>
430 <div style="display: inline-block;
431 padding: 16px;
432 font-size: 16px;">
433 <div style="padding: 16px;
434 transform: rotate(45deg) translate(100px, 0);
435 margin-bottom: 30px;">
436 <div>
437 <a href="http://www.example.com" style="display: block;
438 width: 70px;
439 height: 20px;">
440 <div>Example</div>
441 </a>
442 </div>
443 </div>
444 </div>
445 </body>)");
446 auto out_response = mojom::PaintPreviewCaptureResponse::New();
447 content::RenderFrame* frame = GetFrame();
448
449 RunCapture(frame, &out_response);
450
451 EXPECT_TRUE(out_response->embedding_token.has_value());
452 EXPECT_EQ(frame->GetWebFrame()->GetEmbeddingToken(),
453 out_response->embedding_token.value());
454 EXPECT_EQ(out_response->content_id_to_embedding_token.size(), 0U);
455
456 ASSERT_EQ(out_response->links.size(), 1U);
457 EXPECT_EQ(out_response->links[0]->url, GURL("http://www.example.com"));
458 EXPECT_NEAR(out_response->links[0]->rect.x(), 111, 5);
459 EXPECT_NEAR(out_response->links[0]->rect.y(), 88, 5);
460 #if !defined(OS_ANDROID)
461 EXPECT_NEAR(out_response->links[0]->rect.width(), 58, 10);
462 EXPECT_NEAR(out_response->links[0]->rect.height(), 58, 10);
463 #endif
464 }
465
TEST_F(PaintPreviewRecorderRenderViewTest,CaptureWithScale)466 TEST_F(PaintPreviewRecorderRenderViewTest, CaptureWithScale) {
467 // URLs should be annotated correctly when a CSS transform is applied.
468 LoadHTML(
469 R"(
470 <!doctype html>
471 <body>
472 <div style="display: inline-block;
473 padding: 16px;
474 font-size: 16px;">
475 <div style="padding: 16px;
476 transform: scale(2, 1);
477 margin-bottom: 30px;">
478 <div>
479 <a href="http://www.example.com" style="display: block;
480 width: 70px;
481 height: 20px;">
482 <div>Example</div>
483 </a>
484 </div>
485 </div>
486 </div>
487 </body>)");
488 auto out_response = mojom::PaintPreviewCaptureResponse::New();
489 content::RenderFrame* frame = GetFrame();
490
491 RunCapture(frame, &out_response);
492
493 EXPECT_TRUE(out_response->embedding_token.has_value());
494 EXPECT_EQ(frame->GetWebFrame()->GetEmbeddingToken(),
495 out_response->embedding_token.value());
496 EXPECT_EQ(out_response->content_id_to_embedding_token.size(), 0U);
497
498 ASSERT_EQ(out_response->links.size(), 1U);
499 EXPECT_EQ(out_response->links[0]->url, GURL("http://www.example.com"));
500 EXPECT_NEAR(out_response->links[0]->rect.x(), 5, 3);
501 EXPECT_NEAR(out_response->links[0]->rect.y(), 40, 3);
502 EXPECT_NEAR(out_response->links[0]->rect.width(), 140, 3);
503 EXPECT_NEAR(out_response->links[0]->rect.height(), 20, 3);
504 }
505
TEST_F(PaintPreviewRecorderRenderViewTest,CaptureSaveRestore)506 TEST_F(PaintPreviewRecorderRenderViewTest, CaptureSaveRestore) {
507 // URLs should be annotated correctly when a CSS transform is applied.
508 LoadHTML(
509 R"(
510 <!doctype html>
511 <body>
512 <div style="display: inline-block;
513 padding: 16px;
514 font-size: 16px;">
515 <div style="padding: 16px;
516 transform: translate(20px, 0);
517 margin-bottom: 30px;">
518 <div>
519 <a href="http://www.example.com" style="display: block;
520 width: 70px;
521 height: 20px;">
522 <div>Example</div>
523 </a>
524 </div>
525 </div>
526 <div style="padding: 16px;
527 transform: none;
528 margin-bottom: 30px;">
529 <div>
530 <a href="http://www.chromium.org" style="display: block;
531 width: 80px;
532 height: 20px;">
533 <div>Chromium</div>
534 </a>
535 </div>
536 </div>
537 </div>
538 </body>)");
539 auto out_response = mojom::PaintPreviewCaptureResponse::New();
540 content::RenderFrame* frame = GetFrame();
541
542 RunCapture(frame, &out_response);
543
544 EXPECT_TRUE(out_response->embedding_token.has_value());
545 EXPECT_EQ(frame->GetWebFrame()->GetEmbeddingToken(),
546 out_response->embedding_token.value());
547 EXPECT_EQ(out_response->content_id_to_embedding_token.size(), 0U);
548
549 ASSERT_EQ(out_response->links.size(), 2U);
550 EXPECT_EQ(out_response->links[0]->url, GURL("http://www.chromium.org"));
551 EXPECT_NEAR(out_response->links[0]->rect.x(), 40, 3);
552 EXPECT_NEAR(out_response->links[0]->rect.y(), 122, 3);
553 EXPECT_NEAR(out_response->links[0]->rect.width(), 80, 3);
554 EXPECT_NEAR(out_response->links[0]->rect.height(), 20, 3);
555
556 EXPECT_EQ(out_response->links[1]->url, GURL("http://www.example.com"));
557 EXPECT_NEAR(out_response->links[1]->rect.x(), 60, 3);
558 EXPECT_NEAR(out_response->links[1]->rect.y(), 40, 3);
559 EXPECT_NEAR(out_response->links[1]->rect.width(), 70, 3);
560 EXPECT_NEAR(out_response->links[1]->rect.height(), 20, 3);
561 }
562
563 } // namespace paint_preview
564