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