PageSourceSearch

https://amberholl.ru/bitrix/js/fileman/html_editor/range.js

js amberholl.ru collected 2026-10-04 00:09:28 UTC 149,119 bytes, 3,746 lines download raw bytes

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

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.