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