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 */ 34Object.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 */ 46Object.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 */ 59String.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 */ 73String.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 */ 86String.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 */ 112String.regexSpecialCharacters = function() 113{ 114 return "^[]{}()\\.$*+?|-,"; 115} 116 117/** 118 * @return {string} 119 */ 120String.prototype.escapeForRegExp = function() 121{ 122 return this.escapeCharacters(String.regexSpecialCharacters()); 123} 124 125/** 126 * @return {string} 127 */ 128String.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 */ 136String.prototype.collapseWhitespace = function() 137{ 138 return this.replace(/[\s\xA0]+/g, " "); 139} 140 141/** 142 * @param {number} maxLength 143 * @return {string} 144 */ 145String.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 */ 158String.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 */ 169String.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 */ 180String.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 */ 189String.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 */ 202function sanitizeHref(href) 203{ 204 return href && href.trim().toLowerCase().startsWith("javascript:") ? null : href; 205} 206 207/** 208 * @return {string} 209 */ 210String.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 */ 221String.prototype.startsWith = function(substring) 222{ 223 return !this.lastIndexOf(substring, 0); 224} 225 226/** 227 * @return {boolean} 228 */ 229String.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 */ 239String.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 */ 284Number.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 */ 298Number.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 */ 310Number.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 */ 321Date.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 339Object.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 364Object.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 379Object.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 396Object.defineProperty(Uint32Array.prototype, "sort", { 397 value: Array.prototype.sort 398}); 399 400(function() { 401var 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}; 431Object.defineProperty(Array.prototype, "partition", partition); 432Object.defineProperty(Uint32Array.prototype, "partition", partition); 433 434var sortRange = { 435 /** 436 * @param {function(number, number): number} comparator 437 * @param {number} leftBound 438 * @param {number} rightBound 439 * @param {number} sortWindowLeft 440 * @param {number} sortWindowRight 441 * @return {!Array.<number>} 442 * @this {Array.<number>} 443 */ 444 value: function(comparator, leftBound, rightBound, sortWindowLeft, sortWindowRight) 445 { 446 function quickSortRange(array, comparator, left, right, sortWindowLeft, sortWindowRight) 447 { 448 if (right <= left) 449 return; 450 var pivotIndex = Math.floor(Math.random() * (right - left)) + left; 451 var pivotNewIndex = array.partition(comparator, left, right, pivotIndex); 452 if (sortWindowLeft < pivotNewIndex) 453 quickSortRange(array, comparator, left, pivotNewIndex - 1, sortWindowLeft, sortWindowRight); 454 if (pivotNewIndex < sortWindowRight) 455 quickSortRange(array, comparator, pivotNewIndex + 1, right, sortWindowLeft, sortWindowRight); 456 } 457 if (leftBound === 0 && rightBound === (this.length - 1) && sortWindowLeft === 0 && sortWindowRight >= rightBound) 458 this.sort(comparator); 459 else 460 quickSortRange(this, comparator, leftBound, rightBound, sortWindowLeft, sortWindowRight); 461 return this; 462 } 463} 464Object.defineProperty(Array.prototype, "sortRange", sortRange); 465Object.defineProperty(Uint32Array.prototype, "sortRange", sortRange); 466})(); 467 468Object.defineProperty(Array.prototype, "stableSort", 469{ 470 /** 471 * @param {function(?T, ?T): number=} comparator 472 * @return {!Array.<?T>} 473 * @this {Array.<?T>} 474 * @template T 475 */ 476 value: function(comparator) 477 { 478 function defaultComparator(a, b) 479 { 480 return a < b ? -1 : (a > b ? 1 : 0); 481 } 482 comparator = comparator || defaultComparator; 483 484 var indices = new Array(this.length); 485 for (var i = 0; i < this.length; ++i) 486 indices[i] = i; 487 var self = this; 488 /** 489 * @param {number} a 490 * @param {number} b 491 * @return {number} 492 */ 493 function indexComparator(a, b) 494 { 495 var result = comparator(self[a], self[b]); 496 return result ? result : a - b; 497 } 498 indices.sort(indexComparator); 499 500 for (var i = 0; i < this.length; ++i) { 501 if (indices[i] < 0 || i === indices[i]) 502 continue; 503 var cyclical = i; 504 var saved = this[i]; 505 while (true) { 506 var next = indices[cyclical]; 507 indices[cyclical] = -1; 508 if (next === i) { 509 this[cyclical] = saved; 510 break; 511 } else { 512 this[cyclical] = this[next]; 513 cyclical = next; 514 } 515 } 516 } 517 return this; 518 } 519}); 520 521Object.defineProperty(Array.prototype, "qselect", 522{ 523 /** 524 * @param {number} k 525 * @param {function(number, number): number=} comparator 526 * @return {number|undefined} 527 * @this {Array.<number>} 528 */ 529 value: function(k, comparator) 530 { 531 if (k < 0 || k >= this.length) 532 return; 533 if (!comparator) 534 comparator = function(a, b) { return a - b; } 535 536 var low = 0; 537 var high = this.length - 1; 538 for (;;) { 539 var pivotPosition = this.partition(comparator, low, high, Math.floor((high + low) / 2)); 540 if (pivotPosition === k) 541 return this[k]; 542 else if (pivotPosition > k) 543 high = pivotPosition - 1; 544 else 545 low = pivotPosition + 1; 546 } 547 } 548}); 549 550Object.defineProperty(Array.prototype, "lowerBound", 551{ 552 /** 553 * Return index of the leftmost element that is equal or greater 554 * than the specimen object. If there's no such element (i.e. all 555 * elements are smaller than the specimen) returns array.length. 556 * The function works for sorted array. 557 * 558 * @param {!T} object 559 * @param {function(!T,!S):number=} comparator 560 * @return {number} 561 * @this {Array.<!S>} 562 * @template T,S 563 */ 564 value: function(object, comparator) 565 { 566 function defaultComparator(a, b) 567 { 568 return a < b ? -1 : (a > b ? 1 : 0); 569 } 570 comparator = comparator || defaultComparator; 571 var l = 0; 572 var r = this.length; 573 while (l < r) { 574 var m = (l + r) >> 1; 575 if (comparator(object, this[m]) > 0) 576 l = m + 1; 577 else 578 r = m; 579 } 580 return r; 581 } 582}); 583 584Object.defineProperty(Array.prototype, "upperBound", 585{ 586 /** 587 * Return index of the leftmost element that is greater 588 * than the specimen object. If there's no such element (i.e. all 589 * elements are smaller than the specimen) returns array.length. 590 * The function works for sorted array. 591 * 592 * @param {!T} object 593 * @param {function(!T,!S):number=} comparator 594 * @return {number} 595 * @this {Array.<!S>} 596 * @template T,S 597 */ 598 value: function(object, comparator) 599 { 600 function defaultComparator(a, b) 601 { 602 return a < b ? -1 : (a > b ? 1 : 0); 603 } 604 comparator = comparator || defaultComparator; 605 var l = 0; 606 var r = this.length; 607 while (l < r) { 608 var m = (l + r) >> 1; 609 if (comparator(object, this[m]) >= 0) 610 l = m + 1; 611 else 612 r = m; 613 } 614 return r; 615 } 616}); 617 618Object.defineProperty(Array.prototype, "binaryIndexOf", 619{ 620 /** 621 * @param {!T} value 622 * @param {function(!T,!S):number} comparator 623 * @return {number} 624 * @this {Array.<!S>} 625 * @template T,S 626 */ 627 value: function(value, comparator) 628 { 629 var index = this.lowerBound(value, comparator); 630 return index < this.length && comparator(value, this[index]) === 0 ? index : -1; 631 } 632}); 633 634Object.defineProperty(Array.prototype, "select", 635{ 636 /** 637 * @param {string} field 638 * @return {!Array.<!T>} 639 * @this {Array.<!Object.<string,!T>>} 640 * @template T 641 */ 642 value: function(field) 643 { 644 var result = new Array(this.length); 645 for (var i = 0; i < this.length; ++i) 646 result[i] = this[i][field]; 647 return result; 648 } 649}); 650 651Object.defineProperty(Array.prototype, "peekLast", 652{ 653 /** 654 * @return {!T|undefined} 655 * @this {Array.<!T>} 656 * @template T 657 */ 658 value: function() 659 { 660 return this[this.length - 1]; 661 } 662}); 663 664(function(){ 665 666/** 667 * @param {!Array.<T>} array1 668 * @param {!Array.<T>} array2 669 * @param {function(T,T):number} comparator 670 * @return {!Array.<T>} 671 * @template T 672 */ 673function mergeOrIntersect(array1, array2, comparator, mergeNotIntersect) 674{ 675 var result = []; 676 var i = 0; 677 var j = 0; 678 while (i < array1.length || j < array2.length) { 679 if (i === array1.length) { 680 result = result.concat(array2.slice(j)); 681 j = array2.length; 682 } else if (j === array2.length) { 683 result = result.concat(array1.slice(i)); 684 i = array1.length; 685 } else { 686 var compareValue = comparator(array1[i], array2[j]) 687 if (compareValue < 0) { 688 if (mergeNotIntersect) 689 result.push(array1[i]); 690 ++i; 691 } else if (compareValue > 0) { 692 if (mergeNotIntersect) 693 result.push(array2[j]); 694 ++j; 695 } else { 696 result.push(array1[i]); 697 ++i; 698 ++j; 699 } 700 } 701 } 702 return result; 703} 704 705Object.defineProperty(Array.prototype, "intersectOrdered", 706{ 707 /** 708 * @param {!Array.<T>} array 709 * @param {function(T,T):number} comparator 710 * @return {!Array.<T>} 711 * @this {!Array.<T>} 712 * @template T 713 */ 714 value: function(array, comparator) 715 { 716 return mergeOrIntersect(this, array, comparator, false); 717 } 718}); 719 720Object.defineProperty(Array.prototype, "mergeOrdered", 721{ 722 /** 723 * @param {!Array.<T>} array 724 * @param {function(T,T):number} comparator 725 * @return {!Array.<T>} 726 * @this {!Array.<T>} 727 * @template T 728 */ 729 value: function(array, comparator) 730 { 731 return mergeOrIntersect(this, array, comparator, true); 732 } 733}); 734 735}()); 736 737 738/** 739 * @param {!T} object 740 * @param {!Array.<!S>} list 741 * @param {function(!T,!S):number=} comparator 742 * @param {boolean=} insertionIndexAfter 743 * @return {number} 744 * @template T,S 745 */ 746function insertionIndexForObjectInListSortedByFunction(object, list, comparator, insertionIndexAfter) 747{ 748 if (insertionIndexAfter) 749 return list.upperBound(object, comparator); 750 else 751 return list.lowerBound(object, comparator); 752} 753 754/** 755 * @param {string} format 756 * @param {...*} var_arg 757 * @return {string} 758 */ 759String.sprintf = function(format, var_arg) 760{ 761 return String.vsprintf(format, Array.prototype.slice.call(arguments, 1)); 762} 763 764String.tokenizeFormatString = function(format, formatters) 765{ 766 var tokens = []; 767 var substitutionIndex = 0; 768 769 function addStringToken(str) 770 { 771 tokens.push({ type: "string", value: str }); 772 } 773 774 function addSpecifierToken(specifier, precision, substitutionIndex) 775 { 776 tokens.push({ type: "specifier", specifier: specifier, precision: precision, substitutionIndex: substitutionIndex }); 777 } 778 779 function isDigit(c) 780 { 781 return !!/[0-9]/.exec(c); 782 } 783 784 var index = 0; 785 for (var precentIndex = format.indexOf("%", index); precentIndex !== -1; precentIndex = format.indexOf("%", index)) { 786 addStringToken(format.substring(index, precentIndex)); 787 index = precentIndex + 1; 788 789 if (isDigit(format[index])) { 790 // The first character is a number, it might be a substitution index. 791 var number = parseInt(format.substring(index), 10); 792 while (isDigit(format[index])) 793 ++index; 794 795 // If the number is greater than zero and ends with a "$", 796 // then this is a substitution index. 797 if (number > 0 && format[index] === "$") { 798 substitutionIndex = (number - 1); 799 ++index; 800 } 801 } 802 803 var precision = -1; 804 if (format[index] === ".") { 805 // This is a precision specifier. If no digit follows the ".", 806 // then the precision should be zero. 807 ++index; 808 precision = parseInt(format.substring(index), 10); 809 if (isNaN(precision)) 810 precision = 0; 811 812 while (isDigit(format[index])) 813 ++index; 814 } 815 816 if (!(format[index] in formatters)) { 817 addStringToken(format.substring(precentIndex, index + 1)); 818 ++index; 819 continue; 820 } 821 822 addSpecifierToken(format[index], precision, substitutionIndex); 823 824 ++substitutionIndex; 825 ++index; 826 } 827 828 addStringToken(format.substring(index)); 829 830 return tokens; 831} 832 833String.standardFormatters = { 834 d: function(substitution) 835 { 836 return !isNaN(substitution) ? substitution : 0; 837 }, 838 839 f: function(substitution, token) 840 { 841 if (substitution && token.precision > -1) 842 substitution = substitution.toFixed(token.precision); 843 return !isNaN(substitution) ? substitution : (token.precision > -1 ? Number(0).toFixed(token.precision) : 0); 844 }, 845 846 s: function(substitution) 847 { 848 return substitution; 849 } 850} 851 852/** 853 * @param {string} format 854 * @param {!Array.<*>} substitutions 855 * @return {string} 856 */ 857String.vsprintf = function(format, substitutions) 858{ 859 return String.format(format, substitutions, String.standardFormatters, "", function(a, b) { return a + b; }).formattedResult; 860} 861 862String.format = function(format, substitutions, formatters, initialValue, append) 863{ 864 if (!format || !substitutions || !substitutions.length) 865 return { formattedResult: append(initialValue, format), unusedSubstitutions: substitutions }; 866 867 function prettyFunctionName() 868 { 869 return "String.format(\"" + format + "\", \"" + substitutions.join("\", \"") + "\")"; 870 } 871 872 function warn(msg) 873 { 874 console.warn(prettyFunctionName() + ": " + msg); 875 } 876 877 function error(msg) 878 { 879 console.error(prettyFunctionName() + ": " + msg); 880 } 881 882 var result = initialValue; 883 var tokens = String.tokenizeFormatString(format, formatters); 884 var usedSubstitutionIndexes = {}; 885 886 for (var i = 0; i < tokens.length; ++i) { 887 var token = tokens[i]; 888 889 if (token.type === "string") { 890 result = append(result, token.value); 891 continue; 892 } 893 894 if (token.type !== "specifier") { 895 error("Unknown token type \"" + token.type + "\" found."); 896 continue; 897 } 898 899 if (token.substitutionIndex >= substitutions.length) { 900 // If there are not enough substitutions for the current substitutionIndex 901 // just output the format specifier literally and move on. 902 error("not enough substitution arguments. Had " + substitutions.length + " but needed " + (token.substitutionIndex + 1) + ", so substitution was skipped."); 903 result = append(result, "%" + (token.precision > -1 ? token.precision : "") + token.specifier); 904 continue; 905 } 906 907 usedSubstitutionIndexes[token.substitutionIndex] = true; 908 909 if (!(token.specifier in formatters)) { 910 // Encountered an unsupported format character, treat as a string. 911 warn("unsupported format character \u201C" + token.specifier + "\u201D. Treating as a string."); 912 result = append(result, substitutions[token.substitutionIndex]); 913 continue; 914 } 915 916 result = append(result, formatters[token.specifier](substitutions[token.substitutionIndex], token)); 917 } 918 919 var unusedSubstitutions = []; 920 for (var i = 0; i < substitutions.length; ++i) { 921 if (i in usedSubstitutionIndexes) 922 continue; 923 unusedSubstitutions.push(substitutions[i]); 924 } 925 926 return { formattedResult: result, unusedSubstitutions: unusedSubstitutions }; 927} 928 929/** 930 * @param {string} query 931 * @param {boolean} caseSensitive 932 * @param {boolean} isRegex 933 * @return {!RegExp} 934 */ 935function createSearchRegex(query, caseSensitive, isRegex) 936{ 937 var regexFlags = caseSensitive ? "g" : "gi"; 938 var regexObject; 939 940 if (isRegex) { 941 try { 942 regexObject = new RegExp(query, regexFlags); 943 } catch (e) { 944 // Silent catch. 945 } 946 } 947 948 if (!regexObject) 949 regexObject = createPlainTextSearchRegex(query, regexFlags); 950 951 return regexObject; 952} 953 954/** 955 * @param {string} query 956 * @param {string=} flags 957 * @return {!RegExp} 958 */ 959function createPlainTextSearchRegex(query, flags) 960{ 961 // This should be kept the same as the one in ContentSearchUtils.cpp. 962 var regexSpecialCharacters = String.regexSpecialCharacters(); 963 var regex = ""; 964 for (var i = 0; i < query.length; ++i) { 965 var c = query.charAt(i); 966 if (regexSpecialCharacters.indexOf(c) != -1) 967 regex += "\\"; 968 regex += c; 969 } 970 return new RegExp(regex, flags || ""); 971} 972 973/** 974 * @param {!RegExp} regex 975 * @param {string} content 976 * @return {number} 977 */ 978function countRegexMatches(regex, content) 979{ 980 var text = content; 981 var result = 0; 982 var match; 983 while (text && (match = regex.exec(text))) { 984 if (match[0].length > 0) 985 ++result; 986 text = text.substring(match.index + 1); 987 } 988 return result; 989} 990 991/** 992 * @param {number} value 993 * @param {number} symbolsCount 994 * @return {string} 995 */ 996function numberToStringWithSpacesPadding(value, symbolsCount) 997{ 998 var numberString = value.toString(); 999 var paddingLength = Math.max(0, symbolsCount - numberString.length); 1000 var paddingString = Array(paddingLength + 1).join("\u00a0"); 1001 return paddingString + numberString; 1002} 1003 1004/** 1005 * @return {string} 1006 */ 1007var createObjectIdentifier = function() 1008{ 1009 // It has to be string for better performance. 1010 return "_" + ++createObjectIdentifier._last; 1011} 1012 1013createObjectIdentifier._last = 0; 1014 1015/** 1016 * @constructor 1017 * @template T 1018 */ 1019var Set = function() 1020{ 1021 /** @type {!Object.<string, !T>} */ 1022 this._set = {}; 1023 this._size = 0; 1024} 1025 1026Set.prototype = { 1027 /** 1028 * @param {!T} item 1029 */ 1030 add: function(item) 1031 { 1032 var objectIdentifier = item.__identifier; 1033 if (!objectIdentifier) { 1034 objectIdentifier = createObjectIdentifier(); 1035 item.__identifier = objectIdentifier; 1036 } 1037 if (!this._set[objectIdentifier]) 1038 ++this._size; 1039 this._set[objectIdentifier] = item; 1040 }, 1041 1042 /** 1043 * @param {!T} item 1044 * @return {boolean} 1045 */ 1046 remove: function(item) 1047 { 1048 if (this._set[item.__identifier]) { 1049 --this._size; 1050 delete this._set[item.__identifier]; 1051 return true; 1052 } 1053 return false; 1054 }, 1055 1056 /** 1057 * @return {!Array.<!T>} 1058 */ 1059 items: function() 1060 { 1061 var result = new Array(this._size); 1062 var i = 0; 1063 for (var objectIdentifier in this._set) 1064 result[i++] = this._set[objectIdentifier]; 1065 return result; 1066 }, 1067 1068 /** 1069 * @param {!T} item 1070 * @return {boolean} 1071 */ 1072 hasItem: function(item) 1073 { 1074 return !!this._set[item.__identifier]; 1075 }, 1076 1077 /** 1078 * @return {number} 1079 */ 1080 size: function() 1081 { 1082 return this._size; 1083 }, 1084 1085 clear: function() 1086 { 1087 this._set = {}; 1088 this._size = 0; 1089 } 1090} 1091 1092/** 1093 * @constructor 1094 * @template K,V 1095 */ 1096var Map = function() 1097{ 1098 /** @type {!Object.<string, !Array.<K|V>>} */ 1099 this._map = {}; 1100 this._size = 0; 1101} 1102 1103Map.prototype = { 1104 /** 1105 * @param {K} key 1106 * @param {V} value 1107 */ 1108 put: function(key, value) 1109 { 1110 var objectIdentifier = key.__identifier; 1111 if (!objectIdentifier) { 1112 objectIdentifier = createObjectIdentifier(); 1113 key.__identifier = objectIdentifier; 1114 } 1115 if (!this._map[objectIdentifier]) 1116 ++this._size; 1117 this._map[objectIdentifier] = [key, value]; 1118 }, 1119 1120 /** 1121 * @param {K} key 1122 * @return {V} 1123 */ 1124 remove: function(key) 1125 { 1126 var result = this._map[key.__identifier]; 1127 if (!result) 1128 return undefined; 1129 --this._size; 1130 delete this._map[key.__identifier]; 1131 return result[1]; 1132 }, 1133 1134 /** 1135 * @return {!Array.<K>} 1136 */ 1137 keys: function() 1138 { 1139 return this._list(0); 1140 }, 1141 1142 /** 1143 * @return {!Array.<V>} 1144 */ 1145 values: function() 1146 { 1147 return this._list(1); 1148 }, 1149 1150 /** 1151 * @param {number} index 1152 * @return {!Array.<K|V>} 1153 */ 1154 _list: function(index) 1155 { 1156 var result = new Array(this._size); 1157 var i = 0; 1158 for (var objectIdentifier in this._map) 1159 result[i++] = this._map[objectIdentifier][index]; 1160 return result; 1161 }, 1162 1163 /** 1164 * @param {K} key 1165 * @return {V|undefined} 1166 */ 1167 get: function(key) 1168 { 1169 var entry = this._map[key.__identifier]; 1170 return entry ? entry[1] : undefined; 1171 }, 1172 1173 /** 1174 * @param {K} key 1175 * @return {boolean} 1176 */ 1177 contains: function(key) 1178 { 1179 var entry = this._map[key.__identifier]; 1180 return !!entry; 1181 }, 1182 1183 /** 1184 * @return {number} 1185 */ 1186 size: function() 1187 { 1188 return this._size; 1189 }, 1190 1191 clear: function() 1192 { 1193 this._map = {}; 1194 this._size = 0; 1195 } 1196} 1197 1198/** 1199 * @constructor 1200 * @template T 1201 */ 1202var StringMap = function() 1203{ 1204 /** @type {!Object.<string, T>} */ 1205 this._map = {}; 1206 this._size = 0; 1207} 1208 1209StringMap.prototype = { 1210 /** 1211 * @param {string} key 1212 * @param {T} value 1213 */ 1214 put: function(key, value) 1215 { 1216 if (key === "__proto__") { 1217 if (!this._hasProtoKey) { 1218 ++this._size; 1219 this._hasProtoKey = true; 1220 } 1221 /** @type {T} */ 1222 this._protoValue = value; 1223 return; 1224 } 1225 if (!Object.prototype.hasOwnProperty.call(this._map, key)) 1226 ++this._size; 1227 this._map[key] = value; 1228 }, 1229 1230 /** 1231 * @param {string} key 1232 */ 1233 remove: function(key) 1234 { 1235 var result; 1236 if (key === "__proto__") { 1237 if (!this._hasProtoKey) 1238 return undefined; 1239 --this._size; 1240 delete this._hasProtoKey; 1241 result = this._protoValue; 1242 delete this._protoValue; 1243 return result; 1244 } 1245 if (!Object.prototype.hasOwnProperty.call(this._map, key)) 1246 return undefined; 1247 --this._size; 1248 result = this._map[key]; 1249 delete this._map[key]; 1250 return result; 1251 }, 1252 1253 /** 1254 * @return {!Array.<string>} 1255 */ 1256 keys: function() 1257 { 1258 var result = Object.keys(this._map) || []; 1259 if (this._hasProtoKey) 1260 result.push("__proto__"); 1261 return result; 1262 }, 1263 1264 /** 1265 * @return {!Array.<T>} 1266 */ 1267 values: function() 1268 { 1269 var result = Object.values(this._map); 1270 if (this._hasProtoKey) 1271 result.push(this._protoValue); 1272 return result; 1273 }, 1274 1275 /** 1276 * @param {string} key 1277 */ 1278 get: function(key) 1279 { 1280 if (key === "__proto__") 1281 return this._protoValue; 1282 if (!Object.prototype.hasOwnProperty.call(this._map, key)) 1283 return undefined; 1284 return this._map[key]; 1285 }, 1286 1287 /** 1288 * @param {string} key 1289 * @return {boolean} 1290 */ 1291 contains: function(key) 1292 { 1293 var result; 1294 if (key === "__proto__") 1295 return this._hasProtoKey; 1296 return Object.prototype.hasOwnProperty.call(this._map, key); 1297 }, 1298 1299 /** 1300 * @return {number} 1301 */ 1302 size: function() 1303 { 1304 return this._size; 1305 }, 1306 1307 clear: function() 1308 { 1309 this._map = {}; 1310 this._size = 0; 1311 delete this._hasProtoKey; 1312 delete this._protoValue; 1313 } 1314} 1315 1316/** 1317 * @param {string} url 1318 * @param {boolean=} async 1319 * @param {function(?string)=} callback 1320 * @return {?string} 1321 */ 1322function loadXHR(url, async, callback) 1323{ 1324 function onReadyStateChanged() 1325 { 1326 if (xhr.readyState !== XMLHttpRequest.DONE) 1327 return; 1328 1329 if (xhr.status === 200) { 1330 callback(xhr.responseText); 1331 return; 1332 } 1333 1334 callback(null); 1335 } 1336 1337 var xhr = new XMLHttpRequest(); 1338 xhr.open("GET", url, async); 1339 if (async) 1340 xhr.onreadystatechange = onReadyStateChanged; 1341 xhr.send(null); 1342 1343 if (!async) { 1344 if (xhr.status === 200) 1345 return xhr.responseText; 1346 return null; 1347 } 1348 return null; 1349} 1350 1351/** 1352 * @constructor 1353 */ 1354function StringPool() 1355{ 1356 this.reset(); 1357} 1358 1359StringPool.prototype = { 1360 /** 1361 * @param {string} string 1362 * @return {string} 1363 */ 1364 intern: function(string) 1365 { 1366 // Do not mess with setting __proto__ to anything but null, just handle it explicitly. 1367 if (string === "__proto__") 1368 return "__proto__"; 1369 var result = this._strings[string]; 1370 if (result === undefined) { 1371 this._strings[string] = string; 1372 result = string; 1373 } 1374 return result; 1375 }, 1376 1377 reset: function() 1378 { 1379 this._strings = Object.create(null); 1380 }, 1381 1382 /** 1383 * @param {!Object} obj 1384 * @param {number=} depthLimit 1385 */ 1386 internObjectStrings: function(obj, depthLimit) 1387 { 1388 if (typeof depthLimit !== "number") 1389 depthLimit = 100; 1390 else if (--depthLimit < 0) 1391 throw "recursion depth limit reached in StringPool.deepIntern(), perhaps attempting to traverse cyclical references?"; 1392 1393 for (var field in obj) { 1394 switch (typeof obj[field]) { 1395 case "string": 1396 obj[field] = this.intern(obj[field]); 1397 break; 1398 case "object": 1399 this.internObjectStrings(obj[field], depthLimit); 1400 break; 1401 } 1402 } 1403 } 1404} 1405 1406var _importedScripts = {}; 1407 1408/** 1409 * This function behavior depends on the "debug_devtools" flag value. 1410 * - In debug mode it loads scripts synchronously via xhr request. 1411 * - In release mode every occurrence of "importScript" in the js files 1412 * that have been white listed in the build system gets replaced with 1413 * the script source code on the compilation phase. 1414 * The build system will throw an exception if it found importScript call 1415 * in other files. 1416 * 1417 * To load scripts lazily in release mode call "loadScript" function. 1418 * @param {string} scriptName 1419 */ 1420function importScript(scriptName) 1421{ 1422 if (_importedScripts[scriptName]) 1423 return; 1424 var xhr = new XMLHttpRequest(); 1425 _importedScripts[scriptName] = true; 1426 xhr.open("GET", scriptName, false); 1427 xhr.send(null); 1428 if (!xhr.responseText) 1429 throw "empty response arrived for script '" + scriptName + "'"; 1430 var sourceURL = WebInspector.ParsedURL.completeURL(window.location.href, scriptName); 1431 window.eval(xhr.responseText + "\n//# sourceURL=" + sourceURL); 1432} 1433 1434var loadScript = importScript; 1435 1436/** 1437 * @constructor 1438 */ 1439function CallbackBarrier() 1440{ 1441 this._pendingIncomingCallbacksCount = 0; 1442} 1443 1444CallbackBarrier.prototype = { 1445 /** 1446 * @param {function(!T)=} userCallback 1447 * @return {function(!T=)} 1448 * @template T 1449 */ 1450 createCallback: function(userCallback) 1451 { 1452 console.assert(!this._outgoingCallback, "CallbackBarrier.createCallback() is called after CallbackBarrier.callWhenDone()"); 1453 ++this._pendingIncomingCallbacksCount; 1454 return this._incomingCallback.bind(this, userCallback); 1455 }, 1456 1457 /** 1458 * @param {function()} callback 1459 */ 1460 callWhenDone: function(callback) 1461 { 1462 console.assert(!this._outgoingCallback, "CallbackBarrier.callWhenDone() is called multiple times"); 1463 this._outgoingCallback = callback; 1464 if (!this._pendingIncomingCallbacksCount) 1465 this._outgoingCallback(); 1466 }, 1467 1468 /** 1469 * @param {function(...)=} userCallback 1470 */ 1471 _incomingCallback: function(userCallback) 1472 { 1473 console.assert(this._pendingIncomingCallbacksCount > 0); 1474 if (userCallback) { 1475 var args = Array.prototype.slice.call(arguments, 1); 1476 userCallback.apply(null, args); 1477 } 1478 if (!--this._pendingIncomingCallbacksCount && this._outgoingCallback) 1479 this._outgoingCallback(); 1480 } 1481} 1482