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