author | Aryeh Gregor <ayg@aryeh.name> |
Fri, 24 Feb 2012 10:24:06 -0700 | |
changeset 703 | a089a5315642 |
parent 702 | 33d0413c3be8 |
child 704 | 4cacaa76c31a |
conformancetest/event.html | ||
editing.html | ||
preprocess | ||
source.html |
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/conformancetest/event.html Fri Feb 24 10:24:06 2012 -0700 @@ -0,0 +1,270 @@ +<!doctype html> +<title>Editing event tests</title> +<script src=http://dvcs.w3.org/hg/html/raw-file/tip/tests/resources/testharness.js></script> +<script src=http://dvcs.w3.org/hg/html/raw-file/tip/tests/resources/testharnessreport.js></script> +<div id=test></div> +<div id=log></div> +<script> +"use strict"; + +var div = document.querySelector("#test"); +add_completion_callback(function() { div.parentNode.removeChild(div) }); + +function copyEvent(e) { + var ret = {}; + ret.original = e; + ["type", "target", "currentTarget", "eventPhase", "bubbles", "cancelable", + "defaultPrevented", "isTrusted", "command", "value"].forEach(function(k) { + ret[k] = e[k]; + }); + return ret; +} + +var tests = [ + { + name: "Simple editable div", + html: "<div contenteditable>foo<b>bar</b>baz</div>", + initRange: function(range) { + range.setStart(div.querySelector("b").firstChild, 0); + range.setEnd(div.querySelector("b"), 1); + }, + target: function() { return div.firstChild }, + command: "bold", + value: "", + }, + { + name: "Editable b", + html: "foo<b contenteditable>bar</b>baz", + initRange: function(range) { + range.setStart(div.querySelector("b").firstChild, 0); + range.setEnd(div.querySelector("b"), 1); + }, + target: function() { return div.querySelector("b") }, + command: "bold", + value: "", + }, + { + name: "No editable content", + html: "foo<b>bar</b>baz", + initRange: function(range) { + range.setStart(div.querySelector("b").firstChild, 0); + range.setEnd(div.querySelector("b"), 1); + }, + target: function() { return null }, + command: "bold", + value: "", + }, + { + name: "Partially-selected editable content", + html: "foo<b contenteditable>bar</b>baz", + initRange: function(range) { + range.setStart(div.querySelector("b").firstChild, 0); + range.setEnd(div, 3); + }, + target: function() { return null }, + command: "bold", + value: "", + }, + { + name: "Selection spans two editing hosts", + html: "<div contenteditable>foo</div><div contenteditable>bar</div>", + initRange: function(range) { + range.setStart(div.querySelector("div").firstChild, 2); + range.setEnd(div.querySelector("div + div").firstChild, 1); + }, + target: function() { return null }, + command: "bold", + value: "", + }, + { + name: "Selection includes two editing hosts", + html: "foo<div contenteditable>bar</div>baz<div contenteditable>quz</div>qoz", + initRange: function(range) { + range.setStart(div.firstChild, 2); + range.setEnd(div.lastChild, 1); + }, + target: function() { return null }, + command: "bold", + value: "", + }, + { + name: "Changing selection from handler", + html: "<div contenteditable>foo</div><div contenteditable>bar</div>", + initRange: function(range) { + range.setStart(div.querySelector("div").firstChild, 0); + range.setEnd(div.querySelector("div").firstChild, 3); + }, + target: function() { return div.firstChild }, + finalTarget: function() { return div.lastChild }, + beforeInputAction: function() { + getSelection().removeAllRanges(); + var range = document.createRange(); + range.setStart(div.querySelector("div + div").firstChild, 0); + range.setEnd(div.querySelector("div + div").firstChild, 3); + getSelection().addRange(range); + }, + command: "bold", + value: "", + }, +]; + +var commandTests = { + backColor: ["green"], + createLink: ["http://www.w3.org/community/editing/"], + fontName: ["serif", "Helvetica"], + fontSize: ["6", "15px"], + foreColor: ["green"], + hiliteColor: ["green"], + italic: [], + removeFormat: [], + strikeThrough: [], + subscript: [], + superscript: [], + underline: [], + unlink: [], + delete: [], + formatBlock: ["p"], + forwardDelete: [], + indent: [], + insertHorizontalRule: ["id"], + insertHTML: ["<b>hi</b>"], + insertImage: ["http://example.com/some-image"], + insertLineBreak: [], + insertOrderedList: [], + insertParagraph: [], + insertText: ["abc"], + insertUnorderedList: [], + justifyCenter: [], + justifyFull: [], + justifyLeft: [], + justifyRight: [], + outdent: [], + redo: [], + selectAll: [], + styleWithCSS: [], + undo: [], + useCSS: [], +}; + +Object.keys(commandTests).forEach(function(command) { + commandTests[command] = ["", "quasit"].concat(commandTests[command]); + commandTests[command].forEach(function(value) { + tests.push({ + name: "Command " + command + ", value " + format_value(value), + html: "<div contenteditable>foo<b>bar</b>baz</div>", + initRange: function(range) { + range.setStart(div.querySelector("b").firstChild, 0); + range.setEnd(div.querySelector("b"), 1); + }, + target: function() { + return ["redo", "selectAll", "styleWithCSS", "undo", "useCSS"] + .indexOf(command) == -1 ? div.firstChild : null; + }, + command: command, + value: value, + }); + }); +}); + +tests.forEach(function(obj) { + [true, false].forEach(function(cancel) { + // Kill all event handlers first + var newDiv = div.cloneNode(false); + div.parentNode.insertBefore(newDiv, div); + div.parentNode.removeChild(div); + div = newDiv; + + div.innerHTML = obj.html; + + var originalContents = div.cloneNode(true); + + getSelection().removeAllRanges(); + var range = document.createRange(); + obj.initRange(range); + getSelection().addRange(range); + + var target = obj.target(); + var finalTarget = "finalTarget" in obj ? obj.finalTarget() : target; + var command = obj.command; + var value = obj.value; + + var beforeInputEvents = []; + var inputEvents = []; + div.addEventListener("beforeinput", function(e) { + var copied = copyEvent(e); + copied.inputEventsLength = inputEvents.length; + beforeInputEvents.push(copied); + if (cancel) { + e.preventDefault(); + } + if ("beforeInputAction" in obj) { + obj.beforeInputAction(); + } + }); + div.addEventListener("input", function(e) { inputEvents.push(copyEvent(e)) }); + + // Uncomment this code instead of the execCommand() to make all the + // tests pass, as a sanity check + //var e = new Event("beforeinput", {bubbles: true, cancelable: true}); + //e.command = command; + //e.value = value; + //var ret = target ? target.dispatchEvent(e) : false; + //if (ret) { + // var e = new Event("input", {bubbles: true}); + // e.command = command; + // e.value = value; + // finalTarget.dispatchEvent(e); + //} + + document.execCommand(command, false, value); + + test(function() { + assert_equals(beforeInputEvents.length, target ? 1 : 0, + "number of beforeinput events fired"); + if (beforeInputEvents.length == 0) { + assert_equals(inputEvents.length, 0, "number of input events fired"); + return; + } + var e = beforeInputEvents[0]; + assert_equals(e.inputEventsLength, 0, "number of input events fired"); + assert_equals(e.type, "beforeinput", "event.type"); + assert_equals(e.target, target, "event.target"); + assert_equals(e.currentTarget, div, "event.currentTarget"); + assert_equals(e.eventPhase, Event.BUBBLING_PHASE, "event.eventPhase"); + assert_equals(e.bubbles, true, "event.bubbles"); + assert_equals(e.cancelable, true, "event.cancelable"); + assert_equals(e.defaultPrevented, false, "event.defaultPrevented"); + assert_equals(e.isTrusted, true, "event.isTrusted"); + assert_equals(e.command, command, "e.command"); + assert_equals(e.value, value, "e.value"); + assert_equals(Object.getPrototypeOf(e.original), EditingBeforeInputEvent, + "event prototype"); + assert_true(originalContents.isEqualNode(div), + "div contents not yet changed"); + }, obj.name + ": beforeinput event, " + (cancel ? "canceled" : "uncanceled")); + + test(function() { + assert_equals(inputEvents.length, target && !cancel ? 1 : 0, + "number of input events fired"); + if (!target || cancel) { + assert_true(originalContents.isEqualNode(div), + "div contents must not be changed"); + return; + } + var e = inputEvents[0]; + assert_equals(e.type, "input", "event.type"); + assert_equals(e.target, finalTarget, "event.target"); + assert_equals(e.currentTarget, div, "event.currentTarget"); + assert_equals(e.eventPhase, Event.BUBBLING_PHASE, "event.eventPhase"); + assert_equals(e.bubbles, true, "event.bubbles"); + assert_equals(e.cancelable, false, "event.cancelable"); + assert_equals(e.defaultPrevented, false, "event.defaultPrevented"); + assert_equals(e.isTrusted, true, "event.isTrusted"); + assert_equals(e.command, command, "e.command"); + assert_equals(e.value, value, "e.value"); + assert_equals(Object.getPrototypeOf(e.original), EditingInputEvent, + "event prototype"); + }, obj.name + ": input event, " + (cancel ? "canceled" : "uncanceled")); + }); +}); +</script>
--- a/editing.html Fri Feb 24 08:46:37 2012 -0700 +++ b/editing.html Fri Feb 24 10:24:06 2012 -0700 @@ -1399,6 +1399,49 @@ <h2 id=methods-to-query-and-execute-commands>Methods to query and execute commands</h2> +<p class=XXX>We fire events as requested in +<a href="https://www.w3.org/Bugs/Public/show_bug.cgi?id=13118">bug 13118</a>. +This is a new feature does not currently match any browser. <strong>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.</strong> + +<pre class=idl>[Constructor(DOMString <var title="">type</var>, optional <a href=#editingbeforeinputeventinit>EditingBeforeInputEventInit</a> <var title="">eventInitDict</var>)] +interface <dfn id=editingbeforeinputevent>EditingBeforeInputEvent</dfn> : <a class=external data-anolis-spec=dom href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#event>Event</a> { + readonly attribute DOMString <a href=#dom-editingbeforeinputevent-command title=dom-EditingBeforeInputEvent-command>command</a>; + readonly attribute DOMString <a href=#dom-editingbeforeinputevent-value title=dom-EditingBeforeInputEvent-value>value</a>; +}; + +dictionary <dfn id=editingbeforeinputeventinit>EditingBeforeInputEventInit</dfn> : <a class=external data-anolis-spec=dom href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#eventinit>EventInit</a> { + DOMString command; + DOMString value; +}; + +[Constructor(DOMString <var title="">type</var>, optional <a href=#editinginputeventinit>EditingInputEventInit</a> <var title="">eventInitDict</var>)] +interface <dfn id=editinginputevent>EditingInputEvent</dfn> : <a class=external data-anolis-spec=dom href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#event>Event</a> { + readonly attribute DOMString <a href=#dom-editinginputevent-command title=dom-EditingInputEvent-command>command</a>; + readonly attribute DOMString <a href=#dom-editinginputevent-value title=dom-EditingInputEvent-value>value</a>; +}; + +dictionary <dfn id=editinginputeventinit>EditingInputEventInit</dfn> : <a class=external data-anolis-spec=dom href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#eventinit>EventInit</a> { + DOMString command; + DOMString value; +};</pre> + +<p class=XXX>We have two different interfaces because we might want to add +additional members to the input event but not the beforeinput event, such as a +list of nodes that were affected. + +<p>When an <code><a href=#editingbeforeinputevent>EditingBeforeInputEvent</a></code> object is created, the +<dfn id=dom-editingbeforeinputevent-command title=dom-EditingBeforeInputEvent-command><code>command</code></dfn> and +<dfn id=dom-editingbeforeinputevent-value title=dom-EditingBeforeInputEvent-value><code>value</code></dfn> +attributes must both be initialized to the empty string, unless otherwise +specified. + +<p>When an <code><a href=#editinginputevent>EditingInputEvent</a></code> object is created, the +<dfn id=dom-editinginputevent-command title=dom-EditingInputEvent-command><code>command</code></dfn> and +<dfn id=dom-editinginputevent-value title=dom-EditingInputEvent-value><code>value</code></dfn> attributes must +both be initialized to the empty string, unless otherwise specified. + <p class=comments>TODO: Define behavior for <var title="">show UI</var>. <p>When the <dfn id=execcommand() title=execCommand()><code>execCommand(<var title="">command</var>, @@ -1427,9 +1470,80 @@ <p>If <var title="">command</var> is not <a href=#enabled>enabled</a>, return false. + <li> + <p>If <var title="">command</var> is not in the + <a href=#miscellaneous-commands>Miscellaneous commands</a> section: + + <p class=XXX>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. + + <ol> + <li>Let <var title="">affected editing host</var> be the <a href=#editing-host>editing host</a> + that is an <a class=external data-anolis-spec=dom href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-tree-inclusive-ancestor title=concept-tree-inclusive-ancestor>inclusive ancestor</a> of the <a href=#active-range>active range</a>'s + <a class=external data-anolis-spec=dom href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-range-start-node title=concept-range-start-node>start node</a> and <a class=external data-anolis-spec=dom href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-range-end-node title=concept-range-end-node>end node</a>, and is not the <a class=external data-anolis-spec=dom href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-tree-ancestor title=concept-tree-ancestor>ancestor</a> of any + <a href=#editing-host>editing host</a> that is an <a class=external data-anolis-spec=dom href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-tree-inclusive-ancestor title=concept-tree-inclusive-ancestor>inclusive ancestor</a> of the + <a href=#active-range>active range</a>'s <a class=external data-anolis-spec=dom href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-range-start-node title=concept-range-start-node>start node</a> and <a class=external data-anolis-spec=dom href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-range-end-node title=concept-range-end-node>end node</a>. + + <p class=note>Such an editing host must exist, because otherwise the + command would not be <a href=#enabled>enabled</a>. + + <li><a class=external data-anolis-spec=dom href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-event-dispatch title=concept-event-dispatch>Dispatch</a> an <a class=external data-anolis-spec=dom href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-event title=concept-event>event</a> at <var title="">affected editing host</var> that uses + the <code><a href=#editingbeforeinputevent>EditingBeforeInputEvent</a></code> interface. The event's + <code class=external data-anolis-spec=dom title=dom-Event-type><a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#dom-event-type>type</a></code> attribute must + be initialized to "beforeinput"; its + <code class=external data-anolis-spec=dom title=dom-Event-isTrusted><a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#dom-event-istrusted>isTrusted</a></code>, + <code class=external data-anolis-spec=dom title=dom-Event-bubbles><a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#dom-event-bubbles>bubbles</a></code>, and + <code class=external data-anolis-spec=dom title=dom-Event-cancelable><a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#dom-event-cancelable>cancelable</a></code> + attributes must be initialized to true; its + <code title=dom-EditingBeforeInputEvent-command><a href=#dom-editingbeforeinputevent-command>command</a></code> attribute + must be initialized to <var title="">command</var>; and its + <code title=dom-EditingBeforeInputEvent-value><a href=#dom-editingbeforeinputevent-value>value</a></code> attribute must + be initialized to <var title="">value</var>. + + <li>If the value returned by the previous step is false, return false. + + <li>If <var title="">command</var> is not <a href=#enabled>enabled</a>, return false. + + <p class=XXX>We have to check again whether the command is enabled, because + the beforeinput handler might have done something annoying like + getSelection().removeAllRanges(). + + <li>Let <var title="">affected editing host</var> be the <a href=#editing-host>editing host</a> + that is an <a class=external data-anolis-spec=dom href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-tree-inclusive-ancestor title=concept-tree-inclusive-ancestor>inclusive ancestor</a> of the <a href=#active-range>active range</a>'s + <a class=external data-anolis-spec=dom href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-range-start-node title=concept-range-start-node>start node</a> and <a class=external data-anolis-spec=dom href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-range-end-node title=concept-range-end-node>end node</a>, and is not the <a class=external data-anolis-spec=dom href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-tree-ancestor title=concept-tree-ancestor>ancestor</a> of any + <a href=#editing-host>editing host</a> that is an <a class=external data-anolis-spec=dom href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-tree-inclusive-ancestor title=concept-tree-inclusive-ancestor>inclusive ancestor</a> of the + <a href=#active-range>active range</a>'s <a class=external data-anolis-spec=dom href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-range-start-node title=concept-range-start-node>start node</a> and <a class=external data-anolis-spec=dom href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-range-end-node title=concept-range-end-node>end node</a>. + + <p class=XXX>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. + </ol> + <li>Take the <a href=#action>action</a> for <var title="">command</var>, passing <var title="">value</var> to the instructions as an argument. + <li>If <var title="">command</var> is not in the + <a href=#miscellaneous-commands>Miscellaneous commands</a> section, then + <a class=external data-anolis-spec=dom href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-event-dispatch title=concept-event-dispatch>dispatch</a> an <a class=external data-anolis-spec=dom href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-event title=concept-event>event</a> at <var title="">affected editing host</var> that uses the + <code><a href=#editinginputevent>EditingInputEvent</a></code> interface. The event's + <code class=external data-anolis-spec=dom title=dom-Event-type><a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#dom-event-type>type</a></code> attribute must be + initialized to "input"; its + <code class=external data-anolis-spec=dom title=dom-Event-isTrusted><a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#dom-event-istrusted>isTrusted</a></code> and + <code class=external data-anolis-spec=dom title=dom-Event-bubbles><a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#dom-event-bubbles>bubbles</a></code> attributes + must be initialized to true; its + <code title=dom-EditingInputEvent-command><a href=#dom-editinginputevent-command>command</a></code> attribute must be + initialized to <var title="">command</var>; and its + <code title=dom-EditingInputEvent-value><a href=#dom-editinginputevent-value>value</a></code> attribute must be + initialized to <var title="">value</var>. + <li>Return true. </ol>
--- a/preprocess Fri Feb 24 08:46:37 2012 -0700 +++ b/preprocess Fri Feb 24 10:24:06 2012 -0700 @@ -38,6 +38,7 @@ 'descendant': '<span data-anolis-spec=dom title=concept-tree-descendant>descendant</span>', 'directionality': '<span data-anolis-spec=html title="the directionality">directionality</span>', 'div': '<code data-anolis-spec=html title="the div element">div</code>', + 'dispatch': '<span data-anolis-spec=dom title=concept-event-dispatch>dispatch</span>', 'dd': '<code data-anolis-spec=html title="the dd element">dd</code>', 'dl': '<code data-anolis-spec=html title="the dl element">dl</code>', 'dt': '<code data-anolis-spec=html title="the dt element">dt</code>', @@ -51,6 +52,7 @@ 'endoffset': '<span data-anolis-spec=dom title=concept-range-end-offset>end offset</span>', 'equivalent': '<span title="equivalent values">equivalent</span>', 'extractcontents': '<code data-anolis-spec=dom title=dom-Range-extractContents>extractContents()</code>', + 'event': '<span data-anolis-spec=dom title=concept-event>event</span>', 'firstchild': '<code data-anolis-spec=dom title=dom-Node-firstChild>firstChild</code>', 'followingsibling': '<span data-anolis-spec=dom title="concept-tree-following">following</span> <span data-anolis-spec=dom title=concept-tree-sibling>sibling</span>', 'font': '<code data-anolis-spec=html title=font>font</code>',
--- a/source.html Fri Feb 24 08:46:37 2012 -0700 +++ b/source.html Fri Feb 24 10:24:06 2012 -0700 @@ -1365,6 +1365,50 @@ <h2>Methods to query and execute commands</h2> <!-- @{ --> +<p class=XXX>We fire events as requested in +<a href=https://www.w3.org/Bugs/Public/show_bug.cgi?id=13118>bug 13118</a>. +This is a new feature does not currently match any browser. <strong>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.</strong> + +<pre class=idl> +[Constructor(DOMString <var>type</var>, optional <span>EditingBeforeInputEventInit</span> <var>eventInitDict</var>)] +interface <dfn>EditingBeforeInputEvent</dfn> : <span data-anolis-spec=dom>Event</span> { + readonly attribute DOMString <span title=dom-EditingBeforeInputEvent-command>command</span>; + readonly attribute DOMString <span title=dom-EditingBeforeInputEvent-value>value</span>; +}; + +dictionary <dfn>EditingBeforeInputEventInit</dfn> : <span data-anolis-spec=dom>EventInit</span> { + DOMString command; + DOMString value; +}; + +[Constructor(DOMString <var>type</var>, optional <span>EditingInputEventInit</span> <var>eventInitDict</var>)] +interface <dfn>EditingInputEvent</dfn> : <span data-anolis-spec=dom>Event</span> { + readonly attribute DOMString <span title=dom-EditingInputEvent-command>command</span>; + readonly attribute DOMString <span title=dom-EditingInputEvent-value>value</span>; +}; + +dictionary <dfn>EditingInputEventInit</dfn> : <span data-anolis-spec=dom>EventInit</span> { + DOMString command; + DOMString value; +};</pre> + +<p class=XXX>We have two different interfaces because we might want to add +additional members to the input event but not the beforeinput event, such as a +list of nodes that were affected. + +<p>When an <code>EditingBeforeInputEvent</code> object is created, the +<dfn title=dom-EditingBeforeInputEvent-command><code>command</code></dfn> and +<dfn title=dom-EditingBeforeInputEvent-value><code>value</code></dfn> +attributes must both be initialized to the empty string, unless otherwise +specified. + +<p>When an <code>EditingInputEvent</code> object is created, the +<dfn title=dom-EditingInputEvent-command><code>command</code></dfn> and +<dfn title=dom-EditingInputEvent-value><code>value</code></dfn> attributes must +both be initialized to the empty string, unless otherwise specified. + <p class=comments>TODO: Define behavior for <var>show UI</var>. <p>When the <dfn title=execCommand()><code>execCommand(<var>command</var>, @@ -1394,9 +1438,80 @@ <p>If <var>command</var> is not <span>enabled</span>, return false. + <li> + <p>If <var>command</var> is not in the + <a href=#miscellaneous-commands>Miscellaneous commands</a> section: + + <p class=XXX>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. + + <ol> + <li>Let <var>affected editing host</var> be the <span>editing host</span> + that is an [[inclusiveancestor]] of the <span>active range</span>'s + [[startnode]] and [[endnode]], and is not the [[ancestor]] of any + <span>editing host</span> that is an [[inclusiveancestor]] of the + <span>active range</span>'s [[startnode]] and [[endnode]]. + + <p class=note>Such an editing host must exist, because otherwise the + command would not be <span>enabled</span>. + + <li>[[Dispatch]] an [[event]] at <var>affected editing host</var> that uses + the <code>EditingBeforeInputEvent</code> interface. The event's + <code data-anolis-spec=dom title=dom-Event-type>type</code> attribute must + be initialized to "beforeinput"; its + <code data-anolis-spec=dom title=dom-Event-isTrusted>isTrusted</code>, + <code data-anolis-spec=dom title=dom-Event-bubbles>bubbles</code>, and + <code data-anolis-spec=dom title=dom-Event-cancelable>cancelable</code> + attributes must be initialized to true; its + <code title=dom-EditingBeforeInputEvent-command>command</code> attribute + must be initialized to <var>command</var>; and its + <code title=dom-EditingBeforeInputEvent-value>value</code> attribute must + be initialized to <var>value</var>. + + <li>If the value returned by the previous step is false, return false. + + <li>If <var>command</var> is not <span>enabled</span>, return false. + + <p class=XXX>We have to check again whether the command is enabled, because + the beforeinput handler might have done something annoying like + getSelection().removeAllRanges(). + + <li>Let <var>affected editing host</var> be the <span>editing host</span> + that is an [[inclusiveancestor]] of the <span>active range</span>'s + [[startnode]] and [[endnode]], and is not the [[ancestor]] of any + <span>editing host</span> that is an [[inclusiveancestor]] of the + <span>active range</span>'s [[startnode]] and [[endnode]]. + + <p class=XXX>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. + </ol> + <li>Take the <span>action</span> for <var>command</var>, passing <var>value</var> to the instructions as an argument. + <li>If <var>command</var> is not in the + <a href=#miscellaneous-commands>Miscellaneous commands</a> section, then + [[dispatch]] an [[event]] at <var>affected editing host</var> that uses the + <code>EditingInputEvent</code> interface. The event's + <code data-anolis-spec=dom title=dom-Event-type>type</code> attribute must be + initialized to "input"; its + <code data-anolis-spec=dom title=dom-Event-isTrusted>isTrusted</code> and + <code data-anolis-spec=dom title=dom-Event-bubbles>bubbles</code> attributes + must be initialized to true; its + <code title=dom-EditingInputEvent-command>command</code> attribute must be + initialized to <var>command</var>; and its + <code title=dom-EditingInputEvent-value>value</code> attribute must be + initialized to <var>value</var>. + <li>Return true. </ol>