| 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 |