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