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