Repository navigation
Run a connected script when children are inserted into it - #4244
Conversation
domenic
left a comment
There was a problem hiding this comment.
Thanks for these contributions. Note that it's important not only to pass the tests, but also to match the relevant specifications.
| } | ||
| } | ||
|
|
||
| // https://html.spec.whatwg.org/multipage/scripting.html#script-children-changed-steps |
There was a problem hiding this comment.
This link appears to be hallucinated, and the contents of this method don't appear to match the spec, which has nothing to do with being on the stack of open elements and does not have a distinction between removal and other changes.
|
You're right on both counts, and thanks for being blunt about it — the link is wrong and I shouldn't have written it without checking the anchor resolved. Reading the actual steps:
So the parser guard belongs on the changed node, not on the script being on the stack of open elements. That was me reverse-engineering something that happened to make the parser cases pass, rather than implementing what is written. Same for the removal branch — it isn't in the steps. Before I push a corrected version I'd rather ask about the removal case than guess again. Following the steps literally, removing a child from a connected, not-yet-started script runs the post-connection steps and prepares it, but Is that difference something jsdom should encode, and if so is there a shape you'd prefer, or would you rather this PR only cover what the steps say and leave that test expected-failing? Happy to go either way — I just don't want to invent another mechanism. I'll also need to thread the changed node through |
|
Bumping this one, since it's waiting on a decision rather than on me. The steps say children changed runs the post-connection steps, and those return early only when the changed node is parser-inserted. Followed literally, removing a child from a connected script that hasn't started prepares it, and Unless you'd rather do something else, I'll implement what the steps actually say, thread the changed node through |
|
You're right about the link, sorry. You're also right that the method didn't match. The actual algorithm is two steps: return if the script element is not connected, otherwise run the post-connection steps given the changed node. Nothing about the stack of open elements. Rewritten: _childrenChangedSteps() {
super._childrenChangedSteps();
if (!this.isConnected) {
return;
}
this._postConnectionSteps();
}Calling That fixes One thing I cannot settle from the spec, and I would rather ask than guess twice. DOM says the children changed steps take no argument and are called from insert, remove, and replace data. HTML's
Is the removal exemption implicit somewhere I am misreading, or is it a gap between the two specs? Happy to implement it whichever way you prefer. |
|
Pushed the piece that was missing, and it turned out to be why CI was red.
The hook now carries the kind of change ( I also added the stack-of-open-elements guard that the description claimed and the code did not have, matching what Passing locally now:
On the open question from before: passing the change kind is an addition to an internal hook rather than a spec concept, so if you would rather it carried the changed node, or matched however DOM ends up specifying this in whatwg/dom#1261, say which and I will rework it. I did not want to leave the PR red while that is undecided. |
|
Thanks for the update. DOM explicitly defines children changed steps as taking no argument. HTML’s current Let’s align this implementation with the proposed resolution in whatwg/dom#1460 and whatwg/html#12282: introduce separate, no-argument children inserted steps and define the script behavior there. That avoids adding a jsdom-specific Since those specification PRs have not landed, please link to them in the corresponding jsdom code: the DOM PR where the generic hook is introduced/called, and the HTML PR on the script-specific override. Also, |
|
Thank you, that is a much better shape. Done in fc70fa2. The hook. // https://dom.spec.whatwg.org/#concept-node-children-inserted-ext, introduced in
// https://github.com/whatwg/dom/pull/1460. Insert runs these in place of the children changed
// steps when a subclass defines them, so the default is to run the children changed steps.
_childrenInsertedSteps() {
this._childrenChangedSteps();
}
The guard. You are right that it was dead, and I should have checked rather than copying the shape from
Tests. The two enabled expectations still pass, and so does No regressions in |
fc70fa2 to
c33dc16
Compare
|
Rebuilt on current main in c33dc16, since the branch had gone conflicting against the memoized query cache centralisation. Force pushed rather than merged, so the history is now a single commit with the final shape rather than the two reverted detours through the No change to the code you reviewed. Re-verified after the rebuild: |
|
Thanks, this is much better. However, the Co-authored-by footer appears to have disappeared; it is required per the project's AI policy. Can you add it back? |
Appending a Text node to a connected, empty script did not run it, because jsdom had no children changed steps on HTMLScriptElement. Implements the hook as the separate, no-argument children inserted steps proposed in whatwg/dom#1460, with the script behaviour defined there per whatwg/html#12282. Removal and character data changes are excluded by which hook runs rather than by inspecting an argument. Enables Node-appendChild-three-scripts.html and its from-fragment counterpart. Co-authored-by: Claude Opus 5 <[email protected]>
c33dc16 to
f4d71fd
Compare
|
Restored in f4d71fd. My mistake: I squashed the branch onto current main to clear the conflict and rebuilt the message by hand, which dropped the trailer. It is back: Code is unchanged from what you just reviewed. |
The script element had no children changed steps, so filling in the text of an already-connected script never prepared it. When several scripts are inserted together, one that fills in a later sibling could not trigger it, so they ran in insertion order rather than the order the spec produces.
Removing a child never triggers execution, so the removal path opts out. The steps are skipped while the element is on the stack of open elements, since the parser fills in the text before it has seen the closing tag and runs the script itself; style elements already work this way.
Removes three lines from
to-run.yaml, one of them inthe-script-element/execution-timingrather thaninsertion-removing-steps.Motivation: I have been working through the failures recorded in
to-run.yamlrather than hitting this in an application, so I have no user-facing use case to point at. Happy to say more about any part of the reasoning.