Added Android code
[wl-app.git] / Android / webViewMarker / src / main / assets / rangy-core.js
diff --git a/Android/webViewMarker/src/main/assets/rangy-core.js b/Android/webViewMarker/src/main/assets/rangy-core.js
new file mode 100755 (executable)
index 0000000..a30dd5a
--- /dev/null
@@ -0,0 +1,3224 @@
+/**\r
+ * @license Rangy, a cross-browser JavaScript range and selection library\r
+ * http://code.google.com/p/rangy/\r
+ *\r
+ * Copyright 2012, Tim Down\r
+ * Licensed under the MIT license.\r
+ * Version: 1.2.3\r
+ * Build date: 26 February 2012\r
+ */\r
+window['rangy'] = (function() {\r
+\r
+\r
+    var OBJECT = "object", FUNCTION = "function", UNDEFINED = "undefined";\r
+\r
+    var domRangeProperties = ["startContainer", "startOffset", "endContainer", "endOffset", "collapsed",\r
+        "commonAncestorContainer", "START_TO_START", "START_TO_END", "END_TO_START", "END_TO_END"];\r
+\r
+    var domRangeMethods = ["setStart", "setStartBefore", "setStartAfter", "setEnd", "setEndBefore",\r
+        "setEndAfter", "collapse", "selectNode", "selectNodeContents", "compareBoundaryPoints", "deleteContents",\r
+        "extractContents", "cloneContents", "insertNode", "surroundContents", "cloneRange", "toString", "detach"];\r
+\r
+    var textRangeProperties = ["boundingHeight", "boundingLeft", "boundingTop", "boundingWidth", "htmlText", "text"];\r
+\r
+    // Subset of TextRange's full set of methods that we're interested in\r
+    var textRangeMethods = ["collapse", "compareEndPoints", "duplicate", "getBookmark", "moveToBookmark",\r
+        "moveToElementText", "parentElement", "pasteHTML", "select", "setEndPoint", "getBoundingClientRect"];\r
+\r
+    /*----------------------------------------------------------------------------------------------------------------*/\r
+\r
+    // Trio of functions taken from Peter Michaux's article:\r
+    // http://peter.michaux.ca/articles/feature-detection-state-of-the-art-browser-scripting\r
+    function isHostMethod(o, p) {\r
+        var t = typeof o[p];\r
+        return t == FUNCTION || (!!(t == OBJECT && o[p])) || t == "unknown";\r
+    }\r
+\r
+    function isHostObject(o, p) {\r
+        return !!(typeof o[p] == OBJECT && o[p]);\r
+    }\r
+\r
+    function isHostProperty(o, p) {\r
+        return typeof o[p] != UNDEFINED;\r
+    }\r
+\r
+    // Creates a convenience function to save verbose repeated calls to tests functions\r
+    function createMultiplePropertyTest(testFunc) {\r
+        return function(o, props) {\r
+            var i = props.length;\r
+            while (i--) {\r
+                if (!testFunc(o, props[i])) {\r
+                    return false;\r
+                }\r
+            }\r
+            return true;\r
+        };\r
+    }\r
+\r
+    // Next trio of functions are a convenience to save verbose repeated calls to previous two functions\r
+    var areHostMethods = createMultiplePropertyTest(isHostMethod);\r
+    var areHostObjects = createMultiplePropertyTest(isHostObject);\r
+    var areHostProperties = createMultiplePropertyTest(isHostProperty);\r
+\r
+    function isTextRange(range) {\r
+        return range && areHostMethods(range, textRangeMethods) && areHostProperties(range, textRangeProperties);\r
+    }\r
+\r
+    var api = {\r
+        version: "1.2.3",\r
+        initialized: false,\r
+        supported: true,\r
+\r
+        util: {\r
+            isHostMethod: isHostMethod,\r
+            isHostObject: isHostObject,\r
+            isHostProperty: isHostProperty,\r
+            areHostMethods: areHostMethods,\r
+            areHostObjects: areHostObjects,\r
+            areHostProperties: areHostProperties,\r
+            isTextRange: isTextRange\r
+        },\r
+\r
+        features: {},\r
+\r
+        modules: {},\r
+        config: {\r
+            alertOnWarn: false,\r
+            preferTextRange: false\r
+        }\r
+    };\r
+\r
+    function fail(reason) {\r
+        window.alert("Rangy not supported in your browser. Reason: " + reason);\r
+        api.initialized = true;\r
+        api.supported = false;\r
+    }\r
+\r
+    api.fail = fail;\r
+\r
+    function warn(msg) {\r
+        var warningMessage = "Rangy warning: " + msg;\r
+        if (api.config.alertOnWarn) {\r
+            window.alert(warningMessage);\r
+        } else if (typeof window.console != UNDEFINED && typeof window.console.log != UNDEFINED) {\r
+            window.console.log(warningMessage);\r
+        }\r
+    }\r
+\r
+    api.warn = warn;\r
+\r
+    if ({}.hasOwnProperty) {\r
+        api.util.extend = function(o, props) {\r
+            for (var i in props) {\r
+                if (props.hasOwnProperty(i)) {\r
+                    o[i] = props[i];\r
+                }\r
+            }\r
+        };\r
+    } else {\r
+        fail("hasOwnProperty not supported");\r
+    }\r
+\r
+    var initListeners = [];\r
+    var moduleInitializers = [];\r
+\r
+    // Initialization\r
+    function init() {\r
+        if (api.initialized) {\r
+            return;\r
+        }\r
+        var testRange;\r
+        var implementsDomRange = false, implementsTextRange = false;\r
+\r
+        // First, perform basic feature tests\r
+\r
+        if (isHostMethod(document, "createRange")) {\r
+            testRange = document.createRange();\r
+            if (areHostMethods(testRange, domRangeMethods) && areHostProperties(testRange, domRangeProperties)) {\r
+                implementsDomRange = true;\r
+            }\r
+            testRange.detach();\r
+        }\r
+\r
+        var body = isHostObject(document, "body") ? document.body : document.getElementsByTagName("body")[0];\r
+\r
+        if (body && isHostMethod(body, "createTextRange")) {\r
+            testRange = body.createTextRange();\r
+            if (isTextRange(testRange)) {\r
+                implementsTextRange = true;\r
+            }\r
+        }\r
+\r
+        if (!implementsDomRange && !implementsTextRange) {\r
+            fail("Neither Range nor TextRange are implemented");\r
+        }\r
+\r
+        api.initialized = true;\r
+        api.features = {\r
+            implementsDomRange: implementsDomRange,\r
+            implementsTextRange: implementsTextRange\r
+        };\r
+\r
+        // Initialize modules and call init listeners\r
+        var allListeners = moduleInitializers.concat(initListeners);\r
+        for (var i = 0, len = allListeners.length; i < len; ++i) {\r
+            try {\r
+                allListeners[i](api);\r
+            } catch (ex) {\r
+                if (isHostObject(window, "console") && isHostMethod(window.console, "log")) {\r
+                    window.console.log("Init listener threw an exception. Continuing.", ex);\r
+                }\r
+\r
+            }\r
+        }\r
+    }\r
+\r
+    // Allow external scripts to initialize this library in case it's loaded after the document has loaded\r
+    api.init = init;\r
+\r
+    // Execute listener immediately if already initialized\r
+    api.addInitListener = function(listener) {\r
+        if (api.initialized) {\r
+            listener(api);\r
+        } else {\r
+            initListeners.push(listener);\r
+        }\r
+    };\r
+\r
+    var createMissingNativeApiListeners = [];\r
+\r
+    api.addCreateMissingNativeApiListener = function(listener) {\r
+        createMissingNativeApiListeners.push(listener);\r
+    };\r
+\r
+    function createMissingNativeApi(win) {\r
+        win = win || window;\r
+        init();\r
+\r
+        // Notify listeners\r
+        for (var i = 0, len = createMissingNativeApiListeners.length; i < len; ++i) {\r
+            createMissingNativeApiListeners[i](win);\r
+        }\r
+    }\r
+\r
+    api.createMissingNativeApi = createMissingNativeApi;\r
+\r
+    /**\r
+     * @constructor\r
+     */\r
+    function Module(name) {\r
+        this.name = name;\r
+        this.initialized = false;\r
+        this.supported = false;\r
+    }\r
+\r
+    Module.prototype.fail = function(reason) {\r
+        this.initialized = true;\r
+        this.supported = false;\r
+\r
+        throw new Error("Module '" + this.name + "' failed to load: " + reason);\r
+    };\r
+\r
+    Module.prototype.warn = function(msg) {\r
+        api.warn("Module " + this.name + ": " + msg);\r
+    };\r
+\r
+    Module.prototype.createError = function(msg) {\r
+        return new Error("Error in Rangy " + this.name + " module: " + msg);\r
+    };\r
+\r
+    api.createModule = function(name, initFunc) {\r
+        var module = new Module(name);\r
+        api.modules[name] = module;\r
+\r
+        moduleInitializers.push(function(api) {\r
+            initFunc(api, module);\r
+            module.initialized = true;\r
+            module.supported = true;\r
+        });\r
+    };\r
+\r
+    api.requireModules = function(modules) {\r
+        for (var i = 0, len = modules.length, module, moduleName; i < len; ++i) {\r
+            moduleName = modules[i];\r
+            module = api.modules[moduleName];\r
+            if (!module || !(module instanceof Module)) {\r
+                throw new Error("Module '" + moduleName + "' not found");\r
+            }\r
+            if (!module.supported) {\r
+                throw new Error("Module '" + moduleName + "' not supported");\r
+            }\r
+        }\r
+    };\r
+\r
+    /*----------------------------------------------------------------------------------------------------------------*/\r
+\r
+    // Wait for document to load before running tests\r
+\r
+    var docReady = false;\r
+\r
+    var loadHandler = function(e) {\r
+\r
+        if (!docReady) {\r
+            docReady = true;\r
+            if (!api.initialized) {\r
+                init();\r
+            }\r
+        }\r
+    };\r
+\r
+    // Test whether we have window and document objects that we will need\r
+    if (typeof window == UNDEFINED) {\r
+        fail("No window found");\r
+        return;\r
+    }\r
+    if (typeof document == UNDEFINED) {\r
+        fail("No document found");\r
+        return;\r
+    }\r
+\r
+    if (isHostMethod(document, "addEventListener")) {\r
+        document.addEventListener("DOMContentLoaded", loadHandler, false);\r
+    }\r
+\r
+    // Add a fallback in case the DOMContentLoaded event isn't supported\r
+    if (isHostMethod(window, "addEventListener")) {\r
+        window.addEventListener("load", loadHandler, false);\r
+    } else if (isHostMethod(window, "attachEvent")) {\r
+        window.attachEvent("onload", loadHandler);\r
+    } else {\r
+        fail("Window does not have required addEventListener or attachEvent method");\r
+    }\r
+\r
+    return api;\r
+})();\r
+rangy.createModule("DomUtil", function(api, module) {\r
+\r
+    var UNDEF = "undefined";\r
+    var util = api.util;\r
+\r
+    // Perform feature tests\r
+    if (!util.areHostMethods(document, ["createDocumentFragment", "createElement", "createTextNode"])) {\r
+        module.fail("document missing a Node creation method");\r
+    }\r
+\r
+    if (!util.isHostMethod(document, "getElementsByTagName")) {\r
+        module.fail("document missing getElementsByTagName method");\r
+    }\r
+\r
+    var el = document.createElement("div");\r
+    if (!util.areHostMethods(el, ["insertBefore", "appendChild", "cloneNode"] ||\r
+            !util.areHostObjects(el, ["previousSibling", "nextSibling", "childNodes", "parentNode"]))) {\r
+        module.fail("Incomplete Element implementation");\r
+    }\r
+\r
+    // innerHTML is required for Range's createContextualFragment method\r
+    if (!util.isHostProperty(el, "innerHTML")) {\r
+        module.fail("Element is missing innerHTML property");\r
+    }\r
+\r
+    var textNode = document.createTextNode("test");\r
+    if (!util.areHostMethods(textNode, ["splitText", "deleteData", "insertData", "appendData", "cloneNode"] ||\r
+            !util.areHostObjects(el, ["previousSibling", "nextSibling", "childNodes", "parentNode"]) ||\r
+            !util.areHostProperties(textNode, ["data"]))) {\r
+        module.fail("Incomplete Text Node implementation");\r
+    }\r
+\r
+    /*----------------------------------------------------------------------------------------------------------------*/\r
+\r
+    // Removed use of indexOf because of a bizarre bug in Opera that is thrown in one of the Acid3 tests. I haven't been\r
+    // able to replicate it outside of the test. The bug is that indexOf returns -1 when called on an Array that\r
+    // contains just the document as a single element and the value searched for is the document.\r
+    var arrayContains = /*Array.prototype.indexOf ?\r
+        function(arr, val) {\r
+            return arr.indexOf(val) > -1;\r
+        }:*/\r
+\r
+        function(arr, val) {\r
+            var i = arr.length;\r
+            while (i--) {\r
+                if (arr[i] === val) {\r
+                    return true;\r
+                }\r
+            }\r
+            return false;\r
+        };\r
+\r
+    // Opera 11 puts HTML elements in the null namespace, it seems, and IE 7 has undefined namespaceURI\r
+    function isHtmlNamespace(node) {\r
+        var ns;\r
+        return typeof node.namespaceURI == UNDEF || ((ns = node.namespaceURI) === null || ns == "http://www.w3.org/1999/xhtml");\r
+    }\r
+\r
+    function parentElement(node) {\r
+        var parent = node.parentNode;\r
+        return (parent.nodeType == 1) ? parent : null;\r
+    }\r
+\r
+    function getNodeIndex(node) {\r
+        var i = 0;\r
+        while( (node = node.previousSibling) ) {\r
+            i++;\r
+        }\r
+        return i;\r
+    }\r
+\r
+    function getNodeLength(node) {\r
+        var childNodes;\r
+        return isCharacterDataNode(node) ? node.length : ((childNodes = node.childNodes) ? childNodes.length : 0);\r
+    }\r
+\r
+    function getCommonAncestor(node1, node2) {\r
+        var ancestors = [], n;\r
+        for (n = node1; n; n = n.parentNode) {\r
+            ancestors.push(n);\r
+        }\r
+\r
+        for (n = node2; n; n = n.parentNode) {\r
+            if (arrayContains(ancestors, n)) {\r
+                return n;\r
+            }\r
+        }\r
+\r
+        return null;\r
+    }\r
+\r
+    function isAncestorOf(ancestor, descendant, selfIsAncestor) {\r
+        var n = selfIsAncestor ? descendant : descendant.parentNode;\r
+        while (n) {\r
+            if (n === ancestor) {\r
+                return true;\r
+            } else {\r
+                n = n.parentNode;\r
+            }\r
+        }\r
+        return false;\r
+    }\r
+\r
+    function getClosestAncestorIn(node, ancestor, selfIsAncestor) {\r
+        var p, n = selfIsAncestor ? node : node.parentNode;\r
+        while (n) {\r
+            p = n.parentNode;\r
+            if (p === ancestor) {\r
+                return n;\r
+            }\r
+            n = p;\r
+        }\r
+        return null;\r
+    }\r
+\r
+    function isCharacterDataNode(node) {\r
+        var t = node.nodeType;\r
+        return t == 3 || t == 4 || t == 8 ; // Text, CDataSection or Comment\r
+    }\r
+\r
+    function insertAfter(node, precedingNode) {\r
+        var nextNode = precedingNode.nextSibling, parent = precedingNode.parentNode;\r
+        if (nextNode) {\r
+            parent.insertBefore(node, nextNode);\r
+        } else {\r
+            parent.appendChild(node);\r
+        }\r
+        return node;\r
+    }\r
+\r
+    // Note that we cannot use splitText() because it is bugridden in IE 9.\r
+    function splitDataNode(node, index) {\r
+        var newNode = node.cloneNode(false);\r
+        newNode.deleteData(0, index);\r
+        node.deleteData(index, node.length - index);\r
+        insertAfter(newNode, node);\r
+        return newNode;\r
+    }\r
+\r
+    function getDocument(node) {\r
+        if (node.nodeType == 9) {\r
+            return node;\r
+        } else if (typeof node.ownerDocument != UNDEF) {\r
+            return node.ownerDocument;\r
+        } else if (typeof node.document != UNDEF) {\r
+            return node.document;\r
+        } else if (node.parentNode) {\r
+            return getDocument(node.parentNode);\r
+        } else {\r
+            throw new Error("getDocument: no document found for node");\r
+        }\r
+    }\r
+\r
+    function getWindow(node) {\r
+        var doc = getDocument(node);\r
+        if (typeof doc.defaultView != UNDEF) {\r
+            return doc.defaultView;\r
+        } else if (typeof doc.parentWindow != UNDEF) {\r
+            return doc.parentWindow;\r
+        } else {\r
+            throw new Error("Cannot get a window object for node");\r
+        }\r
+    }\r
+\r
+    function getIframeDocument(iframeEl) {\r
+        if (typeof iframeEl.contentDocument != UNDEF) {\r
+            return iframeEl.contentDocument;\r
+        } else if (typeof iframeEl.contentWindow != UNDEF) {\r
+            return iframeEl.contentWindow.document;\r
+        } else {\r
+            throw new Error("getIframeWindow: No Document object found for iframe element");\r
+        }\r
+    }\r
+\r
+    function getIframeWindow(iframeEl) {\r
+        if (typeof iframeEl.contentWindow != UNDEF) {\r
+            return iframeEl.contentWindow;\r
+        } else if (typeof iframeEl.contentDocument != UNDEF) {\r
+            return iframeEl.contentDocument.defaultView;\r
+        } else {\r
+            throw new Error("getIframeWindow: No Window object found for iframe element");\r
+        }\r
+    }\r
+\r
+    function getBody(doc) {\r
+        return util.isHostObject(doc, "body") ? doc.body : doc.getElementsByTagName("body")[0];\r
+    }\r
+\r
+    function getRootContainer(node) {\r
+        var parent;\r
+        while ( (parent = node.parentNode) ) {\r
+            node = parent;\r
+        }\r
+        return node;\r
+    }\r
+\r
+    function comparePoints(nodeA, offsetA, nodeB, offsetB) {\r
+        // See http://www.w3.org/TR/DOM-Level-2-Traversal-Range/ranges.html#Level-2-Range-Comparing\r
+        var nodeC, root, childA, childB, n;\r
+        if (nodeA == nodeB) {\r
+\r
+            // Case 1: nodes are the same\r
+            return offsetA === offsetB ? 0 : (offsetA < offsetB) ? -1 : 1;\r
+        } else if ( (nodeC = getClosestAncestorIn(nodeB, nodeA, true)) ) {\r
+\r
+            // Case 2: node C (container B or an ancestor) is a child node of A\r
+            return offsetA <= getNodeIndex(nodeC) ? -1 : 1;\r
+        } else if ( (nodeC = getClosestAncestorIn(nodeA, nodeB, true)) ) {\r
+\r
+            // Case 3: node C (container A or an ancestor) is a child node of B\r
+            return getNodeIndex(nodeC) < offsetB  ? -1 : 1;\r
+        } else {\r
+\r
+            // Case 4: containers are siblings or descendants of siblings\r
+            root = getCommonAncestor(nodeA, nodeB);\r
+            childA = (nodeA === root) ? root : getClosestAncestorIn(nodeA, root, true);\r
+            childB = (nodeB === root) ? root : getClosestAncestorIn(nodeB, root, true);\r
+\r
+            if (childA === childB) {\r
+                // This shouldn't be possible\r
+\r
+                throw new Error("comparePoints got to case 4 and childA and childB are the same!");\r
+            } else {\r
+                n = root.firstChild;\r
+                while (n) {\r
+                    if (n === childA) {\r
+                        return -1;\r
+                    } else if (n === childB) {\r
+                        return 1;\r
+                    }\r
+                    n = n.nextSibling;\r
+                }\r
+                throw new Error("Should not be here!");\r
+            }\r
+        }\r
+    }\r
+\r
+    function fragmentFromNodeChildren(node) {\r
+        var fragment = getDocument(node).createDocumentFragment(), child;\r
+        while ( (child = node.firstChild) ) {\r
+            fragment.appendChild(child);\r
+        }\r
+        return fragment;\r
+    }\r
+\r
+    function inspectNode(node) {\r
+        if (!node) {\r
+            return "[No node]";\r
+        }\r
+        if (isCharacterDataNode(node)) {\r
+            return '"' + node.data + '"';\r
+        } else if (node.nodeType == 1) {\r
+            var idAttr = node.id ? ' id="' + node.id + '"' : "";\r
+            return "<" + node.nodeName + idAttr + ">[" + node.childNodes.length + "]";\r
+        } else {\r
+            return node.nodeName;\r
+        }\r
+    }\r
+\r
+    /**\r
+     * @constructor\r
+     */\r
+    function NodeIterator(root) {\r
+        this.root = root;\r
+        this._next = root;\r
+    }\r
+\r
+    NodeIterator.prototype = {\r
+        _current: null,\r
+\r
+        hasNext: function() {\r
+            return !!this._next;\r
+        },\r
+\r
+        next: function() {\r
+            var n = this._current = this._next;\r
+            var child, next;\r
+            if (this._current) {\r
+                child = n.firstChild;\r
+                if (child) {\r
+                    this._next = child;\r
+                } else {\r
+                    next = null;\r
+                    while ((n !== this.root) && !(next = n.nextSibling)) {\r
+                        n = n.parentNode;\r
+                    }\r
+                    this._next = next;\r
+                }\r
+            }\r
+            return this._current;\r
+        },\r
+\r
+        detach: function() {\r
+            this._current = this._next = this.root = null;\r
+        }\r
+    };\r
+\r
+    function createIterator(root) {\r
+        return new NodeIterator(root);\r
+    }\r
+\r
+    /**\r
+     * @constructor\r
+     */\r
+    function DomPosition(node, offset) {\r
+        this.node = node;\r
+        this.offset = offset;\r
+    }\r
+\r
+    DomPosition.prototype = {\r
+        equals: function(pos) {\r
+            return this.node === pos.node & this.offset == pos.offset;\r
+        },\r
+\r
+        inspect: function() {\r
+            return "[DomPosition(" + inspectNode(this.node) + ":" + this.offset + ")]";\r
+        }\r
+    };\r
+\r
+    /**\r
+     * @constructor\r
+     */\r
+    function DOMException(codeName) {\r
+        this.code = this[codeName];\r
+        this.codeName = codeName;\r
+        this.message = "DOMException: " + this.codeName;\r
+    }\r
+\r
+    DOMException.prototype = {\r
+        INDEX_SIZE_ERR: 1,\r
+        HIERARCHY_REQUEST_ERR: 3,\r
+        WRONG_DOCUMENT_ERR: 4,\r
+        NO_MODIFICATION_ALLOWED_ERR: 7,\r
+        NOT_FOUND_ERR: 8,\r
+        NOT_SUPPORTED_ERR: 9,\r
+        INVALID_STATE_ERR: 11\r
+    };\r
+\r
+    DOMException.prototype.toString = function() {\r
+        return this.message;\r
+    };\r
+\r
+    api.dom = {\r
+        arrayContains: arrayContains,\r
+        isHtmlNamespace: isHtmlNamespace,\r
+        parentElement: parentElement,\r
+        getNodeIndex: getNodeIndex,\r
+        getNodeLength: getNodeLength,\r
+        getCommonAncestor: getCommonAncestor,\r
+        isAncestorOf: isAncestorOf,\r
+        getClosestAncestorIn: getClosestAncestorIn,\r
+        isCharacterDataNode: isCharacterDataNode,\r
+        insertAfter: insertAfter,\r
+        splitDataNode: splitDataNode,\r
+        getDocument: getDocument,\r
+        getWindow: getWindow,\r
+        getIframeWindow: getIframeWindow,\r
+        getIframeDocument: getIframeDocument,\r
+        getBody: getBody,\r
+        getRootContainer: getRootContainer,\r
+        comparePoints: comparePoints,\r
+        inspectNode: inspectNode,\r
+        fragmentFromNodeChildren: fragmentFromNodeChildren,\r
+        createIterator: createIterator,\r
+        DomPosition: DomPosition\r
+    };\r
+\r
+    api.DOMException = DOMException;\r
+});rangy.createModule("DomRange", function(api, module) {
+    api.requireModules( ["DomUtil"] );
+
+
+    var dom = api.dom;
+    var DomPosition = dom.DomPosition;
+    var DOMException = api.DOMException;
+    
+    /*----------------------------------------------------------------------------------------------------------------*/
+
+    // Utility functions
+
+    function isNonTextPartiallySelected(node, range) {
+        return (node.nodeType != 3) &&
+               (dom.isAncestorOf(node, range.startContainer, true) || dom.isAncestorOf(node, range.endContainer, true));
+    }
+
+    function getRangeDocument(range) {
+        return dom.getDocument(range.startContainer);
+    }
+
+    function dispatchEvent(range, type, args) {
+        var listeners = range._listeners[type];
+        if (listeners) {
+            for (var i = 0, len = listeners.length; i < len; ++i) {
+                listeners[i].call(range, {target: range, args: args});
+            }
+        }
+    }
+
+    function getBoundaryBeforeNode(node) {
+        return new DomPosition(node.parentNode, dom.getNodeIndex(node));
+    }
+
+    function getBoundaryAfterNode(node) {
+        return new DomPosition(node.parentNode, dom.getNodeIndex(node) + 1);
+    }
+
+    function insertNodeAtPosition(node, n, o) {
+        var firstNodeInserted = node.nodeType == 11 ? node.firstChild : node;
+        if (dom.isCharacterDataNode(n)) {
+            if (o == n.length) {
+                dom.insertAfter(node, n);
+            } else {
+                n.parentNode.insertBefore(node, o == 0 ? n : dom.splitDataNode(n, o));
+            }
+        } else if (o >= n.childNodes.length) {
+            n.appendChild(node);
+        } else {
+            n.insertBefore(node, n.childNodes[o]);
+        }
+        return firstNodeInserted;
+    }
+
+    function cloneSubtree(iterator) {
+        var partiallySelected;
+        for (var node, frag = getRangeDocument(iterator.range).createDocumentFragment(), subIterator; node = iterator.next(); ) {
+            partiallySelected = iterator.isPartiallySelectedSubtree();
+
+            node = node.cloneNode(!partiallySelected);
+            if (partiallySelected) {
+                subIterator = iterator.getSubtreeIterator();
+                node.appendChild(cloneSubtree(subIterator));
+                subIterator.detach(true);
+            }
+
+            if (node.nodeType == 10) { // DocumentType
+                throw new DOMException("HIERARCHY_REQUEST_ERR");
+            }
+            frag.appendChild(node);
+        }
+        return frag;
+    }
+
+    function iterateSubtree(rangeIterator, func, iteratorState) {
+        var it, n;
+        iteratorState = iteratorState || { stop: false };
+        for (var node, subRangeIterator; node = rangeIterator.next(); ) {
+            //log.debug("iterateSubtree, partially selected: " + rangeIterator.isPartiallySelectedSubtree(), nodeToString(node));
+            if (rangeIterator.isPartiallySelectedSubtree()) {
+                // The node is partially selected by the Range, so we can use a new RangeIterator on the portion of the
+                // node selected by the Range.
+                if (func(node) === false) {
+                    iteratorState.stop = true;
+                    return;
+                } else {
+                    subRangeIterator = rangeIterator.getSubtreeIterator();
+                    iterateSubtree(subRangeIterator, func, iteratorState);
+                    subRangeIterator.detach(true);
+                    if (iteratorState.stop) {
+                        return;
+                    }
+                }
+            } else {
+                // The whole node is selected, so we can use efficient DOM iteration to iterate over the node and its
+                // descendant
+                it = dom.createIterator(node);
+                while ( (n = it.next()) ) {
+                    if (func(n) === false) {
+                        iteratorState.stop = true;
+                        return;
+                    }
+                }
+            }
+        }
+    }
+
+    function deleteSubtree(iterator) {
+        var subIterator;
+        while (iterator.next()) {
+            if (iterator.isPartiallySelectedSubtree()) {
+                subIterator = iterator.getSubtreeIterator();
+                deleteSubtree(subIterator);
+                subIterator.detach(true);
+            } else {
+                iterator.remove();
+            }
+        }
+    }
+
+    function extractSubtree(iterator) {
+
+        for (var node, frag = getRangeDocument(iterator.range).createDocumentFragment(), subIterator; node = iterator.next(); ) {
+
+
+            if (iterator.isPartiallySelectedSubtree()) {
+                node = node.cloneNode(false);
+                subIterator = iterator.getSubtreeIterator();
+                node.appendChild(extractSubtree(subIterator));
+                subIterator.detach(true);
+            } else {
+                iterator.remove();
+            }
+            if (node.nodeType == 10) { // DocumentType
+                throw new DOMException("HIERARCHY_REQUEST_ERR");
+            }
+            frag.appendChild(node);
+        }
+        return frag;
+    }
+
+    function getNodesInRange(range, nodeTypes, filter) {
+        //log.info("getNodesInRange, " + nodeTypes.join(","));
+        var filterNodeTypes = !!(nodeTypes && nodeTypes.length), regex;
+        var filterExists = !!filter;
+        if (filterNodeTypes) {
+            regex = new RegExp("^(" + nodeTypes.join("|") + ")$");
+        }
+
+        var nodes = [];
+        iterateSubtree(new RangeIterator(range, false), function(node) {
+            if ((!filterNodeTypes || regex.test(node.nodeType)) && (!filterExists || filter(node))) {
+                nodes.push(node);
+            }
+        });
+        return nodes;
+    }
+
+    function inspect(range) {
+        var name = (typeof range.getName == "undefined") ? "Range" : range.getName();
+        return "[" + name + "(" + dom.inspectNode(range.startContainer) + ":" + range.startOffset + ", " +
+                dom.inspectNode(range.endContainer) + ":" + range.endOffset + ")]";
+    }
+
+    /*----------------------------------------------------------------------------------------------------------------*/
+
+    // RangeIterator code partially borrows from IERange by Tim Ryan (http://github.com/timcameronryan/IERange)
+
+    /**
+     * @constructor
+     */
+    function RangeIterator(range, clonePartiallySelectedTextNodes) {
+        this.range = range;
+        this.clonePartiallySelectedTextNodes = clonePartiallySelectedTextNodes;
+
+
+
+        if (!range.collapsed) {
+            this.sc = range.startContainer;
+            this.so = range.startOffset;
+            this.ec = range.endContainer;
+            this.eo = range.endOffset;
+            var root = range.commonAncestorContainer;
+
+            if (this.sc === this.ec && dom.isCharacterDataNode(this.sc)) {
+                this.isSingleCharacterDataNode = true;
+                this._first = this._last = this._next = this.sc;
+            } else {
+                this._first = this._next = (this.sc === root && !dom.isCharacterDataNode(this.sc)) ?
+                    this.sc.childNodes[this.so] : dom.getClosestAncestorIn(this.sc, root, true);
+                this._last = (this.ec === root && !dom.isCharacterDataNode(this.ec)) ?
+                    this.ec.childNodes[this.eo - 1] : dom.getClosestAncestorIn(this.ec, root, true);
+            }
+
+        }
+    }
+
+    RangeIterator.prototype = {
+        _current: null,
+        _next: null,
+        _first: null,
+        _last: null,
+        isSingleCharacterDataNode: false,
+
+        reset: function() {
+            this._current = null;
+            this._next = this._first;
+        },
+
+        hasNext: function() {
+            return !!this._next;
+        },
+
+        next: function() {
+            // Move to next node
+            var current = this._current = this._next;
+            if (current) {
+                this._next = (current !== this._last) ? current.nextSibling : null;
+
+                // Check for partially selected text nodes
+                if (dom.isCharacterDataNode(current) && this.clonePartiallySelectedTextNodes) {
+                    if (current === this.ec) {
+
+                        (current = current.cloneNode(true)).deleteData(this.eo, current.length - this.eo);
+                    }
+                    if (this._current === this.sc) {
+
+                        (current = current.cloneNode(true)).deleteData(0, this.so);
+                    }
+                }
+            }
+
+            return current;
+        },
+
+        remove: function() {
+            var current = this._current, start, end;
+
+            if (dom.isCharacterDataNode(current) && (current === this.sc || current === this.ec)) {
+                start = (current === this.sc) ? this.so : 0;
+                end = (current === this.ec) ? this.eo : current.length;
+                if (start != end) {
+                    current.deleteData(start, end - start);
+                }
+            } else {
+                if (current.parentNode) {
+                    current.parentNode.removeChild(current);
+                } else {
+
+                }
+            }
+        },
+
+        // Checks if the current node is partially selected
+        isPartiallySelectedSubtree: function() {
+            var current = this._current;
+            return isNonTextPartiallySelected(current, this.range);
+        },
+
+        getSubtreeIterator: function() {
+            var subRange;
+            if (this.isSingleCharacterDataNode) {
+                subRange = this.range.cloneRange();
+                subRange.collapse();
+            } else {
+                subRange = new Range(getRangeDocument(this.range));
+                var current = this._current;
+                var startContainer = current, startOffset = 0, endContainer = current, endOffset = dom.getNodeLength(current);
+
+                if (dom.isAncestorOf(current, this.sc, true)) {
+                    startContainer = this.sc;
+                    startOffset = this.so;
+                }
+                if (dom.isAncestorOf(current, this.ec, true)) {
+                    endContainer = this.ec;
+                    endOffset = this.eo;
+                }
+
+                updateBoundaries(subRange, startContainer, startOffset, endContainer, endOffset);
+            }
+            return new RangeIterator(subRange, this.clonePartiallySelectedTextNodes);
+        },
+
+        detach: function(detachRange) {
+            if (detachRange) {
+                this.range.detach();
+            }
+            this.range = this._current = this._next = this._first = this._last = this.sc = this.so = this.ec = this.eo = null;
+        }
+    };
+
+    /*----------------------------------------------------------------------------------------------------------------*/
+
+    // Exceptions
+
+    /**
+     * @constructor
+     */
+    function RangeException(codeName) {
+        this.code = this[codeName];
+        this.codeName = codeName;
+        this.message = "RangeException: " + this.codeName;
+    }
+
+    RangeException.prototype = {
+        BAD_BOUNDARYPOINTS_ERR: 1,
+        INVALID_NODE_TYPE_ERR: 2
+    };
+
+    RangeException.prototype.toString = function() {
+        return this.message;
+    };
+
+    /*----------------------------------------------------------------------------------------------------------------*/
+
+    /**
+     * Currently iterates through all nodes in the range on creation until I think of a decent way to do it
+     * TODO: Look into making this a proper iterator, not requiring preloading everything first
+     * @constructor
+     */
+    function RangeNodeIterator(range, nodeTypes, filter) {
+        this.nodes = getNodesInRange(range, nodeTypes, filter);
+        this._next = this.nodes[0];
+        this._position = 0;
+    }
+
+    RangeNodeIterator.prototype = {
+        _current: null,
+
+        hasNext: function() {
+            return !!this._next;
+        },
+
+        next: function() {
+            this._current = this._next;
+            this._next = this.nodes[ ++this._position ];
+            return this._current;
+        },
+
+        detach: function() {
+            this._current = this._next = this.nodes = null;
+        }
+    };
+
+    var beforeAfterNodeTypes = [1, 3, 4, 5, 7, 8, 10];
+    var rootContainerNodeTypes = [2, 9, 11];
+    var readonlyNodeTypes = [5, 6, 10, 12];
+    var insertableNodeTypes = [1, 3, 4, 5, 7, 8, 10, 11];
+    var surroundNodeTypes = [1, 3, 4, 5, 7, 8];
+
+    function createAncestorFinder(nodeTypes) {
+        return function(node, selfIsAncestor) {
+            var t, n = selfIsAncestor ? node : node.parentNode;
+            while (n) {
+                t = n.nodeType;
+                if (dom.arrayContains(nodeTypes, t)) {
+                    return n;
+                }
+                n = n.parentNode;
+            }
+            return null;
+        };
+    }
+
+    var getRootContainer = dom.getRootContainer;
+    var getDocumentOrFragmentContainer = createAncestorFinder( [9, 11] );
+    var getReadonlyAncestor = createAncestorFinder(readonlyNodeTypes);
+    var getDocTypeNotationEntityAncestor = createAncestorFinder( [6, 10, 12] );
+
+    function assertNoDocTypeNotationEntityAncestor(node, allowSelf) {
+        if (getDocTypeNotationEntityAncestor(node, allowSelf)) {
+            throw new RangeException("INVALID_NODE_TYPE_ERR");
+        }
+    }
+
+    function assertNotDetached(range) {
+        if (!range.startContainer) {
+            throw new DOMException("INVALID_STATE_ERR");
+        }
+    }
+
+    function assertValidNodeType(node, invalidTypes) {
+        if (!dom.arrayContains(invalidTypes, node.nodeType)) {
+            throw new RangeException("INVALID_NODE_TYPE_ERR");
+        }
+    }
+
+    function assertValidOffset(node, offset) {
+        if (offset < 0 || offset > (dom.isCharacterDataNode(node) ? node.length : node.childNodes.length)) {
+            throw new DOMException("INDEX_SIZE_ERR");
+        }
+    }
+
+    function assertSameDocumentOrFragment(node1, node2) {
+        if (getDocumentOrFragmentContainer(node1, true) !== getDocumentOrFragmentContainer(node2, true)) {
+            throw new DOMException("WRONG_DOCUMENT_ERR");
+        }
+    }
+
+    function assertNodeNotReadOnly(node) {
+        if (getReadonlyAncestor(node, true)) {
+            throw new DOMException("NO_MODIFICATION_ALLOWED_ERR");
+        }
+    }
+
+    function assertNode(node, codeName) {
+        if (!node) {
+            throw new DOMException(codeName);
+        }
+    }
+
+    function isOrphan(node) {
+        return !dom.arrayContains(rootContainerNodeTypes, node.nodeType) && !getDocumentOrFragmentContainer(node, true);
+    }
+
+    function isValidOffset(node, offset) {
+        return offset <= (dom.isCharacterDataNode(node) ? node.length : node.childNodes.length);
+    }
+
+    function isRangeValid(range) {
+        return (!!range.startContainer && !!range.endContainer
+                && !isOrphan(range.startContainer)
+                && !isOrphan(range.endContainer)
+                && isValidOffset(range.startContainer, range.startOffset)
+                && isValidOffset(range.endContainer, range.endOffset));
+    }
+
+    function assertRangeValid(range) {
+        assertNotDetached(range);
+        if (!isRangeValid(range)) {
+            throw new Error("Range error: Range is no longer valid after DOM mutation (" + range.inspect() + ")");
+        }
+    }
+
+    /*----------------------------------------------------------------------------------------------------------------*/
+
+    // Test the browser's innerHTML support to decide how to implement createContextualFragment
+    var styleEl = document.createElement("style");
+    var htmlParsingConforms = false;
+    try {
+        styleEl.innerHTML = "<b>x</b>";
+        htmlParsingConforms = (styleEl.firstChild.nodeType == 3); // Opera incorrectly creates an element node
+    } catch (e) {
+        // IE 6 and 7 throw
+    }
+
+    api.features.htmlParsingConforms = htmlParsingConforms;
+
+    var createContextualFragment = htmlParsingConforms ?
+
+        // Implementation as per HTML parsing spec, trusting in the browser's implementation of innerHTML. See
+        // discussion and base code for this implementation at issue 67.
+        // Spec: http://html5.org/specs/dom-parsing.html#extensions-to-the-range-interface
+        // Thanks to Aleks Williams.
+        function(fragmentStr) {
+            // "Let node the context object's start's node."
+            var node = this.startContainer;
+            var doc = dom.getDocument(node);
+
+            // "If the context object's start's node is null, raise an INVALID_STATE_ERR
+            // exception and abort these steps."
+            if (!node) {
+                throw new DOMException("INVALID_STATE_ERR");
+            }
+
+            // "Let element be as follows, depending on node's interface:"
+            // Document, Document Fragment: null
+            var el = null;
+
+            // "Element: node"
+            if (node.nodeType == 1) {
+                el = node;
+
+            // "Text, Comment: node's parentElement"
+            } else if (dom.isCharacterDataNode(node)) {
+                el = dom.parentElement(node);
+            }
+
+            // "If either element is null or element's ownerDocument is an HTML document
+            // and element's local name is "html" and element's namespace is the HTML
+            // namespace"
+            if (el === null || (
+                el.nodeName == "HTML"
+                && dom.isHtmlNamespace(dom.getDocument(el).documentElement)
+                && dom.isHtmlNamespace(el)
+            )) {
+
+            // "let element be a new Element with "body" as its local name and the HTML
+            // namespace as its namespace.""
+                el = doc.createElement("body");
+            } else {
+                el = el.cloneNode(false);
+            }
+
+            // "If the node's document is an HTML document: Invoke the HTML fragment parsing algorithm."
+            // "If the node's document is an XML document: Invoke the XML fragment parsing algorithm."
+            // "In either case, the algorithm must be invoked with fragment as the input
+            // and element as the context element."
+            el.innerHTML = fragmentStr;
+
+            // "If this raises an exception, then abort these steps. Otherwise, let new
+            // children be the nodes returned."
+
+            // "Let fragment be a new DocumentFragment."
+            // "Append all new children to fragment."
+            // "Return fragment."
+            return dom.fragmentFromNodeChildren(el);
+        } :
+
+        // In this case, innerHTML cannot be trusted, so fall back to a simpler, non-conformant implementation that
+        // previous versions of Rangy used (with the exception of using a body element rather than a div)
+        function(fragmentStr) {
+            assertNotDetached(this);
+            var doc = getRangeDocument(this);
+            var el = doc.createElement("body");
+            el.innerHTML = fragmentStr;
+
+            return dom.fragmentFromNodeChildren(el);
+        };
+
+    /*----------------------------------------------------------------------------------------------------------------*/
+
+    var rangeProperties = ["startContainer", "startOffset", "endContainer", "endOffset", "collapsed",
+        "commonAncestorContainer"];
+
+    var s2s = 0, s2e = 1, e2e = 2, e2s = 3;
+    var n_b = 0, n_a = 1, n_b_a = 2, n_i = 3;
+
+    function RangePrototype() {}
+
+    RangePrototype.prototype = {
+        attachListener: function(type, listener) {
+            this._listeners[type].push(listener);
+        },
+
+        compareBoundaryPoints: function(how, range) {
+            assertRangeValid(this);
+            assertSameDocumentOrFragment(this.startContainer, range.startContainer);
+
+            var nodeA, offsetA, nodeB, offsetB;
+            var prefixA = (how == e2s || how == s2s) ? "start" : "end";
+            var prefixB = (how == s2e || how == s2s) ? "start" : "end";
+            nodeA = this[prefixA + "Container"];
+            offsetA = this[prefixA + "Offset"];
+            nodeB = range[prefixB + "Container"];
+            offsetB = range[prefixB + "Offset"];
+            return dom.comparePoints(nodeA, offsetA, nodeB, offsetB);
+        },
+
+        insertNode: function(node) {
+            assertRangeValid(this);
+            assertValidNodeType(node, insertableNodeTypes);
+            assertNodeNotReadOnly(this.startContainer);
+
+            if (dom.isAncestorOf(node, this.startContainer, true)) {
+                throw new DOMException("HIERARCHY_REQUEST_ERR");
+            }
+
+            // No check for whether the container of the start of the Range is of a type that does not allow
+            // children of the type of node: the browser's DOM implementation should do this for us when we attempt
+            // to add the node
+
+            var firstNodeInserted = insertNodeAtPosition(node, this.startContainer, this.startOffset);
+            this.setStartBefore(firstNodeInserted);
+        },
+
+        cloneContents: function() {
+            assertRangeValid(this);
+
+            var clone, frag;
+            if (this.collapsed) {
+                return getRangeDocument(this).createDocumentFragment();
+            } else {
+                if (this.startContainer === this.endContainer && dom.isCharacterDataNode(this.startContainer)) {
+                    clone = this.startContainer.cloneNode(true);
+                    clone.data = clone.data.slice(this.startOffset, this.endOffset);
+                    frag = getRangeDocument(this).createDocumentFragment();
+                    frag.appendChild(clone);
+                    return frag;
+                } else {
+                    var iterator = new RangeIterator(this, true);
+                    clone = cloneSubtree(iterator);
+                    iterator.detach();
+                }
+                return clone;
+            }
+        },
+
+        canSurroundContents: function() {
+            assertRangeValid(this);
+            assertNodeNotReadOnly(this.startContainer);
+            assertNodeNotReadOnly(this.endContainer);
+
+            // Check if the contents can be surrounded. Specifically, this means whether the range partially selects
+            // no non-text nodes.
+            var iterator = new RangeIterator(this, true);
+            var boundariesInvalid = (iterator._first && (isNonTextPartiallySelected(iterator._first, this)) ||
+                    (iterator._last && isNonTextPartiallySelected(iterator._last, this)));
+            iterator.detach();
+            return !boundariesInvalid;
+        },
+
+        surroundContents: function(node) {
+            assertValidNodeType(node, surroundNodeTypes);
+
+            if (!this.canSurroundContents()) {
+                throw new RangeException("BAD_BOUNDARYPOINTS_ERR");
+            }
+
+            // Extract the contents
+            var content = this.extractContents();
+
+            // Clear the children of the node
+            if (node.hasChildNodes()) {
+                while (node.lastChild) {
+                    node.removeChild(node.lastChild);
+                }
+            }
+
+            // Insert the new node and add the extracted contents
+            insertNodeAtPosition(node, this.startContainer, this.startOffset);
+            node.appendChild(content);
+
+            this.selectNode(node);
+        },
+
+        cloneRange: function() {
+            assertRangeValid(this);
+            var range = new Range(getRangeDocument(this));
+            var i = rangeProperties.length, prop;
+            while (i--) {
+                prop = rangeProperties[i];
+                range[prop] = this[prop];
+            }
+            return range;
+        },
+
+        toString: function() {
+            assertRangeValid(this);
+            var sc = this.startContainer;
+            if (sc === this.endContainer && dom.isCharacterDataNode(sc)) {
+                return (sc.nodeType == 3 || sc.nodeType == 4) ? sc.data.slice(this.startOffset, this.endOffset) : "";
+            } else {
+                var textBits = [], iterator = new RangeIterator(this, true);
+
+                iterateSubtree(iterator, function(node) {
+                    // Accept only text or CDATA nodes, not comments
+
+                    if (node.nodeType == 3 || node.nodeType == 4) {
+                        textBits.push(node.data);
+                    }
+                });
+                iterator.detach();
+                return textBits.join("");
+            }
+        },
+
+        // The methods below are all non-standard. The following batch were introduced by Mozilla but have since
+        // been removed from Mozilla.
+
+        compareNode: function(node) {
+            assertRangeValid(this);
+
+            var parent = node.parentNode;
+            var nodeIndex = dom.getNodeIndex(node);
+
+            if (!parent) {
+                throw new DOMException("NOT_FOUND_ERR");
+            }
+
+            var startComparison = this.comparePoint(parent, nodeIndex),
+                endComparison = this.comparePoint(parent, nodeIndex + 1);
+
+            if (startComparison < 0) { // Node starts before
+                return (endComparison > 0) ? n_b_a : n_b;
+            } else {
+                return (endComparison > 0) ? n_a : n_i;
+            }
+        },
+
+        comparePoint: function(node, offset) {
+            assertRangeValid(this);
+            assertNode(node, "HIERARCHY_REQUEST_ERR");
+            assertSameDocumentOrFragment(node, this.startContainer);
+
+            if (dom.comparePoints(node, offset, this.startContainer, this.startOffset) < 0) {
+                return -1;
+            } else if (dom.comparePoints(node, offset, this.endContainer, this.endOffset) > 0) {
+                return 1;
+            }
+            return 0;
+        },
+
+        createContextualFragment: createContextualFragment,
+
+        toHtml: function() {
+            assertRangeValid(this);
+            var container = getRangeDocument(this).createElement("div");
+            container.appendChild(this.cloneContents());
+            return container.innerHTML;
+        },
+
+        // touchingIsIntersecting determines whether this method considers a node that borders a range intersects
+        // with it (as in WebKit) or not (as in Gecko pre-1.9, and the default)
+        intersectsNode: function(node, touchingIsIntersecting) {
+            assertRangeValid(this);
+            assertNode(node, "NOT_FOUND_ERR");
+            if (dom.getDocument(node) !== getRangeDocument(this)) {
+                return false;
+            }
+
+            var parent = node.parentNode, offset = dom.getNodeIndex(node);
+            assertNode(parent, "NOT_FOUND_ERR");
+
+            var startComparison = dom.comparePoints(parent, offset, this.endContainer, this.endOffset),
+                endComparison = dom.comparePoints(parent, offset + 1, this.startContainer, this.startOffset);
+
+            return touchingIsIntersecting ? startComparison <= 0 && endComparison >= 0 : startComparison < 0 && endComparison > 0;
+        },
+
+
+        isPointInRange: function(node, offset) {
+            assertRangeValid(this);
+            assertNode(node, "HIERARCHY_REQUEST_ERR");
+            assertSameDocumentOrFragment(node, this.startContainer);
+
+            return (dom.comparePoints(node, offset, this.startContainer, this.startOffset) >= 0) &&
+                   (dom.comparePoints(node, offset, this.endContainer, this.endOffset) <= 0);
+        },
+
+        // The methods below are non-standard and invented by me.
+
+        // Sharing a boundary start-to-end or end-to-start does not count as intersection.
+        intersectsRange: function(range, touchingIsIntersecting) {
+            assertRangeValid(this);
+
+            if (getRangeDocument(range) != getRangeDocument(this)) {
+                throw new DOMException("WRONG_DOCUMENT_ERR");
+            }
+
+            var startComparison = dom.comparePoints(this.startContainer, this.startOffset, range.endContainer, range.endOffset),
+                endComparison = dom.comparePoints(this.endContainer, this.endOffset, range.startContainer, range.startOffset);
+
+            return touchingIsIntersecting ? startComparison <= 0 && endComparison >= 0 : startComparison < 0 && endComparison > 0;
+        },
+
+        intersection: function(range) {
+            if (this.intersectsRange(range)) {
+                var startComparison = dom.comparePoints(this.startContainer, this.startOffset, range.startContainer, range.startOffset),
+                    endComparison = dom.comparePoints(this.endContainer, this.endOffset, range.endContainer, range.endOffset);
+
+                var intersectionRange = this.cloneRange();
+
+                if (startComparison == -1) {
+                    intersectionRange.setStart(range.startContainer, range.startOffset);
+                }
+                if (endComparison == 1) {
+                    intersectionRange.setEnd(range.endContainer, range.endOffset);
+                }
+                return intersectionRange;
+            }
+            return null;
+        },
+
+        union: function(range) {
+            if (this.intersectsRange(range, true)) {
+                var unionRange = this.cloneRange();
+                if (dom.comparePoints(range.startContainer, range.startOffset, this.startContainer, this.startOffset) == -1) {
+                    unionRange.setStart(range.startContainer, range.startOffset);
+                }
+                if (dom.comparePoints(range.endContainer, range.endOffset, this.endContainer, this.endOffset) == 1) {
+                    unionRange.setEnd(range.endContainer, range.endOffset);
+                }
+                return unionRange;
+            } else {
+                throw new RangeException("Ranges do not intersect");
+            }
+        },
+
+        containsNode: function(node, allowPartial) {
+            if (allowPartial) {
+                return this.intersectsNode(node, false);
+            } else {
+                return this.compareNode(node) == n_i;
+            }
+        },
+
+        containsNodeContents: function(node) {
+            return this.comparePoint(node, 0) >= 0 && this.comparePoint(node, dom.getNodeLength(node)) <= 0;
+        },
+
+        containsRange: function(range) {
+            return this.intersection(range).equals(range);
+        },
+
+        containsNodeText: function(node) {
+            var nodeRange = this.cloneRange();
+            nodeRange.selectNode(node);
+            var textNodes = nodeRange.getNodes([3]);
+            if (textNodes.length > 0) {
+                nodeRange.setStart(textNodes[0], 0);
+                var lastTextNode = textNodes.pop();
+                nodeRange.setEnd(lastTextNode, lastTextNode.length);
+                var contains = this.containsRange(nodeRange);
+                nodeRange.detach();
+                return contains;
+            } else {
+                return this.containsNodeContents(node);
+            }
+        },
+
+        createNodeIterator: function(nodeTypes, filter) {
+            assertRangeValid(this);
+            return new RangeNodeIterator(this, nodeTypes, filter);
+        },
+
+        getNodes: function(nodeTypes, filter) {
+            assertRangeValid(this);
+            return getNodesInRange(this, nodeTypes, filter);
+        },
+
+        getDocument: function() {
+            return getRangeDocument(this);
+        },
+
+        collapseBefore: function(node) {
+            assertNotDetached(this);
+
+            this.setEndBefore(node);
+            this.collapse(false);
+        },
+
+        collapseAfter: function(node) {
+            assertNotDetached(this);
+
+            this.setStartAfter(node);
+            this.collapse(true);
+        },
+
+        getName: function() {
+            return "DomRange";
+        },
+
+        equals: function(range) {
+            return Range.rangesEqual(this, range);
+        },
+
+        isValid: function() {
+            return isRangeValid(this);
+        },
+
+        inspect: function() {
+            return inspect(this);
+        }
+    };
+
+    function copyComparisonConstantsToObject(obj) {
+        obj.START_TO_START = s2s;
+        obj.START_TO_END = s2e;
+        obj.END_TO_END = e2e;
+        obj.END_TO_START = e2s;
+
+        obj.NODE_BEFORE = n_b;
+        obj.NODE_AFTER = n_a;
+        obj.NODE_BEFORE_AND_AFTER = n_b_a;
+        obj.NODE_INSIDE = n_i;
+    }
+
+    function copyComparisonConstants(constructor) {
+        copyComparisonConstantsToObject(constructor);
+        copyComparisonConstantsToObject(constructor.prototype);
+    }
+
+    function createRangeContentRemover(remover, boundaryUpdater) {
+        return function() {
+            assertRangeValid(this);
+
+            var sc = this.startContainer, so = this.startOffset, root = this.commonAncestorContainer;
+
+            var iterator = new RangeIterator(this, true);
+
+            // Work out where to position the range after content removal
+            var node, boundary;
+            if (sc !== root) {
+                node = dom.getClosestAncestorIn(sc, root, true);
+                boundary = getBoundaryAfterNode(node);
+                sc = boundary.node;
+                so = boundary.offset;
+            }
+
+            // Check none of the range is read-only
+            iterateSubtree(iterator, assertNodeNotReadOnly);
+
+            iterator.reset();
+
+            // Remove the content
+            var returnValue = remover(iterator);
+            iterator.detach();
+
+            // Move to the new position
+            boundaryUpdater(this, sc, so, sc, so);
+
+            return returnValue;
+        };
+    }
+
+    function createPrototypeRange(constructor, boundaryUpdater, detacher) {
+        function createBeforeAfterNodeSetter(isBefore, isStart) {
+            return function(node) {
+                assertNotDetached(this);
+                assertValidNodeType(node, beforeAfterNodeTypes);
+                assertValidNodeType(getRootContainer(node), rootContainerNodeTypes);
+
+                var boundary = (isBefore ? getBoundaryBeforeNode : getBoundaryAfterNode)(node);
+                (isStart ? setRangeStart : setRangeEnd)(this, boundary.node, boundary.offset);
+            };
+        }
+
+        function setRangeStart(range, node, offset) {
+            var ec = range.endContainer, eo = range.endOffset;
+            if (node !== range.startContainer || offset !== range.startOffset) {
+                // Check the root containers of the range and the new boundary, and also check whether the new boundary
+                // is after the current end. In either case, collapse the range to the new position
+                if (getRootContainer(node) != getRootContainer(ec) || dom.comparePoints(node, offset, ec, eo) == 1) {
+                    ec = node;
+                    eo = offset;
+                }
+                boundaryUpdater(range, node, offset, ec, eo);
+            }
+        }
+
+        function setRangeEnd(range, node, offset) {
+            var sc = range.startContainer, so = range.startOffset;
+            if (node !== range.endContainer || offset !== range.endOffset) {
+                // Check the root containers of the range and the new boundary, and also check whether the new boundary
+                // is after the current end. In either case, collapse the range to the new position
+                if (getRootContainer(node) != getRootContainer(sc) || dom.comparePoints(node, offset, sc, so) == -1) {
+                    sc = node;
+                    so = offset;
+                }
+                boundaryUpdater(range, sc, so, node, offset);
+            }
+        }
+
+        function setRangeStartAndEnd(range, node, offset) {
+            if (node !== range.startContainer || offset !== range.startOffset || node !== range.endContainer || offset !== range.endOffset) {
+                boundaryUpdater(range, node, offset, node, offset);
+            }
+        }
+
+        constructor.prototype = new RangePrototype();
+
+        api.util.extend(constructor.prototype, {
+            setStart: function(node, offset) {
+                assertNotDetached(this);
+                assertNoDocTypeNotationEntityAncestor(node, true);
+                assertValidOffset(node, offset);
+
+                setRangeStart(this, node, offset);
+            },
+
+            setEnd: function(node, offset) {
+                assertNotDetached(this);
+                assertNoDocTypeNotationEntityAncestor(node, true);
+                assertValidOffset(node, offset);
+
+                setRangeEnd(this, node, offset);
+            },
+
+            setStartBefore: createBeforeAfterNodeSetter(true, true),
+            setStartAfter: createBeforeAfterNodeSetter(false, true),
+            setEndBefore: createBeforeAfterNodeSetter(true, false),
+            setEndAfter: createBeforeAfterNodeSetter(false, false),
+
+            collapse: function(isStart) {
+                assertRangeValid(this);
+                if (isStart) {
+                    boundaryUpdater(this, this.startContainer, this.startOffset, this.startContainer, this.startOffset);
+                } else {
+                    boundaryUpdater(this, this.endContainer, this.endOffset, this.endContainer, this.endOffset);
+                }
+            },
+
+            selectNodeContents: function(node) {
+                // This doesn't seem well specified: the spec talks only about selecting the node's contents, which
+                // could be taken to mean only its children. However, browsers implement this the same as selectNode for
+                // text nodes, so I shall do likewise
+                assertNotDetached(this);
+                assertNoDocTypeNotationEntityAncestor(node, true);
+
+                boundaryUpdater(this, node, 0, node, dom.getNodeLength(node));
+            },
+
+            selectNode: function(node) {
+                assertNotDetached(this);
+                assertNoDocTypeNotationEntityAncestor(node, false);
+                assertValidNodeType(node, beforeAfterNodeTypes);
+
+                var start = getBoundaryBeforeNode(node), end = getBoundaryAfterNode(node);
+                boundaryUpdater(this, start.node, start.offset, end.node, end.offset);
+            },
+
+            extractContents: createRangeContentRemover(extractSubtree, boundaryUpdater),
+
+            deleteContents: createRangeContentRemover(deleteSubtree, boundaryUpdater),
+
+            canSurroundContents: function() {
+                assertRangeValid(this);
+                assertNodeNotReadOnly(this.startContainer);
+                assertNodeNotReadOnly(this.endContainer);
+
+                // Check if the contents can be surrounded. Specifically, this means whether the range partially selects
+                // no non-text nodes.
+                var iterator = new RangeIterator(this, true);
+                var boundariesInvalid = (iterator._first && (isNonTextPartiallySelected(iterator._first, this)) ||
+                        (iterator._last && isNonTextPartiallySelected(iterator._last, this)));
+                iterator.detach();
+                return !boundariesInvalid;
+            },
+
+            detach: function() {
+                detacher(this);
+            },
+
+            splitBoundaries: function() {
+                assertRangeValid(this);
+
+
+                var sc = this.startContainer, so = this.startOffset, ec = this.endContainer, eo = this.endOffset;
+                var startEndSame = (sc === ec);
+
+                if (dom.isCharacterDataNode(ec) && eo > 0 && eo < ec.length) {
+                    dom.splitDataNode(ec, eo);
+
+                }
+
+                if (dom.isCharacterDataNode(sc) && so > 0 && so < sc.length) {
+
+                    sc = dom.splitDataNode(sc, so);
+                    if (startEndSame) {
+                        eo -= so;
+                        ec = sc;
+                    } else if (ec == sc.parentNode && eo >= dom.getNodeIndex(sc)) {
+                        eo++;
+                    }
+                    so = 0;
+
+                }
+                boundaryUpdater(this, sc, so, ec, eo);
+            },
+
+            normalizeBoundaries: function() {
+                assertRangeValid(this);
+
+                var sc = this.startContainer, so = this.startOffset, ec = this.endContainer, eo = this.endOffset;
+
+                var mergeForward = function(node) {
+                    var sibling = node.nextSibling;
+                    if (sibling && sibling.nodeType == node.nodeType) {
+                        ec = node;
+                        eo = node.length;
+                        node.appendData(sibling.data);
+                        sibling.parentNode.removeChild(sibling);
+                    }
+                };
+
+                var mergeBackward = function(node) {
+                    var sibling = node.previousSibling;
+                    if (sibling && sibling.nodeType == node.nodeType) {
+                        sc = node;
+                        var nodeLength = node.length;
+                        so = sibling.length;
+                        node.insertData(0, sibling.data);
+                        sibling.parentNode.removeChild(sibling);
+                        if (sc == ec) {
+                            eo += so;
+                            ec = sc;
+                        } else if (ec == node.parentNode) {
+                            var nodeIndex = dom.getNodeIndex(node);
+                            if (eo == nodeIndex) {
+                                ec = node;
+                                eo = nodeLength;
+                            } else if (eo > nodeIndex) {
+                                eo--;
+                            }
+                        }
+                    }
+                };
+
+                var normalizeStart = true;
+
+                if (dom.isCharacterDataNode(ec)) {
+                    if (ec.length == eo) {
+                        mergeForward(ec);
+                    }
+                } else {
+                    if (eo > 0) {
+                        var endNode = ec.childNodes[eo - 1];
+                        if (endNode && dom.isCharacterDataNode(endNode)) {
+                            mergeForward(endNode);
+                        }
+                    }
+                    normalizeStart = !this.collapsed;
+                }
+
+                if (normalizeStart) {
+                    if (dom.isCharacterDataNode(sc)) {
+                        if (so == 0) {
+                            mergeBackward(sc);
+                        }
+                    } else {
+                        if (so < sc.childNodes.length) {
+                            var startNode = sc.childNodes[so];
+                            if (startNode && dom.isCharacterDataNode(startNode)) {
+                                mergeBackward(startNode);
+                            }
+                        }
+                    }
+                } else {
+                    sc = ec;
+                    so = eo;
+                }
+
+                boundaryUpdater(this, sc, so, ec, eo);
+            },
+
+            collapseToPoint: function(node, offset) {
+                assertNotDetached(this);
+
+                assertNoDocTypeNotationEntityAncestor(node, true);
+                assertValidOffset(node, offset);
+
+                setRangeStartAndEnd(this, node, offset);
+            }
+        });
+
+        copyComparisonConstants(constructor);
+    }
+
+    /*----------------------------------------------------------------------------------------------------------------*/
+
+    // Updates commonAncestorContainer and collapsed after boundary change
+    function updateCollapsedAndCommonAncestor(range) {
+        range.collapsed = (range.startContainer === range.endContainer && range.startOffset === range.endOffset);
+        range.commonAncestorContainer = range.collapsed ?
+            range.startContainer : dom.getCommonAncestor(range.startContainer, range.endContainer);
+    }
+
+    function updateBoundaries(range, startContainer, startOffset, endContainer, endOffset) {
+        var startMoved = (range.startContainer !== startContainer || range.startOffset !== startOffset);
+        var endMoved = (range.endContainer !== endContainer || range.endOffset !== endOffset);
+
+        range.startContainer = startContainer;
+        range.startOffset = startOffset;
+        range.endContainer = endContainer;
+        range.endOffset = endOffset;
+
+        updateCollapsedAndCommonAncestor(range);
+        dispatchEvent(range, "boundarychange", {startMoved: startMoved, endMoved: endMoved});
+    }
+
+    function detach(range) {
+        assertNotDetached(range);
+        range.startContainer = range.startOffset = range.endContainer = range.endOffset = null;
+        range.collapsed = range.commonAncestorContainer = null;
+        dispatchEvent(range, "detach", null);
+        range._listeners = null;
+    }
+
+    /**
+     * @constructor
+     */
+    function Range(doc) {
+        this.startContainer = doc;
+        this.startOffset = 0;
+        this.endContainer = doc;
+        this.endOffset = 0;
+        this._listeners = {
+            boundarychange: [],
+            detach: []
+        };
+        updateCollapsedAndCommonAncestor(this);
+    }
+
+    createPrototypeRange(Range, updateBoundaries, detach);
+
+    api.rangePrototype = RangePrototype.prototype;
+
+    Range.rangeProperties = rangeProperties;
+    Range.RangeIterator = RangeIterator;
+    Range.copyComparisonConstants = copyComparisonConstants;
+    Range.createPrototypeRange = createPrototypeRange;
+    Range.inspect = inspect;
+    Range.getRangeDocument = getRangeDocument;
+    Range.rangesEqual = function(r1, r2) {
+        return r1.startContainer === r2.startContainer &&
+               r1.startOffset === r2.startOffset &&
+               r1.endContainer === r2.endContainer &&
+               r1.endOffset === r2.endOffset;
+    };
+
+    api.DomRange = Range;
+    api.RangeException = RangeException;
+});rangy.createModule("WrappedRange", function(api, module) {\r
+    api.requireModules( ["DomUtil", "DomRange"] );\r
+\r
+    /**\r
+     * @constructor\r
+     */\r
+    var WrappedRange;\r
+    var dom = api.dom;\r
+    var DomPosition = dom.DomPosition;\r
+    var DomRange = api.DomRange;\r
+\r
+\r
+\r
+    /*----------------------------------------------------------------------------------------------------------------*/\r
+\r
+    /*\r
+    This is a workaround for a bug where IE returns the wrong container element from the TextRange's parentElement()\r
+    method. For example, in the following (where pipes denote the selection boundaries):\r
+\r
+    <ul id="ul"><li id="a">| a </li><li id="b"> b |</li></ul>\r
+\r
+    var range = document.selection.createRange();\r
+    alert(range.parentElement().id); // Should alert "ul" but alerts "b"\r
+\r
+    This method returns the common ancestor node of the following:\r
+    - the parentElement() of the textRange\r
+    - the parentElement() of the textRange after calling collapse(true)\r
+    - the parentElement() of the textRange after calling collapse(false)\r
+     */\r
+    function getTextRangeContainerElement(textRange) {\r
+        var parentEl = textRange.parentElement();\r
+\r
+        var range = textRange.duplicate();\r
+        range.collapse(true);\r
+        var startEl = range.parentElement();\r
+        range = textRange.duplicate();\r
+        range.collapse(false);\r
+        var endEl = range.parentElement();\r
+        var startEndContainer = (startEl == endEl) ? startEl : dom.getCommonAncestor(startEl, endEl);\r
+\r
+        return startEndContainer == parentEl ? startEndContainer : dom.getCommonAncestor(parentEl, startEndContainer);\r
+    }\r
+\r
+    function textRangeIsCollapsed(textRange) {\r
+        return textRange.compareEndPoints("StartToEnd", textRange) == 0;\r
+    }\r
+\r
+    // Gets the boundary of a TextRange expressed as a node and an offset within that node. This function started out as\r
+    // an improved version of code found in Tim Cameron Ryan's IERange (http://code.google.com/p/ierange/) but has\r
+    // grown, fixing problems with line breaks in preformatted text, adding workaround for IE TextRange bugs, handling\r
+    // for inputs and images, plus optimizations.\r
+    function getTextRangeBoundaryPosition(textRange, wholeRangeContainerElement, isStart, isCollapsed) {\r
+        var workingRange = textRange.duplicate();\r
+\r
+        workingRange.collapse(isStart);\r
+        var containerElement = workingRange.parentElement();\r
+\r
+        // Sometimes collapsing a TextRange that's at the start of a text node can move it into the previous node, so\r
+        // check for that\r
+        // TODO: Find out when. Workaround for wholeRangeContainerElement may break this\r
+        if (!dom.isAncestorOf(wholeRangeContainerElement, containerElement, true)) {\r
+            containerElement = wholeRangeContainerElement;\r
+\r
+        }\r
+\r
+\r
+\r
+        // Deal with nodes that cannot "contain rich HTML markup". In practice, this means form inputs, images and\r
+        // similar. See http://msdn.microsoft.com/en-us/library/aa703950%28VS.85%29.aspx\r
+        if (!containerElement.canHaveHTML) {\r
+            return new DomPosition(containerElement.parentNode, dom.getNodeIndex(containerElement));\r
+        }\r
+\r
+        var workingNode = dom.getDocument(containerElement).createElement("span");\r
+        var comparison, workingComparisonType = isStart ? "StartToStart" : "StartToEnd";\r
+        var previousNode, nextNode, boundaryPosition, boundaryNode;\r
+\r
+        // Move the working range through the container's children, starting at the end and working backwards, until the\r
+        // working range reaches or goes past the boundary we're interested in\r
+        do {\r
+            containerElement.insertBefore(workingNode, workingNode.previousSibling);\r
+            workingRange.moveToElementText(workingNode);\r
+        } while ( (comparison = workingRange.compareEndPoints(workingComparisonType, textRange)) > 0 &&\r
+                workingNode.previousSibling);\r
+\r
+        // We've now reached or gone past the boundary of the text range we're interested in\r
+        // so have identified the node we want\r
+        boundaryNode = workingNode.nextSibling;\r
+\r
+        if (comparison == -1 && boundaryNode && dom.isCharacterDataNode(boundaryNode)) {\r
+            // This is a character data node (text, comment, cdata). The working range is collapsed at the start of the\r
+            // node containing the text range's boundary, so we move the end of the working range to the boundary point\r
+            // and measure the length of its text to get the boundary's offset within the node.\r
+            workingRange.setEndPoint(isStart ? "EndToStart" : "EndToEnd", textRange);\r
+\r
+\r
+            var offset;\r
+\r
+            if (/[\r\n]/.test(boundaryNode.data)) {\r
+                /*\r
+                For the particular case of a boundary within a text node containing line breaks (within a <pre> element,\r
+                for example), we need a slightly complicated approach to get the boundary's offset in IE. The facts:\r
+\r
+                - Each line break is represented as \r in the text node's data/nodeValue properties\r
+                - Each line break is represented as \r\n in the TextRange's 'text' property\r
+                - The 'text' property of the TextRange does not contain trailing line breaks\r
+\r
+                To get round the problem presented by the final fact above, we can use the fact that TextRange's\r
+                moveStart() and moveEnd() methods return the actual number of characters moved, which is not necessarily\r
+                the same as the number of characters it was instructed to move. The simplest approach is to use this to\r
+                store the characters moved when moving both the start and end of the range to the start of the document\r
+                body and subtracting the start offset from the end offset (the "move-negative-gazillion" method).\r
+                However, this is extremely slow when the document is large and the range is near the end of it. Clearly\r
+                doing the mirror image (i.e. moving the range boundaries to the end of the document) has the same\r
+                problem.\r
+\r
+                Another approach that works is to use moveStart() to move the start boundary of the range up to the end\r
+                boundary one character at a time and incrementing a counter with the value returned by the moveStart()\r
+                call. However, the check for whether the start boundary has reached the end boundary is expensive, so\r
+                this method is slow (although unlike "move-negative-gazillion" is largely unaffected by the location of\r
+                the range within the document).\r
+\r
+                The method below is a hybrid of the two methods above. It uses the fact that a string containing the\r
+                TextRange's 'text' property with each \r\n converted to a single \r character cannot be longer than the\r
+                text of the TextRange, so the start of the range is moved that length initially and then a character at\r
+                a time to make up for any trailing line breaks not contained in the 'text' property. This has good\r
+                performance in most situations compared to the previous two methods.\r
+                */\r
+                var tempRange = workingRange.duplicate();\r
+                var rangeLength = tempRange.text.replace(/\r\n/g, "\r").length;\r
+\r
+                offset = tempRange.moveStart("character", rangeLength);\r
+                while ( (comparison = tempRange.compareEndPoints("StartToEnd", tempRange)) == -1) {\r
+                    offset++;\r
+                    tempRange.moveStart("character", 1);\r
+                }\r
+            } else {\r
+                offset = workingRange.text.length;\r
+            }\r
+            boundaryPosition = new DomPosition(boundaryNode, offset);\r
+        } else {\r
+\r
+\r
+            // If the boundary immediately follows a character data node and this is the end boundary, we should favour\r
+            // a position within that, and likewise for a start boundary preceding a character data node\r
+            previousNode = (isCollapsed || !isStart) && workingNode.previousSibling;\r
+            nextNode = (isCollapsed || isStart) && workingNode.nextSibling;\r
+\r
+\r
+\r
+            if (nextNode && dom.isCharacterDataNode(nextNode)) {\r
+                boundaryPosition = new DomPosition(nextNode, 0);\r
+            } else if (previousNode && dom.isCharacterDataNode(previousNode)) {\r
+                boundaryPosition = new DomPosition(previousNode, previousNode.length);\r
+            } else {\r
+                boundaryPosition = new DomPosition(containerElement, dom.getNodeIndex(workingNode));\r
+            }\r
+        }\r
+\r
+        // Clean up\r
+        workingNode.parentNode.removeChild(workingNode);\r
+\r
+        return boundaryPosition;\r
+    }\r
+\r
+    // Returns a TextRange representing the boundary of a TextRange expressed as a node and an offset within that node.\r
+    // This function started out as an optimized version of code found in Tim Cameron Ryan's IERange\r
+    // (http://code.google.com/p/ierange/)\r
+    function createBoundaryTextRange(boundaryPosition, isStart) {\r
+        var boundaryNode, boundaryParent, boundaryOffset = boundaryPosition.offset;\r
+        var doc = dom.getDocument(boundaryPosition.node);\r
+        var workingNode, childNodes, workingRange = doc.body.createTextRange();\r
+        var nodeIsDataNode = dom.isCharacterDataNode(boundaryPosition.node);\r
+\r
+        if (nodeIsDataNode) {\r
+            boundaryNode = boundaryPosition.node;\r
+            boundaryParent = boundaryNode.parentNode;\r
+        } else {\r
+            childNodes = boundaryPosition.node.childNodes;\r
+            boundaryNode = (boundaryOffset < childNodes.length) ? childNodes[boundaryOffset] : null;\r
+            boundaryParent = boundaryPosition.node;\r
+        }\r
+\r
+        // Position the range immediately before the node containing the boundary\r
+        workingNode = doc.createElement("span");\r
+\r
+        // Making the working element non-empty element persuades IE to consider the TextRange boundary to be within the\r
+        // element rather than immediately before or after it, which is what we want\r
+        workingNode.innerHTML = "&#feff;";\r
+\r
+        // insertBefore is supposed to work like appendChild if the second parameter is null. However, a bug report\r
+        // for IERange suggests that it can crash the browser: http://code.google.com/p/ierange/issues/detail?id=12\r
+        if (boundaryNode) {\r
+            boundaryParent.insertBefore(workingNode, boundaryNode);\r
+        } else {\r
+            boundaryParent.appendChild(workingNode);\r
+        }\r
+\r
+        workingRange.moveToElementText(workingNode);\r
+        workingRange.collapse(!isStart);\r
+\r
+        // Clean up\r
+        boundaryParent.removeChild(workingNode);\r
+\r
+        // Move the working range to the text offset, if required\r
+        if (nodeIsDataNode) {\r
+            workingRange[isStart ? "moveStart" : "moveEnd"]("character", boundaryOffset);\r
+        }\r
+\r
+        return workingRange;\r
+    }\r
+\r
+    /*----------------------------------------------------------------------------------------------------------------*/\r
+\r
+    if (api.features.implementsDomRange && (!api.features.implementsTextRange || !api.config.preferTextRange)) {\r
+        // This is a wrapper around the browser's native DOM Range. It has two aims:\r
+        // - Provide workarounds for specific browser bugs\r
+        // - provide convenient extensions, which are inherited from Rangy's DomRange\r
+\r
+        (function() {\r
+            var rangeProto;\r
+            var rangeProperties = DomRange.rangeProperties;\r
+            var canSetRangeStartAfterEnd;\r
+\r
+            function updateRangeProperties(range) {\r
+                var i = rangeProperties.length, prop;\r
+                while (i--) {\r
+                    prop = rangeProperties[i];\r
+                    range[prop] = range.nativeRange[prop];\r
+                }\r
+            }\r
+\r
+            function updateNativeRange(range, startContainer, startOffset, endContainer,endOffset) {\r
+                var startMoved = (range.startContainer !== startContainer || range.startOffset != startOffset);\r
+                var endMoved = (range.endContainer !== endContainer || range.endOffset != endOffset);\r
+\r
+                // Always set both boundaries for the benefit of IE9 (see issue 35)\r
+                if (startMoved || endMoved) {\r
+                    range.setEnd(endContainer, endOffset);\r
+                    range.setStart(startContainer, startOffset);\r
+                }\r
+            }\r
+\r
+            function detach(range) {\r
+                range.nativeRange.detach();\r
+                range.detached = true;\r
+                var i = rangeProperties.length, prop;\r
+                while (i--) {\r
+                    prop = rangeProperties[i];\r
+                    range[prop] = null;\r
+                }\r
+            }\r
+\r
+            var createBeforeAfterNodeSetter;\r
+\r
+            WrappedRange = function(range) {\r
+                if (!range) {\r
+                    throw new Error("Range must be specified");\r
+                }\r
+                this.nativeRange = range;\r
+                updateRangeProperties(this);\r
+            };\r
+\r
+            DomRange.createPrototypeRange(WrappedRange, updateNativeRange, detach);\r
+\r
+            rangeProto = WrappedRange.prototype;\r
+\r
+            rangeProto.selectNode = function(node) {\r
+                this.nativeRange.selectNode(node);\r
+                updateRangeProperties(this);\r
+            };\r
+\r
+            rangeProto.deleteContents = function() {\r
+                this.nativeRange.deleteContents();\r
+                updateRangeProperties(this);\r
+            };\r
+\r
+            rangeProto.extractContents = function() {\r
+                var frag = this.nativeRange.extractContents();\r
+                updateRangeProperties(this);\r
+                return frag;\r
+            };\r
+\r
+            rangeProto.cloneContents = function() {\r
+                return this.nativeRange.cloneContents();\r
+            };\r
+\r
+            // TODO: Until I can find a way to programmatically trigger the Firefox bug (apparently long-standing, still\r
+            // present in 3.6.8) that throws "Index or size is negative or greater than the allowed amount" for\r
+            // insertNode in some circumstances, all browsers will have to use the Rangy's own implementation of\r
+            // insertNode, which works but is almost certainly slower than the native implementation.\r
+/*\r
+            rangeProto.insertNode = function(node) {\r
+                this.nativeRange.insertNode(node);\r
+                updateRangeProperties(this);\r
+            };\r
+*/\r
+\r
+            rangeProto.surroundContents = function(node) {\r
+                this.nativeRange.surroundContents(node);\r
+                updateRangeProperties(this);\r
+            };\r
+\r
+            rangeProto.collapse = function(isStart) {\r
+                this.nativeRange.collapse(isStart);\r
+                updateRangeProperties(this);\r
+            };\r
+\r
+            rangeProto.cloneRange = function() {\r
+                return new WrappedRange(this.nativeRange.cloneRange());\r
+            };\r
+\r
+            rangeProto.refresh = function() {\r
+                updateRangeProperties(this);\r
+            };\r
+\r
+            rangeProto.toString = function() {\r
+                return this.nativeRange.toString();\r
+            };\r
+\r
+            // Create test range and node for feature detection\r
+\r
+            var testTextNode = document.createTextNode("test");\r
+            dom.getBody(document).appendChild(testTextNode);\r
+            var range = document.createRange();\r
+\r
+            /*--------------------------------------------------------------------------------------------------------*/\r
+\r
+            // Test for Firefox 2 bug that prevents moving the start of a Range to a point after its current end and\r
+            // correct for it\r
+\r
+            range.setStart(testTextNode, 0);\r
+            range.setEnd(testTextNode, 0);\r
+\r
+            try {\r
+                range.setStart(testTextNode, 1);\r
+                canSetRangeStartAfterEnd = true;\r
+\r
+                rangeProto.setStart = function(node, offset) {\r
+                    this.nativeRange.setStart(node, offset);\r
+                    updateRangeProperties(this);\r
+                };\r
+\r
+                rangeProto.setEnd = function(node, offset) {\r
+                    this.nativeRange.setEnd(node, offset);\r
+                    updateRangeProperties(this);\r
+                };\r
+\r
+                createBeforeAfterNodeSetter = function(name) {\r
+                    return function(node) {\r
+                        this.nativeRange[name](node);\r
+                        updateRangeProperties(this);\r
+                    };\r
+                };\r
+\r
+            } catch(ex) {\r
+\r
+\r
+                canSetRangeStartAfterEnd = false;\r
+\r
+                rangeProto.setStart = function(node, offset) {\r
+                    try {\r
+                        this.nativeRange.setStart(node, offset);\r
+                    } catch (ex) {\r
+                        this.nativeRange.setEnd(node, offset);\r
+                        this.nativeRange.setStart(node, offset);\r
+                    }\r
+                    updateRangeProperties(this);\r
+                };\r
+\r
+                rangeProto.setEnd = function(node, offset) {\r
+                    try {\r
+                        this.nativeRange.setEnd(node, offset);\r
+                    } catch (ex) {\r
+                        this.nativeRange.setStart(node, offset);\r
+                        this.nativeRange.setEnd(node, offset);\r
+                    }\r
+                    updateRangeProperties(this);\r
+                };\r
+\r
+                createBeforeAfterNodeSetter = function(name, oppositeName) {\r
+                    return function(node) {\r
+                        try {\r
+                            this.nativeRange[name](node);\r
+                        } catch (ex) {\r
+                            this.nativeRange[oppositeName](node);\r
+                            this.nativeRange[name](node);\r
+                        }\r
+                        updateRangeProperties(this);\r
+                    };\r
+                };\r
+            }\r
+\r
+            rangeProto.setStartBefore = createBeforeAfterNodeSetter("setStartBefore", "setEndBefore");\r
+            rangeProto.setStartAfter = createBeforeAfterNodeSetter("setStartAfter", "setEndAfter");\r
+            rangeProto.setEndBefore = createBeforeAfterNodeSetter("setEndBefore", "setStartBefore");\r
+            rangeProto.setEndAfter = createBeforeAfterNodeSetter("setEndAfter", "setStartAfter");\r
+\r
+            /*--------------------------------------------------------------------------------------------------------*/\r
+\r
+            // Test for and correct Firefox 2 behaviour with selectNodeContents on text nodes: it collapses the range to\r
+            // the 0th character of the text node\r
+            range.selectNodeContents(testTextNode);\r
+            if (range.startContainer == testTextNode && range.endContainer == testTextNode &&\r
+                    range.startOffset == 0 && range.endOffset == testTextNode.length) {\r
+                rangeProto.selectNodeContents = function(node) {\r
+                    this.nativeRange.selectNodeContents(node);\r
+                    updateRangeProperties(this);\r
+                };\r
+            } else {\r
+                rangeProto.selectNodeContents = function(node) {\r
+                    this.setStart(node, 0);\r
+                    this.setEnd(node, DomRange.getEndOffset(node));\r
+                };\r
+            }\r
+\r
+            /*--------------------------------------------------------------------------------------------------------*/\r
+\r
+            // Test for WebKit bug that has the beahviour of compareBoundaryPoints round the wrong way for constants\r
+            // START_TO_END and END_TO_START: https://bugs.webkit.org/show_bug.cgi?id=20738\r
+\r
+            range.selectNodeContents(testTextNode);\r
+            range.setEnd(testTextNode, 3);\r
+\r
+            var range2 = document.createRange();\r
+            range2.selectNodeContents(testTextNode);\r
+            range2.setEnd(testTextNode, 4);\r
+            range2.setStart(testTextNode, 2);\r
+\r
+            if (range.compareBoundaryPoints(range.START_TO_END, range2) == -1 &\r
+                    range.compareBoundaryPoints(range.END_TO_START, range2) == 1) {\r
+                // This is the wrong way round, so correct for it\r
+\r
+\r
+                rangeProto.compareBoundaryPoints = function(type, range) {\r
+                    range = range.nativeRange || range;\r
+                    if (type == range.START_TO_END) {\r
+                        type = range.END_TO_START;\r
+                    } else if (type == range.END_TO_START) {\r
+                        type = range.START_TO_END;\r
+                    }\r
+                    return this.nativeRange.compareBoundaryPoints(type, range);\r
+                };\r
+            } else {\r
+                rangeProto.compareBoundaryPoints = function(type, range) {\r
+                    return this.nativeRange.compareBoundaryPoints(type, range.nativeRange || range);\r
+                };\r
+            }\r
+\r
+            /*--------------------------------------------------------------------------------------------------------*/\r
+\r
+            // Test for existence of createContextualFragment and delegate to it if it exists\r
+            if (api.util.isHostMethod(range, "createContextualFragment")) {\r
+                rangeProto.createContextualFragment = function(fragmentStr) {\r
+                    return this.nativeRange.createContextualFragment(fragmentStr);\r
+                };\r
+            }\r
+\r
+            /*--------------------------------------------------------------------------------------------------------*/\r
+\r
+            // Clean up\r
+            dom.getBody(document).removeChild(testTextNode);\r
+            range.detach();\r
+            range2.detach();\r
+        })();\r
+\r
+        api.createNativeRange = function(doc) {\r
+            doc = doc || document;\r
+            return doc.createRange();\r
+        };\r
+    } else if (api.features.implementsTextRange) {\r
+        // This is a wrapper around a TextRange, providing full DOM Range functionality using rangy's DomRange as a\r
+        // prototype\r
+\r
+        WrappedRange = function(textRange) {\r
+            this.textRange = textRange;\r
+            this.refresh();\r
+        };\r
+\r
+        WrappedRange.prototype = new DomRange(document);\r
+\r
+        WrappedRange.prototype.refresh = function() {\r
+            var start, end;\r
+\r
+            // TextRange's parentElement() method cannot be trusted. getTextRangeContainerElement() works around that.\r
+            var rangeContainerElement = getTextRangeContainerElement(this.textRange);\r
+\r
+            if (textRangeIsCollapsed(this.textRange)) {\r
+                end = start = getTextRangeBoundaryPosition(this.textRange, rangeContainerElement, true, true);\r
+            } else {\r
+\r
+                start = getTextRangeBoundaryPosition(this.textRange, rangeContainerElement, true, false);\r
+                end = getTextRangeBoundaryPosition(this.textRange, rangeContainerElement, false, false);\r
+            }\r
+\r
+            this.setStart(start.node, start.offset);\r
+            this.setEnd(end.node, end.offset);\r
+        };\r
+\r
+        DomRange.copyComparisonConstants(WrappedRange);\r
+\r
+        // Add WrappedRange as the Range property of the global object to allow expression like Range.END_TO_END to work\r
+        var globalObj = (function() { return this; })();\r
+        if (typeof globalObj.Range == "undefined") {\r
+            globalObj.Range = WrappedRange;\r
+        }\r
+\r
+        api.createNativeRange = function(doc) {\r
+            doc = doc || document;\r
+            return doc.body.createTextRange();\r
+        };\r
+    }\r
+\r
+    if (api.features.implementsTextRange) {\r
+        WrappedRange.rangeToTextRange = function(range) {\r
+            if (range.collapsed) {\r
+                var tr = createBoundaryTextRange(new DomPosition(range.startContainer, range.startOffset), true);\r
+\r
+\r
+\r
+                return tr;\r
+\r
+                //return createBoundaryTextRange(new DomPosition(range.startContainer, range.startOffset), true);\r
+            } else {\r
+                var startRange = createBoundaryTextRange(new DomPosition(range.startContainer, range.startOffset), true);\r
+                var endRange = createBoundaryTextRange(new DomPosition(range.endContainer, range.endOffset), false);\r
+                var textRange = dom.getDocument(range.startContainer).body.createTextRange();\r
+                textRange.setEndPoint("StartToStart", startRange);\r
+                textRange.setEndPoint("EndToEnd", endRange);\r
+                return textRange;\r
+            }\r
+        };\r
+    }\r
+\r
+    WrappedRange.prototype.getName = function() {\r
+        return "WrappedRange";\r
+    };\r
+\r
+    api.WrappedRange = WrappedRange;\r
+\r
+    api.createRange = function(doc) {\r
+        doc = doc || document;\r
+        return new WrappedRange(api.createNativeRange(doc));\r
+    };\r
+\r
+    api.createRangyRange = function(doc) {\r
+        doc = doc || document;\r
+        return new DomRange(doc);\r
+    };\r
+\r
+    api.createIframeRange = function(iframeEl) {\r
+        return api.createRange(dom.getIframeDocument(iframeEl));\r
+    };\r
+\r
+    api.createIframeRangyRange = function(iframeEl) {\r
+        return api.createRangyRange(dom.getIframeDocument(iframeEl));\r
+    };\r
+\r
+    api.addCreateMissingNativeApiListener(function(win) {\r
+        var doc = win.document;\r
+        if (typeof doc.createRange == "undefined") {\r
+            doc.createRange = function() {\r
+                return api.createRange(this);\r
+            };\r
+        }\r
+        doc = win = null;\r
+    });\r
+});rangy.createModule("WrappedSelection", function(api, module) {\r
+    // This will create a selection object wrapper that follows the Selection object found in the WHATWG draft DOM Range\r
+    // spec (http://html5.org/specs/dom-range.html)\r
+\r
+    api.requireModules( ["DomUtil", "DomRange", "WrappedRange"] );\r
+\r
+    api.config.checkSelectionRanges = true;\r
+\r
+    var BOOLEAN = "boolean",\r
+        windowPropertyName = "_rangySelection",\r
+        dom = api.dom,\r
+        util = api.util,\r
+        DomRange = api.DomRange,\r
+        WrappedRange = api.WrappedRange,\r
+        DOMException = api.DOMException,\r
+        DomPosition = dom.DomPosition,\r
+        getSelection,\r
+        selectionIsCollapsed,\r
+        CONTROL = "Control";\r
+\r
+\r
+\r
+    function getWinSelection(winParam) {\r
+        return (winParam || window).getSelection();\r
+    }\r
+\r
+    function getDocSelection(winParam) {\r
+        return (winParam || window).document.selection;\r
+    }\r
+\r
+    // Test for the Range/TextRange and Selection features required\r
+    // Test for ability to retrieve selection\r
+    var implementsWinGetSelection = api.util.isHostMethod(window, "getSelection"),\r
+        implementsDocSelection = api.util.isHostObject(document, "selection");\r
+\r
+    var useDocumentSelection = implementsDocSelection && (!implementsWinGetSelection || api.config.preferTextRange);\r
+\r
+    if (useDocumentSelection) {\r
+        getSelection = getDocSelection;\r
+        api.isSelectionValid = function(winParam) {\r
+            var doc = (winParam || window).document, nativeSel = doc.selection;\r
+\r
+            // Check whether the selection TextRange is actually contained within the correct document\r
+            return (nativeSel.type != "None" || dom.getDocument(nativeSel.createRange().parentElement()) == doc);\r
+        };\r
+    } else if (implementsWinGetSelection) {\r
+        getSelection = getWinSelection;\r
+        api.isSelectionValid = function() {\r
+            return true;\r
+        };\r
+    } else {\r
+        module.fail("Neither document.selection or window.getSelection() detected.");\r
+    }\r
+\r
+    api.getNativeSelection = getSelection;\r
+\r
+    var testSelection = getSelection();\r
+    var testRange = api.createNativeRange(document);\r
+    var body = dom.getBody(document);\r
+\r
+    // Obtaining a range from a selection\r
+    var selectionHasAnchorAndFocus = util.areHostObjects(testSelection, ["anchorNode", "focusNode"] &&\r
+                                     util.areHostProperties(testSelection, ["anchorOffset", "focusOffset"]));\r
+    api.features.selectionHasAnchorAndFocus = selectionHasAnchorAndFocus;\r
+\r
+    // Test for existence of native selection extend() method\r
+    var selectionHasExtend = util.isHostMethod(testSelection, "extend");\r
+    api.features.selectionHasExtend = selectionHasExtend;\r
+\r
+    // Test if rangeCount exists\r
+    var selectionHasRangeCount = (typeof testSelection.rangeCount == "number");\r
+    api.features.selectionHasRangeCount = selectionHasRangeCount;\r
+\r
+    var selectionSupportsMultipleRanges = false;\r
+    var collapsedNonEditableSelectionsSupported = true;\r
+\r
+    if (util.areHostMethods(testSelection, ["addRange", "getRangeAt", "removeAllRanges"]) &&\r
+            typeof testSelection.rangeCount == "number" && api.features.implementsDomRange) {\r
+\r
+        (function() {\r
+            var iframe = document.createElement("iframe");\r
+            iframe.frameBorder = 0;\r
+            iframe.style.position = "absolute";\r
+            iframe.style.left = "-10000px";\r
+            body.appendChild(iframe);\r
+\r
+            var iframeDoc = dom.getIframeDocument(iframe);\r
+            iframeDoc.open();\r
+            iframeDoc.write("<html><head></head><body>12</body></html>");\r
+            iframeDoc.close();\r
+\r
+            var sel = dom.getIframeWindow(iframe).getSelection();\r
+            var docEl = iframeDoc.documentElement;\r
+            var iframeBody = docEl.lastChild, textNode = iframeBody.firstChild;\r
+\r
+            // Test whether the native selection will allow a collapsed selection within a non-editable element\r
+            var r1 = iframeDoc.createRange();\r
+            r1.setStart(textNode, 1);\r
+            r1.collapse(true);\r
+            sel.addRange(r1);\r
+            collapsedNonEditableSelectionsSupported = (sel.rangeCount == 1);\r
+            sel.removeAllRanges();\r
+\r
+            // Test whether the native selection is capable of supporting multiple ranges\r
+            var r2 = r1.cloneRange();\r
+            r1.setStart(textNode, 0);\r
+            r2.setEnd(textNode, 2);\r
+            sel.addRange(r1);\r
+            sel.addRange(r2);\r
+\r
+            selectionSupportsMultipleRanges = (sel.rangeCount == 2);\r
+\r
+            // Clean up\r
+            r1.detach();\r
+            r2.detach();\r
+\r
+            body.removeChild(iframe);\r
+        })();\r
+    }\r
+\r
+    api.features.selectionSupportsMultipleRanges = selectionSupportsMultipleRanges;\r
+    api.features.collapsedNonEditableSelectionsSupported = collapsedNonEditableSelectionsSupported;\r
+\r
+    // ControlRanges\r
+    var implementsControlRange = false, testControlRange;\r
+\r
+    if (body && util.isHostMethod(body, "createControlRange")) {\r
+        testControlRange = body.createControlRange();\r
+        if (util.areHostProperties(testControlRange, ["item", "add"])) {\r
+            implementsControlRange = true;\r
+        }\r
+    }\r
+    api.features.implementsControlRange = implementsControlRange;\r
+\r
+    // Selection collapsedness\r
+    if (selectionHasAnchorAndFocus) {\r
+        selectionIsCollapsed = function(sel) {\r
+            return sel.anchorNode === sel.focusNode && sel.anchorOffset === sel.focusOffset;\r
+        };\r
+    } else {\r
+        selectionIsCollapsed = function(sel) {\r
+            return sel.rangeCount ? sel.getRangeAt(sel.rangeCount - 1).collapsed : false;\r
+        };\r
+    }\r
+\r
+    function updateAnchorAndFocusFromRange(sel, range, backwards) {\r
+        var anchorPrefix = backwards ? "end" : "start", focusPrefix = backwards ? "start" : "end";\r
+        sel.anchorNode = range[anchorPrefix + "Container"];\r
+        sel.anchorOffset = range[anchorPrefix + "Offset"];\r
+        sel.focusNode = range[focusPrefix + "Container"];\r
+        sel.focusOffset = range[focusPrefix + "Offset"];\r
+    }\r
+\r
+    function updateAnchorAndFocusFromNativeSelection(sel) {\r
+        var nativeSel = sel.nativeSelection;\r
+        sel.anchorNode = nativeSel.anchorNode;\r
+        sel.anchorOffset = nativeSel.anchorOffset;\r
+        sel.focusNode = nativeSel.focusNode;\r
+        sel.focusOffset = nativeSel.focusOffset;\r
+    }\r
+\r
+    function updateEmptySelection(sel) {\r
+        sel.anchorNode = sel.focusNode = null;\r
+        sel.anchorOffset = sel.focusOffset = 0;\r
+        sel.rangeCount = 0;\r
+        sel.isCollapsed = true;\r
+        sel._ranges.length = 0;\r
+    }\r
+\r
+    function getNativeRange(range) {\r
+        var nativeRange;\r
+        if (range instanceof DomRange) {\r
+            nativeRange = range._selectionNativeRange;\r
+            if (!nativeRange) {\r
+                nativeRange = api.createNativeRange(dom.getDocument(range.startContainer));\r
+                nativeRange.setEnd(range.endContainer, range.endOffset);\r
+                nativeRange.setStart(range.startContainer, range.startOffset);\r
+                range._selectionNativeRange = nativeRange;\r
+                range.attachListener("detach", function() {\r
+\r
+                    this._selectionNativeRange = null;\r
+                });\r
+            }\r
+        } else if (range instanceof WrappedRange) {\r
+            nativeRange = range.nativeRange;\r
+        } else if (api.features.implementsDomRange && (range instanceof dom.getWindow(range.startContainer).Range)) {\r
+            nativeRange = range;\r
+        }\r
+        return nativeRange;\r
+    }\r
+\r
+    function rangeContainsSingleElement(rangeNodes) {\r
+        if (!rangeNodes.length || rangeNodes[0].nodeType != 1) {\r
+            return false;\r
+        }\r
+        for (var i = 1, len = rangeNodes.length; i < len; ++i) {\r
+            if (!dom.isAncestorOf(rangeNodes[0], rangeNodes[i])) {\r
+                return false;\r
+            }\r
+        }\r
+        return true;\r
+    }\r
+\r
+    function getSingleElementFromRange(range) {\r
+        var nodes = range.getNodes();\r
+        if (!rangeContainsSingleElement(nodes)) {\r
+            throw new Error("getSingleElementFromRange: range " + range.inspect() + " did not consist of a single element");\r
+        }\r
+        return nodes[0];\r
+    }\r
+\r
+    function isTextRange(range) {\r
+        return !!range && typeof range.text != "undefined";\r
+    }\r
+\r
+    function updateFromTextRange(sel, range) {\r
+        // Create a Range from the selected TextRange\r
+        var wrappedRange = new WrappedRange(range);\r
+        sel._ranges = [wrappedRange];\r
+\r
+        updateAnchorAndFocusFromRange(sel, wrappedRange, false);\r
+        sel.rangeCount = 1;\r
+        sel.isCollapsed = wrappedRange.collapsed;\r
+    }\r
+\r
+    function updateControlSelection(sel) {\r
+        // Update the wrapped selection based on what's now in the native selection\r
+        sel._ranges.length = 0;\r
+        if (sel.docSelection.type == "None") {\r
+            updateEmptySelection(sel);\r
+        } else {\r
+            var controlRange = sel.docSelection.createRange();\r
+            if (isTextRange(controlRange)) {\r
+                // This case (where the selection type is "Control" and calling createRange() on the selection returns\r
+                // a TextRange) can happen in IE 9. It happens, for example, when all elements in the selected\r
+                // ControlRange have been removed from the ControlRange and removed from the document.\r
+                updateFromTextRange(sel, controlRange);\r
+            } else {\r
+                sel.rangeCount = controlRange.length;\r
+                var range, doc = dom.getDocument(controlRange.item(0));\r
+                for (var i = 0; i < sel.rangeCount; ++i) {\r
+                    range = api.createRange(doc);\r
+                    range.selectNode(controlRange.item(i));\r
+                    sel._ranges.push(range);\r
+                }\r
+                sel.isCollapsed = sel.rangeCount == 1 && sel._ranges[0].collapsed;\r
+                updateAnchorAndFocusFromRange(sel, sel._ranges[sel.rangeCount - 1], false);\r
+            }\r
+        }\r
+    }\r
+\r
+    function addRangeToControlSelection(sel, range) {\r
+        var controlRange = sel.docSelection.createRange();\r
+        var rangeElement = getSingleElementFromRange(range);\r
+\r
+        // Create a new ControlRange containing all the elements in the selected ControlRange plus the element\r
+        // contained by the supplied range\r
+        var doc = dom.getDocument(controlRange.item(0));\r
+        var newControlRange = dom.getBody(doc).createControlRange();\r
+        for (var i = 0, len = controlRange.length; i < len; ++i) {\r
+            newControlRange.add(controlRange.item(i));\r
+        }\r
+        try {\r
+            newControlRange.add(rangeElement);\r
+        } catch (ex) {\r
+            throw new Error("addRange(): Element within the specified Range could not be added to control selection (does it have layout?)");\r
+        }\r
+        newControlRange.select();\r
+\r
+        // Update the wrapped selection based on what's now in the native selection\r
+        updateControlSelection(sel);\r
+    }\r
+\r
+    var getSelectionRangeAt;\r
+\r
+    if (util.isHostMethod(testSelection,  "getRangeAt")) {\r
+        getSelectionRangeAt = function(sel, index) {\r
+            try {\r
+                return sel.getRangeAt(index);\r
+            } catch(ex) {\r
+                return null;\r
+            }\r
+        };\r
+    } else if (selectionHasAnchorAndFocus) {\r
+        getSelectionRangeAt = function(sel) {\r
+            var doc = dom.getDocument(sel.anchorNode);\r
+            var range = api.createRange(doc);\r
+            range.setStart(sel.anchorNode, sel.anchorOffset);\r
+            range.setEnd(sel.focusNode, sel.focusOffset);\r
+\r
+            // Handle the case when the selection was selected backwards (from the end to the start in the\r
+            // document)\r
+            if (range.collapsed !== this.isCollapsed) {\r
+                range.setStart(sel.focusNode, sel.focusOffset);\r
+                range.setEnd(sel.anchorNode, sel.anchorOffset);\r
+            }\r
+\r
+            return range;\r
+        };\r
+    }\r
+\r
+    /**\r
+     * @constructor\r
+     */\r
+    function WrappedSelection(selection, docSelection, win) {\r
+        this.nativeSelection = selection;\r
+        this.docSelection = docSelection;\r
+        this._ranges = [];\r
+        this.win = win;\r
+        this.refresh();\r
+    }\r
+\r
+    api.getSelection = function(win) {\r
+        win = win || window;\r
+        var sel = win[windowPropertyName];\r
+        var nativeSel = getSelection(win), docSel = implementsDocSelection ? getDocSelection(win) : null;\r
+        if (sel) {\r
+            sel.nativeSelection = nativeSel;\r
+            sel.docSelection = docSel;\r
+            sel.refresh(win);\r
+        } else {\r
+            sel = new WrappedSelection(nativeSel, docSel, win);\r
+            win[windowPropertyName] = sel;\r
+        }\r
+        return sel;\r
+    };\r
+\r
+    api.getIframeSelection = function(iframeEl) {\r
+        return api.getSelection(dom.getIframeWindow(iframeEl));\r
+    };\r
+\r
+    var selProto = WrappedSelection.prototype;\r
+\r
+    function createControlSelection(sel, ranges) {\r
+        // Ensure that the selection becomes of type "Control"\r
+        var doc = dom.getDocument(ranges[0].startContainer);\r
+        var controlRange = dom.getBody(doc).createControlRange();\r
+        for (var i = 0, el; i < rangeCount; ++i) {\r
+            el = getSingleElementFromRange(ranges[i]);\r
+            try {\r
+                controlRange.add(el);\r
+            } catch (ex) {\r
+                throw new Error("setRanges(): Element within the one of the specified Ranges could not be added to control selection (does it have layout?)");\r
+            }\r
+        }\r
+        controlRange.select();\r
+\r
+        // Update the wrapped selection based on what's now in the native selection\r
+        updateControlSelection(sel);\r
+    }\r
+\r
+    // Selecting a range\r
+    if (!useDocumentSelection && selectionHasAnchorAndFocus && util.areHostMethods(testSelection, ["removeAllRanges", "addRange"])) {\r
+        selProto.removeAllRanges = function() {\r
+            this.nativeSelection.removeAllRanges();\r
+            updateEmptySelection(this);\r
+        };\r
+\r
+        var addRangeBackwards = function(sel, range) {\r
+            var doc = DomRange.getRangeDocument(range);\r
+            var endRange = api.createRange(doc);\r
+            endRange.collapseToPoint(range.endContainer, range.endOffset);\r
+            sel.nativeSelection.addRange(getNativeRange(endRange));\r
+            sel.nativeSelection.extend(range.startContainer, range.startOffset);\r
+            sel.refresh();\r
+        };\r
+\r
+        if (selectionHasRangeCount) {\r
+            selProto.addRange = function(range, backwards) {\r
+                if (implementsControlRange && implementsDocSelection && this.docSelection.type == CONTROL) {\r
+                    addRangeToControlSelection(this, range);\r
+                } else {\r
+                    if (backwards && selectionHasExtend) {\r
+                        addRangeBackwards(this, range);\r
+                    } else {\r
+                        var previousRangeCount;\r
+                        if (selectionSupportsMultipleRanges) {\r
+                            previousRangeCount = this.rangeCount;\r
+                        } else {\r
+                            this.removeAllRanges();\r
+                            previousRangeCount = 0;\r
+                        }\r
+                        this.nativeSelection.addRange(getNativeRange(range));\r
+\r
+                        // Check whether adding the range was successful\r
+                        this.rangeCount = this.nativeSelection.rangeCount;\r
+\r
+                        if (this.rangeCount == previousRangeCount + 1) {\r
+                            // The range was added successfully\r
+\r
+                            // Check whether the range that we added to the selection is reflected in the last range extracted from\r
+                            // the selection\r
+                            if (api.config.checkSelectionRanges) {\r
+                                var nativeRange = getSelectionRangeAt(this.nativeSelection, this.rangeCount - 1);\r
+                                if (nativeRange && !DomRange.rangesEqual(nativeRange, range)) {\r
+                                    // Happens in WebKit with, for example, a selection placed at the start of a text node\r
+                                    range = new WrappedRange(nativeRange);\r
+                                }\r
+                            }\r
+                            this._ranges[this.rangeCount - 1] = range;\r
+                            updateAnchorAndFocusFromRange(this, range, selectionIsBackwards(this.nativeSelection));\r
+                            this.isCollapsed = selectionIsCollapsed(this);\r
+                        } else {\r
+                            // The range was not added successfully. The simplest thing is to refresh\r
+                            this.refresh();\r
+                        }\r
+                    }\r
+                }\r
+            };\r
+        } else {\r
+            selProto.addRange = function(range, backwards) {\r
+                if (backwards && selectionHasExtend) {\r
+                    addRangeBackwards(this, range);\r
+                } else {\r
+                    this.nativeSelection.addRange(getNativeRange(range));\r
+                    this.refresh();\r
+                }\r
+            };\r
+        }\r
+\r
+        selProto.setRanges = function(ranges) {\r
+            if (implementsControlRange && ranges.length > 1) {\r
+                createControlSelection(this, ranges);\r
+            } else {\r
+                this.removeAllRanges();\r
+                for (var i = 0, len = ranges.length; i < len; ++i) {\r
+                    this.addRange(ranges[i]);\r
+                }\r
+            }\r
+        };\r
+    } else if (util.isHostMethod(testSelection, "empty") && util.isHostMethod(testRange, "select") &&\r
+               implementsControlRange && useDocumentSelection) {\r
+\r
+        selProto.removeAllRanges = function() {\r
+            // Added try/catch as fix for issue #21\r
+            try {\r
+                this.docSelection.empty();\r
+\r
+                // Check for empty() not working (issue #24)\r
+                if (this.docSelection.type != "None") {\r
+                    // Work around failure to empty a control selection by instead selecting a TextRange and then\r
+                    // calling empty()\r
+                    var doc;\r
+                    if (this.anchorNode) {\r
+                        doc = dom.getDocument(this.anchorNode);\r
+                    } else if (this.docSelection.type == CONTROL) {\r
+                        var controlRange = this.docSelection.createRange();\r
+                        if (controlRange.length) {\r
+                            doc = dom.getDocument(controlRange.item(0)).body.createTextRange();\r
+                        }\r
+                    }\r
+                    if (doc) {\r
+                        var textRange = doc.body.createTextRange();\r
+                        textRange.select();\r
+                        this.docSelection.empty();\r
+                    }\r
+                }\r
+            } catch(ex) {}\r
+            updateEmptySelection(this);\r
+        };\r
+\r
+        selProto.addRange = function(range) {\r
+            if (this.docSelection.type == CONTROL) {\r
+                addRangeToControlSelection(this, range);\r
+            } else {\r
+                WrappedRange.rangeToTextRange(range).select();\r
+                this._ranges[0] = range;\r
+                this.rangeCount = 1;\r
+                this.isCollapsed = this._ranges[0].collapsed;\r
+                updateAnchorAndFocusFromRange(this, range, false);\r
+            }\r
+        };\r
+\r
+        selProto.setRanges = function(ranges) {\r
+            this.removeAllRanges();\r
+            var rangeCount = ranges.length;\r
+            if (rangeCount > 1) {\r
+                createControlSelection(this, ranges);\r
+            } else if (rangeCount) {\r
+                this.addRange(ranges[0]);\r
+            }\r
+        };\r
+    } else {\r
+        module.fail("No means of selecting a Range or TextRange was found");\r
+        return false;\r
+    }\r
+\r
+    selProto.getRangeAt = function(index) {\r
+        if (index < 0 || index >= this.rangeCount) {\r
+            throw new DOMException("INDEX_SIZE_ERR");\r
+        } else {\r
+            return this._ranges[index];\r
+        }\r
+    };\r
+\r
+    var refreshSelection;\r
+\r
+    if (useDocumentSelection) {\r
+        refreshSelection = function(sel) {\r
+            var range;\r
+            if (api.isSelectionValid(sel.win)) {\r
+                range = sel.docSelection.createRange();\r
+            } else {\r
+                range = dom.getBody(sel.win.document).createTextRange();\r
+                range.collapse(true);\r
+            }\r
+\r
+\r
+            if (sel.docSelection.type == CONTROL) {\r
+                updateControlSelection(sel);\r
+            } else if (isTextRange(range)) {\r
+                updateFromTextRange(sel, range);\r
+            } else {\r
+                updateEmptySelection(sel);\r
+            }\r
+        };\r
+    } else if (util.isHostMethod(testSelection, "getRangeAt") && typeof testSelection.rangeCount == "number") {\r
+        refreshSelection = function(sel) {\r
+            if (implementsControlRange && implementsDocSelection && sel.docSelection.type == CONTROL) {\r
+                updateControlSelection(sel);\r
+            } else {\r
+                sel._ranges.length = sel.rangeCount = sel.nativeSelection.rangeCount;\r
+                if (sel.rangeCount) {\r
+                    for (var i = 0, len = sel.rangeCount; i < len; ++i) {\r
+                        sel._ranges[i] = new api.WrappedRange(sel.nativeSelection.getRangeAt(i));\r
+                    }\r
+                    updateAnchorAndFocusFromRange(sel, sel._ranges[sel.rangeCount - 1], selectionIsBackwards(sel.nativeSelection));\r
+                    sel.isCollapsed = selectionIsCollapsed(sel);\r
+                } else {\r
+                    updateEmptySelection(sel);\r
+                }\r
+            }\r
+        };\r
+    } else if (selectionHasAnchorAndFocus && typeof testSelection.isCollapsed == BOOLEAN && typeof testRange.collapsed == BOOLEAN && api.features.implementsDomRange) {\r
+        refreshSelection = function(sel) {\r
+            var range, nativeSel = sel.nativeSelection;\r
+            if (nativeSel.anchorNode) {\r
+                range = getSelectionRangeAt(nativeSel, 0);\r
+                sel._ranges = [range];\r
+                sel.rangeCount = 1;\r
+                updateAnchorAndFocusFromNativeSelection(sel);\r
+                sel.isCollapsed = selectionIsCollapsed(sel);\r
+            } else {\r
+                updateEmptySelection(sel);\r
+            }\r
+        };\r
+    } else {\r
+        module.fail("No means of obtaining a Range or TextRange from the user's selection was found");\r
+        return false;\r
+    }\r
+\r
+    selProto.refresh = function(checkForChanges) {\r
+        var oldRanges = checkForChanges ? this._ranges.slice(0) : null;\r
+        refreshSelection(this);\r
+        if (checkForChanges) {\r
+            var i = oldRanges.length;\r
+            if (i != this._ranges.length) {\r
+                return false;\r
+            }\r
+            while (i--) {\r
+                if (!DomRange.rangesEqual(oldRanges[i], this._ranges[i])) {\r
+                    return false;\r
+                }\r
+            }\r
+            return true;\r
+        }\r
+    };\r
+\r
+    // Removal of a single range\r
+    var removeRangeManually = function(sel, range) {\r
+        var ranges = sel.getAllRanges(), removed = false;\r
+        sel.removeAllRanges();\r
+        for (var i = 0, len = ranges.length; i < len; ++i) {\r
+            if (removed || range !== ranges[i]) {\r
+                sel.addRange(ranges[i]);\r
+            } else {\r
+                // According to the draft WHATWG Range spec, the same range may be added to the selection multiple\r
+                // times. removeRange should only remove the first instance, so the following ensures only the first\r
+                // instance is removed\r
+                removed = true;\r
+            }\r
+        }\r
+        if (!sel.rangeCount) {\r
+            updateEmptySelection(sel);\r
+        }\r
+    };\r
+\r
+    if (implementsControlRange) {\r
+        selProto.removeRange = function(range) {\r
+            if (this.docSelection.type == CONTROL) {\r
+                var controlRange = this.docSelection.createRange();\r
+                var rangeElement = getSingleElementFromRange(range);\r
+\r
+                // Create a new ControlRange containing all the elements in the selected ControlRange minus the\r
+                // element contained by the supplied range\r
+                var doc = dom.getDocument(controlRange.item(0));\r
+                var newControlRange = dom.getBody(doc).createControlRange();\r
+                var el, removed = false;\r
+                for (var i = 0, len = controlRange.length; i < len; ++i) {\r
+                    el = controlRange.item(i);\r
+                    if (el !== rangeElement || removed) {\r
+                        newControlRange.add(controlRange.item(i));\r
+                    } else {\r
+                        removed = true;\r
+                    }\r
+                }\r
+                newControlRange.select();\r
+\r
+                // Update the wrapped selection based on what's now in the native selection\r
+                updateControlSelection(this);\r
+            } else {\r
+                removeRangeManually(this, range);\r
+            }\r
+        };\r
+    } else {\r
+        selProto.removeRange = function(range) {\r
+            removeRangeManually(this, range);\r
+        };\r
+    }\r
+\r
+    // Detecting if a selection is backwards\r
+    var selectionIsBackwards;\r
+    if (!useDocumentSelection && selectionHasAnchorAndFocus && api.features.implementsDomRange) {\r
+        selectionIsBackwards = function(sel) {\r
+            var backwards = false;\r
+            if (sel.anchorNode) {\r
+                backwards = (dom.comparePoints(sel.anchorNode, sel.anchorOffset, sel.focusNode, sel.focusOffset) == 1);\r
+            }\r
+            return backwards;\r
+        };\r
+\r
+        selProto.isBackwards = function() {\r
+            return selectionIsBackwards(this);\r
+        };\r
+    } else {\r
+        selectionIsBackwards = selProto.isBackwards = function() {\r
+            return false;\r
+        };\r
+    }\r
+\r
+    // Selection text\r
+    // This is conformant to the new WHATWG DOM Range draft spec but differs from WebKit and Mozilla's implementation\r
+    selProto.toString = function() {\r
+\r
+        var rangeTexts = [];\r
+        for (var i = 0, len = this.rangeCount; i < len; ++i) {\r
+            rangeTexts[i] = "" + this._ranges[i];\r
+        }\r
+        return rangeTexts.join("");\r
+    };\r
+\r
+    function assertNodeInSameDocument(sel, node) {\r
+        if (sel.anchorNode && (dom.getDocument(sel.anchorNode) !== dom.getDocument(node))) {\r
+            throw new DOMException("WRONG_DOCUMENT_ERR");\r
+        }\r
+    }\r
+\r
+    // No current browsers conform fully to the HTML 5 draft spec for this method, so Rangy's own method is always used\r
+    selProto.collapse = function(node, offset) {\r
+        assertNodeInSameDocument(this, node);\r
+        var range = api.createRange(dom.getDocument(node));\r
+        range.collapseToPoint(node, offset);\r
+        this.removeAllRanges();\r
+        this.addRange(range);\r
+        this.isCollapsed = true;\r
+    };\r
+\r
+    selProto.collapseToStart = function() {\r
+        if (this.rangeCount) {\r
+            var range = this._ranges[0];\r
+            this.collapse(range.startContainer, range.startOffset);\r
+        } else {\r
+            throw new DOMException("INVALID_STATE_ERR");\r
+        }\r
+    };\r
+\r
+    selProto.collapseToEnd = function() {\r
+        if (this.rangeCount) {\r
+            var range = this._ranges[this.rangeCount - 1];\r
+            this.collapse(range.endContainer, range.endOffset);\r
+        } else {\r
+            throw new DOMException("INVALID_STATE_ERR");\r
+        }\r
+    };\r
+\r
+    // The HTML 5 spec is very specific on how selectAllChildren should be implemented so the native implementation is\r
+    // never used by Rangy.\r
+    selProto.selectAllChildren = function(node) {\r
+        assertNodeInSameDocument(this, node);\r
+        var range = api.createRange(dom.getDocument(node));\r
+        range.selectNodeContents(node);\r
+        this.removeAllRanges();\r
+        this.addRange(range);\r
+    };\r
+\r
+    selProto.deleteFromDocument = function() {\r
+        // Sepcial behaviour required for Control selections\r
+        if (implementsControlRange && implementsDocSelection && this.docSelection.type == CONTROL) {\r
+            var controlRange = this.docSelection.createRange();\r
+            var element;\r
+            while (controlRange.length) {\r
+                element = controlRange.item(0);\r
+                controlRange.remove(element);\r
+                element.parentNode.removeChild(element);\r
+            }\r
+            this.refresh();\r
+        } else if (this.rangeCount) {\r
+            var ranges = this.getAllRanges();\r
+            this.removeAllRanges();\r
+            for (var i = 0, len = ranges.length; i < len; ++i) {\r
+                ranges[i].deleteContents();\r
+            }\r
+            // The HTML5 spec says nothing about what the selection should contain after calling deleteContents on each\r
+            // range. Firefox moves the selection to where the final selected range was, so we emulate that\r
+            this.addRange(ranges[len - 1]);\r
+        }\r
+    };\r
+\r
+    // The following are non-standard extensions\r
+    selProto.getAllRanges = function() {\r
+        return this._ranges.slice(0);\r
+    };\r
+\r
+    selProto.setSingleRange = function(range) {\r
+        this.setRanges( [range] );\r
+    };\r
+\r
+    selProto.containsNode = function(node, allowPartial) {\r
+        for (var i = 0, len = this._ranges.length; i < len; ++i) {\r
+            if (this._ranges[i].containsNode(node, allowPartial)) {\r
+                return true;\r
+            }\r
+        }\r
+        return false;\r
+    };\r
+\r
+    selProto.toHtml = function() {\r
+        var html = "";\r
+        if (this.rangeCount) {\r
+            var container = DomRange.getRangeDocument(this._ranges[0]).createElement("div");\r
+            for (var i = 0, len = this._ranges.length; i < len; ++i) {\r
+                container.appendChild(this._ranges[i].cloneContents());\r
+            }\r
+            html = container.innerHTML;\r
+        }\r
+        return html;\r
+    };\r
+\r
+    function inspect(sel) {\r
+        var rangeInspects = [];\r
+        var anchor = new DomPosition(sel.anchorNode, sel.anchorOffset);\r
+        var focus = new DomPosition(sel.focusNode, sel.focusOffset);\r
+        var name = (typeof sel.getName == "function") ? sel.getName() : "Selection";\r
+\r
+        if (typeof sel.rangeCount != "undefined") {\r
+            for (var i = 0, len = sel.rangeCount; i < len; ++i) {\r
+                rangeInspects[i] = DomRange.inspect(sel.getRangeAt(i));\r
+            }\r
+        }\r
+        return "[" + name + "(Ranges: " + rangeInspects.join(", ") +\r
+                ")(anchor: " + anchor.inspect() + ", focus: " + focus.inspect() + "]";\r
+\r
+    }\r
+\r
+    selProto.getName = function() {\r
+        return "WrappedSelection";\r
+    };\r
+\r
+    selProto.inspect = function() {\r
+        return inspect(this);\r
+    };\r
+\r
+    selProto.detach = function() {\r
+        this.win[windowPropertyName] = null;\r
+        this.win = this.anchorNode = this.focusNode = null;\r
+    };\r
+\r
+    WrappedSelection.inspect = inspect;\r
+\r
+    api.Selection = WrappedSelection;\r
+\r
+    api.selectionPrototype = selProto;\r
+\r
+    api.addCreateMissingNativeApiListener(function(win) {\r
+        if (typeof win.getSelection == "undefined") {\r
+            win.getSelection = function() {\r
+                return api.getSelection(this);\r
+            };\r
+        }\r
+        win = null;\r
+    });\r
+});\r