1/* 2 * Copyright (C) 2013 Apple Inc. All rights reserved. 3 * 4 * Redistribution and use in source and binary forms, with or without 5 * modification, are permitted provided that the following conditions 6 * are met: 7 * 1. Redistributions of source code must retain the above copyright 8 * notice, this list of conditions and the following disclaimer. 9 * 2. Redistributions in binary form must reproduce the above copyright 10 * notice, this list of conditions and the following disclaimer in the 11 * documentation and/or other materials provided with the distribution. 12 * 13 * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' 14 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, 15 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 16 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS 17 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 18 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 19 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 20 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 21 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 22 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF 23 * THE POSSIBILITY OF SUCH DAMAGE. 24 */ 25 26Object.defineProperty(Object, "shallowCopy", 27{ 28 value: function(object) 29 { 30 // Make a new object and copy all the key/values. The values are not copied. 31 var copy = {}; 32 var keys = Object.keys(object); 33 for (var i = 0; i < keys.length; ++i) 34 copy[keys[i]] = object[keys[i]]; 35 return copy; 36 } 37}); 38 39Object.defineProperty(Object, "shallowEqual", 40{ 41 value: function(a, b) 42 { 43 // Checks if two objects have the same top-level properties. 44 45 // Check for strict equality in case they are the same object. 46 if (a === b) 47 return true; 48 49 // Only objects can proceed. null is an object, but Object.keys throws for null. 50 if (typeof a !== "object" || typeof b !== "object" || a === null || b === null) 51 return false; 52 53 var aKeys = Object.keys(a); 54 var bKeys = Object.keys(b); 55 56 // Check that each object has the same number of keys. 57 if (aKeys.length !== bKeys.length) 58 return false; 59 60 // Check if all the keys and their values are equal. 61 for (var i = 0; i < aKeys.length; ++i) { 62 // Check that b has the same key as a. 63 if (!(aKeys[i] in b)) 64 return false; 65 66 // Check that the values are strict equal since this is only 67 // a shallow check, not a recursive one. 68 if (a[aKeys[i]] !== b[aKeys[i]]) 69 return false; 70 } 71 72 return true; 73 } 74}); 75 76Object.defineProperty(Object.prototype, "valueForCaseInsensitiveKey", 77{ 78 value: function(key) 79 { 80 if (this.hasOwnProperty(key)) 81 return this[key]; 82 83 var lowerCaseKey = key.toLowerCase(); 84 for (var currentKey in this) { 85 if (currentKey.toLowerCase() === lowerCaseKey) 86 return this[currentKey]; 87 } 88 89 return undefined; 90 } 91}); 92 93Object.defineProperty(Map.prototype, "take", 94{ 95 value: function(key) 96 { 97 var deletedValue = this.get(key); 98 this.delete(key); 99 return deletedValue; 100 } 101}); 102 103Object.defineProperty(Node.prototype, "enclosingNodeOrSelfWithClass", 104{ 105 value: function(className) 106 { 107 for (var node = this; node && node !== this.ownerDocument; node = node.parentNode) 108 if (node.nodeType === Node.ELEMENT_NODE && node.classList.contains(className)) 109 return node; 110 return null; 111 } 112}); 113 114Object.defineProperty(Node.prototype, "enclosingNodeOrSelfWithNodeNameInArray", 115{ 116 value: function(nameArray) 117 { 118 var lowerCaseNameArray = nameArray.map(function(name) { return name.toLowerCase() }); 119 for (var node = this; node && node !== this.ownerDocument; node = node.parentNode) { 120 for (var i = 0; i < nameArray.length; ++i) { 121 if (node.nodeName.toLowerCase() === lowerCaseNameArray[i]) 122 return node; 123 } 124 } 125 126 return null; 127 } 128}); 129 130Object.defineProperty(Node.prototype, "enclosingNodeOrSelfWithNodeName", 131{ 132 value: function(nodeName) 133 { 134 return this.enclosingNodeOrSelfWithNodeNameInArray([nodeName]); 135 } 136}); 137 138Object.defineProperty(Node.prototype, "isAncestor", 139{ 140 value: function(node) 141 { 142 if (!node) 143 return false; 144 145 var currentNode = node.parentNode; 146 while (currentNode) { 147 if (this === currentNode) 148 return true; 149 currentNode = currentNode.parentNode; 150 } 151 152 return false; 153 } 154}); 155 156Object.defineProperty(Node.prototype, "isDescendant", 157{ 158 value: function(descendant) 159 { 160 return !!descendant && descendant.isAncestor(this); 161 } 162}); 163 164 165Object.defineProperty(Node.prototype, "isSelfOrAncestor", 166{ 167 value: function(node) 168 { 169 return !!node && (node === this || this.isAncestor(node)); 170 } 171}); 172 173 174Object.defineProperty(Node.prototype, "isSelfOrDescendant", 175{ 176 value: function(node) 177 { 178 return !!node && (node === this || this.isDescendant(node)); 179 } 180}); 181 182Object.defineProperty(Node.prototype, "traverseNextNode", 183{ 184 value: function(stayWithin) 185 { 186 var node = this.firstChild; 187 if (node) 188 return node; 189 190 if (stayWithin && this === stayWithin) 191 return null; 192 193 node = this.nextSibling; 194 if (node) 195 return node; 196 197 node = this; 198 while (node && !node.nextSibling && (!stayWithin || !node.parentNode || node.parentNode !== stayWithin)) 199 node = node.parentNode; 200 if (!node) 201 return null; 202 203 return node.nextSibling; 204 } 205}); 206 207Object.defineProperty(Node.prototype, "traversePreviousNode", 208{ 209 value: function(stayWithin) 210 { 211 if (stayWithin && this === stayWithin) 212 return null; 213 var node = this.previousSibling; 214 while (node && node.lastChild) 215 node = node.lastChild; 216 if (node) 217 return node; 218 return this.parentNode; 219 } 220}); 221 222 223Object.defineProperty(Node.prototype, "rangeOfWord", 224{ 225 value: function(offset, stopCharacters, stayWithinNode, direction) 226 { 227 var startNode; 228 var startOffset = 0; 229 var endNode; 230 var endOffset = 0; 231 232 if (!stayWithinNode) 233 stayWithinNode = this; 234 235 if (!direction || direction === "backward" || direction === "both") { 236 var node = this; 237 while (node) { 238 if (node === stayWithinNode) { 239 if (!startNode) 240 startNode = stayWithinNode; 241 break; 242 } 243 244 if (node.nodeType === Node.TEXT_NODE) { 245 var start = (node === this ? (offset - 1) : (node.nodeValue.length - 1)); 246 for (var i = start; i >= 0; --i) { 247 if (stopCharacters.indexOf(node.nodeValue[i]) !== -1) { 248 startNode = node; 249 startOffset = i + 1; 250 break; 251 } 252 } 253 } 254 255 if (startNode) 256 break; 257 258 node = node.traversePreviousNode(stayWithinNode); 259 } 260 261 if (!startNode) { 262 startNode = stayWithinNode; 263 startOffset = 0; 264 } 265 } else { 266 startNode = this; 267 startOffset = offset; 268 } 269 270 if (!direction || direction === "forward" || direction === "both") { 271 node = this; 272 while (node) { 273 if (node === stayWithinNode) { 274 if (!endNode) 275 endNode = stayWithinNode; 276 break; 277 } 278 279 if (node.nodeType === Node.TEXT_NODE) { 280 var start = (node === this ? offset : 0); 281 for (var i = start; i < node.nodeValue.length; ++i) { 282 if (stopCharacters.indexOf(node.nodeValue[i]) !== -1) { 283 endNode = node; 284 endOffset = i; 285 break; 286 } 287 } 288 } 289 290 if (endNode) 291 break; 292 293 node = node.traverseNextNode(stayWithinNode); 294 } 295 296 if (!endNode) { 297 endNode = stayWithinNode; 298 endOffset = stayWithinNode.nodeType === Node.TEXT_NODE ? stayWithinNode.nodeValue.length : stayWithinNode.childNodes.length; 299 } 300 } else { 301 endNode = this; 302 endOffset = offset; 303 } 304 305 var result = this.ownerDocument.createRange(); 306 result.setStart(startNode, startOffset); 307 result.setEnd(endNode, endOffset); 308 309 return result; 310 311 } 312}); 313 314Object.defineProperty(Element.prototype, "totalOffsetLeft", 315{ 316 get: function() 317 { 318 return this.getBoundingClientRect().left; 319 } 320}); 321 322Object.defineProperty(Element.prototype, "totalOffsetTop", 323{ 324 get: function() 325 { 326 return this.getBoundingClientRect().top; 327 } 328}); 329 330Object.defineProperty(Element.prototype, "removeChildren", 331{ 332 value: function() 333 { 334 // This has been tested to be the fastest removal method. 335 if (this.firstChild) 336 this.textContent = ""; 337 } 338}); 339 340Object.defineProperty(Element.prototype, "isInsertionCaretInside", 341{ 342 value: function() 343 { 344 var selection = window.getSelection(); 345 if (!selection.rangeCount || !selection.isCollapsed) 346 return false; 347 var selectionRange = selection.getRangeAt(0); 348 return selectionRange.startContainer === this || selectionRange.startContainer.isDescendant(this); 349 } 350}); 351 352Object.defineProperty(Element.prototype, "removeMatchingStyleClasses", 353{ 354 value: function(classNameRegex) 355 { 356 var regex = new RegExp("(^|\\s+)" + classNameRegex + "($|\\s+)"); 357 if (regex.test(this.className)) 358 this.className = this.className.replace(regex, " "); 359 } 360}); 361 362Object.defineProperty(Element.prototype, "createChild", 363{ 364 value: function(elementName, className) 365 { 366 var element = this.ownerDocument.createElement(elementName); 367 if (className) 368 element.className = className; 369 this.appendChild(element); 370 return element; 371 } 372}); 373 374Object.defineProperty(Element.prototype, "isScrolledToBottom", 375{ 376 value: function() 377 { 378 // This code works only for 0-width border 379 return this.scrollTop + this.clientHeight === this.scrollHeight; 380 } 381}); 382 383Object.defineProperty(Element.prototype, "recalculateStyles", 384{ 385 value: function() 386 { 387 this.ownerDocument.defaultView.getComputedStyle(this); 388 } 389}); 390 391Object.defineProperty(DocumentFragment.prototype, "createChild", 392{ 393 value: Element.prototype.createChild 394}); 395 396Object.defineProperty(String.prototype, "contains", 397{ 398 value: function(value) 399 { 400 return this.indexOf(value) !== -1; 401 } 402}); 403 404Object.defineProperty(Array.prototype, "contains", 405{ 406 value: function(value) 407 { 408 return this.indexOf(value) !== -1; 409 } 410}); 411 412Object.defineProperty(Array.prototype, "lastValue", 413{ 414 get: function() 415 { 416 if (!this.length) 417 return undefined; 418 return this[this.length - 1]; 419 } 420}); 421 422Object.defineProperty(Array.prototype, "remove", 423{ 424 value: function(value, onlyFirst) 425 { 426 for (var i = this.length - 1; i >= 0; --i) { 427 if (this[i] === value) { 428 this.splice(i, 1); 429 if (onlyFirst) 430 return; 431 } 432 } 433 } 434}); 435 436Object.defineProperty(Array.prototype, "keySet", 437{ 438 value: function() 439 { 440 var keys = {}; 441 for (var i = 0; i < this.length; ++i) 442 keys[this[i]] = true; 443 return keys; 444 } 445}); 446 447Object.defineProperty(String.prototype, "trimMiddle", 448{ 449 value: function(maxLength) 450 { 451 if (this.length <= maxLength) 452 return this; 453 var leftHalf = maxLength >> 1; 454 var rightHalf = maxLength - leftHalf - 1; 455 return this.substr(0, leftHalf) + "\u2026" + this.substr(this.length - rightHalf, rightHalf); 456 } 457}); 458 459Object.defineProperty(String.prototype, "trimEnd", 460{ 461 value: function(maxLength) 462 { 463 if (this.length <= maxLength) 464 return this; 465 return this.substr(0, maxLength - 1) + "\u2026"; 466 } 467}); 468 469Object.defineProperty(String.prototype, "collapseWhitespace", 470{ 471 value: function() 472 { 473 return this.replace(/[\s\xA0]+/g, " "); 474 } 475}); 476 477Object.defineProperty(String.prototype, "escapeCharacters", 478{ 479 value: function(chars) 480 { 481 var foundChar = false; 482 for (var i = 0; i < chars.length; ++i) { 483 if (this.indexOf(chars.charAt(i)) !== -1) { 484 foundChar = true; 485 break; 486 } 487 } 488 489 if (!foundChar) 490 return this; 491 492 var result = ""; 493 for (var i = 0; i < this.length; ++i) { 494 if (chars.indexOf(this.charAt(i)) !== -1) 495 result += "\\"; 496 result += this.charAt(i); 497 } 498 499 return result; 500 } 501}); 502 503Object.defineProperty(String.prototype, "escapeForRegExp", 504{ 505 value: function() 506 { 507 return this.escapeCharacters("^[]{}()\\.$*+?|"); 508 } 509}); 510 511Object.defineProperty(String.prototype, "capitalize", 512{ 513 value: function() 514 { 515 return this.charAt(0).toUpperCase() + this.slice(1); 516 } 517}); 518 519Object.defineProperty(String, "tokenizeFormatString", 520{ 521 value: function(format) 522 { 523 var tokens = []; 524 var substitutionIndex = 0; 525 526 function addStringToken(str) 527 { 528 tokens.push({ type: "string", value: str }); 529 } 530 531 function addSpecifierToken(specifier, precision, substitutionIndex) 532 { 533 tokens.push({ type: "specifier", specifier: specifier, precision: precision, substitutionIndex: substitutionIndex }); 534 } 535 536 var index = 0; 537 for (var precentIndex = format.indexOf("%", index); precentIndex !== -1; precentIndex = format.indexOf("%", index)) { 538 addStringToken(format.substring(index, precentIndex)); 539 index = precentIndex + 1; 540 541 if (format[index] === "%") { 542 addStringToken("%"); 543 ++index; 544 continue; 545 } 546 547 if (!isNaN(format[index])) { 548 // The first character is a number, it might be a substitution index. 549 var number = parseInt(format.substring(index), 10); 550 while (!isNaN(format[index])) 551 ++index; 552 553 // If the number is greater than zero and ends with a "$", 554 // then this is a substitution index. 555 if (number > 0 && format[index] === "$") { 556 substitutionIndex = (number - 1); 557 ++index; 558 } 559 } 560 561 var precision = -1; 562 if (format[index] === ".") { 563 // This is a precision specifier. If no digit follows the ".", 564 // then the precision should be zero. 565 ++index; 566 567 precision = parseInt(format.substring(index), 10); 568 if (isNaN(precision)) 569 precision = 0; 570 571 while (!isNaN(format[index])) 572 ++index; 573 } 574 575 addSpecifierToken(format[index], precision, substitutionIndex); 576 577 ++substitutionIndex; 578 ++index; 579 } 580 581 addStringToken(format.substring(index)); 582 583 return tokens; 584 } 585}); 586 587Object.defineProperty(String.prototype, "startsWith", 588{ 589 value: function(string) 590 { 591 return this.lastIndexOf(string, 0) === 0; 592 } 593}); 594 595Object.defineProperty(String.prototype, "hash", 596{ 597 get: function() 598 { 599 // Matches the wtf/StringHasher.h (SuperFastHash) algorithm. 600 601 // Arbitrary start value to avoid mapping all 0's to all 0's. 602 const stringHashingStartValue = 0x9e3779b9; 603 604 var result = stringHashingStartValue; 605 var pendingCharacter = null; 606 for (var i = 0; i < this.length; ++i) { 607 var currentCharacter = this[i].charCodeAt(0); 608 if (pendingCharacter === null) { 609 pendingCharacter = currentCharacter; 610 continue; 611 } 612 613 result += pendingCharacter; 614 result = (result << 16) ^ ((currentCharacter << 11) ^ result); 615 result += result >> 11; 616 617 pendingCharacter = null; 618 } 619 620 // Handle the last character in odd length strings. 621 if (pendingCharacter !== null) { 622 result += pendingCharacter; 623 result ^= result << 11; 624 result += result >> 17; 625 } 626 627 // Force "avalanching" of final 31 bits. 628 result ^= result << 3; 629 result += result >> 5; 630 result ^= result << 2; 631 result += result >> 15; 632 result ^= result << 10; 633 634 // Prevent 0 and negative results. 635 return (0xffffffff + result + 1).toString(36); 636 } 637}); 638 639Object.defineProperty(String, "standardFormatters", 640{ 641 value: { 642 d: function(substitution) 643 { 644 return !isNaN(substitution) ? substitution : 0; 645 }, 646 647 f: function(substitution, token) 648 { 649 if (substitution && token.precision > -1) 650 substitution = substitution.toFixed(token.precision); 651 return !isNaN(substitution) ? substitution : (token.precision > -1 ? Number(0).toFixed(token.precision) : 0); 652 }, 653 654 s: function(substitution) 655 { 656 return substitution; 657 } 658 } 659}); 660 661Object.defineProperty(String, "format", 662{ 663 value: function(format, substitutions, formatters, initialValue, append) 664 { 665 if (!format || !substitutions || !substitutions.length) 666 return { formattedResult: append(initialValue, format), unusedSubstitutions: substitutions }; 667 668 function prettyFunctionName() 669 { 670 return "String.format(\"" + format + "\", \"" + substitutions.join("\", \"") + "\")"; 671 } 672 673 function warn(msg) 674 { 675 console.warn(prettyFunctionName() + ": " + msg); 676 } 677 678 function error(msg) 679 { 680 console.error(prettyFunctionName() + ": " + msg); 681 } 682 683 var result = initialValue; 684 var tokens = String.tokenizeFormatString(format); 685 var usedSubstitutionIndexes = {}; 686 687 for (var i = 0; i < tokens.length; ++i) { 688 var token = tokens[i]; 689 690 if (token.type === "string") { 691 result = append(result, token.value); 692 continue; 693 } 694 695 if (token.type !== "specifier") { 696 error("Unknown token type \"" + token.type + "\" found."); 697 continue; 698 } 699 700 if (token.substitutionIndex >= substitutions.length) { 701 // If there are not enough substitutions for the current substitutionIndex 702 // just output the format specifier literally and move on. 703 error("not enough substitution arguments. Had " + substitutions.length + " but needed " + (token.substitutionIndex + 1) + ", so substitution was skipped."); 704 result = append(result, "%" + (token.precision > -1 ? token.precision : "") + token.specifier); 705 continue; 706 } 707 708 usedSubstitutionIndexes[token.substitutionIndex] = true; 709 710 if (!(token.specifier in formatters)) { 711 // Encountered an unsupported format character, treat as a string. 712 warn("unsupported format character \u201C" + token.specifier + "\u201D. Treating as a string."); 713 result = append(result, substitutions[token.substitutionIndex]); 714 continue; 715 } 716 717 result = append(result, formatters[token.specifier](substitutions[token.substitutionIndex], token)); 718 } 719 720 var unusedSubstitutions = []; 721 for (var i = 0; i < substitutions.length; ++i) { 722 if (i in usedSubstitutionIndexes) 723 continue; 724 unusedSubstitutions.push(substitutions[i]); 725 } 726 727 return {formattedResult: result, unusedSubstitutions: unusedSubstitutions}; 728 } 729}); 730 731Object.defineProperty(String.prototype, "format", 732{ 733 value: function() 734 { 735 return String.format(this, arguments, String.standardFormatters, "", function(a, b) { return a + b; }).formattedResult; 736 } 737}); 738 739Object.defineProperty(String.prototype, "insertWordBreakCharacters", 740{ 741 value: function() 742 { 743 // Add zero width spaces after characters that are good to break after. 744 // Otherwise a string with no spaces will not break and overflow its container. 745 // This is mainly used on URL strings, so the characters are tailored for URLs. 746 return this.replace(/([\/;:\)\]\}&?])/g, "$1\u200b"); 747 } 748}); 749 750Object.defineProperty(String.prototype, "removeWordBreakCharacters", 751{ 752 value: function() 753 { 754 // Undoes what insertWordBreakCharacters did. 755 return this.replace(/\u200b/g, ""); 756 } 757}); 758 759Object.defineProperty(Number, "constrain", 760{ 761 value: function(num, min, max) 762 { 763 if (num < min) 764 num = min; 765 else if (num > max) 766 num = max; 767 return num; 768 } 769}); 770 771Object.defineProperty(Number, "secondsToString", 772{ 773 value: function(seconds, higherResolution) 774 { 775 var ms = seconds * 1000; 776 777 if (higherResolution && Math.abs(ms) < 10) 778 return WebInspector.UIString("%.3fms").format(ms); 779 else if (Math.abs(ms) < 10) 780 return WebInspector.UIString("%.2fms").format(ms); 781 782 if (higherResolution && Math.abs(ms) < 100) 783 return WebInspector.UIString("%.2fms").format(ms); 784 else if (Math.abs(ms) < 100) 785 return WebInspector.UIString("%.1fms").format(ms); 786 787 if (higherResolution && Math.abs(ms) < 1000) 788 return WebInspector.UIString("%.1fms").format(ms); 789 else if (Math.abs(ms) < 1000) 790 return WebInspector.UIString("%.0fms").format(ms); 791 792 if (Math.abs(seconds) < 60) 793 return WebInspector.UIString("%.2fs").format(seconds); 794 795 var minutes = seconds / 60; 796 if (Math.abs(minutes) < 60) 797 return WebInspector.UIString("%.1fmin").format(minutes); 798 799 var hours = minutes / 60; 800 if (Math.abs(hours) < 24) 801 return WebInspector.UIString("%.1fhrs").format(hours); 802 803 var days = hours / 24; 804 return WebInspector.UIString("%.1f days").format(days); 805 } 806}); 807 808Object.defineProperty(Number, "bytesToString", 809{ 810 value: function(bytes, higherResolution) 811 { 812 if (higherResolution === undefined) 813 higherResolution = true; 814 815 if (Math.abs(bytes) < 1024) 816 return WebInspector.UIString("%.0f B").format(bytes); 817 818 var kilobytes = bytes / 1024; 819 if (Math.abs(kilobytes) < 10 || (higherResolution && Math.abs(kilobytes) < 1024)) 820 return WebInspector.UIString("%.2f KB").format(kilobytes); 821 else if (Math.abs(kilobytes) < 1024) 822 return WebInspector.UIString("%.1f KB").format(kilobytes); 823 824 var megabytes = kilobytes / 1024; 825 if (higherResolution || Math.abs(megabytes) < 10) 826 return WebInspector.UIString("%.2f MB").format(megabytes); 827 else 828 return WebInspector.UIString("%.1f MB").format(megabytes); 829 } 830}); 831 832Object.defineProperty(Uint32Array, "isLittleEndian", 833{ 834 value: function() 835 { 836 if ("_isLittleEndian" in this) 837 return this._isLittleEndian; 838 839 var buffer = new ArrayBuffer(4); 840 var longData = new Uint32Array(buffer); 841 var data = new Uint8Array(buffer); 842 843 longData[0] = 0x0a0b0c0d; 844 845 this._isLittleEndian = data[0] === 0x0d && data[1] === 0x0c && data[2] === 0x0b && data[3] === 0x0a; 846 847 return this._isLittleEndian; 848 } 849}); 850 851function isEmptyObject(object) 852{ 853 for (var property in object) 854 return false; 855 return true; 856} 857 858function isEnterKey(event) 859{ 860 // Check if this is an IME event. 861 return event.keyCode !== 229 && event.keyIdentifier === "Enter"; 862} 863 864function resolveDotsInPath(path) 865{ 866 if (!path) 867 return path; 868 869 if (path.indexOf("./") === -1) 870 return path; 871 872 console.assert(path.charAt(0) === "/"); 873 874 var result = []; 875 876 var components = path.split("/"); 877 for (var i = 0; i < components.length; ++i) { 878 var component = components[i]; 879 880 // Skip over "./". 881 if (component === ".") 882 continue; 883 884 // Rewind one component for "../". 885 if (component === "..") { 886 if (result.length === 1) 887 continue; 888 result.pop(); 889 continue; 890 } 891 892 result.push(component); 893 } 894 895 return result.join("/"); 896} 897 898function parseMIMEType(fullMimeType) 899{ 900 if (!fullMimeType) 901 return {type: fullMimeType, boundary: null, encoding: null}; 902 903 var typeParts = fullMimeType.split(/\s*;\s*/); 904 console.assert(typeParts.length >= 1); 905 906 var type = typeParts[0]; 907 var boundary = null; 908 var encoding = null; 909 910 for (var i = 1; i < typeParts.length; ++i) { 911 var subparts = typeParts[i].split(/\s*=\s*/); 912 if (subparts.length !== 2) 913 continue; 914 915 if (subparts[0].toLowerCase() === "boundary") 916 boundary = subparts[1]; 917 else if (subparts[0].toLowerCase() === "charset") 918 encoding = subparts[1].replace("^\"|\"$", ""); // Trim quotes. 919 } 920 921 return {type: type, boundary: boundary || null, encoding: encoding || null}; 922} 923 924function simpleGlobStringToRegExp(globString, regExpFlags) 925{ 926 // Only supports "*" globs. 927 928 if (!globString) 929 return null; 930 931 // Escape everything from String.prototype.escapeForRegExp except "*". 932 var regexString = globString.escapeCharacters("^[]{}()\\.$+?|"); 933 934 // Unescape all doubly escaped backslashes in front of escaped asterisks. 935 // So "\\*" will become "\*" again, undoing escapeCharacters escaping of "\". 936 // This makes "\*" match a literal "*" instead of using the "*" for globbing. 937 regexString = regexString.replace(/\\\\\*/g, "\\*"); 938 939 // The following regex doesn't match an asterisk that has a backslash in front. 940 // It also catches consecutive asterisks so they collapse down when replaced. 941 var unescapedAsteriskRegex = /(^|[^\\])\*+/g; 942 if (unescapedAsteriskRegex.test(globString)) { 943 // Replace all unescaped asterisks with ".*". 944 regexString = regexString.replace(unescapedAsteriskRegex, "$1.*"); 945 946 // Match edge boundaries when there is an asterisk to better meet the expectations 947 // of the user. When someone types "*.js" they don't expect "foo.json" to match. They 948 // would only expect that if they type "*.js*". We use \b (instead of ^ and $) to allow 949 // matches inside paths or URLs, so "ba*.js" will match "foo/bar.js" but not "boo/bbar.js". 950 // When there isn't an asterisk the regexString is just a substring search. 951 regexString = "\\b" + regexString + "\\b"; 952 } 953 954 return new RegExp(regexString, regExpFlags); 955} 956 957Object.defineProperty(Array.prototype, "lowerBound", 958{ 959 // Return index of the leftmost element that is equal or greater 960 // than the specimen object. If there's no such element (i.e. all 961 // elements are smaller than the specimen) returns array.length. 962 // The function works for sorted array. 963 value: function(object, comparator) 964 { 965 function defaultComparator(a, b) 966 { 967 return a - b; 968 } 969 comparator = comparator || defaultComparator; 970 var l = 0; 971 var r = this.length; 972 while (l < r) { 973 var m = (l + r) >> 1; 974 if (comparator(object, this[m]) > 0) 975 l = m + 1; 976 else 977 r = m; 978 } 979 return r; 980 } 981}); 982 983Object.defineProperty(Array.prototype, "upperBound", 984{ 985 // Return index of the leftmost element that is greater 986 // than the specimen object. If there's no such element (i.e. all 987 // elements are smaller than the specimen) returns array.length. 988 // The function works for sorted array. 989 value: function(object, comparator) 990 { 991 function defaultComparator(a, b) 992 { 993 return a - b; 994 } 995 comparator = comparator || defaultComparator; 996 var l = 0; 997 var r = this.length; 998 while (l < r) { 999 var m = (l + r) >> 1; 1000 if (comparator(object, this[m]) >= 0) 1001 l = m + 1; 1002 else 1003 r = m; 1004 } 1005 return r; 1006 } 1007}); 1008 1009Object.defineProperty(Array.prototype, "binaryIndexOf", 1010{ 1011 value: function(value, comparator) 1012 { 1013 var index = this.lowerBound(value, comparator); 1014 return index < this.length && comparator(value, this[index]) === 0 ? index : -1; 1015 } 1016}); 1017 1018function clamp(min, value, max) 1019{ 1020 return Math.min(Math.max(min, value), max); 1021} 1022 1023function insertionIndexForObjectInListSortedByFunction(object, list, comparator, insertionIndexAfter) 1024{ 1025 if (insertionIndexAfter) { 1026 return list.upperBound(object, comparator); 1027 } else { 1028 return list.lowerBound(object, comparator); 1029 } 1030} 1031 1032function insertObjectIntoSortedArray(object, array, comparator) 1033{ 1034 array.splice(insertionIndexForObjectInListSortedByFunction(object, array, comparator), 0, object); 1035} 1036