Skip to content

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

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

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

Details:

  • To avoid encouraging racy code, this is sticky activation only, not
    transient activation. This requires adding the "has sticky activation"
    boolean to the user activation data model.
  • 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 push/replace navigations, reloads, and both bfcached
    and non-bfcached traversals, except for iframes recreated during
    non-bfcache traversals.
  • Reloads reuse the entry's document state, which describes the original
    navigation, so they only check whether the reload itself was redirected
    cross-origin.
  • It is never carried over if a cross-origin document initiated the
    navigation or redirected to the destination. To make this hold after
    bfcache eviction, a "was created via cross-origin redirects" bit is
    added to document state.
  • history-action user activation is redefined as a simple boolean,
    instead of using the timestamp infrastructure.

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

@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.
@foolip
foolip requested a review from annevk October 8, 2026 07:43
@foolip

foolip commented Oct 8, 2026

Copy link
Copy Markdown
Member

@annevk I have addressed the blocker, those of the nits I thought were sensible and belong in this PR, and some additional minor things Claude found for me.

@annevk

annevk commented Oct 8, 2026

Copy link
Copy Markdown
Member
AI feedback

Reviewed at b71c8eb. Lint and build pass. Checked and fine: with a single decision point, the initiator check now applies the same way to push/replace, reload, and traversals with or without bfcache. That includes <embed>, whose navigation has a null request, and bfcache restores to a non-latest entry now carry too. The three existing WPTs' expectations still match the new rules. Correction to the earlier AI review: prerender activation goes through finalize a cross-document navigation, so it's covered and doesn't need a nav-speculation issue.

Suggestions

  • PR description: the commit message is still the original text. Consider describing what now lands:

    • the conditions: same origin, a same-origin initiator for the destination entry, and no cross-origin redirects;
    • that it covers push/replace, reload, and traversals, bfcache included;
    • that entries created from a typed URL, a bookmark, or a cross-site link never receive carried activation;
    • the history-action fix: consuming transient activation no longer re-grants history-action activation without a new gesture.

    The implementation bugs were filed before the initiator requirement existed.

  • Initiator condition: this looks at the navigation that created targetEntry. So whether a reload keeps sticky activation depends on how the user first reached the page: no for a typed URL or a search result, yes for a same-origin link. Reloads could safely skip this condition, because displayedDocument is the entry's own document and its activation was earned at that URL. The redirect condition should stay, since the re-fetch can redirect. Consider "navigationType is "reload", or …".

  • Redirect condition: A1 → B → A2 is blocked. But if the user then goes back to A1 and forward to an evicted A2, A2's URL is re-fetched directly, the new document's was created via cross-origin redirects is false, and activation carries. Consider a bit on the document state that is set when its document is created via cross-origin redirects and never cleared. Or accept this gap in Consistent handling of A->B->A navigation scenarios #13015.

  • The new note:

    • "This will also carries over" → "This also carries over".
    • "where this Document" is ambiguous.
    • "not initiated by a cross-origin Document" is inaccurate. The requirement is positive, so navigations from browser UI are excluded too. And for traversals and reloads, what's checked is the navigation that created targetEntry, not the traversal.

    Consider keeping only what isn't obvious from the steps: the conditions are meant to stop a cross-origin party from choosing a same-origin URL that inherits sticky activation; they also apply to later reloads and traversals; and a bfcached Document can gain sticky activation this way.

  • Sticky activation <dd>: "traversals that were initiated by a same-origin Document" suggests the traversal's initiator matters, but UI back/forward carries too. This copy has already drifted once. Consider pointing to the conditions instead of restating them, e.g. "It can also be carried over to the next Window for same-origin navigations and traversals, subject to [these conditions]."

  • Data model note: "The underlying values" doesn't say which values. And "as long as the same Document gets reused" is no longer the whole story, now that sticky activation also carries over to new Windows.

  • Tests: nothing covers the initiator requirement yet, and it's the security-relevant part. Consider cases where a cross-origin page navigates the frame (parent setting iframe.src, popup setting opener.location, <embed> src change), plus a bfcache traversal.

  • Sticky activation definition (optional): "When W's has sticky activation is true, W is said to have sticky activation" defines the state through a boolean with nearly the same name. As a result, "has sticky activation" reads the same whether it means the boolean (here) or the state (here). Consider renaming the booleans, or defining the states directly as booleans.

  • Screen Capture (optional): it sets the last activation timestamp directly after getDisplayMedia() resolves. Under the old model that also re-granted history-action activation; now it only grants transient activation. That's probably the better outcome, but they may want a heads-up.

Nits

  • Sticky activation definition: it's missing "then": "When W's has sticky activation is true, then W is said to have …". This matches the transient and history-action definitions.
  • Data model: the new booleans come history-action first, then sticky. The activation notification steps use the same order. The <dl> below goes sticky, transient, history-action. Consider putting sticky first in both places.
  • Consume user activation (optional): now that sticky and history-action activation are booleans, positive and negative infinity in the last activation timestamp behave the same; transient activation is false either way. Consider dropping the "is not positive infinity" guard, or noting this.
  • update document for history step application: the required stickyActivationToCarryOver now comes after two optional parameters. Consider moving it before entriesForNavigationAPI and reordering the call, or making it optional again.
  • Right above the new step (pre-existing, also on main): history isn't defined in this algorithm. An optional drive-by, like the navigable fix: "document's relevant global object's navigation API".
  • note-user-activation-bfcache-carry-over now labels a general note. Consider renaming it, along with the link from the <dd>.
  • PR title: "navs" → "navigations". In the commit message, drop "but oh well".
  • PR checklist:
  • WPT:
    • navigate-to-crossorigin-redirect.html: "nagivated" is a typo. The test name is copied from the same-origin test ("…across a same-origin navigation").
    • navigate-to-sameorigin.html: the comment says the window has no user activation, but the test asserts sticky activation is true. The message says "redirected", but there's no redirect.

@foolip

foolip commented Oct 8, 2026

Copy link
Copy Markdown
Member

Of the remaining feedback, I think this is the one that could still lead to some normative change:

  • Redirect condition: A1 → B → A2 is blocked. But if the user then goes back to A1 and forward to an evicted A2, A2's URL is re-fetched directly, the new document's was created via cross-origin redirects is false, and activation carries. Consider a bit on the document state that is set when its document is created via cross-origin redirects and never cleared.

I think that would mean adding a boolean to https://html.spec.whatwg.org/#document-state-2 and copying it from the document. I'm a bit skeptical about propagating even more state, but it would be technically straightforward I think.

@annevk happy to do whatever you think is going to be the pattern or principle in #13015.

@foolip

foolip commented Oct 8, 2026

Copy link
Copy Markdown
Member

I checked what Chromium does and the "was created via cross-origin redirects" bit isn't implemented yet and navigate-to-crossorigin-redirect.html fails. So that doesn't really inform the decision, we can do whatever the best model is going forward.

@annevk

annevk commented Oct 9, 2026

Copy link
Copy Markdown
Member
AI feedback

Reviewed at fa92047. Lint and build pass. I skipped the commit message, which is about to be updated. Checked and fine: the reload exemption is safe. A reload can only come from the user, the document itself (history.go(0) reloads the caller's own navigable), or same origin-domain script (location.reload()), and the redirect condition still applies to the re-fetch. The rest of the earlier feedback has been addressed: "then", the note wording, the <dd> no longer restating the conditions, the note id, the optional parameter, and history.

Blockers

  • Redirect condition, re the open question: the new note says the conditions "ensure" that a cross-origin document can't cause the carry-over "by redirecting to the destination". That only holds while the destination stays in bfcache. After A1 → B → A2, which is blocked, going back to A1 and then forward to an evicted A2 re-fetches A2's URL directly. The new document's was created via cross-origin redirects is then false, so activation carries.

    Consider recording this on the document state, next to ever populated, which is the same kind of never-cleared bit. Set it when the document state's document was created via cross-origin redirects. The condition would then be:

    • targetEntry's document's was created via cross-origin redirects is false, which catches a fresh redirect on any re-fetch, reloads included; and
    • unless navigationType is "reload", the document state's bit is false too.

    That way the reload exemption covers both conditions that are about how the entry was first created. If you'd rather not add state, the alternative is to soften "ensure" and list this case in Consistent handling of A->B->A navigation scenarios #13015.

Suggestions

  • The note: besides "ensure" (see above), consider saying that entries created from browser UI, such as a typed URL or a bookmark, have an opaque initiator origin. Navigating or traversing to them therefore never carries sticky activation; only reloading them does. Readers won't infer that from "a cross-origin document cannot cause this".
  • Data model note: "The underlying values" doesn't say which values. And "as long as the same Document gets reused" is no longer the whole story, now that sticky activation also carries over to new Windows.
  • Tests: nothing covers the initiator requirement or the reload exemption yet. Consider:
    • navigations started by a cross-origin page: a parent setting iframe.src, a popup setting opener.location, and an <embed> src change;
    • a reload of an entry first reached from another origin;
    • a bfcache traversal.
  • Sticky activation definition (optional): "When W's has sticky activation is true, then W is said to have sticky activation" defines the state through a boolean with nearly the same name. As a result, "has sticky activation" reads the same whether it means the boolean (here) or the state (here). Consider renaming the booleans, or defining the states directly as booleans.
  • Screen Capture (optional): it sets the last activation timestamp directly after getDisplayMedia() resolves. Under the old model that also re-granted history-action activation; now it only grants transient activation. That's probably the better outcome, but they may want a heads-up.

Nits

  • Sticky activation <dd>: "It is also carried over between windows for same-origin navigations, reloads, and traversals" reads as unconditional. Consider "can also be carried over … subject to [some conditions]", with the link on the conditions rather than on "during reactivation from bfcache". Also consider "Window objects" instead of "windows".
  • Data model: the new booleans come history-action first, then sticky. The activation notification steps use the same order. The <dl> below goes sticky, transient, history-action. Consider putting sticky first in both places.
  • Consume user activation (optional): now that sticky and history-action activation are booleans, positive and negative infinity in the last activation timestamp behave the same; transient activation is false either way. Consider dropping the "is not positive infinity" guard, or noting this.
  • PR checklist:
  • WPT:
    • navigate-to-crossorigin-redirect.html: "nagivated" is a typo. The test name is copied from the same-origin test ("…across a same-origin navigation").
    • navigate-to-sameorigin.html: the comment says the window has no user activation, but the test asserts sticky activation is true. The message says "redirected", but there's no redirect.

@annevk

annevk commented Oct 9, 2026

Copy link
Copy Markdown
Member

I agree with the idea from the bot that we should store the bit on the document so that it remains correct in a bfcache situation.

@foolip

foolip commented Oct 9, 2026 •

Copy link
Copy Markdown
Member

OK, I've added another "was created via cross-origin redirects" bit. Note that it has to be on document state, not on a document.

For the suggestions and nits, I've looked at them in previous rounds and also tried them out as local commits, but I didn't like the changes. Especially for notes I think the review skill's tendency is to ask for more precision, but notes that are more technically correct also become longer and less readable.

@annevk feel free to flag changes that you agree with and I'll make them, though.

@annevk

annevk commented Oct 13, 2026

Copy link
Copy Markdown
Member
AI feedback

Reviewed at 7876a96. Lint and build pass. The new document-state bit closes the gap when the page has been evicted from bfcache, and it's correctly copied when the redirect loop rebuilds the document state.

Blockers

  • Reload exemption: 7876a96 dropped the top-level check that "targetEntry's document's was created via cross-origin redirects is false", and put the new document-state bit inside the reload exemption. So reloads now skip redirect checks entirely. If reloading A2 now redirects A2 → B → A3 (say, an expired session bouncing through an SSO provider), A3 inherits sticky activation even though B chose that URL. This was blocked at fa92047, and the note says it can't happen. Consider restoring that check as a top-level bullet; the document-state bit can stay in the non-reload branch.

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

Looks good! The commit message should be updated to describe the initiator, redirect, reload, and history fixes. And we should make sure to add tests for all those cases. (I guess it's somewhat fine if that happens as a follow-up, but let's make sure it actually happens.)

@foolip

foolip commented Oct 13, 2026

Copy link
Copy Markdown
Member

@annevk thanks! I made a small tweak to a note that I thought was an improvement, and also updated the commit message. Let me see about the test coverage before merging.

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