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