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