OLD | NEW |
1 // Copyright (c) 2013 The Chromium Authors. All rights reserved. | 1 // Copyright (c) 2013 The Chromium Authors. All rights reserved. |
2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 'use strict'; | 5 'use strict'; |
6 | 6 |
7 /** | 7 /** |
8 * @fileoverview Utility objects and functions for Google Now extension. | 8 * @fileoverview Utility objects and functions for Google Now extension. |
9 * Most important entities here: | 9 * Most important entities here: |
10 * (1) 'wrapper' is a module used to add error handling and other services to | 10 * (1) 'wrapper' is a module used to add error handling and other services to |
(...skipping 419 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
430 })(); | 430 })(); |
431 | 431 |
432 wrapper.instrumentChromeApiFunction('alarms.get', 1); | 432 wrapper.instrumentChromeApiFunction('alarms.get', 1); |
433 wrapper.instrumentChromeApiFunction('alarms.onAlarm.addListener', 0); | 433 wrapper.instrumentChromeApiFunction('alarms.onAlarm.addListener', 0); |
434 wrapper.instrumentChromeApiFunction('identity.getAuthToken', 1); | 434 wrapper.instrumentChromeApiFunction('identity.getAuthToken', 1); |
435 wrapper.instrumentChromeApiFunction('identity.onSignInChanged.addListener', 0); | 435 wrapper.instrumentChromeApiFunction('identity.onSignInChanged.addListener', 0); |
436 wrapper.instrumentChromeApiFunction('identity.removeCachedAuthToken', 1); | 436 wrapper.instrumentChromeApiFunction('identity.removeCachedAuthToken', 1); |
437 wrapper.instrumentChromeApiFunction('webstorePrivate.getBrowserLogin', 0); | 437 wrapper.instrumentChromeApiFunction('webstorePrivate.getBrowserLogin', 0); |
438 | 438 |
439 /** | 439 /** |
440 * Add task tracking support to Promise.then. | 440 * Promise adapter for all JS promises to the task manager. |
441 * @override | |
442 */ | 441 */ |
443 Promise.prototype.then = function() { | 442 function registerPromiseAdapter() { |
444 var originalThen = Promise.prototype.then; | 443 var originalThen = Promise.prototype.then; |
445 return function(callback) { | 444 var originalCatch = Promise.prototype.catch; |
446 return originalThen.call(this, wrapper.wrapCallback(callback, false)); | 445 |
| 446 /** |
| 447 * Takes a promise and adds the callback tracker to it. |
| 448 * @param {object} promise Promise that receives the callback tracker. |
| 449 */ |
| 450 function instrumentPromise(promise) { |
| 451 if (promise.__tracker === undefined) { |
| 452 promise.__tracker = createPromiseCallbackTracker(promise); |
| 453 } |
447 } | 454 } |
448 }(); | |
449 | 455 |
450 /** | 456 Promise.prototype.then = function(onResolved, onRejected) { |
451 * Add task tracking support to Promise.catch. | 457 instrumentPromise(this); |
452 * @override | 458 return this.__tracker.handleThen(onResolved, onRejected); |
453 */ | |
454 Promise.prototype.catch = function() { | |
455 var originalCatch = Promise.prototype.catch; | |
456 return function(callback) { | |
457 return originalCatch.call(this, wrapper.wrapCallback(callback, false)); | |
458 } | 459 } |
459 }(); | 460 |
| 461 Promise.prototype.catch = function(onRejected) { |
| 462 instrumentPromise(this); |
| 463 return this.__tracker.handleCatch(onRejected); |
| 464 } |
| 465 |
| 466 /** |
| 467 * Promise Callback Tracker. |
| 468 * Handles coordination of 'then' and 'catch' callbacks in a task |
| 469 * manager compatible way. For an individual promise, either the 'then' |
| 470 * arguments or the 'catch' arguments will be processed, never both. |
| 471 * |
| 472 * Example: |
| 473 * var p = new Promise([Function]); |
| 474 * p.then([ThenA]); |
| 475 * p.then([ThenB]); |
| 476 * p.catch([CatchA]); |
| 477 * On resolution, [ThenA] and [ThenB] will be used. [CatchA] is discarded. |
| 478 * On rejection, vice versa. |
| 479 * |
| 480 * Clarification: |
| 481 * Chained promises create a new promise that is tracked separately from |
| 482 * the originaing promise, as the example below demonstrates: |
| 483 * |
| 484 * var p = new Promise([Function])); |
| 485 * p.then([ThenA]).then([ThenB]).catch([CatchA]); |
| 486 * ^ ^ ^ |
| 487 * | | + Returns a new promise. |
| 488 * | + Returns a new promise. |
| 489 * + Returns a new promise. |
| 490 * |
| 491 * Four promises exist in the above statement, each with its own |
| 492 * resolution and rejection state. However, by default, this state is |
| 493 * chained to the previous promise's resolution or rejection |
| 494 * state. |
| 495 * |
| 496 * If p resolves, then the 'then' calls will execute until all the 'then' |
| 497 * clauses are executed. If the result of either [ThenA] or [ThenB] is a |
| 498 * promise, then that execution state will guide the remaining chain. |
| 499 * Similarly, if [CatchA] returns a promise, it can also guide the |
| 500 * remaining chain. In this specific case, the chain ends, so there |
| 501 * is nothing left to do. |
| 502 * @param {object} promise Promise being tracked. |
| 503 * @return {object} A promise callback tracker. |
| 504 */ |
| 505 function createPromiseCallbackTracker(promise) { |
| 506 /** |
| 507 * Callback Tracker. Holds an array of callbacks created for this promise. |
| 508 * The indirection allows quick checks against the array and clearing the |
| 509 * array without ugly splicing and copying. |
| 510 * @typedef {{ |
| 511 * callback: array.<Function>= |
| 512 * }} |
| 513 */ |
| 514 var CallbackTracker; |
| 515 |
| 516 /** @type {CallbackTracker} */ |
| 517 var thenTracker = {callbacks: []}; |
| 518 /** @type {CallbackTracker} */ |
| 519 var catchTracker = {callbacks: []}; |
| 520 |
| 521 /** |
| 522 * Returns true if the specified value is callable. |
| 523 * @param {*} value Value to check. |
| 524 * @return {boolean} True if the value is a callable. |
| 525 */ |
| 526 function isCallable(value) { |
| 527 return typeof value === 'function'; |
| 528 } |
| 529 |
| 530 /** |
| 531 * Takes a tracker and clears its callbacks in a manner consistent with |
| 532 * the task manager. For the task manager, it also calls all callbacks |
| 533 * by no-oping them first and then calling them. |
| 534 * @param {CallbackTracker} tracker Tracker to clear. |
| 535 */ |
| 536 function clearTracker(tracker) { |
| 537 if (tracker.callbacks) { |
| 538 var callbacksToClear = tracker.callbacks; |
| 539 // No-ops all callbacks of this type. |
| 540 tracker.callbacks = undefined; |
| 541 // Do not wrap the promise then argument! |
| 542 // It will call wrapped callbacks. |
| 543 originalThen.call(Promise.resolve(), function() { |
| 544 for (var i = 0; i < callbacksToClear.length; i++) { |
| 545 callbacksToClear[i](); |
| 546 } |
| 547 }); |
| 548 } |
| 549 } |
| 550 |
| 551 /** |
| 552 * Takes the argument to a 'then' or 'catch' function and applies |
| 553 * a wrapping to callables consistent to ECMA promises. |
| 554 * @param {*} maybeCallback Argument to 'then' or 'catch'. |
| 555 * @param {CallbackTracker} sameTracker Tracker for the call type. |
| 556 * Example: If the argument is from a 'then' call, use thenTracker. |
| 557 * @param {CallbackTracker} otherTracker Tracker for the opposing call type. |
| 558 * Example: If the argument is from a 'then' call, use catchTracker. |
| 559 * @return {*} Consumable argument with necessary wrapping applied. |
| 560 */ |
| 561 function registerAndWrapMaybeCallback( |
| 562 maybeCallback, sameTracker, otherTracker) { |
| 563 // If sameTracker.callbacks is undefined, we've reached an ending state |
| 564 // that means this callback will never be called back. |
| 565 // We will still forward this call on to let the promise system |
| 566 // handle further processing, but since this promise is in an ending state |
| 567 // we can be confident it will never be called back. |
| 568 if (isCallable(maybeCallback) && sameTracker.callbacks) { |
| 569 var handler = wrapper.wrapCallback(function() { |
| 570 if (sameTracker.callbacks) { |
| 571 clearTracker(otherTracker); |
| 572 maybeCallback.apply(null, arguments); |
| 573 } |
| 574 }, false); |
| 575 sameTracker.callbacks.push(handler); |
| 576 return handler; |
| 577 } else { |
| 578 return maybeCallback; |
| 579 } |
| 580 } |
| 581 |
| 582 /** |
| 583 * Tracks then calls equivalent to Promise.prototype.then. |
| 584 * @param {*} onResolved Argument to use if the promise is resolved. |
| 585 * @param {*} onRejected Argument to use if the promise is rejected. |
| 586 * @return {object} Promise resulting from the 'then' call. |
| 587 */ |
| 588 function handleThen(onResolved, onRejected) { |
| 589 var resolutionHandler = |
| 590 registerAndWrapMaybeCallback(onResolved, thenTracker, catchTracker); |
| 591 var rejectionHandler = |
| 592 registerAndWrapMaybeCallback(onRejected, catchTracker, thenTracker); |
| 593 return originalThen.call(promise, resolutionHandler, rejectionHandler); |
| 594 } |
| 595 |
| 596 /** |
| 597 * Tracks then calls equivalent to Promise.prototype.catch. |
| 598 * @param {*} onRejected Argument to use if the promise is rejected. |
| 599 * @return {object} Promise resulting from the 'catch' call. |
| 600 */ |
| 601 function handleCatch(onRejected) { |
| 602 var rejectionHandler = |
| 603 registerAndWrapMaybeCallback(onRejected, catchTracker, thenTracker); |
| 604 return originalCatch.call(promise, rejectionHandler); |
| 605 } |
| 606 |
| 607 // Seeds this promise with at least one 'then' and 'catch' so we always |
| 608 // receive a callback to update the task manager on the state of callbacks. |
| 609 handleThen(function() {}); |
| 610 handleCatch(function() {}); |
| 611 |
| 612 return { |
| 613 handleThen: handleThen, |
| 614 handleCatch: handleCatch |
| 615 }; |
| 616 } |
| 617 } |
| 618 |
| 619 registerPromiseAdapter(); |
460 | 620 |
461 /** | 621 /** |
462 * Builds the object to manage tasks (mutually exclusive chains of events). | 622 * Builds the object to manage tasks (mutually exclusive chains of events). |
463 * @param {function(string, string): boolean} areConflicting Function that | 623 * @param {function(string, string): boolean} areConflicting Function that |
464 * checks if a new task can't be added to a task queue that contains an | 624 * checks if a new task can't be added to a task queue that contains an |
465 * existing task. | 625 * existing task. |
466 * @return {Object} Task manager interface. | 626 * @return {Object} Task manager interface. |
467 */ | 627 */ |
468 function buildTaskManager(areConflicting) { | 628 function buildTaskManager(areConflicting) { |
469 /** | 629 /** |
(...skipping 353 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
823 // One hour is just an arbitrary amount of time chosen. | 983 // One hour is just an arbitrary amount of time chosen. |
824 chrome.alarms.create(alarmName, {periodInMinutes: 60}); | 984 chrome.alarms.create(alarmName, {periodInMinutes: 60}); |
825 | 985 |
826 return { | 986 return { |
827 addListener: addListener, | 987 addListener: addListener, |
828 getAuthToken: getAuthToken, | 988 getAuthToken: getAuthToken, |
829 isSignedIn: isSignedIn, | 989 isSignedIn: isSignedIn, |
830 removeToken: removeToken | 990 removeToken: removeToken |
831 }; | 991 }; |
832 } | 992 } |
OLD | NEW |