Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(324)

Side by Side Diff: chrome/browser/resources/google_now/utility.js

Issue 162273002: Convert Google Now's Authentication Manager to use Promises (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: Merging in https://codereview.chromium.org/174053003/ Created 6 years, 10 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
« no previous file with comments | « chrome/browser/resources/google_now/common_test_util.js ('k') | no next file » | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
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
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 Promises. 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 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 }(); 455
456 Promise.prototype.then = function(onResolved, onRejected) {
457 instrumentPromise(this);
458 return this.__tracker.handleThen(onResolved, onRejected);
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();
449 620
450 /** 621 /**
451 * Builds the object to manage tasks (mutually exclusive chains of events). 622 * Builds the object to manage tasks (mutually exclusive chains of events).
452 * @param {function(string, string): boolean} areConflicting Function that 623 * @param {function(string, string): boolean} areConflicting Function that
453 * 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
454 * existing task. 625 * existing task.
455 * @return {Object} Task manager interface. 626 * @return {Object} Task manager interface.
456 */ 627 */
457 function buildTaskManager(areConflicting) { 628 function buildTaskManager(areConflicting) {
458 /** 629 /**
(...skipping 260 matching lines...) Expand 10 before | Expand all | Expand 10 after
719 /** 890 /**
720 * Wraps chrome.identity to provide limited listening support for 891 * Wraps chrome.identity to provide limited listening support for
721 * the sign in state by polling periodically for the auth token. 892 * the sign in state by polling periodically for the auth token.
722 * @return {Object} The Authentication Manager interface. 893 * @return {Object} The Authentication Manager interface.
723 */ 894 */
724 function buildAuthenticationManager() { 895 function buildAuthenticationManager() {
725 var alarmName = 'sign-in-alarm'; 896 var alarmName = 'sign-in-alarm';
726 897
727 /** 898 /**
728 * Gets an OAuth2 access token. 899 * Gets an OAuth2 access token.
729 * @param {function(string=)} callback Called on completion. 900 * @return {Promise} A promise to get the authentication token. If there is
730 * The string contains the token. It's undefined if there was an error. 901 * no token, the request is rejected.
731 */ 902 */
732 function getAuthToken(callback) { 903 function getAuthToken() {
733 instrumented.identity.getAuthToken({interactive: false}, function(token) { 904 return new Promise(function(resolve, reject) {
734 token = chrome.runtime.lastError ? undefined : token; 905 instrumented.identity.getAuthToken({interactive: false}, function(token) {
735 callback(token); 906 if (chrome.runtime.lastError || !token) {
907 reject();
908 } else {
909 resolve(token);
910 }
911 });
736 }); 912 });
737 } 913 }
738 914
739 /** 915 /**
740 * Determines whether there is an account attached to the profile. 916 * Determines whether there is an account attached to the profile.
741 * @param {function(boolean)} callback Called on completion. 917 * @return {Promise} A promise to determine if there is an account attached
918 * to the profile.
742 */ 919 */
743 function isSignedIn(callback) { 920 function isSignedIn() {
744 instrumented.webstorePrivate.getBrowserLogin(function(accountInfo) { 921 return new Promise(function(resolve) {
745 callback(!!accountInfo.login); 922 instrumented.webstorePrivate.getBrowserLogin(function(accountInfo) {
923 resolve(!!accountInfo.login);
924 });
746 }); 925 });
747 } 926 }
748 927
749 /** 928 /**
750 * Removes the specified cached token. 929 * Removes the specified cached token.
751 * @param {string} token Authentication Token to remove from the cache. 930 * @param {string} token Authentication Token to remove from the cache.
752 * @param {function()} callback Called on completion. 931 * @return {Promise} A promise that resolves on completion.
753 */ 932 */
754 function removeToken(token, callback) { 933 function removeToken(token) {
755 instrumented.identity.removeCachedAuthToken({token: token}, function() { 934 return new Promise(function(resolve) {
756 // Let Chrome now about a possible problem with the token. 935 instrumented.identity.removeCachedAuthToken({token: token}, function() {
757 getAuthToken(function() {}); 936 // Let Chrome know about a possible problem with the token.
758 callback(); 937 getAuthToken();
938 resolve();
939 });
759 }); 940 });
760 } 941 }
761 942
762 var listeners = []; 943 var listeners = [];
763 944
764 /** 945 /**
765 * Registers a listener that gets called back when the signed in state 946 * Registers a listener that gets called back when the signed in state
766 * is found to be changed. 947 * is found to be changed.
767 * @param {function()} callback Called when the answer to isSignedIn changes. 948 * @param {function()} callback Called when the answer to isSignedIn changes.
768 */ 949 */
769 function addListener(callback) { 950 function addListener(callback) {
770 listeners.push(callback); 951 listeners.push(callback);
771 } 952 }
772 953
773 /** 954 /**
774 * Checks if the last signed in state matches the current one. 955 * Checks if the last signed in state matches the current one.
775 * If it doesn't, it notifies the listeners of the change. 956 * If it doesn't, it notifies the listeners of the change.
776 */ 957 */
777 function checkAndNotifyListeners() { 958 function checkAndNotifyListeners() {
778 isSignedIn(function(signedIn) { 959 isSignedIn().then(function(signedIn) {
779 instrumented.storage.local.get('lastSignedInState', function(items) { 960 instrumented.storage.local.get('lastSignedInState', function(items) {
780 items = items || {}; 961 items = items || {};
781 if (items.lastSignedInState != signedIn) { 962 if (items.lastSignedInState != signedIn) {
782 chrome.storage.local.set( 963 chrome.storage.local.set(
783 {lastSignedInState: signedIn}); 964 {lastSignedInState: signedIn});
784 listeners.forEach(function(callback) { 965 listeners.forEach(function(callback) {
785 callback(); 966 callback();
786 }); 967 });
787 } 968 }
788 }); 969 });
(...skipping 13 matching lines...) Expand all
802 // One hour is just an arbitrary amount of time chosen. 983 // One hour is just an arbitrary amount of time chosen.
803 chrome.alarms.create(alarmName, {periodInMinutes: 60}); 984 chrome.alarms.create(alarmName, {periodInMinutes: 60});
804 985
805 return { 986 return {
806 addListener: addListener, 987 addListener: addListener,
807 getAuthToken: getAuthToken, 988 getAuthToken: getAuthToken,
808 isSignedIn: isSignedIn, 989 isSignedIn: isSignedIn,
809 removeToken: removeToken 990 removeToken: removeToken
810 }; 991 };
811 } 992 }
OLDNEW
« no previous file with comments | « chrome/browser/resources/google_now/common_test_util.js ('k') | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698