dom/imptests/editing/implementation.js

changeset 0
6474c204b198
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/dom/imptests/editing/implementation.js	Wed Dec 31 06:09:35 2014 +0100
     1.3 @@ -0,0 +1,8521 @@
     1.4 +"use strict";
     1.5 +
     1.6 +var htmlNamespace = "http://www.w3.org/1999/xhtml";
     1.7 +
     1.8 +var cssStylingFlag = false;
     1.9 +
    1.10 +var defaultSingleLineContainerName = "p";
    1.11 +
    1.12 +// This is bad :(
    1.13 +var globalRange = null;
    1.14 +
    1.15 +// Commands are stored in a dictionary where we call their actions and such
    1.16 +var commands = {};
    1.17 +
    1.18 +///////////////////////////////////////////////////////////////////////////////
    1.19 +////////////////////////////// Utility functions //////////////////////////////
    1.20 +///////////////////////////////////////////////////////////////////////////////
    1.21 +//@{
    1.22 +
    1.23 +function nextNode(node) {
    1.24 +	if (node.hasChildNodes()) {
    1.25 +		return node.firstChild;
    1.26 +	}
    1.27 +	return nextNodeDescendants(node);
    1.28 +}
    1.29 +
    1.30 +function previousNode(node) {
    1.31 +	if (node.previousSibling) {
    1.32 +		node = node.previousSibling;
    1.33 +		while (node.hasChildNodes()) {
    1.34 +			node = node.lastChild;
    1.35 +		}
    1.36 +		return node;
    1.37 +	}
    1.38 +	if (node.parentNode
    1.39 +	&& node.parentNode.nodeType == Node.ELEMENT_NODE) {
    1.40 +		return node.parentNode;
    1.41 +	}
    1.42 +	return null;
    1.43 +}
    1.44 +
    1.45 +function nextNodeDescendants(node) {
    1.46 +	while (node && !node.nextSibling) {
    1.47 +		node = node.parentNode;
    1.48 +	}
    1.49 +	if (!node) {
    1.50 +		return null;
    1.51 +	}
    1.52 +	return node.nextSibling;
    1.53 +}
    1.54 +
    1.55 +/**
    1.56 + * Returns true if ancestor is an ancestor of descendant, false otherwise.
    1.57 + */
    1.58 +function isAncestor(ancestor, descendant) {
    1.59 +	return ancestor
    1.60 +		&& descendant
    1.61 +		&& Boolean(ancestor.compareDocumentPosition(descendant) & Node.DOCUMENT_POSITION_CONTAINED_BY);
    1.62 +}
    1.63 +
    1.64 +/**
    1.65 + * Returns true if ancestor is an ancestor of or equal to descendant, false
    1.66 + * otherwise.
    1.67 + */
    1.68 +function isAncestorContainer(ancestor, descendant) {
    1.69 +	return (ancestor || descendant)
    1.70 +		&& (ancestor == descendant || isAncestor(ancestor, descendant));
    1.71 +}
    1.72 +
    1.73 +/**
    1.74 + * Returns true if descendant is a descendant of ancestor, false otherwise.
    1.75 + */
    1.76 +function isDescendant(descendant, ancestor) {
    1.77 +	return ancestor
    1.78 +		&& descendant
    1.79 +		&& Boolean(ancestor.compareDocumentPosition(descendant) & Node.DOCUMENT_POSITION_CONTAINED_BY);
    1.80 +}
    1.81 +
    1.82 +/**
    1.83 + * Returns true if node1 is before node2 in tree order, false otherwise.
    1.84 + */
    1.85 +function isBefore(node1, node2) {
    1.86 +	return Boolean(node1.compareDocumentPosition(node2) & Node.DOCUMENT_POSITION_FOLLOWING);
    1.87 +}
    1.88 +
    1.89 +/**
    1.90 + * Returns true if node1 is after node2 in tree order, false otherwise.
    1.91 + */
    1.92 +function isAfter(node1, node2) {
    1.93 +	return Boolean(node1.compareDocumentPosition(node2) & Node.DOCUMENT_POSITION_PRECEDING);
    1.94 +}
    1.95 +
    1.96 +function getAncestors(node) {
    1.97 +	var ancestors = [];
    1.98 +	while (node.parentNode) {
    1.99 +		ancestors.unshift(node.parentNode);
   1.100 +		node = node.parentNode;
   1.101 +	}
   1.102 +	return ancestors;
   1.103 +}
   1.104 +
   1.105 +function getInclusiveAncestors(node) {
   1.106 +	return getAncestors(node).concat(node);
   1.107 +}
   1.108 +
   1.109 +function getDescendants(node) {
   1.110 +	var descendants = [];
   1.111 +	var stop = nextNodeDescendants(node);
   1.112 +	while ((node = nextNode(node))
   1.113 +	&& node != stop) {
   1.114 +		descendants.push(node);
   1.115 +	}
   1.116 +	return descendants;
   1.117 +}
   1.118 +
   1.119 +function getInclusiveDescendants(node) {
   1.120 +	return [node].concat(getDescendants(node));
   1.121 +}
   1.122 +
   1.123 +function convertProperty(property) {
   1.124 +	// Special-case for now
   1.125 +	var map = {
   1.126 +		"fontFamily": "font-family",
   1.127 +		"fontSize": "font-size",
   1.128 +		"fontStyle": "font-style",
   1.129 +		"fontWeight": "font-weight",
   1.130 +		"textDecoration": "text-decoration",
   1.131 +	};
   1.132 +	if (typeof map[property] != "undefined") {
   1.133 +		return map[property];
   1.134 +	}
   1.135 +
   1.136 +	return property;
   1.137 +}
   1.138 +
   1.139 +// Return the <font size=X> value for the given CSS size, or undefined if there
   1.140 +// is none.
   1.141 +function cssSizeToLegacy(cssVal) {
   1.142 +	return {
   1.143 +		"x-small": 1,
   1.144 +		"small": 2,
   1.145 +		"medium": 3,
   1.146 +		"large": 4,
   1.147 +		"x-large": 5,
   1.148 +		"xx-large": 6,
   1.149 +		"xxx-large": 7
   1.150 +	}[cssVal];
   1.151 +}
   1.152 +
   1.153 +// Return the CSS size given a legacy size.
   1.154 +function legacySizeToCss(legacyVal) {
   1.155 +	return {
   1.156 +		1: "x-small",
   1.157 +		2: "small",
   1.158 +		3: "medium",
   1.159 +		4: "large",
   1.160 +		5: "x-large",
   1.161 +		6: "xx-large",
   1.162 +		7: "xxx-large",
   1.163 +	}[legacyVal];
   1.164 +}
   1.165 +
   1.166 +// Opera 11 puts HTML elements in the null namespace, it seems.
   1.167 +function isHtmlNamespace(ns) {
   1.168 +	return ns === null
   1.169 +		|| ns === htmlNamespace;
   1.170 +}
   1.171 +
   1.172 +// "the directionality" from HTML.  I don't bother caring about non-HTML
   1.173 +// elements.
   1.174 +//
   1.175 +// "The directionality of an element is either 'ltr' or 'rtl', and is
   1.176 +// determined as per the first appropriate set of steps from the following
   1.177 +// list:"
   1.178 +function getDirectionality(element) {
   1.179 +	// "If the element's dir attribute is in the ltr state
   1.180 +	//     The directionality of the element is 'ltr'."
   1.181 +	if (element.dir == "ltr") {
   1.182 +		return "ltr";
   1.183 +	}
   1.184 +
   1.185 +	// "If the element's dir attribute is in the rtl state
   1.186 +	//     The directionality of the element is 'rtl'."
   1.187 +	if (element.dir == "rtl") {
   1.188 +		return "rtl";
   1.189 +	}
   1.190 +
   1.191 +	// "If the element's dir attribute is in the auto state
   1.192 +	// "If the element is a bdi element and the dir attribute is not in a
   1.193 +	// defined state (i.e. it is not present or has an invalid value)
   1.194 +	//     [lots of complicated stuff]
   1.195 +	//
   1.196 +	// Skip this, since no browser implements it anyway.
   1.197 +
   1.198 +	// "If the element is a root element and the dir attribute is not in a
   1.199 +	// defined state (i.e. it is not present or has an invalid value)
   1.200 +	//     The directionality of the element is 'ltr'."
   1.201 +	if (!isHtmlElement(element.parentNode)) {
   1.202 +		return "ltr";
   1.203 +	}
   1.204 +
   1.205 +	// "If the element has a parent element and the dir attribute is not in a
   1.206 +	// defined state (i.e. it is not present or has an invalid value)
   1.207 +	//     The directionality of the element is the same as the element's
   1.208 +	//     parent element's directionality."
   1.209 +	return getDirectionality(element.parentNode);
   1.210 +}
   1.211 +
   1.212 +//@}
   1.213 +
   1.214 +///////////////////////////////////////////////////////////////////////////////
   1.215 +///////////////////////////// DOM Range functions /////////////////////////////
   1.216 +///////////////////////////////////////////////////////////////////////////////
   1.217 +//@{
   1.218 +
   1.219 +function getNodeIndex(node) {
   1.220 +	var ret = 0;
   1.221 +	while (node.previousSibling) {
   1.222 +		ret++;
   1.223 +		node = node.previousSibling;
   1.224 +	}
   1.225 +	return ret;
   1.226 +}
   1.227 +
   1.228 +// "The length of a Node node is the following, depending on node:
   1.229 +//
   1.230 +// ProcessingInstruction
   1.231 +// DocumentType
   1.232 +//   Always 0.
   1.233 +// Text
   1.234 +// Comment
   1.235 +//   node's length.
   1.236 +// Any other node
   1.237 +//   node's childNodes's length."
   1.238 +function getNodeLength(node) {
   1.239 +	switch (node.nodeType) {
   1.240 +		case Node.PROCESSING_INSTRUCTION_NODE:
   1.241 +		case Node.DOCUMENT_TYPE_NODE:
   1.242 +			return 0;
   1.243 +
   1.244 +		case Node.TEXT_NODE:
   1.245 +		case Node.COMMENT_NODE:
   1.246 +			return node.length;
   1.247 +
   1.248 +		default:
   1.249 +			return node.childNodes.length;
   1.250 +	}
   1.251 +}
   1.252 +
   1.253 +/**
   1.254 + * The position of two boundary points relative to one another, as defined by
   1.255 + * DOM Range.
   1.256 + */
   1.257 +function getPosition(nodeA, offsetA, nodeB, offsetB) {
   1.258 +	// "If node A is the same as node B, return equal if offset A equals offset
   1.259 +	// B, before if offset A is less than offset B, and after if offset A is
   1.260 +	// greater than offset B."
   1.261 +	if (nodeA == nodeB) {
   1.262 +		if (offsetA == offsetB) {
   1.263 +			return "equal";
   1.264 +		}
   1.265 +		if (offsetA < offsetB) {
   1.266 +			return "before";
   1.267 +		}
   1.268 +		if (offsetA > offsetB) {
   1.269 +			return "after";
   1.270 +		}
   1.271 +	}
   1.272 +
   1.273 +	// "If node A is after node B in tree order, compute the position of (node
   1.274 +	// B, offset B) relative to (node A, offset A). If it is before, return
   1.275 +	// after. If it is after, return before."
   1.276 +	if (nodeB.compareDocumentPosition(nodeA) & Node.DOCUMENT_POSITION_FOLLOWING) {
   1.277 +		var pos = getPosition(nodeB, offsetB, nodeA, offsetA);
   1.278 +		if (pos == "before") {
   1.279 +			return "after";
   1.280 +		}
   1.281 +		if (pos == "after") {
   1.282 +			return "before";
   1.283 +		}
   1.284 +	}
   1.285 +
   1.286 +	// "If node A is an ancestor of node B:"
   1.287 +	if (nodeB.compareDocumentPosition(nodeA) & Node.DOCUMENT_POSITION_CONTAINS) {
   1.288 +		// "Let child equal node B."
   1.289 +		var child = nodeB;
   1.290 +
   1.291 +		// "While child is not a child of node A, set child to its parent."
   1.292 +		while (child.parentNode != nodeA) {
   1.293 +			child = child.parentNode;
   1.294 +		}
   1.295 +
   1.296 +		// "If the index of child is less than offset A, return after."
   1.297 +		if (getNodeIndex(child) < offsetA) {
   1.298 +			return "after";
   1.299 +		}
   1.300 +	}
   1.301 +
   1.302 +	// "Return before."
   1.303 +	return "before";
   1.304 +}
   1.305 +
   1.306 +/**
   1.307 + * Returns the furthest ancestor of a Node as defined by DOM Range.
   1.308 + */
   1.309 +function getFurthestAncestor(node) {
   1.310 +	var root = node;
   1.311 +	while (root.parentNode != null) {
   1.312 +		root = root.parentNode;
   1.313 +	}
   1.314 +	return root;
   1.315 +}
   1.316 +
   1.317 +/**
   1.318 + * "contained" as defined by DOM Range: "A Node node is contained in a range
   1.319 + * range if node's furthest ancestor is the same as range's root, and (node, 0)
   1.320 + * is after range's start, and (node, length of node) is before range's end."
   1.321 + */
   1.322 +function isContained(node, range) {
   1.323 +	var pos1 = getPosition(node, 0, range.startContainer, range.startOffset);
   1.324 +	var pos2 = getPosition(node, getNodeLength(node), range.endContainer, range.endOffset);
   1.325 +
   1.326 +	return getFurthestAncestor(node) == getFurthestAncestor(range.startContainer)
   1.327 +		&& pos1 == "after"
   1.328 +		&& pos2 == "before";
   1.329 +}
   1.330 +
   1.331 +/**
   1.332 + * Return all nodes contained in range that the provided function returns true
   1.333 + * for, omitting any with an ancestor already being returned.
   1.334 + */
   1.335 +function getContainedNodes(range, condition) {
   1.336 +	if (typeof condition == "undefined") {
   1.337 +		condition = function() { return true };
   1.338 +	}
   1.339 +	var node = range.startContainer;
   1.340 +	if (node.hasChildNodes()
   1.341 +	&& range.startOffset < node.childNodes.length) {
   1.342 +		// A child is contained
   1.343 +		node = node.childNodes[range.startOffset];
   1.344 +	} else if (range.startOffset == getNodeLength(node)) {
   1.345 +		// No descendant can be contained
   1.346 +		node = nextNodeDescendants(node);
   1.347 +	} else {
   1.348 +		// No children; this node at least can't be contained
   1.349 +		node = nextNode(node);
   1.350 +	}
   1.351 +
   1.352 +	var stop = range.endContainer;
   1.353 +	if (stop.hasChildNodes()
   1.354 +	&& range.endOffset < stop.childNodes.length) {
   1.355 +		// The node after the last contained node is a child
   1.356 +		stop = stop.childNodes[range.endOffset];
   1.357 +	} else {
   1.358 +		// This node and/or some of its children might be contained
   1.359 +		stop = nextNodeDescendants(stop);
   1.360 +	}
   1.361 +
   1.362 +	var nodeList = [];
   1.363 +	while (isBefore(node, stop)) {
   1.364 +		if (isContained(node, range)
   1.365 +		&& condition(node)) {
   1.366 +			nodeList.push(node);
   1.367 +			node = nextNodeDescendants(node);
   1.368 +			continue;
   1.369 +		}
   1.370 +		node = nextNode(node);
   1.371 +	}
   1.372 +	return nodeList;
   1.373 +}
   1.374 +
   1.375 +/**
   1.376 + * As above, but includes nodes with an ancestor that's already been returned.
   1.377 + */
   1.378 +function getAllContainedNodes(range, condition) {
   1.379 +	if (typeof condition == "undefined") {
   1.380 +		condition = function() { return true };
   1.381 +	}
   1.382 +	var node = range.startContainer;
   1.383 +	if (node.hasChildNodes()
   1.384 +	&& range.startOffset < node.childNodes.length) {
   1.385 +		// A child is contained
   1.386 +		node = node.childNodes[range.startOffset];
   1.387 +	} else if (range.startOffset == getNodeLength(node)) {
   1.388 +		// No descendant can be contained
   1.389 +		node = nextNodeDescendants(node);
   1.390 +	} else {
   1.391 +		// No children; this node at least can't be contained
   1.392 +		node = nextNode(node);
   1.393 +	}
   1.394 +
   1.395 +	var stop = range.endContainer;
   1.396 +	if (stop.hasChildNodes()
   1.397 +	&& range.endOffset < stop.childNodes.length) {
   1.398 +		// The node after the last contained node is a child
   1.399 +		stop = stop.childNodes[range.endOffset];
   1.400 +	} else {
   1.401 +		// This node and/or some of its children might be contained
   1.402 +		stop = nextNodeDescendants(stop);
   1.403 +	}
   1.404 +
   1.405 +	var nodeList = [];
   1.406 +	while (isBefore(node, stop)) {
   1.407 +		if (isContained(node, range)
   1.408 +		&& condition(node)) {
   1.409 +			nodeList.push(node);
   1.410 +		}
   1.411 +		node = nextNode(node);
   1.412 +	}
   1.413 +	return nodeList;
   1.414 +}
   1.415 +
   1.416 +// Returns either null, or something of the form rgb(x, y, z), or something of
   1.417 +// the form rgb(x, y, z, w) with w != 0.
   1.418 +function normalizeColor(color) {
   1.419 +	if (color.toLowerCase() == "currentcolor") {
   1.420 +		return null;
   1.421 +	}
   1.422 +
   1.423 +	if (normalizeColor.resultCache === undefined) {
   1.424 +		normalizeColor.resultCache = {};
   1.425 +	}
   1.426 +
   1.427 +	if (normalizeColor.resultCache[color] !== undefined) {
   1.428 +		return normalizeColor.resultCache[color];
   1.429 +	}
   1.430 +
   1.431 +	var originalColor = color;
   1.432 +
   1.433 +	var outerSpan = document.createElement("span");
   1.434 +	document.body.appendChild(outerSpan);
   1.435 +	outerSpan.style.color = "black";
   1.436 +
   1.437 +	var innerSpan = document.createElement("span");
   1.438 +	outerSpan.appendChild(innerSpan);
   1.439 +	innerSpan.style.color = color;
   1.440 +	color = getComputedStyle(innerSpan).color;
   1.441 +
   1.442 +	if (color == "rgb(0, 0, 0)") {
   1.443 +		// Maybe it's really black, maybe it's invalid.
   1.444 +		outerSpan.color = "white";
   1.445 +		color = getComputedStyle(innerSpan).color;
   1.446 +		if (color != "rgb(0, 0, 0)") {
   1.447 +			return normalizeColor.resultCache[originalColor] = null;
   1.448 +		}
   1.449 +	}
   1.450 +
   1.451 +	document.body.removeChild(outerSpan);
   1.452 +
   1.453 +	// I rely on the fact that browsers generally provide consistent syntax for
   1.454 +	// getComputedStyle(), although it's not standardized.  There are only
   1.455 +	// three exceptions I found:
   1.456 +	if (/^rgba\([0-9]+, [0-9]+, [0-9]+, 1\)$/.test(color)) {
   1.457 +		// IE10PP2 seems to do this sometimes.
   1.458 +		return normalizeColor.resultCache[originalColor] =
   1.459 +			color.replace("rgba", "rgb").replace(", 1)", ")");
   1.460 +	}
   1.461 +	if (color == "transparent") {
   1.462 +		// IE10PP2, Firefox 7.0a2, and Opera 11.50 all return "transparent" if
   1.463 +		// the specified value is "transparent".
   1.464 +		return normalizeColor.resultCache[originalColor] =
   1.465 +			"rgba(0, 0, 0, 0)";
   1.466 +	}
   1.467 +	// Chrome 15 dev adds way too many significant figures.  This isn't a full
   1.468 +	// fix, it just fixes one case that comes up in tests.
   1.469 +	color = color.replace(/, 0.496094\)$/, ", 0.5)");
   1.470 +	return normalizeColor.resultCache[originalColor] = color;
   1.471 +}
   1.472 +
   1.473 +// Returns either null, or something of the form #xxxxxx.
   1.474 +function parseSimpleColor(color) {
   1.475 +	color = normalizeColor(color);
   1.476 +	var matches = /^rgb\(([0-9]+), ([0-9]+), ([0-9]+)\)$/.exec(color);
   1.477 +	if (matches) {
   1.478 +		return "#"
   1.479 +			+ parseInt(matches[1]).toString(16).replace(/^.$/, "0$&")
   1.480 +			+ parseInt(matches[2]).toString(16).replace(/^.$/, "0$&")
   1.481 +			+ parseInt(matches[3]).toString(16).replace(/^.$/, "0$&");
   1.482 +	}
   1.483 +	return null;
   1.484 +}
   1.485 +
   1.486 +//@}
   1.487 +
   1.488 +//////////////////////////////////////////////////////////////////////////////
   1.489 +/////////////////////////// Edit command functions ///////////////////////////
   1.490 +//////////////////////////////////////////////////////////////////////////////
   1.491 +
   1.492 +/////////////////////////////////////////////////
   1.493 +///// Methods of the HTMLDocument interface /////
   1.494 +/////////////////////////////////////////////////
   1.495 +//@{
   1.496 +
   1.497 +var executionStackDepth = 0;
   1.498 +
   1.499 +// Helper function for common behavior.
   1.500 +function editCommandMethod(command, range, callback) {
   1.501 +	// Set up our global range magic, but only if we're the outermost function
   1.502 +	if (executionStackDepth == 0 && typeof range != "undefined") {
   1.503 +		globalRange = range;
   1.504 +	} else if (executionStackDepth == 0) {
   1.505 +		globalRange = null;
   1.506 +		globalRange = getActiveRange();
   1.507 +	}
   1.508 +
   1.509 +	executionStackDepth++;
   1.510 +	try {
   1.511 +		var ret = callback();
   1.512 +	} catch(e) {
   1.513 +		executionStackDepth--;
   1.514 +		throw e;
   1.515 +	}
   1.516 +	executionStackDepth--;
   1.517 +	return ret;
   1.518 +}
   1.519 +
   1.520 +function myExecCommand(command, showUi, value, range) {
   1.521 +	// "All of these methods must treat their command argument ASCII
   1.522 +	// case-insensitively."
   1.523 +	command = command.toLowerCase();
   1.524 +
   1.525 +	// "If only one argument was provided, let show UI be false."
   1.526 +	//
   1.527 +	// If range was passed, I can't actually detect how many args were passed
   1.528 +	// . . .
   1.529 +	if (arguments.length == 1
   1.530 +	|| (arguments.length >=4 && typeof showUi == "undefined")) {
   1.531 +		showUi = false;
   1.532 +	}
   1.533 +
   1.534 +	// "If only one or two arguments were provided, let value be the empty
   1.535 +	// string."
   1.536 +	if (arguments.length <= 2
   1.537 +	|| (arguments.length >=4 && typeof value == "undefined")) {
   1.538 +		value = "";
   1.539 +	}
   1.540 +
   1.541 +	return editCommandMethod(command, range, (function(command, showUi, value) { return function() {
   1.542 +		// "If command is not supported or not enabled, return false."
   1.543 +		if (!(command in commands) || !myQueryCommandEnabled(command)) {
   1.544 +			return false;
   1.545 +		}
   1.546 +
   1.547 +		// "Take the action for command, passing value to the instructions as an
   1.548 +		// argument."
   1.549 +		var ret = commands[command].action(value);
   1.550 +
   1.551 +		// Check for bugs
   1.552 +		if (ret !== true && ret !== false) {
   1.553 +			throw "execCommand() didn't return true or false: " + ret;
   1.554 +		}
   1.555 +
   1.556 +		// "If the previous step returned false, return false."
   1.557 +		if (ret === false) {
   1.558 +			return false;
   1.559 +		}
   1.560 +
   1.561 +		// "Return true."
   1.562 +		return true;
   1.563 +	}})(command, showUi, value));
   1.564 +}
   1.565 +
   1.566 +function myQueryCommandEnabled(command, range) {
   1.567 +	// "All of these methods must treat their command argument ASCII
   1.568 +	// case-insensitively."
   1.569 +	command = command.toLowerCase();
   1.570 +
   1.571 +	return editCommandMethod(command, range, (function(command) { return function() {
   1.572 +		// "Return true if command is both supported and enabled, false
   1.573 +		// otherwise."
   1.574 +		if (!(command in commands)) {
   1.575 +			return false;
   1.576 +		}
   1.577 +
   1.578 +		// "Among commands defined in this specification, those listed in
   1.579 +		// Miscellaneous commands are always enabled, except for the cut
   1.580 +		// command and the paste command. The other commands defined here are
   1.581 +		// enabled if the active range is not null, its start node is either
   1.582 +		// editable or an editing host, its end node is either editable or an
   1.583 +		// editing host, and there is some editing host that is an inclusive
   1.584 +		// ancestor of both its start node and its end node."
   1.585 +		return ["copy", "defaultparagraphseparator", "selectall", "stylewithcss",
   1.586 +		"usecss"].indexOf(command) != -1
   1.587 +			|| (
   1.588 +				getActiveRange() !== null
   1.589 +				&& (isEditable(getActiveRange().startContainer) || isEditingHost(getActiveRange().startContainer))
   1.590 +				&& (isEditable(getActiveRange().endContainer) || isEditingHost(getActiveRange().endContainer))
   1.591 +				&& (getInclusiveAncestors(getActiveRange().commonAncestorContainer).some(isEditingHost))
   1.592 +			);
   1.593 +	}})(command));
   1.594 +}
   1.595 +
   1.596 +function myQueryCommandIndeterm(command, range) {
   1.597 +	// "All of these methods must treat their command argument ASCII
   1.598 +	// case-insensitively."
   1.599 +	command = command.toLowerCase();
   1.600 +
   1.601 +	return editCommandMethod(command, range, (function(command) { return function() {
   1.602 +		// "If command is not supported or has no indeterminacy, return false."
   1.603 +		if (!(command in commands) || !("indeterm" in commands[command])) {
   1.604 +			return false;
   1.605 +		}
   1.606 +
   1.607 +		// "Return true if command is indeterminate, otherwise false."
   1.608 +		return commands[command].indeterm();
   1.609 +	}})(command));
   1.610 +}
   1.611 +
   1.612 +function myQueryCommandState(command, range) {
   1.613 +	// "All of these methods must treat their command argument ASCII
   1.614 +	// case-insensitively."
   1.615 +	command = command.toLowerCase();
   1.616 +
   1.617 +	return editCommandMethod(command, range, (function(command) { return function() {
   1.618 +		// "If command is not supported or has no state, return false."
   1.619 +		if (!(command in commands) || !("state" in commands[command])) {
   1.620 +			return false;
   1.621 +		}
   1.622 +
   1.623 +		// "If the state override for command is set, return it."
   1.624 +		if (typeof getStateOverride(command) != "undefined") {
   1.625 +			return getStateOverride(command);
   1.626 +		}
   1.627 +
   1.628 +		// "Return true if command's state is true, otherwise false."
   1.629 +		return commands[command].state();
   1.630 +	}})(command));
   1.631 +}
   1.632 +
   1.633 +// "When the queryCommandSupported(command) method on the HTMLDocument
   1.634 +// interface is invoked, the user agent must return true if command is
   1.635 +// supported, and false otherwise."
   1.636 +function myQueryCommandSupported(command) {
   1.637 +	// "All of these methods must treat their command argument ASCII
   1.638 +	// case-insensitively."
   1.639 +	command = command.toLowerCase();
   1.640 +
   1.641 +	return command in commands;
   1.642 +}
   1.643 +
   1.644 +function myQueryCommandValue(command, range) {
   1.645 +	// "All of these methods must treat their command argument ASCII
   1.646 +	// case-insensitively."
   1.647 +	command = command.toLowerCase();
   1.648 +
   1.649 +	return editCommandMethod(command, range, function() {
   1.650 +		// "If command is not supported or has no value, return the empty string."
   1.651 +		if (!(command in commands) || !("value" in commands[command])) {
   1.652 +			return "";
   1.653 +		}
   1.654 +
   1.655 +		// "If command is "fontSize" and its value override is set, convert the
   1.656 +		// value override to an integer number of pixels and return the legacy
   1.657 +		// font size for the result."
   1.658 +		if (command == "fontsize"
   1.659 +		&& getValueOverride("fontsize") !== undefined) {
   1.660 +			return getLegacyFontSize(getValueOverride("fontsize"));
   1.661 +		}
   1.662 +
   1.663 +		// "If the value override for command is set, return it."
   1.664 +		if (typeof getValueOverride(command) != "undefined") {
   1.665 +			return getValueOverride(command);
   1.666 +		}
   1.667 +
   1.668 +		// "Return command's value."
   1.669 +		return commands[command].value();
   1.670 +	});
   1.671 +}
   1.672 +//@}
   1.673 +
   1.674 +//////////////////////////////
   1.675 +///// Common definitions /////
   1.676 +//////////////////////////////
   1.677 +//@{
   1.678 +
   1.679 +// "An HTML element is an Element whose namespace is the HTML namespace."
   1.680 +//
   1.681 +// I allow an extra argument to more easily check whether something is a
   1.682 +// particular HTML element, like isHtmlElement(node, "OL").  It accepts arrays
   1.683 +// too, like isHtmlElement(node, ["OL", "UL"]) to check if it's an ol or ul.
   1.684 +function isHtmlElement(node, tags) {
   1.685 +	if (typeof tags == "string") {
   1.686 +		tags = [tags];
   1.687 +	}
   1.688 +	if (typeof tags == "object") {
   1.689 +		tags = tags.map(function(tag) { return tag.toUpperCase() });
   1.690 +	}
   1.691 +	return node
   1.692 +		&& node.nodeType == Node.ELEMENT_NODE
   1.693 +		&& isHtmlNamespace(node.namespaceURI)
   1.694 +		&& (typeof tags == "undefined" || tags.indexOf(node.tagName) != -1);
   1.695 +}
   1.696 +
   1.697 +// "A prohibited paragraph child name is "address", "article", "aside",
   1.698 +// "blockquote", "caption", "center", "col", "colgroup", "dd", "details",
   1.699 +// "dir", "div", "dl", "dt", "fieldset", "figcaption", "figure", "footer",
   1.700 +// "form", "h1", "h2", "h3", "h4", "h5", "h6", "header", "hgroup", "hr", "li",
   1.701 +// "listing", "menu", "nav", "ol", "p", "plaintext", "pre", "section",
   1.702 +// "summary", "table", "tbody", "td", "tfoot", "th", "thead", "tr", "ul", or
   1.703 +// "xmp"."
   1.704 +var prohibitedParagraphChildNames = ["address", "article", "aside",
   1.705 +	"blockquote", "caption", "center", "col", "colgroup", "dd", "details",
   1.706 +	"dir", "div", "dl", "dt", "fieldset", "figcaption", "figure", "footer",
   1.707 +	"form", "h1", "h2", "h3", "h4", "h5", "h6", "header", "hgroup", "hr", "li",
   1.708 +	"listing", "menu", "nav", "ol", "p", "plaintext", "pre", "section",
   1.709 +	"summary", "table", "tbody", "td", "tfoot", "th", "thead", "tr", "ul",
   1.710 +	"xmp"];
   1.711 +
   1.712 +// "A prohibited paragraph child is an HTML element whose local name is a
   1.713 +// prohibited paragraph child name."
   1.714 +function isProhibitedParagraphChild(node) {
   1.715 +	return isHtmlElement(node, prohibitedParagraphChildNames);
   1.716 +}
   1.717 +
   1.718 +// "A block node is either an Element whose "display" property does not have
   1.719 +// resolved value "inline" or "inline-block" or "inline-table" or "none", or a
   1.720 +// Document, or a DocumentFragment."
   1.721 +function isBlockNode(node) {
   1.722 +	return node
   1.723 +		&& ((node.nodeType == Node.ELEMENT_NODE && ["inline", "inline-block", "inline-table", "none"].indexOf(getComputedStyle(node).display) == -1)
   1.724 +		|| node.nodeType == Node.DOCUMENT_NODE
   1.725 +		|| node.nodeType == Node.DOCUMENT_FRAGMENT_NODE);
   1.726 +}
   1.727 +
   1.728 +// "An inline node is a node that is not a block node."
   1.729 +function isInlineNode(node) {
   1.730 +	return node && !isBlockNode(node);
   1.731 +}
   1.732 +
   1.733 +// "An editing host is a node that is either an HTML element with a
   1.734 +// contenteditable attribute set to the true state, or the HTML element child
   1.735 +// of a Document whose designMode is enabled."
   1.736 +function isEditingHost(node) {
   1.737 +	return node
   1.738 +		&& isHtmlElement(node)
   1.739 +		&& (node.contentEditable == "true"
   1.740 +		|| (node.parentNode
   1.741 +		&& node.parentNode.nodeType == Node.DOCUMENT_NODE
   1.742 +		&& node.parentNode.designMode == "on"));
   1.743 +}
   1.744 +
   1.745 +// "Something is editable if it is a node; it is not an editing host; it does
   1.746 +// not have a contenteditable attribute set to the false state; its parent is
   1.747 +// an editing host or editable; and either it is an HTML element, or it is an
   1.748 +// svg or math element, or it is not an Element and its parent is an HTML
   1.749 +// element."
   1.750 +function isEditable(node) {
   1.751 +	return node
   1.752 +		&& !isEditingHost(node)
   1.753 +		&& (node.nodeType != Node.ELEMENT_NODE || node.contentEditable != "false")
   1.754 +		&& (isEditingHost(node.parentNode) || isEditable(node.parentNode))
   1.755 +		&& (isHtmlElement(node)
   1.756 +		|| (node.nodeType == Node.ELEMENT_NODE && node.namespaceURI == "http://www.w3.org/2000/svg" && node.localName == "svg")
   1.757 +		|| (node.nodeType == Node.ELEMENT_NODE && node.namespaceURI == "http://www.w3.org/1998/Math/MathML" && node.localName == "math")
   1.758 +		|| (node.nodeType != Node.ELEMENT_NODE && isHtmlElement(node.parentNode)));
   1.759 +}
   1.760 +
   1.761 +// Helper function, not defined in the spec
   1.762 +function hasEditableDescendants(node) {
   1.763 +	for (var i = 0; i < node.childNodes.length; i++) {
   1.764 +		if (isEditable(node.childNodes[i])
   1.765 +		|| hasEditableDescendants(node.childNodes[i])) {
   1.766 +			return true;
   1.767 +		}
   1.768 +	}
   1.769 +	return false;
   1.770 +}
   1.771 +
   1.772 +// "The editing host of node is null if node is neither editable nor an editing
   1.773 +// host; node itself, if node is an editing host; or the nearest ancestor of
   1.774 +// node that is an editing host, if node is editable."
   1.775 +function getEditingHostOf(node) {
   1.776 +	if (isEditingHost(node)) {
   1.777 +		return node;
   1.778 +	} else if (isEditable(node)) {
   1.779 +		var ancestor = node.parentNode;
   1.780 +		while (!isEditingHost(ancestor)) {
   1.781 +			ancestor = ancestor.parentNode;
   1.782 +		}
   1.783 +		return ancestor;
   1.784 +	} else {
   1.785 +		return null;
   1.786 +	}
   1.787 +}
   1.788 +
   1.789 +// "Two nodes are in the same editing host if the editing host of the first is
   1.790 +// non-null and the same as the editing host of the second."
   1.791 +function inSameEditingHost(node1, node2) {
   1.792 +	return getEditingHostOf(node1)
   1.793 +		&& getEditingHostOf(node1) == getEditingHostOf(node2);
   1.794 +}
   1.795 +
   1.796 +// "A collapsed line break is a br that begins a line box which has nothing
   1.797 +// else in it, and therefore has zero height."
   1.798 +function isCollapsedLineBreak(br) {
   1.799 +	if (!isHtmlElement(br, "br")) {
   1.800 +		return false;
   1.801 +	}
   1.802 +
   1.803 +	// Add a zwsp after it and see if that changes the height of the nearest
   1.804 +	// non-inline parent.  Note: this is not actually reliable, because the
   1.805 +	// parent might have a fixed height or something.
   1.806 +	var ref = br.parentNode;
   1.807 +	while (getComputedStyle(ref).display == "inline") {
   1.808 +		ref = ref.parentNode;
   1.809 +	}
   1.810 +	var refStyle = ref.hasAttribute("style") ? ref.getAttribute("style") : null;
   1.811 +	ref.style.height = "auto";
   1.812 +	ref.style.maxHeight = "none";
   1.813 +	ref.style.minHeight = "0";
   1.814 +	var space = document.createTextNode("\u200b");
   1.815 +	var origHeight = ref.offsetHeight;
   1.816 +	if (origHeight == 0) {
   1.817 +		throw "isCollapsedLineBreak: original height is zero, bug?";
   1.818 +	}
   1.819 +	br.parentNode.insertBefore(space, br.nextSibling);
   1.820 +	var finalHeight = ref.offsetHeight;
   1.821 +	space.parentNode.removeChild(space);
   1.822 +	if (refStyle === null) {
   1.823 +		// Without the setAttribute() line, removeAttribute() doesn't work in
   1.824 +		// Chrome 14 dev.  I have no idea why.
   1.825 +		ref.setAttribute("style", "");
   1.826 +		ref.removeAttribute("style");
   1.827 +	} else {
   1.828 +		ref.setAttribute("style", refStyle);
   1.829 +	}
   1.830 +
   1.831 +	// Allow some leeway in case the zwsp didn't create a whole new line, but
   1.832 +	// only made an existing line slightly higher.  Firefox 6.0a2 shows this
   1.833 +	// behavior when the first line is bold.
   1.834 +	return origHeight < finalHeight - 5;
   1.835 +}
   1.836 +
   1.837 +// "An extraneous line break is a br that has no visual effect, in that
   1.838 +// removing it from the DOM would not change layout, except that a br that is
   1.839 +// the sole child of an li is not extraneous."
   1.840 +//
   1.841 +// FIXME: This doesn't work in IE, since IE ignores display: none in
   1.842 +// contenteditable.
   1.843 +function isExtraneousLineBreak(br) {
   1.844 +	if (!isHtmlElement(br, "br")) {
   1.845 +		return false;
   1.846 +	}
   1.847 +
   1.848 +	if (isHtmlElement(br.parentNode, "li")
   1.849 +	&& br.parentNode.childNodes.length == 1) {
   1.850 +		return false;
   1.851 +	}
   1.852 +
   1.853 +	// Make the line break disappear and see if that changes the block's
   1.854 +	// height.  Yes, this is an absurd hack.  We have to reset height etc. on
   1.855 +	// the reference node because otherwise its height won't change if it's not
   1.856 +	// auto.
   1.857 +	var ref = br.parentNode;
   1.858 +	while (getComputedStyle(ref).display == "inline") {
   1.859 +		ref = ref.parentNode;
   1.860 +	}
   1.861 +	var refStyle = ref.hasAttribute("style") ? ref.getAttribute("style") : null;
   1.862 +	ref.style.height = "auto";
   1.863 +	ref.style.maxHeight = "none";
   1.864 +	ref.style.minHeight = "0";
   1.865 +	var brStyle = br.hasAttribute("style") ? br.getAttribute("style") : null;
   1.866 +	var origHeight = ref.offsetHeight;
   1.867 +	if (origHeight == 0) {
   1.868 +		throw "isExtraneousLineBreak: original height is zero, bug?";
   1.869 +	}
   1.870 +	br.setAttribute("style", "display:none");
   1.871 +	var finalHeight = ref.offsetHeight;
   1.872 +	if (refStyle === null) {
   1.873 +		// Without the setAttribute() line, removeAttribute() doesn't work in
   1.874 +		// Chrome 14 dev.  I have no idea why.
   1.875 +		ref.setAttribute("style", "");
   1.876 +		ref.removeAttribute("style");
   1.877 +	} else {
   1.878 +		ref.setAttribute("style", refStyle);
   1.879 +	}
   1.880 +	if (brStyle === null) {
   1.881 +		br.removeAttribute("style");
   1.882 +	} else {
   1.883 +		br.setAttribute("style", brStyle);
   1.884 +	}
   1.885 +
   1.886 +	return origHeight == finalHeight;
   1.887 +}
   1.888 +
   1.889 +// "A whitespace node is either a Text node whose data is the empty string; or
   1.890 +// a Text node whose data consists only of one or more tabs (0x0009), line
   1.891 +// feeds (0x000A), carriage returns (0x000D), and/or spaces (0x0020), and whose
   1.892 +// parent is an Element whose resolved value for "white-space" is "normal" or
   1.893 +// "nowrap"; or a Text node whose data consists only of one or more tabs
   1.894 +// (0x0009), carriage returns (0x000D), and/or spaces (0x0020), and whose
   1.895 +// parent is an Element whose resolved value for "white-space" is "pre-line"."
   1.896 +function isWhitespaceNode(node) {
   1.897 +	return node
   1.898 +		&& node.nodeType == Node.TEXT_NODE
   1.899 +		&& (node.data == ""
   1.900 +		|| (
   1.901 +			/^[\t\n\r ]+$/.test(node.data)
   1.902 +			&& node.parentNode
   1.903 +			&& node.parentNode.nodeType == Node.ELEMENT_NODE
   1.904 +			&& ["normal", "nowrap"].indexOf(getComputedStyle(node.parentNode).whiteSpace) != -1
   1.905 +		) || (
   1.906 +			/^[\t\r ]+$/.test(node.data)
   1.907 +			&& node.parentNode
   1.908 +			&& node.parentNode.nodeType == Node.ELEMENT_NODE
   1.909 +			&& getComputedStyle(node.parentNode).whiteSpace == "pre-line"
   1.910 +		));
   1.911 +}
   1.912 +
   1.913 +// "node is a collapsed whitespace node if the following algorithm returns
   1.914 +// true:"
   1.915 +function isCollapsedWhitespaceNode(node) {
   1.916 +	// "If node is not a whitespace node, return false."
   1.917 +	if (!isWhitespaceNode(node)) {
   1.918 +		return false;
   1.919 +	}
   1.920 +
   1.921 +	// "If node's data is the empty string, return true."
   1.922 +	if (node.data == "") {
   1.923 +		return true;
   1.924 +	}
   1.925 +
   1.926 +	// "Let ancestor be node's parent."
   1.927 +	var ancestor = node.parentNode;
   1.928 +
   1.929 +	// "If ancestor is null, return true."
   1.930 +	if (!ancestor) {
   1.931 +		return true;
   1.932 +	}
   1.933 +
   1.934 +	// "If the "display" property of some ancestor of node has resolved value
   1.935 +	// "none", return true."
   1.936 +	if (getAncestors(node).some(function(ancestor) {
   1.937 +		return ancestor.nodeType == Node.ELEMENT_NODE
   1.938 +			&& getComputedStyle(ancestor).display == "none";
   1.939 +	})) {
   1.940 +		return true;
   1.941 +	}
   1.942 +
   1.943 +	// "While ancestor is not a block node and its parent is not null, set
   1.944 +	// ancestor to its parent."
   1.945 +	while (!isBlockNode(ancestor)
   1.946 +	&& ancestor.parentNode) {
   1.947 +		ancestor = ancestor.parentNode;
   1.948 +	}
   1.949 +
   1.950 +	// "Let reference be node."
   1.951 +	var reference = node;
   1.952 +
   1.953 +	// "While reference is a descendant of ancestor:"
   1.954 +	while (reference != ancestor) {
   1.955 +		// "Let reference be the node before it in tree order."
   1.956 +		reference = previousNode(reference);
   1.957 +
   1.958 +		// "If reference is a block node or a br, return true."
   1.959 +		if (isBlockNode(reference)
   1.960 +		|| isHtmlElement(reference, "br")) {
   1.961 +			return true;
   1.962 +		}
   1.963 +
   1.964 +		// "If reference is a Text node that is not a whitespace node, or is an
   1.965 +		// img, break from this loop."
   1.966 +		if ((reference.nodeType == Node.TEXT_NODE && !isWhitespaceNode(reference))
   1.967 +		|| isHtmlElement(reference, "img")) {
   1.968 +			break;
   1.969 +		}
   1.970 +	}
   1.971 +
   1.972 +	// "Let reference be node."
   1.973 +	reference = node;
   1.974 +
   1.975 +	// "While reference is a descendant of ancestor:"
   1.976 +	var stop = nextNodeDescendants(ancestor);
   1.977 +	while (reference != stop) {
   1.978 +		// "Let reference be the node after it in tree order, or null if there
   1.979 +		// is no such node."
   1.980 +		reference = nextNode(reference);
   1.981 +
   1.982 +		// "If reference is a block node or a br, return true."
   1.983 +		if (isBlockNode(reference)
   1.984 +		|| isHtmlElement(reference, "br")) {
   1.985 +			return true;
   1.986 +		}
   1.987 +
   1.988 +		// "If reference is a Text node that is not a whitespace node, or is an
   1.989 +		// img, break from this loop."
   1.990 +		if ((reference && reference.nodeType == Node.TEXT_NODE && !isWhitespaceNode(reference))
   1.991 +		|| isHtmlElement(reference, "img")) {
   1.992 +			break;
   1.993 +		}
   1.994 +	}
   1.995 +
   1.996 +	// "Return false."
   1.997 +	return false;
   1.998 +}
   1.999 +
  1.1000 +// "Something is visible if it is a node that either is a block node, or a Text
  1.1001 +// node that is not a collapsed whitespace node, or an img, or a br that is not
  1.1002 +// an extraneous line break, or any node with a visible descendant; excluding
  1.1003 +// any node with an ancestor container Element whose "display" property has
  1.1004 +// resolved value "none"."
  1.1005 +function isVisible(node) {
  1.1006 +	if (!node) {
  1.1007 +		return false;
  1.1008 +	}
  1.1009 +
  1.1010 +	if (getAncestors(node).concat(node)
  1.1011 +	.filter(function(node) { return node.nodeType == Node.ELEMENT_NODE })
  1.1012 +	.some(function(node) { return getComputedStyle(node).display == "none" })) {
  1.1013 +		return false;
  1.1014 +	}
  1.1015 +
  1.1016 +	if (isBlockNode(node)
  1.1017 +	|| (node.nodeType == Node.TEXT_NODE && !isCollapsedWhitespaceNode(node))
  1.1018 +	|| isHtmlElement(node, "img")
  1.1019 +	|| (isHtmlElement(node, "br") && !isExtraneousLineBreak(node))) {
  1.1020 +		return true;
  1.1021 +	}
  1.1022 +
  1.1023 +	for (var i = 0; i < node.childNodes.length; i++) {
  1.1024 +		if (isVisible(node.childNodes[i])) {
  1.1025 +			return true;
  1.1026 +		}
  1.1027 +	}
  1.1028 +
  1.1029 +	return false;
  1.1030 +}
  1.1031 +
  1.1032 +// "Something is invisible if it is a node that is not visible."
  1.1033 +function isInvisible(node) {
  1.1034 +	return node && !isVisible(node);
  1.1035 +}
  1.1036 +
  1.1037 +// "A collapsed block prop is either a collapsed line break that is not an
  1.1038 +// extraneous line break, or an Element that is an inline node and whose
  1.1039 +// children are all either invisible or collapsed block props and that has at
  1.1040 +// least one child that is a collapsed block prop."
  1.1041 +function isCollapsedBlockProp(node) {
  1.1042 +	if (isCollapsedLineBreak(node)
  1.1043 +	&& !isExtraneousLineBreak(node)) {
  1.1044 +		return true;
  1.1045 +	}
  1.1046 +
  1.1047 +	if (!isInlineNode(node)
  1.1048 +	|| node.nodeType != Node.ELEMENT_NODE) {
  1.1049 +		return false;
  1.1050 +	}
  1.1051 +
  1.1052 +	var hasCollapsedBlockPropChild = false;
  1.1053 +	for (var i = 0; i < node.childNodes.length; i++) {
  1.1054 +		if (!isInvisible(node.childNodes[i])
  1.1055 +		&& !isCollapsedBlockProp(node.childNodes[i])) {
  1.1056 +			return false;
  1.1057 +		}
  1.1058 +		if (isCollapsedBlockProp(node.childNodes[i])) {
  1.1059 +			hasCollapsedBlockPropChild = true;
  1.1060 +		}
  1.1061 +	}
  1.1062 +
  1.1063 +	return hasCollapsedBlockPropChild;
  1.1064 +}
  1.1065 +
  1.1066 +// "The active range is the range of the selection given by calling
  1.1067 +// getSelection() on the context object. (Thus the active range may be null.)"
  1.1068 +//
  1.1069 +// We cheat and return globalRange if that's defined.  We also ensure that the
  1.1070 +// active range meets the requirements that selection boundary points are
  1.1071 +// supposed to meet, i.e., that the nodes are both Text or Element nodes that
  1.1072 +// descend from a Document.
  1.1073 +function getActiveRange() {
  1.1074 +	var ret;
  1.1075 +	if (globalRange) {
  1.1076 +		ret = globalRange;
  1.1077 +	} else if (getSelection().rangeCount) {
  1.1078 +		ret = getSelection().getRangeAt(0);
  1.1079 +	} else {
  1.1080 +		return null;
  1.1081 +	}
  1.1082 +	if ([Node.TEXT_NODE, Node.ELEMENT_NODE].indexOf(ret.startContainer.nodeType) == -1
  1.1083 +	|| [Node.TEXT_NODE, Node.ELEMENT_NODE].indexOf(ret.endContainer.nodeType) == -1
  1.1084 +	|| !ret.startContainer.ownerDocument
  1.1085 +	|| !ret.endContainer.ownerDocument
  1.1086 +	|| !isDescendant(ret.startContainer, ret.startContainer.ownerDocument)
  1.1087 +	|| !isDescendant(ret.endContainer, ret.endContainer.ownerDocument)) {
  1.1088 +		throw "Invalid active range; test bug?";
  1.1089 +	}
  1.1090 +	return ret;
  1.1091 +}
  1.1092 +
  1.1093 +// "For some commands, each HTMLDocument must have a boolean state override
  1.1094 +// and/or a string value override. These do not change the command's state or
  1.1095 +// value, but change the way some algorithms behave, as specified in those
  1.1096 +// algorithms' definitions. Initially, both must be unset for every command.
  1.1097 +// Whenever the number of ranges in the Selection changes to something
  1.1098 +// different, and whenever a boundary point of the range at a given index in
  1.1099 +// the Selection changes to something different, the state override and value
  1.1100 +// override must be unset for every command."
  1.1101 +//
  1.1102 +// We implement this crudely by using setters and getters.  To verify that the
  1.1103 +// selection hasn't changed, we copy the active range and just check the
  1.1104 +// endpoints match.  This isn't really correct, but it's good enough for us.
  1.1105 +// Unset state/value overrides are undefined.  We put everything in a function
  1.1106 +// so no one can access anything except via the provided functions, since
  1.1107 +// otherwise callers might mistakenly use outdated overrides (if the selection
  1.1108 +// has changed).
  1.1109 +var getStateOverride, setStateOverride, unsetStateOverride,
  1.1110 +	getValueOverride, setValueOverride, unsetValueOverride;
  1.1111 +(function() {
  1.1112 +	var stateOverrides = {};
  1.1113 +	var valueOverrides = {};
  1.1114 +	var storedRange = null;
  1.1115 +
  1.1116 +	function resetOverrides() {
  1.1117 +		if (!storedRange
  1.1118 +		|| storedRange.startContainer != getActiveRange().startContainer
  1.1119 +		|| storedRange.endContainer != getActiveRange().endContainer
  1.1120 +		|| storedRange.startOffset != getActiveRange().startOffset
  1.1121 +		|| storedRange.endOffset != getActiveRange().endOffset) {
  1.1122 +			stateOverrides = {};
  1.1123 +			valueOverrides = {};
  1.1124 +			storedRange = getActiveRange().cloneRange();
  1.1125 +		}
  1.1126 +	}
  1.1127 +
  1.1128 +	getStateOverride = function(command) {
  1.1129 +		resetOverrides();
  1.1130 +		return stateOverrides[command];
  1.1131 +	};
  1.1132 +
  1.1133 +	setStateOverride = function(command, newState) {
  1.1134 +		resetOverrides();
  1.1135 +		stateOverrides[command] = newState;
  1.1136 +	};
  1.1137 +
  1.1138 +	unsetStateOverride = function(command) {
  1.1139 +		resetOverrides();
  1.1140 +		delete stateOverrides[command];
  1.1141 +	}
  1.1142 +
  1.1143 +	getValueOverride = function(command) {
  1.1144 +		resetOverrides();
  1.1145 +		return valueOverrides[command];
  1.1146 +	}
  1.1147 +
  1.1148 +	// "The value override for the backColor command must be the same as the
  1.1149 +	// value override for the hiliteColor command, such that setting one sets
  1.1150 +	// the other to the same thing and unsetting one unsets the other."
  1.1151 +	setValueOverride = function(command, newValue) {
  1.1152 +		resetOverrides();
  1.1153 +		valueOverrides[command] = newValue;
  1.1154 +		if (command == "backcolor") {
  1.1155 +			valueOverrides.hilitecolor = newValue;
  1.1156 +		} else if (command == "hilitecolor") {
  1.1157 +			valueOverrides.backcolor = newValue;
  1.1158 +		}
  1.1159 +	}
  1.1160 +
  1.1161 +	unsetValueOverride = function(command) {
  1.1162 +		resetOverrides();
  1.1163 +		delete valueOverrides[command];
  1.1164 +		if (command == "backcolor") {
  1.1165 +			delete valueOverrides.hilitecolor;
  1.1166 +		} else if (command == "hilitecolor") {
  1.1167 +			delete valueOverrides.backcolor;
  1.1168 +		}
  1.1169 +	}
  1.1170 +})();
  1.1171 +
  1.1172 +//@}
  1.1173 +
  1.1174 +/////////////////////////////
  1.1175 +///// Common algorithms /////
  1.1176 +/////////////////////////////
  1.1177 +
  1.1178 +///// Assorted common algorithms /////
  1.1179 +//@{
  1.1180 +
  1.1181 +// Magic array of extra ranges whose endpoints we want to preserve.
  1.1182 +var extraRanges = [];
  1.1183 +
  1.1184 +function movePreservingRanges(node, newParent, newIndex) {
  1.1185 +	// For convenience, I allow newIndex to be -1 to mean "insert at the end".
  1.1186 +	if (newIndex == -1) {
  1.1187 +		newIndex = newParent.childNodes.length;
  1.1188 +	}
  1.1189 +
  1.1190 +	// "When the user agent is to move a Node to a new location, preserving
  1.1191 +	// ranges, it must remove the Node from its original parent (if any), then
  1.1192 +	// insert it in the new location. In doing so, however, it must ignore the
  1.1193 +	// regular range mutation rules, and instead follow these rules:"
  1.1194 +
  1.1195 +	// "Let node be the moved Node, old parent and old index be the old parent
  1.1196 +	// (which may be null) and index, and new parent and new index be the new
  1.1197 +	// parent and index."
  1.1198 +	var oldParent = node.parentNode;
  1.1199 +	var oldIndex = getNodeIndex(node);
  1.1200 +
  1.1201 +	// We preserve the global range object, the ranges in the selection, and
  1.1202 +	// any range that's in the extraRanges array.  Any other ranges won't get
  1.1203 +	// updated, because we have no references to them.
  1.1204 +	var ranges = [globalRange].concat(extraRanges);
  1.1205 +	for (var i = 0; i < getSelection().rangeCount; i++) {
  1.1206 +		ranges.push(getSelection().getRangeAt(i));
  1.1207 +	}
  1.1208 +	var boundaryPoints = [];
  1.1209 +	ranges.forEach(function(range) {
  1.1210 +		boundaryPoints.push([range.startContainer, range.startOffset]);
  1.1211 +		boundaryPoints.push([range.endContainer, range.endOffset]);
  1.1212 +	});
  1.1213 +
  1.1214 +	boundaryPoints.forEach(function(boundaryPoint) {
  1.1215 +		// "If a boundary point's node is the same as or a descendant of node,
  1.1216 +		// leave it unchanged, so it moves to the new location."
  1.1217 +		//
  1.1218 +		// No modifications necessary.
  1.1219 +
  1.1220 +		// "If a boundary point's node is new parent and its offset is greater
  1.1221 +		// than new index, add one to its offset."
  1.1222 +		if (boundaryPoint[0] == newParent
  1.1223 +		&& boundaryPoint[1] > newIndex) {
  1.1224 +			boundaryPoint[1]++;
  1.1225 +		}
  1.1226 +
  1.1227 +		// "If a boundary point's node is old parent and its offset is old index or
  1.1228 +		// old index + 1, set its node to new parent and add new index − old index
  1.1229 +		// to its offset."
  1.1230 +		if (boundaryPoint[0] == oldParent
  1.1231 +		&& (boundaryPoint[1] == oldIndex
  1.1232 +		|| boundaryPoint[1] == oldIndex + 1)) {
  1.1233 +			boundaryPoint[0] = newParent;
  1.1234 +			boundaryPoint[1] += newIndex - oldIndex;
  1.1235 +		}
  1.1236 +
  1.1237 +		// "If a boundary point's node is old parent and its offset is greater than
  1.1238 +		// old index + 1, subtract one from its offset."
  1.1239 +		if (boundaryPoint[0] == oldParent
  1.1240 +		&& boundaryPoint[1] > oldIndex + 1) {
  1.1241 +			boundaryPoint[1]--;
  1.1242 +		}
  1.1243 +	});
  1.1244 +
  1.1245 +	// Now actually move it and preserve the ranges.
  1.1246 +	if (newParent.childNodes.length == newIndex) {
  1.1247 +		newParent.appendChild(node);
  1.1248 +	} else {
  1.1249 +		newParent.insertBefore(node, newParent.childNodes[newIndex]);
  1.1250 +	}
  1.1251 +
  1.1252 +	globalRange.setStart(boundaryPoints[0][0], boundaryPoints[0][1]);
  1.1253 +	globalRange.setEnd(boundaryPoints[1][0], boundaryPoints[1][1]);
  1.1254 +
  1.1255 +	for (var i = 0; i < extraRanges.length; i++) {
  1.1256 +		extraRanges[i].setStart(boundaryPoints[2*i + 2][0], boundaryPoints[2*i + 2][1]);
  1.1257 +		extraRanges[i].setEnd(boundaryPoints[2*i + 3][0], boundaryPoints[2*i + 3][1]);
  1.1258 +	}
  1.1259 +
  1.1260 +	getSelection().removeAllRanges();
  1.1261 +	for (var i = 1 + extraRanges.length; i < ranges.length; i++) {
  1.1262 +		var newRange = document.createRange();
  1.1263 +		newRange.setStart(boundaryPoints[2*i][0], boundaryPoints[2*i][1]);
  1.1264 +		newRange.setEnd(boundaryPoints[2*i + 1][0], boundaryPoints[2*i + 1][1]);
  1.1265 +		getSelection().addRange(newRange);
  1.1266 +	}
  1.1267 +}
  1.1268 +
  1.1269 +function setTagName(element, newName) {
  1.1270 +	// "If element is an HTML element with local name equal to new name, return
  1.1271 +	// element."
  1.1272 +	if (isHtmlElement(element, newName.toUpperCase())) {
  1.1273 +		return element;
  1.1274 +	}
  1.1275 +
  1.1276 +	// "If element's parent is null, return element."
  1.1277 +	if (!element.parentNode) {
  1.1278 +		return element;
  1.1279 +	}
  1.1280 +
  1.1281 +	// "Let replacement element be the result of calling createElement(new
  1.1282 +	// name) on the ownerDocument of element."
  1.1283 +	var replacementElement = element.ownerDocument.createElement(newName);
  1.1284 +
  1.1285 +	// "Insert replacement element into element's parent immediately before
  1.1286 +	// element."
  1.1287 +	element.parentNode.insertBefore(replacementElement, element);
  1.1288 +
  1.1289 +	// "Copy all attributes of element to replacement element, in order."
  1.1290 +	for (var i = 0; i < element.attributes.length; i++) {
  1.1291 +		replacementElement.setAttributeNS(element.attributes[i].namespaceURI, element.attributes[i].name, element.attributes[i].value);
  1.1292 +	}
  1.1293 +
  1.1294 +	// "While element has children, append the first child of element as the
  1.1295 +	// last child of replacement element, preserving ranges."
  1.1296 +	while (element.childNodes.length) {
  1.1297 +		movePreservingRanges(element.firstChild, replacementElement, replacementElement.childNodes.length);
  1.1298 +	}
  1.1299 +
  1.1300 +	// "Remove element from its parent."
  1.1301 +	element.parentNode.removeChild(element);
  1.1302 +
  1.1303 +	// "Return replacement element."
  1.1304 +	return replacementElement;
  1.1305 +}
  1.1306 +
  1.1307 +function removeExtraneousLineBreaksBefore(node) {
  1.1308 +	// "Let ref be the previousSibling of node."
  1.1309 +	var ref = node.previousSibling;
  1.1310 +
  1.1311 +	// "If ref is null, abort these steps."
  1.1312 +	if (!ref) {
  1.1313 +		return;
  1.1314 +	}
  1.1315 +
  1.1316 +	// "While ref has children, set ref to its lastChild."
  1.1317 +	while (ref.hasChildNodes()) {
  1.1318 +		ref = ref.lastChild;
  1.1319 +	}
  1.1320 +
  1.1321 +	// "While ref is invisible but not an extraneous line break, and ref does
  1.1322 +	// not equal node's parent, set ref to the node before it in tree order."
  1.1323 +	while (isInvisible(ref)
  1.1324 +	&& !isExtraneousLineBreak(ref)
  1.1325 +	&& ref != node.parentNode) {
  1.1326 +		ref = previousNode(ref);
  1.1327 +	}
  1.1328 +
  1.1329 +	// "If ref is an editable extraneous line break, remove it from its
  1.1330 +	// parent."
  1.1331 +	if (isEditable(ref)
  1.1332 +	&& isExtraneousLineBreak(ref)) {
  1.1333 +		ref.parentNode.removeChild(ref);
  1.1334 +	}
  1.1335 +}
  1.1336 +
  1.1337 +function removeExtraneousLineBreaksAtTheEndOf(node) {
  1.1338 +	// "Let ref be node."
  1.1339 +	var ref = node;
  1.1340 +
  1.1341 +	// "While ref has children, set ref to its lastChild."
  1.1342 +	while (ref.hasChildNodes()) {
  1.1343 +		ref = ref.lastChild;
  1.1344 +	}
  1.1345 +
  1.1346 +	// "While ref is invisible but not an extraneous line break, and ref does
  1.1347 +	// not equal node, set ref to the node before it in tree order."
  1.1348 +	while (isInvisible(ref)
  1.1349 +	&& !isExtraneousLineBreak(ref)
  1.1350 +	&& ref != node) {
  1.1351 +		ref = previousNode(ref);
  1.1352 +	}
  1.1353 +
  1.1354 +	// "If ref is an editable extraneous line break:"
  1.1355 +	if (isEditable(ref)
  1.1356 +	&& isExtraneousLineBreak(ref)) {
  1.1357 +		// "While ref's parent is editable and invisible, set ref to its
  1.1358 +		// parent."
  1.1359 +		while (isEditable(ref.parentNode)
  1.1360 +		&& isInvisible(ref.parentNode)) {
  1.1361 +			ref = ref.parentNode;
  1.1362 +		}
  1.1363 +
  1.1364 +		// "Remove ref from its parent."
  1.1365 +		ref.parentNode.removeChild(ref);
  1.1366 +	}
  1.1367 +}
  1.1368 +
  1.1369 +// "To remove extraneous line breaks from a node, first remove extraneous line
  1.1370 +// breaks before it, then remove extraneous line breaks at the end of it."
  1.1371 +function removeExtraneousLineBreaksFrom(node) {
  1.1372 +	removeExtraneousLineBreaksBefore(node);
  1.1373 +	removeExtraneousLineBreaksAtTheEndOf(node);
  1.1374 +}
  1.1375 +
  1.1376 +//@}
  1.1377 +///// Wrapping a list of nodes /////
  1.1378 +//@{
  1.1379 +
  1.1380 +function wrap(nodeList, siblingCriteria, newParentInstructions) {
  1.1381 +	// "If not provided, sibling criteria returns false and new parent
  1.1382 +	// instructions returns null."
  1.1383 +	if (typeof siblingCriteria == "undefined") {
  1.1384 +		siblingCriteria = function() { return false };
  1.1385 +	}
  1.1386 +	if (typeof newParentInstructions == "undefined") {
  1.1387 +		newParentInstructions = function() { return null };
  1.1388 +	}
  1.1389 +
  1.1390 +	// "If every member of node list is invisible, and none is a br, return
  1.1391 +	// null and abort these steps."
  1.1392 +	if (nodeList.every(isInvisible)
  1.1393 +	&& !nodeList.some(function(node) { return isHtmlElement(node, "br") })) {
  1.1394 +		return null;
  1.1395 +	}
  1.1396 +
  1.1397 +	// "If node list's first member's parent is null, return null and abort
  1.1398 +	// these steps."
  1.1399 +	if (!nodeList[0].parentNode) {
  1.1400 +		return null;
  1.1401 +	}
  1.1402 +
  1.1403 +	// "If node list's last member is an inline node that's not a br, and node
  1.1404 +	// list's last member's nextSibling is a br, append that br to node list."
  1.1405 +	if (isInlineNode(nodeList[nodeList.length - 1])
  1.1406 +	&& !isHtmlElement(nodeList[nodeList.length - 1], "br")
  1.1407 +	&& isHtmlElement(nodeList[nodeList.length - 1].nextSibling, "br")) {
  1.1408 +		nodeList.push(nodeList[nodeList.length - 1].nextSibling);
  1.1409 +	}
  1.1410 +
  1.1411 +	// "While node list's first member's previousSibling is invisible, prepend
  1.1412 +	// it to node list."
  1.1413 +	while (isInvisible(nodeList[0].previousSibling)) {
  1.1414 +		nodeList.unshift(nodeList[0].previousSibling);
  1.1415 +	}
  1.1416 +
  1.1417 +	// "While node list's last member's nextSibling is invisible, append it to
  1.1418 +	// node list."
  1.1419 +	while (isInvisible(nodeList[nodeList.length - 1].nextSibling)) {
  1.1420 +		nodeList.push(nodeList[nodeList.length - 1].nextSibling);
  1.1421 +	}
  1.1422 +
  1.1423 +	// "If the previousSibling of the first member of node list is editable and
  1.1424 +	// running sibling criteria on it returns true, let new parent be the
  1.1425 +	// previousSibling of the first member of node list."
  1.1426 +	var newParent;
  1.1427 +	if (isEditable(nodeList[0].previousSibling)
  1.1428 +	&& siblingCriteria(nodeList[0].previousSibling)) {
  1.1429 +		newParent = nodeList[0].previousSibling;
  1.1430 +
  1.1431 +	// "Otherwise, if the nextSibling of the last member of node list is
  1.1432 +	// editable and running sibling criteria on it returns true, let new parent
  1.1433 +	// be the nextSibling of the last member of node list."
  1.1434 +	} else if (isEditable(nodeList[nodeList.length - 1].nextSibling)
  1.1435 +	&& siblingCriteria(nodeList[nodeList.length - 1].nextSibling)) {
  1.1436 +		newParent = nodeList[nodeList.length - 1].nextSibling;
  1.1437 +
  1.1438 +	// "Otherwise, run new parent instructions, and let new parent be the
  1.1439 +	// result."
  1.1440 +	} else {
  1.1441 +		newParent = newParentInstructions();
  1.1442 +	}
  1.1443 +
  1.1444 +	// "If new parent is null, abort these steps and return null."
  1.1445 +	if (!newParent) {
  1.1446 +		return null;
  1.1447 +	}
  1.1448 +
  1.1449 +	// "If new parent's parent is null:"
  1.1450 +	if (!newParent.parentNode) {
  1.1451 +		// "Insert new parent into the parent of the first member of node list
  1.1452 +		// immediately before the first member of node list."
  1.1453 +		nodeList[0].parentNode.insertBefore(newParent, nodeList[0]);
  1.1454 +
  1.1455 +		// "If any range has a boundary point with node equal to the parent of
  1.1456 +		// new parent and offset equal to the index of new parent, add one to
  1.1457 +		// that boundary point's offset."
  1.1458 +		//
  1.1459 +		// Only try to fix the global range.
  1.1460 +		if (globalRange.startContainer == newParent.parentNode
  1.1461 +		&& globalRange.startOffset == getNodeIndex(newParent)) {
  1.1462 +			globalRange.setStart(globalRange.startContainer, globalRange.startOffset + 1);
  1.1463 +		}
  1.1464 +		if (globalRange.endContainer == newParent.parentNode
  1.1465 +		&& globalRange.endOffset == getNodeIndex(newParent)) {
  1.1466 +			globalRange.setEnd(globalRange.endContainer, globalRange.endOffset + 1);
  1.1467 +		}
  1.1468 +	}
  1.1469 +
  1.1470 +	// "Let original parent be the parent of the first member of node list."
  1.1471 +	var originalParent = nodeList[0].parentNode;
  1.1472 +
  1.1473 +	// "If new parent is before the first member of node list in tree order:"
  1.1474 +	if (isBefore(newParent, nodeList[0])) {
  1.1475 +		// "If new parent is not an inline node, but the last visible child of
  1.1476 +		// new parent and the first visible member of node list are both inline
  1.1477 +		// nodes, and the last child of new parent is not a br, call
  1.1478 +		// createElement("br") on the ownerDocument of new parent and append
  1.1479 +		// the result as the last child of new parent."
  1.1480 +		if (!isInlineNode(newParent)
  1.1481 +		&& isInlineNode([].filter.call(newParent.childNodes, isVisible).slice(-1)[0])
  1.1482 +		&& isInlineNode(nodeList.filter(isVisible)[0])
  1.1483 +		&& !isHtmlElement(newParent.lastChild, "BR")) {
  1.1484 +			newParent.appendChild(newParent.ownerDocument.createElement("br"));
  1.1485 +		}
  1.1486 +
  1.1487 +		// "For each node in node list, append node as the last child of new
  1.1488 +		// parent, preserving ranges."
  1.1489 +		for (var i = 0; i < nodeList.length; i++) {
  1.1490 +			movePreservingRanges(nodeList[i], newParent, -1);
  1.1491 +		}
  1.1492 +
  1.1493 +	// "Otherwise:"
  1.1494 +	} else {
  1.1495 +		// "If new parent is not an inline node, but the first visible child of
  1.1496 +		// new parent and the last visible member of node list are both inline
  1.1497 +		// nodes, and the last member of node list is not a br, call
  1.1498 +		// createElement("br") on the ownerDocument of new parent and insert
  1.1499 +		// the result as the first child of new parent."
  1.1500 +		if (!isInlineNode(newParent)
  1.1501 +		&& isInlineNode([].filter.call(newParent.childNodes, isVisible)[0])
  1.1502 +		&& isInlineNode(nodeList.filter(isVisible).slice(-1)[0])
  1.1503 +		&& !isHtmlElement(nodeList[nodeList.length - 1], "BR")) {
  1.1504 +			newParent.insertBefore(newParent.ownerDocument.createElement("br"), newParent.firstChild);
  1.1505 +		}
  1.1506 +
  1.1507 +		// "For each node in node list, in reverse order, insert node as the
  1.1508 +		// first child of new parent, preserving ranges."
  1.1509 +		for (var i = nodeList.length - 1; i >= 0; i--) {
  1.1510 +			movePreservingRanges(nodeList[i], newParent, 0);
  1.1511 +		}
  1.1512 +	}
  1.1513 +
  1.1514 +	// "If original parent is editable and has no children, remove it from its
  1.1515 +	// parent."
  1.1516 +	if (isEditable(originalParent) && !originalParent.hasChildNodes()) {
  1.1517 +		originalParent.parentNode.removeChild(originalParent);
  1.1518 +	}
  1.1519 +
  1.1520 +	// "If new parent's nextSibling is editable and running sibling criteria on
  1.1521 +	// it returns true:"
  1.1522 +	if (isEditable(newParent.nextSibling)
  1.1523 +	&& siblingCriteria(newParent.nextSibling)) {
  1.1524 +		// "If new parent is not an inline node, but new parent's last child
  1.1525 +		// and new parent's nextSibling's first child are both inline nodes,
  1.1526 +		// and new parent's last child is not a br, call createElement("br") on
  1.1527 +		// the ownerDocument of new parent and append the result as the last
  1.1528 +		// child of new parent."
  1.1529 +		if (!isInlineNode(newParent)
  1.1530 +		&& isInlineNode(newParent.lastChild)
  1.1531 +		&& isInlineNode(newParent.nextSibling.firstChild)
  1.1532 +		&& !isHtmlElement(newParent.lastChild, "BR")) {
  1.1533 +			newParent.appendChild(newParent.ownerDocument.createElement("br"));
  1.1534 +		}
  1.1535 +
  1.1536 +		// "While new parent's nextSibling has children, append its first child
  1.1537 +		// as the last child of new parent, preserving ranges."
  1.1538 +		while (newParent.nextSibling.hasChildNodes()) {
  1.1539 +			movePreservingRanges(newParent.nextSibling.firstChild, newParent, -1);
  1.1540 +		}
  1.1541 +
  1.1542 +		// "Remove new parent's nextSibling from its parent."
  1.1543 +		newParent.parentNode.removeChild(newParent.nextSibling);
  1.1544 +	}
  1.1545 +
  1.1546 +	// "Remove extraneous line breaks from new parent."
  1.1547 +	removeExtraneousLineBreaksFrom(newParent);
  1.1548 +
  1.1549 +	// "Return new parent."
  1.1550 +	return newParent;
  1.1551 +}
  1.1552 +
  1.1553 +
  1.1554 +//@}
  1.1555 +///// Allowed children /////
  1.1556 +//@{
  1.1557 +
  1.1558 +// "A name of an element with inline contents is "a", "abbr", "b", "bdi",
  1.1559 +// "bdo", "cite", "code", "dfn", "em", "h1", "h2", "h3", "h4", "h5", "h6", "i",
  1.1560 +// "kbd", "mark", "p", "pre", "q", "rp", "rt", "ruby", "s", "samp", "small",
  1.1561 +// "span", "strong", "sub", "sup", "u", "var", "acronym", "listing", "strike",
  1.1562 +// "xmp", "big", "blink", "font", "marquee", "nobr", or "tt"."
  1.1563 +var namesOfElementsWithInlineContents = ["a", "abbr", "b", "bdi", "bdo",
  1.1564 +	"cite", "code", "dfn", "em", "h1", "h2", "h3", "h4", "h5", "h6", "i",
  1.1565 +	"kbd", "mark", "p", "pre", "q", "rp", "rt", "ruby", "s", "samp", "small",
  1.1566 +	"span", "strong", "sub", "sup", "u", "var", "acronym", "listing", "strike",
  1.1567 +	"xmp", "big", "blink", "font", "marquee", "nobr", "tt"];
  1.1568 +
  1.1569 +// "An element with inline contents is an HTML element whose local name is a
  1.1570 +// name of an element with inline contents."
  1.1571 +function isElementWithInlineContents(node) {
  1.1572 +	return isHtmlElement(node, namesOfElementsWithInlineContents);
  1.1573 +}
  1.1574 +
  1.1575 +function isAllowedChild(child, parent_) {
  1.1576 +	// "If parent is "colgroup", "table", "tbody", "tfoot", "thead", "tr", or
  1.1577 +	// an HTML element with local name equal to one of those, and child is a
  1.1578 +	// Text node whose data does not consist solely of space characters, return
  1.1579 +	// false."
  1.1580 +	if ((["colgroup", "table", "tbody", "tfoot", "thead", "tr"].indexOf(parent_) != -1
  1.1581 +	|| isHtmlElement(parent_, ["colgroup", "table", "tbody", "tfoot", "thead", "tr"]))
  1.1582 +	&& typeof child == "object"
  1.1583 +	&& child.nodeType == Node.TEXT_NODE
  1.1584 +	&& !/^[ \t\n\f\r]*$/.test(child.data)) {
  1.1585 +		return false;
  1.1586 +	}
  1.1587 +
  1.1588 +	// "If parent is "script", "style", "plaintext", or "xmp", or an HTML
  1.1589 +	// element with local name equal to one of those, and child is not a Text
  1.1590 +	// node, return false."
  1.1591 +	if ((["script", "style", "plaintext", "xmp"].indexOf(parent_) != -1
  1.1592 +	|| isHtmlElement(parent_, ["script", "style", "plaintext", "xmp"]))
  1.1593 +	&& (typeof child != "object" || child.nodeType != Node.TEXT_NODE)) {
  1.1594 +		return false;
  1.1595 +	}
  1.1596 +
  1.1597 +	// "If child is a Document, DocumentFragment, or DocumentType, return
  1.1598 +	// false."
  1.1599 +	if (typeof child == "object"
  1.1600 +	&& (child.nodeType == Node.DOCUMENT_NODE
  1.1601 +	|| child.nodeType == Node.DOCUMENT_FRAGMENT_NODE
  1.1602 +	|| child.nodeType == Node.DOCUMENT_TYPE_NODE)) {
  1.1603 +		return false;
  1.1604 +	}
  1.1605 +
  1.1606 +	// "If child is an HTML element, set child to the local name of child."
  1.1607 +	if (isHtmlElement(child)) {
  1.1608 +		child = child.tagName.toLowerCase();
  1.1609 +	}
  1.1610 +
  1.1611 +	// "If child is not a string, return true."
  1.1612 +	if (typeof child != "string") {
  1.1613 +		return true;
  1.1614 +	}
  1.1615 +
  1.1616 +	// "If parent is an HTML element:"
  1.1617 +	if (isHtmlElement(parent_)) {
  1.1618 +		// "If child is "a", and parent or some ancestor of parent is an a,
  1.1619 +		// return false."
  1.1620 +		//
  1.1621 +		// "If child is a prohibited paragraph child name and parent or some
  1.1622 +		// ancestor of parent is an element with inline contents, return
  1.1623 +		// false."
  1.1624 +		//
  1.1625 +		// "If child is "h1", "h2", "h3", "h4", "h5", or "h6", and parent or
  1.1626 +		// some ancestor of parent is an HTML element with local name "h1",
  1.1627 +		// "h2", "h3", "h4", "h5", or "h6", return false."
  1.1628 +		var ancestor = parent_;
  1.1629 +		while (ancestor) {
  1.1630 +			if (child == "a" && isHtmlElement(ancestor, "a")) {
  1.1631 +				return false;
  1.1632 +			}
  1.1633 +			if (prohibitedParagraphChildNames.indexOf(child) != -1
  1.1634 +			&& isElementWithInlineContents(ancestor)) {
  1.1635 +				return false;
  1.1636 +			}
  1.1637 +			if (/^h[1-6]$/.test(child)
  1.1638 +			&& isHtmlElement(ancestor)
  1.1639 +			&& /^H[1-6]$/.test(ancestor.tagName)) {
  1.1640 +				return false;
  1.1641 +			}
  1.1642 +			ancestor = ancestor.parentNode;
  1.1643 +		}
  1.1644 +
  1.1645 +		// "Let parent be the local name of parent."
  1.1646 +		parent_ = parent_.tagName.toLowerCase();
  1.1647 +	}
  1.1648 +
  1.1649 +	// "If parent is an Element or DocumentFragment, return true."
  1.1650 +	if (typeof parent_ == "object"
  1.1651 +	&& (parent_.nodeType == Node.ELEMENT_NODE
  1.1652 +	|| parent_.nodeType == Node.DOCUMENT_FRAGMENT_NODE)) {
  1.1653 +		return true;
  1.1654 +	}
  1.1655 +
  1.1656 +	// "If parent is not a string, return false."
  1.1657 +	if (typeof parent_ != "string") {
  1.1658 +		return false;
  1.1659 +	}
  1.1660 +
  1.1661 +	// "If parent is on the left-hand side of an entry on the following list,
  1.1662 +	// then return true if child is listed on the right-hand side of that
  1.1663 +	// entry, and false otherwise."
  1.1664 +	switch (parent_) {
  1.1665 +		case "colgroup":
  1.1666 +			return child == "col";
  1.1667 +		case "table":
  1.1668 +			return ["caption", "col", "colgroup", "tbody", "td", "tfoot", "th", "thead", "tr"].indexOf(child) != -1;
  1.1669 +		case "tbody":
  1.1670 +		case "thead":
  1.1671 +		case "tfoot":
  1.1672 +			return ["td", "th", "tr"].indexOf(child) != -1;
  1.1673 +		case "tr":
  1.1674 +			return ["td", "th"].indexOf(child) != -1;
  1.1675 +		case "dl":
  1.1676 +			return ["dt", "dd"].indexOf(child) != -1;
  1.1677 +		case "dir":
  1.1678 +		case "ol":
  1.1679 +		case "ul":
  1.1680 +			return ["dir", "li", "ol", "ul"].indexOf(child) != -1;
  1.1681 +		case "hgroup":
  1.1682 +			return /^h[1-6]$/.test(child);
  1.1683 +	}
  1.1684 +
  1.1685 +	// "If child is "body", "caption", "col", "colgroup", "frame", "frameset",
  1.1686 +	// "head", "html", "tbody", "td", "tfoot", "th", "thead", or "tr", return
  1.1687 +	// false."
  1.1688 +	if (["body", "caption", "col", "colgroup", "frame", "frameset", "head",
  1.1689 +	"html", "tbody", "td", "tfoot", "th", "thead", "tr"].indexOf(child) != -1) {
  1.1690 +		return false;
  1.1691 +	}
  1.1692 +
  1.1693 +	// "If child is "dd" or "dt" and parent is not "dl", return false."
  1.1694 +	if (["dd", "dt"].indexOf(child) != -1
  1.1695 +	&& parent_ != "dl") {
  1.1696 +		return false;
  1.1697 +	}
  1.1698 +
  1.1699 +	// "If child is "li" and parent is not "ol" or "ul", return false."
  1.1700 +	if (child == "li"
  1.1701 +	&& parent_ != "ol"
  1.1702 +	&& parent_ != "ul") {
  1.1703 +		return false;
  1.1704 +	}
  1.1705 +
  1.1706 +	// "If parent is on the left-hand side of an entry on the following list
  1.1707 +	// and child is listed on the right-hand side of that entry, return false."
  1.1708 +	var table = [
  1.1709 +		[["a"], ["a"]],
  1.1710 +		[["dd", "dt"], ["dd", "dt"]],
  1.1711 +		[["h1", "h2", "h3", "h4", "h5", "h6"], ["h1", "h2", "h3", "h4", "h5", "h6"]],
  1.1712 +		[["li"], ["li"]],
  1.1713 +		[["nobr"], ["nobr"]],
  1.1714 +		[namesOfElementsWithInlineContents, prohibitedParagraphChildNames],
  1.1715 +		[["td", "th"], ["caption", "col", "colgroup", "tbody", "td", "tfoot", "th", "thead", "tr"]],
  1.1716 +	];
  1.1717 +	for (var i = 0; i < table.length; i++) {
  1.1718 +		if (table[i][0].indexOf(parent_) != -1
  1.1719 +		&& table[i][1].indexOf(child) != -1) {
  1.1720 +			return false;
  1.1721 +		}
  1.1722 +	}
  1.1723 +
  1.1724 +	// "Return true."
  1.1725 +	return true;
  1.1726 +}
  1.1727 +
  1.1728 +
  1.1729 +//@}
  1.1730 +
  1.1731 +//////////////////////////////////////
  1.1732 +///// Inline formatting commands /////
  1.1733 +//////////////////////////////////////
  1.1734 +
  1.1735 +///// Inline formatting command definitions /////
  1.1736 +//@{
  1.1737 +
  1.1738 +// "A node node is effectively contained in a range range if range is not
  1.1739 +// collapsed, and at least one of the following holds:"
  1.1740 +function isEffectivelyContained(node, range) {
  1.1741 +	if (range.collapsed) {
  1.1742 +		return false;
  1.1743 +	}
  1.1744 +
  1.1745 +	// "node is contained in range."
  1.1746 +	if (isContained(node, range)) {
  1.1747 +		return true;
  1.1748 +	}
  1.1749 +
  1.1750 +	// "node is range's start node, it is a Text node, and its length is
  1.1751 +	// different from range's start offset."
  1.1752 +	if (node == range.startContainer
  1.1753 +	&& node.nodeType == Node.TEXT_NODE
  1.1754 +	&& getNodeLength(node) != range.startOffset) {
  1.1755 +		return true;
  1.1756 +	}
  1.1757 +
  1.1758 +	// "node is range's end node, it is a Text node, and range's end offset is
  1.1759 +	// not 0."
  1.1760 +	if (node == range.endContainer
  1.1761 +	&& node.nodeType == Node.TEXT_NODE
  1.1762 +	&& range.endOffset != 0) {
  1.1763 +		return true;
  1.1764 +	}
  1.1765 +
  1.1766 +	// "node has at least one child; and all its children are effectively
  1.1767 +	// contained in range; and either range's start node is not a descendant of
  1.1768 +	// node or is not a Text node or range's start offset is zero; and either
  1.1769 +	// range's end node is not a descendant of node or is not a Text node or
  1.1770 +	// range's end offset is its end node's length."
  1.1771 +	if (node.hasChildNodes()
  1.1772 +	&& [].every.call(node.childNodes, function(child) { return isEffectivelyContained(child, range) })
  1.1773 +	&& (!isDescendant(range.startContainer, node)
  1.1774 +	|| range.startContainer.nodeType != Node.TEXT_NODE
  1.1775 +	|| range.startOffset == 0)
  1.1776 +	&& (!isDescendant(range.endContainer, node)
  1.1777 +	|| range.endContainer.nodeType != Node.TEXT_NODE
  1.1778 +	|| range.endOffset == getNodeLength(range.endContainer))) {
  1.1779 +		return true;
  1.1780 +	}
  1.1781 +
  1.1782 +	return false;
  1.1783 +}
  1.1784 +
  1.1785 +// Like get(All)ContainedNodes(), but for effectively contained nodes.
  1.1786 +function getEffectivelyContainedNodes(range, condition) {
  1.1787 +	if (typeof condition == "undefined") {
  1.1788 +		condition = function() { return true };
  1.1789 +	}
  1.1790 +	var node = range.startContainer;
  1.1791 +	while (isEffectivelyContained(node.parentNode, range)) {
  1.1792 +		node = node.parentNode;
  1.1793 +	}
  1.1794 +
  1.1795 +	var stop = nextNodeDescendants(range.endContainer);
  1.1796 +
  1.1797 +	var nodeList = [];
  1.1798 +	while (isBefore(node, stop)) {
  1.1799 +		if (isEffectivelyContained(node, range)
  1.1800 +		&& condition(node)) {
  1.1801 +			nodeList.push(node);
  1.1802 +			node = nextNodeDescendants(node);
  1.1803 +			continue;
  1.1804 +		}
  1.1805 +		node = nextNode(node);
  1.1806 +	}
  1.1807 +	return nodeList;
  1.1808 +}
  1.1809 +
  1.1810 +function getAllEffectivelyContainedNodes(range, condition) {
  1.1811 +	if (typeof condition == "undefined") {
  1.1812 +		condition = function() { return true };
  1.1813 +	}
  1.1814 +	var node = range.startContainer;
  1.1815 +	while (isEffectivelyContained(node.parentNode, range)) {
  1.1816 +		node = node.parentNode;
  1.1817 +	}
  1.1818 +
  1.1819 +	var stop = nextNodeDescendants(range.endContainer);
  1.1820 +
  1.1821 +	var nodeList = [];
  1.1822 +	while (isBefore(node, stop)) {
  1.1823 +		if (isEffectivelyContained(node, range)
  1.1824 +		&& condition(node)) {
  1.1825 +			nodeList.push(node);
  1.1826 +		}
  1.1827 +		node = nextNode(node);
  1.1828 +	}
  1.1829 +	return nodeList;
  1.1830 +}
  1.1831 +
  1.1832 +// "A modifiable element is a b, em, i, s, span, strong, sub, sup, or u element
  1.1833 +// with no attributes except possibly style; or a font element with no
  1.1834 +// attributes except possibly style, color, face, and/or size; or an a element
  1.1835 +// with no attributes except possibly style and/or href."
  1.1836 +function isModifiableElement(node) {
  1.1837 +	if (!isHtmlElement(node)) {
  1.1838 +		return false;
  1.1839 +	}
  1.1840 +
  1.1841 +	if (["B", "EM", "I", "S", "SPAN", "STRIKE", "STRONG", "SUB", "SUP", "U"].indexOf(node.tagName) != -1) {
  1.1842 +		if (node.attributes.length == 0) {
  1.1843 +			return true;
  1.1844 +		}
  1.1845 +
  1.1846 +		if (node.attributes.length == 1
  1.1847 +		&& node.hasAttribute("style")) {
  1.1848 +			return true;
  1.1849 +		}
  1.1850 +	}
  1.1851 +
  1.1852 +	if (node.tagName == "FONT" || node.tagName == "A") {
  1.1853 +		var numAttrs = node.attributes.length;
  1.1854 +
  1.1855 +		if (node.hasAttribute("style")) {
  1.1856 +			numAttrs--;
  1.1857 +		}
  1.1858 +
  1.1859 +		if (node.tagName == "FONT") {
  1.1860 +			if (node.hasAttribute("color")) {
  1.1861 +				numAttrs--;
  1.1862 +			}
  1.1863 +
  1.1864 +			if (node.hasAttribute("face")) {
  1.1865 +				numAttrs--;
  1.1866 +			}
  1.1867 +
  1.1868 +			if (node.hasAttribute("size")) {
  1.1869 +				numAttrs--;
  1.1870 +			}
  1.1871 +		}
  1.1872 +
  1.1873 +		if (node.tagName == "A"
  1.1874 +		&& node.hasAttribute("href")) {
  1.1875 +			numAttrs--;
  1.1876 +		}
  1.1877 +
  1.1878 +		if (numAttrs == 0) {
  1.1879 +			return true;
  1.1880 +		}
  1.1881 +	}
  1.1882 +
  1.1883 +	return false;
  1.1884 +}
  1.1885 +
  1.1886 +function isSimpleModifiableElement(node) {
  1.1887 +	// "A simple modifiable element is an HTML element for which at least one
  1.1888 +	// of the following holds:"
  1.1889 +	if (!isHtmlElement(node)) {
  1.1890 +		return false;
  1.1891 +	}
  1.1892 +
  1.1893 +	// Only these elements can possibly be a simple modifiable element.
  1.1894 +	if (["A", "B", "EM", "FONT", "I", "S", "SPAN", "STRIKE", "STRONG", "SUB", "SUP", "U"].indexOf(node.tagName) == -1) {
  1.1895 +		return false;
  1.1896 +	}
  1.1897 +
  1.1898 +	// "It is an a, b, em, font, i, s, span, strike, strong, sub, sup, or u
  1.1899 +	// element with no attributes."
  1.1900 +	if (node.attributes.length == 0) {
  1.1901 +		return true;
  1.1902 +	}
  1.1903 +
  1.1904 +	// If it's got more than one attribute, everything after this fails.
  1.1905 +	if (node.attributes.length > 1) {
  1.1906 +		return false;
  1.1907 +	}
  1.1908 +
  1.1909 +	// "It is an a, b, em, font, i, s, span, strike, strong, sub, sup, or u
  1.1910 +	// element with exactly one attribute, which is style, which sets no CSS
  1.1911 +	// properties (including invalid or unrecognized properties)."
  1.1912 +	//
  1.1913 +	// Not gonna try for invalid or unrecognized.
  1.1914 +	if (node.hasAttribute("style")
  1.1915 +	&& node.style.length == 0) {
  1.1916 +		return true;
  1.1917 +	}
  1.1918 +
  1.1919 +	// "It is an a element with exactly one attribute, which is href."
  1.1920 +	if (node.tagName == "A"
  1.1921 +	&& node.hasAttribute("href")) {
  1.1922 +		return true;
  1.1923 +	}
  1.1924 +
  1.1925 +	// "It is a font element with exactly one attribute, which is either color,
  1.1926 +	// face, or size."
  1.1927 +	if (node.tagName == "FONT"
  1.1928 +	&& (node.hasAttribute("color")
  1.1929 +	|| node.hasAttribute("face")
  1.1930 +	|| node.hasAttribute("size")
  1.1931 +	)) {
  1.1932 +		return true;
  1.1933 +	}
  1.1934 +
  1.1935 +	// "It is a b or strong element with exactly one attribute, which is style,
  1.1936 +	// and the style attribute sets exactly one CSS property (including invalid
  1.1937 +	// or unrecognized properties), which is "font-weight"."
  1.1938 +	if ((node.tagName == "B" || node.tagName == "STRONG")
  1.1939 +	&& node.hasAttribute("style")
  1.1940 +	&& node.style.length == 1
  1.1941 +	&& node.style.fontWeight != "") {
  1.1942 +		return true;
  1.1943 +	}
  1.1944 +
  1.1945 +	// "It is an i or em element with exactly one attribute, which is style,
  1.1946 +	// and the style attribute sets exactly one CSS property (including invalid
  1.1947 +	// or unrecognized properties), which is "font-style"."
  1.1948 +	if ((node.tagName == "I" || node.tagName == "EM")
  1.1949 +	&& node.hasAttribute("style")
  1.1950 +	&& node.style.length == 1
  1.1951 +	&& node.style.fontStyle != "") {
  1.1952 +		return true;
  1.1953 +	}
  1.1954 +
  1.1955 +	// "It is an a, font, or span element with exactly one attribute, which is
  1.1956 +	// style, and the style attribute sets exactly one CSS property (including
  1.1957 +	// invalid or unrecognized properties), and that property is not
  1.1958 +	// "text-decoration"."
  1.1959 +	if ((node.tagName == "A" || node.tagName == "FONT" || node.tagName == "SPAN")
  1.1960 +	&& node.hasAttribute("style")
  1.1961 +	&& node.style.length == 1
  1.1962 +	&& node.style.textDecoration == "") {
  1.1963 +		return true;
  1.1964 +	}
  1.1965 +
  1.1966 +	// "It is an a, font, s, span, strike, or u element with exactly one
  1.1967 +	// attribute, which is style, and the style attribute sets exactly one CSS
  1.1968 +	// property (including invalid or unrecognized properties), which is
  1.1969 +	// "text-decoration", which is set to "line-through" or "underline" or
  1.1970 +	// "overline" or "none"."
  1.1971 +	//
  1.1972 +	// The weird extra node.style.length check is for Firefox, which as of
  1.1973 +	// 8.0a2 has annoying and weird behavior here.
  1.1974 +	if (["A", "FONT", "S", "SPAN", "STRIKE", "U"].indexOf(node.tagName) != -1
  1.1975 +	&& node.hasAttribute("style")
  1.1976 +	&& (node.style.length == 1
  1.1977 +	|| (node.style.length == 4
  1.1978 +		&& "MozTextBlink" in node.style
  1.1979 +		&& "MozTextDecorationColor" in node.style
  1.1980 +		&& "MozTextDecorationLine" in node.style
  1.1981 +		&& "MozTextDecorationStyle" in node.style)
  1.1982 +	)
  1.1983 +	&& (node.style.textDecoration == "line-through"
  1.1984 +	|| node.style.textDecoration == "underline"
  1.1985 +	|| node.style.textDecoration == "overline"
  1.1986 +	|| node.style.textDecoration == "none")) {
  1.1987 +		return true;
  1.1988 +	}
  1.1989 +
  1.1990 +	return false;
  1.1991 +}
  1.1992 +
  1.1993 +// "A formattable node is an editable visible node that is either a Text node,
  1.1994 +// an img, or a br."
  1.1995 +function isFormattableNode(node) {
  1.1996 +	return isEditable(node)
  1.1997 +		&& isVisible(node)
  1.1998 +		&& (node.nodeType == Node.TEXT_NODE
  1.1999 +		|| isHtmlElement(node, ["img", "br"]));
  1.2000 +}
  1.2001 +
  1.2002 +// "Two quantities are equivalent values for a command if either both are null,
  1.2003 +// or both are strings and they're equal and the command does not define any
  1.2004 +// equivalent values, or both are strings and the command defines equivalent
  1.2005 +// values and they match the definition."
  1.2006 +function areEquivalentValues(command, val1, val2) {
  1.2007 +	if (val1 === null && val2 === null) {
  1.2008 +		return true;
  1.2009 +	}
  1.2010 +
  1.2011 +	if (typeof val1 == "string"
  1.2012 +	&& typeof val2 == "string"
  1.2013 +	&& val1 == val2
  1.2014 +	&& !("equivalentValues" in commands[command])) {
  1.2015 +		return true;
  1.2016 +	}
  1.2017 +
  1.2018 +	if (typeof val1 == "string"
  1.2019 +	&& typeof val2 == "string"
  1.2020 +	&& "equivalentValues" in commands[command]
  1.2021 +	&& commands[command].equivalentValues(val1, val2)) {
  1.2022 +		return true;
  1.2023 +	}
  1.2024 +
  1.2025 +	return false;
  1.2026 +}
  1.2027 +
  1.2028 +// "Two quantities are loosely equivalent values for a command if either they
  1.2029 +// are equivalent values for the command, or if the command is the fontSize
  1.2030 +// command; one of the quantities is one of "x-small", "small", "medium",
  1.2031 +// "large", "x-large", "xx-large", or "xxx-large"; and the other quantity is
  1.2032 +// the resolved value of "font-size" on a font element whose size attribute has
  1.2033 +// the corresponding value set ("1" through "7" respectively)."
  1.2034 +function areLooselyEquivalentValues(command, val1, val2) {
  1.2035 +	if (areEquivalentValues(command, val1, val2)) {
  1.2036 +		return true;
  1.2037 +	}
  1.2038 +
  1.2039 +	if (command != "fontsize"
  1.2040 +	|| typeof val1 != "string"
  1.2041 +	|| typeof val2 != "string") {
  1.2042 +		return false;
  1.2043 +	}
  1.2044 +
  1.2045 +	// Static variables in JavaScript?
  1.2046 +	var callee = areLooselyEquivalentValues;
  1.2047 +	if (callee.sizeMap === undefined) {
  1.2048 +		callee.sizeMap = {};
  1.2049 +		var font = document.createElement("font");
  1.2050 +		document.body.appendChild(font);
  1.2051 +		["x-small", "small", "medium", "large", "x-large", "xx-large",
  1.2052 +		"xxx-large"].forEach(function(keyword) {
  1.2053 +			font.size = cssSizeToLegacy(keyword);
  1.2054 +			callee.sizeMap[keyword] = getComputedStyle(font).fontSize;
  1.2055 +		});
  1.2056 +		document.body.removeChild(font);
  1.2057 +	}
  1.2058 +
  1.2059 +	return val1 === callee.sizeMap[val2]
  1.2060 +		|| val2 === callee.sizeMap[val1];
  1.2061 +}
  1.2062 +
  1.2063 +//@}
  1.2064 +///// Assorted inline formatting command algorithms /////
  1.2065 +//@{
  1.2066 +
  1.2067 +function getEffectiveCommandValue(node, command) {
  1.2068 +	// "If neither node nor its parent is an Element, return null."
  1.2069 +	if (node.nodeType != Node.ELEMENT_NODE
  1.2070 +	&& (!node.parentNode || node.parentNode.nodeType != Node.ELEMENT_NODE)) {
  1.2071 +		return null;
  1.2072 +	}
  1.2073 +
  1.2074 +	// "If node is not an Element, return the effective command value of its
  1.2075 +	// parent for command."
  1.2076 +	if (node.nodeType != Node.ELEMENT_NODE) {
  1.2077 +		return getEffectiveCommandValue(node.parentNode, command);
  1.2078 +	}
  1.2079 +
  1.2080 +	// "If command is "createLink" or "unlink":"
  1.2081 +	if (command == "createlink" || command == "unlink") {
  1.2082 +		// "While node is not null, and is not an a element that has an href
  1.2083 +		// attribute, set node to its parent."
  1.2084 +		while (node
  1.2085 +		&& (!isHtmlElement(node)
  1.2086 +		|| node.tagName != "A"
  1.2087 +		|| !node.hasAttribute("href"))) {
  1.2088 +			node = node.parentNode;
  1.2089 +		}
  1.2090 +
  1.2091 +		// "If node is null, return null."
  1.2092 +		if (!node) {
  1.2093 +			return null;
  1.2094 +		}
  1.2095 +
  1.2096 +		// "Return the value of node's href attribute."
  1.2097 +		return node.getAttribute("href");
  1.2098 +	}
  1.2099 +
  1.2100 +	// "If command is "backColor" or "hiliteColor":"
  1.2101 +	if (command == "backcolor"
  1.2102 +	|| command == "hilitecolor") {
  1.2103 +		// "While the resolved value of "background-color" on node is any
  1.2104 +		// fully transparent value, and node's parent is an Element, set
  1.2105 +		// node to its parent."
  1.2106 +		//
  1.2107 +		// Another lame hack to avoid flawed APIs.
  1.2108 +		while ((getComputedStyle(node).backgroundColor == "rgba(0, 0, 0, 0)"
  1.2109 +		|| getComputedStyle(node).backgroundColor === ""
  1.2110 +		|| getComputedStyle(node).backgroundColor == "transparent")
  1.2111 +		&& node.parentNode
  1.2112 +		&& node.parentNode.nodeType == Node.ELEMENT_NODE) {
  1.2113 +			node = node.parentNode;
  1.2114 +		}
  1.2115 +
  1.2116 +		// "Return the resolved value of "background-color" for node."
  1.2117 +		return getComputedStyle(node).backgroundColor;
  1.2118 +	}
  1.2119 +
  1.2120 +	// "If command is "subscript" or "superscript":"
  1.2121 +	if (command == "subscript" || command == "superscript") {
  1.2122 +		// "Let affected by subscript and affected by superscript be two
  1.2123 +		// boolean variables, both initially false."
  1.2124 +		var affectedBySubscript = false;
  1.2125 +		var affectedBySuperscript = false;
  1.2126 +
  1.2127 +		// "While node is an inline node:"
  1.2128 +		while (isInlineNode(node)) {
  1.2129 +			var verticalAlign = getComputedStyle(node).verticalAlign;
  1.2130 +
  1.2131 +			// "If node is a sub, set affected by subscript to true."
  1.2132 +			if (isHtmlElement(node, "sub")) {
  1.2133 +				affectedBySubscript = true;
  1.2134 +			// "Otherwise, if node is a sup, set affected by superscript to
  1.2135 +			// true."
  1.2136 +			} else if (isHtmlElement(node, "sup")) {
  1.2137 +				affectedBySuperscript = true;
  1.2138 +			}
  1.2139 +
  1.2140 +			// "Set node to its parent."
  1.2141 +			node = node.parentNode;
  1.2142 +		}
  1.2143 +
  1.2144 +		// "If affected by subscript and affected by superscript are both true,
  1.2145 +		// return the string "mixed"."
  1.2146 +		if (affectedBySubscript && affectedBySuperscript) {
  1.2147 +			return "mixed";
  1.2148 +		}
  1.2149 +
  1.2150 +		// "If affected by subscript is true, return "subscript"."
  1.2151 +		if (affectedBySubscript) {
  1.2152 +			return "subscript";
  1.2153 +		}
  1.2154 +
  1.2155 +		// "If affected by superscript is true, return "superscript"."
  1.2156 +		if (affectedBySuperscript) {
  1.2157 +			return "superscript";
  1.2158 +		}
  1.2159 +
  1.2160 +		// "Return null."
  1.2161 +		return null;
  1.2162 +	}
  1.2163 +
  1.2164 +	// "If command is "strikethrough", and the "text-decoration" property of
  1.2165 +	// node or any of its ancestors has resolved value containing
  1.2166 +	// "line-through", return "line-through". Otherwise, return null."
  1.2167 +	if (command == "strikethrough") {
  1.2168 +		do {
  1.2169 +			if (getComputedStyle(node).textDecoration.indexOf("line-through") != -1) {
  1.2170 +				return "line-through";
  1.2171 +			}
  1.2172 +			node = node.parentNode;
  1.2173 +		} while (node && node.nodeType == Node.ELEMENT_NODE);
  1.2174 +		return null;
  1.2175 +	}
  1.2176 +
  1.2177 +	// "If command is "underline", and the "text-decoration" property of node
  1.2178 +	// or any of its ancestors has resolved value containing "underline",
  1.2179 +	// return "underline". Otherwise, return null."
  1.2180 +	if (command == "underline") {
  1.2181 +		do {
  1.2182 +			if (getComputedStyle(node).textDecoration.indexOf("underline") != -1) {
  1.2183 +				return "underline";
  1.2184 +			}
  1.2185 +			node = node.parentNode;
  1.2186 +		} while (node && node.nodeType == Node.ELEMENT_NODE);
  1.2187 +		return null;
  1.2188 +	}
  1.2189 +
  1.2190 +	if (!("relevantCssProperty" in commands[command])) {
  1.2191 +		throw "Bug: no relevantCssProperty for " + command + " in getEffectiveCommandValue";
  1.2192 +	}
  1.2193 +
  1.2194 +	// "Return the resolved value for node of the relevant CSS property for
  1.2195 +	// command."
  1.2196 +	return getComputedStyle(node)[commands[command].relevantCssProperty];
  1.2197 +}
  1.2198 +
  1.2199 +function getSpecifiedCommandValue(element, command) {
  1.2200 +	// "If command is "backColor" or "hiliteColor" and element's display
  1.2201 +	// property does not have resolved value "inline", return null."
  1.2202 +	if ((command == "backcolor" || command == "hilitecolor")
  1.2203 +	&& getComputedStyle(element).display != "inline") {
  1.2204 +		return null;
  1.2205 +	}
  1.2206 +
  1.2207 +	// "If command is "createLink" or "unlink":"
  1.2208 +	if (command == "createlink" || command == "unlink") {
  1.2209 +		// "If element is an a element and has an href attribute, return the
  1.2210 +		// value of that attribute."
  1.2211 +		if (isHtmlElement(element)
  1.2212 +		&& element.tagName == "A"
  1.2213 +		&& element.hasAttribute("href")) {
  1.2214 +			return element.getAttribute("href");
  1.2215 +		}
  1.2216 +
  1.2217 +		// "Return null."
  1.2218 +		return null;
  1.2219 +	}
  1.2220 +
  1.2221 +	// "If command is "subscript" or "superscript":"
  1.2222 +	if (command == "subscript" || command == "superscript") {
  1.2223 +		// "If element is a sup, return "superscript"."
  1.2224 +		if (isHtmlElement(element, "sup")) {
  1.2225 +			return "superscript";
  1.2226 +		}
  1.2227 +
  1.2228 +		// "If element is a sub, return "subscript"."
  1.2229 +		if (isHtmlElement(element, "sub")) {
  1.2230 +			return "subscript";
  1.2231 +		}
  1.2232 +
  1.2233 +		// "Return null."
  1.2234 +		return null;
  1.2235 +	}
  1.2236 +
  1.2237 +	// "If command is "strikethrough", and element has a style attribute set,
  1.2238 +	// and that attribute sets "text-decoration":"
  1.2239 +	if (command == "strikethrough"
  1.2240 +	&& element.style.textDecoration != "") {
  1.2241 +		// "If element's style attribute sets "text-decoration" to a value
  1.2242 +		// containing "line-through", return "line-through"."
  1.2243 +		if (element.style.textDecoration.indexOf("line-through") != -1) {
  1.2244 +			return "line-through";
  1.2245 +		}
  1.2246 +
  1.2247 +		// "Return null."
  1.2248 +		return null;
  1.2249 +	}
  1.2250 +
  1.2251 +	// "If command is "strikethrough" and element is a s or strike element,
  1.2252 +	// return "line-through"."
  1.2253 +	if (command == "strikethrough"
  1.2254 +	&& isHtmlElement(element, ["S", "STRIKE"])) {
  1.2255 +		return "line-through";
  1.2256 +	}
  1.2257 +
  1.2258 +	// "If command is "underline", and element has a style attribute set, and
  1.2259 +	// that attribute sets "text-decoration":"
  1.2260 +	if (command == "underline"
  1.2261 +	&& element.style.textDecoration != "") {
  1.2262 +		// "If element's style attribute sets "text-decoration" to a value
  1.2263 +		// containing "underline", return "underline"."
  1.2264 +		if (element.style.textDecoration.indexOf("underline") != -1) {
  1.2265 +			return "underline";
  1.2266 +		}
  1.2267 +
  1.2268 +		// "Return null."
  1.2269 +		return null;
  1.2270 +	}
  1.2271 +
  1.2272 +	// "If command is "underline" and element is a u element, return
  1.2273 +	// "underline"."
  1.2274 +	if (command == "underline"
  1.2275 +	&& isHtmlElement(element, "U")) {
  1.2276 +		return "underline";
  1.2277 +	}
  1.2278 +
  1.2279 +	// "Let property be the relevant CSS property for command."
  1.2280 +	var property = commands[command].relevantCssProperty;
  1.2281 +
  1.2282 +	// "If property is null, return null."
  1.2283 +	if (property === null) {
  1.2284 +		return null;
  1.2285 +	}
  1.2286 +
  1.2287 +	// "If element has a style attribute set, and that attribute has the
  1.2288 +	// effect of setting property, return the value that it sets property to."
  1.2289 +	if (element.style[property] != "") {
  1.2290 +		return element.style[property];
  1.2291 +	}
  1.2292 +
  1.2293 +	// "If element is a font element that has an attribute whose effect is
  1.2294 +	// to create a presentational hint for property, return the value that the
  1.2295 +	// hint sets property to.  (For a size of 7, this will be the non-CSS value
  1.2296 +	// "xxx-large".)"
  1.2297 +	if (isHtmlNamespace(element.namespaceURI)
  1.2298 +	&& element.tagName == "FONT") {
  1.2299 +		if (property == "color" && element.hasAttribute("color")) {
  1.2300 +			return element.color;
  1.2301 +		}
  1.2302 +		if (property == "fontFamily" && element.hasAttribute("face")) {
  1.2303 +			return element.face;
  1.2304 +		}
  1.2305 +		if (property == "fontSize" && element.hasAttribute("size")) {
  1.2306 +			// This is not even close to correct in general.
  1.2307 +			var size = parseInt(element.size);
  1.2308 +			if (size < 1) {
  1.2309 +				size = 1;
  1.2310 +			}
  1.2311 +			if (size > 7) {
  1.2312 +				size = 7;
  1.2313 +			}
  1.2314 +			return {
  1.2315 +				1: "x-small",
  1.2316 +				2: "small",
  1.2317 +				3: "medium",
  1.2318 +				4: "large",
  1.2319 +				5: "x-large",
  1.2320 +				6: "xx-large",
  1.2321 +				7: "xxx-large"
  1.2322 +			}[size];
  1.2323 +		}
  1.2324 +	}
  1.2325 +
  1.2326 +	// "If element is in the following list, and property is equal to the
  1.2327 +	// CSS property name listed for it, return the string listed for it."
  1.2328 +	//
  1.2329 +	// A list follows, whose meaning is copied here.
  1.2330 +	if (property == "fontWeight"
  1.2331 +	&& (element.tagName == "B" || element.tagName == "STRONG")) {
  1.2332 +		return "bold";
  1.2333 +	}
  1.2334 +	if (property == "fontStyle"
  1.2335 +	&& (element.tagName == "I" || element.tagName == "EM")) {
  1.2336 +		return "italic";
  1.2337 +	}
  1.2338 +
  1.2339 +	// "Return null."
  1.2340 +	return null;
  1.2341 +}
  1.2342 +
  1.2343 +function reorderModifiableDescendants(node, command, newValue) {
  1.2344 +	// "Let candidate equal node."
  1.2345 +	var candidate = node;
  1.2346 +
  1.2347 +	// "While candidate is a modifiable element, and candidate has exactly one
  1.2348 +	// child, and that child is also a modifiable element, and candidate is not
  1.2349 +	// a simple modifiable element or candidate's specified command value for
  1.2350 +	// command is not equivalent to new value, set candidate to its child."
  1.2351 +	while (isModifiableElement(candidate)
  1.2352 +	&& candidate.childNodes.length == 1
  1.2353 +	&& isModifiableElement(candidate.firstChild)
  1.2354 +	&& (!isSimpleModifiableElement(candidate)
  1.2355 +	|| !areEquivalentValues(command, getSpecifiedCommandValue(candidate, command), newValue))) {
  1.2356 +		candidate = candidate.firstChild;
  1.2357 +	}
  1.2358 +
  1.2359 +	// "If candidate is node, or is not a simple modifiable element, or its
  1.2360 +	// specified command value is not equivalent to new value, or its effective
  1.2361 +	// command value is not loosely equivalent to new value, abort these
  1.2362 +	// steps."
  1.2363 +	if (candidate == node
  1.2364 +	|| !isSimpleModifiableElement(candidate)
  1.2365 +	|| !areEquivalentValues(command, getSpecifiedCommandValue(candidate, command), newValue)
  1.2366 +	|| !areLooselyEquivalentValues(command, getEffectiveCommandValue(candidate, command), newValue)) {
  1.2367 +		return;
  1.2368 +	}
  1.2369 +
  1.2370 +	// "While candidate has children, insert the first child of candidate into
  1.2371 +	// candidate's parent immediately before candidate, preserving ranges."
  1.2372 +	while (candidate.hasChildNodes()) {
  1.2373 +		movePreservingRanges(candidate.firstChild, candidate.parentNode, getNodeIndex(candidate));
  1.2374 +	}
  1.2375 +
  1.2376 +	// "Insert candidate into node's parent immediately after node."
  1.2377 +	node.parentNode.insertBefore(candidate, node.nextSibling);
  1.2378 +
  1.2379 +	// "Append the node as the last child of candidate, preserving ranges."
  1.2380 +	movePreservingRanges(node, candidate, -1);
  1.2381 +}
  1.2382 +
  1.2383 +function recordValues(nodeList) {
  1.2384 +	// "Let values be a list of (node, command, specified command value)
  1.2385 +	// triples, initially empty."
  1.2386 +	var values = [];
  1.2387 +
  1.2388 +	// "For each node in node list, for each command in the list "subscript",
  1.2389 +	// "bold", "fontName", "fontSize", "foreColor", "hiliteColor", "italic",
  1.2390 +	// "strikethrough", and "underline" in that order:"
  1.2391 +	nodeList.forEach(function(node) {
  1.2392 +		["subscript", "bold", "fontname", "fontsize", "forecolor",
  1.2393 +		"hilitecolor", "italic", "strikethrough", "underline"].forEach(function(command) {
  1.2394 +			// "Let ancestor equal node."
  1.2395 +			var ancestor = node;
  1.2396 +
  1.2397 +			// "If ancestor is not an Element, set it to its parent."
  1.2398 +			if (ancestor.nodeType != Node.ELEMENT_NODE) {
  1.2399 +				ancestor = ancestor.parentNode;
  1.2400 +			}
  1.2401 +
  1.2402 +			// "While ancestor is an Element and its specified command value
  1.2403 +			// for command is null, set it to its parent."
  1.2404 +			while (ancestor
  1.2405 +			&& ancestor.nodeType == Node.ELEMENT_NODE
  1.2406 +			&& getSpecifiedCommandValue(ancestor, command) === null) {
  1.2407 +				ancestor = ancestor.parentNode;
  1.2408 +			}
  1.2409 +
  1.2410 +			// "If ancestor is an Element, add (node, command, ancestor's
  1.2411 +			// specified command value for command) to values. Otherwise add
  1.2412 +			// (node, command, null) to values."
  1.2413 +			if (ancestor && ancestor.nodeType == Node.ELEMENT_NODE) {
  1.2414 +				values.push([node, command, getSpecifiedCommandValue(ancestor, command)]);
  1.2415 +			} else {
  1.2416 +				values.push([node, command, null]);
  1.2417 +			}
  1.2418 +		});
  1.2419 +	});
  1.2420 +
  1.2421 +	// "Return values."
  1.2422 +	return values;
  1.2423 +}
  1.2424 +
  1.2425 +function restoreValues(values) {
  1.2426 +	// "For each (node, command, value) triple in values:"
  1.2427 +	values.forEach(function(triple) {
  1.2428 +		var node = triple[0];
  1.2429 +		var command = triple[1];
  1.2430 +		var value = triple[2];
  1.2431 +
  1.2432 +		// "Let ancestor equal node."
  1.2433 +		var ancestor = node;
  1.2434 +
  1.2435 +		// "If ancestor is not an Element, set it to its parent."
  1.2436 +		if (!ancestor || ancestor.nodeType != Node.ELEMENT_NODE) {
  1.2437 +			ancestor = ancestor.parentNode;
  1.2438 +		}
  1.2439 +
  1.2440 +		// "While ancestor is an Element and its specified command value for
  1.2441 +		// command is null, set it to its parent."
  1.2442 +		while (ancestor
  1.2443 +		&& ancestor.nodeType == Node.ELEMENT_NODE
  1.2444 +		&& getSpecifiedCommandValue(ancestor, command) === null) {
  1.2445 +			ancestor = ancestor.parentNode;
  1.2446 +		}
  1.2447 +
  1.2448 +		// "If value is null and ancestor is an Element, push down values on
  1.2449 +		// node for command, with new value null."
  1.2450 +		if (value === null
  1.2451 +		&& ancestor
  1.2452 +		&& ancestor.nodeType == Node.ELEMENT_NODE) {
  1.2453 +			pushDownValues(node, command, null);
  1.2454 +
  1.2455 +		// "Otherwise, if ancestor is an Element and its specified command
  1.2456 +		// value for command is not equivalent to value, or if ancestor is not
  1.2457 +		// an Element and value is not null, force the value of command to
  1.2458 +		// value on node."
  1.2459 +		} else if ((ancestor
  1.2460 +		&& ancestor.nodeType == Node.ELEMENT_NODE
  1.2461 +		&& !areEquivalentValues(command, getSpecifiedCommandValue(ancestor, command), value))
  1.2462 +		|| ((!ancestor || ancestor.nodeType != Node.ELEMENT_NODE)
  1.2463 +		&& value !== null)) {
  1.2464 +			forceValue(node, command, value);
  1.2465 +		}
  1.2466 +	});
  1.2467 +}
  1.2468 +
  1.2469 +
  1.2470 +//@}
  1.2471 +///// Clearing an element's value /////
  1.2472 +//@{
  1.2473 +
  1.2474 +function clearValue(element, command) {
  1.2475 +	// "If element is not editable, return the empty list."
  1.2476 +	if (!isEditable(element)) {
  1.2477 +		return [];
  1.2478 +	}
  1.2479 +
  1.2480 +	// "If element's specified command value for command is null, return the
  1.2481 +	// empty list."
  1.2482 +	if (getSpecifiedCommandValue(element, command) === null) {
  1.2483 +		return [];
  1.2484 +	}
  1.2485 +
  1.2486 +	// "If element is a simple modifiable element:"
  1.2487 +	if (isSimpleModifiableElement(element)) {
  1.2488 +		// "Let children be the children of element."
  1.2489 +		var children = Array.prototype.slice.call(element.childNodes);
  1.2490 +
  1.2491 +		// "For each child in children, insert child into element's parent
  1.2492 +		// immediately before element, preserving ranges."
  1.2493 +		for (var i = 0; i < children.length; i++) {
  1.2494 +			movePreservingRanges(children[i], element.parentNode, getNodeIndex(element));
  1.2495 +		}
  1.2496 +
  1.2497 +		// "Remove element from its parent."
  1.2498 +		element.parentNode.removeChild(element);
  1.2499 +
  1.2500 +		// "Return children."
  1.2501 +		return children;
  1.2502 +	}
  1.2503 +
  1.2504 +	// "If command is "strikethrough", and element has a style attribute that
  1.2505 +	// sets "text-decoration" to some value containing "line-through", delete
  1.2506 +	// "line-through" from the value."
  1.2507 +	if (command == "strikethrough"
  1.2508 +	&& element.style.textDecoration.indexOf("line-through") != -1) {
  1.2509 +		if (element.style.textDecoration == "line-through") {
  1.2510 +			element.style.textDecoration = "";
  1.2511 +		} else {
  1.2512 +			element.style.textDecoration = element.style.textDecoration.replace("line-through", "");
  1.2513 +		}
  1.2514 +		if (element.getAttribute("style") == "") {
  1.2515 +			element.removeAttribute("style");
  1.2516 +		}
  1.2517 +	}
  1.2518 +
  1.2519 +	// "If command is "underline", and element has a style attribute that sets
  1.2520 +	// "text-decoration" to some value containing "underline", delete
  1.2521 +	// "underline" from the value."
  1.2522 +	if (command == "underline"
  1.2523 +	&& element.style.textDecoration.indexOf("underline") != -1) {
  1.2524 +		if (element.style.textDecoration == "underline") {
  1.2525 +			element.style.textDecoration = "";
  1.2526 +		} else {
  1.2527 +			element.style.textDecoration = element.style.textDecoration.replace("underline", "");
  1.2528 +		}
  1.2529 +		if (element.getAttribute("style") == "") {
  1.2530 +			element.removeAttribute("style");
  1.2531 +		}
  1.2532 +	}
  1.2533 +
  1.2534 +	// "If the relevant CSS property for command is not null, unset the CSS
  1.2535 +	// property property of element."
  1.2536 +	if (commands[command].relevantCssProperty !== null) {
  1.2537 +		element.style[commands[command].relevantCssProperty] = '';
  1.2538 +		if (element.getAttribute("style") == "") {
  1.2539 +			element.removeAttribute("style");
  1.2540 +		}
  1.2541 +	}
  1.2542 +
  1.2543 +	// "If element is a font element:"
  1.2544 +	if (isHtmlNamespace(element.namespaceURI) && element.tagName == "FONT") {
  1.2545 +		// "If command is "foreColor", unset element's color attribute, if set."
  1.2546 +		if (command == "forecolor") {
  1.2547 +			element.removeAttribute("color");
  1.2548 +		}
  1.2549 +
  1.2550 +		// "If command is "fontName", unset element's face attribute, if set."
  1.2551 +		if (command == "fontname") {
  1.2552 +			element.removeAttribute("face");
  1.2553 +		}
  1.2554 +
  1.2555 +		// "If command is "fontSize", unset element's size attribute, if set."
  1.2556 +		if (command == "fontsize") {
  1.2557 +			element.removeAttribute("size");
  1.2558 +		}
  1.2559 +	}
  1.2560 +
  1.2561 +	// "If element is an a element and command is "createLink" or "unlink",
  1.2562 +	// unset the href property of element."
  1.2563 +	if (isHtmlElement(element, "A")
  1.2564 +	&& (command == "createlink" || command == "unlink")) {
  1.2565 +		element.removeAttribute("href");
  1.2566 +	}
  1.2567 +
  1.2568 +	// "If element's specified command value for command is null, return the
  1.2569 +	// empty list."
  1.2570 +	if (getSpecifiedCommandValue(element, command) === null) {
  1.2571 +		return [];
  1.2572 +	}
  1.2573 +
  1.2574 +	// "Set the tag name of element to "span", and return the one-node list
  1.2575 +	// consisting of the result."
  1.2576 +	return [setTagName(element, "span")];
  1.2577 +}
  1.2578 +
  1.2579 +
  1.2580 +//@}
  1.2581 +///// Pushing down values /////
  1.2582 +//@{
  1.2583 +
  1.2584 +function pushDownValues(node, command, newValue) {
  1.2585 +	// "If node's parent is not an Element, abort this algorithm."
  1.2586 +	if (!node.parentNode
  1.2587 +	|| node.parentNode.nodeType != Node.ELEMENT_NODE) {
  1.2588 +		return;
  1.2589 +	}
  1.2590 +
  1.2591 +	// "If the effective command value of command is loosely equivalent to new
  1.2592 +	// value on node, abort this algorithm."
  1.2593 +	if (areLooselyEquivalentValues(command, getEffectiveCommandValue(node, command), newValue)) {
  1.2594 +		return;
  1.2595 +	}
  1.2596 +
  1.2597 +	// "Let current ancestor be node's parent."
  1.2598 +	var currentAncestor = node.parentNode;
  1.2599 +
  1.2600 +	// "Let ancestor list be a list of Nodes, initially empty."
  1.2601 +	var ancestorList = [];
  1.2602 +
  1.2603 +	// "While current ancestor is an editable Element and the effective command
  1.2604 +	// value of command is not loosely equivalent to new value on it, append
  1.2605 +	// current ancestor to ancestor list, then set current ancestor to its
  1.2606 +	// parent."
  1.2607 +	while (isEditable(currentAncestor)
  1.2608 +	&& currentAncestor.nodeType == Node.ELEMENT_NODE
  1.2609 +	&& !areLooselyEquivalentValues(command, getEffectiveCommandValue(currentAncestor, command), newValue)) {
  1.2610 +		ancestorList.push(currentAncestor);
  1.2611 +		currentAncestor = currentAncestor.parentNode;
  1.2612 +	}
  1.2613 +
  1.2614 +	// "If ancestor list is empty, abort this algorithm."
  1.2615 +	if (!ancestorList.length) {
  1.2616 +		return;
  1.2617 +	}
  1.2618 +
  1.2619 +	// "Let propagated value be the specified command value of command on the
  1.2620 +	// last member of ancestor list."
  1.2621 +	var propagatedValue = getSpecifiedCommandValue(ancestorList[ancestorList.length - 1], command);
  1.2622 +
  1.2623 +	// "If propagated value is null and is not equal to new value, abort this
  1.2624 +	// algorithm."
  1.2625 +	if (propagatedValue === null && propagatedValue != newValue) {
  1.2626 +		return;
  1.2627 +	}
  1.2628 +
  1.2629 +	// "If the effective command value for the parent of the last member of
  1.2630 +	// ancestor list is not loosely equivalent to new value, and new value is
  1.2631 +	// not null, abort this algorithm."
  1.2632 +	if (newValue !== null
  1.2633 +	&& !areLooselyEquivalentValues(command, getEffectiveCommandValue(ancestorList[ancestorList.length - 1].parentNode, command), newValue)) {
  1.2634 +		return;
  1.2635 +	}
  1.2636 +
  1.2637 +	// "While ancestor list is not empty:"
  1.2638 +	while (ancestorList.length) {
  1.2639 +		// "Let current ancestor be the last member of ancestor list."
  1.2640 +		// "Remove the last member from ancestor list."
  1.2641 +		var currentAncestor = ancestorList.pop();
  1.2642 +
  1.2643 +		// "If the specified command value of current ancestor for command is
  1.2644 +		// not null, set propagated value to that value."
  1.2645 +		if (getSpecifiedCommandValue(currentAncestor, command) !== null) {
  1.2646 +			propagatedValue = getSpecifiedCommandValue(currentAncestor, command);
  1.2647 +		}
  1.2648 +
  1.2649 +		// "Let children be the children of current ancestor."
  1.2650 +		var children = Array.prototype.slice.call(currentAncestor.childNodes);
  1.2651 +
  1.2652 +		// "If the specified command value of current ancestor for command is
  1.2653 +		// not null, clear the value of current ancestor."
  1.2654 +		if (getSpecifiedCommandValue(currentAncestor, command) !== null) {
  1.2655 +			clearValue(currentAncestor, command);
  1.2656 +		}
  1.2657 +
  1.2658 +		// "For every child in children:"
  1.2659 +		for (var i = 0; i < children.length; i++) {
  1.2660 +			var child = children[i];
  1.2661 +
  1.2662 +			// "If child is node, continue with the next child."
  1.2663 +			if (child == node) {
  1.2664 +				continue;
  1.2665 +			}
  1.2666 +
  1.2667 +			// "If child is an Element whose specified command value for
  1.2668 +			// command is neither null nor equivalent to propagated value,
  1.2669 +			// continue with the next child."
  1.2670 +			if (child.nodeType == Node.ELEMENT_NODE
  1.2671 +			&& getSpecifiedCommandValue(child, command) !== null
  1.2672 +			&& !areEquivalentValues(command, propagatedValue, getSpecifiedCommandValue(child, command))) {
  1.2673 +				continue;
  1.2674 +			}
  1.2675 +
  1.2676 +			// "If child is the last member of ancestor list, continue with the
  1.2677 +			// next child."
  1.2678 +			if (child == ancestorList[ancestorList.length - 1]) {
  1.2679 +				continue;
  1.2680 +			}
  1.2681 +
  1.2682 +			// "Force the value of child, with command as in this algorithm
  1.2683 +			// and new value equal to propagated value."
  1.2684 +			forceValue(child, command, propagatedValue);
  1.2685 +		}
  1.2686 +	}
  1.2687 +}
  1.2688 +
  1.2689 +
  1.2690 +//@}
  1.2691 +///// Forcing the value of a node /////
  1.2692 +//@{
  1.2693 +
  1.2694 +function forceValue(node, command, newValue) {
  1.2695 +	// "If node's parent is null, abort this algorithm."
  1.2696 +	if (!node.parentNode) {
  1.2697 +		return;
  1.2698 +	}
  1.2699 +
  1.2700 +	// "If new value is null, abort this algorithm."
  1.2701 +	if (newValue === null) {
  1.2702 +		return;
  1.2703 +	}
  1.2704 +
  1.2705 +	// "If node is an allowed child of "span":"
  1.2706 +	if (isAllowedChild(node, "span")) {
  1.2707 +		// "Reorder modifiable descendants of node's previousSibling."
  1.2708 +		reorderModifiableDescendants(node.previousSibling, command, newValue);
  1.2709 +
  1.2710 +		// "Reorder modifiable descendants of node's nextSibling."
  1.2711 +		reorderModifiableDescendants(node.nextSibling, command, newValue);
  1.2712 +
  1.2713 +		// "Wrap the one-node list consisting of node, with sibling criteria
  1.2714 +		// returning true for a simple modifiable element whose specified
  1.2715 +		// command value is equivalent to new value and whose effective command
  1.2716 +		// value is loosely equivalent to new value and false otherwise, and
  1.2717 +		// with new parent instructions returning null."
  1.2718 +		wrap([node],
  1.2719 +			function(node) {
  1.2720 +				return isSimpleModifiableElement(node)
  1.2721 +					&& areEquivalentValues(command, getSpecifiedCommandValue(node, command), newValue)
  1.2722 +					&& areLooselyEquivalentValues(command, getEffectiveCommandValue(node, command), newValue);
  1.2723 +			},
  1.2724 +			function() { return null }
  1.2725 +		);
  1.2726 +	}
  1.2727 +
  1.2728 +	// "If node is invisible, abort this algorithm."
  1.2729 +	if (isInvisible(node)) {
  1.2730 +		return;
  1.2731 +	}
  1.2732 +
  1.2733 +	// "If the effective command value of command is loosely equivalent to new
  1.2734 +	// value on node, abort this algorithm."
  1.2735 +	if (areLooselyEquivalentValues(command, getEffectiveCommandValue(node, command), newValue)) {
  1.2736 +		return;
  1.2737 +	}
  1.2738 +
  1.2739 +	// "If node is not an allowed child of "span":"
  1.2740 +	if (!isAllowedChild(node, "span")) {
  1.2741 +		// "Let children be all children of node, omitting any that are
  1.2742 +		// Elements whose specified command value for command is neither null
  1.2743 +		// nor equivalent to new value."
  1.2744 +		var children = [];
  1.2745 +		for (var i = 0; i < node.childNodes.length; i++) {
  1.2746 +			if (node.childNodes[i].nodeType == Node.ELEMENT_NODE) {
  1.2747 +				var specifiedValue = getSpecifiedCommandValue(node.childNodes[i], command);
  1.2748 +
  1.2749 +				if (specifiedValue !== null
  1.2750 +				&& !areEquivalentValues(command, newValue, specifiedValue)) {
  1.2751 +					continue;
  1.2752 +				}
  1.2753 +			}
  1.2754 +			children.push(node.childNodes[i]);
  1.2755 +		}
  1.2756 +
  1.2757 +		// "Force the value of each Node in children, with command and new
  1.2758 +		// value as in this invocation of the algorithm."
  1.2759 +		for (var i = 0; i < children.length; i++) {
  1.2760 +			forceValue(children[i], command, newValue);
  1.2761 +		}
  1.2762 +
  1.2763 +		// "Abort this algorithm."
  1.2764 +		return;
  1.2765 +	}
  1.2766 +
  1.2767 +	// "If the effective command value of command is loosely equivalent to new
  1.2768 +	// value on node, abort this algorithm."
  1.2769 +	if (areLooselyEquivalentValues(command, getEffectiveCommandValue(node, command), newValue)) {
  1.2770 +		return;
  1.2771 +	}
  1.2772 +
  1.2773 +	// "Let new parent be null."
  1.2774 +	var newParent = null;
  1.2775 +
  1.2776 +	// "If the CSS styling flag is false:"
  1.2777 +	if (!cssStylingFlag) {
  1.2778 +		// "If command is "bold" and new value is "bold", let new parent be the
  1.2779 +		// result of calling createElement("b") on the ownerDocument of node."
  1.2780 +		if (command == "bold" && (newValue == "bold" || newValue == "700")) {
  1.2781 +			newParent = node.ownerDocument.createElement("b");
  1.2782 +		}
  1.2783 +
  1.2784 +		// "If command is "italic" and new value is "italic", let new parent be
  1.2785 +		// the result of calling createElement("i") on the ownerDocument of
  1.2786 +		// node."
  1.2787 +		if (command == "italic" && newValue == "italic") {
  1.2788 +			newParent = node.ownerDocument.createElement("i");
  1.2789 +		}
  1.2790 +
  1.2791 +		// "If command is "strikethrough" and new value is "line-through", let
  1.2792 +		// new parent be the result of calling createElement("s") on the
  1.2793 +		// ownerDocument of node."
  1.2794 +		if (command == "strikethrough" && newValue == "line-through") {
  1.2795 +			newParent = node.ownerDocument.createElement("s");
  1.2796 +		}
  1.2797 +
  1.2798 +		// "If command is "underline" and new value is "underline", let new
  1.2799 +		// parent be the result of calling createElement("u") on the
  1.2800 +		// ownerDocument of node."
  1.2801 +		if (command == "underline" && newValue == "underline") {
  1.2802 +			newParent = node.ownerDocument.createElement("u");
  1.2803 +		}
  1.2804 +
  1.2805 +		// "If command is "foreColor", and new value is fully opaque with red,
  1.2806 +		// green, and blue components in the range 0 to 255:"
  1.2807 +		if (command == "forecolor" && parseSimpleColor(newValue)) {
  1.2808 +			// "Let new parent be the result of calling createElement("font")
  1.2809 +			// on the ownerDocument of node."
  1.2810 +			newParent = node.ownerDocument.createElement("font");
  1.2811 +
  1.2812 +			// "Set the color attribute of new parent to the result of applying
  1.2813 +			// the rules for serializing simple color values to new value
  1.2814 +			// (interpreted as a simple color)."
  1.2815 +			newParent.setAttribute("color", parseSimpleColor(newValue));
  1.2816 +		}
  1.2817 +
  1.2818 +		// "If command is "fontName", let new parent be the result of calling
  1.2819 +		// createElement("font") on the ownerDocument of node, then set the
  1.2820 +		// face attribute of new parent to new value."
  1.2821 +		if (command == "fontname") {
  1.2822 +			newParent = node.ownerDocument.createElement("font");
  1.2823 +			newParent.face = newValue;
  1.2824 +		}
  1.2825 +	}
  1.2826 +
  1.2827 +	// "If command is "createLink" or "unlink":"
  1.2828 +	if (command == "createlink" || command == "unlink") {
  1.2829 +		// "Let new parent be the result of calling createElement("a") on the
  1.2830 +		// ownerDocument of node."
  1.2831 +		newParent = node.ownerDocument.createElement("a");
  1.2832 +
  1.2833 +		// "Set the href attribute of new parent to new value."
  1.2834 +		newParent.setAttribute("href", newValue);
  1.2835 +
  1.2836 +		// "Let ancestor be node's parent."
  1.2837 +		var ancestor = node.parentNode;
  1.2838 +
  1.2839 +		// "While ancestor is not null:"
  1.2840 +		while (ancestor) {
  1.2841 +			// "If ancestor is an a, set the tag name of ancestor to "span",
  1.2842 +			// and let ancestor be the result."
  1.2843 +			if (isHtmlElement(ancestor, "A")) {
  1.2844 +				ancestor = setTagName(ancestor, "span");
  1.2845 +			}
  1.2846 +
  1.2847 +			// "Set ancestor to its parent."
  1.2848 +			ancestor = ancestor.parentNode;
  1.2849 +		}
  1.2850 +	}
  1.2851 +
  1.2852 +	// "If command is "fontSize"; and new value is one of "x-small", "small",
  1.2853 +	// "medium", "large", "x-large", "xx-large", or "xxx-large"; and either the
  1.2854 +	// CSS styling flag is false, or new value is "xxx-large": let new parent
  1.2855 +	// be the result of calling createElement("font") on the ownerDocument of
  1.2856 +	// node, then set the size attribute of new parent to the number from the
  1.2857 +	// following table based on new value: [table omitted]"
  1.2858 +	if (command == "fontsize"
  1.2859 +	&& ["x-small", "small", "medium", "large", "x-large", "xx-large", "xxx-large"].indexOf(newValue) != -1
  1.2860 +	&& (!cssStylingFlag || newValue == "xxx-large")) {
  1.2861 +		newParent = node.ownerDocument.createElement("font");
  1.2862 +		newParent.size = cssSizeToLegacy(newValue);
  1.2863 +	}
  1.2864 +
  1.2865 +	// "If command is "subscript" or "superscript" and new value is
  1.2866 +	// "subscript", let new parent be the result of calling
  1.2867 +	// createElement("sub") on the ownerDocument of node."
  1.2868 +	if ((command == "subscript" || command == "superscript")
  1.2869 +	&& newValue == "subscript") {
  1.2870 +		newParent = node.ownerDocument.createElement("sub");
  1.2871 +	}
  1.2872 +
  1.2873 +	// "If command is "subscript" or "superscript" and new value is
  1.2874 +	// "superscript", let new parent be the result of calling
  1.2875 +	// createElement("sup") on the ownerDocument of node."
  1.2876 +	if ((command == "subscript" || command == "superscript")
  1.2877 +	&& newValue == "superscript") {
  1.2878 +		newParent = node.ownerDocument.createElement("sup");
  1.2879 +	}
  1.2880 +
  1.2881 +	// "If new parent is null, let new parent be the result of calling
  1.2882 +	// createElement("span") on the ownerDocument of node."
  1.2883 +	if (!newParent) {
  1.2884 +		newParent = node.ownerDocument.createElement("span");
  1.2885 +	}
  1.2886 +
  1.2887 +	// "Insert new parent in node's parent before node."
  1.2888 +	node.parentNode.insertBefore(newParent, node);
  1.2889 +
  1.2890 +	// "If the effective command value of command for new parent is not loosely
  1.2891 +	// equivalent to new value, and the relevant CSS property for command is
  1.2892 +	// not null, set that CSS property of new parent to new value (if the new
  1.2893 +	// value would be valid)."
  1.2894 +	var property = commands[command].relevantCssProperty;
  1.2895 +	if (property !== null
  1.2896 +	&& !areLooselyEquivalentValues(command, getEffectiveCommandValue(newParent, command), newValue)) {
  1.2897 +		newParent.style[property] = newValue;
  1.2898 +	}
  1.2899 +
  1.2900 +	// "If command is "strikethrough", and new value is "line-through", and the
  1.2901 +	// effective command value of "strikethrough" for new parent is not
  1.2902 +	// "line-through", set the "text-decoration" property of new parent to
  1.2903 +	// "line-through"."
  1.2904 +	if (command == "strikethrough"
  1.2905 +	&& newValue == "line-through"
  1.2906 +	&& getEffectiveCommandValue(newParent, "strikethrough") != "line-through") {
  1.2907 +		newParent.style.textDecoration = "line-through";
  1.2908 +	}
  1.2909 +
  1.2910 +	// "If command is "underline", and new value is "underline", and the
  1.2911 +	// effective command value of "underline" for new parent is not
  1.2912 +	// "underline", set the "text-decoration" property of new parent to
  1.2913 +	// "underline"."
  1.2914 +	if (command == "underline"
  1.2915 +	&& newValue == "underline"
  1.2916 +	&& getEffectiveCommandValue(newParent, "underline") != "underline") {
  1.2917 +		newParent.style.textDecoration = "underline";
  1.2918 +	}
  1.2919 +
  1.2920 +	// "Append node to new parent as its last child, preserving ranges."
  1.2921 +	movePreservingRanges(node, newParent, newParent.childNodes.length);
  1.2922 +
  1.2923 +	// "If node is an Element and the effective command value of command for
  1.2924 +	// node is not loosely equivalent to new value:"
  1.2925 +	if (node.nodeType == Node.ELEMENT_NODE
  1.2926 +	&& !areEquivalentValues(command, getEffectiveCommandValue(node, command), newValue)) {
  1.2927 +		// "Insert node into the parent of new parent before new parent,
  1.2928 +		// preserving ranges."
  1.2929 +		movePreservingRanges(node, newParent.parentNode, getNodeIndex(newParent));
  1.2930 +
  1.2931 +		// "Remove new parent from its parent."
  1.2932 +		newParent.parentNode.removeChild(newParent);
  1.2933 +
  1.2934 +		// "Let children be all children of node, omitting any that are
  1.2935 +		// Elements whose specified command value for command is neither null
  1.2936 +		// nor equivalent to new value."
  1.2937 +		var children = [];
  1.2938 +		for (var i = 0; i < node.childNodes.length; i++) {
  1.2939 +			if (node.childNodes[i].nodeType == Node.ELEMENT_NODE) {
  1.2940 +				var specifiedValue = getSpecifiedCommandValue(node.childNodes[i], command);
  1.2941 +
  1.2942 +				if (specifiedValue !== null
  1.2943 +				&& !areEquivalentValues(command, newValue, specifiedValue)) {
  1.2944 +					continue;
  1.2945 +				}
  1.2946 +			}
  1.2947 +			children.push(node.childNodes[i]);
  1.2948 +		}
  1.2949 +
  1.2950 +		// "Force the value of each Node in children, with command and new
  1.2951 +		// value as in this invocation of the algorithm."
  1.2952 +		for (var i = 0; i < children.length; i++) {
  1.2953 +			forceValue(children[i], command, newValue);
  1.2954 +		}
  1.2955 +	}
  1.2956 +}
  1.2957 +
  1.2958 +
  1.2959 +//@}
  1.2960 +///// Setting the selection's value /////
  1.2961 +//@{
  1.2962 +
  1.2963 +function setSelectionValue(command, newValue) {
  1.2964 +	// "If there is no formattable node effectively contained in the active
  1.2965 +	// range:"
  1.2966 +	if (!getAllEffectivelyContainedNodes(getActiveRange())
  1.2967 +	.some(isFormattableNode)) {
  1.2968 +		// "If command has inline command activated values, set the state
  1.2969 +		// override to true if new value is among them and false if it's not."
  1.2970 +		if ("inlineCommandActivatedValues" in commands[command]) {
  1.2971 +			setStateOverride(command, commands[command].inlineCommandActivatedValues
  1.2972 +				.indexOf(newValue) != -1);
  1.2973 +		}
  1.2974 +
  1.2975 +		// "If command is "subscript", unset the state override for
  1.2976 +		// "superscript"."
  1.2977 +		if (command == "subscript") {
  1.2978 +			unsetStateOverride("superscript");
  1.2979 +		}
  1.2980 +
  1.2981 +		// "If command is "superscript", unset the state override for
  1.2982 +		// "subscript"."
  1.2983 +		if (command == "superscript") {
  1.2984 +			unsetStateOverride("subscript");
  1.2985 +		}
  1.2986 +
  1.2987 +		// "If new value is null, unset the value override (if any)."
  1.2988 +		if (newValue === null) {
  1.2989 +			unsetValueOverride(command);
  1.2990 +
  1.2991 +		// "Otherwise, if command is "createLink" or it has a value specified,
  1.2992 +		// set the value override to new value."
  1.2993 +		} else if (command == "createlink" || "value" in commands[command]) {
  1.2994 +			setValueOverride(command, newValue);
  1.2995 +		}
  1.2996 +
  1.2997 +		// "Abort these steps."
  1.2998 +		return;
  1.2999 +	}
  1.3000 +
  1.3001 +	// "If the active range's start node is an editable Text node, and its
  1.3002 +	// start offset is neither zero nor its start node's length, call
  1.3003 +	// splitText() on the active range's start node, with argument equal to the
  1.3004 +	// active range's start offset. Then set the active range's start node to
  1.3005 +	// the result, and its start offset to zero."
  1.3006 +	if (isEditable(getActiveRange().startContainer)
  1.3007 +	&& getActiveRange().startContainer.nodeType == Node.TEXT_NODE
  1.3008 +	&& getActiveRange().startOffset != 0
  1.3009 +	&& getActiveRange().startOffset != getNodeLength(getActiveRange().startContainer)) {
  1.3010 +		// Account for browsers not following range mutation rules
  1.3011 +		var newActiveRange = document.createRange();
  1.3012 +		var newNode;
  1.3013 +		if (getActiveRange().startContainer == getActiveRange().endContainer) {
  1.3014 +			var newEndOffset = getActiveRange().endOffset - getActiveRange().startOffset;
  1.3015 +			newNode = getActiveRange().startContainer.splitText(getActiveRange().startOffset);
  1.3016 +			newActiveRange.setEnd(newNode, newEndOffset);
  1.3017 +			getActiveRange().setEnd(newNode, newEndOffset);
  1.3018 +		} else {
  1.3019 +			newNode = getActiveRange().startContainer.splitText(getActiveRange().startOffset);
  1.3020 +		}
  1.3021 +		newActiveRange.setStart(newNode, 0);
  1.3022 +		getSelection().removeAllRanges();
  1.3023 +		getSelection().addRange(newActiveRange);
  1.3024 +
  1.3025 +		getActiveRange().setStart(newNode, 0);
  1.3026 +	}
  1.3027 +
  1.3028 +	// "If the active range's end node is an editable Text node, and its end
  1.3029 +	// offset is neither zero nor its end node's length, call splitText() on
  1.3030 +	// the active range's end node, with argument equal to the active range's
  1.3031 +	// end offset."
  1.3032 +	if (isEditable(getActiveRange().endContainer)
  1.3033 +	&& getActiveRange().endContainer.nodeType == Node.TEXT_NODE
  1.3034 +	&& getActiveRange().endOffset != 0
  1.3035 +	&& getActiveRange().endOffset != getNodeLength(getActiveRange().endContainer)) {
  1.3036 +		// IE seems to mutate the range incorrectly here, so we need correction
  1.3037 +		// here as well.  The active range will be temporarily in orphaned
  1.3038 +		// nodes, so calling getActiveRange() after splitText() but before
  1.3039 +		// fixing the range will throw an exception.
  1.3040 +		var activeRange = getActiveRange();
  1.3041 +		var newStart = [activeRange.startContainer, activeRange.startOffset];
  1.3042 +		var newEnd = [activeRange.endContainer, activeRange.endOffset];
  1.3043 +		activeRange.endContainer.splitText(activeRange.endOffset);
  1.3044 +		activeRange.setStart(newStart[0], newStart[1]);
  1.3045 +		activeRange.setEnd(newEnd[0], newEnd[1]);
  1.3046 +
  1.3047 +		getSelection().removeAllRanges();
  1.3048 +		getSelection().addRange(activeRange);
  1.3049 +	}
  1.3050 +
  1.3051 +	// "Let element list be all editable Elements effectively contained in the
  1.3052 +	// active range.
  1.3053 +	//
  1.3054 +	// "For each element in element list, clear the value of element."
  1.3055 +	getAllEffectivelyContainedNodes(getActiveRange(), function(node) {
  1.3056 +		return isEditable(node) && node.nodeType == Node.ELEMENT_NODE;
  1.3057 +	}).forEach(function(element) {
  1.3058 +		clearValue(element, command);
  1.3059 +	});
  1.3060 +
  1.3061 +	// "Let node list be all editable nodes effectively contained in the active
  1.3062 +	// range.
  1.3063 +	//
  1.3064 +	// "For each node in node list:"
  1.3065 +	getAllEffectivelyContainedNodes(getActiveRange(), isEditable).forEach(function(node) {
  1.3066 +		// "Push down values on node."
  1.3067 +		pushDownValues(node, command, newValue);
  1.3068 +
  1.3069 +		// "If node is an allowed child of span, force the value of node."
  1.3070 +		if (isAllowedChild(node, "span")) {
  1.3071 +			forceValue(node, command, newValue);
  1.3072 +		}
  1.3073 +	});
  1.3074 +}
  1.3075 +
  1.3076 +
  1.3077 +//@}
  1.3078 +///// The backColor command /////
  1.3079 +//@{
  1.3080 +commands.backcolor = {
  1.3081 +	// Copy-pasted, same as hiliteColor
  1.3082 +	action: function(value) {
  1.3083 +		// Action is further copy-pasted, same as foreColor
  1.3084 +
  1.3085 +		// "If value is not a valid CSS color, prepend "#" to it."
  1.3086 +		//
  1.3087 +		// "If value is still not a valid CSS color, or if it is currentColor,
  1.3088 +		// return false."
  1.3089 +		//
  1.3090 +		// Cheap hack for testing, no attempt to be comprehensive.
  1.3091 +		if (/^([0-9a-fA-F]{3}){1,2}$/.test(value)) {
  1.3092 +			value = "#" + value;
  1.3093 +		}
  1.3094 +		if (!/^(rgba?|hsla?)\(.*\)$/.test(value)
  1.3095 +		&& !parseSimpleColor(value)
  1.3096 +		&& value.toLowerCase() != "transparent") {
  1.3097 +			return false;
  1.3098 +		}
  1.3099 +
  1.3100 +		// "Set the selection's value to value."
  1.3101 +		setSelectionValue("backcolor", value);
  1.3102 +
  1.3103 +		// "Return true."
  1.3104 +		return true;
  1.3105 +	}, standardInlineValueCommand: true, relevantCssProperty: "backgroundColor",
  1.3106 +	equivalentValues: function(val1, val2) {
  1.3107 +		// "Either both strings are valid CSS colors and have the same red,
  1.3108 +		// green, blue, and alpha components, or neither string is a valid CSS
  1.3109 +		// color."
  1.3110 +		return normalizeColor(val1) === normalizeColor(val2);
  1.3111 +	},
  1.3112 +};
  1.3113 +
  1.3114 +//@}
  1.3115 +///// The bold command /////
  1.3116 +//@{
  1.3117 +commands.bold = {
  1.3118 +	action: function() {
  1.3119 +		// "If queryCommandState("bold") returns true, set the selection's
  1.3120 +		// value to "normal". Otherwise set the selection's value to "bold".
  1.3121 +		// Either way, return true."
  1.3122 +		if (myQueryCommandState("bold")) {
  1.3123 +			setSelectionValue("bold", "normal");
  1.3124 +		} else {
  1.3125 +			setSelectionValue("bold", "bold");
  1.3126 +		}
  1.3127 +		return true;
  1.3128 +	}, inlineCommandActivatedValues: ["bold", "600", "700", "800", "900"],
  1.3129 +	relevantCssProperty: "fontWeight",
  1.3130 +	equivalentValues: function(val1, val2) {
  1.3131 +		// "Either the two strings are equal, or one is "bold" and the other is
  1.3132 +		// "700", or one is "normal" and the other is "400"."
  1.3133 +		return val1 == val2
  1.3134 +			|| (val1 == "bold" && val2 == "700")
  1.3135 +			|| (val1 == "700" && val2 == "bold")
  1.3136 +			|| (val1 == "normal" && val2 == "400")
  1.3137 +			|| (val1 == "400" && val2 == "normal");
  1.3138 +	},
  1.3139 +};
  1.3140 +
  1.3141 +//@}
  1.3142 +///// The createLink command /////
  1.3143 +//@{
  1.3144 +commands.createlink = {
  1.3145 +	action: function(value) {
  1.3146 +		// "If value is the empty string, return false."
  1.3147 +		if (value === "") {
  1.3148 +			return false;
  1.3149 +		}
  1.3150 +
  1.3151 +		// "For each editable a element that has an href attribute and is an
  1.3152 +		// ancestor of some node effectively contained in the active range, set
  1.3153 +		// that a element's href attribute to value."
  1.3154 +		//
  1.3155 +		// TODO: We don't actually do this in tree order, not that it matters
  1.3156 +		// unless you're spying with mutation events.
  1.3157 +		getAllEffectivelyContainedNodes(getActiveRange()).forEach(function(node) {
  1.3158 +			getAncestors(node).forEach(function(ancestor) {
  1.3159 +				if (isEditable(ancestor)
  1.3160 +				&& isHtmlElement(ancestor, "a")
  1.3161 +				&& ancestor.hasAttribute("href")) {
  1.3162 +					ancestor.setAttribute("href", value);
  1.3163 +				}
  1.3164 +			});
  1.3165 +		});
  1.3166 +
  1.3167 +		// "Set the selection's value to value."
  1.3168 +		setSelectionValue("createlink", value);
  1.3169 +
  1.3170 +		// "Return true."
  1.3171 +		return true;
  1.3172 +	}
  1.3173 +};
  1.3174 +
  1.3175 +//@}
  1.3176 +///// The fontName command /////
  1.3177 +//@{
  1.3178 +commands.fontname = {
  1.3179 +	action: function(value) {
  1.3180 +		// "Set the selection's value to value, then return true."
  1.3181 +		setSelectionValue("fontname", value);
  1.3182 +		return true;
  1.3183 +	}, standardInlineValueCommand: true, relevantCssProperty: "fontFamily"
  1.3184 +};
  1.3185 +
  1.3186 +//@}
  1.3187 +///// The fontSize command /////
  1.3188 +//@{
  1.3189 +
  1.3190 +// Helper function for fontSize's action plus queryOutputHelper.  It's just the
  1.3191 +// middle of fontSize's action, ripped out into its own function.  Returns null
  1.3192 +// if the size is invalid.
  1.3193 +function normalizeFontSize(value) {
  1.3194 +	// "Strip leading and trailing whitespace from value."
  1.3195 +	//
  1.3196 +	// Cheap hack, not following the actual algorithm.
  1.3197 +	value = value.trim();
  1.3198 +
  1.3199 +	// "If value is not a valid floating point number, and would not be a valid
  1.3200 +	// floating point number if a single leading "+" character were stripped,
  1.3201 +	// return false."
  1.3202 +	if (!/^[-+]?[0-9]+(\.[0-9]+)?([eE][-+]?[0-9]+)?$/.test(value)) {
  1.3203 +		return null;
  1.3204 +	}
  1.3205 +
  1.3206 +	var mode;
  1.3207 +
  1.3208 +	// "If the first character of value is "+", delete the character and let
  1.3209 +	// mode be "relative-plus"."
  1.3210 +	if (value[0] == "+") {
  1.3211 +		value = value.slice(1);
  1.3212 +		mode = "relative-plus";
  1.3213 +	// "Otherwise, if the first character of value is "-", delete the character
  1.3214 +	// and let mode be "relative-minus"."
  1.3215 +	} else if (value[0] == "-") {
  1.3216 +		value = value.slice(1);
  1.3217 +		mode = "relative-minus";
  1.3218 +	// "Otherwise, let mode be "absolute"."
  1.3219 +	} else {
  1.3220 +		mode = "absolute";
  1.3221 +	}
  1.3222 +
  1.3223 +	// "Apply the rules for parsing non-negative integers to value, and let
  1.3224 +	// number be the result."
  1.3225 +	//
  1.3226 +	// Another cheap hack.
  1.3227 +	var num = parseInt(value);
  1.3228 +
  1.3229 +	// "If mode is "relative-plus", add three to number."
  1.3230 +	if (mode == "relative-plus") {
  1.3231 +		num += 3;
  1.3232 +	}
  1.3233 +
  1.3234 +	// "If mode is "relative-minus", negate number, then add three to it."
  1.3235 +	if (mode == "relative-minus") {
  1.3236 +		num = 3 - num;
  1.3237 +	}
  1.3238 +
  1.3239 +	// "If number is less than one, let number equal 1."
  1.3240 +	if (num < 1) {
  1.3241 +		num = 1;
  1.3242 +	}
  1.3243 +
  1.3244 +	// "If number is greater than seven, let number equal 7."
  1.3245 +	if (num > 7) {
  1.3246 +		num = 7;
  1.3247 +	}
  1.3248 +
  1.3249 +	// "Set value to the string here corresponding to number:" [table omitted]
  1.3250 +	value = {
  1.3251 +		1: "x-small",
  1.3252 +		2: "small",
  1.3253 +		3: "medium",
  1.3254 +		4: "large",
  1.3255 +		5: "x-large",
  1.3256 +		6: "xx-large",
  1.3257 +		7: "xxx-large"
  1.3258 +	}[num];
  1.3259 +
  1.3260 +	return value;
  1.3261 +}
  1.3262 +
  1.3263 +commands.fontsize = {
  1.3264 +	action: function(value) {
  1.3265 +		value = normalizeFontSize(value);
  1.3266 +		if (value === null) {
  1.3267 +			return false;
  1.3268 +		}
  1.3269 +
  1.3270 +		// "Set the selection's value to value."
  1.3271 +		setSelectionValue("fontsize", value);
  1.3272 +
  1.3273 +		// "Return true."
  1.3274 +		return true;
  1.3275 +	}, indeterm: function() {
  1.3276 +		// "True if among formattable nodes that are effectively contained in
  1.3277 +		// the active range, there are two that have distinct effective command
  1.3278 +		// values.  Otherwise false."
  1.3279 +		return getAllEffectivelyContainedNodes(getActiveRange(), isFormattableNode)
  1.3280 +		.map(function(node) {
  1.3281 +			return getEffectiveCommandValue(node, "fontsize");
  1.3282 +		}).filter(function(value, i, arr) {
  1.3283 +			return arr.slice(0, i).indexOf(value) == -1;
  1.3284 +		}).length >= 2;
  1.3285 +	}, value: function() {
  1.3286 +		// "If the active range is null, return the empty string."
  1.3287 +		if (!getActiveRange()) {
  1.3288 +			return "";
  1.3289 +		}
  1.3290 +
  1.3291 +		// "Let pixel size be the effective command value of the first
  1.3292 +		// formattable node that is effectively contained in the active range,
  1.3293 +		// or if there is no such node, the effective command value of the
  1.3294 +		// active range's start node, in either case interpreted as a number of
  1.3295 +		// pixels."
  1.3296 +		var node = getAllEffectivelyContainedNodes(getActiveRange(), isFormattableNode)[0];
  1.3297 +		if (node === undefined) {
  1.3298 +			node = getActiveRange().startContainer;
  1.3299 +		}
  1.3300 +		var pixelSize = getEffectiveCommandValue(node, "fontsize");
  1.3301 +
  1.3302 +		// "Return the legacy font size for pixel size."
  1.3303 +		return getLegacyFontSize(pixelSize);
  1.3304 +	}, relevantCssProperty: "fontSize"
  1.3305 +};
  1.3306 +
  1.3307 +function getLegacyFontSize(size) {
  1.3308 +	if (getLegacyFontSize.resultCache === undefined) {
  1.3309 +		getLegacyFontSize.resultCache = {};
  1.3310 +	}
  1.3311 +
  1.3312 +	if (getLegacyFontSize.resultCache[size] !== undefined) {
  1.3313 +		return getLegacyFontSize.resultCache[size];
  1.3314 +	}
  1.3315 +
  1.3316 +	// For convenience in other places in my code, I handle all sizes, not just
  1.3317 +	// pixel sizes as the spec says.  This means pixel sizes have to be passed
  1.3318 +	// in suffixed with "px", not as plain numbers.
  1.3319 +	if (normalizeFontSize(size) !== null) {
  1.3320 +		return getLegacyFontSize.resultCache[size] = cssSizeToLegacy(normalizeFontSize(size));
  1.3321 +	}
  1.3322 +
  1.3323 +	if (["x-small", "x-small", "small", "medium", "large", "x-large", "xx-large", "xxx-large"].indexOf(size) == -1
  1.3324 +	&& !/^[0-9]+(\.[0-9]+)?(cm|mm|in|pt|pc|px)$/.test(size)) {
  1.3325 +		// There is no sensible legacy size for things like "2em".
  1.3326 +		return getLegacyFontSize.resultCache[size] = null;
  1.3327 +	}
  1.3328 +
  1.3329 +	var font = document.createElement("font");
  1.3330 +	document.body.appendChild(font);
  1.3331 +	if (size == "xxx-large") {
  1.3332 +		font.size = 7;
  1.3333 +	} else {
  1.3334 +		font.style.fontSize = size;
  1.3335 +	}
  1.3336 +	var pixelSize = parseInt(getComputedStyle(font).fontSize);
  1.3337 +	document.body.removeChild(font);
  1.3338 +
  1.3339 +	// "Let returned size be 1."
  1.3340 +	var returnedSize = 1;
  1.3341 +
  1.3342 +	// "While returned size is less than 7:"
  1.3343 +	while (returnedSize < 7) {
  1.3344 +		// "Let lower bound be the resolved value of "font-size" in pixels
  1.3345 +		// of a font element whose size attribute is set to returned size."
  1.3346 +		var font = document.createElement("font");
  1.3347 +		font.size = returnedSize;
  1.3348 +		document.body.appendChild(font);
  1.3349 +		var lowerBound = parseInt(getComputedStyle(font).fontSize);
  1.3350 +
  1.3351 +		// "Let upper bound be the resolved value of "font-size" in pixels
  1.3352 +		// of a font element whose size attribute is set to one plus
  1.3353 +		// returned size."
  1.3354 +		font.size = 1 + returnedSize;
  1.3355 +		var upperBound = parseInt(getComputedStyle(font).fontSize);
  1.3356 +		document.body.removeChild(font);
  1.3357 +
  1.3358 +		// "Let average be the average of upper bound and lower bound."
  1.3359 +		var average = (upperBound + lowerBound)/2;
  1.3360 +
  1.3361 +		// "If pixel size is less than average, return the one-element
  1.3362 +		// string consisting of the digit returned size."
  1.3363 +		if (pixelSize < average) {
  1.3364 +			return getLegacyFontSize.resultCache[size] = String(returnedSize);
  1.3365 +		}
  1.3366 +
  1.3367 +		// "Add one to returned size."
  1.3368 +		returnedSize++;
  1.3369 +	}
  1.3370 +
  1.3371 +	// "Return "7"."
  1.3372 +	return getLegacyFontSize.resultCache[size] = "7";
  1.3373 +}
  1.3374 +
  1.3375 +//@}
  1.3376 +///// The foreColor command /////
  1.3377 +//@{
  1.3378 +commands.forecolor = {
  1.3379 +	action: function(value) {
  1.3380 +		// Copy-pasted, same as backColor and hiliteColor
  1.3381 +
  1.3382 +		// "If value is not a valid CSS color, prepend "#" to it."
  1.3383 +		//
  1.3384 +		// "If value is still not a valid CSS color, or if it is currentColor,
  1.3385 +		// return false."
  1.3386 +		//
  1.3387 +		// Cheap hack for testing, no attempt to be comprehensive.
  1.3388 +		if (/^([0-9a-fA-F]{3}){1,2}$/.test(value)) {
  1.3389 +			value = "#" + value;
  1.3390 +		}
  1.3391 +		if (!/^(rgba?|hsla?)\(.*\)$/.test(value)
  1.3392 +		&& !parseSimpleColor(value)
  1.3393 +		&& value.toLowerCase() != "transparent") {
  1.3394 +			return false;
  1.3395 +		}
  1.3396 +
  1.3397 +		// "Set the selection's value to value."
  1.3398 +		setSelectionValue("forecolor", value);
  1.3399 +
  1.3400 +		// "Return true."
  1.3401 +		return true;
  1.3402 +	}, standardInlineValueCommand: true, relevantCssProperty: "color",
  1.3403 +	equivalentValues: function(val1, val2) {
  1.3404 +		// "Either both strings are valid CSS colors and have the same red,
  1.3405 +		// green, blue, and alpha components, or neither string is a valid CSS
  1.3406 +		// color."
  1.3407 +		return normalizeColor(val1) === normalizeColor(val2);
  1.3408 +	},
  1.3409 +};
  1.3410 +
  1.3411 +//@}
  1.3412 +///// The hiliteColor command /////
  1.3413 +//@{
  1.3414 +commands.hilitecolor = {
  1.3415 +	// Copy-pasted, same as backColor
  1.3416 +	action: function(value) {
  1.3417 +		// Action is further copy-pasted, same as foreColor
  1.3418 +
  1.3419 +		// "If value is not a valid CSS color, prepend "#" to it."
  1.3420 +		//
  1.3421 +		// "If value is still not a valid CSS color, or if it is currentColor,
  1.3422 +		// return false."
  1.3423 +		//
  1.3424 +		// Cheap hack for testing, no attempt to be comprehensive.
  1.3425 +		if (/^([0-9a-fA-F]{3}){1,2}$/.test(value)) {
  1.3426 +			value = "#" + value;
  1.3427 +		}
  1.3428 +		if (!/^(rgba?|hsla?)\(.*\)$/.test(value)
  1.3429 +		&& !parseSimpleColor(value)
  1.3430 +		&& value.toLowerCase() != "transparent") {
  1.3431 +			return false;
  1.3432 +		}
  1.3433 +
  1.3434 +		// "Set the selection's value to value."
  1.3435 +		setSelectionValue("hilitecolor", value);
  1.3436 +
  1.3437 +		// "Return true."
  1.3438 +		return true;
  1.3439 +	}, indeterm: function() {
  1.3440 +		// "True if among editable Text nodes that are effectively contained in
  1.3441 +		// the active range, there are two that have distinct effective command
  1.3442 +		// values.  Otherwise false."
  1.3443 +		return getAllEffectivelyContainedNodes(getActiveRange(), function(node) {
  1.3444 +			return isEditable(node) && node.nodeType == Node.TEXT_NODE;
  1.3445 +		}).map(function(node) {
  1.3446 +			return getEffectiveCommandValue(node, "hilitecolor");
  1.3447 +		}).filter(function(value, i, arr) {
  1.3448 +			return arr.slice(0, i).indexOf(value) == -1;
  1.3449 +		}).length >= 2;
  1.3450 +	}, standardInlineValueCommand: true, relevantCssProperty: "backgroundColor",
  1.3451 +	equivalentValues: function(val1, val2) {
  1.3452 +		// "Either both strings are valid CSS colors and have the same red,
  1.3453 +		// green, blue, and alpha components, or neither string is a valid CSS
  1.3454 +		// color."
  1.3455 +		return normalizeColor(val1) === normalizeColor(val2);
  1.3456 +	},
  1.3457 +};
  1.3458 +
  1.3459 +//@}
  1.3460 +///// The italic command /////
  1.3461 +//@{
  1.3462 +commands.italic = {
  1.3463 +	action: function() {
  1.3464 +		// "If queryCommandState("italic") returns true, set the selection's
  1.3465 +		// value to "normal". Otherwise set the selection's value to "italic".
  1.3466 +		// Either way, return true."
  1.3467 +		if (myQueryCommandState("italic")) {
  1.3468 +			setSelectionValue("italic", "normal");
  1.3469 +		} else {
  1.3470 +			setSelectionValue("italic", "italic");
  1.3471 +		}
  1.3472 +		return true;
  1.3473 +	}, inlineCommandActivatedValues: ["italic", "oblique"],
  1.3474 +	relevantCssProperty: "fontStyle"
  1.3475 +};
  1.3476 +
  1.3477 +//@}
  1.3478 +///// The removeFormat command /////
  1.3479 +//@{
  1.3480 +commands.removeformat = {
  1.3481 +	action: function() {
  1.3482 +		// "A removeFormat candidate is an editable HTML element with local
  1.3483 +		// name "abbr", "acronym", "b", "bdi", "bdo", "big", "blink", "cite",
  1.3484 +		// "code", "dfn", "em", "font", "i", "ins", "kbd", "mark", "nobr", "q",
  1.3485 +		// "s", "samp", "small", "span", "strike", "strong", "sub", "sup",
  1.3486 +		// "tt", "u", or "var"."
  1.3487 +		function isRemoveFormatCandidate(node) {
  1.3488 +			return isEditable(node)
  1.3489 +				&& isHtmlElement(node, ["abbr", "acronym", "b", "bdi", "bdo",
  1.3490 +				"big", "blink", "cite", "code", "dfn", "em", "font", "i",
  1.3491 +				"ins", "kbd", "mark", "nobr", "q", "s", "samp", "small",
  1.3492 +				"span", "strike", "strong", "sub", "sup", "tt", "u", "var"]);
  1.3493 +		}
  1.3494 +
  1.3495 +		// "Let elements to remove be a list of every removeFormat candidate
  1.3496 +		// effectively contained in the active range."
  1.3497 +		var elementsToRemove = getAllEffectivelyContainedNodes(getActiveRange(), isRemoveFormatCandidate);
  1.3498 +
  1.3499 +		// "For each element in elements to remove:"
  1.3500 +		elementsToRemove.forEach(function(element) {
  1.3501 +			// "While element has children, insert the first child of element
  1.3502 +			// into the parent of element immediately before element,
  1.3503 +			// preserving ranges."
  1.3504 +			while (element.hasChildNodes()) {
  1.3505 +				movePreservingRanges(element.firstChild, element.parentNode, getNodeIndex(element));
  1.3506 +			}
  1.3507 +
  1.3508 +			// "Remove element from its parent."
  1.3509 +			element.parentNode.removeChild(element);
  1.3510 +		});
  1.3511 +
  1.3512 +		// "If the active range's start node is an editable Text node, and its
  1.3513 +		// start offset is neither zero nor its start node's length, call
  1.3514 +		// splitText() on the active range's start node, with argument equal to
  1.3515 +		// the active range's start offset. Then set the active range's start
  1.3516 +		// node to the result, and its start offset to zero."
  1.3517 +		if (isEditable(getActiveRange().startContainer)
  1.3518 +		&& getActiveRange().startContainer.nodeType == Node.TEXT_NODE
  1.3519 +		&& getActiveRange().startOffset != 0
  1.3520 +		&& getActiveRange().startOffset != getNodeLength(getActiveRange().startContainer)) {
  1.3521 +			// Account for browsers not following range mutation rules
  1.3522 +			if (getActiveRange().startContainer == getActiveRange().endContainer) {
  1.3523 +				var newEnd = getActiveRange().endOffset - getActiveRange().startOffset;
  1.3524 +				var newNode = getActiveRange().startContainer.splitText(getActiveRange().startOffset);
  1.3525 +				getActiveRange().setStart(newNode, 0);
  1.3526 +				getActiveRange().setEnd(newNode, newEnd);
  1.3527 +			} else {
  1.3528 +				getActiveRange().setStart(getActiveRange().startContainer.splitText(getActiveRange().startOffset), 0);
  1.3529 +			}
  1.3530 +		}
  1.3531 +
  1.3532 +		// "If the active range's end node is an editable Text node, and its
  1.3533 +		// end offset is neither zero nor its end node's length, call
  1.3534 +		// splitText() on the active range's end node, with argument equal to
  1.3535 +		// the active range's end offset."
  1.3536 +		if (isEditable(getActiveRange().endContainer)
  1.3537 +		&& getActiveRange().endContainer.nodeType == Node.TEXT_NODE
  1.3538 +		&& getActiveRange().endOffset != 0
  1.3539 +		&& getActiveRange().endOffset != getNodeLength(getActiveRange().endContainer)) {
  1.3540 +			// IE seems to mutate the range incorrectly here, so we need
  1.3541 +			// correction here as well.  Have to be careful to set the range to
  1.3542 +			// something not including the text node so that getActiveRange()
  1.3543 +			// doesn't throw an exception due to a temporarily detached
  1.3544 +			// endpoint.
  1.3545 +			var newStart = [getActiveRange().startContainer, getActiveRange().startOffset];
  1.3546 +			var newEnd = [getActiveRange().endContainer, getActiveRange().endOffset];
  1.3547 +			getActiveRange().setEnd(document.documentElement, 0);
  1.3548 +			newEnd[0].splitText(newEnd[1]);
  1.3549 +			getActiveRange().setStart(newStart[0], newStart[1]);
  1.3550 +			getActiveRange().setEnd(newEnd[0], newEnd[1]);
  1.3551 +		}
  1.3552 +
  1.3553 +		// "Let node list consist of all editable nodes effectively contained
  1.3554 +		// in the active range."
  1.3555 +		//
  1.3556 +		// "For each node in node list, while node's parent is a removeFormat
  1.3557 +		// candidate in the same editing host as node, split the parent of the
  1.3558 +		// one-node list consisting of node."
  1.3559 +		getAllEffectivelyContainedNodes(getActiveRange(), isEditable).forEach(function(node) {
  1.3560 +			while (isRemoveFormatCandidate(node.parentNode)
  1.3561 +			&& inSameEditingHost(node.parentNode, node)) {
  1.3562 +				splitParent([node]);
  1.3563 +			}
  1.3564 +		});
  1.3565 +
  1.3566 +		// "For each of the entries in the following list, in the given order,
  1.3567 +		// set the selection's value to null, with command as given."
  1.3568 +		[
  1.3569 +			"subscript",
  1.3570 +			"bold",
  1.3571 +			"fontname",
  1.3572 +			"fontsize",
  1.3573 +			"forecolor",
  1.3574 +			"hilitecolor",
  1.3575 +			"italic",
  1.3576 +			"strikethrough",
  1.3577 +			"underline",
  1.3578 +		].forEach(function(command) {
  1.3579 +			setSelectionValue(command, null);
  1.3580 +		});
  1.3581 +
  1.3582 +		// "Return true."
  1.3583 +		return true;
  1.3584 +	}
  1.3585 +};
  1.3586 +
  1.3587 +//@}
  1.3588 +///// The strikethrough command /////
  1.3589 +//@{
  1.3590 +commands.strikethrough = {
  1.3591 +	action: function() {
  1.3592 +		// "If queryCommandState("strikethrough") returns true, set the
  1.3593 +		// selection's value to null. Otherwise set the selection's value to
  1.3594 +		// "line-through".  Either way, return true."
  1.3595 +		if (myQueryCommandState("strikethrough")) {
  1.3596 +			setSelectionValue("strikethrough", null);
  1.3597 +		} else {
  1.3598 +			setSelectionValue("strikethrough", "line-through");
  1.3599 +		}
  1.3600 +		return true;
  1.3601 +	}, inlineCommandActivatedValues: ["line-through"]
  1.3602 +};
  1.3603 +
  1.3604 +//@}
  1.3605 +///// The subscript command /////
  1.3606 +//@{
  1.3607 +commands.subscript = {
  1.3608 +	action: function() {
  1.3609 +		// "Call queryCommandState("subscript"), and let state be the result."
  1.3610 +		var state = myQueryCommandState("subscript");
  1.3611 +
  1.3612 +		// "Set the selection's value to null."
  1.3613 +		setSelectionValue("subscript", null);
  1.3614 +
  1.3615 +		// "If state is false, set the selection's value to "subscript"."
  1.3616 +		if (!state) {
  1.3617 +			setSelectionValue("subscript", "subscript");
  1.3618 +		}
  1.3619 +
  1.3620 +		// "Return true."
  1.3621 +		return true;
  1.3622 +	}, indeterm: function() {
  1.3623 +		// "True if either among formattable nodes that are effectively
  1.3624 +		// contained in the active range, there is at least one with effective
  1.3625 +		// command value "subscript" and at least one with some other effective
  1.3626 +		// command value; or if there is some formattable node effectively
  1.3627 +		// contained in the active range with effective command value "mixed".
  1.3628 +		// Otherwise false."
  1.3629 +		var nodes = getAllEffectivelyContainedNodes(getActiveRange(), isFormattableNode);
  1.3630 +		return (nodes.some(function(node) { return getEffectiveCommandValue(node, "subscript") == "subscript" })
  1.3631 +			&& nodes.some(function(node) { return getEffectiveCommandValue(node, "subscript") != "subscript" }))
  1.3632 +			|| nodes.some(function(node) { return getEffectiveCommandValue(node, "subscript") == "mixed" });
  1.3633 +	}, inlineCommandActivatedValues: ["subscript"],
  1.3634 +};
  1.3635 +
  1.3636 +//@}
  1.3637 +///// The superscript command /////
  1.3638 +//@{
  1.3639 +commands.superscript = {
  1.3640 +	action: function() {
  1.3641 +		// "Call queryCommandState("superscript"), and let state be the
  1.3642 +		// result."
  1.3643 +		var state = myQueryCommandState("superscript");
  1.3644 +
  1.3645 +		// "Set the selection's value to null."
  1.3646 +		setSelectionValue("superscript", null);
  1.3647 +
  1.3648 +		// "If state is false, set the selection's value to "superscript"."
  1.3649 +		if (!state) {
  1.3650 +			setSelectionValue("superscript", "superscript");
  1.3651 +		}
  1.3652 +
  1.3653 +		// "Return true."
  1.3654 +		return true;
  1.3655 +	}, indeterm: function() {
  1.3656 +		// "True if either among formattable nodes that are effectively
  1.3657 +		// contained in the active range, there is at least one with effective
  1.3658 +		// command value "superscript" and at least one with some other
  1.3659 +		// effective command value; or if there is some formattable node
  1.3660 +		// effectively contained in the active range with effective command
  1.3661 +		// value "mixed".  Otherwise false."
  1.3662 +		var nodes = getAllEffectivelyContainedNodes(getActiveRange(), isFormattableNode);
  1.3663 +		return (nodes.some(function(node) { return getEffectiveCommandValue(node, "superscript") == "superscript" })
  1.3664 +			&& nodes.some(function(node) { return getEffectiveCommandValue(node, "superscript") != "superscript" }))
  1.3665 +			|| nodes.some(function(node) { return getEffectiveCommandValue(node, "superscript") == "mixed" });
  1.3666 +	}, inlineCommandActivatedValues: ["superscript"],
  1.3667 +};
  1.3668 +
  1.3669 +//@}
  1.3670 +///// The underline command /////
  1.3671 +//@{
  1.3672 +commands.underline = {
  1.3673 +	action: function() {
  1.3674 +		// "If queryCommandState("underline") returns true, set the selection's
  1.3675 +		// value to null. Otherwise set the selection's value to "underline".
  1.3676 +		// Either way, return true."
  1.3677 +		if (myQueryCommandState("underline")) {
  1.3678 +			setSelectionValue("underline", null);
  1.3679 +		} else {
  1.3680 +			setSelectionValue("underline", "underline");
  1.3681 +		}
  1.3682 +		return true;
  1.3683 +	}, inlineCommandActivatedValues: ["underline"]
  1.3684 +};
  1.3685 +
  1.3686 +//@}
  1.3687 +///// The unlink command /////
  1.3688 +//@{
  1.3689 +commands.unlink = {
  1.3690 +	action: function() {
  1.3691 +		// "Let hyperlinks be a list of every a element that has an href
  1.3692 +		// attribute and is contained in the active range or is an ancestor of
  1.3693 +		// one of its boundary points."
  1.3694 +		//
  1.3695 +		// As usual, take care to ensure it's tree order.  The correctness of
  1.3696 +		// the following is left as an exercise for the reader.
  1.3697 +		var range = getActiveRange();
  1.3698 +		var hyperlinks = [];
  1.3699 +		for (
  1.3700 +			var node = range.startContainer;
  1.3701 +			node;
  1.3702 +			node = node.parentNode
  1.3703 +		) {
  1.3704 +			if (isHtmlElement(node, "A")
  1.3705 +			&& node.hasAttribute("href")) {
  1.3706 +				hyperlinks.unshift(node);
  1.3707 +			}
  1.3708 +		}
  1.3709 +		for (
  1.3710 +			var node = range.startContainer;
  1.3711 +			node != nextNodeDescendants(range.endContainer);
  1.3712 +			node = nextNode(node)
  1.3713 +		) {
  1.3714 +			if (isHtmlElement(node, "A")
  1.3715 +			&& node.hasAttribute("href")
  1.3716 +			&& (isContained(node, range)
  1.3717 +			|| isAncestor(node, range.endContainer)
  1.3718 +			|| node == range.endContainer)) {
  1.3719 +				hyperlinks.push(node);
  1.3720 +			}
  1.3721 +		}
  1.3722 +
  1.3723 +		// "Clear the value of each member of hyperlinks."
  1.3724 +		for (var i = 0; i < hyperlinks.length; i++) {
  1.3725 +			clearValue(hyperlinks[i], "unlink");
  1.3726 +		}
  1.3727 +
  1.3728 +		// "Return true."
  1.3729 +		return true;
  1.3730 +	}
  1.3731 +};
  1.3732 +
  1.3733 +//@}
  1.3734 +
  1.3735 +/////////////////////////////////////
  1.3736 +///// Block formatting commands /////
  1.3737 +/////////////////////////////////////
  1.3738 +
  1.3739 +///// Block formatting command definitions /////
  1.3740 +//@{
  1.3741 +
  1.3742 +// "An indentation element is either a blockquote, or a div that has a style
  1.3743 +// attribute that sets "margin" or some subproperty of it."
  1.3744 +function isIndentationElement(node) {
  1.3745 +	if (!isHtmlElement(node)) {
  1.3746 +		return false;
  1.3747 +	}
  1.3748 +
  1.3749 +	if (node.tagName == "BLOCKQUOTE") {
  1.3750 +		return true;
  1.3751 +	}
  1.3752 +
  1.3753 +	if (node.tagName != "DIV") {
  1.3754 +		return false;
  1.3755 +	}
  1.3756 +
  1.3757 +	for (var i = 0; i < node.style.length; i++) {
  1.3758 +		// Approximate check
  1.3759 +		if (/^(-[a-z]+-)?margin/.test(node.style[i])) {
  1.3760 +			return true;
  1.3761 +		}
  1.3762 +	}
  1.3763 +
  1.3764 +	return false;
  1.3765 +}
  1.3766 +
  1.3767 +// "A simple indentation element is an indentation element that has no
  1.3768 +// attributes except possibly
  1.3769 +//
  1.3770 +//   * "a style attribute that sets no properties other than "margin",
  1.3771 +//     "border", "padding", or subproperties of those; and/or
  1.3772 +//   * "a dir attribute."
  1.3773 +function isSimpleIndentationElement(node) {
  1.3774 +	if (!isIndentationElement(node)) {
  1.3775 +		return false;
  1.3776 +	}
  1.3777 +
  1.3778 +	for (var i = 0; i < node.attributes.length; i++) {
  1.3779 +		if (!isHtmlNamespace(node.attributes[i].namespaceURI)
  1.3780 +		|| ["style", "dir"].indexOf(node.attributes[i].name) == -1) {
  1.3781 +			return false;
  1.3782 +		}
  1.3783 +	}
  1.3784 +
  1.3785 +	for (var i = 0; i < node.style.length; i++) {
  1.3786 +		// This is approximate, but it works well enough for my purposes.
  1.3787 +		if (!/^(-[a-z]+-)?(margin|border|padding)/.test(node.style[i])) {
  1.3788 +			return false;
  1.3789 +		}
  1.3790 +	}
  1.3791 +
  1.3792 +	return true;
  1.3793 +}
  1.3794 +
  1.3795 +// "A non-list single-line container is an HTML element with local name
  1.3796 +// "address", "div", "h1", "h2", "h3", "h4", "h5", "h6", "listing", "p", "pre",
  1.3797 +// or "xmp"."
  1.3798 +function isNonListSingleLineContainer(node) {
  1.3799 +	return isHtmlElement(node, ["address", "div", "h1", "h2", "h3", "h4", "h5",
  1.3800 +		"h6", "listing", "p", "pre", "xmp"]);
  1.3801 +}
  1.3802 +
  1.3803 +// "A single-line container is either a non-list single-line container, or an
  1.3804 +// HTML element with local name "li", "dt", or "dd"."
  1.3805 +function isSingleLineContainer(node) {
  1.3806 +	return isNonListSingleLineContainer(node)
  1.3807 +		|| isHtmlElement(node, ["li", "dt", "dd"]);
  1.3808 +}
  1.3809 +
  1.3810 +function getBlockNodeOf(node) {
  1.3811 +	// "While node is an inline node, set node to its parent."
  1.3812 +	while (isInlineNode(node)) {
  1.3813 +		node = node.parentNode;
  1.3814 +	}
  1.3815 +
  1.3816 +	// "Return node."
  1.3817 +	return node;
  1.3818 +}
  1.3819 +
  1.3820 +//@}
  1.3821 +///// Assorted block formatting command algorithms /////
  1.3822 +//@{
  1.3823 +
  1.3824 +function fixDisallowedAncestors(node) {
  1.3825 +	// "If node is not editable, abort these steps."
  1.3826 +	if (!isEditable(node)) {
  1.3827 +		return;
  1.3828 +	}
  1.3829 +
  1.3830 +	// "If node is not an allowed child of any of its ancestors in the same
  1.3831 +	// editing host:"
  1.3832 +	if (getAncestors(node).every(function(ancestor) {
  1.3833 +		return !inSameEditingHost(node, ancestor)
  1.3834 +			|| !isAllowedChild(node, ancestor)
  1.3835 +	})) {
  1.3836 +		// "If node is a dd or dt, wrap the one-node list consisting of node,
  1.3837 +		// with sibling criteria returning true for any dl with no attributes
  1.3838 +		// and false otherwise, and new parent instructions returning the
  1.3839 +		// result of calling createElement("dl") on the context object. Then
  1.3840 +		// abort these steps."
  1.3841 +		if (isHtmlElement(node, ["dd", "dt"])) {
  1.3842 +			wrap([node],
  1.3843 +				function(sibling) { return isHtmlElement(sibling, "dl") && !sibling.attributes.length },
  1.3844 +				function() { return document.createElement("dl") });
  1.3845 +			return;
  1.3846 +		}
  1.3847 +
  1.3848 +		// "If "p" is not an allowed child of the editing host of node, abort
  1.3849 +		// these steps."
  1.3850 +		if (!isAllowedChild("p", getEditingHostOf(node))) {
  1.3851 +			return;
  1.3852 +		}
  1.3853 +
  1.3854 +		// "If node is not a prohibited paragraph child, abort these steps."
  1.3855 +		if (!isProhibitedParagraphChild(node)) {
  1.3856 +			return;
  1.3857 +		}
  1.3858 +
  1.3859 +		// "Set the tag name of node to the default single-line container name,
  1.3860 +		// and let node be the result."
  1.3861 +		node = setTagName(node, defaultSingleLineContainerName);
  1.3862 +
  1.3863 +		// "Fix disallowed ancestors of node."
  1.3864 +		fixDisallowedAncestors(node);
  1.3865 +
  1.3866 +		// "Let children be node's children."
  1.3867 +		var children = [].slice.call(node.childNodes);
  1.3868 +
  1.3869 +		// "For each child in children, if child is a prohibited paragraph
  1.3870 +		// child:"
  1.3871 +		children.filter(isProhibitedParagraphChild)
  1.3872 +		.forEach(function(child) {
  1.3873 +			// "Record the values of the one-node list consisting of child, and
  1.3874 +			// let values be the result."
  1.3875 +			var values = recordValues([child]);
  1.3876 +
  1.3877 +			// "Split the parent of the one-node list consisting of child."
  1.3878 +			splitParent([child]);
  1.3879 +
  1.3880 +			// "Restore the values from values."
  1.3881 +			restoreValues(values);
  1.3882 +		});
  1.3883 +
  1.3884 +		// "Abort these steps."
  1.3885 +		return;
  1.3886 +	}
  1.3887 +
  1.3888 +	// "Record the values of the one-node list consisting of node, and let
  1.3889 +	// values be the result."
  1.3890 +	var values = recordValues([node]);
  1.3891 +
  1.3892 +	// "While node is not an allowed child of its parent, split the parent of
  1.3893 +	// the one-node list consisting of node."
  1.3894 +	while (!isAllowedChild(node, node.parentNode)) {
  1.3895 +		splitParent([node]);
  1.3896 +	}
  1.3897 +
  1.3898 +	// "Restore the values from values."
  1.3899 +	restoreValues(values);
  1.3900 +}
  1.3901 +
  1.3902 +function normalizeSublists(item) {
  1.3903 +	// "If item is not an li or it is not editable or its parent is not
  1.3904 +	// editable, abort these steps."
  1.3905 +	if (!isHtmlElement(item, "LI")
  1.3906 +	|| !isEditable(item)
  1.3907 +	|| !isEditable(item.parentNode)) {
  1.3908 +		return;
  1.3909 +	}
  1.3910 +
  1.3911 +	// "Let new item be null."
  1.3912 +	var newItem = null;
  1.3913 +
  1.3914 +	// "While item has an ol or ul child:"
  1.3915 +	while ([].some.call(item.childNodes, function (node) { return isHtmlElement(node, ["OL", "UL"]) })) {
  1.3916 +		// "Let child be the last child of item."
  1.3917 +		var child = item.lastChild;
  1.3918 +
  1.3919 +		// "If child is an ol or ul, or new item is null and child is a Text
  1.3920 +		// node whose data consists of zero of more space characters:"
  1.3921 +		if (isHtmlElement(child, ["OL", "UL"])
  1.3922 +		|| (!newItem && child.nodeType == Node.TEXT_NODE && /^[ \t\n\f\r]*$/.test(child.data))) {
  1.3923 +			// "Set new item to null."
  1.3924 +			newItem = null;
  1.3925 +
  1.3926 +			// "Insert child into the parent of item immediately following
  1.3927 +			// item, preserving ranges."
  1.3928 +			movePreservingRanges(child, item.parentNode, 1 + getNodeIndex(item));
  1.3929 +
  1.3930 +		// "Otherwise:"
  1.3931 +		} else {
  1.3932 +			// "If new item is null, let new item be the result of calling
  1.3933 +			// createElement("li") on the ownerDocument of item, then insert
  1.3934 +			// new item into the parent of item immediately after item."
  1.3935 +			if (!newItem) {
  1.3936 +				newItem = item.ownerDocument.createElement("li");
  1.3937 +				item.parentNode.insertBefore(newItem, item.nextSibling);
  1.3938 +			}
  1.3939 +
  1.3940 +			// "Insert child into new item as its first child, preserving
  1.3941 +			// ranges."
  1.3942 +			movePreservingRanges(child, newItem, 0);
  1.3943 +		}
  1.3944 +	}
  1.3945 +}
  1.3946 +
  1.3947 +function getSelectionListState() {
  1.3948 +	// "If the active range is null, return "none"."
  1.3949 +	if (!getActiveRange()) {
  1.3950 +		return "none";
  1.3951 +	}
  1.3952 +
  1.3953 +	// "Block-extend the active range, and let new range be the result."
  1.3954 +	var newRange = blockExtend(getActiveRange());
  1.3955 +
  1.3956 +	// "Let node list be a list of nodes, initially empty."
  1.3957 +	//
  1.3958 +	// "For each node contained in new range, append node to node list if the
  1.3959 +	// last member of node list (if any) is not an ancestor of node; node is
  1.3960 +	// editable; node is not an indentation element; and node is either an ol
  1.3961 +	// or ul, or the child of an ol or ul, or an allowed child of "li"."
  1.3962 +	var nodeList = getContainedNodes(newRange, function(node) {
  1.3963 +		return isEditable(node)
  1.3964 +			&& !isIndentationElement(node)
  1.3965 +			&& (isHtmlElement(node, ["ol", "ul"])
  1.3966 +			|| isHtmlElement(node.parentNode, ["ol", "ul"])
  1.3967 +			|| isAllowedChild(node, "li"));
  1.3968 +	});
  1.3969 +
  1.3970 +	// "If node list is empty, return "none"."
  1.3971 +	if (!nodeList.length) {
  1.3972 +		return "none";
  1.3973 +	}
  1.3974 +
  1.3975 +	// "If every member of node list is either an ol or the child of an ol or
  1.3976 +	// the child of an li child of an ol, and none is a ul or an ancestor of a
  1.3977 +	// ul, return "ol"."
  1.3978 +	if (nodeList.every(function(node) {
  1.3979 +		return isHtmlElement(node, "ol")
  1.3980 +			|| isHtmlElement(node.parentNode, "ol")
  1.3981 +			|| (isHtmlElement(node.parentNode, "li") && isHtmlElement(node.parentNode.parentNode, "ol"));
  1.3982 +	})
  1.3983 +	&& !nodeList.some(function(node) { return isHtmlElement(node, "ul") || ("querySelector" in node && node.querySelector("ul")) })) {
  1.3984 +		return "ol";
  1.3985 +	}
  1.3986 +
  1.3987 +	// "If every member of node list is either a ul or the child of a ul or the
  1.3988 +	// child of an li child of a ul, and none is an ol or an ancestor of an ol,
  1.3989 +	// return "ul"."
  1.3990 +	if (nodeList.every(function(node) {
  1.3991 +		return isHtmlElement(node, "ul")
  1.3992 +			|| isHtmlElement(node.parentNode, "ul")
  1.3993 +			|| (isHtmlElement(node.parentNode, "li") && isHtmlElement(node.parentNode.parentNode, "ul"));
  1.3994 +	})
  1.3995 +	&& !nodeList.some(function(node) { return isHtmlElement(node, "ol") || ("querySelector" in node && node.querySelector("ol")) })) {
  1.3996 +		return "ul";
  1.3997 +	}
  1.3998 +
  1.3999 +	var hasOl = nodeList.some(function(node) {
  1.4000 +		return isHtmlElement(node, "ol")
  1.4001 +			|| isHtmlElement(node.parentNode, "ol")
  1.4002 +			|| ("querySelector" in node && node.querySelector("ol"))
  1.4003 +			|| (isHtmlElement(node.parentNode, "li") && isHtmlElement(node.parentNode.parentNode, "ol"));
  1.4004 +	});
  1.4005 +	var hasUl = nodeList.some(function(node) {
  1.4006 +		return isHtmlElement(node, "ul")
  1.4007 +			|| isHtmlElement(node.parentNode, "ul")
  1.4008 +			|| ("querySelector" in node && node.querySelector("ul"))
  1.4009 +			|| (isHtmlElement(node.parentNode, "li") && isHtmlElement(node.parentNode.parentNode, "ul"));
  1.4010 +	});
  1.4011 +	// "If some member of node list is either an ol or the child or ancestor of
  1.4012 +	// an ol or the child of an li child of an ol, and some member of node list
  1.4013 +	// is either a ul or the child or ancestor of a ul or the child of an li
  1.4014 +	// child of a ul, return "mixed"."
  1.4015 +	if (hasOl && hasUl) {
  1.4016 +		return "mixed";
  1.4017 +	}
  1.4018 +
  1.4019 +	// "If some member of node list is either an ol or the child or ancestor of
  1.4020 +	// an ol or the child of an li child of an ol, return "mixed ol"."
  1.4021 +	if (hasOl) {
  1.4022 +		return "mixed ol";
  1.4023 +	}
  1.4024 +
  1.4025 +	// "If some member of node list is either a ul or the child or ancestor of
  1.4026 +	// a ul or the child of an li child of a ul, return "mixed ul"."
  1.4027 +	if (hasUl) {
  1.4028 +		return "mixed ul";
  1.4029 +	}
  1.4030 +
  1.4031 +	// "Return "none"."
  1.4032 +	return "none";
  1.4033 +}
  1.4034 +
  1.4035 +function getAlignmentValue(node) {
  1.4036 +	// "While node is neither null nor an Element, or it is an Element but its
  1.4037 +	// "display" property has resolved value "inline" or "none", set node to
  1.4038 +	// its parent."
  1.4039 +	while ((node && node.nodeType != Node.ELEMENT_NODE)
  1.4040 +	|| (node.nodeType == Node.ELEMENT_NODE
  1.4041 +	&& ["inline", "none"].indexOf(getComputedStyle(node).display) != -1)) {
  1.4042 +		node = node.parentNode;
  1.4043 +	}
  1.4044 +
  1.4045 +	// "If node is not an Element, return "left"."
  1.4046 +	if (!node || node.nodeType != Node.ELEMENT_NODE) {
  1.4047 +		return "left";
  1.4048 +	}
  1.4049 +
  1.4050 +	var resolvedValue = getComputedStyle(node).textAlign
  1.4051 +		// Hack around browser non-standardness
  1.4052 +		.replace(/^-(moz|webkit)-/, "")
  1.4053 +		.replace(/^auto$/, "start");
  1.4054 +
  1.4055 +	// "If node's "text-align" property has resolved value "start", return
  1.4056 +	// "left" if the directionality of node is "ltr", "right" if it is "rtl"."
  1.4057 +	if (resolvedValue == "start") {
  1.4058 +		return getDirectionality(node) == "ltr" ? "left" : "right";
  1.4059 +	}
  1.4060 +
  1.4061 +	// "If node's "text-align" property has resolved value "end", return
  1.4062 +	// "right" if the directionality of node is "ltr", "left" if it is "rtl"."
  1.4063 +	if (resolvedValue == "end") {
  1.4064 +		return getDirectionality(node) == "ltr" ? "right" : "left";
  1.4065 +	}
  1.4066 +
  1.4067 +	// "If node's "text-align" property has resolved value "center", "justify",
  1.4068 +	// "left", or "right", return that value."
  1.4069 +	if (["center", "justify", "left", "right"].indexOf(resolvedValue) != -1) {
  1.4070 +		return resolvedValue;
  1.4071 +	}
  1.4072 +
  1.4073 +	// "Return "left"."
  1.4074 +	return "left";
  1.4075 +}
  1.4076 +
  1.4077 +function getNextEquivalentPoint(node, offset) {
  1.4078 +	// "If node's length is zero, return null."
  1.4079 +	if (getNodeLength(node) == 0) {
  1.4080 +		return null;
  1.4081 +	}
  1.4082 +
  1.4083 +	// "If offset is node's length, and node's parent is not null, and node is
  1.4084 +	// an inline node, return (node's parent, 1 + node's index)."
  1.4085 +	if (offset == getNodeLength(node)
  1.4086 +	&& node.parentNode
  1.4087 +	&& isInlineNode(node)) {
  1.4088 +		return [node.parentNode, 1 + getNodeIndex(node)];
  1.4089 +	}
  1.4090 +
  1.4091 +	// "If node has a child with index offset, and that child's length is not
  1.4092 +	// zero, and that child is an inline node, return (that child, 0)."
  1.4093 +	if (0 <= offset
  1.4094 +	&& offset < node.childNodes.length
  1.4095 +	&& getNodeLength(node.childNodes[offset]) != 0
  1.4096 +	&& isInlineNode(node.childNodes[offset])) {
  1.4097 +		return [node.childNodes[offset], 0];
  1.4098 +	}
  1.4099 +
  1.4100 +	// "Return null."
  1.4101 +	return null;
  1.4102 +}
  1.4103 +
  1.4104 +function getPreviousEquivalentPoint(node, offset) {
  1.4105 +	// "If node's length is zero, return null."
  1.4106 +	if (getNodeLength(node) == 0) {
  1.4107 +		return null;
  1.4108 +	}
  1.4109 +
  1.4110 +	// "If offset is 0, and node's parent is not null, and node is an inline
  1.4111 +	// node, return (node's parent, node's index)."
  1.4112 +	if (offset == 0
  1.4113 +	&& node.parentNode
  1.4114 +	&& isInlineNode(node)) {
  1.4115 +		return [node.parentNode, getNodeIndex(node)];
  1.4116 +	}
  1.4117 +
  1.4118 +	// "If node has a child with index offset − 1, and that child's length is
  1.4119 +	// not zero, and that child is an inline node, return (that child, that
  1.4120 +	// child's length)."
  1.4121 +	if (0 <= offset - 1
  1.4122 +	&& offset - 1 < node.childNodes.length
  1.4123 +	&& getNodeLength(node.childNodes[offset - 1]) != 0
  1.4124 +	&& isInlineNode(node.childNodes[offset - 1])) {
  1.4125 +		return [node.childNodes[offset - 1], getNodeLength(node.childNodes[offset - 1])];
  1.4126 +	}
  1.4127 +
  1.4128 +	// "Return null."
  1.4129 +	return null;
  1.4130 +}
  1.4131 +
  1.4132 +function getFirstEquivalentPoint(node, offset) {
  1.4133 +	// "While (node, offset)'s previous equivalent point is not null, set
  1.4134 +	// (node, offset) to its previous equivalent point."
  1.4135 +	var prev;
  1.4136 +	while (prev = getPreviousEquivalentPoint(node, offset)) {
  1.4137 +		node = prev[0];
  1.4138 +		offset = prev[1];
  1.4139 +	}
  1.4140 +
  1.4141 +	// "Return (node, offset)."
  1.4142 +	return [node, offset];
  1.4143 +}
  1.4144 +
  1.4145 +function getLastEquivalentPoint(node, offset) {
  1.4146 +	// "While (node, offset)'s next equivalent point is not null, set (node,
  1.4147 +	// offset) to its next equivalent point."
  1.4148 +	var next;
  1.4149 +	while (next = getNextEquivalentPoint(node, offset)) {
  1.4150 +		node = next[0];
  1.4151 +		offset = next[1];
  1.4152 +	}
  1.4153 +
  1.4154 +	// "Return (node, offset)."
  1.4155 +	return [node, offset];
  1.4156 +}
  1.4157 +
  1.4158 +//@}
  1.4159 +///// Block-extending a range /////
  1.4160 +//@{
  1.4161 +
  1.4162 +// "A boundary point (node, offset) is a block start point if either node's
  1.4163 +// parent is null and offset is zero; or node has a child with index offset −
  1.4164 +// 1, and that child is either a visible block node or a visible br."
  1.4165 +function isBlockStartPoint(node, offset) {
  1.4166 +	return (!node.parentNode && offset == 0)
  1.4167 +		|| (0 <= offset - 1
  1.4168 +		&& offset - 1 < node.childNodes.length
  1.4169 +		&& isVisible(node.childNodes[offset - 1])
  1.4170 +		&& (isBlockNode(node.childNodes[offset - 1])
  1.4171 +		|| isHtmlElement(node.childNodes[offset - 1], "br")));
  1.4172 +}
  1.4173 +
  1.4174 +// "A boundary point (node, offset) is a block end point if either node's
  1.4175 +// parent is null and offset is node's length; or node has a child with index
  1.4176 +// offset, and that child is a visible block node."
  1.4177 +function isBlockEndPoint(node, offset) {
  1.4178 +	return (!node.parentNode && offset == getNodeLength(node))
  1.4179 +		|| (offset < node.childNodes.length
  1.4180 +		&& isVisible(node.childNodes[offset])
  1.4181 +		&& isBlockNode(node.childNodes[offset]));
  1.4182 +}
  1.4183 +
  1.4184 +// "A boundary point is a block boundary point if it is either a block start
  1.4185 +// point or a block end point."
  1.4186 +function isBlockBoundaryPoint(node, offset) {
  1.4187 +	return isBlockStartPoint(node, offset)
  1.4188 +		|| isBlockEndPoint(node, offset);
  1.4189 +}
  1.4190 +
  1.4191 +function blockExtend(range) {
  1.4192 +	// "Let start node, start offset, end node, and end offset be the start
  1.4193 +	// and end nodes and offsets of the range."
  1.4194 +	var startNode = range.startContainer;
  1.4195 +	var startOffset = range.startOffset;
  1.4196 +	var endNode = range.endContainer;
  1.4197 +	var endOffset = range.endOffset;
  1.4198 +
  1.4199 +	// "If some ancestor container of start node is an li, set start offset to
  1.4200 +	// the index of the last such li in tree order, and set start node to that
  1.4201 +	// li's parent."
  1.4202 +	var liAncestors = getAncestors(startNode).concat(startNode)
  1.4203 +		.filter(function(ancestor) { return isHtmlElement(ancestor, "li") })
  1.4204 +		.slice(-1);
  1.4205 +	if (liAncestors.length) {
  1.4206 +		startOffset = getNodeIndex(liAncestors[0]);
  1.4207 +		startNode = liAncestors[0].parentNode;
  1.4208 +	}
  1.4209 +
  1.4210 +	// "If (start node, start offset) is not a block start point, repeat the
  1.4211 +	// following steps:"
  1.4212 +	if (!isBlockStartPoint(startNode, startOffset)) do {
  1.4213 +		// "If start offset is zero, set it to start node's index, then set
  1.4214 +		// start node to its parent."
  1.4215 +		if (startOffset == 0) {
  1.4216 +			startOffset = getNodeIndex(startNode);
  1.4217 +			startNode = startNode.parentNode;
  1.4218 +
  1.4219 +		// "Otherwise, subtract one from start offset."
  1.4220 +		} else {
  1.4221 +			startOffset--;
  1.4222 +		}
  1.4223 +
  1.4224 +		// "If (start node, start offset) is a block boundary point, break from
  1.4225 +		// this loop."
  1.4226 +	} while (!isBlockBoundaryPoint(startNode, startOffset));
  1.4227 +
  1.4228 +	// "While start offset is zero and start node's parent is not null, set
  1.4229 +	// start offset to start node's index, then set start node to its parent."
  1.4230 +	while (startOffset == 0
  1.4231 +	&& startNode.parentNode) {
  1.4232 +		startOffset = getNodeIndex(startNode);
  1.4233 +		startNode = startNode.parentNode;
  1.4234 +	}
  1.4235 +
  1.4236 +	// "If some ancestor container of end node is an li, set end offset to one
  1.4237 +	// plus the index of the last such li in tree order, and set end node to
  1.4238 +	// that li's parent."
  1.4239 +	var liAncestors = getAncestors(endNode).concat(endNode)
  1.4240 +		.filter(function(ancestor) { return isHtmlElement(ancestor, "li") })
  1.4241 +		.slice(-1);
  1.4242 +	if (liAncestors.length) {
  1.4243 +		endOffset = 1 + getNodeIndex(liAncestors[0]);
  1.4244 +		endNode = liAncestors[0].parentNode;
  1.4245 +	}
  1.4246 +
  1.4247 +	// "If (end node, end offset) is not a block end point, repeat the
  1.4248 +	// following steps:"
  1.4249 +	if (!isBlockEndPoint(endNode, endOffset)) do {
  1.4250 +		// "If end offset is end node's length, set it to one plus end node's
  1.4251 +		// index, then set end node to its parent."
  1.4252 +		if (endOffset == getNodeLength(endNode)) {
  1.4253 +			endOffset = 1 + getNodeIndex(endNode);
  1.4254 +			endNode = endNode.parentNode;
  1.4255 +
  1.4256 +		// "Otherwise, add one to end offset.
  1.4257 +		} else {
  1.4258 +			endOffset++;
  1.4259 +		}
  1.4260 +
  1.4261 +		// "If (end node, end offset) is a block boundary point, break from
  1.4262 +		// this loop."
  1.4263 +	} while (!isBlockBoundaryPoint(endNode, endOffset));
  1.4264 +
  1.4265 +	// "While end offset is end node's length and end node's parent is not
  1.4266 +	// null, set end offset to one plus end node's index, then set end node to
  1.4267 +	// its parent."
  1.4268 +	while (endOffset == getNodeLength(endNode)
  1.4269 +	&& endNode.parentNode) {
  1.4270 +		endOffset = 1 + getNodeIndex(endNode);
  1.4271 +		endNode = endNode.parentNode;
  1.4272 +	}
  1.4273 +
  1.4274 +	// "Let new range be a new range whose start and end nodes and offsets
  1.4275 +	// are start node, start offset, end node, and end offset."
  1.4276 +	var newRange = startNode.ownerDocument.createRange();
  1.4277 +	newRange.setStart(startNode, startOffset);
  1.4278 +	newRange.setEnd(endNode, endOffset);
  1.4279 +
  1.4280 +	// "Return new range."
  1.4281 +	return newRange;
  1.4282 +}
  1.4283 +
  1.4284 +function followsLineBreak(node) {
  1.4285 +	// "Let offset be zero."
  1.4286 +	var offset = 0;
  1.4287 +
  1.4288 +	// "While (node, offset) is not a block boundary point:"
  1.4289 +	while (!isBlockBoundaryPoint(node, offset)) {
  1.4290 +		// "If node has a visible child with index offset minus one, return
  1.4291 +		// false."
  1.4292 +		if (0 <= offset - 1
  1.4293 +		&& offset - 1 < node.childNodes.length
  1.4294 +		&& isVisible(node.childNodes[offset - 1])) {
  1.4295 +			return false;
  1.4296 +		}
  1.4297 +
  1.4298 +		// "If offset is zero or node has no children, set offset to node's
  1.4299 +		// index, then set node to its parent."
  1.4300 +		if (offset == 0
  1.4301 +		|| !node.hasChildNodes()) {
  1.4302 +			offset = getNodeIndex(node);
  1.4303 +			node = node.parentNode;
  1.4304 +
  1.4305 +		// "Otherwise, set node to its child with index offset minus one, then
  1.4306 +		// set offset to node's length."
  1.4307 +		} else {
  1.4308 +			node = node.childNodes[offset - 1];
  1.4309 +			offset = getNodeLength(node);
  1.4310 +		}
  1.4311 +	}
  1.4312 +
  1.4313 +	// "Return true."
  1.4314 +	return true;
  1.4315 +}
  1.4316 +
  1.4317 +function precedesLineBreak(node) {
  1.4318 +	// "Let offset be node's length."
  1.4319 +	var offset = getNodeLength(node);
  1.4320 +
  1.4321 +	// "While (node, offset) is not a block boundary point:"
  1.4322 +	while (!isBlockBoundaryPoint(node, offset)) {
  1.4323 +		// "If node has a visible child with index offset, return false."
  1.4324 +		if (offset < node.childNodes.length
  1.4325 +		&& isVisible(node.childNodes[offset])) {
  1.4326 +			return false;
  1.4327 +		}
  1.4328 +
  1.4329 +		// "If offset is node's length or node has no children, set offset to
  1.4330 +		// one plus node's index, then set node to its parent."
  1.4331 +		if (offset == getNodeLength(node)
  1.4332 +		|| !node.hasChildNodes()) {
  1.4333 +			offset = 1 + getNodeIndex(node);
  1.4334 +			node = node.parentNode;
  1.4335 +
  1.4336 +		// "Otherwise, set node to its child with index offset and set offset
  1.4337 +		// to zero."
  1.4338 +		} else {
  1.4339 +			node = node.childNodes[offset];
  1.4340 +			offset = 0;
  1.4341 +		}
  1.4342 +	}
  1.4343 +
  1.4344 +	// "Return true."
  1.4345 +	return true;
  1.4346 +}
  1.4347 +
  1.4348 +//@}
  1.4349 +///// Recording and restoring overrides /////
  1.4350 +//@{
  1.4351 +
  1.4352 +function recordCurrentOverrides() {
  1.4353 +	// "Let overrides be a list of (string, string or boolean) ordered pairs,
  1.4354 +	// initially empty."
  1.4355 +	var overrides = [];
  1.4356 +
  1.4357 +	// "If there is a value override for "createLink", add ("createLink", value
  1.4358 +	// override for "createLink") to overrides."
  1.4359 +	if (getValueOverride("createlink") !== undefined) {
  1.4360 +		overrides.push(["createlink", getValueOverride("createlink")]);
  1.4361 +	}
  1.4362 +
  1.4363 +	// "For each command in the list "bold", "italic", "strikethrough",
  1.4364 +	// "subscript", "superscript", "underline", in order: if there is a state
  1.4365 +	// override for command, add (command, command's state override) to
  1.4366 +	// overrides."
  1.4367 +	["bold", "italic", "strikethrough", "subscript", "superscript",
  1.4368 +	"underline"].forEach(function(command) {
  1.4369 +		if (getStateOverride(command) !== undefined) {
  1.4370 +			overrides.push([command, getStateOverride(command)]);
  1.4371 +		}
  1.4372 +	});
  1.4373 +
  1.4374 +	// "For each command in the list "fontName", "fontSize", "foreColor",
  1.4375 +	// "hiliteColor", in order: if there is a value override for command, add
  1.4376 +	// (command, command's value override) to overrides."
  1.4377 +	["fontname", "fontsize", "forecolor",
  1.4378 +	"hilitecolor"].forEach(function(command) {
  1.4379 +		if (getValueOverride(command) !== undefined) {
  1.4380 +			overrides.push([command, getValueOverride(command)]);
  1.4381 +		}
  1.4382 +	});
  1.4383 +
  1.4384 +	// "Return overrides."
  1.4385 +	return overrides;
  1.4386 +}
  1.4387 +
  1.4388 +function recordCurrentStatesAndValues() {
  1.4389 +	// "Let overrides be a list of (string, string or boolean) ordered pairs,
  1.4390 +	// initially empty."
  1.4391 +	var overrides = [];
  1.4392 +
  1.4393 +	// "Let node be the first formattable node effectively contained in the
  1.4394 +	// active range, or null if there is none."
  1.4395 +	var node = getAllEffectivelyContainedNodes(getActiveRange())
  1.4396 +		.filter(isFormattableNode)[0];
  1.4397 +
  1.4398 +	// "If node is null, return overrides."
  1.4399 +	if (!node) {
  1.4400 +		return overrides;
  1.4401 +	}
  1.4402 +
  1.4403 +	// "Add ("createLink", node's effective command value for "createLink") to
  1.4404 +	// overrides."
  1.4405 +	overrides.push(["createlink", getEffectiveCommandValue(node, "createlink")]);
  1.4406 +
  1.4407 +	// "For each command in the list "bold", "italic", "strikethrough",
  1.4408 +	// "subscript", "superscript", "underline", in order: if node's effective
  1.4409 +	// command value for command is one of its inline command activated values,
  1.4410 +	// add (command, true) to overrides, and otherwise add (command, false) to
  1.4411 +	// overrides."
  1.4412 +	["bold", "italic", "strikethrough", "subscript", "superscript",
  1.4413 +	"underline"].forEach(function(command) {
  1.4414 +		if (commands[command].inlineCommandActivatedValues
  1.4415 +		.indexOf(getEffectiveCommandValue(node, command)) != -1) {
  1.4416 +			overrides.push([command, true]);
  1.4417 +		} else {
  1.4418 +			overrides.push([command, false]);
  1.4419 +		}
  1.4420 +	});
  1.4421 +
  1.4422 +	// "For each command in the list "fontName", "foreColor", "hiliteColor", in
  1.4423 +	// order: add (command, command's value) to overrides."
  1.4424 +	["fontname", "fontsize", "forecolor", "hilitecolor"].forEach(function(command) {
  1.4425 +		overrides.push([command, commands[command].value()]);
  1.4426 +	});
  1.4427 +
  1.4428 +	// "Add ("fontSize", node's effective command value for "fontSize") to
  1.4429 +	// overrides."
  1.4430 +	overrides.push(["fontsize", getEffectiveCommandValue(node, "fontsize")]);
  1.4431 +
  1.4432 +	// "Return overrides."
  1.4433 +	return overrides;
  1.4434 +}
  1.4435 +
  1.4436 +function restoreStatesAndValues(overrides) {
  1.4437 +	// "Let node be the first formattable node effectively contained in the
  1.4438 +	// active range, or null if there is none."
  1.4439 +	var node = getAllEffectivelyContainedNodes(getActiveRange())
  1.4440 +		.filter(isFormattableNode)[0];
  1.4441 +
  1.4442 +	// "If node is not null, then for each (command, override) pair in
  1.4443 +	// overrides, in order:"
  1.4444 +	if (node) {
  1.4445 +		for (var i = 0; i < overrides.length; i++) {
  1.4446 +			var command = overrides[i][0];
  1.4447 +			var override = overrides[i][1];
  1.4448 +
  1.4449 +			// "If override is a boolean, and queryCommandState(command)
  1.4450 +			// returns something different from override, take the action for
  1.4451 +			// command, with value equal to the empty string."
  1.4452 +			if (typeof override == "boolean"
  1.4453 +			&& myQueryCommandState(command) != override) {
  1.4454 +				commands[command].action("");
  1.4455 +
  1.4456 +			// "Otherwise, if override is a string, and command is neither
  1.4457 +			// "createLink" nor "fontSize", and queryCommandValue(command)
  1.4458 +			// returns something not equivalent to override, take the action
  1.4459 +			// for command, with value equal to override."
  1.4460 +			} else if (typeof override == "string"
  1.4461 +			&& command != "createlink"
  1.4462 +			&& command != "fontsize"
  1.4463 +			&& !areEquivalentValues(command, myQueryCommandValue(command), override)) {
  1.4464 +				commands[command].action(override);
  1.4465 +
  1.4466 +			// "Otherwise, if override is a string; and command is
  1.4467 +			// "createLink"; and either there is a value override for
  1.4468 +			// "createLink" that is not equal to override, or there is no value
  1.4469 +			// override for "createLink" and node's effective command value for
  1.4470 +			// "createLink" is not equal to override: take the action for
  1.4471 +			// "createLink", with value equal to override."
  1.4472 +			} else if (typeof override == "string"
  1.4473 +			&& command == "createlink"
  1.4474 +			&& (
  1.4475 +				(
  1.4476 +					getValueOverride("createlink") !== undefined
  1.4477 +					&& getValueOverride("createlink") !== override
  1.4478 +				) || (
  1.4479 +					getValueOverride("createlink") === undefined
  1.4480 +					&& getEffectiveCommandValue(node, "createlink") !== override
  1.4481 +				)
  1.4482 +			)) {
  1.4483 +				commands.createlink.action(override);
  1.4484 +
  1.4485 +			// "Otherwise, if override is a string; and command is "fontSize";
  1.4486 +			// and either there is a value override for "fontSize" that is not
  1.4487 +			// equal to override, or there is no value override for "fontSize"
  1.4488 +			// and node's effective command value for "fontSize" is not loosely
  1.4489 +			// equivalent to override:"
  1.4490 +			} else if (typeof override == "string"
  1.4491 +			&& command == "fontsize"
  1.4492 +			&& (
  1.4493 +				(
  1.4494 +					getValueOverride("fontsize") !== undefined
  1.4495 +					&& getValueOverride("fontsize") !== override
  1.4496 +				) || (
  1.4497 +					getValueOverride("fontsize") === undefined
  1.4498 +					&& !areLooselyEquivalentValues(command, getEffectiveCommandValue(node, "fontsize"), override)
  1.4499 +				)
  1.4500 +			)) {
  1.4501 +				// "Convert override to an integer number of pixels, and set
  1.4502 +				// override to the legacy font size for the result."
  1.4503 +				override = getLegacyFontSize(override);
  1.4504 +
  1.4505 +				// "Take the action for "fontSize", with value equal to
  1.4506 +				// override."
  1.4507 +				commands.fontsize.action(override);
  1.4508 +
  1.4509 +			// "Otherwise, continue this loop from the beginning."
  1.4510 +			} else {
  1.4511 +				continue;
  1.4512 +			}
  1.4513 +
  1.4514 +			// "Set node to the first formattable node effectively contained in
  1.4515 +			// the active range, if there is one."
  1.4516 +			node = getAllEffectivelyContainedNodes(getActiveRange())
  1.4517 +				.filter(isFormattableNode)[0]
  1.4518 +				|| node;
  1.4519 +		}
  1.4520 +
  1.4521 +	// "Otherwise, for each (command, override) pair in overrides, in order:"
  1.4522 +	} else {
  1.4523 +		for (var i = 0; i < overrides.length; i++) {
  1.4524 +			var command = overrides[i][0];
  1.4525 +			var override = overrides[i][1];
  1.4526 +
  1.4527 +			// "If override is a boolean, set the state override for command to
  1.4528 +			// override."
  1.4529 +			if (typeof override == "boolean") {
  1.4530 +				setStateOverride(command, override);
  1.4531 +			}
  1.4532 +
  1.4533 +			// "If override is a string, set the value override for command to
  1.4534 +			// override."
  1.4535 +			if (typeof override == "string") {
  1.4536 +				setValueOverride(command, override);
  1.4537 +			}
  1.4538 +		}
  1.4539 +	}
  1.4540 +}
  1.4541 +
  1.4542 +//@}
  1.4543 +///// Deleting the selection /////
  1.4544 +//@{
  1.4545 +
  1.4546 +// The flags argument is a dictionary that can have blockMerging,
  1.4547 +// stripWrappers, and/or direction as keys.
  1.4548 +function deleteSelection(flags) {
  1.4549 +	if (flags === undefined) {
  1.4550 +		flags = {};
  1.4551 +	}
  1.4552 +
  1.4553 +	var blockMerging = "blockMerging" in flags ? Boolean(flags.blockMerging) : true;
  1.4554 +	var stripWrappers = "stripWrappers" in flags ? Boolean(flags.stripWrappers) : true;
  1.4555 +	var direction = "direction" in flags ? flags.direction : "forward";
  1.4556 +
  1.4557 +	// "If the active range is null, abort these steps and do nothing."
  1.4558 +	if (!getActiveRange()) {
  1.4559 +		return;
  1.4560 +	}
  1.4561 +
  1.4562 +	// "Canonicalize whitespace at the active range's start."
  1.4563 +	canonicalizeWhitespace(getActiveRange().startContainer, getActiveRange().startOffset);
  1.4564 +
  1.4565 +	// "Canonicalize whitespace at the active range's end."
  1.4566 +	canonicalizeWhitespace(getActiveRange().endContainer, getActiveRange().endOffset);
  1.4567 +
  1.4568 +	// "Let (start node, start offset) be the last equivalent point for the
  1.4569 +	// active range's start."
  1.4570 +	var start = getLastEquivalentPoint(getActiveRange().startContainer, getActiveRange().startOffset);
  1.4571 +	var startNode = start[0];
  1.4572 +	var startOffset = start[1];
  1.4573 +
  1.4574 +	// "Let (end node, end offset) be the first equivalent point for the active
  1.4575 +	// range's end."
  1.4576 +	var end = getFirstEquivalentPoint(getActiveRange().endContainer, getActiveRange().endOffset);
  1.4577 +	var endNode = end[0];
  1.4578 +	var endOffset = end[1];
  1.4579 +
  1.4580 +	// "If (end node, end offset) is not after (start node, start offset):"
  1.4581 +	if (getPosition(endNode, endOffset, startNode, startOffset) !== "after") {
  1.4582 +		// "If direction is "forward", call collapseToStart() on the context
  1.4583 +		// object's Selection."
  1.4584 +		//
  1.4585 +		// Here and in a few other places, we check rangeCount to work around a
  1.4586 +		// WebKit bug: it will sometimes incorrectly remove ranges from the
  1.4587 +		// selection if nodes are removed, so collapseToStart() will throw.
  1.4588 +		// This will break everything if we're using an actual selection, but
  1.4589 +		// if getActiveRange() is really just returning globalRange and that's
  1.4590 +		// all we care about, it will work fine.  I only add the extra check
  1.4591 +		// for errors I actually hit in testing.
  1.4592 +		if (direction == "forward") {
  1.4593 +			if (getSelection().rangeCount) {
  1.4594 +				getSelection().collapseToStart();
  1.4595 +			}
  1.4596 +			getActiveRange().collapse(true);
  1.4597 +
  1.4598 +		// "Otherwise, call collapseToEnd() on the context object's Selection."
  1.4599 +		} else {
  1.4600 +			getSelection().collapseToEnd();
  1.4601 +			getActiveRange().collapse(false);
  1.4602 +		}
  1.4603 +
  1.4604 +		// "Abort these steps."
  1.4605 +		return;
  1.4606 +	}
  1.4607 +
  1.4608 +	// "If start node is a Text node and start offset is 0, set start offset to
  1.4609 +	// the index of start node, then set start node to its parent."
  1.4610 +	if (startNode.nodeType == Node.TEXT_NODE
  1.4611 +	&& startOffset == 0) {
  1.4612 +		startOffset = getNodeIndex(startNode);
  1.4613 +		startNode = startNode.parentNode;
  1.4614 +	}
  1.4615 +
  1.4616 +	// "If end node is a Text node and end offset is its length, set end offset
  1.4617 +	// to one plus the index of end node, then set end node to its parent."
  1.4618 +	if (endNode.nodeType == Node.TEXT_NODE
  1.4619 +	&& endOffset == getNodeLength(endNode)) {
  1.4620 +		endOffset = 1 + getNodeIndex(endNode);
  1.4621 +		endNode = endNode.parentNode;
  1.4622 +	}
  1.4623 +
  1.4624 +	// "Call collapse(start node, start offset) on the context object's
  1.4625 +	// Selection."
  1.4626 +	getSelection().collapse(startNode, startOffset);
  1.4627 +	getActiveRange().setStart(startNode, startOffset);
  1.4628 +
  1.4629 +	// "Call extend(end node, end offset) on the context object's Selection."
  1.4630 +	getSelection().extend(endNode, endOffset);
  1.4631 +	getActiveRange().setEnd(endNode, endOffset);
  1.4632 +
  1.4633 +	// "Let start block be the active range's start node."
  1.4634 +	var startBlock = getActiveRange().startContainer;
  1.4635 +
  1.4636 +	// "While start block's parent is in the same editing host and start block
  1.4637 +	// is an inline node, set start block to its parent."
  1.4638 +	while (inSameEditingHost(startBlock, startBlock.parentNode)
  1.4639 +	&& isInlineNode(startBlock)) {
  1.4640 +		startBlock = startBlock.parentNode;
  1.4641 +	}
  1.4642 +
  1.4643 +	// "If start block is neither a block node nor an editing host, or "span"
  1.4644 +	// is not an allowed child of start block, or start block is a td or th,
  1.4645 +	// set start block to null."
  1.4646 +	if ((!isBlockNode(startBlock) && !isEditingHost(startBlock))
  1.4647 +	|| !isAllowedChild("span", startBlock)
  1.4648 +	|| isHtmlElement(startBlock, ["td", "th"])) {
  1.4649 +		startBlock = null;
  1.4650 +	}
  1.4651 +
  1.4652 +	// "Let end block be the active range's end node."
  1.4653 +	var endBlock = getActiveRange().endContainer;
  1.4654 +
  1.4655 +	// "While end block's parent is in the same editing host and end block is
  1.4656 +	// an inline node, set end block to its parent."
  1.4657 +	while (inSameEditingHost(endBlock, endBlock.parentNode)
  1.4658 +	&& isInlineNode(endBlock)) {
  1.4659 +		endBlock = endBlock.parentNode;
  1.4660 +	}
  1.4661 +
  1.4662 +	// "If end block is neither a block node nor an editing host, or "span" is
  1.4663 +	// not an allowed child of end block, or end block is a td or th, set end
  1.4664 +	// block to null."
  1.4665 +	if ((!isBlockNode(endBlock) && !isEditingHost(endBlock))
  1.4666 +	|| !isAllowedChild("span", endBlock)
  1.4667 +	|| isHtmlElement(endBlock, ["td", "th"])) {
  1.4668 +		endBlock = null;
  1.4669 +	}
  1.4670 +
  1.4671 +	// "Record current states and values, and let overrides be the result."
  1.4672 +	var overrides = recordCurrentStatesAndValues();
  1.4673 +
  1.4674 +	// "If start node and end node are the same, and start node is an editable
  1.4675 +	// Text node:"
  1.4676 +	if (startNode == endNode
  1.4677 +	&& isEditable(startNode)
  1.4678 +	&& startNode.nodeType == Node.TEXT_NODE) {
  1.4679 +		// "Call deleteData(start offset, end offset − start offset) on start
  1.4680 +		// node."
  1.4681 +		startNode.deleteData(startOffset, endOffset - startOffset);
  1.4682 +
  1.4683 +		// "Canonicalize whitespace at (start node, start offset), with fix
  1.4684 +		// collapsed space false."
  1.4685 +		canonicalizeWhitespace(startNode, startOffset, false);
  1.4686 +
  1.4687 +		// "If direction is "forward", call collapseToStart() on the context
  1.4688 +		// object's Selection."
  1.4689 +		if (direction == "forward") {
  1.4690 +			if (getSelection().rangeCount) {
  1.4691 +				getSelection().collapseToStart();
  1.4692 +			}
  1.4693 +			getActiveRange().collapse(true);
  1.4694 +
  1.4695 +		// "Otherwise, call collapseToEnd() on the context object's Selection."
  1.4696 +		} else {
  1.4697 +			getSelection().collapseToEnd();
  1.4698 +			getActiveRange().collapse(false);
  1.4699 +		}
  1.4700 +
  1.4701 +		// "Restore states and values from overrides."
  1.4702 +		restoreStatesAndValues(overrides);
  1.4703 +
  1.4704 +		// "Abort these steps."
  1.4705 +		return;
  1.4706 +	}
  1.4707 +
  1.4708 +	// "If start node is an editable Text node, call deleteData() on it, with
  1.4709 +	// start offset as the first argument and (length of start node − start
  1.4710 +	// offset) as the second argument."
  1.4711 +	if (isEditable(startNode)
  1.4712 +	&& startNode.nodeType == Node.TEXT_NODE) {
  1.4713 +		startNode.deleteData(startOffset, getNodeLength(startNode) - startOffset);
  1.4714 +	}
  1.4715 +
  1.4716 +	// "Let node list be a list of nodes, initially empty."
  1.4717 +	//
  1.4718 +	// "For each node contained in the active range, append node to node list
  1.4719 +	// if the last member of node list (if any) is not an ancestor of node;
  1.4720 +	// node is editable; and node is not a thead, tbody, tfoot, tr, th, or td."
  1.4721 +	var nodeList = getContainedNodes(getActiveRange(),
  1.4722 +		function(node) {
  1.4723 +			return isEditable(node)
  1.4724 +				&& !isHtmlElement(node, ["thead", "tbody", "tfoot", "tr", "th", "td"]);
  1.4725 +		}
  1.4726 +	);
  1.4727 +
  1.4728 +	// "For each node in node list:"
  1.4729 +	for (var i = 0; i < nodeList.length; i++) {
  1.4730 +		var node = nodeList[i];
  1.4731 +
  1.4732 +		// "Let parent be the parent of node."
  1.4733 +		var parent_ = node.parentNode;
  1.4734 +
  1.4735 +		// "Remove node from parent."
  1.4736 +		parent_.removeChild(node);
  1.4737 +
  1.4738 +		// "If the block node of parent has no visible children, and parent is
  1.4739 +		// editable or an editing host, call createElement("br") on the context
  1.4740 +		// object and append the result as the last child of parent."
  1.4741 +		if (![].some.call(getBlockNodeOf(parent_).childNodes, isVisible)
  1.4742 +		&& (isEditable(parent_) || isEditingHost(parent_))) {
  1.4743 +			parent_.appendChild(document.createElement("br"));
  1.4744 +		}
  1.4745 +
  1.4746 +		// "If strip wrappers is true or parent is not an ancestor container of
  1.4747 +		// start node, while parent is an editable inline node with length 0,
  1.4748 +		// let grandparent be the parent of parent, then remove parent from
  1.4749 +		// grandparent, then set parent to grandparent."
  1.4750 +		if (stripWrappers
  1.4751 +		|| (!isAncestor(parent_, startNode) && parent_ != startNode)) {
  1.4752 +			while (isEditable(parent_)
  1.4753 +			&& isInlineNode(parent_)
  1.4754 +			&& getNodeLength(parent_) == 0) {
  1.4755 +				var grandparent = parent_.parentNode;
  1.4756 +				grandparent.removeChild(parent_);
  1.4757 +				parent_ = grandparent;
  1.4758 +			}
  1.4759 +		}
  1.4760 +	}
  1.4761 +
  1.4762 +	// "If end node is an editable Text node, call deleteData(0, end offset) on
  1.4763 +	// it."
  1.4764 +	if (isEditable(endNode)
  1.4765 +	&& endNode.nodeType == Node.TEXT_NODE) {
  1.4766 +		endNode.deleteData(0, endOffset);
  1.4767 +	}
  1.4768 +
  1.4769 +	// "Canonicalize whitespace at the active range's start, with fix collapsed
  1.4770 +	// space false."
  1.4771 +	canonicalizeWhitespace(getActiveRange().startContainer, getActiveRange().startOffset, false);
  1.4772 +
  1.4773 +	// "Canonicalize whitespace at the active range's end, with fix collapsed
  1.4774 +	// space false."
  1.4775 +	canonicalizeWhitespace(getActiveRange().endContainer, getActiveRange().endOffset, false);
  1.4776 +
  1.4777 +	// "If block merging is false, or start block or end block is null, or
  1.4778 +	// start block is not in the same editing host as end block, or start block
  1.4779 +	// and end block are the same:"
  1.4780 +	if (!blockMerging
  1.4781 +	|| !startBlock
  1.4782 +	|| !endBlock
  1.4783 +	|| !inSameEditingHost(startBlock, endBlock)
  1.4784 +	|| startBlock == endBlock) {
  1.4785 +		// "If direction is "forward", call collapseToStart() on the context
  1.4786 +		// object's Selection."
  1.4787 +		if (direction == "forward") {
  1.4788 +			if (getSelection().rangeCount) {
  1.4789 +				getSelection().collapseToStart();
  1.4790 +			}
  1.4791 +			getActiveRange().collapse(true);
  1.4792 +
  1.4793 +		// "Otherwise, call collapseToEnd() on the context object's Selection."
  1.4794 +		} else {
  1.4795 +			if (getSelection().rangeCount) {
  1.4796 +				getSelection().collapseToEnd();
  1.4797 +			}
  1.4798 +			getActiveRange().collapse(false);
  1.4799 +		}
  1.4800 +
  1.4801 +		// "Restore states and values from overrides."
  1.4802 +		restoreStatesAndValues(overrides);
  1.4803 +
  1.4804 +		// "Abort these steps."
  1.4805 +		return;
  1.4806 +	}
  1.4807 +
  1.4808 +	// "If start block has one child, which is a collapsed block prop, remove
  1.4809 +	// its child from it."
  1.4810 +	if (startBlock.children.length == 1
  1.4811 +	&& isCollapsedBlockProp(startBlock.firstChild)) {
  1.4812 +		startBlock.removeChild(startBlock.firstChild);
  1.4813 +	}
  1.4814 +
  1.4815 +	// "If start block is an ancestor of end block:"
  1.4816 +	if (isAncestor(startBlock, endBlock)) {
  1.4817 +		// "Let reference node be end block."
  1.4818 +		var referenceNode = endBlock;
  1.4819 +
  1.4820 +		// "While reference node is not a child of start block, set reference
  1.4821 +		// node to its parent."
  1.4822 +		while (referenceNode.parentNode != startBlock) {
  1.4823 +			referenceNode = referenceNode.parentNode;
  1.4824 +		}
  1.4825 +
  1.4826 +		// "Call collapse() on the context object's Selection, with first
  1.4827 +		// argument start block and second argument the index of reference
  1.4828 +		// node."
  1.4829 +		getSelection().collapse(startBlock, getNodeIndex(referenceNode));
  1.4830 +		getActiveRange().setStart(startBlock, getNodeIndex(referenceNode));
  1.4831 +		getActiveRange().collapse(true);
  1.4832 +
  1.4833 +		// "If end block has no children:"
  1.4834 +		if (!endBlock.hasChildNodes()) {
  1.4835 +			// "While end block is editable and is the only child of its parent
  1.4836 +			// and is not a child of start block, let parent equal end block,
  1.4837 +			// then remove end block from parent, then set end block to
  1.4838 +			// parent."
  1.4839 +			while (isEditable(endBlock)
  1.4840 +			&& endBlock.parentNode.childNodes.length == 1
  1.4841 +			&& endBlock.parentNode != startBlock) {
  1.4842 +				var parent_ = endBlock;
  1.4843 +				parent_.removeChild(endBlock);
  1.4844 +				endBlock = parent_;
  1.4845 +			}
  1.4846 +
  1.4847 +			// "If end block is editable and is not an inline node, and its
  1.4848 +			// previousSibling and nextSibling are both inline nodes, call
  1.4849 +			// createElement("br") on the context object and insert it into end
  1.4850 +			// block's parent immediately after end block."
  1.4851 +			if (isEditable(endBlock)
  1.4852 +			&& !isInlineNode(endBlock)
  1.4853 +			&& isInlineNode(endBlock.previousSibling)
  1.4854 +			&& isInlineNode(endBlock.nextSibling)) {
  1.4855 +				endBlock.parentNode.insertBefore(document.createElement("br"), endBlock.nextSibling);
  1.4856 +			}
  1.4857 +
  1.4858 +			// "If end block is editable, remove it from its parent."
  1.4859 +			if (isEditable(endBlock)) {
  1.4860 +				endBlock.parentNode.removeChild(endBlock);
  1.4861 +			}
  1.4862 +
  1.4863 +			// "Restore states and values from overrides."
  1.4864 +			restoreStatesAndValues(overrides);
  1.4865 +
  1.4866 +			// "Abort these steps."
  1.4867 +			return;
  1.4868 +		}
  1.4869 +
  1.4870 +		// "If end block's firstChild is not an inline node, restore states and
  1.4871 +		// values from overrides, then abort these steps."
  1.4872 +		if (!isInlineNode(endBlock.firstChild)) {
  1.4873 +			restoreStatesAndValues(overrides);
  1.4874 +			return;
  1.4875 +		}
  1.4876 +
  1.4877 +		// "Let children be a list of nodes, initially empty."
  1.4878 +		var children = [];
  1.4879 +
  1.4880 +		// "Append the first child of end block to children."
  1.4881 +		children.push(endBlock.firstChild);
  1.4882 +
  1.4883 +		// "While children's last member is not a br, and children's last
  1.4884 +		// member's nextSibling is an inline node, append children's last
  1.4885 +		// member's nextSibling to children."
  1.4886 +		while (!isHtmlElement(children[children.length - 1], "br")
  1.4887 +		&& isInlineNode(children[children.length - 1].nextSibling)) {
  1.4888 +			children.push(children[children.length - 1].nextSibling);
  1.4889 +		}
  1.4890 +
  1.4891 +		// "Record the values of children, and let values be the result."
  1.4892 +		var values = recordValues(children);
  1.4893 +
  1.4894 +		// "While children's first member's parent is not start block, split
  1.4895 +		// the parent of children."
  1.4896 +		while (children[0].parentNode != startBlock) {
  1.4897 +			splitParent(children);
  1.4898 +		}
  1.4899 +
  1.4900 +		// "If children's first member's previousSibling is an editable br,
  1.4901 +		// remove that br from its parent."
  1.4902 +		if (isEditable(children[0].previousSibling)
  1.4903 +		&& isHtmlElement(children[0].previousSibling, "br")) {
  1.4904 +			children[0].parentNode.removeChild(children[0].previousSibling);
  1.4905 +		}
  1.4906 +
  1.4907 +	// "Otherwise, if start block is a descendant of end block:"
  1.4908 +	} else if (isDescendant(startBlock, endBlock)) {
  1.4909 +		// "Call collapse() on the context object's Selection, with first
  1.4910 +		// argument start block and second argument start block's length."
  1.4911 +		getSelection().collapse(startBlock, getNodeLength(startBlock));
  1.4912 +		getActiveRange().setStart(startBlock, getNodeLength(startBlock));
  1.4913 +		getActiveRange().collapse(true);
  1.4914 +
  1.4915 +		// "Let reference node be start block."
  1.4916 +		var referenceNode = startBlock;
  1.4917 +
  1.4918 +		// "While reference node is not a child of end block, set reference
  1.4919 +		// node to its parent."
  1.4920 +		while (referenceNode.parentNode != endBlock) {
  1.4921 +			referenceNode = referenceNode.parentNode;
  1.4922 +		}
  1.4923 +
  1.4924 +		// "If reference node's nextSibling is an inline node and start block's
  1.4925 +		// lastChild is a br, remove start block's lastChild from it."
  1.4926 +		if (isInlineNode(referenceNode.nextSibling)
  1.4927 +		&& isHtmlElement(startBlock.lastChild, "br")) {
  1.4928 +			startBlock.removeChild(startBlock.lastChild);
  1.4929 +		}
  1.4930 +
  1.4931 +		// "Let nodes to move be a list of nodes, initially empty."
  1.4932 +		var nodesToMove = [];
  1.4933 +
  1.4934 +		// "If reference node's nextSibling is neither null nor a block node,
  1.4935 +		// append it to nodes to move."
  1.4936 +		if (referenceNode.nextSibling
  1.4937 +		&& !isBlockNode(referenceNode.nextSibling)) {
  1.4938 +			nodesToMove.push(referenceNode.nextSibling);
  1.4939 +		}
  1.4940 +
  1.4941 +		// "While nodes to move is nonempty and its last member isn't a br and
  1.4942 +		// its last member's nextSibling is neither null nor a block node,
  1.4943 +		// append its last member's nextSibling to nodes to move."
  1.4944 +		if (nodesToMove.length
  1.4945 +		&& !isHtmlElement(nodesToMove[nodesToMove.length - 1], "br")
  1.4946 +		&& nodesToMove[nodesToMove.length - 1].nextSibling
  1.4947 +		&& !isBlockNode(nodesToMove[nodesToMove.length - 1].nextSibling)) {
  1.4948 +			nodesToMove.push(nodesToMove[nodesToMove.length - 1].nextSibling);
  1.4949 +		}
  1.4950 +
  1.4951 +		// "Record the values of nodes to move, and let values be the result."
  1.4952 +		var values = recordValues(nodesToMove);
  1.4953 +
  1.4954 +		// "For each node in nodes to move, append node as the last child of
  1.4955 +		// start block, preserving ranges."
  1.4956 +		nodesToMove.forEach(function(node) {
  1.4957 +			movePreservingRanges(node, startBlock, -1);
  1.4958 +		});
  1.4959 +
  1.4960 +	// "Otherwise:"
  1.4961 +	} else {
  1.4962 +		// "Call collapse() on the context object's Selection, with first
  1.4963 +		// argument start block and second argument start block's length."
  1.4964 +		getSelection().collapse(startBlock, getNodeLength(startBlock));
  1.4965 +		getActiveRange().setStart(startBlock, getNodeLength(startBlock));
  1.4966 +		getActiveRange().collapse(true);
  1.4967 +
  1.4968 +		// "If end block's firstChild is an inline node and start block's
  1.4969 +		// lastChild is a br, remove start block's lastChild from it."
  1.4970 +		if (isInlineNode(endBlock.firstChild)
  1.4971 +		&& isHtmlElement(startBlock.lastChild, "br")) {
  1.4972 +			startBlock.removeChild(startBlock.lastChild);
  1.4973 +		}
  1.4974 +
  1.4975 +		// "Record the values of end block's children, and let values be the
  1.4976 +		// result."
  1.4977 +		var values = recordValues([].slice.call(endBlock.childNodes));
  1.4978 +
  1.4979 +		// "While end block has children, append the first child of end block
  1.4980 +		// to start block, preserving ranges."
  1.4981 +		while (endBlock.hasChildNodes()) {
  1.4982 +			movePreservingRanges(endBlock.firstChild, startBlock, -1);
  1.4983 +		}
  1.4984 +
  1.4985 +		// "While end block has no children, let parent be the parent of end
  1.4986 +		// block, then remove end block from parent, then set end block to
  1.4987 +		// parent."
  1.4988 +		while (!endBlock.hasChildNodes()) {
  1.4989 +			var parent_ = endBlock.parentNode;
  1.4990 +			parent_.removeChild(endBlock);
  1.4991 +			endBlock = parent_;
  1.4992 +		}
  1.4993 +	}
  1.4994 +
  1.4995 +	// "Let ancestor be start block."
  1.4996 +	var ancestor = startBlock;
  1.4997 +
  1.4998 +	// "While ancestor has an inclusive ancestor ol in the same editing host
  1.4999 +	// whose nextSibling is also an ol in the same editing host, or an
  1.5000 +	// inclusive ancestor ul in the same editing host whose nextSibling is also
  1.5001 +	// a ul in the same editing host:"
  1.5002 +	while (getInclusiveAncestors(ancestor).some(function(node) {
  1.5003 +		return inSameEditingHost(ancestor, node)
  1.5004 +			&& (
  1.5005 +				(isHtmlElement(node, "ol") && isHtmlElement(node.nextSibling, "ol"))
  1.5006 +				|| (isHtmlElement(node, "ul") && isHtmlElement(node.nextSibling, "ul"))
  1.5007 +			) && inSameEditingHost(ancestor, node.nextSibling);
  1.5008 +	})) {
  1.5009 +		// "While ancestor and its nextSibling are not both ols in the same
  1.5010 +		// editing host, and are also not both uls in the same editing host,
  1.5011 +		// set ancestor to its parent."
  1.5012 +		while (!(
  1.5013 +			isHtmlElement(ancestor, "ol")
  1.5014 +			&& isHtmlElement(ancestor.nextSibling, "ol")
  1.5015 +			&& inSameEditingHost(ancestor, ancestor.nextSibling)
  1.5016 +		) && !(
  1.5017 +			isHtmlElement(ancestor, "ul")
  1.5018 +			&& isHtmlElement(ancestor.nextSibling, "ul")
  1.5019 +			&& inSameEditingHost(ancestor, ancestor.nextSibling)
  1.5020 +		)) {
  1.5021 +			ancestor = ancestor.parentNode;
  1.5022 +		}
  1.5023 +
  1.5024 +		// "While ancestor's nextSibling has children, append ancestor's
  1.5025 +		// nextSibling's firstChild as the last child of ancestor, preserving
  1.5026 +		// ranges."
  1.5027 +		while (ancestor.nextSibling.hasChildNodes()) {
  1.5028 +			movePreservingRanges(ancestor.nextSibling.firstChild, ancestor, -1);
  1.5029 +		}
  1.5030 +
  1.5031 +		// "Remove ancestor's nextSibling from its parent."
  1.5032 +		ancestor.parentNode.removeChild(ancestor.nextSibling);
  1.5033 +	}
  1.5034 +
  1.5035 +	// "Restore the values from values."
  1.5036 +	restoreValues(values);
  1.5037 +
  1.5038 +	// "If start block has no children, call createElement("br") on the context
  1.5039 +	// object and append the result as the last child of start block."
  1.5040 +	if (!startBlock.hasChildNodes()) {
  1.5041 +		startBlock.appendChild(document.createElement("br"));
  1.5042 +	}
  1.5043 +
  1.5044 +	// "Remove extraneous line breaks at the end of start block."
  1.5045 +	removeExtraneousLineBreaksAtTheEndOf(startBlock);
  1.5046 +
  1.5047 +	// "Restore states and values from overrides."
  1.5048 +	restoreStatesAndValues(overrides);
  1.5049 +}
  1.5050 +
  1.5051 +
  1.5052 +//@}
  1.5053 +///// Splitting a node list's parent /////
  1.5054 +//@{
  1.5055 +
  1.5056 +function splitParent(nodeList) {
  1.5057 +	// "Let original parent be the parent of the first member of node list."
  1.5058 +	var originalParent = nodeList[0].parentNode;
  1.5059 +
  1.5060 +	// "If original parent is not editable or its parent is null, do nothing
  1.5061 +	// and abort these steps."
  1.5062 +	if (!isEditable(originalParent)
  1.5063 +	|| !originalParent.parentNode) {
  1.5064 +		return;
  1.5065 +	}
  1.5066 +
  1.5067 +	// "If the first child of original parent is in node list, remove
  1.5068 +	// extraneous line breaks before original parent."
  1.5069 +	if (nodeList.indexOf(originalParent.firstChild) != -1) {
  1.5070 +		removeExtraneousLineBreaksBefore(originalParent);
  1.5071 +	}
  1.5072 +
  1.5073 +	// "If the first child of original parent is in node list, and original
  1.5074 +	// parent follows a line break, set follows line break to true. Otherwise,
  1.5075 +	// set follows line break to false."
  1.5076 +	var followsLineBreak_ = nodeList.indexOf(originalParent.firstChild) != -1
  1.5077 +		&& followsLineBreak(originalParent);
  1.5078 +
  1.5079 +	// "If the last child of original parent is in node list, and original
  1.5080 +	// parent precedes a line break, set precedes line break to true.
  1.5081 +	// Otherwise, set precedes line break to false."
  1.5082 +	var precedesLineBreak_ = nodeList.indexOf(originalParent.lastChild) != -1
  1.5083 +		&& precedesLineBreak(originalParent);
  1.5084 +
  1.5085 +	// "If the first child of original parent is not in node list, but its last
  1.5086 +	// child is:"
  1.5087 +	if (nodeList.indexOf(originalParent.firstChild) == -1
  1.5088 +	&& nodeList.indexOf(originalParent.lastChild) != -1) {
  1.5089 +		// "For each node in node list, in reverse order, insert node into the
  1.5090 +		// parent of original parent immediately after original parent,
  1.5091 +		// preserving ranges."
  1.5092 +		for (var i = nodeList.length - 1; i >= 0; i--) {
  1.5093 +			movePreservingRanges(nodeList[i], originalParent.parentNode, 1 + getNodeIndex(originalParent));
  1.5094 +		}
  1.5095 +
  1.5096 +		// "If precedes line break is true, and the last member of node list
  1.5097 +		// does not precede a line break, call createElement("br") on the
  1.5098 +		// context object and insert the result immediately after the last
  1.5099 +		// member of node list."
  1.5100 +		if (precedesLineBreak_
  1.5101 +		&& !precedesLineBreak(nodeList[nodeList.length - 1])) {
  1.5102 +			nodeList[nodeList.length - 1].parentNode.insertBefore(document.createElement("br"), nodeList[nodeList.length - 1].nextSibling);
  1.5103 +		}
  1.5104 +
  1.5105 +		// "Remove extraneous line breaks at the end of original parent."
  1.5106 +		removeExtraneousLineBreaksAtTheEndOf(originalParent);
  1.5107 +
  1.5108 +		// "Abort these steps."
  1.5109 +		return;
  1.5110 +	}
  1.5111 +
  1.5112 +	// "If the first child of original parent is not in node list:"
  1.5113 +	if (nodeList.indexOf(originalParent.firstChild) == -1) {
  1.5114 +		// "Let cloned parent be the result of calling cloneNode(false) on
  1.5115 +		// original parent."
  1.5116 +		var clonedParent = originalParent.cloneNode(false);
  1.5117 +
  1.5118 +		// "If original parent has an id attribute, unset it."
  1.5119 +		originalParent.removeAttribute("id");
  1.5120 +
  1.5121 +		// "Insert cloned parent into the parent of original parent immediately
  1.5122 +		// before original parent."
  1.5123 +		originalParent.parentNode.insertBefore(clonedParent, originalParent);
  1.5124 +
  1.5125 +		// "While the previousSibling of the first member of node list is not
  1.5126 +		// null, append the first child of original parent as the last child of
  1.5127 +		// cloned parent, preserving ranges."
  1.5128 +		while (nodeList[0].previousSibling) {
  1.5129 +			movePreservingRanges(originalParent.firstChild, clonedParent, clonedParent.childNodes.length);
  1.5130 +		}
  1.5131 +	}
  1.5132 +
  1.5133 +	// "For each node in node list, insert node into the parent of original
  1.5134 +	// parent immediately before original parent, preserving ranges."
  1.5135 +	for (var i = 0; i < nodeList.length; i++) {
  1.5136 +		movePreservingRanges(nodeList[i], originalParent.parentNode, getNodeIndex(originalParent));
  1.5137 +	}
  1.5138 +
  1.5139 +	// "If follows line break is true, and the first member of node list does
  1.5140 +	// not follow a line break, call createElement("br") on the context object
  1.5141 +	// and insert the result immediately before the first member of node list."
  1.5142 +	if (followsLineBreak_
  1.5143 +	&& !followsLineBreak(nodeList[0])) {
  1.5144 +		nodeList[0].parentNode.insertBefore(document.createElement("br"), nodeList[0]);
  1.5145 +	}
  1.5146 +
  1.5147 +	// "If the last member of node list is an inline node other than a br, and
  1.5148 +	// the first child of original parent is a br, and original parent is not
  1.5149 +	// an inline node, remove the first child of original parent from original
  1.5150 +	// parent."
  1.5151 +	if (isInlineNode(nodeList[nodeList.length - 1])
  1.5152 +	&& !isHtmlElement(nodeList[nodeList.length - 1], "br")
  1.5153 +	&& isHtmlElement(originalParent.firstChild, "br")
  1.5154 +	&& !isInlineNode(originalParent)) {
  1.5155 +		originalParent.removeChild(originalParent.firstChild);
  1.5156 +	}
  1.5157 +
  1.5158 +	// "If original parent has no children:"
  1.5159 +	if (!originalParent.hasChildNodes()) {
  1.5160 +		// "Remove original parent from its parent."
  1.5161 +		originalParent.parentNode.removeChild(originalParent);
  1.5162 +
  1.5163 +		// "If precedes line break is true, and the last member of node list
  1.5164 +		// does not precede a line break, call createElement("br") on the
  1.5165 +		// context object and insert the result immediately after the last
  1.5166 +		// member of node list."
  1.5167 +		if (precedesLineBreak_
  1.5168 +		&& !precedesLineBreak(nodeList[nodeList.length - 1])) {
  1.5169 +			nodeList[nodeList.length - 1].parentNode.insertBefore(document.createElement("br"), nodeList[nodeList.length - 1].nextSibling);
  1.5170 +		}
  1.5171 +
  1.5172 +	// "Otherwise, remove extraneous line breaks before original parent."
  1.5173 +	} else {
  1.5174 +		removeExtraneousLineBreaksBefore(originalParent);
  1.5175 +	}
  1.5176 +
  1.5177 +	// "If node list's last member's nextSibling is null, but its parent is not
  1.5178 +	// null, remove extraneous line breaks at the end of node list's last
  1.5179 +	// member's parent."
  1.5180 +	if (!nodeList[nodeList.length - 1].nextSibling
  1.5181 +	&& nodeList[nodeList.length - 1].parentNode) {
  1.5182 +		removeExtraneousLineBreaksAtTheEndOf(nodeList[nodeList.length - 1].parentNode);
  1.5183 +	}
  1.5184 +}
  1.5185 +
  1.5186 +// "To remove a node node while preserving its descendants, split the parent of
  1.5187 +// node's children if it has any. If it has no children, instead remove it from
  1.5188 +// its parent."
  1.5189 +function removePreservingDescendants(node) {
  1.5190 +	if (node.hasChildNodes()) {
  1.5191 +		splitParent([].slice.call(node.childNodes));
  1.5192 +	} else {
  1.5193 +		node.parentNode.removeChild(node);
  1.5194 +	}
  1.5195 +}
  1.5196 +
  1.5197 +
  1.5198 +//@}
  1.5199 +///// Canonical space sequences /////
  1.5200 +//@{
  1.5201 +
  1.5202 +function canonicalSpaceSequence(n, nonBreakingStart, nonBreakingEnd) {
  1.5203 +	// "If n is zero, return the empty string."
  1.5204 +	if (n == 0) {
  1.5205 +		return "";
  1.5206 +	}
  1.5207 +
  1.5208 +	// "If n is one and both non-breaking start and non-breaking end are false,
  1.5209 +	// return a single space (U+0020)."
  1.5210 +	if (n == 1 && !nonBreakingStart && !nonBreakingEnd) {
  1.5211 +		return " ";
  1.5212 +	}
  1.5213 +
  1.5214 +	// "If n is one, return a single non-breaking space (U+00A0)."
  1.5215 +	if (n == 1) {
  1.5216 +		return "\xa0";
  1.5217 +	}
  1.5218 +
  1.5219 +	// "Let buffer be the empty string."
  1.5220 +	var buffer = "";
  1.5221 +
  1.5222 +	// "If non-breaking start is true, let repeated pair be U+00A0 U+0020.
  1.5223 +	// Otherwise, let it be U+0020 U+00A0."
  1.5224 +	var repeatedPair;
  1.5225 +	if (nonBreakingStart) {
  1.5226 +		repeatedPair = "\xa0 ";
  1.5227 +	} else {
  1.5228 +		repeatedPair = " \xa0";
  1.5229 +	}
  1.5230 +
  1.5231 +	// "While n is greater than three, append repeated pair to buffer and
  1.5232 +	// subtract two from n."
  1.5233 +	while (n > 3) {
  1.5234 +		buffer += repeatedPair;
  1.5235 +		n -= 2;
  1.5236 +	}
  1.5237 +
  1.5238 +	// "If n is three, append a three-element string to buffer depending on
  1.5239 +	// non-breaking start and non-breaking end:"
  1.5240 +	if (n == 3) {
  1.5241 +		buffer +=
  1.5242 +			!nonBreakingStart && !nonBreakingEnd ? " \xa0 "
  1.5243 +			: nonBreakingStart && !nonBreakingEnd ? "\xa0\xa0 "
  1.5244 +			: !nonBreakingStart && nonBreakingEnd ? " \xa0\xa0"
  1.5245 +			: nonBreakingStart && nonBreakingEnd ? "\xa0 \xa0"
  1.5246 +			: "impossible";
  1.5247 +
  1.5248 +	// "Otherwise, append a two-element string to buffer depending on
  1.5249 +	// non-breaking start and non-breaking end:"
  1.5250 +	} else {
  1.5251 +		buffer +=
  1.5252 +			!nonBreakingStart && !nonBreakingEnd ? "\xa0 "
  1.5253 +			: nonBreakingStart && !nonBreakingEnd ? "\xa0 "
  1.5254 +			: !nonBreakingStart && nonBreakingEnd ? " \xa0"
  1.5255 +			: nonBreakingStart && nonBreakingEnd ? "\xa0\xa0"
  1.5256 +			: "impossible";
  1.5257 +	}
  1.5258 +
  1.5259 +	// "Return buffer."
  1.5260 +	return buffer;
  1.5261 +}
  1.5262 +
  1.5263 +function canonicalizeWhitespace(node, offset, fixCollapsedSpace) {
  1.5264 +	if (fixCollapsedSpace === undefined) {
  1.5265 +		// "an optional boolean argument fix collapsed space that defaults to
  1.5266 +		// true"
  1.5267 +		fixCollapsedSpace = true;
  1.5268 +	}
  1.5269 +
  1.5270 +	// "If node is neither editable nor an editing host, abort these steps."
  1.5271 +	if (!isEditable(node) && !isEditingHost(node)) {
  1.5272 +		return;
  1.5273 +	}
  1.5274 +
  1.5275 +	// "Let start node equal node and let start offset equal offset."
  1.5276 +	var startNode = node;
  1.5277 +	var startOffset = offset;
  1.5278 +
  1.5279 +	// "Repeat the following steps:"
  1.5280 +	while (true) {
  1.5281 +		// "If start node has a child in the same editing host with index start
  1.5282 +		// offset minus one, set start node to that child, then set start
  1.5283 +		// offset to start node's length."
  1.5284 +		if (0 <= startOffset - 1
  1.5285 +		&& inSameEditingHost(startNode, startNode.childNodes[startOffset - 1])) {
  1.5286 +			startNode = startNode.childNodes[startOffset - 1];
  1.5287 +			startOffset = getNodeLength(startNode);
  1.5288 +
  1.5289 +		// "Otherwise, if start offset is zero and start node does not follow a
  1.5290 +		// line break and start node's parent is in the same editing host, set
  1.5291 +		// start offset to start node's index, then set start node to its
  1.5292 +		// parent."
  1.5293 +		} else if (startOffset == 0
  1.5294 +		&& !followsLineBreak(startNode)
  1.5295 +		&& inSameEditingHost(startNode, startNode.parentNode)) {
  1.5296 +			startOffset = getNodeIndex(startNode);
  1.5297 +			startNode = startNode.parentNode;
  1.5298 +
  1.5299 +		// "Otherwise, if start node is a Text node and its parent's resolved
  1.5300 +		// value for "white-space" is neither "pre" nor "pre-wrap" and start
  1.5301 +		// offset is not zero and the (start offset − 1)st element of start
  1.5302 +		// node's data is a space (0x0020) or non-breaking space (0x00A0),
  1.5303 +		// subtract one from start offset."
  1.5304 +		} else if (startNode.nodeType == Node.TEXT_NODE
  1.5305 +		&& ["pre", "pre-wrap"].indexOf(getComputedStyle(startNode.parentNode).whiteSpace) == -1
  1.5306 +		&& startOffset != 0
  1.5307 +		&& /[ \xa0]/.test(startNode.data[startOffset - 1])) {
  1.5308 +			startOffset--;
  1.5309 +
  1.5310 +		// "Otherwise, break from this loop."
  1.5311 +		} else {
  1.5312 +			break;
  1.5313 +		}
  1.5314 +	}
  1.5315 +
  1.5316 +	// "Let end node equal start node and end offset equal start offset."
  1.5317 +	var endNode = startNode;
  1.5318 +	var endOffset = startOffset;
  1.5319 +
  1.5320 +	// "Let length equal zero."
  1.5321 +	var length = 0;
  1.5322 +
  1.5323 +	// "Let collapse spaces be true if start offset is zero and start node
  1.5324 +	// follows a line break, otherwise false."
  1.5325 +	var collapseSpaces = startOffset == 0 && followsLineBreak(startNode);
  1.5326 +
  1.5327 +	// "Repeat the following steps:"
  1.5328 +	while (true) {
  1.5329 +		// "If end node has a child in the same editing host with index end
  1.5330 +		// offset, set end node to that child, then set end offset to zero."
  1.5331 +		if (endOffset < endNode.childNodes.length
  1.5332 +		&& inSameEditingHost(endNode, endNode.childNodes[endOffset])) {
  1.5333 +			endNode = endNode.childNodes[endOffset];
  1.5334 +			endOffset = 0;
  1.5335 +
  1.5336 +		// "Otherwise, if end offset is end node's length and end node does not
  1.5337 +		// precede a line break and end node's parent is in the same editing
  1.5338 +		// host, set end offset to one plus end node's index, then set end node
  1.5339 +		// to its parent."
  1.5340 +		} else if (endOffset == getNodeLength(endNode)
  1.5341 +		&& !precedesLineBreak(endNode)
  1.5342 +		&& inSameEditingHost(endNode, endNode.parentNode)) {
  1.5343 +			endOffset = 1 + getNodeIndex(endNode);
  1.5344 +			endNode = endNode.parentNode;
  1.5345 +
  1.5346 +		// "Otherwise, if end node is a Text node and its parent's resolved
  1.5347 +		// value for "white-space" is neither "pre" nor "pre-wrap" and end
  1.5348 +		// offset is not end node's length and the end offsetth element of
  1.5349 +		// end node's data is a space (0x0020) or non-breaking space (0x00A0):"
  1.5350 +		} else if (endNode.nodeType == Node.TEXT_NODE
  1.5351 +		&& ["pre", "pre-wrap"].indexOf(getComputedStyle(endNode.parentNode).whiteSpace) == -1
  1.5352 +		&& endOffset != getNodeLength(endNode)
  1.5353 +		&& /[ \xa0]/.test(endNode.data[endOffset])) {
  1.5354 +			// "If fix collapsed space is true, and collapse spaces is true,
  1.5355 +			// and the end offsetth code unit of end node's data is a space
  1.5356 +			// (0x0020): call deleteData(end offset, 1) on end node, then
  1.5357 +			// continue this loop from the beginning."
  1.5358 +			if (fixCollapsedSpace
  1.5359 +			&& collapseSpaces
  1.5360 +			&& " " == endNode.data[endOffset]) {
  1.5361 +				endNode.deleteData(endOffset, 1);
  1.5362 +				continue;
  1.5363 +			}
  1.5364 +
  1.5365 +			// "Set collapse spaces to true if the end offsetth element of end
  1.5366 +			// node's data is a space (0x0020), false otherwise."
  1.5367 +			collapseSpaces = " " == endNode.data[endOffset];
  1.5368 +
  1.5369 +			// "Add one to end offset."
  1.5370 +			endOffset++;
  1.5371 +
  1.5372 +			// "Add one to length."
  1.5373 +			length++;
  1.5374 +
  1.5375 +		// "Otherwise, break from this loop."
  1.5376 +		} else {
  1.5377 +			break;
  1.5378 +		}
  1.5379 +	}
  1.5380 +
  1.5381 +	// "If fix collapsed space is true, then while (start node, start offset)
  1.5382 +	// is before (end node, end offset):"
  1.5383 +	if (fixCollapsedSpace) {
  1.5384 +		while (getPosition(startNode, startOffset, endNode, endOffset) == "before") {
  1.5385 +			// "If end node has a child in the same editing host with index end
  1.5386 +			// offset − 1, set end node to that child, then set end offset to end
  1.5387 +			// node's length."
  1.5388 +			if (0 <= endOffset - 1
  1.5389 +			&& endOffset - 1 < endNode.childNodes.length
  1.5390 +			&& inSameEditingHost(endNode, endNode.childNodes[endOffset - 1])) {
  1.5391 +				endNode = endNode.childNodes[endOffset - 1];
  1.5392 +				endOffset = getNodeLength(endNode);
  1.5393 +
  1.5394 +			// "Otherwise, if end offset is zero and end node's parent is in the
  1.5395 +			// same editing host, set end offset to end node's index, then set end
  1.5396 +			// node to its parent."
  1.5397 +			} else if (endOffset == 0
  1.5398 +			&& inSameEditingHost(endNode, endNode.parentNode)) {
  1.5399 +				endOffset = getNodeIndex(endNode);
  1.5400 +				endNode = endNode.parentNode;
  1.5401 +
  1.5402 +			// "Otherwise, if end node is a Text node and its parent's resolved
  1.5403 +			// value for "white-space" is neither "pre" nor "pre-wrap" and end
  1.5404 +			// offset is end node's length and the last code unit of end node's
  1.5405 +			// data is a space (0x0020) and end node precedes a line break:"
  1.5406 +			} else if (endNode.nodeType == Node.TEXT_NODE
  1.5407 +			&& ["pre", "pre-wrap"].indexOf(getComputedStyle(endNode.parentNode).whiteSpace) == -1
  1.5408 +			&& endOffset == getNodeLength(endNode)
  1.5409 +			&& endNode.data[endNode.data.length - 1] == " "
  1.5410 +			&& precedesLineBreak(endNode)) {
  1.5411 +				// "Subtract one from end offset."
  1.5412 +				endOffset--;
  1.5413 +
  1.5414 +				// "Subtract one from length."
  1.5415 +				length--;
  1.5416 +
  1.5417 +				// "Call deleteData(end offset, 1) on end node."
  1.5418 +				endNode.deleteData(endOffset, 1);
  1.5419 +
  1.5420 +			// "Otherwise, break from this loop."
  1.5421 +			} else {
  1.5422 +				break;
  1.5423 +			}
  1.5424 +		}
  1.5425 +	}
  1.5426 +
  1.5427 +	// "Let replacement whitespace be the canonical space sequence of length
  1.5428 +	// length. non-breaking start is true if start offset is zero and start
  1.5429 +	// node follows a line break, and false otherwise. non-breaking end is true
  1.5430 +	// if end offset is end node's length and end node precedes a line break,
  1.5431 +	// and false otherwise."
  1.5432 +	var replacementWhitespace = canonicalSpaceSequence(length,
  1.5433 +		startOffset == 0 && followsLineBreak(startNode),
  1.5434 +		endOffset == getNodeLength(endNode) && precedesLineBreak(endNode));
  1.5435 +
  1.5436 +	// "While (start node, start offset) is before (end node, end offset):"
  1.5437 +	while (getPosition(startNode, startOffset, endNode, endOffset) == "before") {
  1.5438 +		// "If start node has a child with index start offset, set start node
  1.5439 +		// to that child, then set start offset to zero."
  1.5440 +		if (startOffset < startNode.childNodes.length) {
  1.5441 +			startNode = startNode.childNodes[startOffset];
  1.5442 +			startOffset = 0;
  1.5443 +
  1.5444 +		// "Otherwise, if start node is not a Text node or if start offset is
  1.5445 +		// start node's length, set start offset to one plus start node's
  1.5446 +		// index, then set start node to its parent."
  1.5447 +		} else if (startNode.nodeType != Node.TEXT_NODE
  1.5448 +		|| startOffset == getNodeLength(startNode)) {
  1.5449 +			startOffset = 1 + getNodeIndex(startNode);
  1.5450 +			startNode = startNode.parentNode;
  1.5451 +
  1.5452 +		// "Otherwise:"
  1.5453 +		} else {
  1.5454 +			// "Remove the first element from replacement whitespace, and let
  1.5455 +			// element be that element."
  1.5456 +			var element = replacementWhitespace[0];
  1.5457 +			replacementWhitespace = replacementWhitespace.slice(1);
  1.5458 +
  1.5459 +			// "If element is not the same as the start offsetth element of
  1.5460 +			// start node's data:"
  1.5461 +			if (element != startNode.data[startOffset]) {
  1.5462 +				// "Call insertData(start offset, element) on start node."
  1.5463 +				startNode.insertData(startOffset, element);
  1.5464 +
  1.5465 +				// "Call deleteData(start offset + 1, 1) on start node."
  1.5466 +				startNode.deleteData(startOffset + 1, 1);
  1.5467 +			}
  1.5468 +
  1.5469 +			// "Add one to start offset."
  1.5470 +			startOffset++;
  1.5471 +		}
  1.5472 +	}
  1.5473 +}
  1.5474 +
  1.5475 +
  1.5476 +//@}
  1.5477 +///// Indenting and outdenting /////
  1.5478 +//@{
  1.5479 +
  1.5480 +function indentNodes(nodeList) {
  1.5481 +	// "If node list is empty, do nothing and abort these steps."
  1.5482 +	if (!nodeList.length) {
  1.5483 +		return;
  1.5484 +	}
  1.5485 +
  1.5486 +	// "Let first node be the first member of node list."
  1.5487 +	var firstNode = nodeList[0];
  1.5488 +
  1.5489 +	// "If first node's parent is an ol or ul:"
  1.5490 +	if (isHtmlElement(firstNode.parentNode, ["OL", "UL"])) {
  1.5491 +		// "Let tag be the local name of the parent of first node."
  1.5492 +		var tag = firstNode.parentNode.tagName;
  1.5493 +
  1.5494 +		// "Wrap node list, with sibling criteria returning true for an HTML
  1.5495 +		// element with local name tag and false otherwise, and new parent
  1.5496 +		// instructions returning the result of calling createElement(tag) on
  1.5497 +		// the ownerDocument of first node."
  1.5498 +		wrap(nodeList,
  1.5499 +			function(node) { return isHtmlElement(node, tag) },
  1.5500 +			function() { return firstNode.ownerDocument.createElement(tag) });
  1.5501 +
  1.5502 +		// "Abort these steps."
  1.5503 +		return;
  1.5504 +	}
  1.5505 +
  1.5506 +	// "Wrap node list, with sibling criteria returning true for a simple
  1.5507 +	// indentation element and false otherwise, and new parent instructions
  1.5508 +	// returning the result of calling createElement("blockquote") on the
  1.5509 +	// ownerDocument of first node. Let new parent be the result."
  1.5510 +	var newParent = wrap(nodeList,
  1.5511 +		function(node) { return isSimpleIndentationElement(node) },
  1.5512 +		function() { return firstNode.ownerDocument.createElement("blockquote") });
  1.5513 +
  1.5514 +	// "Fix disallowed ancestors of new parent."
  1.5515 +	fixDisallowedAncestors(newParent);
  1.5516 +}
  1.5517 +
  1.5518 +function outdentNode(node) {
  1.5519 +	// "If node is not editable, abort these steps."
  1.5520 +	if (!isEditable(node)) {
  1.5521 +		return;
  1.5522 +	}
  1.5523 +
  1.5524 +	// "If node is a simple indentation element, remove node, preserving its
  1.5525 +	// descendants.  Then abort these steps."
  1.5526 +	if (isSimpleIndentationElement(node)) {
  1.5527 +		removePreservingDescendants(node);
  1.5528 +		return;
  1.5529 +	}
  1.5530 +
  1.5531 +	// "If node is an indentation element:"
  1.5532 +	if (isIndentationElement(node)) {
  1.5533 +		// "Unset the dir attribute of node, if any."
  1.5534 +		node.removeAttribute("dir");
  1.5535 +
  1.5536 +		// "Unset the margin, padding, and border CSS properties of node."
  1.5537 +		node.style.margin = "";
  1.5538 +		node.style.padding = "";
  1.5539 +		node.style.border = "";
  1.5540 +		if (node.getAttribute("style") == ""
  1.5541 +		// Crazy WebKit bug: https://bugs.webkit.org/show_bug.cgi?id=68551
  1.5542 +		|| node.getAttribute("style") == "border-width: initial; border-color: initial; ") {
  1.5543 +			node.removeAttribute("style");
  1.5544 +		}
  1.5545 +
  1.5546 +		// "Set the tag name of node to "div"."
  1.5547 +		setTagName(node, "div");
  1.5548 +
  1.5549 +		// "Abort these steps."
  1.5550 +		return;
  1.5551 +	}
  1.5552 +
  1.5553 +	// "Let current ancestor be node's parent."
  1.5554 +	var currentAncestor = node.parentNode;
  1.5555 +
  1.5556 +	// "Let ancestor list be a list of nodes, initially empty."
  1.5557 +	var ancestorList = [];
  1.5558 +
  1.5559 +	// "While current ancestor is an editable Element that is neither a simple
  1.5560 +	// indentation element nor an ol nor a ul, append current ancestor to
  1.5561 +	// ancestor list and then set current ancestor to its parent."
  1.5562 +	while (isEditable(currentAncestor)
  1.5563 +	&& currentAncestor.nodeType == Node.ELEMENT_NODE
  1.5564 +	&& !isSimpleIndentationElement(currentAncestor)
  1.5565 +	&& !isHtmlElement(currentAncestor, ["ol", "ul"])) {
  1.5566 +		ancestorList.push(currentAncestor);
  1.5567 +		currentAncestor = currentAncestor.parentNode;
  1.5568 +	}
  1.5569 +
  1.5570 +	// "If current ancestor is not an editable simple indentation element:"
  1.5571 +	if (!isEditable(currentAncestor)
  1.5572 +	|| !isSimpleIndentationElement(currentAncestor)) {
  1.5573 +		// "Let current ancestor be node's parent."
  1.5574 +		currentAncestor = node.parentNode;
  1.5575 +
  1.5576 +		// "Let ancestor list be the empty list."
  1.5577 +		ancestorList = [];
  1.5578 +
  1.5579 +		// "While current ancestor is an editable Element that is neither an
  1.5580 +		// indentation element nor an ol nor a ul, append current ancestor to
  1.5581 +		// ancestor list and then set current ancestor to its parent."
  1.5582 +		while (isEditable(currentAncestor)
  1.5583 +		&& currentAncestor.nodeType == Node.ELEMENT_NODE
  1.5584 +		&& !isIndentationElement(currentAncestor)
  1.5585 +		&& !isHtmlElement(currentAncestor, ["ol", "ul"])) {
  1.5586 +			ancestorList.push(currentAncestor);
  1.5587 +			currentAncestor = currentAncestor.parentNode;
  1.5588 +		}
  1.5589 +	}
  1.5590 +
  1.5591 +	// "If node is an ol or ul and current ancestor is not an editable
  1.5592 +	// indentation element:"
  1.5593 +	if (isHtmlElement(node, ["OL", "UL"])
  1.5594 +	&& (!isEditable(currentAncestor)
  1.5595 +	|| !isIndentationElement(currentAncestor))) {
  1.5596 +		// "Unset the reversed, start, and type attributes of node, if any are
  1.5597 +		// set."
  1.5598 +		node.removeAttribute("reversed");
  1.5599 +		node.removeAttribute("start");
  1.5600 +		node.removeAttribute("type");
  1.5601 +
  1.5602 +		// "Let children be the children of node."
  1.5603 +		var children = [].slice.call(node.childNodes);
  1.5604 +
  1.5605 +		// "If node has attributes, and its parent is not an ol or ul, set the
  1.5606 +		// tag name of node to "div"."
  1.5607 +		if (node.attributes.length
  1.5608 +		&& !isHtmlElement(node.parentNode, ["OL", "UL"])) {
  1.5609 +			setTagName(node, "div");
  1.5610 +
  1.5611 +		// "Otherwise:"
  1.5612 +		} else {
  1.5613 +			// "Record the values of node's children, and let values be the
  1.5614 +			// result."
  1.5615 +			var values = recordValues([].slice.call(node.childNodes));
  1.5616 +
  1.5617 +			// "Remove node, preserving its descendants."
  1.5618 +			removePreservingDescendants(node);
  1.5619 +
  1.5620 +			// "Restore the values from values."
  1.5621 +			restoreValues(values);
  1.5622 +		}
  1.5623 +
  1.5624 +		// "Fix disallowed ancestors of each member of children."
  1.5625 +		for (var i = 0; i < children.length; i++) {
  1.5626 +			fixDisallowedAncestors(children[i]);
  1.5627 +		}
  1.5628 +
  1.5629 +		// "Abort these steps."
  1.5630 +		return;
  1.5631 +	}
  1.5632 +
  1.5633 +	// "If current ancestor is not an editable indentation element, abort these
  1.5634 +	// steps."
  1.5635 +	if (!isEditable(currentAncestor)
  1.5636 +	|| !isIndentationElement(currentAncestor)) {
  1.5637 +		return;
  1.5638 +	}
  1.5639 +
  1.5640 +	// "Append current ancestor to ancestor list."
  1.5641 +	ancestorList.push(currentAncestor);
  1.5642 +
  1.5643 +	// "Let original ancestor be current ancestor."
  1.5644 +	var originalAncestor = currentAncestor;
  1.5645 +
  1.5646 +	// "While ancestor list is not empty:"
  1.5647 +	while (ancestorList.length) {
  1.5648 +		// "Let current ancestor be the last member of ancestor list."
  1.5649 +		//
  1.5650 +		// "Remove the last member of ancestor list."
  1.5651 +		currentAncestor = ancestorList.pop();
  1.5652 +
  1.5653 +		// "Let target be the child of current ancestor that is equal to either
  1.5654 +		// node or the last member of ancestor list."
  1.5655 +		var target = node.parentNode == currentAncestor
  1.5656 +			? node
  1.5657 +			: ancestorList[ancestorList.length - 1];
  1.5658 +
  1.5659 +		// "If target is an inline node that is not a br, and its nextSibling
  1.5660 +		// is a br, remove target's nextSibling from its parent."
  1.5661 +		if (isInlineNode(target)
  1.5662 +		&& !isHtmlElement(target, "BR")
  1.5663 +		&& isHtmlElement(target.nextSibling, "BR")) {
  1.5664 +			target.parentNode.removeChild(target.nextSibling);
  1.5665 +		}
  1.5666 +
  1.5667 +		// "Let preceding siblings be the preceding siblings of target, and let
  1.5668 +		// following siblings be the following siblings of target."
  1.5669 +		var precedingSiblings = [].slice.call(currentAncestor.childNodes, 0, getNodeIndex(target));
  1.5670 +		var followingSiblings = [].slice.call(currentAncestor.childNodes, 1 + getNodeIndex(target));
  1.5671 +
  1.5672 +		// "Indent preceding siblings."
  1.5673 +		indentNodes(precedingSiblings);
  1.5674 +
  1.5675 +		// "Indent following siblings."
  1.5676 +		indentNodes(followingSiblings);
  1.5677 +	}
  1.5678 +
  1.5679 +	// "Outdent original ancestor."
  1.5680 +	outdentNode(originalAncestor);
  1.5681 +}
  1.5682 +
  1.5683 +
  1.5684 +//@}
  1.5685 +///// Toggling lists /////
  1.5686 +//@{
  1.5687 +
  1.5688 +function toggleLists(tagName) {
  1.5689 +	// "Let mode be "disable" if the selection's list state is tag name, and
  1.5690 +	// "enable" otherwise."
  1.5691 +	var mode = getSelectionListState() == tagName ? "disable" : "enable";
  1.5692 +
  1.5693 +	var range = getActiveRange();
  1.5694 +	tagName = tagName.toUpperCase();
  1.5695 +
  1.5696 +	// "Let other tag name be "ol" if tag name is "ul", and "ul" if tag name is
  1.5697 +	// "ol"."
  1.5698 +	var otherTagName = tagName == "OL" ? "UL" : "OL";
  1.5699 +
  1.5700 +	// "Let items be a list of all lis that are ancestor containers of the
  1.5701 +	// range's start and/or end node."
  1.5702 +	//
  1.5703 +	// It's annoying to get this in tree order using functional stuff without
  1.5704 +	// doing getDescendants(document), which is slow, so I do it imperatively.
  1.5705 +	var items = [];
  1.5706 +	(function(){
  1.5707 +		for (
  1.5708 +			var ancestorContainer = range.endContainer;
  1.5709 +			ancestorContainer != range.commonAncestorContainer;
  1.5710 +			ancestorContainer = ancestorContainer.parentNode
  1.5711 +		) {
  1.5712 +			if (isHtmlElement(ancestorContainer, "li")) {
  1.5713 +				items.unshift(ancestorContainer);
  1.5714 +			}
  1.5715 +		}
  1.5716 +		for (
  1.5717 +			var ancestorContainer = range.startContainer;
  1.5718 +			ancestorContainer;
  1.5719 +			ancestorContainer = ancestorContainer.parentNode
  1.5720 +		) {
  1.5721 +			if (isHtmlElement(ancestorContainer, "li")) {
  1.5722 +				items.unshift(ancestorContainer);
  1.5723 +			}
  1.5724 +		}
  1.5725 +	})();
  1.5726 +
  1.5727 +	// "For each item in items, normalize sublists of item."
  1.5728 +	items.forEach(normalizeSublists);
  1.5729 +
  1.5730 +	// "Block-extend the range, and let new range be the result."
  1.5731 +	var newRange = blockExtend(range);
  1.5732 +
  1.5733 +	// "If mode is "enable", then let lists to convert consist of every
  1.5734 +	// editable HTML element with local name other tag name that is contained
  1.5735 +	// in new range, and for every list in lists to convert:"
  1.5736 +	if (mode == "enable") {
  1.5737 +		getAllContainedNodes(newRange, function(node) {
  1.5738 +			return isEditable(node)
  1.5739 +				&& isHtmlElement(node, otherTagName);
  1.5740 +		}).forEach(function(list) {
  1.5741 +			// "If list's previousSibling or nextSibling is an editable HTML
  1.5742 +			// element with local name tag name:"
  1.5743 +			if ((isEditable(list.previousSibling) && isHtmlElement(list.previousSibling, tagName))
  1.5744 +			|| (isEditable(list.nextSibling) && isHtmlElement(list.nextSibling, tagName))) {
  1.5745 +				// "Let children be list's children."
  1.5746 +				var children = [].slice.call(list.childNodes);
  1.5747 +
  1.5748 +				// "Record the values of children, and let values be the
  1.5749 +				// result."
  1.5750 +				var values = recordValues(children);
  1.5751 +
  1.5752 +				// "Split the parent of children."
  1.5753 +				splitParent(children);
  1.5754 +
  1.5755 +				// "Wrap children, with sibling criteria returning true for an
  1.5756 +				// HTML element with local name tag name and false otherwise."
  1.5757 +				wrap(children, function(node) { return isHtmlElement(node, tagName) });
  1.5758 +
  1.5759 +				// "Restore the values from values."
  1.5760 +				restoreValues(values);
  1.5761 +
  1.5762 +			// "Otherwise, set the tag name of list to tag name."
  1.5763 +			} else {
  1.5764 +				setTagName(list, tagName);
  1.5765 +			}
  1.5766 +		});
  1.5767 +	}
  1.5768 +
  1.5769 +	// "Let node list be a list of nodes, initially empty."
  1.5770 +	//
  1.5771 +	// "For each node node contained in new range, if node is editable; the
  1.5772 +	// last member of node list (if any) is not an ancestor of node; node
  1.5773 +	// is not an indentation element; and either node is an ol or ul, or its
  1.5774 +	// parent is an ol or ul, or it is an allowed child of "li"; then append
  1.5775 +	// node to node list."
  1.5776 +	var nodeList = getContainedNodes(newRange, function(node) {
  1.5777 +		return isEditable(node)
  1.5778 +		&& !isIndentationElement(node)
  1.5779 +		&& (isHtmlElement(node, ["OL", "UL"])
  1.5780 +		|| isHtmlElement(node.parentNode, ["OL", "UL"])
  1.5781 +		|| isAllowedChild(node, "li"));
  1.5782 +	});
  1.5783 +
  1.5784 +	// "If mode is "enable", remove from node list any ol or ul whose parent is
  1.5785 +	// not also an ol or ul."
  1.5786 +	if (mode == "enable") {
  1.5787 +		nodeList = nodeList.filter(function(node) {
  1.5788 +			return !isHtmlElement(node, ["ol", "ul"])
  1.5789 +				|| isHtmlElement(node.parentNode, ["ol", "ul"]);
  1.5790 +		});
  1.5791 +	}
  1.5792 +
  1.5793 +	// "If mode is "disable", then while node list is not empty:"
  1.5794 +	if (mode == "disable") {
  1.5795 +		while (nodeList.length) {
  1.5796 +			// "Let sublist be an empty list of nodes."
  1.5797 +			var sublist = [];
  1.5798 +
  1.5799 +			// "Remove the first member from node list and append it to
  1.5800 +			// sublist."
  1.5801 +			sublist.push(nodeList.shift());
  1.5802 +
  1.5803 +			// "If the first member of sublist is an HTML element with local
  1.5804 +			// name tag name, outdent it and continue this loop from the
  1.5805 +			// beginning."
  1.5806 +			if (isHtmlElement(sublist[0], tagName)) {
  1.5807 +				outdentNode(sublist[0]);
  1.5808 +				continue;
  1.5809 +			}
  1.5810 +
  1.5811 +			// "While node list is not empty, and the first member of node list
  1.5812 +			// is the nextSibling of the last member of sublist and is not an
  1.5813 +			// HTML element with local name tag name, remove the first member
  1.5814 +			// from node list and append it to sublist."
  1.5815 +			while (nodeList.length
  1.5816 +			&& nodeList[0] == sublist[sublist.length - 1].nextSibling
  1.5817 +			&& !isHtmlElement(nodeList[0], tagName)) {
  1.5818 +				sublist.push(nodeList.shift());
  1.5819 +			}
  1.5820 +
  1.5821 +			// "Record the values of sublist, and let values be the result."
  1.5822 +			var values = recordValues(sublist);
  1.5823 +
  1.5824 +			// "Split the parent of sublist."
  1.5825 +			splitParent(sublist);
  1.5826 +
  1.5827 +			// "Fix disallowed ancestors of each member of sublist."
  1.5828 +			for (var i = 0; i < sublist.length; i++) {
  1.5829 +				fixDisallowedAncestors(sublist[i]);
  1.5830 +			}
  1.5831 +
  1.5832 +			// "Restore the values from values."
  1.5833 +			restoreValues(values);
  1.5834 +		}
  1.5835 +
  1.5836 +	// "Otherwise, while node list is not empty:"
  1.5837 +	} else {
  1.5838 +		while (nodeList.length) {
  1.5839 +			// "Let sublist be an empty list of nodes."
  1.5840 +			var sublist = [];
  1.5841 +
  1.5842 +			// "While either sublist is empty, or node list is not empty and
  1.5843 +			// its first member is the nextSibling of sublist's last member:"
  1.5844 +			while (!sublist.length
  1.5845 +			|| (nodeList.length
  1.5846 +			&& nodeList[0] == sublist[sublist.length - 1].nextSibling)) {
  1.5847 +				// "If node list's first member is a p or div, set the tag name
  1.5848 +				// of node list's first member to "li", and append the result
  1.5849 +				// to sublist. Remove the first member from node list."
  1.5850 +				if (isHtmlElement(nodeList[0], ["p", "div"])) {
  1.5851 +					sublist.push(setTagName(nodeList[0], "li"));
  1.5852 +					nodeList.shift();
  1.5853 +
  1.5854 +				// "Otherwise, if the first member of node list is an li or ol
  1.5855 +				// or ul, remove it from node list and append it to sublist."
  1.5856 +				} else if (isHtmlElement(nodeList[0], ["li", "ol", "ul"])) {
  1.5857 +					sublist.push(nodeList.shift());
  1.5858 +
  1.5859 +				// "Otherwise:"
  1.5860 +				} else {
  1.5861 +					// "Let nodes to wrap be a list of nodes, initially empty."
  1.5862 +					var nodesToWrap = [];
  1.5863 +
  1.5864 +					// "While nodes to wrap is empty, or node list is not empty
  1.5865 +					// and its first member is the nextSibling of nodes to
  1.5866 +					// wrap's last member and the first member of node list is
  1.5867 +					// an inline node and the last member of nodes to wrap is
  1.5868 +					// an inline node other than a br, remove the first member
  1.5869 +					// from node list and append it to nodes to wrap."
  1.5870 +					while (!nodesToWrap.length
  1.5871 +					|| (nodeList.length
  1.5872 +					&& nodeList[0] == nodesToWrap[nodesToWrap.length - 1].nextSibling
  1.5873 +					&& isInlineNode(nodeList[0])
  1.5874 +					&& isInlineNode(nodesToWrap[nodesToWrap.length - 1])
  1.5875 +					&& !isHtmlElement(nodesToWrap[nodesToWrap.length - 1], "br"))) {
  1.5876 +						nodesToWrap.push(nodeList.shift());
  1.5877 +					}
  1.5878 +
  1.5879 +					// "Wrap nodes to wrap, with new parent instructions
  1.5880 +					// returning the result of calling createElement("li") on
  1.5881 +					// the context object. Append the result to sublist."
  1.5882 +					sublist.push(wrap(nodesToWrap,
  1.5883 +						undefined,
  1.5884 +						function() { return document.createElement("li") }));
  1.5885 +				}
  1.5886 +			}
  1.5887 +
  1.5888 +			// "If sublist's first member's parent is an HTML element with
  1.5889 +			// local name tag name, or if every member of sublist is an ol or
  1.5890 +			// ul, continue this loop from the beginning."
  1.5891 +			if (isHtmlElement(sublist[0].parentNode, tagName)
  1.5892 +			|| sublist.every(function(node) { return isHtmlElement(node, ["ol", "ul"]) })) {
  1.5893 +				continue;
  1.5894 +			}
  1.5895 +
  1.5896 +			// "If sublist's first member's parent is an HTML element with
  1.5897 +			// local name other tag name:"
  1.5898 +			if (isHtmlElement(sublist[0].parentNode, otherTagName)) {
  1.5899 +				// "Record the values of sublist, and let values be the
  1.5900 +				// result."
  1.5901 +				var values = recordValues(sublist);
  1.5902 +
  1.5903 +				// "Split the parent of sublist."
  1.5904 +				splitParent(sublist);
  1.5905 +
  1.5906 +				// "Wrap sublist, with sibling criteria returning true for an
  1.5907 +				// HTML element with local name tag name and false otherwise,
  1.5908 +				// and new parent instructions returning the result of calling
  1.5909 +				// createElement(tag name) on the context object."
  1.5910 +				wrap(sublist,
  1.5911 +					function(node) { return isHtmlElement(node, tagName) },
  1.5912 +					function() { return document.createElement(tagName) });
  1.5913 +
  1.5914 +				// "Restore the values from values."
  1.5915 +				restoreValues(values);
  1.5916 +
  1.5917 +				// "Continue this loop from the beginning."
  1.5918 +				continue;
  1.5919 +			}
  1.5920 +
  1.5921 +			// "Wrap sublist, with sibling criteria returning true for an HTML
  1.5922 +			// element with local name tag name and false otherwise, and new
  1.5923 +			// parent instructions being the following:"
  1.5924 +			// . . .
  1.5925 +			// "Fix disallowed ancestors of the previous step's result."
  1.5926 +			fixDisallowedAncestors(wrap(sublist,
  1.5927 +				function(node) { return isHtmlElement(node, tagName) },
  1.5928 +				function() {
  1.5929 +					// "If sublist's first member's parent is not an editable
  1.5930 +					// simple indentation element, or sublist's first member's
  1.5931 +					// parent's previousSibling is not an editable HTML element
  1.5932 +					// with local name tag name, call createElement(tag name)
  1.5933 +					// on the context object and return the result."
  1.5934 +					if (!isEditable(sublist[0].parentNode)
  1.5935 +					|| !isSimpleIndentationElement(sublist[0].parentNode)
  1.5936 +					|| !isEditable(sublist[0].parentNode.previousSibling)
  1.5937 +					|| !isHtmlElement(sublist[0].parentNode.previousSibling, tagName)) {
  1.5938 +						return document.createElement(tagName);
  1.5939 +					}
  1.5940 +
  1.5941 +					// "Let list be sublist's first member's parent's
  1.5942 +					// previousSibling."
  1.5943 +					var list = sublist[0].parentNode.previousSibling;
  1.5944 +
  1.5945 +					// "Normalize sublists of list's lastChild."
  1.5946 +					normalizeSublists(list.lastChild);
  1.5947 +
  1.5948 +					// "If list's lastChild is not an editable HTML element
  1.5949 +					// with local name tag name, call createElement(tag name)
  1.5950 +					// on the context object, and append the result as the last
  1.5951 +					// child of list."
  1.5952 +					if (!isEditable(list.lastChild)
  1.5953 +					|| !isHtmlElement(list.lastChild, tagName)) {
  1.5954 +						list.appendChild(document.createElement(tagName));
  1.5955 +					}
  1.5956 +
  1.5957 +					// "Return the last child of list."
  1.5958 +					return list.lastChild;
  1.5959 +				}
  1.5960 +			));
  1.5961 +		}
  1.5962 +	}
  1.5963 +}
  1.5964 +
  1.5965 +
  1.5966 +//@}
  1.5967 +///// Justifying the selection /////
  1.5968 +//@{
  1.5969 +
  1.5970 +function justifySelection(alignment) {
  1.5971 +	// "Block-extend the active range, and let new range be the result."
  1.5972 +	var newRange = blockExtend(globalRange);
  1.5973 +
  1.5974 +	// "Let element list be a list of all editable Elements contained in new
  1.5975 +	// range that either has an attribute in the HTML namespace whose local
  1.5976 +	// name is "align", or has a style attribute that sets "text-align", or is
  1.5977 +	// a center."
  1.5978 +	var elementList = getAllContainedNodes(newRange, function(node) {
  1.5979 +		return node.nodeType == Node.ELEMENT_NODE
  1.5980 +			&& isEditable(node)
  1.5981 +			// Ignoring namespaces here
  1.5982 +			&& (
  1.5983 +				node.hasAttribute("align")
  1.5984 +				|| node.style.textAlign != ""
  1.5985 +				|| isHtmlElement(node, "center")
  1.5986 +			);
  1.5987 +	});
  1.5988 +
  1.5989 +	// "For each element in element list:"
  1.5990 +	for (var i = 0; i < elementList.length; i++) {
  1.5991 +		var element = elementList[i];
  1.5992 +
  1.5993 +		// "If element has an attribute in the HTML namespace whose local name
  1.5994 +		// is "align", remove that attribute."
  1.5995 +		element.removeAttribute("align");
  1.5996 +
  1.5997 +		// "Unset the CSS property "text-align" on element, if it's set by a
  1.5998 +		// style attribute."
  1.5999 +		element.style.textAlign = "";
  1.6000 +		if (element.getAttribute("style") == "") {
  1.6001 +			element.removeAttribute("style");
  1.6002 +		}
  1.6003 +
  1.6004 +		// "If element is a div or span or center with no attributes, remove
  1.6005 +		// it, preserving its descendants."
  1.6006 +		if (isHtmlElement(element, ["div", "span", "center"])
  1.6007 +		&& !element.attributes.length) {
  1.6008 +			removePreservingDescendants(element);
  1.6009 +		}
  1.6010 +
  1.6011 +		// "If element is a center with one or more attributes, set the tag
  1.6012 +		// name of element to "div"."
  1.6013 +		if (isHtmlElement(element, "center")
  1.6014 +		&& element.attributes.length) {
  1.6015 +			setTagName(element, "div");
  1.6016 +		}
  1.6017 +	}
  1.6018 +
  1.6019 +	// "Block-extend the active range, and let new range be the result."
  1.6020 +	newRange = blockExtend(globalRange);
  1.6021 +
  1.6022 +	// "Let node list be a list of nodes, initially empty."
  1.6023 +	var nodeList = [];
  1.6024 +
  1.6025 +	// "For each node node contained in new range, append node to node list if
  1.6026 +	// the last member of node list (if any) is not an ancestor of node; node
  1.6027 +	// is editable; node is an allowed child of "div"; and node's alignment
  1.6028 +	// value is not alignment."
  1.6029 +	nodeList = getContainedNodes(newRange, function(node) {
  1.6030 +		return isEditable(node)
  1.6031 +			&& isAllowedChild(node, "div")
  1.6032 +			&& getAlignmentValue(node) != alignment;
  1.6033 +	});
  1.6034 +
  1.6035 +	// "While node list is not empty:"
  1.6036 +	while (nodeList.length) {
  1.6037 +		// "Let sublist be a list of nodes, initially empty."
  1.6038 +		var sublist = [];
  1.6039 +
  1.6040 +		// "Remove the first member of node list and append it to sublist."
  1.6041 +		sublist.push(nodeList.shift());
  1.6042 +
  1.6043 +		// "While node list is not empty, and the first member of node list is
  1.6044 +		// the nextSibling of the last member of sublist, remove the first
  1.6045 +		// member of node list and append it to sublist."
  1.6046 +		while (nodeList.length
  1.6047 +		&& nodeList[0] == sublist[sublist.length - 1].nextSibling) {
  1.6048 +			sublist.push(nodeList.shift());
  1.6049 +		}
  1.6050 +
  1.6051 +		// "Wrap sublist. Sibling criteria returns true for any div that has
  1.6052 +		// one or both of the following two attributes and no other attributes,
  1.6053 +		// and false otherwise:"
  1.6054 +		//
  1.6055 +		//   * "An align attribute whose value is an ASCII case-insensitive
  1.6056 +		//     match for alignment.
  1.6057 +		//   * "A style attribute which sets exactly one CSS property
  1.6058 +		//     (including unrecognized or invalid attributes), which is
  1.6059 +		//     "text-align", which is set to alignment.
  1.6060 +		//
  1.6061 +		// "New parent instructions are to call createElement("div") on the
  1.6062 +		// context object, then set its CSS property "text-align" to alignment
  1.6063 +		// and return the result."
  1.6064 +		wrap(sublist,
  1.6065 +			function(node) {
  1.6066 +				return isHtmlElement(node, "div")
  1.6067 +					&& [].every.call(node.attributes, function(attr) {
  1.6068 +						return (attr.name == "align" && attr.value.toLowerCase() == alignment)
  1.6069 +							|| (attr.name == "style" && node.style.length == 1 && node.style.textAlign == alignment);
  1.6070 +					});
  1.6071 +			},
  1.6072 +			function() {
  1.6073 +				var newParent = document.createElement("div");
  1.6074 +				newParent.setAttribute("style", "text-align: " + alignment);
  1.6075 +				return newParent;
  1.6076 +			}
  1.6077 +		);
  1.6078 +	}
  1.6079 +}
  1.6080 +
  1.6081 +
  1.6082 +//@}
  1.6083 +///// Automatic linking /////
  1.6084 +//@{
  1.6085 +// "An autolinkable URL is a string of the following form:"
  1.6086 +var autolinkableUrlRegexp =
  1.6087 +	// "Either a string matching the scheme pattern from RFC 3986 section 3.1
  1.6088 +	// followed by the literal string ://, or the literal string mailto:;
  1.6089 +	// followed by"
  1.6090 +	//
  1.6091 +	// From the RFC: scheme      = ALPHA *( ALPHA / DIGIT / "+" / "-" / "." )
  1.6092 +	"([a-zA-Z][a-zA-Z0-9+.-]*://|mailto:)"
  1.6093 +	// "Zero or more characters other than space characters; followed by"
  1.6094 +	+ "[^ \t\n\f\r]*"
  1.6095 +	// "A character that is not one of the ASCII characters !"'(),-.:;<>[]`{}."
  1.6096 +	+ "[^!\"'(),\\-.:;<>[\\]`{}]";
  1.6097 +
  1.6098 +// "A valid e-mail address is a string that matches the ABNF production 1*(
  1.6099 +// atext / "." ) "@" ldh-str *( "." ldh-str ) where atext is defined in RFC
  1.6100 +// 5322 section 3.2.3, and ldh-str is defined in RFC 1034 section 3.5."
  1.6101 +//
  1.6102 +// atext: ALPHA / DIGIT / "!" / "#" / "$" / "%" / "&" / "'" / "*" / "+" / "-" /
  1.6103 +// "/" / "=" / "?" / "^" / "_" / "`" / "{" / "|" / "}" / "~"
  1.6104 +//
  1.6105 +//<ldh-str> ::= <let-dig-hyp> | <let-dig-hyp> <ldh-str>
  1.6106 +//<let-dig-hyp> ::= <let-dig> | "-"
  1.6107 +//<let-dig> ::= <letter> | <digit>
  1.6108 +var validEmailRegexp =
  1.6109 +	"[a-zA-Z0-9!#$%&'*+\\-/=?^_`{|}~.]+@[a-zA-Z0-9-]+(\.[a-zA-Z0-9-]+)*";
  1.6110 +
  1.6111 +function autolink(node, endOffset) {
  1.6112 +	// "While (node, end offset)'s previous equivalent point is not null, set
  1.6113 +	// it to its previous equivalent point."
  1.6114 +	while (getPreviousEquivalentPoint(node, endOffset)) {
  1.6115 +		var prev = getPreviousEquivalentPoint(node, endOffset);
  1.6116 +		node = prev[0];
  1.6117 +		endOffset = prev[1];
  1.6118 +	}
  1.6119 +
  1.6120 +	// "If node is not a Text node, or has an a ancestor, do nothing and abort
  1.6121 +	// these steps."
  1.6122 +	if (node.nodeType != Node.TEXT_NODE
  1.6123 +	|| getAncestors(node).some(function(ancestor) { return isHtmlElement(ancestor, "a") })) {
  1.6124 +		return;
  1.6125 +	}
  1.6126 +
  1.6127 +	// "Let search be the largest substring of node's data whose end is end
  1.6128 +	// offset and that contains no space characters."
  1.6129 +	var search = /[^ \t\n\f\r]*$/.exec(node.substringData(0, endOffset))[0];
  1.6130 +
  1.6131 +	// "If some substring of search is an autolinkable URL:"
  1.6132 +	if (new RegExp(autolinkableUrlRegexp).test(search)) {
  1.6133 +		// "While there is no substring of node's data ending at end offset
  1.6134 +		// that is an autolinkable URL, decrement end offset."
  1.6135 +		while (!(new RegExp(autolinkableUrlRegexp + "$").test(node.substringData(0, endOffset)))) {
  1.6136 +			endOffset--;
  1.6137 +		}
  1.6138 +
  1.6139 +		// "Let start offset be the start index of the longest substring of
  1.6140 +		// node's data that is an autolinkable URL ending at end offset."
  1.6141 +		var startOffset = new RegExp(autolinkableUrlRegexp + "$").exec(node.substringData(0, endOffset)).index;
  1.6142 +
  1.6143 +		// "Let href be the substring of node's data starting at start offset
  1.6144 +		// and ending at end offset."
  1.6145 +		var href = node.substringData(startOffset, endOffset - startOffset);
  1.6146 +
  1.6147 +	// "Otherwise, if some substring of search is a valid e-mail address:"
  1.6148 +	} else if (new RegExp(validEmailRegexp).test(search)) {
  1.6149 +		// "While there is no substring of node's data ending at end offset
  1.6150 +		// that is a valid e-mail address, decrement end offset."
  1.6151 +		while (!(new RegExp(validEmailRegexp + "$").test(node.substringData(0, endOffset)))) {
  1.6152 +			endOffset--;
  1.6153 +		}
  1.6154 +
  1.6155 +		// "Let start offset be the start index of the longest substring of
  1.6156 +		// node's data that is a valid e-mail address ending at end offset."
  1.6157 +		var startOffset = new RegExp(validEmailRegexp + "$").exec(node.substringData(0, endOffset)).index;
  1.6158 +
  1.6159 +		// "Let href be "mailto:" concatenated with the substring of node's
  1.6160 +		// data starting at start offset and ending at end offset."
  1.6161 +		var href = "mailto:" + node.substringData(startOffset, endOffset - startOffset);
  1.6162 +
  1.6163 +	// "Otherwise, do nothing and abort these steps."
  1.6164 +	} else {
  1.6165 +		return;
  1.6166 +	}
  1.6167 +
  1.6168 +	// "Let original range be the active range."
  1.6169 +	var originalRange = getActiveRange();
  1.6170 +
  1.6171 +	// "Create a new range with start (node, start offset) and end (node, end
  1.6172 +	// offset), and set the context object's selection's range to it."
  1.6173 +	var newRange = document.createRange();
  1.6174 +	newRange.setStart(node, startOffset);
  1.6175 +	newRange.setEnd(node, endOffset);
  1.6176 +	getSelection().removeAllRanges();
  1.6177 +	getSelection().addRange(newRange);
  1.6178 +	globalRange = newRange;
  1.6179 +
  1.6180 +	// "Take the action for "createLink", with value equal to href."
  1.6181 +	commands.createlink.action(href);
  1.6182 +
  1.6183 +	// "Set the context object's selection's range to original range."
  1.6184 +	getSelection().removeAllRanges();
  1.6185 +	getSelection().addRange(originalRange);
  1.6186 +	globalRange = originalRange;
  1.6187 +}
  1.6188 +//@}
  1.6189 +///// The delete command /////
  1.6190 +//@{
  1.6191 +commands["delete"] = {
  1.6192 +	preservesOverrides: true,
  1.6193 +	action: function() {
  1.6194 +		// "If the active range is not collapsed, delete the selection and
  1.6195 +		// return true."
  1.6196 +		if (!getActiveRange().collapsed) {
  1.6197 +			deleteSelection();
  1.6198 +			return true;
  1.6199 +		}
  1.6200 +
  1.6201 +		// "Canonicalize whitespace at the active range's start."
  1.6202 +		canonicalizeWhitespace(getActiveRange().startContainer, getActiveRange().startOffset);
  1.6203 +
  1.6204 +		// "Let node and offset be the active range's start node and offset."
  1.6205 +		var node = getActiveRange().startContainer;
  1.6206 +		var offset = getActiveRange().startOffset;
  1.6207 +
  1.6208 +		// "Repeat the following steps:"
  1.6209 +		while (true) {
  1.6210 +			// "If offset is zero and node's previousSibling is an editable
  1.6211 +			// invisible node, remove node's previousSibling from its parent."
  1.6212 +			if (offset == 0
  1.6213 +			&& isEditable(node.previousSibling)
  1.6214 +			&& isInvisible(node.previousSibling)) {
  1.6215 +				node.parentNode.removeChild(node.previousSibling);
  1.6216 +
  1.6217 +			// "Otherwise, if node has a child with index offset − 1 and that
  1.6218 +			// child is an editable invisible node, remove that child from
  1.6219 +			// node, then subtract one from offset."
  1.6220 +			} else if (0 <= offset - 1
  1.6221 +			&& offset - 1 < node.childNodes.length
  1.6222 +			&& isEditable(node.childNodes[offset - 1])
  1.6223 +			&& isInvisible(node.childNodes[offset - 1])) {
  1.6224 +				node.removeChild(node.childNodes[offset - 1]);
  1.6225 +				offset--;
  1.6226 +
  1.6227 +			// "Otherwise, if offset is zero and node is an inline node, or if
  1.6228 +			// node is an invisible node, set offset to the index of node, then
  1.6229 +			// set node to its parent."
  1.6230 +			} else if ((offset == 0
  1.6231 +			&& isInlineNode(node))
  1.6232 +			|| isInvisible(node)) {
  1.6233 +				offset = getNodeIndex(node);
  1.6234 +				node = node.parentNode;
  1.6235 +
  1.6236 +			// "Otherwise, if node has a child with index offset − 1 and that
  1.6237 +			// child is an editable a, remove that child from node, preserving
  1.6238 +			// its descendants. Then return true."
  1.6239 +			} else if (0 <= offset - 1
  1.6240 +			&& offset - 1 < node.childNodes.length
  1.6241 +			&& isEditable(node.childNodes[offset - 1])
  1.6242 +			&& isHtmlElement(node.childNodes[offset - 1], "a")) {
  1.6243 +				removePreservingDescendants(node.childNodes[offset - 1]);
  1.6244 +				return true;
  1.6245 +
  1.6246 +			// "Otherwise, if node has a child with index offset − 1 and that
  1.6247 +			// child is not a block node or a br or an img, set node to that
  1.6248 +			// child, then set offset to the length of node."
  1.6249 +			} else if (0 <= offset - 1
  1.6250 +			&& offset - 1 < node.childNodes.length
  1.6251 +			&& !isBlockNode(node.childNodes[offset - 1])
  1.6252 +			&& !isHtmlElement(node.childNodes[offset - 1], ["br", "img"])) {
  1.6253 +				node = node.childNodes[offset - 1];
  1.6254 +				offset = getNodeLength(node);
  1.6255 +
  1.6256 +			// "Otherwise, break from this loop."
  1.6257 +			} else {
  1.6258 +				break;
  1.6259 +			}
  1.6260 +		}
  1.6261 +
  1.6262 +		// "If node is a Text node and offset is not zero, or if node is a
  1.6263 +		// block node that has a child with index offset − 1 and that child is
  1.6264 +		// a br or hr or img:"
  1.6265 +		if ((node.nodeType == Node.TEXT_NODE
  1.6266 +		&& offset != 0)
  1.6267 +		|| (isBlockNode(node)
  1.6268 +		&& 0 <= offset - 1
  1.6269 +		&& offset - 1 < node.childNodes.length
  1.6270 +		&& isHtmlElement(node.childNodes[offset - 1], ["br", "hr", "img"]))) {
  1.6271 +			// "Call collapse(node, offset) on the context object's Selection."
  1.6272 +			getSelection().collapse(node, offset);
  1.6273 +			getActiveRange().setEnd(node, offset);
  1.6274 +
  1.6275 +			// "Call extend(node, offset − 1) on the context object's
  1.6276 +			// Selection."
  1.6277 +			getSelection().extend(node, offset - 1);
  1.6278 +			getActiveRange().setStart(node, offset - 1);
  1.6279 +
  1.6280 +			// "Delete the selection."
  1.6281 +			deleteSelection();
  1.6282 +
  1.6283 +			// "Return true."
  1.6284 +			return true;
  1.6285 +		}
  1.6286 +
  1.6287 +		// "If node is an inline node, return true."
  1.6288 +		if (isInlineNode(node)) {
  1.6289 +			return true;
  1.6290 +		}
  1.6291 +
  1.6292 +		// "If node is an li or dt or dd and is the first child of its parent,
  1.6293 +		// and offset is zero:"
  1.6294 +		if (isHtmlElement(node, ["li", "dt", "dd"])
  1.6295 +		&& node == node.parentNode.firstChild
  1.6296 +		&& offset == 0) {
  1.6297 +			// "Let items be a list of all lis that are ancestors of node."
  1.6298 +			//
  1.6299 +			// Remember, must be in tree order.
  1.6300 +			var items = [];
  1.6301 +			for (var ancestor = node.parentNode; ancestor; ancestor = ancestor.parentNode) {
  1.6302 +				if (isHtmlElement(ancestor, "li")) {
  1.6303 +					items.unshift(ancestor);
  1.6304 +				}
  1.6305 +			}
  1.6306 +
  1.6307 +			// "Normalize sublists of each item in items."
  1.6308 +			for (var i = 0; i < items.length; i++) {
  1.6309 +				normalizeSublists(items[i]);
  1.6310 +			}
  1.6311 +
  1.6312 +			// "Record the values of the one-node list consisting of node, and
  1.6313 +			// let values be the result."
  1.6314 +			var values = recordValues([node]);
  1.6315 +
  1.6316 +			// "Split the parent of the one-node list consisting of node."
  1.6317 +			splitParent([node]);
  1.6318 +
  1.6319 +			// "Restore the values from values."
  1.6320 +			restoreValues(values);
  1.6321 +
  1.6322 +			// "If node is a dd or dt, and it is not an allowed child of any of
  1.6323 +			// its ancestors in the same editing host, set the tag name of node
  1.6324 +			// to the default single-line container name and let node be the
  1.6325 +			// result."
  1.6326 +			if (isHtmlElement(node, ["dd", "dt"])
  1.6327 +			&& getAncestors(node).every(function(ancestor) {
  1.6328 +				return !inSameEditingHost(node, ancestor)
  1.6329 +					|| !isAllowedChild(node, ancestor)
  1.6330 +			})) {
  1.6331 +				node = setTagName(node, defaultSingleLineContainerName);
  1.6332 +			}
  1.6333 +
  1.6334 +			// "Fix disallowed ancestors of node."
  1.6335 +			fixDisallowedAncestors(node);
  1.6336 +
  1.6337 +			// "Return true."
  1.6338 +			return true;
  1.6339 +		}
  1.6340 +
  1.6341 +		// "Let start node equal node and let start offset equal offset."
  1.6342 +		var startNode = node;
  1.6343 +		var startOffset = offset;
  1.6344 +
  1.6345 +		// "Repeat the following steps:"
  1.6346 +		while (true) {
  1.6347 +			// "If start offset is zero, set start offset to the index of start
  1.6348 +			// node and then set start node to its parent."
  1.6349 +			if (startOffset == 0) {
  1.6350 +				startOffset = getNodeIndex(startNode);
  1.6351 +				startNode = startNode.parentNode;
  1.6352 +
  1.6353 +			// "Otherwise, if start node has an editable invisible child with
  1.6354 +			// index start offset minus one, remove it from start node and
  1.6355 +			// subtract one from start offset."
  1.6356 +			} else if (0 <= startOffset - 1
  1.6357 +			&& startOffset - 1 < startNode.childNodes.length
  1.6358 +			&& isEditable(startNode.childNodes[startOffset - 1])
  1.6359 +			&& isInvisible(startNode.childNodes[startOffset - 1])) {
  1.6360 +				startNode.removeChild(startNode.childNodes[startOffset - 1]);
  1.6361 +				startOffset--;
  1.6362 +
  1.6363 +			// "Otherwise, break from this loop."
  1.6364 +			} else {
  1.6365 +				break;
  1.6366 +			}
  1.6367 +		}
  1.6368 +
  1.6369 +		// "If offset is zero, and node has an editable ancestor container in
  1.6370 +		// the same editing host that's an indentation element:"
  1.6371 +		if (offset == 0
  1.6372 +		&& getAncestors(node).concat(node).filter(function(ancestor) {
  1.6373 +			return isEditable(ancestor)
  1.6374 +				&& inSameEditingHost(ancestor, node)
  1.6375 +				&& isIndentationElement(ancestor);
  1.6376 +		}).length) {
  1.6377 +			// "Block-extend the range whose start and end are both (node, 0),
  1.6378 +			// and let new range be the result."
  1.6379 +			var newRange = document.createRange();
  1.6380 +			newRange.setStart(node, 0);
  1.6381 +			newRange = blockExtend(newRange);
  1.6382 +
  1.6383 +			// "Let node list be a list of nodes, initially empty."
  1.6384 +			//
  1.6385 +			// "For each node current node contained in new range, append
  1.6386 +			// current node to node list if the last member of node list (if
  1.6387 +			// any) is not an ancestor of current node, and current node is
  1.6388 +			// editable but has no editable descendants."
  1.6389 +			var nodeList = getContainedNodes(newRange, function(currentNode) {
  1.6390 +				return isEditable(currentNode)
  1.6391 +					&& !hasEditableDescendants(currentNode);
  1.6392 +			});
  1.6393 +
  1.6394 +			// "Outdent each node in node list."
  1.6395 +			for (var i = 0; i < nodeList.length; i++) {
  1.6396 +				outdentNode(nodeList[i]);
  1.6397 +			}
  1.6398 +
  1.6399 +			// "Return true."
  1.6400 +			return true;
  1.6401 +		}
  1.6402 +
  1.6403 +		// "If the child of start node with index start offset is a table,
  1.6404 +		// return true."
  1.6405 +		if (isHtmlElement(startNode.childNodes[startOffset], "table")) {
  1.6406 +			return true;
  1.6407 +		}
  1.6408 +
  1.6409 +		// "If start node has a child with index start offset − 1, and that
  1.6410 +		// child is a table:"
  1.6411 +		if (0 <= startOffset - 1
  1.6412 +		&& startOffset - 1 < startNode.childNodes.length
  1.6413 +		&& isHtmlElement(startNode.childNodes[startOffset - 1], "table")) {
  1.6414 +			// "Call collapse(start node, start offset − 1) on the context
  1.6415 +			// object's Selection."
  1.6416 +			getSelection().collapse(startNode, startOffset - 1);
  1.6417 +			getActiveRange().setStart(startNode, startOffset - 1);
  1.6418 +
  1.6419 +			// "Call extend(start node, start offset) on the context object's
  1.6420 +			// Selection."
  1.6421 +			getSelection().extend(startNode, startOffset);
  1.6422 +			getActiveRange().setEnd(startNode, startOffset);
  1.6423 +
  1.6424 +			// "Return true."
  1.6425 +			return true;
  1.6426 +		}
  1.6427 +
  1.6428 +		// "If offset is zero; and either the child of start node with index
  1.6429 +		// start offset minus one is an hr, or the child is a br whose
  1.6430 +		// previousSibling is either a br or not an inline node:"
  1.6431 +		if (offset == 0
  1.6432 +		&& (isHtmlElement(startNode.childNodes[startOffset - 1], "hr")
  1.6433 +			|| (
  1.6434 +				isHtmlElement(startNode.childNodes[startOffset - 1], "br")
  1.6435 +				&& (
  1.6436 +					isHtmlElement(startNode.childNodes[startOffset - 1].previousSibling, "br")
  1.6437 +					|| !isInlineNode(startNode.childNodes[startOffset - 1].previousSibling)
  1.6438 +				)
  1.6439 +			)
  1.6440 +		)) {
  1.6441 +			// "Call collapse(start node, start offset − 1) on the context
  1.6442 +			// object's Selection."
  1.6443 +			getSelection().collapse(startNode, startOffset - 1);
  1.6444 +			getActiveRange().setStart(startNode, startOffset - 1);
  1.6445 +
  1.6446 +			// "Call extend(start node, start offset) on the context object's
  1.6447 +			// Selection."
  1.6448 +			getSelection().extend(startNode, startOffset);
  1.6449 +			getActiveRange().setEnd(startNode, startOffset);
  1.6450 +
  1.6451 +			// "Delete the selection."
  1.6452 +			deleteSelection();
  1.6453 +
  1.6454 +			// "Call collapse(node, offset) on the Selection."
  1.6455 +			getSelection().collapse(node, offset);
  1.6456 +			getActiveRange().setStart(node, offset);
  1.6457 +			getActiveRange().collapse(true);
  1.6458 +
  1.6459 +			// "Return true."
  1.6460 +			return true;
  1.6461 +		}
  1.6462 +
  1.6463 +		// "If the child of start node with index start offset is an li or dt
  1.6464 +		// or dd, and that child's firstChild is an inline node, and start
  1.6465 +		// offset is not zero:"
  1.6466 +		if (isHtmlElement(startNode.childNodes[startOffset], ["li", "dt", "dd"])
  1.6467 +		&& isInlineNode(startNode.childNodes[startOffset].firstChild)
  1.6468 +		&& startOffset != 0) {
  1.6469 +			// "Let previous item be the child of start node with index start
  1.6470 +			// offset minus one."
  1.6471 +			var previousItem = startNode.childNodes[startOffset - 1];
  1.6472 +
  1.6473 +			// "If previous item's lastChild is an inline node other than a br,
  1.6474 +			// call createElement("br") on the context object and append the
  1.6475 +			// result as the last child of previous item."
  1.6476 +			if (isInlineNode(previousItem.lastChild)
  1.6477 +			&& !isHtmlElement(previousItem.lastChild, "br")) {
  1.6478 +				previousItem.appendChild(document.createElement("br"));
  1.6479 +			}
  1.6480 +
  1.6481 +			// "If previous item's lastChild is an inline node, call
  1.6482 +			// createElement("br") on the context object and append the result
  1.6483 +			// as the last child of previous item."
  1.6484 +			if (isInlineNode(previousItem.lastChild)) {
  1.6485 +				previousItem.appendChild(document.createElement("br"));
  1.6486 +			}
  1.6487 +		}
  1.6488 +
  1.6489 +		// "If start node's child with index start offset is an li or dt or dd,
  1.6490 +		// and that child's previousSibling is also an li or dt or dd:"
  1.6491 +		if (isHtmlElement(startNode.childNodes[startOffset], ["li", "dt", "dd"])
  1.6492 +		&& isHtmlElement(startNode.childNodes[startOffset].previousSibling, ["li", "dt", "dd"])) {
  1.6493 +			// "Call cloneRange() on the active range, and let original range
  1.6494 +			// be the result."
  1.6495 +			//
  1.6496 +			// We need to add it to extraRanges so it will actually get updated
  1.6497 +			// when moving preserving ranges.
  1.6498 +			var originalRange = getActiveRange().cloneRange();
  1.6499 +			extraRanges.push(originalRange);
  1.6500 +
  1.6501 +			// "Set start node to its child with index start offset − 1."
  1.6502 +			startNode = startNode.childNodes[startOffset - 1];
  1.6503 +
  1.6504 +			// "Set start offset to start node's length."
  1.6505 +			startOffset = getNodeLength(startNode);
  1.6506 +
  1.6507 +			// "Set node to start node's nextSibling."
  1.6508 +			node = startNode.nextSibling;
  1.6509 +
  1.6510 +			// "Call collapse(start node, start offset) on the context object's
  1.6511 +			// Selection."
  1.6512 +			getSelection().collapse(startNode, startOffset);
  1.6513 +			getActiveRange().setStart(startNode, startOffset);
  1.6514 +
  1.6515 +			// "Call extend(node, 0) on the context object's Selection."
  1.6516 +			getSelection().extend(node, 0);
  1.6517 +			getActiveRange().setEnd(node, 0);
  1.6518 +
  1.6519 +			// "Delete the selection."
  1.6520 +			deleteSelection();
  1.6521 +
  1.6522 +			// "Call removeAllRanges() on the context object's Selection."
  1.6523 +			getSelection().removeAllRanges();
  1.6524 +
  1.6525 +			// "Call addRange(original range) on the context object's
  1.6526 +			// Selection."
  1.6527 +			getSelection().addRange(originalRange);
  1.6528 +			getActiveRange().setStart(originalRange.startContainer, originalRange.startOffset);
  1.6529 +			getActiveRange().setEnd(originalRange.endContainer, originalRange.endOffset);
  1.6530 +
  1.6531 +			// "Return true."
  1.6532 +			extraRanges.pop();
  1.6533 +			return true;
  1.6534 +		}
  1.6535 +
  1.6536 +		// "While start node has a child with index start offset minus one:"
  1.6537 +		while (0 <= startOffset - 1
  1.6538 +		&& startOffset - 1 < startNode.childNodes.length) {
  1.6539 +			// "If start node's child with index start offset minus one is
  1.6540 +			// editable and invisible, remove it from start node, then subtract
  1.6541 +			// one from start offset."
  1.6542 +			if (isEditable(startNode.childNodes[startOffset - 1])
  1.6543 +			&& isInvisible(startNode.childNodes[startOffset - 1])) {
  1.6544 +				startNode.removeChild(startNode.childNodes[startOffset - 1]);
  1.6545 +				startOffset--;
  1.6546 +
  1.6547 +			// "Otherwise, set start node to its child with index start offset
  1.6548 +			// minus one, then set start offset to the length of start node."
  1.6549 +			} else {
  1.6550 +				startNode = startNode.childNodes[startOffset - 1];
  1.6551 +				startOffset = getNodeLength(startNode);
  1.6552 +			}
  1.6553 +		}
  1.6554 +
  1.6555 +		// "Call collapse(start node, start offset) on the context object's
  1.6556 +		// Selection."
  1.6557 +		getSelection().collapse(startNode, startOffset);
  1.6558 +		getActiveRange().setStart(startNode, startOffset);
  1.6559 +
  1.6560 +		// "Call extend(node, offset) on the context object's Selection."
  1.6561 +		getSelection().extend(node, offset);
  1.6562 +		getActiveRange().setEnd(node, offset);
  1.6563 +
  1.6564 +		// "Delete the selection, with direction "backward"."
  1.6565 +		deleteSelection({direction: "backward"});
  1.6566 +
  1.6567 +		// "Return true."
  1.6568 +		return true;
  1.6569 +	}
  1.6570 +};
  1.6571 +
  1.6572 +//@}
  1.6573 +///// The formatBlock command /////
  1.6574 +//@{
  1.6575 +// "A formattable block name is "address", "dd", "div", "dt", "h1", "h2", "h3",
  1.6576 +// "h4", "h5", "h6", "p", or "pre"."
  1.6577 +var formattableBlockNames = ["address", "dd", "div", "dt", "h1", "h2", "h3",
  1.6578 +	"h4", "h5", "h6", "p", "pre"];
  1.6579 +
  1.6580 +commands.formatblock = {
  1.6581 +	preservesOverrides: true,
  1.6582 +	action: function(value) {
  1.6583 +		// "If value begins with a "<" character and ends with a ">" character,
  1.6584 +		// remove the first and last characters from it."
  1.6585 +		if (/^<.*>$/.test(value)) {
  1.6586 +			value = value.slice(1, -1);
  1.6587 +		}
  1.6588 +
  1.6589 +		// "Let value be converted to ASCII lowercase."
  1.6590 +		value = value.toLowerCase();
  1.6591 +
  1.6592 +		// "If value is not a formattable block name, return false."
  1.6593 +		if (formattableBlockNames.indexOf(value) == -1) {
  1.6594 +			return false;
  1.6595 +		}
  1.6596 +
  1.6597 +		// "Block-extend the active range, and let new range be the result."
  1.6598 +		var newRange = blockExtend(getActiveRange());
  1.6599 +
  1.6600 +		// "Let node list be an empty list of nodes."
  1.6601 +		//
  1.6602 +		// "For each node node contained in new range, append node to node list
  1.6603 +		// if it is editable, the last member of original node list (if any) is
  1.6604 +		// not an ancestor of node, node is either a non-list single-line
  1.6605 +		// container or an allowed child of "p" or a dd or dt, and node is not
  1.6606 +		// the ancestor of a prohibited paragraph child."
  1.6607 +		var nodeList = getContainedNodes(newRange, function(node) {
  1.6608 +			return isEditable(node)
  1.6609 +				&& (isNonListSingleLineContainer(node)
  1.6610 +				|| isAllowedChild(node, "p")
  1.6611 +				|| isHtmlElement(node, ["dd", "dt"]))
  1.6612 +				&& !getDescendants(node).some(isProhibitedParagraphChild);
  1.6613 +		});
  1.6614 +
  1.6615 +		// "Record the values of node list, and let values be the result."
  1.6616 +		var values = recordValues(nodeList);
  1.6617 +
  1.6618 +		// "For each node in node list, while node is the descendant of an
  1.6619 +		// editable HTML element in the same editing host, whose local name is
  1.6620 +		// a formattable block name, and which is not the ancestor of a
  1.6621 +		// prohibited paragraph child, split the parent of the one-node list
  1.6622 +		// consisting of node."
  1.6623 +		for (var i = 0; i < nodeList.length; i++) {
  1.6624 +			var node = nodeList[i];
  1.6625 +			while (getAncestors(node).some(function(ancestor) {
  1.6626 +				return isEditable(ancestor)
  1.6627 +					&& inSameEditingHost(ancestor, node)
  1.6628 +					&& isHtmlElement(ancestor, formattableBlockNames)
  1.6629 +					&& !getDescendants(ancestor).some(isProhibitedParagraphChild);
  1.6630 +			})) {
  1.6631 +				splitParent([node]);
  1.6632 +			}
  1.6633 +		}
  1.6634 +
  1.6635 +		// "Restore the values from values."
  1.6636 +		restoreValues(values);
  1.6637 +
  1.6638 +		// "While node list is not empty:"
  1.6639 +		while (nodeList.length) {
  1.6640 +			var sublist;
  1.6641 +
  1.6642 +			// "If the first member of node list is a single-line
  1.6643 +			// container:"
  1.6644 +			if (isSingleLineContainer(nodeList[0])) {
  1.6645 +				// "Let sublist be the children of the first member of node
  1.6646 +				// list."
  1.6647 +				sublist = [].slice.call(nodeList[0].childNodes);
  1.6648 +
  1.6649 +				// "Record the values of sublist, and let values be the
  1.6650 +				// result."
  1.6651 +				var values = recordValues(sublist);
  1.6652 +
  1.6653 +				// "Remove the first member of node list from its parent,
  1.6654 +				// preserving its descendants."
  1.6655 +				removePreservingDescendants(nodeList[0]);
  1.6656 +
  1.6657 +				// "Restore the values from values."
  1.6658 +				restoreValues(values);
  1.6659 +
  1.6660 +				// "Remove the first member from node list."
  1.6661 +				nodeList.shift();
  1.6662 +
  1.6663 +			// "Otherwise:"
  1.6664 +			} else {
  1.6665 +				// "Let sublist be an empty list of nodes."
  1.6666 +				sublist = [];
  1.6667 +
  1.6668 +				// "Remove the first member of node list and append it to
  1.6669 +				// sublist."
  1.6670 +				sublist.push(nodeList.shift());
  1.6671 +
  1.6672 +				// "While node list is not empty, and the first member of
  1.6673 +				// node list is the nextSibling of the last member of
  1.6674 +				// sublist, and the first member of node list is not a
  1.6675 +				// single-line container, and the last member of sublist is
  1.6676 +				// not a br, remove the first member of node list and
  1.6677 +				// append it to sublist."
  1.6678 +				while (nodeList.length
  1.6679 +				&& nodeList[0] == sublist[sublist.length - 1].nextSibling
  1.6680 +				&& !isSingleLineContainer(nodeList[0])
  1.6681 +				&& !isHtmlElement(sublist[sublist.length - 1], "BR")) {
  1.6682 +					sublist.push(nodeList.shift());
  1.6683 +				}
  1.6684 +			}
  1.6685 +
  1.6686 +			// "Wrap sublist. If value is "div" or "p", sibling criteria
  1.6687 +			// returns false; otherwise it returns true for an HTML element
  1.6688 +			// with local name value and no attributes, and false otherwise.
  1.6689 +			// New parent instructions return the result of running
  1.6690 +			// createElement(value) on the context object. Then fix disallowed
  1.6691 +			// ancestors of the result."
  1.6692 +			fixDisallowedAncestors(wrap(sublist,
  1.6693 +				["div", "p"].indexOf(value) == - 1
  1.6694 +					? function(node) { return isHtmlElement(node, value) && !node.attributes.length }
  1.6695 +					: function() { return false },
  1.6696 +				function() { return document.createElement(value) }));
  1.6697 +		}
  1.6698 +
  1.6699 +		// "Return true."
  1.6700 +		return true;
  1.6701 +	}, indeterm: function() {
  1.6702 +		// "If the active range is null, return false."
  1.6703 +		if (!getActiveRange()) {
  1.6704 +			return false;
  1.6705 +		}
  1.6706 +
  1.6707 +		// "Block-extend the active range, and let new range be the result."
  1.6708 +		var newRange = blockExtend(getActiveRange());
  1.6709 +
  1.6710 +		// "Let node list be all visible editable nodes that are contained in
  1.6711 +		// new range and have no children."
  1.6712 +		var nodeList = getAllContainedNodes(newRange, function(node) {
  1.6713 +			return isVisible(node)
  1.6714 +				&& isEditable(node)
  1.6715 +				&& !node.hasChildNodes();
  1.6716 +		});
  1.6717 +
  1.6718 +		// "If node list is empty, return false."
  1.6719 +		if (!nodeList.length) {
  1.6720 +			return false;
  1.6721 +		}
  1.6722 +
  1.6723 +		// "Let type be null."
  1.6724 +		var type = null;
  1.6725 +
  1.6726 +		// "For each node in node list:"
  1.6727 +		for (var i = 0; i < nodeList.length; i++) {
  1.6728 +			var node = nodeList[i];
  1.6729 +
  1.6730 +			// "While node's parent is editable and in the same editing host as
  1.6731 +			// node, and node is not an HTML element whose local name is a
  1.6732 +			// formattable block name, set node to its parent."
  1.6733 +			while (isEditable(node.parentNode)
  1.6734 +			&& inSameEditingHost(node, node.parentNode)
  1.6735 +			&& !isHtmlElement(node, formattableBlockNames)) {
  1.6736 +				node = node.parentNode;
  1.6737 +			}
  1.6738 +
  1.6739 +			// "Let current type be the empty string."
  1.6740 +			var currentType = "";
  1.6741 +
  1.6742 +			// "If node is an editable HTML element whose local name is a
  1.6743 +			// formattable block name, and node is not the ancestor of a
  1.6744 +			// prohibited paragraph child, set current type to node's local
  1.6745 +			// name."
  1.6746 +			if (isEditable(node)
  1.6747 +			&& isHtmlElement(node, formattableBlockNames)
  1.6748 +			&& !getDescendants(node).some(isProhibitedParagraphChild)) {
  1.6749 +				currentType = node.tagName;
  1.6750 +			}
  1.6751 +
  1.6752 +			// "If type is null, set type to current type."
  1.6753 +			if (type === null) {
  1.6754 +				type = currentType;
  1.6755 +
  1.6756 +			// "Otherwise, if type does not equal current type, return true."
  1.6757 +			} else if (type != currentType) {
  1.6758 +				return true;
  1.6759 +			}
  1.6760 +		}
  1.6761 +
  1.6762 +		// "Return false."
  1.6763 +		return false;
  1.6764 +	}, value: function() {
  1.6765 +		// "If the active range is null, return the empty string."
  1.6766 +		if (!getActiveRange()) {
  1.6767 +			return "";
  1.6768 +		}
  1.6769 +
  1.6770 +		// "Block-extend the active range, and let new range be the result."
  1.6771 +		var newRange = blockExtend(getActiveRange());
  1.6772 +
  1.6773 +		// "Let node be the first visible editable node that is contained in
  1.6774 +		// new range and has no children. If there is no such node, return the
  1.6775 +		// empty string."
  1.6776 +		var nodes = getAllContainedNodes(newRange, function(node) {
  1.6777 +			return isVisible(node)
  1.6778 +				&& isEditable(node)
  1.6779 +				&& !node.hasChildNodes();
  1.6780 +		});
  1.6781 +		if (!nodes.length) {
  1.6782 +			return "";
  1.6783 +		}
  1.6784 +		var node = nodes[0];
  1.6785 +
  1.6786 +		// "While node's parent is editable and in the same editing host as
  1.6787 +		// node, and node is not an HTML element whose local name is a
  1.6788 +		// formattable block name, set node to its parent."
  1.6789 +		while (isEditable(node.parentNode)
  1.6790 +		&& inSameEditingHost(node, node.parentNode)
  1.6791 +		&& !isHtmlElement(node, formattableBlockNames)) {
  1.6792 +			node = node.parentNode;
  1.6793 +		}
  1.6794 +
  1.6795 +		// "If node is an editable HTML element whose local name is a
  1.6796 +		// formattable block name, and node is not the ancestor of a prohibited
  1.6797 +		// paragraph child, return node's local name, converted to ASCII
  1.6798 +		// lowercase."
  1.6799 +		if (isEditable(node)
  1.6800 +		&& isHtmlElement(node, formattableBlockNames)
  1.6801 +		&& !getDescendants(node).some(isProhibitedParagraphChild)) {
  1.6802 +			return node.tagName.toLowerCase();
  1.6803 +		}
  1.6804 +
  1.6805 +		// "Return the empty string."
  1.6806 +		return "";
  1.6807 +	}
  1.6808 +};
  1.6809 +
  1.6810 +//@}
  1.6811 +///// The forwardDelete command /////
  1.6812 +//@{
  1.6813 +commands.forwarddelete = {
  1.6814 +	preservesOverrides: true,
  1.6815 +	action: function() {
  1.6816 +		// "If the active range is not collapsed, delete the selection and
  1.6817 +		// return true."
  1.6818 +		if (!getActiveRange().collapsed) {
  1.6819 +			deleteSelection();
  1.6820 +			return true;
  1.6821 +		}
  1.6822 +
  1.6823 +		// "Canonicalize whitespace at the active range's start."
  1.6824 +		canonicalizeWhitespace(getActiveRange().startContainer, getActiveRange().startOffset);
  1.6825 +
  1.6826 +		// "Let node and offset be the active range's start node and offset."
  1.6827 +		var node = getActiveRange().startContainer;
  1.6828 +		var offset = getActiveRange().startOffset;
  1.6829 +
  1.6830 +		// "Repeat the following steps:"
  1.6831 +		while (true) {
  1.6832 +			// "If offset is the length of node and node's nextSibling is an
  1.6833 +			// editable invisible node, remove node's nextSibling from its
  1.6834 +			// parent."
  1.6835 +			if (offset == getNodeLength(node)
  1.6836 +			&& isEditable(node.nextSibling)
  1.6837 +			&& isInvisible(node.nextSibling)) {
  1.6838 +				node.parentNode.removeChild(node.nextSibling);
  1.6839 +
  1.6840 +			// "Otherwise, if node has a child with index offset and that child
  1.6841 +			// is an editable invisible node, remove that child from node."
  1.6842 +			} else if (offset < node.childNodes.length
  1.6843 +			&& isEditable(node.childNodes[offset])
  1.6844 +			&& isInvisible(node.childNodes[offset])) {
  1.6845 +				node.removeChild(node.childNodes[offset]);
  1.6846 +
  1.6847 +			// "Otherwise, if offset is the length of node and node is an
  1.6848 +			// inline node, or if node is invisible, set offset to one plus the
  1.6849 +			// index of node, then set node to its parent."
  1.6850 +			} else if ((offset == getNodeLength(node)
  1.6851 +			&& isInlineNode(node))
  1.6852 +			|| isInvisible(node)) {
  1.6853 +				offset = 1 + getNodeIndex(node);
  1.6854 +				node = node.parentNode;
  1.6855 +
  1.6856 +			// "Otherwise, if node has a child with index offset and that child
  1.6857 +			// is neither a block node nor a br nor an img nor a collapsed
  1.6858 +			// block prop, set node to that child, then set offset to zero."
  1.6859 +			} else if (offset < node.childNodes.length
  1.6860 +			&& !isBlockNode(node.childNodes[offset])
  1.6861 +			&& !isHtmlElement(node.childNodes[offset], ["br", "img"])
  1.6862 +			&& !isCollapsedBlockProp(node.childNodes[offset])) {
  1.6863 +				node = node.childNodes[offset];
  1.6864 +				offset = 0;
  1.6865 +
  1.6866 +			// "Otherwise, break from this loop."
  1.6867 +			} else {
  1.6868 +				break;
  1.6869 +			}
  1.6870 +		}
  1.6871 +
  1.6872 +		// "If node is a Text node and offset is not node's length:"
  1.6873 +		if (node.nodeType == Node.TEXT_NODE
  1.6874 +		&& offset != getNodeLength(node)) {
  1.6875 +			// "Let end offset be offset plus one."
  1.6876 +			var endOffset = offset + 1;
  1.6877 +
  1.6878 +			// "While end offset is not node's length and the end offsetth
  1.6879 +			// element of node's data has general category M when interpreted
  1.6880 +			// as a Unicode code point, add one to end offset."
  1.6881 +			//
  1.6882 +			// TODO: Not even going to try handling anything beyond the most
  1.6883 +			// basic combining marks, since I couldn't find a good list.  I
  1.6884 +			// special-case a few Hebrew diacritics too to test basic coverage
  1.6885 +			// of non-Latin stuff.
  1.6886 +			while (endOffset != node.length
  1.6887 +			&& /^[\u0300-\u036f\u0591-\u05bd\u05c1\u05c2]$/.test(node.data[endOffset])) {
  1.6888 +				endOffset++;
  1.6889 +			}
  1.6890 +
  1.6891 +			// "Call collapse(node, offset) on the context object's Selection."
  1.6892 +			getSelection().collapse(node, offset);
  1.6893 +			getActiveRange().setStart(node, offset);
  1.6894 +
  1.6895 +			// "Call extend(node, end offset) on the context object's
  1.6896 +			// Selection."
  1.6897 +			getSelection().extend(node, endOffset);
  1.6898 +			getActiveRange().setEnd(node, endOffset);
  1.6899 +
  1.6900 +			// "Delete the selection."
  1.6901 +			deleteSelection();
  1.6902 +
  1.6903 +			// "Return true."
  1.6904 +			return true;
  1.6905 +		}
  1.6906 +
  1.6907 +		// "If node is an inline node, return true."
  1.6908 +		if (isInlineNode(node)) {
  1.6909 +			return true;
  1.6910 +		}
  1.6911 +
  1.6912 +		// "If node has a child with index offset and that child is a br or hr
  1.6913 +		// or img, but is not a collapsed block prop:"
  1.6914 +		if (offset < node.childNodes.length
  1.6915 +		&& isHtmlElement(node.childNodes[offset], ["br", "hr", "img"])
  1.6916 +		&& !isCollapsedBlockProp(node.childNodes[offset])) {
  1.6917 +			// "Call collapse(node, offset) on the context object's Selection."
  1.6918 +			getSelection().collapse(node, offset);
  1.6919 +			getActiveRange().setStart(node, offset);
  1.6920 +
  1.6921 +			// "Call extend(node, offset + 1) on the context object's
  1.6922 +			// Selection."
  1.6923 +			getSelection().extend(node, offset + 1);
  1.6924 +			getActiveRange().setEnd(node, offset + 1);
  1.6925 +
  1.6926 +			// "Delete the selection."
  1.6927 +			deleteSelection();
  1.6928 +
  1.6929 +			// "Return true."
  1.6930 +			return true;
  1.6931 +		}
  1.6932 +
  1.6933 +		// "Let end node equal node and let end offset equal offset."
  1.6934 +		var endNode = node;
  1.6935 +		var endOffset = offset;
  1.6936 +
  1.6937 +		// "If end node has a child with index end offset, and that child is a
  1.6938 +		// collapsed block prop, add one to end offset."
  1.6939 +		if (endOffset < endNode.childNodes.length
  1.6940 +		&& isCollapsedBlockProp(endNode.childNodes[endOffset])) {
  1.6941 +			endOffset++;
  1.6942 +		}
  1.6943 +
  1.6944 +		// "Repeat the following steps:"
  1.6945 +		while (true) {
  1.6946 +			// "If end offset is the length of end node, set end offset to one
  1.6947 +			// plus the index of end node and then set end node to its parent."
  1.6948 +			if (endOffset == getNodeLength(endNode)) {
  1.6949 +				endOffset = 1 + getNodeIndex(endNode);
  1.6950 +				endNode = endNode.parentNode;
  1.6951 +
  1.6952 +			// "Otherwise, if end node has a an editable invisible child with
  1.6953 +			// index end offset, remove it from end node."
  1.6954 +			} else if (endOffset < endNode.childNodes.length
  1.6955 +			&& isEditable(endNode.childNodes[endOffset])
  1.6956 +			&& isInvisible(endNode.childNodes[endOffset])) {
  1.6957 +				endNode.removeChild(endNode.childNodes[endOffset]);
  1.6958 +
  1.6959 +			// "Otherwise, break from this loop."
  1.6960 +			} else {
  1.6961 +				break;
  1.6962 +			}
  1.6963 +		}
  1.6964 +
  1.6965 +		// "If the child of end node with index end offset minus one is a
  1.6966 +		// table, return true."
  1.6967 +		if (isHtmlElement(endNode.childNodes[endOffset - 1], "table")) {
  1.6968 +			return true;
  1.6969 +		}
  1.6970 +
  1.6971 +		// "If the child of end node with index end offset is a table:"
  1.6972 +		if (isHtmlElement(endNode.childNodes[endOffset], "table")) {
  1.6973 +			// "Call collapse(end node, end offset) on the context object's
  1.6974 +			// Selection."
  1.6975 +			getSelection().collapse(endNode, endOffset);
  1.6976 +			getActiveRange().setStart(endNode, endOffset);
  1.6977 +
  1.6978 +			// "Call extend(end node, end offset + 1) on the context object's
  1.6979 +			// Selection."
  1.6980 +			getSelection().extend(endNode, endOffset + 1);
  1.6981 +			getActiveRange().setEnd(endNode, endOffset + 1);
  1.6982 +
  1.6983 +			// "Return true."
  1.6984 +			return true;
  1.6985 +		}
  1.6986 +
  1.6987 +		// "If offset is the length of node, and the child of end node with
  1.6988 +		// index end offset is an hr or br:"
  1.6989 +		if (offset == getNodeLength(node)
  1.6990 +		&& isHtmlElement(endNode.childNodes[endOffset], ["br", "hr"])) {
  1.6991 +			// "Call collapse(end node, end offset) on the context object's
  1.6992 +			// Selection."
  1.6993 +			getSelection().collapse(endNode, endOffset);
  1.6994 +			getActiveRange().setStart(endNode, endOffset);
  1.6995 +
  1.6996 +			// "Call extend(end node, end offset + 1) on the context object's
  1.6997 +			// Selection."
  1.6998 +			getSelection().extend(endNode, endOffset + 1);
  1.6999 +			getActiveRange().setEnd(endNode, endOffset + 1);
  1.7000 +
  1.7001 +			// "Delete the selection."
  1.7002 +			deleteSelection();
  1.7003 +
  1.7004 +			// "Call collapse(node, offset) on the Selection."
  1.7005 +			getSelection().collapse(node, offset);
  1.7006 +			getActiveRange().setStart(node, offset);
  1.7007 +			getActiveRange().collapse(true);
  1.7008 +
  1.7009 +			// "Return true."
  1.7010 +			return true;
  1.7011 +		}
  1.7012 +
  1.7013 +		// "While end node has a child with index end offset:"
  1.7014 +		while (endOffset < endNode.childNodes.length) {
  1.7015 +			// "If end node's child with index end offset is editable and
  1.7016 +			// invisible, remove it from end node."
  1.7017 +			if (isEditable(endNode.childNodes[endOffset])
  1.7018 +			&& isInvisible(endNode.childNodes[endOffset])) {
  1.7019 +				endNode.removeChild(endNode.childNodes[endOffset]);
  1.7020 +
  1.7021 +			// "Otherwise, set end node to its child with index end offset and
  1.7022 +			// set end offset to zero."
  1.7023 +			} else {
  1.7024 +				endNode = endNode.childNodes[endOffset];
  1.7025 +				endOffset = 0;
  1.7026 +			}
  1.7027 +		}
  1.7028 +
  1.7029 +		// "Call collapse(node, offset) on the context object's Selection."
  1.7030 +		getSelection().collapse(node, offset);
  1.7031 +		getActiveRange().setStart(node, offset);
  1.7032 +
  1.7033 +		// "Call extend(end node, end offset) on the context object's
  1.7034 +		// Selection."
  1.7035 +		getSelection().extend(endNode, endOffset);
  1.7036 +		getActiveRange().setEnd(endNode, endOffset);
  1.7037 +
  1.7038 +		// "Delete the selection."
  1.7039 +		deleteSelection();
  1.7040 +
  1.7041 +		// "Return true."
  1.7042 +		return true;
  1.7043 +	}
  1.7044 +};
  1.7045 +
  1.7046 +//@}
  1.7047 +///// The indent command /////
  1.7048 +//@{
  1.7049 +commands.indent = {
  1.7050 +	preservesOverrides: true,
  1.7051 +	action: function() {
  1.7052 +		// "Let items be a list of all lis that are ancestor containers of the
  1.7053 +		// active range's start and/or end node."
  1.7054 +		//
  1.7055 +		// Has to be in tree order, remember!
  1.7056 +		var items = [];
  1.7057 +		for (var node = getActiveRange().endContainer; node != getActiveRange().commonAncestorContainer; node = node.parentNode) {
  1.7058 +			if (isHtmlElement(node, "LI")) {
  1.7059 +				items.unshift(node);
  1.7060 +			}
  1.7061 +		}
  1.7062 +		for (var node = getActiveRange().startContainer; node != getActiveRange().commonAncestorContainer; node = node.parentNode) {
  1.7063 +			if (isHtmlElement(node, "LI")) {
  1.7064 +				items.unshift(node);
  1.7065 +			}
  1.7066 +		}
  1.7067 +		for (var node = getActiveRange().commonAncestorContainer; node; node = node.parentNode) {
  1.7068 +			if (isHtmlElement(node, "LI")) {
  1.7069 +				items.unshift(node);
  1.7070 +			}
  1.7071 +		}
  1.7072 +
  1.7073 +		// "For each item in items, normalize sublists of item."
  1.7074 +		for (var i = 0; i < items.length; i++) {
  1.7075 +			normalizeSublists(items[i]);
  1.7076 +		}
  1.7077 +
  1.7078 +		// "Block-extend the active range, and let new range be the result."
  1.7079 +		var newRange = blockExtend(getActiveRange());
  1.7080 +
  1.7081 +		// "Let node list be a list of nodes, initially empty."
  1.7082 +		var nodeList = [];
  1.7083 +
  1.7084 +		// "For each node node contained in new range, if node is editable and
  1.7085 +		// is an allowed child of "div" or "ol" and if the last member of node
  1.7086 +		// list (if any) is not an ancestor of node, append node to node list."
  1.7087 +		nodeList = getContainedNodes(newRange, function(node) {
  1.7088 +			return isEditable(node)
  1.7089 +				&& (isAllowedChild(node, "div")
  1.7090 +				|| isAllowedChild(node, "ol"));
  1.7091 +		});
  1.7092 +
  1.7093 +		// "If the first visible member of node list is an li whose parent is
  1.7094 +		// an ol or ul:"
  1.7095 +		if (isHtmlElement(nodeList.filter(isVisible)[0], "li")
  1.7096 +		&& isHtmlElement(nodeList.filter(isVisible)[0].parentNode, ["ol", "ul"])) {
  1.7097 +			// "Let sibling be node list's first visible member's
  1.7098 +			// previousSibling."
  1.7099 +			var sibling = nodeList.filter(isVisible)[0].previousSibling;
  1.7100 +
  1.7101 +			// "While sibling is invisible, set sibling to its
  1.7102 +			// previousSibling."
  1.7103 +			while (isInvisible(sibling)) {
  1.7104 +				sibling = sibling.previousSibling;
  1.7105 +			}
  1.7106 +
  1.7107 +			// "If sibling is an li, normalize sublists of sibling."
  1.7108 +			if (isHtmlElement(sibling, "li")) {
  1.7109 +				normalizeSublists(sibling);
  1.7110 +			}
  1.7111 +		}
  1.7112 +
  1.7113 +		// "While node list is not empty:"
  1.7114 +		while (nodeList.length) {
  1.7115 +			// "Let sublist be a list of nodes, initially empty."
  1.7116 +			var sublist = [];
  1.7117 +
  1.7118 +			// "Remove the first member of node list and append it to sublist."
  1.7119 +			sublist.push(nodeList.shift());
  1.7120 +
  1.7121 +			// "While the first member of node list is the nextSibling of the
  1.7122 +			// last member of sublist, remove the first member of node list and
  1.7123 +			// append it to sublist."
  1.7124 +			while (nodeList.length
  1.7125 +			&& nodeList[0] == sublist[sublist.length - 1].nextSibling) {
  1.7126 +				sublist.push(nodeList.shift());
  1.7127 +			}
  1.7128 +
  1.7129 +			// "Indent sublist."
  1.7130 +			indentNodes(sublist);
  1.7131 +		}
  1.7132 +
  1.7133 +		// "Return true."
  1.7134 +		return true;
  1.7135 +	}
  1.7136 +};
  1.7137 +
  1.7138 +//@}
  1.7139 +///// The insertHorizontalRule command /////
  1.7140 +//@{
  1.7141 +commands.inserthorizontalrule = {
  1.7142 +	preservesOverrides: true,
  1.7143 +	action: function() {
  1.7144 +		// "Let start node, start offset, end node, and end offset be the
  1.7145 +		// active range's start and end nodes and offsets."
  1.7146 +		var startNode = getActiveRange().startContainer;
  1.7147 +		var startOffset = getActiveRange().startOffset;
  1.7148 +		var endNode = getActiveRange().endContainer;
  1.7149 +		var endOffset = getActiveRange().endOffset;
  1.7150 +
  1.7151 +		// "While start offset is 0 and start node's parent is not null, set
  1.7152 +		// start offset to start node's index, then set start node to its
  1.7153 +		// parent."
  1.7154 +		while (startOffset == 0
  1.7155 +		&& startNode.parentNode) {
  1.7156 +			startOffset = getNodeIndex(startNode);
  1.7157 +			startNode = startNode.parentNode;
  1.7158 +		}
  1.7159 +
  1.7160 +		// "While end offset is end node's length, and end node's parent is not
  1.7161 +		// null, set end offset to one plus end node's index, then set end node
  1.7162 +		// to its parent."
  1.7163 +		while (endOffset == getNodeLength(endNode)
  1.7164 +		&& endNode.parentNode) {
  1.7165 +			endOffset = 1 + getNodeIndex(endNode);
  1.7166 +			endNode = endNode.parentNode;
  1.7167 +		}
  1.7168 +
  1.7169 +		// "Call collapse(start node, start offset) on the context object's
  1.7170 +		// Selection."
  1.7171 +		getSelection().collapse(startNode, startOffset);
  1.7172 +		getActiveRange().setStart(startNode, startOffset);
  1.7173 +
  1.7174 +		// "Call extend(end node, end offset) on the context object's
  1.7175 +		// Selection."
  1.7176 +		getSelection().extend(endNode, endOffset);
  1.7177 +		getActiveRange().setEnd(endNode, endOffset);
  1.7178 +
  1.7179 +		// "Delete the selection, with block merging false."
  1.7180 +		deleteSelection({blockMerging: false});
  1.7181 +
  1.7182 +		// "If the active range's start node is neither editable nor an editing
  1.7183 +		// host, return true."
  1.7184 +		if (!isEditable(getActiveRange().startContainer)
  1.7185 +		&& !isEditingHost(getActiveRange().startContainer)) {
  1.7186 +			return true;
  1.7187 +		}
  1.7188 +
  1.7189 +		// "If the active range's start node is a Text node and its start
  1.7190 +		// offset is zero, call collapse() on the context object's Selection,
  1.7191 +		// with first argument the active range's start node's parent and
  1.7192 +		// second argument the active range's start node's index."
  1.7193 +		if (getActiveRange().startContainer.nodeType == Node.TEXT_NODE
  1.7194 +		&& getActiveRange().startOffset == 0) {
  1.7195 +			var newNode = getActiveRange().startContainer.parentNode;
  1.7196 +			var newOffset = getNodeIndex(getActiveRange().startContainer);
  1.7197 +			getSelection().collapse(newNode, newOffset);
  1.7198 +			getActiveRange().setStart(newNode, newOffset);
  1.7199 +			getActiveRange().collapse(true);
  1.7200 +		}
  1.7201 +
  1.7202 +		// "If the active range's start node is a Text node and its start
  1.7203 +		// offset is the length of its start node, call collapse() on the
  1.7204 +		// context object's Selection, with first argument the active range's
  1.7205 +		// start node's parent, and the second argument one plus the active
  1.7206 +		// range's start node's index."
  1.7207 +		if (getActiveRange().startContainer.nodeType == Node.TEXT_NODE
  1.7208 +		&& getActiveRange().startOffset == getNodeLength(getActiveRange().startContainer)) {
  1.7209 +			var newNode = getActiveRange().startContainer.parentNode;
  1.7210 +			var newOffset = 1 + getNodeIndex(getActiveRange().startContainer);
  1.7211 +			getSelection().collapse(newNode, newOffset);
  1.7212 +			getActiveRange().setStart(newNode, newOffset);
  1.7213 +			getActiveRange().collapse(true);
  1.7214 +		}
  1.7215 +
  1.7216 +		// "Let hr be the result of calling createElement("hr") on the
  1.7217 +		// context object."
  1.7218 +		var hr = document.createElement("hr");
  1.7219 +
  1.7220 +		// "Run insertNode(hr) on the active range."
  1.7221 +		getActiveRange().insertNode(hr);
  1.7222 +
  1.7223 +		// "Fix disallowed ancestors of hr."
  1.7224 +		fixDisallowedAncestors(hr);
  1.7225 +
  1.7226 +		// "Run collapse() on the context object's Selection, with first
  1.7227 +		// argument hr's parent and the second argument equal to one plus hr's
  1.7228 +		// index."
  1.7229 +		getSelection().collapse(hr.parentNode, 1 + getNodeIndex(hr));
  1.7230 +		getActiveRange().setStart(hr.parentNode, 1 + getNodeIndex(hr));
  1.7231 +		getActiveRange().collapse(true);
  1.7232 +
  1.7233 +		// "Return true."
  1.7234 +		return true;
  1.7235 +	}
  1.7236 +};
  1.7237 +
  1.7238 +//@}
  1.7239 +///// The insertHTML command /////
  1.7240 +//@{
  1.7241 +commands.inserthtml = {
  1.7242 +	preservesOverrides: true,
  1.7243 +	action: function(value) {
  1.7244 +		// "Delete the selection."
  1.7245 +		deleteSelection();
  1.7246 +
  1.7247 +		// "If the active range's start node is neither editable nor an editing
  1.7248 +		// host, return true."
  1.7249 +		if (!isEditable(getActiveRange().startContainer)
  1.7250 +		&& !isEditingHost(getActiveRange().startContainer)) {
  1.7251 +			return true;
  1.7252 +		}
  1.7253 +
  1.7254 +		// "Let frag be the result of calling createContextualFragment(value)
  1.7255 +		// on the active range."
  1.7256 +		var frag = getActiveRange().createContextualFragment(value);
  1.7257 +
  1.7258 +		// "Let last child be the lastChild of frag."
  1.7259 +		var lastChild = frag.lastChild;
  1.7260 +
  1.7261 +		// "If last child is null, return true."
  1.7262 +		if (!lastChild) {
  1.7263 +			return true;
  1.7264 +		}
  1.7265 +
  1.7266 +		// "Let descendants be all descendants of frag."
  1.7267 +		var descendants = getDescendants(frag);
  1.7268 +
  1.7269 +		// "If the active range's start node is a block node:"
  1.7270 +		if (isBlockNode(getActiveRange().startContainer)) {
  1.7271 +			// "Let collapsed block props be all editable collapsed block prop
  1.7272 +			// children of the active range's start node that have index
  1.7273 +			// greater than or equal to the active range's start offset."
  1.7274 +			//
  1.7275 +			// "For each node in collapsed block props, remove node from its
  1.7276 +			// parent."
  1.7277 +			[].filter.call(getActiveRange().startContainer.childNodes, function(node) {
  1.7278 +				return isEditable(node)
  1.7279 +					&& isCollapsedBlockProp(node)
  1.7280 +					&& getNodeIndex(node) >= getActiveRange().startOffset;
  1.7281 +			}).forEach(function(node) {
  1.7282 +				node.parentNode.removeChild(node);
  1.7283 +			});
  1.7284 +		}
  1.7285 +
  1.7286 +		// "Call insertNode(frag) on the active range."
  1.7287 +		getActiveRange().insertNode(frag);
  1.7288 +
  1.7289 +		// "If the active range's start node is a block node with no visible
  1.7290 +		// children, call createElement("br") on the context object and append
  1.7291 +		// the result as the last child of the active range's start node."
  1.7292 +		if (isBlockNode(getActiveRange().startContainer)
  1.7293 +		&& ![].some.call(getActiveRange().startContainer.childNodes, isVisible)) {
  1.7294 +			getActiveRange().startContainer.appendChild(document.createElement("br"));
  1.7295 +		}
  1.7296 +
  1.7297 +		// "Call collapse() on the context object's Selection, with last
  1.7298 +		// child's parent as the first argument and one plus its index as the
  1.7299 +		// second."
  1.7300 +		getActiveRange().setStart(lastChild.parentNode, 1 + getNodeIndex(lastChild));
  1.7301 +		getActiveRange().setEnd(lastChild.parentNode, 1 + getNodeIndex(lastChild));
  1.7302 +
  1.7303 +		// "Fix disallowed ancestors of each member of descendants."
  1.7304 +		for (var i = 0; i < descendants.length; i++) {
  1.7305 +			fixDisallowedAncestors(descendants[i]);
  1.7306 +		}
  1.7307 +
  1.7308 +		// "Return true."
  1.7309 +		return true;
  1.7310 +	}
  1.7311 +};
  1.7312 +
  1.7313 +//@}
  1.7314 +///// The insertImage command /////
  1.7315 +//@{
  1.7316 +commands.insertimage = {
  1.7317 +	preservesOverrides: true,
  1.7318 +	action: function(value) {
  1.7319 +		// "If value is the empty string, return false."
  1.7320 +		if (value === "") {
  1.7321 +			return false;
  1.7322 +		}
  1.7323 +
  1.7324 +		// "Delete the selection, with strip wrappers false."
  1.7325 +		deleteSelection({stripWrappers: false});
  1.7326 +
  1.7327 +		// "Let range be the active range."
  1.7328 +		var range = getActiveRange();
  1.7329 +
  1.7330 +		// "If the active range's start node is neither editable nor an editing
  1.7331 +		// host, return true."
  1.7332 +		if (!isEditable(getActiveRange().startContainer)
  1.7333 +		&& !isEditingHost(getActiveRange().startContainer)) {
  1.7334 +			return true;
  1.7335 +		}
  1.7336 +
  1.7337 +		// "If range's start node is a block node whose sole child is a br, and
  1.7338 +		// its start offset is 0, remove its start node's child from it."
  1.7339 +		if (isBlockNode(range.startContainer)
  1.7340 +		&& range.startContainer.childNodes.length == 1
  1.7341 +		&& isHtmlElement(range.startContainer.firstChild, "br")
  1.7342 +		&& range.startOffset == 0) {
  1.7343 +			range.startContainer.removeChild(range.startContainer.firstChild);
  1.7344 +		}
  1.7345 +
  1.7346 +		// "Let img be the result of calling createElement("img") on the
  1.7347 +		// context object."
  1.7348 +		var img = document.createElement("img");
  1.7349 +
  1.7350 +		// "Run setAttribute("src", value) on img."
  1.7351 +		img.setAttribute("src", value);
  1.7352 +
  1.7353 +		// "Run insertNode(img) on the range."
  1.7354 +		range.insertNode(img);
  1.7355 +
  1.7356 +		// "Run collapse() on the Selection, with first argument equal to the
  1.7357 +		// parent of img and the second argument equal to one plus the index of
  1.7358 +		// img."
  1.7359 +		//
  1.7360 +		// Not everyone actually supports collapse(), so we do it manually
  1.7361 +		// instead.  Also, we need to modify the actual range we're given as
  1.7362 +		// well, for the sake of autoimplementation.html's range-filling-in.
  1.7363 +		range.setStart(img.parentNode, 1 + getNodeIndex(img));
  1.7364 +		range.setEnd(img.parentNode, 1 + getNodeIndex(img));
  1.7365 +		getSelection().removeAllRanges();
  1.7366 +		getSelection().addRange(range);
  1.7367 +
  1.7368 +		// IE adds width and height attributes for some reason, so remove those
  1.7369 +		// to actually do what the spec says.
  1.7370 +		img.removeAttribute("width");
  1.7371 +		img.removeAttribute("height");
  1.7372 +
  1.7373 +		// "Return true."
  1.7374 +		return true;
  1.7375 +	}
  1.7376 +};
  1.7377 +
  1.7378 +//@}
  1.7379 +///// The insertLineBreak command /////
  1.7380 +//@{
  1.7381 +commands.insertlinebreak = {
  1.7382 +	preservesOverrides: true,
  1.7383 +	action: function(value) {
  1.7384 +		// "Delete the selection, with strip wrappers false."
  1.7385 +		deleteSelection({stripWrappers: false});
  1.7386 +
  1.7387 +		// "If the active range's start node is neither editable nor an editing
  1.7388 +		// host, return true."
  1.7389 +		if (!isEditable(getActiveRange().startContainer)
  1.7390 +		&& !isEditingHost(getActiveRange().startContainer)) {
  1.7391 +			return true;
  1.7392 +		}
  1.7393 +
  1.7394 +		// "If the active range's start node is an Element, and "br" is not an
  1.7395 +		// allowed child of it, return true."
  1.7396 +		if (getActiveRange().startContainer.nodeType == Node.ELEMENT_NODE
  1.7397 +		&& !isAllowedChild("br", getActiveRange().startContainer)) {
  1.7398 +			return true;
  1.7399 +		}
  1.7400 +
  1.7401 +		// "If the active range's start node is not an Element, and "br" is not
  1.7402 +		// an allowed child of the active range's start node's parent, return
  1.7403 +		// true."
  1.7404 +		if (getActiveRange().startContainer.nodeType != Node.ELEMENT_NODE
  1.7405 +		&& !isAllowedChild("br", getActiveRange().startContainer.parentNode)) {
  1.7406 +			return true;
  1.7407 +		}
  1.7408 +
  1.7409 +		// "If the active range's start node is a Text node and its start
  1.7410 +		// offset is zero, call collapse() on the context object's Selection,
  1.7411 +		// with first argument equal to the active range's start node's parent
  1.7412 +		// and second argument equal to the active range's start node's index."
  1.7413 +		if (getActiveRange().startContainer.nodeType == Node.TEXT_NODE
  1.7414 +		&& getActiveRange().startOffset == 0) {
  1.7415 +			var newNode = getActiveRange().startContainer.parentNode;
  1.7416 +			var newOffset = getNodeIndex(getActiveRange().startContainer);
  1.7417 +			getSelection().collapse(newNode, newOffset);
  1.7418 +			getActiveRange().setStart(newNode, newOffset);
  1.7419 +			getActiveRange().setEnd(newNode, newOffset);
  1.7420 +		}
  1.7421 +
  1.7422 +		// "If the active range's start node is a Text node and its start
  1.7423 +		// offset is the length of its start node, call collapse() on the
  1.7424 +		// context object's Selection, with first argument equal to the active
  1.7425 +		// range's start node's parent and second argument equal to one plus
  1.7426 +		// the active range's start node's index."
  1.7427 +		if (getActiveRange().startContainer.nodeType == Node.TEXT_NODE
  1.7428 +		&& getActiveRange().startOffset == getNodeLength(getActiveRange().startContainer)) {
  1.7429 +			var newNode = getActiveRange().startContainer.parentNode;
  1.7430 +			var newOffset = 1 + getNodeIndex(getActiveRange().startContainer);
  1.7431 +			getSelection().collapse(newNode, newOffset);
  1.7432 +			getActiveRange().setStart(newNode, newOffset);
  1.7433 +			getActiveRange().setEnd(newNode, newOffset);
  1.7434 +		}
  1.7435 +
  1.7436 +		// "Let br be the result of calling createElement("br") on the context
  1.7437 +		// object."
  1.7438 +		var br = document.createElement("br");
  1.7439 +
  1.7440 +		// "Call insertNode(br) on the active range."
  1.7441 +		getActiveRange().insertNode(br);
  1.7442 +
  1.7443 +		// "Call collapse() on the context object's Selection, with br's parent
  1.7444 +		// as the first argument and one plus br's index as the second
  1.7445 +		// argument."
  1.7446 +		getSelection().collapse(br.parentNode, 1 + getNodeIndex(br));
  1.7447 +		getActiveRange().setStart(br.parentNode, 1 + getNodeIndex(br));
  1.7448 +		getActiveRange().setEnd(br.parentNode, 1 + getNodeIndex(br));
  1.7449 +
  1.7450 +		// "If br is a collapsed line break, call createElement("br") on the
  1.7451 +		// context object and let extra br be the result, then call
  1.7452 +		// insertNode(extra br) on the active range."
  1.7453 +		if (isCollapsedLineBreak(br)) {
  1.7454 +			getActiveRange().insertNode(document.createElement("br"));
  1.7455 +
  1.7456 +			// Compensate for nonstandard implementations of insertNode
  1.7457 +			getSelection().collapse(br.parentNode, 1 + getNodeIndex(br));
  1.7458 +			getActiveRange().setStart(br.parentNode, 1 + getNodeIndex(br));
  1.7459 +			getActiveRange().setEnd(br.parentNode, 1 + getNodeIndex(br));
  1.7460 +		}
  1.7461 +
  1.7462 +		// "Return true."
  1.7463 +		return true;
  1.7464 +	}
  1.7465 +};
  1.7466 +
  1.7467 +//@}
  1.7468 +///// The insertOrderedList command /////
  1.7469 +//@{
  1.7470 +commands.insertorderedlist = {
  1.7471 +	preservesOverrides: true,
  1.7472 +	// "Toggle lists with tag name "ol", then return true."
  1.7473 +	action: function() { toggleLists("ol"); return true },
  1.7474 +	// "True if the selection's list state is "mixed" or "mixed ol", false
  1.7475 +	// otherwise."
  1.7476 +	indeterm: function() { return /^mixed( ol)?$/.test(getSelectionListState()) },
  1.7477 +	// "True if the selection's list state is "ol", false otherwise."
  1.7478 +	state: function() { return getSelectionListState() == "ol" },
  1.7479 +};
  1.7480 +
  1.7481 +//@}
  1.7482 +///// The insertParagraph command /////
  1.7483 +//@{
  1.7484 +commands.insertparagraph = {
  1.7485 +	preservesOverrides: true,
  1.7486 +	action: function() {
  1.7487 +		// "Delete the selection."
  1.7488 +		deleteSelection();
  1.7489 +
  1.7490 +		// "If the active range's start node is neither editable nor an editing
  1.7491 +		// host, return true."
  1.7492 +		if (!isEditable(getActiveRange().startContainer)
  1.7493 +		&& !isEditingHost(getActiveRange().startContainer)) {
  1.7494 +			return true;
  1.7495 +		}
  1.7496 +
  1.7497 +		// "Let node and offset be the active range's start node and offset."
  1.7498 +		var node = getActiveRange().startContainer;
  1.7499 +		var offset = getActiveRange().startOffset;
  1.7500 +
  1.7501 +		// "If node is a Text node, and offset is neither 0 nor the length of
  1.7502 +		// node, call splitText(offset) on node."
  1.7503 +		if (node.nodeType == Node.TEXT_NODE
  1.7504 +		&& offset != 0
  1.7505 +		&& offset != getNodeLength(node)) {
  1.7506 +			node.splitText(offset);
  1.7507 +		}
  1.7508 +
  1.7509 +		// "If node is a Text node and offset is its length, set offset to one
  1.7510 +		// plus the index of node, then set node to its parent."
  1.7511 +		if (node.nodeType == Node.TEXT_NODE
  1.7512 +		&& offset == getNodeLength(node)) {
  1.7513 +			offset = 1 + getNodeIndex(node);
  1.7514 +			node = node.parentNode;
  1.7515 +		}
  1.7516 +
  1.7517 +		// "If node is a Text or Comment node, set offset to the index of node,
  1.7518 +		// then set node to its parent."
  1.7519 +		if (node.nodeType == Node.TEXT_NODE
  1.7520 +		|| node.nodeType == Node.COMMENT_NODE) {
  1.7521 +			offset = getNodeIndex(node);
  1.7522 +			node = node.parentNode;
  1.7523 +		}
  1.7524 +
  1.7525 +		// "Call collapse(node, offset) on the context object's Selection."
  1.7526 +		getSelection().collapse(node, offset);
  1.7527 +		getActiveRange().setStart(node, offset);
  1.7528 +		getActiveRange().setEnd(node, offset);
  1.7529 +
  1.7530 +		// "Let container equal node."
  1.7531 +		var container = node;
  1.7532 +
  1.7533 +		// "While container is not a single-line container, and container's
  1.7534 +		// parent is editable and in the same editing host as node, set
  1.7535 +		// container to its parent."
  1.7536 +		while (!isSingleLineContainer(container)
  1.7537 +		&& isEditable(container.parentNode)
  1.7538 +		&& inSameEditingHost(node, container.parentNode)) {
  1.7539 +			container = container.parentNode;
  1.7540 +		}
  1.7541 +
  1.7542 +		// "If container is an editable single-line container in the same
  1.7543 +		// editing host as node, and its local name is "p" or "div":"
  1.7544 +		if (isEditable(container)
  1.7545 +		&& isSingleLineContainer(container)
  1.7546 +		&& inSameEditingHost(node, container.parentNode)
  1.7547 +		&& (container.tagName == "P" || container.tagName == "DIV")) {
  1.7548 +			// "Let outer container equal container."
  1.7549 +			var outerContainer = container;
  1.7550 +
  1.7551 +			// "While outer container is not a dd or dt or li, and outer
  1.7552 +			// container's parent is editable, set outer container to its
  1.7553 +			// parent."
  1.7554 +			while (!isHtmlElement(outerContainer, ["dd", "dt", "li"])
  1.7555 +			&& isEditable(outerContainer.parentNode)) {
  1.7556 +				outerContainer = outerContainer.parentNode;
  1.7557 +			}
  1.7558 +
  1.7559 +			// "If outer container is a dd or dt or li, set container to outer
  1.7560 +			// container."
  1.7561 +			if (isHtmlElement(outerContainer, ["dd", "dt", "li"])) {
  1.7562 +				container = outerContainer;
  1.7563 +			}
  1.7564 +		}
  1.7565 +
  1.7566 +		// "If container is not editable or not in the same editing host as
  1.7567 +		// node or is not a single-line container:"
  1.7568 +		if (!isEditable(container)
  1.7569 +		|| !inSameEditingHost(container, node)
  1.7570 +		|| !isSingleLineContainer(container)) {
  1.7571 +			// "Let tag be the default single-line container name."
  1.7572 +			var tag = defaultSingleLineContainerName;
  1.7573 +
  1.7574 +			// "Block-extend the active range, and let new range be the
  1.7575 +			// result."
  1.7576 +			var newRange = blockExtend(getActiveRange());
  1.7577 +
  1.7578 +			// "Let node list be a list of nodes, initially empty."
  1.7579 +			//
  1.7580 +			// "Append to node list the first node in tree order that is
  1.7581 +			// contained in new range and is an allowed child of "p", if any."
  1.7582 +			var nodeList = getContainedNodes(newRange, function(node) { return isAllowedChild(node, "p") })
  1.7583 +				.slice(0, 1);
  1.7584 +
  1.7585 +			// "If node list is empty:"
  1.7586 +			if (!nodeList.length) {
  1.7587 +				// "If tag is not an allowed child of the active range's start
  1.7588 +				// node, return true."
  1.7589 +				if (!isAllowedChild(tag, getActiveRange().startContainer)) {
  1.7590 +					return true;
  1.7591 +				}
  1.7592 +
  1.7593 +				// "Set container to the result of calling createElement(tag)
  1.7594 +				// on the context object."
  1.7595 +				container = document.createElement(tag);
  1.7596 +
  1.7597 +				// "Call insertNode(container) on the active range."
  1.7598 +				getActiveRange().insertNode(container);
  1.7599 +
  1.7600 +				// "Call createElement("br") on the context object, and append
  1.7601 +				// the result as the last child of container."
  1.7602 +				container.appendChild(document.createElement("br"));
  1.7603 +
  1.7604 +				// "Call collapse(container, 0) on the context object's
  1.7605 +				// Selection."
  1.7606 +				getSelection().collapse(container, 0);
  1.7607 +				getActiveRange().setStart(container, 0);
  1.7608 +				getActiveRange().setEnd(container, 0);
  1.7609 +
  1.7610 +				// "Return true."
  1.7611 +				return true;
  1.7612 +			}
  1.7613 +
  1.7614 +			// "While the nextSibling of the last member of node list is not
  1.7615 +			// null and is an allowed child of "p", append it to node list."
  1.7616 +			while (nodeList[nodeList.length - 1].nextSibling
  1.7617 +			&& isAllowedChild(nodeList[nodeList.length - 1].nextSibling, "p")) {
  1.7618 +				nodeList.push(nodeList[nodeList.length - 1].nextSibling);
  1.7619 +			}
  1.7620 +
  1.7621 +			// "Wrap node list, with sibling criteria returning false and new
  1.7622 +			// parent instructions returning the result of calling
  1.7623 +			// createElement(tag) on the context object. Set container to the
  1.7624 +			// result."
  1.7625 +			container = wrap(nodeList,
  1.7626 +				function() { return false },
  1.7627 +				function() { return document.createElement(tag) }
  1.7628 +			);
  1.7629 +		}
  1.7630 +
  1.7631 +		// "If container's local name is "address", "listing", or "pre":"
  1.7632 +		if (container.tagName == "ADDRESS"
  1.7633 +		|| container.tagName == "LISTING"
  1.7634 +		|| container.tagName == "PRE") {
  1.7635 +			// "Let br be the result of calling createElement("br") on the
  1.7636 +			// context object."
  1.7637 +			var br = document.createElement("br");
  1.7638 +
  1.7639 +			// "Call insertNode(br) on the active range."
  1.7640 +			getActiveRange().insertNode(br);
  1.7641 +
  1.7642 +			// "Call collapse(node, offset + 1) on the context object's
  1.7643 +			// Selection."
  1.7644 +			getSelection().collapse(node, offset + 1);
  1.7645 +			getActiveRange().setStart(node, offset + 1);
  1.7646 +			getActiveRange().setEnd(node, offset + 1);
  1.7647 +
  1.7648 +			// "If br is the last descendant of container, let br be the result
  1.7649 +			// of calling createElement("br") on the context object, then call
  1.7650 +			// insertNode(br) on the active range."
  1.7651 +			//
  1.7652 +			// Work around browser bugs: some browsers select the
  1.7653 +			// newly-inserted node, not per spec.
  1.7654 +			if (!isDescendant(nextNode(br), container)) {
  1.7655 +				getActiveRange().insertNode(document.createElement("br"));
  1.7656 +				getSelection().collapse(node, offset + 1);
  1.7657 +				getActiveRange().setEnd(node, offset + 1);
  1.7658 +			}
  1.7659 +
  1.7660 +			// "Return true."
  1.7661 +			return true;
  1.7662 +		}
  1.7663 +
  1.7664 +		// "If container's local name is "li", "dt", or "dd"; and either it has
  1.7665 +		// no children or it has a single child and that child is a br:"
  1.7666 +		if (["LI", "DT", "DD"].indexOf(container.tagName) != -1
  1.7667 +		&& (!container.hasChildNodes()
  1.7668 +		|| (container.childNodes.length == 1
  1.7669 +		&& isHtmlElement(container.firstChild, "br")))) {
  1.7670 +			// "Split the parent of the one-node list consisting of container."
  1.7671 +			splitParent([container]);
  1.7672 +
  1.7673 +			// "If container has no children, call createElement("br") on the
  1.7674 +			// context object and append the result as the last child of
  1.7675 +			// container."
  1.7676 +			if (!container.hasChildNodes()) {
  1.7677 +				container.appendChild(document.createElement("br"));
  1.7678 +			}
  1.7679 +
  1.7680 +			// "If container is a dd or dt, and it is not an allowed child of
  1.7681 +			// any of its ancestors in the same editing host, set the tag name
  1.7682 +			// of container to the default single-line container name and let
  1.7683 +			// container be the result."
  1.7684 +			if (isHtmlElement(container, ["dd", "dt"])
  1.7685 +			&& getAncestors(container).every(function(ancestor) {
  1.7686 +				return !inSameEditingHost(container, ancestor)
  1.7687 +					|| !isAllowedChild(container, ancestor)
  1.7688 +			})) {
  1.7689 +				container = setTagName(container, defaultSingleLineContainerName);
  1.7690 +			}
  1.7691 +
  1.7692 +			// "Fix disallowed ancestors of container."
  1.7693 +			fixDisallowedAncestors(container);
  1.7694 +
  1.7695 +			// "Return true."
  1.7696 +			return true;
  1.7697 +		}
  1.7698 +
  1.7699 +		// "Let new line range be a new range whose start is the same as
  1.7700 +		// the active range's, and whose end is (container, length of
  1.7701 +		// container)."
  1.7702 +		var newLineRange = document.createRange();
  1.7703 +		newLineRange.setStart(getActiveRange().startContainer, getActiveRange().startOffset);
  1.7704 +		newLineRange.setEnd(container, getNodeLength(container));
  1.7705 +
  1.7706 +		// "While new line range's start offset is zero and its start node is
  1.7707 +		// not a prohibited paragraph child, set its start to (parent of start
  1.7708 +		// node, index of start node)."
  1.7709 +		while (newLineRange.startOffset == 0
  1.7710 +		&& !isProhibitedParagraphChild(newLineRange.startContainer)) {
  1.7711 +			newLineRange.setStart(newLineRange.startContainer.parentNode, getNodeIndex(newLineRange.startContainer));
  1.7712 +		}
  1.7713 +
  1.7714 +		// "While new line range's start offset is the length of its start node
  1.7715 +		// and its start node is not a prohibited paragraph child, set its
  1.7716 +		// start to (parent of start node, 1 + index of start node)."
  1.7717 +		while (newLineRange.startOffset == getNodeLength(newLineRange.startContainer)
  1.7718 +		&& !isProhibitedParagraphChild(newLineRange.startContainer)) {
  1.7719 +			newLineRange.setStart(newLineRange.startContainer.parentNode, 1 + getNodeIndex(newLineRange.startContainer));
  1.7720 +		}
  1.7721 +
  1.7722 +		// "Let end of line be true if new line range contains either nothing
  1.7723 +		// or a single br, and false otherwise."
  1.7724 +		var containedInNewLineRange = getContainedNodes(newLineRange);
  1.7725 +		var endOfLine = !containedInNewLineRange.length
  1.7726 +			|| (containedInNewLineRange.length == 1
  1.7727 +			&& isHtmlElement(containedInNewLineRange[0], "br"));
  1.7728 +
  1.7729 +		// "If the local name of container is "h1", "h2", "h3", "h4", "h5", or
  1.7730 +		// "h6", and end of line is true, let new container name be the default
  1.7731 +		// single-line container name."
  1.7732 +		var newContainerName;
  1.7733 +		if (/^H[1-6]$/.test(container.tagName)
  1.7734 +		&& endOfLine) {
  1.7735 +			newContainerName = defaultSingleLineContainerName;
  1.7736 +
  1.7737 +		// "Otherwise, if the local name of container is "dt" and end of line
  1.7738 +		// is true, let new container name be "dd"."
  1.7739 +		} else if (container.tagName == "DT"
  1.7740 +		&& endOfLine) {
  1.7741 +			newContainerName = "dd";
  1.7742 +
  1.7743 +		// "Otherwise, if the local name of container is "dd" and end of line
  1.7744 +		// is true, let new container name be "dt"."
  1.7745 +		} else if (container.tagName == "DD"
  1.7746 +		&& endOfLine) {
  1.7747 +			newContainerName = "dt";
  1.7748 +
  1.7749 +		// "Otherwise, let new container name be the local name of container."
  1.7750 +		} else {
  1.7751 +			newContainerName = container.tagName.toLowerCase();
  1.7752 +		}
  1.7753 +
  1.7754 +		// "Let new container be the result of calling createElement(new
  1.7755 +		// container name) on the context object."
  1.7756 +		var newContainer = document.createElement(newContainerName);
  1.7757 +
  1.7758 +		// "Copy all attributes of container to new container."
  1.7759 +		for (var i = 0; i < container.attributes.length; i++) {
  1.7760 +			newContainer.setAttributeNS(container.attributes[i].namespaceURI, container.attributes[i].name, container.attributes[i].value);
  1.7761 +		}
  1.7762 +
  1.7763 +		// "If new container has an id attribute, unset it."
  1.7764 +		newContainer.removeAttribute("id");
  1.7765 +
  1.7766 +		// "Insert new container into the parent of container immediately after
  1.7767 +		// container."
  1.7768 +		container.parentNode.insertBefore(newContainer, container.nextSibling);
  1.7769 +
  1.7770 +		// "Let contained nodes be all nodes contained in new line range."
  1.7771 +		var containedNodes = getAllContainedNodes(newLineRange);
  1.7772 +
  1.7773 +		// "Let frag be the result of calling extractContents() on new line
  1.7774 +		// range."
  1.7775 +		var frag = newLineRange.extractContents();
  1.7776 +
  1.7777 +		// "Unset the id attribute (if any) of each Element descendant of frag
  1.7778 +		// that is not in contained nodes."
  1.7779 +		var descendants = getDescendants(frag);
  1.7780 +		for (var i = 0; i < descendants.length; i++) {
  1.7781 +			if (descendants[i].nodeType == Node.ELEMENT_NODE
  1.7782 +			&& containedNodes.indexOf(descendants[i]) == -1) {
  1.7783 +				descendants[i].removeAttribute("id");
  1.7784 +			}
  1.7785 +		}
  1.7786 +
  1.7787 +		// "Call appendChild(frag) on new container."
  1.7788 +		newContainer.appendChild(frag);
  1.7789 +
  1.7790 +		// "While container's lastChild is a prohibited paragraph child, set
  1.7791 +		// container to its lastChild."
  1.7792 +		while (isProhibitedParagraphChild(container.lastChild)) {
  1.7793 +			container = container.lastChild;
  1.7794 +		}
  1.7795 +
  1.7796 +		// "While new container's lastChild is a prohibited paragraph child,
  1.7797 +		// set new container to its lastChild."
  1.7798 +		while (isProhibitedParagraphChild(newContainer.lastChild)) {
  1.7799 +			newContainer = newContainer.lastChild;
  1.7800 +		}
  1.7801 +
  1.7802 +		// "If container has no visible children, call createElement("br") on
  1.7803 +		// the context object, and append the result as the last child of
  1.7804 +		// container."
  1.7805 +		if (![].some.call(container.childNodes, isVisible)) {
  1.7806 +			container.appendChild(document.createElement("br"));
  1.7807 +		}
  1.7808 +
  1.7809 +		// "If new container has no visible children, call createElement("br")
  1.7810 +		// on the context object, and append the result as the last child of
  1.7811 +		// new container."
  1.7812 +		if (![].some.call(newContainer.childNodes, isVisible)) {
  1.7813 +			newContainer.appendChild(document.createElement("br"));
  1.7814 +		}
  1.7815 +
  1.7816 +		// "Call collapse(new container, 0) on the context object's Selection."
  1.7817 +		getSelection().collapse(newContainer, 0);
  1.7818 +		getActiveRange().setStart(newContainer, 0);
  1.7819 +		getActiveRange().setEnd(newContainer, 0);
  1.7820 +
  1.7821 +		// "Return true."
  1.7822 +		return true;
  1.7823 +	}
  1.7824 +};
  1.7825 +
  1.7826 +//@}
  1.7827 +///// The insertText command /////
  1.7828 +//@{
  1.7829 +commands.inserttext = {
  1.7830 +	action: function(value) {
  1.7831 +		// "Delete the selection, with strip wrappers false."
  1.7832 +		deleteSelection({stripWrappers: false});
  1.7833 +
  1.7834 +		// "If the active range's start node is neither editable nor an editing
  1.7835 +		// host, return true."
  1.7836 +		if (!isEditable(getActiveRange().startContainer)
  1.7837 +		&& !isEditingHost(getActiveRange().startContainer)) {
  1.7838 +			return true;
  1.7839 +		}
  1.7840 +
  1.7841 +		// "If value's length is greater than one:"
  1.7842 +		if (value.length > 1) {
  1.7843 +			// "For each element el in value, take the action for the
  1.7844 +			// insertText command, with value equal to el."
  1.7845 +			for (var i = 0; i < value.length; i++) {
  1.7846 +				commands.inserttext.action(value[i]);
  1.7847 +			}
  1.7848 +
  1.7849 +			// "Return true."
  1.7850 +			return true;
  1.7851 +		}
  1.7852 +
  1.7853 +		// "If value is the empty string, return true."
  1.7854 +		if (value == "") {
  1.7855 +			return true;
  1.7856 +		}
  1.7857 +
  1.7858 +		// "If value is a newline (U+00A0), take the action for the
  1.7859 +		// insertParagraph command and return true."
  1.7860 +		if (value == "\n") {
  1.7861 +			commands.insertparagraph.action();
  1.7862 +			return true;
  1.7863 +		}
  1.7864 +
  1.7865 +		// "Let node and offset be the active range's start node and offset."
  1.7866 +		var node = getActiveRange().startContainer;
  1.7867 +		var offset = getActiveRange().startOffset;
  1.7868 +
  1.7869 +		// "If node has a child whose index is offset − 1, and that child is a
  1.7870 +		// Text node, set node to that child, then set offset to node's
  1.7871 +		// length."
  1.7872 +		if (0 <= offset - 1
  1.7873 +		&& offset - 1 < node.childNodes.length
  1.7874 +		&& node.childNodes[offset - 1].nodeType == Node.TEXT_NODE) {
  1.7875 +			node = node.childNodes[offset - 1];
  1.7876 +			offset = getNodeLength(node);
  1.7877 +		}
  1.7878 +
  1.7879 +		// "If node has a child whose index is offset, and that child is a Text
  1.7880 +		// node, set node to that child, then set offset to zero."
  1.7881 +		if (0 <= offset
  1.7882 +		&& offset < node.childNodes.length
  1.7883 +		&& node.childNodes[offset].nodeType == Node.TEXT_NODE) {
  1.7884 +			node = node.childNodes[offset];
  1.7885 +			offset = 0;
  1.7886 +		}
  1.7887 +
  1.7888 +		// "Record current overrides, and let overrides be the result."
  1.7889 +		var overrides = recordCurrentOverrides();
  1.7890 +
  1.7891 +		// "Call collapse(node, offset) on the context object's Selection."
  1.7892 +		getSelection().collapse(node, offset);
  1.7893 +		getActiveRange().setStart(node, offset);
  1.7894 +		getActiveRange().setEnd(node, offset);
  1.7895 +
  1.7896 +		// "Canonicalize whitespace at (node, offset)."
  1.7897 +		canonicalizeWhitespace(node, offset);
  1.7898 +
  1.7899 +		// "Let (node, offset) be the active range's start."
  1.7900 +		node = getActiveRange().startContainer;
  1.7901 +		offset = getActiveRange().startOffset;
  1.7902 +
  1.7903 +		// "If node is a Text node:"
  1.7904 +		if (node.nodeType == Node.TEXT_NODE) {
  1.7905 +			// "Call insertData(offset, value) on node."
  1.7906 +			node.insertData(offset, value);
  1.7907 +
  1.7908 +			// "Call collapse(node, offset) on the context object's Selection."
  1.7909 +			getSelection().collapse(node, offset);
  1.7910 +			getActiveRange().setStart(node, offset);
  1.7911 +
  1.7912 +			// "Call extend(node, offset + 1) on the context object's
  1.7913 +			// Selection."
  1.7914 +			//
  1.7915 +			// Work around WebKit bug: the extend() can throw if the text we're
  1.7916 +			// adding is trailing whitespace.
  1.7917 +			try { getSelection().extend(node, offset + 1); } catch(e) {}
  1.7918 +			getActiveRange().setEnd(node, offset + 1);
  1.7919 +
  1.7920 +		// "Otherwise:"
  1.7921 +		} else {
  1.7922 +			// "If node has only one child, which is a collapsed line break,
  1.7923 +			// remove its child from it."
  1.7924 +			//
  1.7925 +			// FIXME: IE incorrectly returns false here instead of true
  1.7926 +			// sometimes?
  1.7927 +			if (node.childNodes.length == 1
  1.7928 +			&& isCollapsedLineBreak(node.firstChild)) {
  1.7929 +				node.removeChild(node.firstChild);
  1.7930 +			}
  1.7931 +
  1.7932 +			// "Let text be the result of calling createTextNode(value) on the
  1.7933 +			// context object."
  1.7934 +			var text = document.createTextNode(value);
  1.7935 +
  1.7936 +			// "Call insertNode(text) on the active range."
  1.7937 +			getActiveRange().insertNode(text);
  1.7938 +
  1.7939 +			// "Call collapse(text, 0) on the context object's Selection."
  1.7940 +			getSelection().collapse(text, 0);
  1.7941 +			getActiveRange().setStart(text, 0);
  1.7942 +
  1.7943 +			// "Call extend(text, 1) on the context object's Selection."
  1.7944 +			getSelection().extend(text, 1);
  1.7945 +			getActiveRange().setEnd(text, 1);
  1.7946 +		}
  1.7947 +
  1.7948 +		// "Restore states and values from overrides."
  1.7949 +		restoreStatesAndValues(overrides);
  1.7950 +
  1.7951 +		// "Canonicalize whitespace at the active range's start, with fix
  1.7952 +		// collapsed space false."
  1.7953 +		canonicalizeWhitespace(getActiveRange().startContainer, getActiveRange().startOffset, false);
  1.7954 +
  1.7955 +		// "Canonicalize whitespace at the active range's end, with fix
  1.7956 +		// collapsed space false."
  1.7957 +		canonicalizeWhitespace(getActiveRange().endContainer, getActiveRange().endOffset, false);
  1.7958 +
  1.7959 +		// "If value is a space character, autolink the active range's start."
  1.7960 +		if (/^[ \t\n\f\r]$/.test(value)) {
  1.7961 +			autolink(getActiveRange().startContainer, getActiveRange().startOffset);
  1.7962 +		}
  1.7963 +
  1.7964 +		// "Call collapseToEnd() on the context object's Selection."
  1.7965 +		//
  1.7966 +		// Work around WebKit bug: sometimes it blows up the selection and
  1.7967 +		// throws, which we don't want.
  1.7968 +		try { getSelection().collapseToEnd(); } catch(e) {}
  1.7969 +		getActiveRange().collapse(false);
  1.7970 +
  1.7971 +		// "Return true."
  1.7972 +		return true;
  1.7973 +	}
  1.7974 +};
  1.7975 +
  1.7976 +//@}
  1.7977 +///// The insertUnorderedList command /////
  1.7978 +//@{
  1.7979 +commands.insertunorderedlist = {
  1.7980 +	preservesOverrides: true,
  1.7981 +	// "Toggle lists with tag name "ul", then return true."
  1.7982 +	action: function() { toggleLists("ul"); return true },
  1.7983 +	// "True if the selection's list state is "mixed" or "mixed ul", false
  1.7984 +	// otherwise."
  1.7985 +	indeterm: function() { return /^mixed( ul)?$/.test(getSelectionListState()) },
  1.7986 +	// "True if the selection's list state is "ul", false otherwise."
  1.7987 +	state: function() { return getSelectionListState() == "ul" },
  1.7988 +};
  1.7989 +
  1.7990 +//@}
  1.7991 +///// The justifyCenter command /////
  1.7992 +//@{
  1.7993 +commands.justifycenter = {
  1.7994 +	preservesOverrides: true,
  1.7995 +	// "Justify the selection with alignment "center", then return true."
  1.7996 +	action: function() { justifySelection("center"); return true },
  1.7997 +	indeterm: function() {
  1.7998 +		// "Return false if the active range is null.  Otherwise, block-extend
  1.7999 +		// the active range. Return true if among visible editable nodes that
  1.8000 +		// are contained in the result and have no children, at least one has
  1.8001 +		// alignment value "center" and at least one does not. Otherwise return
  1.8002 +		// false."
  1.8003 +		if (!getActiveRange()) {
  1.8004 +			return false;
  1.8005 +		}
  1.8006 +		var nodes = getAllContainedNodes(blockExtend(getActiveRange()), function(node) {
  1.8007 +			return isEditable(node) && isVisible(node) && !node.hasChildNodes();
  1.8008 +		});
  1.8009 +		return nodes.some(function(node) { return getAlignmentValue(node) == "center" })
  1.8010 +			&& nodes.some(function(node) { return getAlignmentValue(node) != "center" });
  1.8011 +	}, state: function() {
  1.8012 +		// "Return false if the active range is null.  Otherwise, block-extend
  1.8013 +		// the active range. Return true if there is at least one visible
  1.8014 +		// editable node that is contained in the result and has no children,
  1.8015 +		// and all such nodes have alignment value "center".  Otherwise return
  1.8016 +		// false."
  1.8017 +		if (!getActiveRange()) {
  1.8018 +			return false;
  1.8019 +		}
  1.8020 +		var nodes = getAllContainedNodes(blockExtend(getActiveRange()), function(node) {
  1.8021 +			return isEditable(node) && isVisible(node) && !node.hasChildNodes();
  1.8022 +		});
  1.8023 +		return nodes.length
  1.8024 +			&& nodes.every(function(node) { return getAlignmentValue(node) == "center" });
  1.8025 +	}, value: function() {
  1.8026 +		// "Return the empty string if the active range is null.  Otherwise,
  1.8027 +		// block-extend the active range, and return the alignment value of the
  1.8028 +		// first visible editable node that is contained in the result and has
  1.8029 +		// no children. If there is no such node, return "left"."
  1.8030 +		if (!getActiveRange()) {
  1.8031 +			return "";
  1.8032 +		}
  1.8033 +		var nodes = getAllContainedNodes(blockExtend(getActiveRange()), function(node) {
  1.8034 +			return isEditable(node) && isVisible(node) && !node.hasChildNodes();
  1.8035 +		});
  1.8036 +		if (nodes.length) {
  1.8037 +			return getAlignmentValue(nodes[0]);
  1.8038 +		} else {
  1.8039 +			return "left";
  1.8040 +		}
  1.8041 +	},
  1.8042 +};
  1.8043 +
  1.8044 +//@}
  1.8045 +///// The justifyFull command /////
  1.8046 +//@{
  1.8047 +commands.justifyfull = {
  1.8048 +	preservesOverrides: true,
  1.8049 +	// "Justify the selection with alignment "justify", then return true."
  1.8050 +	action: function() { justifySelection("justify"); return true },
  1.8051 +	indeterm: function() {
  1.8052 +		// "Return false if the active range is null.  Otherwise, block-extend
  1.8053 +		// the active range. Return true if among visible editable nodes that
  1.8054 +		// are contained in the result and have no children, at least one has
  1.8055 +		// alignment value "justify" and at least one does not. Otherwise
  1.8056 +		// return false."
  1.8057 +		if (!getActiveRange()) {
  1.8058 +			return false;
  1.8059 +		}
  1.8060 +		var nodes = getAllContainedNodes(blockExtend(getActiveRange()), function(node) {
  1.8061 +			return isEditable(node) && isVisible(node) && !node.hasChildNodes();
  1.8062 +		});
  1.8063 +		return nodes.some(function(node) { return getAlignmentValue(node) == "justify" })
  1.8064 +			&& nodes.some(function(node) { return getAlignmentValue(node) != "justify" });
  1.8065 +	}, state: function() {
  1.8066 +		// "Return false if the active range is null.  Otherwise, block-extend
  1.8067 +		// the active range. Return true if there is at least one visible
  1.8068 +		// editable node that is contained in the result and has no children,
  1.8069 +		// and all such nodes have alignment value "justify".  Otherwise return
  1.8070 +		// false."
  1.8071 +		if (!getActiveRange()) {
  1.8072 +			return false;
  1.8073 +		}
  1.8074 +		var nodes = getAllContainedNodes(blockExtend(getActiveRange()), function(node) {
  1.8075 +			return isEditable(node) && isVisible(node) && !node.hasChildNodes();
  1.8076 +		});
  1.8077 +		return nodes.length
  1.8078 +			&& nodes.every(function(node) { return getAlignmentValue(node) == "justify" });
  1.8079 +	}, value: function() {
  1.8080 +		// "Return the empty string if the active range is null.  Otherwise,
  1.8081 +		// block-extend the active range, and return the alignment value of the
  1.8082 +		// first visible editable node that is contained in the result and has
  1.8083 +		// no children. If there is no such node, return "left"."
  1.8084 +		if (!getActiveRange()) {
  1.8085 +			return "";
  1.8086 +		}
  1.8087 +		var nodes = getAllContainedNodes(blockExtend(getActiveRange()), function(node) {
  1.8088 +			return isEditable(node) && isVisible(node) && !node.hasChildNodes();
  1.8089 +		});
  1.8090 +		if (nodes.length) {
  1.8091 +			return getAlignmentValue(nodes[0]);
  1.8092 +		} else {
  1.8093 +			return "left";
  1.8094 +		}
  1.8095 +	},
  1.8096 +};
  1.8097 +
  1.8098 +//@}
  1.8099 +///// The justifyLeft command /////
  1.8100 +//@{
  1.8101 +commands.justifyleft = {
  1.8102 +	preservesOverrides: true,
  1.8103 +	// "Justify the selection with alignment "left", then return true."
  1.8104 +	action: function() { justifySelection("left"); return true },
  1.8105 +	indeterm: function() {
  1.8106 +		// "Return false if the active range is null.  Otherwise, block-extend
  1.8107 +		// the active range. Return true if among visible editable nodes that
  1.8108 +		// are contained in the result and have no children, at least one has
  1.8109 +		// alignment value "left" and at least one does not. Otherwise return
  1.8110 +		// false."
  1.8111 +		if (!getActiveRange()) {
  1.8112 +			return false;
  1.8113 +		}
  1.8114 +		var nodes = getAllContainedNodes(blockExtend(getActiveRange()), function(node) {
  1.8115 +			return isEditable(node) && isVisible(node) && !node.hasChildNodes();
  1.8116 +		});
  1.8117 +		return nodes.some(function(node) { return getAlignmentValue(node) == "left" })
  1.8118 +			&& nodes.some(function(node) { return getAlignmentValue(node) != "left" });
  1.8119 +	}, state: function() {
  1.8120 +		// "Return false if the active range is null.  Otherwise, block-extend
  1.8121 +		// the active range. Return true if there is at least one visible
  1.8122 +		// editable node that is contained in the result and has no children,
  1.8123 +		// and all such nodes have alignment value "left".  Otherwise return
  1.8124 +		// false."
  1.8125 +		if (!getActiveRange()) {
  1.8126 +			return false;
  1.8127 +		}
  1.8128 +		var nodes = getAllContainedNodes(blockExtend(getActiveRange()), function(node) {
  1.8129 +			return isEditable(node) && isVisible(node) && !node.hasChildNodes();
  1.8130 +		});
  1.8131 +		return nodes.length
  1.8132 +			&& nodes.every(function(node) { return getAlignmentValue(node) == "left" });
  1.8133 +	}, value: function() {
  1.8134 +		// "Return the empty string if the active range is null.  Otherwise,
  1.8135 +		// block-extend the active range, and return the alignment value of the
  1.8136 +		// first visible editable node that is contained in the result and has
  1.8137 +		// no children. If there is no such node, return "left"."
  1.8138 +		if (!getActiveRange()) {
  1.8139 +			return "";
  1.8140 +		}
  1.8141 +		var nodes = getAllContainedNodes(blockExtend(getActiveRange()), function(node) {
  1.8142 +			return isEditable(node) && isVisible(node) && !node.hasChildNodes();
  1.8143 +		});
  1.8144 +		if (nodes.length) {
  1.8145 +			return getAlignmentValue(nodes[0]);
  1.8146 +		} else {
  1.8147 +			return "left";
  1.8148 +		}
  1.8149 +	},
  1.8150 +};
  1.8151 +
  1.8152 +//@}
  1.8153 +///// The justifyRight command /////
  1.8154 +//@{
  1.8155 +commands.justifyright = {
  1.8156 +	preservesOverrides: true,
  1.8157 +	// "Justify the selection with alignment "right", then return true."
  1.8158 +	action: function() { justifySelection("right"); return true },
  1.8159 +	indeterm: function() {
  1.8160 +		// "Return false if the active range is null.  Otherwise, block-extend
  1.8161 +		// the active range. Return true if among visible editable nodes that
  1.8162 +		// are contained in the result and have no children, at least one has
  1.8163 +		// alignment value "right" and at least one does not. Otherwise return
  1.8164 +		// false."
  1.8165 +		if (!getActiveRange()) {
  1.8166 +			return false;
  1.8167 +		}
  1.8168 +		var nodes = getAllContainedNodes(blockExtend(getActiveRange()), function(node) {
  1.8169 +			return isEditable(node) && isVisible(node) && !node.hasChildNodes();
  1.8170 +		});
  1.8171 +		return nodes.some(function(node) { return getAlignmentValue(node) == "right" })
  1.8172 +			&& nodes.some(function(node) { return getAlignmentValue(node) != "right" });
  1.8173 +	}, state: function() {
  1.8174 +		// "Return false if the active range is null.  Otherwise, block-extend
  1.8175 +		// the active range. Return true if there is at least one visible
  1.8176 +		// editable node that is contained in the result and has no children,
  1.8177 +		// and all such nodes have alignment value "right".  Otherwise return
  1.8178 +		// false."
  1.8179 +		if (!getActiveRange()) {
  1.8180 +			return false;
  1.8181 +		}
  1.8182 +		var nodes = getAllContainedNodes(blockExtend(getActiveRange()), function(node) {
  1.8183 +			return isEditable(node) && isVisible(node) && !node.hasChildNodes();
  1.8184 +		});
  1.8185 +		return nodes.length
  1.8186 +			&& nodes.every(function(node) { return getAlignmentValue(node) == "right" });
  1.8187 +	}, value: function() {
  1.8188 +		// "Return the empty string if the active range is null.  Otherwise,
  1.8189 +		// block-extend the active range, and return the alignment value of the
  1.8190 +		// first visible editable node that is contained in the result and has
  1.8191 +		// no children. If there is no such node, return "left"."
  1.8192 +		if (!getActiveRange()) {
  1.8193 +			return "";
  1.8194 +		}
  1.8195 +		var nodes = getAllContainedNodes(blockExtend(getActiveRange()), function(node) {
  1.8196 +			return isEditable(node) && isVisible(node) && !node.hasChildNodes();
  1.8197 +		});
  1.8198 +		if (nodes.length) {
  1.8199 +			return getAlignmentValue(nodes[0]);
  1.8200 +		} else {
  1.8201 +			return "left";
  1.8202 +		}
  1.8203 +	},
  1.8204 +};
  1.8205 +
  1.8206 +//@}
  1.8207 +///// The outdent command /////
  1.8208 +//@{
  1.8209 +commands.outdent = {
  1.8210 +	preservesOverrides: true,
  1.8211 +	action: function() {
  1.8212 +		// "Let items be a list of all lis that are ancestor containers of the
  1.8213 +		// range's start and/or end node."
  1.8214 +		//
  1.8215 +		// It's annoying to get this in tree order using functional stuff
  1.8216 +		// without doing getDescendants(document), which is slow, so I do it
  1.8217 +		// imperatively.
  1.8218 +		var items = [];
  1.8219 +		(function(){
  1.8220 +			for (
  1.8221 +				var ancestorContainer = getActiveRange().endContainer;
  1.8222 +				ancestorContainer != getActiveRange().commonAncestorContainer;
  1.8223 +				ancestorContainer = ancestorContainer.parentNode
  1.8224 +			) {
  1.8225 +				if (isHtmlElement(ancestorContainer, "li")) {
  1.8226 +					items.unshift(ancestorContainer);
  1.8227 +				}
  1.8228 +			}
  1.8229 +			for (
  1.8230 +				var ancestorContainer = getActiveRange().startContainer;
  1.8231 +				ancestorContainer;
  1.8232 +				ancestorContainer = ancestorContainer.parentNode
  1.8233 +			) {
  1.8234 +				if (isHtmlElement(ancestorContainer, "li")) {
  1.8235 +					items.unshift(ancestorContainer);
  1.8236 +				}
  1.8237 +			}
  1.8238 +		})();
  1.8239 +
  1.8240 +		// "For each item in items, normalize sublists of item."
  1.8241 +		items.forEach(normalizeSublists);
  1.8242 +
  1.8243 +		// "Block-extend the active range, and let new range be the result."
  1.8244 +		var newRange = blockExtend(getActiveRange());
  1.8245 +
  1.8246 +		// "Let node list be a list of nodes, initially empty."
  1.8247 +		//
  1.8248 +		// "For each node node contained in new range, append node to node list
  1.8249 +		// if the last member of node list (if any) is not an ancestor of node;
  1.8250 +		// node is editable; and either node has no editable descendants, or is
  1.8251 +		// an ol or ul, or is an li whose parent is an ol or ul."
  1.8252 +		var nodeList = getContainedNodes(newRange, function(node) {
  1.8253 +			return isEditable(node)
  1.8254 +				&& (!getDescendants(node).some(isEditable)
  1.8255 +				|| isHtmlElement(node, ["ol", "ul"])
  1.8256 +				|| (isHtmlElement(node, "li") && isHtmlElement(node.parentNode, ["ol", "ul"])));
  1.8257 +		});
  1.8258 +
  1.8259 +		// "While node list is not empty:"
  1.8260 +		while (nodeList.length) {
  1.8261 +			// "While the first member of node list is an ol or ul or is not
  1.8262 +			// the child of an ol or ul, outdent it and remove it from node
  1.8263 +			// list."
  1.8264 +			while (nodeList.length
  1.8265 +			&& (isHtmlElement(nodeList[0], ["OL", "UL"])
  1.8266 +			|| !isHtmlElement(nodeList[0].parentNode, ["OL", "UL"]))) {
  1.8267 +				outdentNode(nodeList.shift());
  1.8268 +			}
  1.8269 +
  1.8270 +			// "If node list is empty, break from these substeps."
  1.8271 +			if (!nodeList.length) {
  1.8272 +				break;
  1.8273 +			}
  1.8274 +
  1.8275 +			// "Let sublist be a list of nodes, initially empty."
  1.8276 +			var sublist = [];
  1.8277 +
  1.8278 +			// "Remove the first member of node list and append it to sublist."
  1.8279 +			sublist.push(nodeList.shift());
  1.8280 +
  1.8281 +			// "While the first member of node list is the nextSibling of the
  1.8282 +			// last member of sublist, and the first member of node list is not
  1.8283 +			// an ol or ul, remove the first member of node list and append it
  1.8284 +			// to sublist."
  1.8285 +			while (nodeList.length
  1.8286 +			&& nodeList[0] == sublist[sublist.length - 1].nextSibling
  1.8287 +			&& !isHtmlElement(nodeList[0], ["OL", "UL"])) {
  1.8288 +				sublist.push(nodeList.shift());
  1.8289 +			}
  1.8290 +
  1.8291 +			// "Record the values of sublist, and let values be the result."
  1.8292 +			var values = recordValues(sublist);
  1.8293 +
  1.8294 +			// "Split the parent of sublist, with new parent null."
  1.8295 +			splitParent(sublist);
  1.8296 +
  1.8297 +			// "Fix disallowed ancestors of each member of sublist."
  1.8298 +			sublist.forEach(fixDisallowedAncestors);
  1.8299 +
  1.8300 +			// "Restore the values from values."
  1.8301 +			restoreValues(values);
  1.8302 +		}
  1.8303 +
  1.8304 +		// "Return true."
  1.8305 +		return true;
  1.8306 +	}
  1.8307 +};
  1.8308 +
  1.8309 +//@}
  1.8310 +
  1.8311 +//////////////////////////////////
  1.8312 +///// Miscellaneous commands /////
  1.8313 +//////////////////////////////////
  1.8314 +
  1.8315 +///// The defaultParagraphSeparator command /////
  1.8316 +//@{
  1.8317 +commands.defaultparagraphseparator = {
  1.8318 +	action: function(value) {
  1.8319 +		// "Let value be converted to ASCII lowercase. If value is then equal
  1.8320 +		// to "p" or "div", set the context object's default single-line
  1.8321 +		// container name to value and return true. Otherwise, return false."
  1.8322 +		value = value.toLowerCase();
  1.8323 +		if (value == "p" || value == "div") {
  1.8324 +			defaultSingleLineContainerName = value;
  1.8325 +			return true;
  1.8326 +		}
  1.8327 +		return false;
  1.8328 +	}, value: function() {
  1.8329 +		// "Return the context object's default single-line container name."
  1.8330 +		return defaultSingleLineContainerName;
  1.8331 +	},
  1.8332 +};
  1.8333 +
  1.8334 +//@}
  1.8335 +///// The selectAll command /////
  1.8336 +//@{
  1.8337 +commands.selectall = {
  1.8338 +	// Note, this ignores the whole globalRange/getActiveRange() thing and
  1.8339 +	// works with actual selections.  Not suitable for autoimplementation.html.
  1.8340 +	action: function() {
  1.8341 +		// "Let target be the body element of the context object."
  1.8342 +		var target = document.body;
  1.8343 +
  1.8344 +		// "If target is null, let target be the context object's
  1.8345 +		// documentElement."
  1.8346 +		if (!target) {
  1.8347 +			target = document.documentElement;
  1.8348 +		}
  1.8349 +
  1.8350 +		// "If target is null, call getSelection() on the context object, and
  1.8351 +		// call removeAllRanges() on the result."
  1.8352 +		if (!target) {
  1.8353 +			getSelection().removeAllRanges();
  1.8354 +
  1.8355 +		// "Otherwise, call getSelection() on the context object, and call
  1.8356 +		// selectAllChildren(target) on the result."
  1.8357 +		} else {
  1.8358 +			getSelection().selectAllChildren(target);
  1.8359 +		}
  1.8360 +
  1.8361 +		// "Return true."
  1.8362 +		return true;
  1.8363 +	}
  1.8364 +};
  1.8365 +
  1.8366 +//@}
  1.8367 +///// The styleWithCSS command /////
  1.8368 +//@{
  1.8369 +commands.stylewithcss = {
  1.8370 +	action: function(value) {
  1.8371 +		// "If value is an ASCII case-insensitive match for the string
  1.8372 +		// "false", set the CSS styling flag to false. Otherwise, set the
  1.8373 +		// CSS styling flag to true.  Either way, return true."
  1.8374 +		cssStylingFlag = String(value).toLowerCase() != "false";
  1.8375 +		return true;
  1.8376 +	}, state: function() { return cssStylingFlag }
  1.8377 +};
  1.8378 +
  1.8379 +//@}
  1.8380 +///// The useCSS command /////
  1.8381 +//@{
  1.8382 +commands.usecss = {
  1.8383 +	action: function(value) {
  1.8384 +		// "If value is an ASCII case-insensitive match for the string "false",
  1.8385 +		// set the CSS styling flag to true. Otherwise, set the CSS styling
  1.8386 +		// flag to false.  Either way, return true."
  1.8387 +		cssStylingFlag = String(value).toLowerCase() == "false";
  1.8388 +		return true;
  1.8389 +	}
  1.8390 +};
  1.8391 +//@}
  1.8392 +
  1.8393 +// Some final setup
  1.8394 +//@{
  1.8395 +(function() {
  1.8396 +// Opera 11.50 doesn't implement Object.keys, so I have to make an explicit
  1.8397 +// temporary, which means I need an extra closure to not leak the temporaries
  1.8398 +// into the global namespace.  >:(
  1.8399 +var commandNames = [];
  1.8400 +for (var command in commands) {
  1.8401 +	commandNames.push(command);
  1.8402 +}
  1.8403 +commandNames.forEach(function(command) {
  1.8404 +	// "If a command does not have a relevant CSS property specified, it
  1.8405 +	// defaults to null."
  1.8406 +	if (!("relevantCssProperty" in commands[command])) {
  1.8407 +		commands[command].relevantCssProperty = null;
  1.8408 +	}
  1.8409 +
  1.8410 +	// "If a command has inline command activated values defined but nothing
  1.8411 +	// else defines when it is indeterminate, it is indeterminate if among
  1.8412 +	// formattable nodes effectively contained in the active range, there is at
  1.8413 +	// least one whose effective command value is one of the given values and
  1.8414 +	// at least one whose effective command value is not one of the given
  1.8415 +	// values."
  1.8416 +	if ("inlineCommandActivatedValues" in commands[command]
  1.8417 +	&& !("indeterm" in commands[command])) {
  1.8418 +		commands[command].indeterm = function() {
  1.8419 +			if (!getActiveRange()) {
  1.8420 +				return false;
  1.8421 +			}
  1.8422 +
  1.8423 +			var values = getAllEffectivelyContainedNodes(getActiveRange(), isFormattableNode)
  1.8424 +				.map(function(node) { return getEffectiveCommandValue(node, command) });
  1.8425 +
  1.8426 +			var matchingValues = values.filter(function(value) {
  1.8427 +				return commands[command].inlineCommandActivatedValues.indexOf(value) != -1;
  1.8428 +			});
  1.8429 +
  1.8430 +			return matchingValues.length >= 1
  1.8431 +				&& values.length - matchingValues.length >= 1;
  1.8432 +		};
  1.8433 +	}
  1.8434 +
  1.8435 +	// "If a command has inline command activated values defined, its state is
  1.8436 +	// true if either no formattable node is effectively contained in the
  1.8437 +	// active range, and the active range's start node's effective command
  1.8438 +	// value is one of the given values; or if there is at least one
  1.8439 +	// formattable node effectively contained in the active range, and all of
  1.8440 +	// them have an effective command value equal to one of the given values."
  1.8441 +	if ("inlineCommandActivatedValues" in commands[command]) {
  1.8442 +		commands[command].state = function() {
  1.8443 +			if (!getActiveRange()) {
  1.8444 +				return false;
  1.8445 +			}
  1.8446 +
  1.8447 +			var nodes = getAllEffectivelyContainedNodes(getActiveRange(), isFormattableNode);
  1.8448 +
  1.8449 +			if (nodes.length == 0) {
  1.8450 +				return commands[command].inlineCommandActivatedValues
  1.8451 +					.indexOf(getEffectiveCommandValue(getActiveRange().startContainer, command)) != -1;
  1.8452 +			} else {
  1.8453 +				return nodes.every(function(node) {
  1.8454 +					return commands[command].inlineCommandActivatedValues
  1.8455 +						.indexOf(getEffectiveCommandValue(node, command)) != -1;
  1.8456 +				});
  1.8457 +			}
  1.8458 +		};
  1.8459 +	}
  1.8460 +
  1.8461 +	// "If a command is a standard inline value command, it is indeterminate if
  1.8462 +	// among formattable nodes that are effectively contained in the active
  1.8463 +	// range, there are two that have distinct effective command values. Its
  1.8464 +	// value is the effective command value of the first formattable node that
  1.8465 +	// is effectively contained in the active range; or if there is no such
  1.8466 +	// node, the effective command value of the active range's start node; or
  1.8467 +	// if that is null, the empty string."
  1.8468 +	if ("standardInlineValueCommand" in commands[command]) {
  1.8469 +		commands[command].indeterm = function() {
  1.8470 +			if (!getActiveRange()) {
  1.8471 +				return false;
  1.8472 +			}
  1.8473 +
  1.8474 +			var values = getAllEffectivelyContainedNodes(getActiveRange())
  1.8475 +				.filter(isFormattableNode)
  1.8476 +				.map(function(node) { return getEffectiveCommandValue(node, command) });
  1.8477 +			for (var i = 1; i < values.length; i++) {
  1.8478 +				if (values[i] != values[i - 1]) {
  1.8479 +					return true;
  1.8480 +				}
  1.8481 +			}
  1.8482 +			return false;
  1.8483 +		};
  1.8484 +
  1.8485 +		commands[command].value = function() {
  1.8486 +			if (!getActiveRange()) {
  1.8487 +				return "";
  1.8488 +			}
  1.8489 +
  1.8490 +			var refNode = getAllEffectivelyContainedNodes(getActiveRange(), isFormattableNode)[0];
  1.8491 +
  1.8492 +			if (typeof refNode == "undefined") {
  1.8493 +				refNode = getActiveRange().startContainer;
  1.8494 +			}
  1.8495 +
  1.8496 +			var ret = getEffectiveCommandValue(refNode, command);
  1.8497 +			if (ret === null) {
  1.8498 +				return "";
  1.8499 +			}
  1.8500 +			return ret;
  1.8501 +		};
  1.8502 +	}
  1.8503 +
  1.8504 +	// "If a command preserves overrides, then before taking its action, the
  1.8505 +	// user agent must record current overrides. After taking the action, if
  1.8506 +	// the active range is collapsed, it must restore states and values from
  1.8507 +	// the recorded list."
  1.8508 +	if ("preservesOverrides" in commands[command]) {
  1.8509 +		var oldAction = commands[command].action;
  1.8510 +
  1.8511 +		commands[command].action = function(value) {
  1.8512 +			var overrides = recordCurrentOverrides();
  1.8513 +			var ret = oldAction(value);
  1.8514 +			if (getActiveRange().collapsed) {
  1.8515 +				restoreStatesAndValues(overrides);
  1.8516 +			}
  1.8517 +			return ret;
  1.8518 +		};
  1.8519 +	}
  1.8520 +});
  1.8521 +})();
  1.8522 +//@}
  1.8523 +
  1.8524 +// vim: foldmarker=@{,@} foldmethod=marker

mercurial