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