blob: 972192f19921bef618885da5b1a0ed1008cde2f2 [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=*/
Vladimir Levin5922b4ae2025-05-15 21:14:5256 {viz::mojom::EnableVizTestApis},
Khushal Sagar91b544222024-03-12 17:36:5957 /*disabled_features=*/{});
58 }
59
60 void SetUpOnMainThread() override {
61 ContentBrowserTest::SetUpOnMainThread();
62 host_resolver()->AddRule("*", "127.0.0.1");
63 embedded_test_server()->ServeFilesFromSourceDirectory(
64 GetTestDataFilePath());
65 net::test_server::RegisterDefaultHandlers(embedded_test_server());
66 ASSERT_TRUE(embedded_test_server()->Start());
67 }
68
69 void WaitForConditionsDone(NavigationRequest* request) {
70 // Inject a condition to know when the VT response has been received but
71 // before the NavigationRequest is notified.
72 run_loop_ = std::make_unique<base::RunLoop>();
73 request->RegisterCommitDeferringConditionForTesting(
74 std::make_unique<TestCondition>(*request, run_loop_.get()));
75 run_loop_->Run();
76 }
77
Khushal Sagar04e638c4b2024-05-13 17:43:1178 bool HasVTOptIn(RenderFrameHost* rfh) {
79 auto* opt_in_state = ViewTransitionOptInState::GetForCurrentDocument(
80 static_cast<RenderFrameHostImpl*>(rfh));
81 return opt_in_state &&
82 opt_in_state->same_origin_opt_in() ==
83 blink::mojom::ViewTransitionSameOriginOptIn::kEnabled;
84 }
85
Khushal Sagar91b544222024-03-12 17:36:5986 private:
87 base::test::ScopedFeatureList feature_list_;
88 std::unique_ptr<base::RunLoop> run_loop_;
89};
90
91IN_PROC_BROWSER_TEST_F(ViewTransitionBrowserTest,
Michael Thiessena74065a32024-06-28 18:48:3592 NavigationCancelledAfterScreenshot) {
Khushal Sagar91b544222024-03-12 17:36:5993 // Start with a page which has an opt-in for VT.
94 GURL test_url(
95 embedded_test_server()->GetURL("/view_transitions/basic-vt-opt-in.html"));
96 ASSERT_TRUE(NavigateToURL(shell()->web_contents(), test_url));
Michael Thiessena74065a32024-06-28 18:48:3597 WaitForCopyableViewInWebContents(shell()->web_contents());
Khushal Sagar91b544222024-03-12 17:36:5998
99 TestNavigationManager navigation_manager(shell()->web_contents(), test_url);
100 ASSERT_TRUE(
101 ExecJs(shell()->web_contents(), "location.href = location.href;"));
102
103 // Wait for response and resume. The navigation should be blocked by the view
104 // transition condition.
105 ASSERT_TRUE(navigation_manager.WaitForResponse());
106 navigation_manager.ResumeNavigation();
107
108 auto* navigation_request =
109 NavigationRequest::From(navigation_manager.GetNavigationHandle());
110 ASSERT_TRUE(navigation_request);
111 ASSERT_TRUE(
112 navigation_request->IsCommitDeferringConditionDeferredForTesting());
113 ASSERT_FALSE(navigation_request->commit_params().view_transition_state);
114
115 WaitForConditionsDone(navigation_request);
116 ASSERT_TRUE(navigation_request->commit_params().view_transition_state);
117
118 mojo::ScopedAllowSyncCallForTesting allow_sync;
119
Zoraiz Naeemfe5b6552024-08-08 19:49:03120 bool has_resources = false;
121 GetHostFrameSinkManager()
122 ->GetFrameSinkManagerTestApi()
123 .HasUnclaimedViewTransitionResources(&has_resources);
124 ASSERT_TRUE(has_resources);
Khushal Sagar91b544222024-03-12 17:36:59125
126 shell()->web_contents()->Stop();
127 ASSERT_FALSE(navigation_manager.was_committed());
Zoraiz Naeemfe5b6552024-08-08 19:49:03128 GetHostFrameSinkManager()
129 ->GetFrameSinkManagerTestApi()
130 .HasUnclaimedViewTransitionResources(&has_resources);
131 ASSERT_FALSE(has_resources);
Khushal Sagarf1b0c972024-03-27 15:31:51132
133 // Ensure the old renderer discards the outgoing transition.
134 EXPECT_TRUE(ExecJs(
135 shell()->web_contents()->GetPrimaryMainFrame(),
136 "(async () => { await document.startViewTransition().ready; })()"));
137}
138
139IN_PROC_BROWSER_TEST_F(ViewTransitionBrowserTest,
140 NavigationCancelledBeforeScreenshot) {
141 // Start with a page which has an opt-in for VT.
142 GURL test_url(
143 embedded_test_server()->GetURL("/view_transitions/basic-vt-opt-in.html"));
144 ASSERT_TRUE(NavigateToURL(shell()->web_contents(), test_url));
Michael Thiessena74065a32024-06-28 18:48:35145 WaitForCopyableViewInWebContents(shell()->web_contents());
Khushal Sagarf1b0c972024-03-27 15:31:51146
147 TestNavigationManager navigation_manager(shell()->web_contents(), test_url);
148 ASSERT_TRUE(
149 ExecJs(shell()->web_contents(), "location.href = location.href;"));
150
151 // Wait for response and resume. The navigation should be blocked by the view
152 // transition condition.
153 ASSERT_TRUE(navigation_manager.WaitForResponse());
154 navigation_manager.ResumeNavigation();
155
156 auto* navigation_request =
157 NavigationRequest::From(navigation_manager.GetNavigationHandle());
158 ASSERT_TRUE(navigation_request);
159 ASSERT_TRUE(
160 navigation_request->IsCommitDeferringConditionDeferredForTesting());
161 ASSERT_FALSE(navigation_request->commit_params().view_transition_state);
162
163 // Stop the navigation while the screenshot request is in flight.
164 shell()->web_contents()->Stop();
165 ASSERT_FALSE(navigation_manager.was_committed());
166
167 // Ensure the old renderer discards the outgoing transition.
168 EXPECT_TRUE(ExecJs(
169 shell()->web_contents()->GetPrimaryMainFrame(),
170 "(async () => { await document.startViewTransition().ready; })()"));
Khushal Sagar91b544222024-03-12 17:36:59171}
172
173IN_PROC_BROWSER_TEST_F(ViewTransitionBrowserTest,
Gaston Rodriguez318b7392024-06-28 14:51:17174 OwnershipTransferredToNewRenderer) {
Khushal Sagar91b544222024-03-12 17:36:59175 // Start with a page which has an opt-in for VT.
176 GURL test_url(
177 embedded_test_server()->GetURL("/view_transitions/basic-vt-opt-in.html"));
178 ASSERT_TRUE(NavigateToURL(shell()->web_contents(), test_url));
Michael Thiessen517e2542024-06-27 14:02:44179 WaitForCopyableViewInWebContents(shell()->web_contents());
Khushal Sagar91b544222024-03-12 17:36:59180
181 TestNavigationManager navigation_manager(shell()->web_contents(), test_url);
182 ASSERT_TRUE(
183 ExecJs(shell()->web_contents(), "location.href = location.href;"));
184 ASSERT_TRUE(navigation_manager.WaitForNavigationFinished());
185 ASSERT_TRUE(static_cast<RenderWidgetHostViewBase*>(
186 shell()->web_contents()->GetRenderWidgetHostView())
187 ->HasViewTransitionResourcesForTesting());
188}
189
David Bokan5ae9e452024-03-26 16:23:59190// Ensure a browser-initiated navigation (i.e. typing URL into omnibox) does
191// not trigger a view transitions.
192IN_PROC_BROWSER_TEST_F(ViewTransitionBrowserTest,
193 NoOpOnBrowserInitiatedNavigations) {
194 // Start with a page which has an opt-in for VT.
195 GURL test_url(
196 embedded_test_server()->GetURL("/view_transitions/basic-vt-opt-in.html"));
197 ASSERT_TRUE(NavigateToURL(shell()->web_contents(), test_url));
198
199 GURL test_url_next(embedded_test_server()->GetURL(
200 "/view_transitions/basic-vt-opt-in.html?next"));
201 ASSERT_TRUE(NavigateToURL(shell()->web_contents(), test_url_next));
202 WaitForCopyableViewInWebContents(shell()->web_contents());
203
204 EXPECT_EQ(false, EvalJs(shell()->web_contents(), "had_incoming_transition"));
205}
206
Khushal Sagar04e638c4b2024-05-13 17:43:11207class ViewTransitionDownloadBrowserTest : public ViewTransitionBrowserTest {
208 public:
209 void SetUpOnMainThread() override {
210 ViewTransitionBrowserTest::SetUpOnMainThread();
211
212 // Set up a test download directory, in order to prevent prompting for
213 // handling downloads.
214 ASSERT_TRUE(downloads_directory_.CreateUniqueTempDir());
215 ShellDownloadManagerDelegate* delegate =
216 static_cast<ShellDownloadManagerDelegate*>(
217 shell()
218 ->web_contents()
219 ->GetBrowserContext()
220 ->GetDownloadManagerDelegate());
221 delegate->SetDownloadBehaviorForTesting(downloads_directory_.GetPath());
222 }
223
224 private:
225 base::ScopedTempDir downloads_directory_;
226};
227
228IN_PROC_BROWSER_TEST_F(ViewTransitionDownloadBrowserTest,
229 NavigationToDownloadLink) {
230 GURL test_url(
231 embedded_test_server()->GetURL("/view_transitions/basic-vt-opt-in.html"));
232 ASSERT_TRUE(NavigateToURL(shell()->web_contents(), test_url));
233 WaitForCopyableViewInWebContents(shell()->web_contents());
234
235 GURL download_url(embedded_test_server()->GetURL("/download-test1.lib"));
236 TestNavigationManager navigation_manager(shell()->web_contents(),
237 download_url);
238 ASSERT_TRUE(ExecJs(shell()->web_contents(),
239 JsReplace("location.href = $1", download_url)));
240
241 // Wait for response and resume. The navigation should not be blocked by the
242 // view transition condition.
243 ASSERT_TRUE(navigation_manager.WaitForRequestStart());
244
245 ASSERT_TRUE(HasVTOptIn(shell()->web_contents()->GetPrimaryMainFrame()));
246 auto* navigation_request =
247 NavigationRequest::From(navigation_manager.GetNavigationHandle());
248 ASSERT_EQ(
249 shell()->web_contents()->GetPrimaryMainFrame()->GetLastCommittedOrigin(),
250 navigation_request->GetTentativeOriginAtRequestTime());
251
252 ASSERT_TRUE(navigation_manager.WaitForNavigationFinished());
253 ASSERT_FALSE(navigation_manager.was_committed());
254}
255
Khushal Sagar4e5aec432024-03-14 23:12:46256class ViewTransitionBrowserTestTraverse
257 : public ViewTransitionBrowserTest,
258 public testing::WithParamInterface<bool> {
259 public:
260 bool BFCacheEnabled() const { return GetParam(); }
261
David Bokan5ae9e452024-03-26 16:23:59262 bool NavigateBack(GURL back_url, WebContents* contents = nullptr) {
263 if (!contents) {
264 contents = shell()->web_contents();
265 }
Khushal Sagar4e5aec432024-03-14 23:12:46266 // We need to trigger the navigation *after* executing the script below so
267 // the event handlers the script relies on are set before they're dispatched
268 // by the navigation.
269 //
270 // We pass this as a callback to EvalJs so the navigation is initiated
271 // before we wait for the script result since it relies on events dispatched
272 // during the navigation.
273 auto trigger_navigation = base::BindOnce(
274 &ViewTransitionBrowserTestTraverse::TriggerBackNavigation,
David Bokan5ae9e452024-03-26 16:23:59275 base::Unretained(this), back_url, contents);
Khushal Sagar4e5aec432024-03-14 23:12:46276
277 auto result =
David Bokan5ae9e452024-03-26 16:23:59278 EvalJs(contents,
Khushal Sagar4e5aec432024-03-14 23:12:46279 JsReplace(
280 R"(
281 (async () => {
282 let navigateFired = false;
283 navigation.onnavigate = (event) => {
284 navigateFired = (event.navigationType === "traverse");
285 };
286 let pageswapfired = new Promise((resolve) => {
287 onpageswap = (e) => {
288 if (!navigateFired || e.viewTransition == null) {
289 resolve(null);
290 return;
291 }
292 activation = e.activation;
293 resolve(activation);
294 };
295 });
296 let result = await pageswapfired;
297 return result != null;
298 })();
299 )"),
300 EXECUTE_SCRIPT_DEFAULT_OPTIONS, ISOLATED_WORLD_ID_GLOBAL,
301 std::move(trigger_navigation));
302 return result.ExtractBool();
303 }
304
David Bokan5ae9e452024-03-26 16:23:59305 void TriggerBackNavigation(GURL back_url, WebContents* web_contents) {
Khushal Sagar4e5aec432024-03-14 23:12:46306 if (BFCacheEnabled()) {
David Bokan5ae9e452024-03-26 16:23:59307 TestActivationManager manager(web_contents, back_url);
308 web_contents->GetController().GoBack();
Khushal Sagar4e5aec432024-03-14 23:12:46309 manager.WaitForNavigationFinished();
310 } else {
David Bokan5ae9e452024-03-26 16:23:59311 TestNavigationManager manager(web_contents, back_url);
312 web_contents->GetController().GoBack();
Khushal Sagar4e5aec432024-03-14 23:12:46313 ASSERT_TRUE(manager.WaitForNavigationFinished());
314 }
315 }
316};
317
318IN_PROC_BROWSER_TEST_P(ViewTransitionBrowserTestTraverse,
319 NavigateEventFiresBeforeCapture) {
320 if (!BFCacheEnabled()) {
321 DisableBackForwardCacheForTesting(
322 shell()->web_contents(),
323 BackForwardCache::DisableForTestingReason::TEST_REQUIRES_NO_CACHING);
324 } else if (!base::FeatureList::IsEnabled(features::kBackForwardCache)) {
325 GTEST_SKIP();
326 }
327
328 GURL test_url(
329 embedded_test_server()->GetURL("/view_transitions/basic-vt-opt-in.html"));
330 ASSERT_TRUE(NavigateToURL(shell()->web_contents(), test_url));
Khushal Sagar4e5aec432024-03-14 23:12:46331 GURL second_url(embedded_test_server()->GetURL(
332 "/view_transitions/basic-vt-opt-in.html?new"));
333 ASSERT_TRUE(NavigateToURL(shell()->web_contents(), second_url));
334 WaitForCopyableViewInWebContents(shell()->web_contents());
Khushal Sagar4e5aec432024-03-14 23:12:46335 auto& nav_controller = static_cast<NavigationControllerImpl&>(
336 shell()->web_contents()->GetController());
337 ASSERT_TRUE(nav_controller.CanGoBack());
338 ASSERT_TRUE(NavigateBack(test_url));
339}
340
David Bokan5ae9e452024-03-26 16:23:59341// A session restore (e.g. "Duplicate Tab", "Undo Close Tab") uses RESTORE
342// navigation types when traversing the session history. Ensure these
343// navigations trigger a view transition.
344IN_PROC_BROWSER_TEST_P(ViewTransitionBrowserTestTraverse,
345 TransitionOnSessionRestoreTraversal) {
346 // A restored session will never have its session history in BFCache so
347 // there's no need to run a BFCache version of the test.
348 if (BFCacheEnabled()) {
349 GTEST_SKIP();
350 }
351
352 // Start with a page which has an opt-in for VT.
353 GURL url_a(
354 embedded_test_server()->GetURL("/view_transitions/basic-vt-opt-in.html"));
355 ASSERT_TRUE(NavigateToURL(shell()->web_contents(), url_a));
356
357 // Navigate to another page with an opt-in. (There's no transition due to
358 // being browser-initiated)
359 GURL url_b(embedded_test_server()->GetURL(
360 "/view_transitions/basic-vt-opt-in.html?next"));
361 ASSERT_TRUE(NavigateToURL(shell()->web_contents(), url_b));
362
363 // Clone the tab and load the page. Note: the cloned web contents must be put
364 // into a window to generate BeginFrames which are required since a view
365 // transition will not trigger unless a frame has been generated and the page
366 // revealed.
367 std::unique_ptr<WebContents> new_tab = shell()->web_contents()->Clone();
368 WebContentsImpl* new_tab_impl = static_cast<WebContentsImpl*>(new_tab.get());
369 shell()->AddNewContents(nullptr, std::move(new_tab), url_b,
370 WindowOpenDisposition::NEW_FOREGROUND_TAB,
371 blink::mojom::WindowFeatures(), false, nullptr);
372 NavigationController& new_controller = new_tab_impl->GetController();
373
374 {
375 TestNavigationObserver clone_observer(new_tab_impl);
376 new_controller.LoadIfNecessary();
377 clone_observer.Wait();
378 }
379
380 // Ensure the page has been revealed before navigating back so that a
381 // transition will be triggered.
382 WaitForCopyableViewInWebContents(new_tab_impl);
383
384 // TODO(crbug.com/331226127) Intentionally ignore the return value as the
385 // navigation API (erroneously?) doesn't fire events for restored traversals.
386 NavigateBack(url_a, new_tab_impl);
387
388 // Ensure a frame has been generated so that the reveal event would have been
389 // fired.
390 WaitForCopyableViewInWebContents(new_tab_impl);
391
392 EXPECT_EQ(true, EvalJs(new_tab_impl, "had_incoming_transition"));
393}
394
Khushal Sagar4e5aec432024-03-14 23:12:46395INSTANTIATE_TEST_SUITE_P(P,
396 ViewTransitionBrowserTestTraverse,
397 ::testing::Bool());
398
Noam Rosenthal09dd02b2024-09-21 21:05:52399class ViewTransitionCaptureTest
400 : public ContentBrowserTest,
Vladimir Levin61a545c62025-04-01 15:26:00401 public ::testing::WithParamInterface<std::pair<bool, std::string>> {
Noam Rosenthal09dd02b2024-09-21 21:05:52402 public:
Vladimir Levin20103b02025-01-30 15:07:38403 ViewTransitionCaptureTest() {
Vladimir Levin8bab1b82025-03-28 20:37:01404 EnablePixelOutput(1.f);
Vladimir Levin20103b02025-01-30 15:07:38405 feature_list_.InitWithFeatures(
406 /*enabled_features=*/
Vladimir Levin32888ea2025-03-11 17:20:48407 {viz::mojom::EnableVizTestApis,
408 features::kViewTransitionCaptureAndDisplay},
Vladimir Levin8bab1b82025-03-28 20:37:01409 /*disabled_features=*/
410 {blink::features::kPaintHolding});
Vladimir Levin20103b02025-01-30 15:07:38411 }
Noam Rosenthal09dd02b2024-09-21 21:05:52412
413 void SetUpOnMainThread() override {
414 ContentBrowserTest::SetUpOnMainThread();
415 host_resolver()->AddRule("*", "127.0.0.1");
416 embedded_test_server()->ServeFilesFromSourceDirectory(
417 GetTestDataFilePath());
418 net::test_server::RegisterDefaultHandlers(embedded_test_server());
419 ASSERT_TRUE(embedded_test_server()->Start());
420 }
421
422 protected:
423 SkBitmap TakeScreenshot() {
Noam Rosenthal09dd02b2024-09-21 21:05:52424 base::test::TestFuture<const SkBitmap&> future_bitmap;
425 shell()->web_contents()->GetRenderWidgetHostView()->CopyFromSurface(
426 gfx::Rect(), gfx::Size(), future_bitmap.GetCallback());
427 return future_bitmap.Take();
428 }
429
Vladimir Levin20103b02025-01-30 15:07:38430 void WaitForSurfaceAnimationManager(RenderFrameHost* render_frame_host) {
431 mojo::ScopedAllowSyncCallForTesting sync_scope;
432 GetHostFrameSinkManager()
433 ->GetFrameSinkManagerTestApi()
434 .WaitForSurfaceAnimationManager(
435 static_cast<RenderFrameHostImpl*>(render_frame_host)
436 ->GetRenderWidgetHost()
437 ->GetFrameSinkId());
438 }
439
Noam Rosenthal09dd02b2024-09-21 21:05:52440 private:
441 base::test::ScopedFeatureList feature_list_;
442};
443
Eriko Kurimotoc98272d2025-06-06 08:19:01444// TODO(https://crbug.com/400187507): Disabled due to continuous flakiness.
Noam Rosenthal09dd02b2024-09-21 21:05:52445IN_PROC_BROWSER_TEST_P(ViewTransitionCaptureTest,
Eriko Kurimotoc98272d2025-06-06 08:19:01446 DISABLED_ViewTransitionNoArtifactDuringCapture) {
Vladimir Levin61a545c62025-04-01 15:26:00447 const auto& [frametest, url] = GetParam();
448 GURL test_url(embedded_test_server()->GetURL(url));
Noam Rosenthal09dd02b2024-09-21 21:05:52449 auto* web_contents = shell()->web_contents();
Noam Rosenthal09dd02b2024-09-21 21:05:52450 ASSERT_TRUE(NavigateToURL(web_contents, test_url));
Vladimir Levin61a545c62025-04-01 15:26:00451 shell()->ResizeWebContentForTests(gfx::Size(100, 100));
Vladimir Levin8bab1b82025-03-28 20:37:01452
Noam Rosenthal09dd02b2024-09-21 21:05:52453 ASSERT_EQ(EvalJs(web_contents, JsReplace(R"(
454 new Promise(resolve => {
Vladimir Levin8bab1b82025-03-28 20:37:01455 requestAnimationFrame(() => {
456 requestAnimationFrame(() => resolve("ok"));
457 });
Noam Rosenthal09dd02b2024-09-21 21:05:52458 }))")),
459 "ok");
Vladimir Levin20103b02025-01-30 15:07:38460 WaitForCopyableViewInWebContents(shell()->web_contents());
Noam Rosenthal09dd02b2024-09-21 21:05:52461 SkBitmap before_bitmap = TakeScreenshot();
462
463 // Sanity to see that we've captured something.
464 ASSERT_NE(before_bitmap.getColor(5, 5), 0u);
Vladimir Levinb3497a92025-03-27 21:21:22465 // This starts a view transition with a callback that signals that we're ok
466 // to capture, but otherwise never finishes running the callback.
Vladimir Levin61a545c62025-04-01 15:26:00467 if (frametest) {
468 ASSERT_EQ(EvalJs(web_contents, JsReplace(R"(
469 new Promise(dom_callback_started => {
470 frame.contentDocument.startViewTransition(async () => {
471 dom_callback_started('ok');
472 await new Promise(() => {});
473 });
474 }))")),
475 "ok");
476 } else {
477 ASSERT_EQ(EvalJs(web_contents, JsReplace(R"(
478 new Promise(dom_callback_started => {
479 document.startViewTransition(async () => {
480 dom_callback_started('ok');
481 await new Promise(() => {});
482 });
483 }))")),
484 "ok");
485 }
Vladimir Levin20103b02025-01-30 15:07:38486 WaitForSurfaceAnimationManager(
487 shell()->web_contents()->GetPrimaryMainFrame());
Noam Rosenthal09dd02b2024-09-21 21:05:52488 auto after_bitmap = TakeScreenshot();
489 ASSERT_EQ(before_bitmap.width(), after_bitmap.width());
490 ASSERT_EQ(before_bitmap.height(), after_bitmap.height());
Vladimir Levinbe48b9c2025-02-25 16:00:47491
Vladimir Levinbe48b9c2025-02-25 16:00:47492 cc::FuzzyPixelComparator comparator;
493 // Allow 50% of pixels to different by at most 3 in all channels.
Vladimir Levine9b048c2025-02-26 18:50:33494 // The small differences on some platforms seem to be noise, and don't
495 // invalidate the test intent.
Vladimir Levinbe48b9c2025-02-25 16:00:47496 comparator.SetAbsErrorLimit(255, 3);
497 comparator.SetErrorPixelsPercentageLimit(0, 50);
498 EXPECT_TRUE(cc::MatchesBitmap(after_bitmap, before_bitmap, comparator));
Noam Rosenthal09dd02b2024-09-21 21:05:52499}
500
501INSTANTIATE_TEST_SUITE_P(
502 P,
503 ViewTransitionCaptureTest,
Vladimir Levin61a545c62025-04-01 15:26:00504 // The pair parameter has the following meaning:
505 // - The first boolean indicates whether this test should invoke
506 // startViewTransition() on the `frame` DOM element's contentDocument (if
507 // true) or on the main frame's document (if false).
508 // - The second string indicates the location of the test to load.
509 testing::Values(
510 std::make_pair(false, "/view_transitions/parent-child.html"),
511 std::make_pair(false, "/view_transitions/parent-child-opacity.html"),
512 std::make_pair(true,
513 "/view_transitions/parent-child-opacity-iframe.html")));
Noam Rosenthal09dd02b2024-09-21 21:05:52514
Khushal Sagar91b544222024-03-12 17:36:59515} // namespace content