This spec is incomplete and it is not expected that it will advance beyond draft status. Authors should not use most of these features directly, but instead use JavaScript editing libraries. The features described in this document are not implemented consistently or fully by user agents, and it is not expected that this will change in the foreseeable future. There is currently no alternative to some execCommand actions related to clipboard content and contentEditable=true is often used to draw the caret and move the caret in the block direction as well as a few minor subpoints. This spec is to meant to help implementations in standardizing these existing features. It is predicted that in the future both specs will be replaced by Content Editable and Input Events.

This document defines the behavior of the editing commands that can be executed with execCommand.

Introduction

The APIs specified here were originally introduced in Microsoft's Internet Explorer, but have subsequently been copied by other browsers in a haphazard and imprecise fashion. Although the behavior specified here does not exactly match any browser at the time of writing, it can serve as a target to converge to in the future.

Where the reasoning behind the specification is of interest, such as when major preexisting rendering engines are known not to match it, the reasoning is available by clicking the "comments" button on the right (requires JavaScript). If you have questions about why the specification says something, check the comments first. They're sometimes longer than the specification text itself, and commonly record what the major browsers do and other essential information.

The principles used for writing this reference list are:

Tests

Tests can be found here.

Issues

This specification is mostly feature-complete. It should be considered mostly stable and awaiting implementater review and feedback.

Significant known issues that I need feedback on, or otherwise am not planning to fix just yet:

A variety of other issues are also noted in the text, formatted like this. Feedback would be appreciated on all of them.

TODO:

Also TODO: Things that are only implemented by a couple of browsers and may or may not be useful to spec:

Things I haven't looked at that multiple browsers implement:

Things that would be useful to address for the future but aren't important to fix right now are in comments prefixed with "TODO".

Commands

Properties of commands

This specification defines a number of commands, identified by ASCII case-insensitive strings. Each command can have several pieces of data associated with it:

Supported commands

If you try doing anything with an unrecognized command (except queryCommandSupported), IE10 Developer Preview throws an "Invalid argument" exception. Firefox 15.0a1 throws NS_ERROR_NOT_IMPLEMENTED on querying indeterm/state/value, and returns false from execCommand/queryCommandEnabled. Chrome 19 dev returns false from everything. Opera Next 12.00 alpha throws NOT_SUPPORTED_ERR for execCommand and returns false for enabled/state/value. Originally I went with IE, although of course with a standard exception type. But after discussion (WebKit bug, Mozilla bug), I changed to match WebKit (except that I return "" for value instead of false). The issue is that there are a whole bunch of IE commands that no one else supports or wants to support, and throwing on execCommand() would make lots of pages break. WebKit was unwilling to take the compat risk, so we took the safer option.

Some commands will be supported in a given user agent, and some will not. All commands defined in this specification must be supported, except optionally the copy command, the cut command, and/or the paste command. Additional vendor-specific commands can also be supported, but implementers must prefix any vendor-specific command names with a vendor-specific string (e.g., "ms", "moz", "webkit", "opera").

I.e., no trying to look good on lazy conformance tests by just sticking in a stub implementation that does nothing.

A command that does absolutely nothing in a particular user agent, such that execCommand() never has any effect and queryCommandEnabled() and queryCommandIndeterm() and queryCommandState() and queryCommandValue() each return the same value all the time, must not be supported.

In a particular user agent, every command must be consistently either supported or not. Specifically, a user agent must not permit one page to see the same command sometimes supported and sometimes not over the course of the same browsing session, unless the user agent has been upgraded or reconfigured in the middle of a session. However, user agents may treat the same command as supported for some pages and not others, e.g., if the command is only supported for certain origins for security reasons.

Authors can tell whether a command is supported using queryCommandSupported().

Enabled commands

At any given time, a supported command can be either enabled or not. Authors can tell whether a command is currently enabled using queryCommandEnabled(). Commands that are not enabled do nothing, as described in the definitions of the various methods that invoke commands.

Testing with bold:

IE10PP2 seems to return true if the active range's start node is editable, false otherwise.

Firefox 6.0a2 seems to always return true if there's anything editable on the page, and throw otherwise. (This is bug 676401.)

Chrome 14 dev seems to behave the same as IE10PP2.

Opera 11.11 seems to always return true if there's anything editable on the page, and false otherwise.

Firefox and Opera behave more or less uselessly. IE doesn't make much sense, in that whether a command is enabled seems meaningless: it will execute it on all nodes in the selection, editable or not. Chrome's definition makes sense in that it will only run the command if it's enabled, but it doesn't make much sense to only have the command run if the start is editable.

It's not clear to me what the point of this method is. There's no way we're going to always return true if the command will do something and false if it won't. I originally just stuck with a really conservative definition that happens to be convenient: if there's nothing selected, obviously nothing will work, and we want to bail out early in that case anyway because all the algorithms will talk about the active range. If there are use-cases for it to be more precise, I could make it so.

Bug 16094 illustrated that we don't really want to be able to modify multiple editing hosts at once, nor do we want to do anything if the start and end aren't both editable, so I co-opted this definition to fit my ends.

Among commands defined in this specification, those listed in Miscellaneous commands are always enabled, except for the cut command and the paste command. The other commands defined here are enabled if the active range is not null, its [=range/start node=] is either editable or an [=editing host=], the editing host of its [=range/start node=] is not an EditContext editing host, its [=range/end node=] is either editable or an [=editing host=], the editing host of its [=range/end node=] is not an EditContext editing host, and there is some [=editing host=] that is an [=tree/inclusive ancestor=] of both its [=range/start node=] and its [=range/end node=].

Methods to query and execute commands

        partial interface Document {
          [CEReactions] boolean execCommand(DOMString commandId, optional boolean showUI = false, optional (TrustedHTML or DOMString) value = "");
        };
      

TODO: Add IDL for queryCommand* functions.

TODO: Define behavior for show UI.

When the execCommand(command, show UI, value) method on the {{Document}} interface is invoked, the user agent must run the following steps:

  1. If only one argument was provided, let show UI be false.
  2. If only one or two arguments were provided, let value be the empty string.
  3. For supported: see comment before Supported commands.

    For enabled: I didn't research this closely, but at a first glance, this is possibly how Chrome 14 dev and Opera 11.11 behave. Maybe also Firefox 6.0a2, except it throws if the command isn't enabled, I think. IE9 returns true in at least some cases even if the command is disabled. TODO: Is this right? Maybe we should be returning false in other cases too?

    If command is not supported or not enabled, return false.

  4. If command is not in the Miscellaneous commands section:

    We don't fire events for copy/cut/paste/undo/redo/selectAll because they should all have their own events. We don't fire events for styleWithCSS/useCSS because it's not obvious where to fire them, or why anyone would want them. We don't fire events for unsupported commands, because then if they became supported and were classified with the miscellaneous events, we'd have to stop firing events for consistency's sake.

    1. Let affected editing host be the [=editing host=] that is an [=tree/inclusive ancestor=] of the active range's [=range/start node=] and [=range/end node=], and is not the [=tree/ancestor=] of any [=editing host=] that is an [=tree/inclusive ancestor=] of the active range's [=range/start node=] and [=range/end node=].

      Such an editing host must exist, because otherwise the command would not be enabled.

    2. [=Fire an event=] named "beforeinput" at affected editing host using {{InputEvent}}, with its {{Event/bubbles}} and {{Event/cancelable}} attributes initialized to true, and its {{InputEvent/data}} attribute initialized to null.
    3. If the value returned by the previous step is false, return false.
    4. If command is not enabled, return false.

      We have to check again whether the command is enabled, because the beforeinput handler might have done something annoying like getSelection().removeAllRanges().

    5. Let affected editing host be the [=editing host=] that is an [=tree/inclusive ancestor=] of the active range's [=range/start node=] and [=range/end node=], and is not the [=tree/ancestor=] of any [=editing host=] that is an [=tree/inclusive ancestor=] of the active range's [=range/start node=] and [=range/end node=].

      This new affected editing host is what we'll fire the input event at in a couple of lines. We want to compute it beforehand just to be safe: bugs in the command action might remove the selection or something bad like that, and we don't want to have to handle it later. We recompute it after the beforeinput event is handled so that if the handler moves the selection to some other editing host, the input event will be fired at the editing host that was actually affected.

  5. Take the action for command, passing value to the instructions as an argument.
  6. If the previous step returned false, return false.
  7. If the action modified DOM tree, then [=fire an event=] named "input" at affected editing host using {{InputEvent}}, with its {{Event/isTrusted}} and {{Event/bubbles}} attributes initialized to true, {{InputEvent/inputType}} attribute initialized to the [=map an edit command to input type value|mapped value=] of command, and its {{InputEvent/data}} attribute initialized to null.
  8. Return true.

To map an edit command to input type value, follow this table:

edit commandinputType
backColorformatBackColor
boldformatBold
createLinkinsertLink
fontNameformatFontName
foreColorformatFontColor
strikethroughformatStrikeThrough
superscriptformatSuperscript
deletedeleteContentBackward
forwardDeletedeleteContentForward
indentformatIndent
insertHorizontalRuleinsertHorizontalRule
insertLineBreakinsertLineBreak
insertOrderedListinsertOrderedList
insertParagraphinsertParagraph
insertTextinsertText
insertUnorderedListinsertUnorderedList
justifyCenterformatJustifyCenter
justifyFullformatJustifyFull
justifyLeftformatJustifyLeft
justifyRightformatJustifyRight
outdentformatOutdent
cutdeleteByCut
pasteinsertFromPaste
redohistoryRedo
undohistoryUndo
If no mapping exists, return an empty string.

When the queryCommandEnabled(command) method on the {{Document}} interface is invoked, the user agent must run the following steps:

  1. See comment before Supported commands.

  2. Return true if command is both supported and enabled, false otherwise.

When the queryCommandIndeterm(command) method on the {{Document}} interface is invoked, the user agent must run the following steps:

  1. For supported: see comment before Supported commands.

    What happens if you call queryCommand(Indeterm|State|Value)() on a command where it makes no sense?

    IE9 consistently returns false for all three. However, any command that has a state defined also has a value defined, which is equal to the state: it returns boolean true or false.

    Firefox 6.0a2 consistently throws NS_ERROR_FAILURE for indeterm/state if not supported, and returns an empty string for value. Exceptions include unlink (seems to always return indeterm/state false), and styleWithCss/useCss (throw NS_ERROR_FAILURE even for value).

    Chrome 14 dev returns false for all three, and even does this for unrecognized commands. It also always defines value if state is defined: it returns the state cast to a string, either "true" or "false".

    Opera 11.11 returns false for state and "" for value (it doesn't support indeterm). Like Chrome, this is even for unrecognized commands.

    Gecko's behavior is the most useful. If the author tries querying some aspect of a command that makes no sense, they shouldn't receive a value that looks like it might make sense but is actually just a constant. Originally, I went even further than Gecko: I required exceptions even for value, since doing otherwise makes no sense. But throwing more exceptions is less compatible on the whole than throwing more exceptions, so based on discussion, I switched to a behavior more like Opera, which is more or less IE/WebKit behavior but made slightly more sane.

    If command is not supported or has no indeterminacy, return false.

  2. Return true if command is indeterminate, otherwise false.

When the queryCommandState(command) method on the {{Document}} interface is invoked, the user agent must run the following steps:

  1. See comment on the comparable line for queryCommandIndeterm().

    If command is not supported or has no state, return false.

  2. If the state override for command is set, return it.
  3. Return true if command's state is true, otherwise false.

Firefox 6.0a2 always throws an exception when this is called. Opera 11.11 seems to return false if there's nothing editable on the page, which is unhelpful. The spec follows IE9 and Chrome 14 dev. The reason this is useful, compared to just running one of the other methods and seeing if you get a NOT_SUPPORTED_ERR, is that other methods might throw different exceptions for other reasons. It's easier to check a boolean than to check exception types, especially since as of June 2011 UAs aren't remotely consistent on what they do with unsupported commands.

Actually, correction: Firefox < 15ish throws an exception if nothing editable is on the page. Otherwise it behaves just like IE/Chrome. See Mozilla bug 742240.

When the queryCommandSupported(command) method on the {{Document}} interface is invoked, the user agent must return true if command is supported and available within the current script on the current site, and false otherwise.

When the queryCommandValue(command) method on the {{Document}} interface is invoked, the user agent must run the following steps:

  1. This is what Firefox 6.0a2 and Opera 11.11 seem to do when the command isn't enabled. Chrome 14 dev seems to return the string "false", and IE9 seems to return boolean false. For the case where there's no value, or the command isn't supported, see the comment on the comparable line for queryCommandIndeterm().

    If command is not supported or has no value, return the empty string.

  2. Yuck. This is incredibly messy, as are lots of other fontSize-related things, but I don't want to define a whole second notion of value for the sake of a single command . . .

    If command is "fontSize" and its value override is set, convert the value override to an integer number of pixels and return the legacy font size for the result.

  3. If the value override for command is set, return it.
  4. Return command's value.

All of these methods must treat their command argument ASCII case-insensitively.

The methods in this section have mostly been designed so that the following invariants hold after execCommand() is called, assuming it didn't throw an exception:

The first two points do not always hold for strikethrough or underline, because it can be impossible to unset text-decoration in CSS. Also, by design, the state of insertOrderedList and insertUnorderedList might be true both before and after calling, because they only remove one level of indentation. unlink should set the value to null. And finally, the state of the various justify commands should always be true after calling, and the value should always be the appropriate string ("center", "justify", "left", or "right"). Any other deviations from these invariants are bugs in the specification.

Common definitions

An HTML element is an {{Element}} whose [=Element/namespace=] is the HTML namespace.

A prohibited paragraph child name is "address", "article", "aside", "blockquote", "caption", "center", "col", "colgroup", "dd", "details", "dir", "div", "dl", "dt", "fieldset", "figcaption", "figure", "footer", "form", "h1", "h2", "h3", "h4", "h5", "h6", "header", "hgroup", "hr", "li", "listing", "menu", "nav", "ol", "p", "plaintext", "pre", "section", "summary", "table", "tbody", "td", "tfoot", "th", "thead", "tr", "ul", or "xmp".

These are all the things that will close a <p> if found as a descendant. I think. Plus table stuff, since that can't be a descendant of a p either, although it won't auto-close it.

A prohibited paragraph child is an HTML element whose [=Element/local name=] is a prohibited paragraph child name.

The block/inline node definitions are CSS-based. "Prohibited paragraph child" is conceptually similar to "block node", but based on the element name. Generally we want to use block/inline node when we're interested in the visual effect, and prohibited paragraph children when we're concerned about parsing or semantics. TODO: Audit all "block node" usages to see if they need to become "visible block node", now that block nodes can be invisible (if they descend from display: none).

A block node is either an {{Element}} whose "display" property does not have resolved value "inline" or "inline-block" or "inline-table" or "none", or a [=document=], or a {{DocumentFragment}}.

An inline node is a node that is not a block node.

Something is editable if it is a node; it is not an [=editing host=]; it does not have a contenteditable attribute set to the false state; its [=tree/parent=] is an [=editing host=] or editable; and either it is an HTML element, or it is an svg or math element, or it is not an {{Element}} and its [=tree/parent=] is an HTML element.

An editable node cannot be a [=document=] or {{DocumentFragment}}, its [=tree/parent=] cannot be null, and it must descend from either an {{Element}} or a [=document=].

The editing host of node is null if node is neither editable nor an [=editing host=]; node itself, if node is an [=editing host=]; or the nearest [=tree/ancestor=] of node that is an [=editing host=], if node is editable.

Two nodes are in the same editing host if the editing host of the first is non-null and the same as the editing host of the second.

Barring bugs, the algorithms here will not alter the attributes of a non-editable element; will not remove a non-editable node from its parent (except to immediately give it a new parent in the same editing host); and will not add, remove, or reorder children of a node unless it is either editable or an editing host. An editing host is never editable, so authors are assured that editing commands will only modify the editing host's contents and not the editing host itself.

A collapsed line break is a [^br^] that begins a line box which has nothing else in it, and therefore has zero height.

Is this a good definition at all? I mean things like <p>foo<br></p>, or the second one in <p>foo<br><br></p>. The way I test it is by adding a text node after it containing a zwsp; if that changes the offsetHeight of its nearest non-inline ancestor, I deem it collapsed. But what if it happens to be display: none right now, for instance? Or its ancestor has a fixed height? Would it be better to use some DOM-based definition?

TODO: The thing about li is a not very nice hack. The issue is that an li won't collapse even if it has no children at all, but that's not true in all browsers (at least not in Opera 11.11), and also it breaks assumptions elsewhere. E.g., if it gets turned into a p.

An extraneous line break is a [^br^] that has no visual effect, in that removing it from the DOM would not change layout, except that a [^br^] that is the sole child of an [^li^] is not extraneous.

Also possibly a bad definition. Again, I test by just removing it and seeing what happens. (Actually, setting display: none, so that it doesn't mess up ranges.)

A whitespace node is either a {{Text}} node whose {{CharacterData/data}} is the empty string; or a {{Text}} node whose {{CharacterData/data}} consists only of one or more tabs (0x0009), line feeds (0x000A), carriage returns (0x000D), and/or spaces (0x0020), and whose [=tree/parent=] is an {{Element}} whose resolved value for "white-space" is "normal" or "nowrap"; or a {{Text}} node whose {{CharacterData/data}} consists only of one or more tabs (0x0009), carriage returns (0x000D), and/or spaces (0x0020), and whose [=tree/parent=] is an {{Element}} whose resolved value for "white-space" is "pre-line".

node is a collapsed whitespace node if the following algorithm returns true:

This definition is also bad. It's a crude attempt to emulate CSS2.1 16.6.1, but leaving out a ton of the subtleties. I actually don't want the exact CSS definitions, because those depend on things like where lines are broken, but I'm not sure this definition is right anyway. E.g., what about a pre-line text node consisting of a single line break that's at the end of a block? That collapses, same idea as an extraneous line break. We could also worry about nodes containing only zwsp or such if we wanted, or display: none, or . . .

  1. If node is not a whitespace node, return false.
  2. If node's {{CharacterData/data}} is the empty string, return true.
  3. Let ancestor be node's [=tree/parent=].
  4. If ancestor is null, return true.
  5. If the "display" property of some [=tree/ancestor=] of node has resolved value "none", return true.
  6. While ancestor is not a block node and its [=tree/parent=] is not null, set ancestor to its [=tree/parent=].
  7. At this point we know node consists of some whitespace, of a sort that will collapse if it's at the start or end of a line. We go backwards until we find the first block boundary, and if everything until there is invisible or whitespace, we conclude that node is collapsed. We assume a block boundary is either when we hit a line break or block node, or we hit the end of ancestor (which is the nearest ancestor block node). All this is very imprecise, of course, but it's fairly simple and will work in common cases.

    We have to avoid invoking the definition of "visible" here to avoid infinite recursion: that depends on the concept of collapsed whitespace nodes. Instead, we repeat the parts we need, which turns out to be "not much of it".

    Let reference be node.

  8. While reference is a [=tree/descendant=] of ancestor:
    1. Let reference be the node before it in [=tree order=].
    2. If reference is a block node or a [^br^], return true.
    3. If reference is a {{Text}} node that is not a whitespace node, or is an [^img^], break from this loop.
  9. We found something before our text node on (probably) the same line, so presumably it's not at the line's start. Now we need to look forward and see if we're at the line's end. If we aren't there either, then we assume we're not collapsed, so return false.

    Let reference be node.

  10. While reference is a [=tree/descendant=] of ancestor:
    1. Let reference be the node after it in [=tree order=], or null if there is no such node.
    2. If reference is a block node or a [^br^], return true.
    3. If reference is a {{Text}} node that is not a whitespace node, or is an [^img^], break from this loop.
  11. Return false.

TODO: Consider whether we really want to depend on img specifically here. It seems more likely that we want something like "any replaced content that has nonzero height and width" or such. When fixing this, make sure to audit for other occurrences of this assumption.

Something is visible if it is a node that either is a block node, or a {{Text}} node that is not a collapsed whitespace node, or an [^img^], or a [^br^] that is not an extraneous line break, or any node with a visible [=tree/descendant=]; excluding any node with an [=tree/inclusive ancestor=] {{Element}} whose "display" property has resolved value "none".

Something is invisible if it is a node that is not visible.

TODO: Reconsider whether we want to lump invisible nodes in here. If we don't and change the definition, make sure to audit all callers, since then a block could have collapsed block prop descendants that aren't children.

A collapsed block prop is either a collapsed line break that is not an extraneous line break, or an {{Element}} that is an inline node and whose [=tree/children=] are all either invisible or collapsed block props and that has at least one [=tree/child=] that is a collapsed block prop.

A collapsed block prop is something like the <br> in <p><br></p>, or the <br> and <span> in <p><span><br></span></p>. These are necessary to stop the block from having zero height when it has no other contents, but serve no purpose and should be removed once the block has other contents that stop it from collapsing.

TODO: I say "first range" because I think that's what Gecko actually does, and Gecko is the only one that allows multiple ranges in a selection. This is keeping in mind that it stores ranges sorted by start, not by the order the user added them, and silently removes or shortens existing ranges to avoid overlap. It probably makes the most sense in the long term to have the command affect all ranges. But I'll leave this for later.

The active range is the [=range=] of the selection given by calling getSelection() on the context object. (Thus the active range may be null.)

Each {{Document}} has a boolean CSS styling flag associated with it, which must initially be false. (The styleWithCSS command can be used to modify or query it, by means of the execCommand() and queryCommandState() methods.)

Each {{Document}} is associated with a string known as the default single-line container name, which must initially be "div". (The defaultParagraphSeparator command can be used to modify or query it, by means of the execCommand() and queryCommandValue() methods.)

For some commands, each {{Document}} must have a boolean state override and/or a string value override. These do not change the command's state or value, but change the way some algorithms behave, as specified in those algorithms' definitions. Initially, both must be unset for every command. Whenever the number of [=ranges=] in the selection changes to something different, and whenever a [=boundary point=] of the [=range=] at a given index in the selection changes to something different, the state override and value override must be unset for every command. The value override for the backColor command must be the same as the value override for the hiliteColor command, such that setting one sets the other to the same thing and unsetting one unsets the other.

The primary purpose of state and value overrides is that if the user runs a command like bold with a collapsed selection, then types something without moving the cursor, they expect it to have the given style (bold or such). Thus the commands like bold set state and value overrides, and insertText checks for them and applies them to the newly-inserted text. Other commands like delete also interact with overrides.

See bug 16207.

When document.open() is called and a [=document=]'s singleton objects are all replaced by new instances of those objects, editing state associated with that document (including the CSS styling flag, default single-line container name, and any state overrides or value overrides) must be reset.

Of course, any action that replaces a [=document=] object entirely, such as reloading the page, will also reset any editing state associated with the document.

When this specification refers to a method or attribute that is defined in a specification, the user agent must treat the method or attribute as defined by that specification. In particular, if a script has overridden a standard property with a custom one, the user agent must only use the overridden property when a script refers to it, and must continue to use the specification-defined behavior when this specification refers to it.

When a list or set of nodes is assigned to a variable without specifying the order, they must be initially in [=tree order=], if they share a root. (If they don't share a root, the order will be specified.) When the user agent is instructed to run particular steps for each member of a list, it must do so sequentially in the list's order.

Common algorithms

Assorted common algorithms

To move a node to a new location, preserving ranges, remove the node from its original [=tree/parent=] (if any), then insert it in the new location. In doing so, follow these rules instead of those defined by the [=insert=] and [=remove=] algorithms:

Many of the algorithms in this specification move nodes around in the DOM. The normal rules for range mutation require that any range endpoints inside those nodes are moved to the node's parent as soon as the node is moved, which would corrupt the selection. For instance, if the user selects the text "foo" and then bolds it, first we produce <b></b>foo, then <b>foo</b>. When we move the "foo" text node into its new parent, we have to do so "preserving ranges", so that the text "foo" is still selected.

  1. Let node be the moved node, old parent and old index be the old [=tree/parent=] (which may be null) and [=tree/index=], and new parent and new index be the new [=tree/parent=] and [=tree/index=].
  2. This is actually implicit, but I state it anyway for completeness.

    If a [=boundary point=]'s node is the same as or a [=tree/descendant=] of node, leave it unchanged, so it moves to the new location.

  3. If a [=boundary point=]'s node is new parent and its [=boundary point/offset=] is greater than new index, add one to its [=boundary point/offset=].
  4. If a [=boundary point=]'s node is old parent and its [=boundary point/offset=] is old index or old index + 1, set its node to new parent and add new indexold index to its [=boundary point/offset=].
  5. If a [=boundary point=]'s node is old parent and its [=boundary point/offset=] is greater than old index + 1, subtract one from its [=boundary point/offset=].

TODO: Do we want to get rid of attributes that are no longer allowed here?

To set the tag name of an {{Element}} element to new name:

This is needed because the DOM doesn't allow any way of changing an existing element's name. Sometimes we want to, e.g., convert a markup element to a span. In that case we invoke this algorithm to create a new element, move it to the right place, copy attributes from the old element, move the old element's children, and remove the old element.

  1. If element is an HTML element with [=Element/local name=] equal to new name, return element.
  2. If element's [=tree/parent=] is null, return element.
  3. Let replacement element be the result of calling createElement(new name) on the {{Node/ownerDocument}} of element.
  4. Insert replacement element into element's [=tree/parent=] immediately before element.
  5. Copy all attributes of element to replacement element, in order.
  6. While element has [=tree/children=], append the first [=tree/child=] of element as the last [=tree/child=] of replacement element, preserving ranges.
  7. Remove element from its [=tree/parent=].
  8. Return replacement element.

To remove extraneous line breaks before a node node:

<br> sometimes has no effect in CSS, such as in the markup foo<br><p>bar</p>. In such cases we like to remove the extra markup to keep things tidy.

  1. Let ref be the {{Node/ previousSibling}} of node.
  2. If ref is null, abort these steps.
  3. While ref has [=tree/children=], set ref to its {{Node/lastChild}}.
  4. While ref is invisible but not an extraneous line break, and ref does not equal node's [=tree/parent=], set ref to the node before it in [=tree order=].
  5. If ref is an editable extraneous line break, remove it from its [=tree/parent=].

To remove extraneous line breaks at the end of a node node:

  1. Let ref be node.
  2. While ref has [=tree/children=], set ref to its {{Node/lastChild}}.
  3. While ref is invisible but not an extraneous line break, and ref does not equal node, set ref to the node before it in [=tree order=].
  4. If ref is an editable extraneous line break:
    1. If the block ends with <span><br></span>, for instance, we want to remove the span too.

      While ref's [=tree/parent=] is editable and invisible, set ref to its [=tree/parent=].

    2. Remove ref from its [=tree/parent=].

To remove extraneous line breaks from a node, first remove extraneous line breaks before it, then remove extraneous line breaks at the end of it.

Wrapping a list of nodes

To wrap a list node list of consecutive [=tree/sibling=] nodes, run the following algorithm. In addition to node list, the algorithm accepts two inputs: an algorithm sibling criteria that accepts a node as input and outputs a boolean, and an algorithm new parent instructions that accepts nothing as input and outputs a node or null. If not provided, sibling criteria returns false and new parent instructions returns null.

This algorithm basically does two things. First, it looks at the previous and next siblings of the nodes in node list. If running sibling criteria on one or both of the siblings returns true, the nodes in node list are moved into the sibling(s). Otherwise, new parent instructions is run, and the result is used to wrap node list. For instance, to wrap node list in a <b>, one might invoke this algorithm with sibling criteria returning true only for <b> elements and new parent instructions creating and returning a new <b> element.

  1. We need to treat [^br^]s as visible here even if they're not, because wrapping them might be significant even if they're invisible: it can turn an extraneous line break into a non-extraneous one.

    If every member of node list is invisible, and none is a [^br^], return null and abort these steps.

  2. If node list's first member's [=tree/parent=] is null, return null and abort these steps.
  3. Trailing br's like this always need to go along with their line. Otherwise they'll create an extra line if we wrap in a block element, instead of vanishing as they should.

    If node list's last member is an inline node that's not a [^br^], and node list's last member's {{Node/nextSibling}} is a [^br^], append that [^br^] to node list.

  4. See bug 13811, bug 14231. If there's a non-adjacent sibling that matches the sibling criteria and only invisible nodes intervene, we want to skip over the invisible nodes. For instance, bolding <b>foo</b><!--bar-->[baz] should produce <b>foo<!--bar-->baz</b>. Similarly, and more usefully, creating an ordered list with <ol><li>foo</ol> <p>[bar]</p> should produce <ol><li>foo</li> <li>[bar]</ol>, not <ol><li>foo</ol> <ol><li>[bar]</ol>.

    While node list's first member's {{Node/previousSibling}} is invisible, prepend it to node list.

  5. While node list's last member's {{Node/nextSibling}} is invisible, append it to node list.
  6. If the {{Node/previousSibling}} of the first member of node list is editable and running sibling criteria on it returns true, let new parent be the {{Node/previousSibling}} of the first member of node list.
  7. Otherwise, if the {{Node/ nextSibling}} of the last member of node list is editable and running sibling criteria on it returns true, let new parent be the {{Node/nextSibling}} of the last member of node list.
  8. Otherwise, run new parent instructions, and let new parent be the result.
  9. This can only happen if new parent instructions is run and it returns null. This can be used to only merge with adjacent siblings, in case you don't want to create a new parent if that fails.

    If new parent is null, abort these steps and return null.

  10. Most callers will create a new element to return in new parent instructions, whose parent will therefore be null. But they can also return an existing node if that makes sense, so the nodes will be moved to an uncle or something. The toggle lists algorithm makes use of this.

    If new parent's [=tree/parent=] is null:

    1. Insert new parent into the [=tree/parent=] of the first member of node list immediately before the first member of node list.
    2. Basically, we want any boundary points around the wrapped nodes to go inside the wrapper. Without this step, wrapping "{}<br>" in a blockquote would go like

      {}<br>
      -> {}<blockquote></blockquote><br>
      -> {}<blockquote><br></blockquote>.
      

      The second line is due to range mutation rules: a boundary point with an offset equal to the index of a newly-inserted node stays put, so it remains before it. With this step, it goes like

      {}<br>
      -> {}<blockquote></blockquote><br>
      -> <blockquote></blockquote>{}<br>
      -> <blockquote>{}<br></blockquote>.
      

      The difference in the final step is because we move the <br> "preserving ranges". This means that adjacent boundary points get swept along with it. Previously, the <blockquote> intervened, so a boundary point after it would get taken along but one before it would not.

      Another solution that one might be tempted to consider would be to just put the wrapper after the wrapped elements. Then the boundary points would stay put, before the wrapper, so they'd still be adjacent to the nodes to be wrapped, like:

      {<p>foo</p>}
      -> {<p>foo</p>}<blockquote></blockquote>
      -> <blockquote>{<p>foo</p>}</blockquote>.
      

      The problem is that this completely breaks if you're wrapping multiple things and not all are selected. It would go like this:

      <p>foo</p>{<p>bar</p>}
      -> <p>foo</p>{<p>bar</p>}<blockquote></blockquote>
      -> <p>foo</p><blockquote>{<p>bar</p>}</blockquote>
      -> <blockquote>{<p>foo</p><p>bar</p>}</blockquote>.
      

      The last step is again because of the range mutation rules: the boundary point stays put when a new node is inserted. They're fundamentally asymmetric.

      An alternative solution would be to define the concept of moving a list of adjacent sibling nodes while preserving ranges, and handle this explicitly at a more abstract level.

      TODO: Think about this some more. Maybe there's a better way.

      If any [=range=] has a [=boundary point=] with node equal to the [=tree/parent=] of new parent and [=boundary point/offset=] equal to the [=tree/index=] of new parent, add one to that [=boundary point=]'s [=boundary point/offset=].

  11. Let original parent be the [=tree/parent=] of the first member of node list.
  12. If new parent is before the first member of node list in [=tree order=]:
    1. If new parent is not an inline node, but the last visible [=tree/child=] of new parent and the first visible member of node list are both inline nodes, and the last [=tree/child=] of new parent is not a [^br^], call createElement("br") on the {{Node/ ownerDocument}} of new parent and append the result as the last [=tree/child=] of new parent.
    2. For each node in node list, append node as the last [=tree/child=] of new parent, preserving ranges.
  13. Otherwise:
    1. If new parent is not an inline node, but the first visible [=tree/child=] of new parent and the last visible member of node list are both inline nodes, and the last member of node list is not a [^br^], call createElement("br") on the {{Node/ ownerDocument}} of new parent and insert the result as the first [=tree/child=] of new parent.
    2. For each node in node list, in reverse order, insert node as the first [=tree/child=] of new parent, preserving ranges.
  14. This could happen if new parent instructions returned a node whose parent wasn't null.

    If original parent is editable and has no [=tree/children=], remove it from its [=tree/parent=].

  15. Probably because both the previous and next sibling met them. We want to merge them in this case.

    If new parent's {{Node/nextSibling}} is editable and running sibling criteria on it returns true:

    1. If new parent is not an inline node, but new parent's last [=tree/child=] and new parent's {{Node/nextSibling}}'s first [=tree/child=] are both inline nodes, and new parent's last [=tree/child=] is not a [^br^], call createElement("br") on the {{Node/ ownerDocument}} of new parent and append the result as the last [=tree/child=] of new parent.
    2. While new parent's {{Node/nextSibling}} has [=tree/children=], append its first [=tree/child=] as the last [=tree/child=] of new parent, preserving ranges.
    3. Remove new parent's {{Node/nextSibling}} from its [=tree/parent=].
  16. Remove extraneous line breaks from new parent.
  17. Return new parent.

Allowed children

List is mostly based on current HTML5, together with obsolete elements. I mostly got the obsolete element list by testing what Firefox 5.0a2 splits when you do insertHorizontalRule.

TODO: The definitions of prohibited paragraph children and elements with inline contents should be in the HTML spec (possibly under a different name) so they don't fall out of sync. They'll do for now.

A name of an element with inline contents is "a", "abbr", "b", "bdi", "bdo", "cite", "code", "dfn", "em", "h1", "h2", "h3", "h4", "h5", "h6", "i", "kbd", "mark", "p", "pre", "q", "rp", "rt", "ruby", "s", "samp", "small", "span", "strong", "sub", "sup", "u", "var", "acronym", "listing", "strike", "xmp", "big", "blink", "font", "marquee", "nobr", or "tt".

An element with inline contents is an HTML element whose [=Element/local name=] is a name of an element with inline contents.

TODO: This list doesn't currently match HTML's validity requirements for a few reasons:

  1. We need to handle invalid elements, which have no conformance requirements but should be treated properly. In particular, they can interfere with serialization (e.g., center cannot descend from p).
  2. Sometimes users give instructions that have to produce invalid DOMs to get the expected effect, like indenting the first item of a list.
  3. The HTML validity requirements are sometimes quite complicated.
  4. I just haven't had bothered to be systematic about it yet. I've only covered what's come up in my tests.

I deliberately allow [^dt^] to contain headers and such, in violation of HTML. If I didn't, then when the user tried to formatBlock a [^dt^] as a header, it would break apart the whole [^dl^], which seems worse. See bug 13201.

A node or string child is an allowed child of a node or string parent if the following algorithm returns true:

Often we move around nodes, and sometimes this can result in unreasonable things like two <p>'s nested inside one another. This algorithm checks for DOMs we never want to have, so that other algorithms can avoid creating them or fix them if they do happen. The fix disallowed ancestors algorithm is one frequently-invoked caller of this algorithm.

  1. If parent is "colgroup", "table", "tbody", "tfoot", "thead", "tr", or an HTML element with [=Element/local name=] equal to one of those, and child is a {{Text}} node whose {{CharacterData/data}} does not consist solely of space characters, return false.
  2. Actually, no node can occur in the DOM after plaintext, generally. But let's not get too carried away.

    If parent is "script", "style", "plaintext", or "xmp", or an HTML element with [=Element/local name=] equal to one of those, and child is not a {{Text}} node, return false.

  3. If child is a [=document=], {{DocumentFragment}}, or {{DocumentType}}, return false.
  4. If child is an HTML element, set child to the [=Element/local name=] of child.
  5. If child is not a string, return true.
  6. If parent is an HTML element:
    1. Cannot be serialized as text/html. In some cases it can, like <a>foo<table><td><a>bar</a></td></table>baz</a>, but it's invalid in those cases too, so no need for complication.

      If child is "a", and parent or some [=tree/ancestor=] of parent is an [^a^], return false.

    2. This generally cannot be serialized either, for p. For other elements with inline contents, this serves to prevent things like <span><p>foo</p></span>, which will parse fine but aren't supposed to happen anyway.

      If child is a prohibited paragraph child name and parent or some [=tree/ancestor=] of parent is an element with inline contents, return false.

    3. Also can't be serialized as text/html.

      If child is "h1", "h2", "h3", "h4", "h5", or "h6", and parent or some [=tree/ancestor=] of parent is an HTML element with [=Element/local name=] "h1", "h2", "h3", "h4", "h5", or "h6", return false.

    4. Further requirements only care about the parent itself, not ancestors, so we don't need to know the node itself.

      Let parent be the [=Element/local name=] of parent.

  7. If parent is an {{Element}} or {{DocumentFragment}}, return true.
  8. If parent is not a string, return false.
  9. We allow children even where some intervening nodes will be inserted, like tr as a child of table.

    If parent is on the left-hand side of an entry on the following list, then return true if child is listed on the right-hand side of that entry, and false otherwise.

    • colgroup: col
    • table: caption, col, colgroup, tbody, td, tfoot, th, thead, tr
    • tbody, tfoot, thead: td, th, tr
    • tr: td, th
    • dl: dt, dd
    • dir, ol, ul: dir, li, ol, ul
    • hgroup: h1, h2, h3, h4, h5, h6
  10. If child is "body", "caption", "col", "colgroup", "frame", "frameset", "head", "html", "tbody", "td", "tfoot", "th", "thead", or "tr", return false.
  11. dd/dt/li will serialize fine as the child of random stuff, but it makes no sense at all, so we want to avoid it anyway.

    If child is "dd" or "dt" and parent is not "dl", return false.

  12. If child is "li" and parent is not "ol" or "ul", return false.
  13. If parent is on the left-hand side of an entry on the following list and child is listed on the right-hand side of that entry, return false.
  14. Return true.

Inline formatting commands

Inline formatting command definitions

The difference between "contained" and "effectively contained" is basically that 1) in <b>[foo]</b>, the text node and the <b> are effectively contained but not contained; and 2) in <b>f[o]o</b>, the text node is effectively contained but not contained, and the <b> is neither effectively contained nor contained.

A node node is effectively contained in a [=range=] range if range is not {{AbstractRange/collapsed}}, and at least one of the following holds:

A modifiable element is a [^b^], [^em^], [^i^], [^s^], [^span^], [^strike^], [^strong^], sub, sup, or [^u^] element with no attributes except possibly style; or a [^font^] element with no attributes except possibly style, color, face, and/or size; or an [^a^] element with no attributes except possibly style and/or href.

A simple modifiable element is an HTML element for which at least one of the following holds:

Conceptually, a simple modifiable element is a modifiable element which specifies a value for at most one command. As the names imply, inline formatting commands will try not to modify anything other than modifiable elements. For instance, <dfn> normally creates italics, but it's not modifiable, so running the italic command will not remove it: it will nest <span style="font-style: normal"> inside.

A formattable node is an editable visible node that is either a {{Text}} node, an [^img^], or a [^br^].

Two quantities are equivalent values for a command if either both are null, or both are strings and they're equal and the command does not define any equivalent values, or both are strings and the command defines equivalent values and they match the definition.

Two quantities are loosely equivalent values for a command if either they are equivalent values for the command, or if the command is the fontSize command; one of the quantities is one of "x-small", "small", "medium", "large", "x-large", "xx-large", or "xxx-large"; and the other quantity is the resolved value of "font-size" on a [^font^] element whose size attribute has the corresponding value set ("1" through "7" respectively).

Loose equivalence needs to be used when comparing effective command values to other values, while regular equivalence is used in other cases. The effective command value for fontSize is converted to pixels, so comparing it to a specified value literally would produce false negatives. But a specified value in pixels is actually different from a specified value like "small" or "x-large", because there is no precise mapping from such keywords to pixels.

If a command has inline command activated values defined but nothing else defines when it is indeterminate, it is indeterminate if among formattable nodes effectively contained in the active range, there is at least one whose effective command value is one of the given values and at least one whose effective command value is not one of the given values.

For bold and similar commands, IE 9 RC seems to consider the state true or false depending on the first element. All other browsers follow the same general idea as the spec, considering a range bold only if all text in it is bold, and this seems to match at least OpenOffice.org's bold feature. Opera 11.11 seemingly doesn't take CSS into account, and only looks at whether something descends from a <b>. I couldn't properly test IE9 because it threw exceptions (Error: Unspecified error.) on most of the tests I ran. But what I have here seems to match Firefox 6.0a2 in every case, and Chrome 14 dev in all cases with a few exceptions.

If a command has inline command activated values defined, its state is true if either no formattable node is effectively contained in the active range, and the active range's [=range/start node=]'s effective command value is one of the given values; or if there is at least one formattable node effectively contained in the active range, and all of them have an effective command value equal to one of the given values.

Testing with hiliteColor: Opera 11.11 seems to always return the effective command value of the active range's start node. Chrome 14 dev returns boolean false consistently, bizarrely enough. Firefox 6.0a2 seems to follow the same idea as the spec, but it likes to return "transparent", including sometimes when the answer really clearly should not be "transparent". IE9 throws exceptions most of the time for backColor, so I can't say for sure, but in the few cases where it doesn't throw it returns a random-looking number, so I'll assume it's crazy like for foreColor.

I decided on something that would guarantee the following invariant: whenever you execute a command with a value provided (assuming value is relevant), queryCommandValue() will always return something equivalent to what you set.

If a command is a standard inline value command, it is indeterminate if among formattable nodes that are effectively contained in the active range, there are two that have distinct effective command values. Its value is the effective command value of the first formattable node that is effectively contained in the active range; or if there is no such node, the effective command value of the active range's [=range/start node=]; or if that is null, the empty string.

The notions of inline command activated values and standard inline value commands are mostly just shorthand to avoid repeating the same boilerplate half a dozen times.

Assorted inline formatting command algorithms

The effective command value of a node node for a given command is returned by the following algorithm, which will return either a string or null:

This is logically somewhat like CSS computed or resolved values, and in fact for most commands it's identical to CSS resolved values (see the end of the algorithm). We need a separate concept for some commands where we can't rely on CSS for some reason: createLink and unlink aren't CSS-related at all, backColor and hiliteColor need special treatment because background-color isn't an inherited property, subscript and superscript rely on <sub>/<sup> instead of CSS vertical-align, and strikethrough and underline don't map to unique CSS properties.

  1. If neither node nor its [=tree/parent=] is an {{Element}}, return null.
  2. If node is not an {{Element}}, return the effective command value of its [=tree/parent=] for command.
  3. If command is "createLink" or "unlink":
    1. While node is not null, and is not an [^a^] element that has an href attribute, set node to its [=tree/parent=].
    2. If node is null, return null.
    3. Return the [=Attr/value=] of node's href attribute.
  4. If command is "backColor" or "hiliteColor":
    1. While the resolved value of "background-color" on node is any fully transparent value, and node's [=tree/parent=] is an {{Element}}, set node to its [=tree/parent=].
    2. What happens if everything's background is fully transparent? See bug. If you do queryCommandValue() for backColor/hiliteColor when no backgrounds are set anywhere, so it goes up to the root element, no two engines agree. IE10PP2 just returns a random-looking number (16777215). Firefox 8.0a2 returns "transparent", Chrome 15 dev returns "rgba(0, 0, 0, 0)", and Opera 11.50 returns "rgb(255, 255, 255)".

      Opera's behavior is incorrect. The current page might be an iframe, in which case the background really will be transparent and the (inaccessible) background color of the parent page will show through. Also, the user might have changed their preferences, but I'm not too worried about that.

      So instead we just return the value as-is. This means that it will be fully transparent, which is perhaps somewhat useless information, but it's the best we can do. More generally, any non-opaque value is not going to tell you what you actually want, namely "what color is the user actually seeing?" We have no realistic way to work around this in the general case.

      Return the resolved value of "background-color" for node.

  5. If command is "subscript" or "superscript":
    1. Let affected by subscript and affected by superscript be two boolean variables, both initially false.
    2. While node is an inline node:
      1. Firefox 6.0a2 ignores vertical-align for this purpose, and only cares about <sub> and <sup> tags themselves. Opera 11.11 is similar, and in fact behaves like that even for commands like bold. The spec originally followed Chrome 14 dev, mainly because WebKit itself will produce spans with vertical-align sub or super, and we want to handle them correctly. However, Ryosuke informs me that WebKit's behavior here is viewed as a bug, so I changed it to match Gecko/Opera.

        If node is a sub, set affected by subscript to true.

      2. Otherwise, if node is a sup, set affected by superscript to true.
      3. Set node to its [=tree/parent=].
    3. If affected by subscript and affected by superscript are both true, return the string "mixed".
    4. If affected by subscript is true, return "subscript".
    5. If affected by superscript is true, return "superscript".
    6. Return null.
  6. If command is "strikethrough", and the "text-decoration" property of node or any of its [=tree/ancestors=] has resolved value containing "line-through", return "line-through". Otherwise, return null.
  7. If command is "underline", and the "text-decoration" property of node or any of its [=tree/ancestors=] has resolved value containing "underline", return "underline". Otherwise, return null.
  8. Return the resolved value for node of the relevant CSS property for command.

The specified command value of an {{Element}} element for a given command is returned by the following algorithm, which will return either a string or null:

This is logically somewhat like CSS inline style. In addition to the caveats for effective command value, we also treat elements like <b> and <font> as having the same meaning as <span>s with inline style set, because they're logically pretty much the same and can in fact be produced by the same command depending on the CSS styling flag.

  1. If command is "backColor" or "hiliteColor" and the {{Element}}'s display property does not have resolved value "inline", return null.
  2. If command is "createLink" or "unlink":
    1. If element is an [^a^] element and has an href attribute, return the [=Attr/value=] of that attribute.
    2. Return null.
  3. If command is "subscript" or "superscript":
    1. If element is a sup, return "superscript".
    2. If element is a sub, return "subscript".
    3. Return null.
  4. If command is "strikethrough", and element has a style attribute set, and that attribute sets "text-decoration":
    1. If element's style attribute sets "text-decoration" to a value containing "line-through", return "line-through".
    2. Return null.
  5. If command is "strikethrough" and element is an [^s^] or [^strike^] element, return "line-through".
  6. If command is "underline", and element has a style attribute set, and that attribute sets "text-decoration":
    1. If element's style attribute sets "text-decoration" to a value containing "underline", return "underline".
    2. Return null.
  7. If command is "underline" and element is a [^u^] element, return "underline".
  8. Let property be the relevant CSS property for command.
  9. If property is null, return null.
  10. If element has a style attribute set, and that attribute has the effect of setting property, return the value that it sets property to.
  11. If element is a [^font^] element that has an attribute whose effect is to create a presentational hint for property, return the value that the hint sets property to. (For a size of 7, this will be the non-CSS value "xxx-large".)
  12. If element is in the following list, and property is equal to the CSS property name listed for it, return the string listed for it.
    • [^b^], [^strong^]: font-weight: "bold"
    • [^i^], [^em^]: font-style: "italic"
  13. Return null.

To record the values of a list of nodes node list:

When we move nodes around, we often change their parents. If their parents had any styles applied, this will make the nodes' styles change too, which often isn't what we want. For instance, if something is wrapped in <blockquote style="color: red">, and a script runs the outdent command on it, the blockquote will be removed and the style will go along with it. Recording the values of its children first, then restoring them afterward, will ensure the nodes don't change color when outdented.

  1. Let values be a list of (node, command, specified command value) triples, initially empty.
  2. As with removeFormat, we put subscript first so it doesn't interfere with fontSize, and omit superscript because it's redundant with subscript.

    For each node in node list, for each command in the list "subscript", "bold", "fontName", "fontSize", "foreColor", "hiliteColor", "italic", "strikethrough", and "underline" in that order:

    1. Let ancestor equal node.
    2. If ancestor is not an {{Element}}, set it to its [=tree/parent=].
    3. While ancestor is an {{Element}} and its specified command value for command is null, set it to its [=tree/parent=].
    4. If ancestor is an {{Element}}, add (node, command, ancestor's specified command value for command) to values. Otherwise add (node, command, null) to values.
  3. Return values.

To restore the values specified by a list values returned by the record the values algorithm:

  1. For each (node, command, value) triple in values:
    1. Let ancestor equal node.
    2. If ancestor is not an {{Element}}, set it to its [=tree/parent=].
    3. While ancestor is an {{Element}} and its specified command value for command is null, set it to its [=tree/parent=].
    4. If value is null and ancestor is an {{Element}}, push down values on node for command, with new value null.
    5. Otherwise, if ancestor is an {{Element}} and its specified command value for command is not equivalent to value, or if ancestor is not an {{Element}} and value is not null, force the value of command to value on node.

Clearing an element's value

To clear the value of an {{Element}} element:

The idea is to remove any specified command value that the element might have for the command. This might involve changing its attributes, setting its tag name, or removing it entirely while leaving its children in place. The key caller is set the selection's value, which clears the values of everything in the selection before doing anything else to keep the markup tidy.

  1. Let command be the current command.
  2. If element is not editable, return the empty list.
  3. We want to abort early so that we don't try unsetting background-color on a non-inline element.

    If element's specified command value for command is null, return the empty list.

  4. If element is a simple modifiable element:
    1. Let children be the [=tree/children=] of element.
    2. For each child in children, insert child into element's [=tree/parent=] immediately before element, preserving ranges.
    3. Remove element from its [=tree/parent=].
    4. Return children.
  5. If command is "strikethrough", and element has a style attribute that sets "text-decoration" to some value containing "line-through", delete "line-through" from the value.
  6. If command is "underline", and element has a style attribute that sets "text-decoration" to some value containing "underline", delete "underline" from the value.
  7. If the relevant CSS property for command is not null, unset that property of element.
  8. If element is a [^font^] element:
    1. If command is "foreColor", unset element's color attribute, if set.
    2. If command is "fontName", unset element's face attribute, if set.
    3. If command is "fontSize", unset element's size attribute, if set.
  9. If element is an [^a^] element and command is "createLink" or "unlink", unset the href property of element.
  10. If we get past this step, we're something like <b class=foo> where we want to keep the extra attributes, so we stick them on a span.

    If element's specified command value for command is null, return the empty list.

  11. Set the tag name of element to "span", and return the one-node list consisting of the result.

Pushing down values

This algorithm goes up to just below the nearest ancestor with the right style, then re-applies the bad styles repeatedly going down, omitting the things we want to have the new style. This is basically what WebKit does, although WebKit sometimes starts higher up and therefore makes more intrusive changes, often creating more markup. IE follows the same general approach too.

Gecko instead seems to start breaking up elements from the bottom, so that the range consists of a few consecutive siblings, and it can then break up the problematic element into a maximum of two pieces. The spec's approach seems to create fewer elements and simpler markup (or at least markup that's no more complex) in most cases I throw at it.

Gecko's approach does have the major advantage that it gets underlines right in many cases for free. E.g.,

<u>foo<font color=red>[bar]baz</font></u>
-> <u>foo</u><font color=red>bar<u>baz</u></font> (spec)
-> <u>foo</u><font color=red>bar</font><u><font color=red>baz</font></u> (Gecko)

The spec's markup here is much shorter and contains fewer elements, but is wrong: the underline under "baz" has changed color from black to red. It might be worth trying to copy Gecko's results in such cases, but that won't solve all underline problems, so perhaps it's not worth it.

Opera also seems to break up the markup surrounding the range, but even more aggressively: even if it doesn't need to pull down styles. In some cases this does actually result in shorter markup, specifically if the existing tags are short (like i or b) and we're adding tags that are long (like span with a style attribute).

To push down values to a node node, given a new value new value:

The idea here is that if an undesired value is being propagated from an ancestor, we remove that style from the ancestor and re-apply it to all the descendants other than node. This way we don't have to have nested styles, which is usually more cluttered (although not always).

  1. Let command be the current command.
  2. E.g., a text node child of a document fragment.

    If node's [=tree/parent=] is not an {{Element}}, abort this algorithm.

  3. If the effective command value of command is loosely equivalent to new value on node, abort this algorithm.
  4. Let current ancestor be node's [=tree/parent=].
  5. Let ancestor list be a list of nodes, initially empty.
  6. While current ancestor is an editable {{Element}} and the effective command value of command is not loosely equivalent to new value on it, append current ancestor to ancestor list, then set current ancestor to its [=tree/parent=].
  7. If ancestor list is empty, abort this algorithm.
  8. Let propagated value be the specified command value of command on the last member of ancestor list.
  9. We can only remove specified values, so if the value isn't specified, give up. Unless we're actually trying to push down a null specified value, like for unlink.

    If propagated value is null and is not equal to new value, abort this algorithm.

  10. If we go all the way up to the root and still don't have the desired value, pushing down values is pointless. It will create extra markup for no purpose. Except if the value is null, which basically just means "try to get rid of anything affecting the current element but don't aim for any specific value".

    Nevertheless, Chrome 14 dev does seem to do this. Running bold on <span style=font-weight:300>f[o]o</span> breaks up the span and adds a <b> as a sibling. In IE9, Firefox 6.0a2, and Opera 11.50, it instead nests the <b> inside the <span>. It's a tradeoff: WebKit's behavior is better for things like

    <font color=red>fo[o</font><font color=blue>b]ar</font>
    -> <font color=red>fo</font><font color=green>[ob]</font><font color=blue>ar</font>
    

    (where the spec adds two extra font tags instead of one), but the spec is simpler for things like

    <font color=red>f[o]o</font>
    -> <font color=red>f<font color=green>[o]</font>o</font>
    

    (where WebKit splits the existing tag up in addition to creating a new tag). I'm not particularly sure which approach is better overall, so I'll go with the majority of browsers. If these algorithms move to use runs of consecutive siblings instead of doing everything node-by-node, it might make sense to break up the parent as long as it won't create an extra node (i.e., we're styling something that includes the first or last child).

    If the effective command value of command is not loosely equivalent to new value on the [=tree/parent=] of the last member of ancestor list, and new value is not null, abort this algorithm.

  11. While ancestor list is not empty:
    1. Let current ancestor be the last member of ancestor list.
    2. Remove the last member from ancestor list.
    3. If the specified command value of current ancestor for command is not null, set propagated value to that value.
    4. Let children be the [=tree/children=] of current ancestor.
    5. If the