From 7c521eb16bb355dc41b34da2012dbf07329f5fcb Mon Sep 17 00:00:00 2001 From: Tim Nguyen Date: Wed, 20 Mar 2024 08:21:51 -0700 Subject: [PATCH] [view-transitions] Skip view transition on hidden pages https://bugs.webkit.org/show_bug.cgi?id=271248 rdar://125017653 Reviewed by NOBODY (OOPS!). Follow: - https://drafts.csswg.org/css-view-transitions-1/#page-visibility-change-steps - https://github.com/w3c/csswg-drafts/issues/9543 - https://github.com/w3c/csswg-drafts/pull/10815 * LayoutTests/TestExpectations: * LayoutTests/imported/w3c/web-platform-tests/css/css-view-transitions/transition-in-hidden-page-expected.txt: * LayoutTests/imported/w3c/web-platform-tests/css/css-view-transitions/transition-in-hidden-page.html: * LayoutTests/imported/w3c/web-platform-tests/css/css-view-transitions/window-resize-aborts-transition-before-ready.html: * LayoutTests/imported/w3c/web-platform-tests/css/css-view-transitions/window-resize-aborts-transition.html: * LayoutTests/platform/mac-wk2/TestExpectations: Remove duplicate test expectations to reduce confusion. * Source/WebCore/dom/Document.cpp: (WebCore::Document::visibilityStateChanged): Make this more robust to allow unregistering clients while iterating. (WebCore::Document::reveal): (WebCore::Document::clearInboundViewTransitionParams): (WebCore::Document::startViewTransition): * Source/WebCore/dom/ViewTransition.cpp: (WebCore::ViewTransition::ViewTransition): (WebCore::ViewTransition::skipViewTransition): (WebCore::ViewTransition::stop): (WebCore::ViewTransition::visibilityStateChanged): * Source/WebCore/dom/ViewTransition.h: --- LayoutTests/TestExpectations | 3 +-- .../transition-in-hidden-page-expected.txt | 6 +++--- .../transition-in-hidden-page.html | 2 +- ...-resize-aborts-transition-before-ready.html | 6 ++++++ .../window-resize-aborts-transition.html | 6 ++++++ LayoutTests/platform/mac-wk2/TestExpectations | 3 --- Source/WebCore/dom/Document.cpp | 18 +++++++++++++++--- Source/WebCore/dom/Document.h | 3 +++ Source/WebCore/dom/ViewTransition.cpp | 15 +++++++++++++++ Source/WebCore/dom/ViewTransition.h | 6 +++++- 10 files changed, 55 insertions(+), 13 deletions(-) diff --git a/LayoutTests/TestExpectations b/LayoutTests/TestExpectations index 9d57d87e657a4..6383f7da972ba 100644 --- a/LayoutTests/TestExpectations +++ b/LayoutTests/TestExpectations @@ -7468,12 +7468,10 @@ imported/w3c/web-platform-tests/css/css-view-transitions/paint-holding-in-iframe imported/w3c/web-platform-tests/css/css-view-transitions/iframe-transition.sub.html [ Skip ] imported/w3c/web-platform-tests/css/css-view-transitions/fragmented-during-transition-skips.html [ Skip ] imported/w3c/web-platform-tests/css/css-view-transitions/root-element-display-none-during-transition-crash.html [ Skip ] -imported/w3c/web-platform-tests/css/css-view-transitions/transition-in-hidden-page.html [ Skip ] # Flakes imported/w3c/web-platform-tests/css/css-view-transitions/synchronous-callback-skipped-before-run.html [ Failure Pass ] imported/w3c/web-platform-tests/css/css-view-transitions/old-content-intrinsic-aspect-ratio.html [ ImageOnlyFailure Pass ] -imported/w3c/web-platform-tests/css/css-view-transitions/navigation/hide-before-reveal.html [ Failure Pass ] imported/w3c/web-platform-tests/css/css-view-transitions/navigation/pageswap-ctor.html [ Failure Pass ] # Reftests with variants are not supported @@ -7494,6 +7492,7 @@ imported/w3c/web-platform-tests/css/css-view-transitions/navigation/chromium-pai imported/w3c/web-platform-tests/css/css-view-transitions/navigation/root-element-transition-iframe-with-startVT-on-main.html [ ImageOnlyFailure ] # https://github.com/w3c/csswg-drafts/issues/10800 imported/w3c/web-platform-tests/css/css-view-transitions/navigation/pageswap-long-delay.html [ Failure ] +webkit.org/b/278028 imported/w3c/web-platform-tests/css/css-view-transitions/navigation/hide-before-reveal.html [ Failure ] # prerender not supported. imported/w3c/web-platform-tests/css/css-view-transitions/navigation/prerender-removed-during-navigation.html [ Skip ] diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-view-transitions/transition-in-hidden-page-expected.txt b/LayoutTests/imported/w3c/web-platform-tests/css/css-view-transitions/transition-in-hidden-page-expected.txt index 717c895bb5251..cc322d58a8d6a 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/css/css-view-transitions/transition-in-hidden-page-expected.txt +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-view-transitions/transition-in-hidden-page-expected.txt @@ -1,5 +1,5 @@ -FAIL A view transition should be immediately skipped if started when document is hidden assert_unreached: Should have rejected: undefined Reached unreachable code -FAIL A view transition should be skipped when a document becomes hidden while processing update callback assert_equals: expected "rejected" but got "fulfilled" -FAIL A view transition should be skipped when a document becomes hidden while animating assert_equals: expected "finished" but got "timeout" +PASS A view transition should be immediately skipped if started when document is hidden +PASS A view transition should be skipped when a document becomes hidden while processing update callback +PASS A view transition should be skipped when a document becomes hidden while animating diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-view-transitions/transition-in-hidden-page.html b/LayoutTests/imported/w3c/web-platform-tests/css/css-view-transitions/transition-in-hidden-page.html index f23d30f96c590..c2d0044c69237 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/css/css-view-transitions/transition-in-hidden-page.html +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-view-transitions/transition-in-hidden-page.html @@ -21,8 +21,8 @@ await wsc.minimize(); assert_true(document.hidden); const transition = document.startViewTransition(); - await wsc.restore(); await promise_rejects_dom(t, "InvalidStateError", transition.ready); + await wsc.restore(); }, "A view transition should be immediately skipped if started when document is hidden"); promise_test(async t => { diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-view-transitions/window-resize-aborts-transition-before-ready.html b/LayoutTests/imported/w3c/web-platform-tests/css/css-view-transitions/window-resize-aborts-transition-before-ready.html index 28abd8452d867..590aa02cba88a 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/css/css-view-transitions/window-resize-aborts-transition-before-ready.html +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-view-transitions/window-resize-aborts-transition-before-ready.html @@ -34,6 +34,12 @@ popup_win = window.open('about:blank', 'popup', 'width=300,height=300'); }); + if (popup_win.document.visibilityState == "hidden") { + await new Promise((resolve) => { + popup_win.document.addEventListener("visibilitychange", resolve, { once: true }); + }); + } + // Resize the window while the update callback is running (i.e. before // capturing the new state). let transition = popup_win.document.startViewTransition(async () => { diff --git a/LayoutTests/imported/w3c/web-platform-tests/css/css-view-transitions/window-resize-aborts-transition.html b/LayoutTests/imported/w3c/web-platform-tests/css/css-view-transitions/window-resize-aborts-transition.html index fd83562316f51..9b799e070afc8 100644 --- a/LayoutTests/imported/w3c/web-platform-tests/css/css-view-transitions/window-resize-aborts-transition.html +++ b/LayoutTests/imported/w3c/web-platform-tests/css/css-view-transitions/window-resize-aborts-transition.html @@ -45,6 +45,12 @@ html::view-transition-old(*) {animation-duration: 10s;opacity: 1;} `; + if (popupDoc.visibilityState == "hidden") { + await new Promise((resolve) => { + popupDoc.addEventListener("visibilitychange", resolve, { once: true }); + }); + } + // Start a transition inside the popup. let transition = popupDoc.startViewTransition(() => { popupDoc.documentElement.classList.add('new'); diff --git a/LayoutTests/platform/mac-wk2/TestExpectations b/LayoutTests/platform/mac-wk2/TestExpectations index fa6bff0d18ae3..f084f6761ece5 100644 --- a/LayoutTests/platform/mac-wk2/TestExpectations +++ b/LayoutTests/platform/mac-wk2/TestExpectations @@ -1789,9 +1789,6 @@ imported/w3c/web-platform-tests/css/css-view-transitions/new-content-intrinsic-a tiled-drawing/scrolling/overflow/overflow-scrolled-down-tile-coverage.html [ Pass Failure ] tiled-drawing/scrolling/overflow/overflow-scrolled-up-tile-coverage.html [ Pass Failure ] -# rdar://133772823 ([Sonoma wk2] imported/w3c/web-platform-tests/css/css-view-transitions/navigation/hide-before-reveal.html is a flaky failure -[ Sonoma+ ] imported/w3c/web-platform-tests/css/css-view-transitions/navigation/hide-before-reveal.html [ Pass Failure ] - # webkit.org/b/278178 [macOS Debug] imported/w3c/web-platform-tests/css/cssom-view/scroll-behavior-smooth-positions.html is a flaky failure imported/w3c/web-platform-tests/css/cssom-view/scroll-behavior-smooth-positions.html [ Pass Failure ] diff --git a/Source/WebCore/dom/Document.cpp b/Source/WebCore/dom/Document.cpp index e402590a72ac8..7721a78d09d73 100644 --- a/Source/WebCore/dom/Document.cpp +++ b/Source/WebCore/dom/Document.cpp @@ -2343,8 +2343,9 @@ void Document::visibilityStateChanged() { // https://w3c.github.io/page-visibility/#reacting-to-visibilitychange-changes queueTaskToDispatchEvent(TaskSource::UserInteraction, Event::create(eventNames().visibilitychangeEvent, Event::CanBubble::Yes, Event::IsCancelable::No)); - for (auto& client : m_visibilityStateCallbackClients) + m_visibilityStateCallbackClients.forEach([](auto& client) { client.visibilityStateChanged(); + }); #if ENABLE(MEDIA_STREAM) && PLATFORM(IOS_FAMILY) if (auto mediaSessionManager = PlatformMediaSessionManager::sharedManagerIfExists()) { @@ -8068,7 +8069,7 @@ void Document::dispatchPagehideEvent(PageshowEventPersistence persisted) // https://www.w3.org/TR/css-view-transitions-2/#vt-rule-algo std::variant> Document::resolveViewTransitionRule() { - if (visibilityState() == VisibilityState::Hidden) + if (hidden()) return SkipTransition { }; auto rule = styleScope().resolver().viewTransitionRule(); @@ -8089,7 +8090,7 @@ void Document::reveal() PageRevealEvent::Init init; - RefPtr inboundTransition = ViewTransition::resolveInboundCrossDocumentViewTransition(*this, std::exchange(m_inboundViewTransitionParams, nullptr)); + RefPtr inboundTransition = ViewTransition::resolveInboundCrossDocumentViewTransition(*this, std::exchange(m_inboundViewTransitionParams, nullptr)); if (inboundTransition) init.viewTransition = inboundTransition; @@ -8110,6 +8111,11 @@ void Document::transferViewTransitionParams(Document& newDocument) newDocument.m_inboundViewTransitionParams = std::exchange(m_inboundViewTransitionParams, nullptr); } +void Document::clearInboundViewTransitionParams() +{ + m_inboundViewTransitionParams = nullptr; +} + void Document::dispatchPageswapEvent(bool canTriggerCrossDocumentViewTransition, RefPtr&& activation) { if (!settings().crossDocumentViewTransitionsEnabled()) @@ -10828,6 +10834,7 @@ void Document::flushDeferredRenderingIsSuppressedForViewTransitionChanges() } } +// https://drafts.csswg.org/css-view-transitions/#ViewTransition-prepare RefPtr Document::startViewTransition(StartViewTransitionCallbackOptions&& callbackOptions) { if (!globalObject()) @@ -10851,6 +10858,11 @@ RefPtr Document::startViewTransition(StartViewTransitionCallback Ref viewTransition = ViewTransition::createSamePage(*this, WTFMove(updateCallback), WTFMove(activeTypes)); + if (hidden()) { + viewTransition->skipViewTransition(Exception { ExceptionCode::InvalidStateError, "View transition was skipped because document visibility state is hidden."_s }); + return viewTransition; + } + if (RefPtr activeViewTransition = m_activeViewTransition) activeViewTransition->skipViewTransition(Exception { ExceptionCode::AbortError, "Old view transition aborted by new view transition."_s }); diff --git a/Source/WebCore/dom/Document.h b/Source/WebCore/dom/Document.h index 90a1d961b7369..3985d572071c5 100644 --- a/Source/WebCore/dom/Document.h +++ b/Source/WebCore/dom/Document.h @@ -1386,7 +1386,10 @@ class Document void dispatchPageshowEvent(PageshowEventPersistence); void dispatchPagehideEvent(PageshowEventPersistence); void dispatchPageswapEvent(bool canTriggerCrossDocumentViewTransition, RefPtr&&); + void transferViewTransitionParams(Document&); + void clearInboundViewTransitionParams(); + WEBCORE_EXPORT void enqueueSecurityPolicyViolationEvent(SecurityPolicyViolationEventInit&&); void enqueueHashchangeEvent(const String& oldURL, const String& newURL); void dispatchPopstateEvent(RefPtr&& stateObject); diff --git a/Source/WebCore/dom/ViewTransition.cpp b/Source/WebCore/dom/ViewTransition.cpp index 894c344960535..9815217199fa3 100644 --- a/Source/WebCore/dom/ViewTransition.cpp +++ b/Source/WebCore/dom/ViewTransition.cpp @@ -68,6 +68,7 @@ ViewTransition::ViewTransition(Document& document, RefPtr&& initialActiveTypes) @@ -859,12 +860,26 @@ RenderViewTransitionCapture* ViewTransition::viewTransitionNewPseudoForCapturedE return nullptr; } +// https://drafts.csswg.org/css-view-transitions/#page-visibility-change-steps +void ViewTransition::visibilityStateChanged() +{ + if (!document()) + return; + + if (protectedDocument()->hidden() && protectedDocument()->activeViewTransition() == this) { + protectedDocument()->clearInboundViewTransitionParams(); + skipViewTransition(Exception { ExceptionCode::InvalidStateError, "Skipping view transition because document visibility state has become hidden."_s }); + } else + ASSERT(!protectedDocument()->activeViewTransition()); +} + void ViewTransition::stop() { if (!document()) return; m_phase = ViewTransitionPhase::Done; + document()->unregisterForVisibilityStateChangedCallbacks(*this); if (document()->activeViewTransition() == this) clearViewTransition(); diff --git a/Source/WebCore/dom/ViewTransition.h b/Source/WebCore/dom/ViewTransition.h index 0013f0bfc2244..729f1ccd7daa6 100644 --- a/Source/WebCore/dom/ViewTransition.h +++ b/Source/WebCore/dom/ViewTransition.h @@ -35,6 +35,7 @@ #include "Styleable.h" #include "ViewTransitionTypeSet.h" #include "ViewTransitionUpdateCallback.h" +#include "VisibilityChangeClient.h" #include #include #include @@ -150,7 +151,7 @@ struct ViewTransitionParams { float initialPageZoom; }; -class ViewTransition : public RefCounted, public CanMakeWeakPtr, public ActiveDOMObject { +class ViewTransition : public RefCounted, public VisibilityChangeClient, public ActiveDOMObject { WTF_MAKE_TZONE_ALLOCATED(ViewTransition); public: static Ref createSamePage(Document&, RefPtr&&, Vector&&); @@ -208,6 +209,9 @@ class ViewTransition : public RefCounted, public CanMakeWeakPtr< void clearViewTransition(); + // VisibilityChangeClient. + void visibilityStateChanged() final; + // ActiveDOMObject. void stop() final;