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