Вход Регистрация
Файл: library/wysihtml5/lib/rangy/rangy-core.js
Строк: 6203
<?php
/**
 * @license Rangy, a cross-browser JavaScript range and selection library
 * http://code.google.com/p/rangy/
 *
 * Copyright 2011, Tim Down
 * Licensed under the MIT license.
 * Version: 1.2.2
 * Build date: 13 November 2011
 */
window['rangy'] = (function() {


    var 
OBJECT "object", FUNCTION = "function"UNDEFINED "undefined";

    var 
domRangeProperties = ["startContainer""startOffset""endContainer""endOffset""collapsed",
        
"commonAncestorContainer""START_TO_START""START_TO_END""END_TO_START""END_TO_END"];

    var 
domRangeMethods = ["setStart""setStartBefore""setStartAfter""setEnd""setEndBefore",
        
"setEndAfter""collapse""selectNode""selectNodeContents""compareBoundaryPoints""deleteContents",
        
"extractContents""cloneContents""insertNode""surroundContents""cloneRange""toString""detach"];

    var 
textRangeProperties = ["boundingHeight""boundingLeft""boundingTop""boundingWidth""htmlText""text"];

    
// Subset of TextRange's full set of methods that we're interested in
    
var textRangeMethods = ["collapse""compareEndPoints""duplicate""getBookmark""moveToBookmark",
        
"moveToElementText""parentElement""pasteHTML""select""setEndPoint""getBoundingClientRect"];

    
/*----------------------------------------------------------------------------------------------------------------*/

    // Trio of functions taken from Peter Michaux's article:
    // http://peter.michaux.ca/articles/feature-detection-state-of-the-art-browser-scripting
    
function isHostMethod(op) {
        var 
typeof o[p];
        return 
== FUNCTION || (!!(== OBJECT && o[p])) || == "unknown";
    }

    function 
isHostObject(op) {
        return !!(
typeof o[p] == OBJECT && o[p]);
    }

    function 
isHostProperty(op) {
        return 
typeof o[p] != UNDEFINED;
    }

    
// Creates a convenience function to save verbose repeated calls to tests functions
    
function createMultiplePropertyTest(testFunc) {
        return function(
oprops) {
            var 
props.length;
            while (
i--) {
                if (!
testFunc(oprops[i])) {
                    return 
false;
                }
            }
            return 
true;
        };
    }

    
// Next trio of functions are a convenience to save verbose repeated calls to previous two functions
    
var areHostMethods createMultiplePropertyTest(isHostMethod);
    var 
areHostObjects createMultiplePropertyTest(isHostObject);
    var 
areHostProperties createMultiplePropertyTest(isHostProperty);

    function 
isTextRange(range) {
        return 
range && areHostMethods(rangetextRangeMethods) && areHostProperties(rangetextRangeProperties);
    }

    var 
api = {
        
version"1.2.2",
        
initializedfalse,
        
supportedtrue,

        
util: {
            
isHostMethodisHostMethod,
            
isHostObjectisHostObject,
            
isHostPropertyisHostProperty,
            
areHostMethodsareHostMethods,
            
areHostObjectsareHostObjects,
            
areHostPropertiesareHostProperties,
            
isTextRangeisTextRange
        
},

        
features: {},

        
modules: {},
        
config: {
            
alertOnWarnfalse,
            
preferTextRangefalse
        
}
    };

    function 
fail(reason) {
        
window.alert("Rangy not supported in your browser. Reason: " reason);
        
api.initialized true;
        
api.supported false;
    }

    
api.fail fail;

    function 
warn(msg) {
        var 
warningMessage "Rangy warning: " msg;
        if (
api.config.alertOnWarn) {
            
window.alert(warningMessage);
        } else if (
typeof window.console != UNDEFINED && typeof window.console.log != UNDEFINED) {
            
window.console.log(warningMessage);
        }
    }

    
api.warn warn;

    if ({}.
hasOwnProperty) {
        
api.util.extend = function(oprops) {
            for (var 
i in props) {
                if (
props.hasOwnProperty(i)) {
                    
o[i] = props[i];
                }
            }
        };
    } else {
        
fail("hasOwnProperty not supported");
    }

    var 
initListeners = [];
    var 
moduleInitializers = [];

    
// Initialization
    
function init() {
        if (
api.initialized) {
            return;
        }
        var 
testRange;
        var 
implementsDomRange falseimplementsTextRange false;

        
// First, perform basic feature tests

        
if (isHostMethod(document"createRange")) {
            
testRange document.createRange();
            if (
areHostMethods(testRangedomRangeMethods) && areHostProperties(testRangedomRangeProperties)) {
                
implementsDomRange true;
            }
            
testRange.detach();
        }

        var 
body isHostObject(document"body") ? document.body document.getElementsByTagName("body")[0];

        if (
body && isHostMethod(body"createTextRange")) {
            
testRange body.createTextRange();
            if (
isTextRange(testRange)) {
                
implementsTextRange true;
            }
        }

        if (!
implementsDomRange && !implementsTextRange) {
            
fail("Neither Range nor TextRange are implemented");
        }

        
api.initialized true;
        
api.features = {
            
implementsDomRangeimplementsDomRange,
            
implementsTextRangeimplementsTextRange
        
};

        
// Initialize modules and call init listeners
        
var allListeners moduleInitializers.concat(initListeners);
        for (var 
0len allListeners.lengthlen; ++i) {
            try {
                
allListeners[i](api);
            } catch (
ex) {
                if (
isHostObject(window"console") && isHostMethod(window.console"log")) {
                    
window.console.log("Init listener threw an exception. Continuing."ex);
                }

            }
        }
    }

    
// Allow external scripts to initialize this library in case it's loaded after the document has loaded
    
api.init init;

    
// Execute listener immediately if already initialized
    
api.addInitListener = function(listener) {
        if (
api.initialized) {
            
listener(api);
        } else {
            
initListeners.push(listener);
        }
    };

    var 
createMissingNativeApiListeners = [];

    
api.addCreateMissingNativeApiListener = function(listener) {
        
createMissingNativeApiListeners.push(listener);
    };

    function 
createMissingNativeApi(win) {
        
win win || window;
        
init();

        
// Notify listeners
        
for (var 0len createMissingNativeApiListeners.lengthlen; ++i) {
            
createMissingNativeApiListeners[i](win);
        }
    }

    
api.createMissingNativeApi createMissingNativeApi;

    
/**
     * @constructor
     */
    
function Module(name) {
        
this.name name;
        
this.initialized false;
        
this.supported false;
    }

    
Module.prototype.fail = function(reason) {
        
this.initialized true;
        
this.supported false;

        throw new 
Error("Module '" this.name "' failed to load: " reason);
    };

    
Module.prototype.warn = function(msg) {
        
api.warn("Module " this.name ": " msg);
    };

    
Module.prototype.createError = function(msg) {
        return new 
Error("Error in Rangy " this.name " module: " msg);
    };

    
api.createModule = function(nameinitFunc) {
        var 
module = new Module(name);
        
api.modules[name] = module;

        
moduleInitializers.push(function(api) {
            
initFunc(apimodule);
            
module.initialized true;
            
module.supported true;
        });
    };

    
api.requireModules = function(modules) {
        for (var 
0len modules.lengthmodulemoduleNamelen; ++i) {
            
moduleName modules[i];
            
module api.modules[moduleName];
            if (!
module || !(module instanceof Module)) {
                throw new 
Error("Module '" moduleName "' not found");
            }
            if (!
module.supported) {
                throw new 
Error("Module '" moduleName "' not supported");
            }
        }
    };

    
/*----------------------------------------------------------------------------------------------------------------*/

    // Wait for document to load before running tests

    
var docReady false;

    var 
loadHandler = function(e) {

        if (!
docReady) {
            
docReady true;
            if (!
api.initialized) {
                
init();
            }
        }
    };

    
// Test whether we have window and document objects that we will need
    
if (typeof window == UNDEFINED) {
        
fail("No window found");
        return;
    }
    if (
typeof document == UNDEFINED) {
        
fail("No document found");
        return;
    }

    if (
isHostMethod(document"addEventListener")) {
        
document.addEventListener("DOMContentLoaded"loadHandlerfalse);
    }

    
// Add a fallback in case the DOMContentLoaded event isn't supported
    
if (isHostMethod(window"addEventListener")) {
        
window.addEventListener("load"loadHandlerfalse);
    } else if (
isHostMethod(window"attachEvent")) {
        
window.attachEvent("onload"loadHandler);
    } else {
        
fail("Window does not have required addEventListener or attachEvent method");
    }

    return 
api;
})();
rangy.createModule("DomUtil", function(apimodule) {

    var 
UNDEF "undefined";
    var 
util api.util;

    
// Perform feature tests
    
if (!util.areHostMethods(document, ["createDocumentFragment""createElement""createTextNode"])) {
        
module.fail("document missing a Node creation method");
    }

    if (!
util.isHostMethod(document"getElementsByTagName")) {
        
module.fail("document missing getElementsByTagName method");
    }

    var 
el document.createElement("div");
    if (!
util.areHostMethods(el, ["insertBefore""appendChild""cloneNode"] ||
            !
util.areHostObjects(el, ["previousSibling""nextSibling""childNodes""parentNode"]))) {
        
module.fail("Incomplete Element implementation");
    }

    
// innerHTML is required for Range's createContextualFragment method
    
if (!util.isHostProperty(el"innerHTML")) {
        
module.fail("Element is missing innerHTML property");
    }

    var 
textNode document.createTextNode("test");
    if (!
util.areHostMethods(textNode, ["splitText""deleteData""insertData""appendData""cloneNode"] ||
            !
util.areHostObjects(el, ["previousSibling""nextSibling""childNodes""parentNode"]) ||
            !
util.areHostProperties(textNode, ["data"]))) {
        
module.fail("Incomplete Text Node implementation");
    }

    
/*----------------------------------------------------------------------------------------------------------------*/

    // Removed use of indexOf because of a bizarre bug in Opera that is thrown in one of the Acid3 tests. I haven't been
    // able to replicate it outside of the test. The bug is that indexOf returns -1 when called on an Array that
    // contains just the document as a single element and the value searched for is the document.
    
var arrayContains /*Array.prototype.indexOf ?
        function(arr, val) {
            return arr.indexOf(val) > -1;
        }:*/

        
function(arrval) {
            var 
arr.length;
            while (
i--) {
                if (
arr[i] === val) {
                    return 
true;
                }
            }
            return 
false;
        };

    
// Opera 11 puts HTML elements in the null namespace, it seems, and IE 7 has undefined namespaceURI
    
function isHtmlNamespace(node) {
        var 
ns;
        return 
typeof node.namespaceURI == UNDEF || ((ns node.namespaceURI) === null || ns == "http://www.w3.org/1999/xhtml");
    }

    function 
parentElement(node) {
        var 
parent node.parentNode;
        return (
parent.nodeType == 1) ? parent null;
    }

    function 
getNodeIndex(node) {
        var 
0;
        while( (
node node.previousSibling) ) {
            
i++;
        }
        return 
i;
    }

    function 
getNodeLength(node) {
        var 
childNodes;
        return 
isCharacterDataNode(node) ? node.length : ((childNodes node.childNodes) ? childNodes.length 0);
    }

    function 
getCommonAncestor(node1node2) {
        var 
ancestors = [], n;
        for (
node1nn.parentNode) {
            
ancestors.push(n);
        }

        for (
node2nn.parentNode) {
            if (
arrayContains(ancestorsn)) {
                return 
n;
            }
        }

        return 
null;
    }

    function 
isAncestorOf(ancestordescendantselfIsAncestor) {
        var 
selfIsAncestor descendant descendant.parentNode;
        while (
n) {
            if (
=== ancestor) {
                return 
true;
            } else {
                
n.parentNode;
            }
        }
        return 
false;
    }

    function 
getClosestAncestorIn(nodeancestorselfIsAncestor) {
        var 
pselfIsAncestor node node.parentNode;
        while (
n) {
            
n.parentNode;
            if (
=== ancestor) {
                return 
n;
            }
            
p;
        }
        return 
null;
    }

    function 
isCharacterDataNode(node) {
        var 
node.nodeType;
        return 
== || == || == // Text, CDataSection or Comment
    
}

    function 
insertAfter(nodeprecedingNode) {
        var 
nextNode precedingNode.nextSiblingparent precedingNode.parentNode;
        if (
nextNode) {
            
parent.insertBefore(nodenextNode);
        } else {
            
parent.appendChild(node);
        }
        return 
node;
    }

    
// Note that we cannot use splitText() because it is bugridden in IE 9.
    
function splitDataNode(nodeindex) {
        var 
newNode node.cloneNode(false);
        
newNode.deleteData(0index);
        
node.deleteData(indexnode.length index);
        
insertAfter(newNodenode);
        return 
newNode;
    }

    function 
getDocument(node) {
        if (
node.nodeType == 9) {
            return 
node;
        } else if (
typeof node.ownerDocument != UNDEF) {
            return 
node.ownerDocument;
        } else if (
typeof node.document != UNDEF) {
            return 
node.document;
        } else if (
node.parentNode) {
            return 
getDocument(node.parentNode);
        } else {
            throw new 
Error("getDocument: no document found for node");
        }
    }

    function 
getWindow(node) {
        var 
doc getDocument(node);
        if (
typeof doc.defaultView != UNDEF) {
            return 
doc.defaultView;
        } else if (
typeof doc.parentWindow != UNDEF) {
            return 
doc.parentWindow;
        } else {
            throw new 
Error("Cannot get a window object for node");
        }
    }

    function 
getIframeDocument(iframeEl) {
        if (
typeof iframeEl.contentDocument != UNDEF) {
            return 
iframeEl.contentDocument;
        } else if (
typeof iframeEl.contentWindow != UNDEF) {
            return 
iframeEl.contentWindow.document;
        } else {
            throw new 
Error("getIframeWindow: No Document object found for iframe element");
        }
    }

    function 
getIframeWindow(iframeEl) {
        if (
typeof iframeEl.contentWindow != UNDEF) {
            return 
iframeEl.contentWindow;
        } else if (
typeof iframeEl.contentDocument != UNDEF) {
            return 
iframeEl.contentDocument.defaultView;
        } else {
            throw new 
Error("getIframeWindow: No Window object found for iframe element");
        }
    }

    function 
getBody(doc) {
        return 
util.isHostObject(doc"body") ? doc.body doc.getElementsByTagName("body")[0];
    }

    function 
getRootContainer(node) {
        var 
parent;
        while ( (
parent node.parentNode) ) {
            
node parent;
        }
        return 
node;
    }

    function 
comparePoints(nodeAoffsetAnodeBoffsetB) {
        
// See http://www.w3.org/TR/DOM-Level-2-Traversal-Range/ranges.html#Level-2-Range-Comparing
        
var nodeCrootchildAchildBn;
        if (
nodeA == nodeB) {

            
// Case 1: nodes are the same
            
return offsetA === offsetB : (offsetA offsetB) ? -1;
        } else if ( (
nodeC getClosestAncestorIn(nodeBnodeAtrue)) ) {

            
// Case 2: node C (container B or an ancestor) is a child node of A
            
return offsetA <= getNodeIndex(nodeC) ? -1;
        } else if ( (
nodeC getClosestAncestorIn(nodeAnodeBtrue)) ) {

            
// Case 3: node C (container A or an ancestor) is a child node of B
            
return getNodeIndex(nodeC) < offsetB  ? -1;
        } else {

            
// Case 4: containers are siblings or descendants of siblings
            
root getCommonAncestor(nodeAnodeB);
            
childA = (nodeA === root) ? root getClosestAncestorIn(nodeAroottrue);
            
childB = (nodeB === root) ? root getClosestAncestorIn(nodeBroottrue);

            if (
childA === childB) {
                
// This shouldn't be possible

                
throw new Error("comparePoints got to case 4 and childA and childB are the same!");
            } else {
                
root.firstChild;
                while (
n) {
                    if (
=== childA) {
                        return -
1;
                    } else if (
=== childB) {
                        return 
1;
                    }
                    
n.nextSibling;
                }
                throw new 
Error("Should not be here!");
            }
        }
    }

    function 
fragmentFromNodeChildren(node) {
        var 
fragment getDocument(node).createDocumentFragment(), child;
        while ( (
child node.firstChild) ) {
            
fragment.appendChild(child);
        }
        return 
fragment;
    }

    function 
inspectNode(node) {
        if (!
node) {
            return 
"[No node]";
        }
        if (
isCharacterDataNode(node)) {
            return 
'"' node.data '"';
        } else if (
node.nodeType == 1) {
            var 
idAttr node.id ' id="' node.id '"' "";
            return 
"<" node.nodeName idAttr ">[" node.childNodes.length "]";
        } else {
            return 
node.nodeName;
        }
    }

    
/**
     * @constructor
     */
    
function NodeIterator(root) {
        
this.root root;
        
this._next root;
    }

    
NodeIterator.prototype = {
        
_currentnull,

        
hasNext: function() {
            return !!
this._next;
        },

        
next: function() {
            var 
this._current this._next;
            var 
childnext;
            if (
this._current) {
                
child n.firstChild;
                if (
child) {
                    
this._next child;
                } else {
                    
next null;
                    while ((
!== this.root) && !(next n.nextSibling)) {
                        
n.parentNode;
                    }
                    
this._next next;
                }
            }
            return 
this._current;
        },

        
detach: function() {
            
this._current this._next this.root null;
        }
    };

    function 
createIterator(root) {
        return new 
NodeIterator(root);
    }

    
/**
     * @constructor
     */
    
function DomPosition(nodeoffset) {
        
this.node node;
        
this.offset offset;
    }

    
DomPosition.prototype = {
        
equals: function(pos) {
            return 
this.node === pos.node this.offset == pos.offset;
        },

        
inspect: function() {
            return 
"[DomPosition(" inspectNode(this.node) + ":" this.offset ")]";
        }
    };

    
/**
     * @constructor
     */
    
function DOMException(codeName) {
        
this.code this[codeName];
        
this.codeName codeName;
        
this.message "DOMException: " this.codeName;
    }

    
DOMException.prototype = {
        
INDEX_SIZE_ERR1,
        
HIERARCHY_REQUEST_ERR3,
        
WRONG_DOCUMENT_ERR4,
        
NO_MODIFICATION_ALLOWED_ERR7,
        
NOT_FOUND_ERR8,
        
NOT_SUPPORTED_ERR9,
        
INVALID_STATE_ERR11
    
};

    
DOMException.prototype.toString = function() {
        return 
this.message;
    };

    
api.dom = {
        
arrayContainsarrayContains,
        
isHtmlNamespaceisHtmlNamespace,
        
parentElementparentElement,
        
getNodeIndexgetNodeIndex,
        
getNodeLengthgetNodeLength,
        
getCommonAncestorgetCommonAncestor,
        
isAncestorOfisAncestorOf,
        
getClosestAncestorIngetClosestAncestorIn,
        
isCharacterDataNodeisCharacterDataNode,
        
insertAfterinsertAfter,
        
splitDataNodesplitDataNode,
        
getDocumentgetDocument,
        
getWindowgetWindow,
        
getIframeWindowgetIframeWindow,
        
getIframeDocumentgetIframeDocument,
        
getBodygetBody,
        
getRootContainergetRootContainer,
        
comparePointscomparePoints,
        
inspectNodeinspectNode,
        
fragmentFromNodeChildrenfragmentFromNodeChildren,
        
createIteratorcreateIterator,
        
DomPositionDomPosition
    
};

    
api.DOMException DOMException;
});
rangy.createModule("DomRange", function(apimodule) {
    
api.requireModules( ["DomUtil"] );


    var 
dom api.dom;
    var 
DomPosition dom.DomPosition;
    var 
DOMException api.DOMException;
    
    
/*----------------------------------------------------------------------------------------------------------------*/

    // Utility functions

    
function isNonTextPartiallySelected(noderange) {
        return (
node.nodeType != 3) &&
               (
dom.isAncestorOf(noderange.startContainertrue) || dom.isAncestorOf(noderange.endContainertrue));
    }

    function 
getRangeDocument(range) {
        return 
dom.getDocument(range.startContainer);
    }

    function 
dispatchEvent(rangetypeargs) {
        var 
listeners range._listeners[type];
        if (
listeners) {
            for (var 
0len listeners.lengthlen; ++i) {
                
listeners[i].call(range, {targetrangeargsargs});
            }
        }
    }

    function 
getBoundaryBeforeNode(node) {
        return new 
DomPosition(node.parentNodedom.getNodeIndex(node));
    }

    function 
getBoundaryAfterNode(node) {
        return new 
DomPosition(node.parentNodedom.getNodeIndex(node) + 1);
    }

    function 
insertNodeAtPosition(nodeno) {
        var 
firstNodeInserted node.nodeType == 11 node.firstChild node;
        if (
dom.isCharacterDataNode(n)) {
            if (
== n.length) {
                
dom.insertAfter(noden);
            } else {
                
n.parentNode.insertBefore(node== dom.splitDataNode(no));
            }
        } else if (
>= n.childNodes.length) {
            
n.appendChild(node);
        } else {
            
n.insertBefore(noden.childNodes[o]);
        }
        return 
firstNodeInserted;
    }

    function 
cloneSubtree(iterator) {
        var 
partiallySelected;
        for (var 
nodefrag getRangeDocument(iterator.range).createDocumentFragment(), subIteratornode 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(rangeIteratorfunciteratorState) {
        var 
itn;
        
iteratorState iteratorState || { stopfalse };
        for (var 
nodesubRangeIteratornode 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(subRangeIteratorfunciteratorState);
                    
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 ( (
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 
nodefrag getRangeDocument(iterator.range).createDocumentFragment(), subIteratornode 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(rangenodeTypesfilter) {
        
//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(rangefalse), 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(rangeclonePartiallySelectedTextNodes) {
        
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.scroottrue);
                
this._last = (this.ec === root && !dom.isCharacterDataNode(this.ec)) ?
                    
this.ec.childNodes[this.eo 1] : dom.getClosestAncestorIn(this.ecroottrue);
            }

        }
    }

    
RangeIterator.prototype = {
        
_currentnull,
        
_nextnull,
        
_firstnull,
        
_lastnull,
        
isSingleCharacterDataNodefalse,

        
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.eocurrent.length this.eo);
                    }
                    if (
this._current === this.sc) {

                        (
current current.cloneNode(true)).deleteData(0this.so);
                    }
                }
            }

            return 
current;
        },

        
remove: function() {
            var 
current this._currentstartend;

            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(startend 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(currentthis.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 currentstartOffset 0endContainer currentendOffset dom.getNodeLength(current);

                if (
dom.isAncestorOf(currentthis.sctrue)) {
                    
startContainer this.sc;
                    
startOffset this.so;
                }
                if (
dom.isAncestorOf(currentthis.ectrue)) {
                    
endContainer this.ec;
                    
endOffset this.eo;
                }

                
updateBoundaries(subRangestartContainerstartOffsetendContainerendOffset);
            }
            return new 
RangeIterator(subRangethis.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_ERR1,
        
INVALID_NODE_TYPE_ERR2
    
};

    
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(rangenodeTypesfilter) {
        
this.nodes getNodesInRange(rangenodeTypesfilter);
        
this._next this.nodes[0];
        
this._position 0;
    }

    
RangeNodeIterator.prototype = {
        
_currentnull,

        
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 = [13457810];
    var 
rootContainerNodeTypes = [2911];
    var 
readonlyNodeTypes = [561012];
    var 
insertableNodeTypes = [1345781011];
    var 
surroundNodeTypes = [134578];

    function 
createAncestorFinder(nodeTypes) {
        return function(
nodeselfIsAncestor) {
            var 
tselfIsAncestor node node.parentNode;
            while (
n) {
                
n.nodeType;
                if (
dom.arrayContains(nodeTypest)) {
                    return 
n;
                }
                
n.parentNode;
            }
            return 
null;
        };
    }

    var 
getRootContainer dom.getRootContainer;
    var 
getDocumentOrFragmentContainer createAncestorFinder( [911] );
    var 
getReadonlyAncestor createAncestorFinder(readonlyNodeTypes);
    var 
getDocTypeNotationEntityAncestor createAncestorFinder( [61012] );

    function 
assertNoDocTypeNotationEntityAncestor(nodeallowSelf) {
        if (
getDocTypeNotationEntityAncestor(nodeallowSelf)) {
            throw new 
RangeException("INVALID_NODE_TYPE_ERR");
        }
    }

    function 
assertNotDetached(range) {
        if (!
range.startContainer) {
            throw new 
DOMException("INVALID_STATE_ERR");
        }
    }

    function 
assertValidNodeType(nodeinvalidTypes) {
        if (!
dom.arrayContains(invalidTypesnode.nodeType)) {
            throw new 
RangeException("INVALID_NODE_TYPE_ERR");
        }
    }

    function 
assertValidOffset(nodeoffset) {
        if (
offset || offset > (dom.isCharacterDataNode(node) ? node.length node.childNodes.length)) {
            throw new 
DOMException("INDEX_SIZE_ERR");
        }
    }

    function 
assertSameDocumentOrFragment(node1node2) {
        if (
getDocumentOrFragmentContainer(node1true) !== getDocumentOrFragmentContainer(node2true)) {
            throw new 
DOMException("WRONG_DOCUMENT_ERR");
        }
    }

    function 
assertNodeNotReadOnly(node) {
        if (
getReadonlyAncestor(nodetrue)) {
            throw new 
DOMException("NO_MODIFICATION_ALLOWED_ERR");
        }
    }

    function 
assertNode(nodecodeName) {
        if (!
node) {
            throw new 
DOMException(codeName);
        }
    }

    function 
isOrphan(node) {
        return !
dom.arrayContains(rootContainerNodeTypesnode.nodeType) && !getDocumentOrFragmentContainer(nodetrue);
    }

    function 
isValidOffset(nodeoffset) {
        return 
offset <= (dom.isCharacterDataNode(node) ? node.length node.childNodes.length);
    }

    function 
assertRangeValid(range) {
        
assertNotDetached(range);
        if (
isOrphan(range.startContainer) || isOrphan(range.endContainer) ||
                !
isValidOffset(range.startContainerrange.startOffset) ||
                !
isValidOffset(range.endContainerrange.endOffset)) {
            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 0s2e 1e2e 2e2s 3;
    var 
n_b 0n_a 1n_b_a 2n_i 3;

    function 
RangePrototype() {}

    
RangePrototype.prototype = {
        
attachListener: function(typelistener) {
            
this._listeners[type].push(listener);
        },

        
compareBoundaryPoints: function(howrange) {
            
assertRangeValid(this);
            
assertSameDocumentOrFragment(this.startContainerrange.startContainer);

            var 
nodeAoffsetAnodeBoffsetB;
            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(nodeAoffsetAnodeBoffsetB);
        },

        
insertNode: function(node) {
            
assertRangeValid(this);
            
assertValidNodeType(nodeinsertableNodeTypes);
            
assertNodeNotReadOnly(this.startContainer);

            if (
dom.isAncestorOf(nodethis.startContainertrue)) {
                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(nodethis.startContainerthis.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.startOffsetthis.endOffset);
                    
frag getRangeDocument(this).createDocumentFragment();
                    
frag.appendChild(clone);
                    return 
frag;
                } else {
                    var 
iterator = new RangeIterator(thistrue);
                    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(thistrue);
            var 
boundariesInvalid = (iterator._first && (isNonTextPartiallySelected(iterator._firstthis)) ||
                    (
iterator._last && isNonTextPartiallySelected(iterator._lastthis)));
            
iterator.detach();
            return !
boundariesInvalid;
        },

        
surroundContents: function(node) {
            
assertValidNodeType(nodesurroundNodeTypes);

            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(nodethis.startContainerthis.startOffset);
            
node.appendChild(content);

            
this.selectNode(node);
        },

        
cloneRange: function() {
            
assertRangeValid(this);
            var 
range = new Range(getRangeDocument(this));
            var 
rangeProperties.lengthprop;
            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 == || sc.nodeType == 4) ? sc.data.slice(this.startOffsetthis.endOffset) : "";
            } else {
                var 
textBits = [], iterator = new RangeIterator(thistrue);

                
iterateSubtree(iterator, function(node) {
                    
// Accept only text or CDATA nodes, not comments

                    
if (node.nodeType == || 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(parentnodeIndex),
                
endComparison this.comparePoint(parentnodeIndex 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(nodeoffset) {
            
assertRangeValid(this);
            
assertNode(node"HIERARCHY_REQUEST_ERR");
            
assertSameDocumentOrFragment(nodethis.startContainer);

            if (
dom.comparePoints(nodeoffsetthis.startContainerthis.startOffset) < 0) {
                return -
1;
            } else if (
dom.comparePoints(nodeoffsetthis.endContainerthis.endOffset) > 0) {
                return 
1;
            }
            return 
0;
        },

        
createContextualFragmentcreateContextualFragment,

        
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(nodetouchingIsIntersecting) {
            
assertRangeValid(this);
            
assertNode(node"NOT_FOUND_ERR");
            if (
dom.getDocument(node) !== getRangeDocument(this)) {
                return 
false;
            }

            var 
parent node.parentNodeoffset dom.getNodeIndex(node);
            
assertNode(parent"NOT_FOUND_ERR");

            var 
startComparison dom.comparePoints(parentoffsetthis.endContainerthis.endOffset),
                
endComparison dom.comparePoints(parentoffset 1this.startContainerthis.startOffset);

            return 
touchingIsIntersecting startComparison <= && endComparison >= startComparison && endComparison 0;
        },


        
isPointInRange: function(nodeoffset) {
            
assertRangeValid(this);
            
assertNode(node"HIERARCHY_REQUEST_ERR");
            
assertSameDocumentOrFragment(nodethis.startContainer);

            return (
dom.comparePoints(nodeoffsetthis.startContainerthis.startOffset) >= 0) &&
                   (
dom.comparePoints(nodeoffsetthis.endContainerthis.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(rangetouchingIsIntersecting) {
            
assertRangeValid(this);

            if (
getRangeDocument(range) != getRangeDocument(this)) {
                throw new 
DOMException("WRONG_DOCUMENT_ERR");
            }

            var 
startComparison dom.comparePoints(this.startContainerthis.startOffsetrange.endContainerrange.endOffset),
                
endComparison dom.comparePoints(this.endContainerthis.endOffsetrange.startContainerrange.startOffset);

            return 
touchingIsIntersecting startComparison <= && endComparison >= startComparison && endComparison 0;
        },

        
intersection: function(range) {
            if (
this.intersectsRange(range)) {
                var 
startComparison dom.comparePoints(this.startContainerthis.startOffsetrange.startContainerrange.startOffset),
                    
endComparison dom.comparePoints(this.endContainerthis.endOffsetrange.endContainerrange.endOffset);

                var 
intersectionRange this.cloneRange();

                if (
startComparison == -1) {
                    
intersectionRange.setStart(range.startContainerrange.startOffset);
                }
                if (
endComparison == 1) {
                    
intersectionRange.setEnd(range.endContainerrange.endOffset);
                }
                return 
intersectionRange;
            }
            return 
null;
        },

        
union: function(range) {
            if (
this.intersectsRange(rangetrue)) {
                var 
unionRange this.cloneRange();
                if (
dom.comparePoints(range.startContainerrange.startOffsetthis.startContainerthis.startOffset) == -1) {
                    
unionRange.setStart(range.startContainerrange.startOffset);
                }
                if (
dom.comparePoints(range.endContainerrange.endOffsetthis.endContainerthis.endOffset) == 1) {
                    
unionRange.setEnd(range.endContainerrange.endOffset);
                }
                return 
unionRange;
            } else {
                throw new 
RangeException("Ranges do not intersect");
            }
        },

        
containsNode: function(nodeallowPartial) {
            if (
allowPartial) {
                return 
this.intersectsNode(nodefalse);
            } else {
                return 
this.compareNode(node) == n_i;
            }
        },

        
containsNodeContents: function(node) {
            return 
this.comparePoint(node0) >= && this.comparePoint(nodedom.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(lastTextNodelastTextNode.length);
                var 
contains this.containsRange(nodeRange);
                
nodeRange.detach();
                return 
contains;
            } else {
                return 
this.containsNodeContents(node);
            }
        },

        
createNodeIterator: function(nodeTypesfilter) {
            
assertRangeValid(this);
            return new 
RangeNodeIterator(thisnodeTypesfilter);
        },

        
getNodes: function(nodeTypesfilter) {
            
assertRangeValid(this);
            return 
getNodesInRange(thisnodeTypesfilter);
        },

        
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(thisrange);
        },

        
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(removerboundaryUpdater) {
        return function() {
            
assertRangeValid(this);

            var 
sc this.startContainerso this.startOffsetroot this.commonAncestorContainer;

            var 
iterator = new RangeIterator(thistrue);

            
// Work out where to position the range after content removal
            
var nodeboundary;
            if (
sc !== root) {
                
node dom.getClosestAncestorIn(scroottrue);
                
boundary getBoundaryAfterNode(node);
                
sc boundary.node;
                
so boundary.offset;
            }

            
// Check none of the range is read-only
            
iterateSubtree(iteratorassertNodeNotReadOnly);

            
iterator.reset();

            
// Remove the content
            
var returnValue remover(iterator);
            
iterator.detach();

            
// Move to the new position
            
boundaryUpdater(thisscsoscso);

            return 
returnValue;
        };
    }

    function 
createPrototypeRange(constructorboundaryUpdaterdetacher) {
        function 
createBeforeAfterNodeSetter(isBeforeisStart) {
            return function(
node) {
                
assertNotDetached(this);
                
assertValidNodeType(nodebeforeAfterNodeTypes);
                
assertValidNodeType(getRootContainer(node), rootContainerNodeTypes);

                var 
boundary = (isBefore getBoundaryBeforeNode getBoundaryAfterNode)(node);
                (
isStart setRangeStart setRangeEnd)(thisboundary.nodeboundary.offset);
            };
        }

        function 
setRangeStart(rangenodeoffset) {
            var 
ec range.endContainereo 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(nodeoffseteceo) == 1) {
                    
ec node;
                    
eo offset;
                }
                
boundaryUpdater(rangenodeoffseteceo);
            }
        }

        function 
setRangeEnd(rangenodeoffset) {
            var 
sc range.startContainerso 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(nodeoffsetscso) == -1) {
                    
sc node;
                    
so offset;
                }
                
boundaryUpdater(rangescsonodeoffset);
            }
        }

        function 
setRangeStartAndEnd(rangenodeoffset) {
            if (
node !== range.startContainer || offset !== range.startOffset || node !== range.endContainer || offset !== range.endOffset) {
                
boundaryUpdater(rangenodeoffsetnodeoffset);
            }
        }

        
constructor.prototype = new RangePrototype();

        
api.util.extend(constructor.prototype, {
            
setStart: function(nodeoffset) {
                
assertNotDetached(this);
                
assertNoDocTypeNotationEntityAncestor(nodetrue);
                
assertValidOffset(nodeoffset);

                
setRangeStart(thisnodeoffset);
            },

            
setEnd: function(nodeoffset) {
                
assertNotDetached(this);
                
assertNoDocTypeNotationEntityAncestor(nodetrue);
                
assertValidOffset(nodeoffset);

                
setRangeEnd(thisnodeoffset);
            },

            
setStartBeforecreateBeforeAfterNodeSetter(truetrue),
            
setStartAftercreateBeforeAfterNodeSetter(falsetrue),
            
setEndBeforecreateBeforeAfterNodeSetter(truefalse),
            
setEndAftercreateBeforeAfterNodeSetter(falsefalse),

            
collapse: function(isStart) {
                
assertRangeValid(this);
                if (
isStart) {
                    
boundaryUpdater(thisthis.startContainerthis.startOffsetthis.startContainerthis.startOffset);
                } else {
                    
boundaryUpdater(thisthis.endContainerthis.endOffsetthis.endContainerthis.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(nodetrue);

                
boundaryUpdater(thisnode0nodedom.getNodeLength(node));
            },

            
selectNode: function(node) {
                
assertNotDetached(this);
                
assertNoDocTypeNotationEntityAncestor(nodefalse);
                
assertValidNodeType(nodebeforeAfterNodeTypes);

                var 
start getBoundaryBeforeNode(node), end getBoundaryAfterNode(node);
                
boundaryUpdater(thisstart.nodestart.offsetend.nodeend.offset);
            },

            
extractContentscreateRangeContentRemover(extractSubtreeboundaryUpdater),

            
deleteContentscreateRangeContentRemover(deleteSubtreeboundaryUpdater),

            
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(thistrue);
                var 
boundariesInvalid = (iterator._first && (isNonTextPartiallySelected(iterator._firstthis)) ||
                        (
iterator._last && isNonTextPartiallySelected(iterator._lastthis)));
                
iterator.detach();
                return !
boundariesInvalid;
            },

            
detach: function() {
                
detacher(this);
            },

            
splitBoundaries: function() {
                
assertRangeValid(this);


                var 
sc this.startContainerso this.startOffsetec this.endContainereo this.endOffset;
                var 
startEndSame = (sc === ec);

                if (
dom.isCharacterDataNode(ec) && eo && eo ec.length) {
                    
dom.splitDataNode(eceo);

                }

                if (
dom.isCharacterDataNode(sc) && so && so sc.length) {

                    
sc dom.splitDataNode(scso);
                    if (
startEndSame) {
                        
eo -= so;
                        
ec sc;
                    } else if (
ec == sc.parentNode && eo >= dom.getNodeIndex(sc)) {
                        
eo++;
                    }
                    
so 0;

                }
                
boundaryUpdater(thisscsoeceo);
            },

            
normalizeBoundaries: function() {
                
assertRangeValid(this);

                var 
sc this.startContainerso this.startOffsetec this.endContainereo 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(0sibling.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(thisscsoeceo);
            },

            
collapseToPoint: function(nodeoffset) {
                
assertNotDetached(this);

                
assertNoDocTypeNotationEntityAncestor(nodetrue);
                
assertValidOffset(nodeoffset);

                
setRangeStartAndEnd(thisnodeoffset);
            }
        });

        
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.startContainerrange.endContainer);
    }

    function 
updateBoundaries(rangestartContainerstartOffsetendContainerendOffset) {
        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", {startMovedstartMovedendMovedendMoved});
    }

    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(RangeupdateBoundariesdetach);

    
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(r1r2) {
        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(apimodule) {
    
api.requireModules( ["DomUtil""DomRange"] );

    
/**
     * @constructor
     */
    
var WrappedRange;
    var 
dom api.dom;
    var 
DomPosition dom.DomPosition;
    var 
DomRange api.DomRange;



    
/*----------------------------------------------------------------------------------------------------------------*/

    /*
    This is a workaround for a bug where IE returns the wrong container element from the TextRange's parentElement()
    method. For example, in the following (where pipes denote the selection boundaries):

    <ul id="ul"><li id="a">| a </li><li id="b"> b |</li></ul>

    var range = document.selection.createRange();
    alert(range.parentElement().id); // Should alert "ul" but alerts "b"

    This method returns the common ancestor node of the following:
    - the parentElement() of the textRange
    - the parentElement() of the textRange after calling collapse(true)
    - the parentElement() of the textRange after calling collapse(false)
     */
    
function getTextRangeContainerElement(textRange) {
        var 
parentEl textRange.parentElement();

        var 
range textRange.duplicate();
        
range.collapse(true);
        var 
startEl range.parentElement();
        
range textRange.duplicate();
        
range.collapse(false);
        var 
endEl range.parentElement();
        var 
startEndContainer = (startEl == endEl) ? startEl dom.getCommonAncestor(startElendEl);

        return 
startEndContainer == parentEl startEndContainer dom.getCommonAncestor(parentElstartEndContainer);
    }

    function 
textRangeIsCollapsed(textRange) {
        return 
textRange.compareEndPoints("StartToEnd"textRange) == 0;
    }

    
// Gets the boundary of a TextRange expressed as a node and an offset within that node. This function started out as
    // an improved version of code found in Tim Cameron Ryan's IERange (http://code.google.com/p/ierange/) but has
    // grown, fixing problems with line breaks in preformatted text, adding workaround for IE TextRange bugs, handling
    // for inputs and images, plus optimizations.
    
function getTextRangeBoundaryPosition(textRangewholeRangeContainerElementisStartisCollapsed) {
        var 
workingRange textRange.duplicate();

        
workingRange.collapse(isStart);
        var 
containerElement workingRange.parentElement();

        
// Sometimes collapsing a TextRange that's at the start of a text node can move it into the previous node, so
        // check for that
        // TODO: Find out when. Workaround for wholeRangeContainerElement may break this
        
if (!dom.isAncestorOf(wholeRangeContainerElementcontainerElementtrue)) {
            
containerElement wholeRangeContainerElement;

        }



        
// Deal with nodes that cannot "contain rich HTML markup". In practice, this means form inputs, images and
        // similar. See http://msdn.microsoft.com/en-us/library/aa703950%28VS.85%29.aspx
        
if (!containerElement.canHaveHTML) {
            return new 
DomPosition(containerElement.parentNodedom.getNodeIndex(containerElement));
        }

        var 
workingNode dom.getDocument(containerElement).createElement("span");
        var 
comparisonworkingComparisonType isStart "StartToStart" "StartToEnd";
        var 
previousNodenextNodeboundaryPositionboundaryNode;

        
// Move the working range through the container's children, starting at the end and working backwards, until the
        // working range reaches or goes past the boundary we're interested in
        
do {
            
containerElement.insertBefore(workingNodeworkingNode.previousSibling);
            
workingRange.moveToElementText(workingNode);
        } while ( (
comparison workingRange.compareEndPoints(workingComparisonTypetextRange)) > &&
                
workingNode.previousSibling);

        
// We've now reached or gone past the boundary of the text range we're interested in
        // so have identified the node we want
        
boundaryNode workingNode.nextSibling;

        if (
comparison == -&& boundaryNode && dom.isCharacterDataNode(boundaryNode)) {
            
// This is a character data node (text, comment, cdata). The working range is collapsed at the start of the
            // node containing the text range's boundary, so we move the end of the working range to the boundary point
            // and measure the length of its text to get the boundary's offset within the node.
            
workingRange.setEndPoint(isStart "EndToStart" "EndToEnd"textRange);


            var 
offset;

            if (/[
rn]/.test(boundaryNode.data)) {
                
/*
                For the particular case of a boundary within a text node containing line breaks (within a <pre> element,
                for example), we need a slightly complicated approach to get the boundary's offset in IE. The facts:

                - Each line break is represented as r in the text node's data/nodeValue properties
                - Each line break is represented as rn in the TextRange's 'text' property
                - The 'text' property of the TextRange does not contain trailing line breaks

                To get round the problem presented by the final fact above, we can use the fact that TextRange's
                moveStart() and moveEnd() methods return the actual number of characters moved, which is not necessarily
                the same as the number of characters it was instructed to move. The simplest approach is to use this to
                store the characters moved when moving both the start and end of the range to the start of the document
                body and subtracting the start offset from the end offset (the "move-negative-gazillion" method).
                However, this is extremely slow when the document is large and the range is near the end of it. Clearly
                doing the mirror image (i.e. moving the range boundaries to the end of the document) has the same
                problem.

                Another approach that works is to use moveStart() to move the start boundary of the range up to the end
                boundary one character at a time and incrementing a counter with the value returned by the moveStart()
                call. However, the check for whether the start boundary has reached the end boundary is expensive, so
                this method is slow (although unlike "move-negative-gazillion" is largely unaffected by the location of
                the range within the document).

                The method below is a hybrid of the two methods above. It uses the fact that a string containing the
                TextRange's 'text' property with each rn converted to a single r character cannot be longer than the
                text of the TextRange, so the start of the range is moved that length initially and then a character at
                a time to make up for any trailing line breaks not contained in the 'text' property. This has good
                performance in most situations compared to the previous two methods.
                */
                
var tempRange workingRange.duplicate();
                var 
rangeLength tempRange.text.replace(/rn/g"r").length;

                
offset tempRange.moveStart("character"rangeLength);
                while ( (
comparison tempRange.compareEndPoints("StartToEnd"tempRange)) == -1) {
                    
offset++;
                    
tempRange.moveStart("character"1);
                }
            } else {
                
offset workingRange.text.length;
            }
            
boundaryPosition = new DomPosition(boundaryNodeoffset);
        } else {


            
// If the boundary immediately follows a character data node and this is the end boundary, we should favour
            // a position within that, and likewise for a start boundary preceding a character data node
            
previousNode = (isCollapsed || !isStart) && workingNode.previousSibling;
            
nextNode = (isCollapsed || isStart) && workingNode.nextSibling;



            if (
nextNode && dom.isCharacterDataNode(nextNode)) {
                
boundaryPosition = new DomPosition(nextNode0);
            } else if (
previousNode && dom.isCharacterDataNode(previousNode)) {
                
boundaryPosition = new DomPosition(previousNodepreviousNode.length);
            } else {
                
boundaryPosition = new DomPosition(containerElementdom.getNodeIndex(workingNode));
            }
        }

        
// Clean up
        
workingNode.parentNode.removeChild(workingNode);

        return 
boundaryPosition;
    }

    
// Returns a TextRange representing the boundary of a TextRange expressed as a node and an offset within that node.
    // This function started out as an optimized version of code found in Tim Cameron Ryan's IERange
    // (http://code.google.com/p/ierange/)
    
function createBoundaryTextRange(boundaryPositionisStart) {
        var 
boundaryNodeboundaryParentboundaryOffset boundaryPosition.offset;
        var 
doc dom.getDocument(boundaryPosition.node);
        var 
workingNodechildNodesworkingRange doc.body.createTextRange();
        var 
nodeIsDataNode dom.isCharacterDataNode(boundaryPosition.node);

        if (
nodeIsDataNode) {
            
boundaryNode boundaryPosition.node;
            
boundaryParent boundaryNode.parentNode;
        } else {
            
childNodes boundaryPosition.node.childNodes;
            
boundaryNode = (boundaryOffset childNodes.length) ? childNodes[boundaryOffset] : null;
            
boundaryParent boundaryPosition.node;
        }

        
// Position the range immediately before the node containing the boundary
        
workingNode doc.createElement("span");

        
// Making the working element non-empty element persuades IE to consider the TextRange boundary to be within the
        // element rather than immediately before or after it, which is what we want
        
workingNode.innerHTML "&#feff;";

        
// insertBefore is supposed to work like appendChild if the second parameter is null. However, a bug report
        // for IERange suggests that it can crash the browser: http://code.google.com/p/ierange/issues/detail?id=12
        
if (boundaryNode) {
            
boundaryParent.insertBefore(workingNodeboundaryNode);
        } else {
            
boundaryParent.appendChild(workingNode);
        }

        
workingRange.moveToElementText(workingNode);
        
workingRange.collapse(!isStart);

        
// Clean up
        
boundaryParent.removeChild(workingNode);

        
// Move the working range to the text offset, if required
        
if (nodeIsDataNode) {
            
workingRange[isStart "moveStart" "moveEnd"]("character"boundaryOffset);
        }

        return 
workingRange;
    }

    
/*----------------------------------------------------------------------------------------------------------------*/

    
if (api.features.implementsDomRange && (!api.features.implementsTextRange || !api.config.preferTextRange)) {
        
// This is a wrapper around the browser's native DOM Range. It has two aims:
        // - Provide workarounds for specific browser bugs
        // - provide convenient extensions, which are inherited from Rangy's DomRange

        
(function() {
            var 
rangeProto;
            var 
rangeProperties DomRange.rangeProperties;
            var 
canSetRangeStartAfterEnd;

            function 
updateRangeProperties(range) {
                var 
rangeProperties.lengthprop;
                while (
i--) {
                    
prop rangeProperties[i];
                    
range[prop] = range.nativeRange[prop];
                }
            }

            function 
updateNativeRange(rangestartContainerstartOffsetendContainer,endOffset) {
                var 
startMoved = (range.startContainer !== startContainer || range.startOffset != startOffset);
                var 
endMoved = (range.endContainer !== endContainer || range.endOffset != endOffset);

                
// Always set both boundaries for the benefit of IE9 (see issue 35)
                
if (startMoved || endMoved) {
                    
range.setEnd(endContainerendOffset);
                    
range.setStart(startContainerstartOffset);
                }
            }

            function 
detach(range) {
                
range.nativeRange.detach();
                
range.detached true;
                var 
rangeProperties.lengthprop;
                while (
i--) {
                    
prop rangeProperties[i];
                    
range[prop] = null;
                }
            }

            var 
createBeforeAfterNodeSetter;

            
WrappedRange = function(range) {
                if (!
range) {
                    throw new 
Error("Range must be specified");
                }
                
this.nativeRange range;
                
updateRangeProperties(this);
            };

            
DomRange.createPrototypeRange(WrappedRangeupdateNativeRangedetach);

            
rangeProto WrappedRange.prototype;

            
rangeProto.selectNode = function(node) {
                
this.nativeRange.selectNode(node);
                
updateRangeProperties(this);
            };

            
rangeProto.deleteContents = function() {
                
this.nativeRange.deleteContents();
                
updateRangeProperties(this);
            };

            
rangeProto.extractContents = function() {
                var 
frag this.nativeRange.extractContents();
                
updateRangeProperties(this);
                return 
frag;
            };

            
rangeProto.cloneContents = function() {
                return 
this.nativeRange.cloneContents();
            };

            
// TODO: Until I can find a way to programmatically trigger the Firefox bug (apparently long-standing, still
            // present in 3.6.8) that throws "Index or size is negative or greater than the allowed amount" for
            // insertNode in some circumstances, all browsers will have to use the Rangy's own implementation of
            // insertNode, which works but is almost certainly slower than the native implementation.
/*
            rangeProto.insertNode = function(node) {
                this.nativeRange.insertNode(node);
                updateRangeProperties(this);
            };
*/

            
rangeProto.surroundContents = function(node) {
                
this.nativeRange.surroundContents(node);
                
updateRangeProperties(this);
            };

            
rangeProto.collapse = function(isStart) {
                
this.nativeRange.collapse(isStart);
                
updateRangeProperties(this);
            };

            
rangeProto.cloneRange = function() {
                return new 
WrappedRange(this.nativeRange.cloneRange());
            };

            
rangeProto.refresh = function() {
                
updateRangeProperties(this);
            };

            
rangeProto.toString = function() {
                return 
this.nativeRange.toString();
            };

            
// Create test range and node for feature detection

            
var testTextNode document.createTextNode("test");
            
dom.getBody(document).appendChild(testTextNode);
            var 
range document.createRange();

            
/*--------------------------------------------------------------------------------------------------------*/

            // Test for Firefox 2 bug that prevents moving the start of a Range to a point after its current end and
            // correct for it

            
range.setStart(testTextNode0);
            
range.setEnd(testTextNode0);

            try {
                
range.setStart(testTextNode1);
                
canSetRangeStartAfterEnd true;

                
rangeProto.setStart = function(nodeoffset) {
                    
this.nativeRange.setStart(nodeoffset);
                    
updateRangeProperties(this);
                };

                
rangeProto.setEnd = function(nodeoffset) {
                    
this.nativeRange.setEnd(nodeoffset);
                    
updateRangeProperties(this);
                };

                
createBeforeAfterNodeSetter = function(name) {
                    return function(
node) {
                        
this.nativeRange[name](node);
                        
updateRangeProperties(this);
                    };
                };

            } catch(
ex) {


                
canSetRangeStartAfterEnd false;

                
rangeProto.setStart = function(nodeoffset) {
                    try {
                        
this.nativeRange.setStart(nodeoffset);
                    } catch (
ex) {
                        
this.nativeRange.setEnd(nodeoffset);
                        
this.nativeRange.setStart(nodeoffset);
                    }
                    
updateRangeProperties(this);
                };

                
rangeProto.setEnd = function(nodeoffset) {
                    try {
                        
this.nativeRange.setEnd(nodeoffset);
                    } catch (
ex) {
                        
this.nativeRange.setStart(nodeoffset);
                        
this.nativeRange.setEnd(nodeoffset);
                    }
                    
updateRangeProperties(this);
                };

                
createBeforeAfterNodeSetter = function(nameoppositeName) {
                    return function(
node) {
                        try {
                            
this.nativeRange[name](node);
                        } catch (
ex) {
                            
this.nativeRange[oppositeName](node);
                            
this.nativeRange[name](node);
                        }
                        
updateRangeProperties(this);
                    };
                };
            }

            
rangeProto.setStartBefore createBeforeAfterNodeSetter("setStartBefore""setEndBefore");
            
rangeProto.setStartAfter createBeforeAfterNodeSetter("setStartAfter""setEndAfter");
            
rangeProto.setEndBefore createBeforeAfterNodeSetter("setEndBefore""setStartBefore");
            
rangeProto.setEndAfter createBeforeAfterNodeSetter("setEndAfter""setStartAfter");

            
/*--------------------------------------------------------------------------------------------------------*/

            // Test for and correct Firefox 2 behaviour with selectNodeContents on text nodes: it collapses the range to
            // the 0th character of the text node
            
range.selectNodeContents(testTextNode);
            if (
range.startContainer == testTextNode && range.endContainer == testTextNode &&
                    
range.startOffset == && range.endOffset == testTextNode.length) {
                
rangeProto.selectNodeContents = function(node) {
                    
this.nativeRange.selectNodeContents(node);
                    
updateRangeProperties(this);
                };
            } else {
                
rangeProto.selectNodeContents = function(node) {
                    
this.setStart(node0);
                    
this.setEnd(nodeDomRange.getEndOffset(node));
                };
            }

            
/*--------------------------------------------------------------------------------------------------------*/

            // Test for WebKit bug that has the beahviour of compareBoundaryPoints round the wrong way for constants
            // START_TO_END and END_TO_START: https://bugs.webkit.org/show_bug.cgi?id=20738

            
range.selectNodeContents(testTextNode);
            
range.setEnd(testTextNode3);

            var 
range2 document.createRange();
            
range2.selectNodeContents(testTextNode);
            
range2.setEnd(testTextNode4);
            
range2.setStart(testTextNode2);

            if (
range.compareBoundaryPoints(range.START_TO_ENDrange2) == -&
                    
range.compareBoundaryPoints(range.END_TO_STARTrange2) == 1) {
                
// This is the wrong way round, so correct for it


                
rangeProto.compareBoundaryPoints = function(typerange) {
                    
range range.nativeRange || range;
                    if (
type == range.START_TO_END) {
                        
type range.END_TO_START;
                    } else if (
type == range.END_TO_START) {
                        
type range.START_TO_END;
                    }
                    return 
this.nativeRange.compareBoundaryPoints(typerange);
                };
            } else {
                
rangeProto.compareBoundaryPoints = function(typerange) {
                    return 
this.nativeRange.compareBoundaryPoints(typerange.nativeRange || range);
                };
            }

            
/*--------------------------------------------------------------------------------------------------------*/

            // Test for existence of createContextualFragment and delegate to it if it exists
            
if (api.util.isHostMethod(range"createContextualFragment")) {
                
rangeProto.createContextualFragment = function(fragmentStr) {
                    return 
this.nativeRange.createContextualFragment(fragmentStr);
                };
            }

            
/*--------------------------------------------------------------------------------------------------------*/

            // Clean up
            
dom.getBody(document).removeChild(testTextNode);
            
range.detach();
            
range2.detach();
        })();

        
api.createNativeRange = function(doc) {
            
doc doc || document;
            return 
doc.createRange();
        };
    } else if (
api.features.implementsTextRange) {
        
// This is a wrapper around a TextRange, providing full DOM Range functionality using rangy's DomRange as a
        // prototype

        
WrappedRange = function(textRange) {
            
this.textRange textRange;
            
this.refresh();
        };

        
WrappedRange.prototype = new DomRange(document);

        
WrappedRange.prototype.refresh = function() {
            var 
startend;

            
// TextRange's parentElement() method cannot be trusted. getTextRangeContainerElement() works around that.
            
var rangeContainerElement getTextRangeContainerElement(this.textRange);

            if (
textRangeIsCollapsed(this.textRange)) {
                
end start getTextRangeBoundaryPosition(this.textRangerangeContainerElementtruetrue);
            } else {

                
start getTextRangeBoundaryPosition(this.textRangerangeContainerElementtruefalse);
                
end getTextRangeBoundaryPosition(this.textRangerangeContainerElementfalsefalse);
            }

            
this.setStart(start.nodestart.offset);
            
this.setEnd(end.nodeend.offset);
        };

        
DomRange.copyComparisonConstants(WrappedRange);

        
// Add WrappedRange as the Range property of the global object to allow expression like Range.END_TO_END to work
        
var globalObj = (function() { return this; })();
        if (
typeof globalObj.Range == "undefined") {
            
globalObj.Range WrappedRange;
        }

        
api.createNativeRange = function(doc) {
            
doc doc || document;
            return 
doc.body.createTextRange();
        };
    }

    if (
api.features.implementsTextRange) {
        
WrappedRange.rangeToTextRange = function(range) {
            if (
range.collapsed) {
                var 
tr createBoundaryTextRange(new DomPosition(range.startContainerrange.startOffset), true);



                return 
tr;

                
//return createBoundaryTextRange(new DomPosition(range.startContainer, range.startOffset), true);
            
} else {
                var 
startRange createBoundaryTextRange(new DomPosition(range.startContainerrange.startOffset), true);
                var 
endRange createBoundaryTextRange(new DomPosition(range.endContainerrange.endOffset), false);
                var 
textRange dom.getDocument(range.startContainer).body.createTextRange();
                
textRange.setEndPoint("StartToStart"startRange);
                
textRange.setEndPoint("EndToEnd"endRange);
                return 
textRange;
            }
        };
    }

    
WrappedRange.prototype.getName = function() {
        return 
"WrappedRange";
    };

    
api.WrappedRange WrappedRange;

    
api.createRange = function(doc) {
        
doc doc || document;
        return new 
WrappedRange(api.createNativeRange(doc));
    };

    
api.createRangyRange = function(doc) {
        
doc doc || document;
        return new 
DomRange(doc);
    };

    
api.createIframeRange = function(iframeEl) {
        return 
api.createRange(dom.getIframeDocument(iframeEl));
    };

    
api.createIframeRangyRange = function(iframeEl) {
        return 
api.createRangyRange(dom.getIframeDocument(iframeEl));
    };

    
api.addCreateMissingNativeApiListener(function(win) {
        var 
doc win.document;
        if (
typeof doc.createRange == "undefined") {
            
doc.createRange = function() {
                return 
api.createRange(this);
            };
        }
        
doc win null;
    });
});
rangy.createModule("WrappedSelection", function(apimodule) {
    
// This will create a selection object wrapper that follows the Selection object found in the WHATWG draft DOM Range
    // spec (http://html5.org/specs/dom-range.html)

    
api.requireModules( ["DomUtil""DomRange""WrappedRange"] );

    
api.config.checkSelectionRanges true;

    var 
BOOLEAN "boolean",
        
windowPropertyName "_rangySelection",
        
dom api.dom,
        
util api.util,
        
DomRange api.DomRange,
        
WrappedRange api.WrappedRange,
        
DOMException api.DOMException,
        
DomPosition dom.DomPosition,
        
getSelection,
        
selectionIsCollapsed,
        
CONTROL "Control";



    function 
getWinSelection(winParam) {
        return (
winParam || window).getSelection();
    }

    function 
getDocSelection(winParam) {
        return (
winParam || window).document.selection;
    }

    
// Test for the Range/TextRange and Selection features required
    // Test for ability to retrieve selection
    
var implementsWinGetSelection api.util.isHostMethod(window"getSelection"),
        
implementsDocSelection api.util.isHostObject(document"selection");

    var 
useDocumentSelection implementsDocSelection && (!implementsWinGetSelection || api.config.preferTextRange);

    if (
useDocumentSelection) {
        
getSelection getDocSelection;
        
api.isSelectionValid = function(winParam) {
            var 
doc = (winParam || window).documentnativeSel doc.selection;

            
// Check whether the selection TextRange is actually contained within the correct document
            
return (nativeSel.type != "None" || dom.getDocument(nativeSel.createRange().parentElement()) == doc);
        };
    } else if (
implementsWinGetSelection) {
        
getSelection getWinSelection;
        
api.isSelectionValid = function() {
            return 
true;
        };
    } else {
        
module.fail("Neither document.selection or window.getSelection() detected.");
    }

    
api.getNativeSelection getSelection;

    var 
testSelection getSelection();
    var 
testRange api.createNativeRange(document);
    var 
body dom.getBody(document);

    
// Obtaining a range from a selection
    
var selectionHasAnchorAndFocus util.areHostObjects(testSelection, ["anchorNode""focusNode"] &&
                                     
util.areHostProperties(testSelection, ["anchorOffset""focusOffset"]));
    
api.features.selectionHasAnchorAndFocus selectionHasAnchorAndFocus;

    
// Test for existence of native selection extend() method
    
var selectionHasExtend util.isHostMethod(testSelection"extend");
    
api.features.selectionHasExtend selectionHasExtend;

    
// Test if rangeCount exists
    
var selectionHasRangeCount = (typeof testSelection.rangeCount == "number");
    
api.features.selectionHasRangeCount selectionHasRangeCount;

    var 
selectionSupportsMultipleRanges false;
    var 
collapsedNonEditableSelectionsSupported true;

    if (
util.areHostMethods(testSelection, ["addRange""getRangeAt""removeAllRanges"]) &&
            
typeof testSelection.rangeCount == "number" && api.features.implementsDomRange) {

        (function() {
            var 
iframe document.createElement("iframe");
            
body.appendChild(iframe);

            var 
iframeDoc dom.getIframeDocument(iframe);
            
iframeDoc.open();
            
iframeDoc.write("<html><head></head><body>12</body></html>");
            
iframeDoc.close();

            var 
sel dom.getIframeWindow(iframe).getSelection();
            var 
docEl iframeDoc.documentElement;
            var 
iframeBody docEl.lastChildtextNode iframeBody.firstChild;

            
// Test whether the native selection will allow a collapsed selection within a non-editable element
            
var r1 iframeDoc.createRange();
            
r1.setStart(textNode1);
            
r1.collapse(true);
            
sel.addRange(r1);
            
collapsedNonEditableSelectionsSupported = (sel.rangeCount == 1);
            
sel.removeAllRanges();

            
// Test whether the native selection is capable of supporting multiple ranges
            
var r2 r1.cloneRange();
            
r1.setStart(textNode0);
            
r2.setEnd(textNode2);
            
sel.addRange(r1);
            
sel.addRange(r2);

            
selectionSupportsMultipleRanges = (sel.rangeCount == 2);

            
// Clean up
            
r1.detach();
            
r2.detach();

            
body.removeChild(iframe);
        })();
    }

    
api.features.selectionSupportsMultipleRanges selectionSupportsMultipleRanges;
    
api.features.collapsedNonEditableSelectionsSupported collapsedNonEditableSelectionsSupported;

    
// ControlRanges
    
var implementsControlRange falsetestControlRange;

    if (
body && util.isHostMethod(body"createControlRange")) {
        
testControlRange body.createControlRange();
        if (
util.areHostProperties(testControlRange, ["item""add"])) {
            
implementsControlRange true;
        }
    }
    
api.features.implementsControlRange implementsControlRange;

    
// Selection collapsedness
    
if (selectionHasAnchorAndFocus) {
        
selectionIsCollapsed = function(sel) {
            return 
sel.anchorNode === sel.focusNode && sel.anchorOffset === sel.focusOffset;
        };
    } else {
        
selectionIsCollapsed = function(sel) {
            return 
sel.rangeCount sel.getRangeAt(sel.rangeCount 1).collapsed false;
        };
    }

    function 
updateAnchorAndFocusFromRange(selrangebackwards) {
        var 
anchorPrefix backwards "end" "start"focusPrefix backwards "start" "end";
        
sel.anchorNode range[anchorPrefix "Container"];
        
sel.anchorOffset range[anchorPrefix "Offset"];
        
sel.focusNode range[focusPrefix "Container"];
        
sel.focusOffset range[focusPrefix "Offset"];
    }

    function 
updateAnchorAndFocusFromNativeSelection(sel) {
        var 
nativeSel sel.nativeSelection;
        
sel.anchorNode nativeSel.anchorNode;
        
sel.anchorOffset nativeSel.anchorOffset;
        
sel.focusNode nativeSel.focusNode;
        
sel.focusOffset nativeSel.focusOffset;
    }

    function 
updateEmptySelection(sel) {
        
sel.anchorNode sel.focusNode null;
        
sel.anchorOffset sel.focusOffset 0;
        
sel.rangeCount 0;
        
sel.isCollapsed true;
        
sel._ranges.length 0;
    }

    function 
getNativeRange(range) {
        var 
nativeRange;
        if (
range instanceof DomRange) {
            
nativeRange range._selectionNativeRange;
            if (!
nativeRange) {
                
nativeRange api.createNativeRange(dom.getDocument(range.startContainer));
                
nativeRange.setEnd(range.endContainerrange.endOffset);
                
nativeRange.setStart(range.startContainerrange.startOffset);
                
range._selectionNativeRange nativeRange;
                
range.attachListener("detach", function() {

                    
this._selectionNativeRange null;
                });
            }
        } else if (
range instanceof WrappedRange) {
            
nativeRange range.nativeRange;
        } else if (
api.features.implementsDomRange && (range instanceof dom.getWindow(range.startContainer).Range)) {
            
nativeRange range;
        }
        return 
nativeRange;
    }

    function 
rangeContainsSingleElement(rangeNodes) {
        if (!
rangeNodes.length || rangeNodes[0].nodeType != 1) {
            return 
false;
        }
        for (var 
1len rangeNodes.lengthlen; ++i) {
            if (!
dom.isAncestorOf(rangeNodes[0], rangeNodes[i])) {
                return 
false;
            }
        }
        return 
true;
    }

    function 
getSingleElementFromRange(range) {
        var 
nodes range.getNodes();
        if (!
rangeContainsSingleElement(nodes)) {
            throw new 
Error("getSingleElementFromRange: range " range.inspect() + " did not consist of a single element");
        }
        return 
nodes[0];
    }

    function 
isTextRange(range) {
        return !!
range && typeof range.text != "undefined";
    }

    function 
updateFromTextRange(selrange) {
        
// Create a Range from the selected TextRange
        
var wrappedRange = new WrappedRange(range);
        
sel._ranges = [wrappedRange];

        
updateAnchorAndFocusFromRange(selwrappedRangefalse);
        
sel.rangeCount 1;
        
sel.isCollapsed wrappedRange.collapsed;
    }

    function 
updateControlSelection(sel) {
        
// Update the wrapped selection based on what's now in the native selection
        
sel._ranges.length 0;
        if (
sel.docSelection.type == "None") {
            
updateEmptySelection(sel);
        } else {
            var 
controlRange sel.docSelection.createRange();
            if (
isTextRange(controlRange)) {
                
// This case (where the selection type is "Control" and calling createRange() on the selection returns
                // a TextRange) can happen in IE 9. It happens, for example, when all elements in the selected
                // ControlRange have been removed from the ControlRange and removed from the document.
                
updateFromTextRange(selcontrolRange);
            } else {
                
sel.rangeCount controlRange.length;
                var 
rangedoc dom.getDocument(controlRange.item(0));
                for (var 
0sel.rangeCount; ++i) {
                    
range api.createRange(doc);
                    
range.selectNode(controlRange.item(i));
                    
sel._ranges.push(range);
                }
                
sel.isCollapsed sel.rangeCount == && sel._ranges[0].collapsed;
                
updateAnchorAndFocusFromRange(selsel._ranges[sel.rangeCount 1], false);
            }
        }
    }

    function 
addRangeToControlSelection(selrange) {
        var 
controlRange sel.docSelection.createRange();
        var 
rangeElement getSingleElementFromRange(range);

        
// Create a new ControlRange containing all the elements in the selected ControlRange plus the element
        // contained by the supplied range
        
var doc dom.getDocument(controlRange.item(0));
        var 
newControlRange dom.getBody(doc).createControlRange();
        for (var 
0len controlRange.lengthlen; ++i) {
            
newControlRange.add(controlRange.item(i));
        }
        try {
            
newControlRange.add(rangeElement);
        } catch (
ex) {
            throw new 
Error("addRange(): Element within the specified Range could not be added to control selection (does it have layout?)");
        }
        
newControlRange.select();

        
// Update the wrapped selection based on what's now in the native selection
        
updateControlSelection(sel);
    }

    var 
getSelectionRangeAt;

    if (
util.isHostMethod(testSelection,  "getRangeAt")) {
        
getSelectionRangeAt = function(selindex) {
            try {
                return 
sel.getRangeAt(index);
            } catch(
ex) {
                return 
null;
            }
        };
    } else if (
selectionHasAnchorAndFocus) {
        
getSelectionRangeAt = function(sel) {
            var 
doc dom.getDocument(sel.anchorNode);
            var 
range api.createRange(doc);
            
range.setStart(sel.anchorNodesel.anchorOffset);
            
range.setEnd(sel.focusNodesel.focusOffset);

            
// Handle the case when the selection was selected backwards (from the end to the start in the
            // document)
            
if (range.collapsed !== this.isCollapsed) {
                
range.setStart(sel.focusNodesel.focusOffset);
                
range.setEnd(sel.anchorNodesel.anchorOffset);
            }

            return 
range;
        };
    }

    
/**
     * @constructor
     */
    
function WrappedSelection(selectiondocSelectionwin) {
        
this.nativeSelection selection;
        
this.docSelection docSelection;
        
this._ranges = [];
        
this.win win;
        
this.refresh();
    }

    
api.getSelection = function(win) {
        
win win || window;
        var 
sel win[windowPropertyName];
        var 
nativeSel getSelection(win), docSel implementsDocSelection getDocSelection(win) : null;
        if (
sel) {
            
sel.nativeSelection nativeSel;
            
sel.docSelection docSel;
            
sel.refresh(win);
        } else {
            
sel = new WrappedSelection(nativeSeldocSelwin);
            
win[windowPropertyName] = sel;
        }
        return 
sel;
    };

    
api.getIframeSelection = function(iframeEl) {
        return 
api.getSelection(dom.getIframeWindow(iframeEl));
    };

    var 
selProto WrappedSelection.prototype;

    function 
createControlSelection(selranges) {
        
// Ensure that the selection becomes of type "Control"
        
var doc dom.getDocument(ranges[0].startContainer);
        var 
controlRange dom.getBody(doc).createControlRange();
        for (var 
0elrangeCount; ++i) {
            
el getSingleElementFromRange(ranges[i]);
            try {
                
controlRange.add(el);
            } catch (
ex) {
                throw new 
Error("setRanges(): Element within the one of the specified Ranges could not be added to control selection (does it have layout?)");
            }
        }
        
controlRange.select();

        
// Update the wrapped selection based on what's now in the native selection
        
updateControlSelection(sel);
    }

    
// Selecting a range
    
if (!useDocumentSelection && selectionHasAnchorAndFocus && util.areHostMethods(testSelection, ["removeAllRanges""addRange"])) {
        
selProto.removeAllRanges = function() {
            
this.nativeSelection.removeAllRanges();
            
updateEmptySelection(this);
        };

        var 
addRangeBackwards = function(selrange) {
            var 
doc DomRange.getRangeDocument(range);
            var 
endRange api.createRange(doc);
            
endRange.collapseToPoint(range.endContainerrange.endOffset);
            
sel.nativeSelection.addRange(getNativeRange(endRange));
            
sel.nativeSelection.extend(range.startContainerrange.startOffset);
            
sel.refresh();
        };

        if (
selectionHasRangeCount) {
            
selProto.addRange = function(rangebackwards) {
                if (
implementsControlRange && implementsDocSelection && this.docSelection.type == CONTROL) {
                    
addRangeToControlSelection(thisrange);
                } else {
                    if (
backwards && selectionHasExtend) {
                        
addRangeBackwards(thisrange);
                    } else {
                        var 
previousRangeCount;
                        if (
selectionSupportsMultipleRanges) {
                            
previousRangeCount this.rangeCount;
                        } else {
                            
this.removeAllRanges();
                            
previousRangeCount 0;
                        }
                        
this.nativeSelection.addRange(getNativeRange(range));

                        
// Check whether adding the range was successful
                        
this.rangeCount this.nativeSelection.rangeCount;

                        if (
this.rangeCount == previousRangeCount 1) {
                            
// The range was added successfully

                            // Check whether the range that we added to the selection is reflected in the last range extracted from
                            // the selection
                            
if (api.config.checkSelectionRanges) {
                                var 
nativeRange getSelectionRangeAt(this.nativeSelectionthis.rangeCount 1);
                                if (
nativeRange && !DomRange.rangesEqual(nativeRangerange)) {
                                    
// Happens in WebKit with, for example, a selection placed at the start of a text node
                                    
range = new WrappedRange(nativeRange);
                                }
                            }
                            
this._ranges[this.rangeCount 1] = range;
                            
updateAnchorAndFocusFromRange(thisrangeselectionIsBackwards(this.nativeSelection));
                            
this.isCollapsed selectionIsCollapsed(this);
                        } else {
                            
// The range was not added successfully. The simplest thing is to refresh
                            
this.refresh();
                        }
                    }
                }
            };
        } else {
            
selProto.addRange = function(rangebackwards) {
                if (
backwards && selectionHasExtend) {
                    
addRangeBackwards(thisrange);
                } else {
                    
this.nativeSelection.addRange(getNativeRange(range));
                    
this.refresh();
                }
            };
        }

        
selProto.setRanges = function(ranges) {
            if (
implementsControlRange && ranges.length 1) {
                
createControlSelection(thisranges);
            } else {
                
this.removeAllRanges();
                for (var 
0len ranges.lengthlen; ++i) {
                    
this.addRange(ranges[i]);
                }
            }
        };
    } else if (
util.isHostMethod(testSelection"empty") && util.isHostMethod(testRange"select") &&
               
implementsControlRange && useDocumentSelection) {

        
selProto.removeAllRanges = function() {
            
// Added try/catch as fix for issue #21
            
try {
                
this.docSelection.empty();

                
// Check for empty() not working (issue #24)
                
if (this.docSelection.type != "None") {
                    
// Work around failure to empty a control selection by instead selecting a TextRange and then
                    // calling empty()
                    
var doc;
                    if (
this.anchorNode) {
                        
doc dom.getDocument(this.anchorNode);
                    } else if (
this.docSelection.type == CONTROL) {
                        var 
controlRange this.docSelection.createRange();
                        if (
controlRange.length) {
                            
doc dom.getDocument(controlRange.item(0)).body.createTextRange();
                        }
                    }
                    if (
doc) {
                        var 
textRange doc.body.createTextRange();
                        
textRange.select();
                        
this.docSelection.empty();
                    }
                }
            } catch(
ex) {}
            
updateEmptySelection(this);
        };

        
selProto.addRange = function(range) {
            if (
this.docSelection.type == CONTROL) {
                
addRangeToControlSelection(thisrange);
            } else {
                
WrappedRange.rangeToTextRange(range).select();
                
this._ranges[0] = range;
                
this.rangeCount 1;
                
this.isCollapsed this._ranges[0].collapsed;
                
updateAnchorAndFocusFromRange(thisrangefalse);
            }
        };

        
selProto.setRanges = function(ranges) {
            
this.removeAllRanges();
            var 
rangeCount ranges.length;
            if (
rangeCount 1) {
                
createControlSelection(thisranges);
            } else if (
rangeCount) {
                
this.addRange(ranges[0]);
            }
        };
    } else {
        
module.fail("No means of selecting a Range or TextRange was found");
        return 
false;
    }

    
selProto.getRangeAt = function(index) {
        if (
index || index >= this.rangeCount) {
            throw new 
DOMException("INDEX_SIZE_ERR");
        } else {
            return 
this._ranges[index];
        }
    };

    var 
refreshSelection;

    if (
useDocumentSelection) {
        
refreshSelection = function(sel) {
            var 
range;
            if (
api.isSelectionValid(sel.win)) {
                
range sel.docSelection.createRange();
            } else {
                
range dom.getBody(sel.win.document).createTextRange();
                
range.collapse(true);
            }


            if (
sel.docSelection.type == CONTROL) {
                
updateControlSelection(sel);
            } else if (
isTextRange(range)) {
                
updateFromTextRange(selrange);
            } else {
                
updateEmptySelection(sel);
            }
        };
    } else if (
util.isHostMethod(testSelection"getRangeAt") && typeof testSelection.rangeCount == "number") {
        
refreshSelection = function(sel) {
            if (
implementsControlRange && implementsDocSelection && sel.docSelection.type == CONTROL) {
                
updateControlSelection(sel);
            } else {
                
sel._ranges.length sel.rangeCount sel.nativeSelection.rangeCount;
                if (
sel.rangeCount) {
                    for (var 
0len sel.rangeCountlen; ++i) {
                        
sel._ranges[i] = new api.WrappedRange(sel.nativeSelection.getRangeAt(i));
                    }
                    
updateAnchorAndFocusFromRange(selsel._ranges[sel.rangeCount 1], selectionIsBackwards(sel.nativeSelection));
                    
sel.isCollapsed selectionIsCollapsed(sel);
                } else {
                    
updateEmptySelection(sel);
                }
            }
        };
    } else if (
selectionHasAnchorAndFocus && typeof testSelection.isCollapsed == BOOLEAN && typeof testRange.collapsed == BOOLEAN && api.features.implementsDomRange) {
        
refreshSelection = function(sel) {
            var 
rangenativeSel sel.nativeSelection;
            if (
nativeSel.anchorNode) {
                
range getSelectionRangeAt(nativeSel0);
                
sel._ranges = [range];
                
sel.rangeCount 1;
                
updateAnchorAndFocusFromNativeSelection(sel);
                
sel.isCollapsed selectionIsCollapsed(sel);
            } else {
                
updateEmptySelection(sel);
            }
        };
    } else {
        
module.fail("No means of obtaining a Range or TextRange from the user's selection was found");
        return 
false;
    }

    
selProto.refresh = function(checkForChanges) {
        var 
oldRanges checkForChanges this._ranges.slice(0) : null;
        
refreshSelection(this);
        if (
checkForChanges) {
            var 
oldRanges.length;
            if (
!= this._ranges.length) {
                return 
false;
            }
            while (
i--) {
                if (!
DomRange.rangesEqual(oldRanges[i], this._ranges[i])) {
                    return 
false;
                }
            }
            return 
true;
        }
    };

    
// Removal of a single range
    
var removeRangeManually = function(selrange) {
        var 
ranges sel.getAllRanges(), removed false;
        
sel.removeAllRanges();
        for (var 
0len ranges.lengthlen; ++i) {
            if (
removed || range !== ranges[i]) {
                
sel.addRange(ranges[i]);
            } else {
                
// According to the draft WHATWG Range spec, the same range may be added to the selection multiple
                // times. removeRange should only remove the first instance, so the following ensures only the first
                // instance is removed
                
removed true;
            }
        }
        if (!
sel.rangeCount) {
            
updateEmptySelection(sel);
        }
    };

    if (
implementsControlRange) {
        
selProto.removeRange = function(range) {
            if (
this.docSelection.type == CONTROL) {
                var 
controlRange this.docSelection.createRange();
                var 
rangeElement getSingleElementFromRange(range);

                
// Create a new ControlRange containing all the elements in the selected ControlRange minus the
                // element contained by the supplied range
                
var doc dom.getDocument(controlRange.item(0));
                var 
newControlRange dom.getBody(doc).createControlRange();
                var 
elremoved false;
                for (var 
0len controlRange.lengthlen; ++i) {
                    
el controlRange.item(i);
                    if (
el !== rangeElement || removed) {
                        
newControlRange.add(controlRange.item(i));
                    } else {
                        
removed true;
                    }
                }
                
newControlRange.select();

                
// Update the wrapped selection based on what's now in the native selection
                
updateControlSelection(this);
            } else {
                
removeRangeManually(thisrange);
            }
        };
    } else {
        
selProto.removeRange = function(range) {
            
removeRangeManually(thisrange);
        };
    }

    
// Detecting if a selection is backwards
    
var selectionIsBackwards;
    if (!
useDocumentSelection && selectionHasAnchorAndFocus && api.features.implementsDomRange) {
        
selectionIsBackwards = function(sel) {
            var 
backwards false;
            if (
sel.anchorNode) {
                
backwards = (dom.comparePoints(sel.anchorNodesel.anchorOffsetsel.focusNodesel.focusOffset) == 1);
            }
            return 
backwards;
        };

        
selProto.isBackwards = function() {
            return 
selectionIsBackwards(this);
        };
    } else {
        
selectionIsBackwards selProto.isBackwards = function() {
            return 
false;
        };
    }

    
// Selection text
    // This is conformant to the new WHATWG DOM Range draft spec but differs from WebKit and Mozilla's implementation
    
selProto.toString = function() {

        var 
rangeTexts = [];
        for (var 
0len this.rangeCountlen; ++i) {
            
rangeTexts[i] = "" this._ranges[i];
        }
        return 
rangeTexts.join("");
    };

    function 
assertNodeInSameDocument(selnode) {
        if (
sel.anchorNode && (dom.getDocument(sel.anchorNode) !== dom.getDocument(node))) {
            throw new 
DOMException("WRONG_DOCUMENT_ERR");
        }
    }

    
// No current browsers conform fully to the HTML 5 draft spec for this method, so Rangy's own method is always used
    
selProto.collapse = function(nodeoffset) {
        
assertNodeInSameDocument(thisnode);
        var 
range api.createRange(dom.getDocument(node));
        
range.collapseToPoint(nodeoffset);
        
this.removeAllRanges();
        
this.addRange(range);
        
this.isCollapsed true;
    };

    
selProto.collapseToStart = function() {
        if (
this.rangeCount) {
            var 
range this._ranges[0];
            
this.collapse(range.startContainerrange.startOffset);
        } else {
            throw new 
DOMException("INVALID_STATE_ERR");
        }
    };

    
selProto.collapseToEnd = function() {
        if (
this.rangeCount) {
            var 
range this._ranges[this.rangeCount 1];
            
this.collapse(range.endContainerrange.endOffset);
        } else {
            throw new 
DOMException("INVALID_STATE_ERR");
        }
    };

    
// The HTML 5 spec is very specific on how selectAllChildren should be implemented so the native implementation is
    // never used by Rangy.
    
selProto.selectAllChildren = function(node) {
        
assertNodeInSameDocument(thisnode);
        var 
range api.createRange(dom.getDocument(node));
        
range.selectNodeContents(node);
        
this.removeAllRanges();
        
this.addRange(range);
    };

    
selProto.deleteFromDocument = function() {
        
// Sepcial behaviour required for Control selections
        
if (implementsControlRange && implementsDocSelection && this.docSelection.type == CONTROL) {
            var 
controlRange this.docSelection.createRange();
            var 
element;
            while (
controlRange.length) {
                
element controlRange.item(0);
                
controlRange.remove(element);
                
element.parentNode.removeChild(element);
            }
            
this.refresh();
        } else if (
this.rangeCount) {
            var 
ranges this.getAllRanges();
            
this.removeAllRanges();
            for (var 
0len ranges.lengthlen; ++i) {
                
ranges[i].deleteContents();
            }
            
// The HTML5 spec says nothing about what the selection should contain after calling deleteContents on each
            // range. Firefox moves the selection to where the final selected range was, so we emulate that
            
this.addRange(ranges[len 1]);
        }
    };

    
// The following are non-standard extensions
    
selProto.getAllRanges = function() {
        return 
this._ranges.slice(0);
    };

    
selProto.setSingleRange = function(range) {
        
this.setRanges( [range] );
    };

    
selProto.containsNode = function(nodeallowPartial) {
        for (var 
0len this._ranges.lengthlen; ++i) {
            if (
this._ranges[i].containsNode(nodeallowPartial)) {
                return 
true;
            }
        }
        return 
false;
    };

    
selProto.toHtml = function() {
        var 
html "";
        if (
this.rangeCount) {
            var 
container DomRange.getRangeDocument(this._ranges[0]).createElement("div");
            for (var 
0len this._ranges.lengthlen; ++i) {
                
container.appendChild(this._ranges[i].cloneContents());
            }
            
html container.innerHTML;
        }
        return 
html;
    };

    function 
inspect(sel) {
        var 
rangeInspects = [];
        var 
anchor = new DomPosition(sel.anchorNodesel.anchorOffset);
        var 
focus = new DomPosition(sel.focusNodesel.focusOffset);
        var 
name = (typeof sel.getName == "function") ? sel.getName() : "Selection";

        if (
typeof sel.rangeCount != "undefined") {
            for (var 
0len sel.rangeCountlen; ++i) {
                
rangeInspects[i] = DomRange.inspect(sel.getRangeAt(i));
            }
        }
        return 
"[" name "(Ranges: " rangeInspects.join(", ") +
                
")(anchor: " anchor.inspect() + ", focus: " focus.inspect() + "]";

    }

    
selProto.getName = function() {
        return 
"WrappedSelection";
    };

    
selProto.inspect = function() {
        return 
inspect(this);
    };

    
selProto.detach = function() {
        
this.win[windowPropertyName] = null;
        
this.win this.anchorNode this.focusNode null;
    };

    
WrappedSelection.inspect inspect;

    
api.Selection WrappedSelection;

    
api.selectionPrototype selProto;

    
api.addCreateMissingNativeApiListener(function(win) {
        if (
typeof win.getSelection == "undefined") {
            
win.getSelection = function() {
                return 
api.getSelection(this);
            };
        }
        
win null;
    });
});
?>
Онлайн: 1
Реклама