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