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