Skip to content

Carry over sticky activation for same-origin navs and traversals - #11454

Open
domenic wants to merge 12 commits into
mainfrom
keep-sticky-activation
Open

Carry over sticky activation for same-origin navs and traversals#11454
domenic wants to merge 12 commits into
mainfrom
keep-sticky-activation

Conversation

@domenic

@domenic domenic commented Jul 15, 2025

Copy link
Copy Markdown
Member

See discussion in WICG/view-transitions#239 and #11328 (comment).

This also includes an editorial update to define history-action user activation as a simple boolean, instead of using the timestamp infrastructure.

Details:

  • To avoid encouraging racy code, this is sticky activation only, not transient activation. This requires adding an additional boolean to the user activation data model, but oh well.
  • This does not propagate the information to iframes or parent frames. It is only for the navigated frame (which can be an iframe), and it copies from its predecessor Window in that same frame.
  • This works for both traversals and push/replace navigations, and both bfcached and non-bfcached traversals. (Except for iframes, which inevitably get unloaded and then re-loaded during non-bfcache traversals that change the top-level page, and so will still lose their sticky activation state.)

/cc @mustaqahmed @nickcoury


(See WHATWG Working Mode: Changes for more details.)


💥 Error: Wattsi server error 💥

PR Preview failed to build. (Last tried on Aug 21, 2026, 12:36 PM UTC).

More

PR Preview relies on a number of web services to run. There seems to be an issue with the following one:

🚨 Wattsi Server - Wattsi Server is the web service used to build the WHATWG HTML spec.

🔗 Related URL

Error output:

      <!DOCTYPE html>
      <html>
      <head>
          <meta name="viewport" content="width=device-width, initial-scale=1">
          <meta name="robots" content="noindex">
          <style>body,html{height:100%;margin:0}body{display:flex;align-items:center;justify-content:center;flex-direction:column;-webkit-font-smoothing:antialiased;text-rendering:optimizeLegibility}p{text-align:center;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen,Ubuntu,Cantarell,Fira Sans,Droid Sans,Helvetica Neue,sans-serif;color:#000;font-size:14px;margin-top:-50px}p.code{font-size:24px;font-weight:500;border-bottom:1px solid #e0e1e2;padding:0 20px 15px}p.text{margin:0}a,a:visited{color:#aaa}</style>
      </head>
      <body>
      <p class="code">
        Error code: 503      </p>
      <p class="text">
        Well, This is unexpected. An Error has occurred, and we are working to fix the problem! We will be up and running shortly. Try refreshing the page or try again in a few minutes.
      </p>
        <div style="display:none;">
          <h1>
    upstream_reset_before_response_started{connection_termination} (503 UC)      </h1>
          <p data-translate="connection_timed_out">App Platform failed to forward this request to the application.</p>
      </div>
      </body>
      </html>
    

This seems to be an issue with the Wattsi Server service. PR Preview doesn't manage this service and so has no control over it. If you've identified an issue with it, you can report the issue to the maintainers of Wattsi Server directly. Please be courteous. Thank you!

If you don't have enough information above to solve the error by yourself or if the issue doesn't seem related to Wattsi Server, you can file an issue with PR Preview.

@domenic domenic added addition/proposal New features or enhancements topic: user activation agenda+ To be discussed at a triage meeting labels Jul 15, 2025
Comment thread source Outdated
@domenic
domenic force-pushed the keep-sticky-activation branch from 546d519 to ad0e6b8 Compare August 20, 2025 05:27
Comment thread source Outdated
interacted in <var>W</var>. It starts false, then changes to true (and never changes back to
false) when <var>W</var> gets the very first <span>activation notification</span>.</p>
false) when <var>W</var> gets the very first <span>activation notification</span>. It is also
carried over between windows for same-origin navigations and traversals.</p>

@smaug---- smaug---- Aug 21, 2025

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, is this quite right? If there is a bfcached page without sticky activation, I don't think the algorithms will set the flag on those window objects if another same origin window gets sticky activation. And I'm not sure what behavior we want there.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're right, that's a great catch! I think we should carry it over to same-origin bfcached documents too, to minimize differences between cases where the bfcache is hit vs. missed.

I'll add a line to the "reactivate" algorithm similar to the one I added to the "create and initialize a new Document" algorithm.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This ended up being more annoying than I'd prefer, as threading things from the predecessor document to the new document seems to be surprisingly unusual. (In particular, if the browser plans to unload and destroy the previous document, we need to grab the state before it does that.)

I have a half-finished local branch with an alternate option, which, at the time we set sticky activation for one window, immediately tries to propagate it to all contiguous same-origin bfcached windows in the same navigable. But I realized that keeping track of "contiguous" would add a good amount of complexity (albeit only locally), and this probably would not be how implementations do it, so I stashed that.

Of course, there's a separate issue here where the whole user activation framework ignores the complexities of propagating the bit across processes, instead just letting people access the Window object from anywhere. That is fairly pervasive in the spec ecosystem though. (That is, although specs these days are relatively good about separating out processes, the rarer cases like this one where we need to propagate state so that it lives in multiple processes are all hand-waved. See w3c/ServiceWorker#1755 (comment) for more rambling.)

@annevk annevk left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks good to me. Since it's restricted to same-origin I don't think this really increases the risk of anything bad happening.

The one thing that gives me pause, but was apparently already the case, is that these values persist "forever". But maybe that's more of a comment to be had on bfcache, that expiring after a couple of days is probably a good idea.

Comment thread source
Comment thread source Outdated
@domenic

domenic commented Sep 18, 2025

Copy link
Copy Markdown
Member Author

Fixed nits.

@mustaqahmed is working on web platform tests; it's been a bit tricky to test but I think we're getting close to a solution. I'll wait to merge until those are ready.

I filed Gecko and MDN bugs, but https://bugs.webkit.org/ is down at the moment so I'll have to do that later.

@annevk

annevk commented Sep 18, 2025

Copy link
Copy Markdown
Member

A colleague brought up some good points:

  • Is this invalidated by a cross-origin redirect? When you navigate from A1 to B, but B redirects to A2. I don't think it currently is, but it probably should be.
  • Should this be restricted to top-level documents? That seems reasonable given the use case.

@domenic

domenic commented Sep 18, 2025

Copy link
Copy Markdown
Member Author
  • Is this invalidated by a cross-origin redirect? When you navigate from A1 to B, but B redirects to A2. I don't think it currently is, but it probably should be.

I agree with this.

  • Should this be restricted to top-level documents? That seems reasonable given the use case.

I'm less sure about this. My instinct was to just do whatever was easiest to spec/implement, which in this case was to allow it to work in iframes.

@domenic

domenic commented Sep 19, 2025

Copy link
Copy Markdown
Member Author
  • Is this invalidated by a cross-origin redirect? When you navigate from A1 to B, but B redirects to A2. I don't think it currently is, but it probably should be.

I agree with this.

I'm no longer sure about this.

It seems like most parts of the spec only compare the endpoint origins in A -> B -> A navigations today:

  • Whether to perform a COOP BCG swap
  • Whether to reuse the initial about:blank Window
  • navigable target names
  • navigation API keys
  • Whether the navigation API fires a traverse navigate event
  • Whether navigation.activation.from is non-null
  • Whether to clear history.state when traversing back to an entry

There's also one cases that is confusing:

  • Whether the pageswap event has a non-null activation property. It is null for A -> B -> A cases, except if bfcache is involved, in which case it's non-null.

The only case, in HTML at least, that unambiguously changes behavior for A -> B -> A cases, is unload timing info, which gets censored in those cases.

Given this situation, I'd prefer sticky activation is carried over in A -> B -> A cases. Unless we have a compelling security story for a hole that carrying it over creates.

Optionally, in the future, someone could investigate whether our choices in all the above-listed cases are coherent, and if we should move to a model that considers A -> B -> A "more cross-origin". (Although I suspect the compat implications might be bad.)

@annevk

annevk commented Sep 19, 2025

Copy link
Copy Markdown
Member

I don't think that's correct? We call "enforce a response's opener policy" for each response we get, which includes redirect responses as navigate doesn't follow those automatically.

The risk of exploitation seems minimal, but it's the standard confused deputy attack scenario. A navigates to B which redirects to A2. A2 doesn't think it's in a state where it can hold sticky activation, but it actually does, which results in something unfortunate.

@domenic

domenic commented Sep 24, 2025

Copy link
Copy Markdown
Member Author

I don't think that's correct? We call "enforce a response's opener policy" for each response we get, which includes redirect responses as navigate doesn't follow those automatically.

You're right, although we only do the final BCG swap checking at the end, the "COOP enforcement result" structure is modified each time through the loop in a cumulative way.

So that leaves us at 6 endpoint-only checks, 2 all-legs checks, and 1 inconsistent-depending-on-bfcache check.

Sticky activation feels more similar to things like navigation API state or history.state from the 6 endpoint-only checks, where if the user experience is A -> (invisible stuff in the middle) -> A, then the appropriate state or sticky activation bit should be propagated to give the expected user experience. But I don't claim that the existing division is the result of a clear principled approach, so I still think there's room for proceeding with this as-is and then doing a full audit and discussion afterward, to see if people agree on the current model.

aarongable pushed a commit to chromium/chromium that referenced this pull request Sep 30, 2025
Intent to Ship:
https://groups.google.com/a/chromium.org/d/msgid/blink-dev/68b0b943.050a0220.270bc4.0090.GAE%40google.com

Spec PR (already secured another browser's approval):
whatwg/html#11454

Fixed: 433729626
Change-Id: Ib475166aeec709837c9d67f6936f51abdc38c6a5
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/6961179
Commit-Queue: Vladimir Levin <vmpstr@chromium.org>
Auto-Submit: Mustaq Ahmed <mustaq@chromium.org>
Reviewed-by: Vladimir Levin <vmpstr@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1523044}
aarongable pushed a commit to chromium/chromium that referenced this pull request Oct 2, 2025
…igation

Original change's description:
> Enable carrying sticky-activation state across same-origin navigation
> 
> Intent to Ship:
> https://groups.google.com/a/chromium.org/d/msgid/blink-dev/68b0b943.050a0220.270bc4.0090.GAE%40google.com
> 
> Spec PR (already secured another browser's approval):
> whatwg/html#11454
> 
> Fixed: 433729626
> Change-Id: Ib475166aeec709837c9d67f6936f51abdc38c6a5
> Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/6961179
> Commit-Queue: Vladimir Levin <vmpstr@chromium.org>
> Auto-Submit: Mustaq Ahmed <mustaq@chromium.org>
> Reviewed-by: Vladimir Levin <vmpstr@chromium.org>
> Cr-Commit-Position: refs/heads/main@{#1523044}

(cherry picked from commit 96f69eb)

Bug: 448428855,433729626
Change-Id: Ib475166aeec709837c9d67f6936f51abdc38c6a5
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7004556
Bot-Commit: Rubber Stamper <rubber-stamper@appspot.gserviceaccount.com>
Reviewed-by: Mustaq Ahmed <mustaq@chromium.org>
Auto-Submit: Chrome Cherry Picker <chrome-cherry-picker@chops-service-accounts.iam.gserviceaccount.com>
Commit-Queue: Rubber Stamper <rubber-stamper@appspot.gserviceaccount.com>
Cr-Commit-Position: refs/branch-heads/7444@{#80}
Cr-Branched-From: 29907d3-refs/heads/main@{#1522585}
@foolip

foolip commented Jan 30, 2026

Copy link
Copy Markdown
Member

I'd like to finish this and have read through the discussion. @annevk raised two issues in #11454 (comment). On iframes, it seems simpler to just allow those navigations to carry over sticky activation.

The remaining issue is A -> B -> A navigation where B is a redirect back to A. This PR currently does carry forward sticky activation in this case.

@annevk what is your preference, to move forward with this as proposed, or ensure that A -> B -> A does not carry forward sticky activation? In either case we should file an issue about the lack of consistency and decide on what we want the default for new things to be.

@annevk

annevk commented Mar 19, 2026

Copy link
Copy Markdown
Member

I would prefer not carrying it forward in those cases and testing that, to prevent potential attacks. If we can somehow find proof those attacks don't exist, maybe an exception can be made.

@foolip

foolip commented Mar 19, 2026

Copy link
Copy Markdown
Member

Thanks @annevk. First step is a test to confirm without a doubt what the behavior implemented in Chromium is. I've asked @mustaqahmed about that.

chromium-wpt-export-bot pushed a commit to web-platform-tests/wpt that referenced this pull request May 20, 2026
- This CL updates the test expectation to match this HTML PR that
  updates the sticky user activation state after an iframe completes
  a same-origin navigation:
  whatwg/html#11454

- Also adds assertion details so that failures on the wpt.fyi dashboard
  becomes more informative and actionable.

Fixed: 40527366
Change-Id: I1662197a6fc1d189d0c435c46688e164862d1b11
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7841474
Commit-Queue: Mustaq Ahmed <mustaq@chromium.org>
Reviewed-by: Philip Jägenstedt <foolip@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1633575}
lando-worker Bot pushed a commit to mozilla-firefox/firefox that referenced this pull request May 22, 2026
…tml match a recent PR, a=testonly

Automatic update from web-platform-tests
Make navigation-state-reset-sameorigin.html match a recent PR

- This CL updates the test expectation to match this HTML PR that
  updates the sticky user activation state after an iframe completes
  a same-origin navigation:
  whatwg/html#11454

- Also adds assertion details so that failures on the wpt.fyi dashboard
  becomes more informative and actionable.

Fixed: 40527366
Change-Id: I1662197a6fc1d189d0c435c46688e164862d1b11
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7841474
Commit-Queue: Mustaq Ahmed <mustaq@chromium.org>
Reviewed-by: Philip Jägenstedt <foolip@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1633575}

--

wpt-commits: 8187b0a8e30dd90ae5bec39b0c4e99dce9e0e8a9
wpt-pr: 60008
gecko-dev-updater pushed a commit to marco-c/gecko-dev-wordified-and-comments-removed that referenced this pull request May 26, 2026
…tml match a recent PR, a=testonly

Automatic update from web-platform-tests
Make navigation-state-reset-sameorigin.html match a recent PR

- This CL updates the test expectation to match this HTML PR that
  updates the sticky user activation state after an iframe completes
  a same-origin navigation:
  whatwg/html#11454

- Also adds assertion details so that failures on the wpt.fyi dashboard
  becomes more informative and actionable.

Fixed: 40527366
Change-Id: I1662197a6fc1d189d0c435c46688e164862d1b11
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7841474
Commit-Queue: Mustaq Ahmed <mustaqchromium.org>
Reviewed-by: Philip Jägenstedt <foolipchromium.org>
Cr-Commit-Position: refs/heads/main{#1633575}

--

wpt-commits: 8187b0a8e30dd90ae5bec39b0c4e99dce9e0e8a9
wpt-pr: 60008

UltraBlame original commit: 2253c3aa1786d97d7f11e9f7a0fc25680b6a3d24
gecko-dev-updater pushed a commit to marco-c/gecko-dev-wordified that referenced this pull request May 26, 2026
…tml match a recent PR, a=testonly

Automatic update from web-platform-tests
Make navigation-state-reset-sameorigin.html match a recent PR

- This CL updates the test expectation to match this HTML PR that
  updates the sticky user activation state after an iframe completes
  a same-origin navigation:
  whatwg/html#11454

- Also adds assertion details so that failures on the wpt.fyi dashboard
  becomes more informative and actionable.

Fixed: 40527366
Change-Id: I1662197a6fc1d189d0c435c46688e164862d1b11
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7841474
Commit-Queue: Mustaq Ahmed <mustaqchromium.org>
Reviewed-by: Philip Jägenstedt <foolipchromium.org>
Cr-Commit-Position: refs/heads/main{#1633575}

--

wpt-commits: 8187b0a8e30dd90ae5bec39b0c4e99dce9e0e8a9
wpt-pr: 60008

UltraBlame original commit: 2253c3aa1786d97d7f11e9f7a0fc25680b6a3d24
gecko-dev-updater pushed a commit to marco-c/gecko-dev-comments-removed that referenced this pull request May 26, 2026
…tml match a recent PR, a=testonly

Automatic update from web-platform-tests
Make navigation-state-reset-sameorigin.html match a recent PR

- This CL updates the test expectation to match this HTML PR that
  updates the sticky user activation state after an iframe completes
  a same-origin navigation:
  whatwg/html#11454

- Also adds assertion details so that failures on the wpt.fyi dashboard
  becomes more informative and actionable.

Fixed: 40527366
Change-Id: I1662197a6fc1d189d0c435c46688e164862d1b11
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7841474
Commit-Queue: Mustaq Ahmed <mustaqchromium.org>
Reviewed-by: Philip Jägenstedt <foolipchromium.org>
Cr-Commit-Position: refs/heads/main{#1633575}

--

wpt-commits: 8187b0a8e30dd90ae5bec39b0c4e99dce9e0e8a9
wpt-pr: 60008

UltraBlame original commit: 2253c3aa1786d97d7f11e9f7a0fc25680b6a3d24
webkit-commit-queue pushed a commit to kate-k-lee/WebKit that referenced this pull request Jun 2, 2026
https://bugs.webkit.org/show_bug.cgi?id=316059

Reviewed by Anne van Kesteren.

Update html/user-activation/navigation-state-reset-sameorigin.html to the
latest upstream version, which adds a check that a same-origin child frame
retains its sticky user-activation state after it completes a same-origin
navigation, matching whatwg/html#11454.

WebKit does not yet implement this behavior, so the new subtest fails and
is baselined into the -expected.txt accordingly.

Upstream commit: web-platform-tests/wpt@8187b0a8e3

* LayoutTests/imported/w3c/web-platform-tests/html/user-activation/navigation-state-reset-sameorigin-expected.txt:
* LayoutTests/imported/w3c/web-platform-tests/html/user-activation/navigation-state-reset-sameorigin.html:

Canonical link: https://commits.webkit.org/314406@main
webkit-commit-queue pushed a commit to kate-k-lee/WebKit that referenced this pull request Jun 22, 2026
… flags

https://bugs.webkit.org/show_bug.cgi?id=316317

Reviewed by Youenn Fablet.

Per the user-activation data model change in the proposed
whatwg/html#11454, track sticky activation and history-action activation
as explicit booleans on LocalDOMWindow instead of deriving them from the
last activation timestamp. m_lastActivationTimestamp continues to drive
transient activation only.

notifyActivated and its ancestor/descendant propagation, consume-history-
action, and WebPage::updateUserActivationTimestamps now update the
booleans alongside the timestamp. This is not gated behind a preference.
Behavior is preserved, except that consuming transient activation no
longer resurrects an already-consumed history-action activation, which
matches the proposed explicit boolean model.

* Source/WebCore/page/LocalDOMWindow.cpp:
(WebCore::LocalDOMWindow::hasStickyActivation const):
(WebCore::LocalDOMWindow::hasHistoryActionActivation const):
(WebCore::LocalDOMWindow::consumeHistoryActionUserActivation):
(WebCore::LocalDOMWindow::notifyActivated):
* Source/WebCore/page/LocalDOMWindow.h:
(WebCore::LocalDOMWindow::setHasStickyActivation):
(WebCore::LocalDOMWindow::setHasHistoryActionActivation):
* Source/WebKit/UIProcess/WebPageProxy.cpp:
(WebKit::WebPageProxy::didNotifyUserActivation):
* Source/WebKit/WebProcess/WebPage/WebPage.cpp:
(WebKit::WebPage::updateUserActivationState):
* Source/WebKit/WebProcess/WebPage/WebPage.h:
* Source/WebKit/WebProcess/WebPage/WebPage.messages.in:

Canonical link: https://commits.webkit.org/315598@main
@foolip

foolip commented Jul 7, 2026

Copy link
Copy Markdown
Member

I've now updated this PR to not carry forward sticky activation through a A -> B -> A navigation. @annevk can you give this another look?

kate-k-lee added a commit to kate-k-lee/WebKit that referenced this pull request Jul 9, 2026
https://bugs.webkit.org/show_bug.cgi?id=313716

Reviewed by NOBODY (OOPS!).

Implement sticky user activation carry-over per whatwg/html#11454,
centralized in LocalDOMWindow::carryOverStickyActivationFromPreviousWindow
and applied at two entry points:

- DocumentWriter::begin carries sticky activation onto a same-origin
  destination, suppressed when the redirect chain crossed origins
  (guards A -> B -> A confused-deputy patterns).
- FrameLoader::open(CachedFrameBase&) applies the same rule on bfcache
  reactivation; no network load, so the redirect suppressor is N/A.

Builds on the explicit sticky/history-action activation booleans on
LocalDOMWindow (bug 316317); carry-over sets m_hasStickyActivation on
the destination window when the source had sticky activation and the
navigation stayed same-origin. Only sticky activation is carried over,
not transient or history-action.

Gated behind StickyUserActivationAcrossSameOriginNavigationsEnabled
(testable, off by default, auto-enabled in WebKitTestRunner).

* LayoutTests/TestExpectations:
* LayoutTests/imported/w3c/web-platform-tests/html/user-activation/navigate-to-crossorigin-redirect-expected.txt:
* LayoutTests/imported/w3c/web-platform-tests/html/user-activation/navigate-to-sameorigin-expected.txt:
* LayoutTests/imported/w3c/web-platform-tests/html/user-activation/navigation-state-reset-sameorigin-expected.txt:
* LayoutTests/platform/ios/TestExpectations:
* Source/WTF/Scripts/Preferences/UnifiedWebPreferences.yaml:
* Source/WebCore/loader/DocumentWriter.cpp:
* Source/WebCore/loader/FrameLoader.cpp:
* Source/WebCore/page/LocalDOMWindow.cpp:
* Source/WebCore/page/LocalDOMWindow.h:
@annevk

annevk commented Aug 21, 2026

Copy link
Copy Markdown
Member

Apart from https://gist.github.com/annevk/8fef6335f0442b9124114bbb54f17b0f there's two other issues worth discussing:

  • If you navigate from A1 to B which redirects to A2 it clears. But if you then go back to A1 through history traversal and then forward, it does not clear. We could check document's "was created via cross-origin redirects" to address this case, I think.
  • There's also a more complicated case where A1 creates an initial about:blank nested document. That then navigates to B which redirects to A2. I think the Window object is reused in that case, which is probably the original sin. That probably needs a separate follow-up, unless you have any good ideas.

@alastaircoote

Copy link
Copy Markdown

What would be the process for feature detection here?

In the instance of, say, a video player that carries playback through from a thumbnail to fullscreen presentation it would be preferable to still use a same-page transition rather then a multi-page that won't preserve playback state. But I'm not clear how you'd be able to know ahead of time whether it will or not.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Development

Successfully merging this pull request may close these issues.

8 participants