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