Skip to content

Calculate ancestorOrigins on document creation and store on document#12071

Open
zcorpan wants to merge 3 commits intomainfrom
zcorpan/ancestororigins-on-document
Open

Calculate ancestorOrigins on document creation and store on document#12071
zcorpan wants to merge 3 commits intomainfrom
zcorpan/ancestororigins-on-document

Conversation

@zcorpan
Copy link
Member

@zcorpan zcorpan commented Jan 9, 2026

When a Location object is created, a Document is not yet created. This is problematic for ancestorOrigins.

Also remove [SameObject] since step 1 can return a separate object.

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


/document-lifecycle.html ( diff )
/document-sequences.html ( diff )
/dom.html ( diff )
/index.html ( diff )
/nav-history-apis.html ( diff )

When a Location object is created, a Document is not yet created. This is problematic for ancestorOrigins.
Copy link
Member

@domfarolino domfarolino left a comment

Choose a reason for hiding this comment

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

I think this looks great, thanks! I have a question about the implications of moving from Location/Window to Document, since these are not always 1:1.

Example: you navigate an iframe from the initial about:blank Document to a same-origin one. I think neither the inner Window nor Location objects change, yet this PR would recalculate ancestor origins after the navigation, where as the current spec would not. Is that ever observable?

I envisioned a case where an iframe's ancestor used document.domain to change its origin before the navigation away from the initial about:blank Document. The current spec would not reflect the ancestor's origin change, since ancestorOrigins never gets recalculated. After this PR, the new document's ancestor origins list gets recalculated to its ancestor's "ancestor origins list" plus the ancestor's origin, which could have changed. On the other hand, we only ever expose its ASCII serialization, which never considers an origin's domain, so I guess it's not observable.

So basically after this PR, in the navigation scenario I described above, the "internal ancestor origins objects list" CAN change, but not in a way that is observable to script. Is that right?

Edit: Regarding the PR template, if there is indeed no observable change here, maybe the "At least two implementers are interested (and none opposed):" becomes "N/A"?

<li><p><span data-x="list append">Append</span> <var>parentDoc</var>'s <span
data-x="concept-document-origin">origin</span> to <var>output</var>.</p></li>

<li>
Copy link
Member

Choose a reason for hiding this comment

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

Maybe we could use https://infra.spec.whatwg.org/#list-extend, instead of iterating manually?

Copy link
Member Author

Choose a reason for hiding this comment

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

#11560 would need to change back to iterating the list though.

@zcorpan
Copy link
Member Author

zcorpan commented Jan 21, 2026

Without the referrerpolicy change I think there's no observable difference. Even with document.domain, that doesn't change the origin returned by ancestorOrigins because https://html.spec.whatwg.org/#ascii-serialisation-of-an-origin seralizes the origin's host, not the origin's domain (demo). There's no other way for the parent document to change its origin or its sandboxing flags after the document is created AFAIK.

There is an observable difference here though which is [SameObject] removal and returning a new DOMTokenList every time (which matches Chromium and Gecko but not WebKit). This is tested in https://wpt.fyi/results/html/browsers/history/the-location-interface/location-ancestor-origins-inactive-document.sub.html?label=experimental&label=master&aligned and web-platform-tests/wpt#56635

@domfarolino
Copy link
Member

There's no other way for the parent document to change its origin or its sandboxing flags after the document is created AFAIK.

Right, well technically it changes its origin by virtue of changing its origin's domain with document.domain, but this doesn't impact same origin checks, only same origin-domain checks I guess. Does that match your understanding?

Even with #11560, there should be no observable change due to document.domain, because #11560 only ever refers to "same origin" checks, never "same origin-domain" checks. Is this right?

@zcorpan
Copy link
Member Author

zcorpan commented Jan 21, 2026

Yes, correct. Though with #11560 you can have an iframe with the initial about:blank document, then change the referrerpolicy attribute, then navigate the iframe. The Location object is the same, but ancestorOrigins is different for the two documents. See https://wpt.fyi/results/html/browsers/history/the-location-interface/location-ancestor-origins-referrerpolicy-snapshot.html?label=experimental&label=master&aligned

@zcorpan
Copy link
Member Author

zcorpan commented Jan 21, 2026

@annevk is this OK for WebKit?

zcorpan added a commit to zcorpan/ServiceWorker that referenced this pull request Jan 22, 2026
This term is now a list (not a DOMTokenList) on Document (not Location).

See whatwg/html#12071
@@ -97027,7 +96869,7 @@ interface <dfn interface>Location</dfn> { // but see also <a href="#the-location
[<span>LegacyUnforgeable</span>] undefined <span data-x="dom-location-replace">replace</span>(USVString url);
[<span>LegacyUnforgeable</span>] undefined <span data-x="dom-location-reload">reload</span>();

[<span>LegacyUnforgeable</span>, SameObject] readonly attribute <span>DOMStringList</span> <span data-x="dom-location-ancestorOrigins">ancestorOrigins</span>;
[<span>LegacyUnforgeable</span>] readonly attribute <span>DOMStringList</span> <span data-x="dom-location-ancestorOrigins">ancestorOrigins</span>;
Copy link
Member

Choose a reason for hiding this comment

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

Not returning the same object each time from a getter seems very surprising. Why would we want that? You don't want obj.x !== obj.x (there's a rule for this in the design principles as well).

Copy link
Member Author

Choose a reason for hiding this comment

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

Step 1 says to return a an empty DOMStringList if this's relevant Document is null.

Since the relevant Document can change over time, I think we can't return the same object always. So options are:

  1. Cache whatever was returned the first time the getter is called. (This matches WebKit.)
  2. Return a new object every time. (Gecko, Chromium.)
  3. Maybe throw an InvalidStateError in step 1?

The current spec is wrong since it says SameObject but then in step 1 sometimes returns an empty list (not a DOMStringList).

Copy link
Member

Choose a reason for hiding this comment

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

We don't have to return the same object always to not violate obj.x !== obj.x. Caching or throwing would work. Caching is probably the most compatible. SameObject is indeed wrong if the returned value has to change at certain points in time though.

Can you observe the object changing in WebKit? Maybe with initial about:blank?

Copy link
Member Author

Choose a reason for hiding this comment

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

Yes, for example removing an iframe. But since WebKit caches the return value from the first access, it's different if you call the getter first before or after the iframe is removed.

https://software.hixie.ch/utilities/js/live-dom-viewer/saved/14480

Maybe option 4 is to create an empty DOMStringList in step 1 but cache it, so there can be two separate objects returned.

Copy link
Member

Choose a reason for hiding this comment

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

I think if you navigate an iframe its Location object stays the same, but the relevant Document changes as well. (Even across a cross-origin navigation I'm pretty sure as you're holding a proxy object, essentially.) We probably want to invalidate the list each time the relevant Document changes. Not a 100% sure how to best specify this though.

Copy link
Member Author

Choose a reason for hiding this comment

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

This PR computes a new list when a Document is created and stores it on the Document object, so that is solved by this PR.

About the chaching or throwing issue, should we go with option 4?

Choose a reason for hiding this comment

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

I don't quite follow here. If iframe can be removed, the location have it's relevant document null, then

let a = location.ancestorOrigins;
...
/* relevant document for location becomes null */
...
let b = location.ancestorOrigins;
a !== b; // true

Also

let loc = i.location;
let list = loc.ancestorOrigins;
i.contentWindow.navigation.navigate("www.some.where.else-same-origin.com");
let whatIsThis = list !== loc.ancestorOrigins;

What value does whatIsThis have?

Is there a reason why we can't make this attribute NewObject?

Copy link
Member Author

Choose a reason for hiding this comment

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

@theIDinside it's ok to have a new object when there's a semantic change, but just accessing ancestorOrigins twice without removing an iframe or navigating should give the same object both times.

Copy link
Member Author

Choose a reason for hiding this comment

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

OK I've attempted to fix this in e3091d3

Copy link
Member Author

Choose a reason for hiding this comment

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

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

Labels

None yet

Development

Successfully merging this pull request may close these issues.

4 participants