Merge m-c to b2g-inbound on a CLOSED TREE.
authorRyan VanderMeulen <ryanvm@gmail.com>
Mon, 03 Feb 2014 15:46:28 -0500
changeset 166672 5d00b6b0908b408c7bd4a5dd8f042b2805942a0c
parent 166671 c2da8d1505fe69bfa260758b67cce6ed49d339aa (current diff)
parent 166629 ba2cc1eda988a1614d8986ae145d28e1268409b9 (diff)
child 166673 c150845d077dce845023d4c3708c1ebbbc1e2816
push id4808
push userryanvm@gmail.com
push dateMon, 03 Feb 2014 23:16:15 +0000
treeherderfx-team@c150845d077d [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
milestone29.0a1
Merge m-c to b2g-inbound on a CLOSED TREE.
--- a/browser/base/content/browser-customization.js
+++ b/browser/base/content/browser-customization.js
@@ -31,20 +31,17 @@ let CustomizationHandler = {
     // Disable the toolbar context menu items
     let menubar = document.getElementById("main-menubar");
     for (let childNode of menubar.childNodes)
       childNode.setAttribute("disabled", true);
 
     let cmd = document.getElementById("cmd_CustomizeToolbars");
     cmd.setAttribute("disabled", "true");
 
-    let splitter = document.getElementById("urlbar-search-splitter");
-    if (splitter) {
-      splitter.parentNode.removeChild(splitter);
-    }
+    UpdateUrlbarSearchSplitterState();
 
     CombinedStopReload.uninit();
     CombinedBackForward.uninit();
     PlacesToolbarHelper.customizeStart();
     BookmarkingUI.customizeStart();
     DownloadsButton.customizeStart();
 
     // The additional padding on the sides of the browser
--- a/browser/base/content/browser-places.js
+++ b/browser/base/content/browser-places.js
@@ -975,18 +975,25 @@ let BookmarkingUI = {
     if (!this._shouldUpdateStarState()) {
       return null;
     }
     let widget = CustomizableUI.getWidget("bookmarks-menu-button")
                                .forWindow(window);
     if (widget.overflowed)
       return widget.anchor;
 
-    return document.getAnonymousElementByAttribute(this.star, "class",
-                                                   "toolbarbutton-icon");
+    let star = this.star;
+    return star ? document.getAnonymousElementByAttribute(star, "class",
+                                                          "toolbarbutton-icon")
+                : null;
+  },
+
+  get notifier() {
+    delete this.notifier;
+    return this.notifier = document.getElementById("bookmarked-notification-anchor");
   },
 
   get broadcaster() {
     delete this.broadcaster;
     let broadcaster = document.getElementById("bookmarkThisPageBroadcaster");
     return this.broadcaster = broadcaster;
   },
 
@@ -1082,17 +1089,17 @@ let BookmarkingUI = {
       insertionPoint: ".panel-subview-footer"
     });
   },
 
   /**
    * Handles star styling based on page proxy state changes.
    */
   onPageProxyStateChanged: function BUI_onPageProxyStateChanged(aState) {
-    if (!this._shouldUpdateStarState()) {
+    if (!this._shouldUpdateStarState() || !this.star) {
       return;
     }
 
     if (aState == "invalid") {
       this.star.setAttribute("disabled", "true");
       this.button.removeAttribute("starred");
       this.button.setAttribute("buttontooltiptext", "");
     }
@@ -1153,16 +1160,17 @@ let BookmarkingUI = {
   },
 
   init: function() {
     CustomizableUI.addListener(this);
     this._updateCustomizationState();
   },
 
   _hasBookmarksObserver: false,
+  _itemIds: [],
   uninit: function BUI_uninit() {
     this._updateBookmarkPageMenuItem(true);
     CustomizableUI.removeListener(this);
 
     this._uninitView();
 
     if (this._hasBookmarksObserver) {
       PlacesUtils.removeLazyBookmarkObserver(this);
@@ -1227,70 +1235,110 @@ let BookmarkingUI = {
     if (!this._shouldUpdateStarState()) {
       if (this.button.hasAttribute("starred")) {
         this.button.removeAttribute("starred");
         this.button.removeAttribute("buttontooltiptext");
       }
       return;
     }
 
-    if (this._itemIds && this._itemIds.length > 0) {
+    if (this._itemIds.length > 0) {
       this.button.setAttribute("starred", "true");
       this.button.setAttribute("buttontooltiptext", this._starredTooltip);
     }
     else {
       this.button.removeAttribute("starred");
       this.button.setAttribute("buttontooltiptext", this._unstarredTooltip);
     }
   },
 
   /**
    * forceReset is passed when we're destroyed and the label should go back
    * to the default (Bookmark This Page) for OS X.
    */
   _updateBookmarkPageMenuItem: function BUI__updateBookmarkPageMenuItem(forceReset) {
-    let isStarred = !forceReset && this._itemIds && this._itemIds.length > 0;
+    let isStarred = !forceReset && this._itemIds.length > 0;
     let label = isStarred ? "editlabel" : "bookmarklabel";
     this.broadcaster.setAttribute("label", this.broadcaster.getAttribute(label));
   },
 
   onMainMenuPopupShowing: function BUI_onMainMenuPopupShowing(event) {
     this._updateBookmarkPageMenuItem();
     PlacesCommandHook.updateBookmarkAllTabsCommand();
   },
 
+  _showBookmarkedNotification: function BUI_showBookmarkedNotification() {
+
+    if (this._notificationTimeout) {
+      clearTimeout(this._notificationTimeout);
+    }
+
+    if (this.notifier.style.transform == '') {
+      let buttonRect = this.button.getBoundingClientRect();
+      let notifierRect = this.notifier.getBoundingClientRect();
+      let topDiff = buttonRect.top - notifierRect.top;
+      let leftDiff = buttonRect.left - notifierRect.left;
+      let heightDiff = buttonRect.height - notifierRect.height;
+      let widthDiff = buttonRect.width - notifierRect.width;
+      let translateX = (leftDiff + .5 * widthDiff) + "px";
+      let translateY = (topDiff + .5 * heightDiff) + "px";
+      this.notifier.style.transform = "translate(" +  translateX + ", " + translateY + ")";
+    }
+
+    let isInBookmarksToolbar = this.button.classList.contains("bookmark-item");
+    if (isInBookmarksToolbar)
+      this.notifier.setAttribute("in-bookmarks-toolbar", true);
+
+    let isInOverflowPanel = this.button.classList.contains("overflowedItem");
+    if (!isInOverflowPanel) {
+      this.notifier.setAttribute("notification", "finish");
+      this.button.setAttribute("notification", "finish");
+    }
+
+    this._notificationTimeout = setTimeout( () => {
+      this.notifier.removeAttribute("notification");
+      this.notifier.removeAttribute("in-bookmarks-toolbar");
+      this.button.removeAttribute("notification");
+      this.notifier.style.transform = '';
+    }, 1000);
+  },
+
   onCommand: function BUI_onCommand(aEvent) {
     if (aEvent.target != aEvent.currentTarget) {
       return;
     }
 
     // Handle special case when the button is in the panel.
     let widget = CustomizableUI.getWidget("bookmarks-menu-button")
                                .forWindow(window);
+    let isBookmarked = this._itemIds.length > 0;
+
     if (this._currentAreaType == CustomizableUI.TYPE_MENU_PANEL) {
       let view = document.getElementById("PanelUI-bookmarks");
       view.addEventListener("ViewShowing", this);
       view.addEventListener("ViewHiding", this);
       widget.node.setAttribute("closemenu", "none");
       PanelUI.showSubView("PanelUI-bookmarks", widget.node,
                           CustomizableUI.AREA_PANEL);
       return;
     }
     if (widget.overflowed) {
       // Allow to close the panel if the page is already bookmarked, cause
       // we are going to open the edit bookmark panel.
-      if (this._itemIds.length > 0)
+      if (isBookmarked)
         widget.node.removeAttribute("closemenu");
       else
         widget.node.setAttribute("closemenu", "none");
     }
 
     // Ignore clicks on the star if we are updating its state.
     if (!this._pendingStmt) {
-      PlacesCommandHook.bookmarkCurrentPage(this._itemIds.length > 0);
+      if (!isBookmarked)
+        this._showBookmarkedNotification();
+      PlacesCommandHook.bookmarkCurrentPage(isBookmarked);
     }
   },
 
   handleEvent: function BUI_handleEvent(aEvent) {
     switch (aEvent.type) {
       case "ViewShowing":
         this.onPanelMenuViewShowing(aEvent);
         break;
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -2118,16 +2118,23 @@ function losslessDecodeURI(aURI) {
 }
 
 function UpdateUrlbarSearchSplitterState()
 {
   var splitter = document.getElementById("urlbar-search-splitter");
   var urlbar = document.getElementById("urlbar-container");
   var searchbar = document.getElementById("search-container");
 
+  if (document.documentElement.getAttribute("customizing") == "true") {
+    if (splitter) {
+      splitter.remove();
+    }
+    return;
+  }
+
   // If the splitter is already in the right place, we don't need to do anything:
   if (splitter &&
       ((splitter.nextSibling == searchbar && splitter.previousSibling == urlbar) ||
        (splitter.nextSibling == urlbar && splitter.previousSibling == searchbar))) {
     return;
   }
 
   var ibefore = null;
--- a/browser/base/content/browser.xul
+++ b/browser/base/content/browser.xul
@@ -457,16 +457,22 @@
 #include ../../components/customizableui/content/panelUI.inc.xul
 
     <hbox id="downloads-animation-container" mousethrough="always">
       <vbox id="downloads-notification-anchor">
         <vbox id="downloads-indicator-notification"/>
       </vbox>
     </hbox>
 
+    <hbox id="bookmarked-notification-container" mousethrough="always">
+      <vbox id="bookmarked-notification-anchor">
+        <vbox id="bookmarked-notification"/>
+      </vbox>
+    </hbox>
+
     <tooltip id="dynamic-shortcut-tooltip"
              onpopupshowing="UpdateDynamicShortcutTooltipText(this.triggerNode)">
       <label id="dynamic-shortcut-tooltip-label"/>
     </tooltip>
   </popupset>
 
 #ifdef CAN_DRAW_IN_TITLEBAR
 <vbox id="titlebar">
--- a/browser/components/customizableui/content/panelUI.inc.xul
+++ b/browser/components/customizableui/content/panelUI.inc.xul
@@ -32,16 +32,17 @@
           <toolbarbutton id="PanelUI-help" label="&helpMenu.label;"
                          closemenu="none"
                          tooltiptext="&appMenuHelp.tooltip;"
                          oncommand="PanelUI.showHelpView(this.parentNode);"/>
           <toolbarseparator/>
           <toolbarbutton id="PanelUI-quit"
 #ifdef XP_WIN
                          label="&quitApplicationCmdWin.label;"
+                         tooltiptext="&quitApplicationCmdWin.tooltip;"
 #else
 #ifdef XP_MACOSX
                          label="&quitApplicationCmdMac.label;"
 #else
                          label="&quitApplicationCmd.label;"
 #endif
 #endif
                          command="cmd_quitApplication"/>
--- a/browser/components/customizableui/content/panelUI.js
+++ b/browser/components/customizableui/content/panelUI.js
@@ -415,22 +415,21 @@ const PanelUI = {
     }
     items.appendChild(fragment);
   },
 
   _updateQuitTooltip: function() {
 #ifndef XP_WIN
 #ifdef XP_MACOSX
     let tooltipId = "quit-button.tooltiptext.mac";
+#else
+    let tooltipId = "quit-button.tooltiptext.linux2";
+#endif
     let brands = Services.strings.createBundle("chrome://branding/locale/brand.properties");
     let stringArgs = [brands.GetStringFromName("brandShortName")];
-#else
-    let tooltipId = "quit-button.tooltiptext.linux";
-    let stringArgs = [];
-#endif
 
     let key = document.getElementById("key_quitApplication");
     stringArgs.push(ShortcutUtils.prettifyShortcut(key));
     let tooltipString = CustomizableUI.getLocalizedProperty({x: tooltipId}, "x", stringArgs);
     let quitButton = document.getElementById("PanelUI-quit");
     quitButton.setAttribute("tooltiptext", tooltipString);
 #endif
   },
--- a/browser/components/customizableui/src/CustomizableUI.jsm
+++ b/browser/components/customizableui/src/CustomizableUI.jsm
@@ -698,16 +698,18 @@ let CustomizableUIInternal = {
         container = areaNode.overflowable.getContainerFor(widgetNode);
       }
 
       this.notifyListeners("onWidgetBeforeDOMChange", widgetNode, null, container, true);
 
       // We remove location attributes here to make sure they're gone too when a
       // widget is removed from a toolbar to the palette. See bug 930950.
       this.removeLocationAttributes(widgetNode);
+      // We also need to remove the panel context menu if it's there:
+      this.ensureButtonContextMenu(widgetNode);
       widgetNode.removeAttribute("wrap");
       if (gPalette.has(aWidgetId) || this.isSpecialWidget(aWidgetId)) {
         container.removeChild(widgetNode);
       } else {
         areaNode.toolbox.palette.appendChild(widgetNode);
       }
       this.notifyListeners("onWidgetAfterDOMChange", widgetNode, null, container, true);
 
@@ -1134,18 +1136,16 @@ let CustomizableUIInternal = {
     }
     return def;
   },
 
   handleWidgetCommand: function(aWidget, aNode, aEvent) {
     LOG("handleWidgetCommand");
 
     if (aWidget.type == "button") {
-      this.maybeAutoHidePanel(aEvent);
-
       if (aWidget.onCommand) {
         try {
           aWidget.onCommand.call(null, aEvent);
         } catch (e) {
           ERROR(e);
         }
       } else {
         //XXXunf Need to think this through more, and formalize.
@@ -1157,20 +1157,16 @@ let CustomizableUIInternal = {
       let ownerWindow = aNode.ownerDocument.defaultView;
       ownerWindow.PanelUI.showSubView(aWidget.viewId, aNode,
                                       this.getPlacementOfWidget(aNode.id).area);
     }
   },
 
   handleWidgetClick: function(aWidget, aNode, aEvent) {
     LOG("handleWidgetClick");
-    if (aWidget.type == "button") {
-      this.maybeAutoHidePanel(aEvent);
-    }
-
     if (aWidget.onClick) {
       try {
         aWidget.onClick.call(null, aEvent);
       } catch(e) {
         Cu.reportError(e);
       }
     } else {
       //XXXunf Need to think this through more, and formalize.
@@ -1317,17 +1313,17 @@ let CustomizableUIInternal = {
     // We can't use event.target because we might have passed a panelview
     // anonymous content boundary as well, and so target points to the
     // panelmultiview in that case. Unfortunately, this means we get
     // anonymous child nodes instead of the real ones, so looking for the 
     // 'stoooop, don't close me' attributes is more involved.
     let target = aEvent.originalTarget;
     let closemenu = "auto";
     let widgetType = "button";
-    while (target.localName != "panel") {
+    while (target.parentNode && target.localName != "panel") {
       closemenu = target.getAttribute("closemenu");
       widgetType = target.getAttribute("widget-type");
       if (closemenu == "none" || closemenu == "single" ||
           widgetType == "view") {
         break;
       }
       target = target.parentNode;
     }
--- a/browser/components/customizableui/src/CustomizeMode.jsm
+++ b/browser/components/customizableui/src/CustomizeMode.jsm
@@ -14,32 +14,37 @@ const kPaletteId = "customization-palett
 const kAboutURI = "about:customizing";
 const kDragDataTypePrefix = "text/toolbarwrapper-id/";
 const kPlaceholderClass = "panel-customization-placeholder";
 const kSkipSourceNodePref = "browser.uiCustomization.skipSourceNodeCheck";
 const kToolbarVisibilityBtn = "customization-toolbar-visibility-button";
 const kDrawInTitlebarPref = "browser.tabs.drawInTitlebar";
 const kMaxTransitionDurationMs = 2000;
 
+const kPanelItemContextMenu = "customizationPanelItemContextMenu";
+const kPaletteItemContextMenu = "customizationPaletteItemContextMenu";
+
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource:///modules/CustomizableUI.jsm");
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource://gre/modules/Task.jsm");
 Cu.import("resource://gre/modules/Promise.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "DragPositionManager",
                                   "resource:///modules/DragPositionManager.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "BrowserUITelemetry",
                                   "resource:///modules/BrowserUITelemetry.jsm");
 
 let gModuleName = "[CustomizeMode]";
 #include logging.js
 
 let gDisableAnimation = null;
 
+let gDraggingInToolbars;
+
 function CustomizeMode(aWindow) {
   if (gDisableAnimation === null) {
     gDisableAnimation = Services.prefs.getPrefType(kPrefCustomizationAnimation) == Ci.nsIPrefBranch.PREF_BOOL &&
                         Services.prefs.getBoolPref(kPrefCustomizationAnimation);
   }
   this.window = aWindow;
   this.document = aWindow.document;
   this.browser = aWindow.gBrowser;
@@ -650,19 +655,16 @@ CustomizeMode.prototype = {
     } else if (aNode.hasAttribute("label")) {
       wrapper.setAttribute("title", aNode.getAttribute("label"));
     }
 
     if (aNode.hasAttribute("flex")) {
       wrapper.setAttribute("flex", aNode.getAttribute("flex"));
     }
 
-
-    const kPanelItemContextMenu = "customizationPanelItemContextMenu";
-    const kPaletteItemContextMenu = "customizationPaletteItemContextMenu";
     let contextMenuAttrName = aNode.getAttribute("context") ? "context" :
                                 aNode.getAttribute("contextmenu") ? "contextmenu" : "";
     let currentContextMenu = aNode.getAttribute(contextMenuAttrName);
     let contextMenuForPlace = aPlace == "panel" ?
                                 kPanelItemContextMenu :
                                 kPaletteItemContextMenu;
     if (aPlace != "toolbar") {
       wrapper.setAttribute("context", contextMenuForPlace);
@@ -699,16 +701,18 @@ CustomizeMode.prototype = {
 
   unwrapToolbarItem: function(aWrapper) {
     if (aWrapper.nodeName != "toolbarpaletteitem") {
       return aWrapper;
     }
     aWrapper.removeEventListener("mousedown", this);
     aWrapper.removeEventListener("mouseup", this);
 
+    let place = aWrapper.getAttribute("place");
+
     let toolbarItem = aWrapper.firstChild;
     if (!toolbarItem) {
       ERROR("no toolbarItem child for " + aWrapper.tagName + "#" + aWrapper.id);
       aWrapper.remove();
       return null;
     }
 
     if (aWrapper.hasAttribute("itemobserves")) {
@@ -731,16 +735,18 @@ CustomizeMode.prototype = {
     }
 
     let wrappedContext = toolbarItem.getAttribute("wrapped-context");
     if (wrappedContext) {
       let contextAttrName = toolbarItem.getAttribute("wrapped-contextAttrName");
       toolbarItem.setAttribute(contextAttrName, wrappedContext);
       toolbarItem.removeAttribute("wrapped-contextAttrName");
       toolbarItem.removeAttribute("wrapped-context");
+    } else if (place == "panel") {
+      toolbarItem.setAttribute("context", kPanelItemContextMenu);
     }
 
     if (aWrapper.parentNode) {
       aWrapper.parentNode.replaceChild(toolbarItem, aWrapper);
     }
     return toolbarItem;
   },
 
@@ -1043,28 +1049,32 @@ CustomizeMode.prototype = {
     dt.effectAllowed = "move";
 
     let itemRect = draggedItem.getBoundingClientRect();
     let itemCenter = {x: itemRect.left + itemRect.width / 2,
                       y: itemRect.top + itemRect.height / 2};
     this._dragOffset = {x: aEvent.clientX - itemCenter.x,
                         y: aEvent.clientY - itemCenter.y};
 
+    gDraggingInToolbars = new Set();
+
     // Hack needed so that the dragimage will still show the
     // item as it appeared before it was hidden.
     this._initializeDragAfterMove = function() {
       // For automated tests, we sometimes start exiting customization mode
       // before this fires, which leaves us with placeholders inserted after
       // we've exited. So we need to check that we are indeed customizing.
       if (this._customizing && !this._transitioning) {
         item.hidden = true;
         this._showPanelCustomizationPlaceholders();
         DragPositionManager.start(this.window);
-        if (!isInToolbar && item.nextSibling) {
-          this._setDragActive(item.nextSibling, "before", draggedItem.id, false);
+        if (item.nextSibling) {
+          this._setDragActive(item.nextSibling, "before", draggedItem.id, isInToolbar);
+        } else if (isInToolbar && item.previousSibling) {
+          this._setDragActive(item.previousSibling, "after", draggedItem.id, isInToolbar);
         }
       }
       this._initializeDragAfterMove = null;
       this.window.clearTimeout(this._dragInitializeTimeout);
     }.bind(this);
     this._dragInitializeTimeout = this.window.setTimeout(this._initializeDragAfterMove, 0);
   },
 
@@ -1403,55 +1413,80 @@ CustomizeMode.prototype = {
            mozSourceNode.ownerDocument.defaultView != this.window;
   },
 
   _setDragActive: function(aItem, aValue, aDraggedItemId, aInToolbar) {
     if (!aItem) {
       return;
     }
 
-    if (aItem.hasAttribute("dragover") != aValue) {
+    if (aItem.getAttribute("dragover") != aValue) {
       aItem.setAttribute("dragover", aValue);
 
       let window = aItem.ownerDocument.defaultView;
       let draggedItem = window.document.getElementById(aDraggedItemId);
       if (!aInToolbar) {
         this._setGridDragActive(aItem, draggedItem, aValue);
       } else {
+        let targetArea = this._getCustomizableParent(aItem);
+        let makeSpaceImmediately = false;
+        if (!gDraggingInToolbars.has(targetArea.id)) {
+          gDraggingInToolbars.add(targetArea.id);
+          let draggedWrapper = this.document.getElementById("wrapper-" + aDraggedItemId);
+          let originArea = this._getCustomizableParent(draggedWrapper);
+          makeSpaceImmediately = originArea == targetArea;
+        }
         // Calculate width of the item when it'd be dropped in this position
         let width = this._getDragItemSize(aItem, draggedItem).width;
         let direction = window.getComputedStyle(aItem).direction;
         let prop, otherProp;
         // If we're inserting before in ltr, or after in rtl:
         if ((aValue == "before") == (direction == "ltr")) {
           prop = "borderLeftWidth";
           otherProp = "border-right-width";
         } else {
           // otherwise:
           prop = "borderRightWidth";
           otherProp = "border-left-width";
         }
+        if (makeSpaceImmediately) {
+          aItem.setAttribute("notransition", "true");
+        }
         aItem.style[prop] = width + 'px';
         aItem.style.removeProperty(otherProp);
+        if (makeSpaceImmediately) {
+          // Force a layout flush:
+          aItem.getBoundingClientRect();
+          aItem.removeAttribute("notransition");
+        }
       }
     }
   },
   _cancelDragActive: function(aItem, aNextItem, aNoTransition) {
     let currentArea = this._getCustomizableParent(aItem);
     if (!currentArea) {
       return;
     }
     let isToolbar = CustomizableUI.getAreaType(currentArea.id) == "toolbar";
     if (isToolbar) {
+      if (aNoTransition) {
+        aItem.setAttribute("notransition", "true");
+      }
       aItem.removeAttribute("dragover");
       // Remove both property values in the case that the end padding
       // had been set.
       aItem.style.removeProperty("border-left-width");
       aItem.style.removeProperty("border-right-width");
+      if (aNoTransition) {
+        // Force a layout flush:
+        aItem.getBoundingClientRect();
+        aItem.removeAttribute("notransition");
+      }
     } else  {
+      aItem.removeAttribute("dragover");
       if (aNextItem) {
         let nextArea = this._getCustomizableParent(aNextItem);
         if (nextArea == currentArea) {
           // No need to do anything if we're still dragging in this area:
           return;
         }
       }
       // Otherwise, clear everything out:
--- a/browser/components/customizableui/test/browser_880164_customization_context_menus.js
+++ b/browser/components/customizableui/test/browser_880164_customization_context_menus.js
@@ -269,16 +269,49 @@ add_task(function() {
   checkContextMenu(contextMenu, expectedEntries);
 
   hiddenPromise = contextMenuHidden(contextMenu);
   contextMenu.hidePopup();
   yield hiddenPromise;
   yield resetCustomization();
 });
 
+// Bug 947586 - After customization, panel items show wrong context menu options
+add_task(function() {
+  yield startCustomizing();
+  yield endCustomizing();
+
+  yield PanelUI.show();
+
+  let contextMenu = document.getElementById("customizationPanelItemContextMenu");
+  let shownContextPromise = contextMenuShown(contextMenu);
+  let newWindowButton = document.getElementById("new-window-button");
+  ok(newWindowButton, "new-window-button was found");
+  EventUtils.synthesizeMouse(newWindowButton, 2, 2, {type: "contextmenu", button: 2});
+  yield shownContextPromise;
+
+  is(PanelUI.panel.state, "open", "The PanelUI should still be open.");
+
+  let expectedEntries = [
+    [".customize-context-moveToToolbar", true],
+    [".customize-context-removeFromPanel", true],
+    ["---"],
+    [".viewCustomizeToolbar", true]
+  ];
+  checkContextMenu(contextMenu, expectedEntries);
+
+  let hiddenContextPromise = contextMenuHidden(contextMenu);
+  contextMenu.hidePopup();
+  yield hiddenContextPromise;
+
+  let hiddenPromise = promisePanelHidden(window);
+  PanelUI.hide();
+  yield hiddenPromise;
+});
+
 function contextMenuShown(aContextMenu) {
   let deferred = Promise.defer();
   let win = aContextMenu.ownerDocument.defaultView;
   let timeoutId = win.setTimeout(() => {
     deferred.reject("Context menu (" + aContextMenu.id + ") did not show within 20 seconds.");
   }, 20000);
   function onPopupShown(e) {
     aContextMenu.removeEventListener("popupshown", onPopupShown);
--- a/browser/components/sessionstore/src/SessionFile.jsm
+++ b/browser/components/sessionstore/src/SessionFile.jsm
@@ -23,23 +23,26 @@ this.EXPORTED_SYMBOLS = ["SessionFile"];
  *   another attempts to copy that file.
  *
  * This implementation uses OS.File, which guarantees property 1.
  */
 
 const Cu = Components.utils;
 const Cc = Components.classes;
 const Ci = Components.interfaces;
+const Cr = Components.results;
 
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource://gre/modules/osfile.jsm");
 Cu.import("resource://gre/modules/osfile/_PromiseWorker.jsm", this);
 Cu.import("resource://gre/modules/Promise.jsm");
 Cu.import("resource://gre/modules/AsyncShutdown.jsm");
+Cu.import("resource://gre/modules/NetUtil.jsm");
+Cu.import("resource://gre/modules/FileUtils.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "console",
   "resource://gre/modules/devtools/Console.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "TelemetryStopwatch",
   "resource://gre/modules/TelemetryStopwatch.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "Task",
   "resource://gre/modules/Task.jsm");
 XPCOMUtils.defineLazyServiceGetter(this, "Telemetry",
@@ -120,20 +123,67 @@ let SessionFileInternal = {
   _latestWrite: null,
 
   /**
    * |true| once we have decided to stop receiving write instructiosn
    */
   _isClosed: false,
 
   read: function () {
-    return SessionWorker.post("read").then(msg => {
-      this._recordTelemetry(msg.telemetry);
-      return msg.ok;
+    // We must initialize the worker during startup so it will be ready to
+    // perform the final write. If shutdown happens soon after startup and
+    // the worker has not started yet we may not write.
+    // See Bug 964531.
+    SessionWorker.post("init");
+
+    return Task.spawn(function () {
+      let data = null;
+      for (let filename of [this.path, this.backupPath]) {
+        try {
+          data = yield this._readSessionFile(filename);
+          break;
+        } catch (ex if ex == Cr.NS_ERROR_FILE_NOT_FOUND) {
+          // Ignore exceptions about non-existent files.
+        }
+      }
+
+      throw new Task.Result(data || "");
+    }.bind(this));
+  },
+
+  /**
+   * Read the session file asynchronously.
+   *
+   * @param filename
+   *        string The name of the session file.
+   * @returns {promise}
+   */
+  _readSessionFile: function (path) {
+    let deferred = Promise.defer();
+    let file = FileUtils.File(path);
+    let durationMs = Date.now();
+
+    NetUtil.asyncFetch(file, function(inputStream, status) {
+      if (!Components.isSuccessCode(status)) {
+        deferred.reject(status);
+        return;
+      }
+
+      let byteLength = inputStream.available();
+      let data = NetUtil.readInputStreamToString(inputStream, byteLength,
+        { charset: "UTF-8" });
+      durationMs = Date.now() - durationMs;
+
+      deferred.resolve(data);
+
+      Telemetry.getHistogramById("FX_SESSION_RESTORE_READ_FILE_MS").add(durationMs);
+      Telemetry.getHistogramById("FX_SESSION_RESTORE_FILE_SIZE_BYTES").add(byteLength);
     });
+
+    return deferred.promise;
   },
 
   gatherTelemetry: function(aStateString) {
     return Task.spawn(function() {
       let msg = yield SessionWorker.post("gatherTelemetry", [aStateString]);
       this._recordTelemetry(msg.telemetry);
       throw new Task.Result(msg.telemetry);
     }.bind(this));
--- a/browser/components/sessionstore/src/SessionWorker.js
+++ b/browser/components/sessionstore/src/SessionWorker.js
@@ -62,37 +62,20 @@ let Agent = {
 
   // The path to sessionstore.js
   path: OS.Path.join(OS.Constants.Path.profileDir, "sessionstore.js"),
 
   // The path to sessionstore.bak
   backupPath: OS.Path.join(OS.Constants.Path.profileDir, "sessionstore.bak"),
 
   /**
-   * Read the session from disk.
-   * In case sessionstore.js does not exist, attempt to read sessionstore.bak.
+   * NO-OP to start the worker.
    */
-  read: function () {
-    for (let path of [this.path, this.backupPath]) {
-      try {
-        let durationMs = Date.now();
-        let bytes = File.read(path);
-        durationMs = Date.now() - durationMs;
-
-        return {
-          result: Decoder.decode(bytes),
-          telemetry: {FX_SESSION_RESTORE_READ_FILE_MS: durationMs,
-                      FX_SESSION_RESTORE_FILE_SIZE_BYTES: bytes.byteLength}
-        };
-      } catch (ex if isNoSuchFileEx(ex)) {
-        // Ignore exceptions about non-existent files.
-      }
-    }
-    // No sessionstore data files found. Return an empty string.
-    return {result: ""};
+  init: function () {
+    return {result: true};
   },
 
   /**
    * Write the session to disk.
    */
   write: function (stateString) {
     let exn;
     let telemetry = {};
--- a/browser/locales/en-US/chrome/browser/browser.dtd
+++ b/browser/locales/en-US/chrome/browser/browser.dtd
@@ -565,16 +565,17 @@ you can use these alternative items. Oth
 <!ENTITY newTabButton.tooltip           "Open a new tab">
 <!ENTITY newWindowButton.tooltip        "Open a new window">
 <!ENTITY sidebarCloseButton.tooltip     "Close sidebar">
 
 <!ENTITY fullScreenButton.tooltip       "Display the window in full screen">
 
 <!ENTITY quitApplicationCmdWin.label       "Exit"> 
 <!ENTITY quitApplicationCmdWin.accesskey   "x">
+<!ENTITY quitApplicationCmdWin.tooltip     "Exit &brandShortName;">
 <!ENTITY goBackCmd.commandKey "[">
 <!ENTITY goForwardCmd.commandKey "]">
 <!ENTITY quitApplicationCmd.label       "Quit"> 
 <!ENTITY quitApplicationCmd.accesskey   "Q">
 <!ENTITY quitApplicationCmdMac.label    "Quit &brandShortName;">
 <!-- LOCALIZATION NOTE(quitApplicationCmdUnix.key): This keyboard shortcut is used by both Linux and OSX builds. -->
 <!ENTITY quitApplicationCmdUnix.key     "Q">
 
--- a/browser/locales/en-US/chrome/browser/customizableui/customizableWidgets.properties
+++ b/browser/locales/en-US/chrome/browser/customizableui/customizableWidgets.properties
@@ -86,13 +86,14 @@ feed-button.tooltiptext = Subscribe to this page…
 characterencoding-button.label = Character Encoding
 characterencoding-button.tooltiptext2 = Show Character Encoding options
 
 email-link-button.label = Email Link
 # LOCALIZATION NOTE (email-link-button.tooltiptext2): Use the unicode ellipsis char,
 # \u2026, or use "..." if \u2026 doesn't suit traditions in your locale.
 email-link-button.tooltiptext2 = Email Link…
 
-# LOCALIZATION NOTE(quit-button.tooltiptext.linux): %S is the keyboard shortcut
-quit-button.tooltiptext.linux = Quit (%S)
+# LOCALIZATION NOTE(quit-button.tooltiptext.linux2): %1$S is the brand name (e.g. Firefox),
+# %2$S is the keyboard shortcut
+quit-button.tooltiptext.linux2 = Quit %1$S (%2$S)
 # LOCALIZATION NOTE(quit-button.tooltiptext.mac): %1$S is the brand name (e.g. Firefox),
 # %2$S is the keyboard shortcut
 quit-button.tooltiptext.mac = Quit %1$S (%2$S)
--- a/browser/metro/base/content/browser.js
+++ b/browser/metro/base/content/browser.js
@@ -1255,17 +1255,17 @@ function Tab(aURI, aParams, aOwner) {
   this._chromeTab = null;
   this._eventDeferred = null;
   this._updateThumbnailTimeout = null;
 
   this._private = false;
   if ("private" in aParams) {
     this._private = aParams.private;
   } else if (aOwner) {
-    this._private = aOwner.private;
+    this._private = aOwner._private;
   }
 
   this.owner = aOwner || null;
 
   // Set to 0 since new tabs that have not been viewed yet are good tabs to
   // toss if app needs more memory.
   this.lastSelected = 0;
 
--- a/browser/metro/base/content/helperui/MenuUI.js
+++ b/browser/metro/base/content/helperui/MenuUI.js
@@ -162,16 +162,17 @@ var ContextMenuUI = {
     if (contentTypes.indexOf("link") != -1 &&
         (contentTypes.indexOf("image") != -1  ||
          contentTypes.indexOf("video") != -1 ||
          contentTypes.indexOf("selected-text") != -1))
       multipleMediaTypes = true;
 
     for (let command of Array.slice(this.commands.childNodes)) {
       command.hidden = true;
+      command.selected = false;
     }
 
     let optionsAvailable = false;
     for (let command of Array.slice(this.commands.childNodes)) {
       let types = command.getAttribute("type").split(",");
       let lowPriority = (command.hasAttribute("priority") &&
         command.getAttribute("priority") == "low");
       let searchTextItem = (command.id == "context-search");
--- a/browser/metro/base/tests/mochitest/browser_context_menu_tests.js
+++ b/browser/metro/base/tests/mochitest/browser_context_menu_tests.js
@@ -869,12 +869,49 @@ gTests.push({
       return clipboardContent == contentToCopy;
     });
     ok(clipboardContent == contentToCopy, "Rich content copied.");
 
     Browser.closeTab(Browser.selectedTab, { forceClose: true });
   }
 });
 
+gTests.push({
+  desc: "Bug 963067 - 'Cut' in the cut, copy, paste menu is always active " +
+        "after a browser launch.",
+  run: function test() {
+    info(chromeRoot + "browser_context_menu_tests_02.html");
+    yield addTab(chromeRoot + "browser_context_menu_tests_02.html");
+
+    purgeEventQueue();
+    emptyClipboard();
+
+    ContextUI.dismiss();
+    yield waitForCondition(() => !ContextUI.navbarVisible);
+
+    let tabWindow = Browser.selectedTab.browser.contentWindow;
+    let input = tabWindow.document.getElementById("text3-input");
+    let cutMenuItem = document.getElementById("context-cut");
+
+    input.select();
+
+    // Emulate RichListBox's behavior and set first item selected by default.
+    cutMenuItem.selected = true;
+
+    let promise = waitForEvent(document, "popupshown");
+    sendContextMenuClickToElement(tabWindow, input);
+    yield promise;
+
+    ok(!cutMenuItem.hidden && !cutMenuItem.selected,
+       "Cut menu item is visible and not selected.");
+
+    promise = waitForEvent(document, "popuphidden");
+    ContextMenuUI.hide();
+    yield promise;
+
+    Browser.closeTab(Browser.selectedTab, { forceClose: true });
+  }
+});
+
 function test() {
   setDevPixelEqualToPx();
   runTests();
 }
--- a/browser/metro/base/tests/mochitest/browser_private_browsing.js
+++ b/browser/metro/base/tests/mochitest/browser_private_browsing.js
@@ -11,17 +11,27 @@ function test() {
 
 gTests.push({
   desc: "Private tab sanity check",
   run: function() {
     let tab = Browser.addTab("about:mozilla");
     is(tab.isPrivate, false, "Tabs are not private by default");
     is(tab.chromeTab.hasAttribute("private"), false,
       "non-private tab has no private attribute");
+
+    let child = Browser.addTab("about:mozilla", false, tab);
+    is(child.isPrivate, false, "Child of a non-private tab is not private");
+
+    Browser.closeTab(child, { forceClose: true });
     Browser.closeTab(tab, { forceClose: true });
 
     tab = Browser.addTab("about:mozilla", false, null, { private: true });
     is(tab.isPrivate, true, "Create a private tab");
     is(tab.chromeTab.getAttribute("private"), "true",
       "private tab has private attribute");
+
+    child = Browser.addTab("about:mozilla", false, tab);
+    is(child.isPrivate, true, "Child of a private tab is private");
+
+    Browser.closeTab(child, { forceClose: true });
     Browser.closeTab(tab, { forceClose: true });
   }
 });
--- a/browser/themes/linux/browser.css
+++ b/browser/themes/linux/browser.css
@@ -112,16 +112,72 @@ toolbarbutton.bookmark-item[open="true"]
   list-style-image: url("chrome://browser/skin/places/bookmarksToolbar.png") !important;
 }
 
 toolbarpaletteitem[place="palette"] > #personal-bookmarks > #bookmarks-toolbar-placeholder,
 #personal-bookmarks[cui-areatype="menu-panel"] > #bookmarks-toolbar-placeholder {
   list-style-image: url("chrome://browser/skin/places/bookmarksToolbar-menuPanel.png") !important;
 }
 
+/* ----- BOOKMARK STAR ANIMATION ----- */
+
+@keyframes animation-bookmarkAdded {
+  from { transform: rotate(0deg) translateX(-20px) rotate(0deg) scale(1); opacity: 0; }
+  60%  { transform: rotate(180deg) translateX(-20px) rotate(-180deg) scale(2.2); opacity: 1; }
+  80%  { opacity: 1; }
+  to   { transform: rotate(180deg) translateX(-20px) rotate(-180deg) scale(1); opacity: 0; }
+}
+
+@keyframes animation-bookmarkAddedToBookmarksBar {
+  from { transform: rotate(0deg) translateX(-12px) rotate(0deg) scale(1); opacity: 0; }
+  60%  { transform: rotate(180deg) translateX(-12px) rotate(-180deg) scale(2.2); opacity: 1; }
+  80%  { opacity: 1; }
+  to   { transform: rotate(180deg) translateX(-12px) rotate(-180deg) scale(1); opacity: 0; }
+}
+
+@keyframes animation-bookmarkPulse {
+  from { transform: scale(1); }
+  50%  { transform: scale(1.3); }
+  to   { transform: scale(1); }
+}
+
+#bookmarked-notification-container {
+  min-height: 1px;
+  min-width: 1px;
+  height: 1px;
+  margin-bottom: -1px;
+  z-index: 5;
+  position: relative;
+}
+
+#bookmarked-notification {
+  background-size: 16px;
+  background-position: center;
+  background-repeat: no-repeat;
+  width: 16px;
+  height: 16px;
+  opacity: 0;
+}
+
+#bookmarked-notification-anchor[notification="finish"] > #bookmarked-notification {
+  background-image: url("chrome://browser/skin/places/bookmarks-notification-finish.png");
+  animation: animation-bookmarkAdded 800ms;
+  animation-timing-function: ease ease ease linear;
+}
+
+#bookmarked-notification-anchor[notification="finish"][in-bookmarks-toolbar=true] > #bookmarked-notification {
+  animation: animation-bookmarkAddedToBookmarksBar 800ms;
+}
+
+#bookmarks-menu-button[notification="finish"] > .toolbarbutton-menubutton-dropmarker > .dropmarker-icon {
+  animation: animation-bookmarkPulse 300ms;
+  animation-delay: 600ms;
+  animation-timing-function: ease-out;
+}
+
 /* Bookmark menus */
 menu.bookmark-item,
 menuitem.bookmark-item {
   min-width: 0;
   max-width: 32em;
 }
 
 .bookmark-item > .menu-iconic-left {
--- a/browser/themes/linux/jar.mn
+++ b/browser/themes/linux/jar.mn
@@ -90,16 +90,17 @@ browser.jar:
   skin/classic/browser/feeds/audioFeedIcon.png        (feeds/feedIcon.png)
   skin/classic/browser/feeds/audioFeedIcon16.png      (feeds/feedIcon16.png)
   skin/classic/browser/feeds/subscribe.css            (feeds/subscribe.css)
   skin/classic/browser/feeds/subscribe-ui.css         (feeds/subscribe-ui.css)
   skin/classic/browser/newtab/newTab.css              (newtab/newTab.css)
   skin/classic/browser/newtab/controls.png            (newtab/controls.png)
   skin/classic/browser/places/bookmarksMenu.png       (places/bookmarksMenu.png)
   skin/classic/browser/places/bookmarksToolbar.png    (places/bookmarksToolbar.png)
+  skin/classic/browser/places/bookmarks-notification-finish.png  (places/bookmarks-notification-finish.png)
   skin/classic/browser/places/bookmarksToolbar-menuPanel.png (places/bookmarksToolbar-menuPanel.png)
   skin/classic/browser/places/calendar.png            (places/calendar.png)
 * skin/classic/browser/places/editBookmarkOverlay.css (places/editBookmarkOverlay.css)
   skin/classic/browser/places/livemark-item.png       (places/livemark-item.png)
   skin/classic/browser/places/star-icons.png          (places/star-icons.png)
   skin/classic/browser/places/starred48.png           (places/starred48.png)
   skin/classic/browser/places/unstarred48.png         (places/unstarred48.png)
   skin/classic/browser/places/places.css              (places/places.css)
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..2dc77f40822399d5dae035273db73b8b1d10ad61
GIT binary patch
literal 2905
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
DneU9e
--- a/browser/themes/osx/browser.css
+++ b/browser/themes/osx/browser.css
@@ -356,16 +356,78 @@ toolbarpaletteitem[place="palette"] > #p
     list-style-image: url("chrome://browser/skin/places/bookmarksToolbar-menuPanel@2x.png") !important;
   }
 
   #bookmarks-toolbar-placeholder > .toolbarbutton-icon {
     width: 16px;
   }
 }
 
+/* ----- BOOKMARK STAR ANIMATION ----- */
+
+@keyframes animation-bookmarkAdded {
+  from { transform: rotate(0deg) translateX(-20px) rotate(0deg) scale(1); opacity: 0; }
+  60%  { transform: rotate(180deg) translateX(-20px) rotate(-180deg) scale(2.2); opacity: 1; }
+  80%  { opacity: 1; }
+  to   { transform: rotate(180deg) translateX(-20px) rotate(-180deg) scale(1); opacity: 0; }
+}
+
+@keyframes animation-bookmarkAddedToBookmarksBar {
+  from { transform: rotate(0deg) translateX(-12px) rotate(0deg) scale(1); opacity: 0; }
+  60%  { transform: rotate(180deg) translateX(-12px) rotate(-180deg) scale(2.2); opacity: 1; }
+  80%  { opacity: 1; }
+  to   { transform: rotate(180deg) translateX(-12px) rotate(-180deg) scale(1); opacity: 0; }
+}
+
+@keyframes animation-bookmarkPulse {
+  from { transform: scale(1); }
+  50%  { transform: scale(1.3); }
+  to   { transform: scale(1); }
+}
+
+#bookmarked-notification-container {
+  min-height: 1px;
+  min-width: 1px;
+  height: 1px;
+  margin-bottom: -1px;
+  z-index: 5;
+  position: relative;
+}
+
+#bookmarked-notification {
+  background-size: 16px;
+  background-position: center;
+  background-repeat: no-repeat;
+  width: 16px;
+  height: 16px;
+  opacity: 0;
+}
+
+#bookmarked-notification-anchor[notification="finish"] > #bookmarked-notification {
+  background-image: url("chrome://browser/skin/places/bookmarks-notification-finish.png");
+  animation: animation-bookmarkAdded 800ms;
+  animation-timing-function: ease ease ease linear;
+}
+
+#bookmarked-notification-anchor[notification="finish"][in-bookmarks-toolbar=true] > #bookmarked-notification {
+  animation: animation-bookmarkAddedToBookmarksBar 800ms;
+}
+
+@media (min-resolution: 2dppx) {
+  #bookmarked-notification-anchor[notification="finish"] > #bookmarked-notification {
+    background-image: url("chrome://browser/skin/places/bookmarks-notification-finish@2x.png");
+  }
+}
+
+#bookmarks-menu-button[notification="finish"] > .toolbarbutton-menubutton-dropmarker > .dropmarker-icon {
+  animation: animation-bookmarkPulse 300ms;
+  animation-delay: 600ms;
+  animation-timing-function: ease-out;
+}
+
 /* ----- BOOKMARK MENUS ----- */
 
 .bookmark-item > .menu-iconic-left > .menu-iconic-icon {
   width: 16px;
   height: 16px;
 }
 
 #bookmarksToolbarFolderMenu,
--- a/browser/themes/osx/jar.mn
+++ b/browser/themes/osx/jar.mn
@@ -152,16 +152,18 @@ browser.jar:
   skin/classic/browser/places/allBookmarks.png              (places/allBookmarks.png)
 * skin/classic/browser/places/places.css                    (places/places.css)
 * skin/classic/browser/places/organizer.css                 (places/organizer.css)
   skin/classic/browser/places/query.png                     (places/query.png)
   skin/classic/browser/places/query@2x.png                  (places/query@2x.png)
   skin/classic/browser/places/bookmarksMenu.png             (places/bookmarksMenu.png)
   skin/classic/browser/places/bookmarksToolbar.png          (places/bookmarksToolbar.png)
   skin/classic/browser/places/bookmarksToolbar@2x.png       (places/bookmarksToolbar@2x.png)
+  skin/classic/browser/places/bookmarks-notification-finish.png  (places/bookmarks-notification-finish.png)
+  skin/classic/browser/places/bookmarks-notification-finish@2x.png  (places/bookmarks-notification-finish@2x.png)
   skin/classic/browser/places/bookmarksToolbar-menuPanel.png    (places/bookmarksToolbar-menuPanel.png)
   skin/classic/browser/places/bookmarksToolbar-menuPanel@2x.png (places/bookmarksToolbar-menuPanel@2x.png)
   skin/classic/browser/places/history.png                   (places/history.png)
   skin/classic/browser/places/history@2x.png                (places/history@2x.png)
   skin/classic/browser/places/star-icons.png                (places/star-icons.png)
   skin/classic/browser/places/star-icons@2x.png             (places/star-icons@2x.png)
   skin/classic/browser/places/toolbar.png                   (places/toolbar.png)
   skin/classic/browser/places/toolbarDropMarker.png         (places/toolbarDropMarker.png)
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..2dc77f40822399d5dae035273db73b8b1d10ad61
GIT binary patch
literal 2905
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
DneU9e
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..aa00fd0e520400658331fa85bbc2898a0dfa657e
GIT binary patch
literal 7523
zc$@)a9h~BcP)<h;3K|Lk000e1NJLTq004jh004jp1^@s6!#-il0012-Nkl<Zc-rk<
z2UJzZ7NrX!DqRo}5RoE+*gymUD*_^2O)>iak4EzoD@8<Uf?z>GDIzG=Xv9L-B;VvG
zE5_KO7)vw)B1MsoNI(Ca`!0ws{lQ3kk2`DaWrFzLo!R@GIdf+286hFAagA$S;~Lkv
z#<f1umR$hh72ksh3sL%$e&+Qv2y>$&z|AQGL}r}^@!4lU3L%4#MaUrrA*2x!vr|CS
zBe|3lf*>^mzyAu`Wq_#tS&(!{0a^PLP@9_u+D=)Zk1%k|fKm9f;rMro2x<Ft5F5MY
zDklO#X$~p%B1pItOR$dHIWTiMQMz>I?(B$Z5m)v&B<1<IXTt2c>0pR|KNKP3{0)ei
z?YzZ_fZyBP)J74<bm+&Bp9*^JSuJ)}TYmgX{ewMKsvjo8&_xG9b$)cS@v0M-T~Adm
zo0kD5<RS3)2e^C(qTC4baT~Y!lCaZx5NF-Kj(Zl^PTq9>{IJDGVcgb7V42VeR{NX5
zY)=Jfulx^;`ZVdWYjVYM6pRu6n<0*wAUW+o5$6T?-1YZIGSywqf%%Nc>-)8qod%1&
zFTnZ`fOQgp6~g);5Bk1Wz<BNHTkaW+F7~OQOTs|`iE$&zzt%6oxc)R4<C0wQZ{5$6
zE6w&)f(;SB|5aEYz&{bAR{sZPME;iKoCW6iZ-=25=#O4Ngd0)*w0>!H{aUW)V7f)<
zPv;E-erGIw>i#!_yn$JCH5jf=D{(zr`%jD+MxzMiXd2;0luxZ6MKIR+WceC{HRo!k
z#5cgy18)}qBLWo2XuoSPZRho`&<j}7^uc-oE*9WZ>z`q{<>L7<!T0{J^|uKC1!5NS
z9LB86dg_|;g1mqs>jk)2fX^a-vVLRdlNDtB)#hmZmPoGU{=ddl6p8_Qf$5RI9pk(J
z|6BjLF@bj(*KV2EF_0%P-BW{JAoB@&fq&S)j0HF^z$ezPfRVqk%gKt>`fIYPC+}@w
zEZZWn;~;Ng;CmION8LEu&I?F!UVuNWUy~w#%dm@ChJklrYSO!r&j<hoGigsXd4XcL
zl)5FH7vMMRPp({v)?a0|w;n8z%qfZQ4vPc4<j{<$?8E2<EZDpN=LPt|`lp8fk~uoy
z4p=7rJ@Od=pl~McX1qX=TWbAc^a2K4c91`;|4aSVSyiU7^*k~Y{ys<qdh0L4jNRD>
z(F;uBVu9Xk{rws1&w^=F!!M;9_}^w+xOu|+fdn)@s*1coA$oyNI4{t<t>4JyRMi(a
z_g79r>o-q)e*rKeKrf)X{#Tf}=T;Ku1$yb+kECe0WP#1pt-q$}Z~7g~(fTJRbPh;J
z<Dx3Ta9w7BTUz}h&I|Nj>;Jdj>a5C%F?Brh_H`ae1iIej1%5xkdI4@l(A%uvYU{5l
zdK+)SWOUhP`#KjSD3i!aiUkVXIWN%5tUnd4|8(^VwEl|m(RE;kWSQ>ke3+pZ&_ORS
zYtQfdoj5PhJ0gG9`mL?DT|TSpm(7r58s9Y_VU69z!~&1q(;7ZTFQCtPfgWnC^~<C6
zk8wF&L)KqDZciO!sgvTn4yFm@1uns?=-Y`-yi-GJoEPZkMn|-0d$!H`w_iCk3a#HX
zv1`e16@c+hj0HTi9=WGAe#Ci!9=3khGc})~^_Q9KegTuv`X|PBJtm<DMtc1Mvtn-V
zbINS<0^AAWZnpm6X#G~!JFcG6@x95AGI4MBfINZGj&jBexTQ6I*v<=Z?-6vb^*=}J
z&nz?ET?-SClnHyg4-?VbXs<&r5PLTsy})G73-pZjCu^_20Ta>s$M5Z4Oh5q`q8Bjq
zJeTX9-t+<M1qO3op!=+UDO&$CqsUqwDRJEo62XXd7h!hXy}h_1;JiRrHk;S1-zMVP
z*V;b6!FY82<KlV-^xdPkmtib$E(g89LfnniW4!>kBIxWkFN?8$IXb58c9v)TW6=7?
z?d>@MFhCJ#t^FCjz#p+}EHIS3fDPvb-mh82+!vl4c~8VTp->dbr<XhiAx2Qn-)86a
zueH3dGlOpu+p{16>ThlH0w19l7(++W@dZ;!yVH$g*auPSiwUN#IaXh5G$=2LE)a7%
z2jWP!BtjY?gOJ7I4fIa`P#joY478@Yo_VoK7p;Hv&gWo)7C1Jx=V5{(&|JfKf&KF`
z!4{vD-Y!<3n+nR<2YJLGwog*nM~OZsjP^uR=U*dwAWO6o4w@<so;p_Y&?dsEU<3Z#
zgi@|(`3?)dgQYgRemke-eGN?Fdk6Wg0?^x93Wi?i@)w+IS&wHD-Eqs<8pSYydDR?O
z2-u&I2o1#04hTbpFNM(oqUEsjuM&+#8(5;pQ6x<VI^EBMgIjv@$McSrc-rlM7(Q*+
z^@9_9Gybdp`H=@2%TpQ*BVT|qTEB5@F9M1{%lj&5FFjN=cHQYKmSI1ivW>qV>2RoU
zqwDF~r5@+O1uK)xLLpneCJddo2x9;%jDD@cFyDP&gvOU&h*o5Y9!^9%uta0$iD+|r
zYX=8qE@sjeIUarLWtaGHt4+k!Llf4gf3NrX(c2n}6N*(A#?~r&#DT&GhhWI3X)tWn
z&!87x!VI`kOz#3clVM~XXn5ZM)z8j@;-cdq?~wpQ7Q{BHf4sL;XW79&jn|&KWD)vf
z^6dEgQ4WU+{BaX&8IFM~3d5Ea#zZs(BRt&FDhy4gp3zHT3~m($q92aC7*3s{#6y<~
zW75t$!uUlZ^o8`ri2iS|Xd-$fOY|5b+W95Xj&7+9iye<X@tKqOaGTYR%SXohr2e3{
z;_w}fPxd`gSs435VSYTwe|Q*)P6xGB7eUkS4(Nmwf<Xix%ihz(8)US4qqyEjej8%h
zXT(3pM7O|b^i;ZGB{0JOPf+)~3M$LLMPZx(`S}T;ydbV=*hg_?BbOe0XtXBz*D0HS
zI5T_iz1<Fn9{W3=s{YL5T$?bgSYa5WFbrCS;lL|nql`aq6$TlfBpD+brRbDB_ugL+
z3$sAj0V`&q#Y*BA$xw7libRZPNA!3)B85cHM55=prq(Z+d!%^%oP<AjSZ%*_bevbp
ze|481xjTGO{8N<$v2_X_MD!6PIs?>J|AIu{MWTy9KcWT<k?1k(KpM4@O@m2@;WNa(
z){i*0AB-^SU{u&M7!mLgg>em3R(uDFpM1>|#{AeOwU6S;wLeYDF<O1{vU%|LDYN75
zMmrqL4|Gnh`W%Jf)=n6c+6jYV40RF(8YKxr3YVkej%gsW`p3Uf|IjW4M5t~ae&9fa
z3Vi{7p)nFY6^V9mO{rhvc%;~OPQrs7R@*Ke8|QWQC*98u-&0={UxIG0UY;TPC=6Me
z#S(oFMz$l`Fq$2ZxYr2ApBoA=+6!b%J8>KCX#s=CdKeX6iXI~eg>fB)@gGq5<Rr+=
zPXwj;vCV29##Lzl>p<?9)hDk`4!V$v`w%hqNhpkyj4;S!Oh;p!fP&CPzJ^iW1iiA<
z^iv?Z{QGwh0sdo=IcJ&5T5|N3EqZ`)?&-~LbM`$5o3t@26N$e23eg`O1!aclUy<lP
zU}R`9=tVq-(R-S~kUH0nXy*T05zV0utz*(o7`!ohDGaTETo~s08>oDK0TdP|Gr~|r
zVGR8+u9Ad-F~-l+cU|A-nq0Ze?K~b?<JCD*rrL)n0<kqebwC9F{0@X?o@Ojx7NL!*
zvYNB^?l$dzCg#Yy?*+vrXJP27OQ7L*A1#_CdUrF7j(*vJ*3Qt+80_Kjw-EgjZDSkD
z6U=Dz82Y>FK_|QnwETI(P+s;8$Sp_$)rB#Y=0V?{^+<1UV{0D{K|`iGiGN+|XdccP
z_{#%mY2t>Fsdf0zhtbMghOWH^x;v`DfF3NS9oPq?pL6Iww29+2F7zWCKobQa_wfnP
z|M#(M_td%ttS6A8GP!>#0Qx05X^RCD4eb+i0+bgWYZ)0<3ZqeZ2KXfg9NE1atSI!O
znm}p!1(@h}{#*BRU_rq<)kyyA!e2}S_)GmLDS_5+G1m9nUo`>>81nUZbKobC5OjAn
zfyNi-YMqWhTZ&VGHWkQT@%>*5K$4adZs|>ShW^*`b#^s_9)2JP{)0Yx0uA5WV1FP#
zkP2c`Rdgzi-w^=}ch6{<W4z^l&d5kq(rym?2tD)yS^+uWaJbkXQ_me1fCNneu9;vt
zW#|1LMr^O+eP9#^e#0n)e(;|aE~l#hfw=*i0tWo$6o5gX7zKf-m@$sWo&}HCTnM9f
z^FE9N9|1Yj+FA=1Q4cS;Wx`ZS40R|7q_rZu4AgSVXtp)q_3)x*crEjRI#C?>1==`u
z>xJZ2xt*^05?`UC0$1`KS_V?K2opj5*g}hA+|R!F)F>>cKy!N|@53WG^mY*WBZ7-s
z><>MSapoOUQ%8fA?oc9#^#Y>oKTyM|V4`bsm5;%eyi(1GCK!n#pumtr?*x&r8BzkX
z5(-bC>z_$`ICM}Ijas}zso+*GK)GYu2%?N2UbZwDi!KJ}h2&LeMl`p55r^J|5eSWt
zXD}na;H+C_i#>ZvS%LO->^o}xt&Mh%FleBx;2>s0C<CTApC}I*wK=a!V@EUdWg~WR
z=q=DfsE3row7AC^?wKufku#e2m5_W%yLTnO-IlO0egJI@4aN`D#$(TxPRGl_b%OJ1
zG$J?=^cwObiTpCKj(v0<$#-I_nkwNQfE31Y@AsyXGaU*^eJKJADyGi+>e+Vf;77H?
zBU%{cap*Z{BKgBY%3*5sqYECH%`R+tdu5E>q-K2G&^fQE;Rn&pTN3Q$OCwPPR*pxX
z?a~U$uUFr}6M^PV4&4qMceRiTu-KjZBa-h<uguZbFYk)H`p$hvac){;dlBf+3mS8e
zJd4&0dfcFno<NfmK@X5$3FeVG7d<kX=2M*{A|KJ8uDk1!W96&}T2Bg%a>)c6`@<!1
z!vhN%)gv$h#Rt=f;Lz0=j^nHvQUzu^b1ozK3z2+dI<`mT<As-^rV(9u;bl7!$dd?g
z&tUqTgHIFG0}Gpm(Zg}*S}^2S!=#9u>mC`6A0hc;=~Q+b`Q35b>1!g;#rnK6ZIhlH
z92QvAJZuL>q!Apt1W3M0NDWNbk#iHtUxehFuvgtmcN_U{5P<?E9Pj|itXcbukEjJc
zX;C8)aOhmH<eO~Ex$TkO_zw*9#<e3~%(N%&Ic7y5*-iwuGZKnUs0Ka-62UMIoddM~
zA)&Qkyfx>ZM_T>Ako@uNiy;GgjQlr<fL<EW$NaP1wD^M4DuE?16dzuV!}|frR}Osv
zMq6_pc%;^UhU8D^HRQiV1O}Lma<GXjNKpxV21B=FCpi2)>>%>%z%VQ~$35l6=d>~B
zz<c4N_mKaZ2<Ys&66K*$1ddiQk2946OF<PoLPFs14!|)}3atnIExC`}&encO<g>4S
z$@Dt%+lfGw6@fD5qYd3No1HDAAAO?~SOzNCA=T|1Is_am#n1-O4b3fZJM(-Ej_D*i
zr`fB?f0GC(HHj%o*D1U6E+__+g9>&|WgCb82NHn7mIlz-oLB66s>W+xI+)RxJYSIi
z@<2(wC|k*U2#H{RW|RBm$lRY40xMt$CxW+wA-@r{gY!yUlB<02<($bZ`2&%BalRnG
zwSWZWqbWPZiogg{mJ3XG=KLZbPzgh@vqQFWcm+5n3ZYFfA}F`i<z(e1Jbz)%)&bxz
z@>?6_qbWPZia>?()$=o&KEN#26}f;aP{s}`Z{?uOk}tod88ia(DxANr2&BU|bO?tI
z<4}1#z9YX)1Up|5fhy&#=Vvy4JZ^jLZ*qavpu~xQF$5xCJs_{j>3Df4Znax51vpZe
zA`jxz4nE=UDq`(KAkCyM)4*hYM&lCWZ8^Wo2GlSjPzvv(Kmo`h`D&Z;o;!Y3w%wU`
zeqWo&r>%Dks0E{h94i7bRs?DoAewrlHGE;TE$_bk<~mTsflv(Zdr$~#0ky!w+POzd
z<8W(z7M-&YIP%{h0;)BngPpX+`7T<;zpzOj1)vb#$AEnv9P|P#qVs=o&jPA2I-adC
zO5}?O32MVSL;aZBw$j1E?$DD+dG7};i~z#=7*GU*a0(n3a^nh~sc@yuH1v!zcBg9v
zmwakYyrR#lw9J_ipSNGeD<4Pz@?m`pD1d=M^`N`y`hD~OE_ApC$7X<8<UL_Q7XZDa
zFpQ3qnTFrUlG#`eayS@rVSNvSLh*!;@68govo97?**zSafs=RN7kB|k(Ha+3KgTfO
z%5|B5=ez;g()TbJ`>o`eUFUf0sW%mK!m*J>4<NWxz*xHNGmClic$7zGi<`FJjXas4
zMv%q9=#!v0VC_AyKUfq_MX@nHQnX5VEVx(BOo@3&0vJTKaGjGYm#cc;tQd%HaPXEs
z3KW2}*JGeq2;aIgql~HZ`3}6o&94pJn<xrEo~e|5xX53A?XAW^G!UVE6gUM)ZzzXJ
zVK>gZ(h+-%jVMMESOMS?M+}!b%DBrle$IiyT?5ws34_>Rs80gLFTJT2M*Cm6jxmuP
zTa{HxU<H8gjovI8iYd=2Q+NMyLdxe!-xdHu8e<@>4L9;ICUS3G3XT=jTSTbpIGt=k
zcQ?}{`07tmepNs*z`&5c3+c^x4!|qB!X>$K1y%i~7>QyfK^1^BZFYEMf#ax+SMNyq
z*MSTQKqjQ`VgUAEcFnCuiis#Tq8N!{B|%*Q$k3Za?y2=3tFOOVEEUw!r^#o4aX|ky
ze}XN>L=+oQj6|`LAg%yJDA9o(G{pJ>YZcbstnE)0(AR+i=;!?eFeW-qu@R-oDOM6V
z0Z@Vi6CDbe@EB`%s3>&6>N_CK&VhXzI0h0MssLl6A2BwXK`|1=N`klopiQ75xCvxB
zb6<YE#M(z79m)xS5x^$ALT2N&I~W_yWlHg2tR#?|Ky(-cH-W57w`FIDuP+0d0{U;}
zAQ9LC!`5dPyQenLxd2^?l>}}jz-|I*x@W?i(f*gOi2FU~4L~r5ehB%sH=k2%L{&Wn
zZd0h;CXmkjtQJ?z4L5ScHZ_72cANu%BuxdY?zB*BL@^S@N&>Yh)V2wPH)Xn>s$Qz>
zd9z$1h$FrYI2DSoc?2^P^Y>zmG>Kv*f!Y*emw<AV=)gM#{RXbiZj?aPO9pe0WIceG
zcNv&%%TA*hiDD&z+7x0pfs`oGVVhJKA+hQK(`m^d4h&EPqP{h>DRkM5cl)@eKy3=K
zn?S0V=rEtU=fP3YwZ+g61<)^uLn}l!;%#3WetS5dcRp|=Ed>Q;Q;6LJQm2YyCY!JQ
zAmUTWM7}*a;1XnY{>?b>jUXNbJ^nmm0r-}X=gl(L(>0$`tRzsILfD!9^tux!IvjL1
zUj1Fz?*&5u1%llU{9Y0N77+6(1MSeq`9=|sZx8S-Y82j3&j>-HhaiT}C9^ua0b`^M
zbXyC>OaieKY`+QQk=FQun$OKbk&Vrm{DAHSxmka-dY^I_7LZ#oegBh#?&*zRpeX<4
zc%m|V^wzv9Qr^W)LK_-Dyt|(bA&vqNUwI$q94y*Fu@Wu?m2fG@|C>U1%$ZgIgP8=!
z@zPbYYqD!ZsKea_mUv;Dc2qXyJ+;|abkse)eu*<L+NQEA6VT$!Q3y^B$I8QWx8!9@
z_&fz6oEpS}y74(t0K#jEz$*IB!xSsAn?n4*6lBi<(j`=5?GF`hmRfncrCSD;h{t(V
z$fp_<{qoB!;|i1U8r4s6nKjjcS5AF6Q&JsY$D&h8bQ%Y%aZJRMIWBfbOLuCA<lPhZ
zE<qtQLw7x|kas0ujC6rwC5o8@VpE7+0@5bXq*;jtF~TcznfXpEu#12LDNMw#e-5&~
z`PF98kJGTi)(RX{YpPbl*4mLnH`|}yQbLH~f73q2KsuF6Qx*QdsYhm$$Lxb8F&aU6
zIU?SrAhfB4`RuNIZXw@V(DA#HjpqX$C}yJANl=!6v<YNsw*B^5p>@xA9S-PZ1h!gy
zEeu@$xOQUH<8NHgJYR*yE2mL~61LQe{M=Nmsf(`F=0HJPggtRbwy-N!G+~7>i1i41
zbX5*k<@|8Q{wIlQ{&@w$UgbO?bj9=2v->r}P@fwG_?-*c^MU-oDa399X<&k5HYPfL
z74oS8Q5-<gfKEbStHsyB0H1<7lZgBuoUynP5<e5kHfGDLDByof^R}el{^qSUiUNO!
zgg`Y92a|BA?jeeIibs0=V(WcHM}}<5dqS>SXcKg9KLU9DkX?PV+UZ2;%DKGrfrBZA
z5`=SrBPqzz@V)*($hW@Jh-WMwKTpcLu+eDyql-=_tNieLY;o|$vL#j&k@x|v#E;#4
zu|w9x3W25us&*#J6lcV^fxH;S5g1RLq_{%JySh__K*6!(m$&h{x5A*-^MNP;{@oO6
zT>{em^=@ZsKT-6|E};P<;@$_sfkb=*^z$li)(_9S>Ug{&m~sp_Xye!l8%iwk67NF%
zUkE{t2^=ww!0PDMZWvcgjxI=*-H=};<Xr<o{yYNTt8Wy)tjvK~`wOCQ`WcU7r^MF+
zpf_D9qlFf!JMXKKl>=AbdM@-n#FNEuz~!0OQ_u;`yJdf@Y%3B!52N=9?EQGkNW4rN
z@xd26W&X6@-zJ3qY>9jgiYsuMv2i_9yAtD#b2956Kd1PE5yE>2Kxl0VSVrAHf=j_k
zSR+Uo$B=(>0h6Ox0i=VT<I$(<aL>D*8K8Frk9bVmdOicK;JiQP94?Kd0+jQ36*NaN
z_vEBvIW->fqMb$j8-yT2(+R~BY=v}9iZd{3pXq{ehsn<T@1?y9>V$miUJBt|`bzy=
zR{>@_?w-Wy#*`626#olA20ehzyyK-SWY*rRp@9+ZD458&1%y3Isft$a%%qYyT%OIR
zbA_~AV{ghB$UAP<b;Q4NxCOt1yh}SFP=RbM?4t<^!It6>qiv5aN_iJG3azhagh2i1
zaDUg~4PY^ONzRlF%mPKe7QmEfCI+B=08LB-JFECT$QSbK;6Q9dcvXPvro6)GiNyzS
zY4nj3uX3gydr`t*TDDQtJ}s%B+m}l1gdjoj23sUso8l0hmh34m(GSbJDuHo|(E0`t
z>VOEC>2Yl#*c~bfM(;+e067!@f1eCuD}t!vd}%gu=fhMX&ziSeL>uu{pyc=HnN@tz
zSMF)`OVAx!-~b!2b@GXLNg|%+=AQ1~YeJA=3T0zFLU9SjClsgXgg&}Oaf^^|)BhC$
z8HbYZgJP^0Lbrg?>0lWY0Dmt7DR^R*QnV5{Tl<O9hKGehKK0B%w85X}>WO$+-$xbZ
zF@>jbxwVWr9mBf_k;<gYQZ!DV*Bgj`ix5Qd`T9{jLU9S3UY<np%A7-`k(wC4h<IXp
z*|(8dKE5&rD16a1g?KEiI100tbRCQerGm+e^0feL_7{oZ&K51BRFFU;=j7_owS#kR
z^z$ri6!I)X(o1O_-=G$lpKBgda1uqb9KD6rYiZkFOZ?k}z#fCAtfDG&)SJ@F8BOl9
z4?c?;wdK+ELB0i5BA)0KJS%|yRM?RFWLiQ|G@hEaY^xEBedhaAuyODuO0$v|pi{v^
z9dTFCBcplt+^;Iu%|22QZhNF6Z0_+2FB}|KSK0-`gbPc2TiTW%h=09NT*77*`>|O?
zX2S`EKvRRQb8^k{IY-KaXC0~BW_P4K0NwsV^myY~@?}v#5;jRs1mi%+n2@W&4(V-`
zLMdt<j=_=+1v-e)C<;B={IKT*cXGC8pucSjh|f4t!}kXw%wltdu`i-{6T77S*|w=c
zlgW*+{YU7c@U`rDmp=}~OS}8c{JKW4ibZ)Om$nXs1VS1y2qBA*Lkva?ph22-8pIZV
z2O^8klnT_FG0T4d;d!Ybiv6Sh_eTtTTmP3_0Ku&k=JhiOFS`K3E4~Mj_Mo59-wE}B
t_NM+Lm~j!}8rQhSHLh`uYh3Gw_CJ1d9Waps8fgFk002ovPDHLkV1i}>Fw_75
--- a/browser/themes/shared/UITour.inc.css
+++ b/browser/themes/shared/UITour.inc.css
@@ -46,16 +46,17 @@
 #UITourTooltipDescription {
   -moz-margin-start: 0;
   -moz-margin-end: 0;
   font-size: 1.15rem;
   line-height: 1.8rem;
 }
 
 #UITourTooltipClose {
+  visibility: hidden; /* XXX Temporarily disabled by bug 966913 */
   -moz-appearance: none;
   border: none;
   background-color: transparent;
   min-width: 0;
   -moz-margin-start: 4px;
   -moz-margin-end: -10px;
   margin-top: -8px;
 }
--- a/browser/themes/shared/customizableui/customizeMode.inc.css
+++ b/browser/themes/shared/customizableui/customizeMode.inc.css
@@ -112,31 +112,32 @@
                     url("chrome://browser/skin/customizableui/background-noise-toolbar.png"),
                     linear-gradient(to bottom, #3e86ce, #3878ba);
   background-position: center top, left center, left top, left top, left top;
   background-repeat: no-repeat, no-repeat, repeat, repeat, no-repeat;
   background-size: auto 12px, 12px 100%, auto, auto, auto;
   background-attachment: scroll, scroll, fixed, fixed, scroll;
 }
 
-toolbarpaletteitem {
+toolbarpaletteitem[place="toolbar"] {
   transition: border-width 250ms ease-in-out;
 }
 
 toolbarpaletteitem[mousedown] {
   cursor: -moz-grabbing;
 }
 
 .panel-customization-placeholder,
 toolbarpaletteitem[place="palette"],
 toolbarpaletteitem[place="panel"] {
   transition: transform .3s ease-in-out;
 }
 
 toolbarpaletteitem[notransition].panel-customization-placeholder,
+toolbarpaletteitem[notransition][place="toolbar"],
 toolbarpaletteitem[notransition][place="palette"],
 toolbarpaletteitem[notransition][place="panel"] {
   transition: none;
 }
 
 toolbarpaletteitem > toolbarbutton > .toolbarbutton-icon,
 toolbarpaletteitem > toolbaritem.panel-wide-item,
 toolbarpaletteitem > toolbarbutton[type="menu-button"] {
--- a/browser/themes/windows/browser.css
+++ b/browser/themes/windows/browser.css
@@ -313,16 +313,61 @@ toolbarbutton.bookmark-item[open="true"]
   list-style-image: url("chrome://browser/skin/places/bookmarksToolbar.png") !important;
 }
 
 toolbarpaletteitem[place="palette"] > #personal-bookmarks > #bookmarks-toolbar-placeholder,
 #personal-bookmarks[cui-areatype="menu-panel"] > #bookmarks-toolbar-placeholder {
   list-style-image: url("chrome://browser/skin/places/bookmarksToolbar-menuPanel.png") !important;
 }
 
+/* ----- BOOKMARK STAR ANIMATION ----- */
+
+@keyframes animation-bookmarkAdded {
+  from { transform: rotate(0deg) translateX(-16px) rotate(0deg) scale(1); opacity: 0; }
+  60%  { transform: rotate(180deg) translateX(-16px) rotate(-180deg) scale(2.2); opacity: 1; }
+  80%  { opacity: 1; }
+  to   { transform: rotate(180deg) translateX(-16px) rotate(-180deg) scale(1); opacity: 0; }
+}
+
+@keyframes animation-bookmarkPulse {
+  from { transform: scale(1); }
+  50%  { transform: scale(1.3); }
+  to   { transform: scale(1); }
+}
+
+#bookmarked-notification-container {
+  min-height: 1px;
+  min-width: 1px;
+  height: 1px;
+  margin-bottom: -1px;
+  z-index: 5;
+  position: relative;
+}
+
+#bookmarked-notification {
+  background-size: 16px;
+  background-position: center;
+  background-repeat: no-repeat;
+  width: 16px;
+  height: 16px;
+  opacity: 0;
+}
+
+#bookmarked-notification-anchor[notification="finish"] > #bookmarked-notification {
+  background-image: url("chrome://browser/skin/places/bookmarks-notification-finish.png");
+  animation: animation-bookmarkAdded 800ms;
+  animation-timing-function: ease ease ease linear;
+}
+
+#bookmarks-menu-button[notification="finish"] > .toolbarbutton-menubutton-dropmarker > .dropmarker-icon {
+  animation: animation-bookmarkPulse 300ms;
+  animation-delay: 600ms;
+  animation-timing-function: ease-out;
+}
+
 /* ::::: bookmark menus ::::: */
 
 menu.bookmark-item,
 menuitem.bookmark-item {
   min-width: 0;
   max-width: 32em;
 }
 
--- a/browser/themes/windows/jar.mn
+++ b/browser/themes/windows/jar.mn
@@ -114,16 +114,17 @@ browser.jar:
         skin/classic/browser/newtab/controls.png                     (newtab/controls.png)
         skin/classic/browser/places/places.css                       (places/places.css)
 *       skin/classic/browser/places/organizer.css                    (places/organizer.css)
         skin/classic/browser/places/bookmark.png                     (places/bookmark.png)
         skin/classic/browser/places/query.png                        (places/query.png)
         skin/classic/browser/places/bookmarksMenu.png                (places/bookmarksMenu.png)
         skin/classic/browser/places/bookmarksToolbar.png             (places/bookmarksToolbar.png)
         skin/classic/browser/places/bookmarksToolbar-menuPanel.png   (places/bookmarksToolbar-menuPanel.png)
+        skin/classic/browser/places/bookmarks-notification-finish.png (places/bookmarks-notification-finish.png)
         skin/classic/browser/places/calendar.png                     (places/calendar.png)
         skin/classic/browser/places/toolbarDropMarker.png            (places/toolbarDropMarker.png)
         skin/classic/browser/places/editBookmarkOverlay.css          (places/editBookmarkOverlay.css)
         skin/classic/browser/places/libraryToolbar.png               (places/libraryToolbar.png)
         skin/classic/browser/places/starred48.png                    (places/starred48.png)
         skin/classic/browser/places/unstarred48.png                  (places/unstarred48.png)
         skin/classic/browser/places/tag.png                          (places/tag.png)
         skin/classic/browser/places/history.png                      (places/history.png)
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..2dc77f40822399d5dae035273db73b8b1d10ad61
GIT binary patch
literal 2905
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
DneU9e
--- a/js/src/builtin/TestingFunctions.cpp
+++ b/js/src/builtin/TestingFunctions.cpp
@@ -1400,21 +1400,17 @@ Neuter(JSContext *cx, unsigned argc, jsv
     args.rval().setUndefined();
     return true;
 }
 
 static bool
 WorkerThreadCount(JSContext *cx, unsigned argc, jsval *vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
-#ifdef JS_THREADSAFE
-    args.rval().setInt32(cx->runtime()->useHelperThreads() ? WorkerThreadState().threadCount : 0);
-#else
-    args.rval().setInt32(0);
-#endif
+    args.rval().setNumber(static_cast<double>(cx->runtime()->workerThreadCount()));
     return true;
 }
 
 static const JSFunctionSpecWithHelp TestingFunctions[] = {
     JS_FN_HELP("gc", ::GC, 0, 0,
 "gc([obj] | 'compartment')",
 "  Run the garbage collector. When obj is given, GC only its compartment.\n"
 "  If 'compartment' is given, GC any compartments that were scheduled for\n"
--- a/js/src/jit/AsmJS.cpp
+++ b/js/src/jit/AsmJS.cpp
@@ -5359,75 +5359,74 @@ CheckFunctionsSequential(ModuleCompiler 
 
 #ifdef JS_THREADSAFE
 
 // Currently, only one asm.js parallel compilation is allowed at a time.
 // This RAII class attempts to claim this parallel compilation using atomic ops
 // on rt->workerThreadState->asmJSCompilationInProgress.
 class ParallelCompilationGuard
 {
-    bool parallelState_;
+    WorkerThreadState *parallelState_;
   public:
-    ParallelCompilationGuard() : parallelState_(false) {}
+    ParallelCompilationGuard() : parallelState_(nullptr) {}
     ~ParallelCompilationGuard() {
         if (parallelState_) {
-            JS_ASSERT(WorkerThreadState().asmJSCompilationInProgress == true);
-            WorkerThreadState().asmJSCompilationInProgress = false;
+            JS_ASSERT(parallelState_->asmJSCompilationInProgress == true);
+            parallelState_->asmJSCompilationInProgress = false;
         }
     }
-    bool claim() {
+    bool claim(WorkerThreadState *state) {
         JS_ASSERT(!parallelState_);
-        if (!WorkerThreadState().asmJSCompilationInProgress.compareExchange(false, true))
+        if (!state->asmJSCompilationInProgress.compareExchange(false, true))
             return false;
-        parallelState_ = true;
+        parallelState_ = state;
         return true;
     }
 };
 
 static bool
 ParallelCompilationEnabled(ExclusiveContext *cx)
 {
     // If 'cx' isn't a JSContext, then we are already off the main thread so
     // off-thread compilation must be enabled. However, since there are a fixed
     // number of worker threads and one is already being consumed by this
     // parsing task, ensure that there another free thread to avoid deadlock.
     // (Note: there is at most one thread used for parsing so we don't have to
     // worry about general dining philosophers.)
-    if (WorkerThreadState().threadCount <= 1)
-        return false;
-
     if (!cx->isJSContext())
-        return true;
+        return cx->workerThreadState()->numThreads > 1;
+
     return cx->asJSContext()->runtime()->canUseParallelIonCompilation();
 }
 
 // State of compilation as tracked and updated by the main thread.
 struct ParallelGroupState
 {
+    WorkerThreadState &state;
     js::Vector<AsmJSParallelTask> &tasks;
     int32_t outstandingJobs; // Good work, jobs!
     uint32_t compiledJobs;
 
-    ParallelGroupState(js::Vector<AsmJSParallelTask> &tasks)
-      : tasks(tasks), outstandingJobs(0), compiledJobs(0)
+    ParallelGroupState(WorkerThreadState &state, js::Vector<AsmJSParallelTask> &tasks)
+      : state(state), tasks(tasks), outstandingJobs(0), compiledJobs(0)
     { }
 };
 
 // Block until a worker-assigned LifoAlloc becomes finished.
 static AsmJSParallelTask *
 GetFinishedCompilation(ModuleCompiler &m, ParallelGroupState &group)
 {
-    AutoLockWorkerThreadState lock;
-
-    while (!WorkerThreadState().asmJSWorkerFailed()) {
-        if (!WorkerThreadState().asmJSFinishedList().empty()) {
+    AutoLockWorkerThreadState lock(*m.cx()->workerThreadState());
+
+    while (!group.state.asmJSWorkerFailed()) {
+        if (!group.state.asmJSFinishedList.empty()) {
             group.outstandingJobs--;
-            return WorkerThreadState().asmJSFinishedList().popCopy();
+            return group.state.asmJSFinishedList.popCopy();
         }
-        WorkerThreadState().wait(GlobalWorkerThreadState::CONSUMER);
+        group.state.wait(WorkerThreadState::CONSUMER);
     }
 
     return nullptr;
 }
 
 static bool
 GenerateCodeForFinishedJob(ModuleCompiler &m, ParallelGroupState &group, AsmJSParallelTask **outTask)
 {
@@ -5467,39 +5466,34 @@ GetUnusedTask(ParallelGroupState &group,
         return false;
     *outTask = &group.tasks[i];
     return true;
 }
 
 static bool
 CheckFunctionsParallelImpl(ModuleCompiler &m, ParallelGroupState &group)
 {
-#ifdef DEBUG
-    {
-        AutoLockWorkerThreadState lock;
-        JS_ASSERT(WorkerThreadState().asmJSWorklist().empty());
-        JS_ASSERT(WorkerThreadState().asmJSFinishedList().empty());
-    }
-#endif
-    WorkerThreadState().resetAsmJSFailureState();
+    JS_ASSERT(group.state.asmJSWorklist.empty());
+    JS_ASSERT(group.state.asmJSFinishedList.empty());
+    group.state.resetAsmJSFailureState();
 
     for (unsigned i = 0; PeekToken(m.parser()) == TOK_FUNCTION; i++) {
         // Get exclusive access to an empty LifoAlloc from the thread group's pool.
         AsmJSParallelTask *task = nullptr;
         if (!GetUnusedTask(group, i, &task) && !GenerateCodeForFinishedJob(m, group, &task))
             return false;
 
         // Generate MIR into the LifoAlloc on the main thread.
         MIRGenerator *mir;
         ModuleCompiler::Func *func;
         if (!CheckFunction(m, task->lifo, &mir, &func))
             return false;
 
         // Perform optimizations and LIR generation on a worker thread.
-        task->init(m.cx()->compartment()->runtimeFromAnyThread(), func, mir);
+        task->init(func, mir);
         if (!StartOffThreadAsmJSCompile(m.cx(), task))
             return false;
 
         group.outstandingJobs++;
     }
 
     // Block for all outstanding workers to complete.
     while (group.outstandingJobs > 0) {
@@ -5508,101 +5502,97 @@ CheckFunctionsParallelImpl(ModuleCompile
             return false;
     }
 
     if (!CheckAllFunctionsDefined(m))
         return false;
 
     JS_ASSERT(group.outstandingJobs == 0);
     JS_ASSERT(group.compiledJobs == m.numFunctions());
-#ifdef DEBUG
-    {
-        AutoLockWorkerThreadState lock;
-        JS_ASSERT(WorkerThreadState().asmJSWorklist().empty());
-        JS_ASSERT(WorkerThreadState().asmJSFinishedList().empty());
-    }
-#endif
-    JS_ASSERT(!WorkerThreadState().asmJSWorkerFailed());
+    JS_ASSERT(group.state.asmJSWorklist.empty());
+    JS_ASSERT(group.state.asmJSFinishedList.empty());
+    JS_ASSERT(!group.state.asmJSWorkerFailed());
     return true;
 }
 
 static void
 CancelOutstandingJobs(ModuleCompiler &m, ParallelGroupState &group)
 {
     // This is failure-handling code, so it's not allowed to fail.
     // The problem is that all memory for compilation is stored in LifoAllocs
     // maintained in the scope of CheckFunctionsParallel() -- so in order
     // for that function to safely return, and thereby remove the LifoAllocs,
     // none of that memory can be in use or reachable by workers.
 
     JS_ASSERT(group.outstandingJobs >= 0);
     if (!group.outstandingJobs)
         return;
 
-    AutoLockWorkerThreadState lock;
+    AutoLockWorkerThreadState lock(*m.cx()->workerThreadState());
 
     // From the compiling tasks, eliminate those waiting for worker assignation.
-    group.outstandingJobs -= WorkerThreadState().asmJSWorklist().length();
-    WorkerThreadState().asmJSWorklist().clear();
+    group.outstandingJobs -= group.state.asmJSWorklist.length();
+    group.state.asmJSWorklist.clear();
 
     // From the compiling tasks, eliminate those waiting for codegen.
-    group.outstandingJobs -= WorkerThreadState().asmJSFinishedList().length();
-    WorkerThreadState().asmJSFinishedList().clear();
+    group.outstandingJobs -= group.state.asmJSFinishedList.length();
+    group.state.asmJSFinishedList.clear();
 
     // Eliminate tasks that failed without adding to the finished list.
-    group.outstandingJobs -= WorkerThreadState().harvestFailedAsmJSJobs();
+    group.outstandingJobs -= group.state.harvestFailedAsmJSJobs();
 
     // Any remaining tasks are therefore undergoing active compilation.
     JS_ASSERT(group.outstandingJobs >= 0);
     while (group.outstandingJobs > 0) {
-        WorkerThreadState().wait(GlobalWorkerThreadState::CONSUMER);
-
-        group.outstandingJobs -= WorkerThreadState().harvestFailedAsmJSJobs();
-        group.outstandingJobs -= WorkerThreadState().asmJSFinishedList().length();
-        WorkerThreadState().asmJSFinishedList().clear();
+        group.state.wait(WorkerThreadState::CONSUMER);
+
+        group.outstandingJobs -= group.state.harvestFailedAsmJSJobs();
+        group.outstandingJobs -= group.state.asmJSFinishedList.length();
+        group.state.asmJSFinishedList.clear();
     }
 
     JS_ASSERT(group.outstandingJobs == 0);
-    JS_ASSERT(WorkerThreadState().asmJSWorklist().empty());
-    JS_ASSERT(WorkerThreadState().asmJSFinishedList().empty());
+    JS_ASSERT(group.state.asmJSWorklist.empty());
+    JS_ASSERT(group.state.asmJSFinishedList.empty());
 }
 
 static const size_t LIFO_ALLOC_PARALLEL_CHUNK_SIZE = 1 << 12;
 
 static bool
 CheckFunctionsParallel(ModuleCompiler &m)
 {
     // If parallel compilation isn't enabled (not enough cores, disabled by
     // pref, etc) or another thread is currently compiling asm.js in parallel,
     // fall back to sequential compilation. (We could lift the latter
     // constraint by hoisting asmJS* state out of WorkerThreadState so multiple
     // concurrent asm.js parallel compilations don't race.)
     ParallelCompilationGuard g;
-    if (!ParallelCompilationEnabled(m.cx()) || !g.claim())
+    if (!ParallelCompilationEnabled(m.cx()) || !g.claim(m.cx()->workerThreadState()))
         return CheckFunctionsSequential(m);
 
     // Saturate all worker threads plus the main thread.
-    size_t numParallelJobs = WorkerThreadState().threadCount + 1;
+    WorkerThreadState &state = *m.cx()->workerThreadState();
+    size_t numParallelJobs = state.numThreads + 1;
 
     // Allocate scoped AsmJSParallelTask objects. Each contains a unique
     // LifoAlloc that provides all necessary memory for compilation.
     js::Vector<AsmJSParallelTask, 0> tasks(m.cx());
     if (!tasks.initCapacity(numParallelJobs))
         return false;
 
     for (size_t i = 0; i < numParallelJobs; i++)
         tasks.infallibleAppend(LIFO_ALLOC_PARALLEL_CHUNK_SIZE);
 
     // With compilation memory in-scope, dispatch worker threads.
-    ParallelGroupState group(tasks);
+    ParallelGroupState group(state, tasks);
     if (!CheckFunctionsParallelImpl(m, group)) {
         CancelOutstandingJobs(m, group);
 
         // If failure was triggered by a worker thread, report error.
-        if (void *maybeFunc = WorkerThreadState().maybeAsmJSFailedFunction()) {
+        if (void *maybeFunc = state.maybeAsmJSFailedFunction()) {
             ModuleCompiler::Func *func = reinterpret_cast<ModuleCompiler::Func *>(maybeFunc);
             return m.failOffset(func->srcOffset(), "allocation failure during compilation");
         }
 
         // Otherwise, the error occurred on the main thread and was already reported.
         return false;
     }
     return true;
--- a/js/src/jit/Ion.cpp
+++ b/js/src/jit/Ion.cpp
@@ -508,30 +508,25 @@ jit::FinishOffThreadBuilder(IonBuilder *
     // destroy the builder and all other data accumulated during compilation,
     // except any final codegen (which includes an assembler and needs to be
     // explicitly destroyed).
     js_delete(builder->backgroundCodegen());
     js_delete(builder->alloc().lifoAlloc());
 }
 
 static inline void
-FinishAllOffThreadCompilations(JSCompartment *comp)
+FinishAllOffThreadCompilations(JitCompartment *ion)
 {
-#ifdef JS_THREADSAFE
-    AutoLockWorkerThreadState lock;
-    GlobalWorkerThreadState::IonBuilderVector &finished = WorkerThreadState().ionFinishedList();
-
-    for (size_t i = 0; i < finished.length(); i++) {
-        IonBuilder *builder = finished[i];
-        if (builder->compartment == CompileCompartment::get(comp)) {
-            FinishOffThreadBuilder(builder);
-            WorkerThreadState().remove(finished, &i);
-        }
+    OffThreadCompilationVector &compilations = ion->finishedOffThreadCompilations();
+
+    for (size_t i = 0; i < compilations.length(); i++) {
+        IonBuilder *builder = compilations[i];
+        FinishOffThreadBuilder(builder);
     }
-#endif
+    compilations.clear();
 }
 
 /* static */ void
 JitRuntime::Mark(JSTracer *trc)
 {
     JS_ASSERT(!trc->runtime->isHeapMinorCollecting());
     Zone *zone = trc->runtime->atomsCompartment()->zone();
     for (gc::CellIterUnderGC i(zone, gc::FINALIZE_JITCODE); !i.done(); i.next()) {
@@ -543,17 +538,17 @@ JitRuntime::Mark(JSTracer *trc)
 void
 JitCompartment::mark(JSTracer *trc, JSCompartment *compartment)
 {
     // Cancel any active or pending off thread compilations. Note that the
     // MIR graph does not hold any nursery pointers, so there's no need to
     // do this for minor GCs.
     JS_ASSERT(!trc->runtime->isHeapMinorCollecting());
     CancelOffThreadIonCompile(compartment, nullptr);
-    FinishAllOffThreadCompilations(compartment);
+    FinishAllOffThreadCompilations(this);
 
     // Free temporary OSR buffer.
     rt->freeOsrTempData();
 }
 
 void
 JitCompartment::sweep(FreeOp *fop)
 {
@@ -1512,97 +1507,79 @@ CompileBackEnd(MIRGenerator *mir, MacroA
     return GenerateCode(mir, lir, maybeMasm);
 }
 
 void
 AttachFinishedCompilations(JSContext *cx)
 {
 #ifdef JS_THREADSAFE
     JitCompartment *ion = cx->compartment()->jitCompartment();
-    if (!ion)
+    if (!ion || !cx->runtime()->workerThreadState)
         return;
 
     types::AutoEnterAnalysis enterTypes(cx);
-    AutoLockWorkerThreadState lock;
-
-    GlobalWorkerThreadState::IonBuilderVector &finished = WorkerThreadState().ionFinishedList();
-
-    // Incorporate any off thread compilations for the compartment which have
-    // finished, failed or have been cancelled.
-    while (true) {
-        IonBuilder *builder = nullptr;
-
-        // Find a finished builder for the compartment.
-        for (size_t i = 0; i < finished.length(); i++) {
-            IonBuilder *testBuilder = finished[i];
-            if (testBuilder->compartment == CompileCompartment::get(cx->compartment())) {
-                builder = testBuilder;
-                WorkerThreadState().remove(finished, &i);
-                break;
-            }
-        }
-        if (!builder)
-            break;
+    AutoLockWorkerThreadState lock(*cx->runtime()->workerThreadState);
+
+    OffThreadCompilationVector &compilations = ion->finishedOffThreadCompilations();
+
+    // Incorporate any off thread compilations which have finished, failed or
+    // have been cancelled.
+    while (!compilations.empty()) {
+        IonBuilder *builder = compilations.popCopy();
 
         if (CodeGenerator *codegen = builder->backgroundCodegen()) {
             RootedScript script(cx, builder->script());
             IonContext ictx(cx, &builder->alloc());
 
             // Root the assembler until the builder is finished below. As it
             // was constructed off thread, the assembler has not been rooted
             // previously, though any GC activity would discard the builder.
             codegen->masm.constructRoot(cx);
 
             bool success;
             {
                 // Release the worker thread lock and root the compiler for GC.
                 AutoTempAllocatorRooter root(cx, &builder->alloc());
-                AutoUnlockWorkerThreadState unlock;
+                AutoUnlockWorkerThreadState unlock(cx->runtime());
                 AutoFlushCache afc("AttachFinishedCompilations", cx->runtime()->jitRuntime());
                 success = codegen->link(cx, builder->constraints());
             }
 
             if (!success) {
                 // Silently ignore OOM during code generation, we're at an
                 // operation callback and can't propagate failures.
                 cx->clearPendingException();
             }
         } else {
             if (builder->abortReason() == AbortReason_Disable)
                 SetIonScript(builder->script(), builder->info().executionMode(), ION_DISABLED_SCRIPT);
         }
 
         FinishOffThreadBuilder(builder);
     }
+
+    compilations.clear();
 #endif
 }
 
 static const size_t BUILDER_LIFO_ALLOC_PRIMARY_CHUNK_SIZE = 1 << 12;
 
 static inline bool
 OffThreadCompilationAvailable(JSContext *cx)
 {
-#ifdef JS_THREADSAFE
     // Even if off thread compilation is enabled, compilation must still occur
     // on the main thread in some cases. Do not compile off thread during an
     // incremental GC, as this may trip incremental read barriers.
     //
-    // Require cpuCount > 1 so that Ion compilation jobs and main-thread
-    // execution are not competing for the same resources.
-    //
     // Skip off thread compilation if PC count profiling is enabled, as
     // CodeGenerator::maybeCreateScriptCounts will not attach script profiles
     // when running off thread.
     return cx->runtime()->canUseParallelIonCompilation()
-        && WorkerThreadState().cpuCount > 1
         && cx->runtime()->gcIncrementalState == gc::NO_INCREMENTAL
         && !cx->runtime()->profilingScripts;
-#else
-    return false;
-#endif
 }
 
 static void
 TrackAllProperties(JSContext *cx, JSObject *obj)
 {
     JS_ASSERT(obj->hasSingletonType());
 
     for (Shape::Range<NoGC> range(obj->lastProperty()); !range.empty(); range.popFront())
@@ -1850,22 +1827,17 @@ CheckScriptSize(JSContext *cx, JSScript*
         }
 
         return Method_Compiled;
     }
 
     if (script->length() > MAX_MAIN_THREAD_SCRIPT_SIZE ||
         numLocalsAndArgs > MAX_MAIN_THREAD_LOCALS_AND_ARGS)
     {
-#ifdef JS_THREADSAFE
-        size_t cpuCount = WorkerThreadState().cpuCount;
-#else
-        size_t cpuCount = 1;
-#endif
-        if (cx->runtime()->canUseParallelIonCompilation() && cpuCount > 1) {
+        if (cx->runtime()->canUseParallelIonCompilation()) {
             // Even if off thread compilation is enabled, there are cases where
             // compilation must still occur on the main thread. Don't compile
             // in these cases (except when profiling scripts, as compilations
             // occurring with profiling should reflect those without), but do
             // not forbid compilation so that the script may be compiled later.
             if (!OffThreadCompilationAvailable(cx) && !cx->runtime()->profilingScripts) {
                 IonSpew(IonSpew_Abort,
                         "Script too large for main thread, skipping (%u bytes) (%u locals/args)",
@@ -2512,17 +2484,17 @@ InvalidateActivation(FreeOp *fop, uint8_
 }
 
 void
 jit::StopAllOffThreadCompilations(JSCompartment *comp)
 {
     if (!comp->jitCompartment())
         return;
     CancelOffThreadIonCompile(comp, nullptr);
-    FinishAllOffThreadCompilations(comp);
+    FinishAllOffThreadCompilations(comp->jitCompartment());
 }
 
 void
 jit::InvalidateAll(FreeOp *fop, Zone *zone)
 {
     for (CompartmentsInZoneIter comp(zone); !comp.done(); comp.next())
         StopAllOffThreadCompilations(comp);
 
--- a/js/src/jit/JitCompartment.h
+++ b/js/src/jit/JitCompartment.h
@@ -54,16 +54,18 @@ struct EnterJitData
 };
 
 typedef void (*EnterJitCode)(void *code, unsigned argc, Value *argv, StackFrame *fp,
                              CalleeToken calleeToken, JSObject *scopeChain,
                              size_t numStackValues, Value *vp);
 
 class IonBuilder;
 
+typedef Vector<IonBuilder*, 0, SystemAllocPolicy> OffThreadCompilationVector;
+
 // ICStubSpace is an abstraction for allocation policy and storage for stub data.
 // There are two kinds of stubs: optimized stubs and fallback stubs (the latter
 // also includes stubs that can make non-tail calls that can GC).
 //
 // Optimized stubs are allocated per-compartment and are always purged when
 // JIT-code is discarded. Fallback stubs are allocated per BaselineScript and
 // are only destroyed when the BaselineScript is destroyed.
 class ICStubSpace
@@ -325,16 +327,22 @@ class JitRuntime
 
 class JitCompartment
 {
     friend class JitActivation;
 
     // Ion state for the compartment's runtime.
     JitRuntime *rt;
 
+    // Any scripts for which off thread compilation has successfully finished,
+    // failed, or been cancelled. All off thread compilations which are started
+    // will eventually appear in this list asynchronously. Protected by the
+    // runtime's analysis lock.
+    OffThreadCompilationVector finishedOffThreadCompilations_;
+
     // Map ICStub keys to ICStub shared code objects.
     typedef WeakValueCache<uint32_t, ReadBarriered<JitCode> > ICStubCodeMap;
     ICStubCodeMap *stubCodes_;
 
     // Keep track of offset into various baseline stubs' code at return
     // point from called script.
     void *baselineCallReturnAddr_;
     void *baselineGetPropReturnAddr_;
@@ -348,16 +356,20 @@ class JitCompartment
     // pointers. This has to be a weak pointer to avoid keeping the whole
     // compartment alive.
     ReadBarriered<JitCode> stringConcatStub_;
     ReadBarriered<JitCode> parallelStringConcatStub_;
 
     JitCode *generateStringConcatStub(JSContext *cx, ExecutionMode mode);
 
   public:
+    OffThreadCompilationVector &finishedOffThreadCompilations() {
+        return finishedOffThreadCompilations_;
+    }
+
     JitCode *getStubCode(uint32_t key) {
         ICStubCodeMap::AddPtr p = stubCodes_->lookupForAdd(key);
         if (p)
             return p->value();
         return nullptr;
     }
     bool putStubCode(uint32_t key, Handle<JitCode *> stubCode) {
         // Make sure to do a lookupForAdd(key) and then insert into that slot, because
--- a/js/src/jsapi.cpp
+++ b/js/src/jsapi.cpp
@@ -4524,17 +4524,17 @@ JS::FinishOffThreadScript(JSContext *may
 {
 #ifdef JS_THREADSAFE
     JS_ASSERT(CurrentThreadCanAccessRuntime(rt));
 
     Maybe<AutoLastFrameCheck> lfc;
     if (maybecx)
         lfc.construct(maybecx);
 
-    return WorkerThreadState().finishParseTask(maybecx, rt, token);
+    return rt->workerThreadState->finishParseTask(maybecx, rt, token);
 #else
     MOZ_ASSUME_UNREACHABLE("Off thread compilation is not available.");
 #endif
 }
 
 JS_PUBLIC_API(JSScript *)
 JS_CompileScript(JSContext *cx, JS::HandleObject obj, const char *ascii,
                  size_t length, const JS::CompileOptions &options)
--- a/js/src/jscntxt.cpp
+++ b/js/src/jscntxt.cpp
@@ -1047,16 +1047,19 @@ js_HandleExecutionInterrupt(JSContext *c
 }
 
 js::ThreadSafeContext::ThreadSafeContext(JSRuntime *rt, PerThreadData *pt, ContextKind kind)
   : ContextFriendFields(rt),
     contextKind_(kind),
     perThreadData(pt),
     allocator_(nullptr)
 {
+#ifdef JS_THREADSAFE
+    JS_ASSERT_IF(kind == Context_Exclusive, rt->workerThreadState != nullptr);
+#endif
 }
 
 bool
 ThreadSafeContext::isForkJoinContext() const
 {
     return contextKind_ == Context_ForkJoin;
 }
 
--- a/js/src/jscntxt.h
+++ b/js/src/jscntxt.h
@@ -277,16 +277,18 @@ struct ThreadSafeContext : ContextFriend
 
     // Accessors for immutable runtime data.
     JSAtomState &names() { return runtime_->atomState; }
     StaticStrings &staticStrings() { return runtime_->staticStrings; }
     const JS::AsmJSCacheOps &asmJSCacheOps() { return runtime_->asmJSCacheOps; }
     PropertyName *emptyString() { return runtime_->emptyString; }
     FreeOp *defaultFreeOp() { return runtime_->defaultFreeOp(); }
     bool useHelperThreads() { return runtime_->useHelperThreads(); }
+    unsigned cpuCount() { return runtime_->cpuCount(); }
+    size_t workerThreadCount() { return runtime_->workerThreadCount(); }
     void *runtimeAddressForJit() { return runtime_; }
     void *stackLimitAddress(StackKind kind) { return &runtime_->mainThread.nativeStackLimit[kind]; }
     void *stackLimitAddressForJitCode(StackKind kind);
     size_t gcSystemPageSize() { return runtime_->gcSystemPageSize; }
     bool signalHandlersInstalled() const { return runtime_->signalHandlersInstalled(); }
     bool jitSupportsFloatingPoint() const { return runtime_->jitSupportsFloatingPoint; }
 
     // Thread local data that may be accessed freely.
@@ -383,16 +385,25 @@ class ExclusiveContext : public ThreadSa
     }
     JSCompartment *atomsCompartment() {
         return runtime_->atomsCompartment();
     }
     ScriptDataTable &scriptDataTable() {
         return runtime_->scriptDataTable();
     }
 
+#ifdef JS_THREADSAFE
+    // Since JSRuntime::workerThreadState is necessarily initialized from the
+    // main thread before the first worker thread can access it, there is no
+    // possibility for a race read/writing it.
+    WorkerThreadState *workerThreadState() {
+        return runtime_->workerThreadState;
+    }
+#endif
+
     // Methods specific to any WorkerThread for the context.
     frontend::CompileError &addPendingCompileError();
     void addPendingOverRecursed();
 };
 
 inline void
 MaybeCheckStackRoots(ExclusiveContext *cx)
 {
@@ -1024,17 +1035,17 @@ bool intrinsic_NewParallelArray(JSContex
 class AutoLockForExclusiveAccess
 {
 #ifdef JS_THREADSAFE
     JSRuntime *runtime;
 
     void init(JSRuntime *rt) {
         runtime = rt;
         if (runtime->numExclusiveThreads) {
-            runtime->assertCanLock(ExclusiveAccessLock);
+            runtime->assertCanLock(JSRuntime::ExclusiveAccessLock);
             PR_Lock(runtime->exclusiveAccessLock);
 #ifdef DEBUG
             runtime->exclusiveAccessOwner = PR_GetCurrentThread();
 #endif
         } else {
             JS_ASSERT(!runtime->mainThreadHasExclusiveAccess);
             runtime->mainThreadHasExclusiveAccess = true;
         }
@@ -1079,17 +1090,17 @@ class AutoLockForExclusiveAccess
 class AutoLockForCompilation
 {
 #ifdef JS_THREADSAFE
     JSRuntime *runtime;
 
     void init(JSRuntime *rt) {
         runtime = rt;
         if (runtime->numCompilationThreads) {
-            runtime->assertCanLock(CompilationLock);
+            runtime->assertCanLock(JSRuntime::CompilationLock);
             PR_Lock(runtime->compilationLock);
 #ifdef DEBUG
             runtime->compilationLockOwner = PR_GetCurrentThread();
 #endif
         } else {
 #ifdef DEBUG
             JS_ASSERT(!runtime->mainThreadHasCompilationLock);
             runtime->mainThreadHasCompilationLock = true;
--- a/js/src/jsgc.cpp
+++ b/js/src/jsgc.cpp
@@ -1708,19 +1708,19 @@ ArenaLists::refillFreeList(ThreadSafeCon
              * return whatever value we get. If we aren't in a ForkJoin
              * session (i.e. we are in a worker thread async with the main
              * thread), we need to first ensure the main thread is not in a GC
              * session.
              */
             mozilla::Maybe<AutoLockWorkerThreadState> lock;
             JSRuntime *rt = zone->runtimeFromAnyThread();
             if (rt->exclusiveThreadsPresent()) {
-                lock.construct();
+                lock.construct<WorkerThreadState &>(*rt->workerThreadState);
                 while (rt->isHeapBusy())
-                    WorkerThreadState().wait(GlobalWorkerThreadState::PRODUCER);
+                    rt->workerThreadState->wait(WorkerThreadState::PRODUCER);
             }
 
             void *thing = cx->allocator()->arenas.allocateFromArenaInline(zone, thingKind);
             if (thing)
                 return thing;
 #else
             MOZ_CRASH();
 #endif
@@ -4295,37 +4295,37 @@ AutoTraceSession::AutoTraceSession(JSRun
     // this lock during GC and the other thread is waiting, make sure we hold
     // the exclusive access lock during GC sessions.
     JS_ASSERT(rt->currentThreadHasExclusiveAccess());
 
     if (rt->exclusiveThreadsPresent()) {
         // Lock the worker thread state when changing the heap state in the
         // presence of exclusive threads, to avoid racing with refillFreeList.
 #ifdef JS_THREADSAFE
-        AutoLockWorkerThreadState lock;
+        AutoLockWorkerThreadState lock(*rt->workerThreadState);
         rt->heapState = heapState;
 #else
         MOZ_CRASH();
 #endif
     } else {
         rt->heapState = heapState;
     }
 }
 
 AutoTraceSession::~AutoTraceSession()
 {
     JS_ASSERT(runtime->isHeapBusy());
 
     if (runtime->exclusiveThreadsPresent()) {
 #ifdef JS_THREADSAFE
-        AutoLockWorkerThreadState lock;
+        AutoLockWorkerThreadState lock(*runtime->workerThreadState);
         runtime->heapState = prevState;
 
         // Notify any worker threads waiting for the trace session to end.
-        WorkerThreadState().notifyAll(GlobalWorkerThreadState::PRODUCER);
+        runtime->workerThreadState->notifyAll(WorkerThreadState::PRODUCER);
 #else
         MOZ_CRASH();
 #endif
     } else {
         runtime->heapState = prevState;
     }
 }
 
--- a/js/src/jsscript.cpp
+++ b/js/src/jsscript.cpp
@@ -1297,25 +1297,21 @@ ScriptSource::setSourceCopy(ExclusiveCon
     // worker threads:
     //  - If we are on a worker thread, there must be another worker thread to
     //    execute our compression task.
     //  - If we are on the main thread, there must be at least two worker
     //    threads since at most one worker thread can be blocking on the main
     //    thread (see WorkerThreadState::canStartParseTask) which would cause a
     //    deadlock if there wasn't a second worker thread that could make
     //    progress on our compression task.
-#ifdef JS_THREADSAFE
-    bool canCompressOffThread =
-        WorkerThreadState().cpuCount > 1 &&
-        WorkerThreadState().threadCount >= 2;
-#else
-    bool canCompressOffThread = false;
-#endif
     const size_t HUGE_SCRIPT = 5 * 1024 * 1024;
-    if (length < HUGE_SCRIPT && canCompressOffThread) {
+    if (length < HUGE_SCRIPT &&
+        cx->cpuCount() > 1 &&
+        cx->workerThreadCount() >= 2)
+    {
         task->ss = this;
         task->chars = src;
         ready_ = false;
         if (!StartOffThreadCompression(cx, task))
             return false;
     } else {
         if (!adjustDataSize(sizeof(jschar) * length))
             return false;
--- a/js/src/jsworkers.cpp
+++ b/js/src/jsworkers.cpp
@@ -9,163 +9,175 @@
 #ifdef JS_THREADSAFE
 
 #include "mozilla/DebugOnly.h"
 
 #include "jsnativestack.h"
 #include "prmjtime.h"
 
 #include "frontend/BytecodeCompiler.h"
+#include "jit/ExecutionModeInlines.h"
 #include "jit/IonBuilder.h"
 #include "vm/Debugger.h"
 
 #include "jscntxtinlines.h"
 #include "jscompartmentinlines.h"
 #include "jsobjinlines.h"
 #include "jsscriptinlines.h"
 
 using namespace js;
 
 using mozilla::ArrayLength;
 using mozilla::DebugOnly;
 
-namespace js {
-
-GlobalWorkerThreadState gWorkerThreadState;
-
-} // namespace js
-
 bool
 js::EnsureWorkerThreadsInitialized(ExclusiveContext *cx)
 {
     // If 'cx' is not a JSContext, we are already off the main thread and the
     // worker threads would have already been initialized.
-    if (!cx->isJSContext())
+    if (!cx->isJSContext()) {
+        JS_ASSERT(cx->workerThreadState() != nullptr);
+        return true;
+    }
+
+    JSRuntime *rt = cx->asJSContext()->runtime();
+    if (rt->workerThreadState)
         return true;
 
-    return WorkerThreadState().ensureInitialized();
-}
-
-static size_t
-ThreadCountForCPUCount(size_t cpuCount)
-{
-    return Max(cpuCount, (size_t)2);
-}
+    rt->workerThreadState = rt->new_<WorkerThreadState>(rt);
+    if (!rt->workerThreadState)
+        return false;
 
-void
-js::SetFakeCPUCount(size_t count)
-{
-    // This must be called before the threads have been initialized.
-    JS_ASSERT(!WorkerThreadState().threads);
+    if (!rt->workerThreadState->init()) {
+        js_delete(rt->workerThreadState);
+        rt->workerThreadState = nullptr;
+        return false;
+    }
 
-    WorkerThreadState().cpuCount = count;
-    WorkerThreadState().threadCount = ThreadCountForCPUCount(count);
+    return true;
 }
 
 #ifdef JS_ION
 
 bool
 js::StartOffThreadAsmJSCompile(ExclusiveContext *cx, AsmJSParallelTask *asmData)
 {
     // Threads already initialized by the AsmJS compiler.
+    JS_ASSERT(cx->workerThreadState() != nullptr);
     JS_ASSERT(asmData->mir);
     JS_ASSERT(asmData->lir == nullptr);
 
-    AutoLockWorkerThreadState lock;
+    WorkerThreadState &state = *cx->workerThreadState();
+    JS_ASSERT(state.numThreads);
+
+    AutoLockWorkerThreadState lock(state);
 
     // Don't append this task if another failed.
-    if (WorkerThreadState().asmJSWorkerFailed())
+    if (state.asmJSWorkerFailed())
         return false;
 
-    if (!WorkerThreadState().asmJSWorklist().append(asmData))
+    if (!state.asmJSWorklist.append(asmData))
         return false;
 
-    WorkerThreadState().notifyOne(GlobalWorkerThreadState::PRODUCER);
+    state.notifyOne(WorkerThreadState::PRODUCER);
     return true;
 }
 
 bool
 js::StartOffThreadIonCompile(JSContext *cx, jit::IonBuilder *builder)
 {
     if (!EnsureWorkerThreadsInitialized(cx))
         return false;
 
-    AutoLockWorkerThreadState lock;
+    WorkerThreadState &state = *cx->runtime()->workerThreadState;
+    JS_ASSERT(state.numThreads);
 
-    if (!WorkerThreadState().ionWorklist().append(builder))
+    AutoLockWorkerThreadState lock(state);
+
+    if (!state.ionWorklist.append(builder))
         return false;
 
     cx->runtime()->addCompilationThread();
 
-    WorkerThreadState().notifyAll(GlobalWorkerThreadState::PRODUCER);
+    state.notifyAll(WorkerThreadState::PRODUCER);
     return true;
 }
 
 /*
  * Move an IonBuilder for which compilation has either finished, failed, or
- * been cancelled into the global finished compilation list. All off thread
- * compilations which are started must eventually be finished.
+ * been cancelled into the Ion compartment's finished compilations list.
+ * All off thread compilations which are started must eventually be finished.
  */
 static void
 FinishOffThreadIonCompile(jit::IonBuilder *builder)
 {
-    WorkerThreadState().ionFinishedList().append(builder);
+    JSCompartment *compartment = builder->script()->compartment();
+    JS_ASSERT(compartment->runtimeFromAnyThread()->workerThreadState);
+    JS_ASSERT(compartment->runtimeFromAnyThread()->workerThreadState->isLocked());
+
+    compartment->jitCompartment()->finishedOffThreadCompilations().append(builder);
 }
 
 #endif // JS_ION
 
 static inline bool
 CompiledScriptMatches(JSCompartment *compartment, JSScript *script, JSScript *target)
 {
     if (script)
         return target == script;
     return target->compartment() == compartment;
 }
 
 void
 js::CancelOffThreadIonCompile(JSCompartment *compartment, JSScript *script)
 {
 #ifdef JS_ION
+    JSRuntime *rt = compartment->runtimeFromMainThread();
+
+    if (!rt->workerThreadState)
+        return;
+
+    WorkerThreadState &state = *rt->workerThreadState;
+
     jit::JitCompartment *jitComp = compartment->jitCompartment();
     if (!jitComp)
         return;
 
-    AutoLockWorkerThreadState lock;
-
-    if (!WorkerThreadState().threads)
-        return;
+    AutoLockWorkerThreadState lock(state);
 
     /* Cancel any pending entries for which processing hasn't started. */
-    GlobalWorkerThreadState::IonBuilderVector &worklist = WorkerThreadState().ionWorklist();
-    for (size_t i = 0; i < worklist.length(); i++) {
-        jit::IonBuilder *builder = worklist[i];
+    for (size_t i = 0; i < state.ionWorklist.length(); i++) {
+        jit::IonBuilder *builder = state.ionWorklist[i];
         if (CompiledScriptMatches(compartment, script, builder->script())) {
             FinishOffThreadIonCompile(builder);
-            WorkerThreadState().remove(worklist, &i);
+            state.ionWorklist[i--] = state.ionWorklist.back();
+            state.ionWorklist.popBack();
         }
     }
 
     /* Wait for in progress entries to finish up. */
-    for (size_t i = 0; i < WorkerThreadState().threadCount; i++) {
-        const WorkerThread &helper = WorkerThreadState().threads[i];
+    for (size_t i = 0; i < state.numThreads; i++) {
+        const WorkerThread &helper = state.threads[i];
         while (helper.ionBuilder &&
                CompiledScriptMatches(compartment, script, helper.ionBuilder->script()))
         {
             helper.ionBuilder->cancel();
-            WorkerThreadState().wait(GlobalWorkerThreadState::CONSUMER);
+            state.wait(WorkerThreadState::CONSUMER);
         }
     }
 
+    jit::OffThreadCompilationVector &compilations = jitComp->finishedOffThreadCompilations();
+
     /* Cancel code generation for any completed entries. */
-    GlobalWorkerThreadState::IonBuilderVector &finished = WorkerThreadState().ionFinishedList();
-    for (size_t i = 0; i < finished.length(); i++) {
-        jit::IonBuilder *builder = finished[i];
+    for (size_t i = 0; i < compilations.length(); i++) {
+        jit::IonBuilder *builder = compilations[i];
         if (CompiledScriptMatches(compartment, script, builder->script())) {
             jit::FinishOffThreadBuilder(builder);
-            WorkerThreadState().remove(finished, &i);
+            compilations[i--] = compilations.back();
+            compilations.popBack();
         }
     }
 #endif // JS_ION
 }
 
 static const JSClass workerGlobalClass = {
     "internal-worker-global", JSCLASS_GLOBAL_FLAGS,
     JS_PropertyStub,  JS_DeletePropertyStub,
@@ -220,65 +232,16 @@ ParseTask::~ParseTask()
 {
     // ParseTask takes over ownership of its input exclusive context.
     js_delete(cx);
 
     for (size_t i = 0; i < errors.length(); i++)
         js_delete(errors[i]);
 }
 
-void
-js::CancelOffThreadParses(JSRuntime *rt)
-{
-    AutoLockWorkerThreadState lock;
-
-    if (!WorkerThreadState().threads)
-        return;
-
-    // Instead of forcibly canceling pending parse tasks, just wait for all scheduled
-    // and in progress ones to complete. Otherwise the final GC may not collect
-    // everything due to zones being used off thread.
-    while (true) {
-        bool pending = false;
-        GlobalWorkerThreadState::ParseTaskVector &worklist = WorkerThreadState().parseWorklist();
-        for (size_t i = 0; i < worklist.length(); i++) {
-            ParseTask *task = worklist[i];
-            if (task->runtimeMatches(rt))
-                pending = true;
-        }
-        if (!pending) {
-            bool inProgress = false;
-            for (size_t i = 0; i < WorkerThreadState().threadCount; i++) {
-                ParseTask *task = WorkerThreadState().threads[i].parseTask;
-                if (task && task->runtimeMatches(rt))
-                    inProgress = true;
-            }
-            if (!inProgress)
-                break;
-        }
-        WorkerThreadState().wait(GlobalWorkerThreadState::CONSUMER);
-    }
-
-    // Clean up any parse tasks which haven't been finished by the main thread.
-    GlobalWorkerThreadState::ParseTaskVector &finished = WorkerThreadState().parseFinishedList();
-    while (true) {
-        bool found = false;
-        for (size_t i = 0; i < finished.length(); i++) {
-            ParseTask *task = finished[i];
-            if (task->runtimeMatches(rt)) {
-                found = true;
-                AutoUnlockWorkerThreadState unlock;
-                WorkerThreadState().finishParseTask(/* maybecx = */ nullptr, rt, task);
-            }
-        }
-        if (!found)
-            break;
-    }
-}
-
 bool
 js::OffThreadParsingMustWaitForGC(JSRuntime *rt)
 {
     // Off thread parsing can't occur during incremental collections on the
     // atoms compartment, to avoid triggering barriers. (Outside the atoms
     // compartment, the compilation will use a new zone that is never
     // collected.) If an atoms-zone GC is in progress, hold off on executing the
     // parse task until the atoms-zone GC completes (see
@@ -348,233 +311,279 @@ js::StartOffThreadParseScript(JSContext 
     if (!task)
         return false;
 
     workercx.forget();
 
     if (!task->init(cx, options))
         return false;
 
+    WorkerThreadState &state = *cx->runtime()->workerThreadState;
+    JS_ASSERT(state.numThreads);
+
     if (OffThreadParsingMustWaitForGC(cx->runtime())) {
-        AutoLockWorkerThreadState lock;
-        if (!WorkerThreadState().parseWaitingOnGC().append(task.get()))
+        if (!state.parseWaitingOnGC.append(task.get()))
             return false;
     } else {
         task->activate(cx->runtime());
 
-        AutoLockWorkerThreadState lock;
+        AutoLockWorkerThreadState lock(state);
 
-        if (!WorkerThreadState().parseWorklist().append(task.get()))
+        if (!state.parseWorklist.append(task.get()))
             return false;
 
-        WorkerThreadState().notifyAll(GlobalWorkerThreadState::PRODUCER);
+        state.notifyAll(WorkerThreadState::PRODUCER);
     }
 
     task.forget();
 
     return true;
 }
 
 void
 js::EnqueuePendingParseTasksAfterGC(JSRuntime *rt)
 {
     JS_ASSERT(!OffThreadParsingMustWaitForGC(rt));
 
-    GlobalWorkerThreadState::ParseTaskVector newTasks;
-    {
-        AutoLockWorkerThreadState lock;
-        GlobalWorkerThreadState::ParseTaskVector &waiting = WorkerThreadState().parseWaitingOnGC();
-
-        for (size_t i = 0; i < waiting.length(); i++) {
-            ParseTask *task = waiting[i];
-            if (task->runtimeMatches(rt)) {
-                newTasks.append(task);
-                WorkerThreadState().remove(waiting, &i);
-            }
-        }
-    }
-
-    if (newTasks.empty())
+    if (!rt->workerThreadState || rt->workerThreadState->parseWaitingOnGC.empty())
         return;
 
     // This logic should mirror the contents of the !activeGCInAtomsZone()
     // branch in StartOffThreadParseScript:
 
-    for (size_t i = 0; i < newTasks.length(); i++)
-        newTasks[i]->activate(rt);
+    WorkerThreadState &state = *rt->workerThreadState;
+
+    for (size_t i = 0; i < state.parseWaitingOnGC.length(); i++)
+        state.parseWaitingOnGC[i]->activate(rt);
 
-    AutoLockWorkerThreadState lock;
+    AutoLockWorkerThreadState lock(state);
+
+    JS_ASSERT(state.parseWorklist.empty());
+    state.parseWorklist.swap(state.parseWaitingOnGC);
+
+    state.notifyAll(WorkerThreadState::PRODUCER);
+}
 
-    for (size_t i = 0; i < newTasks.length(); i++)
-        WorkerThreadState().parseWorklist().append(newTasks[i]);
+void
+js::WaitForOffThreadParsingToFinish(JSRuntime *rt)
+{
+    if (!rt->workerThreadState)
+        return;
+
+    WorkerThreadState &state = *rt->workerThreadState;
+
+    AutoLockWorkerThreadState lock(state);
 
-    WorkerThreadState().notifyAll(GlobalWorkerThreadState::PRODUCER);
+    while (true) {
+        if (state.parseWorklist.empty()) {
+            bool parseInProgress = false;
+            for (size_t i = 0; i < state.numThreads; i++)
+                parseInProgress |= !!state.threads[i].parseTask;
+            if (!parseInProgress)
+                break;
+        }
+        state.wait(WorkerThreadState::CONSUMER);
+    }
 }
 
 #ifdef XP_WIN
 // The default stack size for new threads on Windows is 1MB, but specifying a
 // smaller explicit size to NSPR on thread creation causes our visible memory
 // usage to increase. Just use the default stack size on Windows.
 static const uint32_t WORKER_STACK_SIZE = 0;
 #else
 static const uint32_t WORKER_STACK_SIZE = 512 * 1024;
 #endif
 
 static const uint32_t WORKER_STACK_QUOTA = 450 * 1024;
 
 bool
-GlobalWorkerThreadState::ensureInitialized()
+WorkerThreadState::init()
 {
-    JS_ASSERT(this == &WorkerThreadState());
-    AutoLockWorkerThreadState lock;
+    JS_ASSERT(numThreads == 0);
 
-    if (threads)
+    if (!runtime->useHelperThreads())
         return true;
 
-    threads = js_pod_calloc<WorkerThread>(threadCount);
+    workerLock = PR_NewLock();
+    if (!workerLock)
+        return false;
+
+    consumerWakeup = PR_NewCondVar(workerLock);
+    if (!consumerWakeup)
+        return false;
+
+    producerWakeup = PR_NewCondVar(workerLock);
+    if (!producerWakeup)
+        return false;
+
+    threads = (WorkerThread*) js_pod_calloc<WorkerThread>(runtime->workerThreadCount());
     if (!threads)
         return false;
 
-    for (size_t i = 0; i < threadCount; i++) {
+    for (size_t i = 0; i < runtime->workerThreadCount(); i++) {
         WorkerThread &helper = threads[i];
-        helper.threadData.construct(static_cast<JSRuntime *>(nullptr));
+        helper.runtime = runtime;
+        helper.threadData.construct(runtime);
+        helper.threadData.ref().addToThreadList();
         helper.thread = PR_CreateThread(PR_USER_THREAD,
                                         WorkerThread::ThreadMain, &helper,
                                         PR_PRIORITY_NORMAL, PR_LOCAL_THREAD, PR_JOINABLE_THREAD, WORKER_STACK_SIZE);
         if (!helper.thread || !helper.threadData.ref().init()) {
-            for (size_t j = 0; j < threadCount; j++)
+            for (size_t j = 0; j < runtime->workerThreadCount(); j++)
                 threads[j].destroy();
             js_free(threads);
             threads = nullptr;
             return false;
         }
     }
 
+    numThreads = runtime->workerThreadCount();
     resetAsmJSFailureState();
     return true;
 }
 
-GlobalWorkerThreadState::GlobalWorkerThreadState()
+void
+WorkerThreadState::cleanup()
 {
-    mozilla::PodZero(this);
+    // Do preparatory work for shutdown before the final GC has destroyed most
+    // of the GC heap.
+
+    // Join created threads, to ensure there is no in progress work.
+    if (threads) {
+        for (size_t i = 0; i < numThreads; i++)
+            threads[i].destroy();
+        js_free(threads);
+        threads = nullptr;
+        numThreads = 0;
+    }
 
-    cpuCount = GetCPUCount();
-    threadCount = ThreadCountForCPUCount(cpuCount);
+    // Clean up any parse tasks which haven't been finished yet.
+    while (!parseFinishedList.empty())
+        finishParseTask(/* maybecx = */ nullptr, runtime, parseFinishedList[0]);
+}
 
-    MOZ_ASSERT(cpuCount > 0, "GetCPUCount() seems broken");
+WorkerThreadState::~WorkerThreadState()
+{
+    JS_ASSERT(!threads);
+    JS_ASSERT(parseFinishedList.empty());
 
-    workerLock = PR_NewLock();
-    consumerWakeup = PR_NewCondVar(workerLock);
-    producerWakeup = PR_NewCondVar(workerLock);
+    if (workerLock)
+        PR_DestroyLock(workerLock);
+
+    if (consumerWakeup)
+        PR_DestroyCondVar(consumerWakeup);
+
+    if (producerWakeup)
+        PR_DestroyCondVar(producerWakeup);
 }
 
 void
-GlobalWorkerThreadState::lock()
+WorkerThreadState::lock()
 {
-    JS_ASSERT(!isLocked());
-    AssertCurrentThreadCanLock(WorkerThreadStateLock);
+    runtime->assertCanLock(JSRuntime::WorkerThreadStateLock);
     PR_Lock(workerLock);
 #ifdef DEBUG
     lockOwner = PR_GetCurrentThread();
 #endif
 }
 
 void
-GlobalWorkerThreadState::unlock()
+WorkerThreadState::unlock()
 {
     JS_ASSERT(isLocked());
 #ifdef DEBUG
     lockOwner = nullptr;
 #endif
     PR_Unlock(workerLock);
 }
 
 #ifdef DEBUG
 bool
-GlobalWorkerThreadState::isLocked()
+WorkerThreadState::isLocked()
 {
     return lockOwner == PR_GetCurrentThread();
 }
 #endif
 
 void
-GlobalWorkerThreadState::wait(CondVar which, uint32_t millis)
+WorkerThreadState::wait(CondVar which, uint32_t millis)
 {
     JS_ASSERT(isLocked());
 #ifdef DEBUG
     lockOwner = nullptr;
 #endif
     DebugOnly<PRStatus> status =
         PR_WaitCondVar((which == CONSUMER) ? consumerWakeup : producerWakeup,
                        millis ? PR_MillisecondsToInterval(millis) : PR_INTERVAL_NO_TIMEOUT);
     JS_ASSERT(status == PR_SUCCESS);
 #ifdef DEBUG
     lockOwner = PR_GetCurrentThread();
 #endif
 }
 
 void
-GlobalWorkerThreadState::notifyAll(CondVar which)
+WorkerThreadState::notifyAll(CondVar which)
 {
     JS_ASSERT(isLocked());
     PR_NotifyAllCondVar((which == CONSUMER) ? consumerWakeup : producerWakeup);
 }
 
 void
-GlobalWorkerThreadState::notifyOne(CondVar which)
+WorkerThreadState::notifyOne(CondVar which)
 {
     JS_ASSERT(isLocked());
     PR_NotifyCondVar((which == CONSUMER) ? consumerWakeup : producerWakeup);
 }
 
 bool
-GlobalWorkerThreadState::canStartAsmJSCompile()
+WorkerThreadState::canStartAsmJSCompile()
 {
     // Don't execute an AsmJS job if an earlier one failed.
     JS_ASSERT(isLocked());
-    return (!asmJSWorklist().empty() && !numAsmJSFailedJobs);
+    return (!asmJSWorklist.empty() && !numAsmJSFailedJobs);
 }
 
 bool
-GlobalWorkerThreadState::canStartIonCompile()
+WorkerThreadState::canStartIonCompile()
 {
     // A worker thread can begin an Ion compilation if (a) there is some script
     // which is waiting to be compiled, and (b) no other worker thread is
     // currently compiling a script. The latter condition ensures that two
     // compilations cannot simultaneously occur.
-    if (ionWorklist().empty())
+    if (ionWorklist.empty())
         return false;
-    for (size_t i = 0; i < threadCount; i++) {
+    for (size_t i = 0; i < numThreads; i++) {
         if (threads[i].ionBuilder)
             return false;
     }
     return true;
 }
 
 bool
-GlobalWorkerThreadState::canStartParseTask()
+WorkerThreadState::canStartParseTask()
 {
     // Don't allow simultaneous off thread parses, to reduce contention on the
     // atoms table. Note that asm.js compilation depends on this to avoid
     // stalling the worker thread, as off thread parse tasks can trigger and
     // block on other off thread asm.js compilation tasks.
     JS_ASSERT(isLocked());
-    if (parseWorklist().empty())
+    if (parseWorklist.empty())
         return false;
-    for (size_t i = 0; i < threadCount; i++) {
+    for (size_t i = 0; i < numThreads; i++) {
         if (threads[i].parseTask)
             return false;
     }
     return true;
 }
 
 bool
-GlobalWorkerThreadState::canStartCompressionTask()
+WorkerThreadState::canStartCompressionTask()
 {
-    return !compressionWorklist().empty();
+    return !compressionWorklist.empty();
 }
 
 static void
 CallNewScriptHookForAllScripts(JSContext *cx, HandleScript script)
 {
     // We should never hit this, since nested scripts are also constructed via
     // BytecodeEmitter instances on the stack.
     JS_CHECK_RECURSION(cx, return);
@@ -595,29 +604,29 @@ CallNewScriptHookForAllScripts(JSContext
     }
 
     // The global new script hook is called on every script that was compiled.
     RootedFunction function(cx, script->functionNonDelazifying());
     CallNewScriptHook(cx, script, function);
 }
 
 JSScript *
-GlobalWorkerThreadState::finishParseTask(JSContext *maybecx, JSRuntime *rt, void *token)
+WorkerThreadState::finishParseTask(JSContext *maybecx, JSRuntime *rt, void *token)
 {
     ParseTask *parseTask = nullptr;
 
     // The token is a ParseTask* which should be in the finished list.
     // Find and remove its entry.
     {
-        AutoLockWorkerThreadState lock;
-        ParseTaskVector &finished = parseFinishedList();
-        for (size_t i = 0; i < finished.length(); i++) {
-            if (finished[i] == token) {
-                parseTask = finished[i];
-                remove(finished, &i);
+        AutoLockWorkerThreadState lock(*rt->workerThreadState);
+        for (size_t i = 0; i < parseFinishedList.length(); i++) {
+            if (parseFinishedList[i] == token) {
+                parseTask = parseFinishedList[i];
+                parseFinishedList[i] = parseFinishedList.back();
+                parseFinishedList.popBack();
                 break;
             }
         }
     }
     JS_ASSERT(parseTask);
 
     // Mark the zone as no longer in use by an ExclusiveContext, and available
     // to be collected by the GC.
@@ -677,137 +686,137 @@ GlobalWorkerThreadState::finishParseTask
 
     js_delete(parseTask);
     return script;
 }
 
 void
 WorkerThread::destroy()
 {
+    WorkerThreadState &state = *runtime->workerThreadState;
+
     if (thread) {
         {
-            AutoLockWorkerThreadState lock;
+            AutoLockWorkerThreadState lock(state);
             terminate = true;
 
             /* Notify all workers, to ensure that this thread wakes up. */
-            WorkerThreadState().notifyAll(GlobalWorkerThreadState::PRODUCER);
+            state.notifyAll(WorkerThreadState::PRODUCER);
         }
 
         PR_JoinThread(thread);
     }
 
-    if (!threadData.empty())
+    if (!threadData.empty()) {
+        threadData.ref().removeFromThreadList();
         threadData.destroy();
+    }
 }
 
 /* static */
 void
 WorkerThread::ThreadMain(void *arg)
 {
     PR_SetCurrentThreadName("Analysis Helper");
     static_cast<WorkerThread *>(arg)->threadLoop();
 }
 
 void
-WorkerThread::handleAsmJSWorkload()
+WorkerThread::handleAsmJSWorkload(WorkerThreadState &state)
 {
 #ifdef JS_ION
-    JS_ASSERT(WorkerThreadState().isLocked());
-    JS_ASSERT(WorkerThreadState().canStartAsmJSCompile());
+    JS_ASSERT(state.isLocked());
+    JS_ASSERT(state.canStartAsmJSCompile());
     JS_ASSERT(idle());
 
-    asmData = WorkerThreadState().asmJSWorklist().popCopy();
+    asmData = state.asmJSWorklist.popCopy();
     bool success = false;
 
+    state.unlock();
     do {
-        AutoUnlockWorkerThreadState unlock;
-        PerThreadData::AutoEnterRuntime enter(threadData.addr(), asmData->runtime);
-
-        jit::IonContext icx(asmData->mir->compartment->runtime(),
-                            asmData->mir->compartment,
-                            &asmData->mir->alloc());
+        jit::IonContext icx(jit::CompileRuntime::get(runtime), asmData->mir->compartment, &asmData->mir->alloc());
 
         int64_t before = PRMJ_Now();
 
         if (!OptimizeMIR(asmData->mir))
             break;
 
         asmData->lir = GenerateLIR(asmData->mir);
         if (!asmData->lir)
             break;
 
         int64_t after = PRMJ_Now();
         asmData->compileTime = (after - before) / PRMJ_USEC_PER_MSEC;
 
         success = true;
     } while(0);
+    state.lock();
 
     // On failure, signal parent for harvesting in CancelOutstandingJobs().
     if (!success) {
-        WorkerThreadState().noteAsmJSFailure(asmData->func);
-        WorkerThreadState().notifyAll(GlobalWorkerThreadState::CONSUMER);
+        state.noteAsmJSFailure(asmData->func);
+        state.notifyAll(WorkerThreadState::CONSUMER);
         asmData = nullptr;
         return;
     }
 
     // On success, move work to the finished list.
-    WorkerThreadState().asmJSFinishedList().append(asmData);
+    state.asmJSFinishedList.append(asmData);
     asmData = nullptr;
 
     // Notify the main thread in case it's blocked waiting for a LifoAlloc.
-    WorkerThreadState().notifyAll(GlobalWorkerThreadState::CONSUMER);
+    state.notifyAll(WorkerThreadState::CONSUMER);
 #else
     MOZ_CRASH();
 #endif // JS_ION
 }
 
 void
-WorkerThread::handleIonWorkload()
+WorkerThread::handleIonWorkload(WorkerThreadState &state)
 {
 #ifdef JS_ION
-    JS_ASSERT(WorkerThreadState().isLocked());
-    JS_ASSERT(WorkerThreadState().canStartIonCompile());
+    JS_ASSERT(state.isLocked());
+    JS_ASSERT(state.canStartIonCompile());
     JS_ASSERT(idle());
 
-    ionBuilder = WorkerThreadState().ionWorklist().popCopy();
+    ionBuilder = state.ionWorklist.popCopy();
+
+    DebugOnly<ExecutionMode> executionMode = ionBuilder->info().executionMode();
 
 #if JS_TRACE_LOGGING
     AutoTraceLog logger(TraceLogging::getLogger(TraceLogging::ION_BACKGROUND_COMPILER),
                         TraceLogging::ION_COMPILE_START,
                         TraceLogging::ION_COMPILE_STOP,
                         ionBuilder->script());
 #endif
 
-    JSRuntime *rt = ionBuilder->script()->compartment()->runtimeFromAnyThread();
-
+    state.unlock();
     {
-        AutoUnlockWorkerThreadState unlock;
-        PerThreadData::AutoEnterRuntime enter(threadData.addr(),
-                                              ionBuilder->script()->runtimeFromAnyThread());
-        jit::IonContext ictx(jit::CompileRuntime::get(rt),
+        jit::IonContext ictx(jit::CompileRuntime::get(runtime),
                              jit::CompileCompartment::get(ionBuilder->script()->compartment()),
                              &ionBuilder->alloc());
         AutoEnterIonCompilation ionCompiling;
         bool succeeded = ionBuilder->build();
         ionBuilder->clearForBackEnd();
         if (succeeded)
             ionBuilder->setBackgroundCodegen(jit::CompileBackEnd(ionBuilder));
     }
+    state.lock();
 
     FinishOffThreadIonCompile(ionBuilder);
     ionBuilder = nullptr;
 
     // Ping the main thread so that the compiled code can be incorporated
     // at the next operation callback. Don't interrupt Ion code for this, as
     // this incorporation can be delayed indefinitely without affecting
     // performance as long as the main thread is actually executing Ion code.
-    rt->triggerOperationCallback(JSRuntime::TriggerCallbackAnyThreadDontStopIon);
+    runtime->triggerOperationCallback(JSRuntime::TriggerCallbackAnyThreadDontStopIon);
 
     // Notify the main thread in case it is waiting for the compilation to finish.
-    WorkerThreadState().notifyAll(GlobalWorkerThreadState::CONSUMER);
+    state.notifyAll(WorkerThreadState::CONSUMER);
 #else
     MOZ_CRASH();
 #endif // JS_ION
 }
 
 void
 ExclusiveContext::setWorkerThread(WorkerThread *workerThread)
 {
@@ -829,110 +838,110 @@ ExclusiveContext::addPendingCompileError
 void
 ExclusiveContext::addPendingOverRecursed()
 {
     if (workerThread()->parseTask)
         workerThread()->parseTask->overRecursed = true;
 }
 
 void
-WorkerThread::handleParseWorkload()
+WorkerThread::handleParseWorkload(WorkerThreadState &state)
 {
-    JS_ASSERT(WorkerThreadState().isLocked());
-    JS_ASSERT(WorkerThreadState().canStartParseTask());
+    JS_ASSERT(state.isLocked());
+    JS_ASSERT(state.canStartParseTask());
     JS_ASSERT(idle());
 
-    parseTask = WorkerThreadState().parseWorklist().popCopy();
+    parseTask = state.parseWorklist.popCopy();
     parseTask->cx->setWorkerThread(this);
 
     {
-        AutoUnlockWorkerThreadState unlock;
-        PerThreadData::AutoEnterRuntime enter(threadData.addr(),
-                                              parseTask->exclusiveContextGlobal->runtimeFromAnyThread());
+        AutoUnlockWorkerThreadState unlock(runtime);
         parseTask->script = frontend::CompileScript(parseTask->cx, &parseTask->alloc,
                                                     NullPtr(), NullPtr(),
                                                     parseTask->options,
                                                     parseTask->chars, parseTask->length);
     }
 
     // The callback is invoked while we are still off the main thread.
     parseTask->callback(parseTask, parseTask->callbackData);
 
     // FinishOffThreadScript will need to be called on the script to
     // migrate it into the correct compartment.
-    WorkerThreadState().parseFinishedList().append(parseTask);
+    state.parseFinishedList.append(parseTask);
 
     parseTask = nullptr;
 
     // Notify the main thread in case it is waiting for the parse/emit to finish.
-    WorkerThreadState().notifyAll(GlobalWorkerThreadState::CONSUMER);
+    state.notifyAll(WorkerThreadState::CONSUMER);
 }
 
 void
-WorkerThread::handleCompressionWorkload()
+WorkerThread::handleCompressionWorkload(WorkerThreadState &state)
 {
-    JS_ASSERT(WorkerThreadState().isLocked());
-    JS_ASSERT(WorkerThreadState().canStartCompressionTask());
+    JS_ASSERT(state.isLocked());
+    JS_ASSERT(state.canStartCompressionTask());
     JS_ASSERT(idle());
 
-    compressionTask = WorkerThreadState().compressionWorklist().popCopy();
+    compressionTask = state.compressionWorklist.popCopy();
     compressionTask->workerThread = this;
 
     {
-        AutoUnlockWorkerThreadState unlock;
+        AutoUnlockWorkerThreadState unlock(runtime);
         if (!compressionTask->work())
             compressionTask->setOOM();
     }
 
     compressionTask->workerThread = nullptr;
     compressionTask = nullptr;
 
     // Notify the main thread in case it is waiting for the compression to finish.
-    WorkerThreadState().notifyAll(GlobalWorkerThreadState::CONSUMER);
+    state.notifyAll(WorkerThreadState::CONSUMER);
 }
 
 bool
 js::StartOffThreadCompression(ExclusiveContext *cx, SourceCompressionTask *task)
 {
     if (!EnsureWorkerThreadsInitialized(cx))
         return false;
 
-    AutoLockWorkerThreadState lock;
+    WorkerThreadState &state = *cx->workerThreadState();
+    AutoLockWorkerThreadState lock(state);
 
-    if (!WorkerThreadState().compressionWorklist().append(task))
+    if (!state.compressionWorklist.append(task))
         return false;
 
-    WorkerThreadState().notifyAll(GlobalWorkerThreadState::PRODUCER);
+    state.notifyAll(WorkerThreadState::PRODUCER);
     return true;
 }
 
 bool
-GlobalWorkerThreadState::compressionInProgress(SourceCompressionTask *task)
+WorkerThreadState::compressionInProgress(SourceCompressionTask *task)
 {
     JS_ASSERT(isLocked());
-    for (size_t i = 0; i < compressionWorklist().length(); i++) {
-        if (compressionWorklist()[i] == task)
+    for (size_t i = 0; i < compressionWorklist.length(); i++) {
+        if (compressionWorklist[i] == task)
             return true;
     }
-    for (size_t i = 0; i < threadCount; i++) {
+    for (size_t i = 0; i < numThreads; i++) {
         if (threads[i].compressionTask == task)
             return true;
     }
     return false;
 }
 
 bool
 SourceCompressionTask::complete()
 {
     JS_ASSERT_IF(!ss, !chars);
     if (active()) {
-        AutoLockWorkerThreadState lock;
+        WorkerThreadState &state = *cx->workerThreadState();
+        AutoLockWorkerThreadState lock(state);
 
-        while (WorkerThreadState().compressionInProgress(this))
-            WorkerThreadState().wait(GlobalWorkerThreadState::CONSUMER);
+        while (state.compressionInProgress(this))
+            state.wait(WorkerThreadState::CONSUMER);
 
         ss->ready_ = true;
 
         // Update memory accounting.
         if (!oom)
             cx->updateMallocCounter(ss->computedSizeOfData());
 
         ss = nullptr;
@@ -941,25 +950,25 @@ SourceCompressionTask::complete()
     if (oom) {
         js_ReportOutOfMemory(cx);
         return false;
     }
     return true;
 }
 
 SourceCompressionTask *
-GlobalWorkerThreadState::compressionTaskForSource(ScriptSource *ss)
+WorkerThreadState::compressionTaskForSource(ScriptSource *ss)
 {
     JS_ASSERT(isLocked());
-    for (size_t i = 0; i < compressionWorklist().length(); i++) {
-        SourceCompressionTask *task = compressionWorklist()[i];
+    for (size_t i = 0; i < compressionWorklist.length(); i++) {
+        SourceCompressionTask *task = compressionWorklist[i];
         if (task->source() == ss)
             return task;
     }
-    for (size_t i = 0; i < threadCount; i++) {
+    for (size_t i = 0; i < numThreads; i++) {
         SourceCompressionTask *task = threads[i].compressionTask;
         if (task && task->source() == ss)
             return task;
     }
     return nullptr;
 }
 
 const jschar *
@@ -967,35 +976,37 @@ ScriptSource::getOffThreadCompressionCha
 {
     // If this is being compressed off thread, return its uncompressed chars.
 
     if (ready()) {
         // Compression has already finished on the source.
         return nullptr;
     }
 
-    AutoLockWorkerThreadState lock;
+    WorkerThreadState &state = *cx->workerThreadState();
+    AutoLockWorkerThreadState lock(state);
 
     // Look for a token that hasn't finished compressing and whose source is
     // the given ScriptSource.
-    if (SourceCompressionTask *task = WorkerThreadState().compressionTaskForSource(this))
+    if (SourceCompressionTask *task = state.compressionTaskForSource(this))
         return task->uncompressedChars();
 
     // Compressing has finished, so this ScriptSource is ready. Avoid future
     // queries on the worker thread state when getting the chars.
     ready_ = true;
 
     return nullptr;
 }
 
 void
 WorkerThread::threadLoop()
 {
     JS::AutoAssertNoGC nogc;
-    AutoLockWorkerThreadState lock;
+    WorkerThreadState &state = *runtime->workerThreadState;
+    AutoLockWorkerThreadState lock(state);
 
     js::TlsPerThreadData.set(threadData.addr());
 
     // Compute the thread's stack limit, for over-recursed checks.
     uintptr_t stackLimit = GetNativeStackBase();
 #if JS_STACK_GROWTH_DIRECTION > 0
     stackLimit += WORKER_STACK_QUOTA;
 #else
@@ -1006,35 +1017,35 @@ WorkerThread::threadLoop()
 
     while (true) {
         JS_ASSERT(!ionBuilder && !asmData);
 
         // Block until a task is available.
         while (true) {
             if (terminate)
                 return;
-            if (WorkerThreadState().canStartIonCompile() ||
-                WorkerThreadState().canStartAsmJSCompile() ||
-                WorkerThreadState().canStartParseTask() ||
-                WorkerThreadState().canStartCompressionTask())
+            if (state.canStartIonCompile() ||
+                state.canStartAsmJSCompile() ||
+                state.canStartParseTask() ||
+                state.canStartCompressionTask())
             {
                 break;
             }
-            WorkerThreadState().wait(GlobalWorkerThreadState::PRODUCER);
+            state.wait(WorkerThreadState::PRODUCER);
         }
 
         // Dispatch tasks, prioritizing AsmJS work.
-        if (WorkerThreadState().canStartAsmJSCompile())
-            handleAsmJSWorkload();
-        else if (WorkerThreadState().canStartIonCompile())
-            handleIonWorkload();
-        else if (WorkerThreadState().canStartParseTask())
-            handleParseWorkload();
-        else if (WorkerThreadState().canStartCompressionTask())
-            handleCompressionWorkload();
+        if (state.canStartAsmJSCompile())
+            handleAsmJSWorkload(state);
+        else if (state.canStartIonCompile())
+            handleIonWorkload(state);
+        else if (state.canStartParseTask())
+            handleParseWorkload(state);
+        else if (state.canStartCompressionTask())
+            handleCompressionWorkload(state);
         else
             MOZ_ASSUME_UNREACHABLE("No task to perform");
     }
 }
 
 #else /* JS_THREADSAFE */
 
 using namespace js;
@@ -1055,29 +1066,29 @@ js::StartOffThreadIonCompile(JSContext *
 
 #endif // JS_ION
 
 void
 js::CancelOffThreadIonCompile(JSCompartment *compartment, JSScript *script)
 {
 }
 
-void
-js::CancelOffThreadParses(JSRuntime *rt)
-{
-}
-
 bool
 js::StartOffThreadParseScript(JSContext *cx, const ReadOnlyCompileOptions &options,
                               const jschar *chars, size_t length, HandleObject scopeChain,
                               JS::OffThreadCompileCallback callback, void *callbackData)
 {
     MOZ_ASSUME_UNREACHABLE("Off thread compilation not available in non-THREADSAFE builds");
 }
 
+void
+js::WaitForOffThreadParsingToFinish(JSRuntime *rt)
+{
+}
+
 bool
 js::StartOffThreadCompression(ExclusiveContext *cx, SourceCompressionTask *task)
 {
     MOZ_ASSUME_UNREACHABLE("Off thread compression not available");
 }
 
 bool
 SourceCompressionTask::complete()
--- a/js/src/jsworkers.h
+++ b/js/src/jsworkers.h
@@ -28,131 +28,80 @@ struct WorkerThread;
 struct AsmJSParallelTask;
 struct ParseTask;
 namespace jit {
   class IonBuilder;
 }
 
 #ifdef JS_THREADSAFE
 
-// Per-process state for off thread work items.
-class GlobalWorkerThreadState
+/* Per-runtime state for off thread work items. */
+class WorkerThreadState
 {
   public:
-    // Number of CPUs to treat this machine as having when creating threads.
-    // May be accessed without locking.
-    size_t cpuCount;
-
-    // Number of threads to create. May be accessed without locking.
-    size_t threadCount;
+    /* Available threads. */
+    WorkerThread *threads;
+    size_t numThreads;
 
-    typedef Vector<jit::IonBuilder*, 0, SystemAllocPolicy> IonBuilderVector;
-    typedef Vector<AsmJSParallelTask*, 0, SystemAllocPolicy> AsmJSParallelTaskVector;
-    typedef Vector<ParseTask*, 0, SystemAllocPolicy> ParseTaskVector;
-    typedef Vector<SourceCompressionTask*, 0, SystemAllocPolicy> SourceCompressionTaskVector;
+    enum CondVar {
+        /* For notifying threads waiting for work that they may be able to make progress. */
+        CONSUMER,
 
-    // List of available threads, or null if the thread state has not been initialized.
-    WorkerThread *threads;
+        /* For notifying threads doing work that they may be able to make progress. */
+        PRODUCER
+    };
+
+    /* Shared worklist for Ion worker threads. */
+    Vector<jit::IonBuilder*, 0, SystemAllocPolicy> ionWorklist;
 
-  private:
-    // The lists below are all protected by |lock|.
-
-    // Ion compilation worklist and finished jobs.
-    IonBuilderVector ionWorklist_, ionFinishedList_;
+    /* Worklist for AsmJS worker threads. */
+    Vector<AsmJSParallelTask*, 0, SystemAllocPolicy> asmJSWorklist;
 
-    // AsmJS worklist and finished jobs.
-    //
-    // Simultaneous AsmJS compilations all service the same AsmJS module.
-    // The main thread must pick up finished optimizations and perform codegen.
-    // |asmJSCompilationInProgress| is used to avoid triggering compilations
-    // for more than one module at a time.
-    AsmJSParallelTaskVector asmJSWorklist_, asmJSFinishedList_;
+    /*
+     * Finished list for AsmJS worker threads.
+     * Simultaneous AsmJS compilations all service the same AsmJS module.
+     * The main thread must pick up finished optimizations and perform codegen.
+     */
+    Vector<AsmJSParallelTask*, 0, SystemAllocPolicy> asmJSFinishedList;
 
-  public:
-    // For now, only allow a single parallel asm.js compilation to happen at a
-    // time. This avoids race conditions on asmJSWorklist/asmJSFinishedList/etc.
+    /*
+     * For now, only allow a single parallel asm.js compilation to happen at a
+     * time. This avoids race conditions on asmJSWorklist/asmJSFinishedList/etc.
+     */
     mozilla::Atomic<uint32_t> asmJSCompilationInProgress;
 
-  private:
-    // Script parsing/emitting worklist and finished jobs.
-    ParseTaskVector parseWorklist_, parseFinishedList_;
+    /* Shared worklist for parsing/emitting scripts on worker threads. */
+    Vector<ParseTask*, 0, SystemAllocPolicy> parseWorklist, parseFinishedList;
 
-    // Parse tasks waiting for an atoms-zone GC to complete.
-    ParseTaskVector parseWaitingOnGC_;
+    /* Main-thread-only list of parse tasks waiting for an atoms-zone GC to complete. */
+    Vector<ParseTask*, 0, SystemAllocPolicy> parseWaitingOnGC;
+
+    /* Worklist for source compression worker threads. */
+    Vector<SourceCompressionTask *, 0, SystemAllocPolicy> compressionWorklist;
 
-    // Source compression worklist.
-    SourceCompressionTaskVector compressionWorklist_;
+    WorkerThreadState(JSRuntime *rt) {
+        mozilla::PodZero(this);
+        runtime = rt;
+    }
+    ~WorkerThreadState();
 
-  public:
-    GlobalWorkerThreadState();
+    bool init();
+    void cleanup();
 
-    bool ensureInitialized();
     void lock();
     void unlock();
 
 # ifdef DEBUG
     bool isLocked();
 # endif
 
-    enum CondVar {
-        // For notifying threads waiting for work that they may be able to make progress.
-        CONSUMER,
-
-        // For notifying threads doing work that they may be able to make progress.
-        PRODUCER
-    };
-
     void wait(CondVar which, uint32_t timeoutMillis = 0);
     void notifyAll(CondVar which);
     void notifyOne(CondVar which);
 
-    // Helper method for removing items from the vectors below while iterating over them.
-    template <typename T>
-    void remove(T &vector, size_t *index)
-    {
-        vector[(*index)--] = vector.back();
-        vector.popBack();
-    }
-
-    IonBuilderVector &ionWorklist() {
-        JS_ASSERT(isLocked());
-        return ionWorklist_;
-    }
-    IonBuilderVector &ionFinishedList() {
-        JS_ASSERT(isLocked());
-        return ionFinishedList_;
-    }
-
-    AsmJSParallelTaskVector &asmJSWorklist() {
-        JS_ASSERT(isLocked());
-        return asmJSWorklist_;
-    }
-    AsmJSParallelTaskVector &asmJSFinishedList() {
-        JS_ASSERT(isLocked());
-        return asmJSFinishedList_;
-    }
-
-    ParseTaskVector &parseWorklist() {
-        JS_ASSERT(isLocked());
-        return parseWorklist_;
-    }
-    ParseTaskVector &parseFinishedList() {
-        JS_ASSERT(isLocked());
-        return parseFinishedList_;
-    }
-    ParseTaskVector &parseWaitingOnGC() {
-        JS_ASSERT(isLocked());
-        return parseWaitingOnGC_;
-    }
-
-    SourceCompressionTaskVector &compressionWorklist() {
-        JS_ASSERT(isLocked());
-        return compressionWorklist_;
-    }
-
     bool canStartAsmJSCompile();
     bool canStartIonCompile();
     bool canStartParseTask();
     bool canStartCompressionTask();
 
     uint32_t harvestFailedAsmJSJobs() {
         JS_ASSERT(isLocked());
         uint32_t n = numAsmJSFailedJobs;
@@ -178,16 +127,18 @@ class GlobalWorkerThreadState
     }
 
     JSScript *finishParseTask(JSContext *maybecx, JSRuntime *rt, void *token);
     bool compressionInProgress(SourceCompressionTask *task);
     SourceCompressionTask *compressionTaskForSource(ScriptSource *ss);
 
   private:
 
+    JSRuntime *runtime;
+
     /*
      * Lock protecting all mutable shared state accessed by helper threads, and
      * used by all condition variables.
      */
     PRLock *workerLock;
 
 # ifdef DEBUG
     PRThread *lockOwner;
@@ -205,26 +156,21 @@ class GlobalWorkerThreadState
 
     /*
      * Function index |i| in |Module.function(i)| of first failed AsmJS function.
      * -1 if no function has failed.
      */
     void *asmJSFailedFunction;
 };
 
-static inline GlobalWorkerThreadState &
-WorkerThreadState()
-{
-    extern GlobalWorkerThreadState gWorkerThreadState;
-    return gWorkerThreadState;
-}
-
 /* Individual helper thread, one allocated per core. */
 struct WorkerThread
 {
+    JSRuntime *runtime;
+
     mozilla::Maybe<PerThreadData> threadData;
     PRThread *thread;
 
     /* Indicate to an idle thread that it should finish executing. */
     bool terminate;
 
     /* Any builder currently being compiled by Ion on this thread. */
     jit::IonBuilder *ionBuilder;
@@ -239,38 +185,33 @@ struct WorkerThread
     SourceCompressionTask *compressionTask;
 
     bool idle() const {
         return !ionBuilder && !asmData && !parseTask && !compressionTask;
     }
 
     void destroy();
 
-    void handleAsmJSWorkload();
-    void handleIonWorkload();
-    void handleParseWorkload();
-    void handleCompressionWorkload();
+    void handleAsmJSWorkload(WorkerThreadState &state);
+    void handleIonWorkload(WorkerThreadState &state);
+    void handleParseWorkload(WorkerThreadState &state);
+    void handleCompressionWorkload(WorkerThreadState &state);
 
     static void ThreadMain(void *arg);
     void threadLoop();
 };
 
 #endif /* JS_THREADSAFE */
 
 /* Methods for interacting with worker threads. */
 
-// Initialize worker threads unless already initialized.
+/* Initialize worker threads unless already initialized. */
 bool
 EnsureWorkerThreadsInitialized(ExclusiveContext *cx);
 
-// This allows the JS shell to override GetCPUCount() when passed the
-// --thread-count=N option.
-void
-SetFakeCPUCount(size_t count);
-
 #ifdef JS_ION
 
 /* Perform MIR optimization and LIR generation on a single function. */
 bool
 StartOffThreadAsmJSCompile(ExclusiveContext *cx, AsmJSParallelTask *asmData);
 
 /*
  * Schedule an Ion compilation for a script, given a builder which has been
@@ -283,102 +224,111 @@ StartOffThreadIonCompile(JSContext *cx, 
 
 /*
  * Cancel a scheduled or in progress Ion compilation for script. If script is
  * nullptr, all compilations for the compartment are cancelled.
  */
 void
 CancelOffThreadIonCompile(JSCompartment *compartment, JSScript *script);
 
-/* Cancel all scheduled, in progress or finished parses for runtime. */
-void
-CancelOffThreadParses(JSRuntime *runtime);
-
 /*
  * Start a parse/emit cycle for a stream of source. The characters must stay
  * alive until the compilation finishes.
  */
 bool
 StartOffThreadParseScript(JSContext *cx, const ReadOnlyCompileOptions &options,
                           const jschar *chars, size_t length, HandleObject scopeChain,
                           JS::OffThreadCompileCallback callback, void *callbackData);
 
 /*
  * Called at the end of GC to enqueue any Parse tasks that were waiting on an
  * atoms-zone GC to finish.
  */
 void
 EnqueuePendingParseTasksAfterGC(JSRuntime *rt);
 
+/* Block until in progress and pending off thread parse jobs have finished. */
+void
+WaitForOffThreadParsingToFinish(JSRuntime *rt);
+
 /* Start a compression job for the specified token. */
 bool
 StartOffThreadCompression(ExclusiveContext *cx, SourceCompressionTask *task);
 
 class AutoLockWorkerThreadState
 {
     MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER
 
 #ifdef JS_THREADSAFE
+    WorkerThreadState &state;
+
   public:
-    AutoLockWorkerThreadState(MOZ_GUARD_OBJECT_NOTIFIER_ONLY_PARAM)
+    AutoLockWorkerThreadState(WorkerThreadState &state
+                              MOZ_GUARD_OBJECT_NOTIFIER_PARAM)
+      : state(state)
     {
         MOZ_GUARD_OBJECT_NOTIFIER_INIT;
-        WorkerThreadState().lock();
+        state.lock();
     }
 
     ~AutoLockWorkerThreadState() {
-        WorkerThreadState().unlock();
+        state.unlock();
     }
 #else
   public:
-    AutoLockWorkerThreadState(MOZ_GUARD_OBJECT_NOTIFIER_ONLY_PARAM)
+    AutoLockWorkerThreadState(WorkerThreadState &state
+                              MOZ_GUARD_OBJECT_NOTIFIER_PARAM)
     {
         MOZ_GUARD_OBJECT_NOTIFIER_INIT;
     }
 #endif
 };
 
 class AutoUnlockWorkerThreadState
 {
+    JSRuntime *rt;
     MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER
 
   public:
 
-    AutoUnlockWorkerThreadState(MOZ_GUARD_OBJECT_NOTIFIER_ONLY_PARAM)
+    AutoUnlockWorkerThreadState(JSRuntime *rt
+                                MOZ_GUARD_OBJECT_NOTIFIER_PARAM)
+      : rt(rt)
     {
         MOZ_GUARD_OBJECT_NOTIFIER_INIT;
 #ifdef JS_THREADSAFE
-        WorkerThreadState().unlock();
+        JS_ASSERT(rt->workerThreadState);
+        rt->workerThreadState->unlock();
+#else
+        (void)this->rt;
 #endif
     }
 
     ~AutoUnlockWorkerThreadState()
     {
 #ifdef JS_THREADSAFE
-        WorkerThreadState().lock();
+        rt->workerThreadState->lock();
 #endif
     }
 };
 
 #ifdef JS_ION
 struct AsmJSParallelTask
 {
-    JSRuntime *runtime;     // Associated runtime.
     LifoAlloc lifo;         // Provider of all heap memory used for compilation.
     void *func;             // Really, a ModuleCompiler::Func*
     jit::MIRGenerator *mir; // Passed from main thread to worker.
     jit::LIRGraph *lir;     // Passed from worker to main thread.
     unsigned compileTime;
 
     AsmJSParallelTask(size_t defaultChunkSize)
-      : runtime(nullptr), lifo(defaultChunkSize), func(nullptr), mir(nullptr), lir(nullptr), compileTime(0)
+      : lifo(defaultChunkSize), func(nullptr), mir(nullptr), lir(nullptr), compileTime(0)
     { }
 
-    void init(JSRuntime *rt, void *func, jit::MIRGenerator *mir) {
-        this->runtime = rt;
+    void init(void *func, jit::MIRGenerator *mir) {
         this->func = func;
         this->mir = mir;
         this->lir = nullptr;
     }
 };
 #endif
 
 struct ParseTask
@@ -421,20 +371,16 @@ struct ParseTask
     ParseTask(ExclusiveContext *cx, JSObject *exclusiveContextGlobal, JSContext *initCx,
               const jschar *chars, size_t length, JSObject *scopeChain,
               JS::OffThreadCompileCallback callback, void *callbackData);
     bool init(JSContext *cx, const ReadOnlyCompileOptions &options);
 
     void activate(JSRuntime *rt);
     void finish();
 
-    bool runtimeMatches(JSRuntime *rt) {
-        return exclusiveContextGlobal->runtimeFromAnyThread() == rt;
-    }
-
     ~ParseTask();
 };
 
 #ifdef JS_THREADSAFE
 // Return whether, if a new parse task was started, it would need to wait for
 // an in-progress GC to complete before starting.
 extern bool
 OffThreadParsingMustWaitForGC(JSRuntime *rt);
--- a/js/src/shell/js.cpp
+++ b/js/src/shell/js.cpp
@@ -5451,17 +5451,17 @@ ProcessArgs(JSContext *cx, JSObject *obj
     if (op->getBoolOption('D')) {
         cx->runtime()->profilingScripts = true;
         enableDisassemblyDumps = true;
     }
 
 #ifdef JS_THREADSAFE
     int32_t threadCount = op->getIntOption("thread-count");
     if (threadCount >= 0)
-        SetFakeCPUCount(threadCount);
+        cx->runtime()->setFakeCPUCount(threadCount);
 #endif /* JS_THREADSAFE */
 
 #if defined(JS_ION)
     if (op->getBoolOption("no-ion")) {
         enableIon = false;
         JS::ContextOptionsRef(cx).toggleIon();
     }
     if (op->getBoolOption("no-asmjs")) {
--- a/js/src/vm/Runtime.cpp
+++ b/js/src/vm/Runtime.cpp
@@ -86,31 +86,50 @@ PerThreadData::PerThreadData(JSRuntime *
     activeCompilations(0)
 {}
 
 PerThreadData::~PerThreadData()
 {
     if (dtoaState)
         js_DestroyDtoaState(dtoaState);
 
+    if (isInList())
+        removeFromThreadList();
+
 #ifdef JS_ARM_SIMULATOR
     js_delete(simulator_);
 #endif
 }
 
 bool
 PerThreadData::init()
 {
     dtoaState = js_NewDtoaState();
     if (!dtoaState)
         return false;
 
     return true;
 }
 
+void
+PerThreadData::addToThreadList()
+{
+    // PerThreadData which are created/destroyed off the main thread do not
+    // show up in the runtime's thread list.
+    JS_ASSERT(CurrentThreadCanAccessRuntime(runtime_));
+    runtime_->threadList.insertBack(this);
+}
+
+void
+PerThreadData::removeFromThreadList()
+{
+    JS_ASSERT(CurrentThreadCanAccessRuntime(runtime_));
+    removeFrom(runtime_->threadList);
+}
+
 static const JSWrapObjectCallbacks DefaultWrapObjectCallbacks = {
     TransparentObjectWrapper,
     nullptr,
     nullptr
 };
 
 JSRuntime::JSRuntime(JSUseHelperThreads useHelperThreads)
   : JS::shadow::Runtime(
@@ -120,16 +139,17 @@ JSRuntime::JSRuntime(JSUseHelperThreads 
     ),
     mainThread(this),
     interrupt(0),
     handlingSignal(false),
     operationCallback(nullptr),
 #ifdef JS_THREADSAFE
     operationCallbackLock(nullptr),
     operationCallbackOwner(nullptr),
+    workerThreadState(nullptr),
     exclusiveAccessLock(nullptr),
     exclusiveAccessOwner(nullptr),
     mainThreadHasExclusiveAccess(false),
     numExclusiveThreads(0),
     compilationLock(nullptr),
 #ifdef DEBUG
     compilationLockOwner(nullptr),
     mainThreadHasCompilationLock(false),
@@ -291,23 +311,30 @@ JSRuntime::JSRuntime(JSUseHelperThreads 
     jitSupportsFloatingPoint(false),
     ionPcScriptCache(nullptr),
     threadPool(this),
     defaultJSContextCallback(nullptr),
     ctypesActivityCallback(nullptr),
     parallelWarmup(0),
     ionReturnOverride_(MagicValue(JS_ARG_POISON)),
     useHelperThreads_(useHelperThreads),
+#ifdef JS_THREADSAFE
+    cpuCount_(GetCPUCount()),
+#else
+    cpuCount_(1),
+#endif
     parallelIonCompilationEnabled_(true),
     parallelParsingEnabled_(true),
     isWorkerRuntime_(false)
 #ifdef DEBUG
     , enteredPolicy(nullptr)
 #endif
 {
+    MOZ_ASSERT(cpuCount_ > 0, "GetCPUCount() seems broken");
+
     liveRuntimesCount++;
 
     setGCMode(JSGC_MODE_GLOBAL);
 
     /* Initialize infallibly first, so we can goto bad and JS_DestroyRuntime. */
     JS_INIT_CLIST(&onNewGlobalObjectWatchers);
 
     PodZero(&debugHooks);
@@ -360,16 +387,17 @@ JSRuntime::init(uint32_t maxbytes)
     if (!compilationLock)
         return false;
 #endif
 
     if (!mainThread.init())
         return false;
 
     js::TlsPerThreadData.set(&mainThread);
+    mainThread.addToThreadList();
 
     if (!threadPool.init())
         return false;
 
     if (!js_InitGC(this, maxbytes))
         return false;
 
     if (!gcMarker.init(gcMode()))
@@ -430,25 +458,25 @@ JSRuntime::init(uint32_t maxbytes)
 
 JSRuntime::~JSRuntime()
 {
     JS_ASSERT(!isHeapBusy());
 
     /* Free source hook early, as its destructor may want to delete roots. */
     sourceHook = nullptr;
 
-    /*
-     * Cancel any pending, in progress or completed Ion compilations and
-     * parse tasks. Waiting for AsmJS and compression tasks is done
-     * synchronously (on the main thread or during parse tasks), so no
-     * explicit canceling is needed for these.
-     */
+    /* Off thread compilation and parsing depend on atoms still existing. */
     for (CompartmentsIter comp(this, SkipAtoms); !comp.done(); comp.next())
         CancelOffThreadIonCompile(comp, nullptr);
-    CancelOffThreadParses(this);
+    WaitForOffThreadParsingToFinish(this);
+
+#ifdef JS_THREADSAFE
+    if (workerThreadState)
+        workerThreadState->cleanup();
+#endif
 
     /* Poison common names before final GC. */
     FinishCommonNames(this);
 
     /* Clear debugging state to remove GC roots. */
     for (CompartmentsIter comp(this, SkipAtoms); !comp.done(); comp.next()) {
         comp->clearTraps(defaultFreeOp());
         if (WatchpointMap *wpmap = comp->watchpointMap)
@@ -471,17 +499,21 @@ JSRuntime::~JSRuntime()
     GC(this, GC_NORMAL, JS::gcreason::DESTROY_RUNTIME);
 
     /*
      * Clear the self-hosted global and delete self-hosted classes *after*
      * GC, as finalizers for objects check for clasp->finalize during GC.
      */
     finishSelfHosting();
 
+    mainThread.removeFromThreadList();
+
 #ifdef JS_THREADSAFE
+    js_delete(workerThreadState);
+
     JS_ASSERT(!exclusiveAccessOwner);
     if (exclusiveAccessLock)
         PR_DestroyLock(exclusiveAccessLock);
 
     // Avoid bogus asserts during teardown.
     JS_ASSERT(!numExclusiveThreads);
     mainThreadHasExclusiveAccess = true;
 
@@ -973,17 +1005,17 @@ JSRuntime::assertCanLock(RuntimeLock whi
 #ifdef JS_THREADSAFE
     // In the switch below, each case falls through to the one below it. None
     // of the runtime locks are reentrant, and when multiple locks are acquired
     // it must be done in the order below.
     switch (which) {
       case ExclusiveAccessLock:
         JS_ASSERT(exclusiveAccessOwner != PR_GetCurrentThread());
       case WorkerThreadStateLock:
-        JS_ASSERT(!WorkerThreadState().isLocked());
+        JS_ASSERT_IF(workerThreadState, !workerThreadState->isLocked());
       case CompilationLock:
         JS_ASSERT(compilationLockOwner != PR_GetCurrentThread());
       case OperationCallbackLock:
         JS_ASSERT(!currentThreadOwnsOperationCallbackLock());
       case GCLock:
         JS_ASSERT(gcLockOwner != PR_GetCurrentThread());
         break;
       default:
@@ -1045,19 +1077,9 @@ js::CurrentThreadCanReadCompilationData(
         return true;
 
     return pt->runtime_->currentThreadHasCompilationLock();
 #else
     return true;
 #endif
 }
 
-void
-js::AssertCurrentThreadCanLock(RuntimeLock which)
-{
-#ifdef JS_THREADSAFE
-    PerThreadData *pt = TlsPerThreadData.get();
-    if (pt && pt->runtime_)
-        pt->runtime_->assertCanLock(which);
-#endif
-}
-
 #endif // DEBUG
--- a/js/src/vm/Runtime.h
+++ b/js/src/vm/Runtime.h
@@ -79,16 +79,17 @@ namespace WTF { class BumpPointerAllocat
 namespace js {
 
 typedef Rooted<JSLinearString*> RootedLinearString;
 
 class Activation;
 class ActivationIterator;
 class AsmJSActivation;
 class MathCache;
+class WorkerThreadState;
 
 namespace jit {
 class JitRuntime;
 class JitActivation;
 struct PcScriptCache;
 class Simulator;
 class SimulatorRuntime;
 }
@@ -471,41 +472,25 @@ namespace js {
 #define NAME_OFFSET(name)       offsetof(JSAtomState, name)
 
 inline HandlePropertyName
 AtomStateOffsetToName(const JSAtomState &atomState, size_t offset)
 {
     return *(js::FixedHeapPtr<js::PropertyName>*)((char*)&atomState + offset);
 }
 
-// There are several coarse locks in the enum below. These may be either
-// per-runtime or per-process. When acquiring more than one of these locks,
-// the acquisition must be done in the order below to avoid deadlocks.
-enum RuntimeLock {
-    ExclusiveAccessLock,
-    WorkerThreadStateLock,
-    CompilationLock,
-    OperationCallbackLock,
-    GCLock
-};
-
-#ifdef DEBUG
-void AssertCurrentThreadCanLock(RuntimeLock which);
-#else
-inline void AssertCurrentThreadCanLock(RuntimeLock which) {}
-#endif
-
 /*
  * Encapsulates portions of the runtime/context that are tied to a
- * single active thread.  Instances of this structure can occur for
- * the main thread as |JSRuntime::mainThread|, for select operations
- * performed off thread, such as parsing, and for Parallel JS worker
- * threads.
+ * single active thread.  Normally, as most JS is single-threaded,
+ * there is only one instance of this struct, embedded in the
+ * JSRuntime as the field |mainThread|.  During Parallel JS sections,
+ * however, there will be one instance per worker thread.
  */
-class PerThreadData : public PerThreadDataFriendFields
+class PerThreadData : public PerThreadDataFriendFields,
+                      public mozilla::LinkedListElement<PerThreadData>
 {
     /*
      * Backpointer to the full shared JSRuntime* with which this
      * thread is associated.  This is private because accessing the
      * fields of this runtime can provoke race conditions, so the
      * intention is that access will be mediated through safe
      * functions like |runtimeFromMainThread| and |associatedWith()| below.
      */
@@ -548,17 +533,16 @@ class PerThreadData : public PerThreadDa
      */
   private:
     friend class js::Activation;
     friend class js::ActivationIterator;
     friend class js::jit::JitActivation;
     friend class js::AsmJSActivation;
 #ifdef DEBUG
     friend bool js::CurrentThreadCanReadCompilationData();
-    friend void js::AssertCurrentThreadCanLock(RuntimeLock which);
 #endif
 
     /*
      * Points to the most recent activation running on the thread.
      * See Activation comment in vm/Stack.h.
      */
     js::Activation *activation_;
 
@@ -609,44 +593,27 @@ class PerThreadData : public PerThreadDa
 
     // Number of active bytecode compilation on this thread.
     unsigned activeCompilations;
 
     PerThreadData(JSRuntime *runtime);
     ~PerThreadData();
 
     bool init();
+    void addToThreadList();
+    void removeFromThreadList();
 
     bool associatedWith(const JSRuntime *rt) { return runtime_ == rt; }
     inline JSRuntime *runtimeFromMainThread();
     inline JSRuntime *runtimeIfOnOwnerThread();
 
     inline bool exclusiveThreadsPresent();
     inline void addActiveCompilation();
     inline void removeActiveCompilation();
 
-    // For threads which may be associated with different runtimes, depending
-    // on the work they are doing.
-    class AutoEnterRuntime
-    {
-        PerThreadData *pt;
-
-      public:
-        AutoEnterRuntime(PerThreadData *pt, JSRuntime *rt)
-          : pt(pt)
-        {
-            JS_ASSERT(!pt->runtime_);
-            pt->runtime_ = rt;
-        }
-
-        ~AutoEnterRuntime() {
-            pt->runtime_ = nullptr;
-        }
-    };
-
 #ifdef JS_ARM_SIMULATOR
     js::jit::Simulator *simulator() const;
     void setSimulator(js::jit::Simulator *sim);
     js::jit::SimulatorRuntime *simulatorRuntime() const;
     uintptr_t *addressOfSimulatorStackLimit();
 #endif
 };
 
@@ -675,35 +642,51 @@ struct JSRuntime : public JS::shadow::Ru
      *
      * NB: This field is statically asserted to be at offset
      * sizeof(js::shadow::Runtime). See
      * PerThreadDataFriendFields::getMainThread.
      */
     js::PerThreadData mainThread;
 
     /*
+     * List of per-thread data in the runtime, including mainThread. Currently
+     * this does not include instances of PerThreadData created for PJS.
+     */
+    mozilla::LinkedList<js::PerThreadData> threadList;
+
+    /*
      * If non-zero, we were been asked to call the operation callback as soon
      * as possible.
      */
 #ifdef JS_THREADSAFE
     mozilla::Atomic<int32_t, mozilla::Relaxed> interrupt;
 #else
     int32_t interrupt;
 #endif
 
     /* Set when handling a signal for a thread associated with this runtime. */
     bool handlingSignal;
 
     /* Branch callback */
     JSOperationCallback operationCallback;
 
+    // There are several per-runtime locks indicated by the enum below. When
+    // acquiring multiple of these locks, the acquisition must be done in the
+    // order below to avoid deadlocks.
+    enum RuntimeLock {
+        ExclusiveAccessLock,
+        WorkerThreadStateLock,
+        CompilationLock,
+        OperationCallbackLock,
+        GCLock
+    };
 #ifdef DEBUG
-    void assertCanLock(js::RuntimeLock which);
+    void assertCanLock(RuntimeLock which);
 #else
-    void assertCanLock(js::RuntimeLock which) {}
+    void assertCanLock(RuntimeLock which) {}
 #endif
 
   private:
     /*
      * Lock taken when triggering the operation callback from another thread.
      * Protects all data that is touched in this process.
      */
 #ifdef JS_THREADSAFE
@@ -714,17 +697,17 @@ struct JSRuntime : public JS::shadow::Ru
 #endif // JS_THREADSAFE
   public:
 
     class AutoLockForOperationCallback {
         JSRuntime *rt;
       public:
         AutoLockForOperationCallback(JSRuntime *rt MOZ_GUARD_OBJECT_NOTIFIER_PARAM) : rt(rt) {
             MOZ_GUARD_OBJECT_NOTIFIER_INIT;
-            rt->assertCanLock(js::OperationCallbackLock);
+            rt->assertCanLock(JSRuntime::OperationCallbackLock);
 #ifdef JS_THREADSAFE
             PR_Lock(rt->operationCallbackLock);
             rt->operationCallbackOwner = PR_GetCurrentThread();
 #else
             rt->operationCallbackLockTaken = true;
 #endif // JS_THREADSAFE
         }
         ~AutoLockForOperationCallback() {
@@ -745,16 +728,18 @@ struct JSRuntime : public JS::shadow::Ru
         return operationCallbackOwner == PR_GetCurrentThread();
 #else
         return operationCallbackLockTaken;
 #endif
     }
 
 #ifdef JS_THREADSAFE
 
+    js::WorkerThreadState *workerThreadState;
+
   private:
     /*
      * Lock taken when using per-runtime or per-zone data that could otherwise
      * be accessed simultaneously by both the main thread and another thread
      * with an ExclusiveContext.
      *
      * Locking this only occurs if there is actually a thread other than the
      * main thread with an ExclusiveContext which could access such data.
@@ -1394,17 +1379,17 @@ struct JSRuntime : public JS::shadow::Ru
     PRLock *gcLock;
     mozilla::DebugOnly<PRThread *> gcLockOwner;
 
     friend class js::GCHelperThread;
   public:
 
     void lockGC() {
 #ifdef JS_THREADSAFE
-        assertCanLock(js::GCLock);
+        assertCanLock(GCLock);
         PR_Lock(gcLock);
         JS_ASSERT(!gcLockOwner);
 #ifdef DEBUG
         gcLockOwner = PR_GetCurrentThread();
 #endif
 #endif
     }
 
@@ -1727,16 +1712,17 @@ struct JSRuntime : public JS::shadow::Ru
 
     void triggerOperationCallback(OperationCallbackTrigger trigger);
 
     void addSizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf, JS::RuntimeSizes *runtime);
 
   private:
 
     JSUseHelperThreads useHelperThreads_;
+    unsigned cpuCount_;
 
     // Settings for how helper threads can be used.
     bool parallelIonCompilationEnabled_;
     bool parallelParsingEnabled_;
 
     // True iff this is a DOM Worker runtime.
     bool isWorkerRuntime_;
 
@@ -1748,24 +1734,49 @@ struct JSRuntime : public JS::shadow::Ru
     bool useHelperThreads() const {
 #ifdef JS_THREADSAFE
         return useHelperThreads_ == JS_USE_HELPER_THREADS;
 #else
         return false;
 #endif
     }
 
+    // This allows the JS shell to override GetCPUCount() when passed the
+    // --thread-count=N option.
+    void setFakeCPUCount(size_t count) {
+        cpuCount_ = count;
+    }
+
+    // Return a cached value of GetCPUCount() to avoid making the syscall all
+    // the time. Furthermore, this avoids pathological cases where the result of
+    // GetCPUCount() changes during execution.
+    unsigned cpuCount() const {
+        JS_ASSERT(cpuCount_ > 0);
+        return cpuCount_;
+    }
+
+    // The number of worker threads that will be available after
+    // EnsureWorkerThreadsInitialized has been called successfully.
+    unsigned workerThreadCount() const {
+        if (!useHelperThreads())
+            return 0;
+        return js::Max(2u, cpuCount());
+    }
+
     // Note: these values may be toggled dynamically (in response to about:config
     // prefs changing).
     void setParallelIonCompilationEnabled(bool value) {
         parallelIonCompilationEnabled_ = value;
     }
     bool canUseParallelIonCompilation() const {
+        // Require cpuCount_ > 1 so that Ion compilation jobs and main-thread
+        // execution are not competing for the same resources.
         return useHelperThreads() &&
-               parallelIonCompilationEnabled_;
+               parallelIonCompilationEnabled_ &&
+               cpuCount_ > 1;
     }
     void setParallelParsingEnabled(bool value) {
         parallelParsingEnabled_ = value;
     }
     bool canUseParallelParsing() const {
         return useHelperThreads() &&
                parallelParsingEnabled_;
     }
--- a/js/src/vm/ThreadPool.cpp
+++ b/js/src/vm/ThreadPool.cpp
@@ -386,22 +386,18 @@ ThreadPool::init()
 #else
     return true;
 #endif
 }
 
 uint32_t
 ThreadPool::numWorkers() const
 {
-#ifdef JS_THREADSAFE
     // Subtract one for the main thread, which always exists.
-    return WorkerThreadState().cpuCount - 1;
-#else
-    return 0;
-#endif
+    return runtime_->cpuCount() - 1;
 }
 
 bool
 ThreadPool::workStealing() const
 {
 #ifdef DEBUG
     if (char *stealEnv = getenv("JS_THREADPOOL_STEAL"))
         return !!strtol(stealEnv, nullptr, 10);
--- a/mobile/android/base/BrowserApp.java
+++ b/mobile/android/base/BrowserApp.java
@@ -515,25 +515,16 @@ abstract public class BrowserApp extends
                 hideBrowserSearch();
                 hideHomePager();
 
                 // Re-enable doorhanger notifications. They may trigger on the selected tab above.
                 mDoorHangerPopup.enable();
             }
         });
 
-        mBrowserToolbar.setOnFocusChangeListener(new View.OnFocusChangeListener() {
-            @Override
-            public void onFocusChange(View v, boolean hasFocus) {
-                if (isHomePagerVisible()) {
-                    mHomePager.onToolbarFocusChange(hasFocus);
-                }
-            }
-        });
-
         // Intercept key events for gamepad shortcuts
         mBrowserToolbar.setOnKeyListener(this);
 
         if (mTabsPanel != null) {
             mTabsPanel.setTabsLayoutChangeListener(this);
             updateSideBarState();
         }
 
@@ -1653,17 +1644,17 @@ abstract public class BrowserApp extends
         // Show the toolbar before hiding about:home so the
         // onMetricsChanged callback still works.
         if (isDynamicToolbarEnabled() && mLayerView != null) {
             mLayerView.getLayerMarginsAnimator().showMargins(true);
         }
 
         if (mHomePager == null) {
             final ViewStub homePagerStub = (ViewStub) findViewById(R.id.home_pager_stub);
-            mHomePager = (HomePager) homePagerStub.inflate().findViewById(R.id.home_pager);
+            mHomePager = (HomePager) homePagerStub.inflate();
         }
 
         mHomePager.show(getSupportLoaderManager(),
                         getSupportFragmentManager(),
                         pageId, animator);
 
         // Hide the web content so it cannot be focused by screen readers.
         hideWebContentOnPropertyAnimationEnd(animator);
--- a/mobile/android/base/home/HomeBanner.java
+++ b/mobile/android/base/home/HomeBanner.java
@@ -1,19 +1,15 @@
 /* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
  * This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 package org.mozilla.gecko.home;
 
-import org.mozilla.gecko.animation.PropertyAnimator;
-import org.mozilla.gecko.animation.PropertyAnimator.Property;
-import org.mozilla.gecko.animation.PropertyAnimator.PropertyAnimationListener;
-import org.mozilla.gecko.animation.ViewHelper;
 import org.mozilla.gecko.GeckoAppShell;
 import org.mozilla.gecko.GeckoEvent;
 import org.mozilla.gecko.R;
 import org.mozilla.gecko.gfx.BitmapUtils;
 import org.mozilla.gecko.util.GeckoEventListener;
 import org.mozilla.gecko.util.ThreadUtils;
 
 import org.json.JSONException;
@@ -22,229 +18,121 @@ import org.json.JSONObject;
 import android.content.Context;
 import android.graphics.drawable.Drawable;
 import android.text.Html;
 import android.text.Spanned;
 import android.text.TextUtils;
 import android.util.AttributeSet;
 import android.util.Log;
 import android.view.LayoutInflater;
-import android.view.MotionEvent;
 import android.view.View;
 import android.widget.ImageButton;
 import android.widget.ImageView;
 import android.widget.LinearLayout;
 import android.widget.TextView;
 
 public class HomeBanner extends LinearLayout
                         implements GeckoEventListener {
     private static final String LOGTAG = "GeckoHomeBanner";
 
-    final TextView mTextView;
-    final ImageView mIconView;
-    final ImageButton mCloseButton;
-
-    // Used for tracking scroll length
-    private float mTouchY = -1;
-
-    // Used to detect for upwards scroll to push banner all the way up
-    private boolean mSnapBannerToTop;
-
-    // Used so that we don't move the banner when scrolling between pages
-    private boolean mScrollingPages = false;
-
-    // User has dismissed the banner using the close button
-    private boolean mDismissed = false;
-
     public HomeBanner(Context context) {
         this(context, null);
     }
 
     public HomeBanner(Context context, AttributeSet attrs) {
         super(context, attrs);
 
         LayoutInflater.from(context).inflate(R.layout.home_banner, this);
-        mTextView = (TextView) findViewById(R.id.text);
-        mIconView = (ImageView) findViewById(R.id.icon);
-        mCloseButton = (ImageButton) findViewById(R.id.close);
+    }
 
-        mCloseButton.getDrawable().setAlpha(127);
+    @Override
+    public void onAttachedToWindow() {
+        super.onAttachedToWindow();
+
         // Tapping on the close button will ensure that the banner is never
         // showed again on this session.
-        mCloseButton.setOnClickListener(new View.OnClickListener() {
+        final ImageButton closeButton = (ImageButton) findViewById(R.id.close);
+
+        // The drawable should have 50% opacity.
+        closeButton.getDrawable().setAlpha(127);
+
+        closeButton.setOnClickListener(new View.OnClickListener() {
             @Override
             public void onClick(View view) {
-                animateDown();
-                mDismissed = true;
+                HomeBanner.this.setVisibility(View.GONE);
             }
         });
 
         setOnClickListener(new View.OnClickListener() {
             @Override
             public void onClick(View v) {
                 // Send the current message id back to JS.
-                GeckoAppShell.sendEventToGecko(
-                    GeckoEvent.createBroadcastEvent("HomeBanner:Click",(String) getTag()));
+                GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("HomeBanner:Click", (String) getTag()));
             }
         });
-    }
 
-    @Override
-    public void onAttachedToWindow() {
         GeckoAppShell.getEventDispatcher().registerEventListener("HomeBanner:Data", this);
+        GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("HomeBanner:Get", null));
     }
 
     @Override
     public void onDetachedFromWindow() {
-        GeckoAppShell.getEventDispatcher().unregisterEventListener("HomeBanner:Data", this);
-    }
+        super.onDetachedFromWindow();
 
-    public void show() {
-        if (!mDismissed) {
-            GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("HomeBanner:Get", null));
-        }
-    }
+        GeckoAppShell.getEventDispatcher().unregisterEventListener("HomeBanner:Data", this);
+     }
 
-    public void hide() {
-        animateDown();
-    }
-
-    public void setScrollingPages(boolean scrollingPages) {
-        mScrollingPages = scrollingPages;
+    public boolean isDismissed() {
+        return (getVisibility() == View.GONE);
     }
 
     @Override
-    public void handleMessage(final String event, final JSONObject message) {
-        ThreadUtils.postToUiThread(new Runnable() {
-            @Override
-            public void run() {
-                try {
-                    // Store the current message id to pass back to JS in the view's OnClickListener.
-                    setTag(message.getString("id"));
-                    setText(message.getString("text"));
-                    setIcon(message.optString("iconURI"));
-                    animateUp();
-                } catch (JSONException e) {
-                    Log.e(LOGTAG, "Exception handling " + event + " message", e);
+    public void handleMessage(String event, JSONObject message) {
+        try {
+            // Store the current message id to pass back to JS in the view's OnClickListener.
+            setTag(message.getString("id"));
+
+            // Display styled text from an HTML string.
+            final Spanned text = Html.fromHtml(message.getString("text"));
+            final TextView textView = (TextView) findViewById(R.id.text);
+
+            // Update the banner message on the UI thread.
+            ThreadUtils.postToUiThread(new Runnable() {
+                @Override
+                public void run() {
+                    textView.setText(text);
+                    setVisibility(View.VISIBLE);
                 }
-            }
-        });
-    }
+            });
+        } catch (JSONException e) {
+            Log.e(LOGTAG, "Exception handling " + event + " message", e);
+            return;
+        }
 
-    private void setText(String text) {
-        // Display styled text from an HTML string.
-        final Spanned html = Html.fromHtml(text);
+        final String iconURI = message.optString("iconURI");
+        final ImageView iconView = (ImageView) findViewById(R.id.icon);
 
-        // Update the banner message on the UI thread.
-        mTextView.setText(html);
-    }
-
-    private void setIcon(String iconURI) {
         if (TextUtils.isEmpty(iconURI)) {
             // Hide the image view if we don't have an icon to show.
-            mIconView.setVisibility(View.GONE);
+            iconView.setVisibility(View.GONE);
             return;
         }
 
         BitmapUtils.getDrawable(getContext(), iconURI, new BitmapUtils.BitmapLoader() {
             @Override
             public void onBitmapFound(final Drawable d) {
                 // Bail if getDrawable doesn't find anything.
                 if (d == null) {
-                    mIconView.setVisibility(View.GONE);
+                    iconView.setVisibility(View.GONE);
                     return;
                 }
 
-                // Update the banner icon
-                mIconView.setImageDrawable(d);
-            }
-        });
-    }
-
-    private void animateDown() {
-        // No need to animate if already translated.
-        if (getVisibility() == GONE && ViewHelper.getTranslationY(this) == getHeight()) {
-            return;
-        }
-
-        final PropertyAnimator animator = new PropertyAnimator(100);
-        animator.attach(this, Property.TRANSLATION_Y, getHeight());
-        animator.start();
-        animator.addPropertyAnimationListener(new PropertyAnimationListener() {
-            @Override
-            public void onPropertyAnimationStart() {}
-            public void onPropertyAnimationEnd() {
-                HomeBanner.this.setVisibility(GONE);
+                // Update the banner icon on the UI thread.
+                ThreadUtils.postToUiThread(new Runnable() {
+                    @Override
+                    public void run() {
+                        iconView.setImageDrawable(d);
+                    }
+                });
             }
         });
     }
-
-    private void animateUp() {
-        // No need to animate if already translated.
-        if (getVisibility() == VISIBLE && ViewHelper.getTranslationY(this) == 0) {
-            return;
-        }
-
-        setVisibility(View.VISIBLE);
-        final PropertyAnimator animator = new PropertyAnimator(100);
-        animator.attach(this, Property.TRANSLATION_Y, 0);
-        animator.start();
-    }
-
-    /**
-     * Touches to the HomePager are forwarded here to handle the hiding / showing of the banner
-     * on scroll.
-     */
-    public void handleHomeTouch(MotionEvent event) {
-        if (mDismissed || mScrollingPages) {
-            return;
-        }
-
-        switch (event.getActionMasked()) {
-            case MotionEvent.ACTION_DOWN: {
-                mTouchY = event.getRawY();
-                break;
-            }
-
-            case MotionEvent.ACTION_MOVE: {
-                // There is a chance that we won't receive ACTION_DOWN, if the touch event
-                // actually started on the Grid instead of the List. Treat this as first event.
-                if (mTouchY == -1) {
-                    mTouchY = event.getRawY();
-                    return;
-                }
-
-                final float curY = event.getRawY();
-                final float delta = mTouchY - curY;
-                mSnapBannerToTop = delta <= 0.0f;
-
-                final float height = getHeight();
-                float newTranslationY = ViewHelper.getTranslationY(this) + delta;
-
-                // Clamp the values to be between 0 and height.
-                if (newTranslationY < 0.0f) {
-                    newTranslationY = 0.0f;
-                } else if (newTranslationY > height) {
-                    newTranslationY = height;
-                }
-
-                ViewHelper.setTranslationY(this, newTranslationY);
-                mTouchY = curY;
-                break;
-            }
-
-            case MotionEvent.ACTION_UP:
-            case MotionEvent.ACTION_CANCEL: {
-                mTouchY = -1;
-                final float y = ViewHelper.getTranslationY(this);
-                final float height = getHeight();
-                if (y > 0.0f && y < height) {
-                    if (mSnapBannerToTop) {
-                        animateUp();
-                    } else {
-                        animateDown();
-                    }
-                }
-                break;
-            }
-        }
-    }
 }
--- a/mobile/android/base/home/HomePager.java
+++ b/mobile/android/base/home/HomePager.java
@@ -19,44 +19,39 @@ import android.os.Bundle;
 import android.support.v4.app.Fragment;
 import android.support.v4.app.FragmentManager;
 import android.support.v4.app.LoaderManager;
 import android.support.v4.app.LoaderManager.LoaderCallbacks;
 import android.support.v4.content.Loader;
 import android.support.v4.view.ViewPager;
 import android.view.ViewGroup.LayoutParams;
 import android.util.AttributeSet;
-import android.util.Log;
 import android.view.MotionEvent;
 import android.view.ViewGroup;
-import android.view.ViewParent;
 import android.view.View;
 
 import java.util.ArrayList;
 import java.util.EnumSet;
 import java.util.List;
 
 public class HomePager extends ViewPager {
-    private static final String LOGTAG = "GeckoHomePager";
 
     private static final int LOADER_ID_CONFIG = 0;
 
     private final Context mContext;
     private volatile boolean mLoaded;
     private Decor mDecor;
     private View mTabStrip;
-    private HomeBanner mHomeBanner;
 
     private final OnAddPanelListener mAddPanelListener;
 
     private final HomeConfig mConfig;
     private ConfigLoaderCallbacks mConfigLoaderCallbacks;
 
     private String mInitialPanelId;
-    private int mDefaultPanelIndex;
 
     // Whether or not we need to restart the loader when we show the HomePager.
     private boolean mRestartLoader;
 
     // This is mostly used by UI tests to easily fetch
     // specific list views at runtime.
     static final String LIST_TAG_HISTORY = "history";
     static final String LIST_TAG_BOOKMARKS = "bookmarks";
@@ -239,40 +234,27 @@ public class HomePager extends ViewPager
             });
 
             ViewHelper.setAlpha(this, 0.0f);
 
             animator.attach(this,
                             PropertyAnimator.Property.ALPHA,
                             1.0f);
         }
-
-        // Setup banner and decor listeners
-        mHomeBanner = (HomeBanner) ((ViewGroup) getParent()).findViewById(R.id.home_banner);
-        setOnPageChangeListener(new HomePagerOnPageChangeListener());
     }
 
     /**
      * Hides the pager and removes all child fragments.
      */
     public void hide() {
         mLoaded = false;
         setVisibility(GONE);
         setAdapter(null);
     }
 
-    @Override
-    public void setVisibility(int visibility) {
-        // Ensure that no decorations are overlaying the mainlayout
-        if (mHomeBanner != null) {
-            mHomeBanner.setVisibility(visibility);
-        }
-        super.setVisibility(visibility);
-    }
-
     /**
      * Determines whether the pager is visible.
      *
      * Unlike getVisibility(), this method does not need to be called on the UI
      * thread.
      *
      * @return Whether the pager and its fragments are being displayed
      */
@@ -282,53 +264,28 @@ public class HomePager extends ViewPager
 
     @Override
     public void setCurrentItem(int item, boolean smoothScroll) {
         super.setCurrentItem(item, smoothScroll);
 
         if (mDecor != null) {
             mDecor.onPageSelected(item);
         }
-        if (mHomeBanner != null) {
-            if (item == mDefaultPanelIndex) {
-                mHomeBanner.show();
-            } else {
-                mHomeBanner.hide();
-            }
-        }
     }
 
     @Override
     public boolean onInterceptTouchEvent(MotionEvent event) {
         if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
             // Drop the soft keyboard by stealing focus from the URL bar.
             requestFocus();
         }
 
         return super.onInterceptTouchEvent(event);
     }
 
-    @Override
-    public boolean dispatchTouchEvent(MotionEvent event) {
-        // Get touches to pages, pass to banner, and forward to pages.
-        if (mHomeBanner != null) {
-            mHomeBanner.handleHomeTouch(event);
-        }
-
-        return super.dispatchTouchEvent(event);
-    }
-
-    public void onToolbarFocusChange(boolean hasFocus) {
-        if (hasFocus) {
-            mHomeBanner.hide();
-        } else if (mDefaultPanelIndex == getCurrentItem() || getAdapter().getCount() == 0) {
-            mHomeBanner.show();
-        }
-    }
-
     private void updateUiFromPanelConfigs(List<PanelConfig> panelConfigs) {
         // We only care about the adapter if HomePager is currently
         // loaded, which means it's visible in the activity.
         if (!mLoaded) {
             return;
         }
 
         if (mDecor != null) {
@@ -342,19 +299,16 @@ public class HomePager extends ViewPager
         setAdapter(null);
 
         // Only keep enabled panels.
         final List<PanelConfig> enabledPanels = new ArrayList<PanelConfig>();
 
         for (PanelConfig panelConfig : panelConfigs) {
             if (!panelConfig.isDisabled()) {
                 enabledPanels.add(panelConfig);
-                if (panelConfig.isDefault()) {
-                    mDefaultPanelIndex = enabledPanels.size() - 1;
-                }
             }
         }
 
         // Update the adapter with the new panel configs
         adapter.update(enabledPanels);
 
         // Hide the tab strip if the new configuration contains no panels.
         final int count = enabledPanels.size();
@@ -390,40 +344,9 @@ public class HomePager extends ViewPager
         public void onLoadFinished(Loader<List<PanelConfig>> loader, List<PanelConfig> panelConfigs) {
             updateUiFromPanelConfigs(panelConfigs);
         }
 
         @Override
         public void onLoaderReset(Loader<List<PanelConfig>> loader) {
         }
     }
-
-    private class HomePagerOnPageChangeListener implements ViewPager.OnPageChangeListener {
-        @Override
-        public void onPageSelected(int position) {
-            if (mDecor != null) {
-                mDecor.onPageSelected(position);
-            }
-
-            if (mHomeBanner != null) {
-                if (position == mDefaultPanelIndex) {
-                    mHomeBanner.show();
-                } else {
-                    mHomeBanner.hide();
-                }
-            }
-        }
-
-        @Override
-        public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
-            if (mDecor != null) {
-                mDecor.onPageScrolled(position, positionOffset, positionOffsetPixels);
-            }
-
-            if (mHomeBanner != null) {
-                mHomeBanner.setScrollingPages(positionOffsetPixels > 0);
-            }
-        }
-
-        @Override
-        public void onPageScrollStateChanged(int state) { }
-    }
 }
--- a/mobile/android/base/home/TopSitesPanel.java
+++ b/mobile/android/base/home/TopSitesPanel.java
@@ -81,16 +81,25 @@ public class TopSitesPanel extends HomeF
     private TopSitesGridAdapter mGridAdapter;
 
     // List of top sites
     private ListView mList;
 
     // Grid of top sites
     private TopSitesGridView mGrid;
 
+    // Banner to show snippets.
+    private HomeBanner mBanner;
+
+    // Raw Y value of the last event that happened on the list view.
+    private float mListTouchY = -1;
+
+    // Scrolling direction of the banner.
+    private boolean mSnapBannerToTop;
+
     // Callbacks used for the search and favicon cursor loaders
     private CursorLoaderCallbacks mCursorLoaderCallbacks;
 
     // Callback for thumbnail loader
     private ThumbnailsLoaderCallbacks mThumbnailsLoaderCallbacks;
 
     // Listener for editing pinned sites.
     private EditPinnedSiteListener mEditPinnedSiteListener;
@@ -192,16 +201,25 @@ public class TopSitesPanel extends HomeF
             }
         });
 
         mGrid.setOnUrlOpenListener(mUrlOpenListener);
         mGrid.setOnEditPinnedSiteListener(mEditPinnedSiteListener);
 
         registerForContextMenu(mList);
         registerForContextMenu(mGrid);
+
+        mBanner = (HomeBanner) view.findViewById(R.id.home_banner);
+        mList.setOnTouchListener(new OnTouchListener() {
+            @Override
+            public boolean onTouch(View v, MotionEvent event) {
+                TopSitesPanel.this.handleListTouchEvent(event);
+                return false;
+            }
+        });
     }
 
     @Override
     public void onDestroyView() {
         super.onDestroyView();
 
         // Discard any additional item clicks on the list
         // as the panel is getting destroyed (see bug 930160).
@@ -430,16 +448,70 @@ public class TopSitesPanel extends HomeF
                 @Override
                 public void run() {
                     BrowserDB.pinSite(context.getContentResolver(), url, title, position);
                 }
             });
         }
     }
 
+    private void handleListTouchEvent(MotionEvent event) {
+        // Ignore the event if the banner is hidden for this session.
+        if (mBanner.isDismissed()) {
+            return;
+        }
+
+        switch (event.getActionMasked()) {
+            case MotionEvent.ACTION_DOWN: {
+                mListTouchY = event.getRawY();
+                break;
+            }
+
+            case MotionEvent.ACTION_MOVE: {
+                // There is a chance that we won't receive ACTION_DOWN, if the touch event
+                // actually started on the Grid instead of the List. Treat this as first event.
+                if (mListTouchY == -1) {
+                    mListTouchY = event.getRawY();
+                    return;
+                }
+
+                final float curY = event.getRawY();
+                final float delta = mListTouchY - curY;
+                mSnapBannerToTop = (delta > 0.0f) ? false : true;
+
+                final float height = mBanner.getHeight();
+                float newTranslationY = ViewHelper.getTranslationY(mBanner) + delta;
+
+                // Clamp the values to be between 0 and height.
+                if (newTranslationY < 0.0f) {
+                    newTranslationY = 0.0f;
+                } else if (newTranslationY > height) {
+                    newTranslationY = height;
+                }
+
+                ViewHelper.setTranslationY(mBanner, newTranslationY);
+                mListTouchY = curY;
+                break;
+            }
+
+            case MotionEvent.ACTION_UP:
+            case MotionEvent.ACTION_CANCEL: {
+                mListTouchY = -1;
+                final float y = ViewHelper.getTranslationY(mBanner);
+                final float height = mBanner.getHeight();
+                if (y > 0.0f && y < height) {
+                    final PropertyAnimator animator = new PropertyAnimator(100);
+                    animator.attach(mBanner, Property.TRANSLATION_Y, mSnapBannerToTop ? 0 : height);
+                    animator.start();
+                }
+                break;
+            }
+        }
+    }
+
     private void updateUiFromCursor(Cursor c) {
         mList.setHeaderDividersEnabled(c != null && c.getCount() > mMaxGridEntries);
     }
 
     private static class TopSitesLoader extends SimpleCursorLoader {
         // Max number of search results
         private static final int SEARCH_LIMIT = 30;
         private int mMaxGridEntries;
--- a/mobile/android/base/resources/layout-large-v11/home_pager.xml
+++ b/mobile/android/base/resources/layout-large-v11/home_pager.xml
@@ -9,16 +9,15 @@
 <org.mozilla.gecko.home.HomePager xmlns:android="http://schemas.android.com/apk/res/android"
                                   xmlns:gecko="http://schemas.android.com/apk/res-auto"
                                   android:id="@+id/home_pager"
                                   android:layout_width="fill_parent"
                                   android:layout_height="fill_parent"
                                   android:background="@android:color/white"
                                   android:visibility="gone">
 
-    <org.mozilla.gecko.home.TabMenuStrip android:id="@+id/tablet_menu_strip"
-                                         android:layout_width="fill_parent"
+    <org.mozilla.gecko.home.TabMenuStrip android:layout_width="fill_parent"
                                          android:layout_height="32dip"
                                          android:background="@color/background_light"
                                          android:layout_gravity="top"
                                          gecko:strip="@drawable/home_tab_menu_strip"/>
 
 </org.mozilla.gecko.home.HomePager>
--- a/mobile/android/base/resources/layout/gecko_app.xml
+++ b/mobile/android/base/resources/layout/gecko_app.xml
@@ -31,26 +31,16 @@
                          android:layout_width="fill_parent"
                          android:layout_height="fill_parent">
 
                 <ViewStub android:id="@+id/home_pager_stub"
                           android:layout="@layout/home_pager"
                           android:layout_width="fill_parent"
                           android:layout_height="fill_parent"/>
 
-                <org.mozilla.gecko.home.HomeBanner android:id="@+id/home_banner"
-                                                   style="@style/Widget.HomeBanner"
-                                                   android:layout_width="fill_parent"
-                                                   android:layout_height="@dimen/home_banner_height"
-                                                   android:background="@drawable/home_banner"
-                                                   android:layout_gravity="bottom"
-                                                   android:gravity="center_vertical"
-                                                   android:visibility="gone"
-                                                   android:clickable="true"
-                                                   android:focusable="true"/>
 
             </FrameLayout>
 
         </RelativeLayout>
 
         <org.mozilla.gecko.FindInPageBar android:id="@+id/find_in_page"
                                          android:layout_width="fill_parent"
                                          android:layout_height="wrap_content"
--- a/mobile/android/base/resources/layout/home_pager.xml
+++ b/mobile/android/base/resources/layout/home_pager.xml
@@ -9,18 +9,17 @@
 <org.mozilla.gecko.home.HomePager xmlns:android="http://schemas.android.com/apk/res/android"
                                   xmlns:gecko="http://schemas.android.com/apk/res-auto"
                                   android:id="@+id/home_pager"
                                   android:layout_width="fill_parent"
                                   android:layout_height="fill_parent"
                                   android:background="@android:color/white"
                                   android:visibility="gone">
 
-    <org.mozilla.gecko.home.HomePagerTabStrip android:id="@+id/phone_menu_strip"
-                                              android:layout_width="fill_parent"
+    <org.mozilla.gecko.home.HomePagerTabStrip android:layout_width="fill_parent"
                                               android:layout_height="32dip"
                                               android:layout_gravity="top"
                                               android:gravity="bottom"
                                               android:background="@color/background_light"
                                               gecko:tabIndicatorColor="@color/text_color_highlight"
                                               android:textAppearance="@style/TextAppearance.Widget.HomePagerTabStrip"/>
 
 </org.mozilla.gecko.home.HomePager>
--- a/mobile/android/base/resources/layout/home_top_sites_panel.xml
+++ b/mobile/android/base/resources/layout/home_top_sites_panel.xml
@@ -1,10 +1,28 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!-- This Source Code Form is subject to the terms of the Mozilla Public
    - License, v. 2.0. If a copy of the MPL was not distributed with this
    - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
 
-<org.mozilla.gecko.home.HomeListView xmlns:android="http://schemas.android.com/apk/res/android"
-        android:id="@+id/list"
-        style="@style/Widget.TopSitesListView"
-        android:layout_width="fill_parent"
-        android:layout_height="fill_parent" />
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+             android:layout_width="fill_parent"
+             android:layout_height="fill_parent"
+             android:orientation="vertical">
+
+    <org.mozilla.gecko.home.HomeListView
+            android:id="@+id/list"
+            style="@style/Widget.TopSitesListView"
+            android:layout_width="fill_parent"
+            android:layout_height="fill_parent"/>
+
+    <org.mozilla.gecko.home.HomeBanner android:id="@+id/home_banner"
+                                       style="@style/Widget.HomeBanner"
+                                       android:layout_width="fill_parent"
+                                       android:layout_height="@dimen/home_banner_height"
+                                       android:background="@drawable/home_banner"
+                                       android:layout_gravity="bottom"
+                                       android:gravity="center_vertical"
+                                       android:visibility="gone"
+                                       android:clickable="true"
+                                       android:focusable="true"/>
+
+</FrameLayout>
--- a/mobile/android/base/toolbar/BrowserToolbar.java
+++ b/mobile/android/base/toolbar/BrowserToolbar.java
@@ -137,17 +137,16 @@ public class BrowserToolbar extends Geck
     private List<View> mFocusOrder;
 
     private OnActivateListener mActivateListener;
     private OnCommitListener mCommitListener;
     private OnDismissListener mDismissListener;
     private OnFilterListener mFilterListener;
     private OnStartEditingListener mStartEditingListener;
     private OnStopEditingListener mStopEditingListener;
-    private OnFocusChangeListener mFocusChangeListener;
 
     final private BrowserApp mActivity;
     private boolean mHasSoftMenuButton;
 
     private UIMode mUIMode;
     private boolean mAnimatingEntry;
 
     private int mUrlBarViewOffset;
@@ -311,19 +310,16 @@ public class BrowserToolbar extends Geck
                 setContentDescription(contentDescription);
             }
         });
 
         mUrlEditLayout.setOnFocusChangeListener(new View.OnFocusChangeListener() {
             @Override
             public void onFocusChange(View v, boolean hasFocus) {
                 setSelected(hasFocus);
-                if (mFocusChangeListener != null) {
-                    mFocusChangeListener.onFocusChange(v, hasFocus);
-                }
             }
         });
 
         mTabs.setOnClickListener(new Button.OnClickListener() {
             @Override
             public void onClick(View v) {
                 toggleTabs();
             }
@@ -792,20 +788,16 @@ public class BrowserToolbar extends Geck
     public void setOnStartEditingListener(OnStartEditingListener listener) {
         mStartEditingListener = listener;
     }
 
     public void setOnStopEditingListener(OnStopEditingListener listener) {
         mStopEditingListener = listener;
     }
 
-    public void setOnFocusChangeListener(OnFocusChangeListener listener) {
-        mFocusChangeListener = listener;
-    }
-
     private void showUrlEditLayout() {
         setUrlEditLayoutVisibility(true, null);
     }
 
     private void showUrlEditLayout(PropertyAnimator animator) {
         setUrlEditLayoutVisibility(true, animator);
     }
 
--- a/toolkit/components/crashmonitor/CrashMonitor.jsm
+++ b/toolkit/components/crashmonitor/CrashMonitor.jsm
@@ -29,22 +29,25 @@
  * checkpoint file tells us that the corresponding stage was reached
  * during the last run, the absence of a notification after a crash
  * does not necessarily tell us that the checkpoint wasn't reached.
  */
 
 this.EXPORTED_SYMBOLS = [ "CrashMonitor" ];
 
 const Cu = Components.utils;
+const Cr = Components.results;
 
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/osfile.jsm");
 Cu.import("resource://gre/modules/Promise.jsm");
 Cu.import("resource://gre/modules/Task.jsm");
 Cu.import("resource://gre/modules/AsyncShutdown.jsm");
+Cu.import("resource://gre/modules/NetUtil.jsm");
+Cu.import("resource://gre/modules/FileUtils.jsm");
 
 const NOTIFICATIONS = [
   "final-ui-startup",
   "sessionstore-windows-restored",
   "quit-application-granted",
   "quit-application",
   "profile-change-net-teardown",
   "profile-change-teardown",
@@ -88,35 +91,52 @@ let CrashMonitorInternal = {
   path: OS.Path.join(OS.Constants.Path.profileDir, "sessionCheckpoints.json"),
 
   /**
    * Load checkpoints from previous session asynchronously.
    *
    * @return {Promise} A promise that resolves/rejects once loading is complete
    */
   loadPreviousCheckpoints: function () {
-    let promise = Task.spawn(function () {
-      let notifications;
+    let deferred = Promise.defer();
+    CrashMonitorInternal.previousCheckpoints = deferred.promise;
+
+    let file = FileUtils.File(CrashMonitorInternal.path);
+    NetUtil.asyncFetch(file, function(inputStream, status) {
+      if (!Components.isSuccessCode(status)) {
+        if (status != Cr.NS_ERROR_FILE_NOT_FOUND) {
+          Cu.reportError("Error while loading crash monitor data: " + status);
+        }
+
+        deferred.resolve(null);
+        return;
+      }
+
+      let data = NetUtil.readInputStreamToString(inputStream,
+        inputStream.available(), { charset: "UTF-8" });
+
+      let notifications = null;
       try {
-        let decoder = new TextDecoder();
-        let data = yield OS.File.read(CrashMonitorInternal.path);
-        let contents = decoder.decode(data);
-        notifications = JSON.parse(contents);
-      } catch (ex if ex instanceof OS.File.Error && ex.becauseNoSuchFile) {
-        // If checkpoint file cannot be read
-        throw new Task.Result(null);
+        notifications = JSON.parse(data);
       } catch (ex) {
-        Cu.reportError("Error while loading crash monitor data: " + ex);
-        throw new Task.Result(null);
+        Cu.reportError("Error while parsing crash monitor data: " + ex);
+        deferred.resolve(null);
       }
-      throw new Task.Result(Object.freeze(notifications));
+
+      try {
+        deferred.resolve(Object.freeze(notifications));
+      } catch (ex) {
+        // The only exception we reject from is if notifications is not
+        // an object. This happens when the checkpoints file contained
+        // just a numeric string.
+        deferred.reject(ex);
+      }
     });
 
-    CrashMonitorInternal.previousCheckpoints = promise;
-    return promise;
+    return deferred.promise;
   }
 };
 
 this.CrashMonitor = {
 
   /**
    * Notifications received during previous session.
    *
--- a/toolkit/content/tests/chrome/test_arrowpanel.xul
+++ b/toolkit/content/tests/chrome/test_arrowpanel.xul
@@ -260,19 +260,19 @@ function checkPanelPosition(panel)
   is(arrow.getAttribute("side"), expectedSide, "panel arrow side");
   is(arrow.hidden, false, "panel hidden");
   is(arrow.parentNode.pack, expectedPack, "panel arrow pack");
   is(panel.alignmentPosition, expectedAlignment, "panel alignmentPosition");
 
   panel.hidePopup();
 }
 
-function isWithinHalfPixel(a, b)
+function isWithinHalfPixel(a, b, desc)
 {
-  return Math.abs(a - b) <= 0.5;
+  ok(Math.abs(a - b) <= 0.5, desc);
 }
 
 function checkBigPanel(panel)
 {
   ok(panel.firstChild.getBoundingClientRect().height < 2800, "big panel height");
   panel.hidePopup();
 }
 
--- a/toolkit/content/widgets/text.xml
+++ b/toolkit/content/widgets/text.xml
@@ -54,45 +54,56 @@
 
   <binding id="label-control" extends="chrome://global/content/bindings/text.xml#text-label">
     <content>
       <children/><html:span anonid="accessKeyParens"></html:span>
     </content>
     <implementation implements="nsIDOMXULLabelElement">
       <constructor>
         <![CDATA[
-          try {
-            var prefs = Components.classes["@mozilla.org/preferences-service;1"].
-                                   getService(Components.interfaces.nsIPrefBranch);
-            this.mUnderlineAccesskey = (prefs.getIntPref("ui.key.menuAccessKey") != 0);
-
-            const nsIPrefLocalizedString =
-              Components.interfaces.nsIPrefLocalizedString;
-
-            const prefNameInsertSeparator =
-              "intl.menuitems.insertseparatorbeforeaccesskeys";
-            const prefNameAlwaysAppendAccessKey =
-              "intl.menuitems.alwaysappendaccesskeys";
-
-            var val = prefs.getComplexValue(prefNameInsertSeparator,
-                                            nsIPrefLocalizedString).data;
-            this.mInsertSeparator = (val == "true");
-
-            val = prefs.getComplexValue(prefNameAlwaysAppendAccessKey,
-                                        nsIPrefLocalizedString).data;
-            this.mAlwaysAppendAccessKey = (val == "true");
-          }
-          catch (e) { }
-          this.formatAccessKey();
+          this.formatAccessKey(true);
         ]]>
       </constructor>
 
       <method name="formatAccessKey">
+        <parameter name="firstTime"/>
         <body>
           <![CDATA[
+            var accessKey = this.accessKey;
+            // No need to remove existing formatting the first time.
+            if (firstTime && !accessKey)
+              return;
+
+            if (this.mInsertSeparator === undefined) {
+              try {
+                var prefs = Components.classes["@mozilla.org/preferences-service;1"].
+                                       getService(Components.interfaces.nsIPrefBranch);
+                this.mUnderlineAccesskey = (prefs.getIntPref("ui.key.menuAccessKey") != 0);
+
+                const nsIPrefLocalizedString =
+                  Components.interfaces.nsIPrefLocalizedString;
+
+                const prefNameInsertSeparator =
+                  "intl.menuitems.insertseparatorbeforeaccesskeys";
+                const prefNameAlwaysAppendAccessKey =
+                  "intl.menuitems.alwaysappendaccesskeys";
+
+                var val = prefs.getComplexValue(prefNameInsertSeparator,
+                                                nsIPrefLocalizedString).data;
+                this.mInsertSeparator = (val == "true");
+
+                val = prefs.getComplexValue(prefNameAlwaysAppendAccessKey,
+                                            nsIPrefLocalizedString).data;
+                this.mAlwaysAppendAccessKey = (val == "true");
+              }
+              catch (e) {
+                this.mInsertSeparator = true;
+              }
+            }
+
             if (!this.mUnderlineAccesskey)
               return;
 
             var control = this.labeledControlElement;
             if (!control) {
               var bindingParent = document.getBindingParent(this);
               if (bindingParent instanceof Components.interfaces.nsIDOMXULLabeledControlElement) {
                 control = bindingParent; // For controls that make the <label> an anon child
@@ -111,17 +122,16 @@
             }
 
             var oldHiddenSpan =
               this.getElementsByAttribute('class', 'hiddenColon').item(0);
             if (oldHiddenSpan) {
               this.mergeElement(oldHiddenSpan);
             }
 
-            var accessKey = this.accessKey;
             var labelText = this.textContent;
             if (!accessKey || !labelText || !control) {
               return;
             }
             var accessKeyIndex = -1;
             if (!this.mAlwaysAppendAccessKey) {
               accessKeyIndex = labelText.indexOf(accessKey);
               if (accessKeyIndex < 0) { // Try again in upper case
@@ -213,17 +223,17 @@
             element.parentNode.removeChild(element);
           ]]>
         </body>
       </method>
 
       <field name="mUnderlineAccesskey">
         !/Mac/.test(navigator.platform)
       </field>
-      <field name="mInsertSeparator">true</field>
+      <field name="mInsertSeparator"/>
       <field name="mAlwaysAppendAccessKey">false</field>
 
       <property name="accessKey">
         <getter>
           <![CDATA[
             var accessKey = null;
             var labeledEl = this.labeledControlElement;
             if (labeledEl) {
@@ -240,34 +250,34 @@
             // If this label already has an accesskey attribute store it here as well
             if (this.hasAttribute('accesskey')) {
               this.setAttribute('accesskey', val);
             }
             var control = this.labeledControlElement;
             if (control) {
               control.setAttribute('accesskey', val);
             }
-            this.formatAccessKey();
+            this.formatAccessKey(false);
             return val;
           ]]>
         </setter>
       </property>
 
       <property name="labeledControlElement" readonly="true"
                 onget="var control = this.control; return control ? document.getElementById(control) : null;" />
 
       <property name="control" onget="return this.getAttribute('control');">
         <setter>
           <![CDATA[
             var control = this.labeledControlElement;
             if (control) {
               control.labelElement = null; // No longer pointed to be this label
             }
             this.setAttribute('control', val);
-            this.formatAccessKey();
+            this.formatAccessKey(false);
             return val;
           ]]>
         </setter>
       </property>
 
     </implementation>
 
     <handlers>
--- a/toolkit/devtools/server/child.js
+++ b/toolkit/devtools/server/child.js
@@ -1,33 +1,43 @@
-const {DevToolsUtils} = Cu.import("resource://gre/modules/devtools/DevToolsUtils.jsm", {});
-const {DebuggerServer, ActorPool} = Cu.import("resource://gre/modules/devtools/dbg-server.jsm", {});
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
-if (!DebuggerServer.initialized) {
-  DebuggerServer.init();
-}
+"use strict";
 
-// In case of apps being loaded in parent process, DebuggerServer is already
-// initialized, but child specific actors are not registered.
-// Otherwise, for apps in child process, we need to load actors the first
-// time we load child.js
-DebuggerServer.addChildActors();
+// Encapsulate in its own scope to allows loading this frame script
+// more than once.
+(function () {
+  const {DevToolsUtils} = Cu.import("resource://gre/modules/devtools/DevToolsUtils.jsm", {});
+  const {DebuggerServer, ActorPool} = Cu.import("resource://gre/modules/devtools/dbg-server.jsm", {});
 
-let onConnect = DevToolsUtils.makeInfallible(function (msg) {
-  removeMessageListener("debug:connect", onConnect);
+  if (!DebuggerServer.initialized) {
+    DebuggerServer.init();
+  }
+
+  // In case of apps being loaded in parent process, DebuggerServer is already
+  // initialized, but child specific actors are not registered.
+  // Otherwise, for apps in child process, we need to load actors the first
+  // time we load child.js
+  DebuggerServer.addChildActors();
 
-  let mm = msg.target;
+  let onConnect = DevToolsUtils.makeInfallible(function (msg) {
+    removeMessageListener("debug:connect", onConnect);
 
-  let prefix = msg.data.prefix + docShell.appId;
+    let mm = msg.target;
 
-  let conn = DebuggerServer.connectToParent(prefix, mm);
+    let prefix = msg.data.prefix + docShell.appId;
+
+    let conn = DebuggerServer.connectToParent(prefix, mm);
 
-  let actor = new DebuggerServer.ContentAppActor(conn, content);
-  let actorPool = new ActorPool(conn);
-  actorPool.addActor(actor);
-  conn.addActorPool(actorPool);
+    let actor = new DebuggerServer.ContentAppActor(conn, content);
+    let actorPool = new ActorPool(conn);
+    actorPool.addActor(actor);
+    conn.addActorPool(actorPool);
 
-  sendAsyncMessage("debug:actor", {actor: actor.grip(),
-                                   appId: docShell.appId,
-                                   prefix: prefix});
-});
+    sendAsyncMessage("debug:actor", {actor: actor.grip(),
+                                     appId: docShell.appId,
+                                     prefix: prefix});
+  });
 
-addMessageListener("debug:connect", onConnect);
+  addMessageListener("debug:connect", onConnect);
+})();