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