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