paint_preview_recorder_browsertest.cc 35 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879
  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. #include "components/paint_preview/renderer/paint_preview_recorder_impl.h"
  5. #include "base/files/file.h"
  6. #include "base/files/file_path.h"
  7. #include "base/files/scoped_temp_dir.h"
  8. #include "base/test/scoped_feature_list.h"
  9. #include "base/threading/thread_restrictions.h"
  10. #include "build/build_config.h"
  11. #include "components/paint_preview/common/file_stream.h"
  12. #include "components/paint_preview/common/mojom/paint_preview_recorder.mojom.h"
  13. #include "content/public/renderer/render_frame.h"
  14. #include "content/public/test/render_view_test.h"
  15. #include "content/public/test/test_utils.h"
  16. #include "testing/gtest/include/gtest/gtest.h"
  17. #include "third_party/blink/public/common/features.h"
  18. #include "third_party/blink/public/platform/web_runtime_features.h"
  19. #include "third_party/blink/public/web/web_local_frame.h"
  20. #include "third_party/blink/public/web/web_testing_support.h"
  21. #include "third_party/skia/include/core/SkBitmap.h"
  22. #include "third_party/skia/include/core/SkCanvas.h"
  23. #include "third_party/skia/include/core/SkPicture.h"
  24. #include "ui/native_theme/native_theme_features.h"
  25. namespace paint_preview {
  26. namespace {
  27. constexpr char kCompositeAfterPaint[] = "CompositeAfterPaint";
  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.
  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. std::string CompositeAfterPaintToString(
  40. const ::testing::TestParamInfo<bool>& cap_enabled) {
  41. if (cap_enabled.param) {
  42. return "WithCompositeAfterPaint";
  43. }
  44. return "NoCompositeAfterPaint";
  45. }
  46. } // namespace
  47. class PaintPreviewRecorderRenderViewTest
  48. : public content::RenderViewTest,
  49. public ::testing::WithParamInterface<bool> {
  50. public:
  51. PaintPreviewRecorderRenderViewTest() {
  52. std::vector<base::Feature> enabled;
  53. // TODO(crbug/1022398): This is required to bypass a seemingly unrelated
  54. // DCHECK for |use_overlay_scrollbars_| in NativeThemeAura on ChromeOS when
  55. // painting scrollbars when first calling LoadHTML().
  56. feature_list_.InitAndDisableFeature(features::kOverlayScrollbar);
  57. blink::WebTestingSupport::SaveRuntimeFeatures();
  58. blink::WebRuntimeFeatures::EnableFeatureFromString(kCompositeAfterPaint,
  59. GetParam());
  60. }
  61. ~PaintPreviewRecorderRenderViewTest() override {
  62. // Restore blink runtime features to their original values.
  63. blink::WebTestingSupport::ResetRuntimeFeatures();
  64. }
  65. void SetUp() override {
  66. ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
  67. RenderViewTest::SetUp();
  68. }
  69. base::FilePath MakeTestFilePath(const std::string& filename) {
  70. return temp_dir_.GetPath().AppendASCII(filename);
  71. }
  72. base::FilePath RunCapture(content::RenderFrame* frame,
  73. mojom::PaintPreviewCaptureResponsePtr* out_response,
  74. bool is_main_frame = true,
  75. gfx::Rect clip_rect = gfx::Rect()) {
  76. base::FilePath skp_path = MakeTestFilePath("test.skp");
  77. mojom::PaintPreviewCaptureParamsPtr params =
  78. mojom::PaintPreviewCaptureParams::New();
  79. auto token = base::UnguessableToken::Create();
  80. params->guid = token;
  81. params->clip_rect = clip_rect;
  82. params->clip_rect_is_hint = false;
  83. params->is_main_frame = is_main_frame;
  84. params->capture_links = true;
  85. base::File skp_file(
  86. skp_path, base::File::FLAG_CREATE_ALWAYS | base::File::FLAG_WRITE);
  87. params->file = std::move(skp_file);
  88. PaintPreviewRecorderImpl paint_preview_recorder(frame);
  89. paint_preview_recorder.CapturePaintPreview(
  90. std::move(params),
  91. base::BindOnce(&OnCaptureFinished, mojom::PaintPreviewStatus::kOk,
  92. out_response));
  93. content::RunAllTasksUntilIdle();
  94. return skp_path;
  95. }
  96. private:
  97. base::ScopedTempDir temp_dir_;
  98. base::test::ScopedFeatureList feature_list_;
  99. };
  100. TEST_P(PaintPreviewRecorderRenderViewTest, TestCaptureMainFrameAndClipping) {
  101. LoadHTML(
  102. "<!doctype html>"
  103. "<body>"
  104. " <div style='width: 600px; height: 80vh; "
  105. " background-color: #ff0000'>&nbsp;</div>"
  106. " <a style='display:inline-block' href='http://www.google.com'>Foo</a>"
  107. " <div style='width: 100px; height: 600px; "
  108. " background-color: #000000'>&nbsp;</div>"
  109. " <div style='overflow: hidden; width: 100px; height: 100px;"
  110. " background: orange;'>"
  111. " <div style='width: 500px; height: 500px;"
  112. " background: yellow;'></div>"
  113. " </div>"
  114. "</body>");
  115. auto out_response = mojom::PaintPreviewCaptureResponse::New();
  116. content::RenderFrame* frame = GetMainRenderFrame();
  117. base::FilePath skp_path = RunCapture(frame, &out_response);
  118. EXPECT_TRUE(out_response->embedding_token.has_value());
  119. EXPECT_EQ(frame->GetWebFrame()->GetEmbeddingToken(),
  120. out_response->embedding_token.value());
  121. EXPECT_EQ(out_response->content_id_to_embedding_token.size(), 0U);
  122. EXPECT_EQ(out_response->links.size(), 1U);
  123. EXPECT_EQ(out_response->links[0]->url, GURL("http://www.google.com/"));
  124. // Relaxed checks on dimensions and no checks on positions. This is not
  125. // intended to test the rendering behavior of the page only that a link
  126. // was captured and has a bounding box.
  127. EXPECT_GT(out_response->links[0]->rect.width(), 0);
  128. EXPECT_GT(out_response->links[0]->rect.height(), 0);
  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_P(PaintPreviewRecorderRenderViewTest, TestCaptureMainFrameWithScroll) {
  161. LoadHTML(
  162. "<!doctype html>"
  163. "<body>"
  164. " <div style='width: 600px; height: 200px; "
  165. " background-color: #ff0000'>&nbsp;</div>"
  166. " <div style='width: 600px; height: 5000px; "
  167. " background-color: #00ff00'>&nbsp;</div>"
  168. "</body>");
  169. // Scroll to bottom of page to ensure scroll position has no effect on
  170. // capture.
  171. ExecuteJavaScriptForTests("window.scrollTo(0,document.body.scrollHeight);");
  172. content::RunAllTasksUntilIdle();
  173. auto out_response = mojom::PaintPreviewCaptureResponse::New();
  174. content::RenderFrame* frame = GetMainRenderFrame();
  175. base::FilePath skp_path = RunCapture(frame, &out_response);
  176. EXPECT_TRUE(out_response->embedding_token.has_value());
  177. EXPECT_EQ(frame->GetWebFrame()->GetEmbeddingToken(),
  178. out_response->embedding_token.value());
  179. EXPECT_EQ(out_response->content_id_to_embedding_token.size(), 0U);
  180. // Relaxed checks on dimensions and no checks on positions. This is not
  181. // intended to intensively test the rendering behavior of the page.
  182. sk_sp<SkPicture> pic;
  183. {
  184. base::ScopedAllowBlockingForTesting scope;
  185. FileRStream rstream(base::File(
  186. skp_path, base::File::FLAG_OPEN_ALWAYS | base::File::FLAG_READ));
  187. pic = SkPicture::MakeFromStream(&rstream, nullptr);
  188. }
  189. SkBitmap bitmap;
  190. ASSERT_TRUE(bitmap.tryAllocN32Pixels(pic->cullRect().width(),
  191. pic->cullRect().height()));
  192. SkCanvas canvas(bitmap, SkSurfaceProps{});
  193. canvas.drawPicture(pic);
  194. // This should be inside the top right corner of the top div. Success means
  195. // there was no horizontal or vertical clipping as this region is red,
  196. // matching the div.
  197. EXPECT_EQ(bitmap.getColor(600, 50), 0xFFFF0000U);
  198. // This should be inside the bottom of the bottom div. Success means there was
  199. // no vertical clipping as this region is blue matching the div.
  200. EXPECT_EQ(bitmap.getColor(50, pic->cullRect().height() - 100), 0xFF00FF00U);
  201. }
  202. TEST_P(PaintPreviewRecorderRenderViewTest,
  203. TestCaptureMainFrameAboutScrollPosition) {
  204. LoadHTML(
  205. "<!doctype html>"
  206. "<body>"
  207. " <div style='width: 600px; height: 200px; "
  208. " background-color: #ff0000'>&nbsp;</div>"
  209. " <div style='width: 600px; height: 5000px; "
  210. " background-color: #00ff00'>&nbsp;</div>"
  211. "</body>");
  212. // Scroll to bottom of page to ensure scroll position has no effect on
  213. // capture.
  214. ExecuteJavaScriptForTests("window.scrollTo(0,document.body.scrollHeight);");
  215. content::RunAllTasksUntilIdle();
  216. auto out_response = mojom::PaintPreviewCaptureResponse::New();
  217. content::RenderFrame* frame = GetMainRenderFrame();
  218. base::FilePath skp_path =
  219. RunCapture(frame, &out_response, true, gfx::Rect(-1, -1, 500, 500));
  220. EXPECT_TRUE(out_response->embedding_token.has_value());
  221. EXPECT_EQ(frame->GetWebFrame()->GetEmbeddingToken(),
  222. out_response->embedding_token.value());
  223. EXPECT_EQ(out_response->content_id_to_embedding_token.size(), 0U);
  224. // Scroll offset should be within the [0, 500] bounds.
  225. EXPECT_GT(out_response->scroll_offsets.y(), 0);
  226. EXPECT_LT(out_response->scroll_offsets.y(), 500);
  227. // Frame offset should be > 0 in this case.
  228. EXPECT_GT(out_response->frame_offsets.y(), 0);
  229. // Relaxed checks on dimensions and no checks on positions. This is not
  230. // intended to intensively test the rendering behavior of the page.
  231. sk_sp<SkPicture> pic;
  232. {
  233. base::ScopedAllowBlockingForTesting scope;
  234. FileRStream rstream(base::File(
  235. skp_path, base::File::FLAG_OPEN_ALWAYS | base::File::FLAG_READ));
  236. pic = SkPicture::MakeFromStream(&rstream, nullptr);
  237. }
  238. SkBitmap bitmap;
  239. ASSERT_TRUE(bitmap.tryAllocN32Pixels(pic->cullRect().width(),
  240. pic->cullRect().height()));
  241. SkCanvas canvas(bitmap, SkSurfaceProps{});
  242. canvas.drawPicture(pic);
  243. EXPECT_EQ(bitmap.getColor(50, 10), 0xFF00FF00U);
  244. EXPECT_EQ(bitmap.getColor(50, pic->cullRect().height() - 10), 0xFF00FF00U);
  245. }
  246. TEST_P(PaintPreviewRecorderRenderViewTest,
  247. TestCaptureMainFrameAboutScrollPositionClampedToEdge) {
  248. LoadHTML(
  249. "<!doctype html>"
  250. "<body>"
  251. " <div style='width: 600px; height: 200px; "
  252. " background-color: #ff0000'>&nbsp;</div>"
  253. " <div style='width: 600px; height: 5000px; "
  254. " background-color: #00ff00'>&nbsp;</div>"
  255. "</body>");
  256. // Scroll to bottom of page to ensure scroll position has no effect on
  257. // capture.
  258. ExecuteJavaScriptForTests("window.scrollTo(0,document.body.scrollHeight);");
  259. content::RunAllTasksUntilIdle();
  260. auto out_response = mojom::PaintPreviewCaptureResponse::New();
  261. content::RenderFrame* frame = GetMainRenderFrame();
  262. base::FilePath skp_path =
  263. RunCapture(frame, &out_response, true, gfx::Rect(-1, -1, 500, 2000));
  264. EXPECT_TRUE(out_response->embedding_token.has_value());
  265. EXPECT_EQ(frame->GetWebFrame()->GetEmbeddingToken(),
  266. out_response->embedding_token.value());
  267. EXPECT_EQ(out_response->content_id_to_embedding_token.size(), 0U);
  268. // Scroll offset should be within the [0, 2000] bounds and closer to the
  269. // bottom as it was clamped.
  270. EXPECT_GT(out_response->scroll_offsets.y(), 1100);
  271. EXPECT_LT(out_response->scroll_offsets.y(), 2000);
  272. // Frame offset should be > 0 in this case.
  273. EXPECT_GT(out_response->frame_offsets.y(), 0);
  274. // Relaxed checks on dimensions and no checks on positions. This is not
  275. // intended to intensively test the rendering behavior of the page.
  276. sk_sp<SkPicture> pic;
  277. {
  278. base::ScopedAllowBlockingForTesting scope;
  279. FileRStream rstream(base::File(
  280. skp_path, base::File::FLAG_OPEN_ALWAYS | base::File::FLAG_READ));
  281. pic = SkPicture::MakeFromStream(&rstream, nullptr);
  282. }
  283. SkBitmap bitmap;
  284. EXPECT_EQ(pic->cullRect().width(), 500);
  285. EXPECT_EQ(pic->cullRect().height(), 2000);
  286. ASSERT_TRUE(bitmap.tryAllocN32Pixels(pic->cullRect().width(),
  287. pic->cullRect().height()));
  288. SkCanvas canvas(bitmap, SkSurfaceProps{});
  289. canvas.drawPicture(pic);
  290. EXPECT_EQ(bitmap.getColor(50, 10), 0xFF00FF00U);
  291. EXPECT_EQ(bitmap.getColor(50, pic->cullRect().height() - 10), 0xFF00FF00U);
  292. }
  293. TEST_P(PaintPreviewRecorderRenderViewTest,
  294. TestCaptureMainFrameIgnoreScrollPosition) {
  295. LoadHTML(
  296. "<!doctype html>"
  297. "<body>"
  298. " <div style='width: 600px; height: 200px; "
  299. " background-color: #ff0000'>&nbsp;</div>"
  300. " <div style='width: 600px; height: 5000px; "
  301. " background-color: #00ff00'>&nbsp;</div>"
  302. "</body>");
  303. // Scroll to bottom of page to ensure scroll position has no effect on
  304. // capture.
  305. ExecuteJavaScriptForTests("window.scrollTo(0,document.body.scrollHeight);");
  306. content::RunAllTasksUntilIdle();
  307. auto out_response = mojom::PaintPreviewCaptureResponse::New();
  308. content::RenderFrame* frame = GetMainRenderFrame();
  309. base::FilePath skp_path =
  310. RunCapture(frame, &out_response, true, gfx::Rect(-1, -1, 0, 0));
  311. EXPECT_TRUE(out_response->embedding_token.has_value());
  312. EXPECT_EQ(frame->GetWebFrame()->GetEmbeddingToken(),
  313. out_response->embedding_token.value());
  314. EXPECT_EQ(out_response->content_id_to_embedding_token.size(), 0U);
  315. EXPECT_GT(out_response->scroll_offsets.y(), 0);
  316. // Frame offset should be 0 in this case.
  317. EXPECT_EQ(out_response->frame_offsets.x(), 0);
  318. EXPECT_EQ(out_response->frame_offsets.y(), 0);
  319. // Relaxed checks on dimensions and no checks on positions. This is not
  320. // intended to intensively test the rendering behavior of the page.
  321. sk_sp<SkPicture> pic;
  322. {
  323. base::ScopedAllowBlockingForTesting scope;
  324. FileRStream rstream(base::File(
  325. skp_path, base::File::FLAG_OPEN_ALWAYS | base::File::FLAG_READ));
  326. pic = SkPicture::MakeFromStream(&rstream, nullptr);
  327. }
  328. SkBitmap bitmap;
  329. ASSERT_TRUE(bitmap.tryAllocN32Pixels(pic->cullRect().width(),
  330. pic->cullRect().height()));
  331. SkCanvas canvas(bitmap, SkSurfaceProps{});
  332. canvas.drawPicture(pic);
  333. EXPECT_EQ(bitmap.getColor(600, 50), 0xFFFF0000U);
  334. EXPECT_EQ(bitmap.getColor(50, pic->cullRect().height() - 100), 0xFF00FF00U);
  335. }
  336. TEST_P(PaintPreviewRecorderRenderViewTest, TestCaptureFragment) {
  337. // Use position absolute position to check that the captured link dimensions
  338. // match what is specified.
  339. LoadHTML(
  340. "<!doctype html>"
  341. "<body style='min-height:1000px;'>"
  342. " <a style='position: absolute; left: -15px; top: 0px; width: 40px; "
  343. " height: 30px;' href='#fragment'>Foo</a>"
  344. " <h1 id='fragment'>I'm a fragment</h1>"
  345. "</body>");
  346. auto out_response = mojom::PaintPreviewCaptureResponse::New();
  347. content::RenderFrame* frame = GetMainRenderFrame();
  348. RunCapture(frame, &out_response);
  349. EXPECT_TRUE(out_response->embedding_token.has_value());
  350. EXPECT_EQ(frame->GetWebFrame()->GetEmbeddingToken(),
  351. out_response->embedding_token.value());
  352. EXPECT_EQ(out_response->content_id_to_embedding_token.size(), 0U);
  353. EXPECT_EQ(out_response->links.size(), 1U);
  354. EXPECT_EQ(out_response->links[0]->url, GURL("fragment"));
  355. EXPECT_EQ(out_response->links[0]->rect.x(), -15);
  356. EXPECT_EQ(out_response->links[0]->rect.y(), 0);
  357. EXPECT_EQ(out_response->links[0]->rect.width(), 40);
  358. EXPECT_EQ(out_response->links[0]->rect.height(), 30);
  359. }
  360. TEST_P(PaintPreviewRecorderRenderViewTest, TestCaptureInvalidFile) {
  361. LoadHTML("<body></body>");
  362. mojom::PaintPreviewCaptureParamsPtr params =
  363. mojom::PaintPreviewCaptureParams::New();
  364. auto token = base::UnguessableToken::Create();
  365. params->guid = token;
  366. params->clip_rect = gfx::Rect();
  367. params->is_main_frame = true;
  368. params->capture_links = true;
  369. params->max_capture_size = 0;
  370. base::File skp_file; // Invalid file.
  371. params->file = std::move(skp_file);
  372. content::RenderFrame* frame = GetMainRenderFrame();
  373. PaintPreviewRecorderImpl paint_preview_recorder(frame);
  374. paint_preview_recorder.CapturePaintPreview(
  375. std::move(params),
  376. base::BindOnce(&OnCaptureFinished,
  377. mojom::PaintPreviewStatus::kCaptureFailed, nullptr));
  378. content::RunAllTasksUntilIdle();
  379. }
  380. TEST_P(PaintPreviewRecorderRenderViewTest, TestCaptureInvalidXYClip) {
  381. LoadHTML("<body></body>");
  382. mojom::PaintPreviewCaptureParamsPtr params =
  383. mojom::PaintPreviewCaptureParams::New();
  384. auto token = base::UnguessableToken::Create();
  385. params->guid = token;
  386. params->clip_rect = gfx::Rect(1000000, 1000000, 10, 10);
  387. params->is_main_frame = true;
  388. params->capture_links = true;
  389. params->max_capture_size = 0;
  390. base::FilePath skp_path = MakeTestFilePath("test.skp");
  391. base::File skp_file(skp_path,
  392. base::File::FLAG_CREATE_ALWAYS | base::File::FLAG_WRITE);
  393. params->file = std::move(skp_file);
  394. content::RenderFrame* frame = GetMainRenderFrame();
  395. PaintPreviewRecorderImpl paint_preview_recorder(frame);
  396. paint_preview_recorder.CapturePaintPreview(
  397. std::move(params),
  398. base::BindOnce(&OnCaptureFinished,
  399. mojom::PaintPreviewStatus::kCaptureFailed, nullptr));
  400. content::RunAllTasksUntilIdle();
  401. }
  402. TEST_P(PaintPreviewRecorderRenderViewTest, TestCaptureMainFrameAndLocalFrame) {
  403. LoadHTML(
  404. "<!doctype html>"
  405. "<body style='min-height:1000px;'>"
  406. " <iframe style='width: 500px, height: 500px'"
  407. " srcdoc=\"<div style='width: 100px; height: 100px;"
  408. " background-color: #000000'>&nbsp;</div>\"></iframe>"
  409. "</body>");
  410. auto out_response = mojom::PaintPreviewCaptureResponse::New();
  411. content::RenderFrame* frame = GetMainRenderFrame();
  412. RunCapture(frame, &out_response);
  413. EXPECT_TRUE(out_response->embedding_token.has_value());
  414. EXPECT_EQ(frame->GetWebFrame()->GetEmbeddingToken(),
  415. out_response->embedding_token.value());
  416. EXPECT_EQ(out_response->content_id_to_embedding_token.size(), 0U);
  417. }
  418. TEST_P(PaintPreviewRecorderRenderViewTest, TestCaptureLocalFrame) {
  419. LoadHTML(
  420. "<!doctype html>"
  421. "<body style='min-height:1000px;'>"
  422. " <iframe style='width: 500px; height: 500px'"
  423. " srcdoc=\"<div style='width: 100px; height: 100px;"
  424. " background-color: #000000'>&nbsp;</div>\"></iframe>"
  425. "</body>");
  426. auto out_response = mojom::PaintPreviewCaptureResponse::New();
  427. auto* child_frame = content::RenderFrame::FromWebFrame(
  428. GetMainRenderFrame()->GetWebFrame()->FirstChild()->ToWebLocalFrame());
  429. ASSERT_TRUE(child_frame);
  430. RunCapture(child_frame, &out_response, false);
  431. EXPECT_TRUE(out_response->embedding_token.has_value());
  432. EXPECT_EQ(out_response->content_id_to_embedding_token.size(), 0U);
  433. }
  434. TEST_P(PaintPreviewRecorderRenderViewTest, TestCaptureUnclippedLocalFrame) {
  435. LoadHTML(
  436. "<!doctype html>"
  437. "<body style='min-height:1000px;'>"
  438. " <iframe style='width: 500px; height: 500px'"
  439. " srcdoc=\"<div style='width: 500px; height: 100px;"
  440. " background-color: #00FF00'>&nbsp;</div>"
  441. " <div style='width: 500px; height: 900px;"
  442. " background-color: #FF0000'>&nbsp;</div>\"></iframe>"
  443. "</body>");
  444. auto out_response = mojom::PaintPreviewCaptureResponse::New();
  445. auto* child_web_frame =
  446. GetMainRenderFrame()->GetWebFrame()->FirstChild()->ToWebLocalFrame();
  447. auto* child_frame = content::RenderFrame::FromWebFrame(child_web_frame);
  448. ASSERT_TRUE(child_frame);
  449. child_web_frame->SetScrollOffset(gfx::PointF(0, 400));
  450. base::FilePath skp_path = RunCapture(child_frame, &out_response, false);
  451. EXPECT_TRUE(out_response->embedding_token.has_value());
  452. EXPECT_EQ(out_response->content_id_to_embedding_token.size(), 0U);
  453. sk_sp<SkPicture> pic;
  454. {
  455. base::ScopedAllowBlockingForTesting scope;
  456. FileRStream rstream(base::File(
  457. skp_path, base::File::FLAG_OPEN_ALWAYS | base::File::FLAG_READ));
  458. pic = SkPicture::MakeFromStream(&rstream, nullptr);
  459. }
  460. EXPECT_EQ(pic->cullRect().width(), child_web_frame->DocumentSize().width());
  461. EXPECT_EQ(pic->cullRect().height(), child_web_frame->DocumentSize().height());
  462. SkBitmap bitmap;
  463. ASSERT_TRUE(bitmap.tryAllocN32Pixels(pic->cullRect().width(),
  464. pic->cullRect().height()));
  465. SkCanvas canvas(bitmap);
  466. canvas.drawPicture(pic);
  467. EXPECT_EQ(bitmap.getColor(50, 50), 0xFF00FF00U);
  468. EXPECT_EQ(bitmap.getColor(50, 800), 0xFFFF0000U);
  469. }
  470. TEST_P(PaintPreviewRecorderRenderViewTest, TestCaptureCustomClipRect) {
  471. LoadHTML(
  472. "<!doctype html>"
  473. "<body>"
  474. " <div style='width: 600px; height: 600px; background-color: #0000ff;'>"
  475. " <div style='width: 300px; height: 300px; background-color: "
  476. " #ffff00; position: relative; left: 150px; top: 150px'></div>"
  477. " </div>"
  478. " <a style='position: absolute; left: 160px; top: 170px; width: 40px; "
  479. " height: 30px;' href='http://www.example.com'>Foo</a>"
  480. "</body>");
  481. auto out_response = mojom::PaintPreviewCaptureResponse::New();
  482. content::RenderFrame* frame = GetMainRenderFrame();
  483. gfx::Rect clip_rect = gfx::Rect(150, 150, 300, 300);
  484. base::FilePath skp_path = RunCapture(frame, &out_response, true, clip_rect);
  485. EXPECT_TRUE(out_response->embedding_token.has_value());
  486. EXPECT_EQ(frame->GetWebFrame()->GetEmbeddingToken(),
  487. out_response->embedding_token.value());
  488. EXPECT_EQ(out_response->content_id_to_embedding_token.size(), 0U);
  489. sk_sp<SkPicture> pic;
  490. {
  491. base::ScopedAllowBlockingForTesting scope;
  492. FileRStream rstream(base::File(
  493. skp_path, base::File::FLAG_OPEN_ALWAYS | base::File::FLAG_READ));
  494. pic = SkPicture::MakeFromStream(&rstream, nullptr);
  495. }
  496. EXPECT_EQ(pic->cullRect().height(), 300);
  497. EXPECT_EQ(pic->cullRect().width(), 300);
  498. SkBitmap bitmap;
  499. ASSERT_TRUE(bitmap.tryAllocN32Pixels(pic->cullRect().width(),
  500. pic->cullRect().height()));
  501. SkCanvas canvas(bitmap);
  502. canvas.drawPicture(pic);
  503. EXPECT_EQ(bitmap.getColor(100, 100), 0xFFFFFF00U);
  504. ASSERT_EQ(out_response->links.size(), 1U);
  505. EXPECT_EQ(out_response->links[0]->url, GURL("http://www.example.com"));
  506. EXPECT_EQ(out_response->links[0]->rect.x(), 10);
  507. EXPECT_EQ(out_response->links[0]->rect.y(), 20);
  508. EXPECT_EQ(out_response->links[0]->rect.width(), 40);
  509. EXPECT_EQ(out_response->links[0]->rect.height(), 30);
  510. }
  511. TEST_P(PaintPreviewRecorderRenderViewTest, TestCaptureWithClamp) {
  512. LoadHTML(
  513. "<!doctype html>"
  514. "<body>"
  515. " <div style='width: 600px; height: 600px; background-color: #0000ff;'>"
  516. " <div style='width: 300px; height: 300px; background-color: "
  517. " #ffff00; position: relative; left: 150px; top: 150px'></div>"
  518. " </div>"
  519. " <a style='position: absolute; left: 160px; top: 170px; width: 40px; "
  520. " height: 30px;' href='http://www.example.com'>Foo</a>"
  521. "</body>");
  522. auto out_response = mojom::PaintPreviewCaptureResponse::New();
  523. content::RenderFrame* frame = GetMainRenderFrame();
  524. const size_t kLarge = 1000000;
  525. gfx::Rect clip_rect = gfx::Rect(0, 0, kLarge, kLarge);
  526. base::FilePath skp_path = RunCapture(frame, &out_response, true, clip_rect);
  527. EXPECT_TRUE(out_response->embedding_token.has_value());
  528. EXPECT_EQ(frame->GetWebFrame()->GetEmbeddingToken(),
  529. out_response->embedding_token.value());
  530. EXPECT_EQ(out_response->content_id_to_embedding_token.size(), 0U);
  531. sk_sp<SkPicture> pic;
  532. {
  533. base::ScopedAllowBlockingForTesting scope;
  534. FileRStream rstream(base::File(
  535. skp_path, base::File::FLAG_OPEN_ALWAYS | base::File::FLAG_READ));
  536. pic = SkPicture::MakeFromStream(&rstream, nullptr);
  537. }
  538. EXPECT_LT(pic->cullRect().height(), kLarge);
  539. EXPECT_LT(pic->cullRect().width(), kLarge);
  540. }
  541. TEST_P(PaintPreviewRecorderRenderViewTest, TestCaptureFullIfWidthHeightAre0) {
  542. LoadHTML(
  543. "<!doctype html>"
  544. "<body>"
  545. " <div style='width: 600px; height: 600px; background-color: #0000ff;'>"
  546. " <div style='width: 300px; height: 300px; background-color: "
  547. " #ffff00; position: relative; left: 150px; top: 150px'></div>"
  548. " </div>"
  549. " <a style='position: absolute; left: 160px; top: 170px; width: 40px; "
  550. " height: 30px;' href='http://www.example.com'>Foo</a>"
  551. "</body>");
  552. auto out_response = mojom::PaintPreviewCaptureResponse::New();
  553. content::RenderFrame* frame = GetMainRenderFrame();
  554. gfx::Rect clip_rect = gfx::Rect(1, 1, 0, 0);
  555. base::FilePath skp_path = RunCapture(frame, &out_response, true, clip_rect);
  556. EXPECT_TRUE(out_response->embedding_token.has_value());
  557. EXPECT_EQ(frame->GetWebFrame()->GetEmbeddingToken(),
  558. out_response->embedding_token.value());
  559. EXPECT_EQ(out_response->content_id_to_embedding_token.size(), 0U);
  560. sk_sp<SkPicture> pic;
  561. {
  562. base::ScopedAllowBlockingForTesting scope;
  563. FileRStream rstream(base::File(
  564. skp_path, base::File::FLAG_OPEN_ALWAYS | base::File::FLAG_READ));
  565. pic = SkPicture::MakeFromStream(&rstream, nullptr);
  566. }
  567. EXPECT_GT(pic->cullRect().height(), 0U);
  568. EXPECT_GT(pic->cullRect().width(), 0U);
  569. }
  570. TEST_P(PaintPreviewRecorderRenderViewTest, CaptureWithTranslate) {
  571. // URLs should be annotated correctly when a CSS transform is applied.
  572. LoadHTML(
  573. R"(
  574. <!doctype html>
  575. <body>
  576. <div style="display: inline-block;
  577. padding: 16px;
  578. font-size: 16px;">
  579. <div style="padding: 16px;
  580. transform: translate(10px, 20px);
  581. margin-bottom: 30px;">
  582. <div>
  583. <a href="http://www.example.com" style="display: block;
  584. width: 70px;
  585. height: 20px;">
  586. <div>Example</div>
  587. </a>
  588. </div>
  589. </div>
  590. </div>
  591. </body>)");
  592. auto out_response = mojom::PaintPreviewCaptureResponse::New();
  593. content::RenderFrame* frame = GetMainRenderFrame();
  594. RunCapture(frame, &out_response);
  595. EXPECT_TRUE(out_response->embedding_token.has_value());
  596. EXPECT_EQ(frame->GetWebFrame()->GetEmbeddingToken(),
  597. out_response->embedding_token.value());
  598. EXPECT_EQ(out_response->content_id_to_embedding_token.size(), 0U);
  599. ASSERT_EQ(out_response->links.size(), 1U);
  600. EXPECT_EQ(out_response->links[0]->url, GURL("http://www.example.com"));
  601. EXPECT_NEAR(out_response->links[0]->rect.x(), 50, 3);
  602. EXPECT_NEAR(out_response->links[0]->rect.y(), 60, 3);
  603. EXPECT_NEAR(out_response->links[0]->rect.width(), 70, 3);
  604. EXPECT_NEAR(out_response->links[0]->rect.height(), 20, 3);
  605. }
  606. TEST_P(PaintPreviewRecorderRenderViewTest, CaptureWithTranslateThenRotate) {
  607. // URLs should be annotated correctly when a CSS transform is applied.
  608. LoadHTML(
  609. R"(
  610. <!doctype html>
  611. <body>
  612. <div style="display: inline-block;
  613. padding: 16px;
  614. font-size: 16px;">
  615. <div style="padding: 16px;
  616. transform: translate(100px, 0) rotate(45deg);
  617. margin-bottom: 30px;">
  618. <div>
  619. <a href="http://www.example.com" style="display: block;
  620. width: 70px;
  621. height: 20px;">
  622. <div>Example</div>
  623. </a>
  624. </div>
  625. </div>
  626. </div>
  627. </body>)");
  628. auto out_response = mojom::PaintPreviewCaptureResponse::New();
  629. content::RenderFrame* frame = GetMainRenderFrame();
  630. RunCapture(frame, &out_response);
  631. EXPECT_TRUE(out_response->embedding_token.has_value());
  632. EXPECT_EQ(frame->GetWebFrame()->GetEmbeddingToken(),
  633. out_response->embedding_token.value());
  634. EXPECT_EQ(out_response->content_id_to_embedding_token.size(), 0U);
  635. ASSERT_EQ(out_response->links.size(), 1U);
  636. EXPECT_EQ(out_response->links[0]->url, GURL("http://www.example.com"));
  637. EXPECT_NEAR(out_response->links[0]->rect.x(), 141, 5);
  638. EXPECT_NEAR(out_response->links[0]->rect.y(), 18, 5);
  639. #if !BUILDFLAG(IS_ANDROID)
  640. EXPECT_NEAR(out_response->links[0]->rect.width(), 58, 10);
  641. EXPECT_NEAR(out_response->links[0]->rect.height(), 58, 10);
  642. #endif
  643. }
  644. TEST_P(PaintPreviewRecorderRenderViewTest, CaptureWithRotateThenTranslate) {
  645. // URLs should be annotated correctly when a CSS transform is applied.
  646. LoadHTML(
  647. R"(
  648. <!doctype html>
  649. <body>
  650. <div style="display: inline-block;
  651. padding: 16px;
  652. font-size: 16px;">
  653. <div style="padding: 16px;
  654. transform: rotate(45deg) translate(100px, 0);
  655. margin-bottom: 30px;">
  656. <div>
  657. <a href="http://www.example.com" style="display: block;
  658. width: 70px;
  659. height: 20px;">
  660. <div>Example</div>
  661. </a>
  662. </div>
  663. </div>
  664. </div>
  665. </body>)");
  666. auto out_response = mojom::PaintPreviewCaptureResponse::New();
  667. content::RenderFrame* frame = GetMainRenderFrame();
  668. RunCapture(frame, &out_response);
  669. EXPECT_TRUE(out_response->embedding_token.has_value());
  670. EXPECT_EQ(frame->GetWebFrame()->GetEmbeddingToken(),
  671. out_response->embedding_token.value());
  672. EXPECT_EQ(out_response->content_id_to_embedding_token.size(), 0U);
  673. ASSERT_EQ(out_response->links.size(), 1U);
  674. EXPECT_EQ(out_response->links[0]->url, GURL("http://www.example.com"));
  675. EXPECT_NEAR(out_response->links[0]->rect.x(), 111, 5);
  676. EXPECT_NEAR(out_response->links[0]->rect.y(), 88, 5);
  677. #if !BUILDFLAG(IS_ANDROID)
  678. EXPECT_NEAR(out_response->links[0]->rect.width(), 58, 10);
  679. EXPECT_NEAR(out_response->links[0]->rect.height(), 58, 10);
  680. #endif
  681. }
  682. TEST_P(PaintPreviewRecorderRenderViewTest, CaptureWithScale) {
  683. // URLs should be annotated correctly when a CSS transform is applied.
  684. LoadHTML(
  685. R"(
  686. <!doctype html>
  687. <body>
  688. <div style="display: inline-block;
  689. padding: 16px;
  690. font-size: 16px;">
  691. <div style="padding: 16px;
  692. transform: scale(2, 1);
  693. margin-bottom: 30px;">
  694. <div>
  695. <a href="http://www.example.com" style="display: block;
  696. width: 70px;
  697. height: 20px;">
  698. <div>Example</div>
  699. </a>
  700. </div>
  701. </div>
  702. </div>
  703. </body>)");
  704. auto out_response = mojom::PaintPreviewCaptureResponse::New();
  705. content::RenderFrame* frame = GetMainRenderFrame();
  706. RunCapture(frame, &out_response);
  707. EXPECT_TRUE(out_response->embedding_token.has_value());
  708. EXPECT_EQ(frame->GetWebFrame()->GetEmbeddingToken(),
  709. out_response->embedding_token.value());
  710. EXPECT_EQ(out_response->content_id_to_embedding_token.size(), 0U);
  711. ASSERT_EQ(out_response->links.size(), 1U);
  712. EXPECT_EQ(out_response->links[0]->url, GURL("http://www.example.com"));
  713. EXPECT_NEAR(out_response->links[0]->rect.x(), 5, 3);
  714. EXPECT_NEAR(out_response->links[0]->rect.y(), 40, 3);
  715. EXPECT_NEAR(out_response->links[0]->rect.width(), 140, 3);
  716. EXPECT_NEAR(out_response->links[0]->rect.height(), 20, 3);
  717. }
  718. TEST_P(PaintPreviewRecorderRenderViewTest, CaptureSaveRestore) {
  719. // URLs should be annotated correctly when a CSS transform is applied.
  720. LoadHTML(
  721. R"(
  722. <!doctype html>
  723. <body>
  724. <div style="display: inline-block;
  725. padding: 16px;
  726. font-size: 16px;">
  727. <div style="padding: 16px;
  728. transform: translate(20px, 0);
  729. margin-bottom: 30px;">
  730. <div>
  731. <a href="http://www.example.com" style="display: block;
  732. width: 70px;
  733. height: 20px;">
  734. <div>Example</div>
  735. </a>
  736. </div>
  737. </div>
  738. <div style="padding: 16px;
  739. transform: none;
  740. margin-bottom: 30px;">
  741. <div>
  742. <a href="http://www.chromium.org" style="display: block;
  743. width: 80px;
  744. height: 20px;">
  745. <div>Chromium</div>
  746. </a>
  747. </div>
  748. </div>
  749. </div>
  750. </body>)");
  751. auto out_response = mojom::PaintPreviewCaptureResponse::New();
  752. content::RenderFrame* frame = GetMainRenderFrame();
  753. RunCapture(frame, &out_response);
  754. EXPECT_TRUE(out_response->embedding_token.has_value());
  755. EXPECT_EQ(frame->GetWebFrame()->GetEmbeddingToken(),
  756. out_response->embedding_token.value());
  757. EXPECT_EQ(out_response->content_id_to_embedding_token.size(), 0U);
  758. ASSERT_EQ(out_response->links.size(), 2U);
  759. EXPECT_EQ(out_response->links[0]->url, GURL("http://www.chromium.org"));
  760. EXPECT_NEAR(out_response->links[0]->rect.x(), 40, 3);
  761. EXPECT_NEAR(out_response->links[0]->rect.y(), 122, 3);
  762. EXPECT_NEAR(out_response->links[0]->rect.width(), 80, 3);
  763. EXPECT_NEAR(out_response->links[0]->rect.height(), 20, 3);
  764. EXPECT_EQ(out_response->links[1]->url, GURL("http://www.example.com"));
  765. EXPECT_NEAR(out_response->links[1]->rect.x(), 60, 3);
  766. EXPECT_NEAR(out_response->links[1]->rect.y(), 40, 3);
  767. EXPECT_NEAR(out_response->links[1]->rect.width(), 70, 3);
  768. EXPECT_NEAR(out_response->links[1]->rect.height(), 20, 3);
  769. }
  770. INSTANTIATE_TEST_SUITE_P(All,
  771. PaintPreviewRecorderRenderViewTest,
  772. testing::Values(true, false),
  773. CompositeAfterPaintToString);
  774. } // namespace paint_preview