Skip to content

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

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

domenic wants to merge 19 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 redefines 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.)


/browsing-the-web.html ( diff )
/document-lifecycle.html ( diff )
/interaction.html ( diff )

@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

@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.

@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.

@foolip foolip closed this Sep 15, 2026
@foolip foolip reopened this Sep 15, 2026
@foolip

foolip commented Sep 15, 2026

Copy link
Copy Markdown
Member

@annevk

Apart from https://gist.github.com/annevk/8fef6335f0442b9124114bbb54f17b0f

The blockers there were addressed in 2e8536a and an update to the PR description.

I also took some of the suggestions in 6329610 and f4a1274, but not all of it. In particular the claimed null deref that would be fixed by moving a step into the "Otherwise" branch, I really can't tell from looking at the algorithm that it might do a null deref, the condition is "If changingNavigableContinuation's update-only is true, or targetEntry's document is displayedDocument" and I don't understand what's implied by that being false.

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.

Fixed in c71dd74.

  • 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.

I have a change prepared by AI for this that looks like this:

--- a/source
+++ b/source
@@ -112989,11 +112989,14 @@ <h4>Shared document creation infrastructure</h4>

    <li>
     <p>If <var>browsingContext</var>'s <span>active document</span>'s <span>is initial
-    <code>about:blank</code></span> is true, and <var>browsingContext</var>'s <span>active
+    <code>about:blank</code></span> is true, <var>browsingContext</var>'s <span>active
     document</span>'s <span data-x="concept-document-origin">origin</span> is <span>same
     origin-domain</span> with <var>navigationParams</var>'s <span
-    data-x="navigation-params-origin">origin</span>, then set <var>window</var> to
-    <var>browsingContext</var>'s <span>active window</span>.</p>
+    data-x="navigation-params-origin">origin</span>, and <var>navigationParams</var>'s <span
+    data-x="navigation-params-response">response</span>'s <span
+    data-x="concept-response-has-cross-origin-redirects">has cross-origin redirects</span> is
+    false, then set <var>window</var> to <var>browsingContext</var>'s <span>active
+    window</span>.</p>

     <p class="note">This means that both the <span data-x="is initial about:blank">initial
     <code>about:blank</code></span> <code>Document</code>, and the new <code>Document</code> that

I'm not very confident about this though. It has the appearance of a correct fix, but I don't know this infrastructure. I can create a separate PR for it if you like.

@foolip

foolip commented Sep 15, 2026

Copy link
Copy Markdown
Member

What would be the process for feature detection here?

@alastaircoote I'm afraid it just won't be possible to feature detect this since it doesn't introduce any new API surface area.

@zcorpan

zcorpan commented Sep 29, 2026

Copy link
Copy Markdown
Member
AI review

Review at 2504bf9.

Spec correctness

  • source:111838-111846: when targetEntry's document is null (a 204 on traversal, which sets update-only at 111673), this step reads null's origin. annevk's gist raised it and foolip wasn't sure. I think it's real. Moving the step into the "Otherwise:" (cross-document) branch fixes it, since only reactivate uses the value.
  • source:112627-112633: reactivate only runs when documentsEntryChanged is false. As far as I can tell, a bfcache restore to a non-latest entry skips it, so no carry-over happens (nor pageshow persisted). Example: D pushStates to e2, navigates away, then history.go(-2) to e1. The gap was already there, but the carry-over now depends on it.
  • source:111840-111846 vs 112093-112095: the traversal check blocks on was created via cross-origin redirects with no exception, while pageswap allows it when latest entry is non-null. The mismatch may be intended (annevk's back-then-forward case), but it's worth confirming.
  • source:113383-113390: after a COOP browsing context group switch, browsingContext is new and its about:blank has an opaque origin. So same-origin navigations that swap groups lose sticky activation. Chromium carries it, since it uses the same frame's current RFH. Nobody discussed this.
  • source:85633-85634 / 85557: making history-action activation a boolean changes behavior. Before, consuming transient activation set the timestamp to −∞, which re-granted history-action activation. annevk flagged it as needing a mention in the commit message, and it still isn't there. Chromium already models it as a separate boolean (HistoryUserActivationState), so it seems to match.

Security

  • The rationale for excluding redirects is annevk's confused-deputy concern (A1 → B → A2). A cross-origin initiator seems equivalent, and the spec allows it. A cross-origin opener or parent B can set a frame from A1 to A2 directly, and A2 inherits sticky activation. Since the redirect taint compares hops against the request's origin, B/redirect → A2 is also untainted when B initiated it. If the confused deputy matters for redirects, maybe it matters here too.
  • source:113306-113313: reuse of the initial about:blank Window still carries all activation state, transient included, through cross-origin redirects. annevk called this "probably the original sin". It predates this PR and is unresolved; foolip's draft fix references has cross-origin redirects, which Fetch removed.
  • Chromium only: its history manipulation intervention reads the outgoing document's sticky bit (navigator.cc:499-501). With carry-over, one click lets a chain of same-origin script navigations and pushState entries stay non-skippable on back. Worth raising on crbug 433729626.
  • beforeunload is gated on sticky activation, so a page reached by same-origin navigation can prompt without its own interaction. That's by design and seems fine.

Chromium divergences (navigation_request.cc:7187-7196, flag stable since M142, per the agent's reading of the CLs; I confirmed the snippet and the bfcache early return)

  • Redirects aren't checked at all: only the final origin is compared. So navigate-to-crossorigin-redirect.html fails. mustaqahmed reported compat trouble from an earlier Chrome attempt to not forward the bit through redirects, so this requirement may be hard to ship.
  • Browser-initiated navigations (typed URL, bookmarks, UI back/forward and reload) never carry. The spec has no such condition. Maybe the spec should condition on userInvolvement?
  • Subframes carry across same-site navigations (older compat behavior), where the spec says same-origin.
  • bfcache restores don't carry at all: the early return at navigation_request.cc:7110-7113 runs before the computation.
  • Main-frame javascript: URLs don't carry; the spec would.

Tests

  • There are no traversal tests at all, bfcache or not, even though the PR explicitly covers both. A bfcache test would expose Chromium's gap.
  • No test covers annevk's back-then-forward redirect case (the reason for c71dd74), or the 204 traversal case.
  • Nothing checks that a same-origin redirect (A → A/redirect → A2) still carries, so an implementation that blocks all redirects would pass.
  • The redirect test only uses a same-site hop (www1). That's good for catching a same-site vs same-origin mix-up. A cross-site hop isn't tested.
  • Nothing tests the history-action behavior change. One way: consume via a cancelable close watcher cancel, call window.open(), then check that the next close request isn't cancelable.
  • Nothing covers a navigation initiated by a cross-origin opener or parent, a COOP swap, or the absence of carry-over in sandboxed/opaque frames.
  • navigation-state-reset-sameorigin.html passes in Chromium through the old same-site subframe rule, so it doesn't exercise the new code path there.

On process: annevk approved before the redirect changes and hasn't re-approved, and smaug hasn't approved. The follow-up issue foolip promised about A→B→A consistency doesn't seem to have been filed.

foolip added 2 commits October 1, 2026 12:42
This is intended to carry sticky activation across a same-origin navigation
where the browsing context group changes due to COOP.
@foolip

foolip commented Oct 1, 2026

Copy link
Copy Markdown
Member

@zcorpan I have pushed two fixes that I thought would make sense. The remaining three things under "spec correctness" I don't not find actionable.

For Chromium differences I will just file a Chromium bug to align with this PR.

There are new tests for this, but of course there are tonnes of permutations that could be tested. There is work on this in WebKit, and I think it would make the most sense to identify missing test coverage as part of that.

@foolip

foolip commented Oct 1, 2026

Copy link
Copy Markdown
Member

I've filed https://crbug.com/568041944 and updated OP to reference that.

@foolip

foolip commented Oct 1, 2026

Copy link
Copy Markdown
Member

I have filed #13015 about other A->B->A cases not being consistent.

@annevk

annevk commented Oct 1, 2026 •

Copy link
Copy Markdown
Member
More AI review
  Blockers

  - source:113383-113393, source:111868-111874 — A cross-origin initiator gets around the redirect exclusion.
    - Fetch computes redirect taint relative to request's origin, i.e. the initiator. A hop only taints if both the next URL and the request's origin differ from that hop's origin.
    - So if B starts the navigation, the B → A2 hop doesn't taint. A direct B → A2 navigation has no hops at all. Either way the taint is "same-origin".
    - Per "allowed by sandboxing to navigate" (109447ff), B can navigate A1's navigable without a redirect or user activation. B can be the popup holding opener, A1's opener, A1's parent (iframe.src), or a cross-origin iframe inside A1 (top.location = 
      "https://a.example/a2?…").
    - In each case A2 inherits A1's sticky activation. That's the confused-deputy case you gave as the reason to exclude A1 → B → A2, without even needing a redirect. zcorpan raised this under "Security" and it hasn't been answered.
    - Fix: also require a same-origin initiator.
      - At creation: "navigationParams's request is null, or its origin is same origin with navigationParams's origin". Non-fetch schemes already take their origin from the initiator or parent.
      - Traversals need an equivalent recorded bit.
      - Side effect: browser-UI navigations have an opaque request origin, so typed-URL and bookmark navigations would stop carrying. zcorpan reports that's what Chromium does. Reloads and UI back/forward would still carry.
    - If the exclusion isn't meant to be a security boundary, the commit message should say so.

  Suggestions

  1. PR description / commit message: two normative behaviors aren't described.
     - (a) The redirect exclusion. Add a Details bullet: not carried over when the new Document was created via cross-origin redirects, including later bfcache traversals back to it; see #13015.
     - (b) The history-action rewrite is a behavior fix. In the old model, consuming transient activation set the timestamp to −∞. That made it differ again from the last history-action timestamp, so history-action activation came back without a new gesture. Example:
       cancelable close request → window.open() → a second cancelable close request. WebKit's 315598@main commit message says this explicitly; the HTML commit should too.
     - Also state that reloads and UI-initiated navigations carry.
  2. source:112625-112634, 112695-112703 — The carry-over is attached to reactivate, which only runs when documentsEntryChanged is false.
     - Example: D (at e1) calls pushState (now at e2), then navigates to X; the user clicks X; X calls history.go(-2). D is restored from bfcache, reactivate is skipped, and nothing carries. Pageshow isn't fired either, which is pre-existing.
     - Fix: in "update document for history step application", before the documentsEntryChanged step, add: "If documentIsNew is false and stickyActivationToCarryOver is true, then set document's relevant global object's has sticky activation to true."
     - Then drop the new parameter on the exported reactivate, and file the pre-existing pageshow gap separately.
  3. source:111868-111874 — The back-then-forward fix (c71dd748) only covers bfcache.
     - If A2 (reached via A1 → B → A2) isn't bfcached, the forward traversal re-fetches A2's entry URL directly. That fetch has taint "same-origin", so create-and-initialize carries A1's activation to the same B-chosen URL.
     - Fix: record the taint on the session history entry's document state, or acknowledge the gap in #13015.
  4. Browser UI and reload. As written, typed-URL, bookmark, reload, and UI back/forward same-origin navigations all carry. Chromium reportedly never carries for browser-initiated navigations, and foolip plans to align Chromium with the PR. Decide this explicitly
     (userInvolvement is available), document it, and test it. This ties into the blocker fix.
  5. source:85515-85523 — The note is vague and now incomplete.
     - "The underlying values" doesn't say which values (your gist).
     - "span multiple navigations as long as the same Document gets reused" is no longer the whole story, because sticky activation now also carries to new Windows.
     - Fix: name the three values and add "in addition, sticky activation can be carried over to a new Window, as described above."
  6. 113383-113393 vs 111868-111874 — The same rule is written twice: once via the response's redirect taint, once via was created via cross-origin redirects, which is derived from that same taint (113440-113444). Factor both into one helper and call it in
     create-and-initialize after the Document exists. It's a no-op for the reused Window. Then the blocker fix only has to go in one place.
  7. source:113364, 113367 (pre-existing, next to the diff) — navigable is undefined in create and initialize. This PR fixed it in 3939d915, but merge b7acf326 lost the fix. Adding "Let navigable be navigationParams's navigable" at the top fixes it and shortens the new step.
  8. Test coverage (useful for WebKit bug 313716). The three WPTs only cover push navigations plus one same-site redirect. Missing:
     - bfcache carry-over (smaug's case: X → Y, click Y → back to X)
     - a non-bfcache traversal
     - the back-then-forward redirect case
     - same-origin redirects still carrying
     - a cross-site redirect
     - a cross-origin initiator
     - a same-origin navigation with a COOP browsing context group swap (6c4a933)
     - the history-action change (click → cancelable close → window.open() → next close request not cancelable)
  9. Follow-ups in other specs:
     - (a) Prerendering Revamped: the prerendered Window is created under the initial about:blank of the prerendering traversable, and activation copies nothing. So a same-origin navigation won't carry sticky activation if the destination was prerendered. File a
       nav-speculation issue.
     - (b) Screen Capture sets the last activation timestamp directly (index.html ~547). That used to imply history-action activation too. It probably shouldn't, but they should get a heads-up.
  10. source:85462-85463 (optional, your gist) — The definition is circular. "When W's has sticky activation is true, W is said to have sticky activation". Also, "has sticky activation" reads the same as the boolean (112697) and as the predicate (111868). Rename the booleans,
      or define the states directly as booleans.

  Nits
  
  - source:85462 vs 85499 — the sticky definition says "When …, W is said to have", the history-action one says "When …, then W is said to have". Pick one.
  - source:85441-85443, 85557-85559 — the booleans are ordered history-action, then sticky; the <dl> orders Sticky, Transient, History-action.
  - source:85468-85471 — "traversals that do not involve cross-origin redirects" is imprecise: a traversal itself never involves redirects. Suggest "…unless the destination Document was created via cross-origin redirects". Also prefer "Window objects" over "windows".
  - source:112699-112702 — "traverse" → "traversal"; "if present" is redundant; drop the stray comma; "document" and "Document" are mixed; the redirect condition is missing.
  - source:111868-111874, 113383-113393 — three-clause run-on conditions. Use "If all of the following are true:" lists, as pageswap does at 112086.
  - source:85608-85610 (optional) — the +∞ vs −∞ distinction is now unobservable.
  - PR title: "navs" → "navigations". Drop "but oh well" from the commit message.
  - PR checklist:
    - Cite mozilla/standards-positions#1295 (position: positive).
    - WebKit#547 has no position label; your only comment there predates the redirect changes.
    - Use https://crbug.com/568041944 for the Chromium link.
    - "Chromium is prototyping" may be stale: zcorpan says it's been behind a flag stable since M142.
  - WPT:
    - navigate-to-crossorigin-redirect.html:93: the test name is copy-pasted ("…same-origin navigation"); line 91 has "nagivated".
    - navigate-to-sameorigin.html:79: the comment says the window has no activation, but the test asserts sticky is true; line 83 says "redirected" though there's no redirect.

kate-k-lee added a commit to kate-k-lee/WebKit that referenced this pull request Oct 2, 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:
foolip added 2 commits October 2, 2026 08:39
This was previoiusly broken, "If navigable's container is not null" refered
to variable that was never defined.
The condition looks a bit different in the two branches, but should
amount to the same thing, with the information coming from the
sourceDocument argument to the navigate algorithm.
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.

9 participants