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