Skip to content

Commit a406da2

Browse files
domfarolinolozy219
authored andcommitted
Use DOM's post-connection steps for script elements
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.
1 parent 7f2933b commit a406da2

File tree

1 file changed

+113
-17
lines changed

1 file changed

+113
-17
lines changed

source

+113-17
Original file line numberDiff line numberDiff line change
@@ -1772,10 +1772,9 @@ a.setAttribute('href', 'https://example.com/'); // change the content attribute
17721772
or string, means that the <span>length</span> of the text is zero (i.e., not even containing <span
17731773
data-x="control">controls</span> or U+0020 SPACE).</p>
17741774

1775-
<p>An HTML element can have specific <dfn>HTML element insertion steps</dfn> defined for the
1776-
element's <span data-x="concept-element-local-name">local name</span>. Similarly, an HTML element
1777-
can have specific <dfn>HTML element removing steps</dfn> defined for the element's <span
1778-
data-x="concept-element-local-name">local name</span>.</p>
1775+
<p>An HTML element can have specific <dfn>HTML element insertion steps</dfn>, <dfn>HTML element
1776+
post-connection steps</dfn>, and <dfn>HTML element removing steps</dfn>, all defined for the
1777+
element's <span data-x="concept-element-local-name">local name</span>.</p>
17791778

17801779
<p>The <span data-x="concept-node-insert-ext">insertion steps</span> for the HTML Standard, given
17811780
<var>insertedNode</var>, are defined as the following:</p>
@@ -1806,6 +1805,18 @@ a.setAttribute('href', 'https://example.com/'); // change the content attribute
18061805
<span>node document</span>.</p></li>
18071806
</ol>
18081807

1808+
<p>The <span data-x="concept-node-post-insert-ext">post-connection steps</span> for the HTML
1809+
Standard, given <var>insertedNode</var>, are defined as the following:</p>
1810+
1811+
<ol>
1812+
<li><p>If <var>insertedNode</var> is an element whose <span
1813+
data-x="concept-element-namespace">namespace</span> is the <span>HTML namespace</span>, and this
1814+
standard defines <span data-x="html element post-connection steps">HTML element post-connection
1815+
steps</span> for <var>insertedNode</var>'s <span data-x="concept-element-local-name">local
1816+
name</span>, then run the corresponding <span>HTML element post-connection steps</span> given
1817+
<var>insertedNode</var>.</p></li>
1818+
</ol>
1819+
18091820
<p>The <span data-x="concept-node-remove-ext">removing steps</span> for the HTML Standard, given
18101821
<var>removedNode</var> and <var>oldParent</var>, are defined as the following:</p>
18111822

@@ -3204,6 +3215,7 @@ a.setAttribute('href', 'https://example.com/'); // change the content attribute
32043215
<li><dfn data-x="dom-Document-createElementNS" data-x-href="https://dom.spec.whatwg.org/#dom-document-createelementns"><code>createElementNS()</code></dfn> method</li>
32053216
<li><dfn data-x="dom-Document-getElementById" data-x-href="https://dom.spec.whatwg.org/#dom-nonelementparentnode-getelementbyid"><code>getElementById()</code></dfn> method</li>
32063217
<li><dfn data-x="dom-document-getElementsByClassName" data-x-href="https://dom.spec.whatwg.org/#dom-document-getelementsbyclassname"><code>getElementsByClassName()</code></dfn> method</li>
3218+
<li><dfn data-x="dom-Node-append" data-x-href="https://dom.spec.whatwg.org/#dom-node-append"><code>append()</code></dfn> method</li>
32073219
<li><dfn data-x="dom-Node-appendChild" data-x-href="https://dom.spec.whatwg.org/#dom-node-appendchild"><code>appendChild()</code></dfn> method</li>
32083220
<li><dfn data-x="dom-Node-cloneNode" data-x-href="https://dom.spec.whatwg.org/#dom-node-clonenode"><code>cloneNode()</code></dfn> method</li>
32093221
<li><dfn data-x="dom-Document-importNode" data-x-href="https://dom.spec.whatwg.org/#dom-document-importnode"><code>importNode()</code></dfn> method</li>
@@ -3243,6 +3255,7 @@ a.setAttribute('href', 'https://example.com/'); // change the content attribute
32433255
<li>The <dfn data-x-href="https://dom.spec.whatwg.org/#concept-node-pre-insert">pre-insert</dfn>, <dfn data-x="concept-node-insert" data-x-href="https://dom.spec.whatwg.org/#concept-node-insert">insert</dfn>, <dfn data-x="concept-node-append" data-x-href="https://dom.spec.whatwg.org/#concept-node-append">append</dfn>, <dfn data-x="concept-node-replace" data-x-href="https://dom.spec.whatwg.org/#concept-node-replace">replace</dfn>, <dfn data-x="concept-node-replace-all" data-x-href="https://dom.spec.whatwg.org/#concept-node-replace-all">replace all</dfn>, <dfn data-x-href="https://dom.spec.whatwg.org/#string-replace-all">string replace all</dfn>, <dfn data-x="concept-node-remove" data-x-href="https://dom.spec.whatwg.org/#concept-node-remove">remove</dfn>, and <dfn data-x="concept-node-adopt" data-x-href="https://dom.spec.whatwg.org/#concept-node-adopt">adopt</dfn> algorithms for nodes</li>
32443256
<li>The <dfn data-x="concept-tree-descendant" data-x-href="https://dom.spec.whatwg.org/#concept-tree-descendant">descendant</dfn> concept</li>
32453257
<li>The <dfn data-x="concept-node-insert-ext" data-x-href="https://dom.spec.whatwg.org/#concept-node-insert-ext">insertion steps</dfn>,
3258+
<li>The <dfn data-x="concept-node-post-insert-ext" data-x-href="https://dom.spec.whatwg.org/#concept-node-post-connection-ext">post-connection steps</dfn>,
32463259
<dfn data-x="concept-node-remove-ext" data-x-href="https://dom.spec.whatwg.org/#concept-node-remove-ext">removing steps</dfn>,
32473260
<dfn data-x="concept-node-adopt-ext" data-x-href="https://dom.spec.whatwg.org/#concept-node-adopt-ext">adopting steps</dfn>, and
32483261
<dfn data-x-href="https://dom.spec.whatwg.org/#concept-node-children-changed-ext">children changed steps</dfn> hooks for elements</li>
@@ -62378,22 +62391,105 @@ o............A....e
6237862391

6237962392
<hr>
6238062393

62381-
<p>When a <code>script</code> element <var>el</var> that is not <span>parser-inserted</span>
62382-
experiences one of the events listed in the following list, the user agent must
62383-
<span>immediately</span> <span>prepare the script element</span> <var>el</var>:</p>
62394+
<p>The <code>script</code> <span>HTML element post-connection steps</span>, given
62395+
<var>insertedNode</var>, are:</p>
6238462396

62385-
<ul>
62386-
<li>The <code>script</code> element <span>becomes connected</span>.</li>
62397+
<ol>
62398+
<li>
62399+
<p>If <var>insertedNode</var> is not <span>connected</span>, then return.</p>
6238762400

62388-
<li>The <code>script</code> element is <span>connected</span> and a node or document fragment is
62389-
<span data-x="concept-node-insert-ext">inserted</span> into the <code>script</code> element,
62390-
after any <code>script</code> elements <span data-x="concept-node-insert-ext">inserted</span>
62391-
at that time.</li>
62401+
<div class="example">
62402+
<p>This can happen in the case where an earlier-inserted <code>script</code> removes a
62403+
later-inserted <code>script</code>. For instance:</p>
6239262404

62393-
<li>The <code>script</code> element is <span>connected</span> and has a <code
62394-
data-x="attr-script-src">src</code> attribute set where previously the element had no such
62395-
attribute.</li>
62396-
</ul>
62405+
<pre><code class="html">&lt;script>
62406+
const script1 = document.createElement('script');
62407+
script1.innerText = `
62408+
document.querySelector('#script2').remove();
62409+
`;
62410+
62411+
const script2 = document.createElement('script');
62412+
script2.id = 'script2';
62413+
script2.textContent = `console.log('script#2 running')`;
62414+
62415+
document.body.append(script1, script2);
62416+
&lt;/script></code></pre>
62417+
62418+
<p>Nothing is printed to the console in this example. By the time the <span>HTML element
62419+
post-connection steps</span> run for the first <code>script</code> that was atomically inserted
62420+
by <code data-x="dom-Node-append">append()</code>, it can observe that the second
62421+
<code>script</code> is already <span>connected</span> to the DOM. It removes the second
62422+
<code>script</code>, so that by the time <em>its</em> <span>HTML element post-connection
62423+
steps</span> run, it is no longer <span>connected</span>, and does not get <span
62424+
data-x="prepare the script element">prepared</span>.</p>
62425+
</div>
62426+
</li>
62427+
62428+
<li><p>If <var>insertedNode</var> is <span>parser-inserted</span>, then return.<p></li>
62429+
62430+
<li><p><span>Prepare the script element</span> given <var>insertedNode</var>.</p></li>
62431+
</ol>
62432+
62433+
<p>The <code>script</code> <span>children changed steps</span> are:</p>
62434+
62435+
<ol>
62436+
<li><p>Run the <code>script</code> <span>HTML element post-connection steps</span>, given the
62437+
<code>script</code> element.</p></li>
62438+
</ol>
62439+
62440+
<div class="example">
62441+
<p>This has an interesting implication on the execution order of a <code>script</code> element
62442+
and any newly-inserted child <code>script</code> elements. Consider the following snippet:</p>
62443+
62444+
<pre><code class="html">&lt;script id=outer-script>&lt;/script>
62445+
62446+
&lt;script>
62447+
const outerScript = document.querySelector('#outer-script');
62448+
62449+
const start = new Text('console.log(1);');
62450+
const innerScript = document.createElement('script');
62451+
innerScript.textContent = `console.log('inner script executing')`;
62452+
const end = new Text('console.log(2);');
62453+
62454+
outerScript.append(start, innerScript, end);
62455+
62456+
// Logs:
62457+
// 1
62458+
// 2
62459+
// inner script executing
62460+
&lt;/script></code></pre>
62461+
62462+
<p>By the time the second script block executes, the <code data-x="">outer-script</code> has
62463+
already been <span data-x="prepare the script element">prepared</span>, but because it is empty,
62464+
it did not execute and therefore is not marked as <span>already started</span>. The atomic
62465+
insertion of the <code>Text</code> nodes and nested <code>script</code> element have the
62466+
following effects:</p>
62467+
62468+
<ol>
62469+
<li><p>All three child nodes get atomically inserted as children of <code
62470+
data-x="">outer-script</code>; all of their <span data-x="concept-node-insert-ext">insertion
62471+
steps</span> run, which have no observable consequences in this case.</p></li>
62472+
62473+
<li><p>The <code data-x="">outer-script</code>'s <span>children changed steps</span> run, which
62474+
<span data-x="prepare the script element">prepares</span> that script; because its body is now
62475+
non-empty, this executes the contents of the two <code>Text</code> nodes, in order.</p></li>
62476+
62477+
<li><p>The <code>script</code> <span>HTML element post-connection steps</span> finally run for
62478+
<code data-x="">innerScript</code>, causing its body to execute.</p></li>
62479+
</ol>
62480+
</div>
62481+
62482+
<p>The following <span data-x="concept-element-attributes-change-ext">attribute change
62483+
steps</span>, given <var>element</var>, <var>localName</var>, <var>oldValue</var>,
62484+
<var>value</var>, and <var>namespace</var>, are used for all <code>script</code> elements:</p>
62485+
62486+
<ol>
62487+
<li><p>If <var>namespace</var> is not null, then return.</p></li>
62488+
62489+
<li><p>If <var>localName</var> is <code data-x="attr-script-src">src</code>, then run the
62490+
<code>script</code> <span>HTML element post-connection steps</span>, given
62491+
<var>element</var>.</p></li>
62492+
</ol>
6239762493

6239862494
<p id="prepare-a-script">To <dfn>prepare the script element</dfn> given a <code>script</code>
6239962495
element <var>el</var>:</p>

0 commit comments

Comments
 (0)