Merge f-t to m-c, a=merge
authorPhil Ringnalda <philringnalda@gmail.com>
Mon, 01 Sep 2014 18:03:40 -0700
changeset 224462 96d70813ce16a9e05e4c0d3d5bb26a690ef21d5b
parent 224459 1367ee27ccabf600b8a25fab9382ff3e4d4193be (current diff)
parent 224461 a7819b6a9411e7e5a317d1df42058ba60a5fd03e (diff)
child 224557 3b1a82e632f18a385fe19543ab4005615d889c74
child 224566 f8f3a9b2f9ed0feedde6c98fda7d5f0fc04f375b
child 226185 08f24f225a2de87481bce535f94e499ad27d8aa9
push id3979
push userraliiev@mozilla.com
push dateMon, 13 Oct 2014 16:35:44 +0000
treeherdermozilla-beta@30f2cc610691 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone34.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
Merge f-t to m-c, a=merge
--- a/browser/base/content/browser-loop.js
+++ b/browser/base/content/browser-loop.js
@@ -8,16 +8,21 @@ let LoopUI;
 XPCOMUtils.defineLazyModuleGetter(this, "injectLoopAPI", "resource:///modules/loop/MozLoopAPI.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "MozLoopService", "resource:///modules/loop/MozLoopService.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "PanelFrame", "resource:///modules/PanelFrame.jsm");
 
 
 (function() {
 
   LoopUI = {
+    get toolbarButton() {
+      delete this.toolbarButton;
+      return this.toolbarButton = CustomizableUI.getWidget("loop-call-button").forWindow(window);
+    },
+
     /**
      * Opens the panel for Loop and sizes it appropriately.
      *
      * @param {event} event The event opening the panel, used to anchor
      *                      the panel to the button which triggers it.
      */
     openCallPanel: function(event) {
       let callback = iframe => {
@@ -30,18 +35,45 @@ XPCOMUtils.defineLazyModuleGetter(this, 
       PanelFrame.showPopup(window, event.target, "loop", null,
                            "about:looppanel", null, callback);
     },
 
     /**
      * Triggers the initialization of the loop service.  Called by
      * delayedStartup.
      */
-    initialize: function() {
+    init: function() {
       if (!Services.prefs.getBoolPref("loop.enabled")) {
-        CustomizableUI.getWidget("loop-call-button").forWindow(window).node.hidden = true;
+        this.toolbarButton.node.hidden = true;
         return;
       }
 
+      // Add observer notifications before the service is initialized
+      Services.obs.addObserver(this, "loop-status-changed", false);
+
+
       MozLoopService.initialize();
+      this.updateToolbarState();
+    },
+
+    uninit: function() {
+      Services.obs.removeObserver(this, "loop-status-changed");
+    },
+
+    // Implements nsIObserver
+    observe: function(subject, topic, data) {
+      if (topic != "loop-status-changed") {
+        return;
+      }
+      this.updateToolbarState();
+    },
+
+    updateToolbarState: function() {
+      let state = "";
+      if (MozLoopService.errors.size) {
+        state = "error";
+      } else if (MozLoopService.doNotDisturb) {
+        state = "disabled";
+      }
+      this.toolbarButton.node.setAttribute("state", state);
     },
   };
 })();
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -1208,17 +1208,17 @@ var gBrowserInit = {
     gSyncUI.init();
     gFxAccounts.init();
 #endif
 
 #ifdef MOZ_DATA_REPORTING
     gDataNotificationInfoBar.init();
 #endif
 
-    LoopUI.initialize();
+    LoopUI.init();
 
     gBrowserThumbnails.init();
 
     // Add Devtools menuitems and listeners
     gDevToolsBrowser.registerBrowserWindow(window);
 
     window.addEventListener("mousemove", MousePosTracker, false);
     window.addEventListener("dragover", MousePosTracker, false);
@@ -1379,16 +1379,17 @@ var gBrowserInit = {
       if (Win7Features)
         Win7Features.onCloseWindow();
 
       gPrefService.removeObserver(ctrlTab.prefName, ctrlTab);
       ctrlTab.uninit();
       TabView.uninit();
       SocialUI.uninit();
       gBrowserThumbnails.uninit();
+      LoopUI.uninit();
       FullZoom.destroy();
 
       Services.obs.removeObserver(gSessionHistoryObserver, "browser:purge-session-history");
       Services.obs.removeObserver(gXPInstallObserver, "addon-install-disabled");
       Services.obs.removeObserver(gXPInstallObserver, "addon-install-started");
       Services.obs.removeObserver(gXPInstallObserver, "addon-install-blocked");
       Services.obs.removeObserver(gXPInstallObserver, "addon-install-failed");
       Services.obs.removeObserver(gXPInstallObserver, "addon-install-complete");
--- a/browser/components/loop/MozLoopService.jsm
+++ b/browser/components/loop/MozLoopService.jsm
@@ -56,16 +56,17 @@ let gRegisteredDeferred = null;
 let gPushHandler = null;
 let gHawkClient = null;
 let gRegisteredLoopServer = false;
 let gLocalizedStrings =  null;
 let gInitializeTimer = null;
 let gFxAOAuthClientPromise = null;
 let gFxAOAuthClient = null;
 let gFxAOAuthTokenData = null;
+let gErrors = new Map();
 
 /**
  * Internal helper methods and state
  *
  * The registration is a two-part process. First we need to connect to
  * and register with the push server. Then we need to take the result of that
  * and register with the Loop server.
  */
@@ -130,16 +131,40 @@ let MozLoopServiceInternal = {
 
   /**
    * Sets MozLoopService "do not disturb" pref value.
    *
    * @param {Boolean} aFlag
    */
   set doNotDisturb(aFlag) {
     Services.prefs.setBoolPref("loop.do_not_disturb", Boolean(aFlag));
+    this.notifyStatusChanged();
+  },
+
+  notifyStatusChanged: function() {
+    Services.obs.notifyObservers(null, "loop-status-changed", null);
+  },
+
+  /**
+   * @param {String} errorType a key to identify the type of error. Only one
+   *                           error of a type will be saved at a time.
+   * @param {Object} error     an object describing the error in the format from Hawk errors
+   */
+  setError: function(errorType, error) {
+    gErrors.set(errorType, error);
+    this.notifyStatusChanged();
+  },
+
+  clearError: function(errorType) {
+    gErrors.delete(errorType);
+    this.notifyStatusChanged();
+  },
+
+  get errors() {
+    return gErrors;
   },
 
   /**
    * Starts registration of Loop with the push server, and then will register
    * with the Loop server. It will return early if already registered.
    *
    * @param {Object} mockPushHandler Optional, test-only mock push handler. Used
    *                                 to allow mocking of the MozLoopPushHandler.
@@ -252,16 +277,17 @@ let MozLoopServiceInternal = {
     this.hawkRequest("/registration", "POST", { simplePushURL: pushUrl})
       .then((response) => {
         // If this failed we got an invalid token. storeSessionToken rejects
         // the gRegisteredDeferred promise for us, so here we just need to
         // early return.
         if (!this.storeSessionToken(response.headers))
           return;
 
+        this.clearError("registration");
         gRegisteredDeferred.resolve();
         // No need to clear the promise here, everything was good, so we don't need
         // to re-register.
       }, (error) => {
         // There's other errors than invalid auth token, but we should only do the reset
         // as a last resort.
         if (error.code === 401 && error.errno === INVALID_AUTH_TOKEN) {
           if (this.urlExpiryTimeIsInFuture()) {
@@ -273,16 +299,17 @@ let MozLoopServiceInternal = {
           // Authorization failed, invalid token, we need to try again with a new token.
           Services.prefs.clearUserPref("loop.hawk-session-token");
           this.registerWithLoopServer(pushUrl, true);
           return;
         }
 
         // XXX Bubble the precise details up to the UI somehow (bug 1013248).
         Cu.reportError("Failed to register with the loop server. error: " + error);
+        this.setError("registration", error);
         gRegisteredDeferred.reject(error.errno);
         gRegisteredDeferred = null;
       }
     );
   },
 
   /**
    * Callback from MozLoopPushHandler - A push notification has been received from
@@ -690,16 +717,20 @@ this.MozLoopService = {
    * Sets MozLoopService "do not disturb" value.
    *
    * @param {Boolean} aFlag
    */
   set doNotDisturb(aFlag) {
     MozLoopServiceInternal.doNotDisturb = aFlag;
   },
 
+  get errors() {
+    return MozLoopServiceInternal.errors;
+  },
+
   /**
    * Returns the current locale
    *
    * @return {String} The code of the current locale.
    */
   get locale() {
     try {
       return Services.prefs.getComplexValue("general.useragent.locale",
--- a/browser/components/loop/test/mochitest/browser.ini
+++ b/browser/components/loop/test/mochitest/browser.ini
@@ -8,9 +8,10 @@ support-files =
 [browser_fxa_login.js]
 skip-if = !debug
 [browser_loop_fxa_server.js]
 [browser_LoopContacts.js]
 [browser_mozLoop_appVersionInfo.js]
 [browser_mozLoop_prefs.js]
 [browser_mozLoop_doNotDisturb.js]
 skip-if = buildapp == 'mulet'
+[browser_toolbarbutton.js]
 [browser_mozLoop_pluralStrings.js]
new file mode 100644
--- /dev/null
+++ b/browser/components/loop/test/mochitest/browser_toolbarbutton.js
@@ -0,0 +1,30 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Test the toolbar button states.
+ */
+
+"use strict";
+
+const MozLoopServiceInternal = Cu.import("resource:///modules/loop/MozLoopService.jsm", {}).
+                               MozLoopServiceInternal;
+
+registerCleanupFunction(function*() {
+  MozLoopService.doNotDisturb = false;
+  yield MozLoopServiceInternal.clearError("testing");
+});
+
+add_task(function* test_doNotDisturb() {
+  yield MozLoopService.doNotDisturb = true;
+  Assert.strictEqual(LoopUI.toolbarButton.node.getAttribute("state"), "disabled", "Check button is in disabled state");
+  yield MozLoopService.doNotDisturb = false;
+  Assert.notStrictEqual(LoopUI.toolbarButton.node.getAttribute("state"), "disabled", "Check button is not in disabled state");
+});
+
+add_task(function* test_error() {
+  yield MozLoopServiceInternal.setError("testing", {});
+  Assert.strictEqual(LoopUI.toolbarButton.node.getAttribute("state"), "error", "Check button is in error state");
+  yield MozLoopServiceInternal.clearError("testing");
+  Assert.notStrictEqual(LoopUI.toolbarButton.node.getAttribute("state"), "error", "Check button is not in error state");
+});
--- a/browser/locales/en-US/chrome/browser/loop/loop.properties
+++ b/browser/locales/en-US/chrome/browser/loop/loop.properties
@@ -27,21 +27,30 @@ invitee_expire_days_label=Invitation wil
 ## In this item, don't translate the part between {{..}}
 invitee_expire_hours_label=Invitation will expire in {{expiry_time}} hour;Invitation will expire in {{expiry_time}} hours
 
 # Status text
 display_name_guest=Guest
 display_name_dnd_status=Do Not Disturb
 display_name_available_status=Available
 
+# Error bars
+## LOCALIZATION NOTE(unable_retrieve_url,session_expired_error_description,could_not_authenticate,password_changed_question,try_again_later,could_not_connect,check_internet_connection,login_expired,service_not_available,problem_accessing_account):
+## These may be displayed at the top of the panel here:
+## https://people.mozilla.org/~dhenein/labs/loop-mvp-spec/#error
 unable_retrieve_url=Sorry, we were unable to retrieve a call url.
-## LOCALIZATION NOTE(session_expired_error_description): This may be displayed
-## in a rare error instance at the top of the panel here:
-## https://people.mozilla.org/~dhenein/labs/loop-mvp-spec/#error
 session_expired_error_description=Session expired. All URLs you have previously created and shared will no longer work.
+could_not_authenticate=Could Not Authenticate
+password_changed_question=Did you change your password?
+try_again_later=Please try again later
+could_not_connect=Could Not Connect To The Server
+check_internet_connection=Please check your internet connection
+login_expired=Your Login Has Expired
+service_not_available=Service Unavailable At This Time
+problem_accessing_account=There Was A Problem Accessing Your Account
 
 ## LOCALIZATION NOTE(retry_button): Displayed when there is an error to retry
 ## the appropriate action.
 ## See https://people.mozilla.org/~dhenein/labs/loop-mvp-spec/#error for location
 retry_button=Retry
 
 share_email_subject3=You have been invited to a conversation
 ## LOCALIZATION NOTE (share_email_body3): In this item, don't translate the
--- a/browser/themes/osx/browser.css
+++ b/browser/themes/osx/browser.css
@@ -1317,16 +1317,17 @@ toolbar .toolbarbutton-1 > .toolbarbutto
     list-style-image: url("chrome://browser/skin/loop/toolbar@2x.png");
     -moz-image-region: rect(0, 36px, 36px, 0);
   }
 
   toolbar[brighttext] #loop-call-button > .toolbarbutton-badge-container {
     list-style-image: url("chrome://browser/skin/loop/toolbar-inverted@2x.png");
   }
 
+  #loop-call-button[state="disabled"] > .toolbarbutton-badge-container,
   #loop-call-button[disabled="true"] > .toolbarbutton-badge-container {
     -moz-image-region: rect(0, 72px, 36px, 36px);
   }
 
   #loop-call-button:not([disabled="true"])[state="error"] > .toolbarbutton-badge-container {
     -moz-image-region: rect(0, 108px, 36px, 72px);
   }
 
@@ -1352,16 +1353,17 @@ toolbar .toolbarbutton-1 > .toolbarbutto
     -moz-image-region: rect(0, 64px, 64px, 0);
   }
 
   /* Make sure that the state icons are not shown in the customization palette. */
   toolbarpaletteitem[place="palette"] > #loop-call-button > .toolbarbutton-badge-container {
     -moz-image-region: rect(0, 64px, 64px, 0) !important;
   }
 
+  #loop-call-button[cui-areatype="menu-panel"][state="disabled"] > .toolbarbutton-badge-container,
   #loop-call-button[cui-areatype="menu-panel"][disabled="true"] > .toolbarbutton-badge-container {
     -moz-image-region: rect(0, 128px, 64px, 64px);
   }
 
   #loop-call-button[cui-areatype="menu-panel"]:not([disabled="true"])[state="error"] > .toolbarbutton-badge-container {
     -moz-image-region: rect(0, 192px, 64px, 128px);
   }
 
--- a/browser/themes/shared/menupanel.inc.css
+++ b/browser/themes/shared/menupanel.inc.css
@@ -156,16 +156,17 @@ toolbarpaletteitem[place="palette"] > #l
   -moz-image-region: rect(0, 32px, 32px, 0);
 }
 
 /* Make sure that the state icons are not shown in the customization palette. */
 toolbarpaletteitem[place="palette"] > #loop-call-button > .toolbarbutton-badge-container {
   -moz-image-region: rect(0, 32px, 32px, 0) !important;
 }
 
+#loop-call-button[cui-areatype="menu-panel"][state="disabled"] > .toolbarbutton-badge-container,
 #loop-call-button[cui-areatype="menu-panel"][disabled="true"] > .toolbarbutton-badge-container {
   -moz-image-region: rect(0, 64px, 32px, 32px);
 }
 
 #loop-call-button[cui-areatype="menu-panel"]:not([disabled="true"])[state="error"] > .toolbarbutton-badge-container {
   -moz-image-region: rect(0, 96px, 32px, 64px);
 }
 
--- a/browser/themes/shared/toolbarbuttons.inc.css
+++ b/browser/themes/shared/toolbarbuttons.inc.css
@@ -172,16 +172,17 @@ toolbar[brighttext] #sync-button[status=
   list-style-image: url(chrome://browser/skin/loop/toolbar.png);
   -moz-image-region: rect(0, 18px, 18px, 0);
 }
 
 toolbar[brighttext] #loop-call-button > .toolbarbutton-badge-container {
   list-style-image: url(chrome://browser/skin/loop/toolbar-inverted.png);
 }
 
+#loop-call-button[state="disabled"] > .toolbarbutton-badge-container,
 #loop-call-button[disabled="true"] > .toolbarbutton-badge-container {
   -moz-image-region: rect(0, 36px, 18px, 18px);
 }
 
 #loop-call-button:not([disabled="true"])[state="error"] > .toolbarbutton-badge-container {
   -moz-image-region: rect(0, 54px, 18px, 36px);
 }