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 byContent Editable andInput Events.
This document defines the behavior of the editing commands that can be executed withexecCommand.
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> if we happen to be modifying that node anyway.Tests can be foundhere.
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 ofcommands, identified byASCII case-insensitive strings. Eachcommand can have several pieces of data associated with it:
execCommand(). Everycommand defined in this specification has anaction defined for it in the relevant section. For example,thebold command'saction generally makes the current selection bold, or removes bold if the selection is already bold. An editing toolbar might provide buttons that execute theaction for acommand if clicked, or a script might run anaction without user interaction to achieve some particular effect. Actions return either true or false, which can affect the return value ofexecCommand().queryCommandIndeterm(), depending on the current state of the document. Generally, acommand that has astate defined will beindeterminate if thestate is true for part but not all of the current selection, and acommand that has avalue defined will beindeterminate if different parts of the selection have differentvalues. An editing toolbar might display a button or control in a special way if thecommand isindeterminate, 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, acommand can only beindeterminate if itsstate is false, supposing it has astate.queryCommandState(), depending on the current state of the document. Thestate of acommand is true if it is already in effect, in some sense specific to thecommand. Mostcommands that have astate defined will take oppositeactions depending on whether thestate is true or false, such as making the selection bold if thestate is false and removing bold if thestate is true. Others will just have no effect if thestate is true, likethejustifyCenter command. Still others will have the same effect regardless, likethestyleWithCss command. An editing toolbar might display a button or control differently depending on thestate andindeterminacy of thecommand.queryCommandValue(), depending on the current state of the document. Acommand usually has avalue instead of astate if the property it modifies can take more than two different values, liketheforeColor command. If thecommand isindeterminate, itsvalue is generally based on the start of the selection. Otherwise, in most cases thevalue holds true for the entire selection, but seethejustifyCenter command anditsthreecompanions for an exception. An editing toolbar might display thevalue of acommand as selected in a drop-down or filled in in a text box, if thecommand isn'tindeterminate.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.
Somecommands will besupported in a given user agent, and some will not. Allcommands defined in this specification must besupported, except optionallythecopy command,thecut command, and/orthepaste command. Additionalvendor-specific commands can also besupported, but implementers must prefix anyvendor-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.
Acommand that does absolutely nothing in a particular user agent, such thatexecCommand() never has any effect andqueryCommandEnabled() andqueryCommandIndeterm() andqueryCommandState() andqueryCommandValue() each return the same value all the time, must not besupported.
In a particular user agent, everycommand must be consistently eithersupported or not. Specifically, a user agent must not permit one page to see the samecommand sometimessupported 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 samecommand assupported for some pages and not others, e.g., if thecommand is only supported for certain origins for security reasons.
Authors can tell whether acommand issupported usingqueryCommandSupported().
At any given time, asupported command can be eitherenabled or not. Authors can tell whether acommand is currentlyenabled usingqueryCommandEnabled().Commands that are notenabled do nothing, as described in the definitions of the various methods that invokecommands.
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 isbug 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.
Amongcommands defined in this specification, those listed inMiscellaneous commands are alwaysenabled, except forthecut command andthepaste command. The othercommands defined here areenabled if theactive range is not null, its [=range/start node=] is eithereditable or an [=editing host=], theediting host of its [=range/start node=] is not anEditContext editing host, its [=range/end node=] is eithereditable or an [=editing host=], theediting host of its [=range/end node=] is not anEditContext 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 forshow UI.
When theexecCommand(command,show UI,value) method on the {{Document}} interface is invoked, the user agent must run the following steps:
For supported: see comment beforeSupported 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?
Ifcommand is not in theMiscellaneous 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 beenabled.
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.
Tomap 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 thequeryCommandEnabled(command) method on the {{Document}} interface is invoked, the user agent must run the following steps:
See comment beforeSupported commands.
When thequeryCommandIndeterm(command) method on the {{Document}} interface is invoked, the user agent must run the following steps:
For supported: see comment beforeSupported 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.
Ifcommand is notsupported or has noindeterminacy, return false.
When thequeryCommandState(command) method on the {{Document}} interface is invoked, the user agent must run the following steps:
See comment on the comparable line forqueryCommandIndeterm().
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. SeeMozilla bug 742240.
When thequeryCommandSupported(command) method on the {{Document}} interface is invoked, the user agent must return true ifcommand issupported and available within the current script on the current site, and false otherwise.
When thequeryCommandValue(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 forqueryCommandIndeterm().
Ifcommand is notsupported or has novalue, 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 . . .
Ifcommand is "fontSize" and itsvalue override is set, convert thevalue override to an integer number of pixels and return thelegacy font size for the result.
All of these methods must treat theircommand argumentASCII case-insensitively.
The methods in this section have mostly been designed so that the following invariants hold afterexecCommand() 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 beforeexecCommand() was called (or throw an exception).queryCommandValue() will return something equivalent to the value passed toexecCommand() (or throw an exception). "Equivalent" here needs to be construed broadly in some cases, such asfontSize. The first two points do not always hold forstrikethrough orunderline, because it can be impossible to unset text-decoration in CSS. Also, by design, the state ofinsertOrderedList andinsertUnorderedList 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 variousjustify 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.
AnHTML element is an {{Element}} whose [=Element/namespace=] is theHTML namespace.
Aprohibited 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.
Aprohibited paragraph child is anHTML element whose [=Element/local name=] is aprohibited 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).
Ablock node is either an {{Element}} whose "display" property does not haveresolved value "inline" or "inline-block" or "inline-table" or "none", or a [=document=], or a {{DocumentFragment}}.
Aninline node is anode that is not ablock node.
Something iseditable if it is anode; 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=] oreditable; and either it is anHTML element, or it is an svg or math element, or it is not an {{Element}} and its [=tree/parent=] is anHTML element.
Aneditable node cannot be a [=document=] or {{DocumentFragment}}, its [=tree/parent=] cannot be null, and it must descend from either an {{Element}} or a [=document=].
Theediting host ofnode is null ifnode is neithereditable nor an [=editing host=];node itself, ifnode is an [=editing host=]; or the nearest [=tree/ancestor=] ofnode that is an [=editing host=], ifnode iseditable.
Twonodes arein the same editing host if theediting host of the first is non-null and the same as theediting 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.
Acollapsed 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.
Anextraneous 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.)
Awhitespace 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}} whoseresolved 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}} whoseresolved value for "white-space" is "pre-line".
node is acollapsed 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 knownode 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 thatnode is collapsed. We assume a block boundary is either when we hit a line break or block node, or we hit the end ofancestor (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".
Letreference benode.
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.
Letreference benode.
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 isvisible if it is anode that either is ablock node, or a {{Text}} node that is not acollapsed whitespace node, or an [^img^], or a [^br^] that is not anextraneous line break, or anynode with avisible [=tree/descendant=]; excluding anynode with an [=tree/inclusive ancestor=] {{Element}} whose "display" property hasresolved value "none".
Something isinvisible if it is anode that is notvisible.
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.
Acollapsed block prop is either acollapsed line break that is not anextraneous line break, or an {{Element}} that is aninline node and whose [=tree/children=] are all eitherinvisible orcollapsed block props and that has at least one [=tree/child=] that is acollapsed 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.
Theactive range is the [=range=] of theselection given by callinggetSelection() on thecontext object. (Thus theactive range may be null.)
Each {{Document}} has a booleanCSS styling flag associated with it, which must initially be false. (ThestyleWithCSS command can be used to modify or query it, by means of theexecCommand() andqueryCommandState() methods.)
Each {{Document}} is associated with a string known as thedefault single-line container name, which must initially be "div". (ThedefaultParagraphSeparator command can be used to modify or query it, by means of theexecCommand() andqueryCommandValue() methods.)
For somecommands, each {{Document}} must have a booleanstate override and/or a stringvalue override. These do not change thecommand'sstate orvalue, but change the way some algorithms behave, as specified in those algorithms' definitions. Initially, both must be unset for everycommand. Whenever the number of [=ranges=] in theselection changes to something different, and whenever a [=boundary point=] of the [=range=] at a given index in theselection changes to something different, thestate override andvalue override must be unset for everycommand. Thevalue override forthebackColor command must be the same as thevalue override forthehiliteColor 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 likebold 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 likebold set state and value overrides, andinsertText checks for them and applies them to the newly-inserted text. Other commands likedelete also interact with overrides.
Seebug 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 theCSS styling flag,default single-line container name, and anystate overrides orvalue 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 ofnodes 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 anode to a new location,preserving ranges, remove thenode 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=]'snode is the same as or a [=tree/descendant=] ofnode, 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?
Toset the tag name of an {{Element}}element tonew 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}} ofelement.Toremove extraneous line breaks before anodenode:
<br> sometimes has no effect in CSS, such as in the markupfoo<br><p>bar</p>. In such cases we like to remove the extra markup to keep things tidy.
Toremove extraneous line breaks at the end of anodenode:
Toremove extraneous line breaks from anode, firstremove extraneous line breaks before it, thenremove extraneous line breaks at the end of it.
Towrap a listnode list of consecutive [=tree/sibling=]nodes, run the following algorithm. In addition tonode list, the algorithm accepts two inputs: an algorithmsibling criteria that accepts anode as input and outputs a boolean, and an algorithmnew parent instructions that accepts nothing as input and outputs anode or null. If not provided,sibling criteria returns false andnew parent instructions returns null.
This algorithm basically does two things. First, it looks at the previous and next siblings of the nodes innode list. If runningsibling criteria on one or both of the siblings returns true, the nodes innode list are moved into the sibling(s). Otherwise,new parent instructions is run, and the result is used to wrapnode list. For instance, to wrapnode list in a<b>, one might invoke this algorithm withsibling criteria returning true only for<b> elements andnew 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 ofnode list isinvisible, 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.
Ifnode list's last member is aninline node that's not a [^br^], andnode list's last member's {{Node/nextSibling}} is a [^br^], append that [^br^] tonode list.
Seebug 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>.
Whilenode list's first member's {{Node/previousSibling}} isinvisible, prepend it tonode list.
This can only happen ifnew 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.
Ifnew parent is null, abort these steps and return null.
Most callers will create a new element to return innew 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.
Ifnew 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=] withnode equal to the [=tree/parent=] ofnew parent and [=boundary point/offset=] equal to the [=tree/index=] ofnew parent, add one to that [=boundary point=]'s [=boundary point/offset=].
createElement("br") on the {{Node/ ownerDocument}} ofnew parent and append the result as the last [=tree/child=] ofnew parent.createElement("br") on the {{Node/ ownerDocument}} ofnew parent and insert the result as the first [=tree/child=] ofnew parent.This could happen ifnew parent instructions returned a node whose parent wasn't null.
Iforiginal parent iseditable 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.
Ifnew parent's {{Node/nextSibling}} iseditable and runningsibling criteria on it returns true:
createElement("br") on the {{Node/ ownerDocument}} ofnew parent and append the result as the last [=tree/child=] ofnew 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.
Aname 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".
Anelement with inline contents is anHTML element whose [=Element/local name=] is aname 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. Seebug 13201.
Anode or stringchild is anallowed child of anode or stringparent 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. Thefix 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.
Ifparent is "script", "style", "plaintext", or "xmp", or anHTML element with [=Element/local name=] equal to one of those, andchild 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.
Ifchild is "a", andparent or some [=tree/ancestor=] ofparent 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.
Ifchild is aprohibited paragraph child name andparent or some [=tree/ancestor=] ofparent is anelement with inline contents, return false.
Also can't be serialized as text/html.
Ifchild is "h1", "h2", "h3", "h4", "h5", or "h6", andparent or some [=tree/ancestor=] ofparent is anHTML 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.
Letparent be the [=Element/local name=] ofparent.
We allow children even where some intervening nodes will be inserted, like tr as a child of table.
Ifparent is on the left-hand side of an entry on the following list, then return true ifchild 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.
Ifchild is "dd" or "dt" andparent 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.
Anodenode iseffectively contained in a [=range=]range ifrange 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 isrange's [=range/start node=], it is a {{Text}} node, and its [=Node/length=] is different fromrange'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=] areeffectively contained inrange; and eitherrange's [=range/start node=] is not a [=tree/descendant=] ofnode or is not a {{Text}} node orrange's [=range/start offset=] is zero; and eitherrange's [=range/end node=] is not a [=tree/descendant=] ofnode or is not a {{Text}} node orrange's [=range/end offset=] is its [=range/end node=]'s [=Node/length=].
Amodifiable 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.
Asimple modifiable element is anHTML 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 runningtheitalic command will not remove it: it will nest<span > inside.
Aformattable node is aneditablevisiblenode that is either a {{Text}} node, an [^img^], or a [^br^].
Two quantities areequivalent values for acommand if either both are null, or both are strings and they're equal and thecommand does not define anyequivalent values, or both are strings and thecommand definesequivalent values and they match the definition.
Two quantities areloosely equivalent values for acommand if either they areequivalent values for thecommand, or if thecommand isthefontSize 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 theresolved 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 aspecified value in pixels is actually different from aspecified value like "small" or "x-large", because there is no precise mapping from such keywords to pixels.
If acommand hasinline command activated values defined but nothing else defines when it isindeterminate, it isindeterminate if amongformattable nodeseffectively contained in theactive range, there is at least one whoseeffective command value is one of the given values and at least one whoseeffective 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 acommand hasinline command activated values defined, itsstate is true if either noformattable node iseffectively contained in theactive range, and theactive range's [=range/start node=]'seffective command value is one of the given values; or if there is at least oneformattable nodeeffectively contained in theactive range, and all of them have aneffective 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 astandard inline value command, it isindeterminate if amongformattable nodes that areeffectively contained in theactive range, there are two that have distincteffective command values. Itsvalue is theeffective command value of the firstformattable node that iseffectively contained in theactive range; or if there is no such node, theeffective command value of theactive 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.
Theeffective command value of anodenode for a givencommand 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? Seebug. 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 theresolved value of "background-color" fornode.
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 asa bug, so I changed it to match Gecko/Opera.
Ifnode is a sub, setaffected by subscript to true.
sup, setaffected by superscript to true.Thespecified command value of an {{Element}}element for a givencommand 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 theCSS 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 settingproperty, return the value that it setsproperty to. size of 7, this will be the non-CSS value "xxx-large".)Torecord the values of a list ofnodesnode 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>, and a script runstheoutdent 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 eachnode innode list, for eachcommand in the list "subscript", "bold", "fontName", "fontSize", "foreColor", "hiliteColor", "italic", "strikethrough", and "underline" in that order:
Torestore the values specified by a listvalues returned by therecord the values algorithm:
Toclear the value of an {{Element}}element:
The idea is to remove anyspecified 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 isset 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.
Ifelement'sspecified command value forcommand 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 ofelement.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.
Ifelement'sspecified command value forcommand 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).
Topush down values to anodenode, given a new valuenew 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 thannode. 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.
Ifnode'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.
Ifpropagated value is null and is not equal tonew 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 theeffective command value ofcommand is notloosely equivalent tonew value on the [=tree/parent=] of the last member ofancestor list, andnew 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.
Ifchild is an {{Element}} whosespecified command value forcommand is neither null norequivalent topropagated value, continue with the nextchild.
Toforce the value of anodenode tonew 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 asimple 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 ofnode's {{Node/ previousSibling}}.
Thenew 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 ifsibling criteria didn't return true.
Wrap the one-node list consisting ofnode, withsibling criteria returning true for asimple modifiable element whosespecified command value isequivalent tonew value and whoseeffective command value isloosely equivalent tonew value and false otherwise, and withnew parent instructions returning null.
Ifnode isinvisible, 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 eachnode inchildren, withcommand andnew value as in this invocation of the algorithm.
createElement("b") on the {{Node/ownerDocument}} ofnode.createElement("i") on the {{Node/ownerDocument}} ofnode.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.
Ifcommand is "strikethrough" andnew value is "line-through", letnew parent be the result of callingcreateElement("s") on the {{Node/ ownerDocument}} ofnode.
createElement("u") on the {{Node/ownerDocument}} ofnode.See comment for foreColor for discussion. TODO: Define more carefully what happens when things are out of range or not integers or whatever.
Ifcommand is "foreColor", andnew value is fully opaque with red, green, and blue components in the range 0 to 255:
createElement("font") on the {{Node/ ownerDocument}} ofnode. color attribute ofnew parent to the result of applying the rules for serializing simple color values tonew value (interpreted as a simple color).createElement("font") on the {{Node/ownerDocument}} ofnode, then set the face attribute ofnew parent tonew value.createElement("a") on the {{Node/ownerDocument}} ofnode. href attribute ofnew parent tonew 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.
Letancestor benode'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.
Ifancestor is an [^a^],set the tag name ofancestor to "span", and letancestor 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.
Ifcommand is "fontSize"; andnew value is one of "x-small", "small", "medium", "large", "x-large", "xx-large", or "xxx-large"; and either theCSS styling flag is false, ornew value is "xxx-large": letnew parent be the result of callingcreateElement("font") on the {{Node/ownerDocument}} ofnode, then set the size attribute ofnew parent to the number from the following table based onnew 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 isa bug anyway.
Ifcommand is "subscript" or "superscript" andnew value is "subscript", letnew parent be the result of callingcreateElement("sub") on the {{Node/ownerDocument}} ofnode.
createElement("sup") on the {{Node/ownerDocument}} ofnode.createElement("span") on the {{Node/ownerDocument}} ofnode.This preserves boundary points correctly, as usual.
Insertnew parent innode's [=tree/parent=] beforenode.
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.
Toreorder modifiable descendants of anodenode, given acommandcommand and a valuenew 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.
Insertcandidate intonode's [=tree/parent=] immediately afternode.
Toset the selection's value tonew value:
The effect of this algorithm is to ensure that all nodeseffectively 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 aspecified command value for the command, we unset it (clear its value). This step also removessimple 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 aspecified command value that we're inheriting, we push the value down from that ancestor. Thus if we're unbolding the letter "r" in
foobar baz,
we get
foobar baz.
If we didn't push down values, the final step (forcing values) would instead give us
foobar 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 asimple 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 anallowed 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 noformattable nodeeffectively contained in theactive range:
The last sentence here just prettifies the resulting range a bit.
If theactive range's [=range/start node=] is aneditable {{Text}} node, and its [=range/start offset=] is neither zero nor its [=range/start node=]'s [=Node/length=], callsplitText() on theactive range's [=range/start node=], with argument equal to theactive range's [=range/start offset=]. Then set theactive range's [=range/start node=] to the result, and its [=range/start offset=] to zero.
splitText() on theactive range's [=range/end node=], with argument equal to theactive 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.
Letnode list be alleditablenodeseffectively contained in theactive 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 eachnode innode 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.
Ifnode is anallowed child of "span",force the value ofnode.
backColor commandThis command must not beenabled if theediting host is in theplaintext-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 commandThis command must not beenabled if theediting host is in theplaintext-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: IfqueryCommandState("bold") returns true,set the selection's value to "normal". Otherwiseset 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 commandThis command must not beenabled if theediting host is in theplaintext-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 thenundefined 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.
Ifvalue 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 eacheditable [^a^] element that has an href attribute and is an [=tree/ancestor=] of somenodeeffectively contained in theactive range, set that [^a^] element's href attribute tovalue.
fontName commandThis command must not beenabled if theediting host is in theplaintext-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 tovalue, 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 commandThis command must not beenabled if theediting host is in theplaintext-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 byqueryCommandValue() has to be numeric, so authors can't really use the command sanely no matter what we do. Seebug 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 amongformattable nodes that areeffectively contained in theactive range, there are two that have distincteffective 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.
Letpixel size be theeffective command value of the firstformattable node that iseffectively contained in theactive range, or if there is no such node, theeffective command value of theactive range's [=range/start node=], in either case interpreted as a number of pixels.
Relevant CSS property: "font-size"
Thelegacy font size for an integerpixel size is returned by the following algorithm:
size attribute is set toreturned size. size attribute is set to one plusreturned size.foreColor commandThis command must not beenabled if theediting host is in theplaintext-only state.
Color interpretations:
IE10PP2 Firefox 7.0a2 Chrome 14 dev Opera 11.50blue blue blue #0000ff #0000fff #f - - #f00000#f #f - - #f0000000f #00f - #0000ff #00000f#00f #00f rgb(0, 0, 255) #0000ff #00000f0000ff #0000ff - #0000ff #0000ff#0000ff #0000ff rgb(0, 0, 255) #0000ff #0000ff000000fff #0000ff - - -#000000fff #0000ff - - -rgb(0, 0, 255) rgb(0,0,255) rgb(0, 0, 255) #0000ff #00b000rgb(0%, 0%, 100%) rgb(0,0,255) rgb(0, 0, 255) #0000ff #00b000rgb( 0 ,0 ,255) rgb(0,0,255) rgb(0, 0, 255) #0000ff #00b000rgba(0, 0, 255, 0.0) #ba0000 rgba(0, 0, 255, 0) rgba(0, 0, 255, 0) #00ba00rgb(15, -10, 375) rgb(15,0,255) rgb(15, 0, 255) #0f00ff #00b015rgba(0, 0, 0, 1) #ba0010 rgb(0, 0, 0) - #00ba00rgba(255, 255, 255, 1) #000055 rgb(255, 255, 255) #ffffff #00ba02rgba(0, 0, 255, 0.5) #ba0000 rgba(0, 0, 255, 0.5) rgba(0, 0, 255, 0.5) #00ba00hsl(240, 100%, 50%) #000150 rgb(0, 0, 255) #0000ff #000024cornsilk cornsilk cornsilk #fff8dc #fff8dcpotato quiche #0000c0 - - #000a00transparent transparent - rgba(0, 0, 0, 0) #00a000currentColor #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 forcestyle="" 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).
Ifvalue 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.
Ifvalue 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 commandThis command must not beenabled if theediting host is in theplaintext-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.
Seebug 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.
Ifvalue 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 commandThis command must not beenabled if theediting host is in theplaintext-only state.
Action: IfqueryCommandState("italic") returns true,set the selection's value to "normal". Otherwiseset the selection's value to "italic". Either way, return true.
Inline command activated values: "italic" or "oblique"
Relevant CSS property: "font-style"
removeFormat commandThis command must not beenabled if theediting host is in theplaintext-only state.
Seebug, and alsoresearch 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> 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.
AremoveFormat candidate is aneditableHTML 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 theactive range's [=range/start node=] is aneditable {{Text}} node, and its [=range/start offset=] is neither zero nor its [=range/start node=]'s [=Node/length=], callsplitText() on theactive range's [=range/start node=], with argument equal to theactive range's [=range/start offset=]. Then set theactive range's [=range/start node=] to the result, and its [=range/start offset=] to zero.
splitText() on theactive range's [=range/end node=], with argument equal to theactive 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 eachnode innode list, whilenode's [=tree/parent=] is aremoveFormat candidatein the same editing host asnode,split the parent of the one-node list consisting ofnode.
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, withcommand as given.
strikethrough commandThis command must not beenabled if theediting host is in theplaintext-only state.
TODO: See underline TODO.
Action: IfqueryCommandState("strikethrough") returns true,set the selection's value to null. Otherwiseset the selection's value to "line-through". Either way, return true.
Inline command activated values: "line-through"
subscript commandThis command must not beenabled if theediting host is in theplaintext-only state.
queryCommandState("subscript"), and letstate be the result.Indeterminate: True if either amongformattable nodes that areeffectively contained in theactive range, there is at least one witheffective command value "subscript" and at least one with some othereffective command value; or if there is someformattable nodeeffectively contained in theactive range witheffective 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 commandThis command must not beenabled if theediting host is in theplaintext-only state.
queryCommandState("superscript"), and letstate be the result.Indeterminate: True if either amongformattable nodes that areeffectively contained in theactive range, there is at least one witheffective command value "superscript" and at least one with some othereffective command value; or if there is someformattable nodeeffectively contained in theactive range witheffective command value "mixed". Otherwise false.
Inline command activated values: "superscript"
underline commandThis command must not beenabled if theediting host is in theplaintext-only state.
TODO: There are a lot of problems with underline color and thickness, because text-decoration in CSS is horrible. These aren't prohibitive for normal use and existing browsers don't handle them either, so fixing these problems or working around them can be put off for now.
Action: IfqueryCommandState("underline") returns true,set the selection's value to null. Otherwiseset the selection's value to "underline". Either way, return true.
Inline command activated values: "underline"
unlink commandThis command must not beenabled if theediting host is in theplaintext-only state.
IE 9 RC unlinks the whole link you're pointing at, while others only unlink the current text. The latter behavior seems less expected, as with createLink, although I can't articulate precisely why. Word 2007 and OpenOffice.org 3.2.1 (Ubuntu) seem to give an option to remove the whole link or none of it, which backs the spec's requirement. See also #whatwg logs starting at 2011-05-13 at 16:53 EDT (UTC-0400).
See comment forthecreateLink command about indeterm/state/value.
href attribute and iscontained in theactive range or is an [=tree/ancestor=] of one of its [=boundary points=]. Anindentation element is either a [^blockquote^], or a [^div^] that has a style attribute that sets "margin" or some subproperty of it.
We need to allow stuff that sets border/padding because WebKit (Chrome 12 dev) sets "border: none; padding: 0px" when indenting. We need to allow stuff that sets classes because WebKit sets . We need to allow stuff that sets dir because IE9 does. The criteria could probably be tightened up a bit to reduce false positives, but it'll do for now.
Asimple indentation element is anindentation element that has no attributes except possibly
style attribute that sets no properties other than "margin", "border", "padding", or subproperties of those; and/or dir attribute.The notions of indentation element and simple indentation element parallel those ofmodifiable element andsimple modifiable element.
listing and xmp are included because otherwise insertParagraph inside them won't work, since paragraphs aren't an allowed child.
Anon-list single-line container is anHTML element with [=Element/local name=] "address", "div", "h1", "h2", "h3", "h4", "h5", "h6", "listing", "p", "pre", or "xmp".
Asingle-line container is either anon-list single-line container, or anHTML element with [=Element/local name=] "li", "dt", or "dd".
Theblock node of anodenode is either ablock node or null, as returned by the following algorithm:
Bug 14062. See alsoMozilla bug 590640, specifically comments 48 and on.
If acommandpreserves overrides, then before taking itsaction, the user agent mustrecord current overrides. After taking theaction, if theactive range is {{AbstractRange/collapsed}}, it mustrestore states and values from the recorded list.
All block commands preserve overrides excepttheinsertText command, which treats overrides specially.
TODO: When breaking a non-inline element out of an inline element, like p in b or whatever, it would make sense to re-wrap the contents in the inline tag.
Tofix disallowed ancestors ofnode:
We often run this algorithm after we move a node someplace, just in case it wound up somewhere it's not supposed to be. This avoids things like unserializable DOMs, blocks nested inside inlines, etc.
This case is really intended to handle stuff like list items or table cells that wander outside their proper place. We generally convert them into [^p^]s.
Ifnode is not anallowed child of any of its [=tree/ancestors=]in the same editing host:
createElement("dl") on thecontext object. Then abort these steps.There's no reason to change the node to a paragraph if that won't make it an allowed child anyway.
If "p" is not anallowed child of theediting host ofnode, abort these steps.
Because maybe it somehow wound up as the child of a p, like via insertHTML.
Fix disallowed ancestors ofnode.
This algorithm implies that we don't support a sublist in the middle of an item, only at the end. For instance,
<li>foo<ol>...</ol>bar</li>
gets transformed to
<li>foo</li><ol>...</ol><li>bar</li>
which in particular creates an extra list marker for "bar". This is okay; we don't need to expose all of HTML's markup abilities through execCommand(). Similarly, the superscript and subscript commands don't allow nesting. I didn't see any way to get a sublist in the middle of an item in Word 2007 or in OpenOffice.org 3.2.1 Ubuntu package, nor in any browser using just execCommand(), so it should be no big problem if we require that such nesting not occur. (Existing browsers behave weirdly and inconsistently when confronted with this kind of nesting.)
The reason we need this is that otherwise it gets very confusing to figure out what happens in cases like trying to outdent
<ol><li>[foo<ol><li>bar]</ol>baz</ol>
If we first normalize, then the natural answer is something like
<p>[foo<ol><li>bar]<li>baz</ol>
but if we don't, we'd have to special-case in the toggle lists and outdent algorithms. This might be worthwhile, but it's not at all clear, and what I have works okay, so I'll stick with it for now.
TODO: Investigate fixing this.
Tonormalize sublists in anodeitem:
createElement("li") on the {{Node/ ownerDocument}} ofitem, then insertnew item into the [=tree/parent=] ofitem immediately afteritem.Theselection's list state is returned by the following algorithm:
This is just a helper to tell the state and indeterminacy oftheinsertOrderedList command andtheinsertUnorderedList command:
| ol indeterm | ol state | ul indeterm | ul state | |
|---|---|---|---|---|
| ol | false | true | false | false |
| ul | false | false | false | true |
| mixed | true | false | true | false |
| mixed ol | true | false | false | false |
| mixed ul | false | false | true | false |
| none | false | false | false | false |
The child-of-child case is necessary right now because of the following:
<ol><li>[foo<ol><li>bar]</ol>baz</ol>
With the current (July 2011) block-extend algorithm, this will become:
{<ol><li>foo<ol><li>bar</ol>}baz</ol>because of the magical li handling in block-extend. We want this to register as ol, because after normalizing sublists it will become
{<ol><li>foo</li><ol><li>bar</ol>}<li>baz</ol>But the text node "foo" will wind up in node list, and is not the child of an ol. This is all very messy and has to do with questionable decisions about how to handle nested lists.
If every member ofnode list is either an [^ol^] or the [=tree/child=] of an [^ol^] or the [=tree/child=] of an [^li^] [=tree/child=] of an [^ol^], and none is a [^ul^] or an [=tree/ancestor=] of a [^ul^], return "ol".
This condition and the last are mutually exclusive, so the order is actually irrelevant. Clearly they could only both hold if no member of node list is an ol or ul, so if they both held, every member would have to be either the child of an ol and of a ul, or of an ol and an li, or a ul and an li, or of an li that's the child of both an ol and a ul. This is impossible unless the list is empty, in which case we already aborted.
If every member ofnode list is either a [^ul^] or the [=tree/child=] of a [^ul^] or the [=tree/child=] of an [^li^] [=tree/child=] of a [^ul^], and none is an [^ol^] or an [=tree/ancestor=] of an [^ol^], return "ul".
When querying the value of justify*, IE9 seems to return boolean false across the board when it doesn't throw exceptions, which it usually does in my tests. Chrome 14 dev returns the string "true" or "false" depending on state, as in other cases, which is useless. Opera 11.11 returns "" across the board. Firefox 6.0a2 behaves like with other command values: it returns "center"/"justify"/"left"/"right" depending on the active range's start node. Since this is the only behavior that's possibly useful, it's what I specced. Firefox ties the value closely to the state, returning true for the state if and only if the value matches the desired value, but this seems less useful than what I've specced for the state.
This API is based on the four-state text-align of CSS 2.1. We do some crude mapping to make it not break too badly with CSS3 values, but it's not going to work well given the design of the API.
Thealignment value of anodenode is returned by the following algorithm:
This is basically like the resolved value of text-align, but with two key differences. First, it only ever evaluates to center/justify/left/right, since that's the model that the justify commands work with. Second, it ignores inline elements, because text-align has no effect on them and their alignment is actually governed by their nearest block ancestor (if any).
This means there's no applicable style rule, so probably it will wind up left-aligned. Of course this ignores the fact that the alignment will really be "start", so this is wrong for RTL, but it's a pretty marginal corner case anyway. (It will only happen if, e.g., everything up to and including the html and body elements have display: inline or none.)
Ifnode is not an {{Element}}, return "left".
Sometimes one location corresponds to multiple distinct boundary points. For instance, in the DOM<p>Hello</p>, a boundary point might lie at the beginning of the text node or the beginning of the element node, but these don't logically differ much and will appear the same to the user, so we often want to treat them the same. The algorithms here allow navigating through such equivalent boundary points, for when we want to make the selection as inclusive or exclusive as possible. For deletion, we want to delete as few nodes as possible, so we move the start node forward and the end node backward. In other cases we might do the reverse, expanding the selection. In still other cases we might want to move forward or backward to try getting to a text node.
Given a [=boundary point=] (node,offset), thenext equivalent point is either a [=boundary point=] or null, as returned by the following algorithm:
{}<span></span> were equivalent to<span>{}</span>, it would also be equivalent to<span></span>{}. This produces very unexpected results for nodes like<br>.<span>foo[]</span> is equivalent to<span>foo{}</span>, which is equivalent to<span>foo</span>{}. However,<p>foo{}</p> isnot equivalent to<p>foo</p>{} – the cursor might look like it's in a visibly different position.{}<span>foo</span> is equivalent to<span>{}foo</span>, which is equivalent to<span>[]foo</span>. As noted before, though, we don't descend into empty nodes. And again,{}<p>foo</p> is different from<p>{}foo</p>.Given a [=boundary point=] (node,offset), theprevious equivalent point is either a [=boundary point=] or null, as returned by the following algorithm:
Thefirst equivalent point of a [=boundary point=] (node,offset) is returned by the following algorithm:
Thelast equivalent point of a [=boundary point=] (node,offset) is returned by the following algorithm:
A [=boundary point=] (node,offset) is ablock start point if eithernode's [=tree/parent=] is null andoffset is zero; ornode has a [=tree/child=] with [=tree/index=]offset − 1, and that [=tree/child=] is either avisibleblock node or avisible [^br^].
A [=boundary point=] (node,offset) is ablock end point if eithernode's [=tree/parent=] is null andoffset isnode's [=Node/length=]; ornode has a [=tree/child=] with [=tree/index=]offset, and that [=tree/child=] is avisibleblock node.
A [=boundary point=] is ablock boundary point if it is either ablock start point or ablock end point.
When a user agent is toblock-extend a [=range=]range, it must run the following steps:
Generally, block commands work on any block that contains part of the selection, even if the selection doesn't include the whole block. This algorithm takes an input range, copies it, stretches out the copy to contain entire blocks, and returns the result. Then the caller will normally use it instead of the range it started with. For instance, if the cursor is collapsed in a text node inside a paragraph, this will generally return a range that includes the whole paragraph.
Two bits of magic worth noting. First,<br> counts as a block delimiter here, since it looks the same as a block boundary (assuming no margin etc.) and this is a visual API. We include the<br> as part of the line that precedes it. Second, if the selection is inside an<li>, this will extend it to include the whole<li>. This latter point is weird, and I should re-examine it sometime, but it seems to work.
This just changes something like<div>{<p>foo]</p></div> to{<div><p>foo]</p></div>.
Whilestart offset is zero andstart node's [=tree/parent=] is not null, setstart offset tostart node's [=tree/index=], then setstart node to its [=tree/parent=].
Anodenodefollows a line break if the following algorithm returns true:
Anodenodeprecedes a line break if the following algorithm returns true:
Torecord current overrides:
When restoring, some commands can interfere with others. Specifically, we want to restore createLink before foreColor and underline, and subscript and superscript before fontSize. TODO: This approach only works for default styles (although I'm not sure offhand how we could handle non-default styles in principle).
Firefox 7.0a2 and Opera 11.50 don't honor createLink with collapsed selections. If you insert text, it's not linked. The spec follows Chrome 14 dev. IE9 also ignores createLink with collapsed selections, but its behavior in other cases for collapsed selections is totally different from all other browsers, so it's not a fair comparison.
If there is avalue override for "createLink", add ("createLink",value override for "createLink") tooverrides.
Firefox 7.0a2 and Opera 11.50 will honor repeated subscript/superscript commands on a collapsed selection, allowing you to nest them. The spec follows the general philosophy that we don't allow users to nest subscript/superscript, so the last one wins. Chrome 14 dev is similar to the spec.
For eachcommand in the list "bold", "italic", "strikethrough", "subscript", "superscript", "underline", in order: if there is astate override forcommand, add (command,command'sstate override) tooverrides.
Torecord current states and values:
Thus we will set state overrides based on the first formattable node, to match values. This means that if you have<p>foo<b>[bar</b>baz]</p> and hit backspace and hit A, you'll get<p>foo<b>a[]</b></p>, although bold was previously indeterminate. This is needed to match the behavior of hitting A straight away, since innerText doesn't strip wrappers when it invokes "delete the contents".
For eachcommand in the list "bold", "italic", "strikethrough", "subscript", "superscript", "underline", in order: ifnode'seffective command value forcommand is one of itsinline command activated values, add (command, true) tooverrides, and otherwise add (command, false) tooverrides.
Special case for fontSize, because its values are weird.
Add ("fontSize",node'seffective command value for "fontSize") tooverrides.
This is wrong: it will convert non-pixel sizes to pixel sizes. But I don't see any way to avoid it. Hopefully it won't come up too often. font-size is a real problem, because the mapping from specified value to computed value is lossy and not fully defined (e.g., how many px is "small"?).
Torestore states and values specified by a listoverrides returned by therecord current overrides orrecord current states and values algorithm:
queryCommandState(command) returns something different fromoverride, take theaction forcommand, withvalue equal to the empty string.queryCommandValue(command) returns something notequivalent tooverride, take theaction forcommand, withvalue equal tooverride.This special case is needed becausecreateLink has no value.
Otherwise, ifoverride is a string; andcommand is "createLink"; and either there is avalue override for "createLink" that is not equal tooverride, or there is novalue override for "createLink" andnode'seffective command value for "createLink" is not equal tooverride: take theaction for "createLink", withvalue equal tooverride.
The override will be some CSS value, so we have to convert it to a legacy font size.
Otherwise, ifoverride is a string; andcommand is "fontSize"; and either there is avalue override for "fontSize" that is not equal tooverride, or there is novalue override for "fontSize" andnode'seffective command value for "fontSize" is notloosely equivalent tooverride:
If we took the action for a command, we need to resetnode, because it might have changed. For instance, if the selection wasfoo[bar]baz, the text node could have been split so that the first part is now outside the active range.
Setnode to the firstformattable nodeeffectively contained in theactive range, if there is one.
TODO: Consider what should happen for block merging in corner cases like display: inline-table.
Todelete the selection, given ablock merging flag that defaults to true, astrip wrappers flag that defaults to true, and a stringdirection that defaults to "forward":
The idea behind this algorithm is self-explanatory, but the details wind up being remarkably complicated.
First, any editable nodes inside the selection will be deleted, and the selection will be collapsed. By way of contrast,effectively contained tries to expand the range to include as much as possible, so<p>[foo]</p> contains the<p>. What we do here is contract the range to include as little as possible, so{<p>foo</p>} contains onlyfoo and doesn't delete the paragraph.
After that, if the selection originally started and ended in different blocks, and theblock merging flag is true, the end block will get merged into the start block. This is needed so if the user selects text on several lines and deletes it, the text immediately that was before the selection winds up on the same line as the text immediately after it. For example,<p>fo[o</p><div>b]ar</div> becomes<p>fo[]ar</p>. This procedure winds up being tricky, and takes up a large chunk of the logic.
Tables are a notable special case. If an entire table is contained in the range, it will be deleted. If it's anything less, only the contents of the cells will be deleted and the table structure will be left intact.
Thestrip wrappers flag controls what happens if the deletion removes all the contents of an inline element. If wrappers are being stripped, the empty inline element will be removed: this is usually what you want, because the user can't position the selection inside it. But callers liketheinsertText command that intend to immediately insert new contents want to leave the wrappers, so the new contents are wrapped by the same thing as the old.
Even ifstrip wrappers is true, the algorithm will set astate override andvalue override for any styles it winds up removing. This way, if the user deletes a wrapper that adds a style (or link for that matter), then types something, the new text will get the style from the old text.
This is a selection like<span>foo[</span></span>]bar</span>, where the boundary points are equivalent but not identical. We just collapse it and abort, since there's nothing to delete.
collapseToStart() on thecontext object'sselection.collapseToEnd() on thecontext object'sselection.The previous two steps are so that we won't leave empty text nodes anywhere.
collapse(start node,start offset) on thecontext object'sselection.extend(end node,end offset) on thecontext object'sselection.When we delete a selection that spans multiple blocks, we merge the end block's contents into the start block, like
<p>fo[o</p><pre>b]ar</pre>-> <p>fo[]ar</p>.
We figure out what the start and end blocks are before we start deleting anything.
We only merge to or from block nodes or editing hosts. (This is just in case someone makes a span into an editing host and sticks paragraphs inside it or something . . . we could probably drop that proviso.) Firefox 7.0a2 ignores the display property when merging, so it doesn't merge<span style=display:block> but does merge<p style=display:inline>. This is undesirable, because it's visually wrong. IE10PP2 and Chrome 14 dev behave more like the spec, and Opera 11.50 seems to be unable to make up its mind.
If span isn't an allowed child, it's probably something unpleasant like a table row or a list or such. We don't want to merge to or from something like that, because we'd most likely wind up with the wrong type of child somewhere. It should be pretty hard for this to happen given the normalization we do on the selection; I'm not actually sure how it could happen at all, actually, unless you start out with a DOM that has non-allowed children someplace. So it's basically a sanity check.
We don't let either start block or end block be a td or th. This means we'll never merge to or from a td or th. This matches Firefox 5.0a2, and reportedly Word as well. Chrome 13 dev and Opera 11.11 allow merging from a non-table cell end block to a table cell start block, but not vice versa. In IE9 the delete key just does nothing.
Ifstart block is neither ablock node nor an [=editing host=], or "span" is not anallowed child ofstart block, orstart block is a [^td^] or [^th^], setstart block to null.
Later on we'll restoreoverrides. This ensures that if we delete inline formatting elements and the user then types something, the typed text will have the same style as before.
As far as I can tell, IE9 and Opera 11.50 don't do this at all. If you delete a selection and then start typing, the new text doesn't take on the styles of the old text.
Firefox 7.0a2 seems to do it for some styles but not others. Strikethrough, superscript, subscript, and links seem to be lost, at a minimum.
The spec goes with something like Chrome 14 dev, which tries to preserve lots of stuff.
Record current states and values, and letoverrides be the result.
Now we actually begin deleting things.
This whole piece of the algorithm is based on deleteContents() in DOM Range, copy-pasted and then adjusted to fit.
Ifstart node andend node are the same, andstart node is aneditable {{Text}} node:
deleteData(start offset,end offset −start offset) onstart node.collapseToStart() on thecontext object'sselection.collapseToEnd() on thecontext object'sselection.This is needed to restore any overrides that would otherwise be lost. TODO: In this and similar cases, we could optimize by saving only overrides, not the full state/value.
Restore states and values fromoverrides.
deleteData() on it, withstart offset as the first argument and ([=Node/length=] ofstart node −start offset) as the second argument.IE9 doesn't seem to let you do any intercell deletions: the delete key does nothing if you select across multiple cells. Firefox 5.0a2 and Opera 11.11 behave as the spec says, not removing any table things. Chrome 13 dev will remove entire rows if selected. Note that IE, Firefox, Word 2007, and OpenOffice.org 3.2.1 Ubuntu all switch to a magic cell-selection mode when you try to select between cells, at least in some cases, instead of selecting letter-by-letter.
For eachnodecontained in theactive range, appendnode tonode list if the last member ofnode list (if any) is not an [=tree/ancestor=] ofnode;node iseditable; andnode is not a [^thead^], [^tbody^], [^tfoot^], [^tr^], [^th^], or [^td^].
Do this before stripping wrappers: seebug 13831.
If theblock node ofparent has novisible [=tree/children=], andparent iseditable or an [=editing host=], callcreateElement("br") on thecontext object and append the result as the last [=tree/child=] ofparent.
Taking insertText to test the case where strip wrappers is false, with value a:<p>[foo<b>bar</b>]baz becomes<p>a[]baz per spec, in IE9, and in Chrome 14 dev. Firefox 7.0a2 and Opera 11.50 make it<p>a[]<b></b>baz, with a useless wrapper.<p>foo<b>[bar</b>baz] becomes<p>foo<b>a[]</b> per spec and in IE9 and Firefox 7.0a2 and Opera 11.50; in Chrome 14 dev apparently it initially becomes<p>fooa[], but then the style is recreated. This is detectable if you do something weird like<span style=color:#aBcDeF> instead of<b>: it comes<font class=Apple-style-span color=#abcdef> or such. I follow IE9 in all cases, because it makes the most sense.
Ifstrip wrappers is true orparent is not an [=tree/inclusive ancestor=] ofstart node, whileparent is aneditableinline node with [=Node/length=] 0, letgrandparent be the [=tree/parent=] ofparent, then removeparent fromgrandparent, then setparent tograndparent.
Even ifstrip wrappers is false, we still want to strip wrappers that aren't [=tree/inclusive ancestors=] ofstart node. The idea of not stripping wrappers is that we're going to insert new content right afterward, like text or an image, but that new content will be inserted at the start node. Wrappers in other places still need to be removed, because they would otherwise remain empty.
deleteData(0,end offset) on it.Now we need to merge blocks. The simplest case is something like
<p>fo[o</p><p>bar</p><p>b]az</p>-> <p>fo</p>{}<p>az</p>-> <p>fo{}az</p>where neither block descends from the other. More complicated is something like
foo[<p>]bar</p>-> foo[]bar
or
<p>foo[</p>]bar-> <p>foo[]bar</p>
where one descends from the other.
collapseToStart() on thecontext object'sselection.collapseToEnd() on thecontext object'sselection.We might have added a br to the start/end block in an earlier step. Now we're about to merge the blocks, and we don't want the br's to get in the way. The end block is being destroyed no matter what. If the start block winds up empty after merging, we'll add a new br child at the end so it doesn't collapse.
Ifstart block has one [=tree/child=], which is acollapsed block prop, remove its [=tree/child=] from it.
Just repeatedly blow up the end block in this case.
Ifstart block is an [=tree/ancestor=] ofend block:
collapse() on thecontext object'sselection, with first argumentstart block and second argument the [=tree/index=] ofreference node.createElement("br") on thecontext object and insert it intoend block's [=tree/parent=] immediately afterend block.In this case, pull in everything that comes afterstart block, until we hit a br or block node.
Otherwise, ifstart block is a [=tree/descendant=] ofend block:
collapse() on thecontext object'sselection, with first argumentstart block and second argumentstart block's [=Node/length=].In the last case, just move all the children of the end block to the start block, and then get rid of any elements we emptied that way.
Otherwise:
collapse() on thecontext object'sselection, with first argumentstart block and second argumentstart block's [=Node/length=].We might have deleted the contents between two lists, in which case we should merge them. Seebug 13976.
createElement("br") on thecontext object and append the result as the last [=tree/child=] ofstart block.Tosplit the parent of a listnode list of consecutive [=tree/sibling=]nodes:
This algorithm breaks up the parent ofnode list. If they're the only children of their parent, the parent is removed entirely. If there are preceding or following siblings, the original parent is left intact as the parent of those siblings. If there are both preceding and following siblings, the original parent is left as the parent of the following siblings and a clone is used for the parent of the preceding siblings.
We make sure not to disrupt the appearance any more than necessary. Obviously margins or such on the parent will be lost, but the children will not wind up on the same line as anything they weren't already on the same line as. E.g., if we split the parent of "bar" infoo<p>bar</p>, we getfoo<br>bar, notfoobar. (This is amazingly complicated and error-prone.) We don't preserve inline styles: callers that want to do that should callrecord the values andrestore the values themselves.
All this is useful in a lot of situations, like for outdenting. For inline formatting commands, we almost always rely onpushing down values instead, since that often leads to tidier markup.
TODO: We insert things after the parent. This is bad, because it will cause them to become part of any ranges that immediately follow. For instance, if we're hitting "bar" in
<div><p>foo<p>bar</div>{<p>baz}it becomes
<div><p>foo</div>{<p>bar<p>baz}instead of
<div><p>foo</div><p>bar{<p>baz}because of how range mutation rules work. This doesn't happen if we insert before. This may or may not be important enough to bother working around.
If the first [=tree/child=] oforiginal parent is not innode list, but its last [=tree/child=] is:
createElement("br") on thecontext object and insert the result immediately after the last member ofnode list.cloneNode(false) onoriginal parent. id attribute, unset it.Notice that a boundary point that was immediately before the element will now be immediately before its children, just because of the regular range mutation rules, without needing to worry about preserving ranges. Likewise for boundary points immediately after the element, if we wind up removing the element in the final step. Preserving ranges is only necessary for the sake of boundary points in the element or its descendants.
For eachnode innode list, insertnode into the [=tree/parent=] oforiginal parent immediately beforeoriginal parent,preserving ranges.
createElement("br") on thecontext object and insert the result immediately before the first member ofnode list.createElement("br") on thecontext object and insert the result immediately after the last member ofnode list.The parent might be null if it's a br that we removed in the last step, in which case this step isn't necessary.
Ifnode list's last member's {{Node/nextSibling}} is null, but its [=tree/parent=] is not null,remove extraneous line breaks at the end ofnode list's last member's [=tree/parent=].
To remove anodenode whilepreserving its descendants,split the parent ofnode's [=tree/children=] if it has any. If it has no [=tree/children=], instead remove it from its [=tree/parent=].
Whitespace in HTML normally collapses. However, if the user hits the space bar twice in an HTML editor, they expect to see two spaces, not one. Even if they hit the space bar once at the beginning or end of a line, it would collapse without special handling. The only good solution here is for the author to set white-space: pre-wrap on the editable area, and on everywhere the content is reproduced. But if they don't, we have to painfully hack around the problem.
This is a basically intractable problem because of the unfortunate confluence of three factors. One, our characters are Unicode, and Unicode doesn't know about whitespace collapsing, so it provides no special characters to control it. Two, HTML itself provides no features that control whitespace collapsing without undesired side effects (like inhibiting line breaks or not being allowed inside<p>). Three, we need to support user agents that don't reliably support CSS, since that includes many popular mail clients.
The upshot is we have no good way to control whitespace collapse, so we rely on the least bad way available: . This doesn't collapse with adjacent whitespace in browsers, which is good. But it also doesn't allow a line break opportunity, which is bad. In any run of whitespace that we don't want to collapse, any two regular spaces must be separated by an so they don't collapse together, but we need to carefully limit runs of consecutive s to minimize the damage to line-breaking behavior.
The result is an elaborate and meticulously-crafted hodgepodge of bad compromises that frankly isn't worth the effort to explain here. The saving grace is that it all gets disabled if white-space is set to pre-wrap as it should be, so authors can opt out of the insanity. Interested readers will find detailed rationale for the exact sequences required in the comments.
See long comment beforeinsertText.
Thecanonical space sequence of lengthn, with boolean flagsnon-breaking start andnon-breaking end, is returned by the following algorithm:
Tocanonicalize whitespace at (node,offset), given an optional boolean argumentfix collapsed space that defaults to true:
First we go to the beginning of the current whitespace run.
Repeat the following steps:
TODO: Following a line break is unlikely to be the right criterion.
Otherwise, ifstart offset is zero andstart node does notfollow a line break andstart node's [=tree/parent=] isin the same editing host, setstart offset tostart node's [=tree/index=], then setstart node to its [=tree/parent=].
Now we collapse any consecutive spaces, iffix collapsed space is true.
Letend node equalstart node andend offset equalstart offset.
This tries to delete spaces at the beginning of a line (bug 14119).
Letcollapse spaces be true ifstart offset is zero andstart nodefollows a line break, otherwise false.
TODO: Preceding a line break is unlikely to be the right criterion.
Otherwise, ifend offset isend node's [=Node/length=] andend node does notprecede a line break andend node's [=tree/parent=] isin the same editing host, setend offset to one plusend node's [=tree/index=], then setend node to its [=tree/parent=].
deleteData(end offset, 1) onend node, then continue this loop from the beginning.We've already stripped leading whitespace, and collapsed consecutive spaces. Now we try to strip any collapsed trailing whitespace (bug 14119 again).
Iffix collapsed space is true, then while (start node,start offset) is [=boundary point/before=] (end node,end offset):
deleteData(end offset, 1) onend node.Finally we replace with the canonical sequence.
Letreplacement whitespace be thecanonical space sequence of lengthlength.non-breaking start is true ifstart offset is zero andstart nodefollows a line break, and false otherwise.non-breaking end is true ifend offset isend node's {{CharacterData/length}} andend nodeprecedes a line break, and false otherwise.
We need to insert then delete, so that we don't change range boundary points. TODO: switch to using "replace data" now that DOM Core has defined that.
Call insertData(start offset,element) onstart node.
deleteData(start offset + 1, 1) onstart node. There are two basically different types of indent/outdent: lists, and everything else. For lists we'll wrap the item in a nested list to indent, andsplit its parent to outdent. For everything else we'll wrap in a<blockquote> to indent, and try breaking it out of an ancestorindentation element to outdent.
Indenting winds up being pretty simple: just add an appropriate wrapper. There's not really anything to think about here except which wrapper we want (<ol> or<ul> or<blockquote>), and establishing that is not rocket science.
Outdenting is considerably more complicated. The basic idea we follow is to first find the nearest editable ancestor that's a list orindentation element. If we succeed, and the node we're trying to outdent is the only descendant of the ancestor, of course we can just remove the ancestor and that's that. Otherwise, what we do is remove the ancestor and then indent all its other descendants, much likepushing down values.
But of course, there are complications. We don't always actually want to remove theclosest ancestor that's causing indentation. For one thing, we prefer ancestors that we can remove completely, i.e.,simple indentation elements. When outdenting<blockquote><blockquote >foo</blockquote></blockquote>, removing the inner tag would result in<blockquote><div >foo</div></blockquote>, since we don't want to lose the id. Thus we prefer to remove the outer tag and wind up with<blockquote >foo</blockquote>.
Also, if the node we're outdenting is itself a list, we prefer to remove an ancestorindentation element rather than the list. Otherwise, if the user selected some text, indented it, then added a list, there would be no way to remove the indentation without removing the list first. This way, the user could remove the list with the appropriate list-toggling command or remove the indentation with the outdent command.
We have to handle entire lists of siblings at once, or else we'd wind up doing something like
<ol> {<li>foo</li> <ol><li>bar</li></ol>}</ol>-><ol><ol> <li>foo</li> <li>bar</li></ol></ol>-><ol><ol><ol> <li>foo</li> <li>bar</li></ol></ol></ol>since by the time we got to doing the <ol> that originally contained "bar", we won't remember that we aren't supposed to indent "foo" a second time.
Toindent a listnode list of consecutive [=tree/sibling=]nodes:
This matches IE9, Firefox 4.0, and Chrome 12 dev. If there's a preceding <li>, Opera 11.10 instead adds the new parent to the end of that <li>, so it's not the child of another list, which is invalid. But the other browsers' way of doing things makes things simpler. E.g., if we want to indent an <li> and it has <ol>/<ul> children, we have to distinguish between the case where we want to indent the whole <li> or only the first part. It also allows things like
<ol><li> foo <ol><li>bar</li></ol> baz</li></ol>
in which case it's unclear what we should do if the user selects "foo" and indents. I've fileda bug on HTML5.
Wrapnode list, withsibling criteria returning true for anHTML element with [=Element/local name=]tag and false otherwise, andnew parent instructions returning the result of callingcreateElement(tag) on the {{Node/ ownerDocument}} offirst node.
Firefox 4.0 respects the CSS styling flag for indent, but Chrome 12 dev does not. I always produce blockquotes, even if CSS styling is on, for two reasons. One, IE9 handles inline margin attributes badly: when outdenting, it propagates the margin to the parent, which doesn't actually remove it. Two, in CSS mode I'd want to use <div> to match non-CSS mode, but authors are very likely to want to remove the top/bottom margin, which they can't do if it's not a special tag. Authors who really want divs for indentation could always convert the blockquotes to divs themselves. But if people really want it, I could respect CSS styling mode here too.
The top/bottom margins might be undesirable here, but no more so than for <ol>/<ul>/<p>/etc. Here as there, authors can remove them with CSS if they want.
blockquote indents on both sides, so we don't have to worry about directionality. In theory it would be better if we indented only on the start side, but that requires care to get right in mixed-direction cases. Even once browsers start to support margin-start and so on, we can't use them because a) we have to work okay in legacy browsers and b) it doesn't help if a descendant block has different direction (so should be indented the other way). So let's not worry about it: most browsers don't, and the ones that do get it wrong. Just indent on both sides.
Wrapnode list, withsibling criteria returning true for asimple indentation element and false otherwise, andnew parent instructions returning the result of callingcreateElement("blockquote") on the {{Node/ownerDocument}} offirst node. Letnew parent be the result.
Things that are produced for indentation that we need to consider removing:
For discussion on the list-related stuff, see the comment forinsertOrderedList.
Gecko in CSS mode just adds margin properties to random elements that are lying around. We don't attempt to remove those, because 1) the amount and position of the margin can vary (it increases the margin if there's a preexisting one), so it's potentially complicated, and 2) no browser removes such margins on outdent, including Gecko, except for Gecko in CSS mode. TODO: Consider removing it anyway.
Tooutdent anodenode:
The easy case is when the whole element is indented. In this case we remove the whole thing indiscriminately. In the case of blockquotes created by IE, this might change the direction of some children, but then their direction was probably changed incorrectly in the first place, so no harm.
Ifnode is asimple indentation element, removenode,preserving its descendants. Then abort these steps.
This might be a simple indentation element that had style added to it by Firefox in CSS mode, for instance (color, font-family, etc.).
Ifnode is anindentation element:
dir attribute ofnode, if any.Approximate algorithms when an ancestor is causing the indentation appear to be:
Overall, all flawed, so I'll make up my own, patterned after pushing down styles. First we search ancestors for a simple indentation element, which we stand a chance of completely removing. Failing that, we look for an indentation element that's not simple, so we can't completely remove it.
Letcurrent ancestor benode's [=tree/parent=].
When asked to outdent a list wrapped in a simple indentation element, Chrome 12 dev removes the list instead of the simple indentation element. Opera 11.10 seems to remove both. IE9 and Firefox 4.0 remove the simple indentation element, as does the spec.
Ifnode is an [^ol^] or [^ul^] andcurrent ancestor is not aneditableindentation element:
reversed, start, and type attributes ofnode, if any are set.We can't turn it into a div if it's the child of an ol or ul, because that's not allowed: there's no way to group li's (seeHTML bug 13128).
Ifnode has attributes, and its [=tree/parent=] is not an [^ol^] or [^ul^],set the tag name ofnode to "div".
If we get to this point, we have an ancestor to split up.
Appendcurrent ancestor toancestor list.
We can't outdent it yet, because we need its children to remain intact for the loop.
Letoriginal ancestor becurrent ancestor.
This is the action fortheinsertOrderedList command andtheinsertUnorderedList command, which behave identically except for which list type they target. It does several things that vary contextually.
If everything in the selection is contained in the target list type already, this more or less just outdents everything one step. This is relatively simple.
Otherwise, it's slightly more complicated:
First, any lists of the opposite list type (other tag name) get converted to the target list type (tag name). They get merged into a sibling if appropriate, otherwise weset the tag name.
Then we go through all the affected nodes, handling each run of consecutive siblings separately. Any line that's not already wrapped in an<li> gets wrapped. If the parent at this point isn't a list at all, the run gets wrapped in a list. If it's the wrong type of list, wesplit the parent and rewrap it in the right type of list. That's basically it, except that we have to exercise the usual care to try merging with siblings and so forth.
Research for insertOrderedList/insertUnorderedList: tested the following command sequences in IE9, Firefox 4.0, Chrome 12 dev, Opera 11.10, OpenOffice.org 3.2.1 Ubuntu package, Microsoft Office Word 2007. The commands "ol", "ul", "indent", "outdent" correspond in browsers to "insertOrderedList", "insertUnorderedList", "indent", and "outdent"; in OO.org to "Numbering On/Off", "Bullets On/Off", "Increase Indent", "Decrease Indent"; and in Word to "Numbering", "Bullets", "Increase Indent", "Decrease Indent".
Note: OO has a bunch of extra options, like "Promote One Level", "Demote One Level", "Promote One Level With Subpoints", "Demote One Level With Subpoints", "Insert Unnumbered Entry", "Restart Numbering". The regular "Increase/Decrease Indent" commands work oddly, and I assume they're not really meant to be used inside lists. Thus I also tested with "Promote One Level" and "Demote One Level". These are denoted by OO' instead of OO.
Assume that there are style rules in effect like
ol ol { list-style-type: lower-alpha }ol ol ol { list-style-type: lower-roman }This is the default appearance in Word, and I set OO to something similar with Bullets and Numbering → Outline in the list editing toolbox. I'm ignoring bullet style throughout, for no particular reason.
Ignoring the conceptual model of HTML, which users won't understand, here's the conceptual model I've developed for lists: text is divided up into blocks. Each block has an indentation level and a list marker type. The list marker type can be either nothing, ordered, or unordered. A list block cannot have indentation level less than one. Any given piece of text is part of only one block. A block may be visually non-contiguous, such as if a single list block is interrupted by a further-indented block.
To find the right number (or letter) for an ordered-list block, look at the immediately preceding block, but skip over any blocks of higher indentation level. If there is no immediately preceding block, or it's not an ordered-list block, or it has a lower indentation level, the number is 1 (or a, i, etc.). Otherwise, it's the number of the preceding block plus one.
ol/ul commands change the selected block to that list marker type, or remove the list marker type if it's already the chosen type. If the block has indentation level zero, it increases to one.
indent/outdent commands change the selected block's indentation level. If a list block's indentation level is reduced to zero, it's converted to a regular block.
What this means from an HTML perspective, roughly:
Sheesh, lists are complicated.
Totoggle lists, given a stringtag name (either "ol" or "ul"):
TODO: This overnormalizes, but it seems like the simplest solution for now.
For eachitem initems,normalize sublists ofitem.
Convert it to the right name. If possible, we want to merge with a neighboring list of the correct type. Failing that, we set the tag name.
Iflist's {{Node/ previousSibling}} or {{Node/nextSibling}} is aneditableHTML element with [=Element/local name=]tag name:
We exclude indentation elements so that selecting some random text and doing indent followed by insertOrderedList will have the same result as the reverse. Specifically,
<blockquote>[foo]</blockquote> -><blockquote><ol><li>[foo]</li></ol></blockquote>
per spec and Firefox 4.0 and (more or less) Chrome 12 dev. Opera 11.10 instead does <ol><li>foo</li></ol>, so the indentation vanishes. IE9 does <ol><ol><li>foo</li></ol></ol>, but that doesn't make semantic sense and is different from how it would work if you reversed the commands. OpenOffice.org 3.2.1 (Ubuntu) and Word 2007 both agree with the spec in this case.
For eachnodenodecontained innew range, ifnode iseditable; the last member ofnode list (if any) is not an [=tree/ancestor=] ofnode;node is not anindentation element; and eithernode is an [^ol^] or [^ul^], or its [=tree/parent=] is an [^ol^] or [^ul^], or it is anallowed child of "li"; then appendnode tonode list.
We don't want to touch these. E.g., assuming tag name is "ol",
[foo<ol><li>bar</ol>baz]-> <ol><li>[foo<li>bar<li>baz]</ol>not <ol><li>[foo</li><ol><li>bar</ol><li>baz]</ol>.
But
<ul><li>foo<li>[bar</li><ol><li>baz</ol><li>quz]</ul>-> <ul><li>foo</ul><ol><li>[bar</li><ol><li>baz</ol><li>quz]</ol>not <ul><li>foo</ul><ol><li>[bar</ol><ul><ol><li>baz</ol></ul><ol><li>quz]</ol>
Ifmode is "enable", remove fromnode list any [^ol^] or [^ul^] whose [=tree/parent=] is not also an [^ol^] or [^ul^].
Accumulate consecutive sibling nodes in sublist, first converting them all to li's (except if they're already lists).
While eithersublist is empty, ornode list is not empty and its first member is the {{Node/nextSibling}} ofsublist's last member:
Thus <p>foo</p> becomes <ol><li>foo</ol> instead of <ol><li><p>foo</ol>, and likewise for div, but other things will be put inside the <li>.
Ifnode list's first member is a [^p^] or [^div^],set the tag name ofnode list's first member to "li", and append the result tosublist. Remove the first member fromnode list.
createElement("li") on thecontext object. Append the result tosublist.In this case it's already wrapped properly, nothing more to do.
Ifsublist's first member's [=tree/parent=] is anHTML element with [=Element/local name=]tag name, or if every member ofsublist is an [^ol^] or [^ul^], continue this loop from the beginning.
createElement(tag name) on thecontext object.Special case: something like
<ol><li>foo</ol><blockquote>[bar]</blockquote>
becomes
<ol><li>foo</li><ol><li>[bar]</ol></ol>
instead of
<ol><li>foo</ol><blockquote><ol><li>[bar]</ol></blockquote>.
We handle the special case innew parent instructions instead of outside because we'd prefer to wind up in a sibling if there is one. We handle only previousSibling, not nextSibling, because we really mean for this to cover something like
[foo<blockquote>bar]</blockquote>
which we'll handle node-by-node. TODO: Maybe we should do this differently, like just special-case simple indentation elements in an earlier part of the algorithm? This way's a bit weird.
Ifsublist's first member's [=tree/parent=] is not aneditablesimple indentation element, orsublist's first member's [=tree/parent=]'s {{Node/ previousSibling}} is not aneditableHTML element with [=Element/local name=]tag name, callcreateElement(tag name) on thecontext object and return the result.
createElement(tag name) on thecontext object, and append the result as the last [=tree/child=] oflist. This is theaction for the fourjustify* commands. It's pretty straightforward, with no notable gotchas or special cases. It works more or less like a stripped-down version ofset the selection's value, except it gets to be much simpler because it's much less general. (It's not similar enough to just invoke that algorithm: too many things differ between block and inline elements.)
There are two basic ways this works in browsers: using the align attribute, and using CSS text-align. IE9 and Opera 11.11 use only the align attribute, Chrome 13 dev uses only text-align, and Firefox 5.0a2 varies based on styleWithCSS. The two ways produce entirely different results, which is a real problem, so I don't think Firefox's approach is tenable. text-align is more valid, and for typical contenteditable cases it works the same. But for cases where you have fixed-width blocks, like tables or just divs with a width set, it behaves differently, and in those cases the align attribute is more useful.
TODO: text-align doesn't behave as expected if there are descendant blocks with non-100% width, like tables. The align attribute behaves a lot more nicely in such cases, but it's not valid. Not clear what to do. For now I've stuck with text-align, just because the cases where it misbehaves can't be created by any sequence of stock execCommand()s that I know of, but this needs more careful consideration. Gecko in CSS mode seems to special-case tables, adding auto margins to the table element to get it to align correctly.
TODO: We could do something along the lines of pushing down values here, although no browser does. In fact, it's very likely this can be rewritten in terms of the inline formatting command primitives, but it's not clear if it would be worth the added complexity.
Tojustify the selection to a stringalignment (either "center", "justify", "left", or "right"):
No browser actually removes center, but it makes sense to do so.
Letelement list be a list of alleditable {{Element}}scontained innew range that either has an attribute in theHTML namespace whose [=Attr/local name=] is "align", or has a style attribute that sets "text-align", or is a center.
style attribute. center with no attributes, remove it,preserving its descendants. center with one or more attributes,set the tag name ofelement to "div".This could theoretically be necessary, like if it converted "<div align=right>foo</div>bar" to "foo<br>bar". Now we need to select "foo<br>", nor just "foo".
Block-extend theactive range, and letnew range be the result.
Of tested browsers, only Chrome 13 dev seems to not apply the alignment to nodes that are already aligned. Even then, it does apply it if the alignment is just inherited from the root.
For eachnodenodecontained innew range, appendnode tonode list if the last member ofnode list (if any) is not an [=tree/ancestor=] ofnode;node iseditable;node is anallowed child of "div"; andnode'salignment value is notalignment.
Wrapsublist.Sibling criteria returns true for any [^div^] that has one or both of the following two attributes and no other attributes, and false otherwise:
align attribute whose [=Attr/value=] is anASCII case-insensitive match foralignment. style attribute which sets exactly one CSS property (including unrecognized or invalid attributes), which is "text-align", which is set toalignment.As with inline formatting, I only ever create new elements, and don't ever modify existing ones. This doesn't match how any browser behaves in this case, but for inline formatting it matches everyone but Gecko's CSS mode, so I'm just being consistent.
New parent instructions are to callcreateElement("div") on thecontext object, then set its CSS property "text-align" toalignment and return the result.
When the user inserts whitespace immediately following something that looks like a URL or e-mail address, we automatically runthecreateLink command on it.
Anautolinkable URL is a string of the following form:
IE9 and LibreOffice 3.3.4 both have a whitelist of URL schemes. That would be complicated and involve political decisions, so instead, we'll just accept anything that looks like a hierarchical URL scheme. Google Docs is similar (as of November 9, 2011), but it's too lax, and allows characters in the scheme that can't be in a scheme. For non-hierarchical schemes, we just whitelist mailto:, since it's the only common one that makes sense to autolink.
Either a string matching thescheme pattern fromRFC 3986 section 3.1 followed by the literal string://, or the literal stringmailto:; followed by
We don't try to enforce that the URL is anything resembling valid per spec. Too complicated for not enough gain.
Zero or more characters other thanspace characters; followed by
If the user types a URL followed by some punctuation, we still want to autolink, but we don't want to include the punctuation if it's probably not meant as part of the URL.
IE9 excludes!#&()*+,-.:;<=?@[]^_`{|}~ as trailing characters from both URLs and e-mails. A trailing" or> will prevent autolinking, and a trailing$%'/\ is included in the link.
LibreOffice 3.3.4 excludes trailing!”#'()*+,.:;<=>?[\]^_`{|}~, and prevents autolinking on$%&-@. It includes a trailing/ in URLs, but it inhibits linking for e-mails.
Google Docs (as of November 9, 2011) is complicated. Trailing”’,-. always prevents autolinking of a URL, and trailing#%/?_ is always included in a URL. Trailing!&=$()*+:;<>@[\]^`{|}~ prevent autolinking if there's no? before them, but are included in the URL if there is a?. For e-mails,_ is included, and everything else prevents autolinking.
None of these behaviors makes maximal sense. We should exclude characters if they're more likely as delimiters than actual trailing characters; include them if they're more likely as actual trailing characters; and prevent autolinking if their presence suggests that we're not actually looking at a link or e-mail address. The lists I made up for URLs are: exclude trailing!"'(),-.:;<>[]`{}, include anything else. For e-mail, exclude anything at all.
A character that is not one of the ASCII characters!"'(),-.:;<>[]`{}.
Toautolink (node,end offset):
delete commandThis is the same as hitting backspace (seeAdditional requirements). The easy part is if the selection isn't collapsed: justdelete the selection. But it turns out rich-text editors have a lot of special behaviors for hitting backspace with a collapsed selection. Most obviously, if there's a text node right before the cursor (maybe wrapped in some inline elements), we delete its last character. But some of the special cases we run into are:
<a> gets removed if you backspace while the cursor is right after it, so the link disappears.<br> or<hr> or<img> gets deleted.<li> or<dt> or<dd> that's at the beginning of a list, outdents the current block (rather than merging with the previous block).For all the deletions here, Firefox 7.0a2 will remove wrapper elements like <b> only if they're selected, like {<b>foo</b>}. IE9, Chrome 14 dev, and Opera 11.50 will all remove them even if only their contents are selected, like <b>[foo]</b>. Gecko's behavior in the latter case leaves things like <b>{}</b> in the DOM, which is unhelpful, so I don't.
Needed so that if there are multiple consecutive spaces we backspace over all at once.
Canonicalize whitespace at theactive range's [=range/start=].
First go up as high as possible within the current block, then drill down to the lowest possible level, in the hopes that we'll wind up at the end of a text node, or maybe in a br or hr.
Repeat the following steps:
If there's an invisible node somewhere, Firefox 5.0a2 removes that node and then stops, so each backspace removes one invisible node. All others remove the invisible node and then continue on looking for something visible to remove. The spec follows the latter behavior, since it makes more sense to the user. Of course, the definition of "invisible node" is not necessarily anything like the spec's.
Ifoffset is zero andnode's {{Node/ previousSibling}} is aneditableinvisiblenode, removenode's {{Node/ previousSibling}} from its [=tree/parent=].
When backspacing a link, Firefox 7.0a2, Chrome 14 dev, Opera 11.50, and OpenOffice.org 3.2.1 Ubuntu have no special behavior. IE9 and Word 2007 remove the link instead of deleting its last character. The latter behavior seems more useful and intuitive.
Otherwise, ifnode has a [=tree/child=] with [=tree/index=]offset − 1 and that [=tree/child=] is aneditable [^a^], remove that [=tree/child=] fromnode,preserving its descendants. Then return true.
At this point, node cannot be an invisible node. There are three cases:
Unlike forwardDelete, there's no special case for diacritics. This means backspacing will just delete the last combining diacritic typed, or the whole character if it's precomposed. This matches everything I tested (IE9, Firefox 7.0a2, Chrome 14 dev, etc.).
Ifnode is a {{Text}} node andoffset is not zero, or ifnode is ablock node that has a [=tree/child=] with [=tree/index=]offset − 1 and that [=tree/child=] is a [^br^] or [^hr^] or [^img^]:
collapse(node,offset) on thecontext object'sselection.extend(node,offset − 1) on thecontext object'sselection.At the time of this writing, this should be impossible. Just being safe.
Ifnode is aninline node, return true.
If we're at the beginning of a list, we want to outdent the first list item. This doesn't actually match anyone or anything. Word 2007 and OpenOffice.org 3.2.1 Ubuntu just remove the list marker, which is weird and doesn't map well to HTML. Browsers tend to just merge with the preceding block, which isn't expected.
Ifnode is an [^li^] or [^dt^] or [^dd^] and is the first [=tree/child=] of its [=tree/parent=], andoffset is zero:
Annoying hack to prevent the dl from being re-added when fixing disallowed ancestors. In most cases we want a wrapper dl added, but in two cases (delete and insertParagraph) we're actually trying to outdent the list item. TODO: there might be a better way to do this.
Ifnode is a [^dd^] or [^dt^], and it is not anallowed child of any of its [=tree/ancestors=]in the same editing host,set the tag name ofnode to thedefault single-line container name and letnode be the result.
By this point, we're almost certainly going to merge something, and the only question is what.
Letstart node equalnode and letstart offset equaloffset.
At the beginning of an indented block, outdent it, similar to a list item. Browsers don't do this, word processors do. Note: this copy-pastes from the outdent command action.
Ifoffset is zero, andnode has aneditable [=tree/inclusive ancestor=]in the same editing host that's anindentation element:
This is to avoid stripping a line break from
foo<br><br><table><tr><td>[]bar</table>
and similarly for <hr>. We should just do nothing here.
If the [=tree/child=] ofstart node with [=tree/index=]start offset is a [^table^], return true.
If you try backspacing into a table, select it. This doesn't match any browser; it matches the recommendation of the "behavior when typing in contentEditable elements" document. The idea is that then you can delete it with a second backspace.
Ifstart node has a [=tree/child=] with [=tree/index=]start offset − 1, and that [=tree/child=] is a [^table^]:
collapse(start node,start offset − 1) on thecontext object'sselection.extend(start node,start offset) on thecontext object'sselection.Special case:
<p>foo</p><br><p>[]bar</p>-> <p>foo</p><p>[]bar</p>
and likewise for <hr>. But with <img> we merge like in other cases:
<p>foo</p><img><p>[]bar</p>-> <p>foo</p><img>[]bar.
Browsers don't do this consistently. Firefox 5.0a2 doesn't seem to do it at all.
Ifoffset is zero; and either the [=tree/child=] ofstart node with [=tree/index=]start offset minus one is an [^hr^], or the [=tree/child=] is a [^br^] whose {{Node/previousSibling}} is either a [^br^] or not aninline node:
collapse(start node,start offset − 1) on thecontext object'sselection.extend(start node,start offset) on thecontext object'sselection.collapse(node,offset) on theselection.If you try backspacing out of a list item, merge it with the previous item, but add a line break. Then you have to backspace again if you really want them to be on the same line. This matches Word 2007 and OpenOffice.org 3.2.1 Ubuntu, and also matches "behavior when typing in contentEditable elements", but does not match any browser.
Note that this behavior is quite different from what happens if you actually select the linebreak in between the two lines. In that case, the blocks are merged as normal.
Also note that hitting backspace twice will merge with the previous item. This matches OO.org, but Word will outdent the item on subsequent backspaces. Word's behavior doesn't fit well with the way lists work in HTML, and we probably don't want it.
If the [=tree/child=] ofstart node with [=tree/index=]start offset is an [^li^] or [^dt^] or [^dd^], and that [=tree/child=]'s {{Node/firstChild}} is aninline node, andstart offset is not zero:
If the last child is already a br, we only need to append one extra br. Otherwise we need to append two, since the first will do nothing.
Ifprevious item's {{Node/lastChild}} is aninline node other than a [^br^], callcreateElement("br") on thecontext object and append the result as the last [=tree/child=] ofprevious item.
createElement("br") on thecontext object and append the result as the last [=tree/child=] ofprevious item. When merging adjacent list items, make sure we only merge the items themselves, not any block children. We want<li><p>foo<li><p>[]bar to become<li><p>foo<p>[]bar, not<li><p>foo<br>[]bar or<li><p>foo[]bar. To do the deletion, we need to wipe out the current selection, so we save it as a range. Saving it as a node/offset pair isn't enough, because it might be invalid after we do the deletion. A range will update according to the range mutation rules.
Ifstart node's [=tree/child=] with [=tree/index=]start offset is an [^li^] or [^dt^] or [^dd^], and that [=tree/child=]'s {{Node/previousSibling}} is also an [^li^] or [^dt^] or [^dd^]:
cloneRange() on theactive range, and letoriginal range be the result.collapse(start node,start offset) on thecontext object'sselection.extend(node, 0) on thecontext object'sselection.removeAllRanges() on thecontext object'sselection.addRange(original range) on thecontext object'sselection.General block-merging case.
Whilestart node has a [=tree/child=] with [=tree/index=]start offset minus one:
collapse(start node,start offset) on thecontext object'sselection.extend(node,offset) on thecontext object'sselection.formatBlock commandThis command lets you change what block element particular lines are wrapped in. It will convert an existing wrapper if one exists, and otherwise will create a new one.
Aformattable block name is "address", "dd", "div", "dt", "h1", "h2", "h3", "h4", "h5", "h6", "p", or "pre".
Tested browser versions: IE9, Firefox 4.0, Chrome 13 dev, Opera 11.10.
Firefox and Chrome will replace a <blockquote> by a <p> or other given tag. IE and Opera will nest the <p> inside instead. The latter makes more sense, given that a) we don't support formatBlock with <blockquote> and b) <blockquote>s are logically different, since they can contain many lines.
Firefox will not convert other tags like <p> to <div>, it will only wrap unwrapped lines in a <div>. Firefox also won't replace <div> by things like <p>, it will nest the <p> inside. The spec follows other browsers.
If you try to convert a <dt> to a <div> or <p> or such, Firefox breaks out of the <dl> entirely, leaving ...<dt><br></dt></dl>. Chrome will convert a <dt> or <dd> to the given element, leaving a <div> or <p> or such as the child of a <dl>. I follow IE/Opera, which only affect the contents of <dt>/<dd> (Firefox behaves this way for <dd> as well, just not <dt>). This means you can get invalid DOMs like <dt><p>foo<p></dt>, but they can be serialized as text/html, so I'm not too fussy.
When it comes to <li>, IE/Opera behave like with <dt>/<dd>, which is how I behave too. Firefox apparently refuses to do anything. Chrome tries to wrap the parent list element, breaking it up if only some of the children are selected; this produces unserializable DOMs if you're wrapping with <p>.
When you're converting multiple blocks at once, Chrome replaces them all by one block with <br> stuck in, like <p>foo</p><p>bar</p> -> <div>foo<br>bar</div>. It wipes out intervening block containers too in some cases. This might make sense for <address>/<h*>/<pre>, but other browsers don't do it.
IE9 requires the brackets. If they're not provided, it does nothing.
Ifvalue begins with a "<" character and ends with a ">" character, remove the first and last characters from it.
Opera 11.10 throws NOT_SUPPORTED_ERR for bad elements, all other tested browsers ignore the input. Testing in IE9, Firefox 4.0, Chrome 13 dev, and Opera 11.10, supported elements seem to be:
HTML5 as of May 2011 supports: address, article, aside, blockquote, div, footer, h*, header, hgroup, nav, p, pre, section, which exactly matches Chrome except minus dd/dt/dl.
See mailing list discussion on the subject.
Ifvalue is not aformattable block name, return false.
This tries to avoid misnesting if only some lines of an element are selected, so <h1>[foo]<br>bar</h1> becomes <p>[foo]</p><h1>bar</h1> instead of <h1><p>[foo]</p><br>bar</h1> or such. It tries to heuristically distinguish between divs used as line-breakers and divs used as actual wrappers by checking if they have prohibited paragraph children as descendants. It works for address too, in case there are paragraphs nested inside. Thus <address>[foo]<br>bar</address> becomes <p>[foo]</p><address>bar</address>, but <address>[foo]<p>bar</p></address> becomes <address><p>[foo]</p><p>bar</p></address>. Likewise, we don't break things out of lists or tables or such if they happen to be nested in a <div>.
For eachnode innode list, whilenode is the [=tree/descendant=] of aneditableHTML elementin the same editing host, whose [=Element/local name=] is aformattable block name, and which is not the [=tree/ancestor=] of aprohibited paragraph child,split the parent of the one-node list consisting ofnode.
We have two different behaviors, one for div and p and one for everything else. The basic difference is that for div and p, we assume that it should be one line per element, while for other elements, we put in multiple lines separated by <br>. So if you do formatBlock to p on
<div>foo</div><div>bar</div>
or
foo<br>bar
you get
<p>foo</p><p>bar</p>
but formatBlock to h1 will get you
<h1>foo<br>bar</h1>.
IE9 will just change the elements as they are, so it gives <p>foo</p><p>bar</p> and <h1>foo</h1><h1>bar</h1> for <div>foo</div><div>bar</div>, but <p>foo<br>bar</p> and <h1>foo<br>bar</h1> for foo<br>bar. This is unreasonable, because the two possible inputs here look identical to the user and might have been produced by identical user input.
Firefox 5.0a2 will give results like <p>foo</p><p>bar</p> or <h1>foo</h1><h1>bar</h1> no matter what (modulo oddities in its handling of divs). Opera 11.10 is similar, except it leaves a trailing <br> in the first element.
Chrome 13 dev will give results like <p>foo<br>bar</p> or <h1>foo<br>bar</h1> no matter what.
The specced behavior is a compromise between the existing behaviors, predicated on the fact that <h1>foo</h1><h1>bar</h1> almost never makes sense, and <p>foo<br>bar</p> isn't usually what's wanted either.
Whilenode list is not empty:
If you try to format a single-line container with no children, IE10PP2 inserts an nbsp before formatting. (It uses nbsp instead of <br> to make blocks not collapse, so the equivalent for us would be to insert a <br>.) Firefox 7.0a2 and Opera 11.50 make the element disappear. Chrome 14 dev leaves it alone and doesn't format it. I follow Firefox/Opera just because it's the simplest given how I happen to have written the spec, and it's a corner case, so exact behavior isn't important.
For blocks that contain only a collapsed whitespace node, IE10PP2 and Firefox 7.0a2 convert them like normal. Chrome 14 dev and Opera 11.50 leave it alone and don't format it. I go with the majority, which is again simpler to spec.
Letsublist be the [=tree/children=] of the first member ofnode list.
createElement(value) on thecontext object. Thenfix disallowed ancestors of the result.Firefox 6.0a2 throws, Chrome 14 dev always returns false, Opera 11.11 doesn't support indeterm to start with, IE9 was uncooperative in testing so I'm not sure what it does. I'm speccing it just because it makes sense.
IE9 returns human-readable strings like "Normal" (p/div/etc.), "Formatted" (pre), "Heading 1" (h1), etc. Firefox 6.0a2 and Chrome 14 dev both return the appropriate tag name in lowercase, or the empty string if there is no appropriate tag. Opera 11.11 behaves the same, but with uppercase.
IE9 looks like it recognizes address, h*, pre, dd, dt, ol, ul, and dir, with everything else registering as "Normal". Firefox 6.0a2 recognizes only the arguments it accepts for formatBlock, namely address, h*, p, and pre. Chrome 14 dev recognizes address, div, h*, dd, dl, dt, p, pre plus lots of random other stuff like blockquote and section. I'll go with everything that execCommand("formatblock") accepts as an argument, which at the time of this writing means what Firefox supports plus div.
Opera 11.11 doesn't require it be editable, so it will return "DIV" instead of "" for <div contenteditable>foo</div>.
Whilenode's [=tree/parent=] iseditable andin the same editing host asnode, andnode is not anHTML element whose [=Element/local name=] is aformattable block name, setnode to its [=tree/parent=].
Chrome 14 dev will report "div" for <div><ol><li>foo</ol></div> or such. Opera 11.11 reports "". IE and Firefox didn't cooperate with testing. Opera makes more sense, and matches the fact that formatBlock now doesn't recognize such a div as a formatBlock candidate, so Opera it is.
We don't really need to specify "editable" here, since it has to be editable if we got to this point.
Ifnode is aneditableHTML element whose [=Element/local name=] is aformattable block name, andnode is not the [=tree/ancestor=] of aprohibited paragraph child, returnnode's [=Element/local name=],converted to ASCII lowercase.
forwardDelete command This is the same as hitting the delete key (seeAdditional requirements). It behaves much the same asthedelete command, except of course backwards. Also, some of the special cases for backspacing don't apply, as noted in the comments. The one special case you get when deleting forward but not backward is that if the cursor is before a grapheme cluster that consists of multiple characters, like a base character with combining diacritics, we delete the diacritics too. (Backspacing just deletes the last diacritic, so you have to backspace several times to remove the whole cluster.)
Copy-pasted from delete, see there for comments.
No special link behavior for forwardDelete here, unlike delete.
Otherwise, ifnode has a [=tree/child=] with [=tree/index=]offset and that [=tree/child=] is neither ablock node nor a [^br^] nor an [^img^] nor acollapsed block prop, setnode to that [=tree/child=], then setoffset to zero.
Firefox 7.0a2, Chrome 14 dev, Word 2007, and OpenOffice.org 3.2.1 Ubuntu act as the spec says, getting rid of all diacritics on forward delete. IE9 and Opera 11.50 have no special case and just delete the next character. I go with Firefox/Chrome/Word/OO.
However, when I actually type in the text box as opposed to running semi-automated tests, IE9 has magical behavior: it replaces the base character with something that looks like ◌ U+25CC DOTTED CIRCLE. Further strikes of the delete key remove the diacritics, and the circle vanishes along with the last of them. I wasn't able to get it to actually replace the base character, so I'm not sure what the point is. The circle doesn't seem to appear in the DOM, and apparently it disappears in some circumstances. This might be worth standardizing somehow, I don't know.
TODO: The way we remove diacritics is probably not right. We probably want to normalize to grapheme cluster boundaries, using UAX#29 or something. We also need to handle non-BMP stuff. The idea is that if the cursor is before a character that precedes a combining mark, you need to delete the combining mark too.
Whileend offset is notnode's [=Node/length=] and theend offsetthcode unit ofnode's {{CharacterData/ data}} has general category M when interpreted as a Unicode code point, add one toend offset.
collapse(node,offset) on thecontext object'sselection.extend(node,end offset) on thecontext object'sselection.collapse(node,offset) on thecontext object'sselection.extend(node,offset + 1) on thecontext object'sselection.No special list-item behavior for forwardDelete here, unlike delete.
Letend node equalnode and letend offset equaloffset.
No special indentation element behavior for forwardDelete here, unlike delete.
If the [=tree/child=] ofend node with [=tree/index=]end offset minus one is a [^table^], return true.
collapse(end node,end offset) on thecontext object'sselection.extend(end node,end offset + 1) on thecontext object'sselection.Note, any br will do here: a br immediately after a block is always significant.
Ifoffset is the [=Node/length=] ofnode, and the [=tree/child=] ofend node with [=tree/index=]end offset is an [^hr^] or [^br^]:
collapse(end node,end offset) on thecontext object'sselection.extend(end node,end offset + 1) on thecontext object'sselection.collapse(node,offset) on theselection.No special list-item behavior for forwardDelete here, unlike delete.
Whileend node has a [=tree/child=] with [=tree/index=]end offset:
collapse(node,offset) on thecontext object'sselection.extend(end node,end offset) on thecontext object'sselection.indent commandThis command must not beenabled if theediting host is in theplaintext-only state.
For repeated indentation, everyone except Opera that outputs <blockquote>s just puts them at the outermost possible location, which works well. Opera puts them in the innermost position, which is broken, because it will even put them inside <p> (which will not round-trip through text/html serialization).
Gecko in CSS mode messes up by adding margins even to things like <blockquote> that already have margins from CSS rules, instead of nesting a div, so it doesn't actually increase the indentation. However, if an element has an explicit left margin (assuming LTR), it will increase the margin to 80px, so it works with WebKit's blockquotes.
We have two strategies for handling directionality: always indent on both sides (Firefox non-CSS, Opera) or try to figure out heuristically which side we want (IE, Firefox CSS). The latter approach is only possible by adding extra markup and complexity, so for now we'll take the easy way out and go with just indenting on both sides.
This reasoning doesn't discuss lists. For research on lists, see the comment forinsertOrderedList. List handling is more complicated and I wound up differing from all browsers in lots of ways.
TODO: This overnormalizes, but it seems like the simplest solution for now.
For eachitem initems,normalize sublists ofitem.
Without this step, the last child of the previous sibling might be a list, which the li wouldn't get appended to.
If the firstvisible member ofnode list is an [^li^] whose [=tree/parent=] is an [^ol^] or [^ul^]:
insertHorizontalRule commandThis command must not beenabled if theediting host is in theplaintext-only state.
You'd think interop here would be simple, right? Nope: we have three different behaviors across four browsers. Opera 11.00 is the only one that acts more or less like the spec. IE9 and Chrome 12 dev treat the value as an id, which is weird and probably useless, so I don't do it. Firefox 4.0 produces <hr size=2 width=100%> instead of <hr>, which is also weird and almost definitely useless, so I don't do it. Then you have the varying behavior in splitting up parents to ensure validity . . .
collapse(start node,start offset) on thecontext object'sselection.extend(end node,end offset) on thecontext object'sselection.We don't want to call insertNode at the start or end of a text node, because that will leave an empty text node.
If theactive range's [=range/start node=] is a {{Text}} node and its [=range/start offset=] is zero, callcollapse() on thecontext object'sselection, with first argument theactive range's [=range/start node=]'s [=tree/parent=] and second argument theactive range's [=range/start node=]'s [=tree/index=].
collapse() on thecontext object'sselection, with first argument theactive range's [=range/start node=]'s [=tree/parent=], and the second argument one plus theactive range's [=range/start node=]'s [=tree/index=].createElement("hr") on thecontext object.insertNode(hr) on theactive range.IE9 and Chrome 13 dev seem to never break up any ancestors, which can lead to unserializable DOMs like <hr> inside <p>. Opera 11.11 seems to always break up parents going all the way up to the contenteditable root, even ones like <div> that can contain <hr>. Firefox 5.0a2 acts the most sensibly: it only breaks up things like <p> or <b> that shouldn't contain <hr>. The spec goes with Firefox here (although the list of what to break up isn't precisely identical).
Fix disallowed ancestors ofhr.
collapse() on thecontext object'sselection, with first argumenthr's [=tree/parent=] and the second argument equal to one plushr's [=tree/index=].insertHTML commandNot supported by IE9. Handling of disallowed children is interesting:
Setvalue to the result of invoking get trusted types compliant string withTrustedHTML,this'srelevant global object,value, "Document execCommand", and "script".
Chrome 14 dev and Opera 11.11 do this even if the value is empty. Firefox 5.0a2 throws an exception.
Firefox 7.0a2 and Chrome 14 dev do strip wrappers here, so inserting HTML in the place of <b>[foo]</b> will remove the <b>. Opera 11.50 keeps the wrappers. I follow the majority and leave the "strip wrappers" flag true.
TODO: This has some interesting consequences. For instance, table cells and similar will just vanish if they're not in an appropriate place; and inside a script or style or xmp or such, the argument will effectively be HTML-escaped before use. Some of these consequences might be undesirable.
Letfrag be the result of calling createContextualFragment(value) on theactive range.
Firefox 5.0a2 also seems to not add empty elements like <b></b>, but Chrome 13 dev and Opera 11.11 do.
Iflast child is null, return true.
This is so we don't get something like
<div>[foo]</div>-> <div>{}<br></div>-> <div><p>Some HTML{}</p><br></div>with an extra bogus line break at the end.
If theactive range's [=range/start node=] is ablock node:
We could canonicalize whitespace after inserting the fragment, but let's not. If the author wants HTML, give them HTML behavior. When asked to replace a paragraph's contents with a single space, Firefox 7.0a2 does so but inserts a <br> before it (not after); Chrome 14 dev does so and doesn't insert a <br>, so the paragraph collapses; Opera 11.50 doesn't insert the space at all, and just inserts a <br>. Correct behavior is to insert a space, then insert a <br> after it in the next step because it's an invisible node.
CallinsertNode(frag) on theactive range.
In case we remove all the contents, then remove the extra <br>, then only add a comment or something. In that case we want to re-add the extra <br>. We don't try fixing the actual inserted content: that's the author's lookout.
If theactive range's [=range/start node=] is ablock node with novisible [=tree/children=], callcreateElement("br") on thecontext object and append the result as the last child of theactive range's [=range/start node=].
Need to do this before fixing disallowed ancestors, since otherwise the last child might have been removed (e.g., it's an li).
Callcollapse() on thecontext object'sselection, withlast child's [=tree/parent=] as the first argument and one plus its [=tree/index=] as the second.
We want to fix all descendants, not just children. Consider <div><li>foo</li></div>, for example.
Fix disallowed ancestors of each member ofdescendants.
insertImage commandThis command must not beenabled if theediting host is in theplaintext-only state.
Similar logic to createLink, except even more compelling, since an HTML document linking to itself as an image is just silly. In fact, the current HTML spec instructs UAs to not even try displaying the image, and just fail immediately if the URL is empty. Firefox 4b11 silently does nothing on an empty string, but the other three browsers I tested stick in the <img> anyway.
Ifvalue is the empty string, return false.
Firefox 7.0a2 seems to strip the wrapper or not depending on the exact positioning of the selection: <b>{foo}</b> yes, <b>[foo]</b> no. Chrome 14 dev seems to strip the wrapper regardless. Opera 11.50 seems to keep the wrapper, but place the image outside it. I didn't get IE to cooperate with my tests. I chose to leave wrappers across the board because they might be meaningful: e.g., a background-color when the image is small or not fully opaque.
Delete the selection, withstrip wrappers false.
Same logic as with insertHTML.
Ifrange's [=range/start node=] is ablock node whose sole [=tree/child=] is a [^br^], and its [=range/start offset=] is 0, remove its [=range/start node=]'s [=tree/child=] from it.
createElement("img") on thecontext object.No alt text, so it's probably invalid. This matches all browsers.
RunsetAttribute("src",value) onimg.
This winds up putting it at the original start point of the active range, as currently specced. This matches IE9 and Firefox 5.0a2. Chrome 13 dev puts it at the end point, and Opera 11.11 puts it in between (where the range would collapse if you called deleteContents()).
RuninsertNode(img) onrange.
An image could be inserted inside a link, with either this command or insertHTML. On Chrome 51 dev, inserting an image (with insertImage or insertHTML) inside a link will remove the parent link. Firefox 48 allows this nesting.
getSelection() on thecontext object.collapse() onselection, with first argument equal to the [=tree/parent=] ofimg and the second argument equal to one plus the [=tree/index=] ofimg.insertLineBreak command This is the same as hitting Shift-Enter or such (seeAdditional requirements). It deletes the selection, and replaces it with a<br>. No real complications.
Only implemented in WebKit (Chrome 14 dev). Other tests are entirely manual (i.e., actually hitting Shift-Enter instead of running a command). There's a surprisingly large amount of interop.
IE9 is tripped up by <xmp>, and also often doesn't add an extra <br> when the one it just inserted is extraneous.
Firefox 6.0a2 doesn't notice if you're trying to put the <br> in a bad place, which can result in unserializable DOMs.
Chrome 14 dev inserts a literal linebreak for pre and xmp and maybe other similar elements. This doesn't seem very useful, so I don't bother.
Opera 11.11 isn't heedful of <xmp>, and treats <pre> somewhat oddly.
IE9 doesn't strip wrappers (IE10PP2 didn't work in tests). Firefox 7.0a2 strips wrappers inconsistently depending on the exact selection endpoints. Chrome 14 dev strips wrappers but recreates any styles using new wrappers. Opera 11.50 strips all wrappers.
Delete the selection, withstrip wrappers false.
script, xmp, table, . . .
If theactive range's [=range/start node=] is an {{Element}}, and "br" is not anallowed child of it, return true.
We don't want to call insertNode at the start or end of a text node, because that will leave an empty text node.
If theactive range's [=range/start node=] is a {{Text}} node and its [=range/start offset=] is zero, callcollapse() on thecontext object'sselection, with first argument equal to theactive range's [=range/start node=]'s [=tree/parent=] and second argument equal to theactive range's [=range/start node=]'s [=tree/index=].
collapse() on thecontext object'sselection, with first argument equal to theactive range's [=range/start node=]'s [=tree/parent=] and second argument equal to one plus theactive range's [=range/start node=]'s [=tree/index=].createElement("br") on thecontext object.insertNode(br) on theactive range.collapse() on thecontext object'sselection, withbr's [=tree/parent=] as the first argument and one plusbr's [=tree/index=] as the second argument.createElement("br") on thecontext object and letextra br be the result, then callinsertNode(extra br) on theactive range.insertOrderedList commandThis command must not beenabled if theediting host is in theplaintext-only state.
Action:Toggle lists withtag name "ol", then return true.
Firefox 6.0a2 sort of supports this, but it throws exceptions most of the time. It has the quirk that even if there are no ol's around, it will say it's indeterminate if there are some things that are ul's and some that are not lists at all, but this doesn't make sense and I don't duplicate it. No one else supports indeterminate for insert*List, but it makes sense if you support state.
Indeterminate: True if theselection's list state is "mixed" or "mixed ol", false otherwise.
IE9 throws exceptions in most cases, Firefox 6.0a2 in some cases as well, for no apparent reason. Ignoring those, the spec basically matches all browsers, except with a few weird random mismatches that looked like browser bugs to me.
State: True if theselection's list state is "ol", false otherwise.
insertParagraph commandThis is the same as hitting enter (seeAdditional requirements). The general rule is that we find the nearestsingle-line container ancestor, clone it and insert the clone after it, and then move all the contents after the cursor (along with the cursor itself) to the clone. But there are a few special cases:
<address>,<listing>, and<pre>, we add a<br> instead of cloning it. These are all elements that are meant to contain multiple lines, not have one line per element.<dt> or<dd>, the new element is the opposite type, so you naturally switch between entering terms and definitions.There are three major behaviors here. Firefox 5.0a2 behaves identically to execCommand("formatBlock", false, "p"), which is not really useful. IE9 actually just overwrites the selection with an empty paragraph element, which seems not very useful either. Chrome 13 dev and Opera 11.10 behave basically the same as if the user hit the Return key. This latter behavior seems much more useful, even though it's horribly misnamed, so it's what I'll spec. Comments about IE/Firefox are based on manual tests, where I hit Enter instead of running commands.
(Actually, Opera doesn't behave quite the same for insertParagraph and line breaks. But it's pretty close, and I expect the differences are bugs.)
Then, of course, we have several flavors of line-breaking behavior to choose from. Firefox prefers <br>s, unless it's in the middle of a <p> or something. Opera and IE like <p>. Chrome prefers <div>. And there are lots of subtleties besides. I go with IE/Opera-style behavior, as discussed in a whatwg thread.
Update August 2016: The consensus of current browsers is now to use div as the separator, not p, so the spec is updated accordingly.
splitText(offset) onnode.collapse(node,offset) on thecontext object'sselection.Ifcontainer is aneditablesingle-line containerin the same editing host asnode, and its [=Element/local name=] is "p" or "div":
We add the default wrapper in this case.
Ifcontainer is noteditable or notin the same editing host asnode or is not asingle-line container:
Ideally, we should normalize things so that the cursor is never in a weird place after deletion, but let's be safe and bail out if we do hit this scenario. It's not clear if we need this line in the long term, but at the time of this writing there's at least one corner case where deleting can leave the cursor inside a <tr>.
Iftag is not anallowed child of theactive range's [=range/start node=], return true.
createElement(tag) on thecontext object.insertNode(container) on theactive range.createElement("br") on thecontext object, and append the result as the last [=tree/child=] ofcontainer.collapse(container, 0) on thecontext object'sselection.TODO: It is not at all obvious that this is the correct list of nodes in all cases. It should probably work because of how the block-extend algorithm works, but further thought would be good.
While the {{Node/nextSibling}} of the last member ofnode list is not null and is anallowed child of "p", append it tonode list.
createElement(tag) on thecontext object. Setcontainer to the result.IE9 and Chrome 13 dev just break <pre> up into multiple <pre>s. Firefox 5.0a2 and Opera 11.10 insert a <br> instead, treating it differently from <p>. The latter makes more sense. What might make the most sense is to just insert an actual newline character, though, since this is a pre after all . . .
IE9 and Chrome 13 dev also break <address> up into multiple <address>es. Firefox 5.0a2 inserts <br> instead. Opera 11.10 nests <p>s inside. I don't like Opera's behavior, because it means we nest formatBlock candidates inside one another, so I'll go with Firefox.
listing and xmp work the same as pre in all browsers. For Firefox and Opera, this results in trying to put a br inside an xmp, so I go with IE/Chrome for xmp.
TODO: In cases where hitting enter in a header doesn't break out of the header, we should probably follow this code path too, instead of creating an adjoining header. No browser does this, though, so we don't.
Ifcontainer's [=Element/local name=] is "address", "listing", or "pre":
createElement("br") on thecontext object.insertNode(br) on theactive range.collapse(node,offset + 1) on thecontext object'sselection.Necessary because adding a br to the end of a block element does nothing if there wasn't one there already. A single newline immediately preceding a block boundary does nothing.
Ifbr is the last [=tree/descendant=] ofcontainer, letbr be the result of callingcreateElement("br") on thecontext object, then callinsertNode(br) on theactive range.
Including dt/dd here follows Firefox 5.0a2, as with the special dt/dd handling below.
Ifcontainer's [=Element/local name=] is "li", "dt", or "dd"; and either it has no [=tree/children=] or it has a single [=tree/child=] and that [=tree/child=] is a [^br^]:
createElement("br") on thecontext object and append the result as the last [=tree/child=] ofcontainer.Annoying hack to prevent the dl from being re-added when fixing disallowed ancestors. In most cases we want a wrapper dl added, but in two cases (delete and insertParagraph) we're actually trying to outdent the list item. TODO: there might be a better way to do this.
Ifcontainer is a [^dd^] or [^dt^], and it is not anallowed child of any of its [=tree/ancestors=]in the same editing host,set the tag name ofcontainer to thedefault single-line container name and letcontainer be the result.
We don't want the start to be just inside a node, because if it is, we'll leave behind an empty element either in the new or old container. Empty block nodes are fine, and we'll add a [^br^] later, but empty inline nodes are bad, since the user can't interact with them.
Whilenew line range's [=range/start offset=] is zero and its [=range/start node=] is not aprohibited paragraph child, set its [=range/start=] to ([=tree/parent=] of [=range/start node=], [=tree/index=] of [=range/start node=]).
IE9 makes a new header if there's a trailing <br>. Firefox 5.0a2, Chrome 13 dev, and Opera 11.10 do not, and I follow them, since it makes more sense (such a <br> is invisible).
If the [=Element/local name=] ofcontainer is "h1", "h2", "h3", "h4", "h5", or "h6", andend of line is true, letnew container name be thedefault single-line container name.
This step and the next follow Firefox 5.0a2. IE9 and Chrome 13 dev act as though these two lines were not present (they clone the existing element). Opera 11.10 nests a <p> inside. Firefox is the most useful, assuming a definition list somehow winds up inside the content (like via formatBlock).
Otherwise, if the [=Element/local name=] ofcontainer is "dt" andend of line is true, letnew container name be "dd".
createElement(new container name) on thecontext object. id attribute, unset it.TODO: This blows up any ranges (other than the selection, which we reset), and can alter non-editable nodes, and maybe other bad stuff. May or may not be the best solution. The intermediate fragment is also possibly black-box detectable by DOM mutation events, but I like to pretend those don't exist.
Letfrag be the result of callingextractContents() onnew line range.
id attribute (if any) of each {{Element}} [=tree/descendant=] offrag that is not incontained nodes.appendChild(frag) onnew container. Needed in case we have something like<ol><li><p>[]foo</ol>, which becomes<ol><li><p><li><p>foo</ol>. In this case we want to add the [^br^] to the [^p^], not the [^li^]. Likewise for<ol><li><p>foo[]</ol>.
Whilecontainer's {{Node/lastChild}} is aprohibited paragraph child, setcontainer to its {{Node/lastChild}}.
lastChild is aprohibited paragraph child, setnew container to its {{Node/lastChild}}.createElement("br") on thecontext object, and append the result as the last [=tree/child=] ofcontainer.These two steps follow Firefox 5.0a2, Chrome 13 dev, and Opera 11.10. IE9 instead inserts an which magically does not appear in innerHTML. In all cases, the reason is that an empty block box in CSS will have zero height, so the user won't be able to put the selection cursor inside it.
Ifnew container has novisible [=tree/children=], callcreateElement("br") on thecontext object, and append the result as the last [=tree/child=] ofnew container.
collapse(new container, 0) on thecontext object'sselection.insertText commandThis is the same as typing text (seeAdditional requirements). If the input string is more than one character, first we split it up into one execution per character, for simplicity. The general rule is then that we record each command'sstate override orvalue override, insert the new character and select it, restore any overrides that we saved, and collapse the selection to its end.
The idea of the override business is that the user might run a command like bold when the selection is collapsed. There's nothing to bold in that case, but if the user runs bold and then types a character, they expect it to be bold. Thus we save the requested state in an override and restore it when the user types. Deleting things can also set overrides, if the deleted text was styled.
Whitespace is a special case. The note forcanonical space sequences gives some of the background. If the user tries typing a space, we make it non-breaking so it doesn't collapse with anything, then canonicalize whitespace around the insertion point so line breaking isn't adversely affected.
One last special case is a newline. For that we just pass off totheinsertParagraph command.
Supported only by WebKit. Tests in other browsers were manual.
TODO: This doesn't work well if the input contains things that aren't supposed to appear in HTML, like carriage returns or nulls. Nor is it going to work well if the current cursor position is in between two halves of a non-BMP character. This will result in unserializability. The current spec disregards this, as Chrome 14 dev does. (It's not relevant to other browsers, since they don't support this as a command.)
Important issue: non-breaking space fun! The issue: if the user hits space twice, they expect it to create two spaces, not collapse. Also, if they're at the beginning or end of a line and hit space, again, they expect it not to collapse. Since we don't want to require that all contenteditable element contents always be used only with white-space: pre-wrap, we need to convert to and from non-breaking spaces.
But there's a catch: you can't just make spaces non-breaking willy-nilly, because that doesn't just stop the space from collapsing, it also prevents breaking. (Chrome 14 dev actually cheats here: in contenteditable, it doesn't collapse nbsp, but breaks after it like a regular space.) The upshot of this is that any nbsp needs to be followed by a space, or else it might end up at the beginning of a line and be visible there; and it needs to be preceded by a space, or else it might break a line prematurely. How to achieve both of these goals when there are an even number of spaces to display is left as an exercise for the reader.
Browsers vary greatly in how they handle all this, of course!
The basic philosophy of IE9 is that if you're inserting a space, and one or both of the neighboring characters is a space, change the neighboring characters to non-breaking spaces. This breaks if one of the neighboring characters is part of a run of collapsed whitespace: "foo []bar" becomes "foo []bar", which converts one visible space to three.
Firefox 6.0a2 will sometimes convert the space you're inserting to an nbsp, sometimes convert neighboring spaces to nbsps, and sometimes convert neighboring nbsps to spaces. I cannot discern any clear reason to when it chooses what, except that it seems to prefer runs of nbsp's followed by a single space (although not always). I didn't find any outright bugs, except the inevitable ones like nbsp's sometimes being right after letters.
Chrome 14 dev tries to normalize everything to look like " ...", alternating with space then nbsp. Unfortunately, it does so buggily, because it converts collapsed spaces to nbsp's, so inserting a space before " " makes it into " ", which changes one visible space to four (or arbitrarily many).
Opera 11.11 has varying behavior, like Firefox and Chrome. Like Firefox, I didn't discern an obvious pattern.
This was all discussed.
Unfortunately, we're stuck with this nbsp stuff, because of 1) legacy reasons, 2) mail clients might not support CSS equivalents, 3) authors might not know to apply any CSS to wherever the content is eventually used. The behavior I decided on to minimize the evil is as follows:
This avoids nbsp at the end of a run except where it's needed, so words won't appear indented if they wrap to the next line. It avoids more than two nbsp's in a row, so there won't be huge chunks of space that get wrapped all at once. And it avoids nbsp at the beginning of a run except where it's needed or if there are only two spaces in the run, so words won't have to wrap unnecessarily.
This is still a huge headache, though.
Chrome 14 dev does the deletion even if the value is empty. Of course, other browsers don't expose this as an execCommand(), so no one else has any defined behavior in this case at all, so I follow Chrome.
IE9, Firefox 7.0a2, Chrome 14 dev, and Opera 11.50 all don't strip wrappers, except that as usual, Gecko does if you select the whole wrapper, like {<b>foo</b>}. Also, Chrome 14 dev seems to strip the wrapper and try recreating the style in cases like <b>[foo</b>bar], where it starts in a wrapper but ends after it; this doesn't always work so well, so I don't do it. Firefox 7.0a2 also has the deletion set overrides for indeterminate state commands, so if you run insertText on [foo<b>bar</b>baz] it will make the result bold.
These things don't make any sense to me, so I don't do them. I set overrides based on the first editable text node in the range when deleting; preserve any wrappers at the start of the range; and restore the overrides in case preserving the wrappers isn't enough (like if they weren't set by deletion at all). This behavior seems to closely match IE9.
Delete the selection, withstrip wrappers false.
insertText command, withvalue equal toel.TODO: WebKit also does magic for tabs, wrapping them in a whitespace-preserving span. Should we?
Ifvalue is a newline (U+000A), take theaction fortheinsertParagraph command and return true.
Just to be tidy, add to an existing text node if there is one. Firefox 5.0a2 only adds to an existing one if the range is in a text node. IE9, Chrome 14 dev, and Opera 11.11 also add to an existing text node if the range is in an element adjacent to a text node. If there are two text nodes and it's in between, like foo{}bar, IE and Opera add to the first, Chrome adds to the second, although it probably doesn't matter in practice exactly which we choose.
Ifnode has a [=tree/child=] whose [=tree/index=] isoffset − 1, and that [=tree/child=] is a {{Text}} node, setnode to that [=tree/child=], then setoffset tonode's [=Node/length=].
collapse(node,offset) on thecontext object'sselection.insertData(offset,value) onnode.collapse(node,offset) on thecontext object'sselection.extend(node,offset + 1) on thecontext object'sselection.If some text is inserted into <p><br></p> or similar, we no longer need the <br>.
Ifnode has only one [=tree/child=], which is acollapsed line break, remove its [=tree/child=] from it.
createTextNode(value) on thecontext object.insertNode(text) on theactive range.collapse(text, 0) on thecontext object'sselection.extend(text, 1) on thecontext object'sselection.collapseToEnd() on thecontext object'sselection.insertUnorderedList commandThis command must not beenabled if theediting host is in theplaintext-only state.
See comments forinsertOrderedList.
Action:Toggle lists withtag name "ul", then return true.
Indeterminate: True if theselection's list state is "mixed" or "mixed ul", false otherwise.
State: True if theselection's list state is "ul", false otherwise.
justifyCenter commandThis command must not beenabled if theediting host is in theplaintext-only state.
Action:Justify the selection withalignment "center", then return true.
This roughly matches Chrome 14 dev, although not exactly. Firefox 6.0a2 always returns false.
As a general rule, ignoring nodes with children saves us from treating <div align=left><div align=center>foo</div></div> as though it's indeterminate. Chrome 14 dev seems to only pay attention to text nodes, instead, or something like that. At any rate, it fails on images. Firefox 6.0a2 (for state and value) gets tripped up by examples like the one given.
If we ever support centering of tables and similar, we'd want to pay attention even to some nodes that do have children.
Indeterminate: Return false if theactive range is null. Otherwise,block-extend theactive range. Return true if amongvisibleeditablenodes that arecontained in the result and have no [=tree/children=], at least one hasalignment value "center" and at least one does not. Otherwise return false.
IE9 throws exceptions in almost every case when querying the state of justify*, and Opera 11.11 returns false in every case except some seemingly random crazy ones.
Firefox 6.0a2 returns true for the state of justify* if anything in the range has the right alignment, not if everything does. This isn't consistent with how state works for the inline commands, nor with WebKit.
Chrome 14 dev counts text-align on inline elements, which is wrong, because the property has no effect. It also counts it on non-editable elements, which is wrong, because then the state for justify* wouldn't necessarily be true after executing it. (Chrome actually does align the non-editable elements, but that's just a bug.) Chrome further returns false for justify* if the justification is just the default inherited justification, e.g., left for LTR. This doesn't seem to make sense either.
State is kind of redundant here, because it's true if and only if indeterminate is false and the value is equal to the desired value. However, I'll support it anyway, since Gecko/WebKit do.
State: Return false if theactive range is null. Otherwise,block-extend theactive range. Return true if there is at least onevisibleeditablenode that iscontained in the result and has no [=tree/children=], and all suchnodes havealignment value "center". Otherwise return false.
Not bidi-safe, but it's a pretty marginal corner case where it fails. Firefox 6.0a2 behaves weirdly here: it keys off the start node of the active range, even if that's not contained. Thus {<div align=center>foo</div>} has value "left" and indeterminate false, which would suggest that the whole selection is aligned left, but that's not the case. Chrome 14 dev returns the state cast to a string, as usual. Opera 11.11 always returns the empty string.
Value: Return the empty string if theactive range is null. Otherwise,block-extend theactive range, and return thealignment value of the firstvisibleeditablenode that iscontained in the result and has no [=tree/children=]. If there is no suchnode, return "left".
justifyFull commandThis command must not beenabled if theediting host is in theplaintext-only state.
Action:Justify the selection withalignment "justify", then return true.
Indeterminate: Return false if theactive range is null. Otherwise,block-extend theactive range. Return true if amongvisibleeditablenodes that arecontained in the result and have no [=tree/children=], at least one hasalignment value "justify" and at least one does not. Otherwise return false.
State: Return false if theactive range is null. Otherwise,block-extend theactive range. Return true if there is at least onevisibleeditablenode that iscontained in the result and has no [=tree/children=], and all suchnodes havealignment value "justify". Otherwise return false.
Value: Return the empty string if theactive range is null. Otherwise,block-extend theactive range, and return thealignment value of the firstvisibleeditablenode that iscontained in the result and has no [=tree/children=]. If there is no suchnode, return "left".
justifyLeft commandThis command must not beenabled if theediting host is in theplaintext-only state.
Action:Justify the selection withalignment "left", then return true.
Indeterminate: Return false if theactive range is null. Otherwise,block-extend theactive range. Return true if amongvisibleeditablenodes that arecontained in the result and have no [=tree/children=], at least one hasalignment value "left" and at least one does not. Otherwise return false.
State: Return false if theactive range is null. Otherwise,block-extend theactive range. Return true if there is at least onevisibleeditablenode that iscontained in the result and has no [=tree/children=], and all suchnodes havealignment value "left". Otherwise return false.
Value: Return the empty string if theactive range is null. Otherwise,block-extend theactive range, and return thealignment value of the firstvisibleeditablenode that iscontained in the result and has no [=tree/children=]. If there is no suchnode, return "left".
justifyRight commandThis command must not beenabled if theediting host is in theplaintext-only state.
Action:Justify the selection withalignment "right", then return true.
Indeterminate: Return false if theactive range is null. Otherwise,block-extend theactive range. Return true if amongvisibleeditablenodes that arecontained in the result and have no [=tree/children=], at least one hasalignment value "right" and at least one does not. Otherwise return false.
State: Return false if theactive range is null. Otherwise,block-extend theactive range. Return true if there is at least onevisibleeditablenode that iscontained in the result and has no [=tree/children=], and all suchnodes havealignment value "right". Otherwise return false.
Value: Return the empty string if theactive range is null. Otherwise,block-extend theactive range, and return thealignment value of the firstvisibleeditablenode that iscontained in the result and has no [=tree/children=]. If there is no suchnode, return "left".
outdent commandThis command must not beenabled if theediting host is in theplaintext-only state.
TODO: This overnormalizes, but it seems like the simplest solution for now.
For eachitem initems,normalize sublists ofitem.
This step is kind of weird. For regular outdenting, we start at the inside and outdent going out, so that we remove the innermost indentation, on the theory that that will produce the cleanest markup (remove the most nodes). For lists, we remove the outermost indentation, because it makes a difference whether we remove inner or outer indentation, and logically we want to remove outer. E.g.,
<ol><li>foo</li><ul><li>bar</li></ul></ol>
should become
foo<ul><li>bar</li></ul>
not
foo<ol><li>bar</li></ol>.
But this is a bit weird and I'm wondering if it's really correct. TODO: Reexamine this.
For eachnodenodecontained innew range, appendnode tonode list if the last member ofnode list (if any) is not an [=tree/ancestor=] ofnode;node iseditable; and eithernode has noeditable [=tree/descendants=], or is an [^ol^] or [^ul^], or is an [^li^] whose [=tree/parent=] is an [^ol^] or [^ul^].
copy commandAction: The user agent must either copy the current selection to the clipboard as though the user had requested it, or [=exception/throw=] a {{"SecurityError"}} exception.
If an implementation supports the document.execCommand method and allows calling it with the commands "copy", the implementationmust trigger thecopy action, which again will dispatch the "copy" clipboard event, if the "copy" action did not throw a SecurityError exception.
These are the steps to follow when triggering copy actions through the document.execCommand() API:
Copy commands triggered from document.execCommand() will only affect the contents of the real clipboard if the event is dispatched from an event that is trusted and triggered by the user, or if the implementation is configured to allow this. How implementations can be configured to allow write access to the clipboard is outside the scope of this specification.
IE9 supports copy with a security warning.
cut commandAction: The user agent must either copy the current selection to the clipboard and then delete it, as though the user had requested it, or [=exception/throw=] a {{"SecurityError"}} exception.
If an implementation supports the document.execCommand method and allows calling it with the commands "cut", the implementationmust trigger thecut action, which again will dispatch the "cut" clipboard event.
These are the steps to follow when triggering cut actions through the document.execCommand() API:
Cut commands triggered from document.execCommand() will only affect the contents of the real clipboard if the event is dispatched from an event that is trusted and triggered by the user, or if the implementation is configured to allow this. How implementations can be configured to allow write access to the clipboard is outside the scope of this specification.
IE9 supports cut with a security warning.
paste commandAction: The user agent must eitherdelete the selection and then paste the clipboard's contents to the current cursor position, as though the user had requested it, or [=exception/throw=] a {{"SecurityError"}} exception.
If an implementation supports the document.execCommand method and allows calling it with the commands "paste", the implementationmust trigger thepaste action, which again will dispatch the "paste" clipboard event.
These are the steps to follow when triggering paste actions through the document.execCommand() API:
Paste commands triggered from document.execCommand() will only give access to the contents of the real clipboard if the event is dispatched from an event that is trusted and triggered by the user, or if the implementation is configured to allow this. How implementations can be configured to allow read access to the clipboard is outside the scope of this specification.
IE9 supports paste with a security warning. Firefox reportedly only supports it if you set a pref.
defaultParagraphSeparator commandThis is a new feature, added by request inbug 15527. (Opera already had an o-defaultblock command that worked similarly.)If you are implementing this, please make sure to file any feedback as bugs. The spec is not finalized yet and can still be easily changed.
Action: Letvalue beconverted to ASCII lowercase. Ifvalue is then equal to "p" or "div", set thecontext object'sdefault single-line container name tovalue, then return true. Otherwise, return false.
Value: Return thecontext object'sdefault single-line container name.
redo commandAction: As defined by theUndoManager specification.
selectAll commandThis is totally broken: if executed inside an editing host, it has to select the editing host contents, not the whole document. Seebug.
Tested using roughlythis.
The behavior here is relatively simple and largely matches implementations.
TODO: Is this right even for framesets?
Lettarget bethe body element of thecontext object.
TODO: Is this right even for documents whose root element is not an HTML element?
Iftarget is null, lettarget be thecontext object's {{Document/documentElement}}.
getSelection() on thecontext object, and callremoveAllRanges() on the result.getSelection() on thecontext object, and callselectAllChildren(target) on the result.styleWithCSS commandIE9 and Opera 11.00 don't support this command. By and large, they act the way Gecko and WebKit do when styleWithCSS is off. Gecko invented it, and WebKit alsosupports it. The default in Firefox 4.0 is off, while all other browsers behave like the default is on (and IE/Opera give no way to turn it off), so I default it to on.
Handling ofvalue matches Firefox 5.0a2. Chrome 13 dev treats the case-sensitive string "true" as true, the case-sensitive string "false" as false, and does nothing for any other string. I went with Gecko because this way there are only two possible effects, not three, which makes it easier to reason about and debug. Also, Gecko made up the command, so this is probably more web-compatible. Cursory searches of Google Code and GitHub suggest that authors almost always pass a boolean as the third argument when using styleWithCSS, in which case the two behaviors work the same.
Action: Ifvalue is anASCII case-insensitive match for the string "false", set theCSS styling flag to false. Otherwise, set theCSS styling flag to true. Either way, return true.
This follows Chrome 13 dev. Firefox 5.0a2 doesn't support queryCommandState() for styleWithCSS.
State: True if theCSS styling flag is true, otherwise false.
undo commandAction: As defined by theUndoManager specification.
useCSS command[=Action=]: Ifvalue is an [=ASCII case-insensitive=] match for the string "false", set the [=CSS styling flag=] to true. Otherwise, set the [=CSS styling flag=] to false. Either way, return true.
This command works the same as [=the styleWithCSS command|styleWithCSS=] but with the opposite values and with noqueryCommandState() support. Authors are encouraged to use [=the styleWithCSS command=] instead.
It has been suggested that some things here need to be platform-dependent, not fully standardized. For now I'm standardizing them anyway, because the large majority of behavior should be platform-agnostic. If anyone has suggestions as to particular things that should be left up to platform behavior, please say so.
When the user instructs the user agent to insert a line break inside an [=editing host=], such as by pressing the Enter key while the cursor is in aneditablenode, the user agent must callexecCommand("insertparagraph") on the relevant [=document=].
When the user instructs the user agent to insert a line break inside an [=editing host=] without breaking out of the current block, such as by pressing Shift-Enter or Option-Enter while the cursor is in aneditablenode, the user agent must callexecCommand("insertlinebreak") on the relevant [=document=].
When the user instructs the user agent to delete the previous character inside an [=editing host=], such as by pressing the Backspace key while the cursor is in aneditablenode, the user agent must callexecCommand("delete") on the relevant [=document=].
When the user instructs the user agent to delete the next character inside an [=editing host=], such as by pressing the Delete key while the cursor is in aneditablenode, the user agent must callexecCommand("forwarddelete") on the relevant [=document=].
When the user instructs the user agent to insert text inside an [=editing host=], such as by typing on the keyboard while the cursor is in aneditablenode, the user agent must callexecCommand("inserttext", false,value) on the relevant [=document=], withvalue equal to the text the user provided. If the user inserts multiple characters at once or in quick succession, this specification does not define whether it is treated as one insertion or several consecutive insertions.
The user agent may allow the user to make other changes to editable content, such as causing Ctrl-B to callexecCommand("bold"), when theediting host is in thetrue state. Any such change must be accomplished by callingexecCommand(), so that in particular, "beforeinput" and "input" events fire as appropriate. Thecommand invoked should be one of the standardcommands defined in this specification if possible, and otherwise must be avendor-specific command.
Thanks to: