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