- From: Aryeh Gregor <Simetrical+w3c@gmail.com>
- Date: Mon, 28 Mar 2011 15:28:26 -0400
For execCommand(), it's very important that the position of the selection be defined (and sensible) after taking an editing action. So I've started looking at the problem of defining how Ranges behave when the document mutates, and have determined that current browser behavior (as specced by DOM Level 2 Range) is not optimal. Right now, when a node's parent is changed, Range mutation treats it like the inserted and removed nodes are entirely different. For instance, if you have a range like <p><b>Foo</b> bar <i>{baz}</i> (with the curly braces meaning the range has start (<i>, 0) and end (<i>, 1); square brackets mean the boundary point lies in a text node) and you do p.insertBefore(baz, i), you get <p><b>Foo</b> bar baz<i>{}</i> with the selection collapsed inside <i>. This sort of thing comes up a lot in execCommand(). For instance, if the user wanted to de-italicize the selection given, the natural way to do it would be to move the children of the <i> to its previous siblings, and then remove the <i>. But that gets you <p><b>Foo</b> bar baz{} with the selection collapsed after the <i>'s former children, instead of <p><b>Foo</b> bar {baz} which is what we want: the child selected. I assume browsers hack around this specially for execCommand(), but that's not only a pain to spec, it's also not nice for authors, since their DOM mutations will not benefit from this magic. I've compiled a list of all the times in the spec so far that I mutate the DOM, and the selection behavior I want in each case. I've found that the following behavior would get the desired effect in all cases so far: If a node is moved to a position "immediately before" its original position, then preserve any boundary points in it. "Immediately before" means that there are no nodes lying between the new and old positions: if you made a range whose boundary points are the new and old locations, and ran deleteContents(), it would be a no-op. This would include when the first child of an element is being moved to the element's previous sibling, or to the last child of the previous sibling, or if an element is being moved to its previous sibling's last child, etc. "Preserving boundary points" means that if a boundary point is in the node itself or a descendant, it's transferred as-is to the new location, and also that if it's "immediately before" or "immediately after" the original location (in the same sense as before), it gets moved to the new position's parent right before or after the new location. Likewise, if a node is moved to a position immediately after its original position, preserve its boundary points in the same fashion. As an example of how this would work in a real case, suppose I have <p>{Foo<i>bar</i>} and the user wants to bold the selected text. First you could create a new empty <b>: <p><b></b>{Foo<i>bar</i>} This triggers no new behavior, since the old parent was null. Then move the first selected node there: <p><b>{Foo</b><i>bar</i>} Since we're moving the node immediately before its current position, and the boundary start was immediately before the moved node's original position, we move the boundary start to the new parent of the node, before the new location. Then we move the second selected node: <p><b>{Foo<i>bar</i>}</b> The end of the range was immediately after the moved node, so it gets moved to the node's new parent. (In principle we could have left it in place here, but it makes no real difference, since the resulting range is logically more or less the same, and this way winds up looking a bit neater.) I'd like to know if implementers would be interested in moving to new behavior along these lines. If not, what suggestions would you have for how to spec all this? How is it actually implemented?
Received on Monday, 28 March 2011 12:28:26 UTC