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