Open josepharhar opened 4 years ago
I think without fully understanding (and specifying) #808 it's hard to reason about how moving-within-a-parent should work. I.e., what side effects we need and do not need.
I think the most logical API that follows existing conventions would be parent.moveChildBefore(Node child, Node? referenceChild)
. However, if sole use case is reordering multiple children parent.moveChildren(Node...)
probably makes more sense. Either way this also requires designing new mutation records as this is a new mutation primitive. And we should probably forbid firing (legacy) mutation events.
I would also like to see some more rationale as to why an arbitrary move (with both parents sharing a common root) is less feasible. What are the particular implementation challenges that we would only expose a more restricted move?
I'm not convinced of spec complexity of modifying existing operations being that much higher (though it would technically be somewhat higher): https://github.com/whatwg/dom/issues/880#issuecomment-671033686
If there are any sites out there which rely on the full reparenting logic, they could become broken.
The disconnectedCallback and connectedCallback reactions fire on custom elements when nodes are shuffled like this. It seems very unlikely to me that this would not break sites (including ours).
I would also like to see some more rationale as to why an arbitrary move (with both parents sharing a common root) is less feasible. What are the particular implementation challenges that we would only expose a more restricted move?
This is due to @rniwa's comments in https://github.com/whatwg/html/issues/5484 against reparenting iframes without reloading them.
I think without fully understanding (and specifying) #808 it's hard to reason about how moving-within-a-parent should work. I.e., what side effects we need and do not need.
Moving within a parent should:
<script>
elements, since that is supposed to be run on insertionAre there other types of side effects I'm not aware of?
@josepharhar Why shouldn't it fire a mutation event of a new type? If a new method is added specifically for moving elements around, they can't possibly fire that mutation without invoking that previously-unknown method anyways, so in theory, a page would have to have its contents updated to experience breakage.
Also, I would expect it to invoke a new hook within custom elements to notify it of nodes being reordered. (This is very useful info for them, BTW, as libraries like A-Frame don't use shadow roots to render their data but otherwise need to know layout order at all times.)
As for the rest, I agree.
@josepharhar Why shouldn't it fire a mutation event of a new type? If a new method is added specifically for moving elements around, they can't possibly fire that mutation without invoking that previously-unknown method anyways, so in theory, a page would have to have its contents updated to experience breakage.
Yeah I presume that we would add new mutation signaling stuff for MutationObserver and whatever the equivalent is for custom elements (I'm not super familiar with either yet). I don't think that we should make new mutation events since they seem to be deprecated and people are interested in removing them from the web - I don't have historical context on this though. Right now I'm just trying to work through the relationship between this new proposed method and #808, which is interested in mutation events. To clarify - when I say "mutation events," I am referring to this: https://developer.mozilla.org/en-US/docs/Web/Guide/Events/Mutation_events
I would really like to better understand what the use cases are. Could someone provide a concrete scenario (not just framework X does Y so they need it) in which this capability is desirable?
I would really like to better understand what the use cases are. Could someone provide a concrete scenario (not just framework X does Y so they need it) in which this capability is desirable?
@sebmarkbage @isiahmeadows @marvinhagemeister @developit could any/all of you elaborate on scenarios where state is lost when reordering child nodes? Links to issues would be greatly appreciated, as well as live examples if possible.
@isiahmeadows already made some live examples with four different frameworks listed in the description of #880. It does sound like appendChild has the desired behavior in that particular case though...?
@josepharhar The transitions issue is that of this:
Though now that I'm taking a closer look, I'm not sure movement semantics would help with transitions, as it reproduces even with simple add/remove. So my issue is independent of this.
Edit: Further confirmed that the transitions issue is independent of this.
This is due to @rniwa's comments in whatwg/html#5484 against reparenting iframes without reloading them.
I don't think that discussion was really settled though. Those arguments would apply to moving within a parent as well as you have to contend with subtrees and multiple elements there too.
I don't think that discussion was really settled though. Those arguments would apply to moving within a parent as well as you have to contend with subtrees and multiple elements there too.
@rniwa said that "Since a node can't have multiple parents, it needs to be disconnected at some point in some internal engine state. That's precisely what caused the problem." When reordering children instead of reparenting them across the DOM, we will never have to change the pointer to the parent node, only the node's sibling pointers and the parent's first/last node pointers. @rniwa this does address your concern, right?
I don't think that discussion was really settled though. Those arguments would apply to moving within a parent as well as you have to contend with subtrees and multiple elements there too.
@rniwa said that "Since a node can't have multiple parents, it needs to be disconnected at some point in some internal engine state. That's precisely what caused the problem." When reordering children instead of reparenting them across the DOM, we will never have to change the pointer to the parent node, only the node's sibling pointers and the parent's first/last node pointers. @rniwa this does address your concern, right?
Well, it does address the previous concern but re-ordering nodes isn't something we've ever done so we'd likely encounter new list of issues with it. I'd still like to learn more about what specific use cases would require this new capability, and why careful manipulations of nodes in the user land won't suffice.
@rniwa what problem would be caused by disconnecting an <iframe>
from its parent if we can also guarantee that no script would run until it is attached again?
@rniwa what problem would be caused by disconnecting an
<iframe>
from its parent if we can also guarantee that no script would run until it is attached again?
Well, that's a big "if". Something like that is not possible to implement in WebKit today.
if DOM had pass by reference equivalent this would not be an issue. Need pointers in DOM.
@rniwa what problem would be caused by disconnecting an
<iframe>
from its parent if we can also guarantee that no script would run until it is attached again?Well, that's a big "if". Something like that is not possible to implement in WebKit today.
Now that I'm thinking about this problem more, one serious challenge is correctly invalidating and updating the computed style of each node when this happens due to things like sibling selector, :first-of-type
, :first-child
, etc... that could put display: none
on object and embed elements since right now, making those elements display: none
would unload the "plugin". There might be other subtle challenges that I haven't thought of like what happens to things like focus, selection, etc... since selection end points may clip if the reorder were to happen.
I'm particularly interested in parentNode.reorderChildren
, which could probably greatly simplify the reconciliation algorithms in some UI libraries, where reordering (in as few operations as possible) is often the only really "tricky" part. Reconciliation generally adds a lot of complexity - there are many different implementations with many pros and cons, and the code tends to be fairly difficult to understand.
Some concerns/reservations about this feature though:
Adding this feature would most likely be a breaking change in terms of MutationObserver
, which would likely require a new type of mutation? Existing code that currently captures all mutations would essentially be incompatible with any newer code that uses the feature - and so, most likely, must be regarded as a breaking change?
It probably can't be polyfilled? If you implement a polyfill that uses existing DOM mutations to affect the same change, MutationObserver
would broadcast these as individual changes.
What happens if you pass nodes that aren't already child-nodes of the given parent? Throwing errors of course is one option. Alternatively, this could work like appendChild
and remove the node from somewhere else, if already present in the document.
What happens if you omit an existing child-node from the list? Again, throwing errors is an option. More likely, what someone meant to do though, was remove the missing nodes.
(3) and (4) makes me think a method like setChildren
might be more meaningful - this would clear out nodes no longer in the given list of node, add or move nodes already present in the document, essentially forcing the list of children into a particular state, in a single operation, which is what most UI libraries are trying to do. This seems more consistent with e.g. appendChild
and is probably more generally useful? For example, this would make it easy (and fast) to implement sortable table rows.
regarding parentNode.reorderChildren
, there is handy hackish way that this can be sort of achieved without much reordering, by using css order attribute with flexbox.
regarding
parentNode.reorderChildren
, there is handy hackish way that this can be sort of achieved without much reordering, by using css order attribute with flexbox.
That won't work for anything like a UI library, where someone would expect to be able to use the order
attribute for their own purposes - responsive design, etc. Also, CSS order does not affect tab-order, screen readers, etc. and therefore really has entirely different, totally unrelated uses. Let's stay on topic.
I came across this issue while searching for existing solutions for part of a UI library I'm writing. In essence, the DOM needs a way to move a child node among its sibling nodes without going through the state reset issues caused by having to first take the node out.
This is possible for special cases where we can just move well-behaved sibling nodes .before()
and .after()
nodes that are trickier to deal with, like document.activeElement
, iframes, custom elements with connectedCallback()
, &c. For the general case, we start needing to find out how to minimize the nodes that we move - which means we have to find the longest common subsequence between the current and desired childNodes
. That translates to massive diff, patch, and state normalizing algorithms - all just to try to minimize the number of times we need to (attempt to correctly) reset reordered element state.
Fundamentally, we could solve this by adding in a primitive operation that moves a node without removing it from its parent first. It wouldn't actually "break" anything except for in a hypothetical pathological case with a third party MutationObserver
making bad assumptions, since this operation is equivalent to moving sibling nodes instead. For convenience, we can have analogs for before
, prepend
, append
, after
, replaceChildren
, and replaceWith
that simply don't start by removing their own child nodes.
Just consider how much more efficient it would be to add a .replaceChildren()
analog that only had to internally switch its childNodes
. Reconciliation (both very time and space-complex) would be massively simplified, leading to browsers decreasing resource consumption by massive amounts for certain classes of UI's. UI libraries get to shrink and browsers have to deal with less code across the board. It would be an absolute shame to block this on the grounds of maybe breaking a backwards third party's MutationObserver
.
I'm sure that we will have to also make something along the lines of MutationRecord.movedNodes
too. In any case, this issue is much more important than it seems to be getting attention for.
We basically need a swap function that doesn't remove elements from DOM.
I'm not intimately familiar with DOM innerworkings but if DOM is seen as a Tree then it's not clear why swapping is so hard. In a tree structure, if I wanted to swap I'd assign one child to a temporary variable and then temp to node being swapped.
t = children[1]
children[1] = children[2]
children[2] = t
But maybe overwriting children[1] causes element to be taken out of DOM and wiped clean, DOM is recalculated/rerendered, even though it's added back in next step at children[2]'s position. So I see two solutions:
1) Either have hidden carry over child, that is never visible, so when swapping 1st element it can be assigned to this ever present placeholder child element and therefore remains part of the DOM preventing it getting cut of from DOM tree and wiped clean.
2) Or have batch render/compute. Don't trigger render on element being removed, allow internal swap function to do step 1,2,3 and then recompute/rendering logic.. Preventing children[1] content from being wiped right when it's over written as it's still referred by DOM.
Thoughts?
Please go read the past discussion in this issue as well as https://github.com/whatwg/html/issues/5484 before making any suggestions or asking why something is hard. A lot of use cases have been already mentioned, and many questions have already been raised as well as corresponding implementation challenges. It's really counterproductive to keep repeating the same discussion every 2-3 years.
Please go read the past discussion in this issue as well as whatwg/html#5484 before making any suggestions ... It's really counterproductive to keep repeating the same discussion every 2-3 years.
I have gone down the rabbit-hole around this issue and I think that an atomic DOM child node move operation would be extremely valuable. Completely ignoring any convenience functions on top - and even going as far as excepting certain tricky cases in order to get this ball rolling - it would still be worth pursuing.
Given that we know this type of operation is highly sought after in libraries like React (to name one), and we know that it would greatly improve web performance in many resource-intensive cases, how do we move forward?
an atomic DOM child node move operation would be extremely valuable
Are you suggesting that the reordering idea proposed here isn't good enough, and that we need to be able to move things all around the document? Just clarifying.
how do we move forward?
@rniwa has pointed out several difficulties with adding this capability to the DOM, and there's clearly a lot more complications and corner cases to consider with full tree moving than there is with the reordering proposed here.
I failed to gain traction in this proposal because I didn't get the feedback I wanted from React or Preact in this comment: https://github.com/whatwg/dom/issues/891#issuecomment-690853328 And the Mithril maintainer said that this isn't actually related to their problem here: https://github.com/whatwg/dom/issues/891#issuecomment-690868930
Sorry for the miscommunication, I meant atomic move among siblings, the primitive operation enabling the proposal. The animation problem that the Mithril maintainer mentioned would be orthogonal to this - if I understood it right - because it was due to operations on siblings. Regardless of any complications due to iframes or any other exceptions we can make, this proposal is extremely worthwhile in order to keep input state and prevent unnecessary (dis)connectedCallbacks. Even if you didn't get an enthusiastic comment from a react/preact member here, it's almost guaranteed that you would if this had their attention. This is extremely underrated and you should push for it as much as you can.
counterproductive to keep repeating the same discussion every 2-3 years
Except to show that this is still very much a current, common, desired feature that shouldn't be cancelled or overlooked due to an apparent lack of participation and interest.
I actually don't know any UI library/framework which would not benefit from efficient replaceChildren
. Efficient and state preserving reordering/adding/removing of nodes is basically their most important and also most difficult to implement function.
DOM diffing is what everyone uses these days to make anything on the Web, because it's behind every single library, utility, framework, out there, because lists, baskets after buying, calendars, anything dynamic, needs to update their view without trashing their content all over all the time ... on my side, I've also created and investigated all possible algorithms, including:
I believe if we had a primitive for this case, we'll be innovating on other areas, instead of keep solving the same thing we needed to solve since AJAX existed 10+ years ago, but I am sure all these attempts used in the real-world aren't a good-enough evidence everyone would benefit from native diffing, right?
counterproductive to keep repeating the same discussion every 2-3 years
it's also very counterproductive to keep ignoring developers voice for all these years, imho.
DOM diffing is what everyone uses these days to make anything on the Web, because it's behind every single library, utility, framework, out there
For the record, this is not an exaggeration - even frameworks that do precise updates (Svelte, Solid, Sinuous, etc.) need to perform reordering operations one level deep, when dealing with lists.
It's also worth noting that this feature would support both types of frameworks equally well: frameworks that do precise updates can reorder a single range of child nodes - while frameworks that use a virtual DOM (or some other means of recursive updates) can call this function recursively. In the latter case, having a native reorder method would eliminate most of the complexity either way.
Is it too soon to start thinking about a prototype/proposal/polyfill?
Actually, can this functionality be polyfilled?
It probably can't be precisely polyfilled? If we're talking about details like triggering reflows and repaints etc.?
@WebReflection you have some experience in this area, I think? Any idea how much these details matter? I mean, they matter in terms of perf, which is one reason I think this proposal is relevant - but in terms of things like reflows, repaints, focus management, input and iframe states and so on, a native implementation probably could/should do certain things "better" than what we can do with existing DOM APIs in a polyfill, right? 🤔
I personally wanted this because I needed to show the same iframe for different screens, however, (react) unmounts a component and then mounts a new one for client side routing. This meant that the same iframe would have to reload every time. I figured if I could swap loaded-iframe with hidden div and then swap it back on route change then I'd have be able to keep the iframe element. I tried keep a JS only reference but the act of appending and removing from the DOM would 'erase' the iframe. So if for whatever reason the replaceNode() func can't be implemented even just having programmatic way to keep a reference to DOM element in a manner that preserves its rendering or something would be an upgrade. At the time I was so frustrated I was open to learn C++ and try adding it to chromium.
preserves its rendering or something
The state you're interested is a nested browsing context. These contexts correspond to each "connectedness lifetime" (insertion to removal) of the element, of which there might be more than one. They are not the element itself, though. Disconnecting the element is somewhat like closing a tab.
Right now, if you have connected iframes [ A, B ] as siblings in that order, there is (to my knowledge) no way to get them in the order [ B, A ] without destroying one of their associated browsing contexts. This is a spot where a reordering primitive would potentially enable preserving nested browsing contexts that currently can't be.
But if I understand correctly, what you've described (a) would not be helped by such a method and (b) wouldn't need such help anyway: this case is already addressed by hiding/showing the iframe without disconnecting it. Unfortunately, React's agnosticism to elements having state/lifecycles goes pretty deep, so even though it's already possible, it may not be possible via React? I think that's something React would have to solve - the platform can't really help if it's disconnecting it and it's not an ordering issue. Plus a lot of stuff indirectly hangs off of the current iframe disconnection behavior, so the value bar to clear for even opt-in modifications to how iframe browsing context lifecycles work is probably really high.
I thought maybe Portals might provide an alternative lifecycle here, but it looks like their browsing contexts will also be destroyed on disconnection. I'm not sure if there was any discussion there about the possibility of contexts that stay "alive" after disconnection, but it seems maybe more plausible in that fresher territory than in the already-so-complicated world of iframes?
Any idea how much these details matter?
These details do matter but, considering every library/framework out there is using diffing based on DOM primitives anyway, I would say it's neither that important nor relevant, performance wise, or at least my libraries never had performance issue swapping nodes (after diffing) around.
That being said, the diffing as meant by libraries and frameworks should operate as such, to provide the best DX:
I've proposed and discussed this approach as batched DOM operations a while ago, where moving nodes within a transaction should've behaved as described in this very same comment of mine, but I can't remember what was the argument then to ignore my idea and not going forward.
Once again, this would be just the ideal world, but I wouldn't mind having just a native DOM differ and let engines optimize the rest, although I really do believe my proposed flow might be the most desired, and least unexpected, for developers.
edit about polyfills: it's relatively trivial to implement one but likely extremely hard to avoid iframes destroying their context ... although I don't think this is important because that happens already in the wild and nobody seems to care much.
Highly related: https://github.com/whatwg/dom/issues/880
A fix for this would resolve that issue as well, as it's another case where the current semantics of removing then re-adding causes state issues (in that case, transition state).
@dead-claudia thanks for linking that! After a quick read I'd happily change my "implementation details" with the moved mutation record, in case the node is before and after the diff, and its index changed ... although I don't feel like this is really missing or super useful for developers, as they can already map and check before, and after, the container.diffNodes(before, after)
operation, but it'd probably be a nice addiction, as long as it won't block progress for too long, imho.
@WebReflection I'd be good with something that's just container.moveAfter(child, refNode)
. Not that key reconciliation wouldn't also be useful, mind you.
That could be the very initial building block, so I’d sign for it too.
Actually, moveBefore would be nicer, imho, instead of insertBefore.
I don't think anyone is intentionally being ignored, but there's only a finite number of problems that can be tackled at any given time and it seems that at the moment nobody is pursuing this problem actively. And I strongly suspect that is primarily due to the complexity of the problem and unresolved technical debt in this area, not because nobody is interested. https://github.com/whatwg/html/issues/5484#issuecomment-622826440 has some starting points if someone wants to pick this up and I'd be happy to mentor anyone interested in tackling this.
I don't think anyone is intentionally being ignored
I am sure there's no ill intention there, yet my point is that developers that see no movement over an open issue filed years ago will keep "bothering" about the very same issue every 2 or 3 years or until it's either solved or closed, and I think that's normal.
This particular issue has been discussed in various shapes and colors up to 10 years to my memory, and yet not much happened, but every single library is in need of such primitive and benefits, at least in terms of unnecessary bloat, would be huge, as well as perf more predictable and, hopefully, less surprise, error, bugs prone.
Thanks for pointing at "a starter" though, I hope something will happen soon(ish) 👍
@annevk Is this summary of the “story so far” accurate?:
Naturally I think solving #808 would be great (I have run into those existing discrepancies many times), but I’m a little fuzzy on why it would need to be solved to address #891 given (4) above — though I’m not 100% sure (4) is accurate. Please feel free to correct anything/everything I got wrong here :)
I think fundamentally, we're looking at a "move" operation. And while it does seem simpler if the parent stays identical, we still need to fully understand what happens with insertion and removal (and this might well have to include mutation events as they haven't disappeared) in order to decide what will not happen (and what might happen instead) with move. More concretely, I don't see why the parent staying identical makes #808 disappear. We need to define/design what happens when you move an iframe
or script
element. And in order to do that we need to know what happens when you remove and insert one.
@annevk we already all know what happens when you remove and insert one, right?
‘cause if we don’t to date, I don’t see why that’s relevant at all to decide what happens when these move instead 🤔
Having this proposal taken forward and still having iframes (and other elements) losing state when moving between different parents would feel like a big opportunity loss, because reordering within the same parent will only cover a portion of the use cases. For example, if iframes are part of a drag and drop interaction, they might need to change parents. Moreover, there are many complex interaction patterns that relly on reparenting.
Is this more general version of "moving" being planned somewhere else? Should a new parallel proposal be started?
Just want to note that we see abuse of the CSS order
property to perform semantic ordering because it's easier/faster than using the DOM. I think it would be helpful if we could redirect such people to an appropriate DOM API that made such reordering (for e.g. sorting lists, table rows, etc.) both easy and fast.
I think that the closest practical solution to this is the shadow dom with the imperative slot api.
// Initially <body>abc</body>
const a = document.createTextNode("a")
const b = document.createTextNode("b")
const c = document.createTextNode("c")
document.body.append(a, b, c)
// body is a shadow host with a manually assigned slot
const slot = document.createElement("slot")
document.body
.attachShadow({
mode: "open",
slotAssignment: "manual"
})
.append(slot)
// can put child nodes in any order, exclude them, &c.
// even iframes will behave as if nothing happened
slot.assign(c, b, a)
Since it's just a slot, there's no fumbling with style sheets or anything either, and it works without custom elements. However, there are restrictions on what elements can be shadow hosts.
I think that the closest practical solution to this is the shadow dom with the imperative slot api.
I don't think this is viable as an alternative - but since this API is already spec'ed and has concensus, a spec for generic DOM reordering should probably reference this.
// even iframes will behave as if nothing happened
The proposal makes no mention of preserving state of iframes, input elements, etc. - it doesn't look like this was spec'ed? So if it happens to work that way, that could be just luck. A generic DOM API likely needs to carefully spec this behavior.
makes no mention of preserving state of iframes, input elements, etc.
There’s nothing unique about that proposal AFAIK — this is how slots themselves behave. Adding and removing slots dynamically is also possible but it isn’t reparenting the things that get non-manually assigned to them, just assigning em.
let shadow = document.body.attachShadow({ mode: "closed" });
document.body.innerHTML = "<iframe name=iframe>";
iframe.document.bgColor = "slot assignment isn’t connection/disconnection";
shadow.innerHTML = "<slot>";
Thanks @wlib, @bathos for highlighting this slot assignment behaviour. This interests me in the context of a few attempts I've made in the past to get predictable behaviour when moving chunks of DOM diagonally (ie from one parent node to another) in the least destructive way possible. I tried playing with this API to see if it could be used as a generic solution for re-ordering.
CSS positional selectors stick to DOM ordering and don't reflect assignment order, which is fair enough. More interesting was that CSS list markers and counter increments seemed to respect assignment order in Firefox, while in Chrome they first fell out of sync, and in subsequent reorderings stuck to their last update.
Here's a sandbox demonstrating the inconsistent behaviour.
This indicates some ambiguity in the 'moving without moving' behaviour – perhaps useful in future API considerations.
@barneycarroll that’s a great find + demo.
In CSS Lists 3, the “tree” whose tree order is used to determine counter inheritance is the flattened tree. It takes/updates any resolved counter values during that algorithm, and though it talks about parents and siblings, the note at the end specifically clarifies that the tree in question is the flattened tree. I think that would mean the Firefox behavior is consistent with what’s specified.
An element inherits its initial set of counters from its parent and preceding sibling. It then takes the values for those counters from the values of the matching counters on its preceding element in tree order (which might be its parent, its preceding sibling, or a descendant of its previous sibling). [...] Note: Counter inheritance, like regular CSS inheritance, operates on the “flattened element tree” in the context of the [DOM].
I’m not good at reading CSS specs, though and may be looking in the wrong place or misinterpreting. @tabatkins would you know whether Blink or Gecko (or neither) is rendering @barneycarroll’s demo correctly?
(aside 4 barney: been off twitter for most of a year now but yr name is among the few I miss encountering. nice to see ya)
Unless otherwise specified, Selectors/Cascade is based on the DOM tree, and then everything else in CSS is based on the flattened tree. When this isn't true, it's almost certainly a browser bug; we're still shaking out wrong assumptions that predate the introduction of the flat tree.
Chrome's "out of sync" counters are absolutely a counter invalidation bug; there is no world in which the counter going from 5-9 is correct.
Summary
The goal of this method is to allow for the reordering of child nodes without the need to remove them and re-append them. Currently, reordering child nodes (by re-appending) causes several undesired side effects:
This is a problem for several frameworks, including React, Preact, and Mithril.
Adding a new API vs changing existing APIs
Instead of adding a new API, we could change existing uses of DOM APIs to avoid reparenting. For example:
In this case, you can see that the script wants to move
firstchild
pastsecondchild
and doesn't necessarily want the browser to removefirstchild
from the DOM and reparent it, so we could try to changeappendChild
to keep the parent throughout the DOM modification and avoid the loss of state.However, I think a new API would be a better solution:
appendChild
and other DOM modification methods would get more complicated due to difficulties defining and standardizing the existing behavior.API shape
I have some ideas for what this method could look like:
parentNode.reorderChildren([childOne, childTwo, childThree])
childNode.movePastNextSibling()
childNode.moveBeforePreviousSibling()
childNode.moveBefore(otherChildNode)
childNode.moveAfter(otherChildNode)
I don't want to bikeshed too much on this until it really sounds like we will add a new method, but if any API shape seems particularly good please speak up so I can start prototyping.
Relationship to https://github.com/whatwg/dom/issues/808
@annevk is https://github.com/whatwg/dom/issues/808 blocking us from having a new way to reorder child nodes? That issue seems more concerned about insertions and mutation events which don't really seem to apply to reordering. Couldn't we just have new spec steps with no special functionality for iframes and scripts and no mutation events?
I made this issue based on feedback in this discussion: https://github.com/whatwg/html/issues/5484