Commit reorganization of implementation.js too
authorAryeh Gregor <AryehGregor+gitcommit@gmail.com>
Sun, 10 Jul 2011 09:40:21 -0600
changeset 380 afbcc0a2a352
parent 379 b534abd08917
child 381 421087165b64
Commit reorganization of implementation.js too

Forgot to commit this when I reorganized source.html.
implementation.js
--- a/implementation.js	Sun Jul 10 09:22:10 2011 -0600
+++ b/implementation.js	Sun Jul 10 09:40:21 2011 -0600
@@ -679,6 +679,27 @@
 		&& (typeof tags == "undefined" || tags.indexOf(node.tagName) != -1);
 }
 
+// "A prohibited paragraph child name is "address", "article", "aside",
+// "blockquote", "caption", "center", "col", "colgroup", "dd", "details",
+// "dir", "div", "dl", "dt", "fieldset", "figcaption", "figure", "footer",
+// "form", "h1", "h2", "h3", "h4", "h5", "h6", "header", "hgroup", "hr", "li",
+// "listing", "menu", "nav", "ol", "p", "plaintext", "pre", "section",
+// "summary", "table", "tbody", "td", "tfoot", "th", "thead", "tr", "ul", or
+// "xmp"."
+var prohibitedParagraphChildNames = ["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",
+	"xmp"];
+
+// "A prohibited paragraph child is an HTML element whose local name is a
+// prohibited paragraph child name."
+function isProhibitedParagraphChild(node) {
+	return isHtmlElement(node, prohibitedParagraphChildNames);
+}
+
 // "An inline node is either a Text node, or an Element whose "display"
 // property computes to "inline", "inline-block", or "inline-table"."
 function isInlineNode(node) {
@@ -1074,52 +1095,6 @@
 	removeExtraneousLineBreaksAtTheEndOf(node);
 }
 
-function followsLineBreak(node) {
-	// "Let offset be zero."
-	var offset = 0;
-
-	// "While offset is zero, set offset to the index of node and then set node
-	// to its parent."
-	while (offset == 0) {
-		offset = getNodeIndex(node);
-		node = node.parentNode;
-	}
-
-	// "Let range be a range with start and end (node, offset)."
-	var range = document.createRange();
-	range.setStart(node, offset);
-
-	// "Block-extend range, and let new range be the result."
-	var newRange = blockExtend(range);
-
-	// "Return false if new range's start is before (node, offset), true
-	// otherwise."
-	return getPosition(newRange.startContainer, newRange.startOffset, node, offset) != "before";
-}
-
-function precedesLineBreak(node) {
-	// "Let offset be the length of node."
-	var offset = getNodeLength(node);
-
-	// "While offset is the length of node, set offset to one plus the index of
-	// node and then set node to its parent."
-	while (offset == getNodeLength(node)) {
-		offset = 1 + getNodeIndex(node);
-		node = node.parentNode;
-	}
-
-	// "Let range be a range with start and end (node, offset)."
-	var range = document.createRange();
-	range.setStart(node, offset);
-
-	// "Block-extend range, and let new range be the result."
-	var newRange = blockExtend(range);
-
-	// "Return false if new range's end is after (node, offset), true
-	// otherwise."
-	return getPosition(newRange.endContainer, newRange.endOffset, node, offset) != "after";
-}
-
 //@}
 
 ///// Wrapping a list of nodes /////
@@ -1273,6 +1248,164 @@
 
 //@}
 
+///// Allowed children /////
+//@{
+
+function isAllowedChild(child, parent_) {
+	// "If parent is "colgroup", "table", "tbody", "tfoot", "thead", "tr", or
+	// an HTML element with local name equal to one of those, and child is a
+	// Text node whose data does not consist solely of space characters, return
+	// false."
+	if ((["colgroup", "table", "tbody", "tfoot", "thead", "tr"].indexOf(parent_) != -1
+	|| isHtmlElement(parent_, ["colgroup", "table", "tbody", "tfoot", "thead", "tr"]))
+	&& typeof child == "object"
+	&& child.nodeType == Node.TEXT_NODE
+	&& !/^[ \t\n\f\r]*$/.test(child.data)) {
+		return false;
+	}
+
+	// "If parent is "script", "style", "plaintext", or "xmp", or an HTML
+	// element with local name equal to one of those, and child is not a Text
+	// node, return false."
+	if ((["script", "style", "plaintext", "xmp"].indexOf(parent_) != -1
+	|| isHtmlElement(parent_, ["script", "style", "plaintext", "xmp"]))
+	&& (typeof child != "object" || child.nodeType != Node.TEXT_NODE)) {
+		return false;
+	}
+
+	// "If child is a Document, DocumentFragment, or DocumentType, return
+	// false."
+	if (typeof child == "object"
+	&& (child.nodeType == Node.DOCUMENT_NODE
+	|| child.nodeType == Node.DOCUMENT_FRAGMENT_NODE
+	|| child.nodeType == Node.DOCUMENT_TYPE_NODE)) {
+		return false;
+	}
+
+	// "If child is an HTML element, set child to the local name of child."
+	if (isHtmlElement(child)) {
+		child = child.tagName.toLowerCase();
+	}
+
+	// "If child is not a string, return true."
+	if (typeof child != "string") {
+		return true;
+	}
+
+	// "If parent is an HTML element:"
+	if (isHtmlElement(parent_)) {
+		// "If child is "a", and parent or some ancestor of parent is an a,
+		// return false."
+		//
+		// "If child is a prohibited paragraph child name and parent or some
+		// ancestor of parent is a p or element with inline contents, return
+		// false."
+		//
+		// "If child is "h1", "h2", "h3", "h4", "h5", or "h6", and parent or
+		// some ancestor of parent is an HTML element with local name "h1",
+		// "h2", "h3", "h4", "h5", or "h6", return false."
+		var ancestor = parent_;
+		while (ancestor) {
+			if (child == "a" && isHtmlElement(ancestor, "a")) {
+				return false;
+			}
+			if (prohibitedParagraphChildNames.indexOf(child) != -1
+			&& (isHtmlElement(ancestor, "p")
+			|| isElementWithInlineContents(ancestor))) {
+				return false;
+			}
+			if (/^h[1-6]$/.test(child)
+			&& isHtmlElement(ancestor)
+			&& /^H[1-6]$/.test(ancestor.tagName)) {
+				return false;
+			}
+			ancestor = ancestor.parentNode;
+		}
+
+		// "Let parent be the local name of parent."
+		parent_ = parent_.tagName.toLowerCase();
+	}
+
+	// "If parent is an Element or DocumentFragment, return true."
+	if (typeof parent_ == "object"
+	&& (parent_.nodeType == Node.ELEMENT_NODE
+	|| parent_.nodeType == Node.DOCUMENT_FRAGMENT_NODE)) {
+		return true;
+	}
+
+	// "If parent is not a string, return false."
+	if (typeof parent_ != "string") {
+		return false;
+	}
+
+	// "If parent is in the following table, then return true if child is
+	// listed as an allowed child, and false otherwise."
+	switch (parent_) {
+		case "colgroup":
+			return child == "col";
+		case "table":
+			return ["caption", "col", "colgroup", "tbody", "td", "tfoot", "th", "thead", "tr"].indexOf(child) != -1;
+		case "tbody":
+		case "thead":
+		case "tfoot":
+			return ["td", "th", "tr"].indexOf(child) != -1;
+		case "tr":
+			return ["td", "th"].indexOf(child) != -1;
+		case "dl":
+			return ["dt", "dd"].indexOf(child) != -1;
+		case "dir":
+		case "ol":
+		case "ul":
+			return ["dir", "li", "ol", "ul"].indexOf(child) != -1;
+		case "hgroup":
+			return /^h[1-6]$/.test(child);
+	}
+
+	// "If child is "body", "caption", "col", "colgroup", "frame", "frameset",
+	// "head", "html", "tbody", "td", "tfoot", "th", "thead", or "tr", return
+	// false."
+	if (["body", "caption", "col", "colgroup", "frame", "frameset", "head",
+	"html", "tbody", "td", "tfoot", "th", "thead", "tr"].indexOf(child) != -1) {
+		return false;
+	}
+
+	// "If child is "dd" or "dt" and parent is not "dl", return false."
+	if (["dd", "dt"].indexOf(child) != -1
+	&& parent_ != "dl") {
+		return false;
+	}
+
+	// "If child is "li" and parent is not "ol" or "ul", return false."
+	if (child == "li"
+	&& parent_ != "ol"
+	&& parent_ != "ul") {
+		return false;
+	}
+
+	// "If parent is in the following table and child is listed as a prohibited
+	// child, return false."
+	var table = [
+		[["a"], ["a"]],
+		[["dd", "dt"], ["dd", "dt"]],
+		[["h1", "h2", "h3", "h4", "h5", "h6"], ["h1", "h2", "h3", "h4", "h5", "h6"]],
+		[["li"], ["li"]],
+		[["nobr"], ["nobr"]],
+		[["p"].concat(namesOfElementsWithInlineContents), prohibitedParagraphChildNames],
+		[["td", "th"], ["caption", "col", "colgroup", "tbody", "td", "tfoot", "th", "thead", "tr"]],
+	];
+	for (var i = 0; i < table.length; i++) {
+		if (table[i][0].indexOf(parent_) != -1
+		&& table[i][1].indexOf(child) != -1) {
+			return false;
+		}
+	}
+
+	// "Return true."
+	return true;
+}
+
+//@}
+
 
 //////////////////////////////////////
 ///// Inline formatting commands /////
@@ -3180,27 +3313,6 @@
 ///// Block formatting command definitions /////
 //@{
 
-// "A prohibited paragraph child name is "address", "article", "aside",
-// "blockquote", "caption", "center", "col", "colgroup", "dd", "details",
-// "dir", "div", "dl", "dt", "fieldset", "figcaption", "figure", "footer",
-// "form", "h1", "h2", "h3", "h4", "h5", "h6", "header", "hgroup", "hr", "li",
-// "listing", "menu", "nav", "ol", "p", "plaintext", "pre", "section",
-// "summary", "table", "tbody", "td", "tfoot", "th", "thead", "tr", "ul", or
-// "xmp"."
-var prohibitedParagraphChildNames = ["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",
-	"xmp"];
-
-// "A prohibited paragraph child is an HTML element whose local name is a
-// prohibited paragraph child name."
-function isProhibitedParagraphChild(node) {
-	return isHtmlElement(node, prohibitedParagraphChildNames);
-}
-
 // "A name of an element with inline contents is "a", "abbr", "b", "bdi",
 // "bdo", "cite", "code", "dfn", "em", "h1", "h2", "h3", "h4", "h5", "h6", "i",
 // "kbd", "mark", "pre", "q", "rp", "rt", "ruby", "s", "samp", "small", "span",
@@ -3346,179 +3458,6 @@
 	}
 }
 
-function splitParent(nodeList) {
-	// "Let original parent be the parent of the first member of node list."
-	var originalParent = nodeList[0].parentNode;
-
-	// "If original parent is not editable or its parent is null, do nothing
-	// and abort these steps."
-	if (!isEditable(originalParent)
-	|| !originalParent.parentNode) {
-		return;
-	}
-
-	// "If the first child of original parent is in node list, remove
-	// extraneous line breaks before original parent."
-	if (nodeList.indexOf(originalParent.firstChild) != -1) {
-		removeExtraneousLineBreaksBefore(originalParent);
-	}
-
-	// "If the first child of original parent is in node list, and original
-	// parent follows a line break, set follows line break to true. Otherwise,
-	// set follows line break to false."
-	var followsLineBreak_ = nodeList.indexOf(originalParent.firstChild) != -1
-		&& followsLineBreak(originalParent);
-
-	// "If the last child of original parent is in node list, and original
-	// parent precedes a line break, set precedes line break to true.
-	// Otherwise, set precedes line break to false."
-	var precedesLineBreak_ = nodeList.indexOf(originalParent.lastChild) != -1
-		&& precedesLineBreak(originalParent);
-
-	// "If the first child of original parent is not in node list, but its last
-	// child is:"
-	if (nodeList.indexOf(originalParent.firstChild) == -1
-	&& nodeList.indexOf(originalParent.lastChild) != -1) {
-		// "For each node in node list, in reverse order, insert node into the
-		// parent of original parent immediately after original parent,
-		// preserving ranges."
-		for (var i = nodeList.length - 1; i >= 0; i--) {
-			movePreservingRanges(nodeList[i], originalParent.parentNode, 1 + getNodeIndex(originalParent));
-		}
-
-		// "If precedes line break is true, and the last member of node list
-		// does not precede a line break, call createElement("br") on the
-		// context object and insert the result immediately after the last
-		// member of node list."
-		if (precedesLineBreak_
-		&& !precedesLineBreak(nodeList[nodeList.length - 1])) {
-			nodeList[nodeList.length - 1].parentNode.insertBefore(document.createElement("br"), nodeList[nodeList.length - 1].nextSibling);
-		}
-
-		// "Remove extraneous line breaks at the end of original parent."
-		removeExtraneousLineBreaksAtTheEndOf(originalParent);
-
-		// "Abort these steps."
-		return;
-	}
-
-	// "If the first child of original parent is not in node list:"
-	if (nodeList.indexOf(originalParent.firstChild) == -1) {
-		// "Let cloned parent be the result of calling cloneNode(false) on
-		// original parent."
-		var clonedParent = originalParent.cloneNode(false);
-
-		// "If original parent has an id attribute, unset it."
-		originalParent.removeAttribute("id");
-
-		// "Insert cloned parent into the parent of original parent immediately
-		// before original parent."
-		originalParent.parentNode.insertBefore(clonedParent, originalParent);
-
-		// "While the previousSibling of the first member of node list is not
-		// null, append the first child of original parent as the last child of
-		// cloned parent, preserving ranges."
-		while (nodeList[0].previousSibling) {
-			movePreservingRanges(originalParent.firstChild, clonedParent, clonedParent.childNodes.length);
-		}
-	}
-
-	// "For each node in node list, insert node into the parent of original
-	// parent immediately before original parent, preserving ranges."
-	for (var i = 0; i < nodeList.length; i++) {
-		movePreservingRanges(nodeList[i], originalParent.parentNode, getNodeIndex(originalParent));
-	}
-
-	// "If follows line break is true, and the first member of node list does
-	// not follow a line break, call createElement("br") on the context object
-	// and insert the result immediately before the first member of node list."
-	if (followsLineBreak_
-	&& !followsLineBreak(nodeList[0])) {
-		nodeList[0].parentNode.insertBefore(document.createElement("br"), nodeList[0]);
-	}
-
-	// "If the last member of node list is an inline node other than a br, and
-	// the first child of original parent is a br, and original parent is not
-	// an inline node, remove the first child of original parent from original
-	// parent."
-	if (isInlineNode(nodeList[nodeList.length - 1])
-	&& !isHtmlElement(nodeList[nodeList.length - 1], "br")
-	&& isHtmlElement(originalParent.firstChild, "br")
-	&& !isInlineNode(originalParent)) {
-		originalParent.removeChild(originalParent.firstChild);
-	}
-
-	// "If original parent has no children:"
-	if (!originalParent.hasChildNodes()) {
-		// "Remove original parent from its parent."
-		originalParent.parentNode.removeChild(originalParent);
-
-		// "If precedes line break is true, and the last member of node list
-		// does not precede a line break, call createElement("br") on the
-		// context object and insert the result immediately after the last
-		// member of node list."
-		if (precedesLineBreak_
-		&& !precedesLineBreak(nodeList[nodeList.length - 1])) {
-			nodeList[nodeList.length - 1].parentNode.insertBefore(document.createElement("br"), nodeList[nodeList.length - 1].nextSibling);
-		}
-
-	// "Otherwise, remove extraneous line breaks before original parent."
-	} else {
-		removeExtraneousLineBreaksBefore(originalParent);
-	}
-
-	// "If node list's last member's nextSibling is null, but its parent is not
-	// null, remove extraneous line breaks at the end of node list's last
-	// member's parent."
-	if (!nodeList[nodeList.length - 1].nextSibling
-	&& nodeList[nodeList.length - 1].parentNode) {
-		removeExtraneousLineBreaksAtTheEndOf(nodeList[nodeList.length - 1].parentNode);
-	}
-}
-
-// "To remove a node node while preserving its descendants, split the parent of
-// node's children."
-function removePreservingDescendants(node) {
-	splitParent([].slice.call(node.childNodes));
-}
-
-function indentNodes(nodeList) {
-	// "If node list is empty, do nothing and abort these steps."
-	if (!nodeList.length) {
-		return;
-	}
-
-	// "Let first node be the first member of node list."
-	var firstNode = nodeList[0];
-
-	// "If first node's parent is an ol or ul:"
-	if (isHtmlElement(firstNode.parentNode, ["OL", "UL"])) {
-		// "Let tag be the local name of the parent of first node."
-		var tag = firstNode.parentNode.tagName;
-
-		// "Wrap node list, with sibling criteria matching only HTML elements
-		// with local name tag and new parent instructions returning the result
-		// of calling createElement(tag) on the ownerDocument of first node."
-		wrap(nodeList,
-			function(node) { return isHtmlElement(node, tag) },
-			function() { return firstNode.ownerDocument.createElement(tag) });
-
-		// "Abort these steps."
-		return;
-	}
-
-	// "Wrap node list, with sibling criteria matching any indentation element,
-	// and new parent instructions to return the result of calling
-	// createElement("blockquote") on the ownerDocument of first node. Let new
-	// parent be the result."
-	var newParent = wrap(nodeList,
-		function(node) { return isIndentationElement(node) },
-		function() { return firstNode.ownerDocument.createElement("blockquote") });
-
-	// "Fix disallowed ancestors of new parent."
-	fixDisallowedAncestors(newParent);
-}
-
 function normalizeSublists(item) {
 	// "If item is not an li or it is not editable or its parent is not
 	// editable, abort these steps."
@@ -3635,224 +3574,6 @@
 	return "none";
 }
 
-function canonicalSpaceSequence(n, nonBreakingStart, nonBreakingEnd) {
-	// "If n is zero, return the empty string."
-	if (n == 0) {
-		return "";
-	}
-
-	// "If n is one and both non-breaking start and non-breaking end are false,
-	// return a single space (U+0020)."
-	if (n == 1 && !nonBreakingStart && !nonBreakingEnd) {
-		return " ";
-	}
-
-	// "If n is one, return a single non-breaking space (U+00A0)."
-	if (n == 1) {
-		return "\xa0";
-	}
-
-	// "Let buffer be the empty string."
-	var buffer = "";
-
-	// "If non-breaking start is true, let repeated pair be U+00A0 U+0020.
-	// Otherwise, let it be U+0020 U+00A0."
-	var repeatedPair;
-	if (nonBreakingStart) {
-		repeatedPair = "\xa0 ";
-	} else {
-		repeatedPair = " \xa0";
-	}
-
-	// "While n is greater than three, append repeated pair to buffer and
-	// subtract two from n."
-	while (n > 3) {
-		buffer += repeatedPair;
-		n -= 2;
-	}
-
-	// "If n is three, append a three-element string to buffer depending on
-	// non-breaking start and non-breaking end:"
-	if (n == 3) {
-		buffer +=
-			!nonBreakingStart && !nonBreakingEnd ? " \xa0 "
-			: nonBreakingStart && !nonBreakingEnd ? "\xa0\xa0 "
-			: !nonBreakingStart && nonBreakingEnd ? " \xa0\xa0"
-			: nonBreakingStart && nonBreakingEnd ? "\xa0 \xa0"
-			: "impossible";
-
-	// "Otherwise, append a two-element string to buffer depending on
-	// non-breaking start and non-breaking end:"
-	} else {
-		buffer +=
-			!nonBreakingStart && !nonBreakingEnd ? "\xa0 "
-			: nonBreakingStart && !nonBreakingEnd ? "\xa0 "
-			: !nonBreakingStart && nonBreakingEnd ? " \xa0"
-			: nonBreakingStart && nonBreakingEnd ? "\xa0\xa0"
-			: "impossible";
-	}
-
-	// "Return buffer."
-	return buffer;
-}
-
-function canonicalizeWhitespace(node, offset) {
-	// "If node is neither editable nor an editing host, abort these steps."
-	if (!isEditable(node) && !isEditingHost(node)) {
-		return;
-	}
-
-	// "Let start node equal node and let start offset equal offset."
-	var startNode = node;
-	var startOffset = offset;
-
-	// "Repeat the following steps:"
-	while (true) {
-		// "If start node has a child in the same editing host with index start
-		// offset minus one, set start node to that child, then set start
-		// offset to start node's length."
-		if (0 <= startOffset - 1
-		&& inSameEditingHost(startNode, startNode.childNodes[startOffset - 1])) {
-			startNode = startNode.childNodes[startOffset - 1];
-			startOffset = getNodeLength(startNode);
-
-		// "Otherwise, if start offset is zero and start node does not follow a
-		// line break and start node's parent is in the same editing host, set
-		// start offset to start node's index, then set start node to its
-		// parent."
-		} else if (startOffset == 0
-		&& !followsLineBreak(startNode)
-		&& inSameEditingHost(startNode, startNode.parentNode)) {
-			startOffset = getNodeIndex(startNode);
-			startNode = startNode.parentNode;
-
-		// "Otherwise, if start node is a Text node and its parent's computed
-		// value for "white-space" is neither "pre" nor "pre-wrap" and start
-		// offset is not zero and the (start offset − 1)st element of start
-		// node's data is a space (0x0020) or non-breaking space (0x00A0),
-		// subtract one from start offset."
-		} else if (startNode.nodeType == Node.TEXT_NODE
-		&& ["pre", "pre-wrap"].indexOf(getComputedStyle(startNode.parentNode).whiteSpace) == -1
-		&& startOffset != 0
-		&& /[ \xa0]/.test(startNode.data[startOffset - 1])) {
-			startOffset--;
-
-		// "Otherwise, break from this loop."
-		} else {
-			break;
-		}
-	}
-
-	// "Let end node equal start node and end offset equal start offset."
-	var endNode = startNode;
-	var endOffset = startOffset;
-
-	// "Let length equal zero."
-	var length = 0;
-
-	// "Let follows space be false."
-	var followsSpace = false;
-
-	// "Repeat the following steps:"
-	while (true) {
-		// "If end node has a child in the same editing host with index end
-		// offset, set end node to that child, then set end offset to zero."
-		if (endOffset < endNode.childNodes.length
-		&& inSameEditingHost(endNode, endNode.childNodes[endOffset])) {
-			endNode = endNode.childNodes[endOffset];
-			endOffset = 0;
-
-		// "Otherwise, if end offset is end node's length and end node does not
-		// precede a line break and end node's parent is in the same editing
-		// host, set end offset to one plus end node's index, then set end node
-		// to its parent."
-		} else if (endOffset == getNodeLength(endNode)
-		&& !precedesLineBreak(endNode)
-		&& inSameEditingHost(endNode, endNode.parentNode)) {
-			endOffset = 1 + getNodeIndex(endNode);
-			endNode = endNode.parentNode;
-
-		// "Otherwise, if end node is a Text node and its parent's computed
-		// value for "white-space" is neither "pre" nor "pre-wrap" and end
-		// offset is not end node's length and the end offsetth element of
-		// end node's data is a space (0x0020) or non-breaking space (0x00A0):"
-		} else if (endNode.nodeType == Node.TEXT_NODE
-		&& ["pre", "pre-wrap"].indexOf(getComputedStyle(endNode.parentNode).whiteSpace) == -1
-		&& endOffset != getNodeLength(endNode)
-		&& /[ \xa0]/.test(endNode.data[endOffset])) {
-			// "If follows space is true and the end offsetth element of end
-			// node's data is a space (0x0020), call deleteData(end offset, 1)
-			// on end node, then continue this loop from the beginning."
-			if (followsSpace
-			&& " " == endNode.data[endOffset]) {
-				endNode.deleteData(endOffset, 1);
-				continue;
-			}
-
-			// "Set follows space to true if the end offsetth element of end
-			// node's data is a space (0x0020), false otherwise."
-			followsSpace = " " == endNode.data[endOffset];
-
-			// "Add one to end offset."
-			endOffset++;
-
-			// "Add one to length."
-			length++;
-
-		// "Otherwise, break from this loop."
-		} else {
-			break;
-		}
-	}
-
-	// "Let replacement whitespace be the canonical space sequence of length
-	// length. non-breaking start is true if start offset is zero and start
-	// node follows a line break, and false otherwise. non-breaking end is true
-	// if end offset is end node's length and end node precedes a line break,
-	// and false otherwise."
-	var replacementWhitespace = canonicalSpaceSequence(length,
-		startOffset == 0 && followsLineBreak(startNode),
-		endOffset == getNodeLength(endNode) && precedesLineBreak(endNode));
-
-	// "While (start node, start offset) is before (end node, end offset):"
-	while (getPosition(startNode, startOffset, endNode, endOffset) == "before") {
-		// "If start node has a child with index start offset, set start node
-		// to that child, then set start offset to zero."
-		if (startOffset < startNode.childNodes.length) {
-			startNode = startNode.childNodes[startOffset];
-			startOffset = 0;
-
-		// "Otherwise, if start node is not a Text node or if start offset is
-		// start node's length, set start offset to one plus start node's
-		// index, then set start node to its parent."
-		} else if (startNode.nodeType != Node.TEXT_NODE
-		|| startOffset == getNodeLength(startNode)) {
-			startOffset = 1 + getNodeIndex(startNode);
-			startNode = startNode.parentNode;
-
-		// "Otherwise:"
-		} else {
-			// "Remove the first element from replacement whitespace, and let
-			// element be that element."
-			var element = replacementWhitespace[0];
-			replacementWhitespace = replacementWhitespace.slice(1);
-
-			// "If element is not the same as the start offsetth element of
-			// start node's data:"
-			if (element != startNode.data[startOffset]) {
-				// "Call insertData(start offset, element) on start node."
-				startNode.insertData(startOffset, element);
-
-				// "Call deleteData(start offset + 1, 1) on start node."
-				startNode.deleteData(startOffset + 1, 1);
-			}
-
-			// "Add one to start offset."
-			startOffset++;
-		}
-	}
-}
-
 function getAlignmentValue(node) {
 	// "While node is neither null nor an Element, or it is an Element but its
 	// "display" property has computed value "inline" or "none", set node to
@@ -3874,164 +3595,6 @@
 }
 //@}
 
-///// Allowed children /////
-//@{
-
-function isAllowedChild(child, parent_) {
-	// "If parent is "colgroup", "table", "tbody", "tfoot", "thead", "tr", or
-	// an HTML element with local name equal to one of those, and child is a
-	// Text node whose data does not consist solely of space characters, return
-	// false."
-	if ((["colgroup", "table", "tbody", "tfoot", "thead", "tr"].indexOf(parent_) != -1
-	|| isHtmlElement(parent_, ["colgroup", "table", "tbody", "tfoot", "thead", "tr"]))
-	&& typeof child == "object"
-	&& child.nodeType == Node.TEXT_NODE
-	&& !/^[ \t\n\f\r]*$/.test(child.data)) {
-		return false;
-	}
-
-	// "If parent is "script", "style", "plaintext", or "xmp", or an HTML
-	// element with local name equal to one of those, and child is not a Text
-	// node, return false."
-	if ((["script", "style", "plaintext", "xmp"].indexOf(parent_) != -1
-	|| isHtmlElement(parent_, ["script", "style", "plaintext", "xmp"]))
-	&& (typeof child != "object" || child.nodeType != Node.TEXT_NODE)) {
-		return false;
-	}
-
-	// "If child is a Document, DocumentFragment, or DocumentType, return
-	// false."
-	if (typeof child == "object"
-	&& (child.nodeType == Node.DOCUMENT_NODE
-	|| child.nodeType == Node.DOCUMENT_FRAGMENT_NODE
-	|| child.nodeType == Node.DOCUMENT_TYPE_NODE)) {
-		return false;
-	}
-
-	// "If child is an HTML element, set child to the local name of child."
-	if (isHtmlElement(child)) {
-		child = child.tagName.toLowerCase();
-	}
-
-	// "If child is not a string, return true."
-	if (typeof child != "string") {
-		return true;
-	}
-
-	// "If parent is an HTML element:"
-	if (isHtmlElement(parent_)) {
-		// "If child is "a", and parent or some ancestor of parent is an a,
-		// return false."
-		//
-		// "If child is a prohibited paragraph child name and parent or some
-		// ancestor of parent is a p or element with inline contents, return
-		// false."
-		//
-		// "If child is "h1", "h2", "h3", "h4", "h5", or "h6", and parent or
-		// some ancestor of parent is an HTML element with local name "h1",
-		// "h2", "h3", "h4", "h5", or "h6", return false."
-		var ancestor = parent_;
-		while (ancestor) {
-			if (child == "a" && isHtmlElement(ancestor, "a")) {
-				return false;
-			}
-			if (prohibitedParagraphChildNames.indexOf(child) != -1
-			&& (isHtmlElement(ancestor, "p")
-			|| isElementWithInlineContents(ancestor))) {
-				return false;
-			}
-			if (/^h[1-6]$/.test(child)
-			&& isHtmlElement(ancestor)
-			&& /^H[1-6]$/.test(ancestor.tagName)) {
-				return false;
-			}
-			ancestor = ancestor.parentNode;
-		}
-
-		// "Let parent be the local name of parent."
-		parent_ = parent_.tagName.toLowerCase();
-	}
-
-	// "If parent is an Element or DocumentFragment, return true."
-	if (typeof parent_ == "object"
-	&& (parent_.nodeType == Node.ELEMENT_NODE
-	|| parent_.nodeType == Node.DOCUMENT_FRAGMENT_NODE)) {
-		return true;
-	}
-
-	// "If parent is not a string, return false."
-	if (typeof parent_ != "string") {
-		return false;
-	}
-
-	// "If parent is in the following table, then return true if child is
-	// listed as an allowed child, and false otherwise."
-	switch (parent_) {
-		case "colgroup":
-			return child == "col";
-		case "table":
-			return ["caption", "col", "colgroup", "tbody", "td", "tfoot", "th", "thead", "tr"].indexOf(child) != -1;
-		case "tbody":
-		case "thead":
-		case "tfoot":
-			return ["td", "th", "tr"].indexOf(child) != -1;
-		case "tr":
-			return ["td", "th"].indexOf(child) != -1;
-		case "dl":
-			return ["dt", "dd"].indexOf(child) != -1;
-		case "dir":
-		case "ol":
-		case "ul":
-			return ["dir", "li", "ol", "ul"].indexOf(child) != -1;
-		case "hgroup":
-			return /^h[1-6]$/.test(child);
-	}
-
-	// "If child is "body", "caption", "col", "colgroup", "frame", "frameset",
-	// "head", "html", "tbody", "td", "tfoot", "th", "thead", or "tr", return
-	// false."
-	if (["body", "caption", "col", "colgroup", "frame", "frameset", "head",
-	"html", "tbody", "td", "tfoot", "th", "thead", "tr"].indexOf(child) != -1) {
-		return false;
-	}
-
-	// "If child is "dd" or "dt" and parent is not "dl", return false."
-	if (["dd", "dt"].indexOf(child) != -1
-	&& parent_ != "dl") {
-		return false;
-	}
-
-	// "If child is "li" and parent is not "ol" or "ul", return false."
-	if (child == "li"
-	&& parent_ != "ol"
-	&& parent_ != "ul") {
-		return false;
-	}
-
-	// "If parent is in the following table and child is listed as a prohibited
-	// child, return false."
-	var table = [
-		[["a"], ["a"]],
-		[["dd", "dt"], ["dd", "dt"]],
-		[["h1", "h2", "h3", "h4", "h5", "h6"], ["h1", "h2", "h3", "h4", "h5", "h6"]],
-		[["li"], ["li"]],
-		[["nobr"], ["nobr"]],
-		[["p"].concat(namesOfElementsWithInlineContents), prohibitedParagraphChildNames],
-		[["td", "th"], ["caption", "col", "colgroup", "tbody", "td", "tfoot", "th", "thead", "tr"]],
-	];
-	for (var i = 0; i < table.length; i++) {
-		if (table[i][0].indexOf(parent_) != -1
-		&& table[i][1].indexOf(child) != -1) {
-			return false;
-		}
-	}
-
-	// "Return true."
-	return true;
-}
-
-//@}
-
 ///// Block-extending a range /////
 //@{
 
@@ -4161,6 +3724,52 @@
 	return newRange;
 }
 
+function followsLineBreak(node) {
+	// "Let offset be zero."
+	var offset = 0;
+
+	// "While offset is zero, set offset to the index of node and then set node
+	// to its parent."
+	while (offset == 0) {
+		offset = getNodeIndex(node);
+		node = node.parentNode;
+	}
+
+	// "Let range be a range with start and end (node, offset)."
+	var range = document.createRange();
+	range.setStart(node, offset);
+
+	// "Block-extend range, and let new range be the result."
+	var newRange = blockExtend(range);
+
+	// "Return false if new range's start is before (node, offset), true
+	// otherwise."
+	return getPosition(newRange.startContainer, newRange.startOffset, node, offset) != "before";
+}
+
+function precedesLineBreak(node) {
+	// "Let offset be the length of node."
+	var offset = getNodeLength(node);
+
+	// "While offset is the length of node, set offset to one plus the index of
+	// node and then set node to its parent."
+	while (offset == getNodeLength(node)) {
+		offset = 1 + getNodeIndex(node);
+		node = node.parentNode;
+	}
+
+	// "Let range be a range with start and end (node, offset)."
+	var range = document.createRange();
+	range.setStart(node, offset);
+
+	// "Block-extend range, and let new range be the result."
+	var newRange = blockExtend(range);
+
+	// "Return false if new range's end is after (node, offset), true
+	// otherwise."
+	return getPosition(newRange.endContainer, newRange.endOffset, node, offset) != "after";
+}
+
 //@}
 
 ///// Deleting the contents of a range /////
@@ -4609,9 +4218,410 @@
 
 //@}
 
-///// Outdenting a node /////
+///// Splitting a node list's parent /////
 //@{
 
+function splitParent(nodeList) {
+	// "Let original parent be the parent of the first member of node list."
+	var originalParent = nodeList[0].parentNode;
+
+	// "If original parent is not editable or its parent is null, do nothing
+	// and abort these steps."
+	if (!isEditable(originalParent)
+	|| !originalParent.parentNode) {
+		return;
+	}
+
+	// "If the first child of original parent is in node list, remove
+	// extraneous line breaks before original parent."
+	if (nodeList.indexOf(originalParent.firstChild) != -1) {
+		removeExtraneousLineBreaksBefore(originalParent);
+	}
+
+	// "If the first child of original parent is in node list, and original
+	// parent follows a line break, set follows line break to true. Otherwise,
+	// set follows line break to false."
+	var followsLineBreak_ = nodeList.indexOf(originalParent.firstChild) != -1
+		&& followsLineBreak(originalParent);
+
+	// "If the last child of original parent is in node list, and original
+	// parent precedes a line break, set precedes line break to true.
+	// Otherwise, set precedes line break to false."
+	var precedesLineBreak_ = nodeList.indexOf(originalParent.lastChild) != -1
+		&& precedesLineBreak(originalParent);
+
+	// "If the first child of original parent is not in node list, but its last
+	// child is:"
+	if (nodeList.indexOf(originalParent.firstChild) == -1
+	&& nodeList.indexOf(originalParent.lastChild) != -1) {
+		// "For each node in node list, in reverse order, insert node into the
+		// parent of original parent immediately after original parent,
+		// preserving ranges."
+		for (var i = nodeList.length - 1; i >= 0; i--) {
+			movePreservingRanges(nodeList[i], originalParent.parentNode, 1 + getNodeIndex(originalParent));
+		}
+
+		// "If precedes line break is true, and the last member of node list
+		// does not precede a line break, call createElement("br") on the
+		// context object and insert the result immediately after the last
+		// member of node list."
+		if (precedesLineBreak_
+		&& !precedesLineBreak(nodeList[nodeList.length - 1])) {
+			nodeList[nodeList.length - 1].parentNode.insertBefore(document.createElement("br"), nodeList[nodeList.length - 1].nextSibling);
+		}
+
+		// "Remove extraneous line breaks at the end of original parent."
+		removeExtraneousLineBreaksAtTheEndOf(originalParent);
+
+		// "Abort these steps."
+		return;
+	}
+
+	// "If the first child of original parent is not in node list:"
+	if (nodeList.indexOf(originalParent.firstChild) == -1) {
+		// "Let cloned parent be the result of calling cloneNode(false) on
+		// original parent."
+		var clonedParent = originalParent.cloneNode(false);
+
+		// "If original parent has an id attribute, unset it."
+		originalParent.removeAttribute("id");
+
+		// "Insert cloned parent into the parent of original parent immediately
+		// before original parent."
+		originalParent.parentNode.insertBefore(clonedParent, originalParent);
+
+		// "While the previousSibling of the first member of node list is not
+		// null, append the first child of original parent as the last child of
+		// cloned parent, preserving ranges."
+		while (nodeList[0].previousSibling) {
+			movePreservingRanges(originalParent.firstChild, clonedParent, clonedParent.childNodes.length);
+		}
+	}
+
+	// "For each node in node list, insert node into the parent of original
+	// parent immediately before original parent, preserving ranges."
+	for (var i = 0; i < nodeList.length; i++) {
+		movePreservingRanges(nodeList[i], originalParent.parentNode, getNodeIndex(originalParent));
+	}
+
+	// "If follows line break is true, and the first member of node list does
+	// not follow a line break, call createElement("br") on the context object
+	// and insert the result immediately before the first member of node list."
+	if (followsLineBreak_
+	&& !followsLineBreak(nodeList[0])) {
+		nodeList[0].parentNode.insertBefore(document.createElement("br"), nodeList[0]);
+	}
+
+	// "If the last member of node list is an inline node other than a br, and
+	// the first child of original parent is a br, and original parent is not
+	// an inline node, remove the first child of original parent from original
+	// parent."
+	if (isInlineNode(nodeList[nodeList.length - 1])
+	&& !isHtmlElement(nodeList[nodeList.length - 1], "br")
+	&& isHtmlElement(originalParent.firstChild, "br")
+	&& !isInlineNode(originalParent)) {
+		originalParent.removeChild(originalParent.firstChild);
+	}
+
+	// "If original parent has no children:"
+	if (!originalParent.hasChildNodes()) {
+		// "Remove original parent from its parent."
+		originalParent.parentNode.removeChild(originalParent);
+
+		// "If precedes line break is true, and the last member of node list
+		// does not precede a line break, call createElement("br") on the
+		// context object and insert the result immediately after the last
+		// member of node list."
+		if (precedesLineBreak_
+		&& !precedesLineBreak(nodeList[nodeList.length - 1])) {
+			nodeList[nodeList.length - 1].parentNode.insertBefore(document.createElement("br"), nodeList[nodeList.length - 1].nextSibling);
+		}
+
+	// "Otherwise, remove extraneous line breaks before original parent."
+	} else {
+		removeExtraneousLineBreaksBefore(originalParent);
+	}
+
+	// "If node list's last member's nextSibling is null, but its parent is not
+	// null, remove extraneous line breaks at the end of node list's last
+	// member's parent."
+	if (!nodeList[nodeList.length - 1].nextSibling
+	&& nodeList[nodeList.length - 1].parentNode) {
+		removeExtraneousLineBreaksAtTheEndOf(nodeList[nodeList.length - 1].parentNode);
+	}
+}
+
+// "To remove a node node while preserving its descendants, split the parent of
+// node's children."
+function removePreservingDescendants(node) {
+	splitParent([].slice.call(node.childNodes));
+}
+
+//@}
+
+///// Canonical space sequences /////
+//@{
+
+function canonicalSpaceSequence(n, nonBreakingStart, nonBreakingEnd) {
+	// "If n is zero, return the empty string."
+	if (n == 0) {
+		return "";
+	}
+
+	// "If n is one and both non-breaking start and non-breaking end are false,
+	// return a single space (U+0020)."
+	if (n == 1 && !nonBreakingStart && !nonBreakingEnd) {
+		return " ";
+	}
+
+	// "If n is one, return a single non-breaking space (U+00A0)."
+	if (n == 1) {
+		return "\xa0";
+	}
+
+	// "Let buffer be the empty string."
+	var buffer = "";
+
+	// "If non-breaking start is true, let repeated pair be U+00A0 U+0020.
+	// Otherwise, let it be U+0020 U+00A0."
+	var repeatedPair;
+	if (nonBreakingStart) {
+		repeatedPair = "\xa0 ";
+	} else {
+		repeatedPair = " \xa0";
+	}
+
+	// "While n is greater than three, append repeated pair to buffer and
+	// subtract two from n."
+	while (n > 3) {
+		buffer += repeatedPair;
+		n -= 2;
+	}
+
+	// "If n is three, append a three-element string to buffer depending on
+	// non-breaking start and non-breaking end:"
+	if (n == 3) {
+		buffer +=
+			!nonBreakingStart && !nonBreakingEnd ? " \xa0 "
+			: nonBreakingStart && !nonBreakingEnd ? "\xa0\xa0 "
+			: !nonBreakingStart && nonBreakingEnd ? " \xa0\xa0"
+			: nonBreakingStart && nonBreakingEnd ? "\xa0 \xa0"
+			: "impossible";
+
+	// "Otherwise, append a two-element string to buffer depending on
+	// non-breaking start and non-breaking end:"
+	} else {
+		buffer +=
+			!nonBreakingStart && !nonBreakingEnd ? "\xa0 "
+			: nonBreakingStart && !nonBreakingEnd ? "\xa0 "
+			: !nonBreakingStart && nonBreakingEnd ? " \xa0"
+			: nonBreakingStart && nonBreakingEnd ? "\xa0\xa0"
+			: "impossible";
+	}
+
+	// "Return buffer."
+	return buffer;
+}
+
+function canonicalizeWhitespace(node, offset) {
+	// "If node is neither editable nor an editing host, abort these steps."
+	if (!isEditable(node) && !isEditingHost(node)) {
+		return;
+	}
+
+	// "Let start node equal node and let start offset equal offset."
+	var startNode = node;
+	var startOffset = offset;
+
+	// "Repeat the following steps:"
+	while (true) {
+		// "If start node has a child in the same editing host with index start
+		// offset minus one, set start node to that child, then set start
+		// offset to start node's length."
+		if (0 <= startOffset - 1
+		&& inSameEditingHost(startNode, startNode.childNodes[startOffset - 1])) {
+			startNode = startNode.childNodes[startOffset - 1];
+			startOffset = getNodeLength(startNode);
+
+		// "Otherwise, if start offset is zero and start node does not follow a
+		// line break and start node's parent is in the same editing host, set
+		// start offset to start node's index, then set start node to its
+		// parent."
+		} else if (startOffset == 0
+		&& !followsLineBreak(startNode)
+		&& inSameEditingHost(startNode, startNode.parentNode)) {
+			startOffset = getNodeIndex(startNode);
+			startNode = startNode.parentNode;
+
+		// "Otherwise, if start node is a Text node and its parent's computed
+		// value for "white-space" is neither "pre" nor "pre-wrap" and start
+		// offset is not zero and the (start offset − 1)st element of start
+		// node's data is a space (0x0020) or non-breaking space (0x00A0),
+		// subtract one from start offset."
+		} else if (startNode.nodeType == Node.TEXT_NODE
+		&& ["pre", "pre-wrap"].indexOf(getComputedStyle(startNode.parentNode).whiteSpace) == -1
+		&& startOffset != 0
+		&& /[ \xa0]/.test(startNode.data[startOffset - 1])) {
+			startOffset--;
+
+		// "Otherwise, break from this loop."
+		} else {
+			break;
+		}
+	}
+
+	// "Let end node equal start node and end offset equal start offset."
+	var endNode = startNode;
+	var endOffset = startOffset;
+
+	// "Let length equal zero."
+	var length = 0;
+
+	// "Let follows space be false."
+	var followsSpace = false;
+
+	// "Repeat the following steps:"
+	while (true) {
+		// "If end node has a child in the same editing host with index end
+		// offset, set end node to that child, then set end offset to zero."
+		if (endOffset < endNode.childNodes.length
+		&& inSameEditingHost(endNode, endNode.childNodes[endOffset])) {
+			endNode = endNode.childNodes[endOffset];
+			endOffset = 0;
+
+		// "Otherwise, if end offset is end node's length and end node does not
+		// precede a line break and end node's parent is in the same editing
+		// host, set end offset to one plus end node's index, then set end node
+		// to its parent."
+		} else if (endOffset == getNodeLength(endNode)
+		&& !precedesLineBreak(endNode)
+		&& inSameEditingHost(endNode, endNode.parentNode)) {
+			endOffset = 1 + getNodeIndex(endNode);
+			endNode = endNode.parentNode;
+
+		// "Otherwise, if end node is a Text node and its parent's computed
+		// value for "white-space" is neither "pre" nor "pre-wrap" and end
+		// offset is not end node's length and the end offsetth element of
+		// end node's data is a space (0x0020) or non-breaking space (0x00A0):"
+		} else if (endNode.nodeType == Node.TEXT_NODE
+		&& ["pre", "pre-wrap"].indexOf(getComputedStyle(endNode.parentNode).whiteSpace) == -1
+		&& endOffset != getNodeLength(endNode)
+		&& /[ \xa0]/.test(endNode.data[endOffset])) {
+			// "If follows space is true and the end offsetth element of end
+			// node's data is a space (0x0020), call deleteData(end offset, 1)
+			// on end node, then continue this loop from the beginning."
+			if (followsSpace
+			&& " " == endNode.data[endOffset]) {
+				endNode.deleteData(endOffset, 1);
+				continue;
+			}
+
+			// "Set follows space to true if the end offsetth element of end
+			// node's data is a space (0x0020), false otherwise."
+			followsSpace = " " == endNode.data[endOffset];
+
+			// "Add one to end offset."
+			endOffset++;
+
+			// "Add one to length."
+			length++;
+
+		// "Otherwise, break from this loop."
+		} else {
+			break;
+		}
+	}
+
+	// "Let replacement whitespace be the canonical space sequence of length
+	// length. non-breaking start is true if start offset is zero and start
+	// node follows a line break, and false otherwise. non-breaking end is true
+	// if end offset is end node's length and end node precedes a line break,
+	// and false otherwise."
+	var replacementWhitespace = canonicalSpaceSequence(length,
+		startOffset == 0 && followsLineBreak(startNode),
+		endOffset == getNodeLength(endNode) && precedesLineBreak(endNode));
+
+	// "While (start node, start offset) is before (end node, end offset):"
+	while (getPosition(startNode, startOffset, endNode, endOffset) == "before") {
+		// "If start node has a child with index start offset, set start node
+		// to that child, then set start offset to zero."
+		if (startOffset < startNode.childNodes.length) {
+			startNode = startNode.childNodes[startOffset];
+			startOffset = 0;
+
+		// "Otherwise, if start node is not a Text node or if start offset is
+		// start node's length, set start offset to one plus start node's
+		// index, then set start node to its parent."
+		} else if (startNode.nodeType != Node.TEXT_NODE
+		|| startOffset == getNodeLength(startNode)) {
+			startOffset = 1 + getNodeIndex(startNode);
+			startNode = startNode.parentNode;
+
+		// "Otherwise:"
+		} else {
+			// "Remove the first element from replacement whitespace, and let
+			// element be that element."
+			var element = replacementWhitespace[0];
+			replacementWhitespace = replacementWhitespace.slice(1);
+
+			// "If element is not the same as the start offsetth element of
+			// start node's data:"
+			if (element != startNode.data[startOffset]) {
+				// "Call insertData(start offset, element) on start node."
+				startNode.insertData(startOffset, element);
+
+				// "Call deleteData(start offset + 1, 1) on start node."
+				startNode.deleteData(startOffset + 1, 1);
+			}
+
+			// "Add one to start offset."
+			startOffset++;
+		}
+	}
+}
+
+//@}
+
+///// Indenting and outdenting /////
+//@{
+
+function indentNodes(nodeList) {
+	// "If node list is empty, do nothing and abort these steps."
+	if (!nodeList.length) {
+		return;
+	}
+
+	// "Let first node be the first member of node list."
+	var firstNode = nodeList[0];
+
+	// "If first node's parent is an ol or ul:"
+	if (isHtmlElement(firstNode.parentNode, ["OL", "UL"])) {
+		// "Let tag be the local name of the parent of first node."
+		var tag = firstNode.parentNode.tagName;
+
+		// "Wrap node list, with sibling criteria matching only HTML elements
+		// with local name tag and new parent instructions returning the result
+		// of calling createElement(tag) on the ownerDocument of first node."
+		wrap(nodeList,
+			function(node) { return isHtmlElement(node, tag) },
+			function() { return firstNode.ownerDocument.createElement(tag) });
+
+		// "Abort these steps."
+		return;
+	}
+
+	// "Wrap node list, with sibling criteria matching any indentation element,
+	// and new parent instructions to return the result of calling
+	// createElement("blockquote") on the ownerDocument of first node. Let new
+	// parent be the result."
+	var newParent = wrap(nodeList,
+		function(node) { return isIndentationElement(node) },
+		function() { return firstNode.ownerDocument.createElement("blockquote") });
+
+	// "Fix disallowed ancestors of new parent."
+	fixDisallowedAncestors(newParent);
+}
+
 function outdentNode(node) {
 	// "If node is not editable, abort these steps."
 	if (!isEditable(node)) {