Bug 1084097: make sure that the Loop button only shows up in the palette when unthrottled. r=Unfocused a=lmandel
authorMike de Boer <mdeboer@mozilla.com>
Sun, 19 Oct 2014 11:27:33 +0200
changeset 225740 5840764c4312
parent 225739 8c42ccaf8aa1
child 225741 9e420243b962
push id3996
push userrjesup@wgate.com
push date2014-10-20 01:16 +0000
treeherdermozilla-beta@5840764c4312 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersUnfocused, lmandel
bugs1084097
milestone34.0
Bug 1084097: make sure that the Loop button only shows up in the palette when unthrottled. r=Unfocused a=lmandel
browser/base/content/browser-loop.js
browser/components/customizableui/CustomizableUI.jsm
browser/components/customizableui/CustomizableWidgets.jsm
browser/components/customizableui/CustomizeMode.jsm
browser/components/loop/MozLoopService.jsm
browser/components/loop/test/functional/test_1_browser_call.py
browser/components/loop/test/mochitest/browser_fxa_login.js
browser/components/loop/test/mochitest/browser_mozLoop_softStart.js
browser/components/loop/test/mochitest/head.js
browser/modules/UITour.jsm
browser/themes/linux/browser.css
browser/themes/osx/browser.css
browser/themes/shared/menupanel.inc.css
browser/themes/shared/toolbarbuttons.inc.css
browser/themes/windows/browser.css
--- a/browser/base/content/browser-loop.js
+++ b/browser/base/content/browser-loop.js
@@ -10,17 +10,17 @@ XPCOMUtils.defineLazyModuleGetter(this, 
 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);
+      return this.toolbarButton = CustomizableUI.getWidget("loop-button-throttled").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.
      */
@@ -39,36 +39,19 @@ XPCOMUtils.defineLazyModuleGetter(this, 
                            "about:looppanel", null, callback);
     },
 
     /**
      * Triggers the initialization of the loop service.  Called by
      * delayedStartup.
      */
     init: function() {
-      let toolbarButton = this.toolbarButton;
-      if (!Services.prefs.getBoolPref("loop.enabled")) {
-        if (toolbarButton && toolbarButton.node) {
-          toolbarButton.node.hidden = true;
-        }
-        return;
-      }
-
       // Add observer notifications before the service is initialized
       Services.obs.addObserver(this, "loop-status-changed", false);
 
-      // If we're throttled, check to see if it's our turn to be unthrottled
-      if (Services.prefs.getBoolPref("loop.throttled")) {
-        if (toolbarButton && toolbarButton.node) {
-          toolbarButton.node.hidden = true;
-        }
-        MozLoopService.checkSoftStart(toolbarButton && toolbarButton.node);
-        return;
-      }
-
       MozLoopService.initialize();
       this.updateToolbarState();
     },
 
     uninit: function() {
       Services.obs.removeObserver(this, "loop-status-changed");
     },
 
--- a/browser/components/customizableui/CustomizableUI.jsm
+++ b/browser/components/customizableui/CustomizableUI.jsm
@@ -47,17 +47,17 @@ const kSubviewEvents = [
   "ViewShowing",
   "ViewHiding"
 ];
 
 /**
  * The current version. We can use this to auto-add new default widgets as necessary.
  * (would be const but isn't because of testing purposes)
  */
-let kVersion = 1;
+let kVersion = 2;
 
 /**
  * gPalette is a map of every widget that CustomizableUI.jsm knows about, keyed
  * on their IDs.
  */
 let gPalette = new Map();
 
 /**
@@ -289,16 +289,21 @@ let CustomizableUIInternal = {
         let futurePlacements = gFuturePlacements.get(widget.defaultArea);
         if (futurePlacements) {
           futurePlacements.add(id);
         } else {
           gFuturePlacements.set(widget.defaultArea, new Set([id]));
         }
       }
     }
+
+    if (currentVersion < 2) {
+      // Nuke the old 'loop-call-button' out of orbit.
+      CustomizableUI.removeWidgetFromArea("loop-call-button");
+    }
   },
 
   wrapWidget: function(aWidgetId) {
     if (gGroupWrapperCache.has(aWidgetId)) {
       return gGroupWrapperCache.get(aWidgetId);
     }
 
     let provider = this.getWidgetProvider(aWidgetId);
--- a/browser/components/customizableui/CustomizableWidgets.jsm
+++ b/browser/components/customizableui/CustomizableWidgets.jsm
@@ -898,38 +898,59 @@ const CustomizableWidgets = [{
     id: "email-link-button",
     tooltiptext: "email-link-button.tooltiptext3",
     onCommand: function(aEvent) {
       let win = aEvent.view;
       win.MailIntegration.sendLinkForWindow(win.content);
     }
   }];
 
-if (Services.prefs.getBoolPref("loop.enabled") && !Services.prefs.getBoolPref("loop.throttled")) {
-  CustomizableWidgets.push({
-    id: "loop-call-button",
-    type: "custom",
-    label: "Hello",
-    tooltiptext: "loop-call-button.tooltiptext",
-    onBuild: function(aDocument) {
-      let node = aDocument.createElementNS(kNSXUL, "toolbarbutton");
-      node.setAttribute("id", this.id);
-      node.classList.add("toolbarbutton-1");
-      node.classList.add("chromeclass-toolbar-additional");
-      node.setAttribute("type", "badged");
-      node.setAttribute("label", CustomizableUI.getLocalizedProperty(this, "label"));
-      node.setAttribute("tooltiptext", CustomizableUI.getLocalizedProperty(this, "tooltiptext"));
-      node.setAttribute("removable", "true");
-      node.addEventListener("command", function(event) {
-        aDocument.defaultView.LoopUI.openCallPanel(event);
-      });
-      return node;
+CustomizableWidgets.push({
+  id: "loop-button-throttled",
+  type: "custom",
+  label: "Hello",
+  tooltiptext: "loop-call-button.tooltiptext",
+  onBuild: function(aDocument) {
+    // If we're not supposed to see the button, return zip.
+    if (!Services.prefs.getBoolPref("loop.enabled")) {
+      return null;
     }
-  });
-}
+
+    let node = aDocument.createElementNS(kNSXUL, "toolbarbutton");
+    node.setAttribute("id", this.id);
+    node.classList.add("toolbarbutton-1");
+    node.classList.add("chromeclass-toolbar-additional");
+    node.setAttribute("type", "badged");
+    node.setAttribute("label", CustomizableUI.getLocalizedProperty(this, "label"));
+    node.setAttribute("tooltiptext", CustomizableUI.getLocalizedProperty(this, "tooltiptext"));
+    node.setAttribute("removable", "true");
+    node.addEventListener("command", function(event) {
+      aDocument.defaultView.LoopUI.openCallPanel(event);
+    });
+
+    // If we're throttled, check to see if it's our turn to be unthrottled
+    if (Services.prefs.getBoolPref("loop.throttled")) {
+      // If we're throttled, hide the button.
+      node.setAttribute("hidden", true);
+      aDocument.defaultView.MozLoopService.checkSoftStart(() => {
+        // If the check unthrottled us, reveal the button.
+        if (!Services.prefs.getBoolPref("loop.throttled")) {
+          node.removeAttribute("hidden");
+          // If we're in CustomizationMode and the transition has already finished,
+          // re-populate the palette to make the Loop button appear.
+          if (aDocument.documentElement.hasAttribute("customize-entered")) {
+            aDocument.defaultView.gCustomizeMode.repopulatePalette();
+          }
+        }
+       });
+    }
+
+    return node;
+  }
+});
 
 #ifdef XP_WIN
 #ifdef MOZ_METRO
 if (Services.metro && Services.metro.supported) {
   let widgetArgs = {tooltiptext: "switch-to-metro-button2.tooltiptext"};
   let brandShortName = BrandBundle.GetStringFromName("brandShortName");
   let metroTooltip = CustomizableUI.getLocalizedProperty(widgetArgs, "tooltiptext",
                                                          [brandShortName]);
--- a/browser/components/customizableui/CustomizeMode.jsm
+++ b/browser/components/customizableui/CustomizeMode.jsm
@@ -726,16 +726,19 @@ CustomizeMode.prototype = {
   populatePalette: function() {
     let fragment = this.document.createDocumentFragment();
     let toolboxPalette = this.window.gNavToolbox.palette;
 
     try {
       let unusedWidgets = CustomizableUI.getUnusedWidgets(toolboxPalette);
       for (let widget of unusedWidgets) {
         let paletteItem = this.makePaletteItem(widget, "palette");
+        if (!paletteItem) {
+          continue;
+        }
         fragment.appendChild(paletteItem);
       }
 
       this.visiblePalette.appendChild(fragment);
       this._stowedPalette = this.window.gNavToolbox.palette;
       this.window.gNavToolbox.palette = this.visiblePalette;
     } catch (ex) {
       ERROR(ex);
@@ -743,16 +746,25 @@ CustomizeMode.prototype = {
   },
 
   //XXXunf Maybe this should use -moz-element instead of wrapping the node?
   //       Would ensure no weird interactions/event handling from original node,
   //       and makes it possible to put this in a lazy-loaded iframe/real tab
   //       while still getting rid of the need for overlays.
   makePaletteItem: function(aWidget, aPlace) {
     let widgetNode = aWidget.forWindow(this.window).node;
+    if (!widgetNode) {
+      ERROR("Widget with id " + aWidget.id + " does not return a valid node");
+      return null;
+    }
+    // Do not build a palette item for hidden widgets; there's not much to show.
+    if (widgetNode.hidden) {
+      return null;
+    }
+
     let wrapper = this.createOrUpdateWrapper(widgetNode, aPlace);
     wrapper.appendChild(widgetNode);
     return wrapper;
   },
 
   depopulatePalette: function() {
     return Task.spawn(function() {
       this.visiblePalette.hidden = true;
@@ -778,16 +790,25 @@ CustomizeMode.prototype = {
 
         paletteChild = nextChild;
       }
       this.visiblePalette.hidden = false;
       this.window.gNavToolbox.palette = this._stowedPalette;
     }.bind(this)).then(null, ERROR);
   },
 
+  repopulatePalette: function() {
+    Task.spawn(function* () {
+      // Clear the palette first.
+      yield this.depopulatePalette();
+
+      this.populatePalette();
+    }.bind(this)).then(null, ERROR);
+  },
+
   isCustomizableItem: function(aNode) {
     return aNode.localName == "toolbarbutton" ||
            aNode.localName == "toolbaritem" ||
            aNode.localName == "toolbarseparator" ||
            aNode.localName == "toolbarspring" ||
            aNode.localName == "toolbarspacer";
   },
 
--- a/browser/components/loop/MozLoopService.jsm
+++ b/browser/components/loop/MozLoopService.jsm
@@ -1238,23 +1238,20 @@ this.MozLoopService = {
     });
   }),
 
   /**
    * If we're operating the service in "soft start" mode, and this browser
    * isn't already activated, check whether it's time for it to become active.
    * If so, activate the loop service.
    *
-   * @param {Object} buttonNode DOM node representing the Loop button -- if we
-   *                            change from inactive to active, we need this
-   *                            in order to unhide the Loop button.
    * @param {Function} doneCb   [optional] Callback that is called when the
    *                            check has completed.
    */
-  checkSoftStart(buttonNode, doneCb) {
+  checkSoftStart(doneCb) {
     if (!Services.prefs.getBoolPref("loop.throttled")) {
       if (typeof(doneCb) == "function") {
         doneCb(new Error("Throttling is not active"));
       }
       return;
     }
 
     if (Services.io.offline) {
@@ -1309,19 +1306,16 @@ this.MozLoopService = {
       let now_serving = ((parseInt(address[1]) * 0x10000) +
                          (parseInt(address[2]) * 0x100) +
                          parseInt(address[3]));
 
       if (now_serving > ticket) {
         // Hot diggity! It's our turn! Activate the service.
         log.info("MozLoopService: Activating Loop via soft-start");
         Services.prefs.setBoolPref("loop.throttled", false);
-        if (buttonNode) {
-          buttonNode.hidden = false;
-        }
         this.initialize();
       }
       if (typeof(doneCb) == "function") {
         doneCb(null);
       }
     };
 
     // We use DNS to propagate the slow-start value, since it has well-known
--- a/browser/components/loop/test/functional/test_1_browser_call.py
+++ b/browser/components/loop/test/functional/test_1_browser_call.py
@@ -42,17 +42,17 @@ class Test1BrowserCall(MarionetteTestCas
     # XXX workaround for Marionette bug 1055309
     def wait_for_element_exists(self, by, locator, timeout=None):
         Wait(self.marionette, timeout,
              ignored_exceptions=[NoSuchElementException, StaleElementException]) \
             .until(lambda m: m.find_element(by, locator))
         return self.marionette.find_element(by, locator)
 
     def switch_to_panel(self):
-        button = self.marionette.find_element(By.ID, "loop-call-button")
+        button = self.marionette.find_element(By.ID, "loop-button-throttled")
 
         # click the element
         button.click()
 
         # switch to the frame
         frame = self.marionette.find_element(By.ID, "loop")
         self.marionette.switch_to_frame(frame)
 
--- a/browser/components/loop/test/mochitest/browser_fxa_login.js
+++ b/browser/components/loop/test/mochitest/browser_fxa_login.js
@@ -13,17 +13,17 @@ function* checkFxA401() {
   let err = MozLoopService.errors.get("login");
   ise(err.code, 401, "Check error code");
   ise(err.friendlyMessage, getLoopString("could_not_authenticate"),
       "Check friendlyMessage");
   ise(err.friendlyDetails, getLoopString("password_changed_question"),
       "Check friendlyDetails");
   ise(err.friendlyDetailsButtonLabel, getLoopString("retry_button"),
       "Check friendlyDetailsButtonLabel");
-  let loopButton = document.getElementById("loop-call-button");
+  let loopButton = document.getElementById("loop-button-throttled");
   is(loopButton.getAttribute("state"), "error",
      "state of loop button should be error after a 401 with login");
 
   let loopPanel = document.getElementById("loop-notification-panel");
   yield loadLoopPanel({loopURL: BASE_URL });
   let loopDoc = document.getElementById("loop").contentDocument;
   is(loopDoc.querySelector(".alert-error .message").textContent,
      getLoopString("could_not_authenticate"),
@@ -260,17 +260,17 @@ add_task(function* basicAuthorizationAnd
 
   statusChangedPromise = promiseObserverNotified("loop-status-changed");
   yield loadLoopPanel({loopURL: BASE_URL, stayOnline: true});
   yield statusChangedPromise;
   let loopDoc = document.getElementById("loop").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-call-button");
+  let loopButton = document.getElementById("loop-button-throttled");
   is(loopButton.getAttribute("state"), "", "state of loop button should be empty when not logged in");
 
   let tokenData = yield MozLoopService.logInToFxA();
   yield promiseObserverNotified("loop-status-changed", "login");
   ise(tokenData.access_token, "code1_access_token", "Check access_token");
   ise(tokenData.scope, "profile", "Check scope");
   ise(tokenData.token_type, "bearer", "Check token_type");
 
--- a/browser/components/loop/test/mochitest/browser_mozLoop_softStart.js
+++ b/browser/components/loop/test/mochitest/browser_mozLoop_softStart.js
@@ -34,23 +34,19 @@ let MockDNSService = {
 let LoopService = {};
 for (var prop in MozLoopService) {
   if (MozLoopService.hasOwnProperty(prop)) {
     LoopService[prop] = MozLoopService[prop];
   }
 }
 LoopService._DNSService = MockDNSService;
 
-let MockButton = {
-  hidden: true
-};
-
 let runCheck = function(expectError) {
   return new Promise((resolve, reject) => {
-    LoopService.checkSoftStart(MockButton, error => {
+    LoopService.checkSoftStart(error => {
       if ((!!error) != (!!expectError)) {
         reject(error);
       } else {
         resolve(error);
       }
     })
   });
 }
@@ -71,65 +67,56 @@ add_task(function* test_mozLoop_softStar
 
   let throttled;
   let ticket;
 
   info("Ensure that we pick a valid ticket number.");
   yield runCheck();
   throttled = Services.prefs.getBoolPref("loop.throttled");
   ticket = Services.prefs.getIntPref("loop.soft_start_ticket_number");
-  Assert.equal(MockButton.hidden, true, "Button should still be hidden");
   Assert.equal(throttled, true, "Feature should still be throttled");
   Assert.notEqual(ticket, -1, "Ticket should be changed");
   Assert.ok((ticket < 16777214 && ticket > 0), "Ticket should be in range");
 
   // Try some "interesting" ticket numbers
   for (ticket of [1, 256, 65535, 10000000, 16777214]) {
-    MockButton.hidden = true;
     Services.prefs.setBoolPref("loop.throttled", true);
     Services.prefs.setIntPref("loop.soft_start_ticket_number", ticket);
 
     info("Ensure that we don't activate when the now serving " +
          "number is less than our value.");
     MockDNSService.nowServing = ticket - 1;
     yield runCheck();
     throttled = Services.prefs.getBoolPref("loop.throttled");
-    Assert.equal(MockButton.hidden, true, "Button should still be hidden");
     Assert.equal(throttled, true, "Feature should still be throttled");
 
     info("Ensure that we don't activate when the now serving " +
          "number is equal to our value");
     MockDNSService.nowServing = ticket;
     yield runCheck();
     throttled = Services.prefs.getBoolPref("loop.throttled");
-    Assert.equal(MockButton.hidden, true, "Button should still be hidden");
     Assert.equal(throttled, true, "Feature should still be throttled");
 
     info("Ensure that we *do* activate when the now serving " +
          "number is greater than our value");
     MockDNSService.nowServing = ticket + 1;
     yield runCheck();
     throttled = Services.prefs.getBoolPref("loop.throttled");
-    Assert.equal(MockButton.hidden, false, "Button should not be hidden");
     Assert.equal(throttled, false, "Feature should be unthrottled");
   }
 
   info("Check DNS error behavior");
   MockDNSService.nowServing = 0;
   MockDNSService.resultCode = 0x80000000;
   Services.prefs.setBoolPref("loop.throttled", true);
-  MockButton.hidden = true;
   yield runCheck(true);
   throttled = Services.prefs.getBoolPref("loop.throttled");
-  Assert.equal(MockButton.hidden, true, "Button should be hidden");
   Assert.equal(throttled, true, "Feature should be throttled");
 
   info("Check DNS misconfiguration behavior");
   MockDNSService.nowServing = ticket + 1;
   MockDNSService.resultCode = 0;
   MockDNSService.ipFirstOctet = 6;
   Services.prefs.setBoolPref("loop.throttled", true);
-  MockButton.hidden = true;
   yield runCheck(true);
   throttled = Services.prefs.getBoolPref("loop.throttled");
-  Assert.equal(MockButton.hidden, true, "Button should be hidden");
   Assert.equal(throttled, true, "Feature should be throttled");
 });
--- a/browser/components/loop/test/mochitest/head.js
+++ b/browser/components/loop/test/mochitest/head.js
@@ -12,17 +12,17 @@ const {
 // if offline mode is requested multiple times in a test run.
 const WAS_OFFLINE = Services.io.offline;
 
 var gMozLoopAPI;
 
 function promiseGetMozLoopAPI() {
   let deferred = Promise.defer();
   let loopPanel = document.getElementById("loop-notification-panel");
-  let btn = document.getElementById("loop-call-button");
+  let btn = document.getElementById("loop-button-throttled");
 
   // Wait for the popup to be shown if it's not already, then we can get the iframe and
   // wait for the iframe's load to be completed.
   if (loopPanel.state == "closing" || loopPanel.state == "closed") {
     loopPanel.addEventListener("popupshown", () => {
       loopPanel.removeEventListener("popupshown", onpopupshown, true);
       onpopupshown();
     }, true);
@@ -210,17 +210,17 @@ let mockPushHandler = {
    * Test-only API to simplify notifying a push notification result.
    */
   notify: function(version) {
     this._notificationCallback(version);
   }
 };
 
 // Add the Loop button to the navbar.
-CustomizableUI.addWidgetToArea("loop-call-button", CustomizableUI.AREA_NAVBAR);
+CustomizableUI.addWidgetToArea("loop-button-throttled", CustomizableUI.AREA_NAVBAR);
 
 registerCleanupFunction(function() {
   CustomizableUI.reset();
 });
 
 const mockDb = {
   _store: { },
   _next_guid: 1,
--- a/browser/modules/UITour.jsm
+++ b/browser/modules/UITour.jsm
@@ -97,17 +97,17 @@ this.UITour = {
         return aDocument.getAnonymousElementByAttribute(customizeButton,
                                                         "class",
                                                         "toolbarbutton-icon");
       },
       widgetName: "PanelUI-customize",
     }],
     ["help",        {query: "#PanelUI-help"}],
     ["home",        {query: "#home-button"}],
-    ["loop",        {query: "#loop-call-button"}],
+    ["loop",        {query: "#loop-button-throttled"}],
     ["forget", {
       query: "#panic-button",
       widgetName: "panic-button",
       allowAdd: true }],
     ["privateWindow",  {query: "#privatebrowsing-button"}],
     ["quit",        {query: "#PanelUI-quit"}],
     ["search",      {
       query: "#searchbar",
--- a/browser/themes/linux/browser.css
+++ b/browser/themes/linux/browser.css
@@ -1582,17 +1582,17 @@ richlistitem[type~="action"][actiontype=
 }
 
 /* Popup blocker button */
 #page-report-button {
   list-style-image: url("chrome://browser/skin/Info.png");
 }
 
 /* Loop */
-#loop-call-button {
+#loop-button-throttled {
   list-style-image: url("chrome://global/skin/loop/loop-call.png");
 }
 
 /* social share panel */
 
 .social-share-frame {
   background: linear-gradient(to bottom, rgba(242,242,242,.99), rgba(242,242,242,.95));
   border-left: 1px solid #f8f8f8;
--- a/browser/themes/osx/browser.css
+++ b/browser/themes/osx/browser.css
@@ -1347,83 +1347,83 @@ toolbar .toolbarbutton-1 > .toolbarbutto
 
   #PanelUI-fxa-status > .toolbarbutton-icon,
   #PanelUI-quit > .toolbarbutton-icon,
   #PanelUI-customize > .toolbarbutton-icon,
   #PanelUI-help > .toolbarbutton-icon {
     width: 16px;
   }
 
-  #loop-call-button > .toolbarbutton-badge-container {
+  #loop-button-throttled > .toolbarbutton-badge-container {
     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 {
+  toolbar[brighttext] #loop-button-throttled > .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 {
+  #loop-button-throttled[state="disabled"] > .toolbarbutton-badge-container,
+  #loop-button-throttled[disabled="true"] > .toolbarbutton-badge-container {
     -moz-image-region: rect(0, 72px, 36px, 36px);
   }
 
-  #loop-call-button:not([disabled="true"])[state="error"] > .toolbarbutton-badge-container {
+  #loop-button-throttled:not([disabled="true"])[state="error"] > .toolbarbutton-badge-container {
     -moz-image-region: rect(0, 108px, 36px, 72px);
   }
 
-  #loop-call-button:not([disabled="true"])[state="action"] > .toolbarbutton-badge-container {
+  #loop-button-throttled:not([disabled="true"])[state="action"] > .toolbarbutton-badge-container {
     -moz-image-region: rect(0, 144px, 36px, 108px);
   }
 
-  #loop-call-button:not([disabled="true"])[state="action"]:-moz-any(:hover,:hover:active,[open]) > .toolbarbutton-badge-container {
+  #loop-button-throttled:not([disabled="true"])[state="action"]:-moz-any(:hover,:hover:active,[open]) > .toolbarbutton-badge-container {
     -moz-image-region: rect(0, 180px, 36px, 144px);
   }
 
-  #loop-call-button:not([disabled="true"])[state="active"] > .toolbarbutton-badge-container {
+  #loop-button-throttled:not([disabled="true"])[state="active"] > .toolbarbutton-badge-container {
     -moz-image-region: rect(0, 216px, 36px, 180px);
   }
 
-  #loop-call-button:not([disabled="true"])[state="active"]:-moz-any(:hover,:hover:active,[open]) > .toolbarbutton-badge-container {
+  #loop-button-throttled:not([disabled="true"])[state="active"]:-moz-any(:hover,:hover:active,[open]) > .toolbarbutton-badge-container {
     -moz-image-region: rect(0, 252px, 36px, 216px);
   }
 
-  #loop-call-button[cui-areatype="menu-panel"] > .toolbarbutton-badge-container,
-  toolbarpaletteitem[place="palette"] > #loop-call-button > .toolbarbutton-badge-container {
+  #loop-button-throttled[cui-areatype="menu-panel"] > .toolbarbutton-badge-container,
+  toolbarpaletteitem[place="palette"] > #loop-button-throttled > .toolbarbutton-badge-container {
     list-style-image: url(chrome://browser/skin/loop/menuPanel@2x.png);
     -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 {
+  toolbarpaletteitem[place="palette"] > #loop-button-throttled > .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 {
+  #loop-button-throttled[cui-areatype="menu-panel"][state="disabled"] > .toolbarbutton-badge-container,
+  #loop-button-throttled[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 {
+  #loop-button-throttled[cui-areatype="menu-panel"]:not([disabled="true"])[state="error"] > .toolbarbutton-badge-container {
     -moz-image-region: rect(0, 192px, 64px, 128px);
   }
 
-  #loop-call-button[cui-areatype="menu-panel"]:not([disabled="true"])[state="action"] > .toolbarbutton-badge-container {
+  #loop-button-throttled[cui-areatype="menu-panel"]:not([disabled="true"])[state="action"] > .toolbarbutton-badge-container {
     -moz-image-region: rect(0, 256px, 64px, 192px);
   }
 
-  #loop-call-button[cui-areatype="menu-panel"]:not([disabled="true"])[state="action"]:-moz-any(:hover,:hover:active,[open]) > .toolbarbutton-badge-container {
+  #loop-button-throttled[cui-areatype="menu-panel"]:not([disabled="true"])[state="action"]:-moz-any(:hover,:hover:active,[open]) > .toolbarbutton-badge-container {
     -moz-image-region: rect(0, 320px, 64px, 256px);
   }
 
-  #loop-call-button[cui-areatype="menu-panel"]:not([disabled="true"])[state="active"] > .toolbarbutton-badge-container {
+  #loop-button-throttled[cui-areatype="menu-panel"]:not([disabled="true"])[state="active"] > .toolbarbutton-badge-container {
     -moz-image-region: rect(0, 384px, 64px, 320px);
   }
 
-  #loop-call-button[cui-areatype="menu-panel"]:not([disabled="true"])[state="active"]:-moz-any(:hover,:hover:active,[open]) > .toolbarbutton-badge-container {
+  #loop-button-throttled[cui-areatype="menu-panel"]:not([disabled="true"])[state="active"]:-moz-any(:hover,:hover:active,[open]) > .toolbarbutton-badge-container {
     -moz-image-region: rect(0, 448px, 64px, 384px);
   }
 }
 
 toolbar .toolbarbutton-1:not([type="menu-button"]),
 toolbar .toolbarbutton-1 > .toolbarbutton-menubutton-button {
   min-width: 28px;
 }
--- a/browser/themes/shared/menupanel.inc.css
+++ b/browser/themes/shared/menupanel.inc.css
@@ -155,49 +155,49 @@ toolbarpaletteitem[place="palette"] > #p
 #panic-button[cui-areatype="menu-panel"][panel-multiview-anchor=true] {
   -moz-image-region: rect(32px, 896px, 64px, 864px);
 }
 
 toolbaritem[sdkstylewidget="true"] > toolbarbutton {
   -moz-image-region: rect(0, 832px, 32px, 800px);
 }
 
-#loop-call-button[cui-areatype="menu-panel"] > .toolbarbutton-badge-container,
-toolbarpaletteitem[place="palette"] > #loop-call-button > .toolbarbutton-badge-container {
+#loop-button-throttled[cui-areatype="menu-panel"] > .toolbarbutton-badge-container,
+toolbarpaletteitem[place="palette"] > #loop-button-throttled > .toolbarbutton-badge-container {
   list-style-image: url(chrome://browser/skin/loop/menuPanel.png);
   -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 {
+toolbarpaletteitem[place="palette"] > #loop-button-throttled > .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 {
+#loop-button-throttled[cui-areatype="menu-panel"][state="disabled"] > .toolbarbutton-badge-container,
+#loop-button-throttled[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 {
+#loop-button-throttled[cui-areatype="menu-panel"]:not([disabled="true"])[state="error"] > .toolbarbutton-badge-container {
   -moz-image-region: rect(0, 96px, 32px, 64px);
 }
 
-#loop-call-button[cui-areatype="menu-panel"]:not([disabled="true"])[state="action"] > .toolbarbutton-badge-container {
+#loop-button-throttled[cui-areatype="menu-panel"]:not([disabled="true"])[state="action"] > .toolbarbutton-badge-container {
   -moz-image-region: rect(0, 128px, 32px, 96px);
 }
 
-#loop-call-button[cui-areatype="menu-panel"]:not([disabled="true"])[state="action"]:-moz-any(:hover,:hover:active,[open]) > .toolbarbutton-badge-container {
+#loop-button-throttled[cui-areatype="menu-panel"]:not([disabled="true"])[state="action"]:-moz-any(:hover,:hover:active,[open]) > .toolbarbutton-badge-container {
   -moz-image-region: rect(0, 160px, 32px, 128px);
 }
 
-#loop-call-button[cui-areatype="menu-panel"]:not([disabled="true"])[state="active"] > .toolbarbutton-badge-container {
+#loop-button-throttled[cui-areatype="menu-panel"]:not([disabled="true"])[state="active"] > .toolbarbutton-badge-container {
   -moz-image-region: rect(0, 192px, 32px, 160px);
 }
 
-#loop-call-button[cui-areatype="menu-panel"]:not([disabled="true"])[state="active"]:-moz-any(:hover,:hover:active,[open]) > .toolbarbutton-badge-container {
+#loop-button-throttled[cui-areatype="menu-panel"]:not([disabled="true"])[state="active"]:-moz-any(:hover,:hover:active,[open]) > .toolbarbutton-badge-container {
   -moz-image-region: rect(0, 224px, 32px, 192px);
 }
 
 /* Wide panel control icons */
 
 #edit-controls@inAnyPanel@ > toolbarbutton,
 #zoom-controls@inAnyPanel@ > toolbarbutton,
 toolbarpaletteitem[place="palette"] > #edit-controls > toolbarbutton,
--- a/browser/themes/shared/toolbarbuttons.inc.css
+++ b/browser/themes/shared/toolbarbuttons.inc.css
@@ -186,41 +186,41 @@ toolbar[brighttext] #sync-button[status=
 }
 %endif
 
 #panic-button:-moz-locale-dir(rtl) > .toolbarbutton-icon {
   transform: scaleX(-1);
 }
 
 
-#loop-call-button > .toolbarbutton-badge-container {
+#loop-button-throttled > .toolbarbutton-badge-container {
   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 {
+toolbar[brighttext] #loop-button-throttled > .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 {
+#loop-button-throttled[state="disabled"] > .toolbarbutton-badge-container,
+#loop-button-throttled[disabled="true"] > .toolbarbutton-badge-container {
   -moz-image-region: rect(0, 36px, 18px, 18px);
 }
 
-#loop-call-button:not([disabled="true"])[state="error"] > .toolbarbutton-badge-container {
+#loop-button-throttled:not([disabled="true"])[state="error"] > .toolbarbutton-badge-container {
   -moz-image-region: rect(0, 54px, 18px, 36px);
 }
 
-#loop-call-button:not([disabled="true"])[state="action"] > .toolbarbutton-badge-container {
+#loop-button-throttled:not([disabled="true"])[state="action"] > .toolbarbutton-badge-container {
   -moz-image-region: rect(0, 72px, 18px, 54px);
 }
 
-#loop-call-button:not([disabled="true"])[state="action"]:-moz-any(:hover,:hover:active,[open]) > .toolbarbutton-badge-container {
+#loop-button-throttled:not([disabled="true"])[state="action"]:-moz-any(:hover,:hover:active,[open]) > .toolbarbutton-badge-container {
   -moz-image-region: rect(0, 90px, 18px, 72px);
 }
 
-#loop-call-button:not([disabled="true"])[state="active"] > .toolbarbutton-badge-container {
+#loop-button-throttled:not([disabled="true"])[state="active"] > .toolbarbutton-badge-container {
   -moz-image-region: rect(0, 108px, 18px, 90px);
 }
 
-#loop-call-button:not([disabled="true"])[state="active"]:-moz-any(:hover,:hover:active,[open]) > .toolbarbutton-badge-container {
+#loop-button-throttled:not([disabled="true"])[state="active"]:-moz-any(:hover,:hover:active,[open]) > .toolbarbutton-badge-container {
   -moz-image-region: rect(0, 126px, 18px, 108px);
 }
--- a/browser/themes/windows/browser.css
+++ b/browser/themes/windows/browser.css
@@ -569,17 +569,17 @@ menuitem.bookmark-item {
 
 %ifndef WINDOWS_AERO
 @media (-moz-windows-theme: luna-silver) {
   :-moz-any(@primaryToolbarButtons@),
   #bookmarks-menu-button.toolbarbutton-1 > .toolbarbutton-menubutton-dropmarker > .dropmarker-icon {
     list-style-image: url("chrome://browser/skin/Toolbar-lunaSilver.png");
   }
 
-  #loop-call-button > .toolbarbutton-badge-container {
+  #loop-button-throttled > .toolbarbutton-badge-container {
     list-style-image: url(chrome://browser/skin/loop/toolbar-lunaSilver.png)
   }
 }
 %endif
 
 #main-window:not([customizing]) .toolbarbutton-1[disabled=true] > .toolbarbutton-icon,
 #main-window:not([customizing]) .toolbarbutton-1[disabled=true] > .toolbarbutton-menu-dropmarker,
 #main-window:not([customizing]) .toolbarbutton-1[disabled=true] > .toolbarbutton-menubutton-dropmarker,