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