blob: 1741ab69bfbfb3fffe68dd4582a790f1d00a75ff [file] [log] [blame]
Khushal Sagar91b544222024-03-12 17:36:591// Copyright 2024 The Chromium Authors
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 "base/test/scoped_feature_list.h"
Noam Rosenthal09dd02b2024-09-21 21:05:526#include "base/test/test_future.h"
Vladimir Levinbe48b9c2025-02-25 16:00:477#include "build/buildflag.h"
Vladimir Levin32888ea2025-03-11 17:20:488#include "cc/base/features.h"
Noam Rosenthal09dd02b2024-09-21 21:05:529#include "cc/test/pixel_comparator.h"
10#include "cc/test/pixel_test_utils.h"
Khushal Sagar91b544222024-03-12 17:36:5911#include "components/viz/host/host_frame_sink_manager.h"
12#include "content/browser/compositor/surface_utils.h"
13#include "content/browser/renderer_host/frame_tree_node.h"
Khushal Sagar04e638c4b2024-05-13 17:43:1114#include "content/browser/renderer_host/view_transition_opt_in_state.h"
David Bokan5ae9e452024-03-26 16:23:5915#include "content/browser/web_contents/web_contents_impl.h"
Noam Rosenthal09dd02b2024-09-21 21:05:5216#include "content/public/browser/web_contents_observer.h"
Khushal Sagar4e5aec432024-03-14 23:12:4617#include "content/public/test/back_forward_cache_util.h"
Khushal Sagar91b544222024-03-12 17:36:5918#include "content/public/test/browser_test.h"
19#include "content/public/test/browser_test_utils.h"
20#include "content/public/test/content_browser_test.h"
21#include "content/shell/browser/shell.h"
Khushal Sagar04e638c4b2024-05-13 17:43:1122#include "content/shell/browser/shell_download_manager_delegate.h"
Khushal Sagar4e5aec432024-03-14 23:12:4623#include "content/test/content_browser_test_utils_internal.h"
Khushal Sagar91b544222024-03-12 17:36:5924#include "mojo/public/cpp/bindings/sync_call_restrictions.h"
25#include "net/dns/mock_host_resolver.h"
26#include "net/test/embedded_test_server/default_handlers.h"
Zoraiz Naeemfe5b6552024-08-08 19:49:0327#include "services/viz/privileged/mojom/compositing/features.mojom-features.h"
Khushal Sagar91b544222024-03-12 17:36:5928#include "third_party/blink/public/common/features.h"
Noam Rosenthal09dd02b2024-09-21 21:05:5229#include "third_party/blink/public/common/features_generated.h"
30#include "ui/gfx/geometry/size.h"
Khushal Sagar91b544222024-03-12 17:36:5931
32namespace content {
33
34class ViewTransitionBrowserTest : public ContentBrowserTest {
35 public:
36 class TestCondition : public CommitDeferringCondition {
37 public:
38 TestCondition(NavigationRequest& request, base::RunLoop* run_loop)
39 : CommitDeferringCondition(request), run_loop_(run_loop) {}
40 ~TestCondition() override = default;
41
42 Result WillCommitNavigation(base::OnceClosure resume) override {
43 GetUIThreadTaskRunner()->PostTask(FROM_HERE, run_loop_->QuitClosure());
44 return Result::kDefer;
45 }
46
Lingqi Chif2691ec92024-10-03 02:33:4447 const char* TraceEventName() const override { return "TestCondition"; }
48
Khushal Sagar91b544222024-03-12 17:36:5949 private:
50 raw_ptr<base::RunLoop> run_loop_;
51 };
52
53 ViewTransitionBrowserTest() {
54 feature_list_.InitWithFeatures(
Khushal Sagarf1b0c972024-03-27 15:31:5155 /*enabled_features=*/
Zoraiz Naeemfe5b6552024-08-08 19:49:0356 {blink::features::kViewTransitionOnNavigation,
57 viz::mojom::EnableVizTestApis},
Khushal Sagar91b544222024-03-12 17:36:5958 /*disabled_features=*/{});
59 }
60
61 void SetUpOnMainThread() override {
62 ContentBrowserTest::SetUpOnMainThread();
63 host_resolver()->AddRule("*", "127.0.0.1");
64 embedded_test_server()->ServeFilesFromSourceDirectory(
65 GetTestDataFilePath());
66 net::test_server::RegisterDefaultHandlers(embedded_test_server());
67 ASSERT_TRUE(embedded_test_server()->Start());
68 }
69
70 void WaitForConditionsDone(NavigationRequest* request) {
71 // Inject a condition to know when the VT response has been received but
72 // before the NavigationRequest is notified.
73 run_loop_ = std::make_unique<base::RunLoop>();
74 request->RegisterCommitDeferringConditionForTesting(
75 std::make_unique<TestCondition>(*request, run_loop_.get()));
76 run_loop_->Run();
77 }
78
Khushal Sagar04e638c4b2024-05-13 17:43:1179 bool HasVTOptIn(RenderFrameHost* rfh) {
80 auto* opt_in_state = ViewTransitionOptInState::GetForCurrentDocument(
81 static_cast<RenderFrameHostImpl*>(rfh));
82 return opt_in_state &&
83 opt_in_state->same_origin_opt_in() ==
84 blink::mojom::ViewTransitionSameOriginOptIn::kEnabled;
85 }
86
Khushal Sagar91b544222024-03-12 17:36:5987 private:
88 base::test::ScopedFeatureList feature_list_;
89 std::unique_ptr<base::RunLoop> run_loop_;
90};
91
92IN_PROC_BROWSER_TEST_F(ViewTransitionBrowserTest,
Michael Thiessena74065a32024-06-28 18:48:3593 NavigationCancelledAfterScreenshot) {
Khushal Sagar91b544222024-03-12 17:36:5994 // Start with a page which has an opt-in for VT.
95 GURL test_url(
96 embedded_test_server()->GetURL("/view_transitions/basic-vt-opt-in.html"));
97 ASSERT_TRUE(NavigateToURL(shell()->web_contents(), test_url));
Michael Thiessena74065a32024-06-28 18:48:3598 WaitForCopyableViewInWebContents(shell()->web_contents());
Khushal Sagar91b544222024-03-12 17:36:5999
100 TestNavigationManager navigation_manager(shell()->web_contents(), test_url);
101 ASSERT_TRUE(
102 ExecJs(shell()->web_contents(), "location.href = location.href;"));
103
104 // Wait for response and resume. The navigation should be blocked by the view
105 // transition condition.
106 ASSERT_TRUE(navigation_manager.WaitForResponse());
107 navigation_manager.ResumeNavigation();
108
109 auto* navigation_request =
110 NavigationRequest::From(navigation_manager.GetNavigationHandle());
111 ASSERT_TRUE(navigation_request);
112 ASSERT_TRUE(
113 navigation_request->IsCommitDeferringConditionDeferredForTesting());
114 ASSERT_FALSE(navigation_request->commit_params().view_transition_state);
115
116 WaitForConditionsDone(navigation_request);
117 ASSERT_TRUE(navigation_request->commit_params().view_transition_state);
118
119 mojo::ScopedAllowSyncCallForTesting allow_sync;
120
Zoraiz Naeemfe5b6552024-08-08 19:49:03121 bool has_resources = false;
122 GetHostFrameSinkManager()
123 ->GetFrameSinkManagerTestApi()
124 .HasUnclaimedViewTransitionResources(&has_resources);
125 ASSERT_TRUE(has_resources);
Khushal Sagar91b544222024-03-12 17:36:59126
127 shell()->web_contents()->Stop();
128 ASSERT_FALSE(navigation_manager.was_committed());
Zoraiz Naeemfe5b6552024-08-08 19:49:03129 GetHostFrameSinkManager()
130 ->GetFrameSinkManagerTestApi()
131 .HasUnclaimedViewTransitionResources(&has_resources);
132 ASSERT_FALSE(has_resources);
Khushal Sagarf1b0c972024-03-27 15:31:51133
134 // Ensure the old renderer discards the outgoing transition.
135 EXPECT_TRUE(ExecJs(
136 shell()->web_contents()->GetPrimaryMainFrame(),
137 "(async () => { await document.startViewTransition().ready; })()"));
138}
139
140IN_PROC_BROWSER_TEST_F(ViewTransitionBrowserTest,
141 NavigationCancelledBeforeScreenshot) {
142 // Start with a page which has an opt-in for VT.
143 GURL test_url(
144 embedded_test_server()->GetURL("/view_transitions/basic-vt-opt-in.html"));
145 ASSERT_TRUE(NavigateToURL(shell()->web_contents(), test_url));
Michael Thiessena74065a32024-06-28 18:48:35146 WaitForCopyableViewInWebContents(shell()->web_contents());
Khushal Sagarf1b0c972024-03-27 15:31:51147
148 TestNavigationManager navigation_manager(shell()->web_contents(), test_url);
149 ASSERT_TRUE(
150 ExecJs(shell()->web_contents(), "location.href = location.href;"));
151
152 // Wait for response and resume. The navigation should be blocked by the view
153 // transition condition.
154 ASSERT_TRUE(navigation_manager.WaitForResponse());
155 navigation_manager.ResumeNavigation();
156
157 auto* navigation_request =
158 NavigationRequest::From(navigation_manager.GetNavigationHandle());
159 ASSERT_TRUE(navigation_request);
160 ASSERT_TRUE(
161 navigation_request->IsCommitDeferringConditionDeferredForTesting());
162 ASSERT_FALSE(navigation_request->commit_params().view_transition_state);
163
164 // Stop the navigation while the screenshot request is in flight.
165 shell()->web_contents()->Stop();
166 ASSERT_FALSE(navigation_manager.was_committed());
167
168 // Ensure the old renderer discards the outgoing transition.
169 EXPECT_TRUE(ExecJs(
170 shell()->web_contents()->GetPrimaryMainFrame(),
171 "(async () => { await document.startViewTransition().ready; })()"));
Khushal Sagar91b544222024-03-12 17:36:59172}
173
174IN_PROC_BROWSER_TEST_F(ViewTransitionBrowserTest,
Gaston Rodriguez318b7392024-06-28 14:51:17175 OwnershipTransferredToNewRenderer) {
Khushal Sagar91b544222024-03-12 17:36:59176 // Start with a page which has an opt-in for VT.
177 GURL test_url(
178 embedded_test_server()->GetURL("/view_transitions/basic-vt-opt-in.html"));
179 ASSERT_TRUE(NavigateToURL(shell()->web_contents(), test_url));
Michael Thiessen517e2542024-06-27 14:02:44180 WaitForCopyableViewInWebContents(shell()->web_contents());
Khushal Sagar91b544222024-03-12 17:36:59181
182 TestNavigationManager navigation_manager(shell()->web_contents(), test_url);
183 ASSERT_TRUE(
184 ExecJs(shell()->web_contents(), "location.href = location.href;"));
185 ASSERT_TRUE(navigation_manager.WaitForNavigationFinished());
186 ASSERT_TRUE(static_cast<RenderWidgetHostViewBase*>(
187 shell()->web_contents()->GetRenderWidgetHostView())
188 ->HasViewTransitionResourcesForTesting());
189}
190
David Bokan5ae9e452024-03-26 16:23:59191// Ensure a browser-initiated navigation (i.e. typing URL into omnibox) does
192// not trigger a view transitions.
193IN_PROC_BROWSER_TEST_F(ViewTransitionBrowserTest,
194 NoOpOnBrowserInitiatedNavigations) {
195 // Start with a page which has an opt-in for VT.
196 GURL test_url(
197 embedded_test_server()->GetURL("/view_transitions/basic-vt-opt-in.html"));
198 ASSERT_TRUE(NavigateToURL(shell()->web_contents(), test_url));
199
200 GURL test_url_next(embedded_test_server()->GetURL(
201 "/view_transitions/basic-vt-opt-in.html?next"));
202 ASSERT_TRUE(NavigateToURL(shell()->web_contents(), test_url_next));
203 WaitForCopyableViewInWebContents(shell()->web_contents());
204
205 EXPECT_EQ(false, EvalJs(shell()->web_contents(), "had_incoming_transition"));
206}
207
Khushal Sagar04e638c4b2024-05-13 17:43:11208class ViewTransitionDownloadBrowserTest : public ViewTransitionBrowserTest {
209 public:
210 void SetUpOnMainThread() override {
211 ViewTransitionBrowserTest::SetUpOnMainThread();
212
213 // Set up a test download directory, in order to prevent prompting for
214 // handling downloads.
215 ASSERT_TRUE(downloads_directory_.CreateUniqueTempDir());
216 ShellDownloadManagerDelegate* delegate =
217 static_cast<ShellDownloadManagerDelegate*>(
218 shell()
219 ->web_contents()
220 ->GetBrowserContext()
221 ->GetDownloadManagerDelegate());
222 delegate->SetDownloadBehaviorForTesting(downloads_directory_.GetPath());
223 }
224
225 private:
226 base::ScopedTempDir downloads_directory_;
227};
228
229IN_PROC_BROWSER_TEST_F(ViewTransitionDownloadBrowserTest,
230 NavigationToDownloadLink) {
231 GURL test_url(
232 embedded_test_server()->GetURL("/view_transitions/basic-vt-opt-in.html"));
233 ASSERT_TRUE(NavigateToURL(shell()->web_contents(), test_url));
234 WaitForCopyableViewInWebContents(shell()->web_contents());
235
236 GURL download_url(embedded_test_server()->GetURL("/download-test1.lib"));
237 TestNavigationManager navigation_manager(shell()->web_contents(),
238 download_url);
239 ASSERT_TRUE(ExecJs(shell()->web_contents(),
240 JsReplace("location.href = $1", download_url)));
241
242 // Wait for response and resume. The navigation should not be blocked by the
243 // view transition condition.
244 ASSERT_TRUE(navigation_manager.WaitForRequestStart());
245
246 ASSERT_TRUE(HasVTOptIn(shell()->web_contents()->GetPrimaryMainFrame()));
247 auto* navigation_request =
248 NavigationRequest::From(navigation_manager.GetNavigationHandle());
249 ASSERT_EQ(
250 shell()->web_contents()->GetPrimaryMainFrame()->GetLastCommittedOrigin(),
251 navigation_request->GetTentativeOriginAtRequestTime());
252
253 ASSERT_TRUE(navigation_manager.WaitForNavigationFinished());
254 ASSERT_FALSE(navigation_manager.was_committed());
255}
256
Khushal Sagar4e5aec432024-03-14 23:12:46257class ViewTransitionBrowserTestTraverse
258 : public ViewTransitionBrowserTest,
259 public testing::WithParamInterface<bool> {
260 public:
261 bool BFCacheEnabled() const { return GetParam(); }
262
David Bokan5ae9e452024-03-26 16:23:59263 bool NavigateBack(GURL back_url, WebContents* contents = nullptr) {
264 if (!contents) {
265 contents = shell()->web_contents();
266 }
Khushal Sagar4e5aec432024-03-14 23:12:46267 // We need to trigger the navigation *after* executing the script below so
268 // the event handlers the script relies on are set before they're dispatched
269 // by the navigation.
270 //
271 // We pass this as a callback to EvalJs so the navigation is initiated
272 // before we wait for the script result since it relies on events dispatched
273 // during the navigation.
274 auto trigger_navigation = base::BindOnce(
275 &ViewTransitionBrowserTestTraverse::TriggerBackNavigation,
David Bokan5ae9e452024-03-26 16:23:59276 base::Unretained(this), back_url, contents);
Khushal Sagar4e5aec432024-03-14 23:12:46277
278 auto result =
David Bokan5ae9e452024-03-26 16:23:59279 EvalJs(contents,
Khushal Sagar4e5aec432024-03-14 23:12:46280 JsReplace(
281 R"(
282 (async () => {
283 let navigateFired = false;
284 navigation.onnavigate = (event) => {
285 navigateFired = (event.navigationType === "traverse");
286 };
287 let pageswapfired = new Promise((resolve) => {
288 onpageswap = (e) => {
289 if (!navigateFired || e.viewTransition == null) {
290 resolve(null);
291 return;
292 }
293 activation = e.activation;
294 resolve(activation);
295 };
296 });
297 let result = await pageswapfired;
298 return result != null;
299 })();
300 )"),
301 EXECUTE_SCRIPT_DEFAULT_OPTIONS, ISOLATED_WORLD_ID_GLOBAL,
302 std::move(trigger_navigation));
303 return result.ExtractBool();
304 }
305
David Bokan5ae9e452024-03-26 16:23:59306 void TriggerBackNavigation(GURL back_url, WebContents* web_contents) {
Khushal Sagar4e5aec432024-03-14 23:12:46307 if (BFCacheEnabled()) {
David Bokan5ae9e452024-03-26 16:23:59308 TestActivationManager manager(web_contents, back_url);
309 web_contents->GetController().GoBack();
Khushal Sagar4e5aec432024-03-14 23:12:46310 manager.WaitForNavigationFinished();
311 } else {
David Bokan5ae9e452024-03-26 16:23:59312 TestNavigationManager manager(web_contents, back_url);
313 web_contents->GetController().GoBack();
Khushal Sagar4e5aec432024-03-14 23:12:46314 ASSERT_TRUE(manager.WaitForNavigationFinished());
315 }
316 }
317};
318
319IN_PROC_BROWSER_TEST_P(ViewTransitionBrowserTestTraverse,
320 NavigateEventFiresBeforeCapture) {
321 if (!BFCacheEnabled()) {
322 DisableBackForwardCacheForTesting(
323 shell()->web_contents(),
324 BackForwardCache::DisableForTestingReason::TEST_REQUIRES_NO_CACHING);
325 } else if (!base::FeatureList::IsEnabled(features::kBackForwardCache)) {
326 GTEST_SKIP();
327 }
328
329 GURL test_url(
330 embedded_test_server()->GetURL("/view_transitions/basic-vt-opt-in.html"));
331 ASSERT_TRUE(NavigateToURL(shell()->web_contents(), test_url));
Khushal Sagar4e5aec432024-03-14 23:12:46332 GURL second_url(embedded_test_server()->GetURL(
333 "/view_transitions/basic-vt-opt-in.html?new"));
334 ASSERT_TRUE(NavigateToURL(shell()->web_contents(), second_url));
335 WaitForCopyableViewInWebContents(shell()->web_contents());
Khushal Sagar4e5aec432024-03-14 23:12:46336 auto& nav_controller = static_cast<NavigationControllerImpl&>(
337 shell()->web_contents()->GetController());
338 ASSERT_TRUE(nav_controller.CanGoBack());
339 ASSERT_TRUE(NavigateBack(test_url));
340}
341
David Bokan5ae9e452024-03-26 16:23:59342// A session restore (e.g. "Duplicate Tab", "Undo Close Tab") uses RESTORE
343// navigation types when traversing the session history. Ensure these
344// navigations trigger a view transition.
345IN_PROC_BROWSER_TEST_P(ViewTransitionBrowserTestTraverse,
346 TransitionOnSessionRestoreTraversal) {
347 // A restored session will never have its session history in BFCache so
348 // there's no need to run a BFCache version of the test.
349 if (BFCacheEnabled()) {
350 GTEST_SKIP();
351 }
352
353 // Start with a page which has an opt-in for VT.
354 GURL url_a(
355 embedded_test_server()->GetURL("/view_transitions/basic-vt-opt-in.html"));
356 ASSERT_TRUE(NavigateToURL(shell()->web_contents(), url_a));
357
358 // Navigate to another page with an opt-in. (There's no transition due to
359 // being browser-initiated)
360 GURL url_b(embedded_test_server()->GetURL(
361 "/view_transitions/basic-vt-opt-in.html?next"));
362 ASSERT_TRUE(NavigateToURL(shell()->web_contents(), url_b));
363
364 // Clone the tab and load the page. Note: the cloned web contents must be put
365 // into a window to generate BeginFrames which are required since a view
366 // transition will not trigger unless a frame has been generated and the page
367 // revealed.
368 std::unique_ptr<WebContents> new_tab = shell()->web_contents()->Clone();
369 WebContentsImpl* new_tab_impl = static_cast<WebContentsImpl*>(new_tab.get());
370 shell()->AddNewContents(nullptr, std::move(new_tab), url_b,
371 WindowOpenDisposition::NEW_FOREGROUND_TAB,
372 blink::mojom::WindowFeatures(), false, nullptr);
373 NavigationController& new_controller = new_tab_impl->GetController();
374
375 {
376 TestNavigationObserver clone_observer(new_tab_impl);
377 new_controller.LoadIfNecessary();
378 clone_observer.Wait();
379 }
380
381 // Ensure the page has been revealed before navigating back so that a
382 // transition will be triggered.
383 WaitForCopyableViewInWebContents(new_tab_impl);
384
385 // TODO(crbug.com/331226127) Intentionally ignore the return value as the
386 // navigation API (erroneously?) doesn't fire events for restored traversals.
387 NavigateBack(url_a, new_tab_impl);
388
389 // Ensure a frame has been generated so that the reveal event would have been
390 // fired.
391 WaitForCopyableViewInWebContents(new_tab_impl);
392
393 EXPECT_EQ(true, EvalJs(new_tab_impl, "had_incoming_transition"));
394}
395
Khushal Sagar4e5aec432024-03-14 23:12:46396INSTANTIATE_TEST_SUITE_P(P,
397 ViewTransitionBrowserTestTraverse,
398 ::testing::Bool());
399
Noam Rosenthal09dd02b2024-09-21 21:05:52400class ViewTransitionCaptureTest
401 : public ContentBrowserTest,
402 public ::testing::WithParamInterface<std::string> {
403 public:
Vladimir Levin20103b02025-01-30 15:07:38404 ViewTransitionCaptureTest() {
Vladimir Levin8bab1b82025-03-28 20:37:01405 EnablePixelOutput(1.f);
Vladimir Levin20103b02025-01-30 15:07:38406 feature_list_.InitWithFeatures(
407 /*enabled_features=*/
Vladimir Levin32888ea2025-03-11 17:20:48408 {viz::mojom::EnableVizTestApis,
409 features::kViewTransitionCaptureAndDisplay},
Vladimir Levin8bab1b82025-03-28 20:37:01410 /*disabled_features=*/
411 {blink::features::kPaintHolding});
Vladimir Levin20103b02025-01-30 15:07:38412 }
Noam Rosenthal09dd02b2024-09-21 21:05:52413
414 void SetUpOnMainThread() override {
415 ContentBrowserTest::SetUpOnMainThread();
416 host_resolver()->AddRule("*", "127.0.0.1");
417 embedded_test_server()->ServeFilesFromSourceDirectory(
418 GetTestDataFilePath());
419 net::test_server::RegisterDefaultHandlers(embedded_test_server());
420 ASSERT_TRUE(embedded_test_server()->Start());
421 }
422
423 protected:
424 SkBitmap TakeScreenshot() {
Noam Rosenthal09dd02b2024-09-21 21:05:52425 base::test::TestFuture<const SkBitmap&> future_bitmap;
426 shell()->web_contents()->GetRenderWidgetHostView()->CopyFromSurface(
427 gfx::Rect(), gfx::Size(), future_bitmap.GetCallback());
428 return future_bitmap.Take();
429 }
430
Vladimir Levin20103b02025-01-30 15:07:38431 void WaitForSurfaceAnimationManager(RenderFrameHost* render_frame_host) {
432 mojo::ScopedAllowSyncCallForTesting sync_scope;
433 GetHostFrameSinkManager()
434 ->GetFrameSinkManagerTestApi()
435 .WaitForSurfaceAnimationManager(
436 static_cast<RenderFrameHostImpl*>(render_frame_host)
437 ->GetRenderWidgetHost()
438 ->GetFrameSinkId());
439 }
440
Noam Rosenthal09dd02b2024-09-21 21:05:52441 private:
442 base::test::ScopedFeatureList feature_list_;
443};
444
445IN_PROC_BROWSER_TEST_P(ViewTransitionCaptureTest,
Vladimir Levinbe48b9c2025-02-25 16:00:47446 ViewTransitionNoArtifactDuringCapture) {
Noam Rosenthal09dd02b2024-09-21 21:05:52447 GURL test_url(embedded_test_server()->GetURL(GetParam()));
448 auto* web_contents = shell()->web_contents();
Noam Rosenthal09dd02b2024-09-21 21:05:52449 ASSERT_TRUE(NavigateToURL(web_contents, test_url));
Vladimir Levin8bab1b82025-03-28 20:37:01450 shell()->ResizeWebContentForTests(gfx::Size(20, 20));
451
Noam Rosenthal09dd02b2024-09-21 21:05:52452 ASSERT_EQ(EvalJs(web_contents, JsReplace(R"(
453 new Promise(resolve => {
Vladimir Levin8bab1b82025-03-28 20:37:01454 requestAnimationFrame(() => {
455 requestAnimationFrame(() => resolve("ok"));
456 });
Noam Rosenthal09dd02b2024-09-21 21:05:52457 }))")),
458 "ok");
Vladimir Levin20103b02025-01-30 15:07:38459 WaitForCopyableViewInWebContents(shell()->web_contents());
Noam Rosenthal09dd02b2024-09-21 21:05:52460 SkBitmap before_bitmap = TakeScreenshot();
461
462 // Sanity to see that we've captured something.
463 ASSERT_NE(before_bitmap.getColor(5, 5), 0u);
Vladimir Levinb3497a92025-03-27 21:21:22464 // This starts a view transition with a callback that signals that we're ok
465 // to capture, but otherwise never finishes running the callback.
Noam Rosenthal09dd02b2024-09-21 21:05:52466 ASSERT_EQ(EvalJs(web_contents, JsReplace(R"(
Vladimir Levinb3497a92025-03-27 21:21:22467 new Promise(dom_callback_started => {
468 document.startViewTransition(async () => {
469 dom_callback_started('ok');
470 await new Promise(() => {});
471 });
Noam Rosenthal09dd02b2024-09-21 21:05:52472 }))")),
473 "ok");
Vladimir Levin20103b02025-01-30 15:07:38474 WaitForSurfaceAnimationManager(
475 shell()->web_contents()->GetPrimaryMainFrame());
Noam Rosenthal09dd02b2024-09-21 21:05:52476 auto after_bitmap = TakeScreenshot();
477 ASSERT_EQ(before_bitmap.width(), after_bitmap.width());
478 ASSERT_EQ(before_bitmap.height(), after_bitmap.height());
Vladimir Levinbe48b9c2025-02-25 16:00:47479
Vladimir Levinbe48b9c2025-02-25 16:00:47480 cc::FuzzyPixelComparator comparator;
481 // Allow 50% of pixels to different by at most 3 in all channels.
Vladimir Levine9b048c2025-02-26 18:50:33482 // The small differences on some platforms seem to be noise, and don't
483 // invalidate the test intent.
Vladimir Levinbe48b9c2025-02-25 16:00:47484 comparator.SetAbsErrorLimit(255, 3);
485 comparator.SetErrorPixelsPercentageLimit(0, 50);
486 EXPECT_TRUE(cc::MatchesBitmap(after_bitmap, before_bitmap, comparator));
Noam Rosenthal09dd02b2024-09-21 21:05:52487}
488
489INSTANTIATE_TEST_SUITE_P(
490 P,
491 ViewTransitionCaptureTest,
492 testing::Values("/view_transitions/parent-child.html",
493 "/view_transitions/parent-child-opacity.html"));
494
Khushal Sagar91b544222024-03-12 17:36:59495} // namespace content