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