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