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                 offset = workingRange.text.length;
2033             }
2034             boundaryPosition = new DomPosition(boundaryNode, offset);
2035         } else {
2036 
2037 
2038             // If the boundary immediately follows a character data node and this is the end boundary, we should favour
2039             // a position within that, and likewise for a start boundary preceding a character data node
2040             previousNode = (isCollapsed || !isStart) && workingNode.previousSibling;
2041             nextNode = (isCollapsed || isStart) && workingNode.nextSibling;
2042 
2043 
2044 
2045             if (nextNode && dom.isCharacterDataNode(nextNode)) {
2046                 boundaryPosition = new DomPosition(nextNode, 0);
2047             } else if (previousNode && dom.isCharacterDataNode(previousNode)) {
2048                 boundaryPosition = new DomPosition(previousNode, previousNode.length);
2049             } else {
2050                 boundaryPosition = new DomPosition(containerElement, dom.getNodeIndex(workingNode));
2051             }
2052         }
2053 
2054         // Clean up
2055         workingNode.parentNode.removeChild(workingNode);
2056 
2057         return boundaryPosition;
2058     }
2059 
2060     // Returns a TextRange representing the boundary of a TextRange expressed as a node and an offset within that node.
2061     // This function started out as an optimized version of code found in Tim Cameron Ryan's IERange
2062     // (http://code.google.com/p/ierange/)
2063     function createBoundaryTextRange(boundaryPosition, isStart) {
2064         var boundaryNode, boundaryParent, boundaryOffset = boundaryPosition.offset;
2065         var doc = dom.getDocument(boundaryPosition.node);
2066         var workingNode, childNodes, workingRange = doc.body.createTextRange();
2067         var nodeIsDataNode = dom.isCharacterDataNode(boundaryPosition.node);
2068 
2069         if (nodeIsDataNode) {
2070             boundaryNode = boundaryPosition.node;
2071             boundaryParent = boundaryNode.parentNode;
2072         } else {
2073             childNodes = boundaryPosition.node.childNodes;
2074             boundaryNode = (boundaryOffset < childNodes.length) ? childNodes[boundaryOffset] : null;
2075             boundaryParent = boundaryPosition.node;
2076         }
2077 
2078         // Position the range immediately before the node containing the boundary
2079         workingNode = doc.createElement("span");
2080 
2081         // Making the working element non-empty element persuades IE to consider the TextRange boundary to be within the
2082         // element rather than immediately before or after it, which is what we want
2083         workingNode.innerHTML = "&#feff;";
2084 
2085         // insertBefore is supposed to work like appendChild if the second parameter is null. However, a bug report
2086         // for IERange suggests that it can crash the browser: http://code.google.com/p/ierange/issues/detail?id=12
2087         if (boundaryNode) {
2088             boundaryParent.insertBefore(workingNode, boundaryNode);
2089         } else {
2090             boundaryParent.appendChild(workingNode);
2091         }
2092 
2093 		try {
2094 			workingRange.moveToElementText(workingNode);
2095 	        workingRange.collapse(!isStart);
2096 		} catch ( err ) {
2097 			// @todo window.console.log('problem with moveToElementText');
2098 			//return false;
2099 		}
2100 
2101 		// Clean up
2102 		boundaryParent.removeChild(workingNode);
2103 
2104 		// Move the working range to the text offset, if required
2105 		if (nodeIsDataNode) {
2106 			workingRange[isStart ? "moveStart" : "moveEnd"]("character", boundaryOffset);
2107 		}
2108 
2109 		return workingRange;
2110     }
2111 
2112     /*----------------------------------------------------------------------------------------------------------------*/
2113 
2114     if (api.features.implementsDomRange && (!api.features.implementsTextRange || !api.config.preferTextRange)) {
2115         // This is a wrapper around the browser's native DOM Range. It has two aims:
2116         // - Provide workarounds for specific browser bugs
2117         // - provide convenient extensions, which are inherited from Rangy's DomRange
2118 
2119         (function() {
2120             var rangeProto;
2121             var rangeProperties = DomRange.rangeProperties;
2122             var canSetRangeStartAfterEnd;
2123 
2124             function updateRangeProperties(range) {
2125                 var i = rangeProperties.length, prop;
2126                 while (i--) {
2127                     prop = rangeProperties[i];
2128                     range[prop] = range.nativeRange[prop];
2129                 }
2130             }
2131 
2132             function updateNativeRange(range, startContainer, startOffset, endContainer,endOffset) {
2133                 var startMoved = (range.startContainer !== startContainer || range.startOffset != startOffset);
2134                 var endMoved = (range.endContainer !== endContainer || range.endOffset != endOffset);
2135 
2136                 // Always set both boundaries for the benefit of IE9 (see issue 35)
2137                 if (startMoved || endMoved) {
2138                     range.setEnd(endContainer, endOffset);
2139                     range.setStart(startContainer, startOffset);
2140                 }
2141             }
2142 
2143             function detach(range) {
2144                 range.nativeRange.detach();
2145                 range.detached = true;
2146                 var i = rangeProperties.length, prop;
2147                 while (i--) {
2148                     prop = rangeProperties[i];
2149                     range[prop] = null;
2150                 }
2151             }
2152 
2153             var createBeforeAfterNodeSetter;
2154 
2155             WrappedRange = function(range) {
2156                 if (!range) {
2157                     throw new Error("Range must be specified");
2158                 }
2159                 this.nativeRange = range;
2160                 updateRangeProperties(this);
2161             };
2162 
2163             DomRange.createPrototypeRange(WrappedRange, updateNativeRange, detach);
2164 
2165             rangeProto = WrappedRange.prototype;
2166 
2167             rangeProto.selectNode = function(node) {
2168                 this.nativeRange.selectNode(node);
2169                 updateRangeProperties(this);
2170             };
2171 
2172             rangeProto.deleteContents = function() {
2173                 this.nativeRange.deleteContents();
2174                 updateRangeProperties(this);
2175             };
2176 
2177             rangeProto.extractContents = function() {
2178                 var frag = this.nativeRange.extractContents();
2179                 updateRangeProperties(this);
2180                 return frag;
2181             };
2182 
2183             rangeProto.cloneContents = function() {
2184                 return this.nativeRange.cloneContents();
2185             };
2186 
2187             // TODO: Until I can find a way to programmatically trigger the Firefox bug (apparently long-standing, still
2188             // present in 3.6.8) that throws "Index or size is negative or greater than the allowed amount" for
2189             // insertNode in some circumstances, all browsers will have to use the Rangy's own implementation of
2190             // insertNode, which works but is almost certainly slower than the native implementation.
2191 /*
2192             rangeProto.insertNode = function(node) {
2193                 this.nativeRange.insertNode(node);
2194                 updateRangeProperties(this);
2195             };
2196 */
2197 
2198             rangeProto.surroundContents = function(node) {
2199                 this.nativeRange.surroundContents(node);
2200                 updateRangeProperties(this);
2201             };
2202 
2203             rangeProto.collapse = function(isStart) {
2204                 this.nativeRange.collapse(isStart);
2205                 updateRangeProperties(this);
2206             };
2207 
2208             rangeProto.cloneRange = function() {
2209                 return new WrappedRange(this.nativeRange.cloneRange());
2210             };
2211 
2212             rangeProto.refresh = function() {
2213                 updateRangeProperties(this);
2214             };
2215 
2216             rangeProto.toString = function() {
2217                 return this.nativeRange.toString();
2218             };
2219 
2220             // Create test range and node for feature detection
2221 
2222             var testTextNode = document.createTextNode("test");
2223             dom.getBody(document).appendChild(testTextNode);
2224             var range = document.createRange();
2225 
2226             /*--------------------------------------------------------------------------------------------------------*/
2227 
2228             // Test for Firefox 2 bug that prevents moving the start of a Range to a point after its current end and
2229             // correct for it
2230 
2231             range.setStart(testTextNode, 0);
2232             range.setEnd(testTextNode, 0);
2233 
2234             try {
2235                 range.setStart(testTextNode, 1);
2236                 canSetRangeStartAfterEnd = true;
2237 
2238                 rangeProto.setStart = function(node, offset) {
2239                     this.nativeRange.setStart(node, offset);
2240                     updateRangeProperties(this);
2241                 };
2242 
2243                 rangeProto.setEnd = function(node, offset) {
2244                     this.nativeRange.setEnd(node, offset);
2245                     updateRangeProperties(this);
2246                 };
2247 
2248                 createBeforeAfterNodeSetter = function(name) {
2249                     return function(node) {
2250                         this.nativeRange[name](node);
2251                         updateRangeProperties(this);
2252                     };
2253                 };
2254 
2255             } catch(ex) {
2256 
2257 
2258                 canSetRangeStartAfterEnd = false;
2259 
2260                 rangeProto.setStart = function(node, offset) {
2261                     try {
2262                         this.nativeRange.setStart(node, offset);
2263                     } catch (ex) {
2264                         this.nativeRange.setEnd(node, offset);
2265                         this.nativeRange.setStart(node, offset);
2266                     }
2267                     updateRangeProperties(this);
2268                 };
2269 
2270                 rangeProto.setEnd = function(node, offset) {
2271                     try {
2272                         this.nativeRange.setEnd(node, offset);
2273                     } catch (ex) {
2274                         this.nativeRange.setStart(node, offset);
2275                         this.nativeRange.setEnd(node, offset);
2276                     }
2277                     updateRangeProperties(this);
2278                 };
2279 
2280                 createBeforeAfterNodeSetter = function(name, oppositeName) {
2281                     return function(node) {
2282                         try {
2283                             this.nativeRange[name](node);
2284                         } catch (ex) {
2285                             this.nativeRange[oppositeName](node);
2286                             this.nativeRange[name](node);
2287                         }
2288                         updateRangeProperties(this);
2289                     };
2290                 };
2291             }
2292 
2293             rangeProto.setStartBefore = createBeforeAfterNodeSetter("setStartBefore", "setEndBefore");
2294             rangeProto.setStartAfter = createBeforeAfterNodeSetter("setStartAfter", "setEndAfter");
2295             rangeProto.setEndBefore = createBeforeAfterNodeSetter("setEndBefore", "setStartBefore");
2296             rangeProto.setEndAfter = createBeforeAfterNodeSetter("setEndAfter", "setStartAfter");
2297 
2298             /*--------------------------------------------------------------------------------------------------------*/
2299 
2300             // Test for and correct Firefox 2 behaviour with selectNodeContents on text nodes: it collapses the range to
2301             // the 0th character of the text node
2302             range.selectNodeContents(testTextNode);
2303             if (range.startContainer == testTextNode && range.endContainer == testTextNode &&
2304                     range.startOffset == 0 && range.endOffset == testTextNode.length) {
2305                 rangeProto.selectNodeContents = function(node) {
2306                     this.nativeRange.selectNodeContents(node);
2307                     updateRangeProperties(this);
2308                 };
2309             } else {
2310                 rangeProto.selectNodeContents = function(node) {
2311                     this.setStart(node, 0);
2312                     this.setEnd(node, DomRange.getEndOffset(node));
2313                 };
2314             }
2315 
2316             /*--------------------------------------------------------------------------------------------------------*/
2317 
2318             // Test for WebKit bug that has the beahviour of compareBoundaryPoints round the wrong way for constants
2319             // START_TO_END and END_TO_START: https://bugs.webkit.org/show_bug.cgi?id=20738
2320 
2321             range.selectNodeContents(testTextNode);
2322             range.setEnd(testTextNode, 3);
2323 
2324             var range2 = document.createRange();
2325             range2.selectNodeContents(testTextNode);
2326             range2.setEnd(testTextNode, 4);
2327             range2.setStart(testTextNode, 2);
2328 
2329             if (range.compareBoundaryPoints(range.START_TO_END, range2) == -1 &
2330                     range.compareBoundaryPoints(range.END_TO_START, range2) == 1) {
2331                 // This is the wrong way round, so correct for it
2332 
2333 
2334                 rangeProto.compareBoundaryPoints = function(type, range) {
2335                     range = range.nativeRange || range;
2336                     if (type == range.START_TO_END) {
2337                         type = range.END_TO_START;
2338                     } else if (type == range.END_TO_START) {
2339                         type = range.START_TO_END;
2340                     }
2341                     return this.nativeRange.compareBoundaryPoints(type, range);
2342                 };
2343             } else {
2344                 rangeProto.compareBoundaryPoints = function(type, range) {
2345                     return this.nativeRange.compareBoundaryPoints(type, range.nativeRange || range);
2346                 };
2347             }
2348 
2349             /*--------------------------------------------------------------------------------------------------------*/
2350 
2351             // Test for existence of createContextualFragment and delegate to it if it exists
2352             if (api.util.isHostMethod(range, "createContextualFragment")) {
2353                 rangeProto.createContextualFragment = function(fragmentStr) {
2354                     return this.nativeRange.createContextualFragment(fragmentStr);
2355                 };
2356             }
2357 
2358             /*--------------------------------------------------------------------------------------------------------*/
2359 
2360             // Clean up
2361             dom.getBody(document).removeChild(testTextNode);
2362             range.detach();
2363             range2.detach();
2364         })();
2365 
2366         api.createNativeRange = function(doc) {
2367             doc = doc || document;
2368             return doc.createRange();
2369         };
2370     } else if (api.features.implementsTextRange) {
2371         // This is a wrapper around a TextRange, providing full DOM Range functionality using rangy's DomRange as a
2372         // prototype
2373 
2374         WrappedRange = function(textRange) {
2375             this.textRange = textRange;
2376             this.refresh();
2377         };
2378 
2379         WrappedRange.prototype = new DomRange(document);
2380 
2381         WrappedRange.prototype.refresh = function() {
2382             var start, end;
2383 
2384             // TextRange's parentElement() method cannot be trusted. getTextRangeContainerElement() works around that.
2385             var rangeContainerElement = getTextRangeContainerElement(this.textRange);
2386 
2387             if (textRangeIsCollapsed(this.textRange)) {
2388                 end = start = getTextRangeBoundaryPosition(this.textRange, rangeContainerElement, true, true);
2389             } else {
2390 
2391                 start = getTextRangeBoundaryPosition(this.textRange, rangeContainerElement, true, false);
2392                 end = getTextRangeBoundaryPosition(this.textRange, rangeContainerElement, false, false);
2393             }
2394 
2395             this.setStart(start.node, start.offset);
2396             this.setEnd(end.node, end.offset);
2397         };
2398 
2399 2400         DomRange.copyComparisonConstants(WrappedRange);
2401 
2402         // Add WrappedRange as the Range property of the global object to allow expression like Range.END_TO_END to work
2403         var globalObj = (function() { return this; })();
2404         if (typeof globalObj.Range == "undefined") {
2405             globalObj.Range = WrappedRange;
2406         }
2407 
2408         api.createNativeRange = function(doc) {
2409             doc = doc || document;
2410             return doc.body.createTextRange();
2411         };
2412     }
2413 
2414     if (api.features.implementsTextRange) {
2415         WrappedRange.rangeToTextRange = function(range) {
2416             if (range.collapsed) {
2417                 var tr = createBoundaryTextRange(new DomPosition(range.startContainer, range.startOffset), true);
2418                 return tr;
2419                 //return createBoundaryTextRange(new DomPosition(range.startContainer, range.startOffset), true);
2420             } else {
2421                 var startRange = createBoundaryTextRange(new DomPosition(range.startContainer, range.startOffset), true);
2422                 var endRange = createBoundaryTextRange(new DomPosition(range.endContainer, range.endOffset), false);
2423                 var textRange = dom.getDocument(range.startContainer).body.createTextRange();
2424                 textRange.setEndPoint("StartToStart", startRange);
2425                 textRange.setEndPoint("EndToEnd", endRange);
2426                 return textRange;
2427             }
2428         };
2429     }
2430 
2431     WrappedRange.prototype.getName = function() {
2432         return "WrappedRange";
2433     };
2434 
2435     api.WrappedRange = WrappedRange;
2436 
2437     api.createRange = function(doc) {
2438         doc = doc || document;
2439         return new WrappedRange(api.createNativeRange(doc));
2440     };
2441 
2442     api.createRangyRange = function(doc) {
2443         doc = doc || document;
2444         return new DomRange(doc);
2445     };
2446 
2447     api.createIframeRange = function(iframeEl) {
2448         return api.createRange(dom.getIframeDocument(iframeEl));
2449     };
2450 
2451     api.createIframeRangyRange = function(iframeEl) {
2452         return api.createRangyRange(dom.getIframeDocument(iframeEl));
2453     };
2454 
2455     api.addCreateMissingNativeApiListener(function(win) {
2456         var doc = win.document;
2457         if (typeof doc.createRange == "undefined") {
2458             doc.createRange = function() {
2459                 return api.createRange(this);
2460             };
2461         }
2462         doc = win = null;
2463     });
2464 });rangy.createModule("WrappedSelection", function(api, module) {
2465     // This will create a selection object wrapper that follows the Selection object found in the WHATWG draft DOM Range
2466     // spec (http://html5.org/specs/dom-range.html)
2467 
2468     api.requireModules( ["DomUtil", "DomRange", "WrappedRange"] );
2469 
2470     api.config.checkSelectionRanges = true;
2471 
2472     var BOOLEAN = "boolean",
2473         windowPropertyName = "_rangySelection",
2474         dom = api.dom,
2475         util = api.util,
2476         DomRange = api.DomRange,
2477         WrappedRange = api.WrappedRange,
2478         DOMException = api.DOMException,
2479         DomPosition = dom.DomPosition,
2480         getSelection,
2481         selectionIsCollapsed,
2482         CONTROL = "Control";
2483 
2484 
2485 
2486     function getWinSelection(winParam) {
2487         return (winParam || window).getSelection();
2488     }
2489 
2490     function getDocSelection(winParam) {
2491         return (winParam || window).document.selection;
2492     }
2493 
2494     // Test for the Range/TextRange and Selection features required
2495     // Test for ability to retrieve selection
2496     var implementsWinGetSelection = api.util.isHostMethod(window, "getSelection"),
2497         implementsDocSelection = api.util.isHostObject(document, "selection");
2498 
2499     var useDocumentSelection = implementsDocSelection && (!implementsWinGetSelection || api.config.preferTextRange);
2500 
2501     if (useDocumentSelection) {
2502         getSelection = getDocSelection;
2503         api.isSelectionValid = function(winParam) {
2504             var doc = (winParam || window).document, nativeSel = doc.selection;
2505 
2506             // Check whether the selection TextRange is actually contained within the correct document
2507             return (nativeSel.type != "None" || dom.getDocument(nativeSel.createRange().parentElement()) == doc);
2508         };
2509     } else if (implementsWinGetSelection) {
2510         getSelection = getWinSelection;
2511         api.isSelectionValid = function() {
2512             return true;
2513         };
2514     } else {
2515         module.fail("Neither document.selection or window.getSelection() detected.");
2516     }
2517 
2518     api.getNativeSelection = getSelection;
2519 
2520     var testSelection = getSelection();
2521     var testRange = api.createNativeRange(document);
2522     var body = dom.getBody(document);
2523 
2524     // Obtaining a range from a selection
2525     var selectionHasAnchorAndFocus = util.areHostObjects(testSelection, ["anchorNode", "focusNode"] &&
2526                                      util.areHostProperties(testSelection, ["anchorOffset", "focusOffset"]));
2527     api.features.selectionHasAnchorAndFocus = selectionHasAnchorAndFocus;
2528 
2529     // Test for existence of native selection extend() method
2530     var selectionHasExtend = util.isHostMethod(testSelection, "extend");
2531     api.features.selectionHasExtend = selectionHasExtend;
2532 
2533     // Test if rangeCount exists
2534     var selectionHasRangeCount = (typeof testSelection.rangeCount == "number");
2535     api.features.selectionHasRangeCount = selectionHasRangeCount;
2536 
2537     var selectionSupportsMultipleRanges = false;
2538     var collapsedNonEditableSelectionsSupported = true;
2539 
2540     if (util.areHostMethods(testSelection, ["addRange", "getRangeAt", "removeAllRanges"]) &&
2541             typeof testSelection.rangeCount == "number" && api.features.implementsDomRange) {
2542 
2543         (function() {
2544             var iframe = document.createElement("iframe");
2545             body.appendChild(iframe);
2546 2547 
2548             var iframeDoc = dom.getIframeDocument(iframe);
2549             iframeDoc.open();
2550             iframeDoc.write("<html><head></head><body>12</body></html>");
2551             iframeDoc.close();
2552 
2553             var sel = dom.getIframeWindow(iframe).getSelection();
2554             var docEl = iframeDoc.documentElement;
2555             var iframeBody = docEl.lastChild, textNode = iframeBody.firstChild;
2556 
2557             // Test whether the native selection will allow a collapsed selection within a non-editable element
2558             var r1 = iframeDoc.createRange();
2559             r1.setStart(textNode, 1);
2560             r1.collapse(true);
2561             sel.addRange(r1);
2562             collapsedNonEditableSelectionsSupported = (sel.rangeCount == 1);
2563             sel.removeAllRanges();
2564 
2565             // Test whether the native selection is capable of supporting multiple ranges
2566             var r2 = r1.cloneRange();
2567             r1.setStart(textNode, 0);
2568             r2.setEnd(textNode, 2);
2569             sel.addRange(r1);
2570             sel.addRange(r2);
2571 
2572             selectionSupportsMultipleRanges = (sel.rangeCount == 2);
2573 
2574             // Clean up
2575             r1.detach();
2576             r2.detach();
2577 
2578             body.removeChild(iframe);
2579         })();
2580     }
2581 
2582     api.features.selectionSupportsMultipleRanges = selectionSupportsMultipleRanges;
2583     api.features.collapsedNonEditableSelectionsSupported = collapsedNonEditableSelectionsSupported;
2584 
2585     // ControlRanges
2586     var implementsControlRange = false, testControlRange;
2587 
2588     if (body && util.isHostMethod(body, "createControlRange")) {
2589         testControlRange = body.createControlRange();
2590         if (util.areHostProperties(testControlRange, ["item", "add"])) {
2591             implementsControlRange = true;
2592         }
2593     }
2594     api.features.implementsControlRange = implementsControlRange;
2595 
2596     // Selection collapsedness
2597     if (selectionHasAnchorAndFocus) {
2598         selectionIsCollapsed = function(sel) {
2599             return sel.anchorNode === sel.focusNode && sel.anchorOffset === sel.focusOffset;
2600         };
2601     } else {
2602         selectionIsCollapsed = function(sel) {
2603             return sel.rangeCount ? sel.getRangeAt(sel.rangeCount - 1).collapsed : false;
2604         };
2605     }
2606 
2607     function updateAnchorAndFocusFromRange(sel, range, backwards) {
2608         var anchorPrefix = backwards ? "end" : "start", focusPrefix = backwards ? "start" : "end";
2609         sel.anchorNode = range[anchorPrefix + "Container"];
2610         sel.anchorOffset = range[anchorPrefix + "Offset"];
2611         sel.focusNode = range[focusPrefix + "Container"];
2612         sel.focusOffset = range[focusPrefix + "Offset"];
2613     }
2614 
2615     function updateAnchorAndFocusFromNativeSelection(sel) {
2616         var nativeSel = sel.nativeSelection;
2617         sel.anchorNode = nativeSel.anchorNode;
2618         sel.anchorOffset = nativeSel.anchorOffset;
2619         sel.focusNode = nativeSel.focusNode;
2620         sel.focusOffset = nativeSel.focusOffset;
2621     }
2622 
2623     function updateEmptySelection(sel) {
2624         sel.anchorNode = sel.focusNode = null;
2625         sel.anchorOffset = sel.focusOffset = 0;
2626         sel.rangeCount = 0;
2627         sel.isCollapsed = true;
2628         sel._ranges.length = 0;
2629     }
2630 
2631     function getNativeRange(range) {
2632         var nativeRange;
2633         if (range instanceof DomRange) {
2634             nativeRange = range._selectionNativeRange;
2635             if (!nativeRange) {
2636                 nativeRange = api.createNativeRange(dom.getDocument(range.startContainer));
2637                 nativeRange.setEnd(range.endContainer, range.endOffset);
2638                 nativeRange.setStart(range.startContainer, range.startOffset);
2639                 range._selectionNativeRange = nativeRange;
2640                 range.attachListener("detach", function() {
2641 
2642                     this._selectionNativeRange = null;
2643                 });
2644             }
2645         } else if (range instanceof WrappedRange) {
2646             nativeRange = range.nativeRange;
2647         } else if (api.features.implementsDomRange && (range instanceof dom.getWindow(range.startContainer).Range)) {
2648             nativeRange = range;
2649         }
2650         return nativeRange;
2651     }
2652 
2653     function rangeContainsSingleElement(rangeNodes) {
2654         if (!rangeNodes.length || rangeNodes[0].nodeType != 1) {
2655             return false;
2656         }
2657         for (var i = 1, len = rangeNodes.length; i < len; ++i) {
2658             if (!dom.isAncestorOf(rangeNodes[0], rangeNodes[i])) {
2659                 return false;
2660             }
2661         }
2662         return true;
2663     }
2664 
2665     function getSingleElementFromRange(range) {
2666         var nodes = range.getNodes();
2667         if (!rangeContainsSingleElement(nodes)) {
2668             throw new Error("getSingleElementFromRange: range " + range.inspect() + " did not consist of a single element");
2669         }
2670         return nodes[0];
2671     }
2672 
2673     function isTextRange(range) {
2674         return !!range && typeof range.text != "undefined";
2675     }
2676 
2677     function updateFromTextRange(sel, range) {
2678         // Create a Range from the selected TextRange
2679         var wrappedRange = new WrappedRange(range);
2680         sel._ranges = [wrappedRange];
2681 
2682         updateAnchorAndFocusFromRange(sel, wrappedRange, false);
2683         sel.rangeCount = 1;
2684         sel.isCollapsed = wrappedRange.collapsed;
2685     }
2686 
2687     function updateControlSelection(sel) {
2688         // Update the wrapped selection based on what's now in the native selection
2689         sel._ranges.length = 0;
2690         if (sel.docSelection.type == "None") {
2691             updateEmptySelection(sel);
2692         } else {
2693             var controlRange = sel.docSelection.createRange();
2694             if (isTextRange(controlRange)) {
2695                 // This case (where the selection type is "Control" and calling createRange() on the selection returns
2696                 // a TextRange) can happen in IE 9. It happens, for example, when all elements in the selected
2697                 // ControlRange have been removed from the ControlRange and removed from the document.
2698                 updateFromTextRange(sel, controlRange);
2699             } else {
2700                 sel.rangeCount = controlRange.length;
2701                 var range, doc = dom.getDocument(controlRange.item(0));
2702                 for (var i = 0; i < sel.rangeCount; ++i) {
2703                     range = api.createRange(doc);
2704                     range.selectNode(controlRange.item(i));
2705                     sel._ranges.push(range);
2706                 }
2707                 sel.isCollapsed = sel.rangeCount == 1 && sel._ranges[0].collapsed;
2708                 updateAnchorAndFocusFromRange(sel, sel._ranges[sel.rangeCount - 1], false);
2709             }
2710         }
2711     }
2712 
2713     function addRangeToControlSelection(sel, range) {
2714         var controlRange = sel.docSelection.createRange();
2715         var rangeElement = getSingleElementFromRange(range);
2716 
2717         // Create a new ControlRange containing all the elements in the selected ControlRange plus the element
2718         // contained by the supplied range
2719         var doc = dom.getDocument(controlRange.item(0));
2720         var newControlRange = dom.getBody(doc).createControlRange();
2721         for (var i = 0, len = controlRange.length; i < len; ++i) {
2722             newControlRange.add(controlRange.item(i));
2723         }
2724         try {
2725             newControlRange.add(rangeElement);
2726         } catch (ex) {
2727             throw new Error("addRange(): Element within the specified Range could not be added to control selection (does it have layout?)");
2728         }
2729         newControlRange.select();
2730 
2731         // Update the wrapped selection based on what's now in the native selection
2732         updateControlSelection(sel);
2733     }
2734 
2735     var getSelectionRangeAt;
2736 
2737     if (util.isHostMethod(testSelection,  "getRangeAt")) {
2738         getSelectionRangeAt = function(sel, index) {
2739             try {
2740                 return sel.getRangeAt(index);
2741             } catch(ex) {
2742                 return null;
2743             }
2744         };
2745     } else if (selectionHasAnchorAndFocus) {
2746         getSelectionRangeAt = function(sel) {
2747             var doc = dom.getDocument(sel.anchorNode);
2748             var range = api.createRange(doc);
2749             range.setStart(sel.anchorNode, sel.anchorOffset);
2750             range.setEnd(sel.focusNode, sel.focusOffset);
2751 
2752             // Handle the case when the selection was selected backwards (from the end to the start in the
2753             // document)
2754             if (range.collapsed !== this.isCollapsed) {
2755                 range.setStart(sel.focusNode, sel.focusOffset);
2756                 range.setEnd(sel.anchorNode, sel.anchorOffset);
2757             }
2758 
2759             return range;
2760         };
2761     }
2762 
2763     /**
2764      * @constructor
2765      */
2766     function WrappedSelection(selection, docSelection, win) {
2767         this.nativeSelection = selection;
2768         this.docSelection = docSelection;
2769         this._ranges = [];
2770         this.win = win;
2771         this.refresh();
2772     }
2773 
2774     api.getSelection = function(win) {
2775         win = win || window;
2776         var sel = win[windowPropertyName];
2777         var nativeSel = getSelection(win), docSel = implementsDocSelection ? getDocSelection(win) : null;
2778         if (sel) {
2779             sel.nativeSelection = nativeSel;
2780             sel.docSelection = docSel;
2781             sel.refresh(win);
2782         } else {
2783             sel = new WrappedSelection(nativeSel, docSel, win);
2784             win[windowPropertyName] = sel;
2785         }
2786         return sel;
2787     };
2788 
2789     api.getIframeSelection = function(iframeEl) {
2790         return api.getSelection(dom.getIframeWindow(iframeEl));
2791     };
2792 
2793     var selProto = WrappedSelection.prototype;
2794 
2795     function createControlSelection(sel, ranges) {
2796         // Ensure that the selection becomes of type "Control"
2797         var doc = dom.getDocument(ranges[0].startContainer);
2798         var controlRange = dom.getBody(doc).createControlRange();
2799         for (var i = 0, el; i < rangeCount; ++i) {
2800             el = getSingleElementFromRange(ranges[i]);
2801             try {
2802                 controlRange.add(el);
2803             } catch (ex) {
2804                 throw new Error("setRanges(): Element within the one of the specified Ranges could not be added to control selection (does it have layout?)");
2805             }
2806         }
2807         controlRange.select();
2808 
2809         // Update the wrapped selection based on what's now in the native selection
2810         updateControlSelection(sel);
2811     }
2812 
2813     // Selecting a range
2814     if (!useDocumentSelection && selectionHasAnchorAndFocus && util.areHostMethods(testSelection, ["removeAllRanges", "addRange"])) {
2815         selProto.removeAllRanges = function() {
2816             this.nativeSelection.removeAllRanges();
2817             updateEmptySelection(this);
2818         };
2819 
2820         var addRangeBackwards = function(sel, range) {
2821             var doc = DomRange.getRangeDocument(range);
2822             var endRange = api.createRange(doc);
2823             endRange.collapseToPoint(range.endContainer, range.endOffset);
2824             sel.nativeSelection.addRange(getNativeRange(endRange));
2825             sel.nativeSelection.extend(range.startContainer, range.startOffset);
2826             sel.refresh();
2827         };
2828 
2829         if (selectionHasRangeCount) {
2830             selProto.addRange = function(range, backwards) {
2831                 if (implementsControlRange && implementsDocSelection && this.docSelection.type == CONTROL) {
2832                     addRangeToControlSelection(this, range);
2833                 } else {
2834                     if (backwards && selectionHasExtend) {
2835                         addRangeBackwards(this, range);
2836                     } else {
2837                         var previousRangeCount;
2838                         if (selectionSupportsMultipleRanges) {
2839                             previousRangeCount = this.rangeCount;
2840                         } else {
2841                             this.removeAllRanges();
2842                             previousRangeCount = 0;
2843                         }
2844                         this.nativeSelection.addRange(getNativeRange(range));
2845 
2846                         // Check whether adding the range was successful
2847                         this.rangeCount = this.nativeSelection.rangeCount;
2848 
2849                         if (this.rangeCount == previousRangeCount + 1) {
2850                             // The range was added successfully
2851 
2852                             // Check whether the range that we added to the selection is reflected in the last range extracted from
2853                             // the selection
2854                             if (api.config.checkSelectionRanges) {
2855                                 var nativeRange = getSelectionRangeAt(this.nativeSelection, this.rangeCount - 1);
2856                                 if (nativeRange && !DomRange.rangesEqual(nativeRange, range)) {
2857                                     // Happens in WebKit with, for example, a selection placed at the start of a text node
2858                                     range = new WrappedRange(nativeRange);
2859                                 }
2860                             }
2861                             this._ranges[this.rangeCount - 1] = range;
2862                             updateAnchorAndFocusFromRange(this, range, selectionIsBackwards(this.nativeSelection));
2863                             this.isCollapsed = selectionIsCollapsed(this);
2864                         } else {
2865                             // The range was not added successfully. The simplest thing is to refresh
2866                             this.refresh();
2867                         }
2868                     }
2869                 }
2870             };
2871         } else {
2872             selProto.addRange = function(range, backwards) {
2873                 if (backwards && selectionHasExtend) {
2874                     addRangeBackwards(this, range);
2875                 } else {
2876                     this.nativeSelection.addRange(getNativeRange(range));
2877                     this.refresh();
2878                 }
2879             };
2880         }
2881 
2882         selProto.setRanges = function(ranges) {
2883             if (implementsControlRange && ranges.length > 1) {
2884                 createControlSelection(this, ranges);
2885             } else {
2886                 this.removeAllRanges();
2887                 for (var i = 0, len = ranges.length; i < len; ++i) {
2888                     this.addRange(ranges[i]);
2889                 }
2890             }
2891         };
2892     } else if (util.isHostMethod(testSelection, "empty") && util.isHostMethod(testRange, "select") &&
2893                implementsControlRange && useDocumentSelection) {
2894 
2895         selProto.removeAllRanges = function() {
2896             // Added try/catch as fix for issue #21
2897             try {
2898             
2899             	var isNativeIE7 = (jQuery.browser.msie && jQuery.version < 8 && (typeof document.documentMode === 'undefined'));
2900             	if (!isNativeIE7) {
2901             		this.docSelection.empty();
2902             	}
2903 
2904                 // Check for empty() not working (issue #24)
2905                 if (this.docSelection.type != "None") {
2906 
2907 					if (isNativeIE7) {
2908             			this.docSelection.empty();
2909             		}
2910 
2911                     // Work around failure to empty a control selection by instead selecting a TextRange and then
2912                     // calling empty()
2913                     var doc;
2914                     if (this.anchorNode) {
2915                         doc = dom.getDocument(this.anchorNode);
2916                     } else if (this.docSelection.type == CONTROL) {
2917                         var controlRange = this.docSelection.createRange();
2918                         if (controlRange.length) {
2919                             doc = dom.getDocument(controlRange.item(0)).body.createTextRange();
2920                         }
2921                     }
2922                     if (doc) {
2923                         var textRange = doc.body.createTextRange();
2924                         textRange.select();
2925                         this.docSelection.empty();
2926                     }
2927                 }
2928             } catch(ex) {}
2929             updateEmptySelection(this);
2930         };
2931 
2932         selProto.addRange = function(range) {
2933             if (this.docSelection.type == CONTROL) {
2934                 addRangeToControlSelection(this, range);
2935             } else {
2936 				try {
2937                 WrappedRange.rangeToTextRange(range).select();
2938                 this._ranges[0] = range;
2939                 this.rangeCount = 1;
2940                 this.isCollapsed = this._ranges[0].collapsed;
2941                 updateAnchorAndFocusFromRange(this, range, false);
2942 				} catch (e) {
2943 					// @todo
2944 					// window.console.log('problem at addRange');
2945 				}
2946             }
2947         };
2948 
2949         selProto.setRanges = function(ranges) {
2950             this.removeAllRanges();
2951             var rangeCount = ranges.length;
2952             if (rangeCount > 1) {
2953                 createControlSelection(this, ranges);
2954             } else if (rangeCount) {
2955                 this.addRange(ranges[0]);
2956             }
2957         };
2958     } else {
2959         module.fail("No means of selecting a Range or TextRange was found");
2960         return false;
2961     }
2962 
2963     selProto.getRangeAt = function(index) {
2964         if (index < 0 || index >= this.rangeCount) {
2965             throw new DOMException("INDEX_SIZE_ERR");
2966         } else {
2967             return this._ranges[index];
2968         }
2969     };
2970 
2971     var refreshSelection;
2972 
2973     if (useDocumentSelection) {
2974         refreshSelection = function(sel) {
2975             var range;
2976             if (api.isSelectionValid(sel.win)) {
2977                 range = sel.docSelection.createRange();
2978             } else {
2979                 range = dom.getBody(sel.win.document).createTextRange();
2980                 range.collapse(true);
2981             }
2982 
2983 
2984             if (sel.docSelection.type == CONTROL) {
2985                 updateControlSelection(sel);
2986             } else if (isTextRange(range)) {
2987                 updateFromTextRange(sel, range);
2988             } else {
2989                 updateEmptySelection(sel);
2990             }
2991         };
2992     } else if (util.isHostMethod(testSelection, "getRangeAt") && typeof testSelection.rangeCount == "number") {
2993         refreshSelection = function(sel) {
2994             if (implementsControlRange && implementsDocSelection && sel.docSelection.type == CONTROL) {
2995                 updateControlSelection(sel);
2996             } else {
2997                 sel._ranges.length = sel.rangeCount = sel.nativeSelection.rangeCount;
2998                 if (sel.rangeCount) {
2999                     for (var i = 0, len = sel.rangeCount; i < len; ++i) {
3000                         sel._ranges[i] = new api.WrappedRange(sel.nativeSelection.getRangeAt(i));
3001                     }
3002                     updateAnchorAndFocusFromRange(sel, sel._ranges[sel.rangeCount - 1], selectionIsBackwards(sel.nativeSelection));
3003                     sel.isCollapsed = selectionIsCollapsed(sel);
3004                 } else {
3005                     updateEmptySelection(sel);
3006                 }
3007             }
3008         };
3009     } else if (selectionHasAnchorAndFocus && typeof testSelection.isCollapsed == BOOLEAN && typeof testRange.collapsed == BOOLEAN && api.features.implementsDomRange) {
3010         refreshSelection = function(sel) {
3011             var range, nativeSel = sel.nativeSelection;
3012             if (nativeSel.anchorNode) {
3013                 range = getSelectionRangeAt(nativeSel, 0);
3014                 sel._ranges = [range];
3015                 sel.rangeCount = 1;
3016                 updateAnchorAndFocusFromNativeSelection(sel);
3017                 sel.isCollapsed = selectionIsCollapsed(sel);
3018             } else {
3019                 updateEmptySelection(sel);
3020             }
3021         };
3022     } else {
3023         module.fail("No means of obtaining a Range or TextRange from the user's selection was found");
3024         return false;
3025     }
3026 
3027     selProto.refresh = function(checkForChanges) {
3028         var oldRanges = checkForChanges ? this._ranges.slice(0) : null;
3029         refreshSelection(this);
3030         if (checkForChanges) {
3031             var i = oldRanges.length;
3032             if (i != this._ranges.length) {
3033                 return false;
3034             }
3035             while (i--) {
3036                 if (!DomRange.rangesEqual(oldRanges[i], this._ranges[i])) {
3037                     return false;
3038                 }
3039             }
3040             return true;
3041         }
3042     };
3043 
3044     // Removal of a single range
3045     var removeRangeManually = function(sel, range) {
3046         var ranges = sel.getAllRanges(), removed = false;
3047         sel.removeAllRanges();
3048         for (var i = 0, len = ranges.length; i < len; ++i) {
3049             if (removed || range !== ranges[i]) {
3050                 sel.addRange(ranges[i]);
3051             } else {
3052                 // According to the draft WHATWG Range spec, the same range may be added to the selection multiple
3053                 // times. removeRange should only remove the first instance, so the following ensures only the first
3054                 // instance is removed
3055                 removed = true;
3056             }
3057         }
3058         if (!sel.rangeCount) {
3059             updateEmptySelection(sel);
3060         }
3061     };
3062 
3063     if (implementsControlRange) {
3064         selProto.removeRange = function(range) {
3065             if (this.docSelection.type == CONTROL) {
3066                 var controlRange = this.docSelection.createRange();
3067                 var rangeElement = getSingleElementFromRange(range);
3068 
3069                 // Create a new ControlRange containing all the elements in the selected ControlRange minus the
3070                 // element contained by the supplied range
3071                 var doc = dom.getDocument(controlRange.item(0));
3072                 var newControlRange = dom.getBody(doc).createControlRange();
3073                 var el, removed = false;
3074                 for (var i = 0, len = controlRange.length; i < len; ++i) {
3075                     el = controlRange.item(i);
3076                     if (el !== rangeElement || removed) {
3077                         newControlRange.add(controlRange.item(i));
3078                     } else {
3079                         removed = true;
3080                     }
3081                 }
3082                 newControlRange.select();
3083 
3084                 // Update the wrapped selection based on what's now in the native selection
3085                 updateControlSelection(this);
3086             } else {
3087                 removeRangeManually(this, range);
3088             }
3089         };
3090     } else {
3091         selProto.removeRange = function(range) {
3092             removeRangeManually(this, range);
3093         };
3094     }
3095 
3096     // Detecting if a selection is backwards
3097     var selectionIsBackwards;
3098     if (!useDocumentSelection && selectionHasAnchorAndFocus && api.features.implementsDomRange) {
3099         selectionIsBackwards = function(sel) {
3100             var backwards = false;
3101             if (sel.anchorNode) {
3102                 backwards = (dom.comparePoints(sel.anchorNode, sel.anchorOffset, sel.focusNode, sel.focusOffset) == 1);
3103             }
3104             return backwards;
3105         };
3106 
3107         selProto.isBackwards = function() {
3108             return selectionIsBackwards(this);
3109         };
3110     } else {
3111         selectionIsBackwards = selProto.isBackwards = function() {
3112             return false;
3113         };
3114     }
3115 
3116     // Selection text
3117     // This is conformant to the new WHATWG DOM Range draft spec but differs from WebKit and Mozilla's implementation
3118     selProto.toString = function() {
3119 
3120         var rangeTexts = [];
3121         for (var i = 0, len = this.rangeCount; i < len; ++i) {
3122             rangeTexts[i] = "" + this._ranges[i];
3123         }
3124         return rangeTexts.join("");
3125     };
3126 
3127     function assertNodeInSameDocument(sel, node) {
3128         if (sel.anchorNode && (dom.getDocument(sel.anchorNode) !== dom.getDocument(node))) {
3129             throw new DOMException("WRONG_DOCUMENT_ERR");
3130         }
3131     }
3132 
3133     // No current browsers conform fully to the HTML 5 draft spec for this method, so Rangy's own method is always used
3134     selProto.collapse = function(node, offset) {
3135         assertNodeInSameDocument(this, node);
3136         var range = api.createRange(dom.getDocument(node));
3137         range.collapseToPoint(node, offset);
3138         this.removeAllRanges();
3139         this.addRange(range);
3140         this.isCollapsed = true;
3141     };
3142 
3143     selProto.collapseToStart = function() {
3144         if (this.rangeCount) {
3145             var range = this._ranges[0];
3146             this.collapse(range.startContainer, range.startOffset);
3147         } else {
3148             throw new DOMException("INVALID_STATE_ERR");
3149         }
3150     };
3151 
3152     selProto.collapseToEnd = function() {
3153         if (this.rangeCount) {
3154             var range = this._ranges[this.rangeCount - 1];
3155             this.collapse(range.endContainer, range.endOffset);
3156         } else {
3157             throw new DOMException("INVALID_STATE_ERR");
3158         }
3159     };
3160 
3161     // The HTML 5 spec is very specific on how selectAllChildren should be implemented so the native implementation is
3162     // never used by Rangy.
3163     selProto.selectAllChildren = function(node) {
3164         assertNodeInSameDocument(this, node);
3165         var range = api.createRange(dom.getDocument(node));
3166         range.selectNodeContents(node);
3167         this.removeAllRanges();
3168         this.addRange(range);
3169     };
3170 
3171     selProto.deleteFromDocument = function() {
3172         // Sepcial behaviour required for Control selections
3173         if (implementsControlRange && implementsDocSelection && this.docSelection.type == CONTROL) {
3174             var controlRange = this.docSelection.createRange();
3175             var element;
3176             while (controlRange.length) {
3177                 element = controlRange.item(0);
3178                 controlRange.remove(element);
3179                 element.parentNode.removeChild(element);
3180             }
3181             this.refresh();
3182         } else if (this.rangeCount) {
3183             var ranges = this.getAllRanges();
3184             this.removeAllRanges();
3185             for (var i = 0, len = ranges.length; i < len; ++i) {
3186                 ranges[i].deleteContents();
3187             }
3188             // The HTML5 spec says nothing about what the selection should contain after calling deleteContents on each
3189             // range. Firefox moves the selection to where the final selected range was, so we emulate that
3190             this.addRange(ranges[len - 1]);
3191         }
3192     };
3193 
3194     // The following are non-standard extensions
3195     selProto.getAllRanges = function() {
3196         return this._ranges.slice(0);
3197     };
3198 
3199     selProto.setSingleRange = function(range) {
3200         this.setRanges( [range] );
3201     };
3202 
3203     selProto.containsNode = function(node, allowPartial) {
3204         for (var i = 0, len = this._ranges.length; i < len; ++i) {
3205             if (this._ranges[i].containsNode(node, allowPartial)) {
3206                 return true;
3207             }
3208         }
3209         return false;
3210     };
3211 
3212     selProto.toHtml = function() {
3213         var html = "";
3214         if (this.rangeCount) {
3215             var container = DomRange.getRangeDocument(this._ranges[0]).createElement("div");
3216             for (var i = 0, len = this._ranges.length; i < len; ++i) {
3217                 container.appendChild(this._ranges[i].cloneContents());
3218             }
3219             html = container.innerHTML;
3220         }
3221         return html;
3222     };
3223 
3224     function inspect(sel) {
3225         var rangeInspects = [];
3226         var anchor = new DomPosition(sel.anchorNode, sel.anchorOffset);
3227         var focus = new DomPosition(sel.focusNode, sel.focusOffset);
3228         var name = (typeof sel.getName == "function") ? sel.getName() : "Selection";
3229 
3230         if (typeof sel.rangeCount != "undefined") {
3231             for (var i = 0, len = sel.rangeCount; i < len; ++i) {
3232                 rangeInspects[i] = DomRange.inspect(sel.getRangeAt(i));
3233             }
3234         }
3235         return "[" + name + "(Ranges: " + rangeInspects.join(", ") +
3236                 ")(anchor: " + anchor.inspect() + ", focus: " + focus.inspect() + "]";
3237 
3238     }
3239 
3240     selProto.getName = function() {
3241         return "WrappedSelection";
3242     };
3243 
3244     selProto.inspect = function() {
3245         return inspect(this);
3246     };
3247 
3248     selProto.detach = function() {
3249         this.win[windowPropertyName] = null;
3250         this.win = this.anchorNode = this.focusNode = null;
3251     };
3252 
3253     WrappedSelection.inspect = inspect;
3254 
3255     api.Selection = WrappedSelection;
3256 
3257     api.selectionPrototype = selProto;
3258 
3259     api.addCreateMissingNativeApiListener(function(win) {
3260         if (typeof win.getSelection == "undefined") {
3261             win.getSelection = function() {
3262                 return api.getSelection(this);
3263             };
3264         }
3265         win = null;
3266     });
3267 });
3268 
3269 // TODO we should avoid populating the global namespace
3270 window.rangy = rangy;
3271 return rangy;
3272 });
3273