1 /* 2 * Copyright (C) 2007 Apple Inc. All rights reserved. 3 * Copyright (C) 2012 Google Inc. All rights reserved. 4 * 5 * Redistribution and use in source and binary forms, with or without 6 * modification, are permitted provided that the following conditions 7 * are met: 8 * 9 * 1. Redistributions of source code must retain the above copyright 10 * notice, this list of conditions and the following disclaimer. 11 * 2. Redistributions in binary form must reproduce the above copyright 12 * notice, this list of conditions and the following disclaimer in the 13 * documentation and/or other materials provided with the distribution. 14 * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of 15 * its contributors may be used to endorse or promote products derived 16 * from this software without specific prior written permission. 17 * 18 * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY 19 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 20 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY 22 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 23 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 24 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 25 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 27 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 */ 29 30 /** 31 * @param {Object} obj 32 * @return {boolean} 33 */ 34 Object.isEmpty = function(obj) 35 { 36 for (var i in obj) 37 return false; 38 return true; 39 } 40 41 /** 42 * @param {!Object.<string,T>} obj 43 * @return {!Array.<T>} 44 * @template T 45 */ 46 Object.values = function(obj) 47 { 48 var result = Object.keys(obj); 49 var length = result.length; 50 for (var i = 0; i < length; ++i) 51 result[i] = obj[result[i]]; 52 return result; 53 } 54 55 /** 56 * @param {string} string 57 * @return {!Array.<number>} 58 */ 59 String.prototype.findAll = function(string) 60 { 61 var matches = []; 62 var i = this.indexOf(string); 63 while (i !== -1) { 64 matches.push(i); 65 i = this.indexOf(string, i + string.length); 66 } 67 return matches; 68 } 69 70 /** 71 * @return {!Array.<number>} 72 */ 73 String.prototype.lineEndings = function() 74 { 75 if (!this._lineEndings) { 76 this._lineEndings = this.findAll("\n"); 77 this._lineEndings.push(this.length); 78 } 79 return this._lineEndings; 80 } 81 82 /** 83 * @param {string} chars 84 * @return {string} 85 */ 86 String.prototype.escapeCharacters = function(chars) 87 { 88 var foundChar = false; 89 for (var i = 0; i < chars.length; ++i) { 90 if (this.indexOf(chars.charAt(i)) !== -1) { 91 foundChar = true; 92 break; 93 } 94 } 95 96 if (!foundChar) 97 return String(this); 98 99 var result = ""; 100 for (var i = 0; i < this.length; ++i) { 101 if (chars.indexOf(this.charAt(i)) !== -1) 102 result += "\\"; 103 result += this.charAt(i); 104 } 105 106 return result; 107 } 108 109 /** 110 * @return {string} 111 */ 112 String.regexSpecialCharacters = function() 113 { 114 return "^[]{}()\\.$*+?|-,"; 115 } 116 117 /** 118 * @return {string} 119 */ 120 String.prototype.escapeForRegExp = function() 121 { 122 return this.escapeCharacters(String.regexSpecialCharacters()); 123 } 124 125 /** 126 * @return {string} 127 */ 128 String.prototype.escapeHTML = function() 129 { 130 return this.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """); //" doublequotes just for editor 131 } 132 133 /** 134 * @return {string} 135 */ 136 String.prototype.collapseWhitespace = function() 137 { 138 return this.replace(/[\s\xA0]+/g, " "); 139 } 140 141 /** 142 * @param {number} maxLength 143 * @return {string} 144 */ 145 String.prototype.trimMiddle = function(maxLength) 146 { 147 if (this.length <= maxLength) 148 return String(this); 149 var leftHalf = maxLength >> 1; 150 var rightHalf = maxLength - leftHalf - 1; 151 return this.substr(0, leftHalf) + "\u2026" + this.substr(this.length - rightHalf, rightHalf); 152 } 153 154 /** 155 * @param {number} maxLength 156 * @return {string} 157 */ 158 String.prototype.trimEnd = function(maxLength) 159 { 160 if (this.length <= maxLength) 161 return String(this); 162 return this.substr(0, maxLength - 1) + "\u2026"; 163 } 164 165 /** 166 * @param {?string=} baseURLDomain 167 * @return {string} 168 */ 169 String.prototype.trimURL = function(baseURLDomain) 170 { 171 var result = this.replace(/^(https|http|file):\/\//i, ""); 172 if (baseURLDomain) 173 result = result.replace(new RegExp("^" + baseURLDomain.escapeForRegExp(), "i"), ""); 174 return result; 175 } 176 177 /** 178 * @return {string} 179 */ 180 String.prototype.toTitleCase = function() 181 { 182 return this.substring(0, 1).toUpperCase() + this.substring(1); 183 } 184 185 /** 186 * @param {string} other 187 * @return {number} 188 */ 189 String.prototype.compareTo = function(other) 190 { 191 if (this > other) 192 return 1; 193 if (this < other) 194 return -1; 195 return 0; 196 } 197 198 /** 199 * @param {string} href 200 * @return {string} 201 */ 202 function sanitizeHref(href) 203 { 204 return href && href.trim().toLowerCase().startsWith("javascript:") ? "" : href; 205 } 206 207 /** 208 * @return {string} 209 */ 210 String.prototype.removeURLFragment = function() 211 { 212 var fragmentIndex = this.indexOf("#"); 213 if (fragmentIndex == -1) 214 fragmentIndex = this.length; 215 return this.substring(0, fragmentIndex); 216 } 217 218 /** 219 * @return {boolean} 220 */ 221 String.prototype.startsWith = function(substring) 222 { 223 return !this.lastIndexOf(substring, 0); 224 } 225 226 /** 227 * @return {boolean} 228 */ 229 String.prototype.endsWith = function(substring) 230 { 231 return this.indexOf(substring, this.length - substring.length) !== -1; 232 } 233 234 /** 235 * @param {string} a 236 * @param {string} b 237 * @return {number} 238 */ 239 String.naturalOrderComparator = function(a, b) 240 { 241 var chunk = /^\d+|^\D+/; 242 var chunka, chunkb, anum, bnum; 243 while (1) { 244 if (a) { 245 if (!b) 246 return 1; 247 } else { 248 if (b) 249 return -1; 250 else 251 return 0; 252 } 253 chunka = a.match(chunk)[0]; 254 chunkb = b.match(chunk)[0]; 255 anum = !isNaN(chunka); 256 bnum = !isNaN(chunkb); 257 if (anum && !bnum) 258 return -1; 259 if (bnum && !anum) 260 return 1; 261 if (anum && bnum) { 262 var diff = chunka - chunkb; 263 if (diff) 264 return diff; 265 if (chunka.length !== chunkb.length) { 266 if (!+chunka && !+chunkb) // chunks are strings of all 0s (special case) 267 return chunka.length - chunkb.length; 268 else 269 return chunkb.length - chunka.length; 270 } 271 } else if (chunka !== chunkb) 272 return (chunka < chunkb) ? -1 : 1; 273 a = a.substring(chunka.length); 274 b = b.substring(chunkb.length); 275 } 276 } 277 278 /** 279 * @param {number} num 280 * @param {number} min 281 * @param {number} max 282 * @return {number} 283 */ 284 Number.constrain = function(num, min, max) 285 { 286 if (num < min) 287 num = min; 288 else if (num > max) 289 num = max; 290 return num; 291 } 292 293 /** 294 * @param {number} a 295 * @param {number} b 296 * @return {number} 297 */ 298 Number.gcd = function(a, b) 299 { 300 if (b === 0) 301 return a; 302 else 303 return Number.gcd(b, a % b); 304 } 305 306 /** 307 * @param {string} value 308 * @return {string} 309 */ 310 Number.toFixedIfFloating = function(value) 311 { 312 if (!value || isNaN(value)) 313 return value; 314 var number = Number(value); 315 return number % 1 ? number.toFixed(3) : String(number); 316 } 317 318 /** 319 * @return {string} 320 */ 321 Date.prototype.toISO8601Compact = function() 322 { 323 /** 324 * @param {number} x 325 * @return {string} 326 */ 327 function leadZero(x) 328 { 329 return (x > 9 ? "" : "0") + x; 330 } 331 return this.getFullYear() + 332 leadZero(this.getMonth() + 1) + 333 leadZero(this.getDate()) + "T" + 334 leadZero(this.getHours()) + 335 leadZero(this.getMinutes()) + 336 leadZero(this.getSeconds()); 337 } 338 339 Object.defineProperty(Array.prototype, "remove", 340 { 341 /** 342 * @param {T} value 343 * @param {boolean=} onlyFirst 344 * @this {Array.<T>} 345 * @template T 346 */ 347 value: function(value, onlyFirst) 348 { 349 if (onlyFirst) { 350 var index = this.indexOf(value); 351 if (index !== -1) 352 this.splice(index, 1); 353 return; 354 } 355 356 var length = this.length; 357 for (var i = 0; i < length; ++i) { 358 if (this[i] === value) 359 this.splice(i, 1); 360 } 361 } 362 }); 363 364 Object.defineProperty(Array.prototype, "keySet", 365 { 366 /** 367 * @return {!Object.<string, boolean>} 368 * @this {Array.<*>} 369 */ 370 value: function() 371 { 372 var keys = {}; 373 for (var i = 0; i < this.length; ++i) 374 keys[this[i]] = true; 375 return keys; 376 } 377 }); 378 379 Object.defineProperty(Array.prototype, "rotate", 380 { 381 /** 382 * @param {number} index 383 * @return {!Array.<T>} 384 * @this {Array.<T>} 385 * @template T 386 */ 387 value: function(index) 388 { 389 var result = []; 390 for (var i = index; i < index + this.length; ++i) 391 result.push(this[i % this.length]); 392 return result; 393 } 394 }); 395 396 Object.defineProperty(Uint32Array.prototype, "sort", { 397 value: Array.prototype.sort 398 }); 399 400 (function() { 401 var partition = { 402 /** 403 * @this {Array.<number>} 404 * @param {function(number, number): number} comparator 405 * @param {number} left 406 * @param {number} right 407 * @param {number} pivotIndex 408 */ 409 value: function(comparator, left, right, pivotIndex) 410 { 411 function swap(array, i1, i2) 412 { 413 var temp = array[i1]; 414 array[i1] = array[i2]; 415 array[i2] = temp; 416 } 417 418 var pivotValue = this[pivotIndex]; 419 swap(this, right, pivotIndex); 420 var storeIndex = left; 421 for (var i = left; i < right; ++i) { 422 if (comparator(this[i], pivotValue) < 0) { 423 swap(this, storeIndex, i); 424 ++storeIndex; 425 } 426 } 427 swap(this, right, storeIndex); 428 return storeIndex; 429 } 430 }; 431 Object.defineProperty(Array.prototype, "partition", partition); 432 Object.defineProperty(Uint32Array.prototype, "partition", partition); 433 434 var sortRange = { 435 /** 436 * @param {function(number, number): number} comparator 437 * @param {number} leftBound 438 * @param {number} rightBound 439 * @param {number} k 440 * @return {!Array.<number>} 441 * @this {Array.<number>} 442 */ 443 value: function(comparator, leftBound, rightBound, k) 444 { 445 function quickSortFirstK(array, comparator, left, right, k) 446 { 447 if (right <= left) 448 return; 449 var pivotIndex = Math.floor(Math.random() * (right - left)) + left; 450 var pivotNewIndex = array.partition(comparator, left, right, pivotIndex); 451 quickSortFirstK(array, comparator, left, pivotNewIndex - 1, k); 452 if (pivotNewIndex < left + k - 1) 453 quickSortFirstK(array, comparator, pivotNewIndex + 1, right, left + k - 1 - pivotNewIndex); 454 } 455 456 if (leftBound === 0 && rightBound === (this.length - 1) && k >= this.length) 457 this.sort(comparator); 458 else 459 quickSortFirstK(this, comparator, leftBound, rightBound, k); 460 return this; 461 } 462 } 463 Object.defineProperty(Array.prototype, "sortRange", sortRange); 464 Object.defineProperty(Uint32Array.prototype, "sortRange", sortRange); 465 })(); 466 467 Object.defineProperty(Array.prototype, "qselect", 468 { 469 /** 470 * @param {number} k 471 * @param {function(number, number): number=} comparator 472 * @return {number|undefined} 473 * @this {Array.<number>} 474 */ 475 value: function(k, comparator) 476 { 477 if (k < 0 || k >= this.length) 478 return; 479 if (!comparator) 480 comparator = function(a, b) { return a - b; } 481 482 var low = 0; 483 var high = this.length - 1; 484 for (;;) { 485 var pivotPosition = this.partition(comparator, low, high, Math.floor((high + low) / 2)); 486 if (pivotPosition === k) 487 return this[k]; 488 else if (pivotPosition > k) 489 high = pivotPosition - 1; 490 else 491 low = pivotPosition + 1; 492 } 493 } 494 }); 495 496 Object.defineProperty(Array.prototype, "lowerBound", 497 { 498 /** 499 * Return index of the leftmost element that is equal or greater 500 * than the specimen object. If there's no such element (i.e. all 501 * elements are smaller than the specimen) returns array.length. 502 * The function works for sorted array. 503 * 504 * @param {T} object 505 * @param {function(T,S):number=} comparator 506 * @return {number} 507 * @this {Array.<S>} 508 * @template T,S 509 */ 510 value: function(object, comparator) 511 { 512 function defaultComparator(a, b) 513 { 514 return a < b ? -1 : (a > b ? 1 : 0); 515 } 516 comparator = comparator || defaultComparator; 517 var l = 0; 518 var r = this.length; 519 while (l < r) { 520 var m = (l + r) >> 1; 521 if (comparator(object, this[m]) > 0) 522 l = m + 1; 523 else 524 r = m; 525 } 526 return r; 527 } 528 }); 529 530 Object.defineProperty(Array.prototype, "upperBound", 531 { 532 /** 533 * Return index of the leftmost element that is greater 534 * than the specimen object. If there's no such element (i.e. all 535 * elements are smaller than the specimen) returns array.length. 536 * The function works for sorted array. 537 * 538 * @param {T} object 539 * @param {function(T,S):number=} comparator 540 * @return {number} 541 * @this {Array.<S>} 542 * @template T,S 543 */ 544 value: function(object, comparator) 545 { 546 function defaultComparator(a, b) 547 { 548 return a < b ? -1 : (a > b ? 1 : 0); 549 } 550 comparator = comparator || defaultComparator; 551 var l = 0; 552 var r = this.length; 553 while (l < r) { 554 var m = (l + r) >> 1; 555 if (comparator(object, this[m]) >= 0) 556 l = m + 1; 557 else 558 r = m; 559 } 560 return r; 561 } 562 }); 563 564 Object.defineProperty(Array.prototype, "binaryIndexOf", 565 { 566 /** 567 * @param {T} value 568 * @param {function(T,S):number} comparator 569 * @return {number} 570 * @this {Array.<S>} 571 * @template T,S 572 */ 573 value: function(value, comparator) 574 { 575 var index = this.lowerBound(value, comparator); 576 return index < this.length && comparator(value, this[index]) === 0 ? index : -1; 577 } 578 }); 579 580 Object.defineProperty(Array.prototype, "select", 581 { 582 /** 583 * @param {string} field 584 * @return {!Array.<T>} 585 * @this {Array.<Object.<string,T>>} 586 * @template T 587 */ 588 value: function(field) 589 { 590 var result = new Array(this.length); 591 for (var i = 0; i < this.length; ++i) 592 result[i] = this[i][field]; 593 return result; 594 } 595 }); 596 597 Object.defineProperty(Array.prototype, "peekLast", 598 { 599 /** 600 * @return {T|undefined} 601 * @this {Array.<T>} 602 * @template T 603 */ 604 value: function() 605 { 606 return this[this.length - 1]; 607 } 608 }); 609 610 /** 611 * @param {T} object 612 * @param {Array.<S>} list 613 * @param {function(T,S):number=} comparator 614 * @param {boolean=} insertionIndexAfter 615 * @return {number} 616 * @template T,S 617 */ 618 function insertionIndexForObjectInListSortedByFunction(object, list, comparator, insertionIndexAfter) 619 { 620 if (insertionIndexAfter) 621 return list.upperBound(object, comparator); 622 else 623 return list.lowerBound(object, comparator); 624 } 625 626 /** 627 * @param {string} format 628 * @param {...*} var_arg 629 * @return {string} 630 */ 631 String.sprintf = function(format, var_arg) 632 { 633 return String.vsprintf(format, Array.prototype.slice.call(arguments, 1)); 634 } 635 636 String.tokenizeFormatString = function(format, formatters) 637 { 638 var tokens = []; 639 var substitutionIndex = 0; 640 641 function addStringToken(str) 642 { 643 tokens.push({ type: "string", value: str }); 644 } 645 646 function addSpecifierToken(specifier, precision, substitutionIndex) 647 { 648 tokens.push({ type: "specifier", specifier: specifier, precision: precision, substitutionIndex: substitutionIndex }); 649 } 650 651 function isDigit(c) 652 { 653 return !!/[0-9]/.exec(c); 654 } 655 656 var index = 0; 657 for (var precentIndex = format.indexOf("%", index); precentIndex !== -1; precentIndex = format.indexOf("%", index)) { 658 addStringToken(format.substring(index, precentIndex)); 659 index = precentIndex + 1; 660 661 if (isDigit(format[index])) { 662 // The first character is a number, it might be a substitution index. 663 var number = parseInt(format.substring(index), 10); 664 while (isDigit(format[index])) 665 ++index; 666 667 // If the number is greater than zero and ends with a "$", 668 // then this is a substitution index. 669 if (number > 0 && format[index] === "$") { 670 substitutionIndex = (number - 1); 671 ++index; 672 } 673 } 674 675 var precision = -1; 676 if (format[index] === ".") { 677 // This is a precision specifier. If no digit follows the ".", 678 // then the precision should be zero. 679 ++index; 680 precision = parseInt(format.substring(index), 10); 681 if (isNaN(precision)) 682 precision = 0; 683 684 while (isDigit(format[index])) 685 ++index; 686 } 687 688 if (!(format[index] in formatters)) { 689 addStringToken(format.substring(precentIndex, index + 1)); 690 ++index; 691 continue; 692 } 693 694 addSpecifierToken(format[index], precision, substitutionIndex); 695 696 ++substitutionIndex; 697 ++index; 698 } 699 700 addStringToken(format.substring(index)); 701 702 return tokens; 703 } 704 705 String.standardFormatters = { 706 d: function(substitution) 707 { 708 return !isNaN(substitution) ? substitution : 0; 709 }, 710 711 f: function(substitution, token) 712 { 713 if (substitution && token.precision > -1) 714 substitution = substitution.toFixed(token.precision); 715 return !isNaN(substitution) ? substitution : (token.precision > -1 ? Number(0).toFixed(token.precision) : 0); 716 }, 717 718 s: function(substitution) 719 { 720 return substitution; 721 } 722 } 723 724 /** 725 * @param {string} format 726 * @param {Array.<*>} substitutions 727 * @return {string} 728 */ 729 String.vsprintf = function(format, substitutions) 730 { 731 return String.format(format, substitutions, String.standardFormatters, "", function(a, b) { return a + b; }).formattedResult; 732 } 733 734 String.format = function(format, substitutions, formatters, initialValue, append) 735 { 736 if (!format || !substitutions || !substitutions.length) 737 return { formattedResult: append(initialValue, format), unusedSubstitutions: substitutions }; 738 739 function prettyFunctionName() 740 { 741 return "String.format(\"" + format + "\", \"" + substitutions.join("\", \"") + "\")"; 742 } 743 744 function warn(msg) 745 { 746 console.warn(prettyFunctionName() + ": " + msg); 747 } 748 749 function error(msg) 750 { 751 console.error(prettyFunctionName() + ": " + msg); 752 } 753 754 var result = initialValue; 755 var tokens = String.tokenizeFormatString(format, formatters); 756 var usedSubstitutionIndexes = {}; 757 758 for (var i = 0; i < tokens.length; ++i) { 759 var token = tokens[i]; 760 761 if (token.type === "string") { 762 result = append(result, token.value); 763 continue; 764 } 765 766 if (token.type !== "specifier") { 767 error("Unknown token type \"" + token.type + "\" found."); 768 continue; 769 } 770 771 if (token.substitutionIndex >= substitutions.length) { 772 // If there are not enough substitutions for the current substitutionIndex 773 // just output the format specifier literally and move on. 774 error("not enough substitution arguments. Had " + substitutions.length + " but needed " + (token.substitutionIndex + 1) + ", so substitution was skipped."); 775 result = append(result, "%" + (token.precision > -1 ? token.precision : "") + token.specifier); 776 continue; 777 } 778 779 usedSubstitutionIndexes[token.substitutionIndex] = true; 780 781 if (!(token.specifier in formatters)) { 782 // Encountered an unsupported format character, treat as a string. 783 warn("unsupported format character \u201C" + token.specifier + "\u201D. Treating as a string."); 784 result = append(result, substitutions[token.substitutionIndex]); 785 continue; 786 } 787 788 result = append(result, formatters[token.specifier](substitutions[token.substitutionIndex], token)); 789 } 790 791 var unusedSubstitutions = []; 792 for (var i = 0; i < substitutions.length; ++i) { 793 if (i in usedSubstitutionIndexes) 794 continue; 795 unusedSubstitutions.push(substitutions[i]); 796 } 797 798 return { formattedResult: result, unusedSubstitutions: unusedSubstitutions }; 799 } 800 801 /** 802 * @param {string} query 803 * @param {boolean} caseSensitive 804 * @param {boolean} isRegex 805 * @return {RegExp} 806 */ 807 function createSearchRegex(query, caseSensitive, isRegex) 808 { 809 var regexFlags = caseSensitive ? "g" : "gi"; 810 var regexObject; 811 812 if (isRegex) { 813 try { 814 regexObject = new RegExp(query, regexFlags); 815 } catch (e) { 816 // Silent catch. 817 } 818 } 819 820 if (!regexObject) 821 regexObject = createPlainTextSearchRegex(query, regexFlags); 822 823 return regexObject; 824 } 825 826 /** 827 * @param {string} query 828 * @param {string=} flags 829 * @return {!RegExp} 830 */ 831 function createPlainTextSearchRegex(query, flags) 832 { 833 // This should be kept the same as the one in ContentSearchUtils.cpp. 834 var regexSpecialCharacters = String.regexSpecialCharacters(); 835 var regex = ""; 836 for (var i = 0; i < query.length; ++i) { 837 var c = query.charAt(i); 838 if (regexSpecialCharacters.indexOf(c) != -1) 839 regex += "\\"; 840 regex += c; 841 } 842 return new RegExp(regex, flags || ""); 843 } 844 845 /** 846 * @param {RegExp} regex 847 * @param {string} content 848 * @return {number} 849 */ 850 function countRegexMatches(regex, content) 851 { 852 var text = content; 853 var result = 0; 854 var match; 855 while (text && (match = regex.exec(text))) { 856 if (match[0].length > 0) 857 ++result; 858 text = text.substring(match.index + 1); 859 } 860 return result; 861 } 862 863 /** 864 * @param {number} value 865 * @param {number} symbolsCount 866 * @return {string} 867 */ 868 function numberToStringWithSpacesPadding(value, symbolsCount) 869 { 870 var numberString = value.toString(); 871 var paddingLength = Math.max(0, symbolsCount - numberString.length); 872 var paddingString = Array(paddingLength + 1).join("\u00a0"); 873 return paddingString + numberString; 874 } 875 876 /** 877 * @return {string} 878 */ 879 var createObjectIdentifier = function() 880 { 881 // It has to be string for better performance. 882 return "_" + ++createObjectIdentifier._last; 883 } 884 885 createObjectIdentifier._last = 0; 886 887 /** 888 * @constructor 889 * @template T 890 */ 891 var Set = function() 892 { 893 /** @type {!Object.<string, !T>} */ 894 this._set = {}; 895 this._size = 0; 896 } 897 898 Set.prototype = { 899 /** 900 * @param {!T} item 901 */ 902 add: function(item) 903 { 904 var objectIdentifier = item.__identifier; 905 if (!objectIdentifier) { 906 objectIdentifier = createObjectIdentifier(); 907 item.__identifier = objectIdentifier; 908 } 909 if (!this._set[objectIdentifier]) 910 ++this._size; 911 this._set[objectIdentifier] = item; 912 }, 913 914 /** 915 * @param {!T} item 916 * @return {boolean} 917 */ 918 remove: function(item) 919 { 920 if (this._set[item.__identifier]) { 921 --this._size; 922 delete this._set[item.__identifier]; 923 return true; 924 } 925 return false; 926 }, 927 928 /** 929 * @return {!Array.<!T>} 930 */ 931 items: function() 932 { 933 var result = new Array(this._size); 934 var i = 0; 935 for (var objectIdentifier in this._set) 936 result[i++] = this._set[objectIdentifier]; 937 return result; 938 }, 939 940 /** 941 * @param {!T} item 942 * @return {boolean} 943 */ 944 hasItem: function(item) 945 { 946 return !!this._set[item.__identifier]; 947 }, 948 949 /** 950 * @return {number} 951 */ 952 size: function() 953 { 954 return this._size; 955 }, 956 957 clear: function() 958 { 959 this._set = {}; 960 this._size = 0; 961 } 962 } 963 964 /** 965 * @constructor 966 * @template K,V 967 */ 968 var Map = function() 969 { 970 /** @type {!Object.<string, !Array.<K|V>>} */ 971 this._map = {}; 972 this._size = 0; 973 } 974 975 Map.prototype = { 976 /** 977 * @param {!K} key 978 * @param {V=} value 979 */ 980 put: function(key, value) 981 { 982 var objectIdentifier = key.__identifier; 983 if (!objectIdentifier) { 984 objectIdentifier = createObjectIdentifier(); 985 key.__identifier = objectIdentifier; 986 } 987 if (!this._map[objectIdentifier]) 988 ++this._size; 989 this._map[objectIdentifier] = [key, value]; 990 }, 991 992 /** 993 * @param {!K} key 994 */ 995 remove: function(key) 996 { 997 var result = this._map[key.__identifier]; 998 if (!result) 999 return undefined; 1000 --this._size; 1001 delete this._map[key.__identifier]; 1002 return result[1]; 1003 }, 1004 1005 /** 1006 * @return {!Array.<!K>} 1007 */ 1008 keys: function() 1009 { 1010 return this._list(0); 1011 }, 1012 1013 /** 1014 * @return {!Array.<V>} 1015 */ 1016 values: function() 1017 { 1018 return this._list(1); 1019 }, 1020 1021 /** 1022 * @param {number} index 1023 * @return {!Array.<K|V>} 1024 */ 1025 _list: function(index) 1026 { 1027 var result = new Array(this._size); 1028 var i = 0; 1029 for (var objectIdentifier in this._map) 1030 result[i++] = this._map[objectIdentifier][index]; 1031 return result; 1032 }, 1033 1034 /** 1035 * @param {!K} key 1036 * @return {V|undefined} 1037 */ 1038 get: function(key) 1039 { 1040 var entry = this._map[key.__identifier]; 1041 return entry ? entry[1] : undefined; 1042 }, 1043 1044 /** 1045 * @param {!K} key 1046 * @return {boolean} 1047 */ 1048 contains: function(key) 1049 { 1050 var entry = this._map[key.__identifier]; 1051 return !!entry; 1052 }, 1053 1054 /** 1055 * @return {number} 1056 */ 1057 size: function() 1058 { 1059 return this._size; 1060 }, 1061 1062 clear: function() 1063 { 1064 this._map = {}; 1065 this._size = 0; 1066 } 1067 } 1068 1069 /** 1070 * @constructor 1071 * @template T 1072 */ 1073 var StringMap = function() 1074 { 1075 /** @type {!Object.<string, T>} */ 1076 this._map = {}; 1077 this._size = 0; 1078 } 1079 1080 StringMap.prototype = { 1081 /** 1082 * @param {string} key 1083 * @param {T} value 1084 */ 1085 put: function(key, value) 1086 { 1087 if (key === "__proto__") { 1088 if (!this._hasProtoKey) { 1089 ++this._size; 1090 this._hasProtoKey = true; 1091 } 1092 /** @type {T} */ 1093 this._protoValue = value; 1094 return; 1095 } 1096 if (!Object.prototype.hasOwnProperty.call(this._map, key)) 1097 ++this._size; 1098 this._map[key] = value; 1099 }, 1100 1101 /** 1102 * @param {string} key 1103 */ 1104 remove: function(key) 1105 { 1106 var result; 1107 if (key === "__proto__") { 1108 if (!this._hasProtoKey) 1109 return undefined; 1110 --this._size; 1111 delete this._hasProtoKey; 1112 result = this._protoValue; 1113 delete this._protoValue; 1114 return result; 1115 } 1116 if (!Object.prototype.hasOwnProperty.call(this._map, key)) 1117 return undefined; 1118 --this._size; 1119 result = this._map[key]; 1120 delete this._map[key]; 1121 return result; 1122 }, 1123 1124 /** 1125 * @return {!Array.<string>} 1126 */ 1127 keys: function() 1128 { 1129 var result = Object.keys(this._map) || []; 1130 if (this._hasProtoKey) 1131 result.push("__proto__"); 1132 return result; 1133 }, 1134 1135 /** 1136 * @return {!Array.<T>} 1137 */ 1138 values: function() 1139 { 1140 var result = Object.values(this._map); 1141 if (this._hasProtoKey) 1142 result.push(this._protoValue); 1143 return result; 1144 }, 1145 1146 /** 1147 * @param {string} key 1148 */ 1149 get: function(key) 1150 { 1151 if (key === "__proto__") 1152 return this._protoValue; 1153 if (!Object.prototype.hasOwnProperty.call(this._map, key)) 1154 return undefined; 1155 return this._map[key]; 1156 }, 1157 1158 /** 1159 * @param {string} key 1160 * @return {boolean} 1161 */ 1162 contains: function(key) 1163 { 1164 var result; 1165 if (key === "__proto__") 1166 return this._hasProtoKey; 1167 return Object.prototype.hasOwnProperty.call(this._map, key); 1168 }, 1169 1170 /** 1171 * @return {number} 1172 */ 1173 size: function() 1174 { 1175 return this._size; 1176 }, 1177 1178 clear: function() 1179 { 1180 this._map = {}; 1181 this._size = 0; 1182 delete this._hasProtoKey; 1183 delete this._protoValue; 1184 } 1185 } 1186 1187 /** 1188 * @param {string} url 1189 * @param {boolean=} async 1190 * @param {function(?string)=} callback 1191 * @return {?string} 1192 */ 1193 function loadXHR(url, async, callback) 1194 { 1195 function onReadyStateChanged() 1196 { 1197 if (xhr.readyState !== XMLHttpRequest.DONE) 1198 return; 1199 1200 if (xhr.status === 200) { 1201 callback(xhr.responseText); 1202 return; 1203 } 1204 1205 callback(null); 1206 } 1207 1208 var xhr = new XMLHttpRequest(); 1209 xhr.open("GET", url, async); 1210 if (async) 1211 xhr.onreadystatechange = onReadyStateChanged; 1212 xhr.send(null); 1213 1214 if (!async) { 1215 if (xhr.status === 200) 1216 return xhr.responseText; 1217 return null; 1218 } 1219 return null; 1220 } 1221 1222 /** 1223 * @constructor 1224 */ 1225 function StringPool() 1226 { 1227 this.reset(); 1228 } 1229 1230 StringPool.prototype = { 1231 /** 1232 * @param {string} string 1233 * @return {string} 1234 */ 1235 intern: function(string) 1236 { 1237 // Do not mess with setting __proto__ to anything but null, just handle it explicitly. 1238 if (string === "__proto__") 1239 return "__proto__"; 1240 var result = this._strings[string]; 1241 if (result === undefined) { 1242 this._strings[string] = string; 1243 result = string; 1244 } 1245 return result; 1246 }, 1247 1248 reset: function() 1249 { 1250 this._strings = Object.create(null); 1251 }, 1252 1253 /** 1254 * @param {Object} obj 1255 * @param {number=} depthLimit 1256 */ 1257 internObjectStrings: function(obj, depthLimit) 1258 { 1259 if (typeof depthLimit !== "number") 1260 depthLimit = 100; 1261 else if (--depthLimit < 0) 1262 throw "recursion depth limit reached in StringPool.deepIntern(), perhaps attempting to traverse cyclical references?"; 1263 1264 for (var field in obj) { 1265 switch (typeof obj[field]) { 1266 case "string": 1267 obj[field] = this.intern(obj[field]); 1268 break; 1269 case "object": 1270 this.internObjectStrings(obj[field], depthLimit); 1271 break; 1272 } 1273 } 1274 } 1275 } 1276 1277 var _importedScripts = {}; 1278 1279 /** 1280 * This function behavior depends on the "debug_devtools" flag value. 1281 * - In debug mode it loads scripts synchronously via xhr request. 1282 * - In release mode every occurrence of "importScript" in the js files 1283 * that have been white listed in the build system gets replaced with 1284 * the script source code on the compilation phase. 1285 * The build system will throw an exception if it found importScript call 1286 * in other files. 1287 * 1288 * To load scripts lazily in release mode call "loadScript" function. 1289 * @param {string} scriptName 1290 */ 1291 function importScript(scriptName) 1292 { 1293 if (_importedScripts[scriptName]) 1294 return; 1295 var xhr = new XMLHttpRequest(); 1296 _importedScripts[scriptName] = true; 1297 if (window.flattenImports) 1298 scriptName = scriptName.split("/").reverse()[0]; 1299 xhr.open("GET", scriptName, false); 1300 xhr.send(null); 1301 if (!xhr.responseText) 1302 throw "empty response arrived for script '" + scriptName + "'"; 1303 var sourceURL = WebInspector.ParsedURL.completeURL(window.location.href, scriptName); 1304 window.eval(xhr.responseText + "\n//# sourceURL=" + sourceURL); 1305 } 1306 1307 var loadScript = importScript; 1308 1309 /** 1310 * @constructor 1311 */ 1312 function CallbackBarrier() 1313 { 1314 this._pendingIncomingCallbacksCount = 0; 1315 } 1316 1317 CallbackBarrier.prototype = { 1318 /** 1319 * @param {function(T)=} userCallback 1320 * @return {function(T=)} 1321 * @template T 1322 */ 1323 createCallback: function(userCallback) 1324 { 1325 console.assert(!this._outgoingCallback, "CallbackBarrier.createCallback() is called after CallbackBarrier.callWhenDone()"); 1326 ++this._pendingIncomingCallbacksCount; 1327 return this._incomingCallback.bind(this, userCallback); 1328 }, 1329 1330 /** 1331 * @param {function()} callback 1332 */ 1333 callWhenDone: function(callback) 1334 { 1335 console.assert(!this._outgoingCallback, "CallbackBarrier.callWhenDone() is called multiple times"); 1336 this._outgoingCallback = callback; 1337 if (!this._pendingIncomingCallbacksCount) 1338 this._outgoingCallback(); 1339 }, 1340 1341 /** 1342 * @param {function(...)=} userCallback 1343 */ 1344 _incomingCallback: function(userCallback) 1345 { 1346 console.assert(this._pendingIncomingCallbacksCount > 0); 1347 if (userCallback) { 1348 var args = Array.prototype.slice.call(arguments, 1); 1349 userCallback.apply(null, args); 1350 } 1351 if (!--this._pendingIncomingCallbacksCount && this._outgoingCallback) 1352 this._outgoingCallback(); 1353 } 1354 } 1355