--- a/editcommands.html Thu Jun 23 14:43:28 2011 -0600
+++ b/editcommands.html Thu Jun 23 14:43:44 2011 -0600
@@ -330,6 +330,15 @@
and <var title="">value</var> parameters, even if specified, are ignored except where
otherwise stated.
+<p>The <dfn id=querycommandenabled() title=queryCommandEnabled()><code>queryCommandEnabled(<var title="">command</var>)</code></dfn>
+method on the <code class=external data-anolis-spec=html><a href=http://www.whatwg.org/html/#htmldocument>HTMLDocument</a></code> interface allows
+scripts to ask whether calling <code><a href=#execcommand()>execCommand()</a></code> would have any
+effect.
+
+<p class=XXX>The <dfn id=querycommandindeterm() title=queryCommandIndeterm()><code>queryCommandIndeterm(<var title="">command</var>)</code></dfn>
+method on the <code class=external data-anolis-spec=html><a href=http://www.whatwg.org/html/#htmldocument>HTMLDocument</a></code> interface is a
+useless method that always returns false. I think.
+
<p>The <dfn id=querycommandstate() title=queryCommandState()><code>queryCommandState(<var title="">command</var>)</code></dfn>
method on the <code class=external data-anolis-spec=html><a href=http://www.whatwg.org/html/#htmldocument>HTMLDocument</a></code> interface allows
scripts to ask true-or-false status questions about the current selection, such
@@ -352,9 +361,11 @@
specification.
<p><a href=#command title=command>Commands</a> may have an associated
-<dfn id=action>action</dfn>, <dfn id=state>state</dfn>, <dfn id=value>value</dfn>, and/or <dfn id=relevant-css-property>relevant
-CSS property</dfn>. If not otherwise specified, the <a href=#action>action</a> for a
-<a href=#command>command</a> is to do nothing, the <a href=#state>state</a> is false, the
+<dfn id=action>action</dfn>, <dfn id=enabled-flag>enabled flag</dfn>, <dfn id=indeterminate-flag>indeterminate flag</dfn>,
+<dfn id=state>state</dfn>, <dfn id=value>value</dfn>, and/or <dfn id=relevant-css-property>relevant CSS property</dfn>.
+If not otherwise specified, the <a href=#action>action</a> for a <a href=#command>command</a>
+is to do nothing, the <a href=#enabled-flag>enabled flag</a> is true, the
+<a href=#indeterminate-flag>indeterminate flag</a> is false, the <a href=#state>state</a> is false, the
<a href=#value>value</a> is the empty string, and the <a href=#relevant-css-property>relevant CSS
property</a> is null.
<!--
@@ -379,6 +390,12 @@
<var title="">command</var>, with <var title="">showUI</var> and <var title="">value</var> passed to the
instructions as arguments.
+<p>When <code><a href=#querycommandenabled()>queryCommandEnabled()</a></code> is invoked, the user agent must
+return the <a href=#enabled-flag>enabled flag</a> for <var title="">command</var>.
+
+<p>When <code><a href=#querycommandindeterm()>queryCommandIndeterm()</a></code> is invoked, the user agent must
+return the <a href=#indeterminate-flag>indeterminate flag</a> for <var title="">command</var>.
+
<p>When <code><a href=#querycommandstate()>queryCommandState()</a></code> is invoked, the user agent must return
the <a href=#state>state</a> for <var title="">command</var>.
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/extramethods.html Thu Jun 23 14:43:44 2011 -0600
@@ -0,0 +1,193 @@
+<!doctype html>
+<meta charset=utf-8>
+<title>Auto-running queryCommand*() tests</title>
+<link rel=stylesheet href=tests.css>
+<style>
+#toolbar { display: none }
+body > div > table > tbody > tr { display: table-row !important }
+.yes, .no, .maybe { font-size: 1em }
+body > div > table > tbody > tr > td,
+body > div > table > tbody > tr > th {
+ width: 15%;
+}
+body > div > table > tbody > tr > td[rowspan],
+body > div > table > tbody > tr > th:first-child {
+ width: 50%;
+}
+body > div > table > tbody > tr > td:last-child,
+body > div > table > tbody > tr > th:last-child {
+ width: 5%;
+}
+</style>
+<p>Legend: {[ are the selection anchor, }] are the selection focus, {}
+represent an element boundary point, [] represent a text node boundary point.
+Syntax and some of the tests taken from <a
+href=http://www.browserscope.org/richtext2/test>Browserscope</a>. data-start
+and data-end attributes also represent element boundary points, with the node
+being the element with the attribute and the offset given as the attribute
+value, for cases where HTML parsing doesn't allow text nodes. Currently we
+don't really pay attention to reversed selections at all, so they might get
+displayed as forwards or such.
+
+<h1>Table of Contents</h1>
+<ul>
+</ul>
+
+<script src=implementation.js></script>
+<script src=tests.js></script>
+<script>
+"use strict";
+// Set up all the HTML automatically so I can add new commands to test without
+// copy-pasting in five places
+(function() {
+ var toc = document.querySelector("ul");
+ for (var command in tests) {
+ var li = document.createElement("li");
+ li.innerHTML = "<a href=#" + command + ">" + command + "</a>";
+ toc.appendChild(li);
+
+ var div = document.createElement("div");
+ div.id = command;
+ div.innerHTML = "<h1>" + command + "</h1>"
+ + "<button onclick=\"runTests('" + command + "')\">Run tests</button>"
+ + (command in notes ? "<p>" + notes[command] : "")
+ + "<table border=1><tr><th>Input <th>Feature <th>Spec <th>Browser <th>Same?</table>"
+ + "<p><label>New test input: <input></label>"
+ + "<button onclick=\"addTest('" + command + "')\">Add test</button>";
+ document.body.appendChild(div);
+ }
+})();
+
+function runTests(command) {
+ var runTestsButton = document.querySelector("#" + command + " button");
+ if (runTestsButton.textContent != "Run tests") {
+ return;
+ }
+ runTestsButton.parentNode.removeChild(runTestsButton);
+
+ var addTestButton = document.querySelector("#" + command + " button");
+ var input = document.getElementById(command).getElementsByTagName("input")[0];
+ for (var i = 0; i < tests[command].length; i++) {
+ // This code actually focuses and clicks everything because for some
+ // reason, anything else doesn't work in IE9 . . .
+ //
+ // In case the input contains something unpleasant like newlines, we
+ // smuggle in the string as a data attribute, not just the value. This
+ // is probably unnecessarily magic.
+ if (typeof tests[command][i] == "string") {
+ input.value = tests[command][i];
+ input.setAttribute("data-input", tests[command][i]);
+ } else {
+ input.value = tests[command][i][1];
+ input.setAttribute("data-input", tests[command][i][1]);
+ }
+ input.focus();
+ addTestButton.click();
+ }
+ input.value = "";
+
+ document.querySelector("#" + command).scrollIntoView();
+}
+
+function addTest(command) {
+ var tr = doSetup("#" + command + " > table", 0);
+
+ var test;
+ var input = document.getElementById(command).getElementsByTagName("input")[0];
+
+ if (input.hasAttribute("data-input")) {
+ test = input.getAttribute("data-input");
+ } else {
+ test = input.value;
+ }
+ input.removeAttribute("data-input");
+
+ var inputCell = document.createElement("td");
+ var points = setupCell(inputCell, test);
+ inputCell.rowSpan = 4;
+ tr.appendChild(inputCell);
+ inputCell.firstChild.contentEditable = "true";
+ inputCell.firstChild.spellcheck = false;
+
+ compareMethods(tr, command, points, "Enabled", myQueryCommandEnabled, document.queryCommandEnabled);
+
+ var newTr = document.createElement("tr");
+ tr.parentNode.appendChild(newTr);
+ compareMethods(newTr, command, points, "Indeterm", myQueryCommandIndeterm, document.queryCommandIndeterm);
+
+ newTr = document.createElement("tr");
+ tr.parentNode.appendChild(newTr);
+ compareMethods(newTr, command, points, "State", myQueryCommandState, document.queryCommandState);
+
+ newTr = document.createElement("tr");
+ tr.parentNode.appendChild(newTr);
+ compareMethods(newTr, command, points, "Value", myQueryCommandValue, document.queryCommandValue);
+
+ inputCell.firstChild.contentEditable = "inherit";
+ inputCell.firstChild.removeAttribute("spellcheck");
+ inputCell.firstChild.innerHTML = test;
+ inputCell.lastChild.textContent = inputCell.firstChild.innerHTML;
+}
+
+function compareMethods(tr, command, points, label, specFn, browserFn) {
+ var labelCell = document.createElement("th");
+ labelCell.textContent = label;
+ tr.appendChild(labelCell);
+
+ var specCell = document.createElement("td");
+ tr.appendChild(specCell);
+
+ var specResult;
+ try {
+ var range = document.createRange();
+ range.setStart(points[0], points[1]);
+ range.setEnd(points[2], points[3]);
+ if (range.collapsed) {
+ range.setEnd(points[0], points[1]);
+ }
+
+ specResult = specFn(command, range);
+ } catch (e) {
+ specCell.textContent = "Exception: " + e;
+ if (typeof e == "object" && "stack" in e) {
+ specCell.textContent += " (stack: " + e.stack + ")";
+ }
+ }
+
+ if (typeof specResult != "undefined") {
+ specCell.textContent = typeof specResult + ' "' + specResult + '"';
+ }
+
+
+ var browserCell = document.createElement("td");
+ tr.appendChild(browserCell);
+
+ var browserResult;
+ try {
+ setSelection(points[0], points[1], points[2], points[3]);
+ browserResult = browserFn.call(document, command);
+ } catch (e) {
+ browserCell.textContent = "Exception: " + e;
+ if (typeof e == "object" && "stack" in e) {
+ browserCell.textContent += " (stack: " + e.stack + ")";
+ }
+ }
+
+ getSelection().removeAllRanges();
+
+ if (typeof browserResult != "undefined") {
+ browserCell.textContent = typeof browserResult + ' "' + browserResult + '"';
+ }
+
+
+ var sameCell = document.createElement("td");
+ tr.appendChild(sameCell);
+ if (specResult === browserResult) {
+ sameCell.className = "yes";
+ sameCell.textContent = "\u2713";
+ } else {
+ sameCell.className = "no";
+ sameCell.textContent = "\u2717";
+ }
+}
+</script>
--- a/implementation.js Thu Jun 23 14:43:28 2011 -0600
+++ b/implementation.js Thu Jun 23 14:43:44 2011 -0600
@@ -483,9 +483,7 @@
///// Methods of the HTMLDocument interface /////
/////////////////////////////////////////////////
//@{
-function myExecCommand(command, showUI, value, range) {
- command = command.toLowerCase();
-
+function setupEditCommandMethod(command, range) {
if (typeof range != "undefined") {
globalRange = range;
} else {
@@ -495,58 +493,76 @@
// "If the active range is null, all commands must behave as though they
// were not defined except those in the miscellaneous commands section."
if (!globalRange && command != "selectall" && command != "stylewithcss" && command != "usecss") {
- return;
- }
-
- if (!(command in commands)) {
- return;
- }
-
- commands[command].action(value);
-
- globalRange = null;
-}
-
-function myQueryCommandState(command) {
- command = command.toLowerCase();
-
- if (typeof range != "undefined") {
- globalRange = range;
- } else {
- globalRange = getActiveRange();
- }
-
- if (!globalRange && command != "selectall" && command != "stylewithcss" && command != "usecss") {
- return;
+ return false;
}
if (!(command in commands)) {
- return;
- }
-
- return commands[command].state();
+ return false;
+ }
+
+ return true;
+}
+
+function myExecCommand(command, showUI, value, range) {
+ command = command.toLowerCase();
+
+ if (setupEditCommandMethod(command, range)) {
+ commands[command].action(value);
+ }
globalRange = null;
}
-function myQueryCommandValue(command) {
+function myQueryCommandEnabled(command, range) {
command = command.toLowerCase();
- if (typeof range != "undefined") {
- globalRange = range;
+ if (setupEditCommandMethod(command, range)) {
+ return commands[command].enabled();
} else {
- globalRange = getActiveRange();
- }
-
- if (!globalRange && command != "selectall" && command != "stylewithcss" && command != "usecss") {
- return;
- }
-
- if (!(command in commands)) {
- return;
- }
-
- return commands[command].value();
+ return false;
+ }
+
+ globalRange = null;
+}
+
+function myQueryCommandIndeterm(command, range) {
+ command = command.toLowerCase();
+
+ if (setupEditCommandMethod(command, range)) {
+ return commands[command].indeterm();
+ } else {
+ return false;
+ }
+
+ globalRange = null;
+}
+
+function myQueryCommandState(command, range) {
+ command = command.toLowerCase();
+
+ if (setupEditCommandMethod(command, range)) {
+ return commands[command].state();
+ } else {
+ // "If not otherwise specified, the action for a command is to do
+ // nothing, the state is false, the value is the empty string, and the
+ // relevant CSS property is null."
+ return false;
+ }
+
+ globalRange = null;
+}
+
+function myQueryCommandValue(command, range) {
+ command = command.toLowerCase();
+
+ if (setupEditCommandMethod(command, range)) {
+ return commands[command].value();
+ } else {
+ // "If not otherwise specified, the action for a command is to do
+ // nothing, the state is false, the value is the empty string, and the
+ // relevant CSS property is null."
+ return "";
+ }
globalRange = null;
}
@@ -6304,6 +6320,12 @@
if (!("action" in commands[command])) {
commands[command].action = function() {};
}
+ if (!("enabled" in commands[command])) {
+ commands[command].enabled = function() { return true };
+ }
+ if (!("indeterm" in commands[command])) {
+ commands[command].indeterm = function() { return false };
+ }
if (!("state" in commands[command])) {
commands[command].state = function() { return false };
}
--- a/source.html Thu Jun 23 14:43:28 2011 -0600
+++ b/source.html Thu Jun 23 14:43:44 2011 -0600
@@ -265,6 +265,17 @@
otherwise stated.
<p>The <dfn
+title=queryCommandEnabled()><code>queryCommandEnabled(<var>command</var>)</code></dfn>
+method on the <code data-anolis-spec=html>HTMLDocument</code> interface allows
+scripts to ask whether calling <code>execCommand()</code> would have any
+effect.
+
+<p class=XXX>The <dfn
+title=queryCommandIndeterm()><code>queryCommandIndeterm(<var>command</var>)</code></dfn>
+method on the <code data-anolis-spec=html>HTMLDocument</code> interface is a
+useless method that always returns false. I think.
+
+<p>The <dfn
title=queryCommandState()><code>queryCommandState(<var>command</var>)</code></dfn>
method on the <code data-anolis-spec=html>HTMLDocument</code> interface allows
scripts to ask true-or-false status questions about the current selection, such
@@ -290,9 +301,11 @@
specification.
<p><span title=command>Commands</span> may have an associated
-<dfn>action</dfn>, <dfn>state</dfn>, <dfn>value</dfn>, and/or <dfn>relevant
-CSS property</dfn>. If not otherwise specified, the <span>action</span> for a
-<span>command</span> is to do nothing, the <span>state</span> is false, the
+<dfn>action</dfn>, <dfn>enabled flag</dfn>, <dfn>indeterminate flag</dfn>,
+<dfn>state</dfn>, <dfn>value</dfn>, and/or <dfn>relevant CSS property</dfn>.
+If not otherwise specified, the <span>action</span> for a <span>command</span>
+is to do nothing, the <span>enabled flag</span> is true, the
+<span>indeterminate flag</span> is false, the <span>state</span> is false, the
<span>value</span> is the empty string, and the <span>relevant CSS
property</span> is null.
<!--
@@ -317,6 +330,12 @@
<var>command</var>, with <var>showUI</var> and <var>value</var> passed to the
instructions as arguments.
+<p>When <code>queryCommandEnabled()</code> is invoked, the user agent must
+return the <span>enabled flag</span> for <var>command</var>.
+
+<p>When <code>queryCommandIndeterm()</code> is invoked, the user agent must
+return the <span>indeterminate flag</span> for <var>command</var>.
+
<p>When <code>queryCommandState()</code> is invoked, the user agent must return
the <span>state</span> for <var>command</var>.