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