MDL-64573 output: ajax form event
[moodle.git] / lib / editor / atto / yui / build / moodle-editor_atto-rangy / moodle-editor_atto-rangy.js
1 /**\r
2  * Rangy, a cross-browser JavaScript range and selection library\r
3  * https://github.com/timdown/rangy\r
4  *\r
5  * Copyright 2015, Tim Down\r
6  * Licensed under the MIT license.\r
7  * Version: 1.3.0\r
8  * Build date: 10 May 2015\r
9  */\r
10 \r
11 (function(factory, root) {\r
12     // No AMD or CommonJS support so we place Rangy in (probably) the global variable\r
13     root.rangy = factory();\r
14 })(function() {\r
15 \r
16     var OBJECT = "object", FUNCTION = "function", UNDEFINED = "undefined";\r
17 \r
18     // Minimal set of properties required for DOM Level 2 Range compliance. Comparison constants such as START_TO_START\r
19     // are omitted because ranges in KHTML do not have them but otherwise work perfectly well. See issue 113.\r
20     var domRangeProperties = ["startContainer", "startOffset", "endContainer", "endOffset", "collapsed",\r
21         "commonAncestorContainer"];\r
22 \r
23     // Minimal set of methods required for DOM Level 2 Range compliance\r
24     var domRangeMethods = ["setStart", "setStartBefore", "setStartAfter", "setEnd", "setEndBefore",\r
25         "setEndAfter", "collapse", "selectNode", "selectNodeContents", "compareBoundaryPoints", "deleteContents",\r
26         "extractContents", "cloneContents", "insertNode", "surroundContents", "cloneRange", "toString", "detach"];\r
27 \r
28     var textRangeProperties = ["boundingHeight", "boundingLeft", "boundingTop", "boundingWidth", "htmlText", "text"];\r
29 \r
30     // Subset of TextRange's full set of methods that we're interested in\r
31     var textRangeMethods = ["collapse", "compareEndPoints", "duplicate", "moveToElementText", "parentElement", "select",\r
32         "setEndPoint", "getBoundingClientRect"];\r
33 \r
34     /*----------------------------------------------------------------------------------------------------------------*/\r
35 \r
36     // Trio of functions taken from Peter Michaux's article:\r
37     // http://peter.michaux.ca/articles/feature-detection-state-of-the-art-browser-scripting\r
38     function isHostMethod(o, p) {\r
39         var t = typeof o[p];\r
40         return t == FUNCTION || (!!(t == OBJECT && o[p])) || t == "unknown";\r
41     }\r
42 \r
43     function isHostObject(o, p) {\r
44         return !!(typeof o[p] == OBJECT && o[p]);\r
45     }\r
46 \r
47     function isHostProperty(o, p) {\r
48         return typeof o[p] != UNDEFINED;\r
49     }\r
50 \r
51     // Creates a convenience function to save verbose repeated calls to tests functions\r
52     function createMultiplePropertyTest(testFunc) {\r
53         return function(o, props) {\r
54             var i = props.length;\r
55             while (i--) {\r
56                 if (!testFunc(o, props[i])) {\r
57                     return false;\r
58                 }\r
59             }\r
60             return true;\r
61         };\r
62     }\r
63 \r
64     // Next trio of functions are a convenience to save verbose repeated calls to previous two functions\r
65     var areHostMethods = createMultiplePropertyTest(isHostMethod);\r
66     var areHostObjects = createMultiplePropertyTest(isHostObject);\r
67     var areHostProperties = createMultiplePropertyTest(isHostProperty);\r
68 \r
69     function isTextRange(range) {\r
70         return range && areHostMethods(range, textRangeMethods) && areHostProperties(range, textRangeProperties);\r
71     }\r
72 \r
73     function getBody(doc) {\r
74         return isHostObject(doc, "body") ? doc.body : doc.getElementsByTagName("body")[0];\r
75     }\r
76 \r
77     var forEach = [].forEach ?\r
78         function(arr, func) {\r
79             arr.forEach(func);\r
80         } :\r
81         function(arr, func) {\r
82             for (var i = 0, len = arr.length; i < len; ++i) {\r
83                 func(arr[i], i);\r
84             }\r
85         };\r
86 \r
87     var modules = {};\r
88 \r
89     var isBrowser = (typeof window != UNDEFINED && typeof document != UNDEFINED);\r
90 \r
91     var util = {\r
92         isHostMethod: isHostMethod,\r
93         isHostObject: isHostObject,\r
94         isHostProperty: isHostProperty,\r
95         areHostMethods: areHostMethods,\r
96         areHostObjects: areHostObjects,\r
97         areHostProperties: areHostProperties,\r
98         isTextRange: isTextRange,\r
99         getBody: getBody,\r
100         forEach: forEach\r
101     };\r
102 \r
103     var api = {\r
104         version: "1.3.0",\r
105         initialized: false,\r
106         isBrowser: isBrowser,\r
107         supported: true,\r
108         util: util,\r
109         features: {},\r
110         modules: modules,\r
111         config: {\r
112             alertOnFail: false,\r
113             alertOnWarn: false,\r
114             preferTextRange: false,\r
115             autoInitialize: (typeof rangyAutoInitialize == UNDEFINED) ? true : rangyAutoInitialize\r
116         }\r
117     };\r
118 \r
119     function consoleLog(msg) {\r
120         if (typeof console != UNDEFINED && isHostMethod(console, "log")) {\r
121             console.log(msg);\r
122         }\r
123     }\r
124 \r
125     function alertOrLog(msg, shouldAlert) {\r
126         if (isBrowser && shouldAlert) {\r
127             alert(msg);\r
128         } else  {\r
129             consoleLog(msg);\r
130         }\r
131     }\r
132 \r
133     function fail(reason) {\r
134         api.initialized = true;\r
135         api.supported = false;\r
136         alertOrLog("Rangy is not supported in this environment. Reason: " + reason, api.config.alertOnFail);\r
137     }\r
138 \r
139     api.fail = fail;\r
140 \r
141     function warn(msg) {\r
142         alertOrLog("Rangy warning: " + msg, api.config.alertOnWarn);\r
143     }\r
144 \r
145     api.warn = warn;\r
146 \r
147     // Add utility extend() method\r
148     var extend;\r
149     if ({}.hasOwnProperty) {\r
150         util.extend = extend = function(obj, props, deep) {\r
151             var o, p;\r
152             for (var i in props) {\r
153                 if (props.hasOwnProperty(i)) {\r
154                     o = obj[i];\r
155                     p = props[i];\r
156                     if (deep && o !== null && typeof o == "object" && p !== null && typeof p == "object") {\r
157                         extend(o, p, true);\r
158                     }\r
159                     obj[i] = p;\r
160                 }\r
161             }\r
162             // Special case for toString, which does not show up in for...in loops in IE <= 8\r
163             if (props.hasOwnProperty("toString")) {\r
164                 obj.toString = props.toString;\r
165             }\r
166             return obj;\r
167         };\r
168 \r
169         util.createOptions = function(optionsParam, defaults) {\r
170             var options = {};\r
171             extend(options, defaults);\r
172             if (optionsParam) {\r
173                 extend(options, optionsParam);\r
174             }\r
175             return options;\r
176         };\r
177     } else {\r
178         fail("hasOwnProperty not supported");\r
179     }\r
180 \r
181     // Test whether we're in a browser and bail out if not\r
182     if (!isBrowser) {\r
183         fail("Rangy can only run in a browser");\r
184     }\r
185 \r
186     // Test whether Array.prototype.slice can be relied on for NodeLists and use an alternative toArray() if not\r
187     (function() {\r
188         var toArray;\r
189 \r
190         if (isBrowser) {\r
191             var el = document.createElement("div");\r
192             el.appendChild(document.createElement("span"));\r
193             var slice = [].slice;\r
194             try {\r
195                 if (slice.call(el.childNodes, 0)[0].nodeType == 1) {\r
196                     toArray = function(arrayLike) {\r
197                         return slice.call(arrayLike, 0);\r
198                     };\r
199                 }\r
200             } catch (e) {}\r
201         }\r
202 \r
203         if (!toArray) {\r
204             toArray = function(arrayLike) {\r
205                 var arr = [];\r
206                 for (var i = 0, len = arrayLike.length; i < len; ++i) {\r
207                     arr[i] = arrayLike[i];\r
208                 }\r
209                 return arr;\r
210             };\r
211         }\r
212 \r
213         util.toArray = toArray;\r
214     })();\r
215 \r
216     // Very simple event handler wrapper function that doesn't attempt to solve issues such as "this" handling or\r
217     // normalization of event properties\r
218     var addListener;\r
219     if (isBrowser) {\r
220         if (isHostMethod(document, "addEventListener")) {\r
221             addListener = function(obj, eventType, listener) {\r
222                 obj.addEventListener(eventType, listener, false);\r
223             };\r
224         } else if (isHostMethod(document, "attachEvent")) {\r
225             addListener = function(obj, eventType, listener) {\r
226                 obj.attachEvent("on" + eventType, listener);\r
227             };\r
228         } else {\r
229             fail("Document does not have required addEventListener or attachEvent method");\r
230         }\r
231 \r
232         util.addListener = addListener;\r
233     }\r
234 \r
235     var initListeners = [];\r
236 \r
237     function getErrorDesc(ex) {\r
238         return ex.message || ex.description || String(ex);\r
239     }\r
240 \r
241     // Initialization\r
242     function init() {\r
243         if (!isBrowser || api.initialized) {\r
244             return;\r
245         }\r
246         var testRange;\r
247         var implementsDomRange = false, implementsTextRange = false;\r
248 \r
249         // First, perform basic feature tests\r
250 \r
251         if (isHostMethod(document, "createRange")) {\r
252             testRange = document.createRange();\r
253             if (areHostMethods(testRange, domRangeMethods) && areHostProperties(testRange, domRangeProperties)) {\r
254                 implementsDomRange = true;\r
255             }\r
256         }\r
257 \r
258         var body = getBody(document);\r
259         if (!body || body.nodeName.toLowerCase() != "body") {\r
260             fail("No body element found");\r
261             return;\r
262         }\r
263 \r
264         if (body && isHostMethod(body, "createTextRange")) {\r
265             testRange = body.createTextRange();\r
266             if (isTextRange(testRange)) {\r
267                 implementsTextRange = true;\r
268             }\r
269         }\r
270 \r
271         if (!implementsDomRange && !implementsTextRange) {\r
272             fail("Neither Range nor TextRange are available");\r
273             return;\r
274         }\r
275 \r
276         api.initialized = true;\r
277         api.features = {\r
278             implementsDomRange: implementsDomRange,\r
279             implementsTextRange: implementsTextRange\r
280         };\r
281 \r
282         // Initialize modules\r
283         var module, errorMessage;\r
284         for (var moduleName in modules) {\r
285             if ( (module = modules[moduleName]) instanceof Module ) {\r
286                 module.init(module, api);\r
287             }\r
288         }\r
289 \r
290         // Call init listeners\r
291         for (var i = 0, len = initListeners.length; i < len; ++i) {\r
292             try {\r
293                 initListeners[i](api);\r
294             } catch (ex) {\r
295                 errorMessage = "Rangy init listener threw an exception. Continuing. Detail: " + getErrorDesc(ex);\r
296                 consoleLog(errorMessage);\r
297             }\r
298         }\r
299     }\r
300 \r
301     function deprecationNotice(deprecated, replacement, module) {\r
302         if (module) {\r
303             deprecated += " in module " + module.name;\r
304         }\r
305         api.warn("DEPRECATED: " + deprecated + " is deprecated. Please use " +\r
306         replacement + " instead.");\r
307     }\r
308 \r
309     function createAliasForDeprecatedMethod(owner, deprecated, replacement, module) {\r
310         owner[deprecated] = function() {\r
311             deprecationNotice(deprecated, replacement, module);\r
312             return owner[replacement].apply(owner, util.toArray(arguments));\r
313         };\r
314     }\r
315 \r
316     util.deprecationNotice = deprecationNotice;\r
317     util.createAliasForDeprecatedMethod = createAliasForDeprecatedMethod;\r
318 \r
319     // Allow external scripts to initialize this library in case it's loaded after the document has loaded\r
320     api.init = init;\r
321 \r
322     // Execute listener immediately if already initialized\r
323     api.addInitListener = function(listener) {\r
324         if (api.initialized) {\r
325             listener(api);\r
326         } else {\r
327             initListeners.push(listener);\r
328         }\r
329     };\r
330 \r
331     var shimListeners = [];\r
332 \r
333     api.addShimListener = function(listener) {\r
334         shimListeners.push(listener);\r
335     };\r
336 \r
337     function shim(win) {\r
338         win = win || window;\r
339         init();\r
340 \r
341         // Notify listeners\r
342         for (var i = 0, len = shimListeners.length; i < len; ++i) {\r
343             shimListeners[i](win);\r
344         }\r
345     }\r
346 \r
347     if (isBrowser) {\r
348         api.shim = api.createMissingNativeApi = shim;\r
349         createAliasForDeprecatedMethod(api, "createMissingNativeApi", "shim");\r
350     }\r
351 \r
352     function Module(name, dependencies, initializer) {\r
353         this.name = name;\r
354         this.dependencies = dependencies;\r
355         this.initialized = false;\r
356         this.supported = false;\r
357         this.initializer = initializer;\r
358     }\r
359 \r
360     Module.prototype = {\r
361         init: function() {\r
362             var requiredModuleNames = this.dependencies || [];\r
363             for (var i = 0, len = requiredModuleNames.length, requiredModule, moduleName; i < len; ++i) {\r
364                 moduleName = requiredModuleNames[i];\r
365 \r
366                 requiredModule = modules[moduleName];\r
367                 if (!requiredModule || !(requiredModule instanceof Module)) {\r
368                     throw new Error("required module '" + moduleName + "' not found");\r
369                 }\r
370 \r
371                 requiredModule.init();\r
372 \r
373                 if (!requiredModule.supported) {\r
374                     throw new Error("required module '" + moduleName + "' not supported");\r
375                 }\r
376             }\r
377 \r
378             // Now run initializer\r
379             this.initializer(this);\r
380         },\r
381 \r
382         fail: function(reason) {\r
383             this.initialized = true;\r
384             this.supported = false;\r
385             throw new Error(reason);\r
386         },\r
387 \r
388         warn: function(msg) {\r
389             api.warn("Module " + this.name + ": " + msg);\r
390         },\r
391 \r
392         deprecationNotice: function(deprecated, replacement) {\r
393             api.warn("DEPRECATED: " + deprecated + " in module " + this.name + " is deprecated. Please use " +\r
394                 replacement + " instead");\r
395         },\r
396 \r
397         createError: function(msg) {\r
398             return new Error("Error in Rangy " + this.name + " module: " + msg);\r
399         }\r
400     };\r
401 \r
402     function createModule(name, dependencies, initFunc) {\r
403         var newModule = new Module(name, dependencies, function(module) {\r
404             if (!module.initialized) {\r
405                 module.initialized = true;\r
406                 try {\r
407                     initFunc(api, module);\r
408                     module.supported = true;\r
409                 } catch (ex) {\r
410                     var errorMessage = "Module '" + name + "' failed to load: " + getErrorDesc(ex);\r
411                     consoleLog(errorMessage);\r
412                     if (ex.stack) {\r
413                         consoleLog(ex.stack);\r
414                     }\r
415                 }\r
416             }\r
417         });\r
418         modules[name] = newModule;\r
419         return newModule;\r
420     }\r
421 \r
422     api.createModule = function(name) {\r
423         // Allow 2 or 3 arguments (second argument is an optional array of dependencies)\r
424         var initFunc, dependencies;\r
425         if (arguments.length == 2) {\r
426             initFunc = arguments[1];\r
427             dependencies = [];\r
428         } else {\r
429             initFunc = arguments[2];\r
430             dependencies = arguments[1];\r
431         }\r
432 \r
433         var module = createModule(name, dependencies, initFunc);\r
434 \r
435         // Initialize the module immediately if the core is already initialized\r
436         if (api.initialized && api.supported) {\r
437             module.init();\r
438         }\r
439     };\r
440 \r
441     api.createCoreModule = function(name, dependencies, initFunc) {\r
442         createModule(name, dependencies, initFunc);\r
443     };\r
444 \r
445     /*----------------------------------------------------------------------------------------------------------------*/\r
446 \r
447     // Ensure rangy.rangePrototype and rangy.selectionPrototype are available immediately\r
448 \r
449     function RangePrototype() {}\r
450     api.RangePrototype = RangePrototype;\r
451     api.rangePrototype = new RangePrototype();\r
452 \r
453     function SelectionPrototype() {}\r
454     api.selectionPrototype = new SelectionPrototype();\r
455 \r
456     /*----------------------------------------------------------------------------------------------------------------*/\r
457 \r
458     // DOM utility methods used by Rangy
459     api.createCoreModule("DomUtil", [], function(api, module) {
460         var UNDEF = "undefined";
461         var util = api.util;
462         var getBody = util.getBody;
464         // Perform feature tests
465         if (!util.areHostMethods(document, ["createDocumentFragment", "createElement", "createTextNode"])) {
466             module.fail("document missing a Node creation method");
467         }
469         if (!util.isHostMethod(document, "getElementsByTagName")) {
470             module.fail("document missing getElementsByTagName method");
471         }
473         var el = document.createElement("div");
474         if (!util.areHostMethods(el, ["insertBefore", "appendChild", "cloneNode"] ||
475                 !util.areHostObjects(el, ["previousSibling", "nextSibling", "childNodes", "parentNode"]))) {
476             module.fail("Incomplete Element implementation");
477         }
479         // innerHTML is required for Range's createContextualFragment method
480         if (!util.isHostProperty(el, "innerHTML")) {
481             module.fail("Element is missing innerHTML property");
482         }
484         var textNode = document.createTextNode("test");
485         if (!util.areHostMethods(textNode, ["splitText", "deleteData", "insertData", "appendData", "cloneNode"] ||
486                 !util.areHostObjects(el, ["previousSibling", "nextSibling", "childNodes", "parentNode"]) ||
487                 !util.areHostProperties(textNode, ["data"]))) {
488             module.fail("Incomplete Text Node implementation");
489         }
491         /*----------------------------------------------------------------------------------------------------------------*/
493         // Removed use of indexOf because of a bizarre bug in Opera that is thrown in one of the Acid3 tests. I haven't been
494         // able to replicate it outside of the test. The bug is that indexOf returns -1 when called on an Array that
495         // contains just the document as a single element and the value searched for is the document.
496         var arrayContains = /*Array.prototype.indexOf ?
497             function(arr, val) {
498                 return arr.indexOf(val) > -1;
499             }:*/
501             function(arr, val) {
502                 var i = arr.length;
503                 while (i--) {
504                     if (arr[i] === val) {
505                         return true;
506                     }
507                 }
508                 return false;
509             };
511         // Opera 11 puts HTML elements in the null namespace, it seems, and IE 7 has undefined namespaceURI
512         function isHtmlNamespace(node) {
513             var ns;
514             return typeof node.namespaceURI == UNDEF || ((ns = node.namespaceURI) === null || ns == "http://www.w3.org/1999/xhtml");
515         }
517         function parentElement(node) {
518             var parent = node.parentNode;
519             return (parent.nodeType == 1) ? parent : null;
520         }
522         function getNodeIndex(node) {
523             var i = 0;
524             while( (node = node.previousSibling) ) {
525                 ++i;
526             }
527             return i;
528         }
530         function getNodeLength(node) {
531             switch (node.nodeType) {
532                 case 7:
533                 case 10:
534                     return 0;
535                 case 3:
536                 case 8:
537                     return node.length;
538                 default:
539                     return node.childNodes.length;
540             }
541         }
543         function getCommonAncestor(node1, node2) {
544             var ancestors = [], n;
545             for (n = node1; n; n = n.parentNode) {
546                 ancestors.push(n);
547             }
549             for (n = node2; n; n = n.parentNode) {
550                 if (arrayContains(ancestors, n)) {
551                     return n;
552                 }
553             }
555             return null;
556         }
558         function isAncestorOf(ancestor, descendant, selfIsAncestor) {
559             var n = selfIsAncestor ? descendant : descendant.parentNode;
560             while (n) {
561                 if (n === ancestor) {
562                     return true;
563                 } else {
564                     n = n.parentNode;
565                 }
566             }
567             return false;
568         }
570         function isOrIsAncestorOf(ancestor, descendant) {
571             return isAncestorOf(ancestor, descendant, true);
572         }
574         function getClosestAncestorIn(node, ancestor, selfIsAncestor) {
575             var p, n = selfIsAncestor ? node : node.parentNode;
576             while (n) {
577                 p = n.parentNode;
578                 if (p === ancestor) {
579                     return n;
580                 }
581                 n = p;
582             }
583             return null;
584         }
586         function isCharacterDataNode(node) {
587             var t = node.nodeType;
588             return t == 3 || t == 4 || t == 8 ; // Text, CDataSection or Comment
589         }
591         function isTextOrCommentNode(node) {
592             if (!node) {
593                 return false;
594             }
595             var t = node.nodeType;
596             return t == 3 || t == 8 ; // Text or Comment
597         }
599         function insertAfter(node, precedingNode) {
600             var nextNode = precedingNode.nextSibling, parent = precedingNode.parentNode;
601             if (nextNode) {
602                 parent.insertBefore(node, nextNode);
603             } else {
604                 parent.appendChild(node);
605             }
606             return node;
607         }
609         // Note that we cannot use splitText() because it is bugridden in IE 9.
610         function splitDataNode(node, index, positionsToPreserve) {
611             var newNode = node.cloneNode(false);
612             newNode.deleteData(0, index);
613             node.deleteData(index, node.length - index);
614             insertAfter(newNode, node);
616             // Preserve positions
617             if (positionsToPreserve) {
618                 for (var i = 0, position; position = positionsToPreserve[i++]; ) {
619                     // Handle case where position was inside the portion of node after the split point
620                     if (position.node == node && position.offset > index) {
621                         position.node = newNode;
622                         position.offset -= index;
623                     }
624                     // Handle the case where the position is a node offset within node's parent
625                     else if (position.node == node.parentNode && position.offset > getNodeIndex(node)) {
626                         ++position.offset;
627                     }
628                 }
629             }
630             return newNode;
631         }
633         function getDocument(node) {
634             if (node.nodeType == 9) {
635                 return node;
636             } else if (typeof node.ownerDocument != UNDEF) {
637                 return node.ownerDocument;
638             } else if (typeof node.document != UNDEF) {
639                 return node.document;
640             } else if (node.parentNode) {
641                 return getDocument(node.parentNode);
642             } else {
643                 throw module.createError("getDocument: no document found for node");
644             }
645         }
647         function getWindow(node) {
648             var doc = getDocument(node);
649             if (typeof doc.defaultView != UNDEF) {
650                 return doc.defaultView;
651             } else if (typeof doc.parentWindow != UNDEF) {
652                 return doc.parentWindow;
653             } else {
654                 throw module.createError("Cannot get a window object for node");
655             }
656         }
658         function getIframeDocument(iframeEl) {
659             if (typeof iframeEl.contentDocument != UNDEF) {
660                 return iframeEl.contentDocument;
661             } else if (typeof iframeEl.contentWindow != UNDEF) {
662                 return iframeEl.contentWindow.document;
663             } else {
664                 throw module.createError("getIframeDocument: No Document object found for iframe element");
665             }
666         }
668         function getIframeWindow(iframeEl) {
669             if (typeof iframeEl.contentWindow != UNDEF) {
670                 return iframeEl.contentWindow;
671             } else if (typeof iframeEl.contentDocument != UNDEF) {
672                 return iframeEl.contentDocument.defaultView;
673             } else {
674                 throw module.createError("getIframeWindow: No Window object found for iframe element");
675             }
676         }
678         // This looks bad. Is it worth it?
679         function isWindow(obj) {
680             return obj && util.isHostMethod(obj, "setTimeout") && util.isHostObject(obj, "document");
681         }
683         function getContentDocument(obj, module, methodName) {
684             var doc;
686             if (!obj) {
687                 doc = document;
688             }
690             // Test if a DOM node has been passed and obtain a document object for it if so
691             else if (util.isHostProperty(obj, "nodeType")) {
692                 doc = (obj.nodeType == 1 && obj.tagName.toLowerCase() == "iframe") ?
693                     getIframeDocument(obj) : getDocument(obj);
694             }
696             // Test if the doc parameter appears to be a Window object
697             else if (isWindow(obj)) {
698                 doc = obj.document;
699             }
701             if (!doc) {
702                 throw module.createError(methodName + "(): Parameter must be a Window object or DOM node");
703             }
705             return doc;
706         }
708         function getRootContainer(node) {
709             var parent;
710             while ( (parent = node.parentNode) ) {
711                 node = parent;
712             }
713             return node;
714         }
716         function comparePoints(nodeA, offsetA, nodeB, offsetB) {
717             // See http://www.w3.org/TR/DOM-Level-2-Traversal-Range/ranges.html#Level-2-Range-Comparing
718             var nodeC, root, childA, childB, n;
719             if (nodeA == nodeB) {
720                 // Case 1: nodes are the same
721                 return offsetA === offsetB ? 0 : (offsetA < offsetB) ? -1 : 1;
722             } else if ( (nodeC = getClosestAncestorIn(nodeB, nodeA, true)) ) {
723                 // Case 2: node C (container B or an ancestor) is a child node of A
724                 return offsetA <= getNodeIndex(nodeC) ? -1 : 1;
725             } else if ( (nodeC = getClosestAncestorIn(nodeA, nodeB, true)) ) {
726                 // Case 3: node C (container A or an ancestor) is a child node of B
727                 return getNodeIndex(nodeC) < offsetB  ? -1 : 1;
728             } else {
729                 root = getCommonAncestor(nodeA, nodeB);
730                 if (!root) {
731                     throw new Error("comparePoints error: nodes have no common ancestor");
732                 }
734                 // Case 4: containers are siblings or descendants of siblings
735                 childA = (nodeA === root) ? root : getClosestAncestorIn(nodeA, root, true);
736                 childB = (nodeB === root) ? root : getClosestAncestorIn(nodeB, root, true);
738                 if (childA === childB) {
739                     // This shouldn't be possible
740                     throw module.createError("comparePoints got to case 4 and childA and childB are the same!");
741                 } else {
742                     n = root.firstChild;
743                     while (n) {
744                         if (n === childA) {
745                             return -1;
746                         } else if (n === childB) {
747                             return 1;
748                         }
749                         n = n.nextSibling;
750                     }
751                 }
752             }
753         }
755         /*----------------------------------------------------------------------------------------------------------------*/
757         // Test for IE's crash (IE 6/7) or exception (IE >= 8) when a reference to garbage-collected text node is queried
758         var crashyTextNodes = false;
760         function isBrokenNode(node) {
761             var n;
762             try {
763                 n = node.parentNode;
764                 return false;
765             } catch (e) {
766                 return true;
767             }
768         }
770         (function() {
771             var el = document.createElement("b");
772             el.innerHTML = "1";
773             var textNode = el.firstChild;
774             el.innerHTML = "<br />";
775             crashyTextNodes = isBrokenNode(textNode);
777             api.features.crashyTextNodes = crashyTextNodes;
778         })();
780         /*----------------------------------------------------------------------------------------------------------------*/
782         function inspectNode(node) {
783             if (!node) {
784                 return "[No node]";
785             }
786             if (crashyTextNodes && isBrokenNode(node)) {
787                 return "[Broken node]";
788             }
789             if (isCharacterDataNode(node)) {
790                 return '"' + node.data + '"';
791             }
792             if (node.nodeType == 1) {
793                 var idAttr = node.id ? ' id="' + node.id + '"' : "";
794                 return "<" + node.nodeName + idAttr + ">[index:" + getNodeIndex(node) + ",length:" + node.childNodes.length + "][" + (node.innerHTML || "[innerHTML not supported]").slice(0, 25) + "]";
795             }
796             return node.nodeName;
797         }
799         function fragmentFromNodeChildren(node) {
800             var fragment = getDocument(node).createDocumentFragment(), child;
801             while ( (child = node.firstChild) ) {
802                 fragment.appendChild(child);
803             }
804             return fragment;
805         }
807         var getComputedStyleProperty;
808         if (typeof window.getComputedStyle != UNDEF) {
809             getComputedStyleProperty = function(el, propName) {
810                 return getWindow(el).getComputedStyle(el, null)[propName];
811             };
812         } else if (typeof document.documentElement.currentStyle != UNDEF) {
813             getComputedStyleProperty = function(el, propName) {
814                 return el.currentStyle ? el.currentStyle[propName] : "";
815             };
816         } else {
817             module.fail("No means of obtaining computed style properties found");
818         }
820         function createTestElement(doc, html, contentEditable) {
821             var body = getBody(doc);
822             var el = doc.createElement("div");
823             el.contentEditable = "" + !!contentEditable;
824             if (html) {
825                 el.innerHTML = html;
826             }
828             // Insert the test element at the start of the body to prevent scrolling to the bottom in iOS (issue #292)
829             var bodyFirstChild = body.firstChild;
830             if (bodyFirstChild) {
831                 body.insertBefore(el, bodyFirstChild);
832             } else {
833                 body.appendChild(el);
834             }
836             return el;
837         }
839         function removeNode(node) {
840             return node.parentNode.removeChild(node);
841         }
843         function NodeIterator(root) {
844             this.root = root;
845             this._next = root;
846         }
848         NodeIterator.prototype = {
849             _current: null,
851             hasNext: function() {
852                 return !!this._next;
853             },
855             next: function() {
856                 var n = this._current = this._next;
857                 var child, next;
858                 if (this._current) {
859                     child = n.firstChild;
860                     if (child) {
861                         this._next = child;
862                     } else {
863                         next = null;
864                         while ((n !== this.root) && !(next = n.nextSibling)) {
865                             n = n.parentNode;
866                         }
867                         this._next = next;
868                     }
869                 }
870                 return this._current;
871             },
873             detach: function() {
874                 this._current = this._next = this.root = null;
875             }
876         };
878         function createIterator(root) {
879             return new NodeIterator(root);
880         }
882         function DomPosition(node, offset) {
883             this.node = node;
884             this.offset = offset;
885         }
887         DomPosition.prototype = {
888             equals: function(pos) {
889                 return !!pos && this.node === pos.node && this.offset == pos.offset;
890             },
892             inspect: function() {
893                 return "[DomPosition(" + inspectNode(this.node) + ":" + this.offset + ")]";
894             },
896             toString: function() {
897                 return this.inspect();
898             }
899         };
901         function DOMException(codeName) {
902             this.code = this[codeName];
903             this.codeName = codeName;
904             this.message = "DOMException: " + this.codeName;
905         }
907         DOMException.prototype = {
908             INDEX_SIZE_ERR: 1,
909             HIERARCHY_REQUEST_ERR: 3,
910             WRONG_DOCUMENT_ERR: 4,
911             NO_MODIFICATION_ALLOWED_ERR: 7,
912             NOT_FOUND_ERR: 8,
913             NOT_SUPPORTED_ERR: 9,
914             INVALID_STATE_ERR: 11,
915             INVALID_NODE_TYPE_ERR: 24
916         };
918         DOMException.prototype.toString = function() {
919             return this.message;
920         };
922         api.dom = {
923             arrayContains: arrayContains,
924             isHtmlNamespace: isHtmlNamespace,
925             parentElement: parentElement,
926             getNodeIndex: getNodeIndex,
927             getNodeLength: getNodeLength,
928             getCommonAncestor: getCommonAncestor,
929             isAncestorOf: isAncestorOf,
930             isOrIsAncestorOf: isOrIsAncestorOf,
931             getClosestAncestorIn: getClosestAncestorIn,
932             isCharacterDataNode: isCharacterDataNode,
933             isTextOrCommentNode: isTextOrCommentNode,
934             insertAfter: insertAfter,
935             splitDataNode: splitDataNode,
936             getDocument: getDocument,
937             getWindow: getWindow,
938             getIframeWindow: getIframeWindow,
939             getIframeDocument: getIframeDocument,
940             getBody: getBody,
941             isWindow: isWindow,
942             getContentDocument: getContentDocument,
943             getRootContainer: getRootContainer,
944             comparePoints: comparePoints,
945             isBrokenNode: isBrokenNode,
946             inspectNode: inspectNode,
947             getComputedStyleProperty: getComputedStyleProperty,
948             createTestElement: createTestElement,
949             removeNode: removeNode,
950             fragmentFromNodeChildren: fragmentFromNodeChildren,
951             createIterator: createIterator,
952             DomPosition: DomPosition
953         };
955         api.DOMException = DOMException;
956     });\r
957 \r
958     /*----------------------------------------------------------------------------------------------------------------*/\r
959 \r
960     // Pure JavaScript implementation of DOM Range
961     api.createCoreModule("DomRange", ["DomUtil"], function(api, module) {
962         var dom = api.dom;
963         var util = api.util;
964         var DomPosition = dom.DomPosition;
965         var DOMException = api.DOMException;
967         var isCharacterDataNode = dom.isCharacterDataNode;
968         var getNodeIndex = dom.getNodeIndex;
969         var isOrIsAncestorOf = dom.isOrIsAncestorOf;
970         var getDocument = dom.getDocument;
971         var comparePoints = dom.comparePoints;
972         var splitDataNode = dom.splitDataNode;
973         var getClosestAncestorIn = dom.getClosestAncestorIn;
974         var getNodeLength = dom.getNodeLength;
975         var arrayContains = dom.arrayContains;
976         var getRootContainer = dom.getRootContainer;
977         var crashyTextNodes = api.features.crashyTextNodes;
979         var removeNode = dom.removeNode;
981         /*----------------------------------------------------------------------------------------------------------------*/
983         // Utility functions
985         function isNonTextPartiallySelected(node, range) {
986             return (node.nodeType != 3) &&
987                    (isOrIsAncestorOf(node, range.startContainer) || isOrIsAncestorOf(node, range.endContainer));
988         }
990         function getRangeDocument(range) {
991             return range.document || getDocument(range.startContainer);
992         }
994         function getRangeRoot(range) {
995             return getRootContainer(range.startContainer);
996         }
998         function getBoundaryBeforeNode(node) {
999             return new DomPosition(node.parentNode, getNodeIndex(node));
1000         }
1002         function getBoundaryAfterNode(node) {
1003             return new DomPosition(node.parentNode, getNodeIndex(node) + 1);
1004         }
1006         function insertNodeAtPosition(node, n, o) {
1007             var firstNodeInserted = node.nodeType == 11 ? node.firstChild : node;
1008             if (isCharacterDataNode(n)) {
1009                 if (o == n.length) {
1010                     dom.insertAfter(node, n);
1011                 } else {
1012                     n.parentNode.insertBefore(node, o == 0 ? n : splitDataNode(n, o));
1013                 }
1014             } else if (o >= n.childNodes.length) {
1015                 n.appendChild(node);
1016             } else {
1017                 n.insertBefore(node, n.childNodes[o]);
1018             }
1019             return firstNodeInserted;
1020         }
1022         function rangesIntersect(rangeA, rangeB, touchingIsIntersecting) {
1023             assertRangeValid(rangeA);
1024             assertRangeValid(rangeB);
1026             if (getRangeDocument(rangeB) != getRangeDocument(rangeA)) {
1027                 throw new DOMException("WRONG_DOCUMENT_ERR");
1028             }
1030             var startComparison = comparePoints(rangeA.startContainer, rangeA.startOffset, rangeB.endContainer, rangeB.endOffset),
1031                 endComparison = comparePoints(rangeA.endContainer, rangeA.endOffset, rangeB.startContainer, rangeB.startOffset);
1033             return touchingIsIntersecting ? startComparison <= 0 && endComparison >= 0 : startComparison < 0 && endComparison > 0;
1034         }
1036         function cloneSubtree(iterator) {
1037             var partiallySelected;
1038             for (var node, frag = getRangeDocument(iterator.range).createDocumentFragment(), subIterator; node = iterator.next(); ) {
1039                 partiallySelected = iterator.isPartiallySelectedSubtree();
1040                 node = node.cloneNode(!partiallySelected);
1041                 if (partiallySelected) {
1042                     subIterator = iterator.getSubtreeIterator();
1043                     node.appendChild(cloneSubtree(subIterator));
1044                     subIterator.detach();
1045                 }
1047                 if (node.nodeType == 10) { // DocumentType
1048                     throw new DOMException("HIERARCHY_REQUEST_ERR");
1049                 }
1050                 frag.appendChild(node);
1051             }
1052             return frag;
1053         }
1055         function iterateSubtree(rangeIterator, func, iteratorState) {
1056             var it, n;
1057             iteratorState = iteratorState || { stop: false };
1058             for (var node, subRangeIterator; node = rangeIterator.next(); ) {
1059                 if (rangeIterator.isPartiallySelectedSubtree()) {
1060                     if (func(node) === false) {
1061                         iteratorState.stop = true;
1062                         return;
1063                     } else {
1064                         // The node is partially selected by the Range, so we can use a new RangeIterator on the portion of
1065                         // the node selected by the Range.
1066                         subRangeIterator = rangeIterator.getSubtreeIterator();
1067                         iterateSubtree(subRangeIterator, func, iteratorState);
1068                         subRangeIterator.detach();
1069                         if (iteratorState.stop) {
1070                             return;
1071                         }
1072                     }
1073                 } else {
1074                     // The whole node is selected, so we can use efficient DOM iteration to iterate over the node and its
1075                     // descendants
1076                     it = dom.createIterator(node);
1077                     while ( (n = it.next()) ) {
1078                         if (func(n) === false) {
1079                             iteratorState.stop = true;
1080                             return;
1081                         }
1082                     }
1083                 }
1084             }
1085         }
1087         function deleteSubtree(iterator) {
1088             var subIterator;
1089             while (iterator.next()) {
1090                 if (iterator.isPartiallySelectedSubtree()) {
1091                     subIterator = iterator.getSubtreeIterator();
1092                     deleteSubtree(subIterator);
1093                     subIterator.detach();
1094                 } else {
1095                     iterator.remove();
1096                 }
1097             }
1098         }
1100         function extractSubtree(iterator) {
1101             for (var node, frag = getRangeDocument(iterator.range).createDocumentFragment(), subIterator; node = iterator.next(); ) {
1103                 if (iterator.isPartiallySelectedSubtree()) {
1104                     node = node.cloneNode(false);
1105                     subIterator = iterator.getSubtreeIterator();
1106                     node.appendChild(extractSubtree(subIterator));
1107                     subIterator.detach();
1108                 } else {
1109                     iterator.remove();
1110                 }
1111                 if (node.nodeType == 10) { // DocumentType
1112                     throw new DOMException("HIERARCHY_REQUEST_ERR");
1113                 }
1114                 frag.appendChild(node);
1115             }
1116             return frag;
1117         }
1119         function getNodesInRange(range, nodeTypes, filter) {
1120             var filterNodeTypes = !!(nodeTypes && nodeTypes.length), regex;
1121             var filterExists = !!filter;
1122             if (filterNodeTypes) {
1123                 regex = new RegExp("^(" + nodeTypes.join("|") + ")$");
1124             }
1126             var nodes = [];
1127             iterateSubtree(new RangeIterator(range, false), function(node) {
1128                 if (filterNodeTypes && !regex.test(node.nodeType)) {
1129                     return;
1130                 }
1131                 if (filterExists && !filter(node)) {
1132                     return;
1133                 }
1134                 // Don't include a boundary container if it is a character data node and the range does not contain any
1135                 // of its character data. See issue 190.
1136                 var sc = range.startContainer;
1137                 if (node == sc && isCharacterDataNode(sc) && range.startOffset == sc.length) {
1138                     return;
1139                 }
1141                 var ec = range.endContainer;
1142                 if (node == ec && isCharacterDataNode(ec) && range.endOffset == 0) {
1143                     return;
1144                 }
1146                 nodes.push(node);
1147             });
1148             return nodes;
1149         }
1151         function inspect(range) {
1152             var name = (typeof range.getName == "undefined") ? "Range" : range.getName();
1153             return "[" + name + "(" + dom.inspectNode(range.startContainer) + ":" + range.startOffset + ", " +
1154                     dom.inspectNode(range.endContainer) + ":" + range.endOffset + ")]";
1155         }
1157         /*----------------------------------------------------------------------------------------------------------------*/
1159         // RangeIterator code partially borrows from IERange by Tim Ryan (http://github.com/timcameronryan/IERange)
1161         function RangeIterator(range, clonePartiallySelectedTextNodes) {
1162             this.range = range;
1163             this.clonePartiallySelectedTextNodes = clonePartiallySelectedTextNodes;
1166             if (!range.collapsed) {
1167                 this.sc = range.startContainer;
1168                 this.so = range.startOffset;
1169                 this.ec = range.endContainer;
1170                 this.eo = range.endOffset;
1171                 var root = range.commonAncestorContainer;
1173                 if (this.sc === this.ec && isCharacterDataNode(this.sc)) {
1174                     this.isSingleCharacterDataNode = true;
1175                     this._first = this._last = this._next = this.sc;
1176                 } else {
1177                     this._first = this._next = (this.sc === root && !isCharacterDataNode(this.sc)) ?
1178                         this.sc.childNodes[this.so] : getClosestAncestorIn(this.sc, root, true);
1179                     this._last = (this.ec === root && !isCharacterDataNode(this.ec)) ?
1180                         this.ec.childNodes[this.eo - 1] : getClosestAncestorIn(this.ec, root, true);
1181                 }
1182             }
1183         }
1185         RangeIterator.prototype = {
1186             _current: null,
1187             _next: null,
1188             _first: null,
1189             _last: null,
1190             isSingleCharacterDataNode: false,
1192             reset: function() {
1193                 this._current = null;
1194                 this._next = this._first;
1195             },
1197             hasNext: function() {
1198                 return !!this._next;
1199             },
1201             next: function() {
1202                 // Move to next node
1203                 var current = this._current = this._next;
1204                 if (current) {
1205                     this._next = (current !== this._last) ? current.nextSibling : null;
1207                     // Check for partially selected text nodes
1208                     if (isCharacterDataNode(current) && this.clonePartiallySelectedTextNodes) {
1209                         if (current === this.ec) {
1210                             (current = current.cloneNode(true)).deleteData(this.eo, current.length - this.eo);
1211                         }
1212                         if (this._current === this.sc) {
1213                             (current = current.cloneNode(true)).deleteData(0, this.so);
1214                         }
1215                     }
1216                 }
1218                 return current;
1219             },
1221             remove: function() {
1222                 var current = this._current, start, end;
1224                 if (isCharacterDataNode(current) && (current === this.sc || current === this.ec)) {
1225                     start = (current === this.sc) ? this.so : 0;
1226                     end = (current === this.ec) ? this.eo : current.length;
1227                     if (start != end) {
1228                         current.deleteData(start, end - start);
1229                     }
1230                 } else {
1231                     if (current.parentNode) {
1232                         removeNode(current);
1233                     } else {
1234                     }
1235                 }
1236             },
1238             // Checks if the current node is partially selected
1239             isPartiallySelectedSubtree: function() {
1240                 var current = this._current;
1241                 return isNonTextPartiallySelected(current, this.range);
1242             },
1244             getSubtreeIterator: function() {
1245                 var subRange;
1246                 if (this.isSingleCharacterDataNode) {
1247                     subRange = this.range.cloneRange();
1248                     subRange.collapse(false);
1249                 } else {
1250                     subRange = new Range(getRangeDocument(this.range));
1251                     var current = this._current;
1252                     var startContainer = current, startOffset = 0, endContainer = current, endOffset = getNodeLength(current);
1254                     if (isOrIsAncestorOf(current, this.sc)) {
1255                         startContainer = this.sc;
1256                         startOffset = this.so;
1257                     }
1258                     if (isOrIsAncestorOf(current, this.ec)) {
1259                         endContainer = this.ec;
1260                         endOffset = this.eo;
1261                     }
1263                     updateBoundaries(subRange, startContainer, startOffset, endContainer, endOffset);
1264                 }
1265                 return new RangeIterator(subRange, this.clonePartiallySelectedTextNodes);
1266             },
1268             detach: function() {
1269                 this.range = this._current = this._next = this._first = this._last = this.sc = this.so = this.ec = this.eo = null;
1270             }
1271         };
1273         /*----------------------------------------------------------------------------------------------------------------*/
1275         var beforeAfterNodeTypes = [1, 3, 4, 5, 7, 8, 10];
1276         var rootContainerNodeTypes = [2, 9, 11];
1277         var readonlyNodeTypes = [5, 6, 10, 12];
1278         var insertableNodeTypes = [1, 3, 4, 5, 7, 8, 10, 11];
1279         var surroundNodeTypes = [1, 3, 4, 5, 7, 8];
1281         function createAncestorFinder(nodeTypes) {
1282             return function(node, selfIsAncestor) {
1283                 var t, n = selfIsAncestor ? node : node.parentNode;
1284                 while (n) {
1285                     t = n.nodeType;
1286                     if (arrayContains(nodeTypes, t)) {
1287                         return n;
1288                     }
1289                     n = n.parentNode;
1290                 }
1291                 return null;
1292             };
1293         }
1295         var getDocumentOrFragmentContainer = createAncestorFinder( [9, 11] );
1296         var getReadonlyAncestor = createAncestorFinder(readonlyNodeTypes);
1297         var getDocTypeNotationEntityAncestor = createAncestorFinder( [6, 10, 12] );
1299         function assertNoDocTypeNotationEntityAncestor(node, allowSelf) {
1300             if (getDocTypeNotationEntityAncestor(node, allowSelf)) {
1301                 throw new DOMException("INVALID_NODE_TYPE_ERR");
1302             }
1303         }
1305         function assertValidNodeType(node, invalidTypes) {
1306             if (!arrayContains(invalidTypes, node.nodeType)) {
1307                 throw new DOMException("INVALID_NODE_TYPE_ERR");
1308             }
1309         }
1311         function assertValidOffset(node, offset) {
1312             if (offset < 0 || offset > (isCharacterDataNode(node) ? node.length : node.childNodes.length)) {
1313                 throw new DOMException("INDEX_SIZE_ERR");
1314             }
1315         }
1317         function assertSameDocumentOrFragment(node1, node2) {
1318             if (getDocumentOrFragmentContainer(node1, true) !== getDocumentOrFragmentContainer(node2, true)) {
1319                 throw new DOMException("WRONG_DOCUMENT_ERR");
1320             }
1321         }
1323         function assertNodeNotReadOnly(node) {
1324             if (getReadonlyAncestor(node, true)) {
1325                 throw new DOMException("NO_MODIFICATION_ALLOWED_ERR");
1326             }
1327         }
1329         function assertNode(node, codeName) {
1330             if (!node) {
1331                 throw new DOMException(codeName);
1332             }
1333         }
1335         function isValidOffset(node, offset) {
1336             return offset <= (isCharacterDataNode(node) ? node.length : node.childNodes.length);
1337         }
1339         function isRangeValid(range) {
1340             return (!!range.startContainer && !!range.endContainer &&
1341                     !(crashyTextNodes && (dom.isBrokenNode(range.startContainer) || dom.isBrokenNode(range.endContainer))) &&
1342                     getRootContainer(range.startContainer) == getRootContainer(range.endContainer) &&
1343                     isValidOffset(range.startContainer, range.startOffset) &&
1344                     isValidOffset(range.endContainer, range.endOffset));
1345         }
1347         function assertRangeValid(range) {
1348             if (!isRangeValid(range)) {
1349                 throw new Error("Range error: Range is not valid. This usually happens after DOM mutation. Range: (" + range.inspect() + ")");
1350             }
1351         }
1353         /*----------------------------------------------------------------------------------------------------------------*/
1355         // Test the browser's innerHTML support to decide how to implement createContextualFragment
1356         var styleEl = document.createElement("style");
1357         var htmlParsingConforms = false;
1358         try {
1359             styleEl.innerHTML = "<b>x</b>";
1360             htmlParsingConforms = (styleEl.firstChild.nodeType == 3); // Opera incorrectly creates an element node
1361         } catch (e) {
1362             // IE 6 and 7 throw
1363         }
1365         api.features.htmlParsingConforms = htmlParsingConforms;
1367         var createContextualFragment = htmlParsingConforms ?
1369             // Implementation as per HTML parsing spec, trusting in the browser's implementation of innerHTML. See
1370             // discussion and base code for this implementation at issue 67.
1371             // Spec: http://html5.org/specs/dom-parsing.html#extensions-to-the-range-interface
1372             // Thanks to Aleks Williams.
1373             function(fragmentStr) {
1374                 // "Let node the context object's start's node."
1375                 var node = this.startContainer;
1376                 var doc = getDocument(node);
1378                 // "If the context object's start's node is null, raise an INVALID_STATE_ERR
1379                 // exception and abort these steps."
1380                 if (!node) {
1381                     throw new DOMException("INVALID_STATE_ERR");
1382                 }
1384                 // "Let element be as follows, depending on node's interface:"
1385                 // Document, Document Fragment: null
1386                 var el = null;
1388                 // "Element: node"
1389                 if (node.nodeType == 1) {
1390                     el = node;
1392                 // "Text, Comment: node's parentElement"
1393                 } else if (isCharacterDataNode(node)) {
1394                     el = dom.parentElement(node);
1395                 }
1397                 // "If either element is null or element's ownerDocument is an HTML document
1398                 // and element's local name is "html" and element's namespace is the HTML
1399                 // namespace"
1400                 if (el === null || (
1401                     el.nodeName == "HTML" &&
1402                     dom.isHtmlNamespace(getDocument(el).documentElement) &&
1403                     dom.isHtmlNamespace(el)
1404                 )) {
1406                 // "let element be a new Element with "body" as its local name and the HTML
1407                 // namespace as its namespace.""
1408                     el = doc.createElement("body");
1409                 } else {
1410                     el = el.cloneNode(false);
1411                 }
1413                 // "If the node's document is an HTML document: Invoke the HTML fragment parsing algorithm."
1414                 // "If the node's document is an XML document: Invoke the XML fragment parsing algorithm."
1415                 // "In either case, the algorithm must be invoked with fragment as the input
1416                 // and element as the context element."
1417                 el.innerHTML = fragmentStr;
1419                 // "If this raises an exception, then abort these steps. Otherwise, let new
1420                 // children be the nodes returned."
1422                 // "Let fragment be a new DocumentFragment."
1423                 // "Append all new children to fragment."
1424                 // "Return fragment."
1425                 return dom.fragmentFromNodeChildren(el);
1426             } :
1428             // In this case, innerHTML cannot be trusted, so fall back to a simpler, non-conformant implementation that
1429             // previous versions of Rangy used (with the exception of using a body element rather than a div)
1430             function(fragmentStr) {
1431                 var doc = getRangeDocument(this);
1432                 var el = doc.createElement("body");
1433                 el.innerHTML = fragmentStr;
1435                 return dom.fragmentFromNodeChildren(el);
1436             };
1438         function splitRangeBoundaries(range, positionsToPreserve) {
1439             assertRangeValid(range);
1441             var sc = range.startContainer, so = range.startOffset, ec = range.endContainer, eo = range.endOffset;
1442             var startEndSame = (sc === ec);
1444             if (isCharacterDataNode(ec) && eo > 0 && eo < ec.length) {
1445                 splitDataNode(ec, eo, positionsToPreserve);
1446             }
1448             if (isCharacterDataNode(sc) && so > 0 && so < sc.length) {
1449                 sc = splitDataNode(sc, so, positionsToPreserve);
1450                 if (startEndSame) {
1451                     eo -= so;
1452                     ec = sc;
1453                 } else if (ec == sc.parentNode && eo >= getNodeIndex(sc)) {
1454                     eo++;
1455                 }
1456                 so = 0;
1457             }
1458             range.setStartAndEnd(sc, so, ec, eo);
1459         }
1461         function rangeToHtml(range) {
1462             assertRangeValid(range);
1463             var container = range.commonAncestorContainer.parentNode.cloneNode(false);
1464             container.appendChild( range.cloneContents() );
1465             return container.innerHTML;
1466         }
1468         /*----------------------------------------------------------------------------------------------------------------*/
1470         var rangeProperties = ["startContainer", "startOffset", "endContainer", "endOffset", "collapsed",
1471             "commonAncestorContainer"];
1473         var s2s = 0, s2e = 1, e2e = 2, e2s = 3;
1474         var n_b = 0, n_a = 1, n_b_a = 2, n_i = 3;
1476         util.extend(api.rangePrototype, {
1477             compareBoundaryPoints: function(how, range) {
1478                 assertRangeValid(this);
1479                 assertSameDocumentOrFragment(this.startContainer, range.startContainer);
1481                 var nodeA, offsetA, nodeB, offsetB;
1482                 var prefixA = (how == e2s || how == s2s) ? "start" : "end";
1483                 var prefixB = (how == s2e || how == s2s) ? "start" : "end";
1484                 nodeA = this[prefixA + "Container"];
1485                 offsetA = this[prefixA + "Offset"];
1486                 nodeB = range[prefixB + "Container"];
1487                 offsetB = range[prefixB + "Offset"];
1488                 return comparePoints(nodeA, offsetA, nodeB, offsetB);
1489             },
1491             insertNode: function(node) {
1492                 assertRangeValid(this);
1493                 assertValidNodeType(node, insertableNodeTypes);
1494                 assertNodeNotReadOnly(this.startContainer);
1496                 if (isOrIsAncestorOf(node, this.startContainer)) {
1497                     throw new DOMException("HIERARCHY_REQUEST_ERR");
1498                 }
1500                 // No check for whether the container of the start of the Range is of a type that does not allow
1501                 // children of the type of node: the browser's DOM implementation should do this for us when we attempt
1502                 // to add the node
1504                 var firstNodeInserted = insertNodeAtPosition(node, this.startContainer, this.startOffset);
1505                 this.setStartBefore(firstNodeInserted);
1506             },
1508             cloneContents: function() {
1509                 assertRangeValid(this);
1511                 var clone, frag;
1512                 if (this.collapsed) {
1513                     return getRangeDocument(this).createDocumentFragment();
1514                 } else {
1515                     if (this.startContainer === this.endContainer && isCharacterDataNode(this.startContainer)) {
1516                         clone = this.startContainer.cloneNode(true);
1517                         clone.data = clone.data.slice(this.startOffset, this.endOffset);
1518                         frag = getRangeDocument(this).createDocumentFragment();
1519                         frag.appendChild(clone);
1520                         return frag;
1521                     } else {
1522                         var iterator = new RangeIterator(this, true);
1523                         clone = cloneSubtree(iterator);
1524                         iterator.detach();
1525                     }
1526                     return clone;
1527                 }
1528             },
1530             canSurroundContents: function() {
1531                 assertRangeValid(this);
1532                 assertNodeNotReadOnly(this.startContainer);
1533                 assertNodeNotReadOnly(this.endContainer);
1535                 // Check if the contents can be surrounded. Specifically, this means whether the range partially selects
1536                 // no non-text nodes.
1537                 var iterator = new RangeIterator(this, true);
1538                 var boundariesInvalid = (iterator._first && (isNonTextPartiallySelected(iterator._first, this)) ||
1539                         (iterator._last && isNonTextPartiallySelected(iterator._last, this)));
1540                 iterator.detach();
1541                 return !boundariesInvalid;
1542             },
1544             surroundContents: function(node) {
1545                 assertValidNodeType(node, surroundNodeTypes);
1547                 if (!this.canSurroundContents()) {
1548                     throw new DOMException("INVALID_STATE_ERR");
1549                 }
1551                 // Extract the contents
1552                 var content = this.extractContents();
1554                 // Clear the children of the node
1555                 if (node.hasChildNodes()) {
1556                     while (node.lastChild) {
1557                         node.removeChild(node.lastChild);
1558                     }
1559                 }
1561                 // Insert the new node and add the extracted contents
1562                 insertNodeAtPosition(node, this.startContainer, this.startOffset);
1563                 node.appendChild(content);
1565                 this.selectNode(node);
1566             },
1568             cloneRange: function() {
1569                 assertRangeValid(this);
1570                 var range = new Range(getRangeDocument(this));
1571                 var i = rangeProperties.length, prop;
1572                 while (i--) {
1573                     prop = rangeProperties[i];
1574                     range[prop] = this[prop];
1575                 }
1576                 return range;
1577             },
1579             toString: function() {
1580                 assertRangeValid(this);
1581                 var sc = this.startContainer;
1582                 if (sc === this.endContainer && isCharacterDataNode(sc)) {
1583                     return (sc.nodeType == 3 || sc.nodeType == 4) ? sc.data.slice(this.startOffset, this.endOffset) : "";
1584                 } else {
1585                     var textParts = [], iterator = new RangeIterator(this, true);
1586                     iterateSubtree(iterator, function(node) {
1587                         // Accept only text or CDATA nodes, not comments
1588                         if (node.nodeType == 3 || node.nodeType == 4) {
1589                             textParts.push(node.data);
1590                         }
1591                     });
1592                     iterator.detach();
1593                     return textParts.join("");
1594                 }
1595             },
1597             // The methods below are all non-standard. The following batch were introduced by Mozilla but have since
1598             // been removed from Mozilla.
1600             compareNode: function(node) {
1601                 assertRangeValid(this);
1603                 var parent = node.parentNode;
1604                 var nodeIndex = getNodeIndex(node);
1606                 if (!parent) {
1607                     throw new DOMException("NOT_FOUND_ERR");
1608                 }
1610                 var startComparison = this.comparePoint(parent, nodeIndex),
1611                     endComparison = this.comparePoint(parent, nodeIndex + 1);
1613                 if (startComparison < 0) { // Node starts before
1614                     return (endComparison > 0) ? n_b_a : n_b;
1615                 } else {
1616                     return (endComparison > 0) ? n_a : n_i;
1617                 }
1618             },
1620             comparePoint: function(node, offset) {
1621                 assertRangeValid(this);
1622                 assertNode(node, "HIERARCHY_REQUEST_ERR");
1623                 assertSameDocumentOrFragment(node, this.startContainer);
1625                 if (comparePoints(node, offset, this.startContainer, this.startOffset) < 0) {
1626                     return -1;
1627                 } else if (comparePoints(node, offset, this.endContainer, this.endOffset) > 0) {
1628                     return 1;
1629                 }
1630                 return 0;
1631             },
1633             createContextualFragment: createContextualFragment,
1635             toHtml: function() {
1636                 return rangeToHtml(this);
1637             },
1639             // touchingIsIntersecting determines whether this method considers a node that borders a range intersects
1640             // with it (as in WebKit) or not (as in Gecko pre-1.9, and the default)
1641             intersectsNode: function(node, touchingIsIntersecting) {
1642                 assertRangeValid(this);
1643                 if (getRootContainer(node) != getRangeRoot(this)) {
1644                     return false;
1645                 }
1647                 var parent = node.parentNode, offset = getNodeIndex(node);
1648                 if (!parent) {
1649                     return true;
1650                 }
1652                 var startComparison = comparePoints(parent, offset, this.endContainer, this.endOffset),
1653                     endComparison = comparePoints(parent, offset + 1, this.startContainer, this.startOffset);
1655                 return touchingIsIntersecting ? startComparison <= 0 && endComparison >= 0 : startComparison < 0 && endComparison > 0;
1656             },
1658             isPointInRange: function(node, offset) {
1659                 assertRangeValid(this);
1660                 assertNode(node, "HIERARCHY_REQUEST_ERR");
1661                 assertSameDocumentOrFragment(node, this.startContainer);
1663                 return (comparePoints(node, offset, this.startContainer, this.startOffset) >= 0) &&
1664                        (comparePoints(node, offset, this.endContainer, this.endOffset) <= 0);
1665             },
1667             // The methods below are non-standard and invented by me.
1669             // Sharing a boundary start-to-end or end-to-start does not count as intersection.
1670             intersectsRange: function(range) {
1671                 return rangesIntersect(this, range, false);
1672             },
1674             // Sharing a boundary start-to-end or end-to-start does count as intersection.
1675             intersectsOrTouchesRange: function(range) {
1676                 return rangesIntersect(this, range, true);
1677             },
1679             intersection: function(range) {
1680                 if (this.intersectsRange(range)) {
1681                     var startComparison = comparePoints(this.startContainer, this.startOffset, range.startContainer, range.startOffset),
1682                         endComparison = comparePoints(this.endContainer, this.endOffset, range.endContainer, range.endOffset);
1684                     var intersectionRange = this.cloneRange();
1685                     if (startComparison == -1) {
1686                         intersectionRange.setStart(range.startContainer, range.startOffset);
1687                     }
1688                     if (endComparison == 1) {
1689                         intersectionRange.setEnd(range.endContainer, range.endOffset);
1690                     }
1691                     return intersectionRange;
1692                 }
1693                 return null;
1694             },
1696             union: function(range) {
1697                 if (this.intersectsOrTouchesRange(range)) {
1698                     var unionRange = this.cloneRange();
1699                     if (comparePoints(range.startContainer, range.startOffset, this.startContainer, this.startOffset) == -1) {
1700                         unionRange.setStart(range.startContainer, range.startOffset);
1701                     }
1702                     if (comparePoints(range.endContainer, range.endOffset, this.endContainer, this.endOffset) == 1) {
1703                         unionRange.setEnd(range.endContainer, range.endOffset);
1704                     }
1705                     return unionRange;
1706                 } else {
1707                     throw new DOMException("Ranges do not intersect");
1708                 }
1709             },
1711             containsNode: function(node, allowPartial) {
1712                 if (allowPartial) {
1713                     return this.intersectsNode(node, false);
1714                 } else {
1715                     return this.compareNode(node) == n_i;
1716                 }
1717             },
1719             containsNodeContents: function(node) {
1720                 return this.comparePoint(node, 0) >= 0 && this.comparePoint(node, getNodeLength(node)) <= 0;
1721             },
1723             containsRange: function(range) {
1724                 var intersection = this.intersection(range);
1725                 return intersection !== null && range.equals(intersection);
1726             },
1728             containsNodeText: function(node) {
1729                 var nodeRange = this.cloneRange();
1730                 nodeRange.selectNode(node);
1731                 var textNodes = nodeRange.getNodes([3]);
1732                 if (textNodes.length > 0) {
1733                     nodeRange.setStart(textNodes[0], 0);
1734                     var lastTextNode = textNodes.pop();
1735                     nodeRange.setEnd(lastTextNode, lastTextNode.length);
1736                     return this.containsRange(nodeRange);
1737                 } else {
1738                     return this.containsNodeContents(node);
1739                 }
1740             },
1742             getNodes: function(nodeTypes, filter) {
1743                 assertRangeValid(this);
1744                 return getNodesInRange(this, nodeTypes, filter);
1745             },
1747             getDocument: function() {
1748                 return getRangeDocument(this);
1749             },
1751             collapseBefore: function(node) {
1752                 this.setEndBefore(node);
1753                 this.collapse(false);
1754             },
1756             collapseAfter: function(node) {
1757                 this.setStartAfter(node);
1758                 this.collapse(true);
1759             },
1761             getBookmark: function(containerNode) {
1762                 var doc = getRangeDocument(this);
1763                 var preSelectionRange = api.createRange(doc);
1764                 containerNode = containerNode || dom.getBody(doc);
1765                 preSelectionRange.selectNodeContents(containerNode);
1766                 var range = this.intersection(preSelectionRange);
1767                 var start = 0, end = 0;
1768                 if (range) {
1769                     preSelectionRange.setEnd(range.startContainer, range.startOffset);
1770                     start = preSelectionRange.toString().length;
1771                     end = start + range.toString().length;
1772                 }
1774                 return {
1775                     start: start,
1776                     end: end,
1777                     containerNode: containerNode
1778                 };
1779             },
1781             moveToBookmark: function(bookmark) {
1782                 var containerNode = bookmark.containerNode;
1783                 var charIndex = 0;
1784                 this.setStart(containerNode, 0);
1785                 this.collapse(true);
1786                 var nodeStack = [containerNode], node, foundStart = false, stop = false;
1787                 var nextCharIndex, i, childNodes;
1789                 while (!stop && (node = nodeStack.pop())) {
1790                     if (node.nodeType == 3) {
1791                         nextCharIndex = charIndex + node.length;
1792                         if (!foundStart && bookmark.start >= charIndex && bookmark.start <= nextCharIndex) {
1793                             this.setStart(node, bookmark.start - charIndex);
1794                             foundStart = true;
1795                         }
1796                         if (foundStart && bookmark.end >= charIndex && bookmark.end <= nextCharIndex) {
1797                             this.setEnd(node, bookmark.end - charIndex);
1798                             stop = true;
1799                         }
1800                         charIndex = nextCharIndex;
1801                     } else {
1802                         childNodes = node.childNodes;
1803                         i = childNodes.length;
1804                         while (i--) {
1805                             nodeStack.push(childNodes[i]);
1806                         }
1807                     }
1808                 }
1809             },
1811             getName: function() {
1812                 return "DomRange";
1813             },
1815             equals: function(range) {
1816                 return Range.rangesEqual(this, range);
1817             },
1819             isValid: function() {
1820                 return isRangeValid(this);
1821             },
1823             inspect: function() {
1824                 return inspect(this);
1825             },
1827             detach: function() {
1828                 // In DOM4, detach() is now a no-op.
1829             }
1830         });
1832         function copyComparisonConstantsToObject(obj) {
1833             obj.START_TO_START = s2s;
1834             obj.START_TO_END = s2e;
1835             obj.END_TO_END = e2e;
1836             obj.END_TO_START = e2s;
1838             obj.NODE_BEFORE = n_b;
1839             obj.NODE_AFTER = n_a;
1840             obj.NODE_BEFORE_AND_AFTER = n_b_a;
1841             obj.NODE_INSIDE = n_i;
1842         }
1844         function copyComparisonConstants(constructor) {
1845             copyComparisonConstantsToObject(constructor);
1846             copyComparisonConstantsToObject(constructor.prototype);
1847         }
1849         function createRangeContentRemover(remover, boundaryUpdater) {
1850             return function() {
1851                 assertRangeValid(this);
1853                 var sc = this.startContainer, so = this.startOffset, root = this.commonAncestorContainer;
1855                 var iterator = new RangeIterator(this, true);
1857                 // Work out where to position the range after content removal
1858                 var node, boundary;
1859                 if (sc !== root) {
1860                     node = getClosestAncestorIn(sc, root, true);
1861                     boundary = getBoundaryAfterNode(node);
1862                     sc = boundary.node;
1863                     so = boundary.offset;
1864                 }
1866                 // Check none of the range is read-only
1867                 iterateSubtree(iterator, assertNodeNotReadOnly);
1869                 iterator.reset();
1871                 // Remove the content
1872                 var returnValue = remover(iterator);
1873                 iterator.detach();
1875                 // Move to the new position
1876                 boundaryUpdater(this, sc, so, sc, so);
1878                 return returnValue;
1879             };
1880         }
1882         function createPrototypeRange(constructor, boundaryUpdater) {
1883             function createBeforeAfterNodeSetter(isBefore, isStart) {
1884                 return function(node) {
1885                     assertValidNodeType(node, beforeAfterNodeTypes);
1886                     assertValidNodeType(getRootContainer(node), rootContainerNodeTypes);
1888                     var boundary = (isBefore ? getBoundaryBeforeNode : getBoundaryAfterNode)(node);
1889                     (isStart ? setRangeStart : setRangeEnd)(this, boundary.node, boundary.offset);
1890                 };
1891             }
1893             function setRangeStart(range, node, offset) {
1894                 var ec = range.endContainer, eo = range.endOffset;
1895                 if (node !== range.startContainer || offset !== range.startOffset) {
1896                     // Check the root containers of the range and the new boundary, and also check whether the new boundary
1897                     // is after the current end. In either case, collapse the range to the new position
1898                     if (getRootContainer(node) != getRootContainer(ec) || comparePoints(node, offset, ec, eo) == 1) {
1899                         ec = node;
1900                         eo = offset;
1901                     }
1902                     boundaryUpdater(range, node, offset, ec, eo);
1903                 }
1904             }
1906             function setRangeEnd(range, node, offset) {
1907                 var sc = range.startContainer, so = range.startOffset;
1908                 if (node !== range.endContainer || offset !== range.endOffset) {
1909                     // Check the root containers of the range and the new boundary, and also check whether the new boundary
1910                     // is after the current end. In either case, collapse the range to the new position
1911                     if (getRootContainer(node) != getRootContainer(sc) || comparePoints(node, offset, sc, so) == -1) {
1912                         sc = node;
1913                         so = offset;
1914                     }
1915                     boundaryUpdater(range, sc, so, node, offset);
1916                 }
1917             }
1919             // Set up inheritance
1920             var F = function() {};
1921             F.prototype = api.rangePrototype;
1922             constructor.prototype = new F();
1924             util.extend(constructor.prototype, {
1925                 setStart: function(node, offset) {
1926                     assertNoDocTypeNotationEntityAncestor(node, true);
1927                     assertValidOffset(node, offset);
1929                     setRangeStart(this, node, offset);
1930                 },
1932                 setEnd: function(node, offset) {
1933                     assertNoDocTypeNotationEntityAncestor(node, true);
1934                     assertValidOffset(node, offset);
1936                     setRangeEnd(this, node, offset);
1937                 },
1939                 /**
1940                  * Convenience method to set a range's start and end boundaries. Overloaded as follows:
1941                  * - Two parameters (node, offset) creates a collapsed range at that position
1942                  * - Three parameters (node, startOffset, endOffset) creates a range contained with node starting at
1943                  *   startOffset and ending at endOffset
1944                  * - Four parameters (startNode, startOffset, endNode, endOffset) creates a range starting at startOffset in
1945                  *   startNode and ending at endOffset in endNode
1946                  */
1947                 setStartAndEnd: function() {
1948                     var args = arguments;
1949                     var sc = args[0], so = args[1], ec = sc, eo = so;
1951                     switch (args.length) {
1952                         case 3:
1953                             eo = args[2];
1954                             break;
1955                         case 4:
1956                             ec = args[2];
1957                             eo = args[3];
1958                             break;
1959                     }
1961                     boundaryUpdater(this, sc, so, ec, eo);
1962                 },
1964                 setBoundary: function(node, offset, isStart) {
1965                     this["set" + (isStart ? "Start" : "End")](node, offset);
1966                 },
1968                 setStartBefore: createBeforeAfterNodeSetter(true, true),
1969                 setStartAfter: createBeforeAfterNodeSetter(false, true),
1970                 setEndBefore: createBeforeAfterNodeSetter(true, false),
1971                 setEndAfter: createBeforeAfterNodeSetter(false, false),
1973                 collapse: function(isStart) {
1974                     assertRangeValid(this);
1975                     if (isStart) {
1976                         boundaryUpdater(this, this.startContainer, this.startOffset, this.startContainer, this.startOffset);
1977                     } else {
1978                         boundaryUpdater(this, this.endContainer, this.endOffset, this.endContainer, this.endOffset);
1979                     }
1980                 },
1982                 selectNodeContents: function(node) {
1983                     assertNoDocTypeNotationEntityAncestor(node, true);
1985                     boundaryUpdater(this, node, 0, node, getNodeLength(node));
1986                 },
1988                 selectNode: function(node) {
1989                     assertNoDocTypeNotationEntityAncestor(node, false);
1990                     assertValidNodeType(node, beforeAfterNodeTypes);
1992                     var start = getBoundaryBeforeNode(node), end = getBoundaryAfterNode(node);
1993                     boundaryUpdater(this, start.node, start.offset, end.node, end.offset);
1994                 },
1996                 extractContents: createRangeContentRemover(extractSubtree, boundaryUpdater),
1998                 deleteContents: createRangeContentRemover(deleteSubtree, boundaryUpdater),
2000                 canSurroundContents: function() {
2001                     assertRangeValid(this);
2002                     assertNodeNotReadOnly(this.startContainer);
2003                     assertNodeNotReadOnly(this.endContainer);
2005                     // Check if the contents can be surrounded. Specifically, this means whether the range partially selects
2006                     // no non-text nodes.
2007                     var iterator = new RangeIterator(this, true);
2008                     var boundariesInvalid = (iterator._first && isNonTextPartiallySelected(iterator._first, this) ||
2009                             (iterator._last && isNonTextPartiallySelected(iterator._last, this)));
2010                     iterator.detach();
2011                     return !boundariesInvalid;
2012                 },
2014                 splitBoundaries: function() {
2015                     splitRangeBoundaries(this);
2016                 },
2018                 splitBoundariesPreservingPositions: function(positionsToPreserve) {
2019                     splitRangeBoundaries(this, positionsToPreserve);
2020                 },
2022                 normalizeBoundaries: function() {
2023                     assertRangeValid(this);
2025                     var sc = this.startContainer, so = this.startOffset, ec = this.endContainer, eo = this.endOffset;
2027                     var mergeForward = function(node) {
2028                         var sibling = node.nextSibling;
2029                         if (sibling && sibling.nodeType == node.nodeType) {
2030                             ec = node;
2031                             eo = node.length;
2032                             node.appendData(sibling.data);
2033                             removeNode(sibling);
2034                         }
2035                     };
2037                     var mergeBackward = function(node) {
2038                         var sibling = node.previousSibling;
2039                         if (sibling && sibling.nodeType == node.nodeType) {
2040                             sc = node;
2041                             var nodeLength = node.length;
2042                             so = sibling.length;
2043                             node.insertData(0, sibling.data);
2044                             removeNode(sibling);
2045                             if (sc == ec) {
2046                                 eo += so;
2047                                 ec = sc;
2048                             } else if (ec == node.parentNode) {
2049                                 var nodeIndex = getNodeIndex(node);
2050                                 if (eo == nodeIndex) {
2051                                     ec = node;
2052                                     eo = nodeLength;
2053                                 } else if (eo > nodeIndex) {
2054                                     eo--;
2055                                 }
2056                             }
2057                         }
2058                     };
2060                     var normalizeStart = true;
2061                     var sibling;
2063                     if (isCharacterDataNode(ec)) {
2064                         if (eo == ec.length) {
2065                             mergeForward(ec);
2066                         } else if (eo == 0) {
2067                             sibling = ec.previousSibling;
2068                             if (sibling && sibling.nodeType == ec.nodeType) {
2069                                 eo = sibling.length;
2070                                 if (sc == ec) {
2071                                     normalizeStart = false;
2072                                 }
2073                                 sibling.appendData(ec.data);
2074                                 removeNode(ec);
2075                                 ec = sibling;
2076                             }
2077                         }
2078                     } else {
2079                         if (eo > 0) {
2080                             var endNode = ec.childNodes[eo - 1];
2081                             if (endNode && isCharacterDataNode(endNode)) {
2082                                 mergeForward(endNode);
2083                             }
2084                         }
2085                         normalizeStart = !this.collapsed;
2086                     }
2088                     if (normalizeStart) {
2089                         if (isCharacterDataNode(sc)) {
2090                             if (so == 0) {
2091                                 mergeBackward(sc);
2092                             } else if (so == sc.length) {
2093                                 sibling = sc.nextSibling;
2094                                 if (sibling && sibling.nodeType == sc.nodeType) {
2095                                     if (ec == sibling) {
2096                                         ec = sc;
2097                                         eo += sc.length;
2098                                     }
2099                                     sc.appendData(sibling.data);
2100                                     removeNode(sibling);
2101                                 }
2102                             }
2103                         } else {
2104                             if (so < sc.childNodes.length) {
2105                                 var startNode = sc.childNodes[so];
2106                                 if (startNode && isCharacterDataNode(startNode)) {
2107                                     mergeBackward(startNode);
2108                                 }
2109                             }
2110                         }
2111                     } else {
2112                         sc = ec;
2113                         so = eo;
2114                     }
2116                     boundaryUpdater(this, sc, so, ec, eo);
2117                 },
2119                 collapseToPoint: function(node, offset) {
2120                     assertNoDocTypeNotationEntityAncestor(node, true);
2121                     assertValidOffset(node, offset);
2122                     this.setStartAndEnd(node, offset);
2123                 }
2124             });
2126             copyComparisonConstants(constructor);
2127         }
2129         /*----------------------------------------------------------------------------------------------------------------*/
2131         // Updates commonAncestorContainer and collapsed after boundary change
2132         function updateCollapsedAndCommonAncestor(range) {
2133             range.collapsed = (range.startContainer === range.endContainer && range.startOffset === range.endOffset);
2134             range.commonAncestorContainer = range.collapsed ?
2135                 range.startContainer : dom.getCommonAncestor(range.startContainer, range.endContainer);
2136         }
2138         function updateBoundaries(range, startContainer, startOffset, endContainer, endOffset) {
2139             range.startContainer = startContainer;
2140             range.startOffset = startOffset;
2141             range.endContainer = endContainer;
2142             range.endOffset = endOffset;
2143             range.document = dom.getDocument(startContainer);
2145             updateCollapsedAndCommonAncestor(range);
2146         }
2148         function Range(doc) {
2149             this.startContainer = doc;
2150             this.startOffset = 0;
2151             this.endContainer = doc;
2152             this.endOffset = 0;
2153             this.document = doc;
2154             updateCollapsedAndCommonAncestor(this);
2155         }
2157         createPrototypeRange(Range, updateBoundaries);
2159         util.extend(Range, {
2160             rangeProperties: rangeProperties,
2161             RangeIterator: RangeIterator,
2162             copyComparisonConstants: copyComparisonConstants,
2163             createPrototypeRange: createPrototypeRange,
2164             inspect: inspect,
2165             toHtml: rangeToHtml,
2166             getRangeDocument: getRangeDocument,
2167             rangesEqual: function(r1, r2) {
2168                 return r1.startContainer === r2.startContainer &&
2169                     r1.startOffset === r2.startOffset &&
2170                     r1.endContainer === r2.endContainer &&
2171                     r1.endOffset === r2.endOffset;
2172             }
2173         });
2175         api.DomRange = Range;
2176     });\r
2177 \r
2178     /*----------------------------------------------------------------------------------------------------------------*/\r
2179 \r
2180     // Wrappers for the browser's native DOM Range and/or TextRange implementation
2181     api.createCoreModule("WrappedRange", ["DomRange"], function(api, module) {
2182         var WrappedRange, WrappedTextRange;
2183         var dom = api.dom;
2184         var util = api.util;
2185         var DomPosition = dom.DomPosition;
2186         var DomRange = api.DomRange;
2187         var getBody = dom.getBody;
2188         var getContentDocument = dom.getContentDocument;
2189         var isCharacterDataNode = dom.isCharacterDataNode;
2192         /*----------------------------------------------------------------------------------------------------------------*/
2194         if (api.features.implementsDomRange) {
2195             // This is a wrapper around the browser's native DOM Range. It has two aims:
2196             // - Provide workarounds for specific browser bugs
2197             // - provide convenient extensions, which are inherited from Rangy's DomRange
2199             (function() {
2200                 var rangeProto;
2201                 var rangeProperties = DomRange.rangeProperties;
2203                 function updateRangeProperties(range) {
2204                     var i = rangeProperties.length, prop;
2205                     while (i--) {
2206                         prop = rangeProperties[i];
2207                         range[prop] = range.nativeRange[prop];
2208                     }
2209                     // Fix for broken collapsed property in IE 9.
2210                     range.collapsed = (range.startContainer === range.endContainer && range.startOffset === range.endOffset);
2211                 }
2213                 function updateNativeRange(range, startContainer, startOffset, endContainer, endOffset) {
2214                     var startMoved = (range.startContainer !== startContainer || range.startOffset != startOffset);
2215                     var endMoved = (range.endContainer !== endContainer || range.endOffset != endOffset);
2216                     var nativeRangeDifferent = !range.equals(range.nativeRange);
2218                     // Always set both boundaries for the benefit of IE9 (see issue 35)
2219                     if (startMoved || endMoved || nativeRangeDifferent) {
2220                         range.setEnd(endContainer, endOffset);
2221                         range.setStart(startContainer, startOffset);
2222                     }
2223                 }
2225                 var createBeforeAfterNodeSetter;
2227                 WrappedRange = function(range) {
2228                     if (!range) {
2229                         throw module.createError("WrappedRange: Range must be specified");
2230                     }
2231                     this.nativeRange = range;
2232                     updateRangeProperties(this);
2233                 };
2235                 DomRange.createPrototypeRange(WrappedRange, updateNativeRange);
2237                 rangeProto = WrappedRange.prototype;
2239                 rangeProto.selectNode = function(node) {
2240                     this.nativeRange.selectNode(node);
2241                     updateRangeProperties(this);
2242                 };
2244                 rangeProto.cloneContents = function() {
2245                     return this.nativeRange.cloneContents();
2246                 };
2248                 // Due to a long-standing Firefox bug that I have not been able to find a reliable way to detect,
2249                 // insertNode() is never delegated to the native range.
2251                 rangeProto.surroundContents = function(node) {
2252                     this.nativeRange.surroundContents(node);
2253                     updateRangeProperties(this);
2254                 };
2256                 rangeProto.collapse = function(isStart) {
2257                     this.nativeRange.collapse(isStart);
2258                     updateRangeProperties(this);
2259                 };
2261                 rangeProto.cloneRange = function() {
2262                     return new WrappedRange(this.nativeRange.cloneRange());
2263                 };
2265                 rangeProto.refresh = function() {
2266                     updateRangeProperties(this);
2267                 };
2269                 rangeProto.toString = function() {
2270                     return this.nativeRange.toString();
2271                 };
2273                 // Create test range and node for feature detection
2275                 var testTextNode = document.createTextNode("test");
2276                 getBody(document).appendChild(testTextNode);
2277                 var range = document.createRange();
2279                 /*--------------------------------------------------------------------------------------------------------*/
2281                 // Test for Firefox 2 bug that prevents moving the start of a Range to a point after its current end and
2282                 // correct for it
2284                 range.setStart(testTextNode, 0);
2285                 range.setEnd(testTextNode, 0);
2287                 try {
2288                     range.setStart(testTextNode, 1);
2290                     rangeProto.setStart = function(node, offset) {
2291                         this.nativeRange.setStart(node, offset);
2292                         updateRangeProperties(this);
2293                     };
2295                     rangeProto.setEnd = function(node, offset) {
2296                         this.nativeRange.setEnd(node, offset);
2297                         updateRangeProperties(this);
2298                     };
2300                     createBeforeAfterNodeSetter = function(name) {
2301                         return function(node) {
2302                             this.nativeRange[name](node);
2303                             updateRangeProperties(this);
2304                         };
2305                     };
2307                 } catch(ex) {
2309                     rangeProto.setStart = function(node, offset) {
2310                         try {
2311                             this.nativeRange.setStart(node, offset);
2312                         } catch (ex) {
2313                             this.nativeRange.setEnd(node, offset);
2314                             this.nativeRange.setStart(node, offset);
2315                         }
2316                         updateRangeProperties(this);
2317                     };
2319                     rangeProto.setEnd = function(node, offset) {
2320                         try {
2321                             this.nativeRange.setEnd(node, offset);
2322                         } catch (ex) {
2323                             this.nativeRange.setStart(node, offset);
2324                             this.nativeRange.setEnd(node, offset);
2325                         }
2326                         updateRangeProperties(this);
2327                     };
2329                     createBeforeAfterNodeSetter = function(name, oppositeName) {
2330                         return function(node) {
2331                             try {
2332                                 this.nativeRange[name](node);
2333                             } catch (ex) {
2334                                 this.nativeRange[oppositeName](node);
2335                                 this.nativeRange[name](node);
2336                             }
2337                             updateRangeProperties(this);
2338                         };
2339                     };
2340                 }
2342                 rangeProto.setStartBefore = createBeforeAfterNodeSetter("setStartBefore", "setEndBefore");
2343                 rangeProto.setStartAfter = createBeforeAfterNodeSetter("setStartAfter", "setEndAfter");
2344                 rangeProto.setEndBefore = createBeforeAfterNodeSetter("setEndBefore", "setStartBefore");
2345                 rangeProto.setEndAfter = createBeforeAfterNodeSetter("setEndAfter", "setStartAfter");
2347                 /*--------------------------------------------------------------------------------------------------------*/
2349                 // Always use DOM4-compliant selectNodeContents implementation: it's simpler and less code than testing
2350                 // whether the native implementation can be trusted
2351                 rangeProto.selectNodeContents = function(node) {
2352                     this.setStartAndEnd(node, 0, dom.getNodeLength(node));
2353                 };
2355                 /*--------------------------------------------------------------------------------------------------------*/
2357                 // Test for and correct WebKit bug that has the behaviour of compareBoundaryPoints round the wrong way for
2358                 // constants START_TO_END and END_TO_START: https://bugs.webkit.org/show_bug.cgi?id=20738
2360                 range.selectNodeContents(testTextNode);
2361                 range.setEnd(testTextNode, 3);
2363                 var range2 = document.createRange();
2364                 range2.selectNodeContents(testTextNode);
2365                 range2.setEnd(testTextNode, 4);
2366                 range2.setStart(testTextNode, 2);
2368                 if (range.compareBoundaryPoints(range.START_TO_END, range2) == -1 &&
2369                         range.compareBoundaryPoints(range.END_TO_START, range2) == 1) {
2370                     // This is the wrong way round, so correct for it
2372                     rangeProto.compareBoundaryPoints = function(type, range) {
2373                         range = range.nativeRange || range;
2374                         if (type == range.START_TO_END) {
2375                             type = range.END_TO_START;
2376                         } else if (type == range.END_TO_START) {
2377                             type = range.START_TO_END;
2378                         }
2379                         return this.nativeRange.compareBoundaryPoints(type, range);
2380                     };
2381                 } else {
2382                     rangeProto.compareBoundaryPoints = function(type, range) {
2383                         return this.nativeRange.compareBoundaryPoints(type, range.nativeRange || range);
2384                     };
2385                 }
2387                 /*--------------------------------------------------------------------------------------------------------*/
2389                 // Test for IE deleteContents() and extractContents() bug and correct it. See issue 107.
2391                 var el = document.createElement("div");
2392                 el.innerHTML = "123";
2393                 var textNode = el.firstChild;
2394                 var body = getBody(document);
2395                 body.appendChild(el);
2397                 range.setStart(textNode, 1);
2398                 range.setEnd(textNode, 2);
2399                 range.deleteContents();
2401                 if (textNode.data == "13") {
2402                     // Behaviour is correct per DOM4 Range so wrap the browser's implementation of deleteContents() and
2403                     // extractContents()
2404                     rangeProto.deleteContents = function() {
2405                         this.nativeRange.deleteContents();
2406                         updateRangeProperties(this);
2407                     };
2409                     rangeProto.extractContents = function() {
2410                         var frag = this.nativeRange.extractContents();
2411                         updateRangeProperties(this);
2412                         return frag;
2413                     };
2414                 } else {
2415                 }
2417                 body.removeChild(el);
2418                 body = null;
2420                 /*--------------------------------------------------------------------------------------------------------*/
2422                 // Test for existence of createContextualFragment and delegate to it if it exists
2423                 if (util.isHostMethod(range, "createContextualFragment")) {
2424                     rangeProto.createContextualFragment = function(fragmentStr) {
2425                         return this.nativeRange.createContextualFragment(fragmentStr);
2426                     };
2427                 }
2429                 /*--------------------------------------------------------------------------------------------------------*/
2431                 // Clean up
2432                 getBody(document).removeChild(testTextNode);
2434                 rangeProto.getName = function() {
2435                     return "WrappedRange";
2436                 };
2438                 api.WrappedRange = WrappedRange;
2440                 api.createNativeRange = function(doc) {
2441                     doc = getContentDocument(doc, module, "createNativeRange");
2442                     return doc.createRange();
2443                 };
2444             })();
2445         }
2447         if (api.features.implementsTextRange) {
2448             /*
2449             This is a workaround for a bug where IE returns the wrong container element from the TextRange's parentElement()
2450             method. For example, in the following (where pipes denote the selection boundaries):
2452             <ul id="ul"><li id="a">| a </li><li id="b"> b |</li></ul>
2454             var range = document.selection.createRange();
2455             alert(range.parentElement().id); // Should alert "ul" but alerts "b"
2457             This method returns the common ancestor node of the following:
2458             - the parentElement() of the textRange
2459             - the parentElement() of the textRange after calling collapse(true)
2460             - the parentElement() of the textRange after calling collapse(false)
2461             */
2462             var getTextRangeContainerElement = function(textRange) {
2463                 var parentEl = textRange.parentElement();
2464                 var range = textRange.duplicate();
2465                 range.collapse(true);
2466                 var startEl = range.parentElement();
2467                 range = textRange.duplicate();
2468                 range.collapse(false);
2469                 var endEl = range.parentElement();
2470                 var startEndContainer = (startEl == endEl) ? startEl : dom.getCommonAncestor(startEl, endEl);
2472                 return startEndContainer == parentEl ? startEndContainer : dom.getCommonAncestor(parentEl, startEndContainer);
2473             };
2475             var textRangeIsCollapsed = function(textRange) {
2476                 return textRange.compareEndPoints("StartToEnd", textRange) == 0;
2477             };
2479             // Gets the boundary of a TextRange expressed as a node and an offset within that node. This function started
2480             // out as an improved version of code found in Tim Cameron Ryan's IERange (http://code.google.com/p/ierange/)
2481             // but has grown, fixing problems with line breaks in preformatted text, adding workaround for IE TextRange
2482             // bugs, handling for inputs and images, plus optimizations.
2483             var getTextRangeBoundaryPosition = function(textRange, wholeRangeContainerElement, isStart, isCollapsed, startInfo) {
2484                 var workingRange = textRange.duplicate();
2485                 workingRange.collapse(isStart);
2486                 var containerElement = workingRange.parentElement();
2488                 // Sometimes collapsing a TextRange that's at the start of a text node can move it into the previous node, so
2489                 // check for that
2490                 if (!dom.isOrIsAncestorOf(wholeRangeContainerElement, containerElement)) {
2491                     containerElement = wholeRangeContainerElement;
2492                 }
2495                 // Deal with nodes that cannot "contain rich HTML markup". In practice, this means form inputs, images and
2496                 // similar. See http://msdn.microsoft.com/en-us/library/aa703950%28VS.85%29.aspx
2497                 if (!containerElement.canHaveHTML) {
2498                     var pos = new DomPosition(containerElement.parentNode, dom.getNodeIndex(containerElement));
2499                     return {
2500                         boundaryPosition: pos,
2501                         nodeInfo: {
2502                             nodeIndex: pos.offset,
2503                             containerElement: pos.node
2504                         }
2505                     };
2506                 }
2508                 var workingNode = dom.getDocument(containerElement).createElement("span");
2510                 // Workaround for HTML5 Shiv's insane violation of document.createElement(). See Rangy issue 104 and HTML5
2511                 // Shiv issue 64: https://github.com/aFarkas/html5shiv/issues/64
2512                 if (workingNode.parentNode) {
2513                     dom.removeNode(workingNode);
2514                 }
2516                 var comparison, workingComparisonType = isStart ? "StartToStart" : "StartToEnd";
2517                 var previousNode, nextNode, boundaryPosition, boundaryNode;
2518                 var start = (startInfo && startInfo.containerElement == containerElement) ? startInfo.nodeIndex : 0;
2519                 var childNodeCount = containerElement.childNodes.length;
2520                 var end = childNodeCount;
2522                 // Check end first. Code within the loop assumes that the endth child node of the container is definitely
2523                 // after the range boundary.
2524                 var nodeIndex = end;
2526                 while (true) {
2527                     if (nodeIndex == childNodeCount) {
2528                         containerElement.appendChild(workingNode);
2529                     } else {
2530                         containerElement.insertBefore(workingNode, containerElement.childNodes[nodeIndex]);
2531                     }
2532                     workingRange.moveToElementText(workingNode);
2533                     comparison = workingRange.compareEndPoints(workingComparisonType, textRange);
2534                     if (comparison == 0 || start == end) {
2535                         break;
2536                     } else if (comparison == -1) {
2537                         if (end == start + 1) {
2538                             // We know the endth child node is after the range boundary, so we must be done.
2539                             break;
2540                         } else {
2541                             start = nodeIndex;
2542                         }
2543                     } else {
2544                         end = (end == start + 1) ? start : nodeIndex;
2545                     }
2546                     nodeIndex = Math.floor((start + end) / 2);
2547                     containerElement.removeChild(workingNode);
2548                 }
2551                 // We've now reached or gone past the boundary of the text range we're interested in
2552                 // so have identified the node we want
2553                 boundaryNode = workingNode.nextSibling;
2555                 if (comparison == -1 && boundaryNode && isCharacterDataNode(boundaryNode)) {
2556                     // This is a character data node (text, comment, cdata). The working range is collapsed at the start of
2557                     // the node containing the text range's boundary, so we move the end of the working range to the
2558                     // boundary point and measure the length of its text to get the boundary's offset within the node.
2559                     workingRange.setEndPoint(isStart ? "EndToStart" : "EndToEnd", textRange);
2561                     var offset;
2563                     if (/[\r\n]/.test(boundaryNode.data)) {
2564                         /*
2565                         For the particular case of a boundary within a text node containing rendered line breaks (within a
2566                         <pre> element, for example), we need a slightly complicated approach to get the boundary's offset in
2567                         IE. The facts:
2569                         - Each line break is represented as \r in the text node's data/nodeValue properties
2570                         - Each line break is represented as \r\n in the TextRange's 'text' property
2571                         - The 'text' property of the TextRange does not contain trailing line breaks
2573                         To get round the problem presented by the final fact above, we can use the fact that TextRange's
2574                         moveStart() and moveEnd() methods return the actual number of characters moved, which is not
2575                         necessarily the same as the number of characters it was instructed to move. The simplest approach is
2576                         to use this to store the characters moved when moving both the start and end of the range to the
2577                         start of the document body and subtracting the start offset from the end offset (the
2578                         "move-negative-gazillion" method). However, this is extremely slow when the document is large and
2579                         the range is near the end of it. Clearly doing the mirror image (i.e. moving the range boundaries to
2580                         the end of the document) has the same problem.
2582                         Another approach that works is to use moveStart() to move the start boundary of the range up to the
2583                         end boundary one character at a time and incrementing a counter with the value returned by the
2584                         moveStart() call. However, the check for whether the start boundary has reached the end boundary is
2585                         expensive, so this method is slow (although unlike "move-negative-gazillion" is largely unaffected
2586                         by the location of the range within the document).
2588                         The approach used below is a hybrid of the two methods above. It uses the fact that a string
2589                         containing the TextRange's 'text' property with each \r\n converted to a single \r character cannot
2590                         be longer than the text of the TextRange, so the start of the range is moved that length initially
2591                         and then a character at a time to make up for any trailing line breaks not contained in the 'text'
2592                         property. This has good performance in most situations compared to the previous two methods.
2593                         */
2594                         var tempRange = workingRange.duplicate();
2595                         var rangeLength = tempRange.text.replace(/\r\n/g, "\r").length;
2597                         offset = tempRange.moveStart("character", rangeLength);
2598                         while ( (comparison = tempRange.compareEndPoints("StartToEnd", tempRange)) == -1) {
2599                             offset++;
2600                             tempRange.moveStart("character", 1);
2601                         }
2602                     } else {
2603                         offset = workingRange.text.length;
2604                     }
2605                     boundaryPosition = new DomPosition(boundaryNode, offset);
2606                 } else {
2608                     // If the boundary immediately follows a character data node and this is the end boundary, we should favour
2609                     // a position within that, and likewise for a start boundary preceding a character data node
2610                     previousNode = (isCollapsed || !isStart) && workingNode.previousSibling;
2611                     nextNode = (isCollapsed || isStart) && workingNode.nextSibling;
2612                     if (nextNode && isCharacterDataNode(nextNode)) {
2613                         boundaryPosition = new DomPosition(nextNode, 0);
2614                     } else if (previousNode && isCharacterDataNode(previousNode)) {
2615                         boundaryPosition = new DomPosition(previousNode, previousNode.data.length);
2616                     } else {
2617                         boundaryPosition = new DomPosition(containerElement, dom.getNodeIndex(workingNode));
2618                     }
2619                 }
2621                 // Clean up
2622                 dom.removeNode(workingNode);
2624                 return {
2625                     boundaryPosition: boundaryPosition,
2626                     nodeInfo: {
2627                         nodeIndex: nodeIndex,
2628                         containerElement: containerElement
2629                     }
2630                 };
2631             };
2633             // Returns a TextRange representing the boundary of a TextRange expressed as a node and an offset within that
2634             // node. This function started out as an optimized version of code found in Tim Cameron Ryan's IERange
2635             // (http://code.google.com/p/ierange/)
2636             var createBoundaryTextRange = function(boundaryPosition, isStart) {
2637                 var boundaryNode, boundaryParent, boundaryOffset = boundaryPosition.offset;
2638                 var doc = dom.getDocument(boundaryPosition.node);
2639                 var workingNode, childNodes, workingRange = getBody(doc).createTextRange();
2640                 var nodeIsDataNode = isCharacterDataNode(boundaryPosition.node);
2642                 if (nodeIsDataNode) {
2643                     boundaryNode = boundaryPosition.node;
2644                     boundaryParent = boundaryNode.parentNode;
2645                 } else {
2646                     childNodes = boundaryPosition.node.childNodes;
2647                     boundaryNode = (boundaryOffset < childNodes.length) ? childNodes[boundaryOffset] : null;
2648                     boundaryParent = boundaryPosition.node;
2649                 }
2651                 // Position the range immediately before the node containing the boundary
2652                 workingNode = doc.createElement("span");
2654                 // Making the working element non-empty element persuades IE to consider the TextRange boundary to be within
2655                 // the element rather than immediately before or after it
2656                 workingNode.innerHTML = "&#feff;";
2658                 // insertBefore is supposed to work like appendChild if the second parameter is null. However, a bug report
2659                 // for IERange suggests that it can crash the browser: http://code.google.com/p/ierange/issues/detail?id=12
2660                 if (boundaryNode) {
2661                     boundaryParent.insertBefore(workingNode, boundaryNode);
2662                 } else {
2663                     boundaryParent.appendChild(workingNode);
2664                 }
2666                 workingRange.moveToElementText(workingNode);
2667                 workingRange.collapse(!isStart);
2669                 // Clean up
2670                 boundaryParent.removeChild(workingNode);
2672                 // Move the working range to the text offset, if required
2673                 if (nodeIsDataNode) {
2674                     workingRange[isStart ? "moveStart" : "moveEnd"]("character", boundaryOffset);
2675                 }
2677                 return workingRange;
2678             };
2680             /*------------------------------------------------------------------------------------------------------------*/