From cda03ae18e0d7e31902dce6fa5440b3e8e04edd0 Mon Sep 17 00:00:00 2001 From: Dominic Farolino Date: Wed, 5 Jun 2024 10:29:11 +0200 Subject: [PATCH] Introduce DOM post-connection steps For any given insert operation, these steps run for each inserted node synchronously after all node insertions are complete. This closes #732. The goal here is to separate the following: 1. Script-observable but not-script-executing insertion side effects - This includes synchronously applying styles to the document, etc... 2. Script-executing insertion side effects - This includes creating an iframe's document and synchronously firing its load event For any given call to 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 https://github.com/whatwg/html/issues/1127 and https://github.com/whatwg/dom/issues/575 (per https://github.com/whatwg/dom/pull/732#issuecomment-467403090). 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 here: - https://github.com/whatwg/html/pull/10188 - https://github.com/whatwg/html/issues/10241 --- dom.bs | 85 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 83 insertions(+), 2 deletions(-) diff --git a/dom.bs b/dom.bs index e36c26e1..e8c1c58f 100644 --- a/dom.bs +++ b/dom.bs @@ -2654,12 +2654,69 @@ of a node into a parent before a child, run the

Specifications may define insertion steps for all or some nodes. The -algorithm is passed insertedNode, as indicated in the insert -algorithm below. +algorithm is passed insertedNode, as indicated in the insert algorithm +below. These steps must not modify the node tree that insertedNode +participates in, create browsing contexts, +fire events, or otherwise execute JavaScript. These steps may +[=queue a global task|queue tasks=] to do these things asynchronously, however. + +

+

While the insertion steps cannot execute JavaScript (among other things), they will + indeed have script-observable consequences. Consider the below example: + +


+ const h1 = document.querySelector('h1');
+
+ const fragment = new DocumentFragment();
+ const script = fragment.appendChild(document.createElement('script'));
+ const style = fragment.appendChild(document.createElement('style'));
+
+ script.innerText= 'console.log(getComputedStyle(h1).color)'; // Logs 'rgb(255, 0, 0)'
+ style.innerText = 'h1 {color: rgb(255, 0, 0);}';
+
+ document.body.append(fragment);
+ 
+ +

The script in the above example logs 'rgb(255, 0, 0)' because + the following happen in order: + +

    +
  1. The insert algorithm runs, which will insert the <{script}> and + style elements in order. + +

      +
    1. The HTML Standard's insertion steps run for the <{script}> element; they do + nothing. [[!HTML]] + +

    2. The HTML Standard's insertion steps run for the + style element; they immediately apply its style rules to the + document. [[!HTML]] + +

    3. The HTML Standard's post-connection steps run for the <{script}> element; they run + the script, which immediately observes the style rules that were applied in the above step. + [[!HTML]] +

    +
  2. +
+
+ +

Specifications may also define +post-connection steps for all or some +nodes. The algorithm is passed connectedNode, as indicated in the +insert algorithm below. + +

The purpose of the post-connection steps is to provide an opportunity for +nodes to perform any connection-related operations that modify the node tree +that connectedNode participates in, create browsing contexts, +or otherwise execute JavaScript. These steps allow a batch of nodes to be +inserted atomically with respect to script, with all major side effects +occurring after the batch insertions into the node tree is complete. This ensures +that all pending node tree insertions completely finish before more insertions can occur. +

Specifications may define children changed steps for all or some nodes. The algorithm is passed no argument and is called from insert, @@ -2761,6 +2818,30 @@ before a child, with an optional suppress observers flag, run parent with nodes, « », previousSibling, and child.

  • Run the children changed steps for parent. + +

  • +

    Let staticNodeList be a list of nodes, initially « ».

    + +

    We collect all nodes before calling the + post-connection steps on any one of them, instead of calling the + post-connection steps while we're traversing the node tree. This is because + the post-connection steps can modify the tree's structure, making live traversal unsafe, + possibly leading to the post-connection steps being called multiple times on the same + node.

    +
  • + +
  • +

    For each node of nodes, in tree order: + +

      +
    1. For each shadow-including inclusive descendant inclusiveDescendant of + node, in shadow-including tree order, append + inclusiveDescendant to staticNodeList. +

    +
  • + +
  • For each node of staticNodeList, if node is + connected, then run the post-connection steps with node.