Bug 1098629 - Support the retry button on the error bar in more cases. r=pkerr
authorMatthew Noorenberghe <mozilla@noorenberghe.ca>
Tue, 20 Jan 2015 16:12:30 -0800
changeset 251910 01cc2ba4cb590ec07948469ba2e45813ea0f1438
parent 251799 9a389eb9609fcc133a7c9a41a755afaadf02783c
child 251911 83fa0bab1486dc615d514bfe75e227d680be25da
push id4610
push userjlund@mozilla.com
push dateMon, 30 Mar 2015 18:32:55 +0000
treeherdermozilla-beta@4df54044d9ef [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerspkerr
bugs1098629
milestone38.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1098629 - Support the retry button on the error bar in more cases. r=pkerr
browser/components/loop/MozLoopService.jsm
browser/components/loop/test/mochitest/browser_fxa_login.js
browser/components/loop/test/xpcshell/test_loopservice_hawk_errors.js
browser/components/loop/test/xpcshell/test_loopservice_restart.js
testing/profiles/prefs_general.js
--- a/browser/components/loop/MozLoopService.jsm
+++ b/browser/components/loop/MozLoopService.jsm
@@ -255,16 +255,17 @@ let MozLoopServiceInternal = {
    *                           error of a type will be saved at a time. This value may be used to
    *                           determine user-facing (aka. friendly) strings.
    * @param {Object} error     an object describing the error in the format from Hawk errors
    * @param {Function} [actionCallback] an object describing the label and callback function for error
    *                                    bar's button e.g. to retry.
    */
   setError: function(errorType, error, actionCallback = null) {
     log.debug("setError", errorType, error);
+    log.trace();
     let messageString, detailsString, detailsButtonLabelString, detailsButtonCallback;
     const NETWORK_ERRORS = [
       Cr.NS_ERROR_CONNECTION_REFUSED,
       Cr.NS_ERROR_NET_INTERRUPT,
       Cr.NS_ERROR_NET_RESET,
       Cr.NS_ERROR_NET_TIMEOUT,
       Cr.NS_ERROR_OFFLINE,
       Cr.NS_ERROR_PROXY_CONNECTION_REFUSED,
@@ -295,25 +296,34 @@ let MozLoopServiceInternal = {
       messageString = "service_not_available";
       detailsString = "try_again_later";
       detailsButtonLabelString = "retry_button";
     } else {
       messageString = "generic_failure_title";
     }
 
     error.friendlyMessage = this.localizedStrings.get(messageString);
-    error.friendlyDetails = detailsString ?
-                              this.localizedStrings.get(detailsString) :
-                              null;
+
+    // Default to the generic "retry_button" text even though the button won't be shown if
+    // error.friendlyDetails is null.
     error.friendlyDetailsButtonLabel = detailsButtonLabelString ?
                                          this.localizedStrings.get(detailsButtonLabelString) :
-                                         null;
+                                         this.localizedStrings.get("retry_button");
 
     error.friendlyDetailsButtonCallback = actionCallback || detailsButtonCallback || null;
 
+    if (detailsString) {
+      error.friendlyDetails = this.localizedStrings.get(detailsString);
+    } else if (error.friendlyDetailsButtonCallback) {
+      // If we have a retry callback but no details use the generic try again string.
+      error.friendlyDetails = this.localizedStrings.get("generic_failure_no_reason2");
+    } else {
+      error.friendlyDetails = null;
+    }
+
     gErrors.set(errorType, error);
     this.notifyStatusChanged();
   },
 
   clearError: function(errorType) {
     if (gErrors.has(errorType)) {
       gErrors.delete(errorType);
       this.notifyStatusChanged();
@@ -1214,17 +1224,18 @@ this.MozLoopService = {
       return;
     }
 
     log.debug("MozLoopService: Initializing with already logged-in account");
     MozLoopServiceInternal.promiseRegisteredWithServers(LOOP_SESSION_TYPE.FXA).then(() => {
       deferredInitialization.resolve("initialized to logged-in status");
     }, error => {
       log.debug("MozLoopService: error logging in using cached auth token");
-      MozLoopServiceInternal.setError("login", error);
+      let retryFunc = () => MozLoopServiceInternal.promiseRegisteredWithServers(LOOP_SESSION_TYPE.FXA);
+      MozLoopServiceInternal.setError("login", error, retryFunc);
       deferredInitialization.reject("error logging in using cached auth token");
     });
     yield completedPromise;
   }),
 
   /**
    * Opens the chat window
    *
@@ -1416,37 +1427,27 @@ this.MozLoopService = {
       MozLoopServiceInternal.fxAOAuthTokenData = tokenData;
       return tokenData;
     }).then(tokenData => {
       return MozLoopServiceInternal.promiseRegisteredWithServers(LOOP_SESSION_TYPE.FXA).then(() => {
         MozLoopServiceInternal.clearError("login");
         MozLoopServiceInternal.clearError("profile");
         return MozLoopServiceInternal.fxAOAuthTokenData;
       });
-    }).then(tokenData => {
-      let client = new FxAccountsProfileClient({
-        serverURL: gFxAOAuthClient.parameters.profile_uri,
-        token: tokenData.access_token
-      });
-      client.fetchProfile().then(result => {
-        MozLoopServiceInternal.fxAOAuthProfile = result;
-      }, error => {
-        log.error("Failed to retrieve profile", error);
-        this.setError("profile", error);
-        MozLoopServiceInternal.fxAOAuthProfile = null;
-        MozLoopServiceInternal.notifyStatusChanged();
-      });
+    }).then(Task.async(function* fetchProfile(tokenData) {
+      yield MozLoopService.fetchFxAProfile(tokenData);
       return tokenData;
-    }).catch(error => {
+    })).catch(error => {
       MozLoopServiceInternal.fxAOAuthTokenData = null;
       MozLoopServiceInternal.fxAOAuthProfile = null;
       MozLoopServiceInternal.deferredRegistrations.delete(LOOP_SESSION_TYPE.FXA);
       throw error;
     }).catch((error) => {
-      MozLoopServiceInternal.setError("login", error);
+      MozLoopServiceInternal.setError("login", error,
+                                      () => MozLoopService.logInToFxA());
       // Re-throw for testing
       throw error;
     });
   },
 
   /**
    * Logs the user out from FxA.
    *
@@ -1480,16 +1481,40 @@ this.MozLoopService = {
       // clearError calls notifyStatusChanged so should be done last when the
       // state is clean.
       MozLoopServiceInternal.clearError("registration");
       MozLoopServiceInternal.clearError("login");
       MozLoopServiceInternal.clearError("profile");
     }
   }),
 
+  /**
+   * Fetch/update the FxA Profile for the logged in user.
+   *
+   * @return {Promise} resolving if the profile information was succesfully retrieved
+   *                   rejecting if the profile information couldn't be retrieved.
+   *                   A profile error is registered.
+   **/
+  fetchFxAProfile: function() {
+    log.debug("fetchFxAProfile");
+    let client = new FxAccountsProfileClient({
+      serverURL: gFxAOAuthClient.parameters.profile_uri,
+      token: MozLoopServiceInternal.fxAOAuthTokenData.access_token
+    });
+    return client.fetchProfile().then(result => {
+      MozLoopServiceInternal.fxAOAuthProfile = result;
+      MozLoopServiceInternal.clearError("profile");
+    }, error => {
+      log.error("Failed to retrieve profile", error, this.fetchFxAProfile.bind(this));
+      MozLoopServiceInternal.setError("profile", error);
+      MozLoopServiceInternal.fxAOAuthProfile = null;
+      MozLoopServiceInternal.notifyStatusChanged();
+    });
+  },
+
   openFxASettings: Task.async(function() {
     try {
       let fxAOAuthClient = yield MozLoopServiceInternal.promiseFxAOAuthClient();
       if (!fxAOAuthClient) {
         log.error("Could not get the OAuth client");
         return;
       }
       let url = new URL("/settings", fxAOAuthClient.parameters.content_uri);
--- a/browser/components/loop/test/mochitest/browser_fxa_login.js
+++ b/browser/components/loop/test/mochitest/browser_fxa_login.js
@@ -291,18 +291,19 @@ add_task(function* basicAuthorizationAnd
   let loopDoc = document.getElementById("loop-panel-iframe").contentDocument;
   let visibleEmail = loopDoc.getElementsByClassName("user-identity")[0];
   is(visibleEmail.textContent, "Guest", "Guest should be displayed on the panel when not logged in");
   is(MozLoopService.userProfile, null, "profile should be null before log-in");
   let loopButton = document.getElementById("loop-button");
   is(loopButton.getAttribute("state"), "", "state of loop button should be empty when not logged in");
 
   info("Login");
+  statusChangedPromise = promiseObserverNotified("loop-status-changed", "login");
   let tokenData = yield MozLoopService.logInToFxA();
-  yield promiseObserverNotified("loop-status-changed", "login");
+  yield statusChangedPromise;
   ise(tokenData.access_token, "code1_access_token", "Check access_token");
   ise(tokenData.scope, "profile", "Check scope");
   ise(tokenData.token_type, "bearer", "Check token_type");
 
   is(MozLoopService.userProfile.email, "test@example.com", "email should exist in the profile data");
   is(MozLoopService.userProfile.uid, "1234abcd", "uid should exist in the profile data");
   is(visibleEmail.textContent, "test@example.com", "the email should be correct on the panel");
   is(loopButton.getAttribute("state"), "active", "state of loop button should be active when logged in");
--- a/browser/components/loop/test/xpcshell/test_loopservice_hawk_errors.js
+++ b/browser/components/loop/test/xpcshell/test_loopservice_hawk_errors.js
@@ -69,17 +69,16 @@ add_task(function* guest_401() {
                          "FxA session token should NOT have been cleared");
 
       Assert.strictEqual(MozLoopService.errors.size, 1, "Should be one error");
 
       let err = MozLoopService.errors.get("registration");
       Assert.strictEqual(err.code, 401);
       Assert.strictEqual(err.friendlyMessage, getLoopString("session_expired_error_description"));
       Assert.equal(err.friendlyDetails, null);
-      Assert.equal(err.friendlyDetailsButtonLabel, null);
   });
 });
 
 add_task(cleanup_between_tests);
 
 add_task(function* fxa_401() {
   Services.prefs.setCharPref("loop.hawk-session-token", "guest");
   Services.prefs.setCharPref("loop.hawk-session-token.fxa", "fxa");
@@ -110,17 +109,16 @@ add_task(function* error_404() {
     (error) => {
       MozLoopServiceInternal.setError("testing", error);
       Assert.strictEqual(MozLoopService.errors.size, 1, "Should be one error");
 
       let err = MozLoopService.errors.get("testing");
       Assert.strictEqual(err.code, 404);
       Assert.strictEqual(err.friendlyMessage, getLoopString("generic_failure_title"));
       Assert.equal(err.friendlyDetails, null);
-      Assert.equal(err.friendlyDetailsButtonLabel, null);
   });
 });
 
 add_task(cleanup_between_tests);
 
 add_task(function* error_500() {
   yield MozLoopServiceInternal.hawkRequestInternal(LOOP_SESSION_TYPE.GUEST, "/500", "GET").then(
     () => Assert.ok(false, "Should have rejected"),
@@ -144,17 +142,16 @@ add_task(function* profile_500() {
     (error) => {
       MozLoopServiceInternal.setError("profile", error);
       Assert.strictEqual(MozLoopService.errors.size, 1, "Should be one error");
 
       let err = MozLoopService.errors.get("profile");
       Assert.strictEqual(err.code, 500);
       Assert.strictEqual(err.friendlyMessage, getLoopString("problem_accessing_account"));
       Assert.equal(err.friendlyDetails, null);
-      Assert.equal(err.friendlyDetailsButtonLabel, null);
   });
 });
 
 add_task(cleanup_between_tests);
 
 add_task(function* error_503() {
   yield MozLoopServiceInternal.hawkRequestInternal(LOOP_SESSION_TYPE.GUEST, "/503", "GET").then(
     () => Assert.ok(false, "Should have rejected"),
--- a/browser/components/loop/test/xpcshell/test_loopservice_restart.js
+++ b/browser/components/loop/test/xpcshell/test_loopservice_restart.js
@@ -72,16 +72,19 @@ add_task(function test_initialize_with_i
   (error) => {
     Assert.equal(MozLoopServiceInternal.pushHandler.registrationPushURL, kEndPointUrl, "Push URL should match");
     Assert.equal(Services.prefs.getCharPref(LOOP_FXA_TOKEN_PREF), "",
                  "FXA pref should be cleared if token was invalid");
     Assert.equal(Services.prefs.getCharPref(LOOP_FXA_PROFILE_PREF), "",
                  "FXA profile pref should be cleared if token was invalid");
     Assert.ok(MozLoopServiceInternal.errors.has("login"),
               "Initialization error should have been reported to UI");
+    Assert.ok(MozLoopServiceInternal.errors.has("login"));
+    Assert.ok(MozLoopServiceInternal.errors.get("login").friendlyDetailsButtonCallback,
+              "Check that there is a retry callback");
   });
 });
 
 add_task(function test_initialize_with_fxa_token() {
   Services.prefs.setCharPref(LOOP_FXA_PROFILE_PREF, FAKE_FXA_PROFILE);
   Services.prefs.setCharPref(LOOP_FXA_TOKEN_PREF, FAKE_FXA_TOKEN_DATA);
 
   MozLoopService.errors.clear();
--- a/testing/profiles/prefs_general.js
+++ b/testing/profiles/prefs_general.js
@@ -257,16 +257,17 @@ user_pref("dom.mozApps.debug", true);
 // Enable apps customizations
 user_pref("dom.apps.customization.enabled", true);
 
 // Don't fetch or send directory tiles data from real servers
 user_pref("browser.newtabpage.directory.source", 'data:application/json,{"testing":1}');
 user_pref("browser.newtabpage.directory.ping", "");
 
 // Enable Loop
+user_pref("loop.debug.loglevel", "All");
 user_pref("loop.enabled", true);
 user_pref("loop.throttled", false);
 user_pref("loop.oauth.google.URL", "http://%(server)s/browser/browser/components/loop/test/mochitest/google_service.sjs?action=");
 user_pref("loop.oauth.google.getContactsURL", "http://%(server)s/browser/browser/components/loop/test/mochitest/google_service.sjs?action=contacts");
 user_pref("loop.oauth.google.getGroupsURL", "http://%(server)s/browser/browser/components/loop/test/mochitest/google_service.sjs?action=groups");
 user_pref("loop.server", "http://%(server)s/browser/browser/components/loop/test/mochitest/loop_fxa.sjs?");
 user_pref("loop.CSP","default-src 'self' about: file: chrome: data: wss://* http://* https://*");