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 30Object.isEmpty = function(obj) 31{ 32 for (var i in obj) 33 return false; 34 return true; 35} 36 37Object.values = function(obj) 38{ 39 var keys = Object.keys(obj); 40 var result = []; 41 42 for (var i = 0; i < keys.length; ++i) 43 result.push(obj[keys[i]]); 44 return result; 45} 46 47String.prototype.hasSubstring = function(string, caseInsensitive) 48{ 49 if (!caseInsensitive) 50 return this.indexOf(string) !== -1; 51 return this.match(new RegExp(string.escapeForRegExp(), "i")); 52} 53 54String.prototype.findAll = function(string) 55{ 56 var matches = []; 57 var i = this.indexOf(string); 58 while (i !== -1) { 59 matches.push(i); 60 i = this.indexOf(string, i + string.length); 61 } 62 return matches; 63} 64 65String.prototype.lineEndings = function() 66{ 67 if (!this._lineEndings) { 68 this._lineEndings = this.findAll("\n"); 69 this._lineEndings.push(this.length); 70 } 71 return this._lineEndings; 72} 73 74String.prototype.escapeCharacters = function(chars) 75{ 76 var foundChar = false; 77 for (var i = 0; i < chars.length; ++i) { 78 if (this.indexOf(chars.charAt(i)) !== -1) { 79 foundChar = true; 80 break; 81 } 82 } 83 84 if (!foundChar) 85 return String(this); 86 87 var result = ""; 88 for (var i = 0; i < this.length; ++i) { 89 if (chars.indexOf(this.charAt(i)) !== -1) 90 result += "\\"; 91 result += this.charAt(i); 92 } 93 94 return result; 95} 96 97String.regexSpecialCharacters = function() 98{ 99 return "^[]{}()\\.$*+?|-,"; 100} 101 102String.prototype.escapeForRegExp = function() 103{ 104 return this.escapeCharacters(String.regexSpecialCharacters); 105} 106 107String.prototype.escapeHTML = function() 108{ 109 return this.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """); //" doublequotes just for editor 110} 111 112String.prototype.collapseWhitespace = function() 113{ 114 return this.replace(/[\s\xA0]+/g, " "); 115} 116 117String.prototype.centerEllipsizedToLength = function(maxLength) 118{ 119 if (this.length <= maxLength) 120 return String(this); 121 var leftHalf = maxLength >> 1; 122 var rightHalf = maxLength - leftHalf - 1; 123 return this.substr(0, leftHalf) + "\u2026" + this.substr(this.length - rightHalf, rightHalf); 124} 125 126String.prototype.trimEnd = function(maxLength) 127{ 128 if (this.length <= maxLength) 129 return String(this); 130 return this.substr(0, maxLength - 1) + "\u2026"; 131} 132 133String.prototype.trimURL = function(baseURLDomain) 134{ 135 var result = this.replace(/^(https|http|file):\/\//i, ""); 136 if (baseURLDomain) 137 result = result.replace(new RegExp("^" + baseURLDomain.escapeForRegExp(), "i"), ""); 138 return result; 139} 140 141String.prototype.toTitleCase = function() 142{ 143 return this.substring(0, 1).toUpperCase() + this.substring(1); 144} 145 146/** 147 * @param {string} other 148 * @return {number} 149 */ 150String.prototype.compareTo = function(other) 151{ 152 if (this > other) 153 return 1; 154 if (this < other) 155 return -1; 156 return 0; 157} 158 159/** 160 * @param {string} href 161 * @return {string} 162 */ 163function sanitizeHref(href) 164{ 165 return href && href.trim().toLowerCase().startsWith("javascript:") ? "" : href; 166} 167 168String.prototype.removeURLFragment = function() 169{ 170 var fragmentIndex = this.indexOf("#"); 171 if (fragmentIndex == -1) 172 fragmentIndex = this.length; 173 return this.substring(0, fragmentIndex); 174} 175 176String.prototype.startsWith = function(substring) 177{ 178 return !this.lastIndexOf(substring, 0); 179} 180 181String.prototype.endsWith = function(substring) 182{ 183 return this.indexOf(substring, this.length - substring.length) !== -1; 184} 185 186Number.constrain = function(num, min, max) 187{ 188 if (num < min) 189 num = min; 190 else if (num > max) 191 num = max; 192 return num; 193} 194 195Date.prototype.toISO8601Compact = function() 196{ 197 function leadZero(x) 198 { 199 return x > 9 ? '' + x : '0' + x 200 } 201 return this.getFullYear() + 202 leadZero(this.getMonth() + 1) + 203 leadZero(this.getDate()) + 'T' + 204 leadZero(this.getHours()) + 205 leadZero(this.getMinutes()) + 206 leadZero(this.getSeconds()); 207} 208 209Object.defineProperty(Array.prototype, "remove", 210{ 211 /** 212 * @this {Array.<*>} 213 */ 214 value: function(value, onlyFirst) 215 { 216 if (onlyFirst) { 217 var index = this.indexOf(value); 218 if (index !== -1) 219 this.splice(index, 1); 220 return; 221 } 222 223 var length = this.length; 224 for (var i = 0; i < length; ++i) { 225 if (this[i] === value) 226 this.splice(i, 1); 227 } 228 } 229}); 230 231Object.defineProperty(Array.prototype, "keySet", 232{ 233 /** 234 * @this {Array.<*>} 235 */ 236 value: function() 237 { 238 var keys = {}; 239 for (var i = 0; i < this.length; ++i) 240 keys[this[i]] = true; 241 return keys; 242 } 243}); 244 245Object.defineProperty(Array.prototype, "upperBound", 246{ 247 /** 248 * @this {Array.<number>} 249 */ 250 value: function(value) 251 { 252 var first = 0; 253 var count = this.length; 254 while (count > 0) { 255 var step = count >> 1; 256 var middle = first + step; 257 if (value >= this[middle]) { 258 first = middle + 1; 259 count -= step + 1; 260 } else 261 count = step; 262 } 263 return first; 264 } 265}); 266 267Object.defineProperty(Array.prototype, "rotate", 268{ 269 /** 270 * @this {Array.<*>} 271 * @param {number} index 272 * @return {Array.<*>} 273 */ 274 value: function(index) 275 { 276 var result = []; 277 for (var i = index; i < index + this.length; ++i) 278 result.push(this[i % this.length]); 279 return result; 280 } 281}); 282 283Object.defineProperty(Uint32Array.prototype, "sort", { 284 value: Array.prototype.sort 285}); 286 287(function() { 288var partition = { 289 /** 290 * @this {Array.<number>} 291 * @param {function(number,number):number} comparator 292 * @param {number} left 293 * @param {number} right 294 * @param {number} pivotIndex 295 */ 296 value: function(comparator, left, right, pivotIndex) 297 { 298 function swap(array, i1, i2) 299 { 300 var temp = array[i1]; 301 array[i1] = array[i2]; 302 array[i2] = temp; 303 } 304 305 var pivotValue = this[pivotIndex]; 306 swap(this, right, pivotIndex); 307 var storeIndex = left; 308 for (var i = left; i < right; ++i) { 309 if (comparator(this[i], pivotValue) < 0) { 310 swap(this, storeIndex, i); 311 ++storeIndex; 312 } 313 } 314 swap(this, right, storeIndex); 315 return storeIndex; 316 } 317}; 318Object.defineProperty(Array.prototype, "partition", partition); 319Object.defineProperty(Uint32Array.prototype, "partition", partition); 320 321var sortRange = { 322 /** 323 * @this {Array.<number>} 324 * @param {function(number,number):number} comparator 325 * @param {number} leftBound 326 * @param {number} rightBound 327 * @param {number} k 328 */ 329 value: function(comparator, leftBound, rightBound, k) 330 { 331 function quickSortFirstK(array, comparator, left, right, k) 332 { 333 if (right <= left) 334 return; 335 var pivotIndex = Math.floor(Math.random() * (right - left)) + left; 336 var pivotNewIndex = array.partition(comparator, left, right, pivotIndex); 337 quickSortFirstK(array, comparator, left, pivotNewIndex - 1, k); 338 if (pivotNewIndex < left + k - 1) 339 quickSortFirstK(array, comparator, pivotNewIndex + 1, right, k); 340 } 341 342 if (leftBound === 0 && rightBound === (this.length - 1) && k >= this.length) 343 this.sort(comparator); 344 else 345 quickSortFirstK(this, comparator, leftBound, rightBound, k); 346 return this; 347 } 348} 349Object.defineProperty(Array.prototype, "sortRange", sortRange); 350Object.defineProperty(Uint32Array.prototype, "sortRange", sortRange); 351})(); 352 353Object.defineProperty(Array.prototype, "qselect", 354{ 355 /** 356 * @this {Array.<number>} 357 * @param {number} k 358 * @param {function(number,number):boolean=} comparator 359 */ 360 value: function(k, comparator) 361 { 362 if (k < 0 || k >= this.length) 363 return; 364 if (!comparator) 365 comparator = function(a, b) { return a - b; } 366 367 var low = 0; 368 var high = this.length - 1; 369 for (;;) { 370 var pivotPosition = this.partition(comparator, low, high, Math.floor((high + low) / 2)); 371 if (pivotPosition === k) 372 return this[k]; 373 else if (pivotPosition > k) 374 high = pivotPosition - 1; 375 else 376 low = pivotPosition + 1; 377 } 378 } 379}); 380 381/** 382 * @param {*} object 383 * @param {Array.<*>} array 384 * @param {function(*, *):number} comparator 385 */ 386function binarySearch(object, array, comparator) 387{ 388 var first = 0; 389 var last = array.length - 1; 390 391 while (first <= last) { 392 var mid = (first + last) >> 1; 393 var c = comparator(object, array[mid]); 394 if (c > 0) 395 first = mid + 1; 396 else if (c < 0) 397 last = mid - 1; 398 else 399 return mid; 400 } 401 402 // Return the nearest lesser index, "-1" means "0, "-2" means "1", etc. 403 return -(first + 1); 404} 405 406Object.defineProperty(Array.prototype, "binaryIndexOf", 407{ 408 /** 409 * @this {Array.<*>} 410 * @param {function(*, *):number} comparator 411 */ 412 value: function(value, comparator) 413 { 414 var result = binarySearch(value, this, comparator); 415 return result >= 0 ? result : -1; 416 } 417}); 418 419Object.defineProperty(Array.prototype, "select", 420{ 421 /** 422 * @this {Array.<*>} 423 * @param {string} field 424 * @return {Array.<*>} 425 */ 426 value: function(field) 427 { 428 var result = new Array(this.length); 429 for (var i = 0; i < this.length; ++i) 430 result[i] = this[i][field]; 431 return result; 432 } 433}); 434 435Object.defineProperty(Array.prototype, "peekLast", 436{ 437 /** 438 * @this {Array.<*>} 439 * @return {*} 440 */ 441 value: function() 442 { 443 return this[this.length - 1]; 444 } 445}); 446 447/** 448 * @param {*} anObject 449 * @param {Array.<*>} aList 450 * @param {function(*, *)} aFunction 451 */ 452function insertionIndexForObjectInListSortedByFunction(anObject, aList, aFunction) 453{ 454 var index = binarySearch(anObject, aList, aFunction); 455 if (index < 0) 456 // See binarySearch implementation. 457 return -index - 1; 458 else { 459 // Return the first occurance of an item in the list. 460 while (index > 0 && aFunction(anObject, aList[index - 1]) === 0) 461 index--; 462 return index; 463 } 464} 465 466/** 467 * @param {string} format 468 * @param {...*} var_arg 469 */ 470String.sprintf = function(format, var_arg) 471{ 472 return String.vsprintf(format, Array.prototype.slice.call(arguments, 1)); 473} 474 475String.tokenizeFormatString = function(format, formatters) 476{ 477 var tokens = []; 478 var substitutionIndex = 0; 479 480 function addStringToken(str) 481 { 482 tokens.push({ type: "string", value: str }); 483 } 484 485 function addSpecifierToken(specifier, precision, substitutionIndex) 486 { 487 tokens.push({ type: "specifier", specifier: specifier, precision: precision, substitutionIndex: substitutionIndex }); 488 } 489 490 function isDigit(c) 491 { 492 return !!/[0-9]/.exec(c); 493 } 494 495 var index = 0; 496 for (var precentIndex = format.indexOf("%", index); precentIndex !== -1; precentIndex = format.indexOf("%", index)) { 497 addStringToken(format.substring(index, precentIndex)); 498 index = precentIndex + 1; 499 500 if (isDigit(format[index])) { 501 // The first character is a number, it might be a substitution index. 502 var number = parseInt(format.substring(index), 10); 503 while (isDigit(format[index])) 504 ++index; 505 506 // If the number is greater than zero and ends with a "$", 507 // then this is a substitution index. 508 if (number > 0 && format[index] === "$") { 509 substitutionIndex = (number - 1); 510 ++index; 511 } 512 } 513 514 var precision = -1; 515 if (format[index] === ".") { 516 // This is a precision specifier. If no digit follows the ".", 517 // then the precision should be zero. 518 ++index; 519 precision = parseInt(format.substring(index), 10); 520 if (isNaN(precision)) 521 precision = 0; 522 523 while (isDigit(format[index])) 524 ++index; 525 } 526 527 if (!(format[index] in formatters)) { 528 addStringToken(format.substring(precentIndex, index + 1)); 529 ++index; 530 continue; 531 } 532 533 addSpecifierToken(format[index], precision, substitutionIndex); 534 535 ++substitutionIndex; 536 ++index; 537 } 538 539 addStringToken(format.substring(index)); 540 541 return tokens; 542} 543 544String.standardFormatters = { 545 d: function(substitution) 546 { 547 return !isNaN(substitution) ? substitution : 0; 548 }, 549 550 f: function(substitution, token) 551 { 552 if (substitution && token.precision > -1) 553 substitution = substitution.toFixed(token.precision); 554 return !isNaN(substitution) ? substitution : (token.precision > -1 ? Number(0).toFixed(token.precision) : 0); 555 }, 556 557 s: function(substitution) 558 { 559 return substitution; 560 } 561} 562 563String.vsprintf = function(format, substitutions) 564{ 565 return String.format(format, substitutions, String.standardFormatters, "", function(a, b) { return a + b; }).formattedResult; 566} 567 568String.format = function(format, substitutions, formatters, initialValue, append) 569{ 570 if (!format || !substitutions || !substitutions.length) 571 return { formattedResult: append(initialValue, format), unusedSubstitutions: substitutions }; 572 573 function prettyFunctionName() 574 { 575 return "String.format(\"" + format + "\", \"" + substitutions.join("\", \"") + "\")"; 576 } 577 578 function warn(msg) 579 { 580 console.warn(prettyFunctionName() + ": " + msg); 581 } 582 583 function error(msg) 584 { 585 console.error(prettyFunctionName() + ": " + msg); 586 } 587 588 var result = initialValue; 589 var tokens = String.tokenizeFormatString(format, formatters); 590 var usedSubstitutionIndexes = {}; 591 592 for (var i = 0; i < tokens.length; ++i) { 593 var token = tokens[i]; 594 595 if (token.type === "string") { 596 result = append(result, token.value); 597 continue; 598 } 599 600 if (token.type !== "specifier") { 601 error("Unknown token type \"" + token.type + "\" found."); 602 continue; 603 } 604 605 if (token.substitutionIndex >= substitutions.length) { 606 // If there are not enough substitutions for the current substitutionIndex 607 // just output the format specifier literally and move on. 608 error("not enough substitution arguments. Had " + substitutions.length + " but needed " + (token.substitutionIndex + 1) + ", so substitution was skipped."); 609 result = append(result, "%" + (token.precision > -1 ? token.precision : "") + token.specifier); 610 continue; 611 } 612 613 usedSubstitutionIndexes[token.substitutionIndex] = true; 614 615 if (!(token.specifier in formatters)) { 616 // Encountered an unsupported format character, treat as a string. 617 warn("unsupported format character \u201C" + token.specifier + "\u201D. Treating as a string."); 618 result = append(result, substitutions[token.substitutionIndex]); 619 continue; 620 } 621 622 result = append(result, formatters[token.specifier](substitutions[token.substitutionIndex], token)); 623 } 624 625 var unusedSubstitutions = []; 626 for (var i = 0; i < substitutions.length; ++i) { 627 if (i in usedSubstitutionIndexes) 628 continue; 629 unusedSubstitutions.push(substitutions[i]); 630 } 631 632 return { formattedResult: result, unusedSubstitutions: unusedSubstitutions }; 633} 634 635/** 636 * @param {string} query 637 * @param {boolean} caseSensitive 638 * @param {boolean} isRegex 639 * @return {RegExp} 640 */ 641function createSearchRegex(query, caseSensitive, isRegex) 642{ 643 var regexFlags = caseSensitive ? "g" : "gi"; 644 var regexObject; 645 646 if (isRegex) { 647 try { 648 regexObject = new RegExp(query, regexFlags); 649 } catch (e) { 650 // Silent catch. 651 } 652 } 653 654 if (!regexObject) 655 regexObject = createPlainTextSearchRegex(query, regexFlags); 656 657 return regexObject; 658} 659 660/** 661 * @param {string} query 662 * @param {string=} flags 663 * @return {!RegExp} 664 */ 665function createPlainTextSearchRegex(query, flags) 666{ 667 // This should be kept the same as the one in ContentSearchUtils.cpp. 668 var regexSpecialCharacters = String.regexSpecialCharacters(); 669 var regex = ""; 670 for (var i = 0; i < query.length; ++i) { 671 var c = query.charAt(i); 672 if (regexSpecialCharacters.indexOf(c) != -1) 673 regex += "\\"; 674 regex += c; 675 } 676 return new RegExp(regex, flags || ""); 677} 678 679/** 680 * @param {RegExp} regex 681 * @param {string} content 682 * @return {number} 683 */ 684function countRegexMatches(regex, content) 685{ 686 var text = content; 687 var result = 0; 688 var match; 689 while (text && (match = regex.exec(text))) { 690 if (match[0].length > 0) 691 ++result; 692 text = text.substring(match.index + 1); 693 } 694 return result; 695} 696 697/** 698 * @param {number} value 699 * @param {number} symbolsCount 700 * @return {string} 701 */ 702function numberToStringWithSpacesPadding(value, symbolsCount) 703{ 704 var numberString = value.toString(); 705 var paddingLength = Math.max(0, symbolsCount - numberString.length); 706 var paddingString = Array(paddingLength + 1).join("\u00a0"); 707 return paddingString + numberString; 708} 709 710/** 711 * @return {string} 712 */ 713var createObjectIdentifier = function() 714{ 715 // It has to be string for better performance. 716 return '_' + ++createObjectIdentifier._last; 717} 718 719createObjectIdentifier._last = 0; 720 721/** 722 * @constructor 723 */ 724var Set = function() 725{ 726 /** @type !Object.<string, Object> */ 727 this._set = {}; 728 this._size = 0; 729} 730 731Set.prototype = { 732 /** 733 * @param {!Object} item 734 */ 735 add: function(item) 736 { 737 var objectIdentifier = item.__identifier; 738 if (!objectIdentifier) { 739 objectIdentifier = createObjectIdentifier(); 740 item.__identifier = objectIdentifier; 741 } 742 if (!this._set[objectIdentifier]) 743 ++this._size; 744 this._set[objectIdentifier] = item; 745 }, 746 747 /** 748 * @param {!Object} item 749 */ 750 remove: function(item) 751 { 752 if (this._set[item.__identifier]) { 753 --this._size; 754 delete this._set[item.__identifier]; 755 } 756 }, 757 758 /** 759 * @return {!Array.<Object>} 760 */ 761 items: function() 762 { 763 var result = new Array(this._size); 764 var i = 0; 765 for (var objectIdentifier in this._set) 766 result[i++] = this._set[objectIdentifier]; 767 return result; 768 }, 769 770 /** 771 * @param {!Object} item 772 * @return {?Object} 773 */ 774 hasItem: function(item) 775 { 776 return this._set[item.__identifier]; 777 }, 778 779 /** 780 * @return {number} 781 */ 782 size: function() 783 { 784 return this._size; 785 }, 786 787 clear: function() 788 { 789 this._set = {}; 790 this._size = 0; 791 } 792} 793 794/** 795 * @constructor 796 */ 797var Map = function() 798{ 799 this._map = {}; 800 this._size = 0; 801} 802 803Map.prototype = { 804 /** 805 * @param {Object} key 806 * @param {*=} value 807 */ 808 put: function(key, value) 809 { 810 var objectIdentifier = key.__identifier; 811 if (!objectIdentifier) { 812 objectIdentifier = createObjectIdentifier(); 813 key.__identifier = objectIdentifier; 814 } 815 if (!this._map[objectIdentifier]) 816 ++this._size; 817 this._map[objectIdentifier] = [key, value]; 818 }, 819 820 /** 821 * @param {Object} key 822 */ 823 remove: function(key) 824 { 825 var result = this._map[key.__identifier]; 826 if (!result) 827 return undefined; 828 --this._size; 829 delete this._map[key.__identifier]; 830 return result[1]; 831 }, 832 833 /** 834 * @return {Array.<Object>} 835 */ 836 keys: function() 837 { 838 return this._list(0); 839 }, 840 841 values: function() 842 { 843 return this._list(1); 844 }, 845 846 /** 847 * @param {number} index 848 */ 849 _list: function(index) 850 { 851 var result = new Array(this._size); 852 var i = 0; 853 for (var objectIdentifier in this._map) 854 result[i++] = this._map[objectIdentifier][index]; 855 return result; 856 }, 857 858 /** 859 * @param {Object} key 860 */ 861 get: function(key) 862 { 863 var entry = this._map[key.__identifier]; 864 return entry ? entry[1] : undefined; 865 }, 866 867 /** 868 * @param {Object} key 869 */ 870 contains: function(key) 871 { 872 var entry = this._map[key.__identifier]; 873 return !!entry; 874 }, 875 876 size: function() 877 { 878 return this._size; 879 }, 880 881 clear: function() 882 { 883 this._map = {}; 884 this._size = 0; 885 } 886} 887/** 888 * @param {string} url 889 * @param {boolean=} async 890 * @param {function(?string)=} callback 891 * @return {?string} 892 */ 893function loadXHR(url, async, callback) 894{ 895 function onReadyStateChanged() 896 { 897 if (xhr.readyState !== XMLHttpRequest.DONE) 898 return; 899 900 if (xhr.status === 200) { 901 callback(xhr.responseText); 902 return; 903 } 904 905 callback(null); 906 } 907 908 var xhr = new XMLHttpRequest(); 909 xhr.open("GET", url, async); 910 if (async) 911 xhr.onreadystatechange = onReadyStateChanged; 912 xhr.send(null); 913 914 if (!async) { 915 if (xhr.status === 200) 916 return xhr.responseText; 917 return null; 918 } 919 return null; 920} 921 922/** 923 * @constructor 924 */ 925function StringPool() 926{ 927 this.reset(); 928} 929 930StringPool.prototype = { 931 /** 932 * @param {string} string 933 * @return {string} 934 */ 935 intern: function(string) 936 { 937 // Do not mess with setting __proto__ to anything but null, just handle it explicitly. 938 if (string === "__proto__") 939 return "__proto__"; 940 var result = this._strings[string]; 941 if (result === undefined) { 942 this._strings[string] = string; 943 result = string; 944 } 945 return result; 946 }, 947 948 reset: function() 949 { 950 this._strings = Object.create(null); 951 }, 952 953 /** 954 * @param {Object} obj 955 * @param {number=} depthLimit 956 */ 957 internObjectStrings: function(obj, depthLimit) 958 { 959 if (typeof depthLimit !== "number") 960 depthLimit = 100; 961 else if (--depthLimit < 0) 962 throw "recursion depth limit reached in StringPool.deepIntern(), perhaps attempting to traverse cyclical references?"; 963 964 for (var field in obj) { 965 switch (typeof obj[field]) { 966 case "string": 967 obj[field] = this.intern(obj[field]); 968 break; 969 case "object": 970 this.internObjectStrings(obj[field], depthLimit); 971 break; 972 } 973 } 974 } 975} 976 977var _importedScripts = {}; 978 979/** 980 * This function behavior depends on the "debug_devtools" flag value. 981 * - In debug mode it loads scripts synchronously via xhr request. 982 * - In release mode every occurrence of "importScript" in the js files 983 * that have been white listed in the build system gets replaced with 984 * the script source code on the compilation phase. 985 * The build system will throw an exception if it found importScript call 986 * in other files. 987 * 988 * To load scripts lazily in release mode call "loadScript" function. 989 * @param {string} scriptName 990 */ 991function importScript(scriptName) 992{ 993 if (_importedScripts[scriptName]) 994 return; 995 var xhr = new XMLHttpRequest(); 996 _importedScripts[scriptName] = true; 997 if (window.flattenImports) 998 scriptName = scriptName.split("/").reverse()[0]; 999 xhr.open("GET", scriptName, false); 1000 xhr.send(null); 1001 if (!xhr.responseText) 1002 throw "empty response arrived for script '" + scriptName + "'"; 1003 var sourceURL = WebInspector.ParsedURL.completeURL(window.location.href, scriptName); 1004 window.eval(xhr.responseText + "\n//@ sourceURL=" + sourceURL); 1005} 1006 1007var loadScript = importScript; 1008