Skip to content
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

Use the new definition of the insertion steps #4354

Closed
wants to merge 1 commit into from
Closed

Conversation

nox
Copy link
Member

@nox nox commented Feb 10, 2019

whatwg/dom#732

We defer preparing scripts and updating style blocks during insertion to
make sure all DOM mutations are finished before executing scripts.

This is what Chrome and Firefox seem to already do anyway.


💥 Error: Wattsi server error 💥

PR Preview failed to build. (Last tried on Jan 15, 2021, 7:59 AM UTC).

More

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

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

🔗 Related URL

<html>
<head><title>504 Gateway Time-out</title></head>
<body bgcolor="white">
<center><h1>504 Gateway Time-out</h1></center>
<hr><center>nginx/1.10.3</center>
</body>
</html>

If you don't have enough information above to solve the error by yourself (or to understand to which web service the error is related to, if any), please file an issue.

source Outdated Show resolved Hide resolved
source Outdated
<span data-x="nodes are inserted">inserted</span> into the <code>script</code> element, after any
<code>script</code> elements <span data-x="nodes are inserted">inserted</span> at that time.</li>
<li><p><span>Enqueue</span> the steps to <span data-x="prepare a script">prepare</span>
<var>script</var> to <var>deferredStepsQueue</var>.</p></li>
Copy link
Member

Choose a reason for hiding this comment

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

What if another script has removed this script meanwhile so it's no longer connected? I thought we had to split script execution into a preparation step that had to happen directly, and then an execution step that's deferred.

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 6 of prepare a script is "If the element is not connected, then return. The script is not executed."

Copy link
Member

Choose a reason for hiding this comment

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

That would match Chrome, but not Firefox, per whatwg/dom#575 (comment). That might be okay, but I'll double check if there's test coverage.

@annevk annevk added the compat Standard is not web compatible or proprietary feature needs standardizing label Feb 12, 2019
source Outdated Show resolved Hide resolved
@nox
Copy link
Member Author

nox commented Feb 12, 2019

@bzbarsky @tkent-google

So I found two main differences between Chrome and Firefox:

  • As stated in this comment, they disagree as to when style sheets should be created when inserted at the same time as scripts.
  • As shown in my WPT PR, they disagree about the order in which scripts should be executed when you insert scripts and code in an already-connected empty script element.

My own opinion is that Chrome is more logical on both accounts, i.e. it seems better to me to always create stylesheets before executing scripts, and it seems better to me to always execute scripts in tree order.

@bzbarsky
Copy link
Contributor

i.e. it seems better to me to always create stylesheets before executing scripts

Just to be clear, we're talking about inserting a DOM subtree that has both <script> and <style> elements, with the <script> before the <style> in the DOM, and creating the sheet for the <style> before the <script> runs?

Why does doing that make more sense than just doing the whole thing in tree order?

they disagree about the order in which scripts should be executed when you insert scripts and code in an already-connected empty script element.

I really don't have time to dig through the whole WPT PR, unfortunately. Can you please point to a specific testcase (ideally on http://software.hixie.ch/utilities/js/live-dom-viewer/ so it can just be run easily) that shows this behavior difference?

@nox
Copy link
Member Author

nox commented Feb 12, 2019

Why does doing that make more sense than just doing the whole thing in tree order?

Because all other insertion steps are done before executing scripts, except style sheet creation in Firefox. I don't see why style sheet creation should be postponed like scripts.

I really don't have time to dig through the whole WPT PR, unfortunately. Can you please point to a specific testcase (ideally on http://software.hixie.ch/utilities/js/live-dom-viewer/ so it can just be run easily) that shows this behavior difference?

Sure:

@nox
Copy link
Member Author

nox commented Feb 12, 2019

The link for "stylesheets created before scripts are executed" was wrong, here is the correct one. I fixed it in my comment too.

@bzbarsky
Copy link
Contributor

Because all other insertion steps are done before executing scripts, except style sheet creation in Firefox

I don't believe that's true in Firefox, at least. There's all sorts of stuff that gets run at a "safe point" once insertion is complete, in tree order. Generally stuff that can cause script to execute when it happens. That includes stylesheet creation, script executions, custom element creation callbacks, changes to fullscreen state, changes to what ids are targeting, focus/blur events, etc, etc. See https://searchfox.org/mozilla-central/search?q=AddScriptRunner&case=true for the places that work that way.

I'm not sure whether the spec has this concept of "run at a safe point" (apart from very async things like stable state), but my impression was that part of the question around insertion steps was whethere there should be such a concept and what should use it.

scripts inserted in scripts.

Thank you, that's very helpful. So what happens there in Gecko (and I am open to changing the behavior in this weird edge case, to be clear), is that we go and insert the kids into the parent, as an atomic uninterruptible operation. The <script> kid, when it gets appended, sees that it already has childrem and queues a "run myself when safe". Then we notify up the tree that the descendants have changed. When the parent script receives this notification, it queues a "run myself when safe" thing too. But this happens after the child script has done that.

The current spec status of this stuff is, of course, unclear and hinges on what "a node or document fragment is inserted into the script element" means in item 2 of https://html.spec.whatwg.org/multipage/scripting.html#script-processing-model:prepare-a-script-4, which is exactly what we are trying to define here. It happened to be convenient to implement that item 2 on top of the generic mutation-notification mechanism in Gecko. As I said above, I'm open to some other approach, but not that happy with the typical WebKit approach of just scattering element name checks all over the place to implement things like this (which more naturally leads to the behavior observed in Chrome in this specific edge case).

@nox
Copy link
Member Author

nox commented Feb 12, 2019

I don't believe that's true in Firefox, at least. There's all sorts of stuff that gets run at a "safe point" once insertion is complete, in tree order. Generally stuff that can cause script to execute when it happens. That includes stylesheet creation, script executions, custom element creation callbacks, changes to fullscreen state, changes to what ids are targeting, focus/blur events, etc, etc. See https://searchfox.org/mozilla-central/search?q=AddScriptRunner&case=true for the places that work that way.

Thank you I'll skim through that. I don't understand what you mean by "changes to what ids are targeting" though. What I meant by "insertion steps" are all the things in the HTML that uses that concept from the HTML script, such as setting form owners on form elements, setting the selectedness of options in select elements, etc. Basically all the things that are observable from JS during script execution. At the very least, Chrome seems to do stylesheet creation way before Firefox.

@nox
Copy link
Member Author

nox commented Feb 12, 2019

(Oops submitted too early)

I'm not sure whether the spec has this concept of "run at a safe point" (apart from very async things like stable state), but my impression was that part of the question around insertion steps was whethere there should be such a concept and what should use it.

This is kinda what my DOM changes are trying to create with the "deferredStepsQueue" used in my new insertion algorithm in the DOM PR.

Thank you, that's very helpful. So what happens there in Gecko (and I am open to changing the behavior in this weird edge case, to be clear), is that we go and insert the kids into the parent, as an atomic uninterruptible operation. The <script> kid, when it gets appended, sees that it already has childrem and queues a "run myself when safe". Then we notify up the tree that the descendants have changed. When the parent script receives this notification, it queues a "run myself when safe" thing too. But this happens after the child script has done that.

Yeah that's what I gathered, and it seems quite complex to specify to me, and Chrome does things differently: it starts with the parent script because it executes things in tree order.

The current spec status of this stuff is, of course, unclear and hinges on what "a node or document fragment is inserted into the script element" means in item 2 of https://html.spec.whatwg.org/multipage/scripting.html#script-processing-model:prepare-a-script-4, which is exactly what we are trying to define here. It happened to be convenient to implement that item 2 on top of the generic mutation-notification mechanism in Gecko. As I said above, I'm open to some other approach, but not that happy with the typical WebKit approach of just scattering element name checks all over the place to implement things like this (which more naturally leads to the behavior observed in Chrome in this specific edge case).

Step 2 is changed in this very PR to use the "children added steps" concept I introduced in the DOM PR, specifically because I thought all browsers agreed that the parent script should be executed last, but it turns out that Chrome currently executes it first.

@nox
Copy link
Member Author

nox commented Feb 12, 2019

As I said above, I'm open to some other approach, but not that happy with the typical WebKit approach of just scattering element name checks all over the place to implement things like this (which more naturally leads to the behavior observed in Chrome in this specific edge case).

I don't understand what you mean about element name checks, the changes I made to DOM and HTML don't rely on element name checks, they just make script elements' own "insertion steps" enqueue "prepare a script" to the passed "deferredStepsQueue" thing.

@bzbarsky
Copy link
Contributor

I don't understand what you mean by "changes to what ids are targeting" though.

When you have an idref, what element it references can change when the DOM mutates. This appears in various places in HTML; "setting form owners" is an example of this, because of the "form" attribute.

What I meant by "insertion steps" are all the things in the HTML that uses that concept from the HTML script

Right.

At the very least, Chrome seems to do stylesheet creation way before Firefox.

Sure. In Firefox, stylesheet creation at one point involved doing things that might run script (not page script, but extension script). Those things had to happen at a safe point. Chrome may not have had a way for extensions to hook stylesheet creation, and it's possible that Firefox may not have a way to hook it anymore either. Would need careful auditing.

Anyway, as a general rule, requiring things to happen "more sync" places more constraints on how those things can work and what they're allowed to do. We just need to watch out for that.

and it seems quite complex to specify to me

It's not that hard to specify... You do need to specify the "stuff changed" notification system.

Chrome does things differently: it starts with the parent script because it executes things in tree order

How does the parent script know that it needs to execute at all? And in particular, how does it know that before the child script is inserted and queues its own execution?

Or are you saying that each script queues the execution on itself, not in an actual queue, and then there is a treewalk to collect up the queued-up things?

I don't understand what you mean about element name checks

The way I see it, you have a few options for implementing this thing. You have a generic "add some child nodes" thing on Element. You can then do one of:

  1. Have a hook on HTMLScriptElement that runs before the "add some child nodes" and queues the script to run, then goes on to run "add some child nodes".
  2. Have "add some child nodes" explicitly check what it's adding them to, and if it's a script queue it to run before it inserts the child nodes.
  3. Have "add some child nodes" call a hook on the parent before doing the insertion.
  4. Have "add some child nodes" notify observers of some sort before the insertion.

An ideal solution would have zero cost in the case when the parent is not a script. Firefox has more or less solution 4, but the notification is after insertion, which is why the behavior is different. Solution 2 is closest to zero cost of the remaining things, but is what I meant by "explicit name checks".

That said, I looked at Blink's code just now and their execution of the parent script also comes from a "you had children inserted" notification, which also happens after the children are inserted. So it's worth asking some of the Blink folks exactly what Blink is doing here to produce the observed ordering...

@bzbarsky
Copy link
Contributor

Oh, and the zero cost thing is somewhat important. We basically don't want a bunch of virtual dispatch on the fast paths here if we can avoid it...

@nox
Copy link
Member Author

nox commented Feb 12, 2019

How does the parent script know that it needs to execute at all? And in particular, how does it know that before the child script is inserted and queues its own execution?

Or are you saying that each script queues the execution on itself, not in an actual queue, and then there is a treewalk to collect up the queued-up things?

As far I can tell it follows the model I specified here:

The insertion algorithm passes "deferredStepsQueue" to insertion steps, which are invoked on each inclusive descendants of the nodes we insert (scripts enqueue "prepare a script" in "deferredStepsQueue" here), then the "children added steps" are invoked on the parent (the script parent runs here), then each element in deferredStepsQueue is dequeued and ran, executing the children scripts.

@bzbarsky
Copy link
Contributor

The insertion algorithm passes "deferredStepsQueue" to insertion steps

Ah, so the key part is that insertion doesn't immediately queue things to a "safe point" queue but to some other queue (which may effectively work to implement the safe point, maybe)?

Again, I would be interested in hearing from other implementors what they actually do.

@nox
Copy link
Member Author

nox commented Feb 12, 2019

Ah, so the key part is that insertion doesn't immediately queue things to a "safe point" queue but to some other queue (which may effectively work to implement the safe point, maybe)?

Yes exactly! Basically my thing executes all scripts that were inserted or mutated during a DOM insertion just after this DOM insertion (so all scripts inserted or mutated by inserting a DocumentFragment get executed after all its children were inserted and their insertion steps ran), while Firefox seem to delay some the execution of scripts that are inserted and then mutated by nested DOM mutations until after the DOM mutation that triggered the insertion is done.

So I guess the real question is whether the behaviour I suggest is a "safe enough" point for you. :)

Again, I would be interested in hearing from other implementors what they actually do.

I would love to ping Edge or Safari people but I don't know who they are.

@bzbarsky
Copy link
Contributor

until after the DOM mutation that triggered the insertion is done

I don't think there's any "extra" delaying in Firefox.

So I guess the real question is whether the behaviour I suggest is a "safe enough" point for you. :)

So to be clear, the way "safe point" works in Gecko is that when you start doing "unsafe" stuff you increment a counter; when you stop, you decrement it. When the counter goes to zero, that's a safe point. Generally the end of a mutation is a safe point, but not necessarily.

I would love to ping Edge or Safari people but I don't know who they are.

@rniwa and @travisleithead might know.

@nox
Copy link
Member Author

nox commented Feb 14, 2019

So the Firefox behaviour for the insertion steps of script elements with the new insertion steps model from my DOM PR is:

To run the insertion steps for a script element with script and deferredStepsQueue, the user agent must act as follows:

  1. If script is marked as having "already-started", then return.
  2. If script is marked as being "parser-inserted", then return.
  3. If script is not connected, then return.
  4. Enqueue the following sub-steps to deferredStepsQueue:
    1. Unset script's "already-started" flag.
    2. Prepare script.

This neuters any nested DOM mutation that may trigger again the insertion steps of the script element, as found out in some of the WPT tests I made for this set of change, and as described by @bzbarsky in earlier comments on this PR.

I'll add tests on my WPT PR to check what happens with focused elements and whatnot.

@nox
Copy link
Member Author

nox commented Feb 14, 2019

I couldn't come up with a scenario that implies focused elements.

I found other things to test for though:

  • <meta name> — Can scripts inserted at the same time see referrer policy changes?
  • <meta http-equiv> — How do I even observe those from JS? (wrote a test for that)
  • <iframe> — When is the browsing context created? (wrote a test for that)
  • <source> — Can scripts inserted at the same time see changes to the parent media element's network state? (wrote a test for that)
  • <link rel=stylesheet> — When is link.sheet updated? (wrote a test for that)
  • <link rel=modulepreload> — That can fire an error event in step 12. (weird and only implemented by Chrome)

@nox
Copy link
Member Author

nox commented Feb 18, 2019

It turns out that neither Chrome nor Safari load iframes before scripts when inserted both at the same time. I observed that both when inserting from a div or from a document fragment.

@nox
Copy link
Member Author

nox commented Feb 18, 2019

Only Chrome loads stylesheet links before running scripts, couldn't reproduce that in either Safari or Firefox.

@nox
Copy link
Member Author

nox commented Feb 19, 2019

Only Safari executes scripts before handling new sources in a media element, but that's unsurprising given we already know it inserts nodes one by one when inserting from a document fragment.

@nox
Copy link
Member Author

nox commented Feb 19, 2019

All browsers (except including Safari obviously) handle <meta http-equiv="default-style"> after <script>.

@nox
Copy link
Member Author

nox commented Feb 19, 2019

My test was wrong, it turns out that I forgot to escape a # from an ID selector in a data: URL.

After some experimenting, I ended up finding out that <meta http-equiv=default-style> isn't handled at all when inserted from JS in Firefox (I'll file an issue about that), and that Chrome queues handling it on some sort of "deferredStepsQueue" like script elements.

@nox
Copy link
Member Author

nox commented Feb 25, 2019

@tkent-google Could anyone from the Chrome side of things chime in this thread, please?

@nox
Copy link
Member Author

nox commented Feb 25, 2019

I tried to summarise the discussions here but I failed to do so, IMO every comment from @bzbarsky is important to read because that will make it clear to the Chrome side how they differ from Firefox.

@annevk
Copy link
Member

annevk commented Feb 26, 2019

@domenic @tkent-google the problem is that HTML currently executes script elements upon insertion, but this breaks down if the script element is inserted as part of a DocumentFragment node or child of some other element that is being inserted. In particular, this leaves order ambiguous, it mismatches with implementations as the script can see all the nodes being inserted, but that's at a later point then the insertion steps run. Therefore @nox has been doing an enormous amount of work to figure out what should happen instead. The new setup is roughly that the insertion steps also hand out a queue you can append things to run to. This queue will be iterated over once the insert operation is complete (and all nested inserts in case of DocumentFragment node are handled) and everything in it will be run in order.

Now given this separate queue, this makes all kinds of thing observable, as illustrated by the previous comments. So the question is what's Chrome's opinion on these various scenarios. E.g., is it a good idea that you can have a connected iframe element without a browsing context? Hope this helps.

@nox
Copy link
Member Author

nox commented Mar 15, 2019

Sounds good to me but even just for script Firefox and Chrome disagree on what to do.

@annevk
Copy link
Member

annevk commented Mar 15, 2019

(I also wonder if we can move to only have child node notifications and not separate Text node child notifications. And then filter for nodes that only care about Text node children changing. That seems like a slightly better and future-proof setup.)

@annevk
Copy link
Member

annevk commented Mar 18, 2019

@tkent-google so with two scripts, script1 modifying script2, the order in Chrome is different for:

  1. script1 sets textContent; script1 begin, script2, script1 end;
  2. script1 modifies Text node's data; script1 begin, script1 end, script2
  3. script1 appends a node (any kind); script1 begin, script2, script1 end
  4. script1 removes a node (any kind); script1 begin, script1 end, script2

So based on that I kinda have to question "general child node notifications", no? web-platform-tests/wpt#15886 has these tests in more detail.

@tkent-google
Copy link
Contributor

@tkent-google so with two scripts, script1 modifying script2, the order in Chrome is different for:

HTMLScriptElement doesn't trigger script execution for child node removal and child text data change.
I don't know the reason.
https://cs.chromium.org/chromium/src/third_party/blink/renderer/core/html/html_script_element.cc?type=cs&sq=package:chromium&g=0&l=81
https://trac.webkit.org/browser/webkit/trunk/Source/WebCore/dom/ScriptElement.cpp#L90

@annevk
Copy link
Member

annevk commented Mar 25, 2019

Okay, so there's a general notification, but it comes with context (say a parameter), and how that context is used can be determined by looking at the code, but the rationale for it will likely remain unclear. (It's also not entirely clear to me how that code works, e.g., IsChildRemoval does not return true when type is kAllChildrenRemoved.)

It makes sense to me to add that kind of children changed algorithm to DOM. It's not entirely clear to me how we decide how various elements are to use it.

whatwg/dom#732

We defer preparing scripts and updating style blocks during insertion to
make sure all DOM mutations are finished before executing scripts.

This is what Chrome and Firefox seem to already do anyway.
<p>The user agent must run the <span>update a <code>style</code> block</span> algorithm whenever
one of the following conditions occur:</p>
<p>To run the <span data-x="concept-node-insert-ext">insertion steps</span> for a
<code>style</code> element with <var>style</var> and <var>deferredStepsQueue</var>, the user
Copy link
Member

Choose a reason for hiding this comment

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

FWIW, I'm inclined to remove this from this PR as I don't think we should use deferredStepsQueue for the style element.

Base automatically changed from master to main January 15, 2021 07:57
chromium-wpt-export-bot pushed a commit to web-platform-tests/wpt that referenced this pull request Mar 13, 2024
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

[email protected]

Bug: 40150299
Change-Id: I750ccee0a2be834360c557042e975547c8ddd32c
chromium-wpt-export-bot pushed a commit to web-platform-tests/wpt that referenced this pull request Mar 13, 2024
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

[email protected]

Bug: 40150299
Change-Id: I750ccee0a2be834360c557042e975547c8ddd32c
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/5367238
Reviewed-by: Noam Rosenthal <[email protected]>
Commit-Queue: Dominic Farolino <[email protected]>
Cr-Commit-Position: refs/heads/main@{#1272330}
chromium-wpt-export-bot pushed a commit to web-platform-tests/wpt that referenced this pull request Mar 13, 2024
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

[email protected]

Bug: 40150299
Change-Id: I750ccee0a2be834360c557042e975547c8ddd32c
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/5367238
Reviewed-by: Noam Rosenthal <[email protected]>
Commit-Queue: Dominic Farolino <[email protected]>
Cr-Commit-Position: refs/heads/main@{#1272330}
moz-v2v-gh pushed a commit to mozilla/gecko-dev that referenced this pull request Mar 15, 2024
…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

[email protected]

Bug: 40150299
Change-Id: I750ccee0a2be834360c557042e975547c8ddd32c
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/5367238
Reviewed-by: Noam Rosenthal <[email protected]>
Commit-Queue: Dominic Farolino <[email protected]>
Cr-Commit-Position: refs/heads/main@{#1272330}

--

wpt-commits: cc99c62762135f7e193941e32c3f738960c256be
wpt-pr: 45085
jamienicol pushed a commit to jamienicol/gecko that referenced this pull request Mar 17, 2024
…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

[email protected]

Bug: 40150299
Change-Id: I750ccee0a2be834360c557042e975547c8ddd32c
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/5367238
Reviewed-by: Noam Rosenthal <[email protected]>
Commit-Queue: Dominic Farolino <[email protected]>
Cr-Commit-Position: refs/heads/main@{#1272330}

--

wpt-commits: cc99c62762135f7e193941e32c3f738960c256be
wpt-pr: 45085
BruceDai pushed a commit to BruceDai/wpt that referenced this pull request Mar 25, 2024
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

[email protected]

Bug: 40150299
Change-Id: I750ccee0a2be834360c557042e975547c8ddd32c
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/5367238
Reviewed-by: Noam Rosenthal <[email protected]>
Commit-Queue: Dominic Farolino <[email protected]>
Cr-Commit-Position: refs/heads/main@{#1272330}
domenic pushed a commit that referenced this pull request Aug 29, 2024
Use the newly-introduced DOM Standard "post-connection steps" (see whatwg/dom@0616094), which are run for all nodes in a batch of freshly-inserted nodes, after all DOM insertions take place. The purpose of these steps is to provide an opportunity for script executing side effects to take place during the insertion flow, but after after all DOM mutations are completed atomically.

Before this, the HTML standard executed scripts during the <script> HTML element insertion steps. This means that when a batch of script elements were "atomically" inserted, each script would run synchronously after its DOM insertion and before the next DOM insertion took place.

After this PR, to make progress on whatwg/dom#808 and move to a more "atomic" model where script execution only takes place after all pending DOM tree insertions happen, script execution moves to a model that more closely resembles that of Chromium and Gecko. We push script execution back to the post-connection steps, which run after all DOM insertions are complete. This gives two notable observable differences:

1. All text nodes atomically inserted as children to a script will run when their parent script executes. Imagine you have an empty parser-inserted script element. Before, doing script.append(new Text("..."), new Text("..."), ...) would "prepare" and "execute" the script synchronously after the first text node was inserted, because previously any child node insertion would cause script preparation. With this change, the execution of script is run after the entire batch of children get inserted, because the execution is tied to the "children changed steps", which run after all nodes are inserted.

2. The post-connection steps run after a parent's "children changed steps" run. This means any nested script elements inserted as children to a parent script element will run (as a result of its "post-connection steps") after the parent script gets a chance at running (as a result of its "children changed steps", which run before any post-connection steps). The new spec text has an example of this.

This PR supersedes a portion of #4354.
@domenic
Copy link
Member

domenic commented Aug 29, 2024

@domfarolino the remaining work to obsolete this would be to do a similar PR to #10188 for style elements, right? Is that on your roadmap?

@domfarolino
Copy link
Member

@domenic I don't think so. I agree with @annevk in #4354 (review) (alluded to in whatwg/dom#1261)) in that <style> elements should not be processed in the post-connection steps. This is reflected in the 2/3 browser conformance on the first test in https://wpt.live/dom/nodes/insertion-removing-steps/Node-appendChild-script-and-style.tentative.html, although the remaining tests in that file seem sketchy in WebKit and Firefox.

I've left a comment on this in https://bugs.webkit.org/show_bug.cgi?id=276545#c3, but generally I think WebKit's architecture is more similar to Chromium's in this case, so I don't think there is a strong case to run style application in the post-connection steps.

@domenic
Copy link
Member

domenic commented Sep 4, 2024

Hmm OK, then it sounds like we should close this. Let me know if I misunderstood and there's more work here that we still should work on.

@domenic domenic closed this Sep 4, 2024
dizhang168 pushed a commit to dizhang168/html that referenced this pull request Oct 28, 2024
Use the newly-introduced DOM Standard "post-connection steps" (see whatwg/dom@0616094), which are run for all nodes in a batch of freshly-inserted nodes, after all DOM insertions take place. The purpose of these steps is to provide an opportunity for script executing side effects to take place during the insertion flow, but after after all DOM mutations are completed atomically.

Before this, the HTML standard executed scripts during the <script> HTML element insertion steps. This means that when a batch of script elements were "atomically" inserted, each script would run synchronously after its DOM insertion and before the next DOM insertion took place.

After this PR, to make progress on whatwg/dom#808 and move to a more "atomic" model where script execution only takes place after all pending DOM tree insertions happen, script execution moves to a model that more closely resembles that of Chromium and Gecko. We push script execution back to the post-connection steps, which run after all DOM insertions are complete. This gives two notable observable differences:

1. All text nodes atomically inserted as children to a script will run when their parent script executes. Imagine you have an empty parser-inserted script element. Before, doing script.append(new Text("..."), new Text("..."), ...) would "prepare" and "execute" the script synchronously after the first text node was inserted, because previously any child node insertion would cause script preparation. With this change, the execution of script is run after the entire batch of children get inserted, because the execution is tied to the "children changed steps", which run after all nodes are inserted.

2. The post-connection steps run after a parent's "children changed steps" run. This means any nested script elements inserted as children to a parent script element will run (as a result of its "post-connection steps") after the parent script gets a chance at running (as a result of its "children changed steps", which run before any post-connection steps). The new spec text has an example of this.

This PR supersedes a portion of whatwg#4354.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
compat Standard is not web compatible or proprietary feature needs standardizing
Development

Successfully merging this pull request may close these issues.

6 participants