Make editing hosts not editable
authorAryeh Gregor <AryehGregor+gitcommit@gmail.com>
Mon, 02 May 2011 13:24:13 -0600
changeset 77 b95842210a1d
parent 76 5cba91acf60b
child 78 82b1d1b77eb6
Make editing hosts not editable

This is good because it means we won't try to remove or break up editing
hosts anywhere.
autoimplementation.html
editcommands.html
implementation.js
source.html
--- a/autoimplementation.html	Sun May 01 15:11:10 2011 -0600
+++ b/autoimplementation.html	Mon May 02 13:24:13 2011 -0600
@@ -519,6 +519,15 @@
 		'<blockquote style="margin: 0 40px"><p>foo[bar</p><p>b]az</p></blockquote><p>extra',
 		'<blockquote style="margin: 0 40px"><p>foo[bar]</p></blockquote><p>baz</p><p>extra',
 		'<blockquote style="margin: 0 40px"><p>foo[bar</p></blockquote><p>b]az</p><p>extra',
+		'<div style="margin: 0 40px"><p>foo[bar]</p><p>baz</p></div><p>extra',
+		'<div style="margin: 0 40px"><p>foo[bar</p><p>b]az</p></div><p>extra',
+		'<div style="margin: 0 40px"><p>foo[bar]</p></div><p>baz</p><p>extra',
+		'<div style="margin: 0 40px"><p>foo[bar</p></div><p>b]az</p><p>extra',
+
+		// MDC says "In Firefox, if the selection spans multiple lines at
+		// different levels of indentation, only the least indented lines in
+		// the selection will be indented."  Let's test that.
+		'<blockquote>f[oo<blockquote>b]ar</blockquote></blockquote><p>extra',
 	],
 	inserthorizontalrule: [
 		'foo[]bar',
--- a/editcommands.html	Sun May 01 15:11:10 2011 -0600
+++ b/editcommands.html	Mon May 02 13:24:13 2011 -0600
@@ -27,7 +27,7 @@
 <body class=draft>
 <div class=head id=head>
 <h1>HTML Editing Commands</h1>
-<h2 class="no-num no-toc" id=work-in-progress-&mdash;-last-update-1-may-2011>Work in Progress &mdash; Last Update 1 May 2011</h2>
+<h2 class="no-num no-toc" id=work-in-progress-&mdash;-last-update-2-may-2011>Work in Progress &mdash; Last Update 2 May 2011</h2>
 <dl>
  <dt>Editor
  <dd>Aryeh Gregor &lt;ayg+spec@aryeh.name&gt;
@@ -230,13 +230,19 @@
   <li>Return <var title="">children</var>.
 </ol>
 
-<p>Something is <dfn id=editable>editable</dfn> if either it is an <code class=external data-anolis-spec=domcore><a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#element>Element</a></code> with a <code class=external data-anolis-spec=html title=attr-contenteditable><a href=http://www.whatwg.org/html/#attr-contenteditable>contenteditable</a></code>
-attribute set to the true state; or it is a <code class=external data-anolis-spec=domcore><a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#document>Document</a></code> whose <code class=external data-anolis-spec=html><a href=http://www.whatwg.org/html/#designmode>designMode</a></code> is enabled; or it is a <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-node title=concept-node>node</a> whose
-<a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-tree-parent title=concept-tree-parent>parent</a> is <a href=#editable>editable</a>, but which does not have a <code class=external data-anolis-spec=html title=attr-contenteditable><a href=http://www.whatwg.org/html/#attr-contenteditable>contenteditable</a></code>
-attribute set to the false state.
-
-<p>An <dfn id=editing-host>editing host</dfn> is a <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-node title=concept-node>node</a> that is <a href=#editable>editable</a>, and
-whose <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-tree-parent title=concept-tree-parent>parent</a> is not <a href=#editable>editable</a>.
+<p>An <dfn id=editing-host>editing host</dfn> is a <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-node title=concept-node>node</a> that is either an <code class=external data-anolis-spec=domcore><a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#element>Element</a></code> with
+a <code class=external data-anolis-spec=html title=attr-contenteditable><a href=http://www.whatwg.org/html/#attr-contenteditable>contenteditable</a></code>
+attribute set to the true state, or a <code class=external data-anolis-spec=domcore><a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#document>Document</a></code> whose <code class=external data-anolis-spec=html><a href=http://www.whatwg.org/html/#designmode>designMode</a></code> is enabled.
+
+<p>Something is <dfn id=editable>editable</dfn> if it is a <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-node title=concept-node>node</a> which is not an
+<a href=#editing-host>editing host</a>, does not have a <code class=external data-anolis-spec=html title=attr-contenteditable><a href=http://www.whatwg.org/html/#attr-contenteditable>contenteditable</a></code> attribute set to the false
+state, and whose <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-tree-parent title=concept-tree-parent>parent</a> is an <a href=#editing-host>editing host</a> or
+<a href=#editable>editable</a>.
+
+<p class=note>The algorithms here will generally not remove or alter the
+attributes of any node that is not editable.  Note that an editing host is not
+editable, so authors are assured that editing commands will only modify the
+editing host's contents and not the editing host itself.
 
 <p>A <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-node title=concept-node>node</a> is <dfn id=effectively-contained>effectively contained</dfn> in a <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-range title=concept-range>range</a> if either it
 is <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#contained>contained</a> in the <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-range title=concept-range>range</a>; or it is the <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-range title=concept-range>range</a>'s <a class=external data-anolis-spec=domrange href=http://html5.org/specs/dom-range.html#concept-range-start title=concept-range-start>start</a>
@@ -256,7 +262,7 @@
 be used where only <a class=external data-anolis-spec=html href=http://www.whatwg.org/html/#phrasing-content>phrasing content</a> is expected (not counting unknown or
 obsolete elements, which cannot be used at all); or any <code class=external data-anolis-spec=domcore><a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#element>Element</a></code> whose
 display property computes to something other than "inline", "inline-block", or
-"inline-table"; or any <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-node title=concept-node>node</a> whose <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-tree-parent title=concept-tree-parent>parent</a> is not <a href=#editable>editable</a>.
+"inline-table"; or any <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-node title=concept-node>node</a> that is not <a href=#editable>editable</a>.
 
 <p class=XXX>Currently when we hit an unwrappable element, we ignore it and
 alter its children.  Alternatively, if we would otherwise create a span with a
@@ -789,7 +795,7 @@
   <li>Let <var title="">ancestor list</var> be a list of <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-node title=concept-node>nodes</a>, initially empty.
 
   <li>While <var title="">current ancestor</var> is an <a href=#editable>editable</a> <code class=external data-anolis-spec=domcore><a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#element>Element</a></code>
-  and the <a href=#effective-value>effective value</a> of <var title="">property</var> is not <var title="">new
+  and the <a href=#effective-value>effective value</a> of <var title="">command</var> is not <var title="">new
   value</var> on it, append <var title="">current ancestor</var> to <var title="">ancestor
   list</var>, then set <var title="">current ancestor</var> to its <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-tree-parent title=concept-tree-parent>parent</a>.
 
@@ -809,8 +815,9 @@
   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". -->
-  <li>If the <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-tree-parent title=concept-tree-parent>parent</a> of the last member of <var title="">ancestor list</var> is not
-  an <code class=external data-anolis-spec=domcore><a href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#element>Element</a></code>, and <var title="">new value</var> is not null, abort this algorithm.
+  <li>If the <a href=#effective-value>effective value</a> of <var title="">command</var> is not
+  <var title="">new value</var> on the <a class=external data-anolis-spec=domcore href=http://dvcs.w3.org/hg/domcore/raw-file/tip/Overview.html#concept-tree-parent title=concept-tree-parent>parent</a> of the last member of <var title="">ancestor
+  list</var>, and <var title="">new value</var> is not null, abort this algorithm.
 
   <li>While <var title="">ancestor list</var> is not empty:
 
--- a/implementation.js	Sun May 01 15:11:10 2011 -0600
+++ b/implementation.js	Mon May 02 13:24:13 2011 -0600
@@ -400,24 +400,24 @@
 	return children;
 }
 
-// "Something is editable if either it is an Element with a contenteditable
-// attribute set to the true state; or it is a Document whose designMode is
-// enabled; or it is a node whose parent is editable, but which does not have a
-// contenteditable attribute set to the false state."
+// "An editing host is a node that is either an Element with a contenteditable
+// attribute set to the true state, or a Document whose designMode is enabled."
+function isEditingHost(node) {
+	return node
+		&& ((node.nodeType == Node.ELEMENT_NODE && node.contentEditable == "true")
+		|| (node.nodeType == Node.DOCUMENT_NODE && node.designMode == "on"));
+}
+
+// "Something is editable if it is a node which is not an editing host, does
+// not have a contenteditable attribute set to the false state, and whose
+// parent is an editing host or editable."
 function isEditable(node) {
 	// This is slightly a lie, because we're excluding non-HTML elements with
-	// contentEditable attributes.  Maybe we want to, though . . .
-	return (node instanceof Element && node.contentEditable == "true")
-		|| (node instanceof Document && node.designMode == "on")
-		|| (node instanceof Node && node.contentEditable !== "false" && isEditable(node.parentNode));
-}
-
-// "An editing host is a node that is editable, and whose parent is not
-// editable."
-function isEditingHost(node) {
-	return node instanceof Node
-		&& isEditable(node)
-		&& !isEditable(node.parentNode);
+	// contentEditable attributes.
+	return node
+		&& !isEditingHost(node)
+		&& (node.nodeType != Node.ELEMENT_NODE || node.contentEditable != "false")
+		&& (isEditingHost(node.parentNode) || isEditable(node.parentNode));
 }
 
 /**
@@ -456,7 +456,7 @@
 // phrasing content is expected (not counting unknown or obsolete elements,
 // which cannot be used at all); or any Element whose display property computes
 // to something other than "inline", "inline-block", or "inline-table"; or any
-// node whose parent is not editable."
+// node that is not editable."
 //
 // I don't bother implementing this exactly, just well enough for testing.
 function isUnwrappableNode(node) {
@@ -464,7 +464,7 @@
 		return false;
 	}
 
-	if (!isEditable(node.parentNode)) {
+	if (!isEditable(node)) {
 		return true;
 	}
 
@@ -1352,11 +1352,11 @@
 		return;
 	}
 
-	// "If the parent of the last member of ancestor list is not an Element,
-	// and new value is not null, abort this algorithm."
+	// "If the effective value of command is not new value on the parent of
+	// the last member of ancestor list, and new value is not null, abort this
+	// algorithm."
 	if (newValue !== null
-	&& (!ancestorList[ancestorList.length - 1].parentNode
-	|| ancestorList[ancestorList.length - 1].parentNode.nodeType != Node.ELEMENT_NODE)) {
+	&& !valuesEqual(command, getEffectiveValue(ancestorList[ancestorList.length - 1].parentNode, command), newValue)) {
 		return;
 	}
 
--- a/source.html	Sun May 01 15:11:10 2011 -0600
+++ b/source.html	Mon May 02 13:24:13 2011 -0600
@@ -219,16 +219,21 @@
   <li>Return <var>children</var>.
 </ol>
 
-<p>Something is <dfn>editable</dfn> if either it is an [[element]] with a <code
-data-anolis-spec=html title=attr-contenteditable>contenteditable</code>
-attribute set to the true state; or it is a [[document]] whose <code
-data-anolis-spec=html>designMode</code> is enabled; or it is a [[node]] whose
-[[parent]] is <span>editable</span>, but which does not have a <code
-data-anolis-spec=html title=attr-contenteditable>contenteditable</code>
-attribute set to the false state.
-
-<p>An <dfn>editing host</dfn> is a [[node]] that is <span>editable</span>, and
-whose [[parent]] is not <span>editable</span>.
+<p>An <dfn>editing host</dfn> is a [[node]] that is either an [[element]] with
+a <code data-anolis-spec=html title=attr-contenteditable>contenteditable</code>
+attribute set to the true state, or a [[document]] whose <code
+data-anolis-spec=html>designMode</code> is enabled.
+
+<p>Something is <dfn>editable</dfn> if it is a [[node]] which is not an
+<span>editing host</span>, does not have a <code data-anolis-spec=html
+title=attr-contenteditable>contenteditable</code> attribute set to the false
+state, and whose [[parent]] is an <span>editing host</span> or
+<span>editable</span>.
+
+<p class=note>The algorithms here will generally not remove or alter the
+attributes of any node that is not editable.  Note that an editing host is not
+editable, so authors are assured that editing commands will only modify the
+editing host's contents and not the editing host itself.
 
 <p>A [[node]] is <dfn>effectively contained</dfn> in a [[range]] if either it
 is [[contained]] in the [[range]]; or it is the [[range]]'s [[rangestart]]
@@ -248,7 +253,7 @@
 be used where only [[phrasingcontent]] is expected (not counting unknown or
 obsolete elements, which cannot be used at all); or any [[element]] whose
 display property computes to something other than "inline", "inline-block", or
-"inline-table"; or any [[node]] whose [[parent]] is not <span>editable</span>.
+"inline-table"; or any [[node]] that is not <span>editable</span>.
 
 <p class=XXX>Currently when we hit an unwrappable element, we ignore it and
 alter its children.  Alternatively, if we would otherwise create a span with a
@@ -785,7 +790,7 @@
   <li>Let <var>ancestor list</var> be a list of [[nodes]], initially empty.
 
   <li>While <var>current ancestor</var> is an <span>editable</span> [[element]]
-  and the <span>effective value</span> of <var>property</var> is not <var>new
+  and the <span>effective value</span> of <var>command</var> is not <var>new
   value</var> on it, append <var>current ancestor</var> to <var>ancestor
   list</var>, then set <var>current ancestor</var> to its [[parent]].
 
@@ -805,8 +810,9 @@
   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". -->
-  <li>If the [[parent]] of the last member of <var>ancestor list</var> is not
-  an [[element]], and <var>new value</var> is not null, abort this algorithm.
+  <li>If the <span>effective value</span> of <var>command</var> is not
+  <var>new value</var> on the [[parent]] of the last member of <var>ancestor
+  list</var>, and <var>new value</var> is not null, abort this algorithm.
 
   <li>While <var>ancestor list</var> is not empty: