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