-
Notifications
You must be signed in to change notification settings - Fork 283
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Introduce DOM post-connection steps #1261
base: main
Are you sure you want to change the base?
Conversation
PTAL @annevk! I'll prepare an HTML PR and commit message for this, as well as file the appropriate browser bugs. But I'd love your thoughts on the meat of this so far, since it should be good to once those particulars are done. |
Given the old Chromium change that introduced this behavior: - https://source.chromium.org/chromium/chromium/src/+/604e798ec6ee30f44d57a5c4a44ce3dab3a871ed ... the old discussion in: - whatwg/dom#732 (review) - whatwg/html#4354 (comment) ... and the much newer discussion in: - whatwg/dom#1261 - whatwg/html#10188 This CL upstreams an old test ensuring that removal of a child node from a script node that has not "already started" [1], does not trigger script execution. Only the *insertion* of child nodes (to a non-already-started script node) should trigger that script's execution. [1]: https://html.spec.whatwg.org/C#already-started R=nrosenthal@chromium.org Bug: 40150299 Change-Id: I750ccee0a2be834360c557042e975547c8ddd32c
Just a quick note for reviewers that have already taken a look: I've just edited the OP to elaborate on the fact that all browsers never execute a script as a result of removing a child node from it. This was previously pointed out in #732 (review) and whatwg/html#4354 (comment) as a maybe-Chromium-only thing — although it appears at the time, at least Chromium & WebKit behaved this way, and as of now, all browsers agree on this. Above, I've elaborated on what follow-up work can be done to DOM/HTML to align the spec with implementations. (Like I said, I don't think it needs to be handled immediately in this PR or in whatwg/html#10188.) |
Given the old Chromium change that introduced this behavior: - https://source.chromium.org/chromium/chromium/src/+/604e798ec6ee30f44d57a5c4a44ce3dab3a871ed ... the old discussion in: - whatwg/dom#732 (review) - whatwg/html#4354 (comment) ... and the much newer discussion in: - whatwg/dom#1261 - whatwg/html#10188 This CL upstreams an old test ensuring that removal of a child node from a script node that has not "already started" [1], does not trigger script execution. Only the *insertion* of child nodes (to a non-already-started script node) should trigger that script's execution. [1]: https://html.spec.whatwg.org/C#already-started R=nrosenthal@chromium.org Bug: 40150299 Change-Id: I750ccee0a2be834360c557042e975547c8ddd32c Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/5367238 Reviewed-by: Noam Rosenthal <nrosenthal@chromium.org> Commit-Queue: Dominic Farolino <dom@chromium.org> Cr-Commit-Position: refs/heads/main@{#1272330}
Given the old Chromium change that introduced this behavior: - https://source.chromium.org/chromium/chromium/src/+/604e798ec6ee30f44d57a5c4a44ce3dab3a871ed ... the old discussion in: - whatwg/dom#732 (review) - whatwg/html#4354 (comment) ... and the much newer discussion in: - whatwg/dom#1261 - whatwg/html#10188 This CL upstreams an old test ensuring that removal of a child node from a script node that has not "already started" [1], does not trigger script execution. Only the *insertion* of child nodes (to a non-already-started script node) should trigger that script's execution. [1]: https://html.spec.whatwg.org/C#already-started R=nrosenthal@chromium.org Bug: 40150299 Change-Id: I750ccee0a2be834360c557042e975547c8ddd32c Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/5367238 Reviewed-by: Noam Rosenthal <nrosenthal@chromium.org> Commit-Queue: Dominic Farolino <dom@chromium.org> Cr-Commit-Position: refs/heads/main@{#1272330}
…er execution, a=testonly Automatic update from web-platform-tests WPT: Script child removal does not trigger execution Given the old Chromium change that introduced this behavior: - https://source.chromium.org/chromium/chromium/src/+/604e798ec6ee30f44d57a5c4a44ce3dab3a871ed ... the old discussion in: - whatwg/dom#732 (review) - whatwg/html#4354 (comment) ... and the much newer discussion in: - whatwg/dom#1261 - whatwg/html#10188 This CL upstreams an old test ensuring that removal of a child node from a script node that has not "already started" [1], does not trigger script execution. Only the *insertion* of child nodes (to a non-already-started script node) should trigger that script's execution. [1]: https://html.spec.whatwg.org/C#already-started R=nrosenthal@chromium.org Bug: 40150299 Change-Id: I750ccee0a2be834360c557042e975547c8ddd32c Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/5367238 Reviewed-by: Noam Rosenthal <nrosenthal@chromium.org> Commit-Queue: Dominic Farolino <dom@chromium.org> Cr-Commit-Position: refs/heads/main@{#1272330} -- wpt-commits: cc99c62762135f7e193941e32c3f738960c256be wpt-pr: 45085
…er execution, a=testonly Automatic update from web-platform-tests WPT: Script child removal does not trigger execution Given the old Chromium change that introduced this behavior: - https://source.chromium.org/chromium/chromium/src/+/604e798ec6ee30f44d57a5c4a44ce3dab3a871ed ... the old discussion in: - whatwg/dom#732 (review) - whatwg/html#4354 (comment) ... and the much newer discussion in: - whatwg/dom#1261 - whatwg/html#10188 This CL upstreams an old test ensuring that removal of a child node from a script node that has not "already started" [1], does not trigger script execution. Only the *insertion* of child nodes (to a non-already-started script node) should trigger that script's execution. [1]: https://html.spec.whatwg.org/C#already-started R=nrosenthal@chromium.org Bug: 40150299 Change-Id: I750ccee0a2be834360c557042e975547c8ddd32c Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/5367238 Reviewed-by: Noam Rosenthal <nrosenthal@chromium.org> Commit-Queue: Dominic Farolino <dom@chromium.org> Cr-Commit-Position: refs/heads/main@{#1272330} -- wpt-commits: cc99c62762135f7e193941e32c3f738960c256be wpt-pr: 45085
Given the old Chromium change that introduced this behavior: - https://source.chromium.org/chromium/chromium/src/+/604e798ec6ee30f44d57a5c4a44ce3dab3a871ed ... the old discussion in: - whatwg/dom#732 (review) - whatwg/html#4354 (comment) ... and the much newer discussion in: - whatwg/dom#1261 - whatwg/html#10188 This CL upstreams an old test ensuring that removal of a child node from a script node that has not "already started" [1], does not trigger script execution. Only the *insertion* of child nodes (to a non-already-started script node) should trigger that script's execution. [1]: https://html.spec.whatwg.org/C#already-started R=nrosenthal@chromium.org Bug: 40150299 Change-Id: I750ccee0a2be834360c557042e975547c8ddd32c Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/5367238 Reviewed-by: Noam Rosenthal <nrosenthal@chromium.org> Commit-Queue: Dominic Farolino <dom@chromium.org> Cr-Commit-Position: refs/heads/main@{#1272330}
It's because in WebKit, updating of element.form also happens in the post insertion steps. |
Good to know. Do you think WebKit would be willing to change that? It's not a decision that needs to block this PR, since this PR just gives us the primitives available for HTML to do whatever we decide. But so far it seems 2/3 impls agree on that specific case. |
Unlikely. There are some design & performance constraints about this. |
I'd appreciate some elaboration on this, just to understand why other browsers seem to be OK not doing this. However, it is a low priority discussion relative to this PR, since this PR doesn't change anything WRT that scenario specifically. But if you'd be willing to review this PR that would be great. |
Gentle ping for reviews (and for a response to #1261 (comment)). |
There was a problem hiding this comment.
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. I'm not particularly happy about the current hooks as they don't deal with ordering very well. I suppose HTML largely takes care of that, but at some point we should have something better.
I don't think we need implementation bugs for this PR given its largely non-normative nature. Can you update the proposed commit message so it's clearer where the corresponding HTML work happens? I found these two so far:
Given that we have a concrete HTML PR I think we can merge this, but I'd like to give folks a bit more time to comment. So let's say I merge this next week Wednesday, barring any other feedback. |
Let's see if we can briefly discuss this tomorrow. I do see the appeal of Gecko's script runner approach as it avoids the ordering issue. On the other hand, thus far the ordering issue is largely theoretical given the limited number of things we expect will need to be changed. |
Done. I've updated the first few paragraphs in the OP to include this, and I think those paragraphs would make a good commit message, but let me know if you'd like me to distill it down further. |
<var>node</var>, in <a>shadow-including tree order</a>, run the <a>post-insertion steps</a> with | ||
<var>inclusiveDescendant</var>. | ||
</ol> | ||
</li> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Adding the same comment here what I added to #whatwg matrix channel
" I don't know how the insertion step model works in case of mutations. Say, you have an element and it has 3 child elements. That subtree root element is inserted to document tree. Now child 1 has insertion steps which run some script which removes the child and adds it to be the last of the child nodes. So child list is now 2, 3, 1. Do the insertion steps get run on 1 again, because it is now after 2 and 3? I mean, the insertion step would certainly run when 1 is added back to DOM, but would insertion step run again when iterating through the children of the subtree root."
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unless I'm missing something, the pr doesn't match what Blink does. Isn't there a queue here https://source.chromium.org/chromium/chromium/src/+/main:third_party/blink/renderer/core/dom/container_node.cc;l=430;bpv=0;bpt=0 ?
So the difference between ScriptRunner and that approach is just that ScriptRunner queues operations and NodeVector is for queuing nodes which have post insertion stuff to run.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The queue mechanism here and the one in Blink have no observable differences. In Blink, all elements with post-insertion steps end up in that queue of nodes to get their steps run. In this PR, all elements with post-insertion steps also run after insertion. Should be no behavioral difference.
I can’t speak to script runners at all since there is no proposal for them, and I don’t really understand them.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There is difference. Blink can deal with DOM mutations if they happen while going through post insertion steps. The PR does not. Blink implementation has that explicit queue of nodes to go through, the PR just vaguely talks about descendants (and doesn't explain what happens if DOM is mutated).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
And ScriptRunner is like Blink's implementation, but instead of queuing nodes, one would queue operations (kind of like "nano tasks").
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OK thanks for the discussion @smaug----. Anne and I (among others) also discussed this a bit at the last WHATNOT meeting. The latest commit adds a static node list that we populate with all of the inserted nodes. Then we call the post-insertion steps on each of them in that list (if they are connected), so that we don't perform any live tree traversal operations in between post-insertion steps. PTAL!
dom.bs
Outdated
</li> | ||
|
||
<li><p><a for=list>For each</a> <var>node</var> in <var>staticNodeList</var>, if <var>node</var> is | ||
<a>connected</a>, then run the <a>post-insertion steps</a> with <var>node</var>. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(just wondering what should happen, and what browsers do if some earlier post-connected step moves a node, which hasn't get change to run post-connected steps to be elsewhere in the document - so its post-connected steps get run twice. That might be a case to handle separately in each case - but if that is needed, it might be easy to just add is-connected check to the element specific algorithm)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Great scenario. So to make the case more concrete, you're envisioning something like:
- Insert (a) script and (b) iframe at the same time.
- Both are now in the DOM, but we have not yet made it to the step where we iterate over them and run their post-insertion steps (i.e., the spec step you're commenting on above)
- (a) Run the script's post-insertion steps: this moves the iframe around in the DOM. This both:
- Re-inserts the iframe into the DOM
- Runs the iframe's post-insertion steps
- (b) Re-run the iframe's post-insertion steps, even though it exists elsewhere in the DOM now.
Chromium currently runs the post-insertion steps the second time. I think WebKit does the same from looking at their code, but I can't quite re-produce.
Chromium has code that handles this explicitly for iframes here so that we don't try and re-create an iframe's initial document after it has been inserted. And for scripts, I think you would just rely on the already started boolean to not re-execute a script that has already executed due to insertion (Chromium code here).
I lean towards DOM being OK running the post-insertion steps multiple times, and then having per-element checks do the right thing if they don't want to run multiple times. The reason I lean towards this is because I can construct a case where we actually rely on the second invocation of the post-insertion steps doing stuff.
Imagine a case where you insert two scripts at the same time, i.e., document.body.append(script1, script2)
. script2
has an invalid type
and therefore is not executable as-is. script1
does two things:
- Moves
script2
around in the DOM (thus invoking the second script's post-insertion steps for the first time) - Removes
script2
's invalidtype
attribute, making it finally valid (but not immediately executing it)
At least in Chromium, when script2
's post-insertion steps run for the second time (as a result of the outermost document.body.append(script1, script2)
call finally invoking script2
's post-insertion steps), script2
will finally execute. Here's the code snippet:
const script1 = document.createElement('script');
script1.innerText = `
const div = document.querySelector('div');
div.append(script2);
script2.removeAttribute('type');
`;
const script2 = document.createElement('script');
script2.id = 'script2';
// Makes the script invalid so that it does not run upon insertion.
script2.type = 'foo';
script2.innerText = 'console.log("grazie");';
document.body.append(script1, script2);
For some reason WebKit does not run the script, which I think indicates that the post-insertion steps do not run the second time for script2
. I would love to know why; @rniwa might know. I might've investigated this over in whatwg/html#10188 but I can't remember off the top of my head. Either way, hopefully this sheds some light on this scenario. WDYT @smaug----
This PR introduces a node's set of post-connection steps. For any given #concept-node-insert operation, these steps run for each inserted node synchronously after all DOM insertions are complete. This PR is meant to supersede the similar #732.
The goal here is to separate the following:
For any given call to #concept-node-insert, the above model allows us to keep all of (1) running synchronously after each node's insertion (as part of its insertion steps), while pushing all script-executing (or DOM tree-modifying or frame tree-modifying etc.) side effects to the new set of post-connection steps, which run synchronously during insertion, but after all nodes finish their insertion.
This essentially makes insertions "atomic" from the perspective of script, since script will not run until a given batch of DOM insertions are complete. This two-stage approach aligns the spec with a model most similar to Blink & Gecko's implementation, and fixes #808. This PR also helps out with whatwg/html#1127 and #575 (per #732 (comment)).
To accomplish, this we audited all insertion side effects on the web platform in https://docs.google.com/document/d/1Fu_pgSBziVIBG4MLjorpfkLTpPD6-XI3dTVrx4CZoqY/edit#heading=h.q06t2gg4vpw, and catalogued whether they have script-observable side-effects, whether they invoke script, whether we have tests for them, and how each implementation handles them. This gave us a list of present "insertion steps" that should move to the "post-connection steps", because they invoke script and therefore prevent insertions from being "atomic". This PR is powerless without counterpart changes to HTML, which will actually use the post-connection steps for all current insertion steps that invoke script or modify the frame tree. The follow-up HTML work is tracked, at the time of writing this:
Use DOM's post-insertion steps for
<script>
elements html#10188Meta element and DOM post-insertion steps html#10241
<another incoming PR will be made for iframes>
At least two implementers are interested (and none opposed):
script
ordering" test)load
event being fired synchronously, since I guess it fires this event asynchronously..)Tests are written and can be reviewed and commented upon at:
Implementation bugs are filed: N/A given Introduce DOM post-connection steps #1261 (comment).
MDN issue is filed: …
The top of this comment includes a clear commit message to use.
(See WHATWG Working Mode: Changes for more details.)
Two things need more discussion:
<script>
elements html#10188, since it is slightly orthogonal<script>
inserted into it, it must execute after that child script does. Chromium does not do that; the child mutation of the outer script (that inserts the inner<script>
) triggers the execution of the outer script in its post-connection steps, and then after, the execution of the inner script during its post-connection steps. WebKit is aligned with Chrome on this and Firefox is the odd one out. Maybe we're OK aligning with Chrome/Firefox in this case?pagehide
which all browsers do on same-origin iframes, and (2)blur
events (only Chrome does this).Preview | Diff
Footnotes
The only reason WebKit fails this is because due to the lack of post-connection steps, the script executes after the first text node is appended, so by the time the second text node gets attached, the script is already started ↩