2 * Copyright (C) 2007 Apple Inc. All rights reserved.
3 * Copyright (C) 2012 Google Inc. All rights reserved.
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
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.
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.
30 // FIXME: This performance optimization should be moved to blink so that all developers could enjoy it.
31 // console is retrieved with V8Window.getAttribute method which is slow. Here we copy it to a js variable for faster access.
33 console
.__originalAssert
= console
.assert
;
34 console
.assert = function(value
, message
)
38 console
.__originalAssert(value
, message
);
41 /** @typedef {Array|NodeList|Arguments|{length: number}} */
45 * @param {!Object} obj
48 Object
.isEmpty = function(obj
)
56 * @param {!Object.<string,!T>} obj
57 * @return {!Array.<!T>}
60 Object
.values = function(obj
)
62 var result
= Object
.keys(obj
);
63 var length
= result
.length
;
64 for (var i
= 0; i
< length
; ++i
)
65 result
[i
] = obj
[result
[i
]];
76 return ((m
% n
) + n
) % n
;
80 * @param {string} string
81 * @return {!Array.<number>}
83 String
.prototype.findAll = function(string
)
86 var i
= this.indexOf(string
);
89 i
= this.indexOf(string
, i
+ string
.length
);
95 * @return {!Array.<number>}
97 String
.prototype.lineEndings = function()
99 if (!this._lineEndings
) {
100 this._lineEndings
= this.findAll("\n");
101 this._lineEndings
.push(this.length
);
103 return this._lineEndings
;
109 String
.prototype.lineCount = function()
111 var lineEndings
= this.lineEndings();
112 return lineEndings
.length
;
118 String
.prototype.lineAt = function(lineNumber
)
120 var lineEndings
= this.lineEndings();
121 var lineStart
= lineNumber
> 0 ? lineEndings
[lineNumber
- 1] + 1 : 0;
122 var lineEnd
= lineEndings
[lineNumber
];
123 var lineContent
= this.substring(lineStart
, lineEnd
);
124 if (lineContent
.length
> 0 && lineContent
.charAt(lineContent
.length
- 1) === "\r")
125 lineContent
= lineContent
.substring(0, lineContent
.length
- 1);
130 * @param {string} chars
133 String
.prototype.escapeCharacters = function(chars
)
135 var foundChar
= false;
136 for (var i
= 0; i
< chars
.length
; ++i
) {
137 if (this.indexOf(chars
.charAt(i
)) !== -1) {
147 for (var i
= 0; i
< this.length
; ++i
) {
148 if (chars
.indexOf(this.charAt(i
)) !== -1)
150 result
+= this.charAt(i
);
159 String
.regexSpecialCharacters = function()
161 return "^[]{}()\\.^$*+?|-,";
167 String
.prototype.escapeForRegExp = function()
169 return this.escapeCharacters(String
.regexSpecialCharacters());
175 String
.prototype.escapeHTML = function()
177 return this.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, ""
;"); //" doublequotes just
for editor
183 String
.prototype.unescapeHTML = function()
185 return this.replace(/</g, "<")
186 .replace(/>/g, ">")
187 .replace(/:/g, ":")
188 .replace(/"/g, "\"")
189 .replace(/</g, "<")
190 .replace(/>/g, ">")
191 .replace(/&/g, "&");
197 String
.prototype.collapseWhitespace = function()
199 return this.replace(/[\s\xA0]+/g, " ");
203 * @param {number} maxLength
206 String
.prototype.trimMiddle = function(maxLength
)
208 if (this.length
<= maxLength
)
210 var leftHalf
= maxLength
>> 1;
211 var rightHalf
= maxLength
- leftHalf
- 1;
212 return this.substr(0, leftHalf
) + "\u2026" + this.substr(this.length
- rightHalf
, rightHalf
);
216 * @param {number} maxLength
219 String
.prototype.trimEnd = function(maxLength
)
221 if (this.length
<= maxLength
)
223 return this.substr(0, maxLength
- 1) + "\u2026";
227 * @param {?string=} baseURLDomain
230 String
.prototype.trimURL = function(baseURLDomain
)
232 var result
= this.replace(/^(https|http|file):\/\//i, "");
234 if (result
.toLowerCase().startsWith(baseURLDomain
.toLowerCase()))
235 result
= result
.substr(baseURLDomain
.length
);
243 String
.prototype.toTitleCase = function()
245 return this.substring(0, 1).toUpperCase() + this.substring(1);
249 * @param {string} other
252 String
.prototype.compareTo = function(other
)
262 * @param {string} href
265 function sanitizeHref(href
)
267 return href
&& href
.trim().toLowerCase().startsWith("javascript:") ? null : href
;
273 String
.prototype.removeURLFragment = function()
275 var fragmentIndex
= this.indexOf("#");
276 if (fragmentIndex
== -1)
277 fragmentIndex
= this.length
;
278 return this.substring(0, fragmentIndex
);
282 * @param {string|undefined} string
285 String
.hashCode = function(string
)
290 for (var i
= 0; i
< string
.length
; ++i
)
291 result
= (result
* 3 + string
.charCodeAt(i
)) | 0;
296 * @param {string} string
297 * @param {number} index
300 String
.isDigitAt = function(string
, index
)
302 var c
= string
.charCodeAt(index
);
303 return 48 <= c
&& c
<= 57;
309 String
.prototype.toBase64 = function()
315 function encodeBits(b
)
317 return b
< 26 ? b
+ 65 : b
< 52 ? b
+ 71 : b
< 62 ? b
- 4 : b
=== 62 ? 43 : b
=== 63 ? 47 : 65;
319 var encoder
= new TextEncoder();
320 var data
= encoder
.encode(this.toString());
327 for (var i
= 0; i
< n
; i
++) {
329 v
|= data
[i
] << (16 >>> shift
& 24);
331 encoded
+= String
.fromCharCode(encodeBits(v
>>> 18 & 63), encodeBits(v
>>> 12 & 63), encodeBits(v
>>> 6 & 63), encodeBits(v
& 63));
336 encoded
+= String
.fromCharCode(encodeBits(v
>>> 18 & 63), encodeBits(v
>>> 12 & 63), 61, 61);
337 else if (shift
=== 1)
338 encoded
+= String
.fromCharCode(encodeBits(v
>>> 18 & 63), encodeBits(v
>>> 12 & 63), encodeBits(v
>>> 6 & 63), 61);
347 String
.naturalOrderComparator = function(a
, b
)
349 var chunk
= /^\d+|^\D+/;
350 var chunka
, chunkb
, anum
, bnum
;
361 chunka
= a
.match(chunk
)[0];
362 chunkb
= b
.match(chunk
)[0];
363 anum
= !isNaN(chunka
);
364 bnum
= !isNaN(chunkb
);
370 var diff
= chunka
- chunkb
;
373 if (chunka
.length
!== chunkb
.length
) {
374 if (!+chunka
&& !+chunkb
) // chunks are strings of all 0s (special case)
375 return chunka
.length
- chunkb
.length
;
377 return chunkb
.length
- chunka
.length
;
379 } else if (chunka
!== chunkb
)
380 return (chunka
< chunkb
) ? -1 : 1;
381 a
= a
.substring(chunka
.length
);
382 b
= b
.substring(chunkb
.length
);
387 * @param {number} num
388 * @param {number} min
389 * @param {number} max
392 Number
.constrain = function(num
, min
, max
)
406 Number
.gcd = function(a
, b
)
411 return Number
.gcd(b
, a
% b
);
415 * @param {string} value
418 Number
.toFixedIfFloating = function(value
)
420 if (!value
|| isNaN(value
))
422 var number
= Number(value
);
423 return number
% 1 ? number
.toFixed(3) : String(number
);
429 Date
.prototype.toISO8601Compact = function()
437 return (x
> 9 ? "" : "0") + x
;
439 return this.getFullYear() +
440 leadZero(this.getMonth() + 1) +
441 leadZero(this.getDate()) + "T" +
442 leadZero(this.getHours()) +
443 leadZero(this.getMinutes()) +
444 leadZero(this.getSeconds());
450 Date
.prototype.toConsoleTime = function()
456 function leadZero2(x
)
458 return (x
> 9 ? "" : "0") + x
;
465 function leadZero3(x
)
467 return "0".repeat(3 - x
.toString().length
) + x
;
470 return this.getFullYear() + "-" +
471 leadZero2(this.getMonth() + 1) + "-" +
472 leadZero2(this.getDate()) + " " +
473 leadZero2(this.getHours()) + ":" +
474 leadZero2(this.getMinutes()) + ":" +
475 leadZero2(this.getSeconds()) + "." +
476 leadZero3(this.getMilliseconds());
479 Object
.defineProperty(Array
.prototype, "remove",
483 * @param {boolean=} firstOnly
487 value: function(value
, firstOnly
)
489 var index
= this.indexOf(value
);
493 this.splice(index
, 1);
496 for (var i
= index
+ 1, n
= this.length
; i
< n
; ++i
) {
497 if (this[i
] !== value
)
498 this[index
++] = this[i
];
504 Object
.defineProperty(Array
.prototype, "keySet",
507 * @return {!Object.<string, boolean>}
513 for (var i
= 0; i
< this.length
; ++i
)
514 keys
[this[i
]] = true;
519 Object
.defineProperty(Array
.prototype, "pushAll",
522 * @param {!Array.<!T>} array
526 value: function(array
)
528 Array
.prototype.push
.apply(this, array
);
532 Object
.defineProperty(Array
.prototype, "rotate",
535 * @param {number} index
536 * @return {!Array.<!T>}
540 value: function(index
)
543 for (var i
= index
; i
< index
+ this.length
; ++i
)
544 result
.push(this[i
% this.length
]);
549 Object
.defineProperty(Array
.prototype, "sortNumbers",
552 * @this {Array.<number>}
561 function numericComparator(a
, b
)
566 this.sort(numericComparator
);
570 Object
.defineProperty(Uint32Array
.prototype, "sort", {
571 value
: Array
.prototype.sort
577 * @this {Array.<number>}
578 * @param {function(number, number): number} comparator
579 * @param {number} left
580 * @param {number} right
581 * @param {number} pivotIndex
583 value: function(comparator
, left
, right
, pivotIndex
)
585 function swap(array
, i1
, i2
)
587 var temp
= array
[i1
];
588 array
[i1
] = array
[i2
];
592 var pivotValue
= this[pivotIndex
];
593 swap(this, right
, pivotIndex
);
594 var storeIndex
= left
;
595 for (var i
= left
; i
< right
; ++i
) {
596 if (comparator(this[i
], pivotValue
) < 0) {
597 swap(this, storeIndex
, i
);
601 swap(this, right
, storeIndex
);
605 Object
.defineProperty(Array
.prototype, "partition", partition
);
606 Object
.defineProperty(Uint32Array
.prototype, "partition", partition
);
610 * @param {function(number, number): number} comparator
611 * @param {number} leftBound
612 * @param {number} rightBound
613 * @param {number} sortWindowLeft
614 * @param {number} sortWindowRight
615 * @return {!Array.<number>}
616 * @this {Array.<number>}
618 value: function(comparator
, leftBound
, rightBound
, sortWindowLeft
, sortWindowRight
)
620 function quickSortRange(array
, comparator
, left
, right
, sortWindowLeft
, sortWindowRight
)
624 var pivotIndex
= Math
.floor(Math
.random() * (right
- left
)) + left
;
625 var pivotNewIndex
= array
.partition(comparator
, left
, right
, pivotIndex
);
626 if (sortWindowLeft
< pivotNewIndex
)
627 quickSortRange(array
, comparator
, left
, pivotNewIndex
- 1, sortWindowLeft
, sortWindowRight
);
628 if (pivotNewIndex
< sortWindowRight
)
629 quickSortRange(array
, comparator
, pivotNewIndex
+ 1, right
, sortWindowLeft
, sortWindowRight
);
631 if (leftBound
=== 0 && rightBound
=== (this.length
- 1) && sortWindowLeft
=== 0 && sortWindowRight
>= rightBound
)
632 this.sort(comparator
);
634 quickSortRange(this, comparator
, leftBound
, rightBound
, sortWindowLeft
, sortWindowRight
);
638 Object
.defineProperty(Array
.prototype, "sortRange", sortRange
);
639 Object
.defineProperty(Uint32Array
.prototype, "sortRange", sortRange
);
642 Object
.defineProperty(Array
.prototype, "stableSort",
645 * @param {function(?T, ?T): number=} comparator
646 * @return {!Array.<?T>}
650 value: function(comparator
)
652 function defaultComparator(a
, b
)
654 return a
< b
? -1 : (a
> b
? 1 : 0);
656 comparator
= comparator
|| defaultComparator
;
658 var indices
= new Array(this.length
);
659 for (var i
= 0; i
< this.length
; ++i
)
667 function indexComparator(a
, b
)
669 var result
= comparator(self
[a
], self
[b
]);
670 return result
? result
: a
- b
;
672 indices
.sort(indexComparator
);
674 for (var i
= 0; i
< this.length
; ++i
) {
675 if (indices
[i
] < 0 || i
=== indices
[i
])
680 var next
= indices
[cyclical
];
681 indices
[cyclical
] = -1;
683 this[cyclical
] = saved
;
686 this[cyclical
] = this[next
];
695 Object
.defineProperty(Array
.prototype, "qselect",
699 * @param {function(number, number): number=} comparator
700 * @return {number|undefined}
701 * @this {Array.<number>}
703 value: function(k
, comparator
)
705 if (k
< 0 || k
>= this.length
)
708 comparator = function(a
, b
) { return a
- b
; }
711 var high
= this.length
- 1;
713 var pivotPosition
= this.partition(comparator
, low
, high
, Math
.floor((high
+ low
) / 2));
714 if (pivotPosition
=== k
)
716 else if (pivotPosition
> k
)
717 high
= pivotPosition
- 1;
719 low
= pivotPosition
+ 1;
724 Object
.defineProperty(Array
.prototype, "lowerBound",
727 * Return index of the leftmost element that is equal or greater
728 * than the specimen object. If there's no such element (i.e. all
729 * elements are smaller than the specimen) returns right bound.
730 * The function works for sorted array.
731 * When specified, |left| (inclusive) and |right| (exclusive) indices
732 * define the search window.
735 * @param {function(!T,!S):number=} comparator
736 * @param {number=} left
737 * @param {number=} right
742 value: function(object
, comparator
, left
, right
)
744 function defaultComparator(a
, b
)
746 return a
< b
? -1 : (a
> b
? 1 : 0);
748 comparator
= comparator
|| defaultComparator
;
750 var r
= right
!== undefined ? right
: this.length
;
752 var m
= (l
+ r
) >> 1;
753 if (comparator(object
, this[m
]) > 0)
762 Object
.defineProperty(Array
.prototype, "upperBound",
765 * Return index of the leftmost element that is greater
766 * than the specimen object. If there's no such element (i.e. all
767 * elements are smaller or equal to the specimen) returns right bound.
768 * The function works for sorted array.
769 * When specified, |left| (inclusive) and |right| (exclusive) indices
770 * define the search window.
773 * @param {function(!T,!S):number=} comparator
774 * @param {number=} left
775 * @param {number=} right
780 value: function(object
, comparator
, left
, right
)
782 function defaultComparator(a
, b
)
784 return a
< b
? -1 : (a
> b
? 1 : 0);
786 comparator
= comparator
|| defaultComparator
;
788 var r
= right
!== undefined ? right
: this.length
;
790 var m
= (l
+ r
) >> 1;
791 if (comparator(object
, this[m
]) >= 0)
800 Object
.defineProperty(Uint32Array
.prototype, "lowerBound", {
801 value
: Array
.prototype.lowerBound
804 Object
.defineProperty(Uint32Array
.prototype, "upperBound", {
805 value
: Array
.prototype.upperBound
808 Object
.defineProperty(Float64Array
.prototype, "lowerBound", {
809 value
: Array
.prototype.lowerBound
812 Object
.defineProperty(Array
.prototype, "binaryIndexOf",
816 * @param {function(!T,!S):number} comparator
821 value: function(value
, comparator
)
823 var index
= this.lowerBound(value
, comparator
);
824 return index
< this.length
&& comparator(value
, this[index
]) === 0 ? index
: -1;
828 Object
.defineProperty(Array
.prototype, "select",
831 * @param {string} field
832 * @return {!Array.<!T>}
833 * @this {Array.<!Object.<string,!T>>}
836 value: function(field
)
838 var result
= new Array(this.length
);
839 for (var i
= 0; i
< this.length
; ++i
)
840 result
[i
] = this[i
][field
];
845 Object
.defineProperty(Array
.prototype, "peekLast",
848 * @return {!T|undefined}
854 return this[this.length
- 1];
861 * @param {!Array.<T>} array1
862 * @param {!Array.<T>} array2
863 * @param {function(T,T):number} comparator
864 * @param {boolean} mergeNotIntersect
865 * @return {!Array.<T>}
868 function mergeOrIntersect(array1
, array2
, comparator
, mergeNotIntersect
)
873 while (i
< array1
.length
&& j
< array2
.length
) {
874 var compareValue
= comparator(array1
[i
], array2
[j
]);
875 if (mergeNotIntersect
|| !compareValue
)
876 result
.push(compareValue
<= 0 ? array1
[i
] : array2
[j
]);
877 if (compareValue
<= 0)
879 if (compareValue
>= 0)
882 if (mergeNotIntersect
) {
883 while (i
< array1
.length
)
884 result
.push(array1
[i
++]);
885 while (j
< array2
.length
)
886 result
.push(array2
[j
++]);
891 Object
.defineProperty(Array
.prototype, "intersectOrdered",
894 * @param {!Array.<T>} array
895 * @param {function(T,T):number} comparator
896 * @return {!Array.<T>}
900 value: function(array
, comparator
)
902 return mergeOrIntersect(this, array
, comparator
, false);
906 Object
.defineProperty(Array
.prototype, "mergeOrdered",
909 * @param {!Array.<T>} array
910 * @param {function(T,T):number} comparator
911 * @return {!Array.<T>}
915 value: function(array
, comparator
)
917 return mergeOrIntersect(this, array
, comparator
, true);
926 * @param {!Array.<!S>} list
927 * @param {function(!T,!S):number=} comparator
928 * @param {boolean=} insertionIndexAfter
932 function insertionIndexForObjectInListSortedByFunction(object
, list
, comparator
, insertionIndexAfter
)
934 if (insertionIndexAfter
)
935 return list
.upperBound(object
, comparator
);
937 return list
.lowerBound(object
, comparator
);
941 * @param {string} format
942 * @param {...*} var_arg
945 String
.sprintf = function(format
, var_arg
)
947 return String
.vsprintf(format
, Array
.prototype.slice
.call(arguments
, 1));
951 * @param {string} format
952 * @param {!Object.<string, function(string, ...):*>} formatters
953 * @return {!Array.<!Object>}
955 String
.tokenizeFormatString = function(format
, formatters
)
958 var substitutionIndex
= 0;
960 function addStringToken(str
)
962 if (tokens
.length
&& tokens
[tokens
.length
- 1].type
=== "string")
963 tokens
[tokens
.length
- 1].value
+= str
;
965 tokens
.push({ type
: "string", value
: str
});
968 function addSpecifierToken(specifier
, precision
, substitutionIndex
)
970 tokens
.push({ type
: "specifier", specifier
: specifier
, precision
: precision
, substitutionIndex
: substitutionIndex
});
974 for (var precentIndex
= format
.indexOf("%", index
); precentIndex
!== -1; precentIndex
= format
.indexOf("%", index
)) {
975 if (format
.length
=== index
) // unescaped % sign at the end of the format string.
977 addStringToken(format
.substring(index
, precentIndex
));
978 index
= precentIndex
+ 1;
980 if (format
[index
] === "%") {
981 // %% escape sequence.
987 if (String
.isDigitAt(format
, index
)) {
988 // The first character is a number, it might be a substitution index.
989 var number
= parseInt(format
.substring(index
), 10);
990 while (String
.isDigitAt(format
, index
))
993 // If the number is greater than zero and ends with a "$",
994 // then this is a substitution index.
995 if (number
> 0 && format
[index
] === "$") {
996 substitutionIndex
= (number
- 1);
1002 if (format
[index
] === ".") {
1003 // This is a precision specifier. If no digit follows the ".",
1004 // then the precision should be zero.
1006 precision
= parseInt(format
.substring(index
), 10);
1007 if (isNaN(precision
))
1010 while (String
.isDigitAt(format
, index
))
1014 if (!(format
[index
] in formatters
)) {
1015 addStringToken(format
.substring(precentIndex
, index
+ 1));
1020 addSpecifierToken(format
[index
], precision
, substitutionIndex
);
1022 ++substitutionIndex
;
1026 addStringToken(format
.substring(index
));
1031 String
.standardFormatters
= {
1035 d: function(substitution
)
1037 return !isNaN(substitution
) ? substitution
: 0;
1043 f: function(substitution
, token
)
1045 if (substitution
&& token
.precision
> -1)
1046 substitution
= substitution
.toFixed(token
.precision
);
1047 return !isNaN(substitution
) ? substitution
: (token
.precision
> -1 ? Number(0).toFixed(token
.precision
) : 0);
1053 s: function(substitution
)
1055 return substitution
;
1060 * @param {string} format
1061 * @param {!Array.<*>} substitutions
1064 String
.vsprintf = function(format
, substitutions
)
1066 return String
.format(format
, substitutions
, String
.standardFormatters
, "", function(a
, b
) { return a
+ b
; }).formattedResult
;
1070 * @param {string} format
1071 * @param {?ArrayLike} substitutions
1072 * @param {!Object.<string, function(string, ...):string>} formatters
1073 * @param {!T} initialValue
1074 * @param {function(T, string): T|undefined} append
1075 * @param {!Array.<!Object>=} tokenizedFormat
1076 * @return {!{formattedResult: T, unusedSubstitutions: ?ArrayLike}};
1079 String
.format = function(format
, substitutions
, formatters
, initialValue
, append
, tokenizedFormat
)
1081 if (!format
|| !substitutions
|| !substitutions
.length
)
1082 return { formattedResult
: append(initialValue
, format
), unusedSubstitutions
: substitutions
};
1084 function prettyFunctionName()
1086 return "String.format(\"" + format
+ "\", \"" + Array
.prototype.join
.call(substitutions
, "\", \"") + "\")";
1091 console
.warn(prettyFunctionName() + ": " + msg
);
1096 console
.error(prettyFunctionName() + ": " + msg
);
1099 var result
= initialValue
;
1100 var tokens
= tokenizedFormat
|| String
.tokenizeFormatString(format
, formatters
);
1101 var usedSubstitutionIndexes
= {};
1103 for (var i
= 0; i
< tokens
.length
; ++i
) {
1104 var token
= tokens
[i
];
1106 if (token
.type
=== "string") {
1107 result
= append(result
, token
.value
);
1111 if (token
.type
!== "specifier") {
1112 error("Unknown token type \"" + token
.type
+ "\" found.");
1116 if (token
.substitutionIndex
>= substitutions
.length
) {
1117 // If there are not enough substitutions for the current substitutionIndex
1118 // just output the format specifier literally and move on.
1119 error("not enough substitution arguments. Had " + substitutions
.length
+ " but needed " + (token
.substitutionIndex
+ 1) + ", so substitution was skipped.");
1120 result
= append(result
, "%" + (token
.precision
> -1 ? token
.precision
: "") + token
.specifier
);
1124 usedSubstitutionIndexes
[token
.substitutionIndex
] = true;
1126 if (!(token
.specifier
in formatters
)) {
1127 // Encountered an unsupported format character, treat as a string.
1128 warn("unsupported format character \u201C" + token
.specifier
+ "\u201D. Treating as a string.");
1129 result
= append(result
, substitutions
[token
.substitutionIndex
]);
1133 result
= append(result
, formatters
[token
.specifier
](substitutions
[token
.substitutionIndex
], token
));
1136 var unusedSubstitutions
= [];
1137 for (var i
= 0; i
< substitutions
.length
; ++i
) {
1138 if (i
in usedSubstitutionIndexes
)
1140 unusedSubstitutions
.push(substitutions
[i
]);
1143 return { formattedResult
: result
, unusedSubstitutions
: unusedSubstitutions
};
1147 * @param {string} query
1148 * @param {boolean} caseSensitive
1149 * @param {boolean} isRegex
1152 function createSearchRegex(query
, caseSensitive
, isRegex
)
1154 var regexFlags
= caseSensitive
? "g" : "gi";
1159 regexObject
= new RegExp(query
, regexFlags
);
1166 regexObject
= createPlainTextSearchRegex(query
, regexFlags
);
1172 * @param {string} query
1173 * @param {string=} flags
1176 function createPlainTextSearchRegex(query
, flags
)
1178 // This should be kept the same as the one in ContentSearchUtils.cpp.
1179 var regexSpecialCharacters
= String
.regexSpecialCharacters();
1181 for (var i
= 0; i
< query
.length
; ++i
) {
1182 var c
= query
.charAt(i
);
1183 if (regexSpecialCharacters
.indexOf(c
) != -1)
1187 return new RegExp(regex
, flags
|| "");
1191 * @param {!RegExp} regex
1192 * @param {string} content
1195 function countRegexMatches(regex
, content
)
1200 while (text
&& (match
= regex
.exec(text
))) {
1201 if (match
[0].length
> 0)
1203 text
= text
.substring(match
.index
+ 1);
1209 * @param {number} spacesCount
1212 function spacesPadding(spacesCount
)
1214 return "\u00a0".repeat(spacesCount
);
1218 * @param {number} value
1219 * @param {number} symbolsCount
1222 function numberToStringWithSpacesPadding(value
, symbolsCount
)
1224 var numberString
= value
.toString();
1225 var paddingLength
= Math
.max(0, symbolsCount
- numberString
.length
);
1226 return spacesPadding(paddingLength
) + numberString
;
1230 * @param {!Iterator.<T>} iterator
1231 * @return {!Array.<T>}
1234 Array
.from = function(iterator
)
1237 for (var iteratorValue
= iterator
.next(); !iteratorValue
.done
; iteratorValue
= iterator
.next())
1238 values
.push(iteratorValue
.value
);
1243 * @return {!Array.<T>}
1246 Set
.prototype.valuesArray = function()
1248 return Array
.from(this.values());
1255 Map
.prototype.remove = function(key
)
1257 var value
= this.get(key
);
1263 * @return {!Array.<V>}
1265 * @this {Map.<K, V>}
1267 Map
.prototype.valuesArray = function()
1269 return Array
.from(this.values());
1273 * @return {!Array.<K>}
1275 * @this {Map.<K, V>}
1277 Map
.prototype.keysArray = function()
1279 return Array
.from(this.keys());
1286 var Multimap = function()
1288 /** @type {!Map.<K, !Set.<!V>>} */
1289 this._map
= new Map();
1292 Multimap
.prototype = {
1297 set: function(key
, value
)
1299 var set = this._map
.get(key
);
1302 this._map
.set(key
, set);
1309 * @return {!Set.<!V>}
1313 var result
= this._map
.get(key
);
1323 remove: function(key
, value
)
1325 var values
= this.get(key
);
1326 values
.delete(value
);
1328 this._map
.delete(key
);
1334 removeAll: function(key
)
1336 this._map
.delete(key
);
1340 * @return {!Array.<K>}
1342 keysArray: function()
1344 return this._map
.keysArray();
1348 * @return {!Array.<!V>}
1350 valuesArray: function()
1353 var keys
= this.keysArray();
1354 for (var i
= 0; i
< keys
.length
; ++i
)
1355 result
.pushAll(this.get(keys
[i
]).valuesArray());
1366 * @param {string} url
1367 * @return {!Promise.<string>}
1369 function loadXHR(url
)
1371 return new Promise(load
);
1373 function load(successCallback
, failureCallback
)
1375 function onReadyStateChanged()
1377 if (xhr
.readyState
!== XMLHttpRequest
.DONE
)
1379 if (xhr
.status
!== 200) {
1380 xhr
.onreadystatechange
= null;
1381 failureCallback(new Error(xhr
.status
));
1384 xhr
.onreadystatechange
= null;
1385 successCallback(xhr
.responseText
);
1388 var xhr
= new XMLHttpRequest();
1389 xhr
.withCredentials
= false;
1390 xhr
.open("GET", url
, true);
1391 xhr
.onreadystatechange
= onReadyStateChanged
;
1399 function CallbackBarrier()
1401 this._pendingIncomingCallbacksCount
= 0;
1404 CallbackBarrier
.prototype = {
1406 * @param {function(...)=} userCallback
1407 * @return {function(...)}
1409 createCallback: function(userCallback
)
1411 console
.assert(!this._outgoingCallback
, "CallbackBarrier.createCallback() is called after CallbackBarrier.callWhenDone()");
1412 ++this._pendingIncomingCallbacksCount
;
1413 return this._incomingCallback
.bind(this, userCallback
);
1417 * @param {function()} callback
1419 callWhenDone: function(callback
)
1421 console
.assert(!this._outgoingCallback
, "CallbackBarrier.callWhenDone() is called multiple times");
1422 this._outgoingCallback
= callback
;
1423 if (!this._pendingIncomingCallbacksCount
)
1424 this._outgoingCallback();
1428 * @return {!Promise.<undefined>}
1430 donePromise: function()
1432 return new Promise(promiseConstructor
.bind(this));
1435 * @param {function()} success
1436 * @this {CallbackBarrier}
1438 function promiseConstructor(success
)
1440 this.callWhenDone(success
);
1445 * @param {function(...)=} userCallback
1447 _incomingCallback: function(userCallback
)
1449 console
.assert(this._pendingIncomingCallbacksCount
> 0);
1451 var args
= Array
.prototype.slice
.call(arguments
, 1);
1452 userCallback
.apply(null, args
);
1454 if (!--this._pendingIncomingCallbacksCount
&& this._outgoingCallback
)
1455 this._outgoingCallback();
1462 function suppressUnused(value
)
1467 * @param {function()} callback
1470 self
.setImmediate = function(callback
)
1472 Promise
.resolve().then(callback
);
1477 * @param {function(...?)} callback
1478 * @return {!Promise.<T>}
1481 Promise
.prototype.spread = function(callback
)
1483 return this.then(spreadPromise
);
1485 function spreadPromise(arg
)
1487 return callback
.apply(null, arg
);
1492 * @param {T} defaultValue
1493 * @return {!Promise.<T>}
1496 Promise
.prototype.catchException = function(defaultValue
) {
1497 return this.catch(function (error
) {
1498 console
.error(error
);
1499 return defaultValue
;