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 |