Skip to content

Commit

Permalink
Use DOM's post-connection steps for script elements
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
domfarolino authored Aug 29, 2024
1 parent c974c42 commit ddd2d0d
Showing 1 changed file with 113 additions and 17 deletions.
130 changes: 113 additions & 17 deletions source
Original file line number Diff line number Diff line change
Expand Up @@ -1772,10 +1772,9 @@ a.setAttribute('href', 'https://example.com/'); // change the content attribute
or string, means that the <span>length</span> of the text is zero (i.e., not even containing <span
data-x="control">controls</span> or U+0020 SPACE).</p>

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

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

<p>The <span data-x="concept-node-post-insert-ext">post-connection steps</span> for the HTML
Standard, given <var>insertedNode</var>, are defined as the following:</p>

<ol>
<li><p>If <var>insertedNode</var> is an element whose <span
data-x="concept-element-namespace">namespace</span> is the <span>HTML namespace</span>, and this
standard defines <span data-x="html element post-connection steps">HTML element post-connection
steps</span> for <var>insertedNode</var>'s <span data-x="concept-element-local-name">local
name</span>, then run the corresponding <span>HTML element post-connection steps</span> given
<var>insertedNode</var>.</p></li>
</ol>

<p>The <span data-x="concept-node-remove-ext">removing steps</span> for the HTML Standard, given
<var>removedNode</var> and <var>oldParent</var>, are defined as the following:</p>

Expand Down Expand Up @@ -3204,6 +3215,7 @@ a.setAttribute('href', 'https://example.com/'); // change the content attribute
<li><dfn data-x="dom-Document-createElementNS" data-x-href="https://dom.spec.whatwg.org/#dom-document-createelementns"><code>createElementNS()</code></dfn> method</li>
<li><dfn data-x="dom-Document-getElementById" data-x-href="https://dom.spec.whatwg.org/#dom-nonelementparentnode-getelementbyid"><code>getElementById()</code></dfn> method</li>
<li><dfn data-x="dom-document-getElementsByClassName" data-x-href="https://dom.spec.whatwg.org/#dom-document-getelementsbyclassname"><code>getElementsByClassName()</code></dfn> method</li>
<li><dfn data-x="dom-Node-append" data-x-href="https://dom.spec.whatwg.org/#dom-node-append"><code>append()</code></dfn> method</li>
<li><dfn data-x="dom-Node-appendChild" data-x-href="https://dom.spec.whatwg.org/#dom-node-appendchild"><code>appendChild()</code></dfn> method</li>
<li><dfn data-x="dom-Node-cloneNode" data-x-href="https://dom.spec.whatwg.org/#dom-node-clonenode"><code>cloneNode()</code></dfn> method</li>
<li><dfn data-x="dom-Document-importNode" data-x-href="https://dom.spec.whatwg.org/#dom-document-importnode"><code>importNode()</code></dfn> method</li>
Expand Down Expand Up @@ -3243,6 +3255,7 @@ a.setAttribute('href', 'https://example.com/'); // change the content attribute
<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>
<li>The <dfn data-x="concept-tree-descendant" data-x-href="https://dom.spec.whatwg.org/#concept-tree-descendant">descendant</dfn> concept</li>
<li>The <dfn data-x="concept-node-insert-ext" data-x-href="https://dom.spec.whatwg.org/#concept-node-insert-ext">insertion steps</dfn>,
<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>,
<dfn data-x="concept-node-remove-ext" data-x-href="https://dom.spec.whatwg.org/#concept-node-remove-ext">removing steps</dfn>,
<dfn data-x="concept-node-adopt-ext" data-x-href="https://dom.spec.whatwg.org/#concept-node-adopt-ext">adopting steps</dfn>, and
<dfn data-x-href="https://dom.spec.whatwg.org/#concept-node-children-changed-ext">children changed steps</dfn> hooks for elements</li>
Expand Down Expand Up @@ -62378,22 +62391,105 @@ o............A....e

<hr>

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

<ul>
<li>The <code>script</code> element <span>becomes connected</span>.</li>
<ol>
<li>
<p>If <var>insertedNode</var> is not <span>connected</span>, then return.</p>

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

<li>The <code>script</code> element is <span>connected</span> and has a <code
data-x="attr-script-src">src</code> attribute set where previously the element had no such
attribute.</li>
</ul>
<pre><code class="html">&lt;script>
const script1 = document.createElement('script');
script1.innerText = `
document.querySelector('#script2').remove();
`;

const script2 = document.createElement('script');
script2.id = 'script2';
script2.textContent = `console.log('script#2 running')`;

document.body.append(script1, script2);
&lt;/script></code></pre>

<p>Nothing is printed to the console in this example. By the time the <span>HTML element
post-connection steps</span> run for the first <code>script</code> that was atomically inserted
by <code data-x="dom-Node-append">append()</code>, it can observe that the second
<code>script</code> is already <span>connected</span> to the DOM. It removes the second
<code>script</code>, so that by the time <em>its</em> <span>HTML element post-connection
steps</span> run, it is no longer <span>connected</span>, and does not get <span
data-x="prepare the script element">prepared</span>.</p>
</div>
</li>

<li><p>If <var>insertedNode</var> is <span>parser-inserted</span>, then return.<p></li>

<li><p><span>Prepare the script element</span> given <var>insertedNode</var>.</p></li>
</ol>

<p>The <code>script</code> <span>children changed steps</span> are:</p>

<ol>
<li><p>Run the <code>script</code> <span>HTML element post-connection steps</span>, given the
<code>script</code> element.</p></li>
</ol>

<div class="example">
<p>This has an interesting implication on the execution order of a <code>script</code> element
and any newly-inserted child <code>script</code> elements. Consider the following snippet:</p>

<pre><code class="html">&lt;script id=outer-script>&lt;/script>

&lt;script>
const outerScript = document.querySelector('#outer-script');

const start = new Text('console.log(1);');
const innerScript = document.createElement('script');
innerScript.textContent = `console.log('inner script executing')`;
const end = new Text('console.log(2);');

outerScript.append(start, innerScript, end);

// Logs:
// 1
// 2
// inner script executing
&lt;/script></code></pre>

<p>By the time the second script block executes, the <code data-x="">outer-script</code> has
already been <span data-x="prepare the script element">prepared</span>, but because it is empty,
it did not execute and therefore is not marked as <span>already started</span>. The atomic
insertion of the <code>Text</code> nodes and nested <code>script</code> element have the
following effects:</p>

<ol>
<li><p>All three child nodes get atomically inserted as children of <code
data-x="">outer-script</code>; all of their <span data-x="concept-node-insert-ext">insertion
steps</span> run, which have no observable consequences in this case.</p></li>

<li><p>The <code data-x="">outer-script</code>'s <span>children changed steps</span> run, which
<span data-x="prepare the script element">prepares</span> that script; because its body is now
non-empty, this executes the contents of the two <code>Text</code> nodes, in order.</p></li>

<li><p>The <code>script</code> <span>HTML element post-connection steps</span> finally run for
<code data-x="">innerScript</code>, causing its body to execute.</p></li>
</ol>
</div>

<p>The following <span data-x="concept-element-attributes-change-ext">attribute change
steps</span>, given <var>element</var>, <var>localName</var>, <var>oldValue</var>,
<var>value</var>, and <var>namespace</var>, are used for all <code>script</code> elements:</p>

<ol>
<li><p>If <var>namespace</var> is not null, then return.</p></li>

<li><p>If <var>localName</var> is <code data-x="attr-script-src">src</code>, then run the
<code>script</code> <span>HTML element post-connection steps</span>, given
<var>element</var>.</p></li>
</ol>

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

0 comments on commit ddd2d0d

Please sign in to comment.