1 define(
  2 //['aloha/ecma5'],
  3 ['aloha/ecma5shims', 'aloha/jquery'],
  4 function($_, jQuery) {
  5 	"use strict";
  6 
  7 var htmlNamespace = "http://www.w3.org/1999/xhtml";
  8 
  9 var cssStylingFlag = false;
 10 
 11 // This is bad :(
 12 var globalRange = null;
 13 
 14 // Commands are stored in a dictionary where we call their actions and such
 15 var commands = {};
 16 
 17 ///////////////////////////////////////////////////////////////////////////////
 18 ////////////////////////////// Utility functions //////////////////////////////
 19 ///////////////////////////////////////////////////////////////////////////////
 20 //@{
 21 
 22 /**
 23  * Method to count the number of styles in the given style
 24  */
 25 function getStyleLength(node) {
 26 	if (!node) {
 27 		return 0;
 28 	} else if (!node.style) {
 29 		return 0;
 30 	}
 31 
 32 	// some browsers support .length on styles
 33 	if (typeof node.style.length !== 'undefined') {
 34 		return node.style.length;
 35 	} else {
 36 		// others don't, so we will count
 37 		var styleLength = 0;
 38 		for (var s in node.style) {
 39 			if (node.style[s] && node.style[s] !== 0 && node.style[s] !== 'false') {
 40 				styleLength++;
 41 			}
 42 		}
 43 
 44 		return styleLength;
 45 	}
 46 }
 47 
 48 function toArray(obj) {
 49 	if (!obj) {
 50 		return null;
 51 	}
 52 	var array = [], i, l = obj.length;
 53 	// iterate backwards ensuring that length is an UInt32
 54 	for (i = l >>> 0; i--;) {
 55 		array[i] = obj[i];
 56 	}
 57 	return array;
 58 }
 59 
 60 function nextNode(node) {
 61 	if (node.hasChildNodes()) {
 62 		return node.firstChild;
 63 	}
 64 	return nextNodeDescendants(node);
 65 }
 66 
 67 function previousNode(node) {
 68 	if (node.previousSibling) {
 69 		node = node.previousSibling;
 70 		while (node.hasChildNodes()) {
 71 			node = node.lastChild;
 72 		}
 73 		return node;
 74 	}
 75 	if (node.parentNode
 76 	&& node.parentNode.nodeType == $_.Node.ELEMENT_NODE) {
 77 		return node.parentNode;
 78 	}
 79 	return null;
 80 }
 81 
 82 function nextNodeDescendants(node) {
 83 	while (node && !node.nextSibling) {
 84 		node = node.parentNode;
 85 	}
 86 	if (!node) {
 87 		return null;
 88 	}
 89 	return node.nextSibling;
 90 }
 91 
 92 /**
 93  * Returns true if ancestor is an ancestor of descendant, false otherwise.
 94  */
 95 function isAncestor(ancestor, descendant) {
 96 	return ancestor
 97 		&& descendant
 98 		&& Boolean($_.compareDocumentPosition(ancestor, descendant) & $_.Node.DOCUMENT_POSITION_CONTAINED_BY);
 99 }
100 
101 /**
102  * Returns true if ancestor is an ancestor of or equal to descendant, false
103  * otherwise.
104  */
105 function isAncestorContainer(ancestor, descendant) {
106 	return (ancestor || descendant)
107 		&& (ancestor == descendant || isAncestor(ancestor, descendant));
108 }
109 
110 /**
111  * Returns true if descendant is a descendant of ancestor, false otherwise.
112  */
113 function isDescendant(descendant, ancestor) {
114 	return ancestor
115 		&& descendant
116 		&& Boolean($_.compareDocumentPosition(ancestor, descendant) & $_.Node.DOCUMENT_POSITION_CONTAINED_BY);
117 }
118 
119 /**
120  * Returns true if node1 is before node2 in tree order, false otherwise.
121  */
122 function isBefore(node1, node2) {
123 	return Boolean($_.compareDocumentPosition(node1,node2) & $_.Node.DOCUMENT_POSITION_FOLLOWING);
124 }
125 
126 /**
127  * Returns true if node1 is after node2 in tree order, false otherwise.
128  */
129 function isAfter(node1, node2) {
130 	return Boolean($_.compareDocumentPosition(node1,node2) & $_.Node.DOCUMENT_POSITION_PRECEDING);
131 }
132 
133 function getAncestors(node) {
134 	var ancestors = [];
135 	while (node.parentNode) {
136 		ancestors.unshift(node.parentNode);
137 		node = node.parentNode;
138 	}
139 	return ancestors;
140 }
141 
142 function getDescendants(node) {
143 	var descendants = [];
144 	var stop = nextNodeDescendants(node);
145 	while ((node = nextNode(node))
146 	&& node != stop) {
147 		descendants.push(node);
148 	}
149 	return descendants;
150 }
151 
152 function convertProperty(property) {
153 	// Special-case for now
154 	var map = {
155 		"fontFamily": "font-family",
156 		"fontSize": "font-size",
157 		"fontStyle": "font-style",
158 		"fontWeight": "font-weight",
159 		"textDecoration": "text-decoration"
160 	};
161 	if (typeof map[property] != "undefined") {
162 		return map[property];
163 	}
164 
165 	return property;
166 }
167 
168 // Return the <font size=X> value for the given CSS size, or undefined if there
169 // is none.
170 function cssSizeToLegacy(cssVal) {
171 	return {
172 		"xx-small": 1,
173 		"small": 2,
174 		"medium": 3,
175 		"large": 4,
176 		"x-large": 5,
177 		"xx-large": 6,
178 		"xxx-large": 7
179 	}[cssVal];
180 }
181 
182 // Return the CSS size given a legacy size.
183 function legacySizeToCss(legacyVal) {
184 	return {
185 		1: "xx-small",
186 		2: "small",
187 		3: "medium",
188 		4: "large",
189 		5: "x-large",
190 		6: "xx-large",
191 		7: "xxx-large"
192 	}[legacyVal];
193 }
194 
195 // Opera 11 puts HTML elements in the null namespace, it seems.
196 function isHtmlNamespace(ns) {
197 	return ns === null
198 		|| !ns
199 		|| ns === htmlNamespace;
200 }
201 
202 // "the directionality" from HTML.  I don't bother caring about non-HTML
203 // elements.
204 //
205 // "The directionality of an element is either 'ltr' or 'rtl', and is
206 // determined as per the first appropriate set of steps from the following
207 // list:"
208 function getDirectionality(element) {
209 	// "If the element's dir attribute is in the ltr state
210 	//     The directionality of the element is 'ltr'."
211 	if (element.dir == "ltr") {
212 		return "ltr";
213 	}
214 
215 	// "If the element's dir attribute is in the rtl state
216 	//     The directionality of the element is 'rtl'."
217 	if (element.dir == "rtl") {
218 		return "rtl";
219 	}
220 
221 	// "If the element's dir attribute is in the auto state
222 	// "If the element is a bdi element and the dir attribute is not in a
223 	// defined state (i.e. it is not present or has an invalid value)
224 	//     [lots of complicated stuff]
225 226 	//
227 	// Skip this, since no browser implements it anyway.
228 
229 	// "If the element is a root element and the dir attribute is not in a
230 	// defined state (i.e. it is not present or has an invalid value)
231 	//     The directionality of the element is 'ltr'."
232 	if (!isHtmlElement(element.parentNode)) {
233 		return "ltr";
234 	}
235 
236 	// "If the element has a parent element and the dir attribute is not in a
237 	// defined state (i.e. it is not present or has an invalid value)
238 	//     The directionality of the element is the same as the element's
239 	//     parent element's directionality."
240 	return getDirectionality(element.parentNode);
241 }
242 
243 //@}
244 
245 ///////////////////////////////////////////////////////////////////////////////
246 ///////////////////////////// DOM Range functions /////////////////////////////
247 ///////////////////////////////////////////////////////////////////////////////
248 //@{
249 
250 function getNodeIndex(node) {
251 	var ret = 0;
252 	while (node.previousSibling) {
253 		ret++;
254 		node = node.previousSibling;
255 	}
256 	return ret;
257 }
258 
259 // "The length of a Node node is the following, depending on node:
260 //
261 262 // ProcessingInstruction
263 // DocumentType
264 //   Always 0.
265 // Text
266 // Comment
267 //   node's length.
268 // Any other node
269 //   node's childNodes's length."
270 function getNodeLength(node) {
271 	switch (node.nodeType) {
272 		case $_.Node.PROCESSING_INSTRUCTION_NODE:
273 		case $_.Node.DOCUMENT_TYPE_NODE:
274 			return 0;
275 
276 		case $_.Node.TEXT_NODE:
277 		case $_.Node.COMMENT_NODE:
278 			return node.length;
279 
280 		default:
281 			return node.childNodes.length;
282 	}
283 }
284 
285 /**
286  * The position of two boundary points relative to one another, as defined by
287  * DOM Range.
288  */
289 function getPosition(nodeA, offsetA, nodeB, offsetB) {
290 	// "If node A is the same as node B, return equal if offset A equals offset
291 	// B, before if offset A is less than offset B, and after if offset A is
292 	// greater than offset B."
293 	if (nodeA == nodeB) {
294 		if (offsetA == offsetB) {
295 			return "equal";
296 		}
297 		if (offsetA < offsetB) {
298 			return "before";
299 		}
300 		if (offsetA > offsetB) {
301 			return "after";
302 		}
303 	}
304 
305 	// "If node A is after node B in tree order, compute the position of (node
306 	// B, offset B) relative to (node A, offset A). If it is before, return
307 	// after. If it is after, return before."
308 	if ($_.compareDocumentPosition(nodeB, nodeA) & $_.Node.DOCUMENT_POSITION_FOLLOWING) {
309 		var pos = getPosition(nodeB, offsetB, nodeA, offsetA);
310 		if (pos == "before") {
311 			return "after";
312 		}
313 		if (pos == "after") {
314 			return "before";
315 		}
316 	}
317 
318 	// "If node A is an ancestor of node B:"
319 	if ($_.compareDocumentPosition(nodeB, nodeA) & $_.Node.DOCUMENT_POSITION_CONTAINS) {
320 		// "Let child equal node B."
321 		var child = nodeB;
322 
323 		// "While child is not a child of node A, set child to its parent."
324 		while (child.parentNode != nodeA) {
325 			child = child.parentNode;
326 		}
327 
328 		// "If the index of child is less than offset A, return after."
329 		if (getNodeIndex(child) < offsetA) {
330 			return "after";
331 		}
332 	}
333 
334 	// "Return before."
335 	return "before";
336 }
337 
338 /**
339  * Returns the furthest ancestor of a Node as defined by DOM Range.
340  */
341 function getFurthestAncestor(node) {
342 	var root = node;
343 	while (root.parentNode != null) {
344 		root = root.parentNode;
345 	}
346 	return root;
347 }
348 
349 /**
350  * "contained" as defined by DOM Range: "A Node node is contained in a range
351  * range if node's furthest ancestor is the same as range's root, and (node, 0)
352  * is after range's start, and (node, length of node) is before range's end."
353  */
354 function isContained(node, range) {
355 	var pos1 = getPosition(node, 0, range.startContainer, range.startOffset);
356 	var pos2 = getPosition(node, getNodeLength(node), range.endContainer, range.endOffset);
357 
358 	return getFurthestAncestor(node) == getFurthestAncestor(range.startContainer)
359 		&& pos1 == "after"
360 		&& pos2 == "before";
361 }
362 
363 /**
364  * Return all nodes contained in range that the provided function returns true
365  * for, omitting any with an ancestor already being returned.
366  */
367 function getContainedNodes(range, condition) {
368 	if (typeof condition == "undefined") {
369 		condition = function() { return true };
370 	}
371 	var node = range.startContainer;
372 	if (node.hasChildNodes()
373 	&& range.startOffset < node.childNodes.length) {
374 		// A child is contained
375 		node = node.childNodes[range.startOffset];
376 	} else if (range.startOffset == getNodeLength(node)) {
377 		// No descendant can be contained
378 		node = nextNodeDescendants(node);
379 	} else {
380 		// No children; this node at least can't be contained
381 		node = nextNode(node);
382 	}
383 
384 	var stop = range.endContainer;
385 	if (stop.hasChildNodes()
386 	&& range.endOffset < stop.childNodes.length) {
387 		// The node after the last contained node is a child
388 		stop = stop.childNodes[range.endOffset];
389 	} else {
390 		// This node and/or some of its children might be contained
391 		stop = nextNodeDescendants(stop);
392 	}
393 
394 	var nodeList = [];
395 	while (isBefore(node, stop)) {
396 		if (isContained(node, range)
397 		&& condition(node)) {
398 			nodeList.push(node);
399 			node = nextNodeDescendants(node);
400 			continue;
401 		}
402 		node = nextNode(node);
403 	}
404 	return nodeList;
405 }
406 
407 /**
408  * As above, but includes nodes with an ancestor that's already been returned.
409  */
410 function getAllContainedNodes(range, condition) {
411 	if (typeof condition == "undefined") {
412 		condition = function() { return true };
413 	}
414 	var node = range.startContainer;
415 	if (node.hasChildNodes()
416 	&& range.startOffset < node.childNodes.length) {
417 		// A child is contained
418 		node = node.childNodes[range.startOffset];
419 	} else if (range.startOffset == getNodeLength(node)) {
420 		// No descendant can be contained
421 		node = nextNodeDescendants(node);
422 	} else {
423 		// No children; this node at least can't be contained
424 		node = nextNode(node);
425 	}
426 
427 	var stop = range.endContainer;
428 	if (stop.hasChildNodes()
429 	&& range.endOffset < stop.childNodes.length) {
430 		// The node after the last contained node is a child
431 		stop = stop.childNodes[range.endOffset];
432 	} else {
433 		// This node and/or some of its children might be contained
434 		stop = nextNodeDescendants(stop);
435 	}
436 
437 	var nodeList = [];
438 	while (isBefore(node, stop)) {
439 		if (isContained(node, range)
440 		&& condition(node)) {
441 			nodeList.push(node);
442 		}
443 		node = nextNode(node);
444 	}
445 	return nodeList;
446 }
447 
448 // Returns either null, or something of the form rgb(x, y, z), or something of
449 // the form rgb(x, y, z, w) with w != 0.
450 function normalizeColor(color) {
451 	if (color.toLowerCase() == "currentcolor") {
452 		return null;
453 	}
454 
455 	var outerSpan = document.createElement("span");
456 	document.body.appendChild(outerSpan);
457 	outerSpan.style.color = "black";
458 
459 	var innerSpan = document.createElement("span");
460 	outerSpan.appendChild(innerSpan);
461 	innerSpan.style.color = color;
462 	color = $_.getComputedStyle(innerSpan).color;
463 
464 	if (color == "rgb(0, 0, 0)") {
465 		// Maybe it's really black, maybe it's invalid.
466 		outerSpan.color = "white";
467 		color = $_.getComputedStyle(innerSpan).color;
468 469 		if (color != "rgb(0, 0, 0)") {
470 			return null;
471 		}
472 	}
473 
474 	document.body.removeChild(outerSpan);
475 
476 	// I rely on the fact that browsers generally provide consistent syntax for
477 	// getComputedStyle(), although it's not standardized.  There are only two
478 	// exceptions I found:
479 	if (/^rgba\([0-9]+, [0-9]+, [0-9]+, 1\)$/.test(color)) {
480 		// IE10PP2 seems to do this sometimes.
481 		return color.replace("rgba", "rgb").replace(", 1)", ")");
482 	}
483 	if (color == "transparent") {
484 		// IE10PP2, Firefox 7.0a2, and Opera 11.50 all return "transparent" if
485 		// the specified value is "transparent".
486 		return "rgba(0, 0, 0, 0)";
487 	}
488 	return color;
489 }
490 
491 // Returns either null, or something of the form #xxxxxx, or the color itself
492 // if it's a valid keyword.
493 function parseSimpleColor(color) {
494 	color = color.toLowerCase();
495 	if ($_(["aliceblue", "antiquewhite", "aqua", "aquamarine", "azure", "beige",
496 	"bisque", "black", "blanchedalmond", "blue", "blueviolet", "brown",
497 	"burlywood", "cadetblue", "chartreuse", "chocolate", "coral",
498 	"cornflowerblue", "cornsilk", "crimson", "cyan", "darkblue", "darkcyan",
499 	"darkgoldenrod", "darkgray", "darkgreen", "darkgrey", "darkkhaki",
500 	"darkmagenta", "darkolivegreen", "darkorange", "darkorchid", "darkred",
501 	"darksalmon", "darkseagreen", "darkslateblue", "darkslategray",
502 	"darkslategrey", "darkturquoise", "darkviolet", "deeppink", "deepskyblue",
503 	"dimgray", "dimgrey", "dodgerblue", "firebrick", "floralwhite",
504 	"forestgreen", "fuchsia", "gainsboro", "ghostwhite", "gold", "goldenrod",
505 	"gray", "green", "greenyellow", "grey", "honeydew", "hotpink", "indianred",
506 	"indigo", "ivory", "khaki", "lavender", "lavenderblush", "lawngreen",
507 	"lemonchiffon", "lightblue", "lightcoral", "lightcyan",
508 	"lightgoldenrodyellow", "lightgray", "lightgreen", "lightgrey",
509 	"lightpink", "lightsalmon", "lightseagreen", "lightskyblue",
510 	"lightslategray", "lightslategrey", "lightsteelblue", "lightyellow",
511 	"lime", "limegreen", "linen", "magenta", "maroon", "mediumaquamarine",
512 	"mediumblue", "mediumorchid", "mediumpurple", "mediumseagreen",
513 	"mediumslateblue", "mediumspringgreen", "mediumturquoise",
514 	"mediumvioletred", "midnightblue", "mintcream", "mistyrose", "moccasin",
515 	"navajowhite", "navy", "oldlace", "olive", "olivedrab", "orange",
516 	"orangered", "orchid", "palegoldenrod", "palegreen", "paleturquoise",
517 	"palevioletred", "papayawhip", "peachpuff", "peru", "pink", "plum",
518 	"powderblue", "purple", "red", "rosybrown", "royalblue", "saddlebrown",
519 	"salmon", "sandybrown", "seagreen", "seashell", "sienna", "silver",
520 	"skyblue", "slateblue", "slategray", "slategrey", "snow", "springgreen",
521 	"steelblue", "tan", "teal", "thistle", "tomato", "turquoise", "violet",
522 	"wheat", "white", "whitesmoke", "yellow", "yellowgreen"]).indexOf(color) != -1) {
523 		return color;
524 	}
525 
526 	color = normalizeColor(color);
527 	var matches = /^rgb\(([0-9]+), ([0-9]+), ([0-9]+)\)$/.exec(color);
528 	if (matches) {
529 		return "#"
530 			+ parseInt(matches[1]).toString(16).replace(/^.$/, "0$&")
531 			+ parseInt(matches[2]).toString(16).replace(/^.$/, "0$&")
532 			+ parseInt(matches[3]).toString(16).replace(/^.$/, "0$&");
533 	}
534 	return null;
535 }
536 
537 //@}
538 
539 //////////////////////////////////////////////////////////////////////////////
540 /////////////////////////// Edit command functions ///////////////////////////
541 //////////////////////////////////////////////////////////////////////////////
542 
543 /////////////////////////////////////////////////
544 ///// Methods of the HTMLDocument interface /////
545 /////////////////////////////////////////////////
546 //@{
547 
548 var executionStackDepth = 0;
549 
550 // Helper function for common behavior.
551 function editCommandMethod(command, prop, range, callback) {
552 	// Set up our global range magic, but only if we're the outermost function
553 	if (executionStackDepth == 0 && typeof range != "undefined") {
554 		globalRange = range;
555 	} else if (executionStackDepth == 0) {
556 		globalRange = null;
557 		globalRange = range;
558 	}
559 
560 	// "If command is not supported, raise a NOT_SUPPORTED_ERR exception."
561 	//
562 	// We can't throw a real one, but a string will do for our purposes.
563 	if (!(command in commands)) {
564 		throw "NOT_SUPPORTED_ERR";
565 	}
566 
567 	// "If command has no action, raise an INVALID_ACCESS_ERR exception."
568 	// "If command has no indeterminacy, raise an INVALID_ACCESS_ERR
569 	// exception."
570 	// "If command has no state, raise an INVALID_ACCESS_ERR exception."
571 	// "If command has no value, raise an INVALID_ACCESS_ERR exception."
572 	if (prop != "enabled"
573 	&& !(prop in commands[command])) {
574 		throw "INVALID_ACCESS_ERR";
575 	}
576 
577 	executionStackDepth++;
578 	try {
579 		var ret = callback();
580 	} catch(e) {
581 		executionStackDepth--;
582 		throw e;
583 	}
584 	executionStackDepth--;
585 	return ret;
586 }
587 
588 function myExecCommand(command, showUi, value, range) {
589 	// "All of these methods must treat their command argument ASCII
590 	// case-insensitively."
591 	command = command.toLowerCase();
592 
593 	// "If only one argument was provided, let show UI be false."
594 	//
595 	// If range was passed, I can't actually detect how many args were passed
596 	// . . .
597 	if (arguments.length == 1
598 	|| (arguments.length >=4 && typeof showUi == "undefined")) {
599 		showUi = false;
600 	}
601 
602 	// "If only one or two arguments were provided, let value be the empty
603 	// string."
604 	if (arguments.length <= 2
605 	|| (arguments.length >=4 && typeof value == "undefined")) {
606 		value = "";
607 	}
608 
609 	// "If command is not supported, raise a NOT_SUPPORTED_ERR exception."
610 	//
611 	// "If command has no action, raise an INVALID_ACCESS_ERR exception."
612 	return editCommandMethod(command, "action", range, (function(command, showUi, value) { return function() {
613 		// "If command is not enabled, return false."
614 		if (!myQueryCommandEnabled(command)) {
615 			return false;
616 		}
617 
618 		// "Take the action for command, passing value to the instructions as an
619 		// argument."
620 		commands[command].action(value, range);
621 
622 		// always fix the range after the command is complete
623 		setActiveRange(range);
624 		
625 		// "Return true."
626 		return true;
627 	}})(command, showUi, value));
628 }
629 
630 function myQueryCommandEnabled(command, range) {
631 	// "All of these methods must treat their command argument ASCII
632 	// case-insensitively."
633 	command = command.toLowerCase();
634 
635 	// "If command is not supported, raise a NOT_SUPPORTED_ERR exception."
636 	return editCommandMethod(command, "action", range, (function(command) { return function() {
637 		// "Among commands defined in this specification, those listed in
638 		// Miscellaneous commands are always enabled. The other commands defined
639 		// here are enabled if the active range is not null, and disabled
640 		// otherwise."
641 		return $_( ["copy", "cut", "paste", "selectall", "stylewithcss", "usecss"] ).indexOf(command) != -1
642 			|| range !== null;
643 	}})(command));
644 }
645 
646 function myQueryCommandIndeterm(command, range) {
647 	// "All of these methods must treat their command argument ASCII
648 	// case-insensitively."
649 	command = command.toLowerCase();
650 
651 	// "If command is not supported, raise a NOT_SUPPORTED_ERR exception."
652 	//
653 	// "If command has no indeterminacy, raise an INVALID_ACCESS_ERR
654 	// exception."
655 	return editCommandMethod(command, "indeterm", range, (function(command) { return function() {
656 		// "If command is not enabled, return false."
657 		if (!myQueryCommandEnabled(command, range)) {
658 			return false;
659 		}
660 
661 		// "Return true if command is indeterminate, otherwise false."
662 		return commands[command].indeterm( range );
663 	}})(command));
664 }
665 
666 function myQueryCommandState(command, range) {
667 	// "All of these methods must treat their command argument ASCII
668 	// case-insensitively."
669 	command = command.toLowerCase();
670 
671 	// "If command is not supported, raise a NOT_SUPPORTED_ERR exception."
672 	//
673 	// "If command has no state, raise an INVALID_ACCESS_ERR exception."
674 	return editCommandMethod(command, "state", range, (function(command) { return function() {
675 		// "If command is not enabled, return false."
676 		if (!myQueryCommandEnabled(command, range)) {
677 			return false;
678 		}
679 
680 		// "If the state override for command is set, return it."
681 		if (typeof getStateOverride(command, range) != "undefined") {
682 			return getStateOverride(command, range);
683 		}
684 
685 		// "Return true if command's state is true, otherwise false."
686 		return commands[command].state( range );
687 	}})(command));
688 }
689 
690 // "When the queryCommandSupported(command) method on the HTMLDocument
691 // interface is invoked, the user agent must return true if command is
692 // supported, and false otherwise."
693 function myQueryCommandSupported(command) {
694 	// "All of these methods must treat their command argument ASCII
695 	// case-insensitively."
696 	command = command.toLowerCase();
697 
698 	return command in commands;
699 }
700 
701 function myQueryCommandValue(command, range) {
702 	// "All of these methods must treat their command argument ASCII
703 	// case-insensitively."
704 	command = command.toLowerCase();
705 
706 	// "If command is not supported, raise a NOT_SUPPORTED_ERR exception."
707 	//
708 	// "If command has no value, raise an INVALID_ACCESS_ERR exception."
709 	return editCommandMethod(command, "value", range, function() {
710 		// "If command is not enabled, return the empty string."
711 		if (!myQueryCommandEnabled(command, range)) {
712 			return "";
713 		}
714 
715 		// "If command is "fontSize" and its value override is set, convert the
716 		// value override to an integer number of pixels and return the legacy
717 		// font size for the result."
718 		if (command == "fontsize"
719 		&& getValueOverride("fontsize", range) !== undefined) {
720 			return getLegacyFontSize(getValueOverride("fontsize", range));
721 		}
722 
723 		// "If the value override for command is set, return it."
724 		if (typeof getValueOverride(command, range) != "undefined") {
725 			return getValueOverride(command, range);
726 		}
727 
728 		// "Return command's value."
729 		return commands[command].value();
730 	});
731 }
732 //@}
733 
734 //////////////////////////////
735 ///// Common definitions /////
736 //////////////////////////////
737 //@{
738 
739 // "An HTML element is an Element whose namespace is the HTML namespace."
740 //
741 // I allow an extra argument to more easily check whether something is a
742 // particular HTML element, like isHtmlElement(node, "OL").  It accepts arrays
743 // too, like isHtmlElement(node, ["OL", "UL"]) to check if it's an ol or ul.
744 function isHtmlElement(node, tags) {
745 	if (typeof tags == "string") {
746 		tags = [tags];
747 	}
748 	if (typeof tags == "object") {
749 		tags = $_( tags ).map(function(tag) { return tag.toUpperCase() });
750 	}
751 	return node
752 		&& node.nodeType == $_.Node.ELEMENT_NODE
753 		&& isHtmlNamespace(node.namespaceURI)
754 		&& (typeof tags == "undefined" || $_( tags ).indexOf(node.tagName) != -1);
755 }
756 
757 // "A prohibited paragraph child name is "address", "article", "aside",
758 // "blockquote", "caption", "center", "col", "colgroup", "dd", "details",
759 // "dir", "div", "dl", "dt", "fieldset", "figcaption", "figure", "footer",
760 // "form", "h1", "h2", "h3", "h4", "h5", "h6", "header", "hgroup", "hr", "li",
761 // "listing", "menu", "nav", "ol", "p", "plaintext", "pre", "section",
762 // "summary", "table", "tbody", "td", "tfoot", "th", "thead", "tr", "ul", or
763 // "xmp"."
764 var prohibitedParagraphChildNames = ["address", "article", "aside",
765 	"blockquote", "caption", "center", "col", "colgroup", "dd", "details",
766 	"dir", "div", "dl", "dt", "fieldset", "figcaption", "figure", "footer",
767 	"form", "h1", "h2", "h3", "h4", "h5", "h6", "header", "hgroup", "hr", "li",
768 	"listing", "menu", "nav", "ol", "p", "plaintext", "pre", "section",
769 	"summary", "table", "tbody", "td", "tfoot", "th", "thead", "tr", "ul",
770 	"xmp"];
771 
772 // "A prohibited paragraph child is an HTML element whose local name is a
773 // prohibited paragraph child name."
774 function isProhibitedParagraphChild(node) {
775 	return isHtmlElement(node, prohibitedParagraphChildNames);
776 }
777 
778 // "A block node is either an Element whose "display" property does not have
779 // resolved value "inline" or "inline-block" or "inline-table" or "none", or a
780 // Document, or a DocumentFragment."
781 function isBlockNode(node) {
782 	
783 	return node
784 		&& ((node.nodeType == $_.Node.ELEMENT_NODE && $_( ["inline", "inline-block", "inline-table", "none"] ).indexOf($_.getComputedStyle(node).display) == -1)
785 		|| node.nodeType == $_.Node.DOCUMENT_NODE
786 		|| node.nodeType == $_.Node.DOCUMENT_FRAGMENT_NODE);
787 }
788 
789 // "An inline node is a node that is not a block node."
790 function isInlineNode(node) {
791 	return node && !isBlockNode(node);
792 }
793 
794 // "An editing host is a node that is either an Element with a contenteditable
795 // attribute set to the true state, or the Element child of a Document whose
796 // designMode is enabled."
797 function isEditingHost(node) {
798 	return node
799 		&& node.nodeType == $_.Node.ELEMENT_NODE
800 		&& (node.contentEditable == "true"
801 		|| (node.parentNode
802 		&& node.parentNode.nodeType == $_.Node.DOCUMENT_NODE
803 		&& node.parentNode.designMode == "on"));
804 }
805 
806 // "Something is editable if it is a node which is not an editing host, does
807 // not have a contenteditable attribute set to the false state, and whose
808 // parent is an editing host or editable."
809 function isEditable(node) {
810 	// This is slightly a lie, because we're excluding non-HTML elements with
811 	// contentEditable attributes.
812 	return node
813 		&& !isEditingHost(node)
814 		&& (node.nodeType != $_.Node.ELEMENT_NODE || node.contentEditable != "false" || jQuery(node).hasClass('aloha-table-wrapper'))
815 		&& (isEditingHost(node.parentNode) || isEditable(node.parentNode));
816 }
817 
818 // Helper function, not defined in the spec
819 function hasEditableDescendants(node) {
820 	for (var i = 0; i < node.childNodes.length; i++) {
821 		if (isEditable(node.childNodes[i])
822 		|| hasEditableDescendants(node.childNodes[i])) {
823 			return true;
824 		}
825 	}
826 	return false;
827 }
828 
829 // "The editing host of node is null if node is neither editable nor an editing
830 // host; node itself, if node is an editing host; or the nearest ancestor of
831 // node that is an editing host, if node is editable."
832 function getEditingHostOf(node) {
833 	if (isEditingHost(node)) {
834 		return node;
835 	} else if (isEditable(node)) {
836 		var ancestor = node.parentNode;
837 		while (!isEditingHost(ancestor)) {
838 			ancestor = ancestor.parentNode;
839 		}
840 		return ancestor;
841 	} else {
842 		return null;
843 	}
844 }
845 
846 // "Two nodes are in the same editing host if the editing host of the first is
847 // non-null and the same as the editing host of the second."
848 function inSameEditingHost(node1, node2) {
849 	return getEditingHostOf(node1)
850 		&& getEditingHostOf(node1) == getEditingHostOf(node2);
851 }
852 
853 // "A collapsed line break is a br that begins a line box which has nothing
854 // else in it, and therefore has zero height."
855 function isCollapsedLineBreak(br) {
856 	if (!isHtmlElement(br, "br")) {
857 		return false;
858 	}
859 
860 	// Add a zwsp after it and see if that changes the height of the nearest
861 	// non-inline parent.  Note: this is not actually reliable, because the
862 	// parent might have a fixed height or something.
863 	var ref = br.parentNode;
864 	while ($_.getComputedStyle(ref).display == "inline") {
865 		ref = ref.parentNode;
866 	}
867 
868 869 	var refStyle = $_( ref ).hasAttribute("style") ? ref.getAttribute("style") : null;
870 	ref.style.height = "auto";
871 	ref.style.maxHeight = "none";
872 	ref.style.minHeight = "0";
873 	var space = document.createTextNode("\u200b");
874 	var origHeight = ref.offsetHeight;
875 	if (origHeight == 0) {
876 		throw "isCollapsedLineBreak: original height is zero, bug?";
877 	}
878 	br.parentNode.insertBefore(space, br.nextSibling);
879 	var finalHeight = ref.offsetHeight;
880 	space.parentNode.removeChild(space);
881 	if (refStyle === null) {
882 		// Without the setAttribute() line, removeAttribute() doesn't work in
883 		// Chrome 14 dev.  I have no idea why.
884 		ref.setAttribute("style", "");
885 		ref.removeAttribute("style");
886 	} else {
887 		ref.setAttribute("style", refStyle);
888 	}
889 
890 	// Allow some leeway in case the zwsp didn't create a whole new line, but
891 	// only made an existing line slightly higher.  Firefox 6.0a2 shows this
892 	// behavior when the first line is bold.
893 	return origHeight < finalHeight - 5;
894 }
895 
896 // "An extraneous line break is a br that has no visual effect, in that
897 // removing it from the DOM would not change layout, except that a br that is
898 // the sole child of an li is not extraneous."
899 //
900 // FIXME: This doesn't work in IE, since IE ignores display: none in
901 // contenteditable.
902 function isExtraneousLineBreak(br) {
903 	if (!isHtmlElement(br, "br")) {
904 		return false;
905 	}
906 
907 	if (isHtmlElement(br.parentNode, "li")
908 	&& br.parentNode.childNodes.length == 1) {
909 		return false;
910 	}
911 
912 	// Make the line break disappear and see if that changes the block's
913 	// height.  Yes, this is an absurd hack.  We have to reset height etc. on
914 	// the reference node because otherwise its height won't change if it's not
915 	// auto.
916 	var ref = br.parentNode;
917 	while ($_.getComputedStyle(ref).display == "inline") {
918 		ref = ref.parentNode;
919 	}
920 	var refStyle = $_( ref ).hasAttribute("style") ? ref.getAttribute("style") : null;
921 	ref.style.height = "auto";
922 	ref.style.maxHeight = "none";
923 	ref.style.minHeight = "0";
924 	var brStyle = $_( br ).hasAttribute("style") ? br.getAttribute("style") : null;
925 	var origHeight = ref.offsetHeight;
926 	if (origHeight == 0) {
927 		throw "isExtraneousLineBreak: original height is zero, bug?";
928 	}
929 	br.setAttribute("style", "display:none");
930 	var finalHeight = ref.offsetHeight;
931 	if (refStyle === null) {
932 		// Without the setAttribute() line, removeAttribute() doesn't work in
933 		// Chrome 14 dev.  I have no idea why.
934 		ref.setAttribute("style", "");
935 		ref.removeAttribute("style");
936 	} else {
937 		ref.setAttribute("style", refStyle);
938 	}
939 	if (brStyle === null) {
940 		br.removeAttribute("style");
941 	} else {
942 		br.setAttribute("style", brStyle);
943 	}
944 
945 	return origHeight == finalHeight;
946 }
947 
948 // "A whitespace node is either a Text node whose data is the empty string; or
949 // a Text node whose data consists only of one or more tabs (0x0009), line
950 // feeds (0x000A), carriage returns (0x000D), and/or spaces (0x0020), and whose
951 // parent is an Element whose resolved value for "white-space" is "normal" or
952 // "nowrap"; or a Text node whose data consists only of one or more tabs
953 // (0x0009), carriage returns (0x000D), and/or spaces (0x0020), and whose
954 // parent is an Element whose resolved value for "white-space" is "pre-line"."
955 function isWhitespaceNode(node) {
956 	return node
957 		&& node.nodeType == $_.Node.TEXT_NODE
958 		&& (node.data == ""
959 		|| (
960 			/^[\t\n\r ]+$/.test(node.data)
961 			&& node.parentNode
962 			&& node.parentNode.nodeType == $_.Node.ELEMENT_NODE
963 			&& $_( ["normal", "nowrap"] ).indexOf($_.getComputedStyle(node.parentNode).whiteSpace) != -1
964 		) || (
965 			/^[\t\r ]+$/.test(node.data)
966 			&& node.parentNode
967 			&& node.parentNode.nodeType == $_.Node.ELEMENT_NODE
968 			&& $_.getComputedStyle(node.parentNode).whiteSpace == "pre-line"
969 		) || (
970 			/^[\t\n\r ]+$/.test(node.data)
971 			&& node.parentNode
972 			&& node.parentNode.nodeType == $_.Node.DOCUMENT_FRAGMENT_NODE
973 		));
974 }
975 
976 /**
977  * Collapse sequences of ignorable whitespace (tab (0x0009), line feed (0x000A), carriage return (0x000D), space (0x0020)) to only one space.
978  * Preserve the given range if necessary.
979  * @param node text node
980  * @param range range
981  */
982 function collapseWhitespace(node, range) {
983 	// "If node is neither editable nor an editing host, abort these steps."
984 	if (!isEditable(node) && !isEditingHost(node)) {
985 		return;
986 	}
987 
988 	// if the given node is not a text node, return
989 	if (!node || node.nodeType !== $_.Node.TEXT_NODE) {
990 		return;
991 	}
992 
993 	// if the node is in a pre or pre-wrap node, return
994 	if ($_(["pre", "pre-wrap"]).indexOf($_.getComputedStyle(node.parentNode).whiteSpace) != -1) {
995 		return;
996 	}
997 
998 	// if the given node does not contain sequences of at least two consecutive ignorable whitespace characters, return
999 	if (!/[\t\n\r ]{2,}/.test(node.data)) {
1000 		return;
1001 	}
1002 
1003 	var newData = '';
1004 	var correctStart = range.startContainer == node;
1005 	var correctEnd = range.endContainer == node;
1006 	var wsFound = false;
1007 
1008 	// iterate through the node data
1009 	for (var i = 0; i < node.data.length; ++i) {
1010 		if (/[\t\n\r ]/.test(node.data[i])) {
1011 			// found a whitespace
1012 			if (!wsFound) {
1013 				// this is the first whitespace in the current sequence
1014 				// add a whitespace to the new data sequence
1015 				newData += ' ';
1016 				// remember that we found a whitespace
1017 				wsFound = true;
1018 			} else {
1019 				// this is not the first whitespace in the sequence, so omit this character
1020 				if (correctStart && newData.length < range.startOffset) {
1021 					range.startOffset--;
1022 				}
1023 				if (correctEnd && newData.length < range.endOffset) {
1024 					range.endOffset--;
1025 				}
1026 			}
1027 		} else {
1028 			newData += node.data[i];
1029 			wsFound = false;
1030 		}
1031 	}
1032 
1033 	// set the new data
1034 	node.data = newData;
1035 }
1036 
1037 // "node is a collapsed whitespace node if the following algorithm returns
1038 // true:"
1039 function isCollapsedWhitespaceNode(node) {
1040 	// "If node is not a whitespace node, return false."
1041 	if (!isWhitespaceNode(node)) {
1042 		return false;
1043 	}
1044 
1045 	// "If node's data is the empty string, return true."
1046 	if (node.data == "") {
1047 		return true;
1048 	}
1049 
1050 	// "Let ancestor be node's parent."
1051 	var ancestor = node.parentNode;
1052 
1053 	// "If ancestor is null, return true."
1054 	if (!ancestor) {
1055 		return true;
1056 	}
1057 
1058 	// "If the "display" property of some ancestor of node has resolved value
1059 	// "none", return true."
1060 	if ($_( getAncestors(node) ).some(function(ancestor) {
1061 		return ancestor.nodeType == $_.Node.ELEMENT_NODE
1062 			&& $_.getComputedStyle(ancestor).display == "none";
1063 	})) {
1064 		return true;
1065 	}
1066 
1067 	// "While ancestor is not a block node and its parent is not null, set
1068 	// ancestor to its parent."
1069 	while (!isBlockNode(ancestor)
1070 	&& ancestor.parentNode) {
1071 		ancestor = ancestor.parentNode;
1072 	}
1073 
1074 	// "Let reference be node."
1075 	var reference = node;
1076 
1077 	// "While reference is a descendant of ancestor:"
1078 	while (reference != ancestor) {
1079 		// "Let reference be the node before it in tree order."
1080 		reference = previousNode(reference);
1081 
1082 		// "If reference is a block node or a br, return true."
1083 		if (isBlockNode(reference)
1084 		|| isHtmlElement(reference, "br")) {
1085 			return true;
1086 		}
1087 
1088 		// "If reference is a Text node that is not a whitespace node, or is an
1089 		// img, break from this loop."
1090 		if ((reference.nodeType == $_.Node.TEXT_NODE && !isWhitespaceNode(reference))
1091 		|| isHtmlElement(reference, "img")) {
1092 			break;
1093 		}
1094 	}
1095 
1096 	// "Let reference be node."
1097 	reference = node;
1098 
1099 	// "While reference is a descendant of ancestor:"
1100 	var stop = nextNodeDescendants(ancestor);
1101 	while (reference != stop) {
1102 		// "Let reference be the node after it in tree order, or null if there
1103 		// is no such node."
1104 		reference = nextNode(reference);
1105 
1106 		// "If reference is a block node or a br, return true."
1107 		if (isBlockNode(reference)
1108 		|| isHtmlElement(reference, "br")) {
1109 			return true;
1110 		}
1111 
1112 		// "If reference is a Text node that is not a whitespace node, or is an
1113 		// img, break from this loop."
1114 		if ((reference && reference.nodeType == $_.Node.TEXT_NODE && !isWhitespaceNode(reference))
1115 		|| isHtmlElement(reference, "img")) {
1116 			break;
1117 		}
1118 	}
1119 
1120 	// "Return false."
1121 	return false;
1122 }
1123 
1124 // "Something is visible if it is a node that either is a block node, or a Text
1125 // node that is not a collapsed whitespace node, or an img, or a br that is not
1126 // an extraneous line break, or any node with a visible descendant; excluding
1127 // any node with an ancestor container Element whose "display" property has
1128 // resolved value "none"."
1129 function isVisible(node) {
1130 	if (!node) {
1131 		return false;
1132 	}
1133 
1134 	if ($_( getAncestors(node).concat(node) )
1135 	.filter(function(node) { return node.nodeType == $_.Node.ELEMENT_NODE }, true)
1136 	.some(function(node) { return $_.getComputedStyle(node).display == "none" })) {
1137 		return false;
1138 	}
1139 
1140 	if (isBlockNode(node)
1141 	|| (node.nodeType == $_.Node.TEXT_NODE && !isCollapsedWhitespaceNode(node))
1142 	|| isHtmlElement(node, "img")
1143 	|| (isHtmlElement(node, "br") && !isExtraneousLineBreak(node))) {
1144 		return true;
1145 	}
1146 
1147 	for (var i = 0; i < node.childNodes.length; i++) {
1148 		if (isVisible(node.childNodes[i])) {
1149 			return true;
1150 		}
1151 	}
1152 
1153 	return false;
1154 }
1155 
1156 // "Something is invisible if it is a node that is not visible."
1157 function isInvisible(node) {
1158 	return node && !isVisible(node);
1159 }
1160 
1161 // "A collapsed block prop is either a collapsed line break that is not an
1162 // extraneous line break, or an Element that is an inline node and whose
1163 // children are all either invisible or collapsed block props and that has at
1164 // least one child that is a collapsed block prop."
1165 function isCollapsedBlockProp(node) {
1166 	if (isCollapsedLineBreak(node)
1167 	&& !isExtraneousLineBreak(node)) {
1168 		return true;
1169 	}
1170 
1171 	if (!isInlineNode(node)
1172 	|| node.nodeType != $_.Node.ELEMENT_NODE) {
1173 		return false;
1174 	}
1175 
1176 	var hasCollapsedBlockPropChild = false;
1177 	for (var i = 0; i < node.childNodes.length; i++) {
1178 		if (!isInvisible(node.childNodes[i])
1179 		&& !isCollapsedBlockProp(node.childNodes[i])) {
1180 			return false;
1181 		}
1182 		if (isCollapsedBlockProp(node.childNodes[i])) {
1183 			hasCollapsedBlockPropChild = true;
1184 		}
1185 	}
1186 
1187 	return hasCollapsedBlockPropChild;
1188 }
1189 
1190 function setActiveRange( range ) {
1191 	var rangeObject = new window.GENTICS.Utils.RangeObject();
1192 	
1193 	rangeObject.startContainer = range.startContainer;
1194 	rangeObject.startOffset = range.startOffset;
1195 	rangeObject.endContainer = range.endContainer;
1196 	rangeObject.endOffset = range.endOffset;
1197 	
1198 	rangeObject.select();
1199 1200 }
1201 
1202 // Please note: This method is deprecated and will be removed. 
1203 // Every command should use the value and range parameter. 
1204 // 
1205 // "The active range is the first range in the Selection given by calling
1206 // getSelection() on the context object, or null if there is no such range."
1207 //
1208 // We cheat and return globalRange if that's defined.  We also ensure that the
1209 // active range meets the requirements that selection boundary points are
1210 // supposed to meet, i.e., that the nodes are both Text or Element nodes that
1211 // descend from a Document.
1212 function getActiveRange() {
1213 	var ret;
1214 	if (globalRange) {
1215 		ret = globalRange;
1216 	} else if (Aloha.getSelection().rangeCount) {
1217 		ret = Aloha.getSelection().getRangeAt(0);
1218 	} else {
1219 		return null;
1220 	}
1221 	if ($_( [$_.Node.TEXT_NODE, $_.Node.ELEMENT_NODE] ).indexOf(ret.startContainer.nodeType) == -1
1222 	|| $_( [$_.Node.TEXT_NODE, $_.Node.ELEMENT_NODE] ).indexOf(ret.endContainer.nodeType) == -1
1223 	|| !ret.startContainer.ownerDocument
1224 	|| !ret.endContainer.ownerDocument
1225 	|| !isDescendant(ret.startContainer, ret.startContainer.ownerDocument)
1226 	|| !isDescendant(ret.endContainer, ret.endContainer.ownerDocument)) {
1227 		throw "Invalid active range; test bug?";
1228 	}
1229 	return ret;
1230 }
1231 
1232 // "For some commands, each HTMLDocument must have a boolean state override
1233 // and/or a string value override. These do not change the command's state or
1234 // value, but change the way some algorithms behave, as specified in those
1235 // algorithms' definitions. Initially, both must be unset for every command.
1236 // Whenever the number of ranges in the Selection changes to something
1237 // different, and whenever a boundary point of the range at a given index in
1238 // the Selection changes to something different, the state override and value
1239 // override must be unset for every command."
1240 //
1241 // We implement this crudely by using setters and getters.  To verify that the
1242 // selection hasn't changed, we copy the active range and just check the
1243 // endpoints match.  This isn't really correct, but it's good enough for us.
1244 // Unset state/value overrides are undefined.  We put everything in a function
1245 // so no one can access anything except via the provided functions, since
1246 // otherwise callers might mistakenly use outdated overrides (if the selection
1247 // has changed).
1248 var getStateOverride, setStateOverride, unsetStateOverride,
1249 	getValueOverride, setValueOverride, unsetValueOverride;
1250 (function() {
1251 	var stateOverrides = {};
1252 	var valueOverrides = {};
1253 	var storedRange = null;
1254 
1255 	function resetOverrides(range) {
1256 		if (!storedRange
1257 		|| storedRange.startContainer != range.startContainer
1258 		|| storedRange.endContainer != range.endContainer
1259 		|| storedRange.startOffset != range.startOffset
1260 		|| storedRange.endOffset != range.endOffset) {
1261 			stateOverrides = {};
1262 			valueOverrides = {};
1263 			storedRange = range.cloneRange();
1264 		}
1265 	}
1266 
1267 	getStateOverride = function(command, range) {
1268 		resetOverrides(range);
1269 		return stateOverrides[command];
1270 	};
1271 
1272 	setStateOverride = function(command, newState, range) {
1273 		resetOverrides(range);
1274 		stateOverrides[command] = newState;
1275 	};
1276 
1277 	unsetStateOverride = function(command, range) {
1278 		resetOverrides(range);
1279 		delete stateOverrides[command];
1280 	}
1281 
1282 	getValueOverride = function(command, range) {
1283 		resetOverrides(range);
1284 		return valueOverrides[command];
1285 	}
1286 
1287 	// "The value override for the backColor command must be the same as the
1288 	// value override for the hiliteColor command, such that setting one sets
1289 	// the other to the same thing and unsetting one unsets the other."
1290 	setValueOverride = function(command, newValue, range) {
1291 		resetOverrides(range);
1292 		valueOverrides[command] = newValue;
1293 		if (command == "backcolor") {
1294 			valueOverrides.hilitecolor = newValue;
1295 		} else if (command == "hilitecolor") {
1296 			valueOverrides.backcolor = newValue;
1297 		}
1298 	}
1299 
1300 	unsetValueOverride = function(command, range) {
1301 		resetOverrides(range);
1302 		delete valueOverrides[command];
1303 		if (command == "backcolor") {
1304 			delete valueOverrides.hilitecolor;
1305 		} else if (command == "hilitecolor") {
1306 			delete valueOverrides.backcolor;
1307 		}
1308 	}
1309 })();
1310 
1311 //@}
1312 
1313 /////////////////////////////
1314 ///// Common algorithms /////
1315 /////////////////////////////
1316 
1317 ///// Assorted common algorithms /////
1318 //@{
1319 
1320 function movePreservingRanges(node, newParent, newIndex, range) {
1321 	// For convenience, I allow newIndex to be -1 to mean "insert at the end".
1322 	if (newIndex == -1) {
1323 		newIndex = newParent.childNodes.length;
1324 	}
1325 
1326 	// "When the user agent is to move a Node to a new location, preserving
1327 	// ranges, it must remove the Node from its original parent (if any), then
1328 	// insert it in the new location. In doing so, however, it must ignore the
1329 	// regular range mutation rules, and instead follow these rules:"
1330 
1331 	// "Let node be the moved Node, old parent and old index be the old parent
1332 	// (which may be null) and index, and new parent and new index be the new
1333 	// parent and index."
1334 	var oldParent = node.parentNode;
1335 	var oldIndex = getNodeIndex(node);
1336 
1337 	// We only even attempt to preserve the global range object and the ranges
1338 	// in the selection, not every range out there (the latter is probably
1339 	// impossible).
1340 	var ranges = [range];
1341 	for (var i = 0; i < Aloha.getSelection().rangeCount; i++) {
1342 		ranges.push(Aloha.getSelection().getRangeAt(i));
1343 	}
1344 	var boundaryPoints = [];
1345 	$_( ranges ).forEach(function(range) {
1346 		boundaryPoints.push([range.startContainer, range.startOffset]);
1347 		boundaryPoints.push([range.endContainer, range.endOffset]);
1348 	});
1349 
1350 	$_( boundaryPoints ).forEach(function(boundaryPoint) {
1351 		// "If a boundary point's node is the same as or a descendant of node,
1352 		// leave it unchanged, so it moves to the new location."
1353 		//
1354 		// No modifications necessary.
1355 
1356 		// "If a boundary point's node is new parent and its offset is greater
1357 		// than new index, add one to its offset."
1358 		if (boundaryPoint[0] == newParent
1359 		&& boundaryPoint[1] > newIndex) {
1360 			boundaryPoint[1]++;
1361 		}
1362 
1363 		// "If a boundary point's node is old parent and its offset is old index or
1364 		// old index + 1, set its node to new parent and add new index − old index
1365 		// to its offset."
1366 		if (boundaryPoint[0] == oldParent
1367 		&& (boundaryPoint[1] == oldIndex
1368 		|| boundaryPoint[1] == oldIndex + 1)) {
1369 			boundaryPoint[0] = newParent;
1370 			boundaryPoint[1] += newIndex - oldIndex;
1371 		}
1372 
1373 		// "If a boundary point's node is old parent and its offset is greater than
1374 		// old index + 1, subtract one from its offset."
1375 		if (boundaryPoint[0] == oldParent
1376 		&& boundaryPoint[1] > oldIndex + 1) {
1377 			boundaryPoint[1]--;
1378 		}
1379 	});
1380 
1381 	// Now actually move it and preserve the ranges.
1382 	if (newParent.childNodes.length == newIndex) {
1383 		newParent.appendChild(node);
1384 	} else {
1385 		newParent.insertBefore(node, newParent.childNodes[newIndex]);
1386 	}
1387 
1388 	// if we're off actual node boundaries this implies that the move was
1389 	// part of a deletion process (backspace). If that's the case we 
1390 	// attempt to fix this by restoring the range to the first index of
1391 	// the node that has been moved
1392 	if (boundaryPoints[0][1] > boundaryPoints[0][0].childNodes.length
1393 	&& boundaryPoints[1][1] > boundaryPoints[1][0].childNodes.length) {
1394 		range.setStart(node, 0);
1395 		range.setEnd(node, 0);
1396 	} else {
1397 		range.setStart(boundaryPoints[0][0], boundaryPoints[0][1]);
1398 		range.setEnd(boundaryPoints[1][0], boundaryPoints[1][1]);
1399 
1400 		Aloha.getSelection().removeAllRanges();
1401 		for (var i = 1; i < ranges.length; i++) {
1402 			var newRange = Aloha.createRange();
1403 			newRange.setStart(boundaryPoints[2*i][0], boundaryPoints[2*i][1]);
1404 			newRange.setEnd(boundaryPoints[2*i + 1][0], boundaryPoints[2*i + 1][1]);
1405 			Aloha.getSelection().addRange(newRange);
1406 		}
1407 		if (newRange) {
1408 			range = newRange;
1409 		}
1410 	}
1411 }
1412 
1413 function setTagName(element, newName, range) {
1414 1415 	// "If element is an HTML element with local name equal to new name, return
1416 	// element."
1417 	if (isHtmlElement(element, newName.toUpperCase())) {
1418 		return element;
1419 	}
1420 
1421 	// "If element's parent is null, return element."
1422 	if (!element.parentNode) {
1423 		return element;
1424 	}
1425 
1426 	// "Let replacement element be the result of calling createElement(new
1427 	// name) on the ownerDocument of element."
1428 	var replacementElement = element.ownerDocument.createElement(newName);
1429 
1430 	// "Insert replacement element into element's parent immediately before
1431 	// element."
1432 	element.parentNode.insertBefore(replacementElement, element);
1433 
1434 	// "Copy all attributes of element to replacement element, in order."
1435 	for (var i = 0; i < element.attributes.length; i++) {
1436 		if (typeof replacementElement.setAttributeNS === 'function') {
1437 			replacementElement.setAttributeNS(element.attributes[i].namespaceURI, element.attributes[i].name, element.attributes[i].value);
1438 		} else {
1439 			replacementElement.setAttribute(element.attributes[i].name, element.attributes[i].value);
1440 		}
1441 	}
1442 
1443 	// "While element has children, append the first child of element as the
1444 	// last child of replacement element, preserving ranges."
1445 	while (element.childNodes.length) {
1446 		movePreservingRanges(element.firstChild, replacementElement, replacementElement.childNodes.length, range);
1447 	}
1448 
1449 	// "Remove element from its parent."
1450 	element.parentNode.removeChild(element);
1451 
1452 	// if the range still uses the old element, we modify it to the new one
1453 	if (range.startContainer === element) {
1454 		range.setStart(replacementElement, range.startOffset);
1455 	}
1456 	if (range.endContainer === element) {
1457 		range.setEnd(replacementElement, range.endOffset);
1458 	}
1459 
1460 	// "Return replacement element."
1461 	return replacementElement;
1462 }
1463 
1464 function removeExtraneousLineBreaksBefore(node) {
1465 	// "Let ref be the previousSibling of node."
1466 	var ref = node.previousSibling;
1467 
1468 	// "If ref is null, abort these steps."
1469 	if (!ref) {
1470 		return;
1471 	}
1472 
1473 	// "While ref has children, set ref to its lastChild."
1474 	while (ref.hasChildNodes()) {
1475 		ref = ref.lastChild;
1476 	}
1477 
1478 	// "While ref is invisible but not an extraneous line break, and ref does
1479 	// not equal node's parent, set ref to the node before it in tree order."
1480 	while (isInvisible(ref)
1481 	&& !isExtraneousLineBreak(ref)
1482 	&& ref != node.parentNode) {
1483 		ref = previousNode(ref);
1484 	}
1485 
1486 	// "If ref is an editable extraneous line break, remove it from its
1487 	// parent."
1488 	if (isEditable(ref)
1489 	&& isExtraneousLineBreak(ref)) {
1490 		ref.parentNode.removeChild(ref);
1491 	}
1492 }
1493 
1494 function removeExtraneousLineBreaksAtTheEndOf(node) {
1495 	// "Let ref be node."
1496 	var ref = node;
1497 
1498 	// "While ref has children, set ref to its lastChild."
1499 	while (ref.hasChildNodes()) {
1500 		ref = ref.lastChild;
1501 	}
1502 
1503 	// "While ref is invisible but not an extraneous line break, and ref does
1504 	// not equal node, set ref to the node before it in tree order."
1505 	while (isInvisible(ref)
1506 	&& !isExtraneousLineBreak(ref)
1507 	&& ref != node) {
1508 		ref = previousNode(ref);
1509 	}
1510 
1511 	// "If ref is an editable extraneous line break, remove it from its
1512 	// parent."
1513 	if (isEditable(ref)
1514 	&& isExtraneousLineBreak(ref)) {
1515 		ref.parentNode.removeChild(ref);
1516 	}
1517 }
1518 
1519 // "To remove extraneous line breaks from a node, first remove extraneous line
1520 // breaks before it, then remove extraneous line breaks at the end of it."
1521 function removeExtraneousLineBreaksFrom(node) {
1522 	removeExtraneousLineBreaksBefore(node);
1523 	removeExtraneousLineBreaksAtTheEndOf(node);
1524 }
1525 
1526 //@}
1527 ///// Wrapping a list of nodes /////
1528 //@{
1529 
1530 function wrap(nodeList, siblingCriteria, newParentInstructions, range) {
1531 	// "If not provided, sibling criteria returns false and new parent
1532 	// instructions returns null."
1533 	if (typeof siblingCriteria == "undefined") {
1534 		siblingCriteria = function() { return false };
1535 	}
1536 	if (typeof newParentInstructions == "undefined") {
1537 		newParentInstructions = function() { return null };
1538 	}
1539 
1540 	// "If node list is empty, or the first member of node list is not
1541 	// editable, return null and abort these steps."
1542 	if (!nodeList.length
1543 	|| !isEditable(nodeList[0])) {
1544 		return null;
1545 	}
1546 
1547 	// "If node list's last member is an inline node that's not a br, and node
1548 	// list's last member's nextSibling is a br, append that br to node list."
1549 	if (isInlineNode(nodeList[nodeList.length - 1])
1550 	&& !isHtmlElement(nodeList[nodeList.length - 1], "br")
1551 	&& isHtmlElement(nodeList[nodeList.length - 1].nextSibling, "br")) {
1552 		nodeList.push(nodeList[nodeList.length - 1].nextSibling);
1553 	}
1554 
1555 	// "If the previousSibling of the first member of node list is editable and
1556 	// running sibling criteria on it returns true, let new parent be the
1557 	// previousSibling of the first member of node list."
1558 	var newParent;
1559 	if (isEditable(nodeList[0].previousSibling)
1560 	&& siblingCriteria(nodeList[0].previousSibling)) {
1561 		newParent = nodeList[0].previousSibling;
1562 
1563 	// "Otherwise, if the nextSibling of the last member of node list is
1564 	// editable and running sibling criteria on it returns true, let new parent
1565 	// be the nextSibling of the last member of node list."
1566 	} else if (isEditable(nodeList[nodeList.length - 1].nextSibling)
1567 	&& siblingCriteria(nodeList[nodeList.length - 1].nextSibling)) {
1568 		newParent = nodeList[nodeList.length - 1].nextSibling;
1569 
1570 	// "Otherwise, run new parent instructions, and let new parent be the
1571 	// result."
1572 	} else {
1573 		newParent = newParentInstructions();
1574 	}
1575 
1576 	// "If new parent is null, abort these steps and return null."
1577 	if (!newParent) {
1578 		return null;
1579 	}
1580 
1581 	// "If new parent's parent is null:"
1582 	if (!newParent.parentNode) {
1583 		// "Insert new parent into the parent of the first member of node list
1584 		// immediately before the first member of node list."
1585 		nodeList[0].parentNode.insertBefore(newParent, nodeList[0]);
1586 
1587 		// "If any range has a boundary point with node equal to the parent of
1588 		// new parent and offset equal to the index of new parent, add one to
1589 		// that boundary point's offset."
1590 		//
1591 		// Try to fix range
1592 		var startContainer = range.startContainer, startOffset = range.startOffset,
1593 			endContainer = range.endContainer, endOffset = range.endOffset;
1594 		if (startContainer == newParent.parentNode
1595 		&& startOffset >= getNodeIndex(newParent)) {
1596 			range.setStart(startContainer, startOffset + 1);
1597 		}
1598 		if (endContainer == newParent.parentNode
1599 		&& endOffset >= getNodeIndex(newParent)) {
1600 			range.setEnd(endContainer, endOffset + 1);
1601 		}
1602 
1603 		// Only try to fix the global range. TODO remove globalRange here
1604 		if (globalRange && globalRange !== range) {
1605 			startContainer = globalRange.startContainer, startOffset = globalRange.startOffset,
1606 				endContainer = globalRange.endContainer, endOffset = globalRange.endOffset;
1607 			if (startContainer == newParent.parentNode
1608 			&& startOffset >= getNodeIndex(newParent)) {
1609 				globalRange.setStart(startContainer, startOffset + 1);
1610 			}
1611 			if (endContainer == newParent.parentNode
1612 			&& endOffset >= getNodeIndex(newParent)) {
1613 				globalRange.setEnd(endContainer, endOffset + 1);
1614 			}
1615 		}
1616 	}
1617 
1618 	// "Let original parent be the parent of the first member of node list."
1619 	var originalParent = nodeList[0].parentNode;
1620 
1621 	// "If new parent is before the first member of node list in tree order:"
1622 	if (isBefore(newParent, nodeList[0])) {
1623 		// "If new parent is not an inline node, but the last child of new
1624 		// parent and the first member of node list are both inline nodes, and
1625 		// the last child of new parent is not a br, call createElement("br")
1626 		// on the ownerDocument of new parent and append the result as the last
1627 		// child of new parent."
1628 		if (!isInlineNode(newParent)
1629 		&& isInlineNode(newParent.lastChild)
1630 		&& isInlineNode(nodeList[0])
1631 		&& !isHtmlElement(newParent.lastChild, "BR")) {
1632 			newParent.appendChild(newParent.ownerDocument.createElement("br"));
1633 		}
1634 
1635 		// "For each node in node list, append node as the last child of new
1636 		// parent, preserving ranges."
1637 		for (var i = 0; i < nodeList.length; i++) {
1638 1639 			movePreservingRanges(nodeList[i], newParent, -1, range);
1640 		}
1641 
1642 	// "Otherwise:"
1643 	} else {
1644 		// "If new parent is not an inline node, but the first child of new
1645 		// parent and the last member of node list are both inline nodes, and
1646 		// the last member of node list is not a br, call createElement("br")
1647 		// on the ownerDocument of new parent and insert the result as the
1648 		// first child of new parent."
1649 		if (!isInlineNode(newParent)
1650 		&& isInlineNode(newParent.firstChild)
1651 		&& isInlineNode(nodeList[nodeList.length - 1])
1652 		&& !isHtmlElement(nodeList[nodeList.length - 1], "BR")) {
1653 			newParent.insertBefore(newParent.ownerDocument.createElement("br"), newParent.firstChild);
1654 		}
1655 
1656 		// "For each node in node list, in reverse order, insert node as the
1657 		// first child of new parent, preserving ranges."
1658 		for (var i = nodeList.length - 1; i >= 0; i--) {
1659 			movePreservingRanges(nodeList[i], newParent, 0, range);
1660 		}
1661 	}
1662 
1663 	// "If original parent is editable and has no children, remove it from its
1664 	// parent."
1665 	if (isEditable(originalParent) && !originalParent.hasChildNodes()) {
1666 		originalParent.parentNode.removeChild(originalParent);
1667 1668 	}
1669 
1670 	// "If new parent's nextSibling is editable and running sibling criteria on
1671 	// it returns true:"
1672 	if (isEditable(newParent.nextSibling)
1673 	&& siblingCriteria(newParent.nextSibling)) {
1674 		// "If new parent is not an inline node, but new parent's last child
1675 		// and new parent's nextSibling's first child are both inline nodes,
1676 		// and new parent's last child is not a br, call createElement("br") on
1677 		// the ownerDocument of new parent and append the result as the last
1678 		// child of new parent."
1679 		if (!isInlineNode(newParent)
1680 		&& isInlineNode(newParent.lastChild)
1681 		&& isInlineNode(newParent.nextSibling.firstChild)
1682 		&& !isHtmlElement(newParent.lastChild, "BR")) {
1683 			newParent.appendChild(newParent.ownerDocument.createElement("br"));
1684 		}
1685 
1686 		// "While new parent's nextSibling has children, append its first child
1687 		// as the last child of new parent, preserving ranges."
1688 		while (newParent.nextSibling.hasChildNodes()) {
1689 			movePreservingRanges(newParent.nextSibling.firstChild, newParent, -1, range);
1690 		}
1691 
1692 		// "Remove new parent's nextSibling from its parent."
1693 		newParent.parentNode.removeChild(newParent.nextSibling);
1694 	}
1695 
1696 	// "Remove extraneous line breaks from new parent."
1697 	removeExtraneousLineBreaksFrom(newParent);
1698 
1699 	// "Return new parent."
1700 	return newParent;
1701 }
1702 
1703 
1704 //@}
1705 ///// Allowed children /////
1706 //@{
1707 
1708 // "A name of an element with inline contents is "a", "abbr", "b", "bdi",
1709 // "bdo", "cite", "code", "dfn", "em", "h1", "h2", "h3", "h4", "h5", "h6", "i",
1710 // "kbd", "mark", "p", "pre", "q", "rp", "rt", "ruby", "s", "samp", "small",
1711 // "span", "strong", "sub", "sup", "u", "var", "acronym", "listing", "strike",
1712 // "xmp", "big", "blink", "font", "marquee", "nobr", or "tt"."
1713 var namesOfElementsWithInlineContents = ["a", "abbr", "b", "bdi", "bdo",
1714 	"cite", "code", "dfn", "em", "h1", "h2", "h3", "h4", "h5", "h6", "i",
1715 	"kbd", "mark", "p", "pre", "q", "rp", "rt", "ruby", "s", "samp", "small",
1716 	"span", "strong", "sub", "sup", "u", "var", "acronym", "listing", "strike",
1717 	"xmp", "big", "blink", "font", "marquee", "nobr", "tt"];
1718 
1719 // "An element with inline contents is an HTML element whose local name is a
1720 // name of an element with inline contents."
1721 function isElementWithInlineContents(node) {
1722 	return isHtmlElement(node, namesOfElementsWithInlineContents);
1723 }
1724 
1725 function isAllowedChild(child, parent_) {
1726 	// "If parent is "colgroup", "table", "tbody", "tfoot", "thead", "tr", or
1727 	// an HTML element with local name equal to one of those, and child is a
1728 	// Text node whose data does not consist solely of space characters, return
1729 	// false."
1730 	if (($_( ["colgroup", "table", "tbody", "tfoot", "thead", "tr"] ).indexOf(parent_) != -1
1731 	|| isHtmlElement(parent_, ["colgroup", "table", "tbody", "tfoot", "thead", "tr"]))
1732 	&& typeof child == "object"
1733 	&& child.nodeType == $_.Node.TEXT_NODE
1734 	&& !/^[ \t\n\f\r]*$/.test(child.data)) {
1735 		return false;
1736 	}
1737 
1738 	// "If parent is "script", "style", "plaintext", or "xmp", or an HTML
1739 	// element with local name equal to one of those, and child is not a Text
1740 	// node, return false."
1741 	if (($_( ["script", "style", "plaintext", "xmp"] ).indexOf(parent_) != -1
1742 1743 	|| isHtmlElement(parent_, ["script", "style", "plaintext", "xmp"]))
1744 	&& (typeof child != "object" || child.nodeType != $_.Node.TEXT_NODE)) {
1745 		return false;
1746 1747 	}
1748 
1749 	// "If child is a Document, DocumentFragment, or DocumentType, return
1750 	// false."
1751 	if (typeof child == "object"
1752 	&& (child.nodeType == $_.Node.DOCUMENT_NODE
1753 	|| child.nodeType == $_.Node.DOCUMENT_FRAGMENT_NODE
1754 	|| child.nodeType == $_.Node.DOCUMENT_TYPE_NODE)) {
1755 		return false;
1756 	}
1757 
1758 	// "If child is an HTML element, set child to the local name of child."
1759 	if (isHtmlElement(child)) {
1760 		child = child.tagName.toLowerCase();
1761 	}
1762 
1763 	// "If child is not a string, return true."
1764 	if (typeof child != "string") {
1765 		return true;
1766 	}
1767 
1768 	// "If parent is an HTML element:"
1769 	if (isHtmlElement(parent_)) {
1770 		// "If child is "a", and parent or some ancestor of parent is an a,
1771 		// return false."
1772 		//
1773 		// "If child is a prohibited paragraph child name and parent or some
1774 		// ancestor of parent is an element with inline contents, return
1775 		// false."
1776 		//
1777 		// "If child is "h1", "h2", "h3", "h4", "h5", or "h6", and parent or
1778 		// some ancestor of parent is an HTML element with local name "h1",
1779 		// "h2", "h3", "h4", "h5", or "h6", return false."
1780 		var ancestor = parent_;
1781 		while (ancestor) {
1782 			if (child == "a" && isHtmlElement(ancestor, "a")) {
1783 				return false;
1784 			}
1785 			if ($_( prohibitedParagraphChildNames ).indexOf(child) != -1
1786 			&& isElementWithInlineContents(ancestor)) {
1787 				return false;
1788 			}
1789 			if (/^h[1-6]$/.test(child)
1790 			&& isHtmlElement(ancestor)
1791 			&& /^H[1-6]$/.test(ancestor.tagName)) {
1792 				return false;
1793 			}
1794 			ancestor = ancestor.parentNode;
1795 		}
1796 
1797 		// "Let parent be the local name of parent."
1798 		parent_ = parent_.tagName.toLowerCase();
1799 	}
1800 
1801 	// "If parent is an Element or DocumentFragment, return true."
1802 	if (typeof parent_ == "object"
1803 	&& (parent_.nodeType == $_.Node.ELEMENT_NODE
1804 	|| parent_.nodeType == $_.Node.DOCUMENT_FRAGMENT_NODE)) {
1805 		return true;
1806 	}
1807 
1808 	// "If parent is not a string, return false."
1809 	if (typeof parent_ != "string") {
1810 		return false;
1811 	}
1812 
1813 	// "If parent is on the left-hand side of an entry on the following list,
1814 	// then return true if child is listed on the right-hand side of that
1815 	// entry, and false otherwise."
1816 	switch (parent_) {
1817 		case "colgroup":
1818 			return child == "col";
1819 		case "table":
1820 			return $_( ["caption", "col", "colgroup", "tbody", "td", "tfoot", "th", "thead", "tr"] ).indexOf(child) != -1;
1821 		case "tbody":
1822 		case "thead":
1823 		case "tfoot":
1824 			return $_( ["td", "th", "tr"] ).indexOf(child) != -1;
1825 		case "tr":
1826 			return $_( ["td", "th"] ).indexOf(child) != -1;
1827 		case "dl":
1828 			return $_( ["dt", "dd"] ).indexOf(child) != -1;
1829 		case "dir":
1830 		case "ol":
1831 		case "ul":
1832 			return $_( ["dir", "li", "ol", "ul"] ).indexOf(child) != -1;
1833 		case "hgroup":
1834 			return /^h[1-6]$/.test(child);
1835 	}
1836 
1837 	// "If child is "body", "caption", "col", "colgroup", "frame", "frameset",
1838 	// "head", "html", "tbody", "td", "tfoot", "th", "thead", or "tr", return
1839 	// false."
1840 	if ($_( ["body", "caption", "col", "colgroup", "frame", "frameset", "head",
1841 	"html", "tbody", "td", "tfoot", "th", "thead", "tr"] ).indexOf(child) != -1) {
1842 		return false;
1843 	}
1844 
1845 	// "If child is "dd" or "dt" and parent is not "dl", return false."
1846 	if ($_( ["dd", "dt"] ).indexOf(child) != -1
1847 	&& parent_ != "dl") {
1848 		return false;
1849 	}
1850 
1851 	// "If child is "li" and parent is not "ol" or "ul", return false."
1852 	if (child == "li"
1853 1854 	&& parent_ != "ol"
1855 	&& parent_ != "ul") {
1856 		return false;
1857 	}
1858 
1859 	// "If parent is on the left-hand side of an entry on the following list
1860 	// and child is listed on the right-hand side of that entry, return false."
1861 	var table = [
1862 		[["a"], ["a"]],
1863 		[["dd", "dt"], ["dd", "dt"]],
1864 		[["h1", "h2", "h3", "h4", "h5", "h6"], ["h1", "h2", "h3", "h4", "h5", "h6"]],
1865 		[["li"], ["li"]],
1866 		[["nobr"], ["nobr"]],
1867 		[namesOfElementsWithInlineContents, prohibitedParagraphChildNames],
1868 		[["td", "th"], ["caption", "col", "colgroup", "tbody", "td", "tfoot", "th", "thead", "tr"]]
1869 	];
1870 	for (var i = 0; i < table.length; i++) {
1871 		if ($_( table[i][0] ).indexOf(parent_) != -1
1872 		&& $_( table[i][1] ).indexOf(child) != -1) {
1873 			return false;
1874 		}
1875 	}
1876 
1877 	// "Return true."
1878 	return true;
1879 }
1880 
1881 
1882 //@}
1883 
1884 //////////////////////////////////////
1885 ///// Inline formatting commands /////
1886 //////////////////////////////////////
1887 
1888 ///// Inline formatting command definitions /////
1889 //@{
1890 
1891 // "A node node is effectively contained in a range range if range is not
1892 // collapsed, and at least one of the following holds:"
1893 function isEffectivelyContained(node, range) {
1894 	if (range.collapsed) {
1895 		return false;
1896 	}
1897 
1898 	// "node is contained in range."
1899 	if (isContained(node, range)) {
1900 		return true;
1901 	}
1902 
1903 	// "node is range's start node, it is a Text node, and its length is
1904 	// different from range's start offset."
1905 	if (node == range.startContainer
1906 	&& node.nodeType == $_.Node.TEXT_NODE
1907 	&& getNodeLength(node) != range.startOffset) {
1908 		return true;
1909 	}
1910 
1911 	// "node is range's end node, it is a Text node, and range's end offset is
1912 	// not 0."
1913 	if (node == range.endContainer
1914 	&& node.nodeType == $_.Node.TEXT_NODE
1915 	&& range.endOffset != 0) {
1916 		return true;
1917 	}
1918 
1919 	// "node has at least one child; and all its children are effectively
1920 	// contained in range; and either range's start node is not a descendant of
1921 	// node or is not a Text node or range's start offset is zero; and either
1922 	// range's end node is not a descendant of node or is not a Text node or
1923 	// range's end offset is its end node's length."
1924 	if (node.hasChildNodes()
1925 	&& $_(node.childNodes).every(function(child) { return isEffectivelyContained(child, range) })
1926 	&& (!isDescendant(range.startContainer, node)
1927 	|| range.startContainer.nodeType != $_.Node.TEXT_NODE
1928 	|| range.startOffset == 0)
1929 	&& (!isDescendant(range.endContainer, node)
1930 	|| range.endContainer.nodeType != $_.Node.TEXT_NODE
1931 	|| range.endOffset == getNodeLength(range.endContainer))) {
1932 		return true;
1933 	}
1934 
1935 	return false;
1936 }
1937 
1938 // Like get(All)ContainedNodes(), but for effectively contained nodes.
1939 function getEffectivelyContainedNodes(range, condition) {
1940 	if (typeof condition == "undefined") {
1941 		condition = function() { return true };
1942 	}
1943 	var node = range.startContainer;
1944 	while (isEffectivelyContained(node.parentNode, range)) {
1945 		node = node.parentNode;
1946 	}
1947 
1948 	var stop = nextNodeDescendants(range.endContainer);
1949 
1950 	var nodeList = [];
1951 	while (isBefore(node, stop)) {
1952 		if (isEffectivelyContained(node, range)
1953 		&& condition(node)) {
1954 			nodeList.push(node);
1955 			node = nextNodeDescendants(node);
1956 			continue;
1957 		}
1958 		node = nextNode(node);
1959 	}
1960 	return nodeList;
1961 }
1962 
1963 function getAllEffectivelyContainedNodes(range, condition) {
1964 	if (typeof condition == "undefined") {
1965 		condition = function() { return true };
1966 	}
1967 	var node = range.startContainer;
1968 	while (isEffectivelyContained(node.parentNode, range)) {
1969 		node = node.parentNode;
1970 	}
1971 
1972 	var stop = nextNodeDescendants(range.endContainer);
1973 
1974 	var nodeList = [];
1975 	while (isBefore(node, stop)) {
1976 		if (isEffectivelyContained(node, range)
1977 		&& condition(node)) {
1978 			nodeList.push(node);
1979 		}
1980 		node = nextNode(node);
1981 	}
1982 	return nodeList;
1983 }
1984 
1985 // "A modifiable element is a b, em, i, s, span, strong, sub, sup, or u element
1986 // with no attributes except possibly style; or a font element with no
1987 // attributes except possibly style, color, face, and/or size; or an a element
1988 // with no attributes except possibly style and/or href."
1989 function isModifiableElement(node) {
1990 	if (!isHtmlElement(node)) {
1991 		return false;
1992 	}
1993 
1994 	if ($_( ["B", "EM", "I", "S", "SPAN", "STRIKE", "STRONG", "SUB", "SUP", "U"] ).indexOf(node.tagName) != -1) {
1995 		if (node.attributes.length == 0) {
1996 			return true;
1997 		}
1998 
1999 		if (node.attributes.length == 1
2000 		&& $_( node ).hasAttribute("style")) {
2001 			return true;
2002 		}
2003 	}
2004 
2005 	if (node.tagName == "FONT" || node.tagName == "A") {
2006 		var numAttrs = node.attributes.length;
2007 
2008 		if ($_( node ).hasAttribute("style")) {
2009 			numAttrs--;
2010 		}
2011 
2012 		if (node.tagName == "FONT") {
2013 			if ($_( node ).hasAttribute("color")) {
2014 				numAttrs--;
2015 			}
2016 
2017 			if ($_( node ).hasAttribute("face")) {
2018 				numAttrs--;
2019 			}
2020 
2021 			if ($_( node ).hasAttribute("size")) {
2022 				numAttrs--;
2023 2024 			}
2025 		}
2026 
2027 		if (node.tagName == "A"
2028 		&& $_( node ).hasAttribute("href")) {
2029 			numAttrs--;
2030 		}
2031 
2032 		if (numAttrs == 0) {
2033 			return true;
2034 		}
2035 	}
2036 
2037 	return false;
2038 }
2039 
2040 function isSimpleModifiableElement(node) {
2041 	// "A simple modifiable element is an HTML element for which at least one
2042 	// of the following holds:"
2043 	if (!isHtmlElement(node)) {
2044 		return false;
2045 	}
2046 
2047 	// Only these elements can possibly be a simple modifiable element.
2048 	if ($_( ["A", "B", "EM", "FONT", "I", "S", "SPAN", "STRIKE", "STRONG", "SUB", "SUP", "U"] ).indexOf(node.tagName) == -1) {
2049 		return false;
2050 	}
2051 
2052 	// "It is an a, b, em, font, i, s, span, strike, strong, sub, sup, or u
2053 	// element with no attributes."
2054 	if (node.attributes.length == 0) {
2055 		return true;
2056 	}
2057 
2058 	// If it's got more than one attribute, everything after this fails.
2059 	if (node.attributes.length > 1) {
2060 		return false;
2061 	}
2062 
2063 	// "It is an a, b, em, font, i, s, span, strike, strong, sub, sup, or u
2064 	// element with exactly one attribute, which is style, which sets no CSS
2065 	// properties (including invalid or unrecognized properties)."
2066 	//
2067 	// Not gonna try for invalid or unrecognized.
2068 	if ($_( node ).hasAttribute("style")
2069 	&& getStyleLength(node) == 0) {
2070 		return true;
2071 	}
2072 
2073 	// "It is an a element with exactly one attribute, which is href."
2074 	if (node.tagName == "A"
2075 	&& $_( node ).hasAttribute("href")) {
2076 		return true;
2077 	}
2078 
2079 	// "It is a font element with exactly one attribute, which is either color,
2080 	// face, or size."
2081 	if (node.tagName == "FONT"
2082 	&& ($_( node ).hasAttribute("color")
2083 	|| $_( node ).hasAttribute("face")
2084 	|| $_( node ).hasAttribute("size")
2085 	)) {
2086 		return true;
2087 	}
2088 
2089 	// "It is a b or strong element with exactly one attribute, which is style,
2090 	// and the style attribute sets exactly one CSS property (including invalid
2091 	// or unrecognized properties), which is "font-weight"."
2092 	if ((node.tagName == "B" || node.tagName == "STRONG")
2093 	&& $_( node ).hasAttribute("style")
2094 	&& getStyleLength(node) == 1
2095 	&& node.style.fontWeight != "") {
2096 		return true;
2097 	}
2098 
2099 	// "It is an i or em element with exactly one attribute, which is style,
2100 	// and the style attribute sets exactly one CSS property (including invalid
2101 	// or unrecognized properties), which is "font-style"."
2102 	if ((node.tagName == "I" || node.tagName == "EM")
2103 	&& $_( node ).hasAttribute("style")
2104 	&& getStyleLength(node) == 1
2105 	&& node.style.fontStyle != "") {
2106 		return true;
2107 	}
2108 
2109 	// "It is an a, font, or span element with exactly one attribute, which is
2110 2111 	// style, and the style attribute sets exactly one CSS property (including
2112 	// invalid or unrecognized properties), and that property is not
2113 	// "text-decoration"."
2114 	if ((node.tagName == "A" || node.tagName == "FONT" || node.tagName == "SPAN")
2115 	&& $_( node ).hasAttribute("style")
2116 	&& getStyleLength(node) == 1
2117 	&& node.style.textDecoration == "") {
2118 		return true;
2119 	}
2120 
2121 	// "It is an a, font, s, span, strike, or u element with exactly one
2122 	// attribute, which is style, and the style attribute sets exactly one CSS
2123 	// property (including invalid or unrecognized properties), which is
2124 	// "text-decoration", which is set to "line-through" or "underline" or
2125 	// "overline" or "none"."
2126 	if ($_( ["A", "FONT", "S", "SPAN", "STRIKE", "U"] ).indexOf(node.tagName) != -1
2127 	&& $_( node ).hasAttribute("style")
2128 	&& getStyleLength(node) == 1
2129 	&& (node.style.textDecoration == "line-through"
2130 	|| node.style.textDecoration == "underline"
2131 	|| node.style.textDecoration == "overline"
2132 	|| node.style.textDecoration == "none")) {
2133 		return true;
2134 	}
2135 
2136 	return false;
2137 }
2138 
2139 // "Two quantities are equivalent values for a command if either both are null,
2140 // or both are strings and they're equal and the command does not define any
2141 // equivalent values, or both are strings and the command defines equivalent
2142 // values and they match the definition."
2143 function areEquivalentValues(command, val1, val2) {
2144 	if (val1 === null && val2 === null) {
2145 		return true;
2146 	}
2147 
2148 	if (typeof val1 == "string"
2149 	&& typeof val2 == "string"
2150 	&& val1 == val2
2151 	&& !("equivalentValues" in commands[command])) {
2152 		return true;
2153 	}
2154 
2155 	if (typeof val1 == "string"
2156 	&& typeof val2 == "string"
2157 	&& "equivalentValues" in commands[command]
2158 	&& commands[command].equivalentValues(val1, val2)) {
2159 		return true;
2160 	}
2161 
2162 	return false;
2163 }
2164 
2165 // "Two quantities are loosely equivalent values for a command if either they
2166 // are equivalent values for the command, or if the command is the fontSize
2167 // command; one of the quantities is one of "xx-small", "small", "medium",
2168 // "large", "x-large", "xx-large", or "xxx-large"; and the other quantity is
2169 // the resolved value of "font-size" on a font element whose size attribute has
2170 // the corresponding value set ("1" through "7" respectively)."
2171 function areLooselyEquivalentValues(command, val1, val2) {
2172 	if (areEquivalentValues(command, val1, val2)) {
2173 		return true;
2174 	}
2175 
2176 	if (command != "fontsize"
2177 	|| typeof val1 != "string"
2178 	|| typeof val2 != "string") {
2179 		return false;
2180 	}
2181 
2182 	// Static variables in JavaScript?
2183 	var callee = areLooselyEquivalentValues;
2184 	if (callee.sizeMap === undefined) {
2185 		callee.sizeMap = {};
2186 		var font = document.createElement("font");
2187 		document.body.appendChild(font);
2188 		$_( ["xx-small", "small", "medium", "large", "x-large", "xx-large",
2189 		"xxx-large"] ).forEach(function(keyword) {
2190 			font.size = cssSizeToLegacy(keyword);
2191 			callee.sizeMap[keyword] = $_.getComputedStyle(font).fontSize;
2192 		});
2193 		document.body.removeChild(font);
2194 	}
2195 
2196 	return val1 === callee.sizeMap[val2]
2197 		|| val2 === callee.sizeMap[val1];
2198 }
2199 
2200 //@}
2201 ///// Assorted inline formatting command algorithms /////
2202 //@{
2203 
2204 function getEffectiveCommandValue(node, command) {
2205 	// "If neither node nor its parent is an Element, return null."
2206 	if (node.nodeType != $_.Node.ELEMENT_NODE
2207 	&& (!node.parentNode || node.parentNode.nodeType != $_.Node.ELEMENT_NODE)) {
2208 		return null;
2209 	}
2210 
2211 	// "If node is not an Element, return the effective command value of its
2212 	// parent for command."
2213 	if (node.nodeType != $_.Node.ELEMENT_NODE) {
2214 		return getEffectiveCommandValue(node.parentNode, command);
2215 	}
2216 
2217 	// "If command is "createLink" or "unlink":"
2218 	if (command == "createlink" || command == "unlink") {
2219 		// "While node is not null, and is not an a element that has an href
2220 		// attribute, set node to its parent."
2221 		while (node
2222 		&& (!isHtmlElement(node)
2223 		|| node.tagName != "A"
2224 		|| !$_( node ).hasAttribute("href"))) {
2225 			node = node.parentNode;
2226 		}
2227 
2228 		// "If node is null, return null."
2229 		if (!node) {
2230 			return null;
2231 		}
2232 
2233 		// "Return the value of node's href attribute."
2234 		return node.getAttribute("href");
2235 	}
2236 
2237 	// "If command is "backColor" or "hiliteColor":"
2238 	if (command == "backcolor"
2239 	|| command == "hilitecolor") {
2240 		// "While the resolved value of "background-color" on node is any
2241 		// fully transparent value, and node's parent is an Element, set
2242 		// node to its parent."
2243 		//
2244 		// Another lame hack to avoid flawed APIs.
2245 		while (($_.getComputedStyle(node).backgroundColor == "rgba(0, 0, 0, 0)"
2246 		|| $_.getComputedStyle(node).backgroundColor === ""
2247 		|| $_.getComputedStyle(node).backgroundColor == "transparent")
2248 		&& node.parentNode
2249 		&& node.parentNode.nodeType == $_.Node.ELEMENT_NODE) {
2250 			node = node.parentNode;
2251 		}
2252 
2253 		// "If the resolved value of "background-color" on node is a fully
2254 		// transparent value, return "rgb(255, 255, 255)"."
2255 		if ($_.getComputedStyle(node).backgroundColor == "rgba(0, 0, 0, 0)"
2256         || $_.getComputedStyle(node).backgroundColor === ""
2257         || $_.getComputedStyle(node).backgroundColor == "transparent") {
2258 			return "rgb(255, 255, 255)";
2259 		}
2260 
2261 		// "Otherwise, return the resolved value of "background-color" for
2262 		// node."
2263 		return $_.getComputedStyle(node).backgroundColor;
2264 	}
2265 
2266 	// "If command is "subscript" or "superscript":"
2267 	if (command == "subscript" || command == "superscript") {
2268 		// "Let affected by subscript and affected by superscript be two
2269 		// boolean variables, both initially false."
2270 		var affectedBySubscript = false;
2271 		var affectedBySuperscript = false;
2272 
2273 		// "While node is an inline node:"
2274 		while (isInlineNode(node)) {
2275 			var verticalAlign = $_.getComputedStyle(node).verticalAlign;
2276 
2277 			// "If node is a sub, set affected by subscript to true."
2278 			if (isHtmlElement(node, "sub")) {
2279 				affectedBySubscript = true;
2280 			// "Otherwise, if node is a sup, set affected by superscript to
2281 			// true."
2282 			} else if (isHtmlElement(node, "sup")) {
2283 				affectedBySuperscript = true;
2284 			}
2285 
2286 			// "Set node to its parent."
2287 			node = node.parentNode;
2288 		}
2289 
2290 		// "If affected by subscript and affected by superscript are both true,
2291 		// return the string "mixed"."
2292 		if (affectedBySubscript && affectedBySuperscript) {
2293 			return "mixed";
2294 		}
2295 
2296 		// "If affected by subscript is true, return "subscript"."
2297 		if (affectedBySubscript) {
2298 			return "subscript";
2299 		}
2300 
2301 		// "If affected by superscript is true, return "superscript"."
2302 		if (affectedBySuperscript) {
2303 			return "superscript";
2304 		}
2305 
2306 		// "Return null."
2307 		return null;
2308 	}
2309 
2310 	// "If command is "strikethrough", and the "text-decoration" property of
2311 	// node or any of its ancestors has resolved value containing
2312 	// "line-through", return "line-through". Otherwise, return null."
2313 	if (command == "strikethrough") {
2314 		do {
2315 			if ($_.getComputedStyle(node).textDecoration.indexOf("line-through") != -1) {
2316 				return "line-through";
2317 			}
2318 			node = node.parentNode;
2319 		} while (node && node.nodeType == $_.Node.ELEMENT_NODE);
2320 		return null;
2321 	}
2322 
2323 	// "If command is "underline", and the "text-decoration" property of node
2324 	// or any of its ancestors has resolved value containing "underline",
2325 	// return "underline". Otherwise, return null."
2326 	if (command == "underline") {
2327 		do {
2328 			if ($_.getComputedStyle(node).textDecoration.indexOf("underline") != -1) {
2329 				return "underline";
2330 			}
2331 			node = node.parentNode;
2332 		} while (node && node.nodeType == $_.Node.ELEMENT_NODE);
2333 		return null;
2334 	}
2335 
2336 	if (!("relevantCssProperty" in commands[command])) {
2337 		throw "Bug: no relevantCssProperty for " + command + " in getEffectiveCommandValue";
2338 	}
2339 
2340 	// "Return the resolved value for node of the relevant CSS property for
2341 	// command."
2342 	return $_.getComputedStyle(node)[commands[command].relevantCssProperty].toString();
2343 }
2344 
2345 function getSpecifiedCommandValue(element, command) {
2346 	// "If command is "backColor" or "hiliteColor" and element's display
2347 	// property does not have resolved value "inline", return null."
2348 	if ((command == "backcolor" || command == "hilitecolor")
2349 	&& $_.getComputedStyle(element).display != "inline") {
2350 		return null;
2351 	}
2352 
2353 	// "If command is "createLink" or "unlink":"
2354 	if (command == "createlink" || command == "unlink") {
2355 		// "If element is an a element and has an href attribute, return the
2356 		// value of that attribute."
2357 		if (isHtmlElement(element)
2358 		&& element.tagName == "A"
2359 		&& $_( element ).hasAttribute("href")) {
2360 			return element.getAttribute("href");
2361 		}
2362 
2363 		// "Return null."
2364 		return null;
2365 	}
2366 
2367 	// "If command is "subscript" or "superscript":"
2368 	if (command == "subscript" || command == "superscript") {
2369 		// "If element is a sup, return "superscript"."
2370 		if (isHtmlElement(element, "sup")) {
2371 			return "superscript";
2372 		}
2373 
2374 		// "If element is a sub, return "subscript"."
2375 		if (isHtmlElement(element, "sub")) {
2376 			return "subscript";
2377 		}
2378 
2379 		// "Return null."
2380 		return null;
2381 	}
2382 
2383 	// "If command is "strikethrough", and element has a style attribute set,
2384 	// and that attribute sets "text-decoration":"
2385 	if (command == "strikethrough"
2386 	&& element.style.textDecoration != "") {
2387 		// "If element's style attribute sets "text-decoration" to a value
2388 		// containing "line-through", return "line-through"."
2389 		if (element.style.textDecoration.indexOf("line-through") != -1) {
2390 			return "line-through";
2391 		}
2392 
2393 		// "Return null."
2394 		return null;
2395 	}
2396 
2397 	// "If command is "strikethrough" and element is a s or strike element,
2398 	// return "line-through"."
2399 	if (command == "strikethrough"
2400 	&& isHtmlElement(element, ["S", "STRIKE"])) {
2401 		return "line-through";
2402 	}
2403 
2404 	// "If command is "underline", and element has a style attribute set, and
2405 	// that attribute sets "text-decoration":"
2406 	if (command == "underline"
2407 	&& element.style.textDecoration != "") {
2408 		// "If element's style attribute sets "text-decoration" to a value
2409 		// containing "underline", return "underline"."
2410 		if (element.style.textDecoration.indexOf("underline") != -1) {
2411 			return "underline";
2412 		}
2413 
2414 		// "Return null."
2415 		return null;
2416 	}
2417 
2418 	// "If command is "underline" and element is a u element, return
2419 	// "underline"."
2420 	if (command == "underline"
2421 	&& isHtmlElement(element, "U")) {
2422 		return "underline";
2423 	}
2424 
2425 	// "Let property be the relevant CSS property for command."
2426 	var property = commands[command].relevantCssProperty;
2427 
2428 	// "If property is null, return null."
2429 	if (property === null) {
2430 		return null;
2431 	}
2432 
2433 	// "If element has a style attribute set, and that attribute has the
2434 	// effect of setting property, return the value that it sets property to."
2435 	if (element.style[property] != "") {
2436 		return element.style[property];
2437 	}
2438 
2439 	// "If element is a font element that has an attribute whose effect is
2440 	// to create a presentational hint for property, return the value that the
2441 	// hint sets property to.  (For a size of 7, this will be the non-CSS value
2442 	// "xxx-large".)"
2443 	if (isHtmlNamespace(element.namespaceURI)
2444 	&& element.tagName == "FONT") {
2445 		if (property == "color" && $_( element ).hasAttribute("color")) {
2446 			return element.color;
2447 		}
2448 		if (property == "fontFamily" && $_( element ).hasAttribute("face")) {
2449 			return element.face;
2450 		}
2451 		if (property == "fontSize" && $_( element ).hasAttribute("size")) {
2452 			// This is not even close to correct in general.
2453 			var size = parseInt(element.size);
2454 			if (size < 1) {
2455 				size = 1;
2456 			}
2457 			if (size > 7) {
2458 				size = 7;
2459 			}
2460 			return {
2461 				1: "xx-small",
2462 				2: "small",
2463 				3: "medium",
2464 				4: "large",
2465 				5: "x-large",
2466 				6: "xx-large",
2467 				7: "xxx-large"
2468 			}[size];
2469 		}
2470 	}
2471 
2472 	// "If element is in the following list, and property is equal to the
2473 	// CSS property name listed for it, return the string listed for it."
2474 	//
2475 	// A list follows, whose meaning is copied here.
2476 	if (property == "fontWeight"
2477 	&& (element.tagName == "B" || element.tagName == "STRONG")) {
2478 		return "bold";
2479 	}
2480 	if (property == "fontStyle"
2481 	&& (element.tagName == "I" || element.tagName == "EM")) {
2482 		return "italic";
2483 	}
2484 
2485 	// "Return null."
2486 	return null;
2487 }
2488 
2489 function reorderModifiableDescendants(node, command, newValue, range) {
2490 	// "Let candidate equal node."
2491 	var candidate = node;
2492 
2493 	// "While candidate is a modifiable element, and candidate has exactly one
2494 	// child, and that child is also a modifiable element, and candidate is not
2495 	// a simple modifiable element or candidate's specified command value for
2496 	// command is not equivalent to new value, set candidate to its child."
2497 	while (isModifiableElement(candidate)
2498 	&& candidate.childNodes.length == 1
2499 	&& isModifiableElement(candidate.firstChild)
2500 	&& (!isSimpleModifiableElement(candidate)
2501 	|| !areEquivalentValues(command, getSpecifiedCommandValue(candidate, command), newValue))) {
2502 		candidate = candidate.firstChild;
2503 	}
2504 
2505 	// "If candidate is node, or is not a simple modifiable element, or its
2506 	// specified command value is not equivalent to new value, or its effective
2507 	// command value is not loosely equivalent to new value, abort these
2508 	// steps."
2509 	if (candidate == node
2510 	|| !isSimpleModifiableElement(candidate)
2511 	|| !areEquivalentValues(command, getSpecifiedCommandValue(candidate, command), newValue)
2512 	|| !areLooselyEquivalentValues(command, getEffectiveCommandValue(candidate, command), newValue)) {
2513 		return;
2514 	}
2515 
2516 	// "While candidate has children, insert the first child of candidate into
2517 	// candidate's parent immediately before candidate, preserving ranges."
2518 	while (candidate.hasChildNodes()) {
2519 		movePreservingRanges(candidate.firstChild, candidate.parentNode, getNodeIndex(candidate), range);
2520 	}
2521 
2522 	// "Insert candidate into node's parent immediately after node."
2523 	node.parentNode.insertBefore(candidate, node.nextSibling);
2524 
2525 	// "Append the node as the last child of candidate, preserving ranges."
2526 	movePreservingRanges(node, candidate, -1, range);
2527 }
2528 
2529 function recordValues(nodeList) {
2530 	// "Let values be a list of (node, command, specified command value)
2531 	// triples, initially empty."
2532 	var values = [];
2533 
2534 	// "For each node in node list, for each command in the list "subscript",
2535 	// "bold", "fontName", "fontSize", "foreColor", "hiliteColor", "italic",
2536 	// "strikethrough", and "underline" in that order:"
2537 	$_( nodeList ).forEach(function(node) {
2538 		$_( ["subscript", "bold", "fontname", "fontsize", "forecolor",
2539 		"hilitecolor", "italic", "strikethrough", "underline"] ).forEach(function(command) {
2540 			// "Let ancestor equal node."
2541 			var ancestor = node;
2542 
2543 			// "If ancestor is not an Element, set it to its parent."
2544 			if (ancestor.nodeType != $_.Node.ELEMENT_NODE) {
2545 				ancestor = ancestor.parentNode;
2546 			}
2547 
2548 			// "While ancestor is an Element and its specified command value
2549 			// for command is null, set it to its parent."
2550 			while (ancestor
2551 			&& ancestor.nodeType == $_.Node.ELEMENT_NODE
2552 			&& getSpecifiedCommandValue(ancestor, command) === null) {
2553 				ancestor = ancestor.parentNode;
2554 			}
2555 
2556 			// "If ancestor is an Element, add (node, command, ancestor's
2557 			// specified command value for command) to values. Otherwise add
2558 			// (node, command, null) to values."
2559 			if (ancestor && ancestor.nodeType == $_.Node.ELEMENT_NODE) {
2560 				values.push([node, command, getSpecifiedCommandValue(ancestor, command)]);
2561 			} else {
2562 				values.push([node, command, null]);
2563 			}
2564 		});
2565 	});
2566 
2567 	// "Return values."
2568 	return values;
2569 }
2570 
2571 function restoreValues(values, range) {
2572 	// "For each (node, command, value) triple in values:"
2573 	$_( values ).forEach(function(triple) {
2574 		var node = triple[0];
2575 		var command = triple[1];
2576 		var value = triple[2];
2577 
2578 		// "Let ancestor equal node."
2579 		var ancestor = node;
2580 
2581 		// "If ancestor is not an Element, set it to its parent."
2582 		if (!ancestor || ancestor.nodeType != $_.Node.ELEMENT_NODE) {
2583 			ancestor = ancestor.parentNode;
2584 		}
2585 
2586 		// "While ancestor is an Element and its specified command value for
2587 		// command is null, set it to its parent."
2588 		while (ancestor
2589 		&& ancestor.nodeType == $_.Node.ELEMENT_NODE
2590 		&& getSpecifiedCommandValue(ancestor, command) === null) {
2591 			ancestor = ancestor.parentNode;
2592 		}
2593 
2594 		// "If value is null and ancestor is an Element, push down values on
2595 		// node for command, with new value null."
2596 		if (value === null
2597 		&& ancestor
2598 		&& ancestor.nodeType == $_.Node.ELEMENT_NODE) {
2599 			pushDownValues(node, command, null, range);
2600 
2601 		// "Otherwise, if ancestor is an Element and its specified command
2602 		// value for command is not equivalent to value, or if ancestor is not
2603 		// an Element and value is not null, force the value of command to
2604 		// value on node."
2605 		} else if ((ancestor
2606 		&& ancestor.nodeType == $_.Node.ELEMENT_NODE
2607 		&& !areEquivalentValues(command, getSpecifiedCommandValue(ancestor, command), value))
2608 		|| ((!ancestor || ancestor.nodeType != $_.Node.ELEMENT_NODE)
2609 		&& value !== null)) {
2610 			forceValue(node, command, value, range);
2611 		}
2612 	});
2613 }
2614 
2615 
2616 //@}
2617 ///// Clearing an element's value /////
2618 //@{
2619 
2620 function clearValue(element, command, range) {
2621 	// "If element is not editable, return the empty list."
2622 	if (!isEditable(element)) {
2623 		return [];
2624 	}
2625 
2626 	// "If element's specified command value for command is null, return the
2627 	// empty list."
2628 	if (getSpecifiedCommandValue(element, command) === null) {
2629 		return [];
2630 	}
2631 
2632 	// "If element is a simple modifiable element:"
2633 	if (isSimpleModifiableElement(element)) {
2634 		// "Let children be the children of element."
2635 		var children = Array.prototype.slice.call(toArray(element.childNodes));
2636 
2637 		// "For each child in children, insert child into element's parent
2638 		// immediately before element, preserving ranges."
2639 		for (var i = 0; i < children.length; i++) {
2640 			movePreservingRanges(children[i], element.parentNode, getNodeIndex(element), range);
2641 		}
2642 
2643 		// "Remove element from its parent."
2644 		element.parentNode.removeChild(element);
2645 
2646 		// "Return children."
2647 		return children;
2648 	}
2649 
2650 	// "If command is "strikethrough", and element has a style attribute that
2651 	// sets "text-decoration" to some value containing "line-through", delete
2652 	// "line-through" from the value."
2653 	if (command == "strikethrough"
2654 	&& element.style.textDecoration.indexOf("line-through") != -1) {
2655 		if (element.style.textDecoration == "line-through") {
2656 			element.style.textDecoration = "";
2657 		} else {
2658 			element.style.textDecoration = element.style.textDecoration.replace("line-through", "");
2659 		}
2660 		if (element.getAttribute("style") == "") {
2661 			element.removeAttribute("style");
2662 		}
2663 	}
2664 
2665 	// "If command is "underline", and element has a style attribute that sets
2666 	// "text-decoration" to some value containing "underline", delete
2667 	// "underline" from the value."
2668 	if (command == "underline"
2669 	&& element.style.textDecoration.indexOf("underline") != -1) {
2670 		if (element.style.textDecoration == "underline") {
2671 			element.style.textDecoration = "";
2672 		} else {
2673 			element.style.textDecoration = element.style.textDecoration.replace("underline", "");
2674 		}
2675 		if (element.getAttribute("style") == "") {
2676 			element.removeAttribute("style");
2677 		}
2678 	}
2679 
2680 	// "If the relevant CSS property for command is not null, unset the CSS
2681 	// property property of element."
2682 	if (commands[command].relevantCssProperty !== null) {
2683 		element.style[commands[command].relevantCssProperty] = '';
2684 		if (element.getAttribute("style") == "") {
2685 			element.removeAttribute("style");
2686 		}
2687 	}
2688 
2689 	// "If element is a font element:"
2690 	if (isHtmlNamespace(element.namespaceURI) && element.tagName == "FONT") {
2691 		// "If command is "foreColor", unset element's color attribute, if set."
2692 		if (command == "forecolor") {
2693 			element.removeAttribute("color");
2694 		}
2695 
2696 		// "If command is "fontName", unset element's face attribute, if set."
2697 		if (command == "fontname") {
2698 			element.removeAttribute("face");
2699 		}
2700 
2701 		// "If command is "fontSize", unset element's size attribute, if set."
2702 		if (command == "fontsize") {
2703 			element.removeAttribute("size");
2704 		}
2705 	}
2706 
2707 	// "If element is an a element and command is "createLink" or "unlink",
2708 	// unset the href property of element."
2709 	if (isHtmlElement(element, "A")
2710 	&& (command == "createlink" || command == "unlink")) {
2711 		element.removeAttribute("href");
2712 	}
2713 
2714 	// "If element's specified command value for command is null, return the
2715 	// empty list."
2716 	if (getSpecifiedCommandValue(element, command) === null) {
2717 		return [];
2718 	}
2719 
2720 	// "Set the tag name of element to "span", and return the one-node list
2721 	// consisting of the result."
2722 	return [setTagName(element, "span", range)];
2723 }
2724 
2725 
2726 //@}
2727 ///// Pushing down values /////
2728 //@{
2729 
2730 function pushDownValues(node, command, newValue, range) {
2731 	// "If node's parent is not an Element, abort this algorithm."
2732 	if (!node.parentNode
2733 	|| node.parentNode.nodeType != $_.Node.ELEMENT_NODE) {
2734 		return;
2735 	}
2736 
2737 	// "If the effective command value of command is loosely equivalent to new
2738 	// value on node, abort this algorithm."
2739 	if (areLooselyEquivalentValues(command, getEffectiveCommandValue(node, command), newValue)) {
2740 		return;
2741 	}
2742 
2743 	// "Let current ancestor be node's parent."
2744 	var currentAncestor = node.parentNode;
2745 
2746 	// "Let ancestor list be a list of Nodes, initially empty."
2747 	var ancestorList = [];
2748 
2749 	// "While current ancestor is an editable Element and the effective command
2750 	// value of command is not loosely equivalent to new value on it, append
2751 	// current ancestor to ancestor list, then set current ancestor to its
2752 	// parent."
2753 	while (isEditable(currentAncestor)
2754 	&& currentAncestor.nodeType == $_.Node.ELEMENT_NODE
2755 	&& !areLooselyEquivalentValues(command, getEffectiveCommandValue(currentAncestor, command), newValue)) {
2756 		ancestorList.push(currentAncestor);
2757 		currentAncestor = currentAncestor.parentNode;
2758 	}
2759 
2760 	// "If ancestor list is empty, abort this algorithm."
2761 	if (!ancestorList.length) {
2762 		return;
2763 	}
2764 
2765 	// "Let propagated value be the specified command value of command on the
2766 	// last member of ancestor list."
2767 	var propagatedValue = getSpecifiedCommandValue(ancestorList[ancestorList.length - 1], command);
2768 
2769 	// "If propagated value is null and is not equal to new value, abort this
2770 	// algorithm."
2771 	if (propagatedValue === null && propagatedValue != newValue) {
2772 		return;
2773 	}
2774 
2775 	// "If the effective command value for the parent of the last member of
2776 	// ancestor list is not loosely equivalent to new value, and new value is
2777 	// not null, abort this algorithm."
2778 	if (newValue !== null
2779 	&& !areLooselyEquivalentValues(command, getEffectiveCommandValue(ancestorList[ancestorList.length - 1].parentNode, command), newValue)) {
2780 		return;
2781 	}
2782 
2783 	// "While ancestor list is not empty:"
2784 	while (ancestorList.length) {
2785 		// "Let current ancestor be the last member of ancestor list."
2786 		// "Remove the last member from ancestor list."
2787 		var currentAncestor = ancestorList.pop();
2788 
2789 		// "If the specified command value of current ancestor for command is
2790 		// not null, set propagated value to that value."
2791 		if (getSpecifiedCommandValue(currentAncestor, command) !== null) {
2792 			propagatedValue = getSpecifiedCommandValue(currentAncestor, command);
2793 		}
2794 
2795 		// "Let children be the children of current ancestor."
2796 		var children = Array.prototype.slice.call(toArray(currentAncestor.childNodes));
2797 
2798 		// "If the specified command value of current ancestor for command is
2799 		// not null, clear the value of current ancestor."
2800 		if (getSpecifiedCommandValue(currentAncestor, command) !== null) {
2801 			clearValue(currentAncestor, command, range);
2802 		}
2803 
2804 		// "For every child in children:"
2805 		for (var i = 0; i < children.length; i++) {
2806 			var child = children[i];
2807 
2808 			// "If child is node, continue with the next child."
2809 			if (child == node) {
2810 				continue;
2811 			}
2812 
2813 			// "If child is an Element whose specified command value for
2814 			// command is neither null nor equivalent to propagated value,
2815 			// continue with the next child."
2816 			if (child.nodeType == $_.Node.ELEMENT_NODE
2817 			&& getSpecifiedCommandValue(child, command) !== null
2818 			&& !areEquivalentValues(command, propagatedValue, getSpecifiedCommandValue(child, command))) {
2819 				continue;
2820 			}
2821 
2822 			// "If child is the last member of ancestor list, continue with the
2823 			// next child."
2824 			if (child == ancestorList[ancestorList.length - 1]) {
2825 				continue;
2826 			}
2827 
2828 			// "Force the value of child, with command as in this algorithm
2829 			// and new value equal to propagated value."
2830 			forceValue(child, command, propagatedValue, range);
2831 		}
2832 	}
2833 }
2834 
2835 
2836 //@}
2837 ///// Forcing the value of a node /////
2838 //@{
2839 
2840 function forceValue(node, command, newValue, range) {
2841 	// "If node's parent is null, abort this algorithm."
2842 	if (!node.parentNode) {
2843 		return;
2844 	}
2845 
2846 	// "If new value is null, abort this algorithm."
2847 	if (newValue === null) {
2848 		return;
2849 	}
2850 
2851 	// "If node is an allowed child of "span":"
2852 	if (isAllowedChild(node, "span")) {
2853 		// "Reorder modifiable descendants of node's previousSibling."
2854 		reorderModifiableDescendants(node.previousSibling, command, newValue, range);
2855 
2856 		// "Reorder modifiable descendants of node's nextSibling."
2857 		reorderModifiableDescendants(node.nextSibling, command, newValue, range);
2858 
2859 		// "Wrap the one-node list consisting of node, with sibling criteria
2860 		// returning true for a simple modifiable element whose specified
2861 		// command value is equivalent to new value and whose effective command
2862 		// value is loosely equivalent to new value and false otherwise, and
2863 		// with new parent instructions returning null."
2864 		wrap([node],
2865 			function(node) {
2866 				return isSimpleModifiableElement(node)
2867 					&& areEquivalentValues(command, getSpecifiedCommandValue(node, command), newValue)
2868 					&& areLooselyEquivalentValues(command, getEffectiveCommandValue(node, command), newValue);
2869 			},
2870 			function() { return null },
2871 			range
2872 		);
2873 	}
2874 
2875 	// "If the effective command value of command is loosely equivalent to new
2876 	// value on node, abort this algorithm."
2877 	if (areLooselyEquivalentValues(command, getEffectiveCommandValue(node, command), newValue)) {
2878 		return;
2879 	}
2880 
2881 	// "If node is not an allowed child of "span":"
2882 	if (!isAllowedChild(node, "span")) {
2883 		// "Let children be all children of node, omitting any that are
2884 		// Elements whose specified command value for command is neither null
2885 		// nor equivalent to new value."
2886 		var children = [];
2887 		for (var i = 0; i < node.childNodes.length; i++) {
2888 			if (node.childNodes[i].nodeType == $_.Node.ELEMENT_NODE) {
2889 				var specifiedValue = getSpecifiedCommandValue(node.childNodes[i], command);
2890 
2891 				if (specifiedValue !== null
2892 				&& !areEquivalentValues(command, newValue, specifiedValue)) {
2893 					continue;
2894 				}
2895 			}
2896 			children.push(node.childNodes[i]);
2897 		}
2898 
2899 		// "Force the value of each Node in children, with command and new
2900 		// value as in this invocation of the algorithm."
2901 		for (var i = 0; i < children.length; i++) {
2902 			forceValue(children[i], command, newValue, range);
2903 		}
2904 
2905 		// "Abort this algorithm."
2906 		return;
2907 	}
2908 
2909 	// "If the effective command value of command is loosely equivalent to new
2910 	// value on node, abort this algorithm."
2911 	if (areLooselyEquivalentValues(command, getEffectiveCommandValue(node, command), newValue)) {
2912 		return;
2913 	}
2914 
2915 	// "Let new parent be null."
2916 	var newParent = null;
2917 
2918 	// "If the CSS styling flag is false:"
2919 	if (!cssStylingFlag) {
2920 		// "If command is "bold" and new value is "bold", let new parent be the
2921 		// result of calling createElement("b") on the ownerDocument of node."
2922 		if (command == "bold" && (newValue == "bold" || newValue == "700")) {
2923 			newParent = node.ownerDocument.createElement("b");
2924 		}
2925 
2926 		// "If command is "italic" and new value is "italic", let new parent be
2927 		// the result of calling createElement("i") on the ownerDocument of
2928 		// node."
2929 		if (command == "italic" && newValue == "italic") {
2930 			newParent = node.ownerDocument.createElement("i");
2931 		}
2932 
2933 		// "If command is "strikethrough" and new value is "line-through", let
2934 		// new parent be the result of calling createElement("s") on the
2935 		// ownerDocument of node."
2936 		if (command == "strikethrough" && newValue == "line-through") {
2937 			newParent = node.ownerDocument.createElement("s");
2938 		}
2939 
2940 		// "If command is "underline" and new value is "underline", let new
2941 		// parent be the result of calling createElement("u") on the
2942 		// ownerDocument of node."
2943 		if (command == "underline" && newValue == "underline") {
2944 			newParent = node.ownerDocument.createElement("u");
2945 		}
2946 
2947 		// "If command is "foreColor", and new value is fully opaque with red,
2948 		// green, and blue components in the range 0 to 255:"
2949 		if (command == "forecolor" && parseSimpleColor(newValue)) {
2950 			// "Let new parent be the result of calling createElement("span")
2951 			// on the ownerDocument of node."
2952 			// NOTE: modified this process to create span elements with style attributes
2953 			// instead of oldschool font tags with color attributes
2954 			newParent = node.ownerDocument.createElement("span");
2955 
2956 			// "If new value is an extended color keyword, set the color
2957 			// attribute of new parent to new value."
2958 			//
2959 			// "Otherwise, set the color attribute of new parent to the result
2960 			// of applying the rules for serializing simple color values to new
2961 			// value (interpreted as a simple color)."
2962 			jQuery(newParent).css('color', parseSimpleColor(newValue));
2963 		}
2964 
2965 		// "If command is "fontName", let new parent be the result of calling
2966 		// createElement("font") on the ownerDocument of node, then set the
2967 		// face attribute of new parent to new value."
2968 		if (command == "fontname") {
2969 			newParent = node.ownerDocument.createElement("font");
2970 			newParent.face = newValue;
2971 		}
2972 	}
2973 
2974 	// "If command is "createLink" or "unlink":"
2975 	if (command == "createlink" || command == "unlink") {
2976 		// "Let new parent be the result of calling createElement("a") on the
2977 		// ownerDocument of node."
2978 		newParent = node.ownerDocument.createElement("a");
2979 
2980 		// "Set the href attribute of new parent to new value."
2981 		newParent.setAttribute("href", newValue);
2982 
2983 		// "Let ancestor be node's parent."
2984 		var ancestor = node.parentNode;
2985 
2986 		// "While ancestor is not null:"
2987 		while (ancestor) {
2988 			// "If ancestor is an a, set the tag name of ancestor to "span",
2989 			// and let ancestor be the result."
2990 			if (isHtmlElement(ancestor, "A")) {
2991 				ancestor = setTagName(ancestor, "span", range);
2992 			}
2993 
2994 			// "Set ancestor to its parent."
2995 			ancestor = ancestor.parentNode;
2996 		}
2997 	}
2998 
2999 	// "If command is "fontSize"; and new value is one of "xx-small", "small",
3000 	// "medium", "large", "x-large", "xx-large", or "xxx-large"; and either the
3001 	// CSS styling flag is false, or new value is "xxx-large": let new parent
3002 	// be the result of calling createElement("font") on the ownerDocument of
3003 	// node, then set the size attribute of new parent to the number from the
3004 	// following table based on new value: [table omitted]"
3005 	if (command == "fontsize"
3006 	&& $_( ["xx-small", "small", "medium", "large", "x-large", "xx-large", "xxx-large"] ).indexOf(newValue) != -1
3007 	&& (!cssStylingFlag || newValue == "xxx-large")) {
3008 		newParent = node.ownerDocument.createElement("font");
3009 		newParent.size = cssSizeToLegacy(newValue);
3010 	}
3011 
3012 	// "If command is "subscript" or "superscript" and new value is
3013 	// "subscript", let new parent be the result of calling
3014 	// createElement("sub") on the ownerDocument of node."
3015 	if ((command == "subscript" || command == "superscript")
3016 	&& newValue == "subscript") {
3017 		newParent = node.ownerDocument.createElement("sub");
3018 	}
3019 
3020 	// "If command is "subscript" or "superscript" and new value is
3021 	// "superscript", let new parent be the result of calling
3022 	// createElement("sup") on the ownerDocument of node."
3023 	if ((command == "subscript" || command == "superscript")
3024 	&& newValue == "superscript") {
3025 		newParent = node.ownerDocument.createElement("sup");
3026 	}
3027 
3028 	// "If new parent is null, let new parent be the result of calling
3029 	// createElement("span") on the ownerDocument of node."
3030 	if (!newParent) {
3031 		newParent = node.ownerDocument.createElement("span");
3032 	}
3033 
3034 	// "Insert new parent in node's parent before node."
3035 	node.parentNode.insertBefore(newParent, node);
3036 
3037 	// "If the effective command value of command for new parent is not loosely
3038 	// equivalent to new value, and the relevant CSS property for command is
3039 	// not null, set that CSS property of new parent to new value (if the new
3040 	// value would be valid)."
3041 	var property = commands[command].relevantCssProperty;
3042 	if (property !== null
3043 	&& !areLooselyEquivalentValues(command, getEffectiveCommandValue(newParent, command), newValue)) {
3044 		newParent.style[property] = newValue;
3045 	}
3046 
3047 	// "If command is "strikethrough", and new value is "line-through", and the
3048 	// effective command value of "strikethrough" for new parent is not
3049 	// "line-through", set the "text-decoration" property of new parent to
3050 	// "line-through"."
3051 	if (command == "strikethrough"
3052 	&& newValue == "line-through"
3053 	&& getEffectiveCommandValue(newParent, "strikethrough") != "line-through") {
3054 		newParent.style.textDecoration = "line-through";
3055 	}
3056 
3057 	// "If command is "underline", and new value is "underline", and the
3058 	// effective command value of "underline" for new parent is not
3059 	// "underline", set the "text-decoration" property of new parent to
3060 	// "underline"."
3061 	if (command == "underline"
3062 	&& newValue == "underline"
3063 	&& getEffectiveCommandValue(newParent, "underline") != "underline") {
3064 		newParent.style.textDecoration = "underline";
3065 	}
3066 
3067 	// "Append node to new parent as its last child, preserving ranges."
3068 	movePreservingRanges(node, newParent, newParent.childNodes.length, range);
3069 
3070 	// "If node is an Element and the effective command value of command for
3071 	// node is not loosely equivalent to new value:"
3072 	if (node.nodeType == $_.Node.ELEMENT_NODE
3073 	&& !areEquivalentValues(command, getEffectiveCommandValue(node, command), newValue)) {
3074 		// "Insert node into the parent of new parent before new parent,
3075 		// preserving ranges."
3076 		movePreservingRanges(node, newParent.parentNode, getNodeIndex(newParent), range);
3077 
3078 		// "Remove new parent from its parent."
3079 		newParent.parentNode.removeChild(newParent);
3080 
3081 		// "Let children be all children of node, omitting any that are
3082 		// Elements whose specified command value for command is neither null
3083 		// nor equivalent to new value."
3084 		var children = [];
3085 		for (var i = 0; i < node.childNodes.length; i++) {
3086 			if (node.childNodes[i].nodeType == $_.Node.ELEMENT_NODE) {
3087 				var specifiedValue = getSpecifiedCommandValue(node.childNodes[i], command);
3088 
3089 				if (specifiedValue !== null
3090 				&& !areEquivalentValues(command, newValue, specifiedValue)) {
3091 					continue;
3092 				}
3093 			}
3094 			children.push(node.childNodes[i]);
3095 		}
3096 
3097 		// "Force the value of each Node in children, with command and new
3098 		// value as in this invocation of the algorithm."
3099 		for (var i = 0; i < children.length; i++) {
3100 			forceValue(children[i], command, newValue, range);
3101 		}
3102 	}
3103 }
3104 
3105 
3106 //@}
3107 ///// Setting the selection's value /////
3108 //@{
3109 
3110 function setSelectionValue(command, newValue, range) {
3111 	
3112 	// Use current selected range if no range passed
3113 	range = range || getActiveRange();
3114 	
3115 	// "If there is no editable text node effectively contained in the active
3116 	// range:"
3117 	if (!$_( getAllEffectivelyContainedNodes(range) )
3118 	.filter(function(node) { return node.nodeType == $_.Node.TEXT_NODE}, true)
3119 	.some(isEditable)) {
3120 		// "If command has inline command activated values, set the state
3121 		// override to true if new value is among them and false if it's not."
3122 		if ("inlineCommandActivatedValues" in commands[command]) {
3123 			setStateOverride(command, 
3124       $_(commands[command].inlineCommandActivatedValues).indexOf(newValue) != -1,
3125       range);
3126 		}
3127 
3128 		// "If command is "subscript", unset the state override for
3129 		// "superscript"."
3130 		if (command == "subscript") {
3131 			unsetStateOverride("superscript", range);
3132 		}
3133 
3134 		// "If command is "superscript", unset the state override for
3135 		// "subscript"."
3136 3137 		if (command == "superscript") {
3138 			unsetStateOverride("subscript", range);
3139 		}
3140 
3141 		// "If new value is null, unset the value override (if any)."
3142 		if (newValue === null) {
3143 			unsetValueOverride(command, range);
3144 
3145 		// "Otherwise, if command has a value specified, set the value override
3146 		// to new value."
3147 		} else if ("value" in commands[command]) {
3148 			setValueOverride(command, newValue, range);
3149 		}
3150 
3151 		// "Abort these steps."
3152 		return;
3153 	}
3154 
3155 	// "If the active range's start node is an editable Text node, and its
3156 	// start offset is neither zero nor its start node's length, call
3157 	// splitText() on the active range's start node, with argument equal to the
3158 	// active range's start offset. Then set the active range's start node to
3159 	// the result, and its start offset to zero."
3160 	if (isEditable(range.startContainer)
3161 	&& range.startContainer.nodeType == $_.Node.TEXT_NODE
3162 	&& range.startOffset != 0
3163 	&& range.startOffset != getNodeLength(range.startContainer)) {
3164 		// Account for browsers not following range mutation rules
3165 		var newNode = range.startContainer.splitText(range.startOffset);
3166 		var newActiveRange = Aloha.createRange();
3167 		if (range.startContainer == range.endContainer) {
3168 			var newEndOffset = range.endOffset - range.startOffset;
3169 			newActiveRange.setEnd(newNode, newEndOffset);
3170 			range.setEnd(newNode, newEndOffset);
3171 		}
3172 		newActiveRange.setStart(newNode, 0);
3173 		Aloha.getSelection().removeAllRanges();
3174 		Aloha.getSelection().addRange(newActiveRange);
3175 
3176 		range.setStart(newNode, 0);
3177 	}
3178 
3179 	// "If the active range's end node is an editable Text node, and its end
3180 	// offset is neither zero nor its end node's length, call splitText() on
3181 	// the active range's end node, with argument equal to the active range's
3182 	// end offset."
3183 	if (isEditable(range.endContainer)
3184 	&& range.endContainer.nodeType == $_.Node.TEXT_NODE
3185 	&& range.endOffset != 0
3186 	&& range.endOffset != getNodeLength(range.endContainer)) {
3187 		// IE seems to mutate the range incorrectly here, so we need correction
3188 		// here as well.  The active range will be temporarily in orphaned
3189 		// nodes, so calling getActiveRange() after splitText() but before
3190 		// fixing the range will throw an exception.
3191 		// TODO: check if this is still neccessary 
3192 		var activeRange = range;
3193 		var newStart = [activeRange.startContainer, activeRange.startOffset];
3194 		var newEnd = [activeRange.endContainer, activeRange.endOffset];
3195 		activeRange.endContainer.splitText(activeRange.endOffset);
3196 		activeRange.setStart(newStart[0], newStart[1]);
3197 		activeRange.setEnd(newEnd[0], newEnd[1]);
3198 
3199 		Aloha.getSelection().removeAllRanges();
3200 		Aloha.getSelection().addRange(activeRange);
3201 	}
3202 
3203 	// "Let element list be all editable Elements effectively contained in the
3204 	// active range.
3205 	//
3206 	// "For each element in element list, clear the value of element."
3207 	$_( getAllEffectivelyContainedNodes(getActiveRange(), function(node) {
3208 		return isEditable(node) && node.nodeType == $_.Node.ELEMENT_NODE;
3209 	}) ).forEach(function(element) {
3210 		clearValue(element, command, range);
3211 	});
3212 
3213 	// "Let node list be all editable nodes effectively contained in the active
3214 	// range.
3215 	//
3216 	// "For each node in node list:"
3217 	$_( getAllEffectivelyContainedNodes(range, isEditable) ).forEach(function(node) {
3218 		// "Push down values on node."
3219 		pushDownValues(node, command, newValue, range);
3220 
3221 		// "Force the value of node."
3222 		forceValue(node, command, newValue, range);
3223 	});
3224 }
3225 
3226 
3227 //@}
3228 ///// The backColor command /////
3229 //@{
3230 commands.backcolor = {
3231 	// Copy-pasted, same as hiliteColor
3232 	action: function(value) {
3233 		// Action is further copy-pasted, same as foreColor
3234 
3235 		// "If value is not a valid CSS color, prepend "#" to it."
3236 		//
3237 		// "If value is still not a valid CSS color, or if it is currentColor,
3238 		// abort these steps and do nothing."
3239 		//
3240 		// Cheap hack for testing, no attempt to be comprehensive.
3241 		if (/^([0-9a-fA-F]{3}){1,2}$/.test(value)) {
3242 			value = "#" + value;
3243 		}
3244 		if (!/^(rgba?|hsla?)\(.*\)$/.test(value)
3245 		&& !parseSimpleColor(value)
3246 		&& value.toLowerCase() != "transparent") {
3247 			return;
3248 		}
3249 
3250 		// "Set the selection's value to value."
3251 		setSelectionValue("backcolor", value);
3252 	}, standardInlineValueCommand: true, relevantCssProperty: "backgroundColor",
3253 	equivalentValues: function(val1, val2) {
3254 		// "Either both strings are valid CSS colors and have the same red,
3255 		// green, blue, and alpha components, or neither string is a valid CSS
3256 		// color."
3257 		return normalizeColor(val1) === normalizeColor(val2);
3258 	}
3259 };
3260 
3261 //@}
3262 ///// The bold command /////
3263 //@{
3264 commands.bold = {
3265 	action: function(value, range) {
3266 		// "If queryCommandState("bold") returns true, set the selection's
3267 		// value to "normal". Otherwise set the selection's value to "bold"."
3268 		if (myQueryCommandState("bold", range)) {
3269 			setSelectionValue("bold", "normal", range);
3270 		} else {
3271 			setSelectionValue("bold", "bold", range);
3272 		}
3273 	}, 
3274 	inlineCommandActivatedValues: ["bold", "600", "700", "800", "900"],
3275 	relevantCssProperty: "fontWeight",
3276 	equivalentValues: function(val1, val2) {
3277 		// "Either the two strings are equal, or one is "bold" and the other is
3278 		// "700", or one is "normal" and the other is "400"."
3279 		return val1 == val2
3280 			|| (val1 == "bold" && val2 == "700")
3281 			|| (val1 == "700" && val2 == "bold")
3282 			|| (val1 == "normal" && val2 == "400")
3283 			|| (val1 == "400" && val2 == "normal");
3284 	}
3285 };
3286 
3287 //@}
3288 ///// The createLink command /////
3289 //@{
3290 commands.createlink = {
3291 	action: function(value) {
3292 		// "If value is the empty string, abort these steps and do nothing."
3293 		if (value === "") {
3294 			return;
3295 		}
3296 
3297 		// "For each editable a element that has an href attribute and is an
3298 		// ancestor of some node effectively contained in the active range, set
3299 		// that a element's href attribute to value."
3300 		//
3301 		// TODO: We don't actually do this in tree order, not that it matters
3302 		// unless you're spying with mutation events.
3303 		$_( getAllEffectivelyContainedNodes(getActiveRange()) ).forEach(function(node) {
3304 			$_( getAncestors(node) ).forEach(function(ancestor) {
3305 				if (isEditable(ancestor)
3306 				&& isHtmlElement(ancestor, "a")
3307 				&& $_( ancestor ).hasAttribute("href")) {
3308 					ancestor.setAttribute("href", value);
3309 				}
3310 			});
3311 		});
3312 
3313 		// "Set the selection's value to value."
3314 		setSelectionValue("createlink", value);
3315 	}, standardInlineValueCommand: true
3316 };
3317 
3318 //@}
3319 ///// The fontName command /////
3320 //@{
3321 commands.fontname = {
3322 	action: function(value) {
3323 		// "Set the selection's value to value."
3324 		setSelectionValue("fontname", value);
3325 	}, standardInlineValueCommand: true, relevantCssProperty: "fontFamily"
3326 };
3327 
3328 //@}
3329 ///// The fontSize command /////
3330 3331 //@{
3332 
3333 // Helper function for fontSize's action plus queryOutputHelper.  It's just the
3334 // middle of fontSize's action, ripped out into its own function.
3335 function normalizeFontSize(value) {
3336 	// "Strip leading and trailing whitespace from value."
3337 	//
3338 	// Cheap hack, not following the actual algorithm.
3339 	value = $_(value).trim();
3340 
3341 	// "If value is a valid floating point number, or would be a valid
3342 	// floating point number if a single leading "+" character were
3343 	// stripped:"
3344 	if (/^[-+]?[0-9]+(\.[0-9]+)?([eE][-+]?[0-9]+)?$/.test(value)) {
3345 		var mode;
3346 
3347 		// "If the first character of value is "+", delete the character
3348 		// and let mode be "relative-plus"."
3349 		if (value[0] == "+") {
3350 			value = value.slice(1);
3351 			mode = "relative-plus";
3352 		// "Otherwise, if the first character of value is "-", delete the
3353 		// character and let mode be "relative-minus"."
3354 		} else if (value[0] == "-") {
3355 			value = value.slice(1);
3356 			mode = "relative-minus";
3357 		// "Otherwise, let mode be "absolute"."
3358 		} else {
3359 			mode = "absolute";
3360 		}
3361 
3362 		// "Apply the rules for parsing non-negative integers to value, and
3363 		// let number be the result."
3364 		//
3365 		// Another cheap hack.
3366 		var num = parseInt(value);
3367 
3368 		// "If mode is "relative-plus", add three to number."
3369 		if (mode == "relative-plus") {
3370 			num += 3;
3371 		}
3372 
3373 		// "If mode is "relative-minus", negate number, then add three to
3374 		// it."
3375 		if (mode == "relative-minus") {
3376 			num = 3 - num;
3377 		}
3378 
3379 		// "If number is less than one, let number equal 1."
3380 		if (num < 1) {
3381 			num = 1;
3382 		}
3383 
3384 		// "If number is greater than seven, let number equal 7."
3385 		if (num > 7) {
3386 			num = 7;
3387 		}
3388 
3389 		// "Set value to the string here corresponding to number:" [table
3390 		// omitted]
3391 		value = {
3392 			1: "xx-small",
3393 			2: "small",
3394 			3: "medium",
3395 			4: "large",
3396 			5: "x-large",
3397 			6: "xx-large",
3398 			7: "xxx-large"
3399 		}[num];
3400 	}
3401 
3402 	return value;
3403 }
3404 
3405 commands.fontsize = {
3406 	action: function(value) {
3407 		// "If value is the empty string, abort these steps and do nothing."
3408 		if (value === "") {
3409 			return;
3410 		}
3411 
3412 		value = normalizeFontSize(value);
3413 
3414 		// "If value is not one of the strings "xx-small", "x-small", "small",
3415 		// "medium", "large", "x-large", "xx-large", "xxx-large", and is not a
3416 		// valid CSS absolute length, then abort these steps and do nothing."
3417 		//
3418 		// More cheap hacks to skip valid CSS absolute length checks.
3419 		if ($_(["xx-small", "x-small", "small", "medium", "large", "x-large", "xx-large", "xxx-large"]).indexOf(value) == -1
3420 		&& !/^[0-9]+(\.[0-9]+)?(cm|mm|in|pt|pc)$/.test(value)) {
3421 			return;
3422 		}
3423 
3424 		// "Set the selection's value to value."
3425 		setSelectionValue("fontsize", value);
3426 	}, 
3427 	indeterm: function() {
3428 		// "True if among editable Text nodes that are effectively contained in
3429 		// the active range, there are two that have distinct effective command
3430 		// values.  Otherwise false."
3431 		return $_( getAllEffectivelyContainedNodes(getActiveRange(), function(node) {
3432 			return isEditable(node) && node.nodeType == $_.Node.TEXT_NODE;
3433 		}) ).map(function(node) {
3434 			return getEffectiveCommandValue(node, "fontsize");
3435 		}, true).filter(function(value, i, arr) {
3436 			return $_(arr.slice(0, i)).indexOf(value) == -1;
3437 		}).length >= 2;
3438 	}, 
3439 	value: function(range) {
3440 		// "Let pixel size be the effective command value of the first editable
3441 		// Text node that is effectively contained in the active range, or if
3442 		// there is no such node, the effective command value of the active
3443 		// range's start node, in either case interpreted as a number of
3444 		// pixels."
3445 		var node = getAllEffectivelyContainedNodes(range, function(node) {
3446 			return isEditable(node) && node.nodeType == $_.Node.TEXT_NODE;
3447 		})[0];
3448 		if (node === undefined) {
3449 			node = range.startContainer;
3450 		}
3451 		var pixelSize = getEffectiveCommandValue(node, "fontsize");
3452 
3453 		// "Return the legacy font size for pixel size."
3454 		return getLegacyFontSize(pixelSize);
3455 	}, relevantCssProperty: "fontSize"
3456 };
3457 
3458 function getLegacyFontSize(size) {
3459 	// For convenience in other places in my code, I handle all sizes, not just
3460 	// pixel sizes as the spec says.  This means pixel sizes have to be passed
3461 	// in suffixed with "px", not as plain numbers.
3462 	size = normalizeFontSize(size);
3463 
3464 	if ($_(["xx-small", "x-small", "small", "medium", "large", "x-large", "xx-large", "xxx-large"]).indexOf(size) == -1
3465 	&& !/^[0-9]+(\.[0-9]+)?(cm|mm|in|pt|pc|px)$/.test(size)) {
3466 		// There is no sensible legacy size for things like "2em".
3467 		return null;
3468 	}
3469 
3470 	var font = document.createElement("font");
3471 	document.body.appendChild(font);
3472 	if (size == "xxx-large") {
3473 		font.size = 7;
3474 	} else {
3475 		font.style.fontSize = size;
3476 	}
3477 	var pixelSize = parseInt($_.getComputedStyle(font).fontSize);
3478 	document.body.removeChild(font);
3479 
3480 	// "Let returned size be 1."
3481 	var returnedSize = 1;
3482 
3483 	// "While returned size is less than 7:"
3484 	while (returnedSize < 7) {
3485 		// "Let lower bound be the resolved value of "font-size" in pixels
3486 		// of a font element whose size attribute is set to returned size."
3487 		var font = document.createElement("font");
3488 		font.size = returnedSize;
3489 		document.body.appendChild(font);
3490 		var lowerBound = parseInt($_.getComputedStyle(font).fontSize);
3491 
3492 		// "Let upper bound be the resolved value of "font-size" in pixels
3493 		// of a font element whose size attribute is set to one plus
3494 		// returned size."
3495 		font.size = 1 + returnedSize;
3496 		var upperBound = parseInt($_.getComputedStyle(font).fontSize);
3497 		document.body.removeChild(font);
3498 
3499 		// "Let average be the average of upper bound and lower bound."
3500 		var average = (upperBound + lowerBound)/2;
3501 
3502 		// "If pixel size is less than average, return the one-element
3503 		// string consisting of the digit returned size."
3504 		if (pixelSize < average) {
3505 			return String(returnedSize);
3506 		}
3507 
3508 		// "Add one to returned size."
3509 		returnedSize++;
3510 	}
3511 
3512 	// "Return "7"."
3513 	return "7";
3514 }
3515 
3516 //@}
3517 ///// The foreColor command /////
3518 //@{
3519 commands.forecolor = {
3520 	action: function(value) {
3521 		// Copy-pasted, same as backColor and hiliteColor
3522 
3523 		// "If value is not a valid CSS color, prepend "#" to it."
3524 		//
3525 		// "If value is still not a valid CSS color, or if it is currentColor,
3526 		// abort these steps and do nothing."
3527 		//
3528 		// Cheap hack for testing, no attempt to be comprehensive.
3529 		if (/^([0-9a-fA-F]{3}){1,2}$/.test(value)) {
3530 			value = "#" + value;
3531 		}
3532 		if (!/^(rgba?|hsla?)\(.*\)$/.test(value)
3533 		&& !parseSimpleColor(value)
3534 		&& value.toLowerCase() != "transparent") {
3535 			return;
3536 		}
3537 
3538 		// "Set the selection's value to value."
3539 		setSelectionValue("forecolor", value);
3540 	}, standardInlineValueCommand: true, relevantCssProperty: "color",
3541 	equivalentValues: function(val1, val2) {
3542 		// "Either both strings are valid CSS colors and have the same red,
3543 		// green, blue, and alpha components, or neither string is a valid CSS
3544 		// color."
3545 		return normalizeColor(val1) === normalizeColor(val2);
3546 	}
3547 };
3548 
3549 //@}
3550 ///// The hiliteColor command /////
3551 //@{
3552 commands.hilitecolor = {
3553 	// Copy-pasted, same as backColor
3554 	action: function(value) {
3555 		// Action is further copy-pasted, same as foreColor
3556 
3557 		// "If value is not a valid CSS color, prepend "#" to it."
3558 		//
3559 		// "If value is still not a valid CSS color, or if it is currentColor,
3560 		// abort these steps and do nothing."
3561 		//
3562 		// Cheap hack for testing, no attempt to be comprehensive.
3563 		if (/^([0-9a-fA-F]{3}){1,2}$/.test(value)) {
3564 			value = "#" + value;
3565 		}
3566 		if (!/^(rgba?|hsla?)\(.*\)$/.test(value)
3567 		&& !parseSimpleColor(value)
3568 		&& value.toLowerCase() != "transparent") {
3569 			return;
3570 		}
3571 
3572 		// "Set the selection's value to value."
3573 		setSelectionValue("hilitecolor", value);
3574 	}, indeterm: function() {
3575 		// "True if among editable Text nodes that are effectively contained in
3576 		// the active range, there are two that have distinct effective command
3577 		// values.  Otherwise false."
3578 		return $_( getAllEffectivelyContainedNodes(getActiveRange(), function(node) {
3579 			return isEditable(node) && node.nodeType == $_.Node.TEXT_NODE;
3580 		}) ).map(function(node) {
3581 			return getEffectiveCommandValue(node, "hilitecolor");
3582 		}, true).filter(function(value, i, arr) {
3583 			return $_(arr.slice(0, i)).indexOf(value) == -1;
3584 		}).length >= 2;
3585 	}, standardInlineValueCommand: true, relevantCssProperty: "backgroundColor",
3586 	equivalentValues: function(val1, val2) {
3587 		// "Either both strings are valid CSS colors and have the same red,
3588 		// green, blue, and alpha components, or neither string is a valid CSS
3589 		// color."
3590 		return normalizeColor(val1) === normalizeColor(val2);
3591 	}
3592 };
3593 
3594 //@}
3595 ///// The italic command /////
3596 //@{
3597 commands.italic = {
3598 	action: function( value, range ) {
3599 		// "If queryCommandState("italic") returns true, set the selection's
3600 		// value to "normal". Otherwise set the selection's value to "italic"."
3601 		if (myQueryCommandState("italic", range)) {
3602 			setSelectionValue("italic", "normal", range);
3603 		} else {
3604 			setSelectionValue("italic", "italic", range);
3605 		}
3606 	}, inlineCommandActivatedValues: ["italic", "oblique"],
3607 	relevantCssProperty: "fontStyle"
3608 };
3609 
3610 //@}
3611 ///// The removeFormat command /////
3612 //@{
3613 commands.removeformat = {
3614 	action: function() {
3615 		// "A removeFormat candidate is an editable HTML element with local
3616 		// name "abbr", "acronym", "b", "bdi", "bdo", "big", "blink", "cite",
3617 		// "code", "dfn", "em", "font", "i", "ins", "kbd", "mark", "nobr", "q",
3618 		// "s", "samp", "small", "span", "strike", "strong", "sub", "sup",
3619 		// "tt", "u", or "var"."
3620 		function isRemoveFormatCandidate(node) {
3621 			return isEditable(node)
3622 				&& isHtmlElement(node, ["abbr", "acronym", "b", "bdi", "bdo",
3623 				"big", "blink", "cite", "code", "dfn", "em", "font", "i",
3624 				"ins", "kbd", "mark", "nobr", "q", "s", "samp", "small",
3625 				"span", "strike", "strong", "sub", "sup", "tt", "u", "var"]);
3626 		}
3627 
3628 		// "Let elements to remove be a list of every removeFormat candidate
3629 		// effectively contained in the active range."
3630 		var elementsToRemove = getAllEffectivelyContainedNodes(getActiveRange(), isRemoveFormatCandidate);
3631 
3632 		// "For each element in elements to remove:"
3633 		$_( elementsToRemove ).forEach(function(element) {
3634 			// "While element has children, insert the first child of element
3635 			// into the parent of element immediately before element,
3636 			// preserving ranges."
3637 			while (element.hasChildNodes()) {
3638 				movePreservingRanges(element.firstChild, element.parentNode, getNodeIndex(element), range);
3639 			}
3640 
3641 			// "Remove element from its parent."
3642 			element.parentNode.removeChild(element);
3643 		});
3644 
3645 		// "If the active range's start node is an editable Text node, and its
3646 		// start offset is neither zero nor its start node's length, call
3647 		// splitText() on the active range's start node, with argument equal to
3648 		// the active range's start offset. Then set the active range's start
3649 		// node to the result, and its start offset to zero."
3650 		if (isEditable(getActiveRange().startContainer)
3651 		&& getActiveRange().startContainer.nodeType == $_.Node.TEXT_NODE
3652 		&& getActiveRange().startOffset != 0
3653 		&& getActiveRange().startOffset != getNodeLength(getActiveRange().startContainer)) {
3654 			// Account for browsers not following range mutation rules
3655 			if (getActiveRange().startContainer == getActiveRange().endContainer) {
3656 				var newEnd = getActiveRange().endOffset - getActiveRange().startOffset;
3657 				var newNode = getActiveRange().startContainer.splitText(getActiveRange().startOffset);
3658 				getActiveRange().setStart(newNode, 0);
3659 				getActiveRange().setEnd(newNode, newEnd);
3660 			} else {
3661 				getActiveRange().setStart(getActiveRange().startContainer.splitText(getActiveRange().startOffset), 0);
3662 			}
3663 		}
3664 
3665 		// "If the active range's end node is an editable Text node, and its
3666 		// end offset is neither zero nor its end node's length, call
3667 		// splitText() on the active range's end node, with argument equal to
3668 		// the active range's end offset."
3669 		if (isEditable(getActiveRange().endContainer)
3670 		&& getActiveRange().endContainer.nodeType == $_.Node.TEXT_NODE
3671 		&& getActiveRange().endOffset != 0
3672 		&& getActiveRange().endOffset != getNodeLength(getActiveRange().endContainer)) {
3673 			// IE seems to mutate the range incorrectly here, so we need
3674 			// correction here as well.  Have to be careful to set the range to
3675 			// something not including the text node so that getActiveRange()
3676 			// doesn't throw an exception due to a temporarily detached
3677 			// endpoint.
3678 			var newStart = [getActiveRange().startContainer, getActiveRange().startOffset];
3679 			var newEnd = [getActiveRange().endContainer, getActiveRange().endOffset];
3680 			getActiveRange().setEnd(document.documentElement, 0);
3681 			newEnd[0].splitText(newEnd[1]);
3682 			getActiveRange().setStart(newStart[0], newStart[1]);
3683 			getActiveRange().setEnd(newEnd[0], newEnd[1]);
3684 		}
3685 
3686 		// "Let node list consist of all editable nodes effectively contained
3687 		// in the active range."
3688 		//
3689 		// "For each node in node list, while node's parent is a removeFormat
3690 		// candidate in the same editing host as node, split the parent of the
3691 		// one-node list consisting of node."
3692 		$_( getAllEffectivelyContainedNodes(getActiveRange(), isEditable) ).forEach(function(node) {
3693 			while (isRemoveFormatCandidate(node.parentNode)
3694 			&& inSameEditingHost(node.parentNode, node)) {
3695 				splitParent([node], range);
3696 			}
3697 		});
3698 
3699 		// "For each of the entries in the following list, in the given order,
3700 		// set the selection's value to null, with command as given."
3701 		$_( [
3702 			"subscript",
3703 			"bold",
3704 			"fontname",
3705 			"fontsize",
3706 			"forecolor",
3707 			"hilitecolor",
3708 			"italic",
3709 			"strikethrough",
3710 			"underline",
3711 		] ).forEach(function(command) {
3712 			setSelectionValue(command, null);
3713 		});
3714 	}
3715 };
3716 
3717 //@}
3718 ///// The strikethrough command /////
3719 //@{
3720 commands.strikethrough = {
3721 	action: function() {
3722 		// "If queryCommandState("strikethrough") returns true, set the
3723 		// selection's value to null. Otherwise set the selection's value to
3724 		// "line-through"."
3725 		if (myQueryCommandState("strikethrough")) {
3726 			setSelectionValue("strikethrough", null);
3727 		} else {
3728 			setSelectionValue("strikethrough", "line-through");
3729 		}
3730 	}, inlineCommandActivatedValues: ["line-through"]
3731 };
3732 
3733 //@}
3734 ///// The subscript command /////
3735 //@{
3736 commands.subscript = {
3737 	action: function() {
3738 		// "Call queryCommandState("subscript"), and let state be the result."
3739 		var state = myQueryCommandState("subscript");
3740 
3741 		// "Set the selection's value to null."
3742 		setSelectionValue("subscript", null);
3743 
3744 		// "If state is false, set the selection's value to "subscript"."
3745 		if (!state) {
3746 			setSelectionValue("subscript", "subscript");
3747 		}
3748 	}, indeterm: function() {
3749 		// "True if either among editable Text nodes that are effectively
3750 		// contained in the active range, there is at least one with effective
3751 		// command value "subscript" and at least one with some other effective
3752 		// command value; or if there is some editable Text node effectively
3753 		// contained in the active range with effective command value "mixed".
3754 		// Otherwise false."
3755 		var nodes = getAllEffectivelyContainedNodes(getActiveRange(), function(node) {
3756 			return isEditable(node) && node.nodeType == $_.Node.TEXT_NODE;
3757 		});
3758 		return ($_( nodes ).some(function(node) { return getEffectiveCommandValue(node, "subscript") == "subscript" })
3759 			&& $_( nodes ).some(function(node) { return getEffectiveCommandValue(node, "subscript") != "subscript" }))
3760 			|| $_( nodes ).some(function(node) { return getEffectiveCommandValue(node, "subscript") == "mixed" });
3761 	}, inlineCommandActivatedValues: ["subscript"]
3762 };
3763 
3764 //@}
3765 ///// The superscript command /////
3766 //@{
3767 commands.superscript = {
3768 	action: function() {
3769 		// "Call queryCommandState("superscript"), and let state be the
3770 		// result."
3771 		var state = myQueryCommandState("superscript");
3772 
3773 		// "Set the selection's value to null."
3774 		setSelectionValue("superscript", null);
3775 
3776 		// "If state is false, set the selection's value to "superscript"."
3777 		if (!state) {
3778 			setSelectionValue("superscript", "superscript");
3779 		}
3780 	}, indeterm: function() {
3781 		// "True if either among editable Text nodes that are effectively
3782 		// contained in the active range, there is at least one with effective
3783 		// command value "superscript" and at least one with some other
3784 		// effective command value; or if there is some editable Text node
3785 		// effectively contained in the active range with effective command
3786 		// value "mixed".  Otherwise false."
3787 		var nodes = getAllEffectivelyContainedNodes(getActiveRange(),
3788 				function(node) {
3789 			return isEditable(node) && node.nodeType == $_.Node.TEXT_NODE;
3790 		});
3791 		return ($_( nodes ).some(function(node) { return getEffectiveCommandValue(node, "superscript") == "superscript" })
3792 			&& $_( nodes ).some(function(node) { return getEffectiveCommandValue(node, "superscript") != "superscript" }))
3793 			|| $_( nodes ).some(function(node) { return getEffectiveCommandValue(node, "superscript") == "mixed" });
3794 	}, inlineCommandActivatedValues: ["superscript"]
3795 };
3796 
3797 //@}
3798 ///// The underline command /////
3799 //@{
3800 commands.underline = {
3801 	action: function() {
3802 		// "If queryCommandState("underline") returns true, set the selection's
3803 		// value to null. Otherwise set the selection's value to "underline"."
3804 		if (myQueryCommandState("underline")) {
3805 			setSelectionValue("underline", null);
3806 		} else {
3807 			setSelectionValue("underline", "underline");
3808 		}
3809 	}, inlineCommandActivatedValues: ["underline"]
3810 };
3811 
3812 //@}
3813 ///// The unlink command /////
3814 //@{
3815 commands.unlink = {
3816 	action: function() {
3817 		// "Let hyperlinks be a list of every a element that has an href
3818 		// attribute and is contained in the active range or is an ancestor of
3819 		// one of its boundary points."
3820 		//
3821 		// As usual, take care to ensure it's tree order.  The correctness of
3822 		// the following is left as an exercise for the reader.
3823 		var range = getActiveRange();
3824 		var hyperlinks = [];
3825 		for (
3826 			var node = range.startContainer;
3827 			node;
3828 			node = node.parentNode
3829 		) {
3830 			if (isHtmlElement(node, "A")
3831 			&& $_( node ).hasAttribute("href")) {
3832 				hyperlinks.unshift(node);
3833 			}
3834 		}
3835 		for (
3836 			var node = range.startContainer;
3837 			node != nextNodeDescendants(range.endContainer);
3838 			node = nextNode(node)
3839 		) {
3840 			if (isHtmlElement(node, "A")
3841 			&& $_( node ).hasAttribute("href")
3842 			&& (isContained(node, range)
3843 			|| isAncestor(node, range.endContainer)
3844 			|| node == range.endContainer)) {
3845 				hyperlinks.push(node);
3846 			}
3847 		}
3848 
3849 		// "Clear the value of each member of hyperlinks."
3850 		for (var i = 0; i < hyperlinks.length; i++) {
3851 			clearValue(hyperlinks[i], "unlink", range);
3852 		}
3853 	}, standardInlineValueCommand: true
3854 };
3855 
3856 //@}
3857 
3858 /////////////////////////////////////
3859 ///// Block formatting commands /////
3860 /////////////////////////////////////
3861 3862 
///// Block formatting command definitions /////
3863 //@{
3864 
3865 // "An indentation element is either a blockquote, or a div that has a style
3866 // attribute that sets "margin" or some subproperty of it."
3867 function isIndentationElement(node) {
3868 	if (!isHtmlElement(node)) {
3869 		return false;
3870 	}
3871 
3872 	if (node.tagName == "BLOCKQUOTE") {
3873 		return true;
3874 	}
3875 
3876 	if (node.tagName != "DIV") {
3877 		return false;
3878 	}
3879 
3880 	if (typeof node.style.length !== 'undefined') {
3881 		for (var i = 0; i < node.style.length; i++) {
3882 			// Approximate check
3883 			if (/^(-[a-z]+-)?margin/.test(node.style[i])) {
3884 				return true;
3885 			}
3886 		}
3887 	} else {
3888 		for (var s in node.style) {
3889 			if (/^(-[a-z]+-)?margin/.test(s) && node.style[s] && node.style[s] !== 0) {
3890 				return true;
3891 			}
3892 		}
3893 	}
3894 
3895 	return false;
3896 }
3897 
3898 // "A simple indentation element is an indentation element that has no
3899 // attributes other than one or more of
3900 //
3901 //   * "a style attribute that sets no properties other than "margin", "border",
3902 //     "padding", or subproperties of those;
3903 //   * "a class attribute;
3904 //   * "a dir attribute."
3905 function isSimpleIndentationElement(node) {
3906 	if (!isIndentationElement(node)) {
3907 		return false;
3908 	}
3909 
3910 	if (node.tagName != "BLOCKQUOTE" && node.tagName != "DIV") {
3911 		return false;
3912 	}
3913 
3914 	for (var i = 0; i < node.attributes.length; i++) {
3915 		if (!isHtmlNamespace(node.attributes[i].namespaceURI)
3916 		|| $_(["style", "class", "dir"]).indexOf(node.attributes[i].name) == -1) {
3917 			return false;
3918 		}
3919 	}
3920 
3921 	if (typeof node.style.length !== 'undefined') {
3922 		for (var i = 0; i < node.style.length; i++) {
3923 			// This is approximate, but it works well enough for my purposes.
3924 			if (!/^(-[a-z]+-)?(margin|border|padding)/.test(node.style[i])) {
3925 				return false;
3926 			}
3927 		}
3928 	} else {
3929 		for (var s in node.style) {
3930 			// This is approximate, but it works well enough for my purposes.
3931 			if (!/^(-[a-z]+-)?(margin|border|padding)/.test(s) && node.style[s] && node.style[s] !== 0 && node.style[s] !== 'false') {
3932 				return false;
3933 			}
3934 		}
3935 	}
3936 
3937 	return true;
3938 }
3939 
3940 // "A non-list single-line container is an HTML element with local name
3941 // "address", "div", "h1", "h2", "h3", "h4", "h5", "h6", "listing", "p", "pre",
3942 // or "xmp"."
3943 function isNonListSingleLineContainer(node) {
3944 	return isHtmlElement(node, ["address", "div", "h1", "h2", "h3", "h4", "h5",
3945 		"h6", "listing", "p", "pre", "xmp"]);
3946 }
3947 
3948 // "A single-line container is either a non-list single-line container, or an
3949 // HTML element with local name "li", "dt", or "dd"."
3950 function isSingleLineContainer(node) {
3951 	return isNonListSingleLineContainer(node)
3952 		|| isHtmlElement(node, ["li", "dt", "dd"]);
3953 }
3954 
3955 // "The default single-line container name is "p"."
3956 var defaultSingleLineContainerName = "p";
3957 
3958 
3959 //@}
3960 ///// Assorted block formatting command algorithms /////
3961 //@{
3962 
3963 function fixDisallowedAncestors(node, range) {
3964 	// "If node is not editable, abort these steps."
3965 	if (!isEditable(node)) {
3966 		return;
3967 	}
3968 
3969 	// "If node is not an allowed child of any of its ancestors in the same
3970 	// editing host, and is not an HTML element with local name equal to the
3971 	// default single-line container name:"
3972 	if ($_(getAncestors(node)).every(function(ancestor) {
3973 		return !inSameEditingHost(node, ancestor)
3974 			|| !isAllowedChild(node, ancestor)
3975 	})
3976 	&& !isHtmlElement(node, defaultSingleLineContainerName)) {
3977 		// "If node is a dd or dt, wrap the one-node list consisting of node,
3978 		// with sibling criteria returning true for any dl with no attributes
3979 		// and false otherwise, and new parent instructions returning the
3980 		// result of calling createElement("dl") on the context object. Then
3981 		// abort these steps."
3982 		if (isHtmlElement(node, ["dd", "dt"])) {
3983 			wrap([node],
3984 				function(sibling) { return isHtmlElement(sibling, "dl") && !sibling.attributes.length },
3985 				function() { return document.createElement("dl") },
3986 				range
3987 			);
3988 			return;
3989 		}
3990 
3991 		// "If node is not a prohibited paragraph child, abort these steps."
3992 		if (!isProhibitedParagraphChild(node)) {
3993 			return;
3994 		}
3995 
3996 		// "Set the tag name of node to the default single-line container name,
3997 		// and let node be the result."
3998 		node = setTagName(node, defaultSingleLineContainerName, range);
3999 
4000 		// "Fix disallowed ancestors of node."
4001 		fixDisallowedAncestors(node, range);
4002 
4003 		// "Let descendants be all descendants of node."
4004 		var descendants = getDescendants(node);
4005 
4006 		// "Fix disallowed ancestors of each member of descendants."
4007 		for (var i = 0; i < descendants.length; i++) {
4008 			fixDisallowedAncestors(descendants[i], range);
4009 		}
4010 
4011 		// "Abort these steps."
4012 		return;
4013 	}
4014 
4015 	// "Record the values of the one-node list consisting of node, and let
4016 	// values be the result."
4017 	var values = recordValues([node]);
4018 
4019 	// "While node is not an allowed child of its parent, split the parent of
4020 	// the one-node list consisting of node."
4021 	while (!isAllowedChild(node, node.parentNode)) {
4022 		// If the parent contains only this node and possibly empty text nodes, we rather want to unwrap the node, instead of splitting.
4023 4024 		// With splitting, we would get empty nodes, like:
4025 		// split: <p><p>foo</p></p> -> <p></p><p>foo</p> (bad)
4026 		// unwrap: <p><p>foo</p></p> -> <p>foo</p> (good)
4027 
4028 		// First remove empty text nodes that are children of the parent and correct the range if necessary
4029 		// we do this to have the node being the only child of its parent, so that we can replace the parent with the node
4030 		for (var i = node.parentNode.childNodes.length - 1; i >= 0; --i) {
4031 			if (node.parentNode.childNodes[i].nodeType == 3 && node.parentNode.childNodes[i].data.length == 0) {
4032 				// we remove the empty text node
4033 				node.parentNode.removeChild(node.parentNode.childNodes[i]);
4034 
4035 				// if the range points to somewhere behind the removed text node, we reduce the offset
4036 				if (range.startContainer == node.parentNode && range.startOffset > i) {
4037 					range.startOffset--;
4038 				}
4039 				if (range.endContainer == node.parentNode && range.endOffset > i) {
4040 					range.endOffset--;
4041 				}
4042 			}
4043 		}
4044 
4045 		// now that the parent has only the node as child (because we
4046 		// removed any existing empty text nodes), we can safely unwrap the
4047 		// node, and correct the range if necessary
4048 		if (node.parentNode.childNodes.length == 1) {
4049 			var correctStart = false;
4050 			var correctEnd = false;
4051 			if (range.startContainer === node.parentNode) {
4052 				correctStart = true;
4053 			}
4054 			if (range.endContainer === node.parentNode) {
4055 				correctEnd = true;
4056 			}
4057 			jQuery(node).unwrap();
4058 			if (correctStart) {
4059 				range.startContainer = node.parentNode;
4060 				range.startOffset = range.startOffset + getNodeIndex(node);
4061 			}
4062 			if (correctEnd) {
4063 				range.endContainer = node.parentNode;
4064 				range.endOffset = range.endOffset + getNodeIndex(node);
4065 			}
4066 		} else {
4067 			splitParent([node], range);
4068 		}
4069 	}
4070 
4071 	// "Restore the values from values."
4072 	restoreValues(values, range);
4073 }
4074 
4075 /**
4076  * This method "normalizes" sublists of the given item (which is supposed to be a LI):
4077  * If sublists are found in the LI element, they are moved directly into the outer list.
4078  * @param item item
4079  * @param range range, which will be modified if necessary
4080  */
4081 function normalizeSublists(item, range) {
4082 	// "If item is not an li or it is not editable or its parent is not
4083 	// editable, abort these steps."
4084 	if (!isHtmlElement(item, "LI")
4085 	|| !isEditable(item)
4086 	|| !isEditable(item.parentNode)) {
4087 		return;
4088 	}
4089 
4090 	// "Let new item be null."
4091 	var newItem = null;
4092 
4093 	// "While item has an ol or ul child:"
4094 	while ($_(item.childNodes).some( function (node) { return isHtmlElement(node, ["OL", "UL"]) })) {
4095 		// "Let child be the last child of item."
4096 		var child = item.lastChild;
4097 
4098 		// "If child is an ol or ul, or new item is null and child is a Text
4099 		// node whose data consists of zero of more space characters:"
4100 		if (isHtmlElement(child, ["OL", "UL"])
4101 		|| (!newItem && child.nodeType == $_.Node.TEXT_NODE && /^[ \t\n\f\r]*$/.test(child.data))) {
4102 			// "Set new item to null."
4103 			newItem = null;
4104 
4105 			// "Insert child into the parent of item immediately following
4106 			// item, preserving ranges."
4107 			movePreservingRanges(child, item.parentNode, 1 + getNodeIndex(item), range);
4108 
4109 		// "Otherwise:"
4110 		} else {
4111 			// "If new item is null, let new item be the result of calling
4112 			// createElement("li") on the ownerDocument of item, then insert
4113 			// new item into the parent of item immediately after item."
4114 			if (!newItem) {
4115 				newItem = item.ownerDocument.createElement("li");
4116 				item.parentNode.insertBefore(newItem, item.nextSibling);
4117 			}
4118 
4119 			// "Insert child into new item as its first child, preserving
4120 			// ranges."
4121 			movePreservingRanges(child, newItem, 0, range);
4122 		}
4123 	}
4124 }
4125 
4126 /**
4127  * This method is the exact opposite of normalizeSublists.
4128  * List nodes directly nested into each other are corrected to be nested in li elements (so that the resulting lists conform the html5 specification)
4129  * @param item list node
4130  * @param range range, which is preserved when modifying the list
4131  */
4132 function unNormalizeSublists(item, range) {
4133 	// "If item is not an ol or ol or it is not editable or its parent is not
4134 	// editable, abort these steps."
4135 	if (!isHtmlElement(item, ["OL", "UL"])
4136 	|| !isEditable(item)) {
4137 		return;
4138 	}
4139 
4140 	var $list = jQuery(item);
4141 	$list.children("ol,ul").each(function(index, sublist) {
4142 		if (isHtmlElement(sublist.previousSibling, "LI")) {
4143 			// move the sublist into the LI
4144 			movePreservingRanges(sublist, sublist.previousSibling, sublist.previousSibling.childNodes.length, range);
4145 		}
4146 	});
4147 }
4148 
4149 function getSelectionListState() {
4150 	// "Block-extend the active range, and let new range be the result."
4151 	var newRange = blockExtend(getActiveRange());
4152 
4153 	// "Let node list be a list of nodes, initially empty."
4154 	//
4155 	// "For each node contained in new range, append node to node list if the
4156 	// last member of node list (if any) is not an ancestor of node; node is
4157 	// editable; node is not an indentation element; and node is either an ol
4158 	// or ul, or the child of an ol or ul, or an allowed child of "li"."
4159 	var nodeList = getContainedNodes(newRange, function(node) {
4160 		return isEditable(node)
4161 			&& !isIndentationElement(node)
4162 			&& (isHtmlElement(node, ["ol", "ul"])
4163 			|| isHtmlElement(node.parentNode, ["ol", "ul"])
4164 			|| isAllowedChild(node, "li"));
4165 	});
4166 
4167 	// "If node list is empty, return "none"."
4168 	if (!nodeList.length) {
4169 		return "none";
4170 	}
4171 
4172 	// "If every member of node list is either an ol or the child of an ol or
4173 	// the child of an li child of an ol, and none is a ul or an ancestor of a
4174 	// ul, return "ol"."
4175 	if ($_(nodeList).every(function(node) {
4176 		return isHtmlElement(node, "ol")
4177 			|| isHtmlElement(node.parentNode, "ol")
4178 			|| (isHtmlElement(node.parentNode, "li") && isHtmlElement(node.parentNode.parentNode, "ol"));
4179 	})
4180 	&& !$_( nodeList ).some(function(node) { return isHtmlElement(node, "ul") || ("querySelector" in node && node.querySelector("ul")) })) {
4181 		return "ol";
4182 	}
4183 
4184 	// "If every member of node list is either a ul or the child of a ul or the
4185 	// child of an li child of a ul, and none is an ol or an ancestor of an ol,
4186 	// return "ul"."
4187 	if ($_(nodeList).every(function(node) {
4188 		return isHtmlElement(node, "ul")
4189 			|| isHtmlElement(node.parentNode, "ul")
4190 			|| (isHtmlElement(node.parentNode, "li") && isHtmlElement(node.parentNode.parentNode, "ul"));
4191 	})
4192 	&& !$_( nodeList ).some(function(node) { return isHtmlElement(node, "ol") || ("querySelector" in node && node.querySelector("ol")) })) {
4193 		return "ul";
4194 	}
4195 
4196 	var hasOl = $_( nodeList ).some(function(node) {
4197 		return isHtmlElement(node, "ol")
4198 			|| isHtmlElement(node.parentNode, "ol")
4199 			|| ("querySelector" in node && node.querySelector("ol"))
4200 			|| (isHtmlElement(node.parentNode, "li") && isHtmlElement(node.parentNode.parentNode, "ol"));
4201 	});
4202 	var hasUl = $_( nodeList ).some(function(node) {
4203 		return isHtmlElement(node, "ul")
4204 			|| isHtmlElement(node.parentNode, "ul")
4205 			|| ("querySelector" in node && node.querySelector("ul"))
4206 			|| (isHtmlElement(node.parentNode, "li") && isHtmlElement(node.parentNode.parentNode, "ul"));
4207 	});
4208 	// "If some member of node list is either an ol or the child or ancestor of
4209 	// an ol or the child of an li child of an ol, and some member of node list
4210 	// is either a ul or the child or ancestor of a ul or the child of an li
4211 	// child of a ul, return "mixed"."
4212 	if (hasOl && hasUl) {
4213 		return "mixed";
4214 	}
4215 
4216 	// "If some member of node list is either an ol or the child or ancestor of
4217 	// an ol or the child of an li child of an ol, return "mixed ol"."
4218 	if (hasOl) {
4219 		return "mixed ol";
4220 	}
4221 
4222 	// "If some member of node list is either a ul or the child or ancestor of
4223 	// a ul or the child of an li child of a ul, return "mixed ul"."
4224 	if (hasUl) {
4225 		return "mixed ul";
4226 	}
4227 
4228 	// "Return "none"."
4229 	return "none";
4230 }
4231 
4232 function getAlignmentValue(node) {
4233 	// "While node is neither null nor an Element, or it is an Element but its
4234 	// "display" property has resolved value "inline" or "none", set node to
4235 	// its parent."
4236 	while ((node && node.nodeType != $_.Node.ELEMENT_NODE)
4237 	|| (node.nodeType == $_.Node.ELEMENT_NODE
4238 	&& $_(["inline", "none"]).indexOf($_.getComputedStyle(node).display) != -1)) {
4239 		node = node.parentNode;
4240 	}
4241 
4242 	// "If node is not an Element, return "left"."
4243 	if (!node || node.nodeType != $_.Node.ELEMENT_NODE) {
4244 		return "left";
4245 	}
4246 
4247 	var resolvedValue = $_.getComputedStyle(node).textAlign
4248 		// Hack around browser non-standardness
4249 		.replace(/^-(moz|webkit)-/, "")
4250 		.replace(/^auto$/, "start");
4251 
4252 	// "If node's "text-align" property has resolved value "start", return
4253 	// "left" if the directionality of node is "ltr", "right" if it is "rtl"."
4254 	if (resolvedValue == "start") {
4255 		return getDirectionality(node) == "ltr" ? "left" : "right";
4256 	}
4257 
4258 	// "If node's "text-align" property has resolved value "end", return
4259 	// "right" if the directionality of node is "ltr", "left" if it is "rtl"."
4260 	if (resolvedValue == "end") {
4261 		return getDirectionality(node) == "ltr" ? "right" : "left";
4262 	}
4263 
4264 	// "If node's "text-align" property has resolved value "center", "justify",
4265 	// "left", or "right", return that value."
4266 	if ($_(["center", "justify", "left", "right"]).indexOf(resolvedValue) != -1) {
4267 		return resolvedValue;
4268 	}
4269 
4270 	// "Return "left"."
4271 	return "left";
4272 }
4273 
4274 //@}
4275 ///// Block-extending a range /////
4276 //@{
4277 
4278 // "A boundary point (node, offset) is a block start point if either node's
4279 // parent is null and offset is zero; or node has a child with index offset −
4280 // 1, and that child is either a visible block node or a visible br."
4281 function isBlockStartPoint(node, offset) {
4282 	return (!node.parentNode && offset == 0)
4283 		|| (0 <= offset - 1
4284 		&& offset - 1 < node.childNodes.length
4285 		&& isVisible(node.childNodes[offset - 1])
4286 		&& (isBlockNode(node.childNodes[offset - 1])
4287 		|| isHtmlElement(node.childNodes[offset - 1], "br")));
4288 }
4289 
4290 // "A boundary point (node, offset) is a block end point if either node's
4291 // parent is null and offset is node's length; or node has a child with index
4292 // offset, and that child is a visible block node."
4293 function isBlockEndPoint(node, offset) {
4294 	return (!node.parentNode && offset == getNodeLength(node))
4295 		|| (offset < node.childNodes.length
4296 		&& isVisible(node.childNodes[offset])
4297 		&& isBlockNode(node.childNodes[offset]));
4298 }
4299 
4300 // "A boundary point is a block boundary point if it is either a block start
4301 // point or a block end point."
4302 function isBlockBoundaryPoint(node, offset) {
4303 	return isBlockStartPoint(node, offset)
4304 		|| isBlockEndPoint(node, offset);
4305 }
4306 
4307 function blockExtend(range) {
4308 	// "Let start node, start offset, end node, and end offset be the start
4309 	// and end nodes and offsets of the range."
4310 	var startNode = range.startContainer;
4311 	var startOffset = range.startOffset;
4312 	var endNode = range.endContainer;
4313 	var endOffset = range.endOffset;
4314 
4315 	// "If some ancestor container of start node is an li, set start offset to
4316 	// the index of the last such li in tree order, and set start node to that
4317 	// li's parent."
4318 	var liAncestors = $_( getAncestors(startNode).concat(startNode) )
4319 		.filter(function(ancestor) { return isHtmlElement(ancestor, "li") })
4320 		.slice(-1);
4321 	if (liAncestors.length) {
4322 		startOffset = getNodeIndex(liAncestors[0]);
4323 		startNode = liAncestors[0].parentNode;
4324 	}
4325 
4326 	// "If (start node, start offset) is not a block start point, repeat the
4327 	// following steps:"
4328 	if (!isBlockStartPoint(startNode, startOffset)) do {
4329 		// "If start offset is zero, set it to start node's index, then set
4330 		// start node to its parent."
4331 		if (startOffset == 0) {
4332 			startOffset = getNodeIndex(startNode);
4333 			startNode = startNode.parentNode;
4334 
4335 		// "Otherwise, subtract one from start offset."
4336 		} else {
4337 			startOffset--;
4338 		}
4339 
4340 		// "If (start node, start offset) is a block boundary point, break from
4341 		// this loop."
4342 	} while (!isBlockBoundaryPoint(startNode, startOffset));
4343 
4344 	// "While start offset is zero and start node's parent is not null, set
4345 	// start offset to start node's index, then set start node to its parent."
4346 	while (startOffset == 0
4347 	&& startNode.parentNode) {
4348 		startOffset = getNodeIndex(startNode);
4349 		startNode = startNode.parentNode;
4350 	}
4351 
4352 	// "If some ancestor container of end node is an li, set end offset to one
4353 	// plus the index of the last such li in tree order, and set end node to
4354 	// that li's parent."
4355 	var liAncestors = $_( getAncestors(endNode).concat(endNode) )
4356 		.filter(function(ancestor) { return isHtmlElement(ancestor, "li") })
4357 		.slice(-1);
4358 	if (liAncestors.length) {
4359 		endOffset = 1 + getNodeIndex(liAncestors[0]);
4360 		endNode = liAncestors[0].parentNode;
4361 	}
4362 
4363 	// "If (end node, end offset) is not a block end point, repeat the
4364 	// following steps:"
4365 	if (!isBlockEndPoint(endNode, endOffset)) do {
4366 		// "If end offset is end node's length, set it to one plus end node's
4367 		// index, then set end node to its parent."
4368 		if (endOffset == getNodeLength(endNode)) {
4369 			endOffset = 1 + getNodeIndex(endNode);
4370 			endNode = endNode.parentNode;
4371 
4372 		// "Otherwise, add one to end offset.
4373 		} else {
4374 			endOffset++;
4375 		}
4376 
4377 		// "If (end node, end offset) is a block boundary point, break from
4378 		// this loop."
4379 	} while (!isBlockBoundaryPoint(endNode, endOffset));
4380 
4381 	// "While end offset is end node's length and end node's parent is not
4382 	// null, set end offset to one plus end node's index, then set end node to
4383 	// its parent."
4384 	while (endOffset == getNodeLength(endNode)
4385 	&& endNode.parentNode) {
4386 		endOffset = 1 + getNodeIndex(endNode);
4387 		endNode = endNode.parentNode;
4388 	}
4389 
4390 	// "Let new range be a new range whose start and end nodes and offsets
4391 	// are start node, start offset, end node, and end offset."
4392 	var newRange = Aloha.createRange();
4393 	newRange.setStart(startNode, startOffset);
4394 	newRange.setEnd(endNode, endOffset);
4395 
4396 	// "Return new range."
4397 	return newRange;
4398 }
4399 
4400 function followsLineBreak(node) {
4401 	// "Let offset be zero."
4402 	var offset = 0;
4403 
4404 	// "While (node, offset) is not a block boundary point:"
4405 	while (!isBlockBoundaryPoint(node, offset)) {
4406 		// "If node has a visible child with index offset minus one, return
4407 		// false."
4408 		if (0 <= offset - 1
4409 		&& offset - 1 < node.childNodes.length
4410 		&& isVisible(node.childNodes[offset - 1])) {
4411 			return false;
4412 		}
4413 
4414 		// "If offset is zero or node has no children, set offset to node's
4415 		// index, then set node to its parent."
4416 		if (offset == 0
4417 		|| !node.hasChildNodes()) {
4418 			offset = getNodeIndex(node);
4419 			node = node.parentNode;
4420 
4421 		// "Otherwise, set node to its child with index offset minus one, then
4422 		// set offset to node's length."
4423 		} else {
4424 			node = node.childNodes[offset - 1];
4425 			offset = getNodeLength(node);
4426 		}
4427 	}
4428 
4429 	// "Return true."
4430 	return true;
4431 }
4432 
4433 function precedesLineBreak(node) {
4434 	// "Let offset be node's length."
4435 	var offset = getNodeLength(node);
4436 
4437 	// "While (node, offset) is not a block boundary point:"
4438 	while (!isBlockBoundaryPoint(node, offset)) {
4439 		// "If node has a visible child with index offset, return false."
4440 		if (offset < node.childNodes.length
4441 		&& isVisible(node.childNodes[offset])) {
4442 			return false;
4443 		}
4444 
4445 		// "If offset is node's length or node has no children, set offset to
4446 		// one plus node's index, then set node to its parent."
4447 		if (offset == getNodeLength(node)
4448 		|| !node.hasChildNodes()) {
4449 			offset = 1 + getNodeIndex(node);
4450 			node = node.parentNode;
4451 
4452 		// "Otherwise, set node to its child with index offset and set offset
4453 		// to zero."
4454 		} else {
4455 			node = node.childNodes[offset];
4456 			offset = 0;
4457 		}
4458 	}
4459 
4460 	// "Return true."
4461 	return true;
4462 }
4463 
4464 //@}
4465 ///// Recording and restoring overrides /////
4466 //@{
4467 
4468 function recordCurrentOverrides( range ) {
4469 	// "Let overrides be a list of (string, string or boolean) ordered pairs,
4470 	// initially empty."
4471 	var overrides = [];
4472 
4473 	// "If there is a value override for "createLink", add ("createLink", value
4474 	// override for "createLink") to overrides."
4475 	if (getValueOverride("createlink" ,range) !== undefined) {
4476 		overrides.push(["createlink", getValueOverride("createlink", range)]);
4477 	}
4478 
4479 	// "For each command in the list "bold", "italic", "strikethrough",
4480 	// "subscript", "superscript", "underline", in order: if there is a state
4481 	// override for command, add (command, command's state override) to
4482 	// overrides."
4483 	$_( ["bold", "italic", "strikethrough", "subscript", "superscript",
4484 	"underline"] ).forEach(function(command) {
4485 		if (getStateOverride(command, range) !== undefined) {
4486 			overrides.push([command, getStateOverride(command, range)]);
4487 		}
4488 	});
4489 
4490 	// "For each command in the list "fontName", "fontSize", "foreColor",
4491 	// "hiliteColor", in order: if there is a value override for command, add
4492 	// (command, command's value override) to overrides."
4493 	$_( ["fontname", "fontsize", "forecolor",
4494 	"hilitecolor"] ).forEach(function(command) {
4495 		if (getValueOverride(command, range) !== undefined) {
4496 			overrides.push([command, getValueOverride(command, range)]);
4497 		}
4498 	});
4499 
4500 	// "Return overrides."
4501 	return overrides;
4502 }
4503 
4504 function recordCurrentStatesAndValues(range) {
4505 	// "Let overrides be a list of (string, string or boolean) ordered pairs,
4506 	// initially empty."
4507 	var overrides = [];
4508 
4509 	// "Let node be the first editable Text node effectively contained in the
4510 	// active range, or null if there is none."
4511 	var node = $_( getAllEffectivelyContainedNodes(range) )
4512 		.filter(function(node) { return isEditable(node) && node.nodeType == $_.Node.TEXT_NODE })[0];
4513 
4514 	// "If node is null, return overrides."
4515 	if (!node) {
4516 		return overrides;
4517 	}
4518 
4519 	// "Add ("createLink", value for "createLink") to overrides."
4520 	overrides.push(["createlink", commands.createlink.value(range)]);
4521 
4522 	// "For each command in the list "bold", "italic", "strikethrough",
4523 4524 	// "subscript", "superscript", "underline", in order: if node's effective
4525 	// command value for command is one of its inline command activated values,
4526 	// add (command, true) to overrides, and otherwise add (command, false) to
4527 	// overrides."
4528 	$_( ["bold", "italic", "strikethrough", "subscript", "superscript",
4529 	"underline"] ).forEach(function(command) {
4530 		if ($_(commands[command].inlineCommandActivatedValues)
4531 		.indexOf(getEffectiveCommandValue(node, command)) != -1) {
4532 			overrides.push([command, true]);
4533 		} else {
4534 			overrides.push([command, false]);
4535 		}
4536 	});
4537 
4538 	// "For each command in the list "fontName", "foreColor", "hiliteColor", in
4539 	// order: add (command, command's value) to overrides."
4540 
4541 	$_( ["fontname", "fontsize", "forecolor", "hilitecolor"] ).forEach(function(command) {
4542 		overrides.push([command, commands[command].value(range)]);
4543 	});
4544 
4545 	// "Add ("fontSize", node's effective command value for "fontSize") to
4546 	// overrides."
4547 	overrides.push("fontsize", getEffectiveCommandValue(node, "fontsize"));
4548 
4549 	// "Return overrides."
4550 	return overrides;
4551 }
4552 
4553 function restoreStatesAndValues(overrides, range) {
4554 	// "Let node be the first editable Text node effectively contained in the
4555 	// active range, or null if there is none."
4556 	var node = $_( getAllEffectivelyContainedNodes(range) )
4557 		.filter(function(node) { return isEditable(node) && node.nodeType == $_.Node.TEXT_NODE })[0];
4558 
4559 	// "If node is not null, then for each (command, override) pair in
4560 	// overrides, in order:"
4561 	if (node) {
4562 		for (var i = 0; i < overrides.length; i++) {
4563 			var command = overrides[i][0];
4564 			var override = overrides[i][1];
4565 
4566 			// "If override is a boolean, and queryCommandState(command)
4567 			// returns something different from override, call
4568 			// execCommand(command)."
4569 			if (typeof override == "boolean"
4570 			&& myQueryCommandState(command) != override) {
4571 				myExecCommand(command);
4572 
4573 			// "Otherwise, if override is a string, and command is not
4574 			// "fontSize", and queryCommandValue(command) returns something not
4575 			// equivalent to override, call execCommand(command, false,
4576 			// override)."
4577 			} else if (typeof override == "string"
4578 			&& command != "fontsize"
4579 			&& !areEquivalentValues(command, myQueryCommandValue(command), override)) {
4580 				myExecCommand(command, false, override);
4581 
4582 			// "Otherwise, if override is a string; and command is "fontSize";
4583 			// and either there is a value override for "fontSize" that is not
4584 			// equal to override, or there is no value override for "fontSize"
4585 			// and node's effective command value for "fontSize" is not loosely
4586 			// equivalent to override: call execCommand("fontSize", false,
4587 			// override)."
4588 			} else if (typeof override == "string"
4589 			&& command == "fontsize"
4590 			&& (
4591 				(
4592 					getValueOverride("fontsize", range) !== undefined
4593 					&& getValueOverride("fontsize", range) !== override
4594 				) || (
4595 					getValueOverride("fontsize", range) === undefined
4596 					&& !areLooselyEquivalentValues(command, getEffectiveCommandValue(node, "fontsize"), override)
4597 				)
4598 			)) {
4599 				myExecCommand("fontsize", false, override);
4600 
4601 			// "Otherwise, continue this loop from the beginning."
4602 			} else {
4603 				continue;
4604 			}
4605 
4606 			// "Set node to the first editable Text node effectively contained
4607 			// in the active range, if there is one."
4608 			node = $_( getAllEffectivelyContainedNodes(range) )
4609 				.filter(function(node) { return isEditable(node) && node.nodeType == $_.Node.TEXT_NODE })[0]
4610 				|| node;
4611 		}
4612 
4613 	// "Otherwise, for each (command, override) pair in overrides, in order:"
4614 	} else {
4615 		for (var i = 0; i < overrides.length; i++) {
4616 			var command = overrides[i][0];
4617 			var override = overrides[i][1];
4618 
4619 			// "If override is a boolean, set the state override for command to
4620 			// override."
4621 			if (typeof override == "boolean") {
4622 				setStateOverride(command, override, range);
4623 			}
4624 
4625 			// "If override is a string, set the value override for command to
4626 			// override."
4627 			if (typeof override == "string") {
4628 				setValueOverride(command, override, range);
4629 			}
4630 		}
4631 	}
4632 }
4633 
4634 //@}
4635 ///// Deleting the contents of a range /////
4636 //@{
4637 
4638 function deleteContents() {
4639 	// We accept several different calling conventions:
4640 	//
4641 	// 1) A single argument, which is a range.
4642 	//
4643 	// 2) Two arguments, the first being a range and the second flags.
4644 	//
4645 	// 3) Four arguments, the start and end of a range.
4646 	//
4647 	// 4) Five arguments, the start and end of a range plus flags.
4648 	//
4649 	// The flags argument is a dictionary that can have up to two keys,
4650 	// blockMerging and stripWrappers, whose corresponding values are
4651 	// interpreted as boolean.  E.g., {stripWrappers: false}.
4652 	var range;
4653 	var flags = {};
4654 
4655 	if (arguments.length < 3) {
4656 		range = arguments[0];
4657 	} else {
4658 		range = Aloha.createRange();
4659 		range.setStart(arguments[0], arguments[1]);
4660 		range.setEnd(arguments[2], arguments[3]);
4661 	}
4662 	if (arguments.length == 2) {
4663 		flags = arguments[1];
4664 	}
4665 	if (arguments.length == 5) {
4666 		flags = arguments[4];
4667 	}
4668 
4669 	var blockMerging = "blockMerging" in flags ? !!flags.blockMerging : true;
4670 	var stripWrappers = "stripWrappers" in flags ? !!flags.stripWrappers : true;
4671 
4672 	// "If range is null, abort these steps and do nothing."
4673 	if (!range) {
4674 		return;
4675 	}
4676 
4677 	// "Let start node, start offset, end node, and end offset be range's start
4678 	// and end nodes and offsets."
4679 	var startNode = range.startContainer;
4680 	var startOffset = range.startOffset;
4681 	var endNode = range.endContainer;
4682 	var endOffset = range.endOffset;
4683 
4684 	// "While start node has at least one child:"
4685 	while (startNode.hasChildNodes()) {
4686 		// "If start offset is start node's length, and start node's parent is
4687 		// in the same editing host, and start node is an inline node, set
4688 		// start offset to one plus the index of start node, then set start
4689 		// node to its parent and continue this loop from the beginning."
4690 		if (startOffset == getNodeLength(startNode)
4691 		&& inSameEditingHost(startNode, startNode.parentNode)
4692 		&& isInlineNode(startNode)) {
4693 			startOffset = 1 + getNodeIndex(startNode);
4694 			startNode = startNode.parentNode;
4695 			continue;
4696 		}
4697 
4698 		// "If start offset is start node's length, break from this loop."
4699 		if (startOffset == getNodeLength(startNode)) {
4700 			break;
4701 		}
4702 
4703 		// "Let reference node be the child of start node with index equal to
4704 		// start offset."
4705 		var referenceNode = startNode.childNodes[startOffset];
4706 
4707 		// "If reference node is a block node or an Element with no children,
4708 		// or is neither an Element nor a Text node, break from this loop."
4709 		if (isBlockNode(referenceNode)
4710 		|| (referenceNode.nodeType == $_.Node.ELEMENT_NODE
4711 		&& !referenceNode.hasChildNodes())
4712 		|| (referenceNode.nodeType != $_.Node.ELEMENT_NODE
4713 		&& referenceNode.nodeType != $_.Node.TEXT_NODE)) {
4714 			break;
4715 		}
4716 
4717 		// "Set start node to reference node and start offset to 0."
4718 		startNode = referenceNode;
4719 		startOffset = 0;
4720 	}
4721 
4722 	// "While end node has at least one child:"
4723 	while (endNode.hasChildNodes()) {
4724 		// "If end offset is 0, and end node's parent is in the same editing
4725 		// host, and end node is an inline node, set end offset to the index of
4726 		// end node, then set end node to its parent and continue this loop
4727 		// from the beginning."
4728 		if (endOffset == 0
4729 		&& inSameEditingHost(endNode, endNode.parentNode)
4730 		&& isInlineNode(endNode)) {
4731 			endOffset = getNodeIndex(endNode);
4732 			endNode = endNode.parentNode;
4733 			continue;
4734 		}
4735 
4736 		// "If end offset is 0, break from this loop."
4737 		if (endOffset == 0) {
4738 			break;
4739 		}
4740 
4741 		// "Let reference node be the child of end node with index equal to end
4742 		// offset minus one."
4743 		var referenceNode = endNode.childNodes[endOffset - 1];
4744 
4745 		// "If reference node is a block node or an Element with no children,
4746 		// or is neither an Element nor a Text node, break from this loop."
4747 		if (isBlockNode(referenceNode)
4748 		|| (referenceNode.nodeType == $_.Node.ELEMENT_NODE
4749 		&& !referenceNode.hasChildNodes())
4750 		|| (referenceNode.nodeType != $_.Node.ELEMENT_NODE
4751 		&& referenceNode.nodeType != $_.Node.TEXT_NODE)) {
4752 			break;
4753 		}
4754 
4755 		// "Set end node to reference node and end offset to the length of
4756 		// reference node."
4757 		endNode = referenceNode;
4758 		endOffset = getNodeLength(referenceNode);
4759 	}
4760 
4761 	// "If (end node, end offset) is not after (start node, start offset), set
4762 	// range's end to its start and abort these steps."
4763 	if (getPosition(endNode, endOffset, startNode, startOffset) !== "after") {
4764 		range.setEnd(range.startContainer, range.startOffset);
4765 		return;
4766 	}
4767 
4768 	// "If start node is a Text node and start offset is 0, set start offset to
4769 	// the index of start node, then set start node to its parent."
4770 	if (startNode.nodeType == $_.Node.TEXT_NODE
4771 	&& startOffset == 0
4772 	&& startNode != endNode) {
4773 //		startOffset = getNodeIndex(startNode);
4774 //		startNode = startNode.parentNode;
4775 	}
4776 
4777 	// "If end node is a Text node and end offset is its length, set end offset
4778 	// to one plus the index of end node, then set end node to its parent."
4779 	if (endNode.nodeType == $_.Node.TEXT_NODE
4780 	&& endOffset == getNodeLength(endNode)
4781 	&& startNode != endNode) {
4782 		endOffset = 1 + getNodeIndex(endNode);
4783 		endNode = endNode.parentNode;
4784 	}
4785 
4786 	// "Set range's start to (start node, start offset) and its end to (end
4787 	// node, end offset)."
4788 	range.setStart(startNode, startOffset);
4789 	range.setEnd(endNode, endOffset);
4790 
4791 	// "Let start block be the start node of range."
4792 	var startBlock = range.startContainer;
4793 
4794 	// "While start block's parent is in the same editing host and start block
4795 	// is an inline node, set start block to its parent."
4796 	while (inSameEditingHost(startBlock, startBlock.parentNode)
4797 	&& isInlineNode(startBlock)) {
4798 		startBlock = startBlock.parentNode;
4799 	}
4800 
4801 	// "If start block is neither a block node nor an editing host, or "span"
4802 	// is not an allowed child of start block, or start block is a td or th,
4803 	// set start block to null."
4804 	if ((!isBlockNode(startBlock) && !isEditingHost(startBlock))
4805 	|| !isAllowedChild("span", startBlock)
4806 	|| isHtmlElement(startBlock, ["td", "th"])) {
4807 		startBlock = null;
4808 	}
4809 
4810 	// "Let end block be the end node of range."
4811 	var endBlock = range.endContainer;
4812 	
4813 	// "While end block's parent is in the same editing host and end block is
4814 	// an inline node, set end block to its parent."
4815 	while (inSameEditingHost(endBlock, endBlock.parentNode)
4816 	&& isInlineNode(endBlock)) {
4817 		endBlock = endBlock.parentNode;
4818 	}
4819 	
4820 	// "If end block is neither a block node nor an editing host, or "span" is
4821 	// not an allowed child of end block, or end block is a td or th, set end
4822 	// block to null."
4823 	if ((!isBlockNode(endBlock) && !isEditingHost(endBlock))
4824 	|| !isAllowedChild("span", endBlock)
4825 	|| isHtmlElement(endBlock, ["td", "th"])) {
4826 		endBlock = null;
4827 	}
4828 
4829 	// "Record current states and values, and let overrides be the result."
4830 	var overrides = recordCurrentStatesAndValues(range);
4831 	// "If start node and end node are the same, and start node is an editable
4832 	// Text node:"
4833 	if (startNode == endNode
4834 	&& isEditable(startNode)
4835 	&& startNode.nodeType == $_.Node.TEXT_NODE) {
4836 		// "Let parent be the parent of node."
4837 		var parent_ = startNode.parentNode;
4838 
4839 		// "Call deleteData(start offset, end offset − start offset) on start
4840 		// node."
4841 		startNode.deleteData(startOffset, endOffset - startOffset);
4842 
4843 		// "Canonicalize whitespace at (start node, start offset)."
4844 		canonicalizeWhitespace(startNode, startOffset);
4845 
4846 		// "Set range's end to its start."
4847 		range.setEnd(range.startContainer, range.startOffset);
4848 
4849 		// "Restore states and values from overrides."
4850 		restoreStatesAndValues(overrides, range);
4851 
4852 		// "If parent is editable or an editing host, is not an inline node,
4853 		// and has no children, call createElement("br") on the context object
4854 		// and append the result as the last child of parent."
4855 		// only do this, if the offsetHeight is 0
4856 		if ((isEditable(parent_) || isEditingHost(parent_))
4857 		&& !isInlineNode(parent_)
4858 		&& parent_.offsetHeight === 0) {
4859 			parent_.appendChild(createEndBreak());
4860 		}
4861 
4862 		// "Abort these steps."
4863 		return;
4864 	}
4865 
4866 	// "If start node is an editable Text node, call deleteData() on it, with
4867 	// start offset as the first argument and (length of start node − start
4868 	// offset) as the second argument."
4869 	if (isEditable(startNode)
4870 	&& startNode.nodeType == $_.Node.TEXT_NODE) {
4871 		startNode.deleteData(startOffset, getNodeLength(startNode) - startOffset);
4872 	}
4873 
4874 	// "Let node list be a list of nodes, initially empty."
4875 	//
4876 	// "For each node contained in range, append node to node list if the last
4877 	// member of node list (if any) is not an ancestor of node; node is
4878 	// editable; and node is not a thead, tbody, tfoot, tr, th, or td."
4879 	var nodeList = getContainedNodes(range,
4880 		function(node) {
4881 			return isEditable(node)
4882 				&& !isHtmlElement(node, ["thead", "tbody", "tfoot", "tr", "th", "td"]);
4883 		}
4884 	);
4885 
4886 	// "For each node in node list:"
4887 	for (var i = 0; i < nodeList.length; i++) {
4888 		var node = nodeList[i];
4889 
4890 		// "Let parent be the parent of node."
4891 		var parent_ = node.parentNode;
4892 
4893 		// "Remove node from parent."
4894 		parent_.removeChild(node);
4895 
4896 		// "If strip wrappers is true or parent is not an ancestor container of
4897 		// start node, while parent is an editable inline node with length 0,
4898 		// let grandparent be the parent of parent, then remove parent from
4899 		// grandparent, then set parent to grandparent."
4900 		if (stripWrappers
4901 		|| (!isAncestor(parent_, startNode) && parent_ != startNode)) {
4902 			while (isEditable(parent_)
4903 			&& isInlineNode(parent_)
4904 			&& getNodeLength(parent_) == 0) {
4905 				var grandparent = parent_.parentNode;
4906 				grandparent.removeChild(parent_);
4907 				parent_ = grandparent;
4908 			}
4909 		}
4910 
4911 		// "If parent is editable or an editing host, is not an inline node,
4912 		// and has no children, call createElement("br") on the context object
4913 		// and append the result as the last child of parent."
4914 		// only do this, if the offsetHeight is 0
4915 		if ((isEditable(parent_) || isEditingHost(parent_))
4916 		&& !isInlineNode(parent_)
4917 		&& !parent_.hasChildNodes()
4918 		&& parent_.offsetHeight === 0) {
4919 			parent_.appendChild(createEndBreak());
4920 		}
4921 	}
4922 
4923 	// "If end node is an editable Text node, call deleteData(0, end offset) on
4924 	// it."
4925 	if (isEditable(endNode)
4926 	&& endNode.nodeType == $_.Node.TEXT_NODE) {
4927 		endNode.deleteData(0, endOffset);
4928 	}
4929 
4930 	// "Canonicalize whitespace at range's start."
4931 	canonicalizeWhitespace(range.startContainer, range.startOffset);
4932 
4933 	// "Canonicalize whitespace at range's end."
4934 	canonicalizeWhitespace(range.endContainer, range.endOffset);
4935 
4936 	// "If block merging is false, or start block or end block is null, or
4937 	// start block is not in the same editing host as end block, or start block
4938 	// and end block are the same:"
4939 	if (!blockMerging
4940 	|| !startBlock
4941 	|| !endBlock
4942 	|| !inSameEditingHost(startBlock, endBlock)
4943 	|| startBlock == endBlock) {
4944 		// "Set range's end to its start."
4945 		range.setEnd(range.startContainer, range.startOffset);
4946 
4947 		// "Restore states and values from overrides."
4948 		restoreStatesAndValues(overrides, range);
4949 
4950 		// "Abort these steps."
4951 		return;
4952 	}
4953 
4954 	// "If start block has one child, which is a collapsed block prop, remove
4955 	// its child from it."
4956 	if (startBlock.children.length == 1
4957 	&& isCollapsedBlockProp(startBlock.firstChild)) {
4958 		startBlock.removeChild(startBlock.firstChild);
4959 	}
4960 
4961 	// "If end block has one child, which is a collapsed block prop, remove its
4962 	// child from it."
4963 	if (endBlock.children.length == 1
4964 	&& isCollapsedBlockProp(endBlock.firstChild)) {
4965 		endBlock.removeChild(endBlock.firstChild);
4966 	}
4967 
4968 	// "If start block is an ancestor of end block:"
4969 	if (isAncestor(startBlock, endBlock)) {
4970 		// "Let reference node be end block."
4971 		var referenceNode = endBlock;
4972 
4973 		// "While reference node is not a child of start block, set reference
4974 		// node to its parent."
4975 		while (referenceNode.parentNode != startBlock) {
4976 			referenceNode = referenceNode.parentNode;
4977 		}
4978 
4979 		// "Set the start and end of range to (start block, index of reference
4980 		// node)."
4981 		range.setStart(startBlock, getNodeIndex(referenceNode));
4982 		range.setEnd(startBlock, getNodeIndex(referenceNode));
4983 
4984 		// "If end block has no children:"
4985 		if (!endBlock.hasChildNodes()) {
4986 			// "While end block is editable and is the only child of its parent
4987 			// and is not a child of start block, let parent equal end block,
4988 			// then remove end block from parent, then set end block to
4989 			// parent."
4990 			while (isEditable(endBlock)
4991 			&& endBlock.parentNode.childNodes.length == 1
4992 			&& endBlock.parentNode != startBlock) {
4993 				var parent_ = endBlock;
4994 				parent_.removeChild(endBlock);
4995 				endBlock = parent_;
4996 			}
4997 
4998 			// "If end block is editable and is not an inline node, and its
4999 			// previousSibling and nextSibling are both inline nodes, call
5000 			// createElement("br") on the context object and insert it into end
5001 			// block's parent immediately after end block."
5002 
5003 			if (isEditable(endBlock)
5004 			&& !isInlineNode(endBlock)
5005 			&& isInlineNode(endBlock.previousSibling)
5006 			&& isInlineNode(endBlock.nextSibling)) {
5007 				endBlock.parentNode.insertBefore(document.createElement("br"), endBlock.nextSibling);
5008 			}
5009 
5010 			// "If end block is editable, remove it from its parent."
5011 			if (isEditable(endBlock)) {
5012 				endBlock.parentNode.removeChild(endBlock);
5013 			}
5014 
5015 			// "Restore states and values from overrides."
5016 			restoreStatesAndValues(overrides, range);
5017 
5018 			// "Abort these steps."
5019 			return;
5020 		}
5021 
5022 		// "If end block's firstChild is not an inline node, restore states and
5023 		// values from overrides, then abort these steps."
5024 		if (!isInlineNode(endBlock.firstChild)) {
5025 			restoreStatesAndValues(overrides, range);
5026 			return;
5027 		}
5028 
5029 		// "Let children be a list of nodes, initially empty."
5030 		var children = [];
5031 
5032 		// "Append the first child of end block to children."
5033 		children.push(endBlock.firstChild);
5034 
5035 		// "While children's last member is not a br, and children's last
5036 		// member's nextSibling is an inline node, append children's last
5037 		// member's nextSibling to children."
5038 		while (!isHtmlElement(children[children.length - 1], "br")
5039 		&& isInlineNode(children[children.length - 1].nextSibling)) {
5040 			children.push(children[children.length - 1].nextSibling);
5041 		}
5042 
5043 		// "Record the values of children, and let values be the result."
5044 		var values = recordValues(children);
5045 
5046 		// "While children's first member's parent is not start block, split
5047 		// the parent of children."
5048 		while (children[0].parentNode != startBlock) {
5049 			splitParent(children, range);
5050 		}
5051 
5052 		// "If children's first member's previousSibling is an editable br,
5053 		// remove that br from its parent."
5054 		if (isEditable(children[0].previousSibling)
5055 		&& isHtmlElement(children[0].previousSibling, "br")) {
5056 			children[0].parentNode.removeChild(children[0].previousSibling);
5057 		}
5058 
5059 	// "Otherwise, if start block is a descendant of end block:"
5060 	} else if (isDescendant(startBlock, endBlock)) {
5061 		// "Set the start and end of range to (start block, length of start
5062 		// block)."
5063 		range.setStart(startBlock, getNodeLength(startBlock));
5064 		range.setEnd(startBlock, getNodeLength(startBlock));
5065 
5066 		// "Let reference node be start block."
5067 		var referenceNode = startBlock;
5068 
5069 		// "While reference node is not a child of end block, set reference
5070 		// node to its parent."
5071 		while (referenceNode.parentNode != endBlock) {
5072 			referenceNode = referenceNode.parentNode;
5073 		}
5074 
5075 		// "If reference node's nextSibling is an inline node and start block's
5076 		// lastChild is a br, remove start block's lastChild from it."
5077 		if (isInlineNode(referenceNode.nextSibling)
5078 		&& isHtmlElement(startBlock.lastChild, "br")) {
5079 			startBlock.removeChild(startBlock.lastChild);
5080 		}
5081 
5082 		// "Let nodes to move be a list of nodes, initially empty."
5083 		var nodesToMove = [];
5084 
5085 		// "If reference node's nextSibling is neither null nor a br nor a
5086 		// block node, append it to nodes to move."
5087 		if (referenceNode.nextSibling
5088 		&& !isHtmlElement(referenceNode.nextSibling, "br")
5089 		&& !isBlockNode(referenceNode.nextSibling)) {
5090 			nodesToMove.push(referenceNode.nextSibling);
5091 		}
5092 
5093 		// "While nodes to move is nonempty and its last member's nextSibling
5094 		// is neither null nor a br nor a block node, append it to nodes to
5095 		// move."
5096 		if (nodesToMove.length
5097 		&& nodesToMove[nodesToMove.length - 1].nextSibling
5098 		&& !isHtmlElement(nodesToMove[nodesToMove.length - 1].nextSibling, "br")
5099 		&& !isBlockNode(nodesToMove[nodesToMove.length - 1].nextSibling)) {
5100 			nodesToMove.push(nodesToMove[nodesToMove.length - 1].nextSibling);
5101 		}
5102 
5103 		// "Record the values of nodes to move, and let values be the result."
5104 		var values = recordValues(nodesToMove);
5105 
5106 		// "For each node in nodes to move, append node as the last child of
5107 		// start block, preserving ranges."
5108 		$_( nodesToMove ).forEach(function(node) {
5109 			movePreservingRanges(node, startBlock, -1, range);
5110 		});
5111 
5112 		// "If the nextSibling of reference node is a br, remove it from its
5113 		// parent."
5114 		if (isHtmlElement(referenceNode.nextSibling, "br")) {
5115 			referenceNode.parentNode.removeChild(referenceNode.nextSibling);
5116 		}
5117 
5118 	// "Otherwise:"
5119 	} else {
5120 		// "Set the start and end of range to (start block, length of start
5121 		// block)."
5122 		range.setStart(startBlock, getNodeLength(startBlock));
5123 		range.setEnd(startBlock, getNodeLength(startBlock));
5124 
5125 		// "If end block's firstChild is an inline node and start block's
5126 		// lastChild is a br, remove start block's lastChild from it."
5127 		if (isInlineNode(endBlock.firstChild)
5128 		&& isHtmlElement(startBlock.lastChild, "br")) {
5129 			startBlock.removeChild(startBlock.lastChild);
5130 		}
5131 
5132 		// "Record the values of end block's children, and let values be the
5133 		// result."
5134 		var values = recordValues([].slice.call(toArray(endBlock.childNodes)));
5135 
5136 		// "While end block has children, append the first child of end block
5137 		// to start block, preserving ranges."
5138 		while (endBlock.hasChildNodes()) {
5139 			movePreservingRanges(endBlock.firstChild, startBlock, -1, range);
5140 		}
5141 
5142 		// "While end block has no children, let parent be the parent of end
5143 		// block, then remove end block from parent, then set end block to
5144 		// parent."
5145 		while (!endBlock.hasChildNodes()) {
5146 			var parent_ = endBlock.parentNode;
5147 			parent_.removeChild(endBlock);
5148 			endBlock = parent_;
5149 		}
5150 	}
5151 
5152 	// "Restore the values from values."
5153 	restoreValues(values, range);
5154 
5155 	// "If start block has no children, call createElement("br") on the context
5156 	// object and append the result as the last child of start block."
5157 	if (!startBlock.hasChildNodes() && startBlock.offsetHeight == 0) {
5158 		startBlock.appendChild(createEndBreak());
5159 	}
5160 
5161 	// "Restore states and values from overrides."
5162 	restoreStatesAndValues(overrides, range);
5163 }
5164 
5165 
5166 //@}
5167 ///// Splitting a node list's parent /////
5168 //@{
5169 
5170 function splitParent(nodeList, range) {
5171 	// "Let original parent be the parent of the first member of node list."
5172 	var originalParent = nodeList[0].parentNode;
5173 
5174 	// "If original parent is not editable or its parent is null, do nothing
5175 	// and abort these steps."
5176 	if (!isEditable(originalParent)
5177 	|| !originalParent.parentNode) {
5178 		return;
5179 	}
5180 
5181 	// "If the first child of original parent is in node list, remove
5182 	// extraneous line breaks before original parent."
5183 	if ($_(nodeList).indexOf(originalParent.firstChild) != -1) {
5184 		removeExtraneousLineBreaksBefore(originalParent);
5185 	}
5186 
5187 	// "If the first child of original parent is in node list, and original
5188 	// parent follows a line break, set follows line break to true. Otherwise,
5189 	// set follows line break to false."
5190 	var followsLineBreak_ = $_(nodeList).indexOf(originalParent.firstChild) != -1
5191 		&& followsLineBreak(originalParent);
5192 
5193 	// "If the last child of original parent is in node list, and original
5194 	// parent precedes a line break, set precedes line break to true.
5195 	// Otherwise, set precedes line break to false."
5196 	var precedesLineBreak_ = $_(nodeList).indexOf(originalParent.lastChild) != -1
5197 		&& precedesLineBreak(originalParent);
5198 
5199 	// "If the first child of original parent is not in node list, but its last
5200 	// child is:"
5201 	if ($_(nodeList).indexOf(originalParent.firstChild) == -1
5202 	&& $_(nodeList).indexOf(originalParent.lastChild) != -1) {
5203 		// "For each node in node list, in reverse order, insert node into the
5204 		// parent of original parent immediately after original parent,
5205 		// preserving ranges."
5206 		for (var i = nodeList.length - 1; i >= 0; i--) {
5207 			movePreservingRanges(nodeList[i], originalParent.parentNode, 1 + getNodeIndex(originalParent), range);
5208 		}
5209 
5210 		// "If precedes line break is true, and the last member of node list
5211 		// does not precede a line break, call createElement("br") on the
5212 		// context object and insert the result immediately after the last
5213 		// member of node list."
5214 		if (precedesLineBreak_
5215 		&& !precedesLineBreak(nodeList[nodeList.length - 1])) {
5216 			nodeList[nodeList.length - 1].parentNode.insertBefore(document.createElement("br"), nodeList[nodeList.length - 1].nextSibling);
5217 		}
5218 
5219 		// "Remove extraneous line breaks at the end of original parent."
5220 		removeExtraneousLineBreaksAtTheEndOf(originalParent);
5221 
5222 		// "Abort these steps."
5223 		return;
5224 	}
5225 
5226 	// "If the first child of original parent is not in node list:"
5227 	if ($_(nodeList).indexOf(originalParent.firstChild) == -1) {
5228 		// "Let cloned parent be the result of calling cloneNode(false) on
5229 		// original parent."
5230 		var clonedParent = originalParent.cloneNode(false);
5231 
5232 		// "If original parent has an id attribute, unset it."
5233 		originalParent.removeAttribute("id");
5234 
5235 		// "Insert cloned parent into the parent of original parent immediately
5236 		// before original parent."
5237 		originalParent.parentNode.insertBefore(clonedParent, originalParent);
5238 
5239 		// "While the previousSibling of the first member of node list is not
5240 		// null, append the first child of original parent as the last child of
5241 		// cloned parent, preserving ranges."
5242 		while (nodeList[0].previousSibling) {
5243 			movePreservingRanges(originalParent.firstChild, clonedParent, clonedParent.childNodes.length, range);
5244 		}
5245 	}
5246 
5247 	// "For each node in node list, insert node into the parent of original
5248 	// parent immediately before original parent, preserving ranges."
5249 	for (var i = 0; i < nodeList.length; i++) {
5250 		movePreservingRanges(nodeList[i], originalParent.parentNode, getNodeIndex(originalParent), range);
5251 	}
5252 
5253 	// "If follows line break is true, and the first member of node list does
5254 	// not follow a line break, call createElement("br") on the context object
5255 	// and insert the result immediately before the first member of node list."
5256 	if (followsLineBreak_
5257 	&& !followsLineBreak(nodeList[0])) {
5258 		nodeList[0].parentNode.insertBefore(document.createElement("br"), nodeList[0]);
5259 	}
5260 
5261 	// "If the last member of node list is an inline node other than a br, and
5262 	// the first child of original parent is a br, and original parent is not
5263 	// an inline node, remove the first child of original parent from original
5264 	// parent."
5265 	if (isInlineNode(nodeList[nodeList.length - 1])
5266 	&& !isHtmlElement(nodeList[nodeList.length - 1], "br")
5267 	&& isHtmlElement(originalParent.firstChild, "br")
5268 	&& !isInlineNode(originalParent)) {
5269 		originalParent.removeChild(originalParent.firstChild);
5270 	}
5271 
5272 	// "If original parent has no children:"
5273 	if (!originalParent.hasChildNodes()) {
5274 		// if the current range is collapsed and at the end of the originalParent.parentNode
5275 		// the offset will not be available anymore after the next step (remove child)
5276 		// that's why we need to fix the range to prevent a bogus offset
5277 		if (originalParent.parentNode === range.startContainer
5278 		&& originalParent.parentNode === range.endContainer
5279 		&& range.startContainer === range.endContainer
5280 		&& range.startOffset === range.endOffset
5281 		&& originalParent.parentNode.childNodes.length === range.startOffset) {
5282 			range.startOffset = originalParent.parentNode.childNodes.length - 1;
5283 			range.endOffset = range.startOffset;
5284 		}
5285 
5286 		// "Remove original parent from its parent."
5287 		originalParent.parentNode.removeChild(originalParent);
5288 
5289 		// "If precedes line break is true, and the last member of node list
5290 		// does not precede a line break, call createElement("br") on the
5291 		// context object and insert the result immediately after the last
5292 		// member of node list."
5293 		if (precedesLineBreak_
5294 		&& !precedesLineBreak(nodeList[nodeList.length - 1])) {
5295 			nodeList[nodeList.length - 1].parentNode.insertBefore(document.createElement("br"), nodeList[nodeList.length - 1].nextSibling);
5296 		}
5297 
5298 	// "Otherwise, remove extraneous line breaks before original parent."
5299 	} else {
5300 		removeExtraneousLineBreaksBefore(originalParent);
5301 	}
5302 
5303 	// "If node list's last member's nextSibling is null, but its parent is not
5304 	// null, remove extraneous line breaks at the end of node list's last
5305 	// member's parent."
5306 	if (!nodeList[nodeList.length - 1].nextSibling
5307 	&& nodeList[nodeList.length - 1].parentNode) {
5308 		removeExtraneousLineBreaksAtTheEndOf(nodeList[nodeList.length - 1].parentNode);
5309 	}
5310 }
5311 
5312 // "To remove a node node while preserving its descendants, split the parent of
5313 // node's children if it has any. If it has no children, instead remove it from
5314 // its parent."
5315 function removePreservingDescendants(node, range) {
5316 	if (node.hasChildNodes()) {
5317 		splitParent([].slice.call(toArray(node.childNodes)), range);
5318 	} else {
5319 		node.parentNode.removeChild(node);
5320 	}
5321 }
5322 
5323 
5324 //@}
5325 ///// Canonical space sequences /////
5326 //@{
5327 
5328 function canonicalSpaceSequence(n, nonBreakingStart, nonBreakingEnd) {
5329 	// "If n is zero, return the empty string."
5330 	if (n == 0) {
5331 		return "";
5332 	}
5333 
5334 	// "If n is one and both non-breaking start and non-breaking end are false,
5335 	// return a single space (U+0020)."
5336 	if (n == 1 && !nonBreakingStart && !nonBreakingEnd) {
5337 		return " ";
5338 	}
5339 
5340 	// "If n is one, return a single non-breaking space (U+00A0)."
5341 	if (n == 1) {
5342 		return "\xa0";
5343 	}
5344 
5345 	// "Let buffer be the empty string."
5346 	var buffer = "";
5347 
5348 	// "If non-breaking start is true, let repeated pair be U+00A0 U+0020.
5349 	// Otherwise, let it be U+0020 U+00A0."
5350 	var repeatedPair;
5351 	if (nonBreakingStart) {
5352 		repeatedPair = "\xa0 ";
5353 	} else {
5354 		repeatedPair = " \xa0";
5355 	}
5356 
5357 	// "While n is greater than three, append repeated pair to buffer and
5358 	// subtract two from n."
5359 	while (n > 3) {
5360 		buffer += repeatedPair;
5361 		n -= 2;
5362 	}
5363 
5364 	// "If n is three, append a three-element string to buffer depending on
5365 	// non-breaking start and non-breaking end:"
5366 	if (n == 3) {
5367 		buffer +=
5368 			!nonBreakingStart && !nonBreakingEnd ? " \xa0 "
5369 			: nonBreakingStart && !nonBreakingEnd ? "\xa0\xa0 "
5370 			: !nonBreakingStart && nonBreakingEnd ? " \xa0\xa0"
5371 			: nonBreakingStart && nonBreakingEnd ? "\xa0 \xa0"
5372 			: "impossible";
5373 
5374 	// "Otherwise, append a two-element string to buffer depending on
5375 	// non-breaking start and non-breaking end:"
5376 	} else {
5377 		buffer +=
5378 			!nonBreakingStart && !nonBreakingEnd ? "\xa0 "
5379 			: nonBreakingStart && !nonBreakingEnd ? "\xa0 "
5380 			: !nonBreakingStart && nonBreakingEnd ? " \xa0"
5381 			: nonBreakingStart && nonBreakingEnd ? "\xa0\xa0"
5382 			: "impossible";
5383 	}
5384 
5385 	// "Return buffer."
5386 	return buffer;
5387 }
5388 
5389 function canonicalizeWhitespace(node, offset) {
5390 	// "If node is neither editable nor an editing host, abort these steps."
5391 	if (!isEditable(node) && !isEditingHost(node)) {
5392 		return;
5393 	}
5394 
5395 	// "Let start node equal node and let start offset equal offset."
5396 	var startNode = node;
5397 	var startOffset = offset;
5398 
5399 	// "Repeat the following steps:"
5400 	while (true) {
5401 		// "If start node has a child in the same editing host with index start
5402 		// offset minus one, set start node to that child, then set start
5403 		// offset to start node's length."
5404 		if (0 <= startOffset - 1
5405 		&& inSameEditingHost(startNode, startNode.childNodes[startOffset - 1])) {
5406 			startNode = startNode.childNodes[startOffset - 1];
5407 			startOffset = getNodeLength(startNode);
5408 
5409 		// "Otherwise, if start offset is zero and start node does not follow a
5410 		// line break and start node's parent is in the same editing host, set
5411 		// start offset to start node's index, then set start node to its
5412 		// parent."
5413 		} else if (startOffset == 0
5414 		&& !followsLineBreak(startNode)
5415 		&& inSameEditingHost(startNode, startNode.parentNode)) {
5416 			startOffset = getNodeIndex(startNode);
5417 			startNode = startNode.parentNode;
5418 
5419 		// "Otherwise, if start node is a Text node and its parent's resolved
5420 		// value for "white-space" is neither "pre" nor "pre-wrap" and start
5421 		// offset is not zero and the (start offset − 1)st element of start
5422 		// node's data is a space (0x0020) or non-breaking space (0x00A0),
5423 		// subtract one from start offset."
5424 		} else if (startNode.nodeType == $_.Node.TEXT_NODE
5425 		&& $_(["pre", "pre-wrap"]).indexOf($_.getComputedStyle(startNode.parentNode).whiteSpace) == -1
5426 		&& startOffset != 0
5427 		&& /[ \xa0]/.test(startNode.data[startOffset - 1])) {
5428 			startOffset--;
5429 
5430 		// "Otherwise, break from this loop."
5431 		} else {
5432 			break;
5433 		}
5434 	}
5435 
5436 	// "Let end node equal start node and end offset equal start offset."
5437 	var endNode = startNode;
5438 	var endOffset = startOffset;
5439 
5440 	// "Let length equal zero."
5441 	var length = 0;
5442 
5443 	// "Let follows space be false."
5444 	var followsSpace = false;
5445 
5446 	// "Repeat the following steps:"
5447 	while (true) {
5448 		// "If end node has a child in the same editing host with index end
5449 		// offset, set end node to that child, then set end offset to zero."
5450 		if (endOffset < endNode.childNodes.length
5451 		&& inSameEditingHost(endNode, endNode.childNodes[endOffset])) {
5452 			endNode = endNode.childNodes[endOffset];
5453 			endOffset = 0;
5454 
5455 		// "Otherwise, if end offset is end node's length and end node does not
5456 		// precede a line break and end node's parent is in the same editing
5457 		// host, set end offset to one plus end node's index, then set end node
5458 		// to its parent."
5459 		} else if (endOffset == getNodeLength(endNode)
5460 		&& !precedesLineBreak(endNode)
5461 		&& inSameEditingHost(endNode, endNode.parentNode)) {
5462 			endOffset = 1 + getNodeIndex(endNode);
5463 			endNode = endNode.parentNode;
5464 
5465 		// "Otherwise, if end node is a Text node and its parent's resolved
5466 		// value for "white-space" is neither "pre" nor "pre-wrap" and end
5467 		// offset is not end node's length and the end offsetth element of
5468 		// end node's data is a space (0x0020) or non-breaking space (0x00A0):"
5469 		} else if (endNode.nodeType == $_.Node.TEXT_NODE
5470 		&& $_(["pre", "pre-wrap"]).indexOf($_.getComputedStyle(endNode.parentNode).whiteSpace) == -1
5471 		&& endOffset != getNodeLength(endNode)
5472 		&& /[ \xa0]/.test(endNode.data[endOffset])) {
5473 			// "If follows space is true and the end offsetth element of end
5474 			// node's data is a space (0x0020), call deleteData(end offset, 1)
5475 			// on end node, then continue this loop from the beginning."
5476 			if (followsSpace
5477 			&& " " == endNode.data[endOffset]) {
5478 				endNode.deleteData(endOffset, 1);
5479 				continue;
5480 			}
5481 
5482 			// "Set follows space to true if the end offsetth element of end
5483 			// node's data is a space (0x0020), false otherwise."
5484 			followsSpace = " " == endNode.data[endOffset];
5485 
5486 			// "Add one to end offset."
5487 			endOffset++;
5488 
5489 			// "Add one to length."
5490 			length++;
5491 
5492 		// "Otherwise, break from this loop."
5493 		} else {
5494 			break;
5495 		}
5496 	}
5497 
5498 	// "Let replacement whitespace be the canonical space sequence of length
5499 	// length. non-breaking start is true if start offset is zero and start
5500 	// node follows a line break, and false otherwise. non-breaking end is true
5501 	// if end offset is end node's length and end node precedes a line break,
5502 	// and false otherwise."
5503 	var replacementWhitespace = canonicalSpaceSequence(length,
5504 		startOffset == 0 && followsLineBreak(startNode),
5505 		endOffset == getNodeLength(endNode) && precedesLineBreak(endNode));
5506 
5507 	// "While (start node, start offset) is before (end node, end offset):"
5508 	while (getPosition(startNode, startOffset, endNode, endOffset) == "before") {
5509 		// "If start node has a child with index start offset, set start node
5510 		// to that child, then set start offset to zero."
5511 		if (startOffset < startNode.childNodes.length) {
5512 			startNode = startNode.childNodes[startOffset];
5513 			startOffset = 0;
5514 
5515 		// "Otherwise, if start node is not a Text node or if start offset is
5516 		// start node's length, set start offset to one plus start node's
5517 		// index, then set start node to its parent."
5518 		} else if (startNode.nodeType != $_.Node.TEXT_NODE
5519 		|| startOffset == getNodeLength(startNode)) {
5520 			startOffset = 1 + getNodeIndex(startNode);
5521 			startNode = startNode.parentNode;
5522 
5523 		// "Otherwise:"
5524 		} else {
5525 			// "Remove the first element from replacement whitespace, and let
5526 			// element be that element."
5527 			var element = replacementWhitespace[0];
5528 			replacementWhitespace = replacementWhitespace.slice(1);
5529 
5530 			// "If element is not the same as the start offsetth element of
5531 			// start node's data:"
5532 			if (element != startNode.data[startOffset]) {
5533 				// "Call insertData(start offset, element) on start node."
5534 				startNode.insertData(startOffset, element);
5535 
5536 				// "Call deleteData(start offset + 1, 1) on start node."
5537 				startNode.deleteData(startOffset + 1, 1);
5538 			}
5539 
5540 			// "Add one to start offset."
5541 			startOffset++;
5542 		}
5543 	}
5544 }
5545 
5546 
5547 //@}
5548 ///// Indenting and outdenting /////
5549 //@{
5550 
5551 function indentNodes(nodeList, range) {
5552 	// "If node list is empty, do nothing and abort these steps."
5553 	if (!nodeList.length) {
5554 		return;
5555 	}
5556 
5557 	// "Let first node be the first member of node list."
5558 	var firstNode = nodeList[0];
5559 
5560 	// "If first node's parent is an ol or ul:"
5561 	if (isHtmlElement(firstNode.parentNode, ["OL", "UL"])) {
5562 		// "Let tag be the local name of the parent of first node."
5563 		var tag = firstNode.parentNode.tagName;
5564 
5565 		// "Wrap node list, with sibling criteria returning true for an HTML
5566 		// element with local name tag and false otherwise, and new parent
5567 		// instructions returning the result of calling createElement(tag) on
5568 		// the ownerDocument of first node."
5569 		wrap(nodeList,
5570 			function(node) { return isHtmlElement(node, tag) },
5571 			function() { return firstNode.ownerDocument.createElement(tag) },
5572 			range
5573 		);
5574 
5575 		// "Abort these steps."
5576 		return;
5577 	}
5578 
5579 	// "Wrap node list, with sibling criteria returning true for a simple
5580 	// indentation element and false otherwise, and new parent instructions
5581 	// returning the result of calling createElement("blockquote") on the
5582 	// ownerDocument of first node. Let new parent be the result."
5583 	var newParent = wrap(nodeList,
5584 		function(node) { return isSimpleIndentationElement(node) },
5585 		function() { return firstNode.ownerDocument.createElement("blockquote") },
5586 		range
5587 	);
5588 
5589 	// "Fix disallowed ancestors of new parent."
5590 	fixDisallowedAncestors(newParent, range);
5591 }
5592 
5593 function outdentNode(node, range) {
5594 	// "If node is not editable, abort these steps."
5595 	if (!isEditable(node)) {
5596 		return;
5597 	}
5598 
5599 	// "If node is a simple indentation element, remove node, preserving its
5600 	// descendants.  Then abort these steps."
5601 	if (isSimpleIndentationElement(node)) {
5602 		removePreservingDescendants(node, range);
5603 		return;
5604 	}
5605 
5606 	// "If node is an indentation element:"
5607 	if (isIndentationElement(node)) {
5608 		// "Unset the class and dir attributes of node, if any."
5609 		node.removeAttribute("class");
5610 		node.removeAttribute("dir");
5611 
5612 		// "Unset the margin, padding, and border CSS properties of node."
5613 		node.style.margin = "";
5614 		node.style.padding = "";
5615 		node.style.border = "";
5616 		if (node.getAttribute("style") == "") {
5617 			node.removeAttribute("style");
5618 		}
5619 
5620 		// "Set the tag name of node to "div"."
5621 		setTagName(node, "div", range);
5622 
5623 		// "Abort these steps."
5624 		return;
5625 	}
5626 
5627 	// "Let current ancestor be node's parent."
5628 	var currentAncestor = node.parentNode;
5629 
5630 	// "Let ancestor list be a list of nodes, initially empty."
5631 	var ancestorList = [];
5632 
5633 	// "While current ancestor is an editable Element that is neither a simple
5634 	// indentation element nor an ol nor a ul, append current ancestor to
5635 	// ancestor list and then set current ancestor to its parent."
5636 	while (isEditable(currentAncestor)
5637 	&& currentAncestor.nodeType == $_.Node.ELEMENT_NODE
5638 	&& !isSimpleIndentationElement(currentAncestor)
5639 	&& !isHtmlElement(currentAncestor, ["ol", "ul"])) {
5640 		ancestorList.push(currentAncestor);
5641 		currentAncestor = currentAncestor.parentNode;
5642 	}
5643 
5644 	// "If current ancestor is not an editable simple indentation element:"
5645 	if (!isEditable(currentAncestor)
5646 	|| !isSimpleIndentationElement(currentAncestor)) {
5647 		// "Let current ancestor be node's parent."
5648 		currentAncestor = node.parentNode;
5649 
5650 		// "Let ancestor list be the empty list."
5651 		ancestorList = [];
5652 
5653 		// "While current ancestor is an editable Element that is neither an
5654 		// indentation element nor an ol nor a ul, append current ancestor to
5655 		// ancestor list and then set current ancestor to its parent."
5656 		while (isEditable(currentAncestor)
5657 		&& currentAncestor.nodeType == $_.Node.ELEMENT_NODE
5658 		&& !isIndentationElement(currentAncestor)
5659 		&& !isHtmlElement(currentAncestor, ["ol", "ul"])) {
5660 			ancestorList.push(currentAncestor);
5661 			currentAncestor = currentAncestor.parentNode;
5662 		}
5663 	}
5664 
5665 	// "If node is an ol or ul and current ancestor is not an editable
5666 	// indentation element:"
5667 	if (isHtmlElement(node, ["OL", "UL"])
5668 	&& (!isEditable(currentAncestor)
5669 	|| !isIndentationElement(currentAncestor))) {
5670 		// "Unset the reversed, start, and type attributes of node, if any are
5671 		// set."
5672 		node.removeAttribute("reversed");
5673 		node.removeAttribute("start");
5674 		node.removeAttribute("type");
5675 
5676 		// "Let children be the children of node."
5677 		var children = [].slice.call(toArray(node.childNodes));
5678 
5679 		// "If node has attributes, and its parent is not an ol or ul, set the
5680 		// tag name of node to "div"."
5681 		if (node.attributes.length
5682 		&& !isHtmlElement(node.parentNode, ["OL", "UL"])) {
5683 			setTagName(node, "div", range);
5684 
5685 		// "Otherwise:"
5686 		} else {
5687 			// "Record the values of node's children, and let values be the
5688 			// result."
5689 			var values = recordValues([].slice.call(toArray(node.childNodes)));
5690 
5691 			// "Remove node, preserving its descendants."
5692 			removePreservingDescendants(node, range);
5693 
5694 			// "Restore the values from values."
5695 			restoreValues(values, range);
5696 		}
5697 
5698 		// "Fix disallowed ancestors of each member of children."
5699 		for (var i = 0; i < children.length; i++) {
5700 			fixDisallowedAncestors(children[i], range);
5701 		}
5702 
5703 		// "Abort these steps."
5704 		return;
5705 	}
5706 
5707 	// "If current ancestor is not an editable indentation element, abort these
5708 	// steps."
5709 	if (!isEditable(currentAncestor)
5710 	|| !isIndentationElement(currentAncestor)) {
5711 		return;
5712 	}
5713 
5714 	// "Append current ancestor to ancestor list."
5715 	ancestorList.push(currentAncestor);
5716 
5717 	// "Let original ancestor be current ancestor."
5718 	var originalAncestor = currentAncestor;
5719 
5720 	// "While ancestor list is not empty:"
5721 	while (ancestorList.length) {
5722 		// "Let current ancestor be the last member of ancestor list."
5723 		//
5724 		// "Remove the last member of ancestor list."
5725 		currentAncestor = ancestorList.pop();
5726 
5727 		// "Let target be the child of current ancestor that is equal to either
5728 		// node or the last member of ancestor list."
5729 		var target = node.parentNode == currentAncestor
5730 			? node
5731 			: ancestorList[ancestorList.length - 1];
5732 
5733 		// "If target is an inline node that is not a br, and its nextSibling
5734 		// is a br, remove target's nextSibling from its parent."
5735 		if (isInlineNode(target)
5736 		&& !isHtmlElement(target, "BR")
5737 5738 		&& isHtmlElement(target.nextSibling, "BR")) {
5739 			target.parentNode.removeChild(target.nextSibling);
5740 		}
5741 
5742 		// "Let preceding siblings be the preceding siblings of target, and let
5743 		// following siblings be the following siblings of target."
5744 		var precedingSiblings = [].slice.call(toArray(currentAncestor.childNodes), 0, getNodeIndex(target));
5745 		var followingSiblings = [].slice.call(toArray(currentAncestor.childNodes), 1 + getNodeIndex(target));
5746 
5747 		// "Indent preceding siblings."
5748 		indentNodes(precedingSiblings, range);
5749 
5750 		// "Indent following siblings."
5751 		indentNodes(followingSiblings, range);
5752 	}
5753 
5754 	// "Outdent original ancestor."
5755 	outdentNode(originalAncestor, range);
5756 }
5757 
5758 
5759 //@}
5760 ///// Toggling lists /////
5761 //@{
5762 
5763 function toggleLists(tagName, range) {
5764 	// "Let mode be "disable" if the selection's list state is tag name, and
5765 	// "enable" otherwise."
5766 	var mode = getSelectionListState() == tagName ? "disable" : "enable";
5767 
5768 	tagName = tagName.toUpperCase();
5769 
5770 	// "Let other tag name be "ol" if tag name is "ul", and "ul" if tag name is
5771 	// "ol"."
5772 	var otherTagName = tagName == "OL" ? "UL" : "OL";
5773 
5774 	// "Let items be a list of all lis that are ancestor containers of the
5775 	// range's start and/or end node."
5776 	//
5777 	// It's annoying to get this in tree order using functional stuff without
5778 	// doing getDescendants(document), which is slow, so I do it imperatively.
5779 	var items = [];
5780 	(function(){
5781 		for (
5782 			var ancestorContainer = range.endContainer;
5783 			ancestorContainer != range.commonAncestorContainer;
5784 			ancestorContainer = ancestorContainer.parentNode
5785 		) {
5786 			if (isHtmlElement(ancestorContainer, "li")) {
5787 				items.unshift(ancestorContainer);
5788 			}
5789 		}
5790 		for (
5791 			var ancestorContainer = range.startContainer;
5792 			ancestorContainer;
5793 			ancestorContainer = ancestorContainer.parentNode
5794 		) {
5795 			if (isHtmlElement(ancestorContainer, "li")) {
5796 				items.unshift(ancestorContainer);
5797 			}
5798 		}
5799 	})();
5800 
5801 	// "For each item in items, normalize sublists of item."
5802 	$_( items ).forEach( function( thisArg ) {
5803 			normalizeSublists( thisArg, range);
5804 	});
5805 
5806 	// "Block-extend the range, and let new range be the result."
5807 	var newRange = blockExtend(range);
5808 
5809 	// "If mode is "enable", then let lists to convert consist of every
5810 	// editable HTML element with local name other tag name that is contained
5811 	// in new range, and for every list in lists to convert:"
5812 	if (mode == "enable") {
5813 		$_( getAllContainedNodes(newRange, function(node) {
5814 			return isEditable(node)
5815 				&& isHtmlElement(node, otherTagName);
5816 		}) ).forEach(function(list) {
5817 			// "If list's previousSibling or nextSibling is an editable HTML
5818 			// element with local name tag name:"
5819 			if ((isEditable(list.previousSibling) && isHtmlElement(list.previousSibling, tagName))
5820 			|| (isEditable(list.nextSibling) && isHtmlElement(list.nextSibling, tagName))) {
5821 				// "Let children be list's children."
5822 				var children = [].slice.call(toArray(list.childNodes));
5823 
5824 				// "Record the values of children, and let values be the
5825 				// result."
5826 				var values = recordValues(children);
5827 
5828 				// "Split the parent of children."
5829 				splitParent(children, range);
5830 
5831 				// "Wrap children, with sibling criteria returning true for an
5832 				// HTML element with local name tag name and false otherwise."
5833 				wrap(children, 
5834 					function(node) { return isHtmlElement(node, tagName) },
5835 					function() {return null },
5836 					range
5837 				);
5838 
5839 				// "Restore the values from values."
5840 5841 				restoreValues(values, range);
5842 
5843 			// "Otherwise, set the tag name of list to tag name."
5844 			} else {
5845 				setTagName(list, tagName, range);
5846 			}
5847 		});
5848 	}
5849 
5850 	// "Let node list be a list of nodes, initially empty."
5851 	//
5852 	// "For each node node contained in new range, if node is editable; the
5853 	// last member of node list (if any) is not an ancestor of node; node
5854 	// is not an indentation element; and either node is an ol or ul, or its
5855 	// parent is an ol or ul, or it is an allowed child of "li"; then append
5856 	// node to node list."
5857 	var nodeList = getContainedNodes(newRange, function(node) {
5858 		return isEditable(node)
5859 		&& !isIndentationElement(node)
5860 		&& (isHtmlElement(node, ["OL", "UL"])
5861 		|| isHtmlElement(node.parentNode, ["OL", "UL"])
5862 		|| isAllowedChild(node, "li"));
5863 	});
5864 
5865 	// "If mode is "enable", remove from node list any ol or ul whose parent is
5866 	// not also an ol or ul."
5867 	if (mode == "enable") {
5868 		nodeList = $_( nodeList ).filter(function(node) {
5869 			return !isHtmlElement(node, ["ol", "ul"])
5870 				|| isHtmlElement(node.parentNode, ["ol", "ul"]);
5871 		});
5872 	}
5873 
5874 	// "If mode is "disable", then while node list is not empty:"
5875 	if (mode == "disable") {
5876 		while (nodeList.length) {
5877 			// "Let sublist be an empty list of nodes."
5878 			var sublist = [];
5879 
5880 			// "Remove the first member from node list and append it to
5881 			// sublist."
5882 			sublist.push(nodeList.shift());
5883 
5884 			// "If the first member of sublist is an HTML element with local
5885 			// name tag name, outdent it and continue this loop from the
5886 			// beginning."
5887 			if (isHtmlElement(sublist[0], tagName)) {
5888 				outdentNode(sublist[0], range);
5889 				continue;
5890 			}
5891 
5892 			// "While node list is not empty, and the first member of node list
5893 			// is the nextSibling of the last member of sublist and is not an
5894 			// HTML element with local name tag name, remove the first member
5895 			// from node list and append it to sublist."
5896 			while (nodeList.length
5897 			&& nodeList[0] == sublist[sublist.length - 1].nextSibling
5898 			&& !isHtmlElement(nodeList[0], tagName)) {
5899 				sublist.push(nodeList.shift());
5900 			}
5901 
5902 			// "Record the values of sublist, and let values be the result."
5903 			var values = recordValues(sublist);
5904 
5905 			// "Split the parent of sublist."
5906 			splitParent(sublist, range);
5907 
5908 			// "Fix disallowed ancestors of each member of sublist."
5909 			for (var i = 0; i < sublist.length; i++) {
5910 				fixDisallowedAncestors(sublist[i], range);
5911 			}
5912 
5913 			// "Restore the values from values."
5914 			restoreValues(values, range);
5915 		}
5916 
5917 	// "Otherwise, while node list is not empty:"
5918 	} else {
5919 		while (nodeList.length) {
5920 			// "Let sublist be an empty list of nodes."
5921 			var sublist = [];
5922 
5923 			// "While either sublist is empty, or node list is not empty and
5924 			// its first member is the nextSibling of sublist's last member:"
5925 			while (!sublist.length
5926 			|| (nodeList.length
5927 			&& nodeList[0] == sublist[sublist.length - 1].nextSibling)) {
5928 				// "If node list's first member is a p or div, set the tag name
5929 				// of node list's first member to "li", and append the result
5930 				// to sublist. Remove the first member from node list."
5931 				if (isHtmlElement(nodeList[0], ["p", "div"])) {
5932 					sublist.push(setTagName(nodeList[0], "li", range));
5933 					nodeList.shift();
5934 
5935 				// "Otherwise, if the first member of node list is an li or ol
5936 				// or ul, remove it from node list and append it to sublist."
5937 				} else if (isHtmlElement(nodeList[0], ["li", "ol", "ul"])) {
5938 					sublist.push(nodeList.shift());
5939 
5940 				// "Otherwise:"
5941 				} else {
5942 					// "Let nodes to wrap be a list of nodes, initially empty."
5943 					var nodesToWrap = [];
5944 
5945 					// "While nodes to wrap is empty, or node list is not empty
5946 					// and its first member is the nextSibling of nodes to
5947 					// wrap's last member and the first member of node list is
5948 					// an inline node and the last member of nodes to wrap is
5949 					// an inline node other than a br, remove the first member
5950 					// from node list and append it to nodes to wrap."
5951 					while (!nodesToWrap.length
5952 					|| (nodeList.length
5953 					&& nodeList[0] == nodesToWrap[nodesToWrap.length - 1].nextSibling
5954 					&& isInlineNode(nodeList[0])
5955 					&& isInlineNode(nodesToWrap[nodesToWrap.length - 1])
5956 					&& !isHtmlElement(nodesToWrap[nodesToWrap.length - 1], "br"))) {
5957 						nodesToWrap.push(nodeList.shift());
5958 					}
5959 
5960 					// "Wrap nodes to wrap, with new parent instructions
5961 					// returning the result of calling createElement("li") on
5962 					// the context object. Append the result to sublist."
5963 					sublist.push(
5964 						wrap(nodesToWrap,
5965 							undefined,
5966 							function() { return document.createElement("li") },
5967 							range
5968 						)
5969 					);
5970 				}
5971 			}
5972 
5973 			// "If sublist's first member's parent is an HTML element with
5974 			// local name tag name, or if every member of sublist is an ol or
5975 			// ul, continue this loop from the beginning."
5976 			if (isHtmlElement(sublist[0].parentNode, tagName)
5977 			|| $_(sublist).every(function(node) { return isHtmlElement(node, ["ol", "ul"]) })) {
5978 				continue;
5979 			}
5980 
5981 			// "If sublist's first member's parent is an HTML element with
5982 			// local name other tag name:"
5983 			if (isHtmlElement(sublist[0].parentNode, otherTagName)) {
5984 				// "Record the values of sublist, and let values be the
5985 				// result."
5986 				var values = recordValues(sublist);
5987 
5988 				// "Split the parent of sublist."
5989 				splitParent(sublist, range);
5990 
5991 				// "Wrap sublist, with sibling criteria returning true for an
5992 				// HTML element with local name tag name and false otherwise,
5993 				// and new parent instructions returning the result of calling
5994 				// createElement(tag name) on the context object."
5995 				wrap(sublist,
5996 					function(node) { return isHtmlElement(node, tagName) },
5997 					function() { return document.createElement(tagName) },
5998 					range
5999 				);
6000 
6001 				// "Restore the values from values."
6002 				restoreValues(values, range);
6003 
6004 				// "Continue this loop from the beginning."
6005 				continue;
6006 			}
6007 
6008 			// "Wrap sublist, with sibling criteria returning true for an HTML
6009 			// element with local name tag name and false otherwise, and new
6010 			// parent instructions being the following:"
6011 			// . . .
6012 			// "Fix disallowed ancestors of the previous step's result."
6013 			fixDisallowedAncestors(
6014 				wrap(sublist,
6015 					function(node) { return isHtmlElement(node, tagName) },
6016 					function() {
6017 						// "If sublist's first member's parent is not an editable
6018 						// simple indentation element, or sublist's first member's
6019 						// parent's previousSibling is not an editable HTML element
6020 						// with local name tag name, call createElement(tag name)
6021 						// on the context object and return the result."
6022 						if (!isEditable(sublist[0].parentNode)
6023 						|| !isSimpleIndentationElement(sublist[0].parentNode)
6024 						|| !isEditable(sublist[0].parentNode.previousSibling)
6025 						|| !isHtmlElement(sublist[0].parentNode.previousSibling, tagName)) {
6026 							return document.createElement(tagName);
6027 						}
6028 	
6029 						// "Let list be sublist's first member's parent's
6030 						// previousSibling."
6031 						var list = sublist[0].parentNode.previousSibling;
6032 	
6033 						// "Normalize sublists of list's lastChild."
6034 						normalizeSublists(list.lastChild, range);
6035 	
6036 						// "If list's lastChild is not an editable HTML element
6037 						// with local name tag name, call createElement(tag name)
6038 						// on the context object, and append the result as the last
6039 						// child of list."
6040 						if (!isEditable(list.lastChild)
6041 						|| !isHtmlElement(list.lastChild, tagName)) {
6042 							list.appendChild(document.createElement(tagName));
6043 						}
6044 	
6045 						// "Return the last child of list."
6046 						return list.lastChild;
6047 					},
6048 					range
6049 				)
6050 				, range
6051 			);
6052 		}
6053 	}
6054 }
6055 
6056 
6057 //@}
6058 ///// Justifying the selection /////
6059 //@{
6060 
6061 function justifySelection(alignment, range) {
6062 	
6063 	// "Block-extend the active range, and let new range be the result."
6064 	var newRange = blockExtend(range);
6065 
6066 	// "Let element list be a list of all editable Elements contained in new
6067 	// range that either has an attribute in the HTML namespace whose local
6068 	// name is "align", or has a style attribute that sets "text-align", or is
6069 	// a center."
6070 	var elementList = getAllContainedNodes(newRange, function(node) {
6071 		return node.nodeType == $_.Node.ELEMENT_NODE
6072 			&& isEditable(node)
6073 			// Ignoring namespaces here
6074 			&& (
6075 				$_( node ).hasAttribute("align")
6076 				|| node.style.textAlign != ""
6077 				|| isHtmlElement(node, "center")
6078 			);
6079 	});
6080 
6081 	// "For each element in element list:"
6082 	for (var i = 0; i < elementList.length; i++) {
6083 		var element = elementList[i];
6084 
6085 		// "If element has an attribute in the HTML namespace whose local name
6086 		// is "align", remove that attribute."
6087 		element.removeAttribute("align");
6088 
6089 		// "Unset the CSS property "text-align" on element, if it's set by a
6090 		// style attribute."
6091 		element.style.textAlign = "";
6092 		if (element.getAttribute("style") == "") {
6093 			element.removeAttribute("style");
6094 		}
6095 
6096 		// "If element is a div or span or center with no attributes, remove
6097 		// it, preserving its descendants."
6098 		if (isHtmlElement(element, ["div", "span", "center"])
6099 		&& !element.attributes.length) {
6100 			removePreservingDescendants(element, range);
6101 		}
6102 
6103 		// "If element is a center with one or more attributes, set the tag
6104 		// name of element to "div"."
6105 		if (isHtmlElement(element, "center")
6106 		&& element.attributes.length) {
6107 			setTagName(element, "div", range);
6108 		}
6109 	}
6110 
6111 	// "Block-extend the active range, and let new range be the result."
6112 	newRange = blockExtend(globalRange);
6113 
6114 	// "Let node list be a list of nodes, initially empty."
6115 	var nodeList = [];
6116 
6117 	// "For each node node contained in new range, append node to node list if
6118 	// the last member of node list (if any) is not an ancestor of node; node
6119 	// is editable; node is an allowed child of "div"; and node's alignment
6120 	// value is not alignment."
6121 	nodeList = getContainedNodes(newRange, function(node) {
6122 		return isEditable(node)
6123 			&& isAllowedChild(node, "div")
6124 			&& getAlignmentValue(node) != alignment;
6125 	});
6126 
6127 	// "While node list is not empty:"
6128 	while (nodeList.length) {
6129 		// "Let sublist be a list of nodes, initially empty."
6130 		var sublist = [];
6131 
6132 		// "Remove the first member of node list and append it to sublist."
6133 		sublist.push(nodeList.shift());
6134 
6135 		// "While node list is not empty, and the first member of node list is
6136 		// the nextSibling of the last member of sublist, remove the first
6137 		// member of node list and append it to sublist."
6138 		while (nodeList.length
6139 		&& nodeList[0] == sublist[sublist.length - 1].nextSibling) {
6140 			sublist.push(nodeList.shift());
6141 		}
6142 
6143 		// "Wrap sublist. Sibling criteria returns true for any div that has
6144 		// one or both of the following two attributes and no other attributes,
6145 		// and false otherwise:"
6146 		//
6147 		//   * "An align attribute whose value is an ASCII case-insensitive
6148 		//     match for alignment.
6149 		//   * "A style attribute which sets exactly one CSS property
6150 		//     (including unrecognized or invalid attributes), which is
6151 		//     "text-align", which is set to alignment.
6152 		//
6153 		// "New parent instructions are to call createElement("div") on the
6154 		// context object, then set its CSS property "text-align" to alignment
6155 		// and return the result."
6156 		wrap(sublist,
6157 			function(node) {
6158 				return isHtmlElement(node, "div")
6159 					&& $_(node.attributes).every(function(attr) {
6160 						return (attr.name == "align" && attr.value.toLowerCase() == alignment)
6161 							|| (attr.name == "style" && getStyleLength(node) == 1 && node.style.textAlign == alignment);
6162 					});
6163 			},
6164 			function() {
6165 				var newParent = document.createElement("div");
6166 				newParent.setAttribute("style", "text-align: " + alignment);
6167 				return newParent;
6168 			},
6169 			range
6170 		);
6171 	}
6172 }
6173 
6174 
6175 //@}
6176 ///// Create an end break /////
6177 //@{
6178 function createEndBreak() {
6179 	var endBr = document.createElement("br");
6180 	endBr.setAttribute("class", "aloha-end-br");
6181 	return endBr;
6182 }
6183 
6184 
6185 //@}
6186 ///// The delete command /////
6187 //@{
6188 commands["delete"] = {
6189 	action: function(value, range) {
6190 		// "If the active range is not collapsed, delete the contents of the
6191 		// active range and abort these steps."
6192 		if (!range.collapsed) {
6193 			deleteContents(range);
6194 			return;
6195 		}
6196 
6197 		// "Canonicalize whitespace at (active range's start node, active
6198 		// range's start offset)."
6199 		canonicalizeWhitespace(range.startContainer, range.startOffset);
6200 
6201 		// "Let node and offset be the active range's start node and offset."
6202 		var node = range.startContainer;
6203 		var offset = range.startOffset;
6204 		var isBr = false;
6205 		var isHr = false;
6206 
6207 		// "Repeat the following steps:"
6208 		while ( true ) {
6209 			// we need to reset isBr and isHr on every interation of the loop
6210 			if ( offset > 0 ) {
6211 				isBr = isHtmlElement(node.childNodes[offset - 1], "br") || false;
6212 				isHr = isHtmlElement(node.childNodes[offset - 1], "hr") || false;
6213 			}
6214 
6215 			// "If offset is zero and node's previousSibling is an editable
6216 			// invisible node, remove node's previousSibling from its parent."
6217 			if (offset == 0
6218 			&& isEditable(node.previousSibling)
6219 			&& isInvisible(node.previousSibling)) {
6220 				node.parentNode.removeChild(node.previousSibling);
6221 
6222 			// "Otherwise, if node has a child with index offset − 1 and that
6223 			// child is an editable invisible node, remove that child from
6224 			// node, then subtract one from offset."
6225 			} else if (0 <= offset - 1
6226 			&& offset - 1 < node.childNodes.length
6227 			&& isEditable(node.childNodes[offset - 1])
6228 			&& (isInvisible(node.childNodes[offset - 1]) || isBr || isHr )) {
6229 				node.removeChild(node.childNodes[offset - 1]);
6230 				offset--;
6231 				if (isBr || isHr) {
6232 					range.setStart(node, offset);
6233 					range.setEnd(node, offset);
6234 					return;
6235 				}
6236 
6237 			// "Otherwise, if offset is zero and node is an inline node, or if
6238 			// node is an invisible node, set offset to the index of node, then
6239 			// set node to its parent."
6240 			} else if ((offset == 0
6241 			&& isInlineNode(node))
6242 			|| isInvisible(node)) {
6243 				offset = getNodeIndex(node);
6244 				node = node.parentNode;
6245 
6246 			// "Otherwise, if node has a child with index offset − 1 and that
6247 			// child is an editable a, remove that child from node, preserving
6248 			// its descendants. Then abort these steps."
6249 			} else if (0 <= offset - 1
6250 			&& offset - 1 < node.childNodes.length
6251 			&& isEditable(node.childNodes[offset - 1])
6252 			&& isHtmlElement(node.childNodes[offset - 1], "a")) {
6253 				removePreservingDescendants(node.childNodes[offset - 1], range);
6254 				return;
6255 
6256 			// "Otherwise, if node has a child with index offset − 1 and that
6257 			// child is not a block node or a br or an img, set node to that
6258 			// child, then set offset to the length of node."
6259 			} else if (0 <= offset - 1
6260 			&& offset - 1 < node.childNodes.length
6261 			&& !isBlockNode(node.childNodes[offset - 1])
6262 			&& !isHtmlElement(node.childNodes[offset - 1], ["br", "img"])) {
6263 				node = node.childNodes[offset - 1];
6264 				offset = getNodeLength(node);
6265 
6266 			// "Otherwise, break from this loop."
6267 			} else {
6268 				break;
6269 			}
6270 		}
6271 
6272 		// collapse whitespace sequences
6273 		collapseWhitespace(node, range);
6274 		offset = range.startOffset;
6275 
6276 		// "If node is a Text node and offset is not zero, call collapse(node,
6277 		// offset) on the Selection. Then delete the contents of the range with
6278 		// start (node, offset − 1) and end (node, offset) and abort these
6279 		// steps."
6280 		if (node.nodeType == $_.Node.TEXT_NODE
6281 		&& offset != 0) {
6282 			range.setStart(node, offset);
6283 			range.setEnd(node, offset);
6284 			// fix range start container offset according to old code
6285 			// so we can still pass our range and have it modified, but
6286 			// also conform with the previous implementation
6287 			range.startOffset -= 1;
6288 			deleteContents(range);
6289 			return;
6290 		}
6291 
6292 		// "If node is an inline node, abort these steps."
6293 		if (isInlineNode(node)) {
6294 			return;
6295 		}
6296 
6297 		// "If node has a child with index offset − 1 and that child is a br or
6298 		// hr or img, call collapse(node, offset) on the Selection. Then delete
6299 		// the contents of the range with start (node, offset − 1) and end
6300 		// (node, offset) and abort these steps."
6301 		if (0 <= offset - 1
6302 		&& offset - 1 < node.childNodes.length
6303 		&& isHtmlElement(node.childNodes[offset - 1], ["br", "hr", "img"])) {
6304 			range.setStart(node, offset);
6305 			range.setEnd(node, offset);
6306 			deleteContents(range);
6307 			return;
6308 		}
6309 
6310 		// "If node is an li or dt or dd and is the first child of its parent,
6311 		// and offset is zero:"
6312 		if (isHtmlElement(node, ["li", "dt", "dd"])
6313 		&& node == node.parentNode.firstChild
6314 		&& offset == 0) {
6315 			// "Let items be a list of all lis that are ancestors of node."
6316 			//
6317 			// Remember, must be in tree order.
6318 			var items = [];
6319 			for (var ancestor = node.parentNode; ancestor; ancestor = ancestor.parentNode) {
6320 				if (isHtmlElement(ancestor, "li")) {
6321 					items.unshift(ancestor);
6322 				}
6323 			}
6324 
6325 			// "Normalize sublists of each item in items."
6326 			for (var i = 0; i < items.length; i++) {
6327 				normalizeSublists(items[i], range);
6328 			}
6329 
6330 			// "Record the values of the one-node list consisting of node, and
6331 			// let values be the result."
6332 			var values = recordValues([node]);
6333 
6334 			// "Split the parent of the one-node list consisting of node."
6335 			splitParent([node], range);
6336 
6337 			// "Restore the values from values."
6338 			restoreValues(values, range);
6339 
6340 			// "If node is a dd or dt, and it is not an allowed child of any of
6341 			// its ancestors in the same editing host, set the tag name of node
6342 			// to the default single-line container name and let node be the
6343 			// result."
6344 			if (isHtmlElement(node, ["dd", "dt"])
6345 			&& $_(getAncestors(node)).every(function(ancestor) {
6346 				return !inSameEditingHost(node, ancestor)
6347 					|| !isAllowedChild(node, ancestor)
6348 			})) {
6349 				node = setTagName(node, defaultSingleLineContainerName, range);
6350 			}
6351 
6352 			// "Fix disallowed ancestors of node."
6353 			fixDisallowedAncestors(node, range);
6354 
6355 			// fix the lists to be html5 conformant
6356 			for (var i = 0; i < items.length; i++) {
6357 				unNormalizeSublists(items[i].parentNode, range);
6358 			}
6359 
6360 			// "Abort these steps."
6361 			return;
6362 		}
6363 
6364 		// "Let start node equal node and let start offset equal offset."
6365 		var startNode = node;
6366 		var startOffset = offset;
6367 
6368 		// "Repeat the following steps:"
6369 		while (true) {
6370 			// "If start offset is zero, set start offset to the index of start
6371 			// node and then set start node to its parent."
6372 			if (startOffset == 0) {
6373 				startOffset = getNodeIndex(startNode);
6374 				startNode = startNode.parentNode;
6375 
6376 			// "Otherwise, if start node has an editable invisible child with
6377 			// index start offset minus one, remove it from start node and
6378 			// subtract one from start offset."
6379 			} else if (0 <= startOffset - 1
6380 			&& startOffset - 1 < startNode.childNodes.length
6381 			&& isEditable(startNode.childNodes[startOffset - 1])
6382 			&& isInvisible(startNode.childNodes[startOffset - 1])) {
6383 				startNode.removeChild(startNode.childNodes[startOffset - 1]);
6384 				startOffset--;
6385 
6386 			// "Otherwise, break from this loop."
6387 			} else {
6388 				break;
6389 			}
6390 		}
6391 
6392 		// "If offset is zero, and node has an editable ancestor container in
6393 		// the same editing host that's an indentation element:"
6394 		if (offset == 0
6395 		&& $_( getAncestors(node).concat(node) ).filter(function(ancestor) {
6396 			return isEditable(ancestor)
6397 				&& inSameEditingHost(ancestor, node)
6398 				&& isIndentationElement(ancestor);
6399 		}).length) {
6400 			// "Block-extend the range whose start and end are both (node, 0),
6401 			// and let new range be the result."
6402 			var newRange = Aloha.createRange();
6403 			newRange.setStart(node, 0);
6404 			newRange.setEnd(node, 0);
6405 			newRange = blockExtend(newRange);
6406 
6407 			// "Let node list be a list of nodes, initially empty."
6408 			//
6409 			// "For each node current node contained in new range, append
6410 			// current node to node list if the last member of node list (if
6411 			// any) is not an ancestor of current node, and current node is
6412 			// editable but has no editable descendants."
6413 			var nodeList = getContainedNodes(newRange, function(currentNode) {
6414 				return isEditable(currentNode)
6415 					&& !hasEditableDescendants(currentNode);
6416 			});
6417 
6418 			// "Outdent each node in node list."
6419 			for (var i = 0; i < nodeList.length; i++) {
6420 				outdentNode(nodeList[i], range);
6421 			}
6422 
6423 			// "Abort these steps."
6424 			return;
6425 		}
6426 
6427 		// "If the child of start node with index start offset is a table,
6428 		// abort these steps."
6429 		if (isHtmlElement(startNode.childNodes[startOffset], "table")) {
6430 			return;
6431 		}
6432 
6433 		// "If start node has a child with index start offset − 1, and that
6434 		// child is a table:"
6435 		if (0 <= startOffset - 1
6436 		&& startOffset - 1 < startNode.childNodes.length
6437 		&& isHtmlElement(startNode.childNodes[startOffset - 1], "table")) {
6438 			// "Call collapse(start node, start offset − 1) on the context
6439 			// object's Selection."
6440 			range.setStart(startNode, startOffset - 1);
6441 
6442 			// "Call extend(start node, start offset) on the context object's
6443 			// Selection."
6444 			range.setEnd(startNode, startOffset);
6445 
6446 			// "Abort these steps."
6447 			return;
6448 		}
6449 
6450 		// "If offset is zero; and either the child of start node with index
6451 		// start offset minus one is an hr, or the child is a br whose
6452 		// previousSibling is either a br or not an inline node:"
6453 		if (offset == 0
6454 		&& (isHtmlElement(startNode.childNodes[startOffset - 1], "hr")
6455 			|| (
6456 				isHtmlElement(startNode.childNodes[startOffset - 1], "br")
6457 				&& (
6458 					isHtmlElement(startNode.childNodes[startOffset - 1].previousSibling, "br")
6459 					|| !isInlineNode(startNode.childNodes[startOffset - 1].previousSibling)
6460 				)
6461 			)
6462 		)) {
6463 			// "Call collapse(node, offset) on the Selection."
6464 			range.setStart(node, offset);
6465 			range.setEnd(node, offset);
6466 
6467 			// "Delete the contents of the range with start (start node, start
6468 			// offset − 1) and end (start node, start offset)."
6469 			deleteContents(startNode, startOffset - 1, startNode, startOffset);
6470 
6471 			// "Abort these steps."
6472 			return;
6473 		}
6474 
6475 		// "If the child of start node with index start offset is an li or dt
6476 		// or dd, and that child's firstChild is an inline node, and start
6477 		// offset is not zero:"
6478 		if (isHtmlElement(startNode.childNodes[startOffset], ["li", "dt", "dd"])
6479 		&& isInlineNode(startNode.childNodes[startOffset].firstChild)
6480 		&& startOffset != 0) {
6481 			// "Let previous item be the child of start node with index start
6482 			// offset minus one."
6483 			var previousItem = startNode.childNodes[startOffset - 1];
6484 
6485 			// "If previous item's lastChild is an inline node other than a br,
6486 			// call createElement("br") on the context object and append the
6487 			// result as the last child of previous item."
6488 			if (isInlineNode(previousItem.lastChild)
6489 			&& !isHtmlElement(previousItem.lastChild, "br")) {
6490 				previousItem.appendChild(document.createElement("br"));
6491 			}
6492 
6493 			// "If previous item's lastChild is an inline node, call
6494 			// createElement("br") on the context object and append the result
6495 			// as the last child of previous item."
6496 			if (isInlineNode(previousItem.lastChild)) {
6497 				previousItem.appendChild(document.createElement("br"));
6498 			}
6499 		}
6500 
6501 		// "If the child of start node with index start offset is an li or dt
6502 		// or dd, and its previousSibling is also an li or dt or dd, set start
6503 		// node to its child with index start offset − 1, then set start offset
6504 		// to start node's length, then set node to start node's nextSibling,
6505 		// then set offset to 0."
6506 		if (isHtmlElement(startNode.childNodes[startOffset], ["li", "dt", "dd"])
6507 		&& isHtmlElement(startNode.childNodes[startOffset - 1], ["li", "dt", "dd"])) {
6508 			startNode = startNode.childNodes[startOffset - 1];
6509 			startOffset = getNodeLength(startNode);
6510 			node = startNode.nextSibling;
6511 			offset = 0;
6512 
6513 		// "Otherwise, while start node has a child with index start offset
6514 		// minus one:"
6515 		} else {
6516 			while (0 <= startOffset - 1
6517 			&& startOffset - 1 < startNode.childNodes.length) {
6518 				// "If start node's child with index start offset minus one is
6519 				// editable and invisible, remove it from start node, then
6520 				// subtract one from start offset."
6521 				if (isEditable(startNode.childNodes[startOffset - 1])
6522 				&& isInvisible(startNode.childNodes[startOffset - 1])) {
6523 					startNode.removeChild(startNode.childNodes[startOffset - 1]);
6524 					startOffset--;
6525 
6526 				// "Otherwise, set start node to its child with index start
6527 				// offset minus one, then set start offset to the length of
6528 				// start node."
6529 				} else {
6530 					startNode = startNode.childNodes[startOffset - 1];
6531 					startOffset = getNodeLength(startNode);
6532 				}
6533 			}
6534 		}
6535 
6536 		// "Delete the contents of the range with start (start node, start
6537 		// offset) and end (node, offset)."
6538 		var delRange = Aloha.createRange();
6539 		delRange.setStart(startNode, startOffset);
6540 		delRange.setEnd(node, offset);
6541 		deleteContents(delRange);
6542 
6543 		if (!isAncestorContainer(document.body, range.startContainer)) {
6544 			if (delRange.startContainer.hasChildNodes() || delRange.startContainer.nodeType == $_.Node.TEXT_NODE) {
6545 				range.setStart(delRange.startContainer, delRange.startOffset);
6546 				range.setEnd(delRange.startContainer, delRange.startOffset);
6547 			} else {
6548 				range.setStart(delRange.startContainer.parentNode, getNodeIndex(delRange.startContainer));
6549 				range.setEnd(delRange.startContainer.parentNode, getNodeIndex(delRange.startContainer));
6550 			}
6551 		}
6552 	}
6553 };
6554 
6555 //@}
6556 ///// The formatBlock command /////
6557 //@{
6558 // "A formattable block name is "address", "dd", "div", "dt", "h1", "h2", "h3",
6559 // "h4", "h5", "h6", "p", or "pre"."
6560 var formattableBlockNames = ["address", "dd", "div", "dt", "h1", "h2", "h3",
6561 	"h4", "h5", "h6", "p", "pre"];
6562 
6563 commands.formatblock = {
6564 	action: function(value) {
6565 		// "If value begins with a "<" character and ends with a ">" character,
6566 		// remove the first and last characters from it."
6567 		if (/^<.*>$/.test(value)) {
6568 			value = value.slice(1, -1);
6569 		}
6570 
6571 		// "Let value be converted to ASCII lowercase."
6572 		value = value.toLowerCase();
6573 
6574 		// "If value is not a formattable block name, abort these steps and do
6575 		// nothing."
6576 		if ($_(formattableBlockNames).indexOf(value) == -1) {
6577 			return;
6578 		}
6579 
6580 		// "Block-extend the active range, and let new range be the result."
6581 		var newRange = blockExtend(getActiveRange());
6582 
6583 		// "Let node list be an empty list of nodes."
6584 		//
6585 		// "For each node node contained in new range, append node to node list
6586 		// if it is editable, the last member of original node list (if any) is
6587 		// not an ancestor of node, node is either a non-list single-line
6588 		// container or an allowed child of "p" or a dd or dt, and node is not
6589 		// the ancestor of a prohibited paragraph child."
6590 		var nodeList = getContainedNodes(newRange, function(node) {
6591 			return isEditable(node)
6592 				&& (isNonListSingleLineContainer(node)
6593 				|| isAllowedChild(node, "p")
6594 				|| isHtmlElement(node, ["dd", "dt"]))
6595 				&& !$_( getDescendants(node) ).some(isProhibitedParagraphChild);
6596 		});
6597 
6598 		// "Record the values of node list, and let values be the result."
6599 		var values = recordValues(nodeList);
6600 
6601 		// "For each node in node list, while node is the descendant of an
6602 		// editable HTML element in the same editing host, whose local name is
6603 		// a formattable block name, and which is not the ancestor of a
6604 		// prohibited paragraph child, split the parent of the one-node list
6605 		// consisting of node."
6606 		for (var i = 0; i < nodeList.length; i++) {
6607 			var node = nodeList[i];
6608 			while ($_( getAncestors(node) ).some(function(ancestor) {
6609 				return isEditable(ancestor)
6610 					&& inSameEditingHost(ancestor, node)
6611 					&& isHtmlElement(ancestor, formattableBlockNames)
6612 					&& !$_( getDescendants(ancestor) ).some(isProhibitedParagraphChild);
6613 			})) {
6614 				splitParent([node], range);
6615 			}
6616 		}
6617 
6618 		// "Restore the values from values."
6619 		restoreValues(values, range);
6620 
6621 		// "While node list is not empty:"
6622 		while (nodeList.length) {
6623 			var sublist;
6624 
6625 			// "If the first member of node list is a single-line
6626 			// container:"
6627 			if (isSingleLineContainer(nodeList[0])) {
6628 				// "Let sublist be the children of the first member of node
6629 				// list."
6630 				sublist = [].slice.call(toArray(nodeList[0].childNodes));
6631 
6632 				// "Record the values of sublist, and let values be the
6633 				// result."
6634 				var values = recordValues(sublist);
6635 
6636 				// "Remove the first member of node list from its parent,
6637 				// preserving its descendants."
6638 				removePreservingDescendants(nodeList[0], range);
6639 
6640 				// "Restore the values from values."
6641 				restoreValues(values, range);
6642 
6643 				// "Remove the first member from node list."
6644 				nodeList.shift();
6645 
6646 			// "Otherwise:"
6647 			} else {
6648 				// "Let sublist be an empty list of nodes."
6649 				sublist = [];
6650 
6651 				// "Remove the first member of node list and append it to
6652 				// sublist."
6653 				sublist.push(nodeList.shift());
6654 
6655 				// "While node list is not empty, and the first member of
6656 				// node list is the nextSibling of the last member of
6657 				// sublist, and the first member of node list is not a
6658 				// single-line container, and the last member of sublist is
6659 				// not a br, remove the first member of node list and
6660 				// append it to sublist."
6661 				while (nodeList.length
6662 				&& nodeList[0] == sublist[sublist.length - 1].nextSibling
6663 				&& !isSingleLineContainer(nodeList[0])
6664 				&& !isHtmlElement(sublist[sublist.length - 1], "BR")) {
6665 					sublist.push(nodeList.shift());
6666 				}
6667 			}
6668 
6669 			// "Wrap sublist. If value is "div" or "p", sibling criteria
6670 			// returns false; otherwise it returns true for an HTML element
6671 			// with local name value and no attributes, and false otherwise.
6672 			// New parent instructions return the result of running
6673 			// createElement(value) on the context object. Then fix disallowed
6674 			// ancestors of the result."
6675 			fixDisallowedAncestors(
6676 				wrap(sublist,
6677 					$_(["div", "p"]).indexOf(value) == - 1
6678 						? function(node) { return isHtmlElement(node, value) && !node.attributes.length }
6679 						: function() { return false },
6680 					function() { return document.createElement(value) },
6681 					range
6682 				),
6683 				range
6684 			);
6685 		}
6686 	}, indeterm: function() {
6687 		// "Block-extend the active range, and let new range be the result."
6688 		var newRange = blockExtend(getActiveRange());
6689 
6690 		// "Let node list be all visible editable nodes that are contained in
6691 		// new range and have no children."
6692 		var nodeList = getAllContainedNodes(newRange, function(node) {
6693 			return isVisible(node)
6694 				&& isEditable(node)
6695 				&& !node.hasChildNodes();
6696 		});
6697 
6698 		// "If node list is empty, return false."
6699 		if (!nodeList.length) {
6700 			return false;
6701 		}
6702 
6703 		// "Let type be null."
6704 		var type = null;
6705 
6706 		// "For each node in node list:"
6707 		for (var i = 0; i < nodeList.length; i++) {
6708 			var node = nodeList[i];
6709 
6710 			// "While node's parent is editable and in the same editing host as
6711 			// node, and node is not an HTML element whose local name is a
6712 			// formattable block name, set node to its parent."
6713 			while (isEditable(node.parentNode)
6714 			&& inSameEditingHost(node, node.parentNode)
6715 			&& !isHtmlElement(node, formattableBlockNames)) {
6716 				node = node.parentNode;
6717 			}
6718 
6719 			// "Let current type be the empty string."
6720 			var currentType = "";
6721 
6722 			// "If node is an editable HTML element whose local name is a
6723 			// formattable block name, and node is not the ancestor of a
6724 			// prohibited paragraph child, set current type to node's local
6725 6726 			// name."
6727 			if (isEditable(node)
6728 			&& isHtmlElement(node, formattableBlockNames)
6729 			&& !$_( getDescendants(node) ).some(isProhibitedParagraphChild)) {
6730 				currentType = node.tagName;
6731 			}
6732 
6733 			// "If type is null, set type to current type."
6734 			if (type === null) {
6735 				type = currentType;
6736 
6737 			// "Otherwise, if type does not equal current type, return true."
6738 			} else if (type != currentType) {
6739 				return true;
6740 			}
6741 		}
6742 
6743 		// "Return false."
6744 		return false;
6745 	}, value: function() {
6746 		// "Block-extend the active range, and let new range be the result."
6747 		var newRange = blockExtend(getActiveRange());
6748 
6749 		// "Let node be the first visible editable node that is contained in
6750 		// new range and has no children. If there is no such node, return the
6751 		// empty string."
6752 		var nodes = getAllContainedNodes(newRange, function(node) {
6753 			return isVisible(node)
6754 				&& isEditable(node)
6755 				&& !node.hasChildNodes();
6756 		});
6757 		if (!nodes.length) {
6758 			return "";
6759 		}
6760 		var node = nodes[0];
6761 
6762 		// "While node's parent is editable and in the same editing host as
6763 		// node, and node is not an HTML element whose local name is a
6764 		// formattable block name, set node to its parent."
6765 		while (isEditable(node.parentNode)
6766 		&& inSameEditingHost(node, node.parentNode)
6767 		&& !isHtmlElement(node, formattableBlockNames)) {
6768 			node = node.parentNode;
6769 		}
6770 
6771 		// "If node is an editable HTML element whose local name is a
6772 		// formattable block name, and node is not the ancestor of a prohibited
6773 		// paragraph child, return node's local name, converted to ASCII
6774 		// lowercase."
6775 		if (isEditable(node)
6776 		&& isHtmlElement(node, formattableBlockNames)
6777 		&& !$_( getDescendants(node) ).some(isProhibitedParagraphChild)) {
6778 			return node.tagName.toLowerCase();
6779 		}
6780 
6781 		// "Return the empty string."
6782 		return "";
6783 	}
6784 };
6785 
6786 //@}
6787 ///// The forwardDelete command /////
6788 //@{
6789 commands.forwarddelete = {
6790 	action: function(value, range) {
6791 	
6792 		// "If the active range is not collapsed, delete the contents of the
6793 		// active range and abort these steps."
6794 		if (!range.collapsed) {
6795 			deleteContents(range);
6796 			return;
6797 		}
6798 
6799 		// "Canonicalize whitespace at (active range's start node, active
6800 		// range's start offset)."
6801 		canonicalizeWhitespace(range.startContainer, range.startOffset);
6802 
6803 		// "Let node and offset be the active range's start node and offset."
6804 		var node = range.startContainer;
6805 		var offset = range.startOffset;
6806 		var isBr = false;
6807 		var isHr = false;
6808 
6809 		// "Repeat the following steps:"
6810 		while (true) {
6811 			// check whether the next element is a br or hr
6812 			if ( offset < node.childNodes.length ) {
6813 				isBr = isHtmlElement(node.childNodes[offset], "br") || false;
6814 				isHr = isHtmlElement(node.childNodes[offset], "hr") || false;
6815 			}
6816 
6817 			// "If offset is the length of node and node's nextSibling is an
6818 			// editable invisible node, remove node's nextSibling from its
6819 			// parent."
6820 			if (offset == getNodeLength(node)
6821 			&& isEditable(node.nextSibling)
6822 			&& isInvisible(node.nextSibling)) {
6823 				node.parentNode.removeChild(node.nextSibling);
6824 
6825 			// "Otherwise, if node has a child with index offset and that child
6826 			// is an editable invisible node, remove that child from node."
6827 			} else if (offset < node.childNodes.length
6828 			&& isEditable(node.childNodes[offset])
6829 			&& (isInvisible(node.childNodes[offset]) || isBr || isHr )) {
6830 				node.removeChild(node.childNodes[offset]);
6831 				if (isBr || isHr) {
6832 					range.setStart(node, offset);
6833 					range.setEnd(node, offset);
6834 					return;
6835 				}
6836 
6837 			// "Otherwise, if node has a child with index offset and that child
6838 			// is a collapsed block prop, add one to offset."
6839 			} else if (offset < node.childNodes.length
6840 			&& isCollapsedBlockProp(node.childNodes[offset])) {
6841 				offset++;
6842 
6843 			// "Otherwise, if offset is the length of node and node is an
6844 			// inline node, or if node is invisible, set offset to one plus the
6845 			// index of node, then set node to its parent."
6846 			} else if ((offset == getNodeLength(node)
6847 			&& isInlineNode(node))
6848 			|| isInvisible(node)) {
6849 				offset = 1 + getNodeIndex(node);
6850 				node = node.parentNode;
6851 
6852 			// "Otherwise, if node has a child with index offset and that child
6853 			// is not a block node or a br or an img, set node to that child,
6854 			// then set offset to zero."
6855 			} else if (offset < node.childNodes.length
6856 			&& !isBlockNode(node.childNodes[offset])
6857 			&& !isHtmlElement(node.childNodes[offset], ["br", "img"])) {
6858 				node = node.childNodes[offset];
6859 				offset = 0;
6860 
6861 			// "Otherwise, break from this loop."
6862 			} else {
6863 				break;
6864 			}
6865 		}
6866 
6867 		// collapse whitespace in the node, if it is a text node
6868 		collapseWhitespace(node, range);
6869 		offset = range.startOffset;
6870 
6871 		// "If node is a Text node and offset is not node's length:"
6872 		if (node.nodeType == $_.Node.TEXT_NODE
6873 		&& offset != getNodeLength(node)) {
6874 			// "Call collapse(node, offset) on the Selection."
6875 			range.setStart(node, offset);
6876 			range.setEnd(node, offset);
6877 
6878 			// "Let end offset be offset plus one."
6879 			var endOffset = offset + 1;
6880 
6881 			// "While end offset is not node's length and the end offsetth
6882 			// element of node's data has general category M when interpreted
6883 			// as a Unicode code point, add one to end offset."
6884 			//
6885 			// TODO: Not even going to try handling anything beyond the most
6886 			// basic combining marks, since I couldn't find a good list.  I
6887 			// special-case a few Hebrew diacritics too to test basic coverage
6888 			// of non-Latin stuff.
6889 			while (endOffset != node.length
6890 			&& /^[\u0300-\u036f\u0591-\u05bd\u05c1\u05c2]$/.test(node.data[endOffset])) {
6891 				endOffset++;
6892 			}
6893 
6894 			// "Delete the contents of the range with start (node, offset) and
6895 			// end (node, end offset)."
6896 			deleteContents(node, offset, node, endOffset);
6897 
6898 			// "Abort these steps."
6899 			return;
6900 		}
6901 
6902 		// "If node is an inline node, abort these steps."
6903 		if (isInlineNode(node)) {
6904 			return;
6905 		}
6906 
6907 		// "If node has a child with index offset and that child is a br or hr
6908 		// or img, call collapse(node, offset) on the Selection. Then delete
6909 		// the contents of the range with start (node, offset) and end (node,
6910 		// offset + 1) and abort these steps."
6911 		if (offset < node.childNodes.length
6912 		&& isHtmlElement(node.childNodes[offset], ["br", "hr", "img"])) {
6913 			range.setStart(node, offset);
6914 			range.setEnd(node, offset);
6915 			deleteContents(node, offset, node, offset + 1);
6916 			return;
6917 		}
6918 
6919 		// "Let end node equal node and let end offset equal offset."
6920 		var endNode = node;
6921 		var endOffset = offset;
6922 
6923 		// "Repeat the following steps:"
6924 		while (true) {
6925 			// "If end offset is the length of end node, set end offset to one
6926 			// plus the index of end node and then set end node to its parent."
6927 			if (endOffset == getNodeLength(endNode)) {
6928 				endOffset = 1 + getNodeIndex(endNode);
6929 				endNode = endNode.parentNode;
6930 
6931 			// "Otherwise, if end node has a an editable invisible child with
6932 			// index end offset, remove it from end node."
6933 			} else if (endOffset < endNode.childNodes.length
6934 			&& isEditable(endNode.childNodes[endOffset])
6935 			&& isInvisible(endNode.childNodes[endOffset])) {
6936 				endNode.removeChild(endNode.childNodes[endOffset]);
6937 
6938 			// "Otherwise, break from this loop."
6939 			} else {
6940 				break;
6941 			}
6942 		}
6943 
6944 		// "If the child of end node with index end offset minus one is a
6945 		// table, abort these steps."
6946 		if (isHtmlElement(endNode.childNodes[endOffset - 1], "table")) {
6947 			return;
6948 		}
6949 
6950 		// "If the child of end node with index end offset is a table:"
6951 		if (isHtmlElement(endNode.childNodes[endOffset], "table")) {
6952 			// "Call collapse(end node, end offset) on the context object's
6953 			// Selection."
6954 			range.setStart(endNode, endOffset);
6955 
6956 			// "Call extend(end node, end offset + 1) on the context object's
6957 			// Selection."
6958 			range.setEnd(endNode, endOffset + 1);
6959 
6960 			// "Abort these steps."
6961 			return;
6962 		}
6963 
6964 		// "If offset is the length of node, and the child of end node with
6965 		// index end offset is an hr or br:"
6966 		if (offset == getNodeLength(node)
6967 		&& isHtmlElement(endNode.childNodes[endOffset], ["br", "hr"])) {
6968 			// "Call collapse(node, offset) on the Selection."
6969 			range.setStart(node, offset);
6970 			range.setEnd(node, offset);
6971 
6972 			// "Delete the contents of the range with end (end node, end
6973 			// offset) and end (end node, end offset + 1)."
6974 			deleteContents(endNode, endOffset, endNode, endOffset + 1);
6975 
6976 			// "Abort these steps."
6977 			return;
6978 		}
6979 
6980 		// "While end node has a child with index end offset:"
6981 		while (endOffset < endNode.childNodes.length) {
6982 			// "If end node's child with index end offset is editable and
6983 			// invisible, remove it from end node."
6984 			if (isEditable(endNode.childNodes[endOffset])
6985 			&& isInvisible(endNode.childNodes[endOffset])) {
6986 				endNode.removeChild(endNode.childNodes[endOffset]);
6987 
6988 			// "Otherwise, set end node to its child with index end offset and
6989 			// set end offset to zero."
6990 			} else {
6991 				endNode = endNode.childNodes[endOffset];
6992 				endOffset = 0;
6993 			}
6994 		}
6995 
6996 		// "Delete the contents of the range with start (node, offset) and end
6997 		// (end node, end offset)."
6998 		deleteContents(node, offset, endNode, endOffset);
6999 	}
7000 };
7001 
7002 //@}
7003 ///// The indent command /////
7004 //@{
7005 commands.indent = {
7006 	action: function() {
7007 		// "Let items be a list of all lis that are ancestor containers of the
7008 		// active range's start and/or end node."
7009 		//
7010 		// Has to be in tree order, remember!
7011 		var items = [];
7012 		for (var node = getActiveRange().endContainer; node != getActiveRange().commonAncestorContainer; node = node.parentNode) {
7013 			if (isHtmlElement(node, "LI")) {
7014 				items.unshift(node);
7015 			}
7016 		}
7017 		for (var node = getActiveRange().startContainer; node != getActiveRange().commonAncestorContainer; node = node.parentNode) {
7018 			if (isHtmlElement(node, "LI")) {
7019 				items.unshift(node);
7020 			}
7021 		}
7022 		for (var node = getActiveRange().commonAncestorContainer; node; node = node.parentNode) {
7023 			if (isHtmlElement(node, "LI")) {
7024 				items.unshift(node);
7025 			}
7026 		}
7027 
7028 		// "For each item in items, normalize sublists of item."
7029 		for (var i = 0; i < items.length; i++) {
7030 			normalizeSublists(items[i, range]);
7031 		}
7032 
7033 		// "Block-extend the active range, and let new range be the result."
7034 		var newRange = blockExtend(getActiveRange());
7035 
7036 		// "Let node list be a list of nodes, initially empty."
7037 		var nodeList = [];
7038 
7039 		// "For each node node contained in new range, if node is editable and
7040 		// is an allowed child of "div" or "ol" and if the last member of node
7041 		// list (if any) is not an ancestor of node, append node to node list."
7042 		nodeList = getContainedNodes(newRange, function(node) {
7043 			return isEditable(node)
7044 				&& (isAllowedChild(node, "div")
7045 				|| isAllowedChild(node, "ol"));
7046 		});
7047 
7048 		// "If the first member of node list is an li whose parent is an ol or
7049 		// ul, and its previousSibling is an li as well, normalize sublists of
7050 		// its previousSibling."
7051 		if (nodeList.length
7052 		&& isHtmlElement(nodeList[0], "LI")
7053 		&& isHtmlElement(nodeList[0].parentNode, ["OL", "UL"])
7054 		&& isHtmlElement(nodeList[0].previousSibling, "LI")) {
7055 			normalizeSublists(nodeList[0].previousSibling, range);
7056 		}
7057 
7058 		// "While node list is not empty:"
7059 		while (nodeList.length) {
7060 			// "Let sublist be a list of nodes, initially empty."
7061 			var sublist = [];
7062 7063 
			// "Remove the first member of node list and append it to sublist."
7064 			sublist.push(nodeList.shift());
7065 
7066 			// "While the first member of node list is the nextSibling of the
7067 			// last member of sublist, remove the first member of node list and
7068 			// append it to sublist."
7069 			while (nodeList.length
7070 			&& nodeList[0] == sublist[sublist.length - 1].nextSibling) {
7071 				sublist.push(nodeList.shift());
7072 			}
7073 
7074 			// "Indent sublist."
7075 			indentNodes(sublist, range);
7076 		}
7077 	}
7078 };
7079 
7080 //@}
7081 ///// The insertHorizontalRule command /////
7082 //@{
7083 commands.inserthorizontalrule = {
7084 	action: function(value, range) {
7085 		
7086 		// "While range's start offset is 0 and its start node's parent is not
7087 		// null, set range's start to (parent of start node, index of start
7088 		// node)."
7089 		while (range.startOffset == 0
7090 		&& range.startContainer.parentNode) {
7091 			range.setStart(range.startContainer.parentNode, getNodeIndex(range.startContainer));
7092 		}
7093 
7094 		// "While range's end offset is the length of its end node, and its end
7095 		// node's parent is not null, set range's end to (parent of end node, 1
7096 		// + index of start node)."
7097 		while (range.endOffset == getNodeLength(range.endContainer)
7098 		&& range.endContainer.parentNode) {
7099 			range.setEnd(range.endContainer.parentNode, 1 + getNodeIndex(range.endContainer));
7100 		}
7101 
7102 		// "Delete the contents of range, with block merging false."
7103 		deleteContents(range, {blockMerging: false});
7104 
7105 		// "If the active range's start node is neither editable nor an editing
7106 		// host, abort these steps."
7107 		if (!isEditable(getActiveRange().startContainer)
7108 		&& !isEditingHost(getActiveRange().startContainer)) {
7109 			return;
7110 		}
7111 
7112 		// "If the active range's start node is a Text node and its start
7113 		// offset is zero, set the active range's start and end to (parent of
7114 		// start node, index of start node)."
7115 		if (getActiveRange().startContainer.nodeType == $_.Node.TEXT_NODE
7116 		&& getActiveRange().startOffset == 0) {
7117 			getActiveRange().setStart(getActiveRange().startContainer.parentNode, getNodeIndex(getActiveRange().startContainer));
7118 			getActiveRange().collapse(true);
7119 		}
7120 
7121 		// "If the active range's start node is a Text node and its start
7122 		// offset is the length of its start node, set the active range's start
7123 		// and end to (parent of start node, 1 + index of start node)."
7124 		if (getActiveRange().startContainer.nodeType == $_.Node.TEXT_NODE
7125 		&& getActiveRange().startOffset == getNodeLength(getActiveRange().startContainer)) {
7126 			getActiveRange().setStart(getActiveRange().startContainer.parentNode, 1 + getNodeIndex(getActiveRange().startContainer));
7127 			getActiveRange().collapse(true);
7128 		}
7129 
7130 		// "Let hr be the result of calling createElement("hr") on the
7131 		// context object."
7132 		var hr = document.createElement("hr");
7133 
7134 		// "Run insertNode(hr) on the range."
7135 		range.insertNode(hr);
7136 
7137 		// "Fix disallowed ancestors of hr."
7138 		fixDisallowedAncestors(hr, range);
7139 
7140 		// "Run collapse() on the Selection, with first argument equal to the
7141 		// parent of hr and the second argument equal to one plus the index of
7142 		// hr."
7143 		//
7144 		// Not everyone actually supports collapse(), so we do it manually
7145 		// instead.  Also, we need to modify the actual range we're given as
7146 		// well, for the sake of autoimplementation.html's range-filling-in.
7147 		range.setStart(hr.parentNode, 1 + getNodeIndex(hr));
7148 		range.setEnd(hr.parentNode, 1 + getNodeIndex(hr));
7149 		Aloha.getSelection().removeAllRanges();
7150 		Aloha.getSelection().addRange(range);
7151 	}
7152 };
7153 
7154 //@}
7155 ///// The insertHTML command /////
7156 //@{
7157 commands.inserthtml = {
7158 	action: function(value, range) {
7159 		
7160 		
7161 		// "Delete the contents of the active range."
7162 		deleteContents(range);
7163 
7164 		// "If the active range's start node is neither editable nor an editing
7165 		// host, abort these steps."
7166 		if (!isEditable(range.startContainer)
7167 		&& !isEditingHost(range.startContainer)) {
7168 			return;
7169 		}
7170 
7171 		// "Let frag be the result of calling createContextualFragment(value)
7172 		// on the active range."
7173 		var frag = range.createContextualFragment(value);
7174 
7175 		// "Let last child be the lastChild of frag."
7176 		var lastChild = frag.lastChild;
7177 
7178 		// "If last child is null, abort these steps."
7179 		if (!lastChild) {
7180 			return;
7181 		}
7182 
7183 		// "Let descendants be all descendants of frag."
7184 		var descendants = getDescendants(frag);
7185 
7186 		// "If the active range's start node is a block node:"
7187 		if (isBlockNode(range.startContainer)) {
7188 			// "Let collapsed block props be all editable collapsed block prop
7189 			// children of the active range's start node that have index
7190 			// greater than or equal to the active range's start offset."
7191 			//
7192 			// "For each node in collapsed block props, remove node from its
7193 			// parent."
7194 			$_(range.startContainer.childNodes).filter(function(node, range) {
7195 				return isEditable(node)
7196 					&& isCollapsedBlockProp(node)
7197 					&& getNodeIndex(node) >= range.startOffset;
7198 			}, true).forEach(function(node) {
7199 				node.parentNode.removeChild(node);
7200 			});
7201 		}
7202 
7203 		// "Call insertNode(frag) on the active range."
7204 		range.insertNode(frag);
7205 
7206 		// "If the active range's start node is a block node with no visible
7207 		// children, call createElement("br") on the context object and append
7208 		// the result as the last child of the active range's start node."
7209 		if (isBlockNode(range.startContainer)
7210 		&& !$_(range.startContainer.childNodes).some(isVisible)) {
7211 			range.startContainer.appendChild(createEndBreak());
7212 		}
7213 
7214 		// "Call collapse() on the context object's Selection, with last
7215 		// child's parent as the first argument and one plus its index as the
7216 		// second."
7217 		range.setStart(lastChild.parentNode, 1 + getNodeIndex(lastChild));
7218 		range.setEnd(lastChild.parentNode, 1 + getNodeIndex(lastChild));
7219 
7220 		// "Fix disallowed ancestors of each member of descendants."
7221 		for (var i = 0; i < descendants.length; i++) {
7222 			fixDisallowedAncestors(descendants[i], range);
7223 		}
7224 		
7225 		setActiveRange( range );
7226 	}
7227 };
7228 
7229 7230 //@}
7231 ///// The insertImage command /////
7232 //@{
7233 commands.insertimage = {
7234 	action: function(value) {
7235 		// "If value is the empty string, abort these steps and do nothing."
7236 		if (value === "") {
7237 			return;
7238 		}
7239 
7240 		// "Let range be the active range."
7241 		var range = getActiveRange();
7242 
7243 		// "Delete the contents of range, with strip wrappers false."
7244 		deleteContents(range, {stripWrappers: false});
7245 
7246 		// "If the active range's start node is neither editable nor an editing
7247 		// host, abort these steps."
7248 		if (!isEditable(getActiveRange().startContainer)
7249 		&& !isEditingHost(getActiveRange().startContainer)) {
7250 			return;
7251 		}
7252 
7253 		// "If range's start node is a block node whose sole child is a br, and
7254 		// its start offset is 0, remove its start node's child from it."
7255 		if (isBlockNode(range.startContainer)
7256 		&& range.startContainer.childNodes.length == 1
7257 		&& isHtmlElement(range.startContainer.firstChild, "br")
7258 		&& range.startOffset == 0) {
7259 			range.startContainer.removeChild(range.startContainer.firstChild);
7260 		}
7261 
7262 		// "Let img be the result of calling createElement("img") on the
7263 		// context object."
7264 		var img = document.createElement("img");
7265 
7266 		// "Run setAttribute("src", value) on img."
7267 		img.setAttribute("src", value);
7268 
7269 		// "Run insertNode(img) on the range."
7270 		range.insertNode(img);
7271 
7272 		// "Run collapse() on the Selection, with first argument equal to the
7273 		// parent of img and the second argument equal to one plus the index of
7274 		// img."
7275 		//
7276 		// Not everyone actually supports collapse(), so we do it manually
7277 		// instead.  Also, we need to modify the actual range we're given as
7278 		// well, for the sake of autoimplementation.html's range-filling-in.
7279 		range.setStart(img.parentNode, 1 + getNodeIndex(img));
7280 		range.setEnd(img.parentNode, 1 + getNodeIndex(img));
7281 		Aloha.getSelection().removeAllRanges();
7282 		Aloha.getSelection().addRange(range);
7283 
7284 		// IE adds width and height attributes for some reason, so remove those
7285 		// to actually do what the spec says.
7286 		img.removeAttribute("width");
7287 		img.removeAttribute("height");
7288 	}
7289 };
7290 
7291 //@}
7292 ///// The insertLineBreak command /////
7293 //@{
7294 commands.insertlinebreak = {
7295 	action: function(value, range) {
7296 		// "Delete the contents of the active range, with strip wrappers false."
7297 		deleteContents(range, {stripWrappers: false});
7298 
7299 		// "If the active range's start node is neither editable nor an editing
7300 		// host, abort these steps."
7301 		if (!isEditable(range.startContainer)
7302 		&& !isEditingHost(range.startContainer)) {
7303 			return;
7304 		}
7305 
7306 		// "If the active range's start node is an Element, and "br" is not an
7307 		// allowed child of it, abort these steps."
7308 		if (range.startContainer.nodeType == $_.Node.ELEMENT_NODE
7309 		&& !isAllowedChild("br", range.startContainer)) {
7310 			return;
7311 		}
7312 
7313 		// "If the active range's start node is not an Element, and "br" is not
7314 		// an allowed child of the active range's start node's parent, abort
7315 		// these steps."
7316 		if (range.startContainer.nodeType != $_.Node.ELEMENT_NODE
7317 		&& !isAllowedChild("br", range.startContainer.parentNode)) {
7318 			return;
7319 		}
7320 
7321 		// "If the active range's start node is a Text node and its start
7322 		// offset is zero, call collapse() on the context object's Selection,
7323 		// with first argument equal to the active range's start node's parent
7324 		// and second argument equal to the active range's start node's index."
7325 		if (range.startContainer.nodeType == $_.Node.TEXT_NODE
7326 		&& range.startOffset == 0) {
7327 			var newNode = range.startContainer.parentNode;
7328 			var newOffset = getNodeIndex(range.startContainer);
7329 			Aloha.getSelection().collapse(newNode, newOffset);
7330 			range.setStart(newNode, newOffset);
7331 			range.setEnd(newNode, newOffset);
7332 		}
7333 
7334 		// "If the active range's start node is a Text node and its start
7335 		// offset is the length of its start node, call collapse() on the
7336 		// context object's Selection, with first argument equal to the active
7337 		// range's start node's parent and second argument equal to one plus
7338 		// the active range's start node's index."
7339 		if (range.startContainer.nodeType == $_.Node.TEXT_NODE
7340 		&& range.startOffset == getNodeLength(range.startContainer)) {
7341 			var newNode = range.startContainer.parentNode;
7342 			var newOffset = 1 + getNodeIndex(range.startContainer);
7343 			Aloha.getSelection().collapse(newNode, newOffset);
7344 			range.setStart(newNode, newOffset);
7345 			range.setEnd(newNode, newOffset);
7346 		}
7347 
7348 		// "Let br be the result of calling createElement("br") on the context
7349 		// object."
7350 		var br = document.createElement("br");
7351 
7352 		// "Call insertNode(br) on the active range."
7353 		range.insertNode(br);
7354 
7355 		// "Call collapse() on the context object's Selection, with br's parent
7356 		// as the first argument and one plus br's index as the second
7357 		// argument."
7358 		Aloha.getSelection().collapse(br.parentNode, 1 + getNodeIndex(br));
7359 		range.setStart(br.parentNode, 1 + getNodeIndex(br));
7360 		range.setEnd(br.parentNode, 1 + getNodeIndex(br));
7361 
7362 		// "If br is a collapsed line break, call createElement("br") on the
7363 		// context object and let extra br be the result, then call
7364 		// insertNode(extra br) on the active range."
7365 		if (isCollapsedLineBreak(br)) {
7366 			range.insertNode(createEndBreak());
7367 
7368 			// Compensate for nonstandard implementations of insertNode
7369 			Aloha.getSelection().collapse(br.parentNode, 1 + getNodeIndex(br));
7370 			range.setStart(br.parentNode, 1 + getNodeIndex(br));
7371 			range.setEnd(br.parentNode, 1 + getNodeIndex(br));
7372 		}
7373 	}
7374 };
7375 
7376 //@}
7377 ///// The insertOrderedList command /////
7378 //@{
7379 commands.insertorderedlist = {
7380 	// "Toggle lists with tag name "ol"."
7381 	action: function() { toggleLists("ol") },
7382 	// "True if the selection's list state is "mixed" or "mixed ol", false
7383 	// otherwise."
7384 	indeterm: function() { return /^mixed( ol)?$/.test(getSelectionListState()) },
7385 	// "True if the selection's list state is "ol", false otherwise."
7386 	state: function() { return getSelectionListState() == "ol" }
7387 };
7388 
7389 //@}
7390 ///// The insertParagraph command /////
7391 //@{
7392 commands.insertparagraph = {
7393 	action: function(value, range) {
7394 		
7395 		// "Delete the contents of the active range."
7396 		deleteContents(range);
7397 
7398 		// "If the active range's start node is neither editable nor an editing
7399 		// host, abort these steps."
7400 		if (!isEditable(range.startContainer)
7401 		&& !isEditingHost(range.startContainer)) {
7402 			return;
7403 		}
7404 
7405 		// "Let node and offset be the active range's start node and offset."
7406 		var node = range.startContainer;
7407 		var offset = range.startOffset;
7408 
7409 		// "If node is a Text node, and offset is neither 0 nor the length of
7410 		// node, call splitText(offset) on node."
7411 		if (node.nodeType == $_.Node.TEXT_NODE
7412 		&& offset != 0
7413 		&& offset != getNodeLength(node)) {
7414 			node.splitText(offset);
7415 		}
7416 
7417 		// "If node is a Text node and offset is its length, set offset to one
7418 		// plus the index of node, then set node to its parent."
7419 		if (node.nodeType == $_.Node.TEXT_NODE
7420 		&& offset == getNodeLength(node)) {
7421 			offset = 1 + getNodeIndex(node);
7422 			node = node.parentNode;
7423 		}
7424 
7425 		// "If node is a Text or Comment node, set offset to the index of node,
7426 		// then set node to its parent."
7427 		if (node.nodeType == $_.Node.TEXT_NODE
7428 		|| node.nodeType == $_.Node.COMMENT_NODE) {
7429 			offset = getNodeIndex(node);
7430 			node = node.parentNode;
7431 		}
7432 
7433 		// "Call collapse(node, offset) on the context object's Selection."
7434 		Aloha.getSelection().collapse(node, offset);
7435 		range.setStart(node, offset);
7436 		range.setEnd(node, offset);
7437 
7438 		// "Let container equal node."
7439 		var container = node;
7440 
7441 		// "While container is not a single-line container, and container's
7442 		// parent is editable and in the same editing host as node, set
7443 		// container to its parent."
7444 		while (!isSingleLineContainer(container)
7445 		&& isEditable(container.parentNode)
7446 		&& inSameEditingHost(node, container.parentNode)) {
7447 			container = container.parentNode;
7448 		}
7449 
7450 		// "If container is not editable or not in the same editing host as
7451 		// node or is not a single-line container:"
7452 		if (!isEditable(container)
7453 		|| !inSameEditingHost(container, node)
7454 		|| !isSingleLineContainer(container)) {
7455 			// "Let tag be the default single-line container name."
7456 			var tag = defaultSingleLineContainerName;
7457 
7458 			// "Block-extend the active range, and let new range be the
7459 			// result."
7460 			var newRange = blockExtend(range);
7461 
7462 			// "Let node list be a list of nodes, initially empty."
7463 			//
7464 			// "Append to node list the first node in tree order that is
7465 			// contained in new range and is an allowed child of "p", if any."
7466 			var nodeList = getContainedNodes(newRange, function(node) { return isAllowedChild(node, "p") })
7467 				.slice(0, 1);
7468 
7469 			// "If node list is empty:"
7470 			if (!nodeList.length) {
7471 				// "If tag is not an allowed child of the active range's start
7472 				// node, abort these steps."
7473 				if (!isAllowedChild(tag, range.startContainer)) {
7474 					return;
7475 				}
7476 
7477 				// "Set container to the result of calling createElement(tag)
7478 				// on the context object."
7479 				container = document.createElement(tag);
7480 
7481 				// "Call insertNode(container) on the active range."
7482 				range.insertNode(container);
7483 
7484 				// "Call createElement("br") on the context object, and append
7485 				// the result as the last child of container."
7486 				container.appendChild(createEndBreak());
7487 
7488 				// "Call collapse(container, 0) on the context object's
7489 				// Selection."
7490 				// TODO: remove selection from command
7491 				Aloha.getSelection().collapse(container, 0); 
7492 				range.setStart(container, 0);
7493 				range.setEnd(container, 0);
7494 
7495 				// "Abort these steps."
7496 				return;
7497 			}
7498 
7499 			// "While the nextSibling of the last member of node list is not
7500 			// null and is an allowed child of "p", append it to node list."
7501 			while (nodeList[nodeList.length - 1].nextSibling
7502 			&& isAllowedChild(nodeList[nodeList.length - 1].nextSibling, "p")) {
7503 				nodeList.push(nodeList[nodeList.length - 1].nextSibling);
7504 			}
7505 
7506 			// "Wrap node list, with sibling criteria returning false and new
7507 			// parent instructions returning the result of calling
7508 			// createElement(tag) on the context object. Set container to the
7509 			// result."
7510 			container = wrap(nodeList,
7511 				function() { return false },
7512 				function() { return document.createElement(tag) },
7513 				range
7514 			);
7515 		}
7516 
7517 		// "If container's local name is "address", "listing", or "pre":"
7518 		if (container.tagName == "ADDRESS"
7519 		|| container.tagName == "LISTING"
7520 		|| container.tagName == "PRE") {
7521 			// "Let br be the result of calling createElement("br") on the
7522 			// context object."
7523 			var br = document.createElement("br");
7524 
7525 			// remember the old height
7526 			var oldHeight = container.offsetHeight;
7527 
7528 			// "Call insertNode(br) on the active range."
7529 			range.insertNode(br);
7530 
7531 			// determine the new height
7532 			var newHeight = container.offsetHeight;
7533 
7534 			// "Call collapse(node, offset + 1) on the context object's
7535 			// Selection."
7536 			Aloha.getSelection().collapse(node, offset + 1);
7537 			range.setStart(node, offset + 1);
7538 			range.setEnd(node, offset + 1);
7539 
7540 			// "If br is the last descendant of container, let br be the result
7541 			// of calling createElement("br") on the context object, then call
7542 			// insertNode(br) on the active range." (Fix: only do this, if the container height did not change by inserting a single <br/>)
7543 			//
7544 			// Work around browser bugs: some browsers select the
7545 			// newly-inserted node, not per spec.
7546 			if (oldHeight == newHeight && !isDescendant(nextNode(br), container)) {
7547 				range.insertNode(createEndBreak());
7548 				Aloha.getSelection().collapse(node, offset + 1);
7549 				range.setEnd(node, offset + 1);
7550 			}
7551 
7552 			// "Abort these steps."
7553 			return;
7554 		}
7555 
7556 		// "If container's local name is "li", "dt", or "dd"; and either it has
7557 		// no children or it has a single child and that child is a br:"
7558 		if ($_(["LI", "DT", "DD"]).indexOf(container.tagName) != -1
7559 		&& (!container.hasChildNodes()
7560 		|| (container.childNodes.length == 1
7561 		&& isHtmlElement(container.firstChild, "br")))) {
7562 			// "Split the parent of the one-node list consisting of container."
7563 			splitParent([container], range);
7564 
7565 			// "If container has no children, call createElement("br") on the
7566 			// context object and append the result as the last child of
7567 			// container."
7568 			// only do this, if inserting the br does NOT modify the offset height of the container
7569 			if (!container.hasChildNodes()) {
7570 				var oldHeight = container.offsetHeight, endBr = createEndBreak();
7571 				container.appendChild(endBr);
7572 				if (container.offsetHeight !== oldHeight) {
7573 					container.removeChild(endBr);
7574 				}
7575 			}
7576 
7577 			// "If container is a dd or dt, and it is not an allowed child of
7578 			// any of its ancestors in the same editing host, set the tag name
7579 			// of container to the default single-line container name and let
7580 			// container be the result."
7581 			if (isHtmlElement(container, ["dd", "dt"])
7582 			&& $_( getAncestors(container) ).every(function(ancestor) {
7583 				return !inSameEditingHost(container, ancestor)
7584 					|| !isAllowedChild(container, ancestor)
7585 			})) {
7586 				container = setTagName(container, defaultSingleLineContainerName, range);
7587 			}
7588 
7589 			// "Fix disallowed ancestors of container."
7590 			fixDisallowedAncestors(container, range);
7591 
7592 			// fix invalid nested lists
7593 			if (isHtmlElement(container, "li")
7594 			&& isHtmlElement(container.nextSibling, "li")
7595 			&& isHtmlElement(container.nextSibling.firstChild, ["ol", "ul"])) {
7596 				// we found a li containing only a br followed by a li containing a list as first element: merge the two li's
7597 				var listParent = container.nextSibling, length = container.nextSibling.childNodes.length;
7598 				for (var i = 0; i < length; i++) {
7599 					container.appendChild(listParent.childNodes[i]);
7600 				}
7601 				listParent.parentNode.removeChild(listParent);
7602 			}
7603 
7604 			// "Abort these steps."
7605 			return;
7606 		}
7607 
7608 		// "Let new line range be a new range whose start is the same as
7609 		// the active range's, and whose end is (container, length of
7610 		// container)."
7611 		var newLineRange = Aloha.createRange();
7612 		newLineRange.setStart(range.startContainer, range.startOffset);
7613 		newLineRange.setEnd(container, getNodeLength(container));
7614 
7615 		// "While new line range's start offset is zero and its start node is
7616 		// not container, set its start to (parent of start node, index of
7617 		// start node)."
7618 		while (newLineRange.startOffset == 0
7619 		&& newLineRange.startContainer != container) {
7620 			newLineRange.setStart(newLineRange.startContainer.parentNode, getNodeIndex(newLineRange.startContainer));
7621 		}
7622 
7623 		// "While new line range's start offset is the length of its start node
7624 		// and its start node is not container, set its start to (parent of
7625 7626 		// start node, 1 + index of start node)."
7627 		while (newLineRange.startOffset == getNodeLength(newLineRange.startContainer)
7628 		&& newLineRange.startContainer != container) {
7629 			newLineRange.setStart(newLineRange.startContainer.parentNode, 1 + getNodeIndex(newLineRange.startContainer));
7630 		}
7631 
7632 		// "Let end of line be true if new line range contains either nothing
7633 		// or a single br, and false otherwise."
7634 		var containedInNewLineRange = getContainedNodes(newLineRange);
7635 		var endOfLine = !containedInNewLineRange.length
7636 			|| (containedInNewLineRange.length == 1
7637 			&& isHtmlElement(containedInNewLineRange[0], "br"));
7638 
7639 		// "If the local name of container is "h1", "h2", "h3", "h4", "h5", or
7640 		// "h6", and end of line is true, let new container name be the default
7641 		// single-line container name."
7642 		var newContainerName;
7643 		if (/^H[1-6]$/.test(container.tagName)
7644 		&& endOfLine) {
7645 			newContainerName = defaultSingleLineContainerName;
7646 
7647 		// "Otherwise, if the local name of container is "dt" and end of line
7648 		// is true, let new container name be "dd"."
7649 		} else if (container.tagName == "DT"
7650 		&& endOfLine) {
7651 			newContainerName = "dd";
7652 
7653 		// "Otherwise, if the local name of container is "dd" and end of line
7654 		// is true, let new container name be "dt"."
7655 		} else if (container.tagName == "DD"
7656 		&& endOfLine) {
7657 			newContainerName = "dt";
7658 
7659 		// "Otherwise, let new container name be the local name of container."
7660 		} else {
7661 			newContainerName = container.tagName.toLowerCase();
7662 		}
7663 
7664 		// "Let new container be the result of calling createElement(new
7665 		// container name) on the context object."
7666 		var newContainer = document.createElement(newContainerName);
7667 
7668 		// "Copy all non empty attributes of the container to new container."
7669 		for ( var i = 0; i < container.attributes.length; i++ ) {
7670 			if ( typeof newContainer.setAttributeNS === 'function' ) {
7671 				newContainer.setAttributeNS(container.attributes[i].namespaceURI, container.attributes[i].name, container.attributes[i].value);
7672 			} else if ( container.attributes[i].value.length > 0 
7673 						&& container.attributes[i].value != 'null'
7674 						&& container.attributes[i].value > 0) {
7675 				newContainer.setAttribute(container.attributes[i].name, container.attributes[i].value);
7676 			}
7677 		}
7678 
7679 		// "If new container has an id attribute, unset it."
7680 		newContainer.removeAttribute("id");
7681 
7682 		// "Insert new container into the parent of container immediately after
7683 		// container."
7684 		container.parentNode.insertBefore(newContainer, container.nextSibling);
7685 
7686 		// "Let contained nodes be all nodes contained in new line range."
7687 		var containedNodes = getAllContainedNodes(newLineRange);
7688 
7689 		// "Let frag be the result of calling extractContents() on new line
7690 		// range."
7691 		var frag = newLineRange.extractContents();
7692 
7693 		// "Unset the id attribute (if any) of each Element descendant of frag
7694 		// that is not in contained nodes."
7695 		var descendants = getDescendants(frag);
7696 		for (var i = 0; i < descendants.length; i++) {
7697 			if (descendants[i].nodeType == $_.Node.ELEMENT_NODE
7698 			&& $_(containedNodes).indexOf(descendants[i]) == -1) {
7699 				descendants[i].removeAttribute("id");
7700 			}
7701 		}
7702 
7703 		var fragChildren = [], fragChild = frag.firstChild;
7704 		if (fragChild) {
7705 			do {
7706 				if (!isWhitespaceNode(fragChild)) {
7707 					fragChildren.push(fragChild);
7708 				}
7709 			} while(fragChild = fragChild.nextSibling);
7710 		}
7711 
7712 		// if newContainer is a li and frag contains only a list, we add a br in the li (but only if the height would not change)
7713 		if (isHtmlElement(newContainer, "li") && fragChildren.length && isHtmlElement(fragChildren[0], ["ul", "ol"])) {
7714 			var oldHeight = newContainer.offsetHeight;
7715 			var endBr = createEndBreak();
7716 			newContainer.appendChild(endBr);
7717 			var newHeight = newContainer.offsetHeight;
7718 			if (oldHeight !== newHeight) {
7719 				newContainer.removeChild(endBr);
7720 			}
7721 		}
7722 
7723 		// "Call appendChild(frag) on new container."
7724 		newContainer.appendChild(frag);
7725 
7726 		// "If container has no visible children, call createElement("br") on
7727 		// the context object, and append the result as the last child of
7728 		// container."
7729 		if (container.offsetHeight == 0 && !$_(container.childNodes).some(isVisible)) {
7730 			container.appendChild(createEndBreak());
7731 		}
7732 
7733 		// "If new container has no visible children, call createElement("br")
7734 		// on the context object, and append the result as the last child of
7735 		// new container."
7736 		if (newContainer.offsetHeight == 0 && !$_(newContainer.childNodes).some(isVisible)) {
7737 			newContainer.appendChild(createEndBreak());
7738 		}
7739 
7740 		// "Call collapse(new container, 0) on the context object's Selection."
7741 		Aloha.getSelection().collapse(newContainer, 0);
7742 		range.setStart(newContainer, 0);
7743 		range.setEnd(newContainer, 0);
7744 	}
7745 };
7746 
7747 //@}
7748 ///// The insertText command /////
7749 //@{
7750 commands.inserttext = {
7751 	action: function(value, range) {
7752 		// "Delete the contents of the active range, with strip wrappers
7753 		// false."
7754 		deleteContents(range, {stripWrappers: false});
7755 
7756 		// "If the active range's start node is neither editable nor an editing
7757 		// host, abort these steps."
7758 		if (!isEditable(range.startContainer)
7759 		&& !isEditingHost(range.startContainer)) {
7760 			return;
7761 		}
7762 
7763 		// "If value's length is greater than one:"
7764 		if (value.length > 1) {
7765 			// "For each element el in value, take the action for the
7766 			// insertText command, with value equal to el."
7767 			for (var i = 0; i < value.length; i++) {
7768 				commands.inserttext.action( value[i], range );
7769 			}
7770 
7771 			// "Abort these steps."
7772 			return;
7773 		}
7774 
7775 		// "If value is the empty string, abort these steps."
7776 		if (value == "") {
7777 			return;
7778 		}
7779 
7780 		// "If value is a newline (U+00A0), take the action for the
7781 		// insertParagraph command and abort these steps."
7782 		if (value == "\n") {
7783 			commands.insertparagraph.action( '', range );
7784 			return;
7785 		}
7786 
7787 		// "Let node and offset be the active range's start node and offset."
7788 		var node = range.startContainer;
7789 		var offset = range.startOffset;
7790 
7791 		// "If node has a child whose index is offset − 1, and that child is a
7792 		// Text node, set node to that child, then set offset to node's
7793 		// length."
7794 		if (0 <= offset - 1
7795 		&& offset - 1 < node.childNodes.length
7796 		&& node.childNodes[offset - 1].nodeType == $_.Node.TEXT_NODE) {
7797 			node = node.childNodes[offset - 1];
7798 			offset = getNodeLength(node);
7799 		}
7800 
7801 		// "If node has a child whose index is offset, and that child is a Text
7802 		// node, set node to that child, then set offset to zero."
7803 		if (0 <= offset
7804 		&& offset < node.childNodes.length
7805 		&& node.childNodes[offset].nodeType == $_.Node.TEXT_NODE) {
7806 			node = node.childNodes[offset];
7807 			offset = 0;
7808 		}
7809 
7810 		// "If value is a space (U+0020), and either node is an Element whose
7811 		// resolved value for "white-space" is neither "pre" nor "pre-wrap" or
7812 		// node is not an Element but its parent is an Element whose resolved
7813 		// value for "white-space" is neither "pre" nor "pre-wrap", set value
7814 		// to a non-breaking space (U+00A0)."
7815 		var refElement = node.nodeType == $_.Node.ELEMENT_NODE ? node : node.parentNode;
7816 		if (value == " "
7817 		&& refElement.nodeType == $_.Node.ELEMENT_NODE
7818 		&& $_(["pre", "pre-wrap"]).indexOf($_.getComputedStyle(refElement).whiteSpace) == -1) {
7819 			value = "\xa0";
7820 		}
7821 
7822 		// "Record current overrides, and let overrides be the result."
7823 		var overrides = recordCurrentOverrides( range );
7824 
7825 		// "If node is a Text node:"
7826 		if (node.nodeType == $_.Node.TEXT_NODE) {
7827 			// "Call insertData(offset, value) on node."
7828 			node.insertData(offset, value);
7829 
7830 			// "Call collapse(node, offset) on the context object's Selection."
7831 			Aloha.getSelection().collapse(node, offset);
7832 			range.setStart(node, offset);
7833 
7834 			// "Call extend(node, offset + 1) on the context object's
7835 			// Selection."
7836 			Aloha.getSelection().extend(node, offset + 1);
7837 			range.setEnd(node, offset + 1);
7838 
7839 		// "Otherwise:"
7840 		} else {
7841 			// "If node has only one child, which is a collapsed line break,
7842 			// remove its child from it."
7843 			//
7844 			// FIXME: IE incorrectly returns false here instead of true
7845 			// sometimes?
7846 			if (node.childNodes.length == 1
7847 			&& isCollapsedLineBreak(node.firstChild)) {
7848 				node.removeChild(node.firstChild);
7849 			}
7850 
7851 			// "Let text be the result of calling createTextNode(value) on the
7852 			// context object."
7853 			var text = document.createTextNode(value);
7854 
7855 			// "Call insertNode(text) on the active range."
7856 			range.insertNode(text);
7857 
7858 			// "Call collapse(text, 0) on the context object's Selection."
7859 			Aloha.getSelection().collapse(text, 0);
7860 			range.setStart(text, 0);
7861 
7862 			// "Call extend(text, 1) on the context object's Selection."
7863 			Aloha.getSelection().extend(text, 1);
7864 			range.setEnd(text, 1);
7865 		}
7866 
7867 		// "Restore states and values from overrides."
7868 		restoreStatesAndValues(overrides);
7869 
7870 		// "Canonicalize whitespace at the active range's start."
7871 		canonicalizeWhitespace(range.startContainer, range.startOffset);
7872 
7873 		// "Canonicalize whitespace at the active range's end."
7874 		canonicalizeWhitespace(range.endContainer, range.endOffset);
7875 
7876 		// "Call collapseToEnd() on the context object's Selection."
7877 		Aloha.getSelection().collapseToEnd();
7878 		range.collapse(false);
7879 	}
7880 };
7881 
7882 //@}
7883 ///// The insertUnorderedList command /////
7884 //@{
7885 commands.insertunorderedlist = {
7886 	// "Toggle lists with tag name "ul"."
7887 	action: function() { toggleLists("ul") },
7888 	// "True if the selection's list state is "mixed" or "mixed ul", false
7889 	// otherwise."
7890 	indeterm: function() { return /^mixed( ul)?$/.test(getSelectionListState()) },
7891 	// "True if the selection's list state is "ul", false otherwise."
7892 	state: function() { return getSelectionListState() == "ul" }
7893 };
7894 
7895 //@}
7896 ///// The justifyCenter command /////
7897 //@{
7898 commands.justifycenter = {
7899 	// "Justify the selection with alignment "center"."
7900 	action: function(value, range) { justifySelection("center", range) },
7901 	indeterm: function() {
7902 		// "Block-extend the active range. Return true if among visible
7903 		// editable nodes that are contained in the result and have no
7904 		// children, at least one has alignment value "center" and at least one
7905 		// does not. Otherwise return false."
7906 		var nodes = getAllContainedNodes(blockExtend(getActiveRange()), function(node) {
7907 			return isEditable(node) && isVisible(node) && !node.hasChildNodes();
7908 		});
7909 		return $_( nodes ).some(function(node) { return getAlignmentValue(node) == "center" })
7910 			&& $_( nodes ).some(function(node) { return getAlignmentValue(node) != "center" });
7911 	}, state: function() {
7912 		// "Block-extend the active range. Return true if there is at least one
7913 		// visible editable node that is contained in the result and has no
7914 		// children, and all such nodes have alignment value "center".
7915 		// Otherwise return false."
7916 		var nodes = getAllContainedNodes(blockExtend(getActiveRange()), function(node) {
7917 			return isEditable(node) && isVisible(node) && !node.hasChildNodes();
7918 		});
7919 		return nodes.length
7920 			&& $_( nodes ).every(function(node) { return getAlignmentValue(node) == "center" });
7921 	}, value: function() {
7922 		// "Block-extend the active range, and return the alignment value of
7923 		// the first visible editable node that is contained in the result and
7924 		// has no children. If there is no such node, return "left"."
7925 		var nodes = getAllContainedNodes(blockExtend(getActiveRange()), function(node) {
7926 			return isEditable(node) && isVisible(node) && !node.hasChildNodes();
7927 		});
7928 		if (nodes.length) {
7929 			return getAlignmentValue(nodes[0]);
7930 		} else {
7931 			return "left";
7932 		}
7933 	}
7934 };
7935 
7936 //@}
7937 ///// The justifyFull command /////
7938 //@{
7939 commands.justifyfull = {
7940 	// "Justify the selection with alignment "justify"."
7941 	action: function(value, range) { justifySelection("justify", range) },
7942 	indeterm: function() {
7943 		// "Block-extend the active range. Return true if among visible
7944 		// editable nodes that are contained in the result and have no
7945 		// children, at least one has alignment value "justify" and at least
7946 		// one does not. Otherwise return false."
7947 		var nodes = getAllContainedNodes(blockExtend(getActiveRange()), function(node) {
7948 			return isEditable(node) && isVisible(node) && !node.hasChildNodes();
7949 		});
7950 		return $_( nodes ).some(function(node) { return getAlignmentValue(node) == "justify" })
7951 			&& $_( nodes ).some(function(node) { return getAlignmentValue(node) != "justify" });
7952 	}, state: function() {
7953 		// "Block-extend the active range. Return true if there is at least one
7954 		// visible editable node that is contained in the result and has no
7955 		// children, and all such nodes have alignment value "justify".
7956 		// Otherwise return false."
7957 		var nodes = getAllContainedNodes(blockExtend(getActiveRange()), function(node) {
7958 			return isEditable(node) && isVisible(node) && !node.hasChildNodes();
7959 		});
7960 		return nodes.length
7961 			&& $_( nodes ).every(function(node) { return getAlignmentValue(node) == "justify" });
7962 	}, value: function() {
7963 		// "Block-extend the active range, and return the alignment value of
7964 		// the first visible editable node that is contained in the result and
7965 		// has no children. If there is no such node, return "left"."
7966 		var nodes = getAllContainedNodes(blockExtend(getActiveRange()), function(node) {
7967 			return isEditable(node) && isVisible(node) && !node.hasChildNodes();
7968 		});
7969 		if (nodes.length) {
7970 			return getAlignmentValue(nodes[0]);
7971 		} else {
7972 			return "left";
7973 		}
7974 	}
7975 };
7976 
7977 //@}
7978 ///// The justifyLeft command /////
7979 //@{
7980 commands.justifyleft = {
7981 	// "Justify the selection with alignment "left"."
7982 	action: function(value, range) { justifySelection("left", range) },
7983 	indeterm: function() {
7984 		// "Block-extend the active range. Return true if among visible
7985 		// editable nodes that are contained in the result and have no
7986 		// children, at least one has alignment value "left" and at least one
7987 		// does not. Otherwise return false."
7988 		var nodes = getAllContainedNodes(blockExtend(getActiveRange()), function(node) {
7989 			return isEditable(node) && isVisible(node) && !node.hasChildNodes();
7990 		});
7991 		return $_( nodes ).some(function(node) { return getAlignmentValue(node) == "left" })
7992 			&& $_( nodes ).some(function(node) { return getAlignmentValue(node) != "left" });
7993 	}, state: function() {
7994 		// "Block-extend the active range. Return true if there is at least one
7995 		// visible editable node that is contained in the result and has no
7996 		// children, and all such nodes have alignment value "left".  Otherwise
7997 		// return false."
7998 		var nodes = getAllContainedNodes(blockExtend(getActiveRange()), function(node) {
7999 			return isEditable(node) && isVisible(node) && !node.hasChildNodes();
8000 		});
8001 		return nodes.length
8002 			&& $_( nodes ).every(function(node) { return getAlignmentValue(node) == "left" });
8003 	}, value: function() {
8004 		// "Block-extend the active range, and return the alignment value of
8005 		// the first visible editable node that is contained in the result and
8006 		// has no children. If there is no such node, return "left"."
8007 		var nodes = getAllContainedNodes(blockExtend(getActiveRange()), function(node) {
8008 			return isEditable(node) && isVisible(node) && !node.hasChildNodes();
8009 		});
8010 		if (nodes.length) {
8011 			return getAlignmentValue(nodes[0]);
8012 		} else {
8013 			return "left";
8014 		}
8015 	}
8016 };
8017 
8018 //@}
8019 ///// The justifyRight command /////
8020 //@{
8021 commands.justifyright = {
8022 	// "Justify the selection with alignment "right"."
8023 	action: function(value, range) { justifySelection("right", range) },
8024 	indeterm: function() {
8025 		// "Block-extend the active range. Return true if among visible
8026 		// editable nodes that are contained in the result and have no
8027 		// children, at least one has alignment value "right" and at least one
8028 		// does not. Otherwise return false."
8029 		var nodes = getAllContainedNodes(blockExtend(getActiveRange()), function(node) {
8030 			return isEditable(node) && isVisible(node) && !node.hasChildNodes();
8031 		});
8032 		return $_( nodes ).some(function(node) { return getAlignmentValue(node) == "right" })
8033 			&& $_( nodes ).some(function(node) { return getAlignmentValue(node) != "right" });
8034 	}, state: function() {
8035 		// "Block-extend the active range. Return true if there is at least one
8036 		// visible editable node that is contained in the result and has no
8037 		// children, and all such nodes have alignment value "right".
8038 		// Otherwise return false."
8039 		var nodes = getAllContainedNodes(blockExtend(getActiveRange()), function(node) {
8040 			return isEditable(node) && isVisible(node) && !node.hasChildNodes();
8041 		});
8042 		return nodes.length
8043 			&& $_( nodes ).every(function(node) { return getAlignmentValue(node) == "right" });
8044 	}, value: function() {
8045 		// "Block-extend the active range, and return the alignment value of
8046 		// the first visible editable node that is contained in the result and
8047 		// has no children. If there is no such node, return "left"."
8048 		var nodes = getAllContainedNodes(blockExtend(getActiveRange()), function(node) {
8049 			return isEditable(node) && isVisible(node) && !node.hasChildNodes();
8050 		});
8051 		if (nodes.length) {
8052 			return getAlignmentValue(nodes[0]);
8053 		} else {
8054 			return "left";
8055 		}
8056 	}
8057 };
8058 
8059 //@}
8060 ///// The outdent command /////
8061 //@{
8062 commands.outdent = {
8063 	action: function() {
8064 		// "Let items be a list of all lis that are ancestor containers of the
8065 		// range's start and/or end node."
8066 		//
8067 		// It's annoying to get this in tree order using functional stuff
8068 		// without doing getDescendants(document), which is slow, so I do it
8069 		// imperatively.
8070 		var items = [];
8071 		(function(){
8072 			for (
8073 				var ancestorContainer = getActiveRange().endContainer;
8074 				ancestorContainer != getActiveRange().commonAncestorContainer;
8075 				ancestorContainer = ancestorContainer.parentNode
8076 			) {
8077 				if (isHtmlElement(ancestorContainer, "li")) {
8078 					items.unshift(ancestorContainer);
8079 				}
8080 			}
8081 			for (
8082 				var ancestorContainer = getActiveRange().startContainer;
8083 				ancestorContainer;
8084 				ancestorContainer = ancestorContainer.parentNode
8085 			) {
8086 				if (isHtmlElement(ancestorContainer, "li")) {
8087 					items.unshift(ancestorContainer);
8088 				}
8089 			}
8090 		})();
8091 
8092 		// "For each item in items, normalize sublists of item."
8093 		$_( items ).forEach( function( thisArg) {
8094 			normalizeSublists( thisArg, range);
8095 		});
8096 
8097 		// "Block-extend the active range, and let new range be the result."
8098 		var newRange = blockExtend(getActiveRange());
8099 
8100 		// "Let node list be a list of nodes, initially empty."
8101 		//
8102 		// "For each node node contained in new range, append node to node list
8103 		// if the last member of node list (if any) is not an ancestor of node;
8104 		// node is editable; and either node has no editable descendants, or is
8105 		// an ol or ul, or is an li whose parent is an ol or ul."
8106 		var nodeList = getContainedNodes(newRange, function(node) {
8107 			return isEditable(node)
8108 				&& (!$_( getDescendants(node) ).some(isEditable)
8109 				|| isHtmlElement(node, ["ol", "ul"])
8110 				|| (isHtmlElement(node, "li") && isHtmlElement(node.parentNode, ["ol", "ul"])));
8111 		});
8112 
8113 		// "While node list is not empty:"
8114 		while (nodeList.length) {
8115 			// "While the first member of node list is an ol or ul or is not
8116 			// the child of an ol or ul, outdent it and remove it from node
8117 			// list."
8118 			while (nodeList.length
8119 			&& (isHtmlElement(nodeList[0], ["OL", "UL"])
8120 			|| !isHtmlElement(nodeList[0].parentNode, ["OL", "UL"]))) {
8121 				outdentNode(nodeList.shift(), range);
8122 			}
8123 
8124 			// "If node list is empty, break from these substeps."
8125 			if (!nodeList.length) {
8126 				break;
8127 			}
8128 
8129 			// "Let sublist be a list of nodes, initially empty."
8130 			var sublist = [];
8131 
8132 			// "Remove the first member of node list and append it to sublist."
8133 			sublist.push(nodeList.shift());
8134 
8135 			// "While the first member of node list is the nextSibling of the
8136 			// last member of sublist, and the first member of node list is not
8137 			// an ol or ul, remove the first member of node list and append it
8138 			// to sublist."
8139 			while (nodeList.length
8140 			&& nodeList[0] == sublist[sublist.length - 1].nextSibling
8141 			&& !isHtmlElement(nodeList[0], ["OL", "UL"])) {
8142 				sublist.push(nodeList.shift());
8143 			}
8144 
8145 			// "Record the values of sublist, and let values be the result."
8146 			var values = recordValues(sublist);
8147 
8148 			// "Split the parent of sublist, with new parent null."
8149 			splitParent(sublist, range);
8150 
8151 			// "Fix disallowed ancestors of each member of sublist."
8152 			$_( sublist ).forEach(fixDisallowedAncestors);
8153 
8154 			// "Restore the values from values."
8155 			restoreValues(values, range);
8156 		}
8157 	}
8158 };
8159 
8160 //@}
8161 
8162 //////////////////////////////////
8163 ///// Miscellaneous commands /////
8164 //////////////////////////////////
8165 
8166 ///// The selectAll command /////
8167 //@{
8168 commands.selectall = {
8169 	// Note, this ignores the whole globalRange/getActiveRange() thing and
8170 	// works with actual selections.  Not suitable for autoimplementation.html.
8171 	action: function() {
8172 		// "Let target be the body element of the context object."
8173 		var target = document.body;
8174 
8175 		// "If target is null, let target be the context object's
8176 		// documentElement."
8177 		if (!target) {
8178 			target = document.documentElement;
8179 		}
8180 
8181 		// "If target is null, call getSelection() on the context object, and
8182 		// call removeAllRanges() on the result."
8183 		if (!target) {
8184 			Aloha.getSelection().removeAllRanges();
8185 
8186 		// "Otherwise, call getSelection() on the context object, and call
8187 		// selectAllChildren(target) on the result."
8188 		} else {
8189 			Aloha.getSelection().selectAllChildren(target);
8190 		}
8191 	}
8192 };
8193 
8194 //@}
8195 ///// The styleWithCSS command /////
8196 //@{
8197 commands.stylewithcss = {
8198 	action: function(value) {
8199 		// "If value is an ASCII case-insensitive match for the string
8200 		// "false", set the CSS styling flag to false. Otherwise, set the
8201 		// CSS styling flag to true."
8202 		cssStylingFlag = String(value).toLowerCase() != "false";
8203 	}, state: function() { return cssStylingFlag }
8204 };
8205 
8206 //@}
8207 ///// The useCSS command /////
8208 //@{
8209 commands.usecss = {
8210 	action: function(value) {
8211 		// "If value is an ASCII case-insensitive match for the string "false",
8212 		// set the CSS styling flag to true. Otherwise, set the CSS styling
8213 		// flag to false."
8214 		cssStylingFlag = String(value).toLowerCase() == "false";
8215 	}
8216 };
8217 //@}
8218 
8219 // Some final setup
8220 //@{
8221 (function() {
8222 // Opera 11.50 doesn't implement Object.keys, so I have to make an explicit
8223 // temporary, which means I need an extra closure to not leak the temporaries
8224 // into the global namespace.  >:(
8225 var commandNames = [];
8226 for (var command in commands) {
8227 	commandNames.push(command);
8228 }
8229 $_( commandNames ).forEach(function(command) {
8230 	// "If a command does not have a relevant CSS property specified, it
8231 	// defaults to null."
8232 	if (!("relevantCssProperty" in commands[command])) {
8233 		commands[command].relevantCssProperty = null;
8234 	}
8235 
8236 	// "If a command has inline command activated values defined but
8237 	// nothing else defines when it is indeterminate, it is indeterminate
8238 	// if among editable Text nodes effectively contained in the active
8239 	// range, there is at least one whose effective command value is one of
8240 	// the given values and at least one whose effective command value is
8241 	// not one of the given values."
8242 	if ("inlineCommandActivatedValues" in commands[command]
8243 	&& !("indeterm" in commands[command])) {
8244 		commands[command].indeterm = function( range ) {
8245 			var values = $_( getAllEffectivelyContainedNodes(range, function(node) {
8246 				return isEditable(node)
8247 					&& node.nodeType == $_.Node.TEXT_NODE;
8248 			}) ).map(function(node) { return getEffectiveCommandValue(node, command) });
8249 
8250 			var matchingValues = $_( values ).filter(function(value) {
8251 				return $_( commands[command].inlineCommandActivatedValues ).indexOf(value) != -1;
8252 			});
8253 
8254 			return matchingValues.length >= 1
8255 				&& values.length - matchingValues.length >= 1;
8256 		};
8257 	}
8258 
8259 	// "If a command has inline command activated values defined, its state
8260 	// is true if either no editable Text node is effectively contained in
8261 	// the active range, and the active range's start node's effective
8262 	// command value is one of the given values; or if there is at least
8263 	// one editable Text node effectively contained in the active range,
8264 	// and all of them have an effective command value equal to one of the
8265 	// given values."
8266 	if ("inlineCommandActivatedValues" in commands[command]) {
8267 		commands[command].state = function(range) {
8268 			var nodes = getAllEffectivelyContainedNodes(range, function(node) {
8269 				return isEditable(node)
8270 					&& node.nodeType == $_.Node.TEXT_NODE;
8271 			});
8272 
8273 			if (nodes.length == 0) {
8274 				return $_( commands[command].inlineCommandActivatedValues )
8275 					.indexOf(getEffectiveCommandValue(range.startContainer, command)) != -1;
8276 				return ret;
8277 			} else {
8278 				return $_( nodes ).every(function(node) {
8279 					return $_( commands[command].inlineCommandActivatedValues )
8280 						.indexOf(getEffectiveCommandValue(node, command)) != -1;
8281 				});
8282 			}
8283 		};
8284 	}
8285 
8286 	// "If a command is a standard inline value command, it is
8287 	// indeterminate if among editable Text nodes that are effectively
8288 	// contained in the active range, there are two that have distinct
8289 	// effective command values. Its value is the effective command value
8290 	// of the first editable Text node that is effectively contained in the
8291 	// active range, or if there is no such node, the effective command
8292 	// value of the active range's start node."
8293 	if ("standardInlineValueCommand" in commands[command]) {
8294 		commands[command].indeterm = function() {
8295 			var values = $_(getAllEffectivelyContainedNodes(getActiveRange()))
8296 				.filter(function(node) { return isEditable(node) && node.nodeType == $_.Node.TEXT_NODE }, true)
8297 				.map(function(node) { return getEffectiveCommandValue(node, command) });
8298 			for (var i = 1; i < values.length; i++) {
8299 				if (values[i] != values[i - 1]) {
8300 					return true;
8301 				}
8302 			}
8303 			return false;
8304 		};
8305 
8306 		commands[command].value = function(range) {
8307 			var refNode = getAllEffectivelyContainedNodes(range, function(node) {
8308 				return isEditable(node)
8309 					&& node.nodeType == $_.Node.TEXT_NODE;
8310 			})[0];
8311 
8312 			if (typeof refNode == "undefined") {
8313 				refNode = range.startContainer;
8314 			}
8315 
8316 			return getEffectiveCommandValue(refNode, command);
8317 		};
8318 	}
8319 });
8320 })();
8321 //@}
8322 return {
8323 	commands: commands,
8324 	execCommand: myExecCommand,
8325 	queryCommandIndeterm: myQueryCommandIndeterm,
8326 	queryCommandState: myQueryCommandState,
8327 	queryCommandValue: myQueryCommandValue,
8328 	queryCommandEnabled: myQueryCommandEnabled,
8329 	queryCommandSupported: myQueryCommandSupported
8330 }
8331 }); // end define
8332 // vim: foldmarker=@{,@} foldmethod=marker