Home | History | Annotate | Download | only in front_end
      1 /*
      2  * Copyright (C) 2007 Apple Inc.  All rights reserved.
      3  * Copyright (C) 2012 Google Inc. All rights reserved.
      4  *
      5  * Redistribution and use in source and binary forms, with or without
      6  * modification, are permitted provided that the following conditions
      7  * are met:
      8  *
      9  * 1.  Redistributions of source code must retain the above copyright
     10  *     notice, this list of conditions and the following disclaimer.
     11  * 2.  Redistributions in binary form must reproduce the above copyright
     12  *     notice, this list of conditions and the following disclaimer in the
     13  *     documentation and/or other materials provided with the distribution.
     14  * 3.  Neither the name of Apple Computer, Inc. ("Apple") nor the names of
     15  *     its contributors may be used to endorse or promote products derived
     16  *     from this software without specific prior written permission.
     17  *
     18  * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
     19  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
     20  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
     21  * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
     22  * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
     23  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
     24  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
     25  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
     26  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
     27  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
     28  */
     29 
     30 /**
     31  * @param {!Object} obj
     32  * @return {boolean}
     33  */
     34 Object.isEmpty = function(obj)
     35 {
     36     for (var i in obj)
     37         return false;
     38     return true;
     39 }
     40 
     41 /**
     42  * @param {!Object.<string,!T>} obj
     43  * @return {!Array.<!T>}
     44  * @template T
     45  */
     46 Object.values = function(obj)
     47 {
     48     var result = Object.keys(obj);
     49     var length = result.length;
     50     for (var i = 0; i < length; ++i)
     51         result[i] = obj[result[i]];
     52     return result;
     53 }
     54 
     55 /**
     56  * @param {string} string
     57  * @return {!Array.<number>}
     58  */
     59 String.prototype.findAll = function(string)
     60 {
     61     var matches = [];
     62     var i = this.indexOf(string);
     63     while (i !== -1) {
     64         matches.push(i);
     65         i = this.indexOf(string, i + string.length);
     66     }
     67     return matches;
     68 }
     69 
     70 /**
     71  * @return {!Array.<number>}
     72  */
     73 String.prototype.lineEndings = function()
     74 {
     75     if (!this._lineEndings) {
     76         this._lineEndings = this.findAll("\n");
     77         this._lineEndings.push(this.length);
     78     }
     79     return this._lineEndings;
     80 }
     81 
     82 /**
     83  * @param {string} chars
     84  * @return {string}
     85  */
     86 String.prototype.escapeCharacters = function(chars)
     87 {
     88     var foundChar = false;
     89     for (var i = 0; i < chars.length; ++i) {
     90         if (this.indexOf(chars.charAt(i)) !== -1) {
     91             foundChar = true;
     92             break;
     93         }
     94     }
     95 
     96     if (!foundChar)
     97         return String(this);
     98 
     99     var result = "";
    100     for (var i = 0; i < this.length; ++i) {
    101         if (chars.indexOf(this.charAt(i)) !== -1)
    102             result += "\\";
    103         result += this.charAt(i);
    104     }
    105 
    106     return result;
    107 }
    108 
    109 /**
    110  * @return {string}
    111  */
    112 String.regexSpecialCharacters = function()
    113 {
    114     return "^[]{}()\\.$*+?|-,";
    115 }
    116 
    117 /**
    118  * @return {string}
    119  */
    120 String.prototype.escapeForRegExp = function()
    121 {
    122     return this.escapeCharacters(String.regexSpecialCharacters());
    123 }
    124 
    125 /**
    126  * @return {string}
    127  */
    128 String.prototype.escapeHTML = function()
    129 {
    130     return this.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;"); //" doublequotes just for editor
    131 }
    132 
    133 /**
    134  * @return {string}
    135  */
    136 String.prototype.collapseWhitespace = function()
    137 {
    138     return this.replace(/[\s\xA0]+/g, " ");
    139 }
    140 
    141 /**
    142  * @param {number} maxLength
    143  * @return {string}
    144  */
    145 String.prototype.trimMiddle = function(maxLength)
    146 {
    147     if (this.length <= maxLength)
    148         return String(this);
    149     var leftHalf = maxLength >> 1;
    150     var rightHalf = maxLength - leftHalf - 1;
    151     return this.substr(0, leftHalf) + "\u2026" + this.substr(this.length - rightHalf, rightHalf);
    152 }
    153 
    154 /**
    155  * @param {number} maxLength
    156  * @return {string}
    157  */
    158 String.prototype.trimEnd = function(maxLength)
    159 {
    160     if (this.length <= maxLength)
    161         return String(this);
    162     return this.substr(0, maxLength - 1) + "\u2026";
    163 }
    164 
    165 /**
    166  * @param {?string=} baseURLDomain
    167  * @return {string}
    168  */
    169 String.prototype.trimURL = function(baseURLDomain)
    170 {
    171     var result = this.replace(/^(https|http|file):\/\//i, "");
    172     if (baseURLDomain)
    173         result = result.replace(new RegExp("^" + baseURLDomain.escapeForRegExp(), "i"), "");
    174     return result;
    175 }
    176 
    177 /**
    178  * @return {string}
    179  */
    180 String.prototype.toTitleCase = function()
    181 {
    182     return this.substring(0, 1).toUpperCase() + this.substring(1);
    183 }
    184 
    185 /**
    186  * @param {string} other
    187  * @return {number}
    188  */
    189 String.prototype.compareTo = function(other)
    190 {
    191     if (this > other)
    192         return 1;
    193     if (this < other)
    194         return -1;
    195     return 0;
    196 }
    197 
    198 /**
    199  * @param {string} href
    200  * @return {?string}
    201  */
    202 function sanitizeHref(href)
    203 {
    204     return href && href.trim().toLowerCase().startsWith("javascript:") ? null : href;
    205 }
    206 
    207 /**
    208  * @return {string}
    209  */
    210 String.prototype.removeURLFragment = function()
    211 {
    212     var fragmentIndex = this.indexOf("#");
    213     if (fragmentIndex == -1)
    214         fragmentIndex = this.length;
    215     return this.substring(0, fragmentIndex);
    216 }
    217 
    218 /**
    219  * @return {boolean}
    220  */
    221 String.prototype.startsWith = function(substring)
    222 {
    223     return !this.lastIndexOf(substring, 0);
    224 }
    225 
    226 /**
    227  * @return {boolean}
    228  */
    229 String.prototype.endsWith = function(substring)
    230 {
    231     return this.indexOf(substring, this.length - substring.length) !== -1;
    232 }
    233 
    234 /**
    235  * @param {string} a
    236  * @param {string} b
    237  * @return {number}
    238  */
    239 String.naturalOrderComparator = function(a, b)
    240 {
    241     var chunk = /^\d+|^\D+/;
    242     var chunka, chunkb, anum, bnum;
    243     while (1) {
    244         if (a) {
    245             if (!b)
    246                 return 1;
    247         } else {
    248             if (b)
    249                 return -1;
    250             else
    251                 return 0;
    252         }
    253         chunka = a.match(chunk)[0];
    254         chunkb = b.match(chunk)[0];
    255         anum = !isNaN(chunka);
    256         bnum = !isNaN(chunkb);
    257         if (anum && !bnum)
    258             return -1;
    259         if (bnum && !anum)
    260             return 1;
    261         if (anum && bnum) {
    262             var diff = chunka - chunkb;
    263             if (diff)
    264                 return diff;
    265             if (chunka.length !== chunkb.length) {
    266                 if (!+chunka && !+chunkb) // chunks are strings of all 0s (special case)
    267                     return chunka.length - chunkb.length;
    268                 else
    269                     return chunkb.length - chunka.length;
    270             }
    271         } else if (chunka !== chunkb)
    272             return (chunka < chunkb) ? -1 : 1;
    273         a = a.substring(chunka.length);
    274         b = b.substring(chunkb.length);
    275     }
    276 }
    277 
    278 /**
    279  * @param {number} num
    280  * @param {number} min
    281  * @param {number} max
    282  * @return {number}
    283  */
    284 Number.constrain = function(num, min, max)
    285 {
    286     if (num < min)
    287         num = min;
    288     else if (num > max)
    289         num = max;
    290     return num;
    291 }
    292 
    293 /**
    294  * @param {number} a
    295  * @param {number} b
    296  * @return {number}
    297  */
    298 Number.gcd = function(a, b)
    299 {
    300     if (b === 0)
    301         return a;
    302     else
    303         return Number.gcd(b, a % b);
    304 }
    305 
    306 /**
    307  * @param {string} value
    308  * @return {string}
    309  */
    310 Number.toFixedIfFloating = function(value)
    311 {
    312     if (!value || isNaN(value))
    313         return value;
    314     var number = Number(value);
    315     return number % 1 ? number.toFixed(3) : String(number);
    316 }
    317 
    318 /**
    319  * @return {string}
    320  */
    321 Date.prototype.toISO8601Compact = function()
    322 {
    323     /**
    324      * @param {number} x
    325      * @return {string}
    326      */
    327     function leadZero(x)
    328     {
    329         return (x > 9 ? "" : "0") + x;
    330     }
    331     return this.getFullYear() +
    332            leadZero(this.getMonth() + 1) +
    333            leadZero(this.getDate()) + "T" +
    334            leadZero(this.getHours()) +
    335            leadZero(this.getMinutes()) +
    336            leadZero(this.getSeconds());
    337 }
    338 
    339 Object.defineProperty(Array.prototype, "remove",
    340 {
    341     /**
    342      * @param {!T} value
    343      * @param {boolean=} onlyFirst
    344      * @this {Array.<!T>}
    345      * @template T
    346      */
    347     value: function(value, onlyFirst)
    348     {
    349         if (onlyFirst) {
    350             var index = this.indexOf(value);
    351             if (index !== -1)
    352                 this.splice(index, 1);
    353             return;
    354         }
    355 
    356         var length = this.length;
    357         for (var i = 0; i < length; ++i) {
    358             if (this[i] === value)
    359                 this.splice(i, 1);
    360         }
    361     }
    362 });
    363 
    364 Object.defineProperty(Array.prototype, "keySet",
    365 {
    366     /**
    367      * @return {!Object.<string, boolean>}
    368      * @this {Array.<*>}
    369      */
    370     value: function()
    371     {
    372         var keys = {};
    373         for (var i = 0; i < this.length; ++i)
    374             keys[this[i]] = true;
    375         return keys;
    376     }
    377 });
    378 
    379 Object.defineProperty(Array.prototype, "rotate",
    380 {
    381     /**
    382      * @param {number} index
    383      * @return {!Array.<!T>}
    384      * @this {Array.<!T>}
    385      * @template T
    386      */
    387     value: function(index)
    388     {
    389         var result = [];
    390         for (var i = index; i < index + this.length; ++i)
    391             result.push(this[i % this.length]);
    392         return result;
    393     }
    394 });
    395 
    396 Object.defineProperty(Uint32Array.prototype, "sort", {
    397    value: Array.prototype.sort
    398 });
    399 
    400 (function() {
    401 var partition = {
    402     /**
    403      * @this {Array.<number>}
    404      * @param {function(number, number): number} comparator
    405      * @param {number} left
    406      * @param {number} right
    407      * @param {number} pivotIndex
    408      */
    409     value: function(comparator, left, right, pivotIndex)
    410     {
    411         function swap(array, i1, i2)
    412         {
    413             var temp = array[i1];
    414             array[i1] = array[i2];
    415             array[i2] = temp;
    416         }
    417 
    418         var pivotValue = this[pivotIndex];
    419         swap(this, right, pivotIndex);
    420         var storeIndex = left;
    421         for (var i = left; i < right; ++i) {
    422             if (comparator(this[i], pivotValue) < 0) {
    423                 swap(this, storeIndex, i);
    424                 ++storeIndex;
    425             }
    426         }
    427         swap(this, right, storeIndex);
    428         return storeIndex;
    429     }
    430 };
    431 Object.defineProperty(Array.prototype, "partition", partition);
    432 Object.defineProperty(Uint32Array.prototype, "partition", partition);
    433 
    434 var sortRange = {
    435     /**
    436      * @param {function(number, number): number} comparator
    437      * @param {number} leftBound
    438      * @param {number} rightBound
    439      * @param {number} 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 }
    464 Object.defineProperty(Array.prototype, "sortRange", sortRange);
    465 Object.defineProperty(Uint32Array.prototype, "sortRange", sortRange);
    466 })();
    467 
    468 Object.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 
    521 Object.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 
    550 Object.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 
    584 Object.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 
    618 Object.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 
    634 Object.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 
    651 Object.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  */
    673 function 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 
    705 Object.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 
    720 Object.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  */
    746 function 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  */
    759 String.sprintf = function(format, var_arg)
    760 {
    761     return String.vsprintf(format, Array.prototype.slice.call(arguments, 1));
    762 }
    763 
    764 String.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 
    833 String.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  */
    857 String.vsprintf = function(format, substitutions)
    858 {
    859     return String.format(format, substitutions, String.standardFormatters, "", function(a, b) { return a + b; }).formattedResult;
    860 }
    861 
    862 String.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  */
    935 function 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  */
    959 function 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  */
    978 function 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  */
    996 function 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  */
   1007 var createObjectIdentifier = function()
   1008 {
   1009     // It has to be string for better performance.
   1010     return "_" + ++createObjectIdentifier._last;
   1011 }
   1012 
   1013 createObjectIdentifier._last = 0;
   1014 
   1015 /**
   1016  * @constructor
   1017  * @template T
   1018  */
   1019 var Set = function()
   1020 {
   1021     /** @type {!Object.<string, !T>} */
   1022     this._set = {};
   1023     this._size = 0;
   1024 }
   1025 
   1026 Set.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  */
   1096 var Map = function()
   1097 {
   1098     /** @type {!Object.<string, !Array.<K|V>>} */
   1099     this._map = {};
   1100     this._size = 0;
   1101 }
   1102 
   1103 Map.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  */
   1202 var StringMap = function()
   1203 {
   1204     /** @type {!Object.<string, T>} */
   1205     this._map = {};
   1206     this._size = 0;
   1207 }
   1208 
   1209 StringMap.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  */
   1322 function 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  */
   1354 function StringPool()
   1355 {
   1356     this.reset();
   1357 }
   1358 
   1359 StringPool.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 
   1406 var _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  */
   1420 function 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 
   1434 var loadScript = importScript;
   1435 
   1436 /**
   1437  * @constructor
   1438  */
   1439 function CallbackBarrier()
   1440 {
   1441     this._pendingIncomingCallbacksCount = 0;
   1442 }
   1443 
   1444 CallbackBarrier.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