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/** @typedef {Array|NodeList|Arguments|{length: number}} */ 31var ArrayLike; 32 33/** 34 * @param {!Object} obj 35 * @return {boolean} 36 */ 37Object.isEmpty = function(obj) 38{ 39 for (var i in obj) 40 return false; 41 return true; 42} 43 44/** 45 * @param {!Object.<string,!T>} obj 46 * @return {!Array.<!T>} 47 * @template T 48 */ 49Object.values = function(obj) 50{ 51 var result = Object.keys(obj); 52 var length = result.length; 53 for (var i = 0; i < length; ++i) 54 result[i] = obj[result[i]]; 55 return result; 56} 57 58/** 59 * @param {number} m 60 * @param {number} n 61 * @return {number} 62 */ 63function mod(m, n) 64{ 65 return ((m % n) + n) % n; 66} 67 68/** 69 * @param {string} string 70 * @return {!Array.<number>} 71 */ 72String.prototype.findAll = function(string) 73{ 74 var matches = []; 75 var i = this.indexOf(string); 76 while (i !== -1) { 77 matches.push(i); 78 i = this.indexOf(string, i + string.length); 79 } 80 return matches; 81} 82 83/** 84 * @return {!Array.<number>} 85 */ 86String.prototype.lineEndings = function() 87{ 88 if (!this._lineEndings) { 89 this._lineEndings = this.findAll("\n"); 90 this._lineEndings.push(this.length); 91 } 92 return this._lineEndings; 93} 94 95/** 96 * @return {number} 97 */ 98String.prototype.lineCount = function() 99{ 100 var lineEndings = this.lineEndings(); 101 return lineEndings.length; 102} 103 104/** 105 * @return {string} 106 */ 107String.prototype.lineAt = function(lineNumber) 108{ 109 var lineEndings = this.lineEndings(); 110 var lineStart = lineNumber > 0 ? lineEndings[lineNumber - 1] + 1 : 0; 111 var lineEnd = lineEndings[lineNumber]; 112 var lineContent = this.substring(lineStart, lineEnd); 113 if (lineContent.length > 0 && lineContent.charAt(lineContent.length - 1) === "\r") 114 lineContent = lineContent.substring(0, lineContent.length - 1); 115 return lineContent; 116} 117 118/** 119 * @param {string} chars 120 * @return {string} 121 */ 122String.prototype.escapeCharacters = function(chars) 123{ 124 var foundChar = false; 125 for (var i = 0; i < chars.length; ++i) { 126 if (this.indexOf(chars.charAt(i)) !== -1) { 127 foundChar = true; 128 break; 129 } 130 } 131 132 if (!foundChar) 133 return String(this); 134 135 var result = ""; 136 for (var i = 0; i < this.length; ++i) { 137 if (chars.indexOf(this.charAt(i)) !== -1) 138 result += "\\"; 139 result += this.charAt(i); 140 } 141 142 return result; 143} 144 145/** 146 * @return {string} 147 */ 148String.regexSpecialCharacters = function() 149{ 150 return "^[]{}()\\.^$*+?|-,"; 151} 152 153/** 154 * @return {string} 155 */ 156String.prototype.escapeForRegExp = function() 157{ 158 return this.escapeCharacters(String.regexSpecialCharacters()); 159} 160 161/** 162 * @return {string} 163 */ 164String.prototype.escapeHTML = function() 165{ 166 return this.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """); //" doublequotes just for editor 167} 168 169/** 170 * @return {string} 171 */ 172String.prototype.unescapeHTML = function() 173{ 174 return this.replace(/</g, "<") 175 .replace(/>/g, ">") 176 .replace(/:/g, ":") 177 .replace(/"/g, "\"") 178 .replace(/</g, "<") 179 .replace(/>/g, ">") 180 .replace(/&/g, "&"); 181} 182 183/** 184 * @return {string} 185 */ 186String.prototype.collapseWhitespace = function() 187{ 188 return this.replace(/[\s\xA0]+/g, " "); 189} 190 191/** 192 * @param {number} maxLength 193 * @return {string} 194 */ 195String.prototype.trimMiddle = function(maxLength) 196{ 197 if (this.length <= maxLength) 198 return String(this); 199 var leftHalf = maxLength >> 1; 200 var rightHalf = maxLength - leftHalf - 1; 201 return this.substr(0, leftHalf) + "\u2026" + this.substr(this.length - rightHalf, rightHalf); 202} 203 204/** 205 * @param {number} maxLength 206 * @return {string} 207 */ 208String.prototype.trimEnd = function(maxLength) 209{ 210 if (this.length <= maxLength) 211 return String(this); 212 return this.substr(0, maxLength - 1) + "\u2026"; 213} 214 215/** 216 * @param {?string=} baseURLDomain 217 * @return {string} 218 */ 219String.prototype.trimURL = function(baseURLDomain) 220{ 221 var result = this.replace(/^(https|http|file):\/\//i, ""); 222 if (baseURLDomain) 223 result = result.replace(new RegExp("^" + baseURLDomain.escapeForRegExp(), "i"), ""); 224 return result; 225} 226 227/** 228 * @return {string} 229 */ 230String.prototype.toTitleCase = function() 231{ 232 return this.substring(0, 1).toUpperCase() + this.substring(1); 233} 234 235/** 236 * @param {string} other 237 * @return {number} 238 */ 239String.prototype.compareTo = function(other) 240{ 241 if (this > other) 242 return 1; 243 if (this < other) 244 return -1; 245 return 0; 246} 247 248/** 249 * @param {string} href 250 * @return {?string} 251 */ 252function sanitizeHref(href) 253{ 254 return href && href.trim().toLowerCase().startsWith("javascript:") ? null : href; 255} 256 257/** 258 * @return {string} 259 */ 260String.prototype.removeURLFragment = function() 261{ 262 var fragmentIndex = this.indexOf("#"); 263 if (fragmentIndex == -1) 264 fragmentIndex = this.length; 265 return this.substring(0, fragmentIndex); 266} 267 268/** 269 * @return {boolean} 270 */ 271String.prototype.startsWith = function(substring) 272{ 273 return !this.lastIndexOf(substring, 0); 274} 275 276/** 277 * @return {boolean} 278 */ 279String.prototype.endsWith = function(substring) 280{ 281 return this.indexOf(substring, this.length - substring.length) !== -1; 282} 283 284/** 285 * @return {number} 286 */ 287String.prototype.hashCode = function() 288{ 289 var result = 0; 290 for (var i = 0; i < this.length; ++i) 291 result = (result * 3 + this.charCodeAt(i)) | 0; 292 return result; 293} 294 295/** 296 * @param {number} index 297 * @return {boolean} 298 */ 299String.prototype.isDigitAt = function(index) 300{ 301 var c = this.charCodeAt(index); 302 return 48 <= c && c <= 57; 303} 304 305/** 306 * @param {string} a 307 * @param {string} b 308 * @return {number} 309 */ 310String.naturalOrderComparator = function(a, b) 311{ 312 var chunk = /^\d+|^\D+/; 313 var chunka, chunkb, anum, bnum; 314 while (1) { 315 if (a) { 316 if (!b) 317 return 1; 318 } else { 319 if (b) 320 return -1; 321 else 322 return 0; 323 } 324 chunka = a.match(chunk)[0]; 325 chunkb = b.match(chunk)[0]; 326 anum = !isNaN(chunka); 327 bnum = !isNaN(chunkb); 328 if (anum && !bnum) 329 return -1; 330 if (bnum && !anum) 331 return 1; 332 if (anum && bnum) { 333 var diff = chunka - chunkb; 334 if (diff) 335 return diff; 336 if (chunka.length !== chunkb.length) { 337 if (!+chunka && !+chunkb) // chunks are strings of all 0s (special case) 338 return chunka.length - chunkb.length; 339 else 340 return chunkb.length - chunka.length; 341 } 342 } else if (chunka !== chunkb) 343 return (chunka < chunkb) ? -1 : 1; 344 a = a.substring(chunka.length); 345 b = b.substring(chunkb.length); 346 } 347} 348 349/** 350 * @param {number} num 351 * @param {number} min 352 * @param {number} max 353 * @return {number} 354 */ 355Number.constrain = function(num, min, max) 356{ 357 if (num < min) 358 num = min; 359 else if (num > max) 360 num = max; 361 return num; 362} 363 364/** 365 * @param {number} a 366 * @param {number} b 367 * @return {number} 368 */ 369Number.gcd = function(a, b) 370{ 371 if (b === 0) 372 return a; 373 else 374 return Number.gcd(b, a % b); 375} 376 377/** 378 * @param {string} value 379 * @return {string} 380 */ 381Number.toFixedIfFloating = function(value) 382{ 383 if (!value || isNaN(value)) 384 return value; 385 var number = Number(value); 386 return number % 1 ? number.toFixed(3) : String(number); 387} 388 389/** 390 * @return {string} 391 */ 392Date.prototype.toISO8601Compact = function() 393{ 394 /** 395 * @param {number} x 396 * @return {string} 397 */ 398 function leadZero(x) 399 { 400 return (x > 9 ? "" : "0") + x; 401 } 402 return this.getFullYear() + 403 leadZero(this.getMonth() + 1) + 404 leadZero(this.getDate()) + "T" + 405 leadZero(this.getHours()) + 406 leadZero(this.getMinutes()) + 407 leadZero(this.getSeconds()); 408} 409 410/** 411 * @return {string} 412 */ 413Date.prototype.toConsoleTime = function() 414{ 415 /** 416 * @param {number} x 417 * @return {string} 418 */ 419 function leadZero2(x) 420 { 421 return (x > 9 ? "" : "0") + x; 422 } 423 424 /** 425 * @param {number} x 426 * @return {string} 427 */ 428 function leadZero3(x) 429 { 430 return (Array(4 - x.toString().length)).join('0') + x; 431 } 432 433 return this.getFullYear() + "-" + 434 leadZero2(this.getMonth() + 1) + "-" + 435 leadZero2(this.getDate()) + " " + 436 leadZero2(this.getHours()) + ":" + 437 leadZero2(this.getMinutes()) + ":" + 438 leadZero2(this.getSeconds()) + "." + 439 leadZero3(this.getMilliseconds()); 440} 441 442Object.defineProperty(Array.prototype, "remove", 443{ 444 /** 445 * @param {!T} value 446 * @param {boolean=} firstOnly 447 * @this {Array.<!T>} 448 * @template T 449 */ 450 value: function(value, firstOnly) 451 { 452 var index = this.indexOf(value); 453 if (index === -1) 454 return; 455 if (firstOnly) { 456 this.splice(index, 1); 457 return; 458 } 459 for (var i = index + 1, n = this.length; i < n; ++i) { 460 if (this[i] !== value) 461 this[index++] = this[i]; 462 } 463 this.length = index; 464 } 465}); 466 467Object.defineProperty(Array.prototype, "keySet", 468{ 469 /** 470 * @return {!Object.<string, boolean>} 471 * @this {Array.<*>} 472 */ 473 value: function() 474 { 475 var keys = {}; 476 for (var i = 0; i < this.length; ++i) 477 keys[this[i]] = true; 478 return keys; 479 } 480}); 481 482Object.defineProperty(Array.prototype, "pushAll", 483{ 484 /** 485 * @param {!Array.<!T>} array 486 * @this {Array.<!T>} 487 * @template T 488 */ 489 value: function(array) 490 { 491 Array.prototype.push.apply(this, array); 492 } 493}); 494 495Object.defineProperty(Array.prototype, "rotate", 496{ 497 /** 498 * @param {number} index 499 * @return {!Array.<!T>} 500 * @this {Array.<!T>} 501 * @template T 502 */ 503 value: function(index) 504 { 505 var result = []; 506 for (var i = index; i < index + this.length; ++i) 507 result.push(this[i % this.length]); 508 return result; 509 } 510}); 511 512Object.defineProperty(Array.prototype, "sortNumbers", 513{ 514 /** 515 * @this {Array.<number>} 516 */ 517 value: function() 518 { 519 /** 520 * @param {number} a 521 * @param {number} b 522 * @return {number} 523 */ 524 function numericComparator(a, b) 525 { 526 return a - b; 527 } 528 529 this.sort(numericComparator); 530 } 531}); 532 533Object.defineProperty(Uint32Array.prototype, "sort", { 534 value: Array.prototype.sort 535}); 536 537(function() { 538var partition = { 539 /** 540 * @this {Array.<number>} 541 * @param {function(number, number): number} comparator 542 * @param {number} left 543 * @param {number} right 544 * @param {number} pivotIndex 545 */ 546 value: function(comparator, left, right, pivotIndex) 547 { 548 function swap(array, i1, i2) 549 { 550 var temp = array[i1]; 551 array[i1] = array[i2]; 552 array[i2] = temp; 553 } 554 555 var pivotValue = this[pivotIndex]; 556 swap(this, right, pivotIndex); 557 var storeIndex = left; 558 for (var i = left; i < right; ++i) { 559 if (comparator(this[i], pivotValue) < 0) { 560 swap(this, storeIndex, i); 561 ++storeIndex; 562 } 563 } 564 swap(this, right, storeIndex); 565 return storeIndex; 566 } 567}; 568Object.defineProperty(Array.prototype, "partition", partition); 569Object.defineProperty(Uint32Array.prototype, "partition", partition); 570 571var sortRange = { 572 /** 573 * @param {function(number, number): number} comparator 574 * @param {number} leftBound 575 * @param {number} rightBound 576 * @param {number} sortWindowLeft 577 * @param {number} sortWindowRight 578 * @return {!Array.<number>} 579 * @this {Array.<number>} 580 */ 581 value: function(comparator, leftBound, rightBound, sortWindowLeft, sortWindowRight) 582 { 583 function quickSortRange(array, comparator, left, right, sortWindowLeft, sortWindowRight) 584 { 585 if (right <= left) 586 return; 587 var pivotIndex = Math.floor(Math.random() * (right - left)) + left; 588 var pivotNewIndex = array.partition(comparator, left, right, pivotIndex); 589 if (sortWindowLeft < pivotNewIndex) 590 quickSortRange(array, comparator, left, pivotNewIndex - 1, sortWindowLeft, sortWindowRight); 591 if (pivotNewIndex < sortWindowRight) 592 quickSortRange(array, comparator, pivotNewIndex + 1, right, sortWindowLeft, sortWindowRight); 593 } 594 if (leftBound === 0 && rightBound === (this.length - 1) && sortWindowLeft === 0 && sortWindowRight >= rightBound) 595 this.sort(comparator); 596 else 597 quickSortRange(this, comparator, leftBound, rightBound, sortWindowLeft, sortWindowRight); 598 return this; 599 } 600} 601Object.defineProperty(Array.prototype, "sortRange", sortRange); 602Object.defineProperty(Uint32Array.prototype, "sortRange", sortRange); 603})(); 604 605Object.defineProperty(Array.prototype, "stableSort", 606{ 607 /** 608 * @param {function(?T, ?T): number=} comparator 609 * @return {!Array.<?T>} 610 * @this {Array.<?T>} 611 * @template T 612 */ 613 value: function(comparator) 614 { 615 function defaultComparator(a, b) 616 { 617 return a < b ? -1 : (a > b ? 1 : 0); 618 } 619 comparator = comparator || defaultComparator; 620 621 var indices = new Array(this.length); 622 for (var i = 0; i < this.length; ++i) 623 indices[i] = i; 624 var self = this; 625 /** 626 * @param {number} a 627 * @param {number} b 628 * @return {number} 629 */ 630 function indexComparator(a, b) 631 { 632 var result = comparator(self[a], self[b]); 633 return result ? result : a - b; 634 } 635 indices.sort(indexComparator); 636 637 for (var i = 0; i < this.length; ++i) { 638 if (indices[i] < 0 || i === indices[i]) 639 continue; 640 var cyclical = i; 641 var saved = this[i]; 642 while (true) { 643 var next = indices[cyclical]; 644 indices[cyclical] = -1; 645 if (next === i) { 646 this[cyclical] = saved; 647 break; 648 } else { 649 this[cyclical] = this[next]; 650 cyclical = next; 651 } 652 } 653 } 654 return this; 655 } 656}); 657 658Object.defineProperty(Array.prototype, "qselect", 659{ 660 /** 661 * @param {number} k 662 * @param {function(number, number): number=} comparator 663 * @return {number|undefined} 664 * @this {Array.<number>} 665 */ 666 value: function(k, comparator) 667 { 668 if (k < 0 || k >= this.length) 669 return; 670 if (!comparator) 671 comparator = function(a, b) { return a - b; } 672 673 var low = 0; 674 var high = this.length - 1; 675 for (;;) { 676 var pivotPosition = this.partition(comparator, low, high, Math.floor((high + low) / 2)); 677 if (pivotPosition === k) 678 return this[k]; 679 else if (pivotPosition > k) 680 high = pivotPosition - 1; 681 else 682 low = pivotPosition + 1; 683 } 684 } 685}); 686 687Object.defineProperty(Array.prototype, "lowerBound", 688{ 689 /** 690 * Return index of the leftmost element that is equal or greater 691 * than the specimen object. If there's no such element (i.e. all 692 * elements are smaller than the specimen) returns right bound. 693 * The function works for sorted array. 694 * When specified, |left| (inclusive) and |right| (exclusive) indices 695 * define the search window. 696 * 697 * @param {!T} object 698 * @param {function(!T,!S):number=} comparator 699 * @param {number=} left 700 * @param {number=} right 701 * @return {number} 702 * @this {Array.<!S>} 703 * @template T,S 704 */ 705 value: function(object, comparator, left, right) 706 { 707 function defaultComparator(a, b) 708 { 709 return a < b ? -1 : (a > b ? 1 : 0); 710 } 711 comparator = comparator || defaultComparator; 712 var l = left || 0; 713 var r = right !== undefined ? right : this.length; 714 while (l < r) { 715 var m = (l + r) >> 1; 716 if (comparator(object, this[m]) > 0) 717 l = m + 1; 718 else 719 r = m; 720 } 721 return r; 722 } 723}); 724 725Object.defineProperty(Array.prototype, "upperBound", 726{ 727 /** 728 * Return index of the leftmost element that is greater 729 * than the specimen object. If there's no such element (i.e. all 730 * elements are smaller or equal to the specimen) returns right bound. 731 * The function works for sorted array. 732 * When specified, |left| (inclusive) and |right| (exclusive) indices 733 * define the search window. 734 * 735 * @param {!T} object 736 * @param {function(!T,!S):number=} comparator 737 * @param {number=} left 738 * @param {number=} right 739 * @return {number} 740 * @this {Array.<!S>} 741 * @template T,S 742 */ 743 value: function(object, comparator, left, right) 744 { 745 function defaultComparator(a, b) 746 { 747 return a < b ? -1 : (a > b ? 1 : 0); 748 } 749 comparator = comparator || defaultComparator; 750 var l = left || 0; 751 var r = right !== undefined ? right : this.length; 752 while (l < r) { 753 var m = (l + r) >> 1; 754 if (comparator(object, this[m]) >= 0) 755 l = m + 1; 756 else 757 r = m; 758 } 759 return r; 760 } 761}); 762 763Object.defineProperty(Uint32Array.prototype, "lowerBound", { 764 value: Array.prototype.lowerBound 765}); 766 767Object.defineProperty(Uint32Array.prototype, "upperBound", { 768 value: Array.prototype.upperBound 769}); 770 771Object.defineProperty(Float64Array.prototype, "lowerBound", { 772 value: Array.prototype.lowerBound 773}); 774 775Object.defineProperty(Array.prototype, "binaryIndexOf", 776{ 777 /** 778 * @param {!T} value 779 * @param {function(!T,!S):number} comparator 780 * @return {number} 781 * @this {Array.<!S>} 782 * @template T,S 783 */ 784 value: function(value, comparator) 785 { 786 var index = this.lowerBound(value, comparator); 787 return index < this.length && comparator(value, this[index]) === 0 ? index : -1; 788 } 789}); 790 791Object.defineProperty(Array.prototype, "select", 792{ 793 /** 794 * @param {string} field 795 * @return {!Array.<!T>} 796 * @this {Array.<!Object.<string,!T>>} 797 * @template T 798 */ 799 value: function(field) 800 { 801 var result = new Array(this.length); 802 for (var i = 0; i < this.length; ++i) 803 result[i] = this[i][field]; 804 return result; 805 } 806}); 807 808Object.defineProperty(Array.prototype, "peekLast", 809{ 810 /** 811 * @return {!T|undefined} 812 * @this {Array.<!T>} 813 * @template T 814 */ 815 value: function() 816 { 817 return this[this.length - 1]; 818 } 819}); 820 821(function(){ 822 823/** 824 * @param {!Array.<T>} array1 825 * @param {!Array.<T>} array2 826 * @param {function(T,T):number} comparator 827 * @param {boolean} mergeNotIntersect 828 * @return {!Array.<T>} 829 * @template T 830 */ 831function mergeOrIntersect(array1, array2, comparator, mergeNotIntersect) 832{ 833 var result = []; 834 var i = 0; 835 var j = 0; 836 while (i < array1.length && j < array2.length) { 837 var compareValue = comparator(array1[i], array2[j]); 838 if (mergeNotIntersect || !compareValue) 839 result.push(compareValue <= 0 ? array1[i] : array2[j]); 840 if (compareValue <= 0) 841 i++; 842 if (compareValue >= 0) 843 j++; 844 } 845 if (mergeNotIntersect) { 846 while (i < array1.length) 847 result.push(array1[i++]); 848 while (j < array2.length) 849 result.push(array2[j++]); 850 } 851 return result; 852} 853 854Object.defineProperty(Array.prototype, "intersectOrdered", 855{ 856 /** 857 * @param {!Array.<T>} array 858 * @param {function(T,T):number} comparator 859 * @return {!Array.<T>} 860 * @this {!Array.<T>} 861 * @template T 862 */ 863 value: function(array, comparator) 864 { 865 return mergeOrIntersect(this, array, comparator, false); 866 } 867}); 868 869Object.defineProperty(Array.prototype, "mergeOrdered", 870{ 871 /** 872 * @param {!Array.<T>} array 873 * @param {function(T,T):number} comparator 874 * @return {!Array.<T>} 875 * @this {!Array.<T>} 876 * @template T 877 */ 878 value: function(array, comparator) 879 { 880 return mergeOrIntersect(this, array, comparator, true); 881 } 882}); 883 884}()); 885 886 887/** 888 * @param {!T} object 889 * @param {!Array.<!S>} list 890 * @param {function(!T,!S):number=} comparator 891 * @param {boolean=} insertionIndexAfter 892 * @return {number} 893 * @template T,S 894 */ 895function insertionIndexForObjectInListSortedByFunction(object, list, comparator, insertionIndexAfter) 896{ 897 if (insertionIndexAfter) 898 return list.upperBound(object, comparator); 899 else 900 return list.lowerBound(object, comparator); 901} 902 903/** 904 * @param {string} format 905 * @param {...*} var_arg 906 * @return {string} 907 */ 908String.sprintf = function(format, var_arg) 909{ 910 return String.vsprintf(format, Array.prototype.slice.call(arguments, 1)); 911} 912 913/** 914 * @param {string} format 915 * @param {!Object.<string, function(string, ...):*>} formatters 916 * @return {!Array.<!Object>} 917 */ 918String.tokenizeFormatString = function(format, formatters) 919{ 920 var tokens = []; 921 var substitutionIndex = 0; 922 923 function addStringToken(str) 924 { 925 tokens.push({ type: "string", value: str }); 926 } 927 928 function addSpecifierToken(specifier, precision, substitutionIndex) 929 { 930 tokens.push({ type: "specifier", specifier: specifier, precision: precision, substitutionIndex: substitutionIndex }); 931 } 932 933 var index = 0; 934 for (var precentIndex = format.indexOf("%", index); precentIndex !== -1; precentIndex = format.indexOf("%", index)) { 935 addStringToken(format.substring(index, precentIndex)); 936 index = precentIndex + 1; 937 938 if (format[index] === "%") { 939 // %% escape sequence. 940 addStringToken("%"); 941 ++index; 942 continue; 943 } 944 945 if (format.isDigitAt(index)) { 946 // The first character is a number, it might be a substitution index. 947 var number = parseInt(format.substring(index), 10); 948 while (format.isDigitAt(index)) 949 ++index; 950 951 // If the number is greater than zero and ends with a "$", 952 // then this is a substitution index. 953 if (number > 0 && format[index] === "$") { 954 substitutionIndex = (number - 1); 955 ++index; 956 } 957 } 958 959 var precision = -1; 960 if (format[index] === ".") { 961 // This is a precision specifier. If no digit follows the ".", 962 // then the precision should be zero. 963 ++index; 964 precision = parseInt(format.substring(index), 10); 965 if (isNaN(precision)) 966 precision = 0; 967 968 while (format.isDigitAt(index)) 969 ++index; 970 } 971 972 if (!(format[index] in formatters)) { 973 addStringToken(format.substring(precentIndex, index + 1)); 974 ++index; 975 continue; 976 } 977 978 addSpecifierToken(format[index], precision, substitutionIndex); 979 980 ++substitutionIndex; 981 ++index; 982 } 983 984 addStringToken(format.substring(index)); 985 986 return tokens; 987} 988 989String.standardFormatters = { 990 /** 991 * @return {number} 992 */ 993 d: function(substitution) 994 { 995 return !isNaN(substitution) ? substitution : 0; 996 }, 997 998 /** 999 * @return {number} 1000 */ 1001 f: function(substitution, token) 1002 { 1003 if (substitution && token.precision > -1) 1004 substitution = substitution.toFixed(token.precision); 1005 return !isNaN(substitution) ? substitution : (token.precision > -1 ? Number(0).toFixed(token.precision) : 0); 1006 }, 1007 1008 /** 1009 * @return {string} 1010 */ 1011 s: function(substitution) 1012 { 1013 return substitution; 1014 } 1015} 1016 1017/** 1018 * @param {string} format 1019 * @param {!Array.<*>} substitutions 1020 * @return {string} 1021 */ 1022String.vsprintf = function(format, substitutions) 1023{ 1024 return String.format(format, substitutions, String.standardFormatters, "", function(a, b) { return a + b; }).formattedResult; 1025} 1026 1027/** 1028 * @param {string} format 1029 * @param {?ArrayLike} substitutions 1030 * @param {!Object.<string, function(string, ...):string>} formatters 1031 * @param {!T} initialValue 1032 * @param {function(T, string): T|undefined} append 1033 * @param {!Array.<!Object>=} tokenizedFormat 1034 * @return {!{formattedResult: T, unusedSubstitutions: ?ArrayLike}}; 1035 * @template T 1036 */ 1037String.format = function(format, substitutions, formatters, initialValue, append, tokenizedFormat) 1038{ 1039 if (!format || !substitutions || !substitutions.length) 1040 return { formattedResult: append(initialValue, format), unusedSubstitutions: substitutions }; 1041 1042 function prettyFunctionName() 1043 { 1044 return "String.format(\"" + format + "\", \"" + Array.prototype.join.call(substitutions, "\", \"") + "\")"; 1045 } 1046 1047 function warn(msg) 1048 { 1049 console.warn(prettyFunctionName() + ": " + msg); 1050 } 1051 1052 function error(msg) 1053 { 1054 console.error(prettyFunctionName() + ": " + msg); 1055 } 1056 1057 var result = initialValue; 1058 var tokens = tokenizedFormat || String.tokenizeFormatString(format, formatters); 1059 var usedSubstitutionIndexes = {}; 1060 1061 for (var i = 0; i < tokens.length; ++i) { 1062 var token = tokens[i]; 1063 1064 if (token.type === "string") { 1065 result = append(result, token.value); 1066 continue; 1067 } 1068 1069 if (token.type !== "specifier") { 1070 error("Unknown token type \"" + token.type + "\" found."); 1071 continue; 1072 } 1073 1074 if (token.substitutionIndex >= substitutions.length) { 1075 // If there are not enough substitutions for the current substitutionIndex 1076 // just output the format specifier literally and move on. 1077 error("not enough substitution arguments. Had " + substitutions.length + " but needed " + (token.substitutionIndex + 1) + ", so substitution was skipped."); 1078 result = append(result, "%" + (token.precision > -1 ? token.precision : "") + token.specifier); 1079 continue; 1080 } 1081 1082 usedSubstitutionIndexes[token.substitutionIndex] = true; 1083 1084 if (!(token.specifier in formatters)) { 1085 // Encountered an unsupported format character, treat as a string. 1086 warn("unsupported format character \u201C" + token.specifier + "\u201D. Treating as a string."); 1087 result = append(result, substitutions[token.substitutionIndex]); 1088 continue; 1089 } 1090 1091 result = append(result, formatters[token.specifier](substitutions[token.substitutionIndex], token)); 1092 } 1093 1094 var unusedSubstitutions = []; 1095 for (var i = 0; i < substitutions.length; ++i) { 1096 if (i in usedSubstitutionIndexes) 1097 continue; 1098 unusedSubstitutions.push(substitutions[i]); 1099 } 1100 1101 return { formattedResult: result, unusedSubstitutions: unusedSubstitutions }; 1102} 1103 1104/** 1105 * @param {string} query 1106 * @param {boolean} caseSensitive 1107 * @param {boolean} isRegex 1108 * @return {!RegExp} 1109 */ 1110function createSearchRegex(query, caseSensitive, isRegex) 1111{ 1112 var regexFlags = caseSensitive ? "g" : "gi"; 1113 var regexObject; 1114 1115 if (isRegex) { 1116 try { 1117 regexObject = new RegExp(query, regexFlags); 1118 } catch (e) { 1119 // Silent catch. 1120 } 1121 } 1122 1123 if (!regexObject) 1124 regexObject = createPlainTextSearchRegex(query, regexFlags); 1125 1126 return regexObject; 1127} 1128 1129/** 1130 * @param {string} query 1131 * @param {string=} flags 1132 * @return {!RegExp} 1133 */ 1134function createPlainTextSearchRegex(query, flags) 1135{ 1136 // This should be kept the same as the one in ContentSearchUtils.cpp. 1137 var regexSpecialCharacters = String.regexSpecialCharacters(); 1138 var regex = ""; 1139 for (var i = 0; i < query.length; ++i) { 1140 var c = query.charAt(i); 1141 if (regexSpecialCharacters.indexOf(c) != -1) 1142 regex += "\\"; 1143 regex += c; 1144 } 1145 return new RegExp(regex, flags || ""); 1146} 1147 1148/** 1149 * @param {!RegExp} regex 1150 * @param {string} content 1151 * @return {number} 1152 */ 1153function countRegexMatches(regex, content) 1154{ 1155 var text = content; 1156 var result = 0; 1157 var match; 1158 while (text && (match = regex.exec(text))) { 1159 if (match[0].length > 0) 1160 ++result; 1161 text = text.substring(match.index + 1); 1162 } 1163 return result; 1164} 1165 1166/** 1167 * @param {number} spacesCount 1168 * @return {string} 1169 */ 1170function spacesPadding(spacesCount) 1171{ 1172 return Array(spacesCount).join("\u00a0"); 1173} 1174 1175/** 1176 * @param {number} value 1177 * @param {number} symbolsCount 1178 * @return {string} 1179 */ 1180function numberToStringWithSpacesPadding(value, symbolsCount) 1181{ 1182 var numberString = value.toString(); 1183 var paddingLength = Math.max(0, symbolsCount - numberString.length); 1184 return spacesPadding(paddingLength) + numberString; 1185} 1186 1187/** 1188 * @return {string} 1189 */ 1190var createObjectIdentifier = function() 1191{ 1192 // It has to be string for better performance. 1193 return "_" + ++createObjectIdentifier._last; 1194} 1195 1196createObjectIdentifier._last = 0; 1197 1198/** 1199 * @constructor 1200 * @template T 1201 */ 1202var Set = function() 1203{ 1204 /** @type {!Object.<string, !T>} */ 1205 this._set = {}; 1206 this._size = 0; 1207} 1208 1209/** 1210 * @param {!Array.<!T>} array 1211 * @return {!Set.<T>} 1212 * @template T 1213 */ 1214Set.fromArray = function(array) 1215{ 1216 var result = new Set(); 1217 array.forEach(function(item) { result.add(item); }); 1218 return result; 1219} 1220 1221Set.prototype = { 1222 /** 1223 * @param {!T} item 1224 */ 1225 add: function(item) 1226 { 1227 var objectIdentifier = item.__identifier; 1228 if (!objectIdentifier) { 1229 objectIdentifier = createObjectIdentifier(); 1230 item.__identifier = objectIdentifier; 1231 } 1232 if (!this._set[objectIdentifier]) 1233 ++this._size; 1234 this._set[objectIdentifier] = item; 1235 }, 1236 1237 /** 1238 * @param {!T} item 1239 * @return {boolean} 1240 */ 1241 remove: function(item) 1242 { 1243 if (this._set[item.__identifier]) { 1244 --this._size; 1245 delete this._set[item.__identifier]; 1246 return true; 1247 } 1248 return false; 1249 }, 1250 1251 /** 1252 * @return {!Array.<!T>} 1253 */ 1254 values: function() 1255 { 1256 var result = new Array(this._size); 1257 var i = 0; 1258 for (var objectIdentifier in this._set) 1259 result[i++] = this._set[objectIdentifier]; 1260 return result; 1261 }, 1262 1263 /** 1264 * @param {!T} item 1265 * @return {boolean} 1266 */ 1267 contains: function(item) 1268 { 1269 return !!this._set[item.__identifier]; 1270 }, 1271 1272 /** 1273 * @return {number} 1274 */ 1275 size: function() 1276 { 1277 return this._size; 1278 }, 1279 1280 clear: function() 1281 { 1282 this._set = {}; 1283 this._size = 0; 1284 } 1285} 1286 1287/** 1288 * @constructor 1289 * @template K,V 1290 */ 1291var Map = function() 1292{ 1293 /** @type {!Object.<string, !Array.<K|V>>} */ 1294 this._map = {}; 1295 this._size = 0; 1296} 1297 1298Map.prototype = { 1299 /** 1300 * @param {K} key 1301 * @param {V} value 1302 */ 1303 set: function(key, value) 1304 { 1305 var objectIdentifier = key.__identifier; 1306 if (!objectIdentifier) { 1307 objectIdentifier = createObjectIdentifier(); 1308 key.__identifier = objectIdentifier; 1309 } 1310 if (!this._map[objectIdentifier]) 1311 ++this._size; 1312 this._map[objectIdentifier] = [key, value]; 1313 }, 1314 1315 /** 1316 * @param {K} key 1317 * @return {V} 1318 */ 1319 remove: function(key) 1320 { 1321 var result = this._map[key.__identifier]; 1322 if (!result) 1323 return undefined; 1324 --this._size; 1325 delete this._map[key.__identifier]; 1326 return result[1]; 1327 }, 1328 1329 /** 1330 * @return {!Array.<K>} 1331 */ 1332 keys: function() 1333 { 1334 return this._list(0); 1335 }, 1336 1337 /** 1338 * @return {!Array.<V>} 1339 */ 1340 values: function() 1341 { 1342 return this._list(1); 1343 }, 1344 1345 /** 1346 * @param {number} index 1347 * @return {!Array.<K|V>} 1348 */ 1349 _list: function(index) 1350 { 1351 var result = new Array(this._size); 1352 var i = 0; 1353 for (var objectIdentifier in this._map) 1354 result[i++] = this._map[objectIdentifier][index]; 1355 return result; 1356 }, 1357 1358 /** 1359 * @param {K} key 1360 * @return {V|undefined} 1361 */ 1362 get: function(key) 1363 { 1364 var entry = this._map[key.__identifier]; 1365 return entry ? entry[1] : undefined; 1366 }, 1367 1368 /** 1369 * @param {K} key 1370 * @return {boolean} 1371 */ 1372 has: function(key) 1373 { 1374 var entry = this._map[key.__identifier]; 1375 return !!entry; 1376 }, 1377 1378 /** 1379 * @return {number} 1380 */ 1381 get size() 1382 { 1383 return this._size; 1384 }, 1385 1386 clear: function() 1387 { 1388 this._map = {}; 1389 this._size = 0; 1390 } 1391} 1392 1393/** 1394 * @constructor 1395 * @template T 1396 */ 1397var StringMap = function() 1398{ 1399 /** @type {!Object.<string, T>} */ 1400 this._map = {}; 1401 this._size = 0; 1402} 1403 1404StringMap.prototype = { 1405 /** 1406 * @param {string} key 1407 * @param {T} value 1408 */ 1409 set: function(key, value) 1410 { 1411 if (key === "__proto__") { 1412 if (!this._hasProtoKey) { 1413 ++this._size; 1414 this._hasProtoKey = true; 1415 } 1416 /** @type {T} */ 1417 this._protoValue = value; 1418 return; 1419 } 1420 if (!Object.prototype.hasOwnProperty.call(this._map, key)) 1421 ++this._size; 1422 this._map[key] = value; 1423 }, 1424 1425 /** 1426 * @param {string} key 1427 * @return {T|undefined} 1428 */ 1429 remove: function(key) 1430 { 1431 var result; 1432 if (key === "__proto__") { 1433 if (!this._hasProtoKey) 1434 return undefined; 1435 --this._size; 1436 delete this._hasProtoKey; 1437 result = this._protoValue; 1438 delete this._protoValue; 1439 return result; 1440 } 1441 if (!Object.prototype.hasOwnProperty.call(this._map, key)) 1442 return undefined; 1443 --this._size; 1444 result = this._map[key]; 1445 delete this._map[key]; 1446 return result; 1447 }, 1448 1449 /** 1450 * @return {!Array.<string>} 1451 */ 1452 keys: function() 1453 { 1454 var result = Object.keys(this._map) || []; 1455 if (this._hasProtoKey) 1456 result.push("__proto__"); 1457 return result; 1458 }, 1459 1460 /** 1461 * @return {!Array.<T>} 1462 */ 1463 values: function() 1464 { 1465 var result = Object.values(this._map); 1466 if (this._hasProtoKey) 1467 result.push(this._protoValue); 1468 return result; 1469 }, 1470 1471 /** 1472 * @param {string} key 1473 * @return {T|undefined} 1474 */ 1475 get: function(key) 1476 { 1477 if (key === "__proto__") 1478 return this._protoValue; 1479 if (!Object.prototype.hasOwnProperty.call(this._map, key)) 1480 return undefined; 1481 return this._map[key]; 1482 }, 1483 1484 /** 1485 * @param {string} key 1486 * @return {boolean} 1487 */ 1488 has: function(key) 1489 { 1490 var result; 1491 if (key === "__proto__") 1492 return this._hasProtoKey; 1493 return Object.prototype.hasOwnProperty.call(this._map, key); 1494 }, 1495 1496 /** 1497 * @return {number} 1498 */ 1499 get size() 1500 { 1501 return this._size; 1502 }, 1503 1504 clear: function() 1505 { 1506 this._map = {}; 1507 this._size = 0; 1508 delete this._hasProtoKey; 1509 delete this._protoValue; 1510 } 1511} 1512 1513/** 1514 * @constructor 1515 * @extends {StringMap.<Set.<!T>>} 1516 * @template T 1517 */ 1518var StringMultimap = function() 1519{ 1520 StringMap.call(this); 1521} 1522 1523StringMultimap.prototype = { 1524 /** 1525 * @param {string} key 1526 * @param {T} value 1527 */ 1528 set: function(key, value) 1529 { 1530 if (key === "__proto__") { 1531 if (!this._hasProtoKey) { 1532 ++this._size; 1533 this._hasProtoKey = true; 1534 /** @type {!Set.<T>} */ 1535 this._protoValue = new Set(); 1536 } 1537 this._protoValue.add(value); 1538 return; 1539 } 1540 if (!Object.prototype.hasOwnProperty.call(this._map, key)) { 1541 ++this._size; 1542 this._map[key] = new Set(); 1543 } 1544 this._map[key].add(value); 1545 }, 1546 1547 /** 1548 * @param {string} key 1549 * @return {!Set.<!T>} 1550 */ 1551 get: function(key) 1552 { 1553 var result = StringMap.prototype.get.call(this, key); 1554 if (!result) 1555 result = new Set(); 1556 return result; 1557 }, 1558 1559 /** 1560 * @param {string} key 1561 * @param {T} value 1562 */ 1563 remove: function(key, value) 1564 { 1565 var values = this.get(key); 1566 values.remove(value); 1567 if (!values.size()) 1568 StringMap.prototype.remove.call(this, key) 1569 }, 1570 1571 /** 1572 * @param {string} key 1573 */ 1574 removeAll: function(key) 1575 { 1576 StringMap.prototype.remove.call(this, key); 1577 }, 1578 1579 /** 1580 * @return {!Array.<!T>} 1581 */ 1582 values: function() 1583 { 1584 var result = []; 1585 var keys = this.keys(); 1586 for (var i = 0; i < keys.length; ++i) 1587 result.pushAll(this.get(keys[i]).values()); 1588 return result; 1589 }, 1590 1591 __proto__: StringMap.prototype 1592} 1593 1594/** 1595 * @constructor 1596 */ 1597var StringSet = function() 1598{ 1599 /** @type {!StringMap.<boolean>} */ 1600 this._map = new StringMap(); 1601} 1602 1603/** 1604 * @param {!Array.<string>} array 1605 * @return {!StringSet} 1606 */ 1607StringSet.fromArray = function(array) 1608{ 1609 var result = new StringSet(); 1610 array.forEach(function(item) { result.add(item); }); 1611 return result; 1612} 1613 1614StringSet.prototype = { 1615 /** 1616 * @param {string} value 1617 */ 1618 add: function(value) 1619 { 1620 this._map.set(value, true); 1621 }, 1622 1623 /** 1624 * @param {string} value 1625 * @return {boolean} 1626 */ 1627 remove: function(value) 1628 { 1629 return !!this._map.remove(value); 1630 }, 1631 1632 /** 1633 * @return {!Array.<string>} 1634 */ 1635 values: function() 1636 { 1637 return this._map.keys(); 1638 }, 1639 1640 /** 1641 * @param {string} value 1642 * @return {boolean} 1643 */ 1644 contains: function(value) 1645 { 1646 return this._map.has(value); 1647 }, 1648 1649 /** 1650 * @return {number} 1651 */ 1652 size: function() 1653 { 1654 return this._map.size; 1655 }, 1656 1657 clear: function() 1658 { 1659 this._map.clear(); 1660 } 1661} 1662 1663/** 1664 * @param {string} url 1665 * @return {!Promise.<string>} 1666 */ 1667function loadXHR(url) 1668{ 1669 return new Promise(load); 1670 1671 function load(successCallback, failureCallback) 1672 { 1673 function onReadyStateChanged() 1674 { 1675 if (xhr.readyState !== XMLHttpRequest.DONE) 1676 return; 1677 if (xhr.status !== 200) { 1678 xhr.onreadystatechange = null; 1679 failureCallback(new Error(xhr.status)); 1680 return; 1681 } 1682 xhr.onreadystatechange = null; 1683 successCallback(xhr.responseText); 1684 } 1685 1686 var xhr = new XMLHttpRequest(); 1687 xhr.open("GET", url, true); 1688 xhr.onreadystatechange = onReadyStateChanged; 1689 xhr.send(null); 1690 } 1691} 1692 1693/** 1694 * @constructor 1695 */ 1696function CallbackBarrier() 1697{ 1698 this._pendingIncomingCallbacksCount = 0; 1699} 1700 1701CallbackBarrier.prototype = { 1702 /** 1703 * @param {function(...)=} userCallback 1704 * @return {function(...)} 1705 */ 1706 createCallback: function(userCallback) 1707 { 1708 console.assert(!this._outgoingCallback, "CallbackBarrier.createCallback() is called after CallbackBarrier.callWhenDone()"); 1709 ++this._pendingIncomingCallbacksCount; 1710 return this._incomingCallback.bind(this, userCallback); 1711 }, 1712 1713 /** 1714 * @param {function()} callback 1715 */ 1716 callWhenDone: function(callback) 1717 { 1718 console.assert(!this._outgoingCallback, "CallbackBarrier.callWhenDone() is called multiple times"); 1719 this._outgoingCallback = callback; 1720 if (!this._pendingIncomingCallbacksCount) 1721 this._outgoingCallback(); 1722 }, 1723 1724 /** 1725 * @param {function(...)=} userCallback 1726 */ 1727 _incomingCallback: function(userCallback) 1728 { 1729 console.assert(this._pendingIncomingCallbacksCount > 0); 1730 if (userCallback) { 1731 var args = Array.prototype.slice.call(arguments, 1); 1732 userCallback.apply(null, args); 1733 } 1734 if (!--this._pendingIncomingCallbacksCount && this._outgoingCallback) 1735 this._outgoingCallback(); 1736 } 1737} 1738 1739/** 1740 * @param {*} value 1741 */ 1742function suppressUnused(value) 1743{ 1744} 1745 1746/** 1747 * @param {function()} callback 1748 */ 1749self.setImmediate = (function() { 1750 var callbacks = []; 1751 function run() { 1752 var cbList = callbacks.slice(); 1753 callbacks.length = 0; 1754 cbList.forEach(function(callback) { callback(); }); 1755 }; 1756 return function setImmediate(callback) { 1757 if (!callbacks.length) 1758 new Promise(function(resolve,reject){ resolve(null);}).then(run); 1759 callbacks.push(callback); 1760 }; 1761})(); 1762