1 /**
  2  * @license Rangy, a cross-browser JavaScript range and selection library
  3  * http://code.google.com/p/rangy/
  4  *
  5  * Copyright 2011, Tim Down
  6  * Licensed under the MIT license.
  7  * Version: 1.2.1
  8  * Build date: 8 October 2011
  9  */
 10 define('aloha/rangy-core', [], function() {
 11 var rangy = (function() {
 12 
 13 
 14     var OBJECT = "object", FUNCTION = "function", UNDEFINED = "undefined";
 15 
 16     var domRangeProperties = ["startContainer", "startOffset", "endContainer", "endOffset", "collapsed",
 17         "commonAncestorContainer", "START_TO_START", "START_TO_END", "END_TO_START", "END_TO_END"];
 18 
 19     var domRangeMethods = ["setStart", "setStartBefore", "setStartAfter", "setEnd", "setEndBefore",
 20         "setEndAfter", "collapse", "selectNode", "selectNodeContents", "compareBoundaryPoints", "deleteContents",
 21         "extractContents", "cloneContents", "insertNode", "surroundContents", "cloneRange", "toString", "detach"];
 22 
 23     var textRangeProperties = ["boundingHeight", "boundingLeft", "boundingTop", "boundingWidth", "htmlText", "text"];
 24 
 25     // Subset of TextRange's full set of methods that we're interested in
 26     var textRangeMethods = ["collapse", "compareEndPoints", "duplicate", "getBookmark", "moveToBookmark",
 27         "moveToElementText", "parentElement", "pasteHTML", "select", "setEndPoint", "getBoundingClientRect"];
 28 
 29     /*----------------------------------------------------------------------------------------------------------------*/
 30 
 31     // Trio of functions taken from Peter Michaux's article:
 32     // http://peter.michaux.ca/articles/feature-detection-state-of-the-art-browser-scripting
 33     function isHostMethod(o, p) {
 34         var t = typeof o[p];
 35         return t == FUNCTION || (!!(t == OBJECT && o[p])) || t == "unknown";
 36     }
 37 
 38     function isHostObject(o, p) {
 39         return !!(typeof o[p] == OBJECT && o[p]);
 40     }
 41 
 42     function isHostProperty(o, p) {
 43         return typeof o[p] != UNDEFINED;
 44     }
 45 
 46     // Creates a convenience function to save verbose repeated calls to tests functions
 47     function createMultiplePropertyTest(testFunc) {
 48         return function(o, props) {
 49             var i = props.length;
 50             while (i--) {
 51                 if (!testFunc(o, props[i])) {
 52                     return false;
 53                 }
 54             }
 55             return true;
 56         };
 57     }
 58 
 59     // Next trio of functions are a convenience to save verbose repeated calls to previous two functions
 60     var areHostMethods = createMultiplePropertyTest(isHostMethod);
 61     var areHostObjects = createMultiplePropertyTest(isHostObject);
 62     var areHostProperties = createMultiplePropertyTest(isHostProperty);
 63 
 64     function isTextRange(range) {
 65         return range && areHostMethods(range, textRangeMethods) && areHostProperties(range, textRangeProperties);
 66     }
 67 
 68     var api = {
 69         version: "1.2.1",
 70         initialized: false,
 71         supported: true,
 72 
 73         util: {
 74             isHostMethod: isHostMethod,
 75             isHostObject: isHostObject,
 76             isHostProperty: isHostProperty,
 77             areHostMethods: areHostMethods,
 78             areHostObjects: areHostObjects,
 79             areHostProperties: areHostProperties,
 80             isTextRange: isTextRange
 81         },
 82 
 83         features: {},
 84 
 85         modules: {},
 86         config: {
 87             alertOnWarn: false,
 88             // Note: this was set to true, see issue https://github.com/alohaeditor/Aloha-Editor/issues/474
 89             preferTextRange: true
 90         }
 91     };
 92 
 93     function fail(reason) {
 94         window.alert("Rangy not supported in your browser. Reason: " + reason);
 95         api.initialized = true;
 96         api.supported = false;
 97     }
 98 
 99     api.fail = fail;
100 
101     function warn(msg) {
102         var warningMessage = "Rangy warning: " + msg;
103         if (api.config.alertOnWarn) {
104             window.alert(warningMessage);
105         } else if (typeof window.console != UNDEFINED && typeof window.console.log != UNDEFINED) {
106             window.console.log(warningMessage);
107         }
108     }
109 
110     api.warn = warn;
111 
112     if ({}.hasOwnProperty) {
113         api.util.extend = function(o, props) {
114             for (var i in props) {
115                 if (props.hasOwnProperty(i)) {
116                     o[i] = props[i];
117                 }
118             }
119         };
120     } else {
121         fail("hasOwnProperty not supported");
122     }
123 
124     var initListeners = [];
125     var moduleInitializers = [];
126 
127     // Initialization
128     function init() {
129         if (api.initialized) {
130             return;
131         }
132         var testRange;
133         var implementsDomRange = false, implementsTextRange = false;
134 
135         // First, perform basic feature tests
136 
137         if (isHostMethod(document, "createRange")) {
138             testRange = document.createRange();
139             if (areHostMethods(testRange, domRangeMethods) && areHostProperties(testRange, domRangeProperties)) {
140                 implementsDomRange = true;
141             }
142             testRange.detach();
143         }
144 
145         var body = isHostObject(document, "body") ? document.body : document.getElementsByTagName("body")[0];
146 
147         if (body && isHostMethod(body, "createTextRange")) {
148             testRange = body.createTextRange();
149             if (isTextRange(testRange)) {
150                 implementsTextRange = true;
151             }
152         }
153 
154         if (!implementsDomRange && !implementsTextRange) {
155             fail("Neither Range nor TextRange are implemented");
156         }
157 
158         api.initialized = true;
159         api.features = {
160             implementsDomRange: implementsDomRange,
161             implementsTextRange: implementsTextRange
162         };
163 
164         // Initialize modules and call init listeners
165         var allListeners = moduleInitializers.concat(initListeners);
166         for (var i = 0, len = allListeners.length; i < len; ++i) {
167             try {
168                 allListeners[i](api);
169             } catch (ex) {
170                 if (isHostObject(window, "console") && isHostMethod(window.console, "log")) {
171                     window.console.log("Init listener threw an exception. Continuing.", ex);
172                 }
173 
174             }
175         }
176     }
177 
178     // Allow external scripts to initialize this library in case it's loaded after the document has loaded
179     api.init = init;
180 
181     // Execute listener immediately if already initialized
182     api.addInitListener = function(listener) {
183         if (api.initialized) {
184             listener(api);
185         } else {
186             initListeners.push(listener);
187         }
188     };
189 
190     var createMissingNativeApiListeners = [];
191 
192     api.addCreateMissingNativeApiListener = function(listener) {
193         createMissingNativeApiListeners.push(listener);
194     };
195 
196     function createMissingNativeApi(win) {
197         win = win || window;
198         init();
199 
200         // Notify listeners
201         for (var i = 0, len = createMissingNativeApiListeners.length; i < len; ++i) {
202             createMissingNativeApiListeners[i](win);
203         }
204     }
205 
206     api.createMissingNativeApi = createMissingNativeApi;
207 
208     /**
209      * @constructor
210      */
211     function Module(name) {
212         this.name = name;
213         this.initialized = false;
214         this.supported = false;
215     }
216 
217     Module.prototype.fail = function(reason) {
218         this.initialized = true;
219         this.supported = false;
220 
221         throw new Error("Module '" + this.name + "' failed to load: " + reason);
222     };
223 
224     Module.prototype.warn = function(msg) {
225         api.warn("Module " + this.name + ": " + msg);
226     };
227 
228     Module.prototype.createError = function(msg) {
229         return new Error("Error in Rangy " + this.name + " module: " + msg);
230     };
231 
232     api.createModule = function(name, initFunc) {
233         var module = new Module(name);
234         api.modules[name] = module;
235 
236         moduleInitializers.push(function(api) {
237             initFunc(api, module);
238             module.initialized = true;
239             module.supported = true;
240         });
241     };
242 
243     api.requireModules = function(modules) {
244         for (var i = 0, len = modules.length, module, moduleName; i < len; ++i) {
245             moduleName = modules[i];
246             module = api.modules[moduleName];
247             if (!module || !(module instanceof Module)) {
248                 throw new Error("Module '" + moduleName + "' not found");
249             }
250             if (!module.supported) {
251                 throw new Error("Module '" + moduleName + "' not supported");
252             }
253         }
254     };
255 
256     /*----------------------------------------------------------------------------------------------------------------*/
257 
258     // Wait for document to load before running tests
259 
260     var docReady = false;
261 
262     var loadHandler = function(e) {
263 
264         if (!docReady) {
265             docReady = true;
266             if (!api.initialized) {
267                 init();
268             }
269         }
270     };
271 
272     // Test whether we have window and document objects that we will need
273     if (typeof window == UNDEFINED) {
274         fail("No window found");
275         return;
276     }
277     if (typeof document == UNDEFINED) {
278         fail("No document found");
279         return;
280     }
281 
282     if (isHostMethod(document, "addEventListener")) {
283         document.addEventListener("DOMContentLoaded", loadHandler, false);
284     }
285 
286     // Add a fallback in case the DOMContentLoaded event isn't supported
287     if (isHostMethod(window, "addEventListener")) {
288         window.addEventListener("load", loadHandler, false);
289     } else if (isHostMethod(window, "attachEvent")) {
290         window.attachEvent("onload", loadHandler);
291     } else {
292         fail("Window does not have required addEventListener or attachEvent method");
293     }
294 
295     return api;
296 })();
297 rangy.createModule("DomUtil", function(api, module) {
298 
299     var UNDEF = "undefined";
300     var util = api.util;
301 
302     // Perform feature tests
303     if (!util.areHostMethods(document, ["createDocumentFragment", "createElement", "createTextNode"])) {
304         module.fail("document missing a Node creation method");
305     }
306 
307     if (!util.isHostMethod(document, "getElementsByTagName")) {
308         module.fail("document missing getElementsByTagName method");
309     }
310 
311     var el = document.createElement("div");
312     if (!util.areHostMethods(el, ["insertBefore", "appendChild", "cloneNode"] ||
313             !util.areHostObjects(el, ["previousSibling", "nextSibling", "childNodes", "parentNode"]))) {
314         module.fail("Incomplete Element implementation");
315     }
316 
317     // innerHTML is required for Range's createContextualFragment method
318     if (!util.isHostProperty(el, "innerHTML")) {
319         module.fail("Element is missing innerHTML property");
320     }
321 
322     var textNode = document.createTextNode("test");
323     if (!util.areHostMethods(textNode, ["splitText", "deleteData", "insertData", "appendData", "cloneNode"] ||
324             !util.areHostObjects(el, ["previousSibling", "nextSibling", "childNodes", "parentNode"]) ||
325             !util.areHostProperties(textNode, ["data"]))) {
326         module.fail("Incomplete Text Node implementation");
327     }
328 
329     /*----------------------------------------------------------------------------------------------------------------*/
330 
331     // Removed use of indexOf because of a bizarre bug in Opera that is thrown in one of the Acid3 tests. I haven't been
332     // able to replicate it outside of the test. The bug is that indexOf returns -1 when called on an Array that
333     // contains just the document as a single element and the value searched for is the document.
334     var arrayContains = /*Array.prototype.indexOf ?
335         function(arr, val) {
336             return arr.indexOf(val) > -1;
337         }:*/
338 
339         function(arr, val) {
340             var i = arr.length;
341             while (i--) {
342                 if (arr[i] === val) {
343                     return true;
344                 }
345             }
346             return false;
347         };
348 
349     // Opera 11 puts HTML elements in the null namespace, it seems, and IE 7 has undefined namespaceURI
350     function isHtmlNamespace(node) {
351         var ns;
352         return typeof node.namespaceURI == UNDEF || ((ns = node.namespaceURI) === null || ns == "http://www.w3.org/1999/xhtml");
353     }
354 
355     function parentElement(node) {
356         var parent = node.parentNode;
357         return (parent.nodeType == 1) ? parent : null;
358     }
359 
360     function getNodeIndex(node) {
361         var i = 0;
362         while( (node = node.previousSibling) ) {
363             i++;
364         }
365         return i;
366     }
367 
368     function getNodeLength(node) {
369         var childNodes;
370         return isCharacterDataNode(node) ? node.length : ((childNodes = node.childNodes) ? childNodes.length : 0);
371     }
372 
373     function getCommonAncestor(node1, node2) {
374         var ancestors = [], n;
375         for (n = node1; n; n = n.parentNode) {
376             ancestors.push(n);
377         }
378 
379         for (n = node2; n; n = n.parentNode) {
380             if (arrayContains(ancestors, n)) {
381                 return n;
382             }
383         }
384 
385         return null;
386     }
387 
388     function isAncestorOf(ancestor, descendant, selfIsAncestor) {
389         var n = selfIsAncestor ? descendant : descendant.parentNode;
390         while (n) {
391             if (n === ancestor) {
392                 return true;
393             } else {
394                 n = n.parentNode;
395             }
396         }
397         return false;
398     }
399 
400     function getClosestAncestorIn(node, ancestor, selfIsAncestor) {
401         var p, n = selfIsAncestor ? node : node.parentNode;
402         while (n) {
403             p = n.parentNode;
404             if (p === ancestor) {
405                 return n;
406             }
407             n = p;
408         }
409         return null;
410     }
411 
412     function isCharacterDataNode(node) {
413         var t = node.nodeType;
414         return t == 3 || t == 4 || t == 8 ; // Text, CDataSection or Comment
415     }
416 
417     function insertAfter(node, precedingNode) {
418         var nextNode = precedingNode.nextSibling, parent = precedingNode.parentNode;
419         if (nextNode) {
420             parent.insertBefore(node, nextNode);
421         } else {
422             parent.appendChild(node);
423         }
424         return node;
425     }
426 
427     // Note that we cannot use splitText() because it is bugridden in IE 9.
428     function splitDataNode(node, index) {
429         var newNode = node.cloneNode(false);
430         newNode.deleteData(0, index);
431         node.deleteData(index, node.length - index);
432         insertAfter(newNode, node);
433         return newNode;
434     }
435 
436     function getDocument(node) {
437         if (node.nodeType == 9) {
438             return node;
439         } else if (typeof node.ownerDocument != UNDEF) {
440             return node.ownerDocument;
441         } else if (typeof node.document != UNDEF) {
442             return node.document;
443         } else if (node.parentNode) {
444             return getDocument(node.parentNode);
445         } else {
446             throw new Error("getDocument: no document found for node");
447         }
448     }
449 
450     function getWindow(node) {
451         var doc = getDocument(node);
452         if (typeof doc.defaultView != UNDEF) {
453             return doc.defaultView;
454         } else if (typeof doc.parentWindow != UNDEF) {
455             return doc.parentWindow;
456         } else {
457             throw new Error("Cannot get a window object for node");
458         }
459     }
460 
461     function getIframeDocument(iframeEl) {
462         if (typeof iframeEl.contentDocument != UNDEF) {
463             return iframeEl.contentDocument;
464         } else if (typeof iframeEl.contentWindow != UNDEF) {
465             return iframeEl.contentWindow.document;
466         } else {
467             throw new Error("getIframeWindow: No Document object found for iframe element");
468         }
469     }
470 
471     function getIframeWindow(iframeEl) {
472         if (typeof iframeEl.contentWindow != UNDEF) {
473             return iframeEl.contentWindow;
474         } else if (typeof iframeEl.contentDocument != UNDEF) {
475             return iframeEl.contentDocument.defaultView;
476         } else {
477             throw new Error("getIframeWindow: No Window object found for iframe element");
478         }
479     }
480 
481     function getBody(doc) {
482         return util.isHostObject(doc, "body") ? doc.body : doc.getElementsByTagName("body")[0];
483     }
484 
485     function getRootContainer(node) {
486         var parent;
487         while ( (parent = node.parentNode) ) {
488             node = parent;
489         }
490         return node;
491     }
492 
493     /**
494 495 	 * This is a very ugly workaround for an IE9 issue Before comparing DOM
496 	 * elements "normalize" them. There are cases, where anchorNode and
497 	 * focusNode in a nativeselection point to DOM elements with same
498 	 * parentNode, same previousSibling and same nextSibling, but the nodes
499 	 * themselves are not the same
500 	 * If such nodes are compared in the comparePoints method, an error occurs.
501 	 * To fix this, we move to the previousSibling/nextSibling/parentNode and back, to hopefully get
502 	 * the "correct" node in the DOM
503 	 * @param node node to fix
504 	 * @return normalized node
505 	 */
506     function fixNode(node) {
507     	if (!node) {
508     		return;
509     	}
510     	if (node.previousSibling) {
511     		return node.previousSibling.nextSibling;
512     	} else if (node.nextSibling) {
513     		return node.nextSibling.previousSibling;
514     	} else if (node.parentNode) {
515     		return node.parentNode.firstChild;
516     	} else {
517     		return node;
518     	}
519     }
520 
521     function comparePoints(nodeA, offsetA, nodeB, offsetB) {
522     	// fix the nodes before comparing them
523     	nodeA = fixNode(nodeA);
524     	nodeB = fixNode(nodeB);
525         // See http://www.w3.org/TR/DOM-Level-2-Traversal-Range/ranges.html#Level-2-Range-Comparing
526         var nodeC, root, childA, childB, n;
527         if (nodeA == nodeB) {
528 
529             // Case 1: nodes are the same
530             return offsetA === offsetB ? 0 : (offsetA < offsetB) ? -1 : 1;
531         } else if ( (nodeC = getClosestAncestorIn(nodeB, nodeA, true)) ) {
532 
533             // Case 2: node C (container B or an ancestor) is a child node of A
534             return offsetA <= getNodeIndex(nodeC) ? -1 : 1;
535         } else if ( (nodeC = getClosestAncestorIn(nodeA, nodeB, true)) ) {
536 
537             // Case 3: node C (container A or an ancestor) is a child node of B
538             return getNodeIndex(nodeC) < offsetB  ? -1 : 1;
539         } else {
540 
541             // Case 4: containers are siblings or descendants of siblings
542             root = getCommonAncestor(nodeA, nodeB);
543             childA = (nodeA === root) ? root : getClosestAncestorIn(nodeA, root, true);
544             childB = (nodeB === root) ? root : getClosestAncestorIn(nodeB, root, true);
545 
546             if (childA === childB) {
547                 // This shouldn't be possible
548 
549                 throw new Error("comparePoints got to case 4 and childA and childB are the same!");
550             } else {
551                 n = root.firstChild;
552                 while (n) {
553                     if (n === childA) {
554 555                         return -1;
556                     } else if (n === childB) {
557                         return 1;
558                     }
559                     n = n.nextSibling;
560                 }
561                 throw new Error("Should not be here!");
562             }
563         }
564     }
565 
566     function fragmentFromNodeChildren(node) {
567         var fragment = getDocument(node).createDocumentFragment(), child;
568         while ( (child = node.firstChild) ) {
569             fragment.appendChild(child);
570         }
571         return fragment;
572     }
573 
574     function inspectNode(node) {
575         if (!node) {
576             return "[No node]";
577         }
578         if (isCharacterDataNode(node)) {
579             return '"' + node.data + '"';
580         } else if (node.nodeType == 1) {
581             var idAttr = node.id ? ' id="' + node.id + '"' : "";
582             return "<" + node.nodeName + idAttr + ">[" + node.childNodes.length + "]";
583         } else {
584             return node.nodeName;
585         }
586     }
587 
588     /**
589      * @constructor
590      */
591     function NodeIterator(root) {
592         this.root = root;
593         this._next = root;
594     }
595 
596     NodeIterator.prototype = {
597         _current: null,
598 
599         hasNext: function() {
600             return !!this._next;
601         },
602 
603         next: function() {
604             var n = this._current = this._next;
605             var child, next;
606             if (this._current) {
607                 child = n.firstChild;
608                 if (child) {
609                     this._next = child;
610                 } else {
611                     next = null;
612                     while ((n !== this.root) && !(next = n.nextSibling)) {
613                         n = n.parentNode;
614                     }
615                     this._next = next;
616                 }
617             }
618             return this._current;
619         },
620 
621         detach: function() {
622             this._current = this._next = this.root = null;
623         }
624     };
625 
626     function createIterator(root) {
627         return new NodeIterator(root);
628     }
629 
630     /**
631      * @constructor
632      */
633     function DomPosition(node, offset) {
634         this.node = node;
635         this.offset = offset;
636     }
637 
638     DomPosition.prototype = {
639         equals: function(pos) {
640             return this.node === pos.node & this.offset == pos.offset;
641         },
642 
643         inspect: function() {
644             return "[DomPosition(" + inspectNode(this.node) + ":" + this.offset + ")]";
645         }
646     };
647 
648     /**
649      * @constructor
650      */
651     function DOMException(codeName) {
652         this.code = this[codeName];
653         this.codeName = codeName;
654         this.message = "DOMException: " + this.codeName;
655     }
656 
657     DOMException.prototype = {
658         INDEX_SIZE_ERR: 1,
659         HIERARCHY_REQUEST_ERR: 3,
660         WRONG_DOCUMENT_ERR: 4,
661         NO_MODIFICATION_ALLOWED_ERR: 7,
662         NOT_FOUND_ERR: 8,
663         NOT_SUPPORTED_ERR: 9,
664         INVALID_STATE_ERR: 11
665     };
666 
667     DOMException.prototype.toString = function() {
668         return this.message;
669     };
670 
671     api.dom = {
672         arrayContains: arrayContains,
673         isHtmlNamespace: isHtmlNamespace,
674         parentElement: parentElement,
675         getNodeIndex: getNodeIndex,
676         getNodeLength: getNodeLength,
677         getCommonAncestor: getCommonAncestor,
678         isAncestorOf: isAncestorOf,
679         getClosestAncestorIn: getClosestAncestorIn,
680         isCharacterDataNode: isCharacterDataNode,
681         insertAfter: insertAfter,
682         splitDataNode: splitDataNode,
683         getDocument: getDocument,
684         getWindow: getWindow,
685         getIframeWindow: getIframeWindow,
686         getIframeDocument: getIframeDocument,
687         getBody: getBody,
688         getRootContainer: getRootContainer,
689         comparePoints: comparePoints,
690         inspectNode: inspectNode,
691         fragmentFromNodeChildren: fragmentFromNodeChildren,
692         createIterator: createIterator,
693         DomPosition: DomPosition
694     };
695 
696     api.DOMException = DOMException;
697 });rangy.createModule("DomRange", function(api, module) {
698     api.requireModules( ["DomUtil"] );
699 
700 
701     var dom = api.dom;
702     var DomPosition = dom.DomPosition;
703     var DOMException = api.DOMException;
704     
705     /*----------------------------------------------------------------------------------------------------------------*/
706 
707     // Utility functions
708 
709     function isNonTextPartiallySelected(node, range) {
710         return (node.nodeType != 3) &&
711                (dom.isAncestorOf(node, range.startContainer, true) || dom.isAncestorOf(node, range.endContainer, true));
712     }
713 
714     function getRangeDocument(range) {
715         return dom.getDocument(range.startContainer);
716     }
717 
718     function dispatchEvent(range, type, args) {
719         var listeners = range._listeners[type];
720         if (listeners) {
721             for (var i = 0, len = listeners.length; i < len; ++i) {
722                 listeners[i].call(range, {target: range, args: args});
723             }
724         }
725     }
726 
727     function getBoundaryBeforeNode(node) {
728         return new DomPosition(node.parentNode, dom.getNodeIndex(node));
729     }
730 
731     function getBoundaryAfterNode(node) {
732         return new DomPosition(node.parentNode, dom.getNodeIndex(node) + 1);
733     }
734 
735     function insertNodeAtPosition(node, n, o) {
736         var firstNodeInserted = node.nodeType == 11 ? node.firstChild : node;
737         if (dom.isCharacterDataNode(n)) {
738             if (o == n.length) {
739                 dom.insertAfter(node, n);
740             } else {
741                 n.parentNode.insertBefore(node, o == 0 ? n : dom.splitDataNode(n, o));
742             }
743         } else if (o >= n.childNodes.length) {
744             n.appendChild(node);
745         } else {
746             n.insertBefore(node, n.childNodes[o]);
747         }
748         return firstNodeInserted;
749     }
750 
751     function cloneSubtree(iterator) {
752         var partiallySelected;
753         for (var node, frag = getRangeDocument(iterator.range).createDocumentFragment(), subIterator; node = iterator.next(); ) {
754             partiallySelected = iterator.isPartiallySelectedSubtree();
755 
756             node = node.cloneNode(!partiallySelected);
757             if (partiallySelected) {
758                 subIterator = iterator.getSubtreeIterator();
759                 node.appendChild(cloneSubtree(subIterator));
760                 subIterator.detach(true);
761             }
762 
763             if (node.nodeType == 10) { // DocumentType
764                 throw new DOMException("HIERARCHY_REQUEST_ERR");
765             }
766             frag.appendChild(node);
767         }
768         return frag;
769     }
770 
771     function iterateSubtree(rangeIterator, func, iteratorState) {
772         var it, n;
773         iteratorState = iteratorState || { stop: false };
774         for (var node, subRangeIterator; node = rangeIterator.next(); ) {
775             //log.debug("iterateSubtree, partially selected: " + rangeIterator.isPartiallySelectedSubtree(), nodeToString(node));
776             if (rangeIterator.isPartiallySelectedSubtree()) {
777                 // The node is partially selected by the Range, so we can use a new RangeIterator on the portion of the
778                 // node selected by the Range.
779                 if (func(node) === false) {
780                     iteratorState.stop = true;
781                     return;
782                 } else {
783                     subRangeIterator = rangeIterator.getSubtreeIterator();
784                     iterateSubtree(subRangeIterator, func, iteratorState);
785                     subRangeIterator.detach(true);
786                     if (iteratorState.stop) {
787                         return;
788                     }
789                 }
790             } else {
791                 // The whole node is selected, so we can use efficient DOM iteration to iterate over the node and its
792                 // descendant
793                 it = dom.createIterator(node);
794                 while ( (n = it.next()) ) {
795                     if (func(n) === false) {
796                         iteratorState.stop = true;
797                         return;
798                     }
799                 }
800             }
801         }
802     }
803 
804     function deleteSubtree(iterator) {
805         var subIterator;
806         while (iterator.next()) {
807             if (iterator.isPartiallySelectedSubtree()) {
808                 subIterator = iterator.getSubtreeIterator();
809                 deleteSubtree(subIterator);
810                 subIterator.detach(true);
811             } else {
812                 iterator.remove();
813             }
814         }
815     }
816 
817     function extractSubtree(iterator) {
818 
819         for (var node, frag = getRangeDocument(iterator.range).createDocumentFragment(), subIterator; node = iterator.next(); ) {
820 
821 
822             if (iterator.isPartiallySelectedSubtree()) {
823                 node = node.cloneNode(false);
824                 subIterator = iterator.getSubtreeIterator();
825                 node.appendChild(extractSubtree(subIterator));
826                 subIterator.detach(true);
827             } else {
828                 iterator.remove();
829             }
830             if (node.nodeType == 10) { // DocumentType
831                 throw new DOMException("HIERARCHY_REQUEST_ERR");
832             }
833             frag.appendChild(node);
834         }
835         return frag;
836     }
837 
838     function getNodesInRange(range, nodeTypes, filter) {
839         //log.info("getNodesInRange, " + nodeTypes.join(","));
840         var filterNodeTypes = !!(nodeTypes && nodeTypes.length), regex;
841         var filterExists = !!filter;
842         if (filterNodeTypes) {
843             regex = new RegExp("^(" + nodeTypes.join("|") + ")$");
844         }
845 
846         var nodes = [];
847         iterateSubtree(new RangeIterator(range, false), function(node) {
848             if ((!filterNodeTypes || regex.test(node.nodeType)) && (!filterExists || filter(node))) {
849                 nodes.push(node);
850             }
851         });
852         return nodes;
853     }
854 
855     function inspect(range) {
856         var name = (typeof range.getName == "undefined") ? "Range" : range.getName();
857         return "[" + name + "(" + dom.inspectNode(range.startContainer) + ":" + range.startOffset + ", " +
858                 dom.inspectNode(range.endContainer) + ":" + range.endOffset + ")]";
859     }
860 
861     /*----------------------------------------------------------------------------------------------------------------*/
862 
863     // RangeIterator code partially borrows from IERange by Tim Ryan (http://github.com/timcameronryan/IERange)
864 
865     /**
866      * @constructor
867      */
868     function RangeIterator(range, clonePartiallySelectedTextNodes) {
869         this.range = range;
870         this.clonePartiallySelectedTextNodes = clonePartiallySelectedTextNodes;
871 
872 
873 
874         if (!range.collapsed) {
875             this.sc = range.startContainer;
876             this.so = range.startOffset;
877             this.ec = range.endContainer;
878             this.eo = range.endOffset;
879             var root = range.commonAncestorContainer;
880 
881             if (this.sc === this.ec && dom.isCharacterDataNode(this.sc)) {
882                 this.isSingleCharacterDataNode = true;
883                 this._first = this._last = this._next = this.sc;
884             } else {
885                 this._first = this._next = (this.sc === root && !dom.isCharacterDataNode(this.sc)) ?
886                     this.sc.childNodes[this.so] : dom.getClosestAncestorIn(this.sc, root, true);
887                 this._last = (this.ec === root && !dom.isCharacterDataNode(this.ec)) ?
888                     this.ec.childNodes[this.eo - 1] : dom.getClosestAncestorIn(this.ec, root, true);
889             }
890 
891         }
892     }
893 
894     RangeIterator.prototype = {
895         _current: null,
896         _next: null,
897         _first: null,
898         _last: null,
899         isSingleCharacterDataNode: false,
900 
901         reset: function() {
902             this._current = null;
903             this._next = this._first;
904         },
905 
906         hasNext: function() {
907             return !!this._next;
908         },
909 
910         next: function() {
911             // Move to next node
912             var current = this._current = this._next;
913             if (current) {
914                 this._next = (current !== this._last) ? current.nextSibling : null;
915 
916                 // Check for partially selected text nodes
917                 if (dom.isCharacterDataNode(current) && this.clonePartiallySelectedTextNodes) {
918                     if (current === this.ec) {
919 
920                         (current = current.cloneNode(true)).deleteData(this.eo, current.length - this.eo);
921                     }
922                     if (this._current === this.sc) {
923 
924                         (current = current.cloneNode(true)).deleteData(0, this.so);
925                     }
926                 }
927             }
928 
929             return current;
930         },
931 
932         remove: function() {
933             var current = this._current, start, end;
934 
935             if (dom.isCharacterDataNode(current) && (current === this.sc || current === this.ec)) {
936                 start = (current === this.sc) ? this.so : 0;
937                 end = (current === this.ec) ? this.eo : current.length;
938                 if (start != end) {
939                     current.deleteData(start, end - start);
940                 }
941             } else {
942                 if (current.parentNode) {
943                     current.parentNode.removeChild(current);
944                 } else {
945 
946                 }
947             }
948         },
949 
950         // Checks if the current node is partially selected
951         isPartiallySelectedSubtree: function() {
952             var current = this._current;
953             return isNonTextPartiallySelected(current, this.range);
954         },
955 
956         getSubtreeIterator: function() {
957             var subRange;
958             if (this.isSingleCharacterDataNode) {
959                 subRange = this.range.cloneRange();
960                 subRange.collapse();
961             } else {
962                 subRange = new Range(getRangeDocument(this.range));
963                 var current = this._current;
964                 var startContainer = current, startOffset = 0, endContainer = current, endOffset = dom.getNodeLength(current);
965 
966                 if (dom.isAncestorOf(current, this.sc, true)) {
967                     startContainer = this.sc;
968                     startOffset = this.so;
969                 }
970                 if (dom.isAncestorOf(current, this.ec, true)) {
971                     endContainer = this.ec;
972                     endOffset = this.eo;
973                 }
974 
975                 updateBoundaries(subRange, startContainer, startOffset, endContainer, endOffset);
976             }
977             return new RangeIterator(subRange, this.clonePartiallySelectedTextNodes);
978         },
979 
980         detach: function(detachRange) {
981             if (detachRange) {
982                 this.range.detach();
983             }
984             this.range = this._current = this._next = this._first = this._last = this.sc = this.so = this.ec = this.eo = null;
985         }
986     };
987 
988     /*----------------------------------------------------------------------------------------------------------------*/
989 
990     // Exceptions
991 
992     /**
993 994      * @constructor
995      */
996     function RangeException(codeName) {
997         this.code = this[codeName];
998         this.codeName = codeName;
999         this.message = "RangeException: " + this.codeName;
1000     }
1001 
1002     RangeException.prototype = {
1003         BAD_BOUNDARYPOINTS_ERR: 1,
1004         INVALID_NODE_TYPE_ERR: 2
1005     };
1006 
1007     RangeException.prototype.toString = function() {
1008         return this.message;
1009     };
1010 
1011     /*----------------------------------------------------------------------------------------------------------------*/
1012 
1013     /**
1014      * Currently iterates through all nodes in the range on creation until I think of a decent way to do it
1015      * TODO: Look into making this a proper iterator, not requiring preloading everything first
1016      * @constructor
1017      */
1018     function RangeNodeIterator(range, nodeTypes, filter) {
1019         this.nodes = getNodesInRange(range, nodeTypes, filter);
1020         this._next = this.nodes[0];
1021         this._position = 0;
1022     }
1023 
1024     RangeNodeIterator.prototype = {
1025         _current: null,
1026 
1027         hasNext: function() {
1028             return !!this._next;
1029         },
1030 
1031         next: function() {
1032             this._current = this._next;
1033             this._next = this.nodes[ ++this._position ];
1034             return this._current;
1035         },
1036 
1037         detach: function() {
1038             this._current = this._next = this.nodes = null;
1039         }
1040     };
1041 
1042     var beforeAfterNodeTypes = [1, 3, 4, 5, 7, 8, 10];
1043     var rootContainerNodeTypes = [2, 9, 11];
1044     var readonlyNodeTypes = [5, 6, 10, 12];
1045     var insertableNodeTypes = [1, 3, 4, 5, 7, 8, 10, 11];
1046     var surroundNodeTypes = [1, 3, 4, 5, 7, 8];
1047 
1048     function createAncestorFinder(nodeTypes) {
1049         return function(node, selfIsAncestor) {
1050             var t, n = selfIsAncestor ? node : node.parentNode;
1051             while (n) {
1052                 t = n.nodeType;
1053                 if (dom.arrayContains(nodeTypes, t)) {
1054                     return n;
1055                 }
1056                 n = n.parentNode;
1057             }
1058             return null;
1059         };
1060     }
1061 
1062     var getRootContainer = dom.getRootContainer;
1063     var getDocumentOrFragmentContainer = createAncestorFinder( [9, 11] );
1064     var getReadonlyAncestor = createAncestorFinder(readonlyNodeTypes);
1065     var getDocTypeNotationEntityAncestor = createAncestorFinder( [6, 10, 12] );
1066 
1067     function assertNoDocTypeNotationEntityAncestor(node, allowSelf) {
1068         if (getDocTypeNotationEntityAncestor(node, allowSelf)) {
1069             throw new RangeException("INVALID_NODE_TYPE_ERR");
1070         }
1071     }
1072 
1073     function assertNotDetached(range) {
1074         if (!range.startContainer) {
1075             throw new DOMException("INVALID_STATE_ERR");
1076         }
1077     }
1078 
1079     function assertValidNodeType(node, invalidTypes) {
1080         if (!dom.arrayContains(invalidTypes, node.nodeType)) {
1081 1082             throw new RangeException("INVALID_NODE_TYPE_ERR");
1083         }
1084     }
1085 
1086     function assertValidOffset(node, offset) {
1087         if (offset < 0 || offset > (dom.isCharacterDataNode(node) ? node.length : node.childNodes.length)) {
1088             throw new DOMException("INDEX_SIZE_ERR");
1089         }
1090     }
1091 
1092     function assertSameDocumentOrFragment(node1, node2) {
1093         if (getDocumentOrFragmentContainer(node1, true) !== getDocumentOrFragmentContainer(node2, true)) {
1094             throw new DOMException("WRONG_DOCUMENT_ERR");
1095         }
1096     }
1097 
1098     function assertNodeNotReadOnly(node) {
1099         if (getReadonlyAncestor(node, true)) {
1100             throw new DOMException("NO_MODIFICATION_ALLOWED_ERR");
1101         }
1102     }
1103 
1104     function assertNode(node, codeName) {
1105         if (!node) {
1106             throw new DOMException(codeName);
1107         }
1108     }
1109 
1110     function isOrphan(node) {
1111         return !dom.arrayContains(rootContainerNodeTypes, node.nodeType) && !getDocumentOrFragmentContainer(node, true);
1112     }
1113 
1114     function isValidOffset(node, offset) {
1115         return offset <= (dom.isCharacterDataNode(node) ? node.length : node.childNodes.length);
1116     }
1117 
1118     function assertRangeValid(range) {
1119         assertNotDetached(range);
1120         if (isOrphan(range.startContainer) || isOrphan(range.endContainer) ||
1121                 !isValidOffset(range.startContainer, range.startOffset) ||
1122                 !isValidOffset(range.endContainer, range.endOffset)) {
1123             throw new Error("Range error: Range is no longer valid after DOM mutation (" + range.inspect() + ")");
1124         }
1125     }
1126 
1127     /*----------------------------------------------------------------------------------------------------------------*/
1128 
1129     // Test the browser's innerHTML support to decide how to implement createContextualFragment
1130     var styleEl = document.createElement("style");
1131     var htmlParsingConforms = false;
1132     try {
1133         styleEl.innerHTML = "<b>x</b>";
1134         htmlParsingConforms = (styleEl.firstChild.nodeType == 3); // Opera incorrectly creates an element node
1135     } catch (e) {
1136         // IE 6 and 7 throw
1137     }
1138 
1139     api.features.htmlParsingConforms = htmlParsingConforms;
1140 
1141     var createContextualFragment = htmlParsingConforms ?
1142 
1143         // Implementation as per HTML parsing spec, trusting in the browser's implementation of innerHTML. See
1144         // discussion and base code for this implementation at issue 67.
1145         // Spec: http://html5.org/specs/dom-parsing.html#extensions-to-the-range-interface
1146         // Thanks to Aleks Williams.
1147         function(fragmentStr) {
1148             // "Let node the context object's start's node."
1149             var node = this.startContainer;
1150             var doc = dom.getDocument(node);
1151 
1152             // "If the context object's start's node is null, raise an INVALID_STATE_ERR
1153             // exception and abort these steps."
1154             if (!node) {
1155                 throw new DOMException("INVALID_STATE_ERR");
1156             }
1157 
1158             // "Let element be as follows, depending on node's interface:"
1159             // Document, Document Fragment: null
1160             var el = null;
1161 
1162             // "Element: node"
1163             if (node.nodeType == 1) {
1164                 el = node;
1165 
1166             // "Text, Comment: node's parentElement"
1167             } else if (dom.isCharacterDataNode(node)) {
1168                 el = dom.parentElement(node);
1169             }
1170 
1171             // "If either element is null or element's ownerDocument is an HTML document
1172             // and element's local name is "html" and element's namespace is the HTML
1173             // namespace"
1174             if (el === null || (
1175                 el.nodeName == "HTML"
1176                 && dom.isHtmlNamespace(dom.getDocument(el).documentElement)
1177                 && dom.isHtmlNamespace(el)
1178             )) {
1179 
1180             // "let element be a new Element with "body" as its local name and the HTML
1181             // namespace as its namespace.""
1182                 el = doc.createElement("body");
1183             } else {
1184                 el = el.cloneNode(false);
1185             }
1186 
1187             // "If the node's document is an HTML document: Invoke the HTML fragment parsing algorithm."
1188             // "If the node's document is an XML document: Invoke the XML fragment parsing algorithm."
1189             // "In either case, the algorithm must be invoked with fragment as the input
1190             // and element as the context element."
1191             el.innerHTML = fragmentStr;
1192 
1193             // "If this raises an exception, then abort these steps. Otherwise, let new
1194             // children be the nodes returned."
1195 
1196 1197             // "Let fragment be a new DocumentFragment."
1198             // "Append all new children to fragment."
1199             // "Return fragment."
1200             return dom.fragmentFromNodeChildren(el);
1201         } :
1202 
1203         // In this case, innerHTML cannot be trusted, so fall back to a simpler, non-conformant implementation that
1204         // previous versions of Rangy used (with the exception of using a body element rather than a div)
1205         function(fragmentStr) {
1206             assertNotDetached(this);
1207             var doc = getRangeDocument(this);
1208             var el = doc.createElement("body");
1209             el.innerHTML = fragmentStr;
1210 
1211             return dom.fragmentFromNodeChildren(el);
1212         };
1213 
1214     /*----------------------------------------------------------------------------------------------------------------*/
1215 
1216     var rangeProperties = ["startContainer", "startOffset", "endContainer", "endOffset", "collapsed",
1217         "commonAncestorContainer"];
1218 
1219     var s2s = 0, s2e = 1, e2e = 2, e2s = 3;
1220     var n_b = 0, n_a = 1, n_b_a = 2, n_i = 3;
1221 
1222     function RangePrototype() {}
1223 
1224     RangePrototype.prototype = {
1225         attachListener: function(type, listener) {
1226             this._listeners[type].push(listener);
1227         },
1228 
1229         compareBoundaryPoints: function(how, range) {
1230             assertRangeValid(this);
1231             assertSameDocumentOrFragment(this.startContainer, range.startContainer);
1232 
1233             var nodeA, offsetA, nodeB, offsetB;
1234             var prefixA = (how == e2s || how == s2s) ? "start" : "end";
1235             var prefixB = (how == s2e || how == s2s) ? "start" : "end";
1236             nodeA = this[prefixA + "Container"];
1237             offsetA = this[prefixA + "Offset"];
1238             nodeB = range[prefixB + "Container"];
1239             offsetB = range[prefixB + "Offset"];
1240             return dom.comparePoints(nodeA, offsetA, nodeB, offsetB);
1241         },
1242 
1243         insertNode: function(node) {
1244             assertRangeValid(this);
1245             assertValidNodeType(node, insertableNodeTypes);
1246             assertNodeNotReadOnly(this.startContainer);
1247 
1248             if (dom.isAncestorOf(node, this.startContainer, true)) {
1249                 throw new DOMException("HIERARCHY_REQUEST_ERR");
1250             }
1251 
1252             // No check for whether the container of the start of the Range is of a type that does not allow
1253             // children of the type of node: the browser's DOM implementation should do this for us when we attempt
1254             // to add the node
1255 
1256             var firstNodeInserted = insertNodeAtPosition(node, this.startContainer, this.startOffset);
1257             this.setStartBefore(firstNodeInserted);
1258         },
1259 
1260         cloneContents: function() {
1261             assertRangeValid(this);
1262 
1263             var clone, frag;
1264             if (this.collapsed) {
1265                 return getRangeDocument(this).createDocumentFragment();
1266             } else {
1267                 if (this.startContainer === this.endContainer && dom.isCharacterDataNode(this.startContainer)) {
1268                     clone = this.startContainer.cloneNode(true);
1269                     clone.data = clone.data.slice(this.startOffset, this.endOffset);
1270                     frag = getRangeDocument(this).createDocumentFragment();
1271                     frag.appendChild(clone);
1272                     return frag;
1273                 } else {
1274                     var iterator = new RangeIterator(this, true);
1275                     clone = cloneSubtree(iterator);
1276                     iterator.detach();
1277                 }
1278                 return clone;
1279             }
1280         },
1281 
1282         canSurroundContents: function() {
1283             assertRangeValid(this);
1284             assertNodeNotReadOnly(this.startContainer);
1285             assertNodeNotReadOnly(this.endContainer);
1286 
1287             // Check if the contents can be surrounded. Specifically, this means whether the range partially selects
1288             // no non-text nodes.
1289             var iterator = new RangeIterator(this, true);
1290             var boundariesInvalid = (iterator._first && (isNonTextPartiallySelected(iterator._first, this)) ||
1291                     (iterator._last && isNonTextPartiallySelected(iterator._last, this)));
1292             iterator.detach();
1293             return !boundariesInvalid;
1294         },
1295 
1296         surroundContents: function(node) {
1297             assertValidNodeType(node, surroundNodeTypes);
1298 
1299             if (!this.canSurroundContents()) {
1300                 throw new RangeException("BAD_BOUNDARYPOINTS_ERR");
1301             }
1302 
1303             // Extract the contents
1304             var content = this.extractContents();
1305 
1306             // Clear the children of the node
1307             if (node.hasChildNodes()) {
1308                 while (node.lastChild) {
1309                     node.removeChild(node.lastChild);
1310                 }
1311             }
1312 
1313             // Insert the new node and add the extracted contents
1314             insertNodeAtPosition(node, this.startContainer, this.startOffset);
1315             node.appendChild(content);
1316 
1317             this.selectNode(node);
1318         },
1319 
1320         cloneRange: function() {
1321             assertRangeValid(this);
1322             var range = new Range(getRangeDocument(this));
1323             var i = rangeProperties.length, prop;
1324             while (i--) {
1325                 prop = rangeProperties[i];
1326                 range[prop] = this[prop];
1327             }
1328             return range;
1329         },
1330 
1331         toString: function() {
1332             assertRangeValid(this);
1333             var sc = this.startContainer;
1334             if (sc === this.endContainer && dom.isCharacterDataNode(sc)) {
1335                 return (sc.nodeType == 3 || sc.nodeType == 4) ? sc.data.slice(this.startOffset, this.endOffset) : "";
1336             } else {
1337                 var textBits = [], iterator = new RangeIterator(this, true);
1338 
1339                 iterateSubtree(iterator, function(node) {
1340                     // Accept only text or CDATA nodes, not comments
1341 
1342                     if (node.nodeType == 3 || node.nodeType == 4) {
1343                         textBits.push(node.data);
1344                     }
1345                 });
1346                 iterator.detach();
1347                 return textBits.join("");
1348             }
1349         },
1350 
1351         // The methods below are all non-standard. The following batch were introduced by Mozilla but have since
1352         // been removed from Mozilla.
1353 
1354         compareNode: function(node) {
1355             assertRangeValid(this);
1356 
1357             var parent = node.parentNode;
1358             var nodeIndex = dom.getNodeIndex(node);
1359 
1360             if (!parent) {
1361                 throw new DOMException("NOT_FOUND_ERR");
1362             }
1363 
1364             var startComparison = this.comparePoint(parent, nodeIndex),
1365                 endComparison = this.comparePoint(parent, nodeIndex + 1);
1366 
1367             if (startComparison < 0) { // Node starts before
1368                 return (endComparison > 0) ? n_b_a : n_b;
1369             } else {
1370                 return (endComparison > 0) ? n_a : n_i;
1371             }
1372         },
1373 
1374         comparePoint: function(node, offset) {
1375             assertRangeValid(this);
1376             assertNode(node, "HIERARCHY_REQUEST_ERR");
1377             assertSameDocumentOrFragment(node, this.startContainer);
1378 
1379             if (dom.comparePoints(node, offset, this.startContainer, this.startOffset) < 0) {
1380                 return -1;
1381             } else if (dom.comparePoints(node, offset, this.endContainer, this.endOffset) > 0) {
1382                 return 1;
1383             }
1384             return 0;
1385         },
1386 
1387         createContextualFragment: createContextualFragment,
1388 
1389         toHtml: function() {
1390             assertRangeValid(this);
1391             var container = getRangeDocument(this).createElement("div");
1392             container.appendChild(this.cloneContents());
1393             return container.innerHTML;
1394         },
1395 
1396         // touchingIsIntersecting determines whether this method considers a node that borders a range intersects
1397         // with it (as in WebKit) or not (as in Gecko pre-1.9, and the default)
1398         intersectsNode: function(node, touchingIsIntersecting) {
1399             assertRangeValid(this);
1400             assertNode(node, "NOT_FOUND_ERR");
1401             if (dom.getDocument(node) !== getRangeDocument(this)) {
1402                 return false;
1403             }
1404 
1405             var parent = node.parentNode, offset = dom.getNodeIndex(node);
1406             assertNode(parent, "NOT_FOUND_ERR");
1407 
1408             var startComparison = dom.comparePoints(parent, offset, this.endContainer, this.endOffset),
1409                 endComparison = dom.comparePoints(parent, offset + 1, this.startContainer, this.startOffset);
1410 
1411             return touchingIsIntersecting ? startComparison <= 0 && endComparison >= 0 : startComparison < 0 && endComparison > 0;
1412         },
1413 
1414 
1415         isPointInRange: function(node, offset) {
1416             assertRangeValid(this);
1417             assertNode(node, "HIERARCHY_REQUEST_ERR");
1418             assertSameDocumentOrFragment(node, this.startContainer);
1419 
1420             return (dom.comparePoints(node, offset, this.startContainer, this.startOffset) >= 0) &&
1421                    (dom.comparePoints(node, offset, this.endContainer, this.endOffset) <= 0);
1422         },
1423 
1424         // The methods below are non-standard and invented by me.
1425 
1426         // Sharing a boundary start-to-end or end-to-start does not count as intersection.
1427         intersectsRange: function(range, touchingIsIntersecting) {
1428             assertRangeValid(this);
1429 
1430             if (getRangeDocument(range) != getRangeDocument(this)) {
1431                 throw new DOMException("WRONG_DOCUMENT_ERR");
1432             }
1433 
1434             var startComparison = dom.comparePoints(this.startContainer, this.startOffset, range.endContainer, range.endOffset),
1435                 endComparison = dom.comparePoints(this.endContainer, this.endOffset, range.startContainer, range.startOffset);
1436 
1437             return touchingIsIntersecting ? startComparison <= 0 && endComparison >= 0 : startComparison < 0 && endComparison > 0;
1438         },
1439 
1440         intersection: function(range) {
1441             if (this.intersectsRange(range)) {
1442                 var startComparison = dom.comparePoints(this.startContainer, this.startOffset, range.startContainer, range.startOffset),
1443                     endComparison = dom.comparePoints(this.endContainer, this.endOffset, range.endContainer, range.endOffset);
1444 
1445                 var intersectionRange = this.cloneRange();
1446 
1447                 if (startComparison == -1) {
1448                     intersectionRange.setStart(range.startContainer, range.startOffset);
1449                 }
1450                 if (endComparison == 1) {
1451                     intersectionRange.setEnd(range.endContainer, range.endOffset);
1452                 }
1453                 return intersectionRange;
1454             }
1455             return null;
1456         },
1457 
1458         union: function(range) {
1459             if (this.intersectsRange(range, true)) {
1460                 var unionRange = this.cloneRange();
1461                 if (dom.comparePoints(range.startContainer, range.startOffset, this.startContainer, this.startOffset) == -1) {
1462                     unionRange.setStart(range.startContainer, range.startOffset);
1463                 }
1464                 if (dom.comparePoints(range.endContainer, range.endOffset, this.endContainer, this.endOffset) == 1) {
1465                     unionRange.setEnd(range.endContainer, range.endOffset);
1466                 }
1467 1468                 return unionRange;
1469             } else {
1470                 throw new RangeException("Ranges do not intersect");
1471             }
1472         },
1473 
1474         containsNode: function(node, allowPartial) {
1475             if (allowPartial) {
1476                 return this.intersectsNode(node, false);
1477             } else {
1478                 return this.compareNode(node) == n_i;
1479             }
1480         },
1481 
1482         containsNodeContents: function(node) {
1483             return this.comparePoint(node, 0) >= 0 && this.comparePoint(node, dom.getNodeLength(node)) <= 0;
1484         },
1485 
1486         containsRange: function(range) {
1487             return this.intersection(range).equals(range);
1488         },
1489 
1490         containsNodeText: function(node) {
1491             var nodeRange = this.cloneRange();
1492             nodeRange.selectNode(node);
1493             var textNodes = nodeRange.getNodes([3]);
1494             if (textNodes.length > 0) {
1495                 nodeRange.setStart(textNodes[0], 0);
1496                 var lastTextNode = textNodes.pop();
1497                 nodeRange.setEnd(lastTextNode, lastTextNode.length);
1498                 var contains = this.containsRange(nodeRange);
1499                 nodeRange.detach();
1500                 return contains;
1501             } else {
1502                 return this.containsNodeContents(node);
1503             }
1504         },
1505 
1506         createNodeIterator: function(nodeTypes, filter) {
1507             assertRangeValid(this);
1508             return new RangeNodeIterator(this, nodeTypes, filter);
1509         },
1510 
1511         getNodes: function(nodeTypes, filter) {
1512             assertRangeValid(this);
1513             return getNodesInRange(this, nodeTypes, filter);
1514         },
1515 
1516         getDocument: function() {
1517             return getRangeDocument(this);
1518         },
1519 
1520         collapseBefore: function(node) {
1521             assertNotDetached(this);
1522 
1523             this.setEndBefore(node);
1524             this.collapse(false);
1525         },
1526 
1527         collapseAfter: function(node) {
1528             assertNotDetached(this);
1529 
1530             this.setStartAfter(node);
1531             this.collapse(true);
1532         },
1533 
1534         getName: function() {
1535             return "DomRange";
1536         },
1537 
1538         equals: function(range) {
1539             return Range.rangesEqual(this, range);
1540         },
1541 
1542         inspect: function() {
1543             return inspect(this);
1544         }
1545     };
1546 
1547     function copyComparisonConstantsToObject(obj) {
1548         obj.START_TO_START = s2s;
1549         obj.START_TO_END = s2e;
1550         obj.END_TO_END = e2e;
1551         obj.END_TO_START = e2s;
1552 
1553         obj.NODE_BEFORE = n_b;
1554         obj.NODE_AFTER = n_a;
1555         obj.NODE_BEFORE_AND_AFTER = n_b_a;
1556         obj.NODE_INSIDE = n_i;
1557     }
1558 
1559     function copyComparisonConstants(constructor) {
1560         copyComparisonConstantsToObject(constructor);
1561         copyComparisonConstantsToObject(constructor.prototype);
1562     }
1563 
1564     function createRangeContentRemover(remover, boundaryUpdater) {
1565         return function() {
1566             assertRangeValid(this);
1567 
1568             var sc = this.startContainer, so = this.startOffset, root = this.commonAncestorContainer;
1569 
1570             var iterator = new RangeIterator(this, true);
1571 
1572             // Work out where to position the range after content removal
1573             var node, boundary;
1574             if (sc !== root) {
1575                 node = dom.getClosestAncestorIn(sc, root, true);
1576                 boundary = getBoundaryAfterNode(node);
1577                 sc = boundary.node;
1578                 so = boundary.offset;
1579             }
1580 
1581             // Check none of the range is read-only
1582             iterateSubtree(iterator, assertNodeNotReadOnly);
1583 
1584             iterator.reset();
1585 
1586             // Remove the content
1587             var returnValue = remover(iterator);
1588             iterator.detach();
1589 
1590             // Move to the new position
1591             boundaryUpdater(this, sc, so, sc, so);
1592 
1593             return returnValue;
1594         };
1595     }
1596 
1597     function createPrototypeRange(constructor, boundaryUpdater, detacher) {
1598         function createBeforeAfterNodeSetter(isBefore, isStart) {
1599             return function(node) {
1600                 assertNotDetached(this);
1601                 assertValidNodeType(node, beforeAfterNodeTypes);
1602                 assertValidNodeType(getRootContainer(node), rootContainerNodeTypes);
1603 
1604                 var boundary = (isBefore ? getBoundaryBeforeNode : getBoundaryAfterNode)(node);
1605                 (isStart ? setRangeStart : setRangeEnd)(this, boundary.node, boundary.offset);
1606             };
1607         }
1608 
1609         function setRangeStart(range, node, offset) {
1610             var ec = range.endContainer, eo = range.endOffset;
1611             if (node !== range.startContainer || offset !== this.startOffset) {
1612                 // Check the root containers of the range and the new boundary, and also check whether the new boundary
1613                 // is after the current end. In either case, collapse the range to the new position
1614                 if (getRootContainer(node) != getRootContainer(ec) || dom.comparePoints(node, offset, ec, eo) == 1) {
1615                     ec = node;
1616                     eo = offset;
1617                 }
1618                 boundaryUpdater(range, node, offset, ec, eo);
1619             }
1620 1621         }
1622 
1623         function setRangeEnd(range, node, offset) {
1624             var sc = range.startContainer, so = range.startOffset;
1625             if (node !== range.endContainer || offset !== this.endOffset) {
1626                 // Check the root containers of the range and the new boundary, and also check whether the new boundary
1627                 // is after the current end. In either case, collapse the range to the new position
1628                 if (getRootContainer(node) != getRootContainer(sc) || dom.comparePoints(node, offset, sc, so) == -1) {
1629                     sc = node;
1630                     so = offset;
1631                 }
1632                 boundaryUpdater(range, sc, so, node, offset);
1633             }
1634         }
1635 
1636         function setRangeStartAndEnd(range, node, offset) {
1637             if (node !== range.startContainer || offset !== this.startOffset || node !== range.endContainer || offset !== this.endOffset) {
1638                 boundaryUpdater(range, node, offset, node, offset);
1639             }
1640         }
1641 
1642         constructor.prototype = new RangePrototype();
1643 
1644         api.util.extend(constructor.prototype, {
1645             setStart: function(node, offset) {
1646                 assertNotDetached(this);
1647                 assertNoDocTypeNotationEntityAncestor(node, true);
1648                 assertValidOffset(node, offset);
1649 
1650                 setRangeStart(this, node, offset);
1651             },
1652 
1653             setEnd: function(node, offset) {
1654                 assertNotDetached(this);
1655                 assertNoDocTypeNotationEntityAncestor(node, true);
1656                 assertValidOffset(node, offset);
1657 
1658                 setRangeEnd(this, node, offset);
1659             },
1660 
1661             setStartBefore: createBeforeAfterNodeSetter(true, true),
1662             setStartAfter: createBeforeAfterNodeSetter(false, true),
1663             setEndBefore: createBeforeAfterNodeSetter(true, false),
1664             setEndAfter: createBeforeAfterNodeSetter(false, false),
1665 
1666             collapse: function(isStart) {
1667                 assertRangeValid(this);
1668                 if (isStart) {
1669                     boundaryUpdater(this, this.startContainer, this.startOffset, this.startContainer, this.startOffset);
1670                 } else {
1671                     boundaryUpdater(this, this.endContainer, this.endOffset, this.endContainer, this.endOffset);
1672                 }
1673             },
1674 
1675             selectNodeContents: function(node) {
1676                 // This doesn't seem well specified: the spec talks only about selecting the node's contents, which
1677                 // could be taken to mean only its children. However, browsers implement this the same as selectNode for
1678                 // text nodes, so I shall do likewise
1679                 assertNotDetached(this);
1680                 assertNoDocTypeNotationEntityAncestor(node, true);
1681 
1682                 boundaryUpdater(this, node, 0, node, dom.getNodeLength(node));
1683             },
1684 
1685             selectNode: function(node) {
1686                 assertNotDetached(this);
1687                 assertNoDocTypeNotationEntityAncestor(node, false);
1688                 assertValidNodeType(node, beforeAfterNodeTypes);
1689 
1690                 var start = getBoundaryBeforeNode(node), end = getBoundaryAfterNode(node);
1691                 boundaryUpdater(this, start.node, start.offset, end.node, end.offset);
1692             },
1693 
1694             extractContents: createRangeContentRemover(extractSubtree, boundaryUpdater),
1695 
1696             deleteContents: createRangeContentRemover(deleteSubtree, boundaryUpdater),
1697 
1698             canSurroundContents: function() {
1699                 assertRangeValid(this);
1700                 assertNodeNotReadOnly(this.startContainer);
1701                 assertNodeNotReadOnly(this.endContainer);
1702 
1703                 // Check if the contents can be surrounded. Specifically, this means whether the range partially selects
1704                 // no non-text nodes.
1705                 var iterator = new RangeIterator(this, true);
1706                 var boundariesInvalid = (iterator._first && (isNonTextPartiallySelected(iterator._first, this)) ||
1707                         (iterator._last && isNonTextPartiallySelected(iterator._last, this)));
1708                 iterator.detach();
1709                 return !boundariesInvalid;
1710             },
1711 
1712             detach: function() {
1713                 detacher(this);
1714             },
1715 
1716             splitBoundaries: function() {
1717                 assertRangeValid(this);
1718 
1719 
1720                 var sc = this.startContainer, so = this.startOffset, ec = this.endContainer, eo = this.endOffset;
1721                 var startEndSame = (sc === ec);
1722 
1723                 if (dom.isCharacterDataNode(ec) && eo > 0 && eo < ec.length) {
1724                     dom.splitDataNode(ec, eo);
1725 
1726                 }
1727 
1728                 if (dom.isCharacterDataNode(sc) && so > 0 && so < sc.length) {
1729 
1730                     sc = dom.splitDataNode(sc, so);
1731                     if (startEndSame) {
1732                         eo -= so;
1733                         ec = sc;
1734                     } else if (ec == sc.parentNode && eo >= dom.getNodeIndex(sc)) {
1735                         eo++;
1736                     }
1737                     so = 0;
1738 
1739                 }
1740                 boundaryUpdater(this, sc, so, ec, eo);
1741             },
1742 
1743             normalizeBoundaries: function() {
1744                 assertRangeValid(this);
1745 
1746                 var sc = this.startContainer, so = this.startOffset, ec = this.endContainer, eo = this.endOffset;
1747 
1748                 var mergeForward = function(node) {
1749                     var sibling = node.nextSibling;
1750                     if (sibling && sibling.nodeType == node.nodeType) {
1751                         ec = node;
1752                         eo = node.length;
1753                         node.appendData(sibling.data);
1754                         sibling.parentNode.removeChild(sibling);
1755                     }
1756                 };
1757 
1758                 var mergeBackward = function(node) {
1759                     var sibling = node.previousSibling;
1760                     if (sibling && sibling.nodeType == node.nodeType) {
1761                         sc = node;
1762                         var nodeLength = node.length;
1763                         so = sibling.length;
1764                         node.insertData(0, sibling.data);
1765                         sibling.parentNode.removeChild(sibling);
1766                         if (sc == ec) {
1767                             eo += so;
1768                             ec = sc;
1769                         } else if (ec == node.parentNode) {
1770                             var nodeIndex = dom.getNodeIndex(node);
1771                             if (eo == nodeIndex) {
1772                                 ec = node;
1773                                 eo = nodeLength;
1774                             } else if (eo > nodeIndex) {
1775                                 eo--;
1776                             }
1777                         }
1778                     }
1779                 };
1780 
1781                 var normalizeStart = true;
1782 
1783                 if (dom.isCharacterDataNode(ec)) {
1784                     if (ec.length == eo) {
1785                         mergeForward(ec);
1786                     }
1787                 } else {
1788                     if (eo > 0) {
1789                         var endNode = ec.childNodes[eo - 1];
1790                         if (endNode && dom.isCharacterDataNode(endNode)) {
1791                             mergeForward(endNode);
1792                         }
1793                     }
1794                     normalizeStart = !this.collapsed;
1795                 }
1796 
1797                 if (normalizeStart) {
1798                     if (dom.isCharacterDataNode(sc)) {
1799                         if (so == 0) {
1800                             mergeBackward(sc);
1801                         }
1802                     } else {
1803                         if (so < sc.childNodes.length) {
1804                             var startNode = sc.childNodes[so];
1805                             if (startNode && dom.isCharacterDataNode(startNode)) {
1806                                 mergeBackward(startNode);
1807                             }
1808                         }
1809                     }
1810                 } else {
1811                     sc = ec;
1812                     so = eo;
1813                 }
1814 
1815                 boundaryUpdater(this, sc, so, ec, eo);
1816             },
1817 
1818             collapseToPoint: function(node, offset) {
1819                 assertNotDetached(this);
1820 
1821                 assertNoDocTypeNotationEntityAncestor(node, true);
1822                 assertValidOffset(node, offset);
1823 
1824                 setRangeStartAndEnd(this, node, offset);
1825             }
1826         });
1827 
1828         copyComparisonConstants(constructor);
1829     }
1830 
1831     /*----------------------------------------------------------------------------------------------------------------*/
1832 
1833     // Updates commonAncestorContainer and collapsed after boundary change
1834     function updateCollapsedAndCommonAncestor(range) {
1835         range.collapsed = (range.startContainer === range.endContainer && range.startOffset === range.endOffset);
1836         range.commonAncestorContainer = range.collapsed ?
1837             range.startContainer : dom.getCommonAncestor(range.startContainer, range.endContainer);
1838     }
1839 
1840     function updateBoundaries(range, startContainer, startOffset, endContainer, endOffset) {
1841         var startMoved = (range.startContainer !== startContainer || range.startOffset !== startOffset);
1842         var endMoved = (range.endContainer !== endContainer || range.endOffset !== endOffset);
1843 
1844         range.startContainer = startContainer;
1845         range.startOffset = startOffset;
1846         range.endContainer = endContainer;
1847         range.endOffset = endOffset;
1848 
1849         updateCollapsedAndCommonAncestor(range);
1850         dispatchEvent(range, "boundarychange", {startMoved: startMoved, endMoved: endMoved});
1851     }
1852 
1853     function detach(range) {
1854         assertNotDetached(range);
1855         range.startContainer = range.startOffset = range.endContainer = range.endOffset = null;
1856         range.collapsed = range.commonAncestorContainer = null;
1857         dispatchEvent(range, "detach", null);
1858         range._listeners = null;
1859     }
1860 
1861     /**
1862      * @constructor
1863      */
1864     function Range(doc) {
1865         this.startContainer = doc;
1866         this.startOffset = 0;
1867         this.endContainer = doc;
1868         this.endOffset = 0;
1869         this._listeners = {
1870             boundarychange: [],
1871             detach: []
1872         };
1873         updateCollapsedAndCommonAncestor(this);
1874     }
1875 
1876     createPrototypeRange(Range, updateBoundaries, detach);
1877 
1878     api.rangePrototype = RangePrototype.prototype;
1879 
1880     Range.rangeProperties = rangeProperties;
1881     Range.RangeIterator = RangeIterator;
1882     Range.copyComparisonConstants = copyComparisonConstants;
1883     Range.createPrototypeRange = createPrototypeRange;
1884     Range.inspect = inspect;
1885     Range.getRangeDocument = getRangeDocument;
1886     Range.rangesEqual = function(r1, r2) {
1887         return r1.startContainer === r2.startContainer &&
1888                r1.startOffset === r2.startOffset &&
1889                r1.endContainer === r2.endContainer &&
1890                r1.endOffset === r2.endOffset;
1891     };
1892 
1893     api.DomRange = Range;
1894     api.RangeException = RangeException;
1895 });rangy.createModule("WrappedRange", function(api, module) {
1896     api.requireModules( ["DomUtil", "DomRange"] );
1897 
1898     /**
1899      * @constructor
1900      */
1901     var WrappedRange;
1902     var dom = api.dom;
1903     var DomPosition = dom.DomPosition;
1904     var DomRange = api.DomRange;
1905 
1906 
1907 
1908     /*----------------------------------------------------------------------------------------------------------------*/
1909 
1910     /*
1911     This is a workaround for a bug where IE returns the wrong container element from the TextRange's parentElement()
1912     method. For example, in the following (where pipes denote the selection boundaries):
1913 
1914     <ul id="ul"><li id="a">| a </li><li id="b"> b |</li></ul>
1915 
1916     var range = document.selection.createRange();
1917     alert(range.parentElement().id); // Should alert "ul" but alerts "b"
1918 
1919     This method returns the common ancestor node of the following:
1920     - the parentElement() of the textRange
1921     - the parentElement() of the textRange after calling collapse(true)
1922     - the parentElement() of the textRange after calling collapse(false)
1923      */
1924     function getTextRangeContainerElement(textRange) {
1925         var parentEl = textRange.parentElement();
1926 
1927         var range = textRange.duplicate();
1928         range.collapse(true);
1929         var startEl = range.parentElement();
1930         range = textRange.duplicate();
1931         range.collapse(false);
1932         var endEl = range.parentElement();
1933         var startEndContainer = (startEl == endEl) ? startEl : dom.getCommonAncestor(startEl, endEl);
1934 
1935         return startEndContainer == parentEl ? startEndContainer : dom.getCommonAncestor(parentEl, startEndContainer);
1936     }
1937 
1938     function textRangeIsCollapsed(textRange) {
1939         return textRange.compareEndPoints("StartToEnd", textRange) == 0;
1940     }
1941 
1942     // Gets the boundary of a TextRange expressed as a node and an offset within that node. This function started out as
1943     // an improved version of code found in Tim Cameron Ryan's IERange (http://code.google.com/p/ierange/) but has
1944     // grown, fixing problems with line breaks in preformatted text, adding workaround for IE TextRange bugs, handling
1945     // for inputs and images, plus optimizations.
1946     function getTextRangeBoundaryPosition(textRange, wholeRangeContainerElement, isStart, isCollapsed) {
1947         var workingRange = textRange.duplicate();
1948 
1949         workingRange.collapse(isStart);
1950         var containerElement = workingRange.parentElement();
1951 
1952         // Sometimes collapsing a TextRange that's at the start of a text node can move it into the previous node, so
1953         // check for that
1954         // TODO: Find out when. Workaround for wholeRangeContainerElement may break this
1955         if (!dom.isAncestorOf(wholeRangeContainerElement, containerElement, true)) {
1956             containerElement = wholeRangeContainerElement;
1957 
1958         }
1959 
1960 
1961 
1962         // Deal with nodes that cannot "contain rich HTML markup". In practice, this means form inputs, images and
1963         // similar. See http://msdn.microsoft.com/en-us/library/aa703950%28VS.85%29.aspx
1964         if (!containerElement.canHaveHTML) {
1965             return new DomPosition(containerElement.parentNode, dom.getNodeIndex(containerElement));
1966         }
1967 
1968         var workingNode = dom.getDocument(containerElement).createElement("span");
1969         var comparison, workingComparisonType = isStart ? "StartToStart" : "StartToEnd";
1970         var previousNode, nextNode, boundaryPosition, boundaryNode;
1971 
1972         // Move the working range through the container's children, starting at the end and working backwards, until the
1973         // working range reaches or goes past the boundary we're interested in
1974         do {
1975             containerElement.insertBefore(workingNode, workingNode.previousSibling);
1976             workingRange.moveToElementText(workingNode);
1977         } while ( (comparison = workingRange.compareEndPoints(workingComparisonType, textRange)) > 0 &&
1978                 workingNode.previousSibling);
1979 
1980         // We've now reached or gone past the boundary of the text range we're interested in
1981         // so have identified the node we want
1982         boundaryNode = workingNode.nextSibling;
1983 
1984         if (comparison == -1 && boundaryNode && dom.isCharacterDataNode(boundaryNode)) {
1985             // This is a character data node (text, comment, cdata). The working range is collapsed at the start of the
1986             // node containing the text range's boundary, so we move the end of the working range to the boundary point
1987             // and measure the length of its text to get the boundary's offset within the node.
1988             workingRange.setEndPoint(isStart ? "EndToStart" : "EndToEnd", textRange);
1989 
1990 
1991             var offset;
1992 
1993             if (/[\r\n]/.test(boundaryNode.data)) {
1994                 /*
1995                 For the particular case of a boundary within a text node containing line breaks (within a <pre> element,
1996                 for example), we need a slightly complicated approach to get the boundary's offset in IE. The facts:
1997 
1998                 - Each line break is represented as \r in the text node's data/nodeValue properties
1999                 - Each line break is represented as \r\n in the TextRange's 'text' property
2000                 - The 'text' property of the TextRange does not contain trailing line breaks
2001 
2002                 To get round the problem presented by the final fact above, we can use the fact that TextRange's
2003                 moveStart() and moveEnd() methods return the actual number of characters moved, which is not necessarily
2004                 the same as the number of characters it was instructed to move. The simplest approach is to use this to
2005                 store the characters moved when moving both the start and end of the range to the start of the document
2006                 body and subtracting the start offset from the end offset (the "move-negative-gazillion" method).
2007                 However, this is extremely slow when the document is large and the range is near the end of it. Clearly
2008                 doing the mirror image (i.e. moving the range boundaries to the end of the document) has the same
2009                 problem.
2010 
2011                 Another approach that works is to use moveStart() to move the start boundary of the range up to the end
2012                 boundary one character at a time and incrementing a counter with the value returned by the moveStart()
2013                 call. However, the check for whether the start boundary has reached the end boundary is expensive, so
2014                 this method is slow (although unlike "move-negative-gazillion" is largely unaffected by the location of
2015                 the range within the document).
2016 
2017                 The method below is a hybrid of the two methods above. It uses the fact that a string containing the
2018                 TextRange's 'text' property with each \r\n converted to a single \r character cannot be longer than the
2019                 text of the TextRange, so the start of the range is moved that length initially and then a character at
2020                 a time to make up for any trailing line breaks not contained in the 'text' property. This has good
2021                 performance in most situations compared to the previous two methods.
2022                 */
2023                 var tempRange = workingRange.duplicate();
2024                 var rangeLength = tempRange.text.replace(/\r\n/g, "\r").length;
2025 
2026                 offset = tempRange.moveStart("character", rangeLength);
2027                 while ( (comparison = tempRange.compareEndPoints("StartToEnd", tempRange)) == -1) {
2028                     offset++;
2029                     tempRange.moveStart("character", 1);
2030                 }
2031             } else {
2032             	// IE7 sometimes has weird workingranges that apparently do not start in the workingNode any more, but in
2033             	// some kind of phantom paragraph, that cannot be found in the DOM.
2034             	// in such situations, the workingRange.text no longer is a substring at the start of the boundaryNode.data
2035             	// If we find such a situation, we skip all characters at the start of the workingRange.data, that are not
2036             	// at the start of the boundaryNode.data.
2037             	// Before comparing, we have to replace all nbsp with normal spaces
2038             	var wrText = workingRange.text.replace(/\u00a0/g, " ");
2039             	var bnText = boundaryNode.data.replace(/\u00a0/g, " ");
2040             	if (bnText.indexOf(wrText) !== 0) {
2041             		while (wrText.length > 0 && bnText.indexOf(wrText) !== 0) {
2042             			wrText = wrText.substr(1);
2043             		}
2044             		offset = wrText.length;
2045             	} else {
2046             		offset = workingRange.text.length;
2047             	}
2048             }
2049             boundaryPosition = new DomPosition(boundaryNode, offset);
2050         } else {
2051 
2052 
2053             // If the boundary immediately follows a character data node and this is the end boundary, we should favour
2054             // a position within that, and likewise for a start boundary preceding a character data node
2055             previousNode = (isCollapsed || !isStart) && workingNode.previousSibling;
2056             nextNode = (isCollapsed || isStart) && workingNode.nextSibling;
2057 
2058 
2059 
2060             if (nextNode && dom.isCharacterDataNode(nextNode)) {
2061                 boundaryPosition = new DomPosition(nextNode, 0);
2062             } else if (previousNode && dom.isCharacterDataNode(previousNode)) {
2063                 boundaryPosition = new DomPosition(previousNode, previousNode.length);
2064             } else {
2065                 boundaryPosition = new DomPosition(containerElement, dom.getNodeIndex(workingNode));
2066             }
2067         }
2068 
2069         // Clean up
2070         workingNode.parentNode.removeChild(workingNode);
2071 
2072         return boundaryPosition;
2073     }
2074 
2075     // Returns a TextRange representing the boundary of a TextRange expressed as a node and an offset within that node.
2076     // This function started out as an optimized version of code found in Tim Cameron Ryan's IERange
2077     // (http://code.google.com/p/ierange/)
2078     function createBoundaryTextRange(boundaryPosition, isStart) {
2079         var boundaryNode, boundaryParent, boundaryOffset = boundaryPosition.offset;
2080         var doc = dom.getDocument(boundaryPosition.node);
2081         var workingNode, childNodes, workingRange = doc.body.createTextRange();
2082         var nodeIsDataNode = dom.isCharacterDataNode(boundaryPosition.node);
2083 
2084         if (nodeIsDataNode) {
2085             boundaryNode = boundaryPosition.node;
2086             boundaryParent = boundaryNode.parentNode;
2087         } else {
2088             childNodes = boundaryPosition.node.childNodes;
2089             boundaryNode = (boundaryOffset < childNodes.length) ? childNodes[boundaryOffset] : null;
2090             boundaryParent = boundaryPosition.node;
2091         }
2092 
2093         // Position the range immediately before the node containing the boundary
2094         workingNode = doc.createElement("span");
2095 
2096         // Making the working element non-empty element persuades IE to consider the TextRange boundary to be within the
2097         // element rather than immediately before or after it, which is what we want
2098         workingNode.innerHTML = "&#feff;";
2099 
2100         // insertBefore is supposed to work like appendChild if the second parameter is null. However, a bug report
2101         // for IERange suggests that it can crash the browser: http://code.google.com/p/ierange/issues/detail?id=12
2102         if (boundaryNode) {
2103             boundaryParent.insertBefore(workingNode, boundaryNode);
2104         } else {
2105             boundaryParent.appendChild(workingNode);
2106         }
2107 
2108 		try {
2109 			workingRange.moveToElementText(workingNode);
2110 	        workingRange.collapse(!isStart);
2111 		} catch ( err ) {
2112 			// @todo window.console.log('problem with moveToElementText');
2113 			//return false;
2114 		}
2115 
2116 		// Clean up
2117 		boundaryParent.removeChild(workingNode);
2118 
2119 		// Move the working range to the text offset, if required
2120 		if (nodeIsDataNode) {
2121 			workingRange[isStart ? "moveStart" : "moveEnd"]("character", boundaryOffset);
2122 		}
2123 
2124 		return workingRange;
2125     }
2126 
2127     /*----------------------------------------------------------------------------------------------------------------*/
2128 
2129     if (api.features.implementsDomRange && (!api.features.implementsTextRange || !api.config.preferTextRange)) {
2130         // This is a wrapper around the browser's native DOM Range. It has two aims:
2131         // - Provide workarounds for specific browser bugs
2132         // - provide convenient extensions, which are inherited from Rangy's DomRange
2133 
2134         (function() {
2135             var rangeProto;
2136             var rangeProperties = DomRange.rangeProperties;
2137             var canSetRangeStartAfterEnd;
2138 
2139             function updateRangeProperties(range) {
2140                 var i = rangeProperties.length, prop;
2141                 while (i--) {
2142                     prop = rangeProperties[i];
2143                     range[prop] = range.nativeRange[prop];
2144                 }
2145             }
2146 
2147             function updateNativeRange(range, startContainer, startOffset, endContainer,endOffset) {
2148                 var startMoved = (range.startContainer !== startContainer || range.startOffset != startOffset);
2149                 var endMoved = (range.endContainer !== endContainer || range.endOffset != endOffset);
2150 
2151                 // Always set both boundaries for the benefit of IE9 (see issue 35)
2152                 if (startMoved || endMoved) {
2153                     range.setEnd(endContainer, endOffset);
2154                     range.setStart(startContainer, startOffset);
2155                 }
2156             }
2157 
2158             function detach(range) {
2159                 range.nativeRange.detach();
2160                 range.detached = true;
2161                 var i = rangeProperties.length, prop;
2162                 while (i--) {
2163                     prop = rangeProperties[i];
2164                     range[prop] = null;
2165                 }
2166             }
2167 
2168             var createBeforeAfterNodeSetter;
2169 
2170             WrappedRange = function(range) {
2171                 if (!range) {
2172                     throw new Error("Range must be specified");
2173                 }
2174                 this.nativeRange = range;
2175                 updateRangeProperties(this);
2176             };
2177 
2178             DomRange.createPrototypeRange(WrappedRange, updateNativeRange, detach);
2179 
2180             rangeProto = WrappedRange.prototype;
2181 
2182             rangeProto.selectNode = function(node) {
2183                 this.nativeRange.selectNode(node);
2184                 updateRangeProperties(this);
2185             };
2186 
2187             rangeProto.deleteContents = function() {
2188                 this.nativeRange.deleteContents();
2189                 updateRangeProperties(this);
2190             };
2191 
2192             rangeProto.extractContents = function() {
2193                 var frag = this.nativeRange.extractContents();
2194                 updateRangeProperties(this);
2195                 return frag;
2196             };
2197 
2198             rangeProto.cloneContents = function() {
2199                 return this.nativeRange.cloneContents();
2200             };
2201 
2202             // TODO: Until I can find a way to programmatically trigger the Firefox bug (apparently long-standing, still
2203             // present in 3.6.8) that throws "Index or size is negative or greater than the allowed amount" for
2204             // insertNode in some circumstances, all browsers will have to use the Rangy's own implementation of
2205             // insertNode, which works but is almost certainly slower than the native implementation.
2206 /*
2207             rangeProto.insertNode = function(node) {
2208                 this.nativeRange.insertNode(node);
2209                 updateRangeProperties(this);
2210             };
2211 */
2212 
2213 2214             rangeProto.surroundContents = function(node) {
2215                 this.nativeRange.surroundContents(node);
2216                 updateRangeProperties(this);
2217             };
2218 
2219             rangeProto.collapse = function(isStart) {
2220                 this.nativeRange.collapse(isStart);
2221                 updateRangeProperties(this);
2222 2223             };
2224 
2225             rangeProto.cloneRange = function() {
2226                 return new WrappedRange(this.nativeRange.cloneRange());
2227             };
2228 
2229             rangeProto.refresh = function() {
2230                 updateRangeProperties(this);
2231             };
2232 
2233             rangeProto.toString = function() {
2234                 return this.nativeRange.toString();
2235             };
2236 
2237             // Create test range and node for feature detection
2238 
2239             var testTextNode = document.createTextNode("test");
2240             dom.getBody(document).appendChild(testTextNode);
2241             var range = document.createRange();
2242 
2243             /*--------------------------------------------------------------------------------------------------------*/
2244 
2245             // Test for Firefox 2 bug that prevents moving the start of a Range to a point after its current end and
2246             // correct for it
2247 
2248             range.setStart(testTextNode, 0);
2249             range.setEnd(testTextNode, 0);
2250 
2251             try {
2252                 range.setStart(testTextNode, 1);
2253                 canSetRangeStartAfterEnd = true;
2254 
2255                 rangeProto.setStart = function(node, offset) {
2256                     this.nativeRange.setStart(node, offset);
2257                     updateRangeProperties(this);
2258                 };
2259 
2260                 rangeProto.setEnd = function(node, offset) {
2261                     this.nativeRange.setEnd(node, offset);
2262                     updateRangeProperties(this);
2263                 };
2264 
2265                 createBeforeAfterNodeSetter = function(name) {
2266                     return function(node) {
2267                         this.nativeRange[name](node);
2268                         updateRangeProperties(this);
2269                     };
2270                 };
2271 
2272             } catch(ex) {
2273 
2274 
2275                 canSetRangeStartAfterEnd = false;
2276 
2277                 rangeProto.setStart = function(node, offset) {
2278                     try {
2279                         this.nativeRange.setStart(node, offset);
2280                     } catch (ex) {
2281                         this.nativeRange.setEnd(node, offset);
2282                         this.nativeRange.setStart(node, offset);
2283                     }
2284                     updateRangeProperties(this);
2285                 };
2286 
2287                 rangeProto.setEnd = function(node, offset) {
2288                     try {
2289                         this.nativeRange.setEnd(node, offset);
2290                     } catch (ex) {
2291                         this.nativeRange.setStart(node, offset);
2292                         this.nativeRange.setEnd(node, offset);
2293                     }
2294                     updateRangeProperties(this);
2295                 };
2296 
2297                 createBeforeAfterNodeSetter = function(name, oppositeName) {
2298                     return function(node) {
2299                         try {
2300                             this.nativeRange[name](node);
2301                         } catch (ex) {
2302                             this.nativeRange[oppositeName](node);
2303                             this.nativeRange[name](node);
2304                         }
2305                         updateRangeProperties(this);
2306                     };
2307                 };
2308             }
2309 
2310             rangeProto.setStartBefore = createBeforeAfterNodeSetter("setStartBefore", "setEndBefore");
2311             rangeProto.setStartAfter = createBeforeAfterNodeSetter("setStartAfter", "setEndAfter");
2312             rangeProto.setEndBefore = createBeforeAfterNodeSetter("setEndBefore", "setStartBefore");
2313             rangeProto.setEndAfter = createBeforeAfterNodeSetter("setEndAfter", "setStartAfter");
2314 
2315             /*--------------------------------------------------------------------------------------------------------*/
2316 
2317             // Test for and correct Firefox 2 behaviour with selectNodeContents on text nodes: it collapses the range to
2318             // the 0th character of the text node
2319             range.selectNodeContents(testTextNode);
2320             if (range.startContainer == testTextNode && range.endContainer == testTextNode &&
2321                     range.startOffset == 0 && range.endOffset == testTextNode.length) {
2322                 rangeProto.selectNodeContents = function(node) {
2323                     this.nativeRange.selectNodeContents(node);
2324                     updateRangeProperties(this);
2325                 };
2326             } else {
2327                 rangeProto.selectNodeContents = function(node) {
2328                     this.setStart(node, 0);
2329                     this.setEnd(node, DomRange.getEndOffset(node));
2330                 };
2331             }
2332 
2333             /*--------------------------------------------------------------------------------------------------------*/
2334 
2335             // Test for WebKit bug that has the beahviour of compareBoundaryPoints round the wrong way for constants
2336             // START_TO_END and END_TO_START: https://bugs.webkit.org/show_bug.cgi?id=20738
2337 
2338             range.selectNodeContents(testTextNode);
2339             range.setEnd(testTextNode, 3);
2340 
2341             var range2 = document.createRange();
2342             range2.selectNodeContents(testTextNode);
2343             range2.setEnd(testTextNode, 4);
2344             range2.setStart(testTextNode, 2);
2345 
2346             if (range.compareBoundaryPoints(range.START_TO_END, range2) == -1 &
2347                     range.compareBoundaryPoints(range.END_TO_START, range2) == 1) {
2348                 // This is the wrong way round, so correct for it
2349 
2350 
2351                 rangeProto.compareBoundaryPoints = function(type, range) {
2352                     range = range.nativeRange || range;
2353                     if (type == range.START_TO_END) {
2354                         type = range.END_TO_START;
2355                     } else if (type == range.END_TO_START) {
2356                         type = range.START_TO_END;
2357                     }
2358                     return this.nativeRange.compareBoundaryPoints(type, range);
2359                 };
2360             } else {
2361                 rangeProto.compareBoundaryPoints = function(type, range) {
2362                     return this.nativeRange.compareBoundaryPoints(type, range.nativeRange || range);
2363                 };
2364             }
2365 
2366             /*--------------------------------------------------------------------------------------------------------*/
2367 
2368             // Test for existence of createContextualFragment and delegate to it if it exists
2369             if (api.util.isHostMethod(range, "createContextualFragment")) {
2370                 rangeProto.createContextualFragment = function(fragmentStr) {
2371                     return this.nativeRange.createContextualFragment(fragmentStr);
2372                 };
2373             }
2374 
2375             /*--------------------------------------------------------------------------------------------------------*/
2376 
2377             // Clean up
2378             dom.getBody(document).removeChild(testTextNode);
2379             range.detach();
2380             range2.detach();
2381         })();
2382 
2383         api.createNativeRange = function(doc) {
2384             doc = doc || document;
2385             return doc.createRange();
2386         };
2387     } else if (api.features.implementsTextRange) {
2388         // This is a wrapper around a TextRange, providing full DOM Range functionality using rangy's DomRange as a
2389         // prototype
2390 
2391         WrappedRange = function(textRange) {
2392             this.textRange = textRange;
2393             this.refresh();
2394         };
2395 
2396         WrappedRange.prototype = new DomRange(document);
2397 
2398         WrappedRange.prototype.refresh = function() {
2399             var start, end;
2400 
2401             // TextRange's parentElement() method cannot be trusted. getTextRangeContainerElement() works around that.
2402             var rangeContainerElement = getTextRangeContainerElement(this.textRange);
2403 
2404             if (textRangeIsCollapsed(this.textRange)) {
2405                 end = start = getTextRangeBoundaryPosition(this.textRange, rangeContainerElement, true, true);
2406             } else {
2407 
2408                 start = getTextRangeBoundaryPosition(this.textRange, rangeContainerElement, true, false);
2409                 end = getTextRangeBoundaryPosition(this.textRange, rangeContainerElement, false, false);
2410             }
2411 
2412             this.setStart(start.node, start.offset);
2413             this.setEnd(end.node, end.offset);
2414         };
2415 
2416         DomRange.copyComparisonConstants(WrappedRange);
2417 
2418         // Add WrappedRange as the Range property of the global object to allow expression like Range.END_TO_END to work
2419         var globalObj = (function() { return this; })();
2420         if (typeof globalObj.Range == "undefined") {
2421             globalObj.Range = WrappedRange;
2422         }
2423 
2424         api.createNativeRange = function(doc) {
2425             doc = doc || document;
2426             return doc.body.createTextRange();
2427         };
2428     }
2429 
2430     if (api.features.implementsTextRange) {
2431         WrappedRange.rangeToTextRange = function(range) {
2432             if (range.collapsed) {
2433                 var tr = createBoundaryTextRange(new DomPosition(range.startContainer, range.startOffset), true);
2434                 return tr;
2435                 //return createBoundaryTextRange(new DomPosition(range.startContainer, range.startOffset), true);
2436             } else {
2437                 var startRange = createBoundaryTextRange(new DomPosition(range.startContainer, range.startOffset), true);
2438                 var endRange = createBoundaryTextRange(new DomPosition(range.endContainer, range.endOffset), false);
2439                 var textRange = dom.getDocument(range.startContainer).body.createTextRange();
2440                 textRange.setEndPoint("StartToStart", startRange);
2441                 textRange.setEndPoint("EndToEnd", endRange);
2442                 return textRange;
2443             }
2444         };
2445     }
2446 
2447     WrappedRange.prototype.getName = function() {
2448         return "WrappedRange";
2449     };
2450 
2451     api.WrappedRange = WrappedRange;
2452 
2453     api.createRange = function(doc) {
2454         doc = doc || document;
2455         return new WrappedRange(api.createNativeRange(doc));
2456     };
2457 
2458     api.createRangyRange = function(doc) {
2459         doc = doc || document;
2460         return new DomRange(doc);
2461     };
2462 
2463     api.createIframeRange = function(iframeEl) {
2464         return api.createRange(dom.getIframeDocument(iframeEl));
2465     };
2466 
2467     api.createIframeRangyRange = function(iframeEl) {
2468         return api.createRangyRange(dom.getIframeDocument(iframeEl));
2469     };
2470 
2471     api.addCreateMissingNativeApiListener(function(win) {
2472         var doc = win.document;
2473         if (typeof doc.createRange == "undefined") {
2474             doc.createRange = function() {
2475                 return api.createRange(this);
2476             };
2477         }
2478         doc = win = null;
2479     });
2480 });rangy.createModule("WrappedSelection", function(api, module) {
2481     // This will create a selection object wrapper that follows the Selection object found in the WHATWG draft DOM Range
2482     // spec (http://html5.org/specs/dom-range.html)
2483 
2484     api.requireModules( ["DomUtil", "DomRange", "WrappedRange"] );
2485 
2486     api.config.checkSelectionRanges = true;
2487 
2488     var BOOLEAN = "boolean",
2489         windowPropertyName = "_rangySelection",
2490         dom = api.dom,
2491         util = api.util,
2492         DomRange = api.DomRange,
2493         WrappedRange = api.WrappedRange,
2494         DOMException = api.DOMException,
2495         DomPosition = dom.DomPosition,
2496         getSelection,
2497         selectionIsCollapsed,
2498         CONTROL = "Control";
2499 
2500 
2501 
2502     function getWinSelection(winParam) {
2503         return (winParam || window).getSelection();
2504     }
2505 
2506     function getDocSelection(winParam) {
2507         return (winParam || window).document.selection;
2508     }
2509 
2510     // Test for the Range/TextRange and Selection features required
2511     // Test for ability to retrieve selection
2512     var implementsWinGetSelection = api.util.isHostMethod(window, "getSelection"),
2513         implementsDocSelection = api.util.isHostObject(document, "selection");
2514 
2515     var useDocumentSelection = implementsDocSelection && (!implementsWinGetSelection || api.config.preferTextRange);
2516 
2517     if (useDocumentSelection) {
2518         getSelection = getDocSelection;
2519         api.isSelectionValid = function(winParam) {
2520             var doc = (winParam || window).document, nativeSel = doc.selection;
2521 
2522             // Check whether the selection TextRange is actually contained within the correct document
2523             return (nativeSel.type != "None" || dom.getDocument(nativeSel.createRange().parentElement()) == doc);
2524         };
2525     } else if (implementsWinGetSelection) {
2526         getSelection = getWinSelection;
2527         api.isSelectionValid = function() {
2528             return true;
2529         };
2530     } else {
2531         module.fail("Neither document.selection or window.getSelection() detected.");
2532     }
2533 
2534     api.getNativeSelection = getSelection;
2535 
2536     var testSelection = getSelection();
2537     var testRange = api.createNativeRange(document);
2538     var body = dom.getBody(document);
2539 
2540     // Obtaining a range from a selection
2541     var selectionHasAnchorAndFocus = util.areHostObjects(testSelection, ["anchorNode", "focusNode"] &&
2542                                      util.areHostProperties(testSelection, ["anchorOffset", "focusOffset"]));
2543     api.features.selectionHasAnchorAndFocus = selectionHasAnchorAndFocus;
2544 
2545     // Test for existence of native selection extend() method
2546     var selectionHasExtend = util.isHostMethod(testSelection, "extend");
2547     api.features.selectionHasExtend = selectionHasExtend;
2548 
2549     // Test if rangeCount exists
2550     var selectionHasRangeCount = (typeof testSelection.rangeCount == "number");
2551     api.features.selectionHasRangeCount = selectionHasRangeCount;
2552 
2553     var selectionSupportsMultipleRanges = false;
2554     var collapsedNonEditableSelectionsSupported = true;
2555 
2556     if (util.areHostMethods(testSelection, ["addRange", "getRangeAt", "removeAllRanges"]) &&
2557             typeof testSelection.rangeCount == "number" && api.features.implementsDomRange) {
2558 
2559         (function() {
2560             var iframe = document.createElement("iframe");
2561             body.appendChild(iframe);
2562 
2563             var iframeDoc = dom.getIframeDocument(iframe);
2564             iframeDoc.open();
2565             iframeDoc.write("<html><head></head><body>12</body></html>");
2566             iframeDoc.close();
2567 
2568             var sel = dom.getIframeWindow(iframe).getSelection();
2569             var docEl = iframeDoc.documentElement;
2570             var iframeBody = docEl.lastChild, textNode = iframeBody.firstChild;
2571 
2572             // Test whether the native selection will allow a collapsed selection within a non-editable element
2573             var r1 = iframeDoc.createRange();
2574             r1.setStart(textNode, 1);
2575             r1.collapse(true);
2576             sel.addRange(r1);
2577             collapsedNonEditableSelectionsSupported = (sel.rangeCount == 1);
2578             sel.removeAllRanges();
2579 
2580             // Test whether the native selection is capable of supporting multiple ranges
2581             var r2 = r1.cloneRange();
2582             r1.setStart(textNode, 0);
2583             r2.setEnd(textNode, 2);
2584             sel.addRange(r1);
2585             sel.addRange(r2);
2586 
2587             selectionSupportsMultipleRanges = (sel.rangeCount == 2);
2588 
2589             // Clean up
2590             r1.detach();
2591             r2.detach();
2592 
2593             body.removeChild(iframe);
2594         })();
2595     }
2596 
2597     api.features.selectionSupportsMultipleRanges = selectionSupportsMultipleRanges;
2598     api.features.collapsedNonEditableSelectionsSupported = collapsedNonEditableSelectionsSupported;
2599 
2600     // ControlRanges
2601     var implementsControlRange = false, testControlRange;
2602 
2603     if (body && util.isHostMethod(body, "createControlRange")) {
2604         testControlRange = body.createControlRange();
2605         if (util.areHostProperties(testControlRange, ["item", "add"])) {
2606             implementsControlRange = true;
2607         }
2608     }
2609     api.features.implementsControlRange = implementsControlRange;
2610 
2611     // Selection collapsedness
2612     if (selectionHasAnchorAndFocus) {
2613         selectionIsCollapsed = function(sel) {
2614             return sel.anchorNode === sel.focusNode && sel.anchorOffset === sel.focusOffset;
2615         };
2616     } else {
2617         selectionIsCollapsed = function(sel) {
2618             return sel.rangeCount ? sel.getRangeAt(sel.rangeCount - 1).collapsed : false;
2619         };
2620     }
2621 
2622     function updateAnchorAndFocusFromRange(sel, range, backwards) {
2623         var anchorPrefix = backwards ? "end" : "start", focusPrefix = backwards ? "start" : "end";
2624         sel.anchorNode = range[anchorPrefix + "Container"];
2625         sel.anchorOffset = range[anchorPrefix + "Offset"];
2626         sel.focusNode = range[focusPrefix + "Container"];
2627         sel.focusOffset = range[focusPrefix + "Offset"];
2628     }
2629 
2630     function updateAnchorAndFocusFromNativeSelection(sel) {
2631         var nativeSel = sel.nativeSelection;
2632         sel.anchorNode = nativeSel.anchorNode;
2633         sel.anchorOffset = nativeSel.anchorOffset;
2634         sel.focusNode = nativeSel.focusNode;
2635         sel.focusOffset = nativeSel.focusOffset;
2636     }
2637 
2638     function updateEmptySelection(sel) {
2639         sel.anchorNode = sel.focusNode = null;
2640 2641         sel.anchorOffset = sel.focusOffset = 0;
2642         sel.rangeCount = 0;
2643         sel.isCollapsed = true;
2644         sel._ranges.length = 0;
2645     }
2646 
2647     function getNativeRange(range) {
2648         var nativeRange;
2649         if (range instanceof DomRange) {
2650             nativeRange = range._selectionNativeRange;
2651             if (!nativeRange) {
2652                 nativeRange = api.createNativeRange(dom.getDocument(range.startContainer));
2653                 nativeRange.setEnd(range.endContainer, range.endOffset);
2654                 nativeRange.setStart(range.startContainer, range.startOffset);
2655                 range._selectionNativeRange = nativeRange;
2656                 range.attachListener("detach", function() {
2657 
2658                     this._selectionNativeRange = null;
2659                 });
2660             }
2661         } else if (range instanceof WrappedRange) {
2662             nativeRange = range.nativeRange;
2663         } else if (api.features.implementsDomRange && (range instanceof dom.getWindow(range.startContainer).Range)) {
2664             nativeRange = range;
2665         }
2666         return nativeRange;
2667     }
2668 
2669     function rangeContainsSingleElement(rangeNodes) {
2670         if (!rangeNodes.length || rangeNodes[0].nodeType != 1) {
2671             return false;
2672         }
2673         for (var i = 1, len = rangeNodes.length; i < len; ++i) {
2674             if (!dom.isAncestorOf(rangeNodes[0], rangeNodes[i])) {
2675                 return false;
2676             }
2677         }
2678         return true;
2679     }
2680 
2681     function getSingleElementFromRange(range) {
2682         var nodes = range.getNodes();
2683         if (!rangeContainsSingleElement(nodes)) {
2684             throw new Error("getSingleElementFromRange: range " + range.inspect() + " did not consist of a single element");
2685         }
2686         return nodes[0];
2687     }
2688 
2689     function isTextRange(range) {
2690         return !!range && typeof range.text != "undefined";
2691     }
2692 
2693     function updateFromTextRange(sel, range) {
2694         // Create a Range from the selected TextRange
2695         var wrappedRange = new WrappedRange(range);
2696         sel._ranges = [wrappedRange];
2697 
2698 2699         updateAnchorAndFocusFromRange(sel, wrappedRange, false);
2700         sel.rangeCount = 1;
2701         sel.isCollapsed = wrappedRange.collapsed;
2702     }
2703 
2704     function updateControlSelection(sel) {
2705         // Update the wrapped selection based on what's now in the native selection
2706         sel._ranges.length = 0;
2707         if (sel.docSelection.type == "None") {
2708             updateEmptySelection(sel);
2709         } else {
2710             var controlRange = sel.docSelection.createRange();
2711             if (isTextRange(controlRange)) {
2712                 // This case (where the selection type is "Control" and calling createRange() on the selection returns
2713                 // a TextRange) can happen in IE 9. It happens, for example, when all elements in the selected
2714                 // ControlRange have been removed from the ControlRange and removed from the document.
2715                 updateFromTextRange(sel, controlRange);
2716             } else {
2717                 sel.rangeCount = controlRange.length;
2718                 var range, doc = dom.getDocument(controlRange.item(0));
2719                 for (var i = 0; i < sel.rangeCount; ++i) {
2720                     range = api.createRange(doc);
2721                     range.selectNode(controlRange.item(i));
2722                     sel._ranges.push(range);
2723                 }
2724                 sel.isCollapsed = sel.rangeCount == 1 && sel._ranges[0].collapsed;
2725                 updateAnchorAndFocusFromRange(sel, sel._ranges[sel.rangeCount - 1], false);
2726             }
2727         }
2728     }
2729 
2730     function addRangeToControlSelection(sel, range) {
2731         var controlRange = sel.docSelection.createRange();
2732         var rangeElement = getSingleElementFromRange(range);
2733 
2734         // Create a new ControlRange containing all the elements in the selected ControlRange plus the element
2735         // contained by the supplied range
2736         var doc = dom.getDocument(controlRange.item(0));
2737         var newControlRange = dom.getBody(doc).createControlRange();
2738         for (var i = 0, len = controlRange.length; i < len; ++i) {
2739             newControlRange.add(controlRange.item(i));
2740         }
2741         try {
2742             newControlRange.add(rangeElement);
2743         } catch (ex) {
2744             throw new Error("addRange(): Element within the specified Range could not be added to control selection (does it have layout?)");
2745         }
2746         newControlRange.select();
2747 
2748         // Update the wrapped selection based on what's now in the native selection
2749         updateControlSelection(sel);
2750     }
2751 
2752     var getSelectionRangeAt;
2753 
2754     if (util.isHostMethod(testSelection,  "getRangeAt")) {
2755         getSelectionRangeAt = function(sel, index) {
2756             try {
2757                 return sel.getRangeAt(index);
2758             } catch(ex) {
2759                 return null;
2760             }
2761         };
2762     } else if (selectionHasAnchorAndFocus) {
2763         getSelectionRangeAt = function(sel) {
2764             var doc = dom.getDocument(sel.anchorNode);
2765             var range = api.createRange(doc);
2766             range.setStart(sel.anchorNode, sel.anchorOffset);
2767             range.setEnd(sel.focusNode, sel.focusOffset);
2768 
2769             // Handle the case when the selection was selected backwards (from the end to the start in the
2770             // document)
2771             if (range.collapsed !== this.isCollapsed) {
2772                 range.setStart(sel.focusNode, sel.focusOffset);
2773                 range.setEnd(sel.anchorNode, sel.anchorOffset);
2774             }
2775 
2776             return range;
2777         };
2778     }
2779 
2780     /**
2781      * @constructor
2782      */
2783     function WrappedSelection(selection, docSelection, win) {
2784         this.nativeSelection = selection;
2785         this.docSelection = docSelection;
2786         this._ranges = [];
2787         this.win = win;
2788         this.refresh();
2789     }
2790 
2791     api.getSelection = function(win) {
2792         win = win || window;
2793         var sel = win[windowPropertyName];
2794         var nativeSel = getSelection(win), docSel = implementsDocSelection ? getDocSelection(win) : null;
2795         if (sel) {
2796             sel.nativeSelection = nativeSel;
2797             sel.docSelection = docSel;
2798             sel.refresh(win);
2799         } else {
2800             sel = new WrappedSelection(nativeSel, docSel, win);
2801             win[windowPropertyName] = sel;
2802         }
2803         return sel;
2804     };
2805 
2806     api.getIframeSelection = function(iframeEl) {
2807         return api.getSelection(dom.getIframeWindow(iframeEl));
2808     };
2809 
2810     var selProto = WrappedSelection.prototype;
2811 
2812     function createControlSelection(sel, ranges) {
2813         // Ensure that the selection becomes of type "Control"
2814         var doc = dom.getDocument(ranges[0].startContainer);
2815         var controlRange = dom.getBody(doc).createControlRange();
2816         for (var i = 0, el; i < rangeCount; ++i) {
2817             el = getSingleElementFromRange(ranges[i]);
2818             try {
2819                 controlRange.add(el);
2820             } catch (ex) {
2821                 throw new Error("setRanges(): Element within the one of the specified Ranges could not be added to control selection (does it have layout?)");
2822             }
2823         }
2824         controlRange.select();
2825 
2826         // Update the wrapped selection based on what's now in the native selection
2827         updateControlSelection(sel);
2828     }
2829 
2830     // Selecting a range
2831     if (!useDocumentSelection && selectionHasAnchorAndFocus && util.areHostMethods(testSelection, ["removeAllRanges", "addRange"])) {
2832         selProto.removeAllRanges = function() {
2833             this.nativeSelection.removeAllRanges();
2834             updateEmptySelection(this);
2835         };
2836 
2837         var addRangeBackwards = function(sel, range) {
2838             var doc = DomRange.getRangeDocument(range);
2839             var endRange = api.createRange(doc);
2840             endRange.collapseToPoint(range.endContainer, range.endOffset);
2841             sel.nativeSelection.addRange(getNativeRange(endRange));
2842             sel.nativeSelection.extend(range.startContainer, range.startOffset);
2843             sel.refresh();
2844         };
2845 
2846         if (selectionHasRangeCount) {
2847             selProto.addRange = function(range, backwards) {
2848                 if (implementsControlRange && implementsDocSelection && this.docSelection.type == CONTROL) {
2849                     addRangeToControlSelection(this, range);
2850                 } else {
2851                     if (backwards && selectionHasExtend) {
2852                         addRangeBackwards(this, range);
2853                     } else {
2854                         var previousRangeCount;
2855                         if (selectionSupportsMultipleRanges) {
2856                             previousRangeCount = this.rangeCount;
2857                         } else {
2858                             this.removeAllRanges();
2859                             previousRangeCount = 0;
2860                         }
2861                         this.nativeSelection.addRange(getNativeRange(range));
2862 
2863                         // Check whether adding the range was successful
2864                         this.rangeCount = this.nativeSelection.rangeCount;
2865 
2866                         if (this.rangeCount == previousRangeCount + 1) {
2867                             // The range was added successfully
2868 
2869                             // Check whether the range that we added to the selection is reflected in the last range extracted from
2870                             // the selection
2871                             if (api.config.checkSelectionRanges) {
2872                                 var nativeRange = getSelectionRangeAt(this.nativeSelection, this.rangeCount - 1);
2873                                 if (nativeRange && !DomRange.rangesEqual(nativeRange, range)) {
2874                                     // Happens in WebKit with, for example, a selection placed at the start of a text node
2875                                     range = new WrappedRange(nativeRange);
2876                                 }
2877                             }
2878                             this._ranges[this.rangeCount - 1] = range;
2879                             updateAnchorAndFocusFromRange(this, range, selectionIsBackwards(this.nativeSelection));
2880                             this.isCollapsed = selectionIsCollapsed(this);
2881                         } else {
2882                             // The range was not added successfully. The simplest thing is to refresh
2883                             this.refresh();
2884                         }
2885                     }
2886                 }
2887             };
2888         } else {
2889             selProto.addRange = function(range, backwards) {
2890                 if (backwards && selectionHasExtend) {
2891                     addRangeBackwards(this, range);
2892                 } else {
2893                     this.nativeSelection.addRange(getNativeRange(range));
2894                     this.refresh();
2895                 }
2896             };
2897         }
2898 
2899         selProto.setRanges = function(ranges) {
2900             if (implementsControlRange && ranges.length > 1) {
2901                 createControlSelection(this, ranges);
2902             } else {
2903                 this.removeAllRanges();
2904                 for (var i = 0, len = ranges.length; i < len; ++i) {
2905                     this.addRange(ranges[i]);
2906                 }
2907             }
2908         };
2909     } else if (util.isHostMethod(testSelection, "empty") && util.isHostMethod(testRange, "select") &&
2910                implementsControlRange && useDocumentSelection) {
2911 
2912         selProto.removeAllRanges = function() {
2913             // Added try/catch as fix for issue #21
2914             try {
2915             
2916             	var isNativeIE7 = (jQuery.browser.msie && jQuery.browser.version < 8 && (typeof document.documentMode === 'undefined'));
2917             	if (!isNativeIE7) {
2918             		this.docSelection.empty();
2919             	}
2920 
2921                 // Check for empty() not working (issue #24)
2922                 if (this.docSelection.type != "None") {
2923 
2924 					if (isNativeIE7) {
2925             			this.docSelection.empty();
2926             		}
2927 
2928                     // Work around failure to empty a control selection by instead selecting a TextRange and then
2929                     // calling empty()
2930                     var doc;
2931                     if (this.anchorNode) {
2932                         doc = dom.getDocument(this.anchorNode);
2933                     } else if (this.docSelection.type == CONTROL) {
2934                         var controlRange = this.docSelection.createRange();
2935                         if (controlRange.length) {
2936                             doc = dom.getDocument(controlRange.item(0)).body.createTextRange();
2937                         }
2938                     }
2939                     if (doc) {
2940                         var textRange = doc.body.createTextRange();
2941                         textRange.select();
2942                         this.docSelection.empty();
2943                     }
2944                 }
2945             } catch(ex) {}
2946             updateEmptySelection(this);
2947         };
2948 
2949         selProto.addRange = function(range) {
2950             if (this.docSelection.type == CONTROL) {
2951                 addRangeToControlSelection(this, range);
2952             } else {
2953 				try {
2954                 WrappedRange.rangeToTextRange(range).select();
2955                 this._ranges[0] = range;
2956                 this.rangeCount = 1;
2957                 this.isCollapsed = this._ranges[0].collapsed;
2958                 updateAnchorAndFocusFromRange(this, range, false);
2959 				} catch (e) {
2960 					// @todo
2961 					// window.console.log('problem at addRange');
2962 				}
2963             }
2964         };
2965 
2966         selProto.setRanges = function(ranges) {
2967             this.removeAllRanges();
2968             var rangeCount = ranges.length;
2969             if (rangeCount > 1) {
2970                 createControlSelection(this, ranges);
2971             } else if (rangeCount) {
2972                 this.addRange(ranges[0]);
2973             }
2974         };
2975     } else {
2976         module.fail("No means of selecting a Range or TextRange was found");
2977         return false;
2978     }
2979 
2980     selProto.getRangeAt = function(index) {
2981         if (index < 0 || index >= this.rangeCount) {
2982             throw new DOMException("INDEX_SIZE_ERR");
2983         } else {
2984             return this._ranges[index];
2985         }
2986     };
2987 
2988     var refreshSelection;
2989 
2990     if (useDocumentSelection) {
2991         refreshSelection = function(sel) {
2992             var range;
2993             if (api.isSelectionValid(sel.win)) {
2994                 range = sel.docSelection.createRange();
2995             } else {
2996                 range = dom.getBody(sel.win.document).createTextRange();
2997                 range.collapse(true);
2998             }
2999 
3000 
3001             if (sel.docSelection.type == CONTROL) {
3002                 updateControlSelection(sel);
3003             } else if (isTextRange(range)) {
3004                 updateFromTextRange(sel, range);
3005             } else {
3006                 updateEmptySelection(sel);
3007             }
3008         };
3009     } else if (util.isHostMethod(testSelection, "getRangeAt") && typeof testSelection.rangeCount == "number") {
3010         refreshSelection = function(sel) {
3011             if (implementsControlRange && implementsDocSelection && sel.docSelection.type == CONTROL) {
3012                 updateControlSelection(sel);
3013             } else {
3014                 sel._ranges.length = sel.rangeCount = sel.nativeSelection.rangeCount;
3015                 if (sel.rangeCount) {
3016                     for (var i = 0, len = sel.rangeCount; i < len; ++i) {
3017                         sel._ranges[i] = new api.WrappedRange(sel.nativeSelection.getRangeAt(i));
3018                     }
3019                     updateAnchorAndFocusFromRange(sel, sel._ranges[sel.rangeCount - 1], selectionIsBackwards(sel.nativeSelection));
3020                     sel.isCollapsed = selectionIsCollapsed(sel);
3021                 } else {
3022                     updateEmptySelection(sel);
3023                 }
3024             }
3025         };
3026     } else if (selectionHasAnchorAndFocus && typeof testSelection.isCollapsed == BOOLEAN && typeof testRange.collapsed == BOOLEAN && api.features.implementsDomRange) {
3027         refreshSelection = function(sel) {
3028             var range, nativeSel = sel.nativeSelection;
3029             if (nativeSel.anchorNode) {
3030                 range = getSelectionRangeAt(nativeSel, 0);
3031                 sel._ranges = [range];
3032                 sel.rangeCount = 1;
3033                 updateAnchorAndFocusFromNativeSelection(sel);
3034                 sel.isCollapsed = selectionIsCollapsed(sel);
3035             } else {
3036                 updateEmptySelection(sel);
3037             }
3038         };
3039     } else {
3040         module.fail("No means of obtaining a Range or TextRange from the user's selection was found");
3041         return false;
3042     }
3043 
3044     selProto.refresh = function(checkForChanges) {
3045         var oldRanges = checkForChanges ? this._ranges.slice(0) : null;
3046         refreshSelection(this);
3047         if (checkForChanges) {
3048             var i = oldRanges.length;
3049             if (i != this._ranges.length) {
3050                 return false;
3051             }
3052             while (i--) {
3053                 if (!DomRange.rangesEqual(oldRanges[i], this._ranges[i])) {
3054                     return false;
3055                 }
3056             }
3057             return true;
3058         }
3059     };
3060 
3061     // Removal of a single range
3062     var removeRangeManually = function(sel, range) {
3063         var ranges = sel.getAllRanges(), removed = false;
3064         sel.removeAllRanges();
3065         for (var i = 0, len = ranges.length; i < len; ++i) {
3066             if (removed || range !== ranges[i]) {
3067                 sel.addRange(ranges[i]);
3068             } else {
3069                 // According to the draft WHATWG Range spec, the same range may be added to the selection multiple
3070                 // times. removeRange should only remove the first instance, so the following ensures only the first
3071                 // instance is removed
3072                 removed = true;
3073             }
3074         }
3075         if (!sel.rangeCount) {
3076             updateEmptySelection(sel);
3077         }
3078     };
3079 
3080     if (implementsControlRange) {
3081         selProto.removeRange = function(range) {
3082             if (this.docSelection.type == CONTROL) {
3083                 var controlRange = this.docSelection.createRange();
3084                 var rangeElement = getSingleElementFromRange(range);
3085 
3086                 // Create a new ControlRange containing all the elements in the selected ControlRange minus the
3087                 // element contained by the supplied range
3088                 var doc = dom.getDocument(controlRange.item(0));
3089                 var newControlRange = dom.getBody(doc).createControlRange();
3090                 var el, removed = false;
3091                 for (var i = 0, len = controlRange.length; i < len; ++i) {
3092                     el = controlRange.item(i);
3093                     if (el !== rangeElement || removed) {
3094                         newControlRange.add(controlRange.item(i));
3095                     } else {
3096                         removed = true;
3097                     }
3098                 }
3099                 newControlRange.select();
3100 
3101                 // Update the wrapped selection based on what's now in the native selection
3102                 updateControlSelection(this);
3103             } else {
3104                 removeRangeManually(this, range);
3105             }
3106         };
3107     } else {
3108         selProto.removeRange = function(range) {
3109             removeRangeManually(this, range);
3110         };
3111     }
3112 
3113     // Detecting if a selection is backwards
3114     var selectionIsBackwards;
3115     if (!useDocumentSelection && selectionHasAnchorAndFocus && api.features.implementsDomRange) {
3116         selectionIsBackwards = function(sel) {
3117             var backwards = false;
3118             if (sel.anchorNode) {
3119                 backwards = (dom.comparePoints(sel.anchorNode, sel.anchorOffset, sel.focusNode, sel.focusOffset) == 1);
3120             }
3121             return backwards;
3122         };
3123 
3124         selProto.isBackwards = function() {
3125             return selectionIsBackwards(this);
3126         };
3127     } else {
3128         selectionIsBackwards = selProto.isBackwards = function() {
3129             return false;
3130         };
3131     }
3132 
3133     // Selection text
3134     // This is conformant to the new WHATWG DOM Range draft spec but differs from WebKit and Mozilla's implementation
3135     selProto.toString = function() {
3136 
3137         var rangeTexts = [];
3138         for (var i = 0, len = this.rangeCount; i < len; ++i) {
3139             rangeTexts[i] = "" + this._ranges[i];
3140         }
3141         return rangeTexts.join("");
3142     };
3143 
3144     function assertNodeInSameDocument(sel, node) {
3145         if (sel.anchorNode && (dom.getDocument(sel.anchorNode) !== dom.getDocument(node))) {
3146             throw new DOMException("WRONG_DOCUMENT_ERR");
3147         }
3148     }
3149 
3150     // No current browsers conform fully to the HTML 5 draft spec for this method, so Rangy's own method is always used
3151     selProto.collapse = function(node, offset) {
3152         assertNodeInSameDocument(this, node);
3153         var range = api.createRange(dom.getDocument(node));
3154         range.collapseToPoint(node, offset);
3155         this.removeAllRanges();
3156         this.addRange(range);
3157         this.isCollapsed = true;
3158     };
3159 
3160     selProto.collapseToStart = function() {
3161         if (this.rangeCount) {
3162             var range = this._ranges[0];
3163             this.collapse(range.startContainer, range.startOffset);
3164         } else {
3165             throw new DOMException("INVALID_STATE_ERR");
3166         }
3167     };
3168 
3169     selProto.collapseToEnd = function() {
3170         if (this.rangeCount) {
3171             var range = this._ranges[this.rangeCount - 1];
3172             this.collapse(range.endContainer, range.endOffset);
3173         } else {
3174             throw new DOMException("INVALID_STATE_ERR");
3175         }
3176     };
3177 
3178     // The HTML 5 spec is very specific on how selectAllChildren should be implemented so the native implementation is
3179     // never used by Rangy.
3180     selProto.selectAllChildren = function(node) {
3181         assertNodeInSameDocument(this, node);
3182         var range = api.createRange(dom.getDocument(node));
3183         range.selectNodeContents(node);
3184         this.removeAllRanges();
3185         this.addRange(range);
3186     };
3187 
3188     selProto.deleteFromDocument = function() {
3189         // Sepcial behaviour required for Control selections
3190         if (implementsControlRange && implementsDocSelection && this.docSelection.type == CONTROL) {
3191             var controlRange = this.docSelection.createRange();
3192             var element;
3193             while (controlRange.length) {
3194                 element = controlRange.item(0);
3195                 controlRange.remove(element);
3196                 element.parentNode.removeChild(element);
3197             }
3198             this.refresh();
3199         } else if (this.rangeCount) {
3200             var ranges = this.getAllRanges();
3201             this.removeAllRanges();
3202             for (var i = 0, len = ranges.length; i < len; ++i) {
3203                 ranges[i].deleteContents();
3204             }
3205             // The HTML5 spec says nothing about what the selection should contain after calling deleteContents on each
3206             // range. Firefox moves the selection to where the final selected range was, so we emulate that
3207             this.addRange(ranges[len - 1]);
3208         }
3209     };
3210 
3211     // The following are non-standard extensions
3212     selProto.getAllRanges = function() {
3213         return this._ranges.slice(0);
3214     };
3215 
3216     selProto.setSingleRange = function(range) {
3217         this.setRanges( [range] );
3218     };
3219 
3220     selProto.containsNode = function(node, allowPartial) {
3221 3222         for (var i = 0, len = this._ranges.length; i < len; ++i) {
3223             if (this._ranges[i].containsNode(node, allowPartial)) {
3224                 return true;
3225             }
3226         }
3227         return false;
3228     };
3229 
3230     selProto.toHtml = function() {
3231         var html = "";
3232         if (this.rangeCount) {
3233             var container = DomRange.getRangeDocument(this._ranges[0]).createElement("div");
3234             for (var i = 0, len = this._ranges.length; i < len; ++i) {
3235                 container.appendChild(this._ranges[i].cloneContents());
3236             }
3237             html = container.innerHTML;
3238         }
3239         return html;
3240     };
3241 
3242     function inspect(sel) {
3243         var rangeInspects = [];
3244         var anchor = new DomPosition(sel.anchorNode, sel.anchorOffset);
3245         var focus = new DomPosition(sel.focusNode, sel.focusOffset);
3246         var name = (typeof sel.getName == "function") ? sel.getName() : "Selection";
3247 
3248         if (typeof sel.rangeCount != "undefined") {
3249             for (var i = 0, len = sel.rangeCount; i < len; ++i) {
3250                 rangeInspects[i] = DomRange.inspect(sel.getRangeAt(i));
3251             }
3252         }
3253         return "[" + name + "(Ranges: " + rangeInspects.join(", ") +
3254                 ")(anchor: " + anchor.inspect() + ", focus: " + focus.inspect() + "]";
3255 
3256     }
3257 
3258     selProto.getName = function() {
3259         return "WrappedSelection";
3260     };
3261 
3262     selProto.inspect = function() {
3263         return inspect(this);
3264     };
3265 
3266     selProto.detach = function() {
3267         this.win[windowPropertyName] = null;
3268         this.win = this.anchorNode = this.focusNode = null;
3269     };
3270 
3271     WrappedSelection.inspect = inspect;
3272 
3273     api.Selection = WrappedSelection;
3274 
3275     api.selectionPrototype = selProto;
3276 
3277     api.addCreateMissingNativeApiListener(function(win) {
3278         if (typeof win.getSelection == "undefined") {
3279             win.getSelection = function() {
3280                 return api.getSelection(this);
3281             };
3282         }
3283         win = null;
3284     });
3285 });
3286 
3287 // TODO we should avoid populating the global namespace
3288 window.rangy = rangy;
3289 return rangy;
3290 });
3291