Move moved sections in implementation.js too
authorAryeh Gregor <AryehGregor+gitcommit@gmail.com>
Sun, 19 Jun 2011 10:41:54 -0600
changeset 290 443821c02635
parent 289 ab23b599b7f1
child 291 47b1dc8e4e8e
Move moved sections in implementation.js too

So I don't get confused.
implementation.js
--- a/implementation.js	Sun Jun 19 10:37:45 2011 -0600
+++ b/implementation.js	Sun Jun 19 10:41:54 2011 -0600
@@ -660,69 +660,6 @@
 		&& getEditingHostOf(node1) == getEditingHostOf(node2);
 }
 
-// "A prohibited paragraph child name is "address", "article", "aside",
-// "blockquote", "caption", "center", "col", "colgroup", "details", "dd",
-// "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", "details", "dd",
-	"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",
-// "strong", "sub", "sup", "u", "var", "acronym", "listing", "strike", "xmp",
-// "big", "blink", "font", "marquee", "nobr", or "tt"."
-var namesOfElementsWithInlineContents = ["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", "strong", "sub", "sup", "u", "var", "acronym", "listing", "strike",
-	"xmp", "big", "blink", "font", "marquee", "nobr", "tt"];
-
-// "An element with inline contents is an HTML element whose local name is a
-// name of an element with inline contents."
-function isElementWithInlineContents(node) {
-	return isHtmlElement(node, namesOfElementsWithInlineContents);
-}
-
-// "A visible node is a node that either is a prohibited paragraph child, or a
-// Text node whose data is not empty, or a br or img, or any node with a
-// descendant that is a visible node."
-function isVisibleNode(node) {
-	if (!node) {
-		return false;
-	}
-	if (isProhibitedParagraphChild(node)
-	|| (node.nodeType == Node.TEXT_NODE && node.length)
-	|| isHtmlElement(node, ["br", "img"])) {
-		return true;
-	}
-	for (var i = 0; i < node.childNodes.length; i++) {
-		if (isVisibleNode(node.childNodes[i])) {
-			return true;
-		}
-	}
-	return false;
-}
-
-// "An invisible node is a node that is not a visible node."
-function isInvisibleNode(node) {
-	return node && !isVisibleNode(node);
-}
-
 //@}
 
 /////////////////////////////
@@ -732,159 +669,6 @@
 ///// Assorted common algorithms /////
 //@{
 
-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;
-}
-
 function movePreservingRanges(node, newParent, newIndex) {
 	// For convenience, I allow newIndex to be -1 to mean "insert at the end".
 	if (newIndex == -1) {
@@ -1315,437 +1099,6 @@
 
 //@}
 
-///// Deleting the contents of a range /////
-//@{
-
-function deleteContents(node1, offset1, node2, offset2) {
-	var range;
-
-	// We allow passing four arguments instead of one, in which case they're
-	// the start and end points of the range.
-	if (typeof offset1 == "undefined") {
-		range = node1;
-	} else {
-		range = document.createRange();
-		range.setStart(node1, offset1);
-		range.setEnd(node2, offset2);
-	}
-
-	// "If range is null, abort these steps and do nothing."
-	if (!range) {
-		return;
-	}
-
-	// "Let start node, start offset, end node, and end offset be range's start
-	// and end nodes and offsets."
-	var startNode = range.startContainer;
-	var startOffset = range.startOffset;
-	var endNode = range.endContainer;
-	var endOffset = range.endOffset;
-
-	// "While start node has at least one child:"
-	while (startNode.hasChildNodes()) {
-		// "If start offset is start node's length, and start node's parent is
-		// in the same editing host, and start node is not a prohibited
-		// paragraph child, set start offset to one plus the index of start
-		// node, then set start node to its parent and continue this loop from
-		// the beginning."
-		if (startOffset == getNodeLength(startNode)
-		&& inSameEditingHost(startNode, startNode.parentNode)
-		&& !isProhibitedParagraphChild(startNode)) {
-			startOffset = 1 + getNodeIndex(startNode);
-			startNode = startNode.parentNode;
-			continue;
-		}
-
-		// "If start offset is start node's length, break from this loop."
-		if (startOffset == getNodeLength(startNode)) {
-			break;
-		}
-
-		// "Let reference node be the child of start node with index equal to
-		// start offset."
-		var referenceNode = startNode.childNodes[startOffset];
-
-		// "If reference node is a prohibited paragraph child or an Element
-		// with no children, break from this loop."
-		if (isProhibitedParagraphChild(referenceNode)
-		|| (referenceNode.nodeType == Node.ELEMENT_NODE
-		&& !referenceNode.hasChildNodes())) {
-			break;
-		}
-
-		// "Set start node to reference node and start offset to 0."
-		startNode = referenceNode;
-		startOffset = 0;
-	}
-
-	// "While end node has at least one child:"
-	while (endNode.hasChildNodes()) {
-		// "If end offset is 0, and end node's parent is in the same editing
-		// host, and end node is not a prohibited paragraph child, set end
-		// offset to the index of end node, then set end node to its parent and
-		// continue this loop from the beginning."
-		if (endOffset == 0
-		&& inSameEditingHost(endNode, endNode.parentNode)
-		&& !isProhibitedParagraphChild(endNode)) {
-			endOffset = getNodeIndex(endNode);
-			endNode = endNode.parentNode;
-			continue;
-		}
-
-		// "If end offset is 0, break from this loop."
-		if (endOffset == 0) {
-			break;
-		}
-
-		// "Let reference node be the child of end node with index equal to end
-		// offset minus one."
-		var referenceNode = endNode.childNodes[endOffset - 1];
-
-		// "If reference node is a prohibited paragraph child or an Element
-		// with no children, break from this loop."
-		if (isProhibitedParagraphChild(referenceNode)
-		|| (referenceNode.nodeType == Node.ELEMENT_NODE
-		&& !referenceNode.hasChildNodes())) {
-			break;
-		}
-
-		// "Set end node to reference node and end offset to the length of
-		// reference node."
-		endNode = referenceNode;
-		endOffset = getNodeLength(referenceNode);
-	}
-
-	// "If (end node, end offset) is before (start node, start offset), set
-	// range's end to its start and abort these steps."
-	var startPoint = document.createRange();
-	startPoint.setStart(startNode, startOffset);
-	var endPoint = document.createRange();
-	endPoint.setStart(endNode, endOffset);
-	if (startPoint.compareBoundaryPoints(Range.START_TO_START, endPoint) == 1) {
-		range.setEnd(range.startContainer, range.startOffset);
-		return;
-	}
-
-	// "If start node is a Text or Comment node and start offset is 0, set
-	// start offset to the index of start node, then set start node to its
-	// parent."
-	if ((startNode.nodeType == Node.TEXT_NODE
-	|| startNode.nodeType == Node.COMMENT_NODE)
-	&& startOffset == 0) {
-		startOffset = getNodeIndex(startNode);
-		startNode = startNode.parentNode;
-	}
-
-	// "If end node is a Text or Comment node and end offset is its length, set
-	// end offset to one plus the index of end node, then set end node to its
-	// parent."
-	if ((endNode.nodeType == Node.TEXT_NODE
-	|| endNode.nodeType == Node.COMMENT_NODE)
-	&& endOffset == getNodeLength(endNode)) {
-		endOffset = 1 + getNodeIndex(endNode);
-		endNode = endNode.parentNode;
-	}
-
-	// "Set range's start to (start node, start offset) and its end to (end
-	// node, end offset)."
-	range.setStart(startNode, startOffset);
-	range.setEnd(endNode, endOffset);
-
-	// "Let start block be the start node of range."
-	var startBlock = range.startContainer;
-
-	// "While start block's parent is in the same editing host and start block
-	// is not a prohibited paragraph child, set start block to its parent."
-	while (inSameEditingHost(startBlock, startBlock.parentNode)
-	&& !isProhibitedParagraphChild(startBlock)) {
-		startBlock = startBlock.parentNode;
-	}
-
-	// "If start block is neither a prohibited paragraph child nor an editing
-	// host, or "span" is not an allowed child of start block, or start block
-	// is a td or th, set start block to null."
-	if ((!isProhibitedParagraphChild(startBlock) && !isEditingHost(startBlock))
-	|| !isAllowedChild("span", startBlock)
-	|| isHtmlElement(startBlock, ["td", "th"])) {
-		startBlock = null;
-	}
-
-	// "Let end block be the end node of range."
-	var endBlock = range.endContainer;
-
-	// "While end block's parent is in the same editing host and end block is
-	// not a prohibited paragraph child, set end block to its parent."
-	while (inSameEditingHost(endBlock, endBlock.parentNode)
-	&& !isProhibitedParagraphChild(endBlock)) {
-		endBlock = endBlock.parentNode;
-	}
-
-	// "If end block is neither a prohibited paragraph child nor an editing
-	// host, or "span" is not an allowed child of end block, or end block is a
-	// td or th, set end block to null."
-	if ((!isProhibitedParagraphChild(endBlock) && !isEditingHost(endBlock))
-	|| !isAllowedChild("span", endBlock)
-	|| isHtmlElement(endBlock, ["td", "th"])) {
-		endBlock = null;
-	}
-
-	// "If start node and end node are the same, and start node is an editable
-	// Text or Comment node, call deleteData(start offset, end offset − start
-	// offset) on start node."
-	if (startNode == endNode
-	&& isEditable(startNode)
-	&& (startNode.nodeType == Node.TEXT_NODE || startNode.nodeType == Node.COMMENT_NODE)) {
-		startNode.deleteData(startOffset, endOffset - startOffset);
-
-	// "Otherwise:"
-	} else {
-		// "If start node is an editable Text or Comment node, call
-		// deleteData() on it, with start offset as the first argument and
-		// (length of start node − start offset) as the second argument."
-		if (isEditable(startNode)
-		&& (startNode.nodeType == Node.TEXT_NODE
-		|| startNode.nodeType == Node.COMMENT_NODE)) {
-			startNode.deleteData(startOffset, getNodeLength(startNode) - startOffset);
-		}
-
-		// "Let node list be a list of nodes, initially empty."
-		//
-		// "For each node contained in range, append node to node list if the
-		// last member of node list (if any) is not an ancestor of node; node
-		// is editable; and node is not a thead, tbody, tfoot, tr, th, or td."
-		var nodeList = collectContainedNodes(range,
-			function(node) {
-				return isEditable(node)
-					&& !isHtmlElement(node, ["thead", "tbody", "tfoot", "tr", "th", "td"]);
-			}
-		);
-
-		// "For each node in node list:"
-		for (var i = 0; i < nodeList.length; i++) {
-			var node = nodeList[i];
-
-			// "Let parent be the parent of node."
-			var parent_ = node.parentNode;
-
-			// "Remove node from parent."
-			parent_.removeChild(node);
-
-			// "While parent is an editable inline node with length 0, let
-			// grandparent be the parent of parent, then remove parent from
-			// grandparent, then set parent to grandparent."
-			while (isEditable(parent_)
-			&& isInlineNode(parent_)
-			&& getNodeLength(parent_) == 0) {
-				var grandparent = parent_.parentNode;
-				grandparent.removeChild(parent_);
-				parent_ = grandparent;
-			}
-
-			// "If parent is editable or an editing host, is not an inline
-			// node, and has no children, call createElement("br") on the
-			// context object and append the result as the last child of
-			// parent."
-			if ((isEditable(parent_) || isEditingHost(parent_))
-			&& !isInlineNode(parent_)
-			&& !parent_.hasChildNodes()) {
-				parent_.appendChild(document.createElement("br"));
-			}
-		}
-
-		// "If end node is an editable Text or Comment node, call deleteData(0,
-		// end offset) on it."
-		if (isEditable(endNode)
-		&& (endNode.nodeType == Node.TEXT_NODE
-		|| endNode.nodeType == Node.COMMENT_NODE)) {
-			endNode.deleteData(0, endOffset);
-		}
-	}
-
-	// "If start block or end block is null, or start block is not in the same
-	// editing host as end block, or start block and end block are the same,
-	// set range's end to its start and then abort these steps."
-	if (!startBlock
-	|| !endBlock
-	|| !inSameEditingHost(startBlock, endBlock)
-	|| startBlock == endBlock) {
-		range.setEnd(range.startContainer, range.startOffset);
-		return;
-	}
-
-	// "If start block has one child, which is a br, remove its child from it."
-	if (startBlock.children.length == 1
-	&& isHtmlElement(startBlock.firstChild, "br")) {
-		startBlock.removeChild(startBlock.firstChild);
-	}
-
-	// "If end block has one child, which is a br, remove its child from it."
-	if (endBlock.children.length == 1
-	&& isHtmlElement(endBlock.firstChild, "br")) {
-		endBlock.removeChild(endBlock.firstChild);
-	}
-
-	// "If start block is an ancestor of end block:"
-	if (isAncestor(startBlock, endBlock)) {
-		// "Let reference node be end block."
-		var referenceNode = endBlock;
-
-		// "While reference node is not a child of start block, set reference
-		// node to its parent."
-		while (referenceNode.parentNode != startBlock) {
-			referenceNode = referenceNode.parentNode;
-		}
-
-		// "Set the start and end of range to (start block, index of reference
-		// node)."
-		range.setStart(startBlock, getNodeIndex(referenceNode));
-		range.setEnd(startBlock, getNodeIndex(referenceNode));
-
-		// "If end block has no children:"
-		if (!endBlock.hasChildNodes()) {
-			// "While end block is editable and is the only child of its parent
-			// and is not a child of start block, let parent equal end block,
-			// then remove end block from parent, then set end block to
-			// parent."
-			while (isEditable(endBlock)
-			&& endBlock.parentNode.childNodes.length == 1
-			&& endBlock.parentNode != startBlock) {
-				var parent_ = endBlock;
-				parent_.removeChild(endBlock);
-				endBlock = parent_;
-			}
-
-			// "If end block is editable and is not an inline node, and its
-			// previousSibling and nextSibling are both inline nodes, call
-			// createElement("br") on the context object and insert it into end
-			// block's parent immediately after end block."
-			if (isEditable(endBlock)
-			&& !isInlineNode(endBlock)
-			&& isInlineNode(endBlock.previousSibling)
-			&& isInlineNode(endBlock.nextSibling)) {
-				endBlock.parentNode.insertBefore(document.createElement("br"), endBlock.nextSibling);
-			}
-
-			// "If end block is editable, remove it from its parent."
-			if (isEditable(endBlock)) {
-				endBlock.parentNode.removeChild(endBlock);
-			}
-
-			// "Abort these steps."
-			return;
-		}
-
-		// "If end block's firstChild is not an inline node, abort these
-		// steps."
-		if (!isInlineNode(endBlock.firstChild)) {
-			return;
-		}
-
-		// "Let children be an array of nodes, initially empty."
-		var children = [];
-
-		// "Append the first child of end block to children."
-		children.push(endBlock.firstChild);
-
-		// "While children's last member is not a br, and children's last
-		// member's nextSibling is an inline node, append children's last
-		// member's nextSibling to children."
-		while (!isHtmlElement(children[children.length - 1], "br")
-		&& isInlineNode(children[children.length - 1].nextSibling)) {
-			children.push(children[children.length - 1].nextSibling);
-		}
-
-		// "While children's first member's parent is not start block, split
-		// the parent of children."
-		while (children[0].parentNode != startBlock) {
-			splitParent(children);
-		}
-
-		// "If children's first member's previousSibling is an editable br,
-		// remove that br from its parent."
-		if (isEditable(children[0].previousSibling)
-		&& isHtmlElement(children[0].previousSibling, "br")) {
-			children[0].parentNode.removeChild(children[0].previousSibling);
-		}
-
-	// "Otherwise, if start block is a descendant of end block:"
-	} else if (isDescendant(startBlock, endBlock)) {
-		// "Set the start and end of range to (start block, length of start
-		// block)."
-		range.setStart(startBlock, getNodeLength(startBlock));
-		range.setEnd(startBlock, getNodeLength(startBlock));
-
-		// "Let reference node be start block."
-		var referenceNode = startBlock;
-
-		// "While reference node is not a child of end block, set reference
-		// node to its parent."
-		while (referenceNode.parentNode != endBlock) {
-			referenceNode = referenceNode.parentNode;
-		}
-
-		// "If reference node's nextSibling is an inline node and start block's
-		// lastChild is a br, remove start block's lastChild from it."
-		if (isInlineNode(referenceNode.nextSibling)
-		&& isHtmlElement(startBlock.lastChild, "br")) {
-			startBlock.removeChild(startBlock.lastChild);
-		}
-
-		// "While the nextSibling of reference node is neither null nor a br
-		// nor a prohibited paragraph child, append the nextSibling of
-		// reference node as the last child of start block, preserving ranges."
-		while (referenceNode.nextSibling
-		&& !isHtmlElement(referenceNode.nextSibling, "br")
-		&& !isProhibitedParagraphChild(referenceNode.nextSibling)) {
-			movePreservingRanges(referenceNode.nextSibling, startBlock, -1);
-		}
-
-		// "If the nextSibling of reference node is a br, remove it from its
-		// parent."
-		if (isHtmlElement(referenceNode.nextSibling, "br")) {
-			referenceNode.parentNode.removeChild(referenceNode.nextSibling);
-		}
-
-	// "Otherwise:"
-	} else {
-		// "Set the start and end of range to (start block, length of start
-		// block)."
-		range.setStart(startBlock, getNodeLength(startBlock));
-		range.setEnd(startBlock, getNodeLength(startBlock));
-
-		// "If end block's firstChild is an inline node and start block's
-		// lastChild is a br, remove start block's lastChild from it."
-		if (isInlineNode(endBlock.firstChild)
-		&& isHtmlElement(startBlock.lastChild, "br")) {
-			startBlock.removeChild(startBlock.lastChild);
-		}
-
-		// "While end block has children, append the first child of end block
-		// to start block, preserving ranges."
-		while (endBlock.hasChildNodes()) {
-			movePreservingRanges(endBlock.firstChild, startBlock, -1);
-		}
-
-		// "While end block has no children, let parent be the parent of end
-		// block, then remove end block from parent, then set end block to
-		// parent."
-		while (!endBlock.hasChildNodes()) {
-			var parent_ = endBlock.parentNode;
-			parent_.removeChild(endBlock);
-			endBlock = parent_;
-		}
-	}
-
-	// "If start block has no children, call createElement("br") on the context
-	// object and append the result as the last child of start block."
-	if (!startBlock.hasChildNodes()) {
-		startBlock.appendChild(document.createElement("br"));
-	}
-}
-
-//@}
-
 
 //////////////////////////////////////
 ///// Inline formatting commands /////
@@ -3267,109 +2620,6 @@
 };
 //@}
 
-///// The insertHTML command /////
-//@{
-commands.inserthtml = {
-	action: function(value) {
-		// "Delete the contents of the active range."
-		deleteContents(getActiveRange());
-
-		// "Let frag be the result of calling createContextualFragment(value)
-		// on the active range."
-		var frag = getActiveRange().createContextualFragment(value);
-
-		// "Let last child be the lastChild of frag."
-		var lastChild = frag.lastChild;
-
-		// "If last child is null, abort these steps."
-		if (!lastChild) {
-			return;
-		}
-
-		// "Let descendants be all descendants of frag."
-		var descendants = getDescendants(frag);
-
-		// "If the active range's start node is a prohibited paragraph child
-		// whose sole child is a br, and its start offset is 0, remove its
-		// start node's child from it."
-		if (isProhibitedParagraphChild(getActiveRange().startContainer)
-		&& getActiveRange().startContainer.childNodes.length == 1
-		&& isHtmlElement(getActiveRange().startContainer.firstChild, "br")
-		&& getActiveRange().startOffset == 0) {
-			getActiveRange().startContainer.removeChild(getActiveRange().startContainer.firstChild);
-		}
-
-		// "Call insertNode(frag) on the active range."
-		getActiveRange().insertNode(frag);
-
-		// "Set the active range's start and end to (last child, length of last
-		// child)."
-		getActiveRange().setStart(lastChild, getNodeLength(lastChild));
-		getActiveRange().setEnd(lastChild, getNodeLength(lastChild));
-
-		// "Fix disallowed ancestors of each member of descendants."
-		for (var i = 0; i < descendants.length; i++) {
-			fixDisallowedAncestors(descendants[i]);
-		}
-	}
-};
-//@}
-
-///// The insertImage command /////
-//@{
-commands.insertimage = {
-	action: function(value) {
-		// "If value is the empty string, abort these steps and do nothing."
-		if (value === "") {
-			return;
-		}
-
-		// "Let range be the active range."
-		var range = getActiveRange();
-
-		// "Delete the contents of range."
-		deleteContents(range);
-
-		// "If range's start node is a prohibited paragraph child whose sole
-		// child is a br, and its start offset is 0, remove its start node's
-		// child from it."
-		if (isProhibitedParagraphChild(range.startContainer)
-		&& range.startContainer.childNodes.length == 1
-		&& isHtmlElement(range.startContainer.firstChild, "br")
-		&& range.startOffset == 0) {
-			range.startContainer.removeChild(range.startContainer.firstChild);
-		}
-
-		// "Let img be the result of calling createElement("img") on the
-		// context object."
-		var img = document.createElement("img");
-
-		// "Run setAttribute("src", value) on img."
-		img.setAttribute("src", value);
-
-		// "Run insertNode(img) on the range."
-		range.insertNode(img);
-
-		// "Run collapse() on the Selection, with first argument equal to the
-		// parent of img and the second argument equal to one plus the index of
-		// img."
-		//
-		// Not everyone actually supports collapse(), so we do it manually
-		// instead.  Also, we need to modify the actual range we're given as
-		// well, for the sake of autoimplementation.html's range-filling-in.
-		range.setStart(img.parentNode, 1 + getNodeIndex(img));
-		range.setEnd(img.parentNode, 1 + getNodeIndex(img));
-		getSelection().removeAllRanges();
-		getSelection().addRange(range);
-
-		// IE adds width and height attributes for some reason, so remove those
-		// to actually do what the spec says.
-		img.removeAttribute("width");
-		img.removeAttribute("height");
-	}
-};
-//@}
-
 ///// The italic command /////
 //@{
 commands.italic = {
@@ -3626,6 +2876,44 @@
 ///// Block formatting command definitions /////
 //@{
 
+// "A prohibited paragraph child name is "address", "article", "aside",
+// "blockquote", "caption", "center", "col", "colgroup", "details", "dd",
+// "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", "details", "dd",
+	"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",
+// "strong", "sub", "sup", "u", "var", "acronym", "listing", "strike", "xmp",
+// "big", "blink", "font", "marquee", "nobr", or "tt"."
+var namesOfElementsWithInlineContents = ["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", "strong", "sub", "sup", "u", "var", "acronym", "listing", "strike",
+	"xmp", "big", "blink", "font", "marquee", "nobr", "tt"];
+
+// "An element with inline contents is an HTML element whose local name is a
+// name of an element with inline contents."
+function isElementWithInlineContents(node) {
+	return isHtmlElement(node, namesOfElementsWithInlineContents);
+}
+
 // "A potential indentation element is either a blockquote, or a div that has a
 // style attribute that sets "margin" or some subproperty of it."
 function isPotentialIndentationElement(node) {
@@ -3701,6 +2989,31 @@
 // "The default single-line container name is "p"."
 var defaultSingleLineContainerName = "p";
 
+// "A visible node is a node that either is a prohibited paragraph child, or a
+// Text node whose data is not empty, or a br or img, or any node with a
+// descendant that is a visible node."
+function isVisibleNode(node) {
+	if (!node) {
+		return false;
+	}
+	if (isProhibitedParagraphChild(node)
+	|| (node.nodeType == Node.TEXT_NODE && node.length)
+	|| isHtmlElement(node, ["br", "img"])) {
+		return true;
+	}
+	for (var i = 0; i < node.childNodes.length; i++) {
+		if (isVisibleNode(node.childNodes[i])) {
+			return true;
+		}
+	}
+	return false;
+}
+
+// "An invisible node is a node that is not a visible node."
+function isInvisibleNode(node) {
+	return node && !isVisibleNode(node);
+}
+
 //@}
 
 ///// Assorted block formatting algorithm commands /////
@@ -3885,6 +3198,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;
+}
+
+//@}
+
 ///// Block-extending a range /////
 //@{
 
@@ -4018,6 +3489,437 @@
 
 //@}
 
+///// Deleting the contents of a range /////
+//@{
+
+function deleteContents(node1, offset1, node2, offset2) {
+	var range;
+
+	// We allow passing four arguments instead of one, in which case they're
+	// the start and end points of the range.
+	if (typeof offset1 == "undefined") {
+		range = node1;
+	} else {
+		range = document.createRange();
+		range.setStart(node1, offset1);
+		range.setEnd(node2, offset2);
+	}
+
+	// "If range is null, abort these steps and do nothing."
+	if (!range) {
+		return;
+	}
+
+	// "Let start node, start offset, end node, and end offset be range's start
+	// and end nodes and offsets."
+	var startNode = range.startContainer;
+	var startOffset = range.startOffset;
+	var endNode = range.endContainer;
+	var endOffset = range.endOffset;
+
+	// "While start node has at least one child:"
+	while (startNode.hasChildNodes()) {
+		// "If start offset is start node's length, and start node's parent is
+		// in the same editing host, and start node is not a prohibited
+		// paragraph child, set start offset to one plus the index of start
+		// node, then set start node to its parent and continue this loop from
+		// the beginning."
+		if (startOffset == getNodeLength(startNode)
+		&& inSameEditingHost(startNode, startNode.parentNode)
+		&& !isProhibitedParagraphChild(startNode)) {
+			startOffset = 1 + getNodeIndex(startNode);
+			startNode = startNode.parentNode;
+			continue;
+		}
+
+		// "If start offset is start node's length, break from this loop."
+		if (startOffset == getNodeLength(startNode)) {
+			break;
+		}
+
+		// "Let reference node be the child of start node with index equal to
+		// start offset."
+		var referenceNode = startNode.childNodes[startOffset];
+
+		// "If reference node is a prohibited paragraph child or an Element
+		// with no children, break from this loop."
+		if (isProhibitedParagraphChild(referenceNode)
+		|| (referenceNode.nodeType == Node.ELEMENT_NODE
+		&& !referenceNode.hasChildNodes())) {
+			break;
+		}
+
+		// "Set start node to reference node and start offset to 0."
+		startNode = referenceNode;
+		startOffset = 0;
+	}
+
+	// "While end node has at least one child:"
+	while (endNode.hasChildNodes()) {
+		// "If end offset is 0, and end node's parent is in the same editing
+		// host, and end node is not a prohibited paragraph child, set end
+		// offset to the index of end node, then set end node to its parent and
+		// continue this loop from the beginning."
+		if (endOffset == 0
+		&& inSameEditingHost(endNode, endNode.parentNode)
+		&& !isProhibitedParagraphChild(endNode)) {
+			endOffset = getNodeIndex(endNode);
+			endNode = endNode.parentNode;
+			continue;
+		}
+
+		// "If end offset is 0, break from this loop."
+		if (endOffset == 0) {
+			break;
+		}
+
+		// "Let reference node be the child of end node with index equal to end
+		// offset minus one."
+		var referenceNode = endNode.childNodes[endOffset - 1];
+
+		// "If reference node is a prohibited paragraph child or an Element
+		// with no children, break from this loop."
+		if (isProhibitedParagraphChild(referenceNode)
+		|| (referenceNode.nodeType == Node.ELEMENT_NODE
+		&& !referenceNode.hasChildNodes())) {
+			break;
+		}
+
+		// "Set end node to reference node and end offset to the length of
+		// reference node."
+		endNode = referenceNode;
+		endOffset = getNodeLength(referenceNode);
+	}
+
+	// "If (end node, end offset) is before (start node, start offset), set
+	// range's end to its start and abort these steps."
+	var startPoint = document.createRange();
+	startPoint.setStart(startNode, startOffset);
+	var endPoint = document.createRange();
+	endPoint.setStart(endNode, endOffset);
+	if (startPoint.compareBoundaryPoints(Range.START_TO_START, endPoint) == 1) {
+		range.setEnd(range.startContainer, range.startOffset);
+		return;
+	}
+
+	// "If start node is a Text or Comment node and start offset is 0, set
+	// start offset to the index of start node, then set start node to its
+	// parent."
+	if ((startNode.nodeType == Node.TEXT_NODE
+	|| startNode.nodeType == Node.COMMENT_NODE)
+	&& startOffset == 0) {
+		startOffset = getNodeIndex(startNode);
+		startNode = startNode.parentNode;
+	}
+
+	// "If end node is a Text or Comment node and end offset is its length, set
+	// end offset to one plus the index of end node, then set end node to its
+	// parent."
+	if ((endNode.nodeType == Node.TEXT_NODE
+	|| endNode.nodeType == Node.COMMENT_NODE)
+	&& endOffset == getNodeLength(endNode)) {
+		endOffset = 1 + getNodeIndex(endNode);
+		endNode = endNode.parentNode;
+	}
+
+	// "Set range's start to (start node, start offset) and its end to (end
+	// node, end offset)."
+	range.setStart(startNode, startOffset);
+	range.setEnd(endNode, endOffset);
+
+	// "Let start block be the start node of range."
+	var startBlock = range.startContainer;
+
+	// "While start block's parent is in the same editing host and start block
+	// is not a prohibited paragraph child, set start block to its parent."
+	while (inSameEditingHost(startBlock, startBlock.parentNode)
+	&& !isProhibitedParagraphChild(startBlock)) {
+		startBlock = startBlock.parentNode;
+	}
+
+	// "If start block is neither a prohibited paragraph child nor an editing
+	// host, or "span" is not an allowed child of start block, or start block
+	// is a td or th, set start block to null."
+	if ((!isProhibitedParagraphChild(startBlock) && !isEditingHost(startBlock))
+	|| !isAllowedChild("span", startBlock)
+	|| isHtmlElement(startBlock, ["td", "th"])) {
+		startBlock = null;
+	}
+
+	// "Let end block be the end node of range."
+	var endBlock = range.endContainer;
+
+	// "While end block's parent is in the same editing host and end block is
+	// not a prohibited paragraph child, set end block to its parent."
+	while (inSameEditingHost(endBlock, endBlock.parentNode)
+	&& !isProhibitedParagraphChild(endBlock)) {
+		endBlock = endBlock.parentNode;
+	}
+
+	// "If end block is neither a prohibited paragraph child nor an editing
+	// host, or "span" is not an allowed child of end block, or end block is a
+	// td or th, set end block to null."
+	if ((!isProhibitedParagraphChild(endBlock) && !isEditingHost(endBlock))
+	|| !isAllowedChild("span", endBlock)
+	|| isHtmlElement(endBlock, ["td", "th"])) {
+		endBlock = null;
+	}
+
+	// "If start node and end node are the same, and start node is an editable
+	// Text or Comment node, call deleteData(start offset, end offset − start
+	// offset) on start node."
+	if (startNode == endNode
+	&& isEditable(startNode)
+	&& (startNode.nodeType == Node.TEXT_NODE || startNode.nodeType == Node.COMMENT_NODE)) {
+		startNode.deleteData(startOffset, endOffset - startOffset);
+
+	// "Otherwise:"
+	} else {
+		// "If start node is an editable Text or Comment node, call
+		// deleteData() on it, with start offset as the first argument and
+		// (length of start node − start offset) as the second argument."
+		if (isEditable(startNode)
+		&& (startNode.nodeType == Node.TEXT_NODE
+		|| startNode.nodeType == Node.COMMENT_NODE)) {
+			startNode.deleteData(startOffset, getNodeLength(startNode) - startOffset);
+		}
+
+		// "Let node list be a list of nodes, initially empty."
+		//
+		// "For each node contained in range, append node to node list if the
+		// last member of node list (if any) is not an ancestor of node; node
+		// is editable; and node is not a thead, tbody, tfoot, tr, th, or td."
+		var nodeList = collectContainedNodes(range,
+			function(node) {
+				return isEditable(node)
+					&& !isHtmlElement(node, ["thead", "tbody", "tfoot", "tr", "th", "td"]);
+			}
+		);
+
+		// "For each node in node list:"
+		for (var i = 0; i < nodeList.length; i++) {
+			var node = nodeList[i];
+
+			// "Let parent be the parent of node."
+			var parent_ = node.parentNode;
+
+			// "Remove node from parent."
+			parent_.removeChild(node);
+
+			// "While parent is an editable inline node with length 0, let
+			// grandparent be the parent of parent, then remove parent from
+			// grandparent, then set parent to grandparent."
+			while (isEditable(parent_)
+			&& isInlineNode(parent_)
+			&& getNodeLength(parent_) == 0) {
+				var grandparent = parent_.parentNode;
+				grandparent.removeChild(parent_);
+				parent_ = grandparent;
+			}
+
+			// "If parent is editable or an editing host, is not an inline
+			// node, and has no children, call createElement("br") on the
+			// context object and append the result as the last child of
+			// parent."
+			if ((isEditable(parent_) || isEditingHost(parent_))
+			&& !isInlineNode(parent_)
+			&& !parent_.hasChildNodes()) {
+				parent_.appendChild(document.createElement("br"));
+			}
+		}
+
+		// "If end node is an editable Text or Comment node, call deleteData(0,
+		// end offset) on it."
+		if (isEditable(endNode)
+		&& (endNode.nodeType == Node.TEXT_NODE
+		|| endNode.nodeType == Node.COMMENT_NODE)) {
+			endNode.deleteData(0, endOffset);
+		}
+	}
+
+	// "If start block or end block is null, or start block is not in the same
+	// editing host as end block, or start block and end block are the same,
+	// set range's end to its start and then abort these steps."
+	if (!startBlock
+	|| !endBlock
+	|| !inSameEditingHost(startBlock, endBlock)
+	|| startBlock == endBlock) {
+		range.setEnd(range.startContainer, range.startOffset);
+		return;
+	}
+
+	// "If start block has one child, which is a br, remove its child from it."
+	if (startBlock.children.length == 1
+	&& isHtmlElement(startBlock.firstChild, "br")) {
+		startBlock.removeChild(startBlock.firstChild);
+	}
+
+	// "If end block has one child, which is a br, remove its child from it."
+	if (endBlock.children.length == 1
+	&& isHtmlElement(endBlock.firstChild, "br")) {
+		endBlock.removeChild(endBlock.firstChild);
+	}
+
+	// "If start block is an ancestor of end block:"
+	if (isAncestor(startBlock, endBlock)) {
+		// "Let reference node be end block."
+		var referenceNode = endBlock;
+
+		// "While reference node is not a child of start block, set reference
+		// node to its parent."
+		while (referenceNode.parentNode != startBlock) {
+			referenceNode = referenceNode.parentNode;
+		}
+
+		// "Set the start and end of range to (start block, index of reference
+		// node)."
+		range.setStart(startBlock, getNodeIndex(referenceNode));
+		range.setEnd(startBlock, getNodeIndex(referenceNode));
+
+		// "If end block has no children:"
+		if (!endBlock.hasChildNodes()) {
+			// "While end block is editable and is the only child of its parent
+			// and is not a child of start block, let parent equal end block,
+			// then remove end block from parent, then set end block to
+			// parent."
+			while (isEditable(endBlock)
+			&& endBlock.parentNode.childNodes.length == 1
+			&& endBlock.parentNode != startBlock) {
+				var parent_ = endBlock;
+				parent_.removeChild(endBlock);
+				endBlock = parent_;
+			}
+
+			// "If end block is editable and is not an inline node, and its
+			// previousSibling and nextSibling are both inline nodes, call
+			// createElement("br") on the context object and insert it into end
+			// block's parent immediately after end block."
+			if (isEditable(endBlock)
+			&& !isInlineNode(endBlock)
+			&& isInlineNode(endBlock.previousSibling)
+			&& isInlineNode(endBlock.nextSibling)) {
+				endBlock.parentNode.insertBefore(document.createElement("br"), endBlock.nextSibling);
+			}
+
+			// "If end block is editable, remove it from its parent."
+			if (isEditable(endBlock)) {
+				endBlock.parentNode.removeChild(endBlock);
+			}
+
+			// "Abort these steps."
+			return;
+		}
+
+		// "If end block's firstChild is not an inline node, abort these
+		// steps."
+		if (!isInlineNode(endBlock.firstChild)) {
+			return;
+		}
+
+		// "Let children be an array of nodes, initially empty."
+		var children = [];
+
+		// "Append the first child of end block to children."
+		children.push(endBlock.firstChild);
+
+		// "While children's last member is not a br, and children's last
+		// member's nextSibling is an inline node, append children's last
+		// member's nextSibling to children."
+		while (!isHtmlElement(children[children.length - 1], "br")
+		&& isInlineNode(children[children.length - 1].nextSibling)) {
+			children.push(children[children.length - 1].nextSibling);
+		}
+
+		// "While children's first member's parent is not start block, split
+		// the parent of children."
+		while (children[0].parentNode != startBlock) {
+			splitParent(children);
+		}
+
+		// "If children's first member's previousSibling is an editable br,
+		// remove that br from its parent."
+		if (isEditable(children[0].previousSibling)
+		&& isHtmlElement(children[0].previousSibling, "br")) {
+			children[0].parentNode.removeChild(children[0].previousSibling);
+		}
+
+	// "Otherwise, if start block is a descendant of end block:"
+	} else if (isDescendant(startBlock, endBlock)) {
+		// "Set the start and end of range to (start block, length of start
+		// block)."
+		range.setStart(startBlock, getNodeLength(startBlock));
+		range.setEnd(startBlock, getNodeLength(startBlock));
+
+		// "Let reference node be start block."
+		var referenceNode = startBlock;
+
+		// "While reference node is not a child of end block, set reference
+		// node to its parent."
+		while (referenceNode.parentNode != endBlock) {
+			referenceNode = referenceNode.parentNode;
+		}
+
+		// "If reference node's nextSibling is an inline node and start block's
+		// lastChild is a br, remove start block's lastChild from it."
+		if (isInlineNode(referenceNode.nextSibling)
+		&& isHtmlElement(startBlock.lastChild, "br")) {
+			startBlock.removeChild(startBlock.lastChild);
+		}
+
+		// "While the nextSibling of reference node is neither null nor a br
+		// nor a prohibited paragraph child, append the nextSibling of
+		// reference node as the last child of start block, preserving ranges."
+		while (referenceNode.nextSibling
+		&& !isHtmlElement(referenceNode.nextSibling, "br")
+		&& !isProhibitedParagraphChild(referenceNode.nextSibling)) {
+			movePreservingRanges(referenceNode.nextSibling, startBlock, -1);
+		}
+
+		// "If the nextSibling of reference node is a br, remove it from its
+		// parent."
+		if (isHtmlElement(referenceNode.nextSibling, "br")) {
+			referenceNode.parentNode.removeChild(referenceNode.nextSibling);
+		}
+
+	// "Otherwise:"
+	} else {
+		// "Set the start and end of range to (start block, length of start
+		// block)."
+		range.setStart(startBlock, getNodeLength(startBlock));
+		range.setEnd(startBlock, getNodeLength(startBlock));
+
+		// "If end block's firstChild is an inline node and start block's
+		// lastChild is a br, remove start block's lastChild from it."
+		if (isInlineNode(endBlock.firstChild)
+		&& isHtmlElement(startBlock.lastChild, "br")) {
+			startBlock.removeChild(startBlock.lastChild);
+		}
+
+		// "While end block has children, append the first child of end block
+		// to start block, preserving ranges."
+		while (endBlock.hasChildNodes()) {
+			movePreservingRanges(endBlock.firstChild, startBlock, -1);
+		}
+
+		// "While end block has no children, let parent be the parent of end
+		// block, then remove end block from parent, then set end block to
+		// parent."
+		while (!endBlock.hasChildNodes()) {
+			var parent_ = endBlock.parentNode;
+			parent_.removeChild(endBlock);
+			endBlock = parent_;
+		}
+	}
+
+	// "If start block has no children, call createElement("br") on the context
+	// object and append the result as the last child of start block."
+	if (!startBlock.hasChildNodes()) {
+		startBlock.appendChild(document.createElement("br"));
+	}
+}
+
+//@}
+
 ///// Outdenting a node /////
 //@{
 
@@ -5317,6 +5219,109 @@
 };
 //@}
 
+///// The insertHTML command /////
+//@{
+commands.inserthtml = {
+	action: function(value) {
+		// "Delete the contents of the active range."
+		deleteContents(getActiveRange());
+
+		// "Let frag be the result of calling createContextualFragment(value)
+		// on the active range."
+		var frag = getActiveRange().createContextualFragment(value);
+
+		// "Let last child be the lastChild of frag."
+		var lastChild = frag.lastChild;
+
+		// "If last child is null, abort these steps."
+		if (!lastChild) {
+			return;
+		}
+
+		// "Let descendants be all descendants of frag."
+		var descendants = getDescendants(frag);
+
+		// "If the active range's start node is a prohibited paragraph child
+		// whose sole child is a br, and its start offset is 0, remove its
+		// start node's child from it."
+		if (isProhibitedParagraphChild(getActiveRange().startContainer)
+		&& getActiveRange().startContainer.childNodes.length == 1
+		&& isHtmlElement(getActiveRange().startContainer.firstChild, "br")
+		&& getActiveRange().startOffset == 0) {
+			getActiveRange().startContainer.removeChild(getActiveRange().startContainer.firstChild);
+		}
+
+		// "Call insertNode(frag) on the active range."
+		getActiveRange().insertNode(frag);
+
+		// "Set the active range's start and end to (last child, length of last
+		// child)."
+		getActiveRange().setStart(lastChild, getNodeLength(lastChild));
+		getActiveRange().setEnd(lastChild, getNodeLength(lastChild));
+
+		// "Fix disallowed ancestors of each member of descendants."
+		for (var i = 0; i < descendants.length; i++) {
+			fixDisallowedAncestors(descendants[i]);
+		}
+	}
+};
+//@}
+
+///// The insertImage command /////
+//@{
+commands.insertimage = {
+	action: function(value) {
+		// "If value is the empty string, abort these steps and do nothing."
+		if (value === "") {
+			return;
+		}
+
+		// "Let range be the active range."
+		var range = getActiveRange();
+
+		// "Delete the contents of range."
+		deleteContents(range);
+
+		// "If range's start node is a prohibited paragraph child whose sole
+		// child is a br, and its start offset is 0, remove its start node's
+		// child from it."
+		if (isProhibitedParagraphChild(range.startContainer)
+		&& range.startContainer.childNodes.length == 1
+		&& isHtmlElement(range.startContainer.firstChild, "br")
+		&& range.startOffset == 0) {
+			range.startContainer.removeChild(range.startContainer.firstChild);
+		}
+
+		// "Let img be the result of calling createElement("img") on the
+		// context object."
+		var img = document.createElement("img");
+
+		// "Run setAttribute("src", value) on img."
+		img.setAttribute("src", value);
+
+		// "Run insertNode(img) on the range."
+		range.insertNode(img);
+
+		// "Run collapse() on the Selection, with first argument equal to the
+		// parent of img and the second argument equal to one plus the index of
+		// img."
+		//
+		// Not everyone actually supports collapse(), so we do it manually
+		// instead.  Also, we need to modify the actual range we're given as
+		// well, for the sake of autoimplementation.html's range-filling-in.
+		range.setStart(img.parentNode, 1 + getNodeIndex(img));
+		range.setEnd(img.parentNode, 1 + getNodeIndex(img));
+		getSelection().removeAllRanges();
+		getSelection().addRange(range);
+
+		// IE adds width and height attributes for some reason, so remove those
+		// to actually do what the spec says.
+		img.removeAttribute("width");
+		img.removeAttribute("height");
+	}
+};
+//@}
+
 ///// The insertOrderedList command /////
 commands.insertorderedlist = {
 	// "Toggle lists with tag name "ol"."