jquery-ui实现一个多tab标签页可拖动编辑器记事本效果代码

代码语言:html

所属分类:其他

代码描述:jquery-ui实现一个多tab标签页可拖动编辑器效果代码

代码标签: jquery-ui tab 标签页 编辑器 记事本 拖动

下面为部分代码预览,完整代码请点击下载或在bfwstudio webide中打开

<!DOCTYPE html>

<html lang="en">

<head>

    <meta charset="UTF-8">



    <style>
    
    @font-face {
	font-family: 'icomoon';

	src:url('data:application/font-woff;base64,d09GRk9UVE8AAAdMAAoAAAAABwQAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAABDRkYgAAAA9AAAA8AAAAPAUtNf6E9TLzIAAAS0AAAAYAAAAGAIQvy+Y21hcAAABRQAAABEAAAAROYaAIdnYXNwAAAFWAAAAAgAAAAIAAAAEGhlYWQAAAVgAAAANgAAADb+9fzjaGhlYQAABZgAAAAkAAAAJAPiAexobXR4AAAFvAAAACwAAAAsEwABAG1heHAAAAXoAAAABgAAAAYAC1AAbmFtZQAABfAAAAE5AAABOUQYtNZwb3N0AAAHLAAAACAAAAAgAAMAAAEABAQAAQEBCGljb21vb24AAQIAAQA8+BwC+BsD+BgEHgoAGVMSX4uLHgoAGVMSX4uLDAeLa/iU+JQFHQAAAKIPHQAAAKcRHQAAAAkdAAADtxIADAEBCA8SFxwhJiswNTo/aWNvbW9vbmljb21vb251MjB1RTYwMHVFNjAxdUU2MDJ1RTYwM3VFNjA0dUU2MDV1RTYwNnVFNjA3dUU2MDgAAAIBiQAJAAsCAAEABAAHAF0AtAEPAXYBzAH4AmICkQL2/JQO+5QOi/gUFfiUiwWLawX8lIsFi2sV9/SLBYtrBfv0iwWLaxX4lIsFi2sF/JSLBYtrFff0iwWLawX79IsFi2sV+JSLBYtrBfyUiwWLaxX39IsFi2sF+/SLBQ6L+BQV+JSLBYtrBfyUiwXraxX31IsFi2sF+9SLBYsrFffUiwWLawX71IsFiysV99SLBYtrBfvUiwUr93QV+JSLBYtrBfyUiwWLKxX4lIsFi2sF/JSLBQ6L+BQV+JSLBYtrBfyUiwX3NGsV9/SLBYtrBfv0iwX7NGsV+JSLBYtrBfyUiwX3NGsV9/SLBYtrBfv0iwX7NGsV+JSLBYtrBfyUiwX3NGsV9/SLBYtrBfv0iwUO9/b3ghWeoZaoi6oIi9JSxESLCPs0iwWL/FQF91SLBdKLxMSL0giLuXK0ZqEI+zb3JhW+iwWni6Jui2gIi2h0bm+LCFiLBYv3FAXb+9QVPIsFi/cUBduLBaiLo26LaAiLaHNubosIDvf0+FQVy4sFi/tkBYs7Q0sziwgzi0PLi9sIi/dkBcuLBYv7ZAWLd5R4nHwInXqkgqaLCKaLpJSdnAicmpSei58Ii/dkBfuU/BQV99SLBYtLBfvUiwUO+FT4VBWLawVLiwX7NPwUBcuLBYtrBft0iwWLqwXLiwX3NPgUBUuLBYurBQ74hPe0FftEiwWL90QFi5SEkoKLCCuLBYKLhISLggiL+0QF+0SLBYKLhISLggiLKwWLgpKElIsI90SLBYv7RAWLgpKElIsI64sFlIuSkouUCIv3RAX3RIsFlIuSkouUCIvrBYuUhJKCiwgOi/ekFYsrBYuCkoSUiwj4dIsFlIuSkouUCIvrBYuUhJKCiwj8dIsFgouEhIuCCA73VPdPFYuL8cjEcwiXnZeflp8IU5lBf4uLCIuL6sTFeQiXoJaglp4IXY5ehIuLCIuLx6/BkQicppuhmZYI+6yL+zv71Ev7VAiriwXr9zQFi4ura+urCKKToqChpwhTmUB+i4sIDviUFPiUFYsMCgADAgABkAAFAAABTAFmAAAARwFMAWYAAAD1ABkAhAAAAAAAAAAAAAAAAAAAAAEQAAAAAAAAAAAAAAAAAAAAAEAAIOYIAeD/4P/gAeAAIAAAAAEAAAAAAAAAAAAAACAAAAAAAAIAAAADAAAAFAADAAEAAAAUAAQAMAAAAAgACAACAAAAIOYI//3//wAAACDmAP/9////4RoCAAMAAQAAAAAAAAAAAAEAAf//AA8AAQAAAAEAAEkagQJfDzz1AAsCAAAAAADO614TAAAAAM7rXhMAAP/gAgAB4AAAAAgAAgAAAAAAAAABAAAB4P/gAAACAAAAAAACAAABAAAAAAAAAAAAAAAAAAAACwAAAAABAAAAAgAAAAIAAAACAAAAAgAAYAIAAGACAABAAgAAAAIAAAACAAAAAABQAAALAAAAAAAOAK4AAQAAAAAAAQAOAAAAAQAAAAAAAgAOAEcAAQAAAAAAAwAOACQAAQAAAAAABAAOAFUAAQAAAAAABQAWAA4AAQAAAAAABgAHADIAAQAAAAAACgAoAGMAAwABBAkAAQAOAAAAAwABBAkAAgAOAEcAAwABBAkAAwAOACQAAwABBAkABAAOAFUAAwABBAkABQAWAA4AAwABBAkABgAOADkAAwABBAkACgAoAGMAaQBjAG8AbQBvAG8AbgBWAGUAcgBzAGkAbwBuACAAMQAuADAAaQBjAG8AbQBvAG8Abmljb21vb24AaQBjAG8AbQBvAG8AbgBSAGUAZwB1AGwAYQByAGkAYwBvAG0AbwBvAG4ARwBlAG4AZQByAGEAdABlAGQAIABiAHkAIABJAGMAbwBNAG8AbwBuAAAAAAMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=') format('woff'),
		url('') format('truetype'),
		url('') format('svg');
	font-weight: normal;
	font-style: normal;
}

[class^="icon-"], [class*=" icon-"] {
	font-family: 'icomoon';
	speak: none;
	font-style: normal;
	font-weight: normal;
	font-variant: normal;
	text-transform: none;
	line-height: 1;

	/* Better Font Rendering =========== */
	-webkit-font-smoothing: antialiased;
	-moz-osx-font-smoothing: grayscale;
}

.icon-paragraph-left:before {
	content: "\e600";
}
.icon-paragraph-center:before {
	content: "\e601";
}
.icon-paragraph-right:before {
	content: "\e602";
}
.icon-bold:before {
	content: "\e603";
}
.icon-underline:before {
	content: "\e604";
}
.icon-italic:before {
	content: "\e605";
}
.icon-plus:before {
	content: "\e606";
}
.icon-minus:before {
	content: "\e607";
}
.icon-quill:before {
	content: "\e608";
}
        @import url(https://fonts.googleapis.com/css?family=Telex);
        * {
          -webkit-box-sizing: border-box;
          -moz-box-sizing: border-box;
          box-sizing: border-box;
          -webkit-font-smoothing: antialiased;
        }
        
        body {
          background: #5deaba;
        }
        
        .launch {
          font-family: "Telex", sans-serif;
          text-decoration: none;
          width: 240px;
          height: 60px;
          display: block;
          text-align: center;
          line-height: 38px;
          border-radius: 3px;
          font-size: 60px;
          color: #fff;
          position: absolute;
          left: 50%;
          top: 50%;
          margin: -30px 0 0 -120px;
          display: none;
        }
        
        .texteditor {
          width: 550px;
          height: 450px;
          position: absolute;
          left: 50%;
          top: 50%;
          background: #fff;
          margin: -205px 0 0 -275px;
          border-radius: 0 0 10px 10px;
          box-shadow: 0px -12px 200px #20b080;
        }
        .texteditor .ops {
          height: 288px;
          width: 50px;
          background: #ebebeb;
          position: absolute;
          right: 0px;
          border-radius: 0 10px 10px 0;
          z-index: 1;
          display: none;
        }
        .texteditor .ops ul {
          list-style: none;
          margin: 0;
          padding: 0;
        }
        .texteditor .ops ul li {
          text-align: center;
          padding: 10px 0;
        }
        .texteditor .ops ul li:first-child {
          border-radius: 0 10px 0 0;
        }
        .texteditor .ops ul li:last-child {
          border-radius: 0 0 10px 0;
        }
        .texteditor .ops ul li:hover {
          background: #31d687;
        }
        .texteditor .ops ul li:hover a {
          color: #fff;
        }
        .texteditor .ops ul li a {
          text-decoration: none;
          color: grey;
          display: block;
        }
        .texteditor .textareawrap .fontsize {
          background: #5deaba;
          position: absolute;
          z-index: 400;
          bottom: 5px;
          left: 5px;
          padding: 5px 10px;
          font-family: "Telex", sans-serif;
          color: #fff;
          font-size: 16px;
          border-radius: 0 0 0 5px;
          display: none;
        }
        .texteditor .textareawrap .textarea {
          display: none;
          margin: 0;
          padding: 10px;
          width: 100%;
          height: 100%;
          position: absolute;
          border: 0;
          outline: none;
          resize: none;
          overflow: auto;
          border-radius: 0 0 10px 10px;
          font-family: "Telex", sans-serif;
          font-size: 13px;
          color: #666;
          background: #fff;
          z-index: 2;
        }
        .texteditor .top {
          font-family: "Telex", sans-serif;
          position: absolute;
          height: 45px;
          background: #ebebeb;
          top: -45px;
          width: 100%;
          border-radius: 10px 10px 0 0;
          border-bottom: 1px solid #bababa;
          cursor: move;
          overflow: hidden;
        }
        .texteditor .top .icon-quill {
          font-size: 25px;
          float: right;
          margin: 0 10px 0 0;
          color: grey;
          text-decoration: none;
        }
        .texteditor .top .icon-quill:hover {
          color: #31d687;
        }
        .texteditor ul.controls {
          padding: 0;
          margin: 16px 0 0 15px;
          list-style: none;
          float: left;
          width: 65px;
        }
        .texteditor ul.controls li {
          width: 15px;
          height: 15px;
          float: left;
          margin: 0 4%;
          border-radius: 50%;
          cursor: pointer;
        }
        .texteditor ul.controls li:after {
          content: "";
          background: linear-gradient(225deg, #ffffff 50%, transparent 50%);
          width: 15px;
          height: 15px;
          position: absolute;
          z-index: 2;
          border-radius: 50%;
          opacity: 0.4;
        }
        .texteditor ul.controls .close {
          background: #e57069;
        }
        .texteditor ul.controls .min {
          background: #f5ce56;
        }
        .texteditor ul.controls .expand {
          background: #31d687;
        }
        .texteditor ul.tabs {
          margin: 9px 0 0 100px;
          padding: 0;
          list-style: none;
        }
        .texteditor ul.tabs .addtab {
          font-size: 22px;
          z-index: 305;
          color: grey;
          position: relative;
          cursor: pointer;
          float: left;
          margin: 0 0 0 5px;
        }
        .texteditor ul.tabs .addtab:hover {
          color: #31d687;
        }
        .texteditor ul.tabs li {
          float: left;
          width: auto;
          margin: 0 -5px;
          padding: 0 15px;
          border-bottom: 35px solid #dad8d8;
          border-right: 20px solid transparent;
          border-left: 20px solid transparent;
          height: 0;
          line-height: 37px;
          position: relative;
          z-index: 2;
          cursor: pointer;
        }
        .texteditor ul.tabs li span {
          position: absolute;
          top: -12px;
          left: 2px;
          display: none;
          width: 17px;
          height: 17px;
          border-radius: 50%;
          top: -5px;
          left: -7px;
          text-align: center;
          line-height: 16px;
          background: #e57069;
          font-size: 20px;
          z-index: 3;
        }
        .texteditor ul.tabs li span:after {
          content: "";
          background: linear-gradient(225deg, #ffffff 50%, transparent 50%);
          width: 15px;
          height: 15px;
          position: absolute;
          z-index: 2;
          border-radius: 50%;
          opacity: 0.2;
          left: 2px;
        }
        .texteditor ul.tabs li a {
          text-decoration: none;
          color: #fff;
        }
        
        .ui-resizable-e {
          right: 0;
          position: absolute;
          width: 5px;
          height: 100%;
        }
        
        .ui-resizable-s {
          bottom: 0;
          height: 5px;
          width: 100%;
          position: absolute;
        }
        
        .ui-icon-gripsmall-diagonal-se {
          z-index: 90;
          bottom: 0;
          right: 0;
          position: absolute;
          width: 10px;
          height: 10px;
        }
        
        .ui-resizable-s:hover {
          cursor: ns-resize;
        }
        
        .ui-resizable-e:hover {
          cursor: ew-resize;
        }
        
        .ui-icon-gripsmall-diagonal-se:hover {
          cursor: nwse-resize;
        }
        
        .active {
          border-bottom-color: #bababa !important;
          z-index: 300 !important;
        }
        
        .bold {
          font-weight: bold;
        }
        
        .italic {
          font-style: italic;
        }
        
        .underline {
          text-decoration: underline;
        }
        
        .leftalign {
          text-align: left;
          display: inline;
        }
        
        .rightalign {
          text-align: right;
          display: block;
        }
        
        .centeralign {
          text-align: center;
          display: block;
        }
    </style>



</head>

<body>

    <a href="#" class="launch"> LAUNCH </a>
    <div class="texteditor">
        <div class="top">
            <ul class="controls">
                <li class="close">
                    <a href="#"></a>
                </li>
                <li class="min">
                    <a href="#"></a>
                </li>
                <li class="expand">
                    <a href="#"></a>
                </li>
            </ul>
            <ul class="tabs">
                <span class="tabwrap">
      </span>
            </ul>
            <a href="#" class="icon-quill"></a>
        </div>
        <div class="textareawrap">
            <span class="fontsize"></span>
        </div>
        <aside class="ops">
            <ul>
                <li>
                    <a href="#" class="icon-bold"></a>
                </li>
                <li>
                    <a class="icon-underline" href="#"></a>
                </li>
                <li>
                    <a class="icon-italic" href="#"></a>
                </li>
                <li>
                    <a class="icon-paragraph-left" href="#"></a>
                </li>
                <li>
                    <a class="icon-paragraph-right" href="#"></a>
                </li>
                <li>
                    <a class="icon-paragraph-center" href="#"></a>
                </li>
                <li>
                    <a class="icon-plus" href="#"></a>
                </li>
                <li>
                    <a class="icon-minus" href="#"></a>
                </li>
            </ul>
        </aside>
    </div>
    <script type="text/javascript" src="//repo.bfw.wiki/bfwrepo/js/jquery.2.11.js"></script>
    <script type="text/javascript" src="//repo.bfw.wiki/bfwrepo/js/jquery-ui-1.10.3.min.js"></script>
    <script>
        (function (factory, root) {
          if (typeof define == "function" && define.amd) {
            // AMD. Register as an anonymous module.
            define(factory);
          } else if (typeof module != "undefined" && typeof exports == "object") {
            // Node/CommonJS style
            module.exports = factory();
          } else {
            // No AMD or CommonJS support so we place Rangy in (probably) the global variable
            root.rangy = factory();
          }
        })(function () {
        
          var OBJECT = "object",FUNCTION = "function",UNDEFINED = "undefined";
        
          // Minimal set of properties required for DOM Level 2 Range compliance. Comparison constants such as START_TO_START
          // are omitted because ranges in KHTML do not have them but otherwise work perfectly well. See issue 113.
          var domRangeProperties = ["startContainer", "startOffset", "endContainer", "endOffset", "collapsed",
          "commonAncestorContainer"];
        
          // Minimal set of methods required for DOM Level 2 Range compliance
          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", "moveToElementText", "parentElement", "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(o, p) {
            var t = typeof o[p];
            return t == FUNCTION || !!(t == OBJECT && o[p]) || t == "unknown";
          }
        
          function isHostObject(o, p) {
            return !!(typeof o[p] == OBJECT && o[p]);
          }
        
          function isHostProperty(o, p) {
            return typeof o[p] != UNDEFINED;
          }
        
          // Creates a convenience function to save verbose repeated calls to tests functions
          function createMultiplePropertyTest(testFunc) {
            return function (o, props) {
              var i = props.length;
              while (i--) {if (window.CP.shouldStopExecution(0)) break;
                if (!testFunc(o, props[i])) {
                  return false;
                }
              }window.CP.exitedLoop(0);
              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(range, textRangeMethods) && areHostProperties(range, textRangeProperties);
          }
        
          function getBody(doc) {
            return isHostObject(doc, "body") ? doc.body : doc.getElementsByTagName("body")[0];
          }
        
          var forEach = [].forEach ?
          function (arr, func) {
            arr.forEach(func);
          } :
          function (arr, func) {
            for (var i = 0, len = arr.length; i < len; ++i) {if (window.CP.shouldStopExecution(1)) break;
              func(arr[i], i);
            }window.CP.exitedLoop(1);
          };
        
          var modules = {};
        
          var isBrowser = typeof window != UNDEFINED && typeof document != UNDEFINED;
        
          var util = {
            isHostMethod: isHostMethod,
            isHostObject: isHostObject,
            isHostProperty: isHostProperty,
            areHostMethods: areHostMethods,
            areHostObjects: areHostObjects,
            areHostProperties: areHostProperties,
            isTextRange: isTextRange,
            getBody: getBody,
            forEach: forEach };
        
        
          var api = {
            version: "1.3.1-dev",
            initialized: false,
            isBrowser: isBrowser,
            supported: true,
            util: util,
            features: {},
            modules: modules,
            config: {
              alertOnFail: false,
              alertOnWarn: false,
              preferTextRange: false,
              autoInitialize: typeof rangyAutoInitialize == UNDEFINED ? true : rangyAutoInitialize } };
        
        
        
          function consoleLog(msg) {
            if (typeof console != UNDEFINED && isHostMethod(console, "log")) {
              console.log(msg);
            }
          }
        
          function alertOrLog(msg, shouldAlert) {
            if (isBrowser && shouldAlert) {
              alert(msg);
            } else {
              consoleLog(msg);
            }
          }
        
          function fail(reason) {
            api.initialized = true;
            api.supported = false;
            alertOrLog("Rangy is not supported in this environment. Reason: " + reason, api.config.alertOnFail);
          }
        
          api.fail = fail;
        
          function warn(msg) {
            alertOrLog("Rangy warning: " + msg, api.config.alertOnWarn);
          }
        
          api.warn = warn;
        
          // Add utility extend() method
          var extend;
          if ({}.hasOwnProperty) {
            util.extend = extend = function (obj, props, deep) {
              var o, p;
              for (var i in props) {
                if (props.hasOwnProperty(i)) {
                  o = obj[i];
                  p = props[i];
                  if (deep && o !== null && typeof o == "object" && p !== null && typeof p == "object") {
                    extend(o, p, true);
                  }
                  obj[i] = p;
                }
              }
              // Special case for toString, which does not show up in for...in loops in IE <= 8
              if (props.hasOwnProperty("toString")) {
                obj.toString = props.toString;
              }
              return obj;
            };
        
            util.createOptions = function (optionsParam, defaults) {
              var options = {};
              extend(options, defaults);
              if (optionsParam) {
                extend(options, optionsParam);
              }
              return options;
            };
          } else {
            fail("hasOwnProperty not supported");
          }
        
          // Test whether we're in a browser and bail out if not
          if (!isBrowser) {
            fail("Rangy can only run in a browser");
          }
        
          // Test whether Array.prototype.slice can be relied on for NodeLists and use an alternative toArray() if not
          (function () {
            var toArray;
        
            if (isBrowser) {
              var el = document.createElement("div");
              el.appendChild(document.createElement("span"));
              var slice = [].slice;
              try {
                if (slice.call(el.childNodes, 0)[0].nodeType == 1) {
                  toArray = function (arrayLike) {
                    return slice.call(arrayLike, 0);
                  };
                }
              } catch (e) {}
            }
        
            if (!toArray) {
              toArray = function (arrayLike) {
                var arr = [];
                for (var i = 0, len = arrayLike.length; i < len; ++i) {if (window.CP.shouldStopExecution(2)) break;
                  arr[i] = arrayLike[i];
                }window.CP.exitedLoop(2);
                return arr;
              };
            }
        
            util.toArray = toArray;
          })();
        
          // Very simple event handler wrapper function that doesn't attempt to solve issues such as "this" handling or
          // normalization of event properties
          var addListener;
          if (isBrowser) {
            if (isHostMethod(document, "addEventListener")) {
              addListener = function (obj, eventType, listener) {
                obj.addEventListener(eventType, listener, false);
              };
            } else if (isHostMethod(document, "attachEvent")) {
              addListener = function (obj, eventType, listener) {
                obj.attachEvent("on" + eventType, listener);
              };
            } else {
              fail("Document does not have required addEventListener or attachEvent method");
            }
        
            util.addListener = addListener;
          }
        
          var initListeners = [];
        
          function getErrorDesc(ex) {
            return ex.message || ex.description || String(ex);
          }
        
          // Initialization
          function init() {
            if (!isBrowser || api.initialized) {
              return;
            }
            var testRange;
            var implementsDomRange = false,implementsTextRange = false;
        
            // First, perform basic feature tests
        
            if (isHostMethod(document, "createRange")) {
              testRange = document.createRange();
              if (areHostMethods(testRange, domRangeMethods) && areHostProperties(testRange, domRangeProperties)) {
                implementsDomRange = true;
              }
            }
        
            var body = getBody(document);
            if (!body || body.nodeName.toLowerCase() != "body") {
              fail("No body element found");
              return;
            }
        
            if (body && isHostMethod(body, "createTextRange")) {
              testRange = body.createTextRange();
              if (isTextRange(testRange)) {
                implementsTextRange = true;
              }
            }
        
            if (!implementsDomRange && !implementsTextRange) {
              fail("Neither Range nor TextRange are available");
              return;
            }
        
            api.initialized = true;
            api.features = {
              implementsDomRange: implementsDomRange,
              implementsTextRange: implementsTextRange };
        
        
            // Initialize modules
            var module, errorMessage;
            for (var moduleName in modules) {
              if ((module = modules[moduleName]) instanceof Module) {
                module.init(module, api);
              }
            }
        
            // Call init listeners
            for (var i = 0, len = initListeners.length; i < len; ++i) {if (window.CP.shouldStopExecution(3)) break;
              try {
                initListeners[i](api);
              } catch (ex) {
                errorMessage = "Rangy init listener threw an exception. Continuing. Detail: " + getErrorDesc(ex);
                consoleLog(errorMessage);
              }
            }window.CP.exitedLoop(3);
          }
        
          function deprecationNotice(deprecated, replacement, module) {
            if (module) {
              deprecated += " in module " + module.name;
            }
            api.warn("DEPRECATED: " + deprecated + " is deprecated. Please use " +
            replacement + " instead.");
          }
        
          function createAliasForDeprecatedMethod(owner, deprecated, replacement, module) {
            owner[deprecated] = function () {
              deprecationNotice(deprecated, replacement, module);
              return owner[replacement].apply(owner, util.toArray(arguments));
            };
          }
        
          util.deprecationNotice = deprecationNotice;
          util.createAliasForDeprecatedMethod = createAliasForDeprecatedMethod;
        
          // 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 shimListeners = [];
        
          api.addShimListener = function (listener) {
            shimListeners.push(listener);
          };
        
          function shim(win) {
            win = win || window;
            init();
        
            // Notify listeners
            for (var i = 0, len = shimListeners.length; i < len; ++i) {if (window.CP.shouldStopExecution(4)) break;
              shimListeners[i](win);
            }window.CP.exitedLoop(4);
          }
        
          if (isBrowser) {
            api.shim = api.createMissingNativeApi = shim;
            createAliasForDeprecatedMethod(api, "createMissingNativeApi", "shim");
          }
        
          function Module(name, dependencies, initializer) {
            this.name = name;
            this.dependencies = dependencies;
            this.initialized = false;
            this.supported = false;
            this.initializer = initializer;
          }
        
          Module.prototype = {
            init: function () {
              var requiredModuleNames = this.dependencies || [];
              for (var i = 0, len = requiredModuleNames.length, requiredModule, moduleName; i < len; ++i) {if (window.CP.shouldStopExecution(5)) break;
                moduleName = requiredModuleNames[i];
        
                requiredModule = modules[moduleName];
                if (!requiredModule || !(requiredModule instanceof Module)) {
                  throw new Error("required module '" + moduleName + "' not found");
                }
        
                requiredModule.init();
        
                if (!requiredModule.supported) {
                  throw new Error("required module '" + moduleName + "' not supported");
                }
              }
        
              // Now run initializer
              window.CP.exitedLoop(5);this.initializer(this);
            },
        
            fail: function (reason) {
              this.initialized = true;
              this.supported = false;
              throw new Error(reason);
            },
        
            warn: function (msg) {
              api.warn("Module " + this.name + ": " + msg);
            },
        
            deprecationNotice: function (deprecated, replacement) {
              api.warn("DEPRECATED: " + deprecated + " in module " + this.name + " is deprecated. Please use " +
              replacement + " instead");
            },
        
            createError: function (msg) {
              return new Error("Error in Rangy " + this.name + " module: " + msg);
            } };
        
        
          function createModule(name, dependencies, initFunc) {
            var newModule = new Module(name, dependencies, function (module) {
              if (!module.initialized) {
                module.initialized = true;
                try {
                  initFunc(api, module);
                  module.supported = true;
                } catch (ex) {
                  var errorMessage = "Module '" + name + "' failed to load: " + getErrorDesc(ex);
                  consoleLog(errorMessage);
                  if (ex.stack) {
                    consoleLog(ex.stack);
                  }
                }
              }
            });
            modules[name] = newModule;
            return newModule;
          }
        
          api.createModule = function (name) {
            // Allow 2 or 3 arguments (second argument is an optional array of dependencies)
            var initFunc, dependencies;
            if (arguments.length == 2) {
              initFunc = arguments[1];
              dependencies = [];
            } else {
              initFunc = arguments[2];
              dependencies = arguments[1];
            }
        
            var module = createModule(name, dependencies, initFunc);
        
            // Initialize the module immediately if the core is already initialized
            if (api.initialized && api.supported) {
              module.init();
            }
          };
        
          api.createCoreModule = function (name, dependencies, initFunc) {
            createModule(name, dependencies, initFunc);
          };
        
          /*----------------------------------------------------------------------------------------------------------------*/
        
          // Ensure rangy.rangePrototype and rangy.selectionPrototype are available immediately
        
          function RangePrototype() {}
          api.RangePrototype = RangePrototype;
          api.rangePrototype = new RangePrototype();
        
          function SelectionPrototype() {}
          api.selectionPrototype = new SelectionPrototype();
        
          /*----------------------------------------------------------------------------------------------------------------*/
        
          // DOM utility methods used by Rangy
          api.createCoreModule("DomUtil", [], function (api, module) {
            var UNDEF = "undefined";
            var util = api.util;
            var getBody = util.getBody;
        
            // 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 (arr, val) {
              var i = arr.length;
              while (i--) {if (window.CP.shouldStopExecution(6)) break;
                if (arr[i] === val) {
                  return true;
                }
              }window.CP.exitedLoop(6);
              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 i = 0;
              while (node = node.previousSibling) {if (window.CP.shouldStopExecution(7)) break;
                ++i;
              }window.CP.exitedLoop(7);
              return i;
            }
        
            function getNodeLength(node) {
              switch (node.nodeType) {
                case 7:
                case 10:
                  return 0;
                case 3:
                case 8:
                  return node.length;
                default:
                  return node.childNodes.length;}
        
            }
        
            function getCommonAncestor(node1, node2) {
              var ancestors = [],n;
              for (n = node1; n; n = n.parentNode) {if (window.CP.shouldStopExecution(8)) break;
                ancestors.push(n);
              }window.CP.exitedLoop(8);
        
              for (n = node2; n; n = n.parentNode) {if (window.CP.shouldStopExecution(9)) break;
                if (arrayContains(ancestors, n)) {
                  return n;
                }
              }window.CP.exitedLoop(9);
        
              return null;
            }
        
            function isAncestorOf(ancestor, descendant, selfIsAncestor) {
              var n = selfIsAncestor ? descendant : descendant.parentNode;
              while (n) {if (window.CP.shouldStopExecution(10)) break;
                if (n === ancestor) {
                  return true;
                } else {
                  n = n.parentNode;
                }
              }window.CP.exitedLoop(10);
              return false;
            }
        
            function isOrIsAncestorOf(ancestor, descendant) {
              return isAncestorOf(ancestor, descendant, true);
            }
        
            function getClosestAncestorIn(node, ancestor, selfIsAncestor) {
              var p,n = selfIsAncestor ? node : node.parentNode;
              while (n) {if (window.CP.shouldStopExecution(11)) break;
                p = n.parentNode;
                if (p === ancestor) {
                  return n;
                }
                n = p;
              }window.CP.exitedLoop(11);
              return null;
            }
        
            function isCharacterDataNode(node) {
              var t = node.nodeType;
              return t == 3 || t == 4 || t == 8; // Text, CDataSection or Comment
            }
        
            function isTextOrCommentNode(node) {
              if (!node) {
                return false;
              }
              var t = node.nodeType;
              return t == 3 || t == 8; // Text or Comment
            }
        
            function insertAfter(node, precedingNode) {
              var nextNode = precedingNode.nextSibling,parent = precedingNode.parentNode;
              if (nextNode) {
                parent.insertBefore(node, nextNode);
              } else {
                parent.appendChild(node);
              }
              return node;
            }
        
            // Note that we cannot use splitText() because it is bugridden in IE 9.
            function splitDataNode(node, index, positionsToPreserve) {
              var newNode = node.cloneNode(false);
              newNode.deleteData(0, index);
              node.deleteData(index, node.length - index);
              insertAfter(newNode, node);
        
              // Preserve positions
              if (positionsToPreserve) {
                for (var i = 0, position; position = positionsToPreserve[i++];) {if (window.CP.shouldStopExecution(12)) break;
                  // Handle case where position was inside the portion of node after the split point
                  if (position.node == node && position.offset > index) {
                    position.node = newNode;
                    position.offset -= index;
                  }
                  // Handle the case where the position is a node offset within node's parent
                  else if (position.node == node.parentNode && position.offset > getNodeIndex(node)) {
                      ++position.offset;
                    }
                }window.CP.exitedLoop(12);
              }
              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 module.createError("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 module.createError("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 module.createError("getIframeDocument: 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 module.createError("getIframeWindow: No Window object found for iframe element");
              }
            }
        
            // This looks bad. Is it worth it?
            function isWindow(obj) {
              return obj && util.isHostMethod(obj, "setTimeout") && util.isHostObject(obj, "document");
            }
        
            function getContentDocument(obj, module, methodName) {
              var doc;
        
              if (!obj) {
                doc = document;
              }
        
              // Test if a DOM node has been passed and obtain a document object for it if so
              else if (util.isHostProperty(obj, "nodeType")) {
                  doc = obj.nodeType == 1 && obj.tagName.toLowerCase() == "iframe" ?
                  getIframeDocument(obj) : getDocument(obj);
                }
        
                // Test if the doc parameter appears to be a Window object
                else if (isWindow(obj)) {
                    doc = obj.document;
                  }
        
              if (!doc) {
                throw module.createError(methodName + "(): Parameter must be a Window object or DOM node");
              }
        
              return doc;
            }
        
            function getRootContainer(node) {
              var parent;
              while (parent = node.parentNode) {if (window.CP.shouldStopExecution(13)) break;
                node = parent;
              }window.CP.exitedLoop(13);
              return node;
            }
        
            function comparePoints(nodeA, offsetA, nodeB, offsetB) {
              // See http://www.w3.org/TR/DOM-Level-2-Traversal-Range/ranges.html#Level-2-Range-Comparing
              var nodeC, root, childA, childB, n;
              if (nodeA == nodeB) {
                // Case 1: nodes are the same
                return offsetA === offsetB ? 0 : offsetA < offsetB ? -1 : 1;
              } else if (nodeC = getClosestAncestorIn(nodeB, nodeA, true)) {
                // Case 2: node C (container B or an ancestor) is a child node of A
                return offsetA <= getNodeIndex(nodeC) ? -1 : 1;
              } else if (nodeC = getClosestAncestorIn(nodeA, nodeB, true)) {
                // Case 3: node C (container A or an ancestor) is a child node of B
                return getNodeIndex(nodeC) < offsetB ? -1 : 1;
              } else {
                root = getCommonAncestor(nodeA, nodeB);
                if (!root) {
                  throw new Error("comparePoints error: nodes have no common ancestor");
                }
        
                // Case 4: containers are siblings or descendants of siblings
                childA = nodeA === root ? root : getClosestAncestorIn(nodeA, root, true);
                childB = nodeB === root ? root : getClosestAncestorIn(nodeB, root, true);
        
                if (childA === childB) {
                  // This shouldn't be possible
                  throw module.createError("comparePoints got to case 4 and childA and childB are the same!");
                } else {
                  n = root.firstChild;
                  while (n) {if (window.CP.shouldStopExecution(14)) break;
                    if (n === childA) {
                      return -1;
                    } else if (n === childB) {
                      return 1;
                    }
                    n = n.nextSibling;
                  }window.CP.exitedLoop(14);
                }
              }
            }
        
            /*----------------------------------------------------------------------------------------------------------------*/
        
            // Test for IE's crash (IE 6/7) or exception (IE >= 8) when a reference to garbage-collected text node is queried
            var crashyTextNodes = false;
        
            function isBrokenNode(node) {
              var n;
              try {
                n = node.parentNode;
                return false;
              } catch (e) {
                return true;
              }
            }
        
            (function () {
              var el = document.createElement("b");
              el.innerHTML = "1";
              var textNode = el.firstChild;
              el.innerHTML = "<br />";
              crashyTextNodes = isBrokenNode(textNode);
        
              api.features.crashyTextNodes = crashyTextNodes;
            })();
        
            /*----------------------------------------------------------------------------------------------------------------*/
        
            function inspectNode(node) {
              if (!node) {
                return "[No node]";
              }
              if (crashyTextNodes && isBrokenNode(node)) {
                return "[Broken node]";
              }
              if (isCharacterDataNode(node)) {
                return '"' + node.data + '"';
              }
              if (node.nodeType == 1) {
                var idAttr = node.id ? ' id="' + node.id + '"' : "";
                return "<" + node.nodeName + idAttr + ">[index:" + getNodeIndex(node) + ",length:" + node.childNodes.length + "][" + (node.innerHTML || "[innerHTML not supported]").slice(0, 25) + "]";
              }
              return node.nodeName;
            }
        
            function fragmentFromNodeChildren(node) {
              var fragment = getDocument(node).createDocumentFragment(),child;
              while (child = node.firstChild) {if (window.CP.shouldStopExecution(15)) break;
                fragment.appendChild(child);
              }window.CP.exitedLoop(15);
              return fragment;
            }
        
            var getComputedStyleProperty;
            if (typeof window.getComputedStyle != UNDEF) {
              getComputedStyleProperty = function (el, propName) {
                return getWindow(el).getComputedStyle(el, null)[propName];
              };
            } else if (typeof document.documentElement.currentStyle != UNDEF) {
              getComputedStyleProperty = function (el, propName) {
                return el.currentStyle ? el.currentStyle[propName] : "";
              };
            } else {
              module.fail("No means of obtaining computed style properties found");
            }
        
            function createTestElement(doc, html, contentEditable) {
              var body = getBody(doc);
              var el = doc.createElement("div");
              el.contentEditable = "" + !!contentEditable;
              if (html) {
                el.innerHTML = html;
              }
        
              // Insert the test element at the start of the body to prevent scrolling to the bottom in iOS (issue #292)
              var bodyFirstChild = body.firstChild;
              if (bodyFirstChild) {
                body.insertBefore(el, bodyFirstChild);
              } else {
                body.appendChild(el);
              }
        
              return el;
            }
        
            function removeNode(node) {
              return node.parentNode.removeChild(node);
            }
        
            function NodeIterator(root) {
              this.root = root;
              this._next = root;
            }
        
            NodeIterator.prototype = {
              _current: null,
        
              hasNext: function () {
                return !!this._next;
              },
        
              next: function () {
                var n = this._current = this._next;
                var child, next;
                if (this._current) {
                  child = n.firstChild;
                  if (child) {
                    this._next = child;
                  } else {
                    next = null;
                    while (n !== this.root && !(next = n.nextSibling)) {if (window.CP.shouldStopExecution(16)) break;
                      n = n.parentNode;
                    }window.CP.exitedLoop(16);
                    this._next = next;
                  }
                }
                return this._current;
              },
        
              detach: function () {
                this._current = this._next = this.root = null;
              } };
        
        
            function createIterator(root) {
              return new NodeIterator(root);
            }
        
            function DomPosition(node, offset) {
              this.node = node;
              this.offset = offset;
            }
        
            DomPosition.prototype = {
              equals: function (pos) {
                return !!pos && this.node === pos.node && this.offset == pos.offset;
              },
        
              inspect: function () {
                return "[DomPosition(" + inspectNode(this.node) + ":" + this.offset + ")]";
              },
        
              toString: function () {
                return this.inspect();
              } };
        
        
            function DOMException(codeName) {
              this.code = this[codeName];
              this.codeName = codeName;
              this.message = "DOMException: " + this.codeName;
            }
        
            DOMException.prototype = {
              INDEX_SIZE_ERR: 1,
              HIERARCHY_REQUEST_ERR: 3,
              WRONG_DOCUMENT_ERR: 4,
              NO_MODIFICATION_ALLOWED_ERR: 7,
              NOT_FOUND_ERR: 8,
              NOT_SUPPORTED_ERR: 9,
              INVALID_STATE_ERR: 11,
              INVALID_NODE_TYPE_ERR: 24 };
        
        
            DOMException.prototype.toString = function () {
              return this.message;
            };
        
            api.dom = {
              arrayContains: arrayContains,
              isHtmlNamespace: isHtmlNamespace,
              parentElement: parentElement,
              getNodeIndex: getNodeIndex,
              getNodeLength: getNodeLength,
              getCommonAncestor: getCommonAncestor,
              isAncestorOf: isAncestorOf,
              isOrIsAncestorOf: isOrIsAncestorOf,
              getClosestAncestorIn: getClosestAncestorIn,
              isCharacterDataNode: isCharacterDataNode,
              isTextOrCommentNode: isTextOrCommentNode,
              insertAfter: insertAfter,
              splitDataNode: splitDataNode,
              getDocument: getDocument,
              getWindow: getWindow,
              getIframeWindow: getIframeWindow,
              getIframeDocument: getIframeDocument,
              getBody: getBody,
              isWindow: isWindow,
              getContentDocument: getContentDocument,
              getRootContainer: getRootContainer,
              comparePoints: comparePoints,
              isBrokenNode: isBrokenNode,
              inspectNode: inspectNode,
              getComputedStyleProperty: getComputedStyleProperty,
              createTestElement: createTestElement,
              removeNode: removeNode,
              fragmentFromNodeChildren: fragmentFromNodeChildren,
              createIterator: createIterator,
              DomPosition: DomPosition };
        
        
            api.DOMException = DOMException;
          });
        
          /*----------------------------------------------------------------------------------------------------------------*/
        
          // Pure JavaScript implementation of DOM Range
          api.createCoreModule("DomRange", ["DomUtil"], function (api, module) {
            var dom = api.dom;
            var util = api.util;
            var DomPosition = dom.DomPosition;
            var DOMException = api.DOMException;
        
            var isCharacterDataNode = dom.isCharacterDataNode;
            var getNodeIndex = dom.getNodeIndex;
            var isOrIsAncestorOf = dom.isOrIsAncestorOf;
            var getDocument = dom.getDocument;
            var comparePoints = dom.comparePoints;
            var splitDataNode = dom.splitDataNode;
            var getClosestAncestorIn = dom.getClosestAncestorIn;
            var getNodeLength = dom.getNodeLength;
            var arrayContains = dom.arrayContains;
            var getRootContainer = dom.getRootContainer;
            var crashyTextNodes = api.features.crashyTextNodes;
        
            var removeNode = dom.removeNode;
        
            /*----------------------------------------------------------------------------------------------------------------*/
        
            // Utility functions
        
            function isNonTextPartiallySelected(node, range) {
              return node.nodeType != 3 && (
              isOrIsAncestorOf(node, range.startContainer) || isOrIsAncestorOf(node, range.endContainer));
            }
        
            function getRangeDocument(range) {
              return range.document || getDocument(range.startContainer);
            }
        
            function getRangeRoot(range) {
              return getRootContainer(range.startContainer);
            }
        
            function getBoundaryBeforeNode(node) {
              return new DomPosition(node.parentNode, getNodeIndex(node));
            }
        
            function getBoundaryAfterNode(node) {
              return new DomPosition(node.parentNode, getNodeIndex(node) + 1);
            }
        
            function insertNodeAtPosition(node, n, o) {
              var firstNodeInserted = node.nodeType == 11 ? node.firstChild : node;
              if (isCharacterDataNode(n)) {
                if (o == n.length) {
                  dom.insertAfter(node, n);
                } else {
                  n.parentNode.insertBefore(node, o == 0 ? n : splitDataNode(n, o));
                }
              } else if (o >= n.childNodes.length) {
                n.appendChild(node);
              } else {
                n.insertBefore(node, n.childNodes[o]);
              }
              return firstNodeInserted;
            }
        
            function rangesIntersect(rangeA, rangeB, touchingIsIntersecting) {
              assertRangeValid(rangeA);
              assertRangeValid(rangeB);
        
              if (getRangeDocument(rangeB) != getRangeDocument(rangeA)) {
                throw new DOMException("WRONG_DOCUMENT_ERR");
              }
        
              var startComparison = comparePoints(rangeA.startContainer, rangeA.startOffset, rangeB.endContainer, rangeB.endOffset),
              endComparison = comparePoints(rangeA.endContainer, rangeA.endOffset, rangeB.startContainer, rangeB.startOffset);
        
              return touchingIsIntersecting ? startComparison <= 0 && endComparison >= 0 : startComparison < 0 && endComparison > 0;
            }
        
            function cloneSubtree(iterator) {
              var partiallySelected;
              for (var node, frag = getRangeDocument(iterator.range).createDocumentFragment(), subIterator; node = iterator.next();) {if (window.CP.shouldStopExecution(17)) break;
                partiallySelected = iterator.isPartiallySelectedSubtree();
                node = node.cloneNode(!partiallySelected);
                if (partiallySelected) {
                  subIterator = iterator.getSubtreeIterator();
                  node.appendChild(cloneSubtree(subIterator));
                  subIterator.detach();
                }
        
                if (node.nodeType == 10) {// DocumentType
                  throw new DOMException("HIERARCHY_REQUEST_ERR");
                }
                frag.appendChild(node);
              }window.CP.exitedLoop(17);
              return frag;
            }
        
            function iterateSubtree(rangeIterator, func, iteratorState) {
              var it, n;
              iteratorState = iteratorState || { stop: false };
              for (var node, subRangeIterator; node = rangeIterator.next();) {if (window.CP.shouldStopExecution(18)) break;
                if (rangeIterator.isPartiallySelectedSubtree()) {
                  if (func(node) === false) {
                    iteratorState.stop = true;
                    return;
                  } else {
                    // 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.
                    subRangeIterator = rangeIterator.getSubtreeIterator();
                    iterateSubtree(subRangeIterator, func, iteratorState);
                    subRangeIterator.detach();
                    if (iteratorState.stop) {
                      return;
                    }
                  }
                } else {
                  // The whole node is selected, so we can use efficient DOM iteration to iterate over the node and its
                  // descendants
                  it = dom.createIterator(node);
                  while (n = it.next()) {if (window.CP.shouldStopExecution(19)) break;
                    if (func(n) === false) {
                      iteratorState.stop = true;
                      return;
                    }
                  }window.CP.exitedLoop(19);
                }
              }window.CP.exitedLoop(18);
            }
        
            function deleteSubtree(iterator) {
              var subIterator;
              while (iterator.next()) {if (window.CP.shouldStopExecution(20)) break;
                if (iterator.isPartiallySelectedSubtree()) {
                  subIterator = iterator.getSubtreeIterator();
                  deleteSubtree(subIterator);
                  subIterator.detach();
                } else {
                  iterator.remove();
                }
              }window.CP.exitedLoop(20);
            }
        
            function extractSubtree(iterator) {
              for (var node, frag = getRangeDocument(iterator.range).createDocumentFragment(), subIterator; node = iterator.next();) {if (window.CP.shouldStopExecution(21)) break;
        
                if (iterator.isPartiallySelectedSubtree()) {
                  node = node.cloneNode(false);
                  subIterator = iterator.getSubtreeIterator();
                  node.appendChild(extractSubtree(subIterator));
                  subIterator.detach();
                } else {
                  iterator.remove();
                }
                if (node.nodeType == 10) {// DocumentType
                  throw new DOMException("HIERARCHY_REQUEST_ERR");
                }
                frag.appendChild(node);
              }window.CP.exitedLoop(21);
              return frag;
            }
        
            function getNodesInRange(range, nodeTypes, filter) {
              var filterNodeTypes = !!(nodeTypes && nodeTypes.length),regex;
              var filterExists = !!filter;
              if (filterNodeTypes) {
                regex = new RegExp("^(" + nodeTypes.join("|") + ")$");
              }
        
              var nodes = [];
              iterateSubtree(new RangeIterator(range, false), function (node) {
                if (filterNodeTypes && !regex.test(node.nodeType)) {
                  return;
                }
                if (filterExists && !filter(node)) {
                  return;
                }
                // Don't include a boundary container if it is a character data node and the range does not contain any
                // of its character data. See issue 190.
                var sc = range.startContainer;
                if (node == sc && isCharacterDataNode(sc) && range.startOffset == sc.length) {
                  return;
                }
        
                var ec = range.endContainer;
                if (node == ec && isCharacterDataNode(ec) && range.endOffset == 0) {
                  return;
                }
        
                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 (https://github.com/timcameronryan/IERange)
        
            function RangeIterator(range, clonePartiallySelectedTextNodes) {
              this.range = range;
              this.clonePartiallySelectedTextNodes = clonePartiallySelectedTextNodes;
        
        
              if (!range.collapsed) {
                this.sc = range.startContainer;
                this.so = range.startOffset;
                this.ec = range.endContainer;
                this.eo = range.endOffset;
                var root = range.commonAncestorContainer;
        
                if (this.sc === this.ec && isCharacterDataNode(this.sc)) {
                  this.isSingleCharacterDataNode = true;
                  this._first = this._last = this._next = this.sc;
                } else {
                  this._first = this._next = this.sc === root && !isCharacterDataNode(this.sc) ?
                  this.sc.childNodes[this.so] : getClosestAncestorIn(this.sc, root, true);
                  this._last = this.ec === root && !isCharacterDataNode(this.ec) ?
                  this.ec.childNodes[this.eo - 1] : getClosestAncestorIn(this.ec, root, true);
                }
              }
            }
        
            RangeIterator.prototype = {
              _current: null,
              _next: null,
              _first: null,
              _last: null,
              isSingleCharacterDataNode: false,
        
              reset: function () {
                this._current = null;
                this._next = this._first;
              },
        
              hasNext: function () {
                return !!this._next;
              },
        
              next: function () {
                // Move to next node
                var current = this._current = this._next;
                if (current) {
                  this._next = current !== this._last ? current.nextSibling : null;
        
                  // Check for partially selected text nodes
                  if (isCharacterDataNode(current) && this.clonePartiallySelectedTextNodes) {
                    if (current === this.ec) {
                      (current = current.cloneNode(true)).deleteData(this.eo, current.length - this.eo);
                    }
                    if (this._current === this.sc) {
                      (current = current.cloneNode(true)).deleteData(0, this.so);
                    }
                  }
                }
        
                return current;
              },
        
              remove: function () {
                var current = this._current,start,end;
        
                if (isCharacterDataNode(current) && (current === this.sc || current === this.ec)) {
                  start = current === this.sc ? this.so : 0;
                  end = current === this.ec ? this.eo : current.length;
                  if (start != end) {
                    current.deleteData(start, end - start);
                  }
                } else {
                  if (current.parentNode) {
                    removeNode(current);
                  } else {
                  }
                }
              },
        
              // Checks if the current node is partially selected
              isPartiallySelectedSubtree: function () {
                var current = this._current;
                return isNonTextPartiallySelected(current, this.range);
              },
        
              getSubtreeIterator: function () {
                var subRange;
                if (this.isSingleCharacterDataNode) {
                  subRange = this.range.cloneRange();
                  subRange.collapse(false);
                } else {
                  subRange = new Range(getRangeDocument(this.range));
                  var current = this._current;
                  var startContainer = current,startOffset = 0,endContainer = current,endOffset = getNodeLength(current);
        
                  if (isOrIsAncestorOf(current, this.sc)) {
                    startContainer = this.sc;
                    startOffset = this.so;
                  }
                  if (isOrIsAncestorOf(current, this.ec)) {
                    endContainer = this.ec;
                    endOffset = this.eo;
                  }
        
                  updateBoundaries(subRange, startContainer, startOffset, endContainer, endOffset);
                }
                return new RangeIterator(subRange, this.clonePartiallySelectedTextNodes);
              },
        
              detach: function () {
                this.range = this._current = this._next = this._first = this._last = this.sc = this.so = this.ec = this.eo = null;
              } };
        
        
            /*----------------------------------------------------------------------------------------------------------------*/
        
            var beforeAfterNodeTypes = [1, 3, 4, 5, 7, 8, 10];
            var rootContainerNodeTypes = [2, 9, 11];
            var readonlyNodeTypes = [5, 6, 10, 12];
            var insertableNodeTypes = [1, 3, 4, 5, 7, 8, 10, 11];
            var surroundNodeTypes = [1, 3, 4, 5, 7, 8];
        
            function createAncestorFinder(nodeTypes) {
              return function (node, selfIsAncestor) {
                var t,n = selfIsAncestor ? node : node.parentNode;
                while (n) {if (window.CP.shouldStopExecution(22)) break;
                  t = n.nodeType;
                  if (arrayContains(nodeTypes, t)) {
                    return n;
                  }
                  n = n.parentNode;
                }window.CP.exitedLoop(22);
                return null;
              };
            }
        
            var getDocumentOrFragmentContainer = createAncestorFinder([9, 11]);
            var getReadonlyAncestor = createAncestorFinder(readonlyNodeTypes);
            var getDocTypeNotationEntityAncestor = createAncestorFinder([6, 10, 12]);
        
            function assertNoDocTypeNotationEntityAncestor(node, allowSelf) {
              if (getDocTypeNotationEntityAncestor(node, allowSelf)) {
                throw new DOMException("INVALID_NODE_TYPE_ERR");
              }
            }
        
            function assertValidNodeType(node, invalidTypes) {
              if (!arrayContains(invalidTypes, node.nodeType)) {
                throw new DOMException("INVALID_NODE_TYPE_ERR");
              }
            }
        
            function assertValidOffset(node, offset) {
              if (offset < 0 || offset > (isCharacterDataNode(node) ? node.length : node.childNodes.length)) {
                throw new DOMException("INDEX_SIZE_ERR");
              }
            }
        
            function assertSameDocumentOrFragment(node1, node2) {
              if (getDocumentOrFragmentContainer(node1, true) !== getDocumentOrFragmentContainer(node2, true)) {
                throw new DOMException("WRONG_DOCUMENT_ERR");
              }
            }
        
            function assertNodeNotReadOnly(node) {
              if (getReadonlyAncestor(node, true)) {
                throw new DOMException("NO_MODIFICATION_ALLOWED_ERR");
              }
            }
        
            function assertNode(node, codeName) {
              if (!node) {
                throw new DOMException(codeName);
              }
            }
        
            function isValidOffset(node, offset) {
              return offset <= (isCharacterDataNode(node) ? node.length : node.childNodes.length);
            }
        
            function isRangeValid(range) {
              return !!range.startContainer && !!range.endContainer &&
              !(crashyTextNodes && (dom.isBrokenNode(range.startContainer) || dom.isBrokenNode(range.endContainer))) &&
              getRootContainer(range.startContainer) == getRootContainer(range.endContainer) &&
              isValidOffset(range.startContainer, range.startOffset) &&
              isValidOffset(range.endContainer, range.endOffset);
            }
        
            function assertRangeValid(range) {
              if (!isRangeValid(range)) {
                throw new Error("Range error: Range is not valid. This usually happens after DOM mutation. Range: (" + 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 = 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 (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(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) {
              var doc = getRangeDocument(this);
              var el = doc.createElement("body");
              el.innerHTML = fragmentStr;
        
              return dom.fragmentFromNodeChildren(el);
            };
        
            function splitRangeBoundaries(range, positionsToPreserve) {
              assertRangeValid(range);
        
              var sc = range.startContainer,so = range.startOffset,ec = range.endContainer,eo = range.endOffset;
              var startEndSame = sc === ec;
        
              if (isCharacterDataNode(ec) && eo > 0 && eo < ec.length) {
                splitDataNode(ec, eo, positionsToPreserve);
              }
        
              if (isCharacterDataNode(sc) && so > 0 && so < sc.length) {
                sc = splitDataNode(sc, so, positionsToPreserve);
                if (startEndSame) {
                  eo -= so;
                  ec = sc;
                } else if (ec == sc.parentNode && eo >= getNodeIndex(sc)) {
                  eo++;
                }
                so = 0;
              }
              range.setStartAndEnd(sc, so, ec, eo);
            }
        
            function rangeToHtml(range) {
              assertRangeValid(range);
              var container = range.commonAncestorContainer.parentNode.cloneNode(false);
              container.appendChild(range.cloneContents());
              return container.innerHTML;
            }
        
            /*----------------------------------------------------------------------------------------------------------------*/
        
            var rangeProperties = ["startContainer", "startOffset", "endContainer", "endOffset", "collapsed",
            "commonAncestorContainer"];
        
            var s2s = 0,s2e = 1,e2e = 2,e2s = 3;
            var n_b = 0,n_a = 1,n_b_a = 2,n_i = 3;
        
            util.extend(api.rangePrototype, {
              compareBoundaryPoints: function (how, range) {
                assertRangeValid(this);
                assertSameDocumentOrFragment(this.startContainer, range.startContainer);
        
                var nodeA, offsetA, nodeB, offsetB;
                var prefixA = how == e2s || how == s2s ? "start" : "end";
                var prefixB = how == s2e || how == s2s ? "start" : "end";
                nodeA = this[prefixA + "Container"];
                offsetA = this[prefixA + "Offset"];
                nodeB = range[prefixB + "Container"];
                offsetB = range[prefixB + "Offset"];
                return comparePoints(nodeA, offsetA, nodeB, offsetB);
              },
        
              insertNode: function (node) {
                assertRangeValid(this);
                assertValidNodeType(node, insertableNodeTypes);
                assertNodeNotReadOnly(this.startContainer);
        
                if (isOrIsAncestorOf(node, this.startContainer)) {
                  throw new DOMException("HIERARCHY_REQUEST_ERR");
                }
        
                // No check for whether the container of the start of the Range is of a type that does not allow
                // children of the type of node: the browser's DOM implementation should do this for us when we attempt
                // to add the node
        
                var firstNodeInserted = insertNodeAtPosition(node, this.startContainer, this.startOffset);
                this.setStartBefore(firstNodeInserted);
              },
        
              cloneContents: function () {
                assertRangeValid(this);
        
                var clone, frag;
                if (this.collapsed) {
                  return getRangeDocument(this).createDocumentFragment();
                } else {
                  if (this.startContainer === this.endContainer && isCharacterDataNode(this.startContainer)) {
                    clone = this.startContainer.cloneNode(true);
                    clone.data = clone.data.slice(this.startOffset, this.endOffset);
                    frag = getRangeDocument(this).createDocumentFragment();
                    frag.appendChild(clone);
                    return frag;
                  } else {
                    var iterator = new RangeIterator(this, true);
                    clone = cloneSubtree(iterator);
                    iterator.detach();
                  }
                  return clone;
                }
              },
        
              canSurroundContents: function () {
                assertRangeValid(this);
                assertNodeNotReadOnly(this.startContainer);
                assertNodeNotReadOnly(this.endContainer);
        
                // Check if the contents can be surrounded. Specifically, this means whether the range partially selects
                // no non-text nodes.
                var iterator = new RangeIterator(this, true);
                var boundariesInvalid = iterator._first && isNonTextPartiallySelected(iterator._first, this) ||
                iterator._last && isNonTextPartiallySelected(iterator._last, this);
                iterator.detach();
                return !boundariesInvalid;
              },
        
              surroundContents: function (node) {
                assertValidNodeType(node, surroundNodeTypes);
        
                if (!this.canSurroundContents()) {
                  throw new DOMException("INVALID_STATE_ERR");
                }
        
                // Extract the contents
                var content = this.extractContents();
        
                // Clear the children of the node
                if (node.hasChildNodes()) {
                  while (node.lastChild) {if (window.CP.shouldStopExecution(23)) break;
                    node.removeChild(node.lastChild);
                  }window.CP.exitedLoop(23);
                }
        
                // Insert the new node and add the extracted contents
                insertNodeAtPosition(node, this.startContainer, this.startOffset);
                node.appendChild(content);
        
                this.selectNode(node);
              },
        
              cloneRange: function () {
                assertRangeValid(this);
                var range = new Range(getRangeDocument(this));
                var i = rangeProperties.length,prop;
                while (i--) {if (window.CP.shouldStopExecution(24)) break;
                  prop = rangeProperties[i];
                  range[prop] = this[prop];
                }window.CP.exitedLoop(24);
                return range;
              },
        
              toString: function () {
                assertRangeValid(this);
                var sc = this.startContainer;
                if (sc === this.endContainer && isCharacterDataNode(sc)) {
                  return sc.nodeType == 3 || sc.nodeType == 4 ? sc.data.slice(this.startOffset, this.endOffset) : "";
                } else {
                  var textParts = [],iterator = new RangeIterator(this, true);
                  iterateSubtree(iterator, function (node) {
                    // Accept only text or CDATA nodes, not comments
                    if (node.nodeType == 3 || node.nodeType == 4) {
                      textParts.push(node.data);
                    }
                  });
                  iterator.detach();
                  return textParts.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 = getNodeIndex(node);
        
                if (!parent) {
                  throw new DOMException("NOT_FOUND_ERR");
                }
        
                var startComparison = this.comparePoint(parent, nodeIndex),
                endComparison = this.comparePoint(parent, nodeIndex + 1);
        
                if (startComparison < 0) {// Node starts before
                  return endComparison > 0 ? n_b_a : n_b;
                } else {
                  return endComparison > 0 ? n_a : n_i;
                }
              },
        
              comparePoint: function (node, offset) {
                assertRangeValid(this);
                assertNode(node, "HIERARCHY_REQUEST_ERR");
                assertSameDocumentOrFragment(node, this.startContainer);
        
                if (comparePoints(node, offset, this.startContainer, this.startOffset) < 0) {
                  return -1;
                } else if (comparePoints(node, offset, this.endContainer, this.endOffset) > 0) {
                  return 1;
                }
                return 0;
              },
        
              createContextualFragment: createContextualFragment,
        
              toHtml: function () {
                return rangeToHtml(this);
              },
        
              // touchingIsIntersecting determines whether this method considers a node that borders a range intersects
              // with it (as in WebKit) or not (as in Gecko pre-1.9, and the default)
              intersectsNode: function (node, touchingIsIntersecting) {
                assertRangeValid(this);
                if (getRootContainer(node) != getRangeRoot(this)) {
                  return false;
                }
        
                var parent = node.parentNode,offset = getNodeIndex(node);
                if (!parent) {
                  return true;
                }
        
                var startComparison = comparePoints(parent, offset, this.endContainer, this.endOffset),
                endComparison = comparePoints(parent, offset + 1, this.startContainer, this.startOffset);
        
                return touchingIsIntersecting ? startComparison <= 0 && endComparison >= 0 : startComparison < 0 && endComparison > 0;
              },
        
              isPointInRange: function (node, offset) {
                assertRangeValid(this);
                assertNode(node, "HIERARCHY_REQUEST_ERR");
                assertSameDocumentOrFragment(node, this.startContainer);
        
                return comparePoints(node, offset, this.startContainer, this.startOffset) >= 0 &&
                comparePoints(node, offset, this.endContainer, this.endOffset) <= 0;
              },
        
              // The methods below are non-standard and invented by me.
        
              // Sharing a boundary start-to-end or end-to-start does not count as intersection.
              intersectsRange: function (range) {
                return rangesIntersect(this, range, false);
              },
        
              // Sharing a boundary start-to-end or end-to-start does count as intersection.
              intersectsOrTouchesRange: function (range) {
                return rangesIntersect(this, range, true);
              },
        
              intersection: function (range) {
                if (this.intersectsRange(range)) {
                  var startComparison = comparePoints(this.startContainer, this.startOffset, range.startContainer, range.startOffset),
                  endComparison = comparePoints(this.endContainer, this.endOffset, range.endContainer, range.endOffset);
        
                  var intersectionRange = this.cloneRange();
                  if (startComparison == -1) {
                    intersectionRange.setStart(range.startContainer, range.startOffset);
                  }
                  if (endComparison == 1) {
                    intersectionRange.setEnd(range.endContainer, range.endOffset);
                  }
                  return intersectionRange;
                }
                return null;
              },
        
              union: function (range) {
                if (this.intersectsOrTouchesRange(range)) {
                  var unionRange = this.cloneRange();
                  if (comparePoints(range.startContainer, range.startOffset, this.startContainer, this.startOffset) == -1) {
                    unionRange.setStart(range.startContainer, range.startOffset);
                  }
                  if (comparePoints(range.endContainer, range.endOffset, this.endContainer, this.endOffset) == 1) {
                    unionRange.setEnd(range.endContainer, range.endOffset);
                  }
                  return unionRange;
                } else {
                  throw new DOMException("Ranges do not intersect");
                }
              },
        
              containsNode: function (node, allowPartial) {
                if (allowPartial) {
                  return this.intersectsNode(node, false);
                } else {
                  return this.compareNode(node) == n_i;
                }
              },
        
              containsNodeContents: function (node) {
                return this.comparePoint(node, 0) >= 0 && this.comparePoint(node, getNodeLength(node)) <= 0;
              },
        
              containsRange: function (range) {
                var intersection = this.intersection(range);
                return intersection !== null && range.equals(intersection);
              },
        
              containsNodeText: function (node) {
                var nodeRange = this.cloneRange();
                nodeRange.selectNode(node);
                var textNodes = nodeRange.getNodes([3]);
                if (textNodes.length > 0) {
                  nodeRange.setStart(textNodes[0], 0);
                  var lastTextNode = textNodes.pop();
                  nodeRange.setEnd(lastTextNode, lastTextNode.length);
                  return this.containsRange(nodeRange);
                } else {
                  return this.containsNodeContents(node);
                }
              },
        
              getNodes: function (nodeTypes, filter) {
                assertRangeValid(this);
                return getNodesInRange(this, nodeTypes, filter);
              },
        
              getDocument: function () {
                return getRangeDocument(this);
              },
        
              collapseBefore: function (node) {
                this.setEndBefore(node);
                this.collapse(false);
              },
        
              collapseAfter: function (node) {
                this.setStartAfter(node);
                this.collapse(true);
              },
        
              getBookmark: function (containerNode) {
                var doc = getRangeDocument(this);
                var preSelectionRange = api.createRange(doc);
                containerNode = containerNode || dom.getBody(doc);
                preSelectionRange.selectNodeContents(containerNode);
                var range = this.intersection(preSelectionRange);
                var start = 0,end = 0;
                if (range) {
                  preSelectionRange.setEnd(range.startContainer, range.startOffset);
                  start = preSelectionRange.toString().length;
                  end = start + range.toString().length;
                }
        
                return {
                  start: start,
                  end: end,
                  containerNode: containerNode };
        
              },
        
              moveToBookmark: function (bookmark) {
                var containerNode = bookmark.containerNode;
                var charIndex = 0;
                this.setStart(containerNode, 0);
                this.collapse(true);
                var nodeStack = [containerNode],node,foundStart = false,stop = false;
                var nextCharIndex, i, childNodes;
        
                while (!stop && (node = nodeStack.pop())) {if (window.CP.shouldStopExecution(25)) break;
                  if (node.nodeType == 3) {
                    nextCharIndex = charIndex + node.length;
                    if (!foundStart && bookmark.start >= charIndex && bookmark.start <= nextCharIndex) {
                      this.setStart(node, bookmark.start - charIndex);
                      foundStart = true;
                    }
                    if (foundStart && bookmark.end >= charIndex && bookmark.end <= nextCharIndex) {
                      this.setEnd(node, bookmark.end - charIndex);
                      stop = true;
                    }
                    charIndex = nextCharIndex;
                  } else {
                    childNodes = node.childNodes;
                    i = childNodes.length;
                    while (i--) {if (window.CP.shouldStopExecution(26)) break;
                      nodeStack.push(childNodes[i]);
                    }window.CP.exitedLoop(26);
                  }
                }window.CP.exitedLoop(25);
              },
        
              getName: function () {
                return "DomRange";
              },
        
              equals: function (range) {
                return Range.rangesEqual(this, range);
              },
        
              isValid: function () {
                return isRangeValid(this);
              },
        
              inspect: function () {
                return inspect(this);
              },
        
              detach: function () {
                // In DOM4, detach() is now a no-op.
              } });
        
        
            function copyComparisonConstantsToObject(obj) {
              obj.START_TO_START = s2s;
              obj.START_TO_END = s2e;
              obj.END_TO_END = e2e;
              obj.END_TO_START = e2s;
        
              obj.NODE_BEFORE = n_b;
              obj.NODE_AFTER = n_a;
              obj.NODE_BEFORE_AND_AFTER = n_b_a;
              obj.NODE_INSIDE = n_i;
            }
        
            function copyComparisonConstants(constructor) {
              copyComparisonConstantsToObject(constructor);
              copyComparisonConstantsToObject(constructor.prototype);
            }
        
            function createRangeContentRemover(remover, boundaryUpdater) {
              return function () {
                assertRangeValid(this);
        
                var sc = this.startContainer,so = this.startOffset,root = this.commonAncestorContainer;
        
                var iterator = new RangeIterator(this, true);
        
                // Work out where to position the range after content removal
                var node, boundary;
                if (sc !== root) {
                  node = getClosestAncestorIn(sc, root, true);
                  boundary = getBoundaryAfterNode(node);
                  sc = boundary.node;
                  so = boundary.offset;
                }
        
                // Check none of the range is read-only
                iterateSubtree(iterator, assertNodeNotReadOnly);
        
                iterator.reset();
        
                // Remove the content
                var returnValue = remover(iterator);
                iterator.detach();
        
                // Move to the new position
                boundaryUpdater(this, sc, so, sc, so);
        
                return returnValue;
              };
            }
        
            function createPrototypeRange(constructor, boundaryUpdater) {
              function createBeforeAfterNodeSetter(isBefore, isStart) {
                return function (node) {
                  assertValidNodeType(node, beforeAfterNodeTypes);
                  assertValidNodeType(getRootContainer(node), rootContainerNodeTypes);
        
                  var boundary = (isBefore ? getBoundaryBeforeNode : getBoundaryAfterNode)(node);
                  (isStart ? setRangeStart : setRangeEnd)(this, boundary.node, boundary.offset);
                };
              }
        
              function setRangeStart(range, node, offset) {
                var ec = range.endContainer,eo = range.endOffset;
                if (node !== range.startContainer || offset !== range.startOffset) {
                  // Check the root containers of the range and the new boundary, and also check whether the new boundary
                  // is after the current end. In either case, collapse the range to the new position
                  if (getRootContainer(node) != getRootContainer(ec) || comparePoints(node, offset, ec, eo) == 1) {
                    ec = node;
                    eo = offset;
                  }
                  boundaryUpdater(range, node, offset, ec, eo);
                }
              }
        
              function setRangeEnd(range, node, offset) {
                var sc = range.startContainer,so = range.startOffset;
                if (node !== range.endContainer || offset !== range.endOffset) {
                  // Check the root containers of the range and the new boundary, and also check whether the new boundary
                  // is after the current end. In either case, collapse the range to the new position
                  if (getRootContainer(node) != getRootContainer(sc) || comparePoints(node, offset, sc, so) == -1) {
                    sc = node;
                    so = offset;
                  }
                  boundaryUpdater(range, sc, so, node, offset);
                }
              }
        
              // Set up inheritance
              var F = function () {};
              F.prototype = api.rangePrototype;
              constructor.prototype = new F();
        
              util.extend(constructor.prototype, {
                setStart: function (node, offset) {
                  assertNoDocTypeNotationEntityAncestor(node, true);
                  assertValidOffset(node, offset);
        
                  setRangeStart(this, node, offset);
                },
        
                setEnd: function (node, offset) {
                  assertNoDocTypeNotationEntityAncestor(node, true);
                  assertValidOffset(node, offset);
        
                  setRangeEnd(this, node, offset);
                },
        
                /**
                 * Convenience method to set a range's start and end boundaries. Overloaded as follows:
                 * - Two parameters (node, offset) creates a collapsed range at that position
                 * - Three parameters (node, startOffset, endOffset) creates a range contained with node starting at
                 *   startOffset and ending at endOffset
                 * - Four parameters (startNode, startOffset, endNode, endOffset) creates a range starting at startOffset in
                 *   startNode and ending at endOffset in endNode
                 */
                setStartAndEnd: function () {
                  var args = arguments;
                  var sc = args[0],so = args[1],ec = sc,eo = so;
        
                  switch (args.length) {
                    case 3:
                      eo = args[2];
                      break;
                    case 4:
                      ec = args[2];
                      eo = args[3];
                      break;}
        
        
                  boundaryUpdater(this, sc, so, ec, eo);
                },
        
                setBoundary: function (node, offset, isStart) {
                  this["set" + (isStart ? "Start" : "End")](node, offset);
                },
        
                setStartBefore: createBeforeAfterNodeSetter(true, true),
                setStartAfter: createBeforeAfterNodeSetter(false, true),
                setEndBefore: createBeforeAfterNodeSetter(true, false),
                setEndAfter: createBeforeAfterNodeSetter(false, false),
        
                collapse: function (isStart) {
                  assertRangeValid(this);
                  if (isStart) {
                    boundaryUpdater(this, this.startContainer, this.startOffset, this.startContainer, this.startOffset);
                  } else {
                    boundaryUpdater(this, this.endContainer, this.endOffset, this.endContainer, this.endOffset);
                  }
                },
        
                selectNodeContents: function (node) {
                  assertNoDocTypeNotationEntityAncestor(node, true);
        
                  boundaryUpdater(this, node, 0, node, getNodeLength(node));
                },
        
                selectNode: function (node) {
                  assertNoDocTypeNotationEntityAncestor(node, false);
                  assertValidNodeType(node, beforeAfterNodeTypes);
        
                  var start = getBoundaryBeforeNode(node),end = getBoundaryAfterNode(node);
                  boundaryUpdater(this, start.node, start.offset, end.node, end.offset);
                },
        
                extractContents: createRangeContentRemover(extractSubtree, boundaryUpdater),
        
                deleteContents: createRangeContentRemover(deleteSubtree, boundaryUpdater),
        
                canSurroundContents: function () {
                  assertRangeValid(this);
                  assertNodeNotReadOnly(this.startContainer);
                  assertNodeNotReadOnly(this.endContainer);
        
                  // Check if the contents can be surrounded. Specifically, this means whether the range partially selects
                  // no non-text nodes.
                  var iterator = new RangeIterator(this, true);
                  var boundariesInvalid = iterator._first && isNonTextPartiallySelected(iterator._first, this) ||
                  iterator._last && isNonTextPartiallySelected(iterator._last, this);
                  iterator.detach();
                  return !boundariesInvalid;
                },
        
                splitBoundaries: function () {
                  splitRangeBoundaries(this);
                },
        
                splitBoundariesPreservingPositions: function (positionsToPreserve) {
                  splitRangeBoundaries(this, positionsToPreserve);
                },
        
                normalizeBoundaries: function () {
                  assertRangeValid(this);
        
                  var sc = this.startContainer,so = this.startOffset,ec = this.endContainer,eo = this.endOffset;
        
                  var mergeForward = function (node) {
                    var sibling = node.nextSibling;
                    if (sibling && sibling.nodeType == node.nodeType) {
                      ec = node;
                      eo = node.length;
                      node.appendData(sibling.data);
                      removeNode(sibling);
                    }
                  };
        
                  var mergeBackward = function (node) {
                    var sibling = node.previousSibling;
                    if (sibling && sibling.nodeType == node.nodeType) {
                      sc = node;
                      var nodeLength = node.length;
                      so = sibling.length;
                      node.insertData(0, sibling.data);
                      removeNode(sibling);
                      if (sc == ec) {
                        eo += so;
                        ec = sc;
                      } else if (ec == node.parentNode) {
                        var nodeIndex = getNodeIndex(node);
                        if (eo == nodeIndex) {
                          ec = node;
                          eo = nodeLength;
                        } else if (eo > nodeIndex) {
                          eo--;
                        }
                      }
                    }
                  };
        
                  var normalizeStart = true;
                  var sibling;
        
                  if (isCharacterDataNode(ec)) {
                    if (eo == ec.length) {
                      mergeForward(ec);
                    } else if (eo == 0) {
                      sibling = ec.previousSibling;
                      if (sibling && sibling.nodeType == ec.nodeType) {
                        eo = sibling.length;
                        if (sc == ec) {
                          normalizeStart = false;
                        }
                        sibling.appendData(ec.data);
                        removeNode(ec);
                        ec = sibling;
                      }
                    }
                  } else {
                    if (eo > 0) {
                      var endNode = ec.childNodes[eo - 1];
                      if (endNode && isCharacterDataNode(endNode)) {
                        mergeForward(endNode);
                      }
                    }
                    normalizeStart = !this.collapsed;
                  }
        
                  if (normalizeStart) {
                    if (isCharacterDataNode(sc)) {
                      if (so == 0) {
                        mergeBackward(sc);
                      } else if (so == sc.length) {
                        sibling = sc.nextSibling;
                        if (sibling && sibling.nodeType == sc.nodeType) {
                          if (ec == sibling) {
                            ec = sc;
                            eo += sc.length;
                          }
                          sc.appendData(sibling.data);
                          removeNode(sibling);
                        }
                      }
                    } else {
                      if (so < sc.childNodes.length) {
                        var startNode = sc.childNodes[so];
                        if (startNode && isCharacterDataNode(startNode)) {
                          mergeBackward(startNode);
                        }
                      }
                    }
                  } else {
                    sc = ec;
                    so = eo;
                  }
        
                  boundaryUpdater(this, sc, so, ec, eo);
                },
        
                collapseToPoint: function (node, offset) {
                  assertNoDocTypeNotationEntityAncestor(node, true);
                  assertValidOffset(node, offset);
                  this.setStartAndEnd(node, offset);
                } });
        
        
              copyComparisonConstants(constructor);
            }
        
            /*----------------------------------------------------------------------------------------------------------------*/
        
            // Updates commonAncestorContainer and collapsed after boundary change
            function updateCollapsedAndCommonAncestor(range) {
              range.collapsed = range.startContainer === range.endContainer && range.startOffset === range.endOffset;
              range.commonAncestorContainer = range.collapsed ?
              range.startContainer : dom.getCommonAncestor(range.startContainer, range.endContainer);
            }
        
            function updateBoundaries(range, startContainer, startOffset, endContainer, endOffset) {
              range.startContainer = startContainer;
              range.startOffset = startOffset;
              range.endContainer = endContainer;
              range.endOffset = endOffset;
              range.document = dom.getDocument(startContainer);
        
              updateCollapsedAndCommonAncestor(range);
            }
        
            function Range(doc) {
              this.startContainer = doc;
              this.startOffset = 0;
              this.endContainer = doc;
              this.endOffset = 0;
              this.document = doc;
              updateCollapsedAndCommonAncestor(this);
            }
        
            createPrototypeRange(Range, updateBoundaries);
        
            util.extend(Range, {
              rangeProperties: rangeProperties,
              RangeIterator: RangeIterator,
              copyComparisonConstants: copyComparisonConstants,
              createPrototypeRange: createPrototypeRange,
              inspect: inspect,
              toHtml: rangeToHtml,
              getRangeDocument: getRangeDocument,
              rangesEqual: function (r1, r2) {
                return r1.startContainer === r2.startContainer &&
                r1.startOffset === r2.startOffset &&
                r1.endContainer === r2.endContainer &&
                r1.endOffset === r2.endOffset;
              } });
        
        
            api.DomRange = Range;
          });
        
          /*----------------------------------------------------------------------------------------------------------------*/
        
          // Wrappers for the browser's native DOM Range and/or TextRange implementation
          api.createCoreModule("WrappedRange", ["DomRange"], function (api, module) {
            var WrappedRange, WrappedTextRange;
            var dom = api.dom;
            var util = api.util;
            var DomPosition = dom.DomPosition;
            var DomRange = api.DomRange;
            var getBody = dom.getBody;
            var getContentDocument = dom.getContentDocument;
            var isCharacterDataNode = dom.isCharacterDataNode;
        
        
            /*----------------------------------------------------------------------------------------------------------------*/
        
            if (api.features.implementsDomRange) {
              // 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;
        
                function updateRangeProperties(range) {
                  var i = rangeProperties.length,prop;
                  while (i--) {if (window.CP.shouldStopExecution(27)) break;
                    prop = rangeProperties[i];
                    range[prop] = range.nativeRange[prop];
                  }
                  // Fix for broken collapsed property in IE 9.
                  window.CP.exitedLoop(27);range.collapsed = range.startContainer === range.endContainer && range.startOffset === range.endOffset;
                }
        
                function updateNativeRange(range, startContainer, startOffset, endContainer, endOffset) {
                  var startMoved = range.startContainer !== startContainer || range.startOffset != startOffset;
                  var endMoved = range.endContainer !== endContainer || range.endOffset != endOffset;
                  var nativeRangeDifferent = !range.equals(range.nativeRange);
        
                  // Always set both boundaries for the benefit of IE9 (see issue 35)
                  if (startMoved || endMoved || nativeRangeDifferent) {
                    range.setEnd(endContainer, endOffset);
                    range.setStart(startContainer, startOffset);
                  }
                }
        
                var createBeforeAfterNodeSetter;
        
                WrappedRange = function (range) {
                  if (!range) {
                    throw module.createError("WrappedRange: Range must be specified");
                  }
                  this.nativeRange = range;
                  updateRangeProperties(this);
                };
        
                DomRange.createPrototypeRange(WrappedRange, updateNativeRange);
        
                rangeProto = WrappedRange.prototype;
        
                rangeProto.selectNode = function (node) {
                  this.nativeRange.selectNode(node);
                  updateRangeProperties(this);
                };
        
                rangeProto.cloneContents = function () {
                  return this.nativeRange.cloneContents();
                };
        
                // Due to a long-standing Firefox bug that I have not been able to find a reliable way to detect,
                // insertNode() is never delegated to the native range.
        
                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");
                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(testTextNode, 0);
                range.setEnd(testTextNode, 0);
        
                try {
                  range.setStart(testTextNode, 1);
        
                  rangeProto.setStart = function (node, offset) {
                    this.nativeRange.setStart(node, offset);
                    updateRangeProperties(this);
                  };
        
                  rangeProto.setEnd = function (node, offset) {
                    this.nativeRange.setEnd(node, offset);
                    updateRangeProperties(this);
                  };
        
                  createBeforeAfterNodeSetter = function (name) {
                    return function (node) {
                      this.nativeRange[name](node);
                      updateRangeProperties(this);
                    };
                  };
        
                } catch (ex) {
        
                  rangeProto.setStart = function (node, offset) {
                    try {
                      this.nativeRange.setStart(node, offset);
                    } catch (ex) {
                      this.nativeRange.setEnd(node, offset);
                      this.nativeRange.setStart(node, offset);
                    }
                    updateRangeProperties(this);
                  };
        
                  rangeProto.setEnd = function (node, offset) {
                    try {
                      this.nativeRange.setEnd(node, offset);
                    } catch (ex) {
                      this.nativeRange.setStart(node, offset);
                      this.nativeRange.setEnd(node, offset);
                    }
                    updateRangeProperties(this);
                  };
        
                  createBeforeAfterNodeSetter = function (name, oppositeName) {
                    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");
        
                /*--------------------------------------------------------------------------------------------------------*/
        
                // Always use DOM4-compliant selectNodeContents implementation: it's simpler and less code than testing
                // whether the native implementation can be trusted
                rangeProto.selectNodeContents = function (node) {
                  this.setStartAndEnd(node, 0, dom.getNodeLength(node));
                };
        
                /*--------------------------------------------------------------------------------------------------------*/
        
                // Test for and correct WebKit bug that has the behaviour 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(testTextNode, 3);
        
                var range2 = document.createRange();
                range2.selectNodeContents(testTextNode);
                range2.setEnd(testTextNode, 4);
                range2.setStart(testTextNode, 2);
        
                if (range.compareBoundaryPoints(range.START_TO_END, range2) == -1 &&
                range.compareBoundaryPoints(range.END_TO_START, range2) == 1) {
                  // This is the wrong way round, so correct for it
        
                  rangeProto.compareBoundaryPoints = function (type, range) {
                    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(type, range);
                  };
                } else {
                  rangeProto.compareBoundaryPoints = function (type, range) {
                    return this.nativeRange.compareBoundaryPoints(type, range.nativeRange || range);
                  };
                }
        
                /*--------------------------------------------------------------------------------------------------------*/
        
                // Test for IE deleteContents() and extractContents() bug and correct it. See issue 107.
        
                var el = document.createElement("div");
                el.innerHTML = "123";
                var textNode = el.firstChild;
                var body = getBody(document);
                body.appendChild(el);
        
                range.setStart(textNode, 1);
                range.setEnd(textNode, 2);
                range.deleteContents();
        
                if (textNode.data == "13") {
                  // Behaviour is correct per DOM4 Range so wrap the browser's implementation of deleteContents() and
                  // extractContents()
                  rangeProto.deleteContents = function () {
                    this.nativeRange.deleteContents();
                    updateRangeProperties(this);
                  };
        
                  rangeProto.extractContents = function () {
                    var frag = this.nativeRange.extractContents();
                    updateRangeProperties(this);
                    return frag;
                  };
                } else {
                }
        
                body.removeChild(el);
                body = null;
        
                /*--------------------------------------------------------------------------------------------------------*/
        
                // Test for existence of createContextualFragment and delegate to it if it exists
                if (util.isHostMethod(range, "createContextualFragment")) {
                  rangeProto.createContextualFragment = function (fragmentStr) {
                    return this.nativeRange.createContextualFragment(fragmentStr);
                  };
                }
        
                /*--------------------------------------------------------------------------------------------------------*/
        
                // Clean up
                getBody(document).removeChild(testTextNode);
        
                rangeProto.getName = function () {
                  return "WrappedRange";
                };
        
                api.WrappedRange = WrappedRange;
        
                api.createNativeRange = function (doc) {
                  doc = getContentDocument(doc, module, "createNativeRange");
                  return doc.createRange();
                };
              })();
            }
        
            if (api.features.implementsTextRange) {
              /*
              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)
              */
        
        
        
              var getTextRangeContainerElement = function (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(startEl, endEl);
        
                return startEndContainer == parentEl ? startEndContainer : dom.getCommonAncestor(parentEl, startEndContainer);
              };
        
              var textRangeIsCollapsed = function (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 (https://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.
              var getTextRangeBoundaryPosition = function (textRange, wholeRangeContainerElement, isStart, isCollapsed, startInfo) {
                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
                if (!dom.isOrIsAncestorOf(wholeRangeContainerElement, containerElement)) {
                  containerElement = wholeRangeContainerElement;
                }
        
        
                // Deal with nodes that cannot "contain rich HTML markup". In practice, this means form inputs, images and
                // similar. See https://msdn.microsoft.com/en-us/library/aa703950%28VS.85%29.aspx
                if (!containerElement.canHaveHTML) {
                  var pos = new DomPosition(containerElement.parentNode, dom.getNodeIndex(containerElement));
                  return {
                    boundaryPosition: pos,
                    nodeInfo: {
                      nodeIndex: pos.offset,
                      containerElement: pos.node } };
        
        
                }
        
                var workingNode = dom.getDocument(containerElement).createElement("span");
        
                // Workaround for HTML5 Shiv's insane violation of document.createElement(). See Rangy issue 104 and HTML5
                // Shiv issue 64: https://github.com/aFarkas/html5shiv/issues/64
                if (workingNode.parentNode) {
                  dom.removeNode(workingNode);
                }
        
                var comparison,workingComparisonType = isStart ? "StartToStart" : "StartToEnd";
                var previousNode, nextNode, boundaryPosition, boundaryNode;
                var start = startInfo && startInfo.containerElement == containerElement ? startInfo.nodeIndex : 0;
                var childNodeCount = containerElement.childNodes.length;
                var end = childNodeCount;
        
                // Check end first. Code within the loop assumes that the endth child node of the container is definitely
                // after the range boundary.
                var nodeIndex = end;
        
                while (true) {if (window.CP.shouldStopExecution(28)) break;
                  if (nodeIndex == childNodeCount) {
                    containerElement.appendChild(workingNode);
                  } else {
                    containerElement.insertBefore(workingNode, containerElement.childNodes[nodeIndex]);
                  }
                  workingRange.moveToElementText(workingNode);
                  comparison = workingRange.compareEndPoints(workingComparisonType, textRange);
                  if (comparison == 0 || start == end) {
                    break;
                  } else if (comparison == -1) {
                    if (end == start + 1) {
                      // We know the endth child node is after the range boundary, so we must be done.
                      break;
                    } else {
                      start = nodeIndex;
                    }
                  } else {
                    end = end == start + 1 ? start : nodeIndex;
                  }
                  nodeIndex = Math.floor((start + end) / 2);
                  containerElement.removeChild(workingNode);
                }
        
        
                // We've now reached or gone past the boundary of the text range we're interested in
                // so have identified the node we want
                window.CP.exitedLoop(28);boundaryNode = workingNode.nextSibling;
        
                if (comparison == -1 && boundaryNode && 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 (/[\r\n]/.test(boundaryNode.data)) {
                    /*
                    For the particular case of a boundary within a text node containing rendered line breaks (within a
                    <pre> element, for example), we need a slightly co.........完整代码请登录后点击上方下载按钮下载查看

网友评论0