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