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
.
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:
<b>
for bold (if the CSS styling flag is
false), and convert <strong>
and
<span style="font-weight: bold">
if we
happen to be modifying that node anyway.
Tests can be found here.
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:
I haven't paid much attention to performance. The algorithms here aren't performance-critical in most cases, but I might have accidentally included some algorithms that are too slow anyway on large pages. Generally I haven't worried about throwing nodes away and recreating them multiple times or things like that, as long as it produces the correct result.
If it would be useful to implementers for me to spend time and spec complexity on avoiding some of the masses of useless operations that are currently required, please say so. All intermediate DOM states are black-box detectable via mutation events or whatever their replacement will be, so implementers theoretically can't optimize most of this stuff too much themselves, but in practice I doubt anyone will rely on the intermediate DOM states much.
<span style=font-weight:bold>
instead of
<b>
, while if it's off, it produces stuff
like <font color=red>
instead of
<span style=color:red>
. The issue is that
authors might want a mix, like making the markup as concise as possible
while still conforming, and they can't do that. Changing the flag on a
per-command basis doesn't help because of things like the "restore the
values" algorithm, which might create several different types of style
at once and has to use the same styling flag for all of them. This was
discussed back in March in
this thread, along with a number of other things, but at that
time I hadn't written commands that change multiple styles at once,
so it seemed feasible to ask authors to switch styleWithCSS on or off
on a per-command basis.
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".
This specification defines a number of commands, identified by ASCII case-insensitive strings. Each command can have several pieces of data associated with it:
execCommand()
. Every command defined in this specification has an
action defined for it in the relevant
section. For example, the bold
command's action generally
makes the current selection bold, or removes bold if the selection
is already bold. An editing toolbar might provide buttons that
execute the action for a command if clicked, or a script might run an
action without user interaction to achieve
some particular effect. Actions return either true or false, which
can affect the return value of execCommand()
.
queryCommandIndeterm()
,
depending on the current state of the document. Generally, a
command that has a state defined will be indeterminate if the state is true for part but not all of the current
selection, and a command that has a value defined will be indeterminate if different parts of the
selection have different values.
An editing toolbar might display a button or control in a special
way if the command is indeterminate, like showing a "bold" button as
partially depressed, or leaving a font size selector blank instead
of showing the font size of the current selection. As a rule, a
command can only be indeterminate if its state is false, supposing it has a state.
queryCommandState()
, depending on
the current state of the document. The state
of a command is true if it is already in
effect, in some sense specific to the command. Most commands that have a state
defined will take opposite actions depending on whether the state is true or false, such as making the selection
bold if the state is false and removing bold
if the state is true. Others will just have no
effect if the state is true, like the justifyCenter
command. Still others will have the
same effect regardless, like the styleWithCss
command. An editing toolbar might display a button or control
differently depending on the state and
indeterminacy of
the command.
queryCommandValue()
, depending on
the current state of the document. A command
usually has a value instead of a state if the property it modifies can take more than
two different values, like the
foreColor
command. If the command is indeterminate, its value
is generally based on the start of the selection. Otherwise, in
most cases the value holds true for the entire
selection, but see the
justifyCenter
command and its
three companions for an exception. An
editing toolbar might display the value of a
command as selected in a drop-down or filled
in in a text box, if the command isn't
indeterminate.
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()
.
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=].
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:
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 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.
Such an editing host must exist, because otherwise the command would not be enabled.
We have to check again whether the command is enabled, because the beforeinput handler might have done something annoying like getSelection().removeAllRanges().
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.
To map an edit command to input type value, follow this table:
edit command | inputType |
---|---|
backColor | formatBackColor |
bold | formatBold |
createLink | insertLink |
fontName | formatFontName |
foreColor | formatFontColor |
strikethrough | formatStrikeThrough |
superscript | formatSuperscript |
delete | deleteContentBackward |
forwardDelete | deleteContentForward |
indent | formatIndent |
insertHorizontalRule | insertHorizontalRule |
insertLineBreak | insertLineBreak |
insertOrderedList | insertOrderedList |
insertParagraph | insertParagraph |
insertText | insertText |
insertUnorderedList | insertUnorderedList |
justifyCenter | formatJustifyCenter |
justifyFull | formatJustifyFull |
justifyLeft | formatJustifyLeft |
justifyRight | formatJustifyRight |
outdent | formatOutdent |
cut | deleteByCut |
paste | insertFromPaste |
redo | historyRedo |
undo | historyUndo |
When the queryCommandEnabled(command)
method on the {{Document}} interface is
invoked, the user agent must run the following steps:
See comment before Supported commands.
When the queryCommandIndeterm(command)
method on the {{Document}} interface is
invoked, the user agent must run the following steps:
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.
When the queryCommandState(command)
method on the {{Document}} interface is
invoked, the user agent must run the following steps:
See comment on the comparable line for queryCommandIndeterm().
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:
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.
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.
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:
queryCommandIndeterm()
will return false (or
throw an exception).
queryCommandState()
will return the opposite
of what it did before execCommand()
was
called (or throw an exception).
queryCommandValue()
will return something
equivalent to the value passed to execCommand()
(or throw an exception). "Equivalent" here
needs to be construed broadly in some cases, such as fontSize
.
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.
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 . . .
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.
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.
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.
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.
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.
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.
createElement(new
name)
on the {{Node/ownerDocument}} of 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.
To remove extraneous line breaks at the end of a node node:
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.
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.
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.
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.
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.
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.
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:
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=].
createElement("br")
on the {{Node/ ownerDocument}} of new
parent and append the result as the last [=tree/child=]
of new parent.
createElement("br")
on the {{Node/ ownerDocument}} of new
parent and insert the result as the first [=tree/child=]
of new parent.
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=].
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:
createElement("br")
on the {{Node/ ownerDocument}} of new parent
and append the result as the last [=tree/child=] of new parent.
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:
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.
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.
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.
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.
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.
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.
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.
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.
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:
So like <b>f[oo]</b> or <b>f[o]o</b> or <b>f[oo</b>}, but not <b>foo[</b>} or <b>f[]oo</b>.
node is range's [=range/start node=], it is a {{Text}} node, and its [=Node/length=] is different from range's [=range/start offset=].
Basically, anything whose children are all effectively contained should be effectively contained itself, except that in a case like <b>f[o]o</b> we don't want <b> to be effectively contained even though the text node is. That's because we split the text node before we actually do anything, and the <b> will no longer be effectively contained.
node has at least one [=tree/child=]; and all its [=tree/children=] are effectively contained in range; and either range's [=range/start node=] is not a [=tree/descendant=] of node or is not a {{Text}} node or range's [=range/start offset=] is zero; and either range's [=range/end node=] is not a [=tree/descendant=] of node or is not a {{Text}} node or range's [=range/end offset=] is its [=range/end node=]'s [=Node/length=].
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:
sub
,
sup
, or [^u^] element with no attributes.
sub
,
sup
, or [^u^] element with exactly one attribute, which
is
style
, which sets no CSS properties (including invalid
or unrecognized properties).
href
.
color
,
face
, or
size
.
style
, and the
style
attribute sets exactly one CSS property (including
invalid or unrecognized properties), which is "font-weight".
style
, and the
style
attribute sets exactly one CSS property (including
invalid or unrecognized properties), which is "font-style".
style
, and the
style
attribute sets exactly one CSS property (including
invalid or unrecognized properties), and that property is not
"text-decoration".
style
, and the
style
attribute sets exactly one CSS property (including
invalid or unrecognized properties), which is "text-decoration",
which is set to "line-through" or "underline" or "overline" or
"none".
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.
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.
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.
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.
sup
, set affected by
superscript to true.
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.
href
attribute, return the [=Attr/value=] of that
attribute.
style
attribute set, and that attribute sets
"text-decoration":
style
attribute sets "text-decoration" to a value
containing "line-through", return "line-through".
style
attribute set, and that attribute sets
"text-decoration":
style
attribute sets "text-decoration" to a value
containing "underline", return "underline".
style
attribute set, and that attribute has the effect
of setting property, return the value that it
sets property to.
size
of 7, this will be the non-CSS value "xxx-large".)
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.
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:
To restore the values specified by a list values returned by the record the values algorithm:
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.
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.
style
attribute that sets "text-decoration" to some
value containing "line-through", delete "line-through" from the
value.
style
attribute that sets "text-decoration" to some
value containing "underline", delete "underline" from the value.
href
property of element.
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.
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).
E.g., a text node child of a document fragment.
If node's [=tree/parent=] is not an {{Element}}, abort this algorithm.
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.
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.
TODO: This will be incorrect for relative font sizes. If the font size on the parent was removed and the font size on the child is in ems or percents or something, it will now change value. This isn't likely to come up, so we'll ignore it for now.
If child is an {{Element}} whose specified command value for command is neither null nor equivalent to propagated value, continue with the next child.
To force the value of a node node to new value:
This algorithm checks if the node has the desired value, and if not, it wraps the node (or, if that's not possible, its descendants) in a simple modifiable element. After forcing the value, descendants might still have a different value.
Even if the value matches, we stick it in a preceding sibling if possible. This ensures "a<cite>b</cite>c" -> "<i>a<cite>b</cite>c</i>" instead of "<i>a</i><cite>b</cite><i>c</i>". While we're at it, we also handle more elaborate cases like <b>foo</b>[bar]<b>baz</b> and even <i><b>foo</b></i>[bar]<i><b>baz</b></i> (the latter becomes <b><i>foo</i>bar<i>baz</i></b>).
Theoretically this algorithm could pointlessly reorganize the DOM in the event of unreasonable style rules, but it's not a big enough deal for us to care, since the resulting style will still be right.
Reorder modifiable descendants of node's {{Node/ previousSibling}}.
The new parent instructions we'd want are too complicated to reasonably feed into the wrap algorithm, so we just let them return null and do the wrapping ourselves if sibling criteria didn't return true.
Wrap the one-node list consisting of node, with sibling criteria returning true for a simple modifiable element whose specified command value is equivalent to new value and whose effective command value is loosely equivalent to new value and false otherwise, and with new parent instructions returning null.
If node is invisible, abort this algorithm.
This means that if it has no children, we do nothing. IE9 inserts an empty wrapper element in that case, but I'm not sure what the point is, and no one else does, so I don't. WebKit seems to ignore the node if its only child consists solely of whitespace, but I don't see any grounds for that and no one else does, so I don't.
Force the value of each node in children, with command and new value as in this invocation of the algorithm.
createElement("b")
on the {{Node/ownerDocument}} of node.
createElement("i")
on the {{Node/ownerDocument}} of node.
TODO: Actual UAs use strike, not s, but s is shorter and HTML5 makes strike invalid. I've gone with s for now, but maybe we want to change the spec to require strike.
If command is "strikethrough" and
new value is "line-through", let
new parent be the result of calling
createElement("s")
on the {{Node/ ownerDocument}} of node.
createElement("u")
on the {{Node/ownerDocument}} of node.
See comment for foreColor for discussion. TODO: Define more carefully what happens when things are out of range or not integers or whatever.
If command is "foreColor", and new value is fully opaque with red, green, and blue components in the range 0 to 255:
createElement("font")
on the {{Node/ ownerDocument}} of node.
color
attribute of new
parent to the result of applying the
rules for serializing simple color values to
new value (interpreted as a
simple color).
createElement("font")
on the {{Node/ownerDocument}} of node, then
set the
face
attribute of new parent to
new value.
createElement("a")
on the {{Node/ownerDocument}} of node.
href
attribute of new parent to
new value.
Nested a elements are bad, because they can't be serialized to text/html. hrefs should already have been cleared in a previous step, but we might have <a name> or such lurking about.
Let ancestor be node's [=tree/parent=].
TODO: This will mean any link-specific attributes will be transferred, which makes them both invalid and useless. Is that okay? I don't really want to list them all, because that sort of list is prone to bitrot.
If ancestor is an [^a^], set the tag name of ancestor to "span", and let ancestor be the result.
WebKit is the only engine that ever outputs anything but font tags for fontSize. For size=7, it uses font-size: -webkit-xxx-large. We just output a font tag no matter what for size=7.
If command is "fontSize"; and new value is one of "x-small", "small", "medium",
"large", "x-large", "xx-large", or "xxx-large"; and either the
CSS styling flag is false, or
new value is "xxx-large": let new parent be the result of calling createElement("font")
on the {{Node/ownerDocument}} of node, then
set the
size
attribute of new parent to
the number from the following table based on new
value:
We always use sup/sub elements, even in CSS mode, following Gecko and contradicting WebKit. This is because <span value="vertical-align: sub/super">, the obvious equivalent (and what WebKit uses), behaves quite differently: it doesn't reduce font-size, which is ugly. WebKit's behavior is a bug anyway.
If command is "subscript" or "superscript"
and new value is "subscript", let new parent be the result of calling createElement("sub")
on the {{Node/ownerDocument}} of node.
createElement("sup")
on the {{Node/ownerDocument}} of node.
createElement("span")
on the {{Node/ownerDocument}} of node.
This preserves boundary points correctly, as usual.
Insert new parent in node's [=tree/parent=] before node.
Need to be explicit. I think "if the new value would be valid" means "if the new value isn't xxx-large for font-size", need to double-check.
To reorder modifiable descendants of a node node, given a command command and a value new value:
If candidate had no children, any boundary point inside it will get moved to its parent here, which is okay. We don't want to preserve ranges, because that would move boundary points that originally were in candidate but were moved to its parent by the last step to move to node's parent.
We move to after node so that boundary points before and after node wind up consistently inside candidate when we move preserving ranges. If we had
{<node>foo<candidate></candidate></node>}
it thus becomes
{<node>foo</node>}<candidate></candidate>
by the range mutation rules, and then when we move preserving ranges, it becomes
<candidate>{<node>foo</node>}</candidate>
which is reasonable.
If we had inserted candidate before node, instead it would go
{<candidate></candidate><node>foo</node>} {<candidate><node>foo</node>}</candidate>
because of the interaction of regular range mutation rules with preserving-ranges rules.
Insert candidate into node's [=tree/parent=] immediately after node.
To set the selection's value to new value:
The effect of this algorithm is to ensure that all nodes effectively contained in the selection have the value requested, producing the simplest markup possible to achieve that effect. It's inspired by the approach WebKit takes. The only places where the algorithm should fail are when there's an !important CSS rule that conflicts with the requested style (which we don't try to override because we assume it's !important for a reason), or when it's literally impossible to succeed (such as when a text-decoration or link URL is propagated from a non-editable ancestor). Any other failures are bugs.
First, if a node has a specified command value for the command, we unset it (clear its value). This step also removes simple modifiable elements entirely, and replaces elements like [^b^] or [^font^] with [^span^]s if they aren't simple modifiable elements. This will be sufficient if the desired value is inherited from an ancestor, or if it's the default (like font-style: normal) and no conflicting value is inherited from an ancestor. Even if clearing values doesn't actually fix the style of the node we're dealing with, we do it anyway to simplify the generated markup.
If clearing values didn't work, and it looks like an ancestor has a specified command value that we're inheriting, we push the value down from that ancestor. Thus if we're unbolding the letter "r" in
foo bar baz,
we get
foo bar baz.
If we didn't push down values, the final step (forcing values) would instead give us
foo bar baz,
which is much longer and uglier. We take care not to disturb the style or semantics of anything but the node we're dealing with.
We'll only push down values if some ancestor actually has the value we want, so we can inherit it. Otherwise, it will just create useless markup.
Finally, if neither of the above strategies worked, we have to add new markup to get the desired value (forcing the value). First we try just sticking it into its previous or next sibling, if that's a simple modifiable element (so it won't add any styles or semantics we don't want). Otherwise, we create a new simple modifiable element and wrap it in that. It's common that a previous sibling is the simple modifiable element we want, because often we'll set the value of several consecutive siblings in succession. In that case, the element created for the first can be reused for the later ones.
This last step works a bit differently if the node isn't an allowed child of "span". In that case, wrapping it in a simple modifiable element would make the document less conforming than it already was, or would cause other problems. Instead, we recursively force the value of its children. The recursion will terminate when we hit a node that's an allowed child of "span", or when there are no further descendants. (In the latter case, there are no descendants that are text nodes or such, so we don't really need to style anything.)
After all this, the node is guaranteed to have the value we want, barring bugs in the algorithm or the two exceptions noted earlier (!important style rules, and impossible cases). We then re-run the algorithm on each child recursively. Typically this means just clearing the value of each descendant, because it should then inherit the value we just set on its ancestor. In the unusual case that a descendant's value is wrong even after we clear its value, such as because of a non-inline style rule (like trying to unbold a heading), we'll repeat the above steps to ensure that the value really gets set as desired.
IE9 seems to wrap the whole line instead, or something like that, although it does nothing for createLink. We follow all other browsers' general behavior: change the state/value, and then make sure that takes effect if the user types something before changing the cursor position.
If there is no formattable node effectively contained in the active range:
The last sentence here just prettifies the resulting range a bit.
If the active range's [=range/start
node=] is an editable {{Text}} node, and
its [=range/start offset=] is neither zero nor its [=range/start
node=]'s [=Node/length=], call splitText()
on the active range's [=range/start
node=], with argument equal to the active
range's [=range/start offset=]. Then set the active range's [=range/start node=] to the
result, and its [=range/start offset=] to zero.
splitText()
on the active range's [=range/end node=],
with argument equal to the active range's
[=range/end offset=].
We skip non-editable nodes.
I chose to go with the non-IE behavior, per discussion. Ignoring non-editable things is convenient for the common use-case of an editor, where you don't want the user to bold random parts of the UI when they hit the bold button. For cases where it's not desired, you can always turn designMode on briefly before using execCommand(), so the non-IE behavior is a lot easier to work around than the IE behavior.
I don't see the value in ever just ignoring execCommand(). If the start and end are not editable, I'm going to say you should still style any editable nodes in between. I'm also going to ignore non-editable nodes for the purposes of determining state, so (for instance) if all the editable nodes are bolded, it will unbold instead of bolding.
Let node list be all editable nodes effectively contained in the active range.
TODO: This is inefficient. It would be most efficient to only push down values on the highest-level effectively contained nodes, and to batch operations so we handle runs of adjacent siblings at once. Should we bother fixing this?
For each node in node list:
If the node isn't an allowed child of "span", forcing its value will just force its children's value, which is redundant. So don't.
If node is an allowed child of "span", force the value of node.
backColor
command
This command must not be enabled if the editing host is in the plaintext-only state.
For historical reasons, backColor and hiliteColor behave identically.
We have three behaviors to choose from for this one:
(1) is obviously redundant, but has plurality support, so we could spec it that way if the other ways were useless.
(3) is incoherent from a user perspective. For instance, if you try it on paragraphs the background will have big gaps where the margins are. If you try it on an inline element that's a child of the editing host, it will do nothing or apply the background to everything or such, even though such an inline element is visually indistinguishable from one sitting inside a div. This would only make sense if we take considerable effort to ensure that block elements all have no margins, or if we wrap things in a div if they have margins, or something like that.
That leaves (2). That might be useful if it actually set the document's background color, but it seems like it sets table cell backgrounds sometimes instead, which is really confusing.
The path of least resistance is to standardize this as meaning the same thing as hiliteColor, and make up new commands if we want to do things like set the document background color. See hiliteColor for comments.
Relevant CSS property: "background-color"
Equivalent values: Either both strings are valid CSS colors and have the same red, green, blue, and alpha components, or neither string is a valid CSS color.
bold
command
This command must not be enabled if the editing host is in the plaintext-only state.
If the selection is collapsed (but not if it contains nothing but is not collapsed), IE9 wraps the whole line in a <strong>. This seems bizarre and no one else does it, so I don't do it. It's a similar story for similar commands (fontName, italic, etc.). Except not for strikethrough, where it just does nothing if the selection is empty. Why strikethrough? I don't know.
Action: If queryCommandState("bold")
returns
true, set the selection's
value to "normal". Otherwise set the selection's value to "bold".
Either way, return true.
The cutoff of 600 matches Chrome 14 dev. The cutoff used by IE9 and Firefox 6.0a2 seems to be 500, and the distinction isn't relevant for Opera 11.11 (it doesn't use CSS here at all AFAICT). On my test systems with default fonts, Chrome 14 dev displays 700 and up as bold, while the other three display 600 and up as bold.
Thus in Chrome on my system, the bold command will behave a bit oddly the first time you hit it if there's anything in the range with font-weight: 600, but it will look right in other browsers. On the other hand, if I followed IE/Firefox, it would look wrong on all my browsers for font-weight: 500.
700 actually makes more sense: then you'd view 100-300 as light, 400-600 as medium, 700-900 as bold. But that's not how it seems to work in browsers, so I'll go with 600 as the cutoff.
Inline command activated values: "bold", "600", "700", "800", or "900"
Relevant CSS property: "font-weight"
Equivalent values: Either the two strings are equal, or one is "bold" and the other is "700", or one is "normal" and the other is "400".
createLink
command
This command must not be enabled if the editing host is in the plaintext-only state.
If the selection doesn't contain anything (meaning, e.g., deleteContents() doesn't change anything), then Chrome 12 dev inserts a link at the selection start, with the text equal to the link URL. Other browsers don't do it, so I don't either.
IE10PP2, Firefox 7.0a2, Chrome 14 dev, and Opera 11.50 all do not support indeterminate, state, or value for createLink or unlink. I previously defined indeterminate and value anyway because they make sense, but then undefined them. The nontrivial thing is what value to return if there's no link, since any string can occur as a link href, in principle.
What are the use-cases for indeterm, state, or value for createLink/unlink?
Firefox 4b11 and Chrome 11 dev both silently do nothing in this case. IE 9 RC and Opera 11 both treat the request literally. Gecko and WebKit probably have it right here: users who enter no URL are very unlikely to want to link to a relative URL resolving to the current document. If they really want to, they can always specify "#" for the value, or the author can rewrite it, so it's not like this makes the API less useful.
If value is the empty string, return false.
There are three approaches here. For instance, if you ask browsers to create a link to "http://example.org" on the "b" here:
<a href=http://example.com><b>Abc</b></a>
Chrome 10 dev produces:
<b><a href=http://example.com>A</a><a href=http://example.org>b</a><a href=http://example.com>c</a></b>
Firefox 4b11 produces (roughly):
<a href=http://example.com><b>A<a href=http://example.org>b</a>c</b></a>
(This doesn't round-trip through text/html serialization.) IE 9 RC and Opera 11 produce simply:
<a href=http://example.org><b>Abc</b></a>
The last behavior probably best matches user expectations. If you happen to miss out a character when selecting the link you want to change, do you really intend to only change the link of part of it?
For each editable [^a^] element that has
an
href
attribute and is an [=tree/ancestor=] of some
node effectively contained in the
active range, set that [^a^]
element's
href
attribute to value.
fontName
command
This command must not be enabled if the editing host is in the plaintext-only state.
UAs differ a bit in the details here:
Setting an empty font-family has the effect of inheriting the font from the parent (although I don't see where the February 24, 2011 CSS 3 Fonts draft says that). Thus it makes sense that if we special-case this, it should be to unset the font somehow.
Special-casing the empty string to do nothing doesn't make sense to me. With createLink we'd expect the user to enter the URL themselves, so it makes sense to special-case clicking OK without entering anything. But here it's very likely that the font list will be fixed by the author (how many users will understand CSS font-family syntax?), so I don't think such usability concerns apply.
Action: Set the selection's value to value, then return true.
The value is complicated.
I'm just going to punt on this and say it should be the resolved value of font-family. I'll leave CSSOM to decide what that means if there are no applicable style rules.
Relevant CSS property: "font-family"
fontSize
command
This command must not be enabled if the editing host is in the plaintext-only state.
What all of these have in common is that they force the author to
deal with legacy font values and don't let them use CSS. This is
undesirable, but to avoid it we'd really have to create a new
command. If nothing else, the value returned by queryCommandValue()
has to be numeric, so authors can't
really use the command sanely no matter what we do. See bug
14251.
Note that 1 is the same size as x-small in browsers, not xx-small, contrary to the CSS Fonts spec.
The entry for 7 here is an issue: there's no CSS value that corresponds to it. Even if we got one added to the drafts, it wouldn't be backward-compatible to use it. WebKit is the only engine that supports CSS output for fontSize, and it uses -webkit-xxx-large in this case, which is unworkable. Instead, we just always output a font tag for size 7. If authors want conforming markup, they'll need to give CSS sizes above size 7, not legacy sizes.
This follows Firefox 6.0a2. Chrome 14 dev always returns false. Note that indeterminacy here keys off the effective command value, while the value is based only on an approximation (a number from one to seven). Thus it's possible for every subrange of the selection to have the same value, but for the selection to still be indeterminate. Setting the fontSize to the value will make it determinate without changing anything's value.
Indeterminate: True if among formattable nodes that are effectively contained in the active range, there are two that have distinct effective command values. Otherwise false.
Chrome's behavior seems the most useful. As usual, IE returns a variable type and all other browsers return strings, and we follow other browsers.
If the selection isn't someplace editable, Chrome works like usual; some other browsers behave differently. I see no reason to behave differently.
See comment for standard inline value commands on how I decided on this choice of node.
Let pixel size be 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=], in either case interpreted as a number of pixels.
Relevant CSS property: "font-size"
The legacy font size for an integer pixel size is returned by the following algorithm:
size
attribute is set to returned
size.
size
attribute is set to one plus returned size.
foreColor
command
This command must not be enabled if the editing host is in the plaintext-only state.
Color interpretations:
IE10PP2 Firefox 7.0a2 Chrome 14 dev Opera 11.50 blue blue blue #0000ff #0000ff f #f - - #f00000 #f #f - - #f00000 00f #00f - #0000ff #00000f #00f #00f rgb(0, 0, 255) #0000ff #00000f 0000ff #0000ff - #0000ff #0000ff #0000ff #0000ff rgb(0, 0, 255) #0000ff #0000ff 000000fff #0000ff - - - #000000fff #0000ff - - - rgb(0, 0, 255) rgb(0,0,255) rgb(0, 0, 255) #0000ff #00b000 rgb(0%, 0%, 100%) rgb(0,0,255) rgb(0, 0, 255) #0000ff #00b000 rgb( 0 ,0 ,255) rgb(0,0,255) rgb(0, 0, 255) #0000ff #00b000 rgba(0, 0, 255, 0.0) #ba0000 rgba(0, 0, 255, 0) rgba(0, 0, 255, 0) #00ba00 rgb(15, -10, 375) rgb(15,0,255) rgb(15, 0, 255) #0f00ff #00b015 rgba(0, 0, 0, 1) #ba0010 rgb(0, 0, 0) - #00ba00 rgba(255, 255, 255, 1) #000055 rgb(255, 255, 255) #ffffff #00ba02 rgba(0, 0, 255, 0.5) #ba0000 rgba(0, 0, 255, 0.5) rgba(0, 0, 255, 0.5) #00ba00 hsl(240, 100%, 50%) #000150 rgb(0, 0, 255) #0000ff #000024 cornsilk cornsilk cornsilk #fff8dc #fff8dc potato quiche #0000c0 - - #000a00 transparent transparent - rgba(0, 0, 0, 0) #00a000 currentColor #c0e000 currentcolor rgba(0, 0, 0, 0) #c000e0
The interpretations given for Firefox are only in styleWithCSS mode. In non-styleWithCSS mode, it just outputs the string literally as the <font color> attribute value, which can lead to different results. The given output for Chrome is for <font>; the output in styleWithCSS mode is the same, but rgb() is used instead of hex notation, and "transparent" and "currentcolor" are passed through under those names. IE and Opera only support <font> to begin with.
Conclusions:
What I'm going to say is that it either has to be a valid CSS
color, or prefixing it with # must result in a valid CSS color. For
<font>
, I'll say that the output color
should be normalized to #xxxxxx form. If the color is not a simple
color (fully opaque with all channels between 0 and 255), I'll
force style=""
even if styleWithCSS mode is
off. Some of this disagrees with all browsers, but it's unlikely to
hurt and it makes sense.
TODO: Define "valid CSS color" (here and in other color places).
If value is not a valid CSS color, prepend "#" to it.
currentColor is bad for the same reason as relative font sizes. It will confuse the algorithm, and doesn't seem very useful anyway.
If value is still not a valid CSS color, or if it is currentColor, return false.
Opera 11 seems to return true for the state if there's some color style applied, false otherwise, which seems fairly useless; authors want to use value here, not state. So I'll match other browsers and not define any state.
For value, the spec essentially matches Firefox 6.0a2 and Chrome 14 dev, as far as how to decide what color the node has. IE9 seems to always return the number 0 for some bizarre reason. There are some cases where Firefox returns the empty string for some reason, and it seems to select the active node a little differently. Opera uses #xxxxxx format for getComputedStyle() but rgb() here, and also drops the transparent part of the color if there is any.
Relevant CSS property: "color"
Equivalent values: Either both strings are valid CSS colors and have the same red, green, blue, and alpha components, or neither string is a valid CSS color.
hiliteColor
command
This command must not be enabled if the editing host is in the plaintext-only state.
For historical reasons, backColor and hiliteColor behave identically.
IE 9 RC doesn't support this. It uses backColor instead, but Gecko and Opera treat that differently, while all non-IE browsers treat hiliteColor the same, so I'm standardizing hiliteColor as the way to highlight text.
This is slightly tricky, because background-color does different things on block and inline elements. Given the name ("hiliteColor"), we really only want to apply it to inline elements. This is how everyone but Gecko behaves, but Gecko sometimes applies it to blocks too. WebKit doesn't set it on non-inline elements, but does clear it and push it down from them.
The spec doesn't do any of these: background-color on non-inline elements is not touched by hiliteColor, neither created nor removed. If users want to remove the style, they need to use removeFormat. Adding it usually makes no sense; see the comment for backColor.
For color parsing, see the comment for foreColor.
See bug 13829.
currentColor is bad for the same reason as relative font sizes. It will confuse the algorithm, and doesn't seem very useful anyway. For hiliteColor you could conceive of it being useful, but it will still confuse the algorithm, so ban it for now anyway.
If value is still not a valid CSS color, or if it is currentColor, return false.
For indeterminacy, this follows no one. Firefox 6.0a2 and Chrome 14 dev both always return false. However, the spec makes sense, since it's consistent with other commands.
Relevant CSS property: "background-color"
Equivalent values: Either both strings are valid CSS colors and have the same red, green, blue, and alpha components, or neither string is a valid CSS color.
italic
command
This command must not be enabled if the editing host is in the plaintext-only state.
Action: If queryCommandState("italic")
returns
true, set the selection's
value to "normal". Otherwise set the selection's value to
"italic". Either way, return true.
Inline command activated values: "italic" or "oblique"
Relevant CSS property: "font-style"
removeFormat
command
This command must not be enabled if the editing host is in the plaintext-only state.
See bug, and also research by Ryosuke for WebKit.
Tested in IE 9, Firefox 4.0, Chrome 12 dev, Opera 11.00.
All elements whose default rendering is display: block are left untouched by all browsers (although IE seems to throw an exception on <marquee> for some reason).
It's not clear to me why we should leave <a> alone, but everyone but Opera does. In OpenOffice.org 3.2.1, doing "Default Formatting (Ctrl+M)" doesn't remove links. In Microsoft Word 2007, doing "Clear Formatting" also doesn't remove links. Verdict: don't remove links. Apparently they don't logically qualify as "formatting".
Conclusion: IE/WebKit is a solid majority by market share and they're closely interoperable, since WebKit copied IE here. Also, it makes more sense to assume that unrecognized elements don't represent any kind of inline formatting, i.e., have a blacklist of elements to remove instead of a whitelist to keep. Thus I remove more or less the same things as IE/WebKit.
I remove blink because IE does it and it makes sense, although
Chrome doesn't; I remove abbr although only Firefox does, for
consistency with acronym; and I remove bdi and mark because they're
evidently left alone only because they're unrecognized. Finally, I
remove span because otherwise, something like <span style="font-variant: small-caps">
will be
left intact, which isn't expected and matches no browser except IE.
(Chrome doesn't remove spans in general, but it does remove spans
with style attributes, or something like that.)
Browsers will split up all these inline elements if the selection is contained within them. Opera does strip unrecognized elements with display: block if they're within the selection, but doesn't split them up if they contain the selection.
Chrome 14 dev removes style attributes from every element in the range, but IE10PP2, Firefox 7.0a2, and Opera 11.50 do not, so I go with them. As noted above, this means I need to remove spans. I could conceivably change to remove only spans with style attributes, but it doesn't seem worth it: I'll just match Gecko.
TODO: This has to be kept in sync when new HTML elements are added. I need to figure out some way of coordinating this.
A removeFormat candidate is an editable HTML element with [=Element/local name=] "abbr", "acronym", "b", "bdi", "bdo", "big", "blink", "cite", "code", "dfn", "em", "font", "i", "ins", "kbd", "mark", "nobr", "q", "s", "samp", "small", "span", "strike", "strong", "sub", "sup", "tt", "u", or "var".
The last sentence just prettifies the resulting range a bit.
If the active range's [=range/start
node=] is an editable {{Text}} node, and
its [=range/start offset=] is neither zero nor its [=range/start
node=]'s [=Node/length=], call splitText()
on the active range's [=range/start
node=], with argument equal to the active
range's [=range/start offset=]. Then set the active range's [=range/start node=] to the
result, and its [=range/start offset=] to zero.
splitText()
on the active range's [=range/end node=],
with argument equal to the active range's
[=range/end offset=].
TODO: Splitting the parent is really a block algorithm. It's not clear whether it's desirable to use for inline nodes. Perhaps it's okay, but it makes me a little uneasy.
For each node in node list, while node's [=tree/parent=] is a removeFormat candidate in the same editing host as node, split the parent of the one-node list consisting of node.
This step is for cases like <p style=font-weight:bold>foo[bar]baz</p>, where splitting/removing tags won't help. We don't need to run superscript, since subscript does the same thing here. We run subscript first so <sub>/<sup> won't upset fontSize.
For each of the entries in the following list, in the given order, set the selection's value to null, with command as given.
strikethrough
command
This command must not be enabled if the editing host is in the plaintext-only state.
TODO: See underline TODO.
Action: If queryCommandState("strikethrough")
returns true, set the
selection's value to null. Otherwise set the selection's value to
"line-through". Either way, return true.
Inline command activated values: "line-through"
subscript
command
This command must not be enabled if the editing host is in the plaintext-only state.
queryCommandState("subscript")
, and
let state be the result.
Indeterminate: True if either among formattable nodes that are effectively contained in the active range, there is at least one with effective command value "subscript" and at least one with some other effective command value; or if there is some formattable node effectively contained in the active range with effective command value "mixed". Otherwise false.
For <sup><sub>foo</sub></sup>, Firefox 6.0a2 and Opera 11.11 say the state is true for both superscript and subscript, and indeterminate is false; Chrome 14 dev says it's true for subscript but not superscript, and indeterminate is false. We follow neither of these behaviors: we return false for both states, and say indeterminate is true. The reason is because we want to return true for a state if we'll do nothing, false if we'll do something; and if we have nesting like this, we'll always do something, namely get rid of all those ancestors and replace them with a single tag. This matches what happens in other indeterminate situations, so it's fair to consider it indeterminate.
Inline command activated values: "subscript"
superscript
command
This command must not be enabled if the editing host is in the plaintext-only state.
queryCommandState("superscript")
,
and let state be the result.
Indeterminate: True if either among formattable nodes that are effectively contained in the active range, there is at least one with effective command value "superscript" and at least one with some other effective command value; or if there is some formattable node