merge fx-team to mozilla-central
authorCarsten "Tomcat" Book <cbook@mozilla.com>
Thu, 24 Apr 2014 11:39:30 +0200
changeset 199415 c8055a00235db1ddadfdd74e1d8181537117c23c
parent 199401 6965a913e858b6feb2944649940a646f40eb4037 (current diff)
parent 199414 3544b8716eb11f273ac2c3be1cb192aa62a6e613 (diff)
child 199441 9d3da41ad0b6ef945bd06c680a9e23fcfc1fa406
push id486
push userasasaki@mozilla.com
push dateMon, 14 Jul 2014 18:39:42 +0000
treeherdermozilla-release@d33428174ff1 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
milestone31.0a1
first release with
nightly linux32
c8055a00235d / 31.0a1 / 20140424030204 / files
nightly linux64
c8055a00235d / 31.0a1 / 20140424030204 / files
nightly mac
c8055a00235d / 31.0a1 / 20140424030204 / files
nightly win32
c8055a00235d / 31.0a1 / 20140424030204 / files
nightly win64
c8055a00235d / 31.0a1 / 20140424030204 / files
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
releases
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
merge fx-team to mozilla-central
--- a/browser/base/content/browser-customization.js
+++ b/browser/base/content/browser-customization.js
@@ -34,17 +34,16 @@ let CustomizationHandler = {
       childNode.setAttribute("disabled", true);
 
     let cmd = document.getElementById("cmd_CustomizeToolbars");
     cmd.setAttribute("disabled", "true");
 
     UpdateUrlbarSearchSplitterState();
 
     CombinedStopReload.uninit();
-    CombinedBackForward.uninit();
     PlacesToolbarHelper.customizeStart();
     DownloadsButton.customizeStart();
 
     // The additional padding on the sides of the browser
     // can cause the customize tab to get clipped.
     let tabContainer = gBrowser.tabContainer;
     if (tabContainer.getAttribute("overflow") == "true") {
       let tabstrip = tabContainer.mTabstrip;
@@ -82,17 +81,16 @@ let CustomizationHandler = {
     }
 
     PlacesToolbarHelper.customizeDone();
     DownloadsButton.customizeDone();
 
     // The url bar splitter state is dependent on whether stop/reload
     // and the location bar are combined, so we need this ordering
     CombinedStopReload.init();
-    CombinedBackForward.init();
     UpdateUrlbarSearchSplitterState();
 
     // Update the urlbar
     if (gURLBar) {
       URLBarSetURI();
       XULBrowserWindow.asyncUpdateUI();
     }
 
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -255,25 +255,17 @@ function UpdateBackForwardCommands(aWebN
   var forwardDisabled = forwardBroadcaster.hasAttribute("disabled");
   if (backDisabled == aWebNavigation.canGoBack) {
     if (backDisabled)
       backBroadcaster.removeAttribute("disabled");
     else
       backBroadcaster.setAttribute("disabled", true);
   }
 
-  let canGoForward = aWebNavigation.canGoForward;
-  if (forwardDisabled) {
-    // Force the button to either be hidden (if we are already disabled,
-    // and should be), or to show if we're about to un-disable it:
-    // otherwise no transition will occur and it'll never show:
-    CombinedBackForward.setForwardButtonOcclusion(!canGoForward);
-  }
-
-  if (forwardDisabled == canGoForward) {
+  if (forwardDisabled == aWebNavigation.canGoForward) {
     if (forwardDisabled)
       forwardBroadcaster.removeAttribute("disabled");
     else
       forwardBroadcaster.setAttribute("disabled", true);
   }
 }
 
 /**
@@ -915,17 +907,16 @@ var gBrowserInit = {
         gURLBar.setAttribute("readonly", "true");
         gURLBar.setAttribute("enablehistory", "false");
       }
       goSetCommandEnabled("cmd_newNavigatorTab", false);
     }
 
     // Misc. inits.
     CombinedStopReload.init();
-    CombinedBackForward.init();
     gPrivateBrowsingUI.init();
     TabsInTitlebar.init();
 
     // Wait until chrome is painted before executing code not critical to making the window visible
     this._boundDelayedStartup = this._delayedStartup.bind(this, mustLoadSidebar);
     window.addEventListener("MozAfterPaint", this._boundDelayedStartup);
 
     this._loadHandled = true;
@@ -1262,17 +1253,16 @@ var gBrowserInit = {
     if (desc && !desc.get) {
       DeveloperToolbar.destroy();
     }
 
     // First clean up services initialized in gBrowserInit.onLoad (or those whose
     // uninit methods don't depend on the services having been initialized).
 
     CombinedStopReload.uninit();
-    CombinedBackForward.uninit();
 
     gGestureSupport.init(false);
 
     gHistorySwipeAnimation.uninit();
 
     FullScreen.cleanup();
 
 #ifdef MOZ_SERVICES_SYNC
@@ -3794,17 +3784,16 @@ var XULBrowserWindow = {
     } catch (e) {}
     gIdentityHandler.checkIdentity(this._state, uri);
   },
 
   // simulate all change notifications after switching tabs
   onUpdateCurrentBrowser: function XWB_onUpdateCurrentBrowser(aStateFlags, aStatus, aMessage, aTotalProgress) {
     if (FullZoom.updateBackgroundTabs)
       FullZoom.onLocationChange(gBrowser.currentURI, true);
-    CombinedBackForward.setForwardButtonOcclusion(!gBrowser.webProgress.canGoForward);
     var nsIWebProgressListener = Components.interfaces.nsIWebProgressListener;
     var loadingDone = aStateFlags & nsIWebProgressListener.STATE_STOP;
     // use a pseudo-object instead of a (potentially nonexistent) channel for getting
     // a correct error message - and make sure that the UI is always either in
     // loading (STATE_START) or done (STATE_STOP) mode
     this.onStateChange(
       gBrowser.webProgress,
       { URI: gBrowser.currentURI },
@@ -3869,53 +3858,16 @@ var LinkTargetDisplay = {
 
   _hide: function () {
     clearTimeout(this._timer);
 
     XULBrowserWindow.updateStatusField();
   }
 };
 
-let CombinedBackForward = {
-  init: function() {
-    this.forwardButton = document.getElementById("forward-button");
-    // Add a transition listener to the url bar to hide the forward button
-    // when necessary
-    if (gURLBar)
-      gURLBar.addEventListener("transitionend", this);
-    // On startup, or if the user customizes, our listener isn't attached,
-    // and no transitions fire anyway, so we need to make sure we've hidden the
-    // button if necessary:
-    if (this.forwardButton && this.forwardButton.hasAttribute("disabled")) {
-      this.setForwardButtonOcclusion(true);
-    }
-  },
-  uninit: function() {
-    if (gURLBar)
-      gURLBar.removeEventListener("transitionend", this);
-  },
-  handleEvent: function(aEvent) {
-    if (aEvent.type == "transitionend" &&
-        (aEvent.propertyName == "margin-left" || aEvent.propertyName == "margin-right") &&
-        this.forwardButton.hasAttribute("disabled")) {
-      this.setForwardButtonOcclusion(true);
-    }
-  },
-  setForwardButtonOcclusion: function(shouldBeOccluded) {
-    if (!this.forwardButton)
-      return;
-
-    let hasAttribute = this.forwardButton.hasAttribute("occluded-by-urlbar");
-    if (shouldBeOccluded && !hasAttribute)
-      this.forwardButton.setAttribute("occluded-by-urlbar", "true");
-    else if (!shouldBeOccluded && hasAttribute)
-      this.forwardButton.removeAttribute("occluded-by-urlbar");
-  }
-}
-
 var CombinedStopReload = {
   init: function () {
     if (this._initialized)
       return;
 
     let reload = document.getElementById("urlbar-reload-button");
     let stop = document.getElementById("urlbar-stop-button");
     if (!stop || !reload || reload.nextSibling != stop)
--- a/browser/base/content/browser.xul
+++ b/browser/base/content/browser.xul
@@ -651,31 +651,24 @@
                      class="chromeclass-location" overflows="false">
           <toolbarbutton id="back-button" class="toolbarbutton-1 chromeclass-toolbar-additional"
                          label="&backCmd.label;"
                          command="Browser:BackOrBackDuplicate"
                          cui-areatype="toolbar"
                          onclick="checkForMiddleClick(this, event);"
                          tooltip="back-button-tooltip"
                          context="backForwardMenu"/>
-          <toolbarbutton id="forward-button" class="toolbarbutton-1 chromeclass-toolbar-additional"
-                         label="&forwardCmd.label;"
-                         command="Browser:ForwardOrForwardDuplicate"
-                         cui-areatype="toolbar"
-                         onclick="checkForMiddleClick(this, event);"
-                         tooltip="forward-button-tooltip"
-                         context="backForwardMenu"/>
-          <dummyobservertarget hidden="true"
-                               onbroadcast="if (this.getAttribute('disabled') == 'true')
-                                              this.parentNode.setAttribute('forwarddisabled', 'true');
-                                            else
-                                              this.parentNode.removeAttribute('forwarddisabled');">
-            <observes element="Browser:ForwardOrForwardDuplicate" attribute="disabled"/>
-          </dummyobservertarget>
-          <hbox id="urlbar-wrapper" flex="1" align="center">
+          <hbox id="urlbar-wrapper" flex="1">
+            <toolbarbutton id="forward-button" class="toolbarbutton-1 chromeclass-toolbar-additional"
+                           label="&forwardCmd.label;"
+                           command="Browser:ForwardOrForwardDuplicate"
+                           cui-areatype="toolbar"
+                           onclick="checkForMiddleClick(this, event);"
+                           tooltip="forward-button-tooltip"
+                           context="backForwardMenu"/>
             <textbox id="urlbar" flex="1"
                      placeholder="&urlbar.placeholder2;"
                      type="autocomplete"
                      autocompletesearch="urlinline history"
                      autocompletesearchparam="enable-actions"
                      autocompletepopup="PopupAutoCompleteRichResult"
                      completeselectedindex="true"
                      tabscrolling="true"
@@ -1183,32 +1176,23 @@
                          oncommand="DeveloperToolbar.hide();"
                          tooltiptext="&devToolbarCloseButton.tooltiptext;"/>
 #endif
    </toolbar>
   </vbox>
 
   <svg:svg height="0">
 #include tab-shape.inc.svg
-
+    <svg:clipPath id="urlbar-back-button-clip-path" clipPathUnits="userSpaceOnUse">
 #ifndef XP_MACOSX
-    <svg:clipPath id="keyhole-forward-clip-path" clipPathUnits="objectBoundingBox">
-      <svg:path d="m 0,0 c .3,.25 .3,.75, 0,1 l 1,0 0,-1 z"/>
-    </svg:clipPath>
-    <svg:clipPath id="urlbar-back-button-clip-path" clipPathUnits="userSpaceOnUse">
-      <svg:path d="m 0,-5 l 0,7.8 c 2.5,3.2 4,6.2 4,10.2 c 0,4 -1.5,7 -4,10 l 0,22l10000,0 l 0,-50 l -10000,0 z"/>
+      <svg:path d="m 1,-5 l 0,7.8 c 2.5,3.2 4,6.2 4,10.2 c 0,4 -1.5,7 -4,10 l 0,22l10000,0 l 0,-50 l -10000,0 z"/>
+#else
+      <svg:path d="M -11,-5 a 16 16 0 0 1 0,34 l 10000,0 l 0,-34 l -10000,0 z"/>
+#endif
     </svg:clipPath>
-#else
-    <svg:clipPath id="osx-keyhole-forward-clip-path" clipPathUnits="userSpaceOnUse">
-      <svg:path d="M 0,0 a 16 16 0 0 1 0,24 l 10000,0 l 0,-24 l -10000,0 z"/>
-    </svg:clipPath>
-    <svg:clipPath id="osx-urlbar-back-button-clip-path" clipPathUnits="userSpaceOnUse">
-      <svg:path d="M -12,-5 a 16 16 0 0 1 0,34 l 10000,0 l 0,-34 l -10000,0 z"/>
-    </svg:clipPath>
-#endif
   </svg:svg>
 
 </vbox>
 # <iframe id="tab-view"> is dynamically appended as the 2nd child of #tab-view-deck.
 #     Introducing the iframe dynamically, as needed, was found to be better than
 #     starting with an empty iframe here in browser.xul from a Ts standpoint.
 </deck>
 
--- a/browser/base/content/tabbrowser.xml
+++ b/browser/base/content/tabbrowser.xml
@@ -1007,22 +1007,22 @@
 
             newBrowser.setAttribute("type", "content-primary");
             newBrowser.docShellIsActive =
               (window.windowState != window.STATE_MINIMIZED);
             this.mCurrentBrowser = newBrowser;
             this.mCurrentTab = this.tabContainer.selectedItem;
             this.showTab(this.mCurrentTab);
 
-            var backForwardContainer = document.getElementById("urlbar-container");
-            if (backForwardContainer) {
-              backForwardContainer.setAttribute("switchingtabs", "true");
+            var forwardButtonContainer = document.getElementById("urlbar-wrapper");
+            if (forwardButtonContainer) {
+              forwardButtonContainer.setAttribute("switchingtabs", "true");
               window.addEventListener("MozAfterPaint", function removeSwitchingtabsAttr() {
                 window.removeEventListener("MozAfterPaint", removeSwitchingtabsAttr);
-                backForwardContainer.removeAttribute("switchingtabs");
+                forwardButtonContainer.removeAttribute("switchingtabs");
               });
             }
 
             this._appendStatusPanel();
 
             if (updateBlockedPopups)
               this.mCurrentBrowser.updateBlockedPopups(false);
 
--- a/browser/base/content/test/general/browser_aboutHome.js
+++ b/browser/base/content/test/general/browser_aboutHome.js
@@ -85,67 +85,68 @@ let gTests = [
        "Search engine logo's alt text is a nonempty string");
 
     isnot(altText, "undefined",
           "Search engine logo's alt text shouldn't be the string 'undefined'");
   }
 },
 
 // Disabled on Linux for intermittent issues with FHR, see Bug 945667.
-// Disabled always due to bug 992485
 {
   desc: "Check that performing a search fires a search event and records to " +
         "Firefox Health Report.",
   setup: function () { },
   run: function () {
-    // Skip this test always for now since it loads google.com and that causes bug 992485
-    return;
-
     // Skip this test on Linux.
     if (navigator.platform.indexOf("Linux") == 0) { return; }
 
     try {
       let cm = Cc["@mozilla.org/categorymanager;1"].getService(Ci.nsICategoryManager);
       cm.getCategoryEntry("healthreport-js-provider-default", "SearchesProvider");
     } catch (ex) {
       // Health Report disabled, or no SearchesProvider.
       return Promise.resolve();
     }
 
     let numSearchesBefore = 0;
-    let deferred = Promise.defer();
+    let searchEventDeferred = Promise.defer();
     let doc = gBrowser.contentDocument;
     let engineName = doc.documentElement.getAttribute("searchEngineName");
 
     doc.addEventListener("AboutHomeSearchEvent", function onSearch(e) {
       let data = JSON.parse(e.detail);
       is(data.engineName, engineName, "Detail is search engine name");
 
       // We use executeSoon() to ensure that this code runs after the
       // count has been updated in browser.js, since it uses the same
       // event.
       executeSoon(function () {
         getNumberOfSearches(engineName).then(num => {
           is(num, numSearchesBefore + 1, "One more search recorded.");
-          deferred.resolve();
+          searchEventDeferred.resolve();
         });
       });
     }, true, true);
 
     // Get the current number of recorded searches.
+    let searchStr = "a search";
     getNumberOfSearches(engineName).then(num => {
       numSearchesBefore = num;
 
       info("Perform a search.");
-      doc.getElementById("searchText").value = "a search";
+      doc.getElementById("searchText").value = searchStr;
       doc.getElementById("searchSubmit").click();
-      gBrowser.stop();
     });
 
-    return deferred.promise;
+    let expectedURL = Services.search.currentEngine.
+                      getSubmission(searchStr, null, "homepage").
+                      uri.spec;
+    let loadPromise = waitForDocLoadAndStopIt(expectedURL);
+
+    return Promise.all([searchEventDeferred.promise, loadPromise]);
   }
 },
 
 {
   desc: "Check snippets map is cleared if cached version is old",
   setup: function (aSnippetsMap)
   {
     aSnippetsMap.set("snippets", "test");
--- a/browser/base/content/test/general/head.js
+++ b/browser/base/content/test/general/head.js
@@ -287,16 +287,48 @@ function promiseHistoryClearedState(aURI
          "history visit " + aURI.spec + " should " + niceStr + " exist");
       callbackDone();
     });
   });
 
   return deferred.promise;
 }
 
+/**
+ * Waits for the next top-level document load in the current browser.  The URI
+ * of the document is compared against aExpectedURL.  The load is then stopped
+ * before it actually starts.
+ *
+ * @param aExpectedURL
+ *        The URL of the document that is expected to load.
+ * @return promise
+ */
+function waitForDocLoadAndStopIt(aExpectedURL) {
+  let deferred = Promise.defer();
+  let progressListener = {
+    onStateChange: function (webProgress, req, flags, status) {
+      info("waitForDocLoadAndStopIt: onStateChange: " + req.name);
+      let docStart = Ci.nsIWebProgressListener.STATE_IS_DOCUMENT |
+                     Ci.nsIWebProgressListener.STATE_START;
+      if ((flags & docStart) && webProgress.isTopLevel) {
+        info("waitForDocLoadAndStopIt: Document start: " +
+             req.QueryInterface(Ci.nsIChannel).URI.spec);
+        is(req.originalURI.spec, aExpectedURL,
+           "waitForDocLoadAndStopIt: The expected URL was loaded");
+        req.cancel(Components.results.NS_ERROR_FAILURE);
+        gBrowser.removeProgressListener(progressListener);
+        deferred.resolve();
+      }
+    },
+  };
+  gBrowser.addProgressListener(progressListener);
+  info("waitForDocLoadAndStopIt: Waiting for URL: " + aExpectedURL);
+  return deferred.promise;
+}
+
 let FullZoomHelper = {
 
   selectTabAndWaitForLocationChange: function selectTabAndWaitForLocationChange(tab) {
     if (!tab)
       throw new Error("tab must be given.");
     if (gBrowser.selectedTab == tab)
       return Promise.resolve();
     gBrowser.selectedTab = tab;
--- a/browser/components/downloads/content/downloadsOverlay.xul
+++ b/browser/components/downloads/content/downloadsOverlay.xul
@@ -99,16 +99,20 @@
       <richlistbox id="downloadsListBox"
                    class="plain"
                    flex="1"
                    context="downloadsContextMenu"
                    onmouseover="DownloadsView.onDownloadMouseOver(event);"
                    onmouseout="DownloadsView.onDownloadMouseOut(event);"
                    oncontextmenu="DownloadsView.onDownloadContextMenu(event);"
                    ondragstart="DownloadsView.onDownloadDragStart(event);"/>
+      <description id="emptyDownloads"
+                   mousethrough="always">
+         &downloadsPanelEmpty.label;
+      </description>
 
       <vbox id="downloadsFooter">
         <hbox id="downloadsSummary"
               align="center"
               orient="horizontal"
               onkeydown="DownloadsSummary.onKeyDown(event);"
               onclick="DownloadsSummary.onClick(event);">
           <image class="downloadTypeIcon" />
--- a/browser/locales/en-US/chrome/browser/downloads/downloads.dtd
+++ b/browser/locales/en-US/chrome/browser/downloads/downloads.dtd
@@ -79,13 +79,18 @@
 <!ENTITY clearDownloadsButton.tooltip     "Clears completed, canceled and failed downloads">
 
 <!-- LOCALIZATION NOTE (downloadsListEmpty.label):
      This string is shown when there are no items in the Downloads view, when it
      is displayed inside a browser tab.
      -->
 <!ENTITY downloadsListEmpty.label         "There are no downloads.">
 
+<!-- LOCALIZATION NOTE (downloadsPanelEmpty.label):
+     This string is shown when there are no items in the Downloads Panel.
+     -->
+<!ENTITY downloadsPanelEmpty.label        "No downloads for this session.">
+
 <!-- LOCALIZATION NOTE (downloadsListNoMatch.label):
      This string is shown when some search terms are specified, but there are no
      results in the Downloads view.
      -->
 <!ENTITY downloadsListNoMatch.label       "Could not find any matching downloads.">
--- a/browser/themes/linux/browser.css
+++ b/browser/themes/linux/browser.css
@@ -10,17 +10,17 @@
 @namespace html url("http://www.w3.org/1999/xhtml");
 @namespace svg url("http://www.w3.org/2000/svg");
 
 %include ../shared/browser.inc
 %include linuxShared.inc
 %filter substitution
 
 %define forwardTransitionLength 150ms
-%define conditionalForwardWithUrlbar window:not([chromehidden~="toolbar"]) #urlbar-container
+%define conditionalForwardWithUrlbar window:not([chromehidden~="toolbar"]) #urlbar-wrapper
 %define conditionalForwardWithUrlbarWidth 30
 
 #menubar-items {
   -moz-box-orient: vertical; /* for flex hack */
 }
 
 #main-menubar {
   -moz-box-flex: 1; /* make menu items expand to fill toolbar height */
@@ -730,45 +730,49 @@ toolbarbutton[sdk-button="true"][cui-are
 }
 
 #main-window:not([customizing]) #back-button[disabled] > .toolbarbutton-icon {
   box-shadow: 0 0 0 1px hsla(210,54%,20%,.55),
               0 1px 0 hsla(210,54%,20%,.65) !important;
   transition: none;
 }
 
-#back-button:-moz-locale-dir(rtl) > .toolbarbutton-icon,
-#forward-button:-moz-locale-dir(rtl) {
+#back-button:-moz-locale-dir(rtl) > .toolbarbutton-icon {
   transform: scaleX(-1);
 }
 
-@conditionalForwardWithUrlbar@:not(:hover) > #forward-button[disabled] {
-  opacity: 0;
-}
-
-@conditionalForwardWithUrlbar@:not([switchingtabs]) > #forward-button {
-  transition: opacity @forwardTransitionLength@ ease-out;
-}
-
-@conditionalForwardWithUrlbar@ > #forward-button[occluded-by-urlbar] {
-  visibility: hidden;
-}
-
 #forward-button {
   padding: 0;
 }
 
 #forward-button > .toolbarbutton-icon {
   background-clip: padding-box;
-  clip-path: url("chrome://browser/content/browser.xul#keyhole-forward-clip-path");
-  margin-left: -6px;
+  padding-left: 9px;
+  padding-right: 3px;
+  border: 1px solid #9a9a9a;
   border-left-style: none;
   border-radius: 0;
-  padding: 2px 3px 2px 9px;
-  border: 1px solid #9a9a9a;
+}
+
+@conditionalForwardWithUrlbar@:not([switchingtabs]) > #forward-button {
+  transition: margin-left @forwardTransitionLength@ ease-out;
+}
+
+@conditionalForwardWithUrlbar@ > #forward-button[disabled] {
+  margin-left: -@conditionalForwardWithUrlbarWidth@px;
+}
+
+@conditionalForwardWithUrlbar@:hover:not([switchingtabs]) > #forward-button[disabled] {
+  /* delay the hiding of the forward button when hovered to avoid accidental clicks on the url bar */
+  transition-delay: 100s;
+}
+
+@conditionalForwardWithUrlbar@:not(:hover) > #forward-button[disabled] {
+  /* when not hovered anymore, trigger a new transition to hide the forward button immediately */
+  margin-left: -@conditionalForwardWithUrlbarWidth@.01px;
 }
 
 /* tabview menu item */
 
 #menu_tabview {
   list-style-image: url(chrome://browser/skin/tabview/tabview.png);
   -moz-image-region: rect(0, 80px, 16px, 64px);
 }
@@ -898,67 +902,44 @@ toolbarbutton[sdk-button="true"][cui-are
 .urlbar-history-dropmarker {
   -moz-appearance: toolbarbutton-dropdown;
 }
 
 #urlbar-container {
   -moz-box-align: center;
 }
 
-@conditionalForwardWithUrlbar@ > #urlbar-wrapper {
-  padding-left: @conditionalForwardWithUrlbarWidth@px;
-  -moz-margin-start: -@conditionalForwardWithUrlbarWidth@px;
-  position: relative;
-  pointer-events: none;
+@conditionalForwardWithUrlbar@ > #urlbar {
+  -moz-border-start: none;
+  margin-left: 0;
 }
 
-@conditionalForwardWithUrlbar@ > #urlbar-wrapper > #urlbar {
-  -moz-border-start: none;
-  margin-left: 0;
-  pointer-events: all;
-}
-
-@conditionalForwardWithUrlbar@:not([switchingtabs]) > #urlbar-wrapper > #urlbar {
-  transition: margin-left @forwardTransitionLength@ ease-out;
-}
-
-@conditionalForwardWithUrlbar@ > #urlbar-wrapper > #urlbar:-moz-locale-dir(ltr) {
+@conditionalForwardWithUrlbar@ > #urlbar:-moz-locale-dir(ltr) {
   border-top-left-radius: 0;
   border-bottom-left-radius: 0;
 }
 
-@conditionalForwardWithUrlbar@ > #urlbar-wrapper > #urlbar:-moz-locale-dir(rtl) {
+@conditionalForwardWithUrlbar@ > #urlbar:-moz-locale-dir(rtl) {
   border-top-right-radius: 0;
   border-bottom-right-radius: 0;
 }
 
-@conditionalForwardWithUrlbar@[forwarddisabled] > #urlbar-wrapper {
+@conditionalForwardWithUrlbar@ {
   clip-path: url("chrome://browser/content/browser.xul#urlbar-back-button-clip-path");
-}
-
-@conditionalForwardWithUrlbar@[forwarddisabled] > #urlbar-wrapper > #urlbar {
-  margin-left: -@conditionalForwardWithUrlbarWidth@px;
+  -moz-margin-start: -5px;
 }
 
-@conditionalForwardWithUrlbar@[forwarddisabled]:hover:not([switchingtabs]) > #urlbar-wrapper > #urlbar {
-  /* delay the hiding of the forward button when hovered to avoid accidental clicks on the url bar */
-  transition-delay: 100s;
+@conditionalForwardWithUrlbar@:-moz-locale-dir(rtl),
+@conditionalForwardWithUrlbar@ > #urlbar:-moz-locale-dir(rtl) {
+  /* let urlbar-back-button-clip-path clip the urlbar's right side for RTL */
+  transform: scaleX(-1);
 }
 
-@conditionalForwardWithUrlbar@[forwarddisabled][switchingtabs] + #urlbar-container > #urlbar,
-@conditionalForwardWithUrlbar@[forwarddisabled]:not(:hover) > #urlbar-wrapper > #urlbar {
-  /* when switching tabs, or when not hovered anymore, trigger a new transition
-   * to hide the forward button immediately */
-  margin-left: -@conditionalForwardWithUrlbarWidth@.01px;
-}
-
-@conditionalForwardWithUrlbar@ > #urlbar-wrapper:-moz-locale-dir(rtl),
-@conditionalForwardWithUrlbar@ > #urlbar-wrapper > #urlbar:-moz-locale-dir(rtl) {
-  /* let windows-urlbar-back-button-mask clip the urlbar's right side for RTL */
-  transform: scaleX(-1);
+@conditionalForwardWithUrlbar@:-moz-locale-dir(rtl) {
+  -moz-box-direction: reverse;
 }
 
 #urlbar-icons {
   -moz-box-align: center;
 }
 
 .urlbar-icon {
   cursor: pointer;
@@ -1010,43 +991,43 @@ toolbarbutton[sdk-button="true"][cui-are
   border-bottom-right-radius: 1.5px;
 }
 
 #notification-popup-box:not([hidden]) + #identity-box {
   -moz-padding-start: 10px;
   border-radius: 0;
 }
 
-@conditionalForwardWithUrlbar@ > #urlbar-wrapper > #urlbar > #identity-box {
+@conditionalForwardWithUrlbar@ > #urlbar > #identity-box {
   border-radius: 0;
 }
 
-@conditionalForwardWithUrlbar@[forwarddisabled] > #urlbar-wrapper > #urlbar > #notification-popup-box[hidden] + #identity-box:-moz-locale-dir(ltr) {
-  padding-left: 5px;
-  transition: padding-left;
+@conditionalForwardWithUrlbar@:not([switchingtabs]) > #urlbar > #identity-box {
+  transition: padding-left, padding-right;
 }
 
-@conditionalForwardWithUrlbar@[forwarddisabled] > #urlbar-wrapper > #urlbar > #notification-popup-box[hidden] + #identity-box:-moz-locale-dir(rtl) {
-  padding-right: 5px;
-  transition: padding-right;
+@conditionalForwardWithUrlbar@ > #forward-button[disabled] + #urlbar > #notification-popup-box[hidden] + #identity-box:-moz-locale-dir(ltr) {
+  padding-left: 5px;
 }
 
-@conditionalForwardWithUrlbar@[forwarddisabled]:hover:not([switchingtabs]) > #urlbar-wrapper > #urlbar > #notification-popup-box[hidden] + #identity-box {
+@conditionalForwardWithUrlbar@ > #forward-button[disabled] + #urlbar > #notification-popup-box[hidden] + #identity-box:-moz-locale-dir(rtl) {
+  padding-right: 5px;
+}
+
+@conditionalForwardWithUrlbar@:hover:not([switchingtabs]) > #forward-button[disabled] + #urlbar > #notification-popup-box[hidden] + #identity-box {
   /* forward button hiding is delayed when hovered */
   transition-delay: 100s;
 }
 
-@conditionalForwardWithUrlbar@[forwarddisabled][switchingtabs] + #urlbar-container > #urlbar > #notification-popup-box[hidden] + #identity-box:-moz-locale-dir(ltr),
-@conditionalForwardWithUrlbar@[forwarddisabled]:not(:hover) > #urlbar-wrapper > #urlbar > #notification-popup-box[hidden] + #identity-box:-moz-locale-dir(ltr) {
+@conditionalForwardWithUrlbar@:not(:hover) > #forward-button[disabled] + #urlbar > #notification-popup-box[hidden] + #identity-box:-moz-locale-dir(ltr) {
   /* when not hovered anymore, trigger a new non-delayed transition to react to the forward button hiding */
   padding-left: 5.01px;
 }
 
-@conditionalForwardWithUrlbar@[forwarddisabled][switchingtabs] + #urlbar-container > #urlbar > #notification-popup-box[hidden] + #identity-box:-moz-locale-dir(rtl),
-@conditionalForwardWithUrlbar@[forwarddisabled]:not(:hover) > #urlbar-wrapper > #urlbar > #notification-popup-box[hidden] + #identity-box:-moz-locale-dir(rtl) {
+@conditionalForwardWithUrlbar@:not(:hover) > #forward-button[disabled] + #urlbar > #notification-popup-box[hidden] + #identity-box:-moz-locale-dir(rtl) {
   /* when not hovered anymore, trigger a new non-delayed transition to react to the forward button hiding */
   padding-right: 5.01px;
 }
 
 #urlbar[pageproxystate="valid"] > #identity-box.chromeUI,
 #urlbar[pageproxystate="valid"] > #identity-box.verifiedIdentity {
   -moz-margin-end: 4px;
 }
@@ -1066,24 +1047,20 @@ toolbarbutton[sdk-button="true"][cui-are
 }
 
 %include ../shared/identity-block.inc.css
 
 #page-proxy-favicon {
   margin-top: 1px;
   margin-bottom: 1px;
   -moz-margin-start: 3px;
-  -moz-margin-end: 2px;
+  -moz-margin-end: 1px;
   -moz-image-region: rect(0, 16px, 16px, 0);
 }
 
-@conditionalForwardWithUrlbar@ > #urlbar-wrapper > #urlbar > #identity-box > #page-proxy-favicon {
-  -moz-margin-end: 1px;
-}
-
 #identity-box:hover > #page-proxy-favicon {
   -moz-image-region: rect(0, 32px, 16px, 16px);
 }
 
 #identity-box:hover:active > #page-proxy-favicon,
 #identity-box[open=true] > #page-proxy-favicon {
   -moz-image-region: rect(0, 48px, 16px, 32px);
 }
--- a/browser/themes/linux/downloads/downloads.css
+++ b/browser/themes/linux/downloads/downloads.css
@@ -13,23 +13,32 @@
   padding: 4px;
   color: inherit;
 }
 
 #downloadsPanel:not([hasdownloads]) > #downloadsListBox {
   display: none;
 }
 
+#downloadsPanel[hasdownloads] > #emptyDownloads {
+  display: none;
+}
+
+#emptyDownloads {
+  padding: 10px 20px;
+  max-width: 40ch;
+}
+
 #downloadsHistory {
   background: transparent;
   color: -moz-nativehyperlinktext;
   cursor: pointer;
 }
 
-#downloadsPanel[hasdownloads] > #downloadsFooter {
+#downloadsFooter {
   border-top: 1px solid ThreeDShadow;
   background-image: linear-gradient(hsla(0,0%,0%,.15), hsla(0,0%,0%,.08) 6px);
 }
 
 #downloadsHistory > .button-box {
   margin: 1em;
 }
 
--- a/browser/themes/osx/browser.css
+++ b/browser/themes/osx/browser.css
@@ -2,18 +2,18 @@
  * 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/. */
 
 @import url("chrome://global/skin/");
 
 %include shared.inc
 %filter substitution
 %define forwardTransitionLength 150ms
-%define conditionalForwardWithUrlbar window:not([chromehidden~="toolbar"]) #urlbar-container
-%define conditionalForwardWithUrlbarWidth 30
+%define conditionalForwardWithUrlbar window:not([chromehidden~="toolbar"]) #urlbar-wrapper
+%define conditionalForwardWithUrlbarWidth 32
 %define spaceAboveTabbar 9px
 %define toolbarButtonPressed :hover:active:not([disabled="true"]):not([cui-areatype="menu-panel"])
 %define windowButtonMarginTop 11px
 
 @namespace url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul");
 @namespace html url("http://www.w3.org/1999/xhtml");
 @namespace svg url("http://www.w3.org/2000/svg");
 
@@ -1415,30 +1415,27 @@ toolbarbutton[sdk-button="true"][cui-are
 }
 
 #back-button:-moz-window-inactive,
 #forward-button:-moz-window-inactive {
   background-color: rgba(0,0,0,0.04);
   border-color: rgba(0,0,0,0.2);
 }
 
-#back-button:-moz-locale-dir(rtl),
-#forward-button:-moz-locale-dir(rtl) {
+#back-button:-moz-locale-dir(rtl) {
   transform: scaleX(-1);
 }
 
 /* Back button styles */
 
 #back-button {
-  -moz-margin-end: -7px;
-  position: relative;
-  z-index: 1;
   width: 32px;
   height: 32px;
   padding: 4px 5px 4px 3px;
+  -moz-margin-end: 0;
   border-radius: 10000px;
 }
 
 #back-button:not(:-moz-lwtheme) {
   height: 33px;
   padding: 4px 5px 5px 3px;
   margin-bottom: -1px;
   background: url(chrome://browser/skin/keyhole-circle.png) 0 0 no-repeat;
@@ -1458,68 +1455,50 @@ toolbarbutton[sdk-button="true"][cui-are
 #back-button:not([disabled="true"]):active:hover:not(:-moz-lwtheme),
 #back-button[open="true"]:not(:-moz-lwtheme) {
   background-position: -32px 0;
 }
 
 /* Forward button styles */
 
 #forward-button {
-  -moz-margin-start: 0;
-  -moz-margin-end: 0;
+  margin-left: -2px;
+  margin-right: 0;
+  padding-left: 2px;
   width: 32px;
-  clip-path: url(chrome://browser/content/browser.xul#osx-keyhole-forward-clip-path);
 }
 
 #forward-button > .toolbarbutton-icon {
   /* shift the icon away from the back button */
   margin-left: 3px;
   margin-right: -1px;
 }
 
-#forward-button:-moz-lwtheme {
-  -moz-padding-start: 2px;
-  -moz-padding-end: 0;
-}
-
 #forward-button:not(:-moz-lwtheme) {
-  -moz-padding-start: 2px;
   background: linear-gradient(hsl(0,0%,99%), hsl(0,0%,67%)) padding-box;
   border: 1px solid;
   border-color: hsl(0,0%,31%) hsla(0,0%,29%,.6) hsl(0,0%,27%);
   box-shadow: inset 0 1px 0 hsla(0,0%,100%,.35),
               0 1px 0 hsla(0,0%,100%,.2);
 }
 
-#urlbar-container:not([switchingtabs]) > #forward-button {
-  transition: opacity @forwardTransitionLength@ ease-out;
-}
-
 #forward-button:hover:active:not(:-moz-lwtheme) {
   background-image: linear-gradient(hsl(0,0%,74%), hsl(0,0%,61%));
   box-shadow: inset rgba(0,0,0,.3) 0 -6px 10px,
               inset #000 0 1px 3px,
               inset rgba(0,0,0,.2) 0 1px 3px,
               0 1px 0 hsla(0,0%,100%,.2);
 }
 
 #forward-button:-moz-window-inactive:not(:-moz-lwtheme) {
   border-color: hsl(0,0%,64%) hsl(0,0%,65%) hsl(0,0%,66%);
   background-image: linear-gradient(hsl(0,0%,99%), hsl(0,0%,82%));
   box-shadow: inset 0 1px 0 hsla(0,0%,100%,.35);
 }
 
-#urlbar-container:not(:hover) > #forward-button[disabled] {
-  opacity: 0;
-}
-
-@conditionalForwardWithUrlbar@ > #forward-button[occluded-by-urlbar] {
-  visibility: hidden;
-}
-
 @media (-moz-mac-lion-theme) {
   #forward-button:not(:-moz-lwtheme) {
     background-image: linear-gradient(hsla(0,0%,100%,.73), hsla(0,0%,100%,.05) 85%);
     border-color: hsla(0,0%,0%,.35) hsla(0,0%,0%,.25) hsla(0,0%,0%,.2);
     box-shadow: inset 0 1px 0 hsla(0,0%,100%,.2),
                 inset 0 0 1px hsla(0,0%,100%,.1),
                 0 1px 0 hsla(0,0%,100%,.2);
   }
@@ -1533,16 +1512,34 @@ toolbarbutton[sdk-button="true"][cui-are
   }
 
   #forward-button:-moz-window-inactive:not(:-moz-lwtheme) {
     background-image: none;
     border-color: hsla(0,0%,0%,.2);
   }
 }
 
+@conditionalForwardWithUrlbar@:not([switchingtabs]) > #forward-button {
+  transition: margin-left @forwardTransitionLength@ ease-out;
+}
+
+@conditionalForwardWithUrlbar@ > #forward-button[disabled] {
+  margin-left: -@conditionalForwardWithUrlbarWidth@px;
+}
+
+@conditionalForwardWithUrlbar@:hover:not([switchingtabs]) > #forward-button[disabled] {
+  /* delay the hiding of the forward button when hovered to avoid accidental clicks on the url bar */
+  transition-delay: 100s;
+}
+
+@conditionalForwardWithUrlbar@:not(:hover) > #forward-button[disabled] {
+  /* when not hovered anymore, trigger a new transition to hide the forward button immediately */
+  margin-left: -@conditionalForwardWithUrlbarWidth@.01px;
+}
+
 .unified-nav-back[_moz-menuactive]:-moz-locale-dir(ltr),
 .unified-nav-forward[_moz-menuactive]:-moz-locale-dir(rtl) {
   list-style-image: url("chrome://browser/skin/menu-back.png") !important;
 }
 
 .unified-nav-forward[_moz-menuactive]:-moz-locale-dir(ltr),
 .unified-nav-back[_moz-menuactive]:-moz-locale-dir(rtl) {
   list-style-image: url("chrome://browser/skin/menu-forward.png") !important;
@@ -1716,69 +1713,46 @@ toolbarbutton[sdk-button="true"][cui-are
   -moz-box-align: center;
 }
 
 #urlbar {
   -moz-padding-end: 4px;
   border-radius: @toolbarbuttonCornerRadius@;
 }
 
-@conditionalForwardWithUrlbar@ > #urlbar-wrapper {
-  padding-left: @conditionalForwardWithUrlbarWidth@px;
-  -moz-margin-start: -@conditionalForwardWithUrlbarWidth@px;
-  position: relative;
-  pointer-events: none;
-}
-
-@conditionalForwardWithUrlbar@ > #urlbar-wrapper > #urlbar {
+@conditionalForwardWithUrlbar@ > #urlbar {
   -moz-border-start: none;
   margin-left: 0;
-  pointer-events: all;
-}
-
-@conditionalForwardWithUrlbar@:not([switchingtabs]) > #urlbar-wrapper > #urlbar {
-  transition: margin-left @forwardTransitionLength@ ease-out;
-}
-
-@conditionalForwardWithUrlbar@ > #urlbar-wrapper > #urlbar:-moz-locale-dir(ltr) {
+}
+
+@conditionalForwardWithUrlbar@ > #urlbar:-moz-locale-dir(ltr) {
   border-top-left-radius: 0;
   border-bottom-left-radius: 0;
 }
 
-@conditionalForwardWithUrlbar@ > #urlbar-wrapper > #urlbar:-moz-locale-dir(rtl) {
+@conditionalForwardWithUrlbar@ > #urlbar:-moz-locale-dir(rtl) {
   border-top-right-radius: 0;
   border-bottom-right-radius: 0;
 }
 
-@conditionalForwardWithUrlbar@[forwarddisabled] > #urlbar-wrapper {
-  clip-path: url("chrome://browser/content/browser.xul#osx-urlbar-back-button-clip-path");
-}
-
-@conditionalForwardWithUrlbar@[forwarddisabled] > #urlbar-wrapper > #urlbar {
-  margin-left: -@conditionalForwardWithUrlbarWidth@px;
-}
-
-@conditionalForwardWithUrlbar@[forwarddisabled]:hover:not([switchingtabs]) > #urlbar-wrapper > #urlbar {
-  /* delay the hiding of the forward button when hovered to avoid accidental clicks on the url bar */
-  transition-delay: 100s;
-}
-
-@conditionalForwardWithUrlbar@[forwarddisabled][switchingtabs] + #urlbar-container > #urlbar,
-@conditionalForwardWithUrlbar@[forwarddisabled]:not(:hover) > #urlbar-wrapper > #urlbar {
-  /* when switching tabs, or when not hovered anymore, trigger a new transition
-   * to hide the forward button immediately */
-  margin-left: -@conditionalForwardWithUrlbarWidth@.01px;
-}
-
-@conditionalForwardWithUrlbar@ > #urlbar-wrapper:-moz-locale-dir(rtl),
-@conditionalForwardWithUrlbar@ > #urlbar-wrapper > #urlbar:-moz-locale-dir(rtl) {
-  /* let osx-urlbar-back-button-clip-path clip the urlbar's right side for RTL */
+@conditionalForwardWithUrlbar@ {
+  clip-path: url("chrome://browser/content/browser.xul#urlbar-back-button-clip-path");
+  -moz-margin-start: -6px;
+}
+
+@conditionalForwardWithUrlbar@:-moz-locale-dir(rtl),
+@conditionalForwardWithUrlbar@ > #urlbar:-moz-locale-dir(rtl) {
+  /* let urlbar-back-button-clip-path clip the urlbar's right side for RTL */
   transform: scaleX(-1);
 }
 
+@conditionalForwardWithUrlbar@:-moz-locale-dir(rtl) {
+  -moz-box-direction: reverse;
+}
+
 #identity-box {
   -moz-margin-end: 3px;
   padding-top: 1px;
   padding-bottom: 1px;
   -moz-padding-start: 4px;
   -moz-padding-end: 0;
   font-size: .9em;
 }
@@ -1793,42 +1767,42 @@ toolbarbutton[sdk-button="true"][cui-are
   border-bottom-right-radius: 2px;
 }
 
 #notification-popup-box:not([hidden]) + #identity-box {
   -moz-padding-start: 10px;
   border-radius: 0;
 }
 
-@conditionalForwardWithUrlbar@ > #urlbar-wrapper > #urlbar > #identity-box {
+@conditionalForwardWithUrlbar@ > #urlbar > #identity-box {
   border-radius: 0;
 }
 
-@conditionalForwardWithUrlbar@[forwarddisabled] > #urlbar-wrapper > #urlbar > #notification-popup-box[hidden] + #identity-box:-moz-locale-dir(ltr) {
-  transition: 0s padding-left;
+@conditionalForwardWithUrlbar@:not([switchingtabs]) > #urlbar > #identity-box {
+  transition: padding-left, padding-right;
+}
+
+@conditionalForwardWithUrlbar@ > #forward-button[disabled] + #urlbar > #notification-popup-box[hidden] + #identity-box:-moz-locale-dir(ltr) {
   padding-left: 10px;
 }
 
-@conditionalForwardWithUrlbar@[forwarddisabled] > #urlbar-wrapper > #urlbar > #notification-popup-box[hidden] + #identity-box:-moz-locale-dir(rtl) {
-  transition: 0s padding-right;
+@conditionalForwardWithUrlbar@ > #forward-button[disabled] + #urlbar > #notification-popup-box[hidden] + #identity-box:-moz-locale-dir(rtl) {
   padding-right: 10px;
 }
 
-@conditionalForwardWithUrlbar@[forwarddisabled]:hover:not([switchingtabs]) > #urlbar-wrapper > #urlbar > #notification-popup-box[hidden] + #identity-box {
-  /* delay the hiding of the forward button when hovered to avoid accidental clicks on the url bar */
+@conditionalForwardWithUrlbar@:hover:not([switchingtabs]) > #forward-button[disabled] + #urlbar > #notification-popup-box[hidden] + #identity-box {
+  /* forward button hiding is delayed when hovered */
   transition-delay: 100s;
 }
 
-@conditionalForwardWithUrlbar@[forwarddisabled][switchingtabs] + #urlbar-container > #urlbar > #notification-popup-box[hidden] + #identity-box:-moz-locale-dir(ltr),
-@conditionalForwardWithUrlbar@[forwarddisabled]:not(:hover) > #urlbar-wrapper > #urlbar > #notification-popup-box[hidden] + #identity-box:-moz-locale-dir(ltr) {
+@conditionalForwardWithUrlbar@:not(:hover) > #forward-button[disabled] + #urlbar > #notification-popup-box[hidden] + #identity-box:-moz-locale-dir(ltr) {
   padding-left: 10.01px;
 }
 
-@conditionalForwardWithUrlbar@[forwarddisabled][switchingtabs] + #urlbar-container > #urlbar > #notification-popup-box[hidden] + #identity-box:-moz-locale-dir(rtl),
-@conditionalForwardWithUrlbar@[forwarddisabled]:not(:hover) > #urlbar-wrapper > #urlbar > #notification-popup-box[hidden] + #identity-box:-moz-locale-dir(rtl) {
+@conditionalForwardWithUrlbar@:not(:hover) > #forward-button[disabled] + #urlbar > #notification-popup-box[hidden] + #identity-box:-moz-locale-dir(rtl) {
   padding-right: 10.01px;
 }
 
 #urlbar[pageproxystate="valid"] > #identity-box.chromeUI,
 #urlbar[pageproxystate="valid"] > #identity-box.verifiedIdentity {
   -moz-padding-end: 4px;
 }
 
@@ -3395,17 +3369,17 @@ toolbarbutton.chevron > .toolbarbutton-m
 }
 
 @media (min-resolution: 2dppx) {
   #notification-popup-box {
     border-image: url("chrome://browser/skin/urlbar-arrow@2x.png") 0 16 0 0 fill;
   }
 }
 
-@conditionalForwardWithUrlbar@[forwarddisabled] > #urlbar-wrapper > #urlbar > #notification-popup-box {
+@conditionalForwardWithUrlbar@[forwarddisabled] > #urlbar > #notification-popup-box {
   padding-left: 7px;
 }
 
 #notification-popup-box:-moz-locale-dir(rtl),
 .notification-anchor-icon:-moz-locale-dir(rtl) {
   transform: scaleX(-1);
 }
 
--- a/browser/themes/osx/downloads/downloads.css
+++ b/browser/themes/osx/downloads/downloads.css
@@ -17,28 +17,37 @@
   padding: 4px;
   color: inherit;
 }
 
 #downloadsPanel:not([hasdownloads]) > #downloadsListBox {
   display: none;
 }
 
+#downloadsPanel[hasdownloads] > #emptyDownloads {
+  display: none;
+}
+
+#emptyDownloads {
+  padding: 10px 20px;
+  max-width: 40ch;
+}
+
 #downloadsFooter {
   border-bottom-left-radius: 4px;
   border-bottom-right-radius: 4px;
 }
 
 #downloadsHistory {
   background: transparent;
   color: hsl(210,100%,75%);
   cursor: pointer;
 }
 
-#downloadsPanel[hasdownloads] > #downloadsFooter {
+#downloadsFooter {
   background: #e5e5e5;
   border-top: 1px solid hsla(0,0%,0%,.1);
   box-shadow: 0 -1px hsla(0,0%,100%,.5) inset, 0 1px 1px hsla(0,0%,0%,.03) inset;
 }
 
 #downloadsHistory > .button-box {
   color: #808080;
   margin: 1em;
@@ -47,21 +56,16 @@
 #downloadsPanel[keyfocus] > #downloadsFooter > #downloadsSummary:focus,
 #downloadsPanel[keyfocus] > #downloadsFooter > #downloadsHistory:focus {
   outline: 2px -moz-mac-focusring solid;
   outline-offset: -2px;
   -moz-outline-radius-bottomleft: 4px;
   -moz-outline-radius-bottomright: 4px;
 }
 
-#downloadsPanel:not([hasdownloads]) > #downloadsFooter > #downloadsHistory:focus {
-  -moz-outline-radius-topleft: 4px;
-  -moz-outline-radius-topright: 4px;
-}
-
 /*** Downloads Summary and List items ***/
 
 #downloadsSummary,
 richlistitem[type="download"] {
   height: 7em;
   -moz-padding-end: 0;
   color: inherit;
 }
--- a/browser/themes/windows/browser.css
+++ b/browser/themes/windows/browser.css
@@ -9,17 +9,17 @@
 @namespace svg url("http://www.w3.org/2000/svg");
 
 %include ../shared/browser.inc
 %include windowsShared.inc
 %filter substitution
 %define toolbarShadowColor hsla(209,67%,12%,0.35)
 %define navbarTextboxCustomBorder border-color: rgba(0,0,0,.32);
 %define forwardTransitionLength 150ms
-%define conditionalForwardWithUrlbar window:not([chromehidden~="toolbar"]) #urlbar-container
+%define conditionalForwardWithUrlbar window:not([chromehidden~="toolbar"]) #urlbar-wrapper
 %define conditionalForwardWithUrlbarWidth 30
 
 #menubar-items {
   -moz-box-orient: vertical; /* for flex hack */
 }
 
 #main-menubar {
   -moz-box-flex: 1; /* make menu items expand to fill toolbar height */
@@ -881,46 +881,38 @@ toolbarbutton[sdk-button="true"][cui-are
 }
 
 #forward-button > menupopup {
   margin-top: 1px !important;
 }
 
 #forward-button > .toolbarbutton-icon {
   background-clip: padding-box !important;
-  /*mask: url(keyhole-forward-mask.svg#mask); XXX: this regresses twinopen */
-  clip-path: url(chrome://browser/content/browser.xul#keyhole-forward-clip-path) !important;
-  margin-left: -6px !important;
   border-left-style: none !important;
   border-radius: 0 !important;
   padding-left: 9px !important;
   padding-right: 3px !important;
 }
 
-%ifdef WINDOWS_AERO
-@media (-moz-os-version: windows-vista),
-       (-moz-os-version: windows-win7) {
-%endif
-  #forward-button > .toolbarbutton-icon {
-    margin-left: -6px !important;
-  }
-%ifdef WINDOWS_AERO
-}
-%endif
-
 @conditionalForwardWithUrlbar@:not([switchingtabs]) > #forward-button {
-  transition: opacity @forwardTransitionLength@ ease-out;
+  transition: margin-left @forwardTransitionLength@ ease-out;
+}
+
+@conditionalForwardWithUrlbar@ > #forward-button[disabled] {
+  margin-left: -@conditionalForwardWithUrlbarWidth@px;
+}
+
+@conditionalForwardWithUrlbar@:hover:not([switchingtabs]) > #forward-button[disabled] {
+  /* delay the hiding of the forward button when hovered to avoid accidental clicks on the url bar */
+  transition-delay: 100s;
 }
 
 @conditionalForwardWithUrlbar@:not(:hover) > #forward-button[disabled] {
-  opacity: 0;
-}
-
-@conditionalForwardWithUrlbar@ > #forward-button[occluded-by-urlbar] {
-  visibility: hidden;
+  /* when not hovered anymore, trigger a new transition to hide the forward button immediately */
+  margin-left: -@conditionalForwardWithUrlbarWidth@.01px;
 }
 
 #back-button {
   padding-top: 3px !important;
   padding-bottom: 3px !important;
   -moz-padding-start: 5px !important;
   -moz-padding-end: 0 !important;
   position: relative !important;
@@ -994,18 +986,17 @@ toolbarbutton[sdk-button="true"][cui-are
     box-shadow: 0 0 0 1px hsla(210,54%,20%,.55),
                 0 1px 0 hsla(210,54%,20%,.65) !important;
     transition: none;
   }
 %ifdef WINDOWS_AERO
 }
 %endif
 
-#back-button:-moz-locale-dir(rtl) > .toolbarbutton-icon,
-#forward-button:-moz-locale-dir(rtl) {
+#back-button:-moz-locale-dir(rtl) > .toolbarbutton-icon {
   transform: scaleX(-1);
 }
 
 .unified-nav-back[_moz-menuactive]:-moz-locale-dir(ltr),
 .unified-nav-forward[_moz-menuactive]:-moz-locale-dir(rtl) {
   list-style-image: url("chrome://browser/skin/menu-back.png") !important;
 }
 
@@ -1167,69 +1158,46 @@ toolbarbutton[sdk-button="true"][cui-are
   background-color: rgba(255,255,255,.9);
 }
 
 #urlbar:-moz-lwtheme[focused]:not([readonly]),
 .searchbar-textbox:-moz-lwtheme[focused] {
   background-color: white;
 }
 
-@conditionalForwardWithUrlbar@ > #urlbar-wrapper {
-  padding-left: @conditionalForwardWithUrlbarWidth@px;
-  -moz-margin-start: -@conditionalForwardWithUrlbarWidth@px;
-  position: relative;
-  pointer-events: none;
-}
-
-@conditionalForwardWithUrlbar@ > #urlbar-wrapper > #urlbar {
+@conditionalForwardWithUrlbar@ > #urlbar {
   -moz-border-start: none;
   margin-left: 0;
-  pointer-events: all;
-}
-
-@conditionalForwardWithUrlbar@:not([switchingtabs]) > #urlbar-wrapper > #urlbar {
-  transition: margin-left @forwardTransitionLength@ ease-out;
-}
-
-@conditionalForwardWithUrlbar@ > #urlbar-wrapper > #urlbar:-moz-locale-dir(ltr) {
+}
+
+@conditionalForwardWithUrlbar@ > #urlbar:-moz-locale-dir(ltr) {
   border-top-left-radius: 0;
   border-bottom-left-radius: 0;
 }
 
-@conditionalForwardWithUrlbar@ > #urlbar-wrapper > #urlbar:-moz-locale-dir(rtl) {
+@conditionalForwardWithUrlbar@ > #urlbar:-moz-locale-dir(rtl) {
   border-top-right-radius: 0;
   border-bottom-right-radius: 0;
 }
 
-@conditionalForwardWithUrlbar@[forwarddisabled] > #urlbar-wrapper {
+@conditionalForwardWithUrlbar@ {
   clip-path: url("chrome://browser/content/browser.xul#urlbar-back-button-clip-path");
-}
-
-@conditionalForwardWithUrlbar@[forwarddisabled] > #urlbar-wrapper > #urlbar {
-  margin-left: -@conditionalForwardWithUrlbarWidth@px;
-}
-
-@conditionalForwardWithUrlbar@[forwarddisabled]:hover:not([switchingtabs]) > #urlbar-wrapper > #urlbar {
-  /* delay the hiding of the forward button when hovered to avoid accidental clicks on the url bar */
-  transition-delay: 100s;
-}
-
-@conditionalForwardWithUrlbar@[forwarddisabled][switchingtabs] + #urlbar-container > #urlbar,
-@conditionalForwardWithUrlbar@[forwarddisabled]:not(:hover) > #urlbar-wrapper > #urlbar {
-  /* when switching tabs, or when not hovered anymore, trigger a new transition
-   * to hide the forward button immediately */
-  margin-left: -@conditionalForwardWithUrlbarWidth@.01px;
-}
-
-@conditionalForwardWithUrlbar@ > #urlbar-wrapper:-moz-locale-dir(rtl),
-@conditionalForwardWithUrlbar@ > #urlbar-wrapper > #urlbar:-moz-locale-dir(rtl) {
-  /* let windows-urlbar-back-button-mask clip the urlbar's right side for RTL */
+  -moz-margin-start: -5px;
+}
+
+@conditionalForwardWithUrlbar@:-moz-locale-dir(rtl),
+@conditionalForwardWithUrlbar@ > #urlbar:-moz-locale-dir(rtl) {
+  /* let urlbar-back-button-clip-path clip the urlbar's right side for RTL */
   transform: scaleX(-1);
 }
 
+@conditionalForwardWithUrlbar@:-moz-locale-dir(rtl) {
+  -moz-box-direction: reverse;
+}
+
 html|*.urlbar-input:-moz-lwtheme::-moz-placeholder,
 .searchbar-textbox:-moz-lwtheme > .autocomplete-textbox-container > .textbox-input-box > html|*.textbox-input::-moz-placeholder {
   opacity: 1.0;
   color: #777;
 }
 
 #urlbar-container {
   -moz-box-align: center;
@@ -1309,43 +1277,43 @@ html|*.urlbar-input:-moz-lwtheme::-moz-p
   border-bottom-right-radius: 1.5px;
 }
 
 #notification-popup-box:not([hidden]) + #identity-box {
   -moz-padding-start: 10px;
   border-radius: 0;
 }
 
-@conditionalForwardWithUrlbar@ > #urlbar-wrapper > #urlbar > #identity-box {
+@conditionalForwardWithUrlbar@ > #urlbar > #identity-box {
   border-radius: 0;
 }
 
-@conditionalForwardWithUrlbar@[forwarddisabled] > #urlbar-wrapper > #urlbar > #notification-popup-box[hidden] + #identity-box:-moz-locale-dir(ltr) {
+@conditionalForwardWithUrlbar@:not([switchingtabs]) > #urlbar > #identity-box {
+  transition: padding-left, padding-right;
+}
+
+@conditionalForwardWithUrlbar@ > #forward-button[disabled] + #urlbar > #notification-popup-box[hidden] + #identity-box:-moz-locale-dir(ltr) {
   padding-left: 5px;
-  transition: padding-left;
-}
-
-@conditionalForwardWithUrlbar@[forwarddisabled] > #urlbar-wrapper > #urlbar > #notification-popup-box[hidden] + #identity-box:-moz-locale-dir(rtl) {
+}
+
+@conditionalForwardWithUrlbar@ > #forward-button[disabled] + #urlbar > #notification-popup-box[hidden] + #identity-box:-moz-locale-dir(rtl) {
   padding-right: 5px;
-  transition: padding-right;
-}
-
-@conditionalForwardWithUrlbar@[forwarddisabled]:hover:not([switchingtabs]) > #urlbar-wrapper > #urlbar > #notification-popup-box[hidden] + #identity-box {
+}
+
+@conditionalForwardWithUrlbar@:hover:not([switchingtabs]) > #forward-button[disabled] + #urlbar > #notification-popup-box[hidden] + #identity-box {
   /* forward button hiding is delayed when hovered */
   transition-delay: 100s;
 }
 
-@conditionalForwardWithUrlbar@[forwarddisabled][switchingtabs] + #urlbar-container > #urlbar > #notification-popup-box[hidden] + #identity-box:-moz-locale-dir(ltr),
-@conditionalForwardWithUrlbar@[forwarddisabled]:not(:hover) > #urlbar-wrapper > #urlbar > #notification-popup-box[hidden] + #identity-box:-moz-locale-dir(ltr) {
+@conditionalForwardWithUrlbar@:not(:hover) > #forward-button[disabled] + #urlbar > #notification-popup-box[hidden] + #identity-box:-moz-locale-dir(ltr) {
   /* when not hovered anymore, trigger a new non-delayed transition to react to the forward button hiding */
   padding-left: 5.01px;
 }
 
-@conditionalForwardWithUrlbar@[forwarddisabled][switchingtabs] + #urlbar-container > #urlbar > #notification-popup-box[hidden] + #identity-box:-moz-locale-dir(rtl),
-@conditionalForwardWithUrlbar@[forwarddisabled]:not(:hover) > #urlbar-wrapper > #urlbar > #notification-popup-box[hidden] + #identity-box:-moz-locale-dir(rtl) {
+@conditionalForwardWithUrlbar@:not(:hover) > #forward-button[disabled] + #urlbar > #notification-popup-box[hidden] + #identity-box:-moz-locale-dir(rtl) {
   /* when not hovered anymore, trigger a new non-delayed transition to react to the forward button hiding */
   padding-right: 5.01px;
 }
 
 #urlbar[pageproxystate="valid"] > #identity-box.chromeUI,
 #urlbar[pageproxystate="valid"] > #identity-box.verifiedIdentity {
   -moz-margin-end: 4px;
 }
@@ -1390,24 +1358,20 @@ html|*.urlbar-input:-moz-lwtheme::-moz-p
 /* page proxy icon */
 
 %include ../shared/identity-block.inc.css
 
 #page-proxy-favicon {
   margin-top: 1px;
   margin-bottom: 1px;
   -moz-margin-start: 3px;
-  -moz-margin-end: 2px;
+  -moz-margin-end: 1px;
   -moz-image-region: rect(0, 16px, 16px, 0);
 }
 
-@conditionalForwardWithUrlbar@ > #urlbar-wrapper > #urlbar > #identity-box > #page-proxy-favicon {
-  -moz-margin-end: 1px;
-}
-
 #identity-box:hover > #page-proxy-favicon {
   -moz-image-region: rect(0, 32px, 16px, 16px);
 }
 
 #identity-box:hover:active > #page-proxy-favicon,
 #identity-box[open=true] > #page-proxy-favicon {
   -moz-image-region: rect(0, 48px, 16px, 32px);
 }
--- a/browser/themes/windows/downloads/downloads.css
+++ b/browser/themes/windows/downloads/downloads.css
@@ -13,16 +13,25 @@
   padding: 4px;
   color: inherit;
 }
 
 #downloadsPanel:not([hasdownloads]) > #downloadsListBox {
   display: none;
 }
 
+#downloadsPanel[hasdownloads] > #emptyDownloads {
+  display: none;
+}
+
+#emptyDownloads {
+  padding: 10px 20px;
+  max-width: 40ch;
+}
+
 #downloadsHistory {
   background: transparent;
   cursor: pointer;
 }
 
 %ifdef WINDOWS_AERO
 @media (-moz-os-version: windows-vista),
        (-moz-os-version: windows-win7) {
@@ -39,46 +48,46 @@
   outline-offset: -1px;
 }
 
 #downloadsHistory > .button-box {
   border: none;
   margin: 1em;
 }
 
-#downloadsPanel[hasdownloads] > #downloadsFooter {
+#downloadsFooter {
   background-color: hsla(210,4%,10%,.04);
   box-shadow: 0 1px 0 hsla(210,4%,10%,.08) inset;
   transition-duration: 150ms;
   transition-property: background-color;
 }
 
-#downloadsPanel[hasdownloads] > #downloadsFooter:hover {
+#downloadsFooter:hover {
   background-color: hsla(210,4%,10%,.05);
 }
 
-#downloadsPanel[hasdownloads] > #downloadsFooter:hover:active {
+#downloadsFooter:hover:active {
   background-color: hsla(210,4%,10%,.1);
   box-shadow: 0 2px 0 0 hsla(210,4%,10%,.1) inset;
 }
 
 %ifdef WINDOWS_AERO
 @media (-moz-os-version: windows-vista),
        (-moz-os-version: windows-win7) {
 %endif
 @media (-moz-windows-default-theme) {
-  #downloadsPanel[hasdownloads] > #downloadsFooter {
+  #downloadsFooter {
     border-bottom-left-radius: 3px;
     border-bottom-right-radius: 3px;
     transition-duration: 0s;
   }
 
-  #downloadsPanel[hasdownloads] > #downloadsFooter,
-  #downloadsPanel[hasdownloads] > #downloadsFooter:hover,
-  #downloadsPanel[hasdownloads] > #downloadsFooter:hover:active {
+  #downloadsFooter,
+  #downloadsFooter:hover,
+  #downloadsFooter:hover:active {
 %ifdef WINDOWS_AERO
     background-color: #f1f5fb;
 %else
     background-color: hsla(216,45%,88%,.98);
 %endif
     box-shadow: 0px 1px 2px rgb(204,214,234) inset;
   }
 }
--- a/dom/plugins/base/nsPluginHost.cpp
+++ b/dom/plugins/base/nsPluginHost.cpp
@@ -1624,68 +1624,58 @@ int64_t GetPluginLastModifiedTime(const 
   localfile->GetLastModifiedTime(&fileModTime);
 #endif
 
   return fileModTime;
 }
 
 bool
 GetPluginIsFromExtension(const nsCOMPtr<nsIFile>& pluginFile,
-                         const nsCOMPtr<nsISimpleEnumerator>& extensionDirs)
+                         const nsCOMArray<nsIFile>& extensionDirs)
 {
-  if (!extensionDirs) {
-    return false;
-  }
-
-  bool hasMore;
-  while (NS_SUCCEEDED(extensionDirs->HasMoreElements(&hasMore)) && hasMore) {
-    nsCOMPtr<nsISupports> supports;
-    nsresult rv = extensionDirs->GetNext(getter_AddRefs(supports));
-    if (NS_FAILED(rv)) {
-      continue;
-    }
-
-    nsCOMPtr<nsIFile> extDir(do_QueryInterface(supports, &rv));
-    if (NS_FAILED(rv)) {
-      continue;
-    }
-
-    nsCOMPtr<nsIFile> dir;
-    if (NS_FAILED(extDir->Clone(getter_AddRefs(dir)))) {
-      continue;
-    }
-
+  for (uint32_t i = 0; i < extensionDirs.Length(); ++i) {
     bool contains;
-    if (NS_FAILED(dir->Contains(pluginFile, true, &contains)) || !contains) {
+    if (NS_FAILED(extensionDirs[i]->Contains(pluginFile, true, &contains)) || !contains) {
       continue;
     }
 
     return true;
   }
 
   return false;
 }
 
-nsCOMPtr<nsISimpleEnumerator>
-GetExtensionDirectories()
+void
+GetExtensionDirectories(nsCOMArray<nsIFile>& dirs)
 {
   nsCOMPtr<nsIProperties> dirService = do_GetService(NS_DIRECTORY_SERVICE_CONTRACTID);
   if (!dirService) {
-    return nullptr;
+    return;
   }
 
   nsCOMPtr<nsISimpleEnumerator> list;
   nsresult rv = dirService->Get(XRE_EXTENSIONS_DIR_LIST,
                                 NS_GET_IID(nsISimpleEnumerator),
                                 getter_AddRefs(list));
   if (NS_FAILED(rv)) {
-    return nullptr;
+    return;
   }
 
-  return list;
+  bool more;
+  while (NS_SUCCEEDED(list->HasMoreElements(&more)) && more) {
+    nsCOMPtr<nsISupports> next;
+    if (NS_FAILED(list->GetNext(getter_AddRefs(next)))) {
+      break;
+    }
+    nsCOMPtr<nsIFile> file = do_QueryInterface(next);
+    if (file) {
+      file->Normalize();
+      dirs.AppendElement(file);
+    }
+  }
 }
 
 struct CompareFilesByTime
 {
   bool
   LessThan(const nsCOMPtr<nsIFile>& a, const nsCOMPtr<nsIFile>& b) const
   {
     return GetPluginLastModifiedTime(a) < GetPluginLastModifiedTime(b);
@@ -1741,20 +1731,18 @@ nsresult nsPluginHost::ScanPluginsDirect
 
     if (nsPluginsDir::IsPluginFile(dirEntry)) {
       pluginFiles.AppendElement(dirEntry);
     }
   }
 
   pluginFiles.Sort(CompareFilesByTime());
 
-  nsCOMPtr<nsISimpleEnumerator> extensionDirs = GetExtensionDirectories();
-  if (!extensionDirs) {
-    PLUGIN_LOG(PLUGIN_LOG_ALWAYS, ("Could not get extension directories."));
-  }
+  nsCOMArray<nsIFile> extensionDirs;
+  GetExtensionDirectories(extensionDirs);
 
   bool warnOutdated = false;
 
   for (int32_t i = (pluginFiles.Length() - 1); i >= 0; i--) {
     nsCOMPtr<nsIFile>& localfile = pluginFiles[i];
 
     nsString utf16FilePath;
     rv = localfile->GetPath(utf16FilePath);
--- a/dom/plugins/test/testaddon/Makefile.in
+++ b/dom/plugins/test/testaddon/Makefile.in
@@ -1,23 +1,23 @@
 # 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/.
 
 include $(topsrcdir)/config/rules.mk
 
 ifeq ($(MOZ_WIDGET_TOOLKIT),cocoa)
-plugin_file_name = Test.plugin
+plugin_file_names = Test.plugin SecondTest.plugin
 addon_file_name = testaddon_$(TARGET_XPCOM_ABI).xpi
 else
-plugin_file_name = $(DLL_PREFIX)nptest$(DLL_SUFFIX)
+plugin_file_names = $(DLL_PREFIX)nptest$(DLL_SUFFIX) $(DLL_PREFIX)npsecondtest$(DLL_SUFFIX)
 addon_file_name = testaddon.xpi
 endif
 
 # This is so hacky. Waiting on bug 988938.
 testdir = $(abspath $(DEPTH)/_tests/xpcshell/dom/plugins/test/unit/)
 addonpath = $(testdir)/$(addon_file_name)
 
 libs::
 	$(NSINSTALL) -D $(testdir)
 	rm -f $(addonpath)
 	cd $(srcdir) && zip -rD $(addonpath) install.rdf
-	cd $(DIST) && zip -rD $(addonpath) plugins/$(plugin_file_name)
+	cd $(DIST) && zip -rD $(addonpath) $(foreach name,$(plugin_file_names),plugins/$(name))
--- a/dom/plugins/test/unit/head_plugins.js
+++ b/dom/plugins/test/unit/head_plugins.js
@@ -9,37 +9,23 @@ Cu.import("resource://gre/modules/Promis
 
 const gIsWindows = ("@mozilla.org/windows-registry-key;1" in Cc);
 const gIsOSX = ("nsILocalFileMac" in Ci);
 const gIsLinux = ("@mozilla.org/gnome-gconf-service;1" in Cc) ||
   ("@mozilla.org/gio-service;1" in Cc);
 const gDirSvc = Cc["@mozilla.org/file/directory_service;1"].getService(Ci.nsIProperties);
 
 // Finds the test plugin library
-function get_test_plugin() {
+function get_test_plugin(secondplugin=false) {
   var pluginEnum = gDirSvc.get("APluginsDL", Ci.nsISimpleEnumerator);
   while (pluginEnum.hasMoreElements()) {
     let dir = pluginEnum.getNext().QueryInterface(Ci.nsILocalFile);
+    let name = get_platform_specific_plugin_name(secondplugin);
     let plugin = dir.clone();
-    // OSX plugin
-    plugin.append("Test.plugin");
-    if (plugin.exists()) {
-      plugin.normalize();
-      return plugin;
-    }
-    plugin = dir.clone();
-    // *nix plugin
-    plugin.append("libnptest.so");
-    if (plugin.exists()) {
-      plugin.normalize();
-      return plugin;
-    }
-    // Windows plugin
-    plugin = dir.clone();
-    plugin.append("nptest.dll");
+    plugin.append(name);
     if (plugin.exists()) {
       plugin.normalize();
       return plugin;
     }
   }
   return null;
 }
 
@@ -88,21 +74,27 @@ function do_get_profile_startup() {
       throw Components.results.NS_ERROR_NO_INTERFACE;
     }
   };
   dirSvc.QueryInterface(Components.interfaces.nsIDirectoryService)
         .registerProvider(provider);
   return file.clone();
 }
 
-function get_platform_specific_plugin_name() {
-  if (gIsWindows) return "nptest.dll";
-  else if (gIsOSX) return "Test.plugin";
-  else if (gIsLinux) return "libnptest.so";
-  else return null;
+function get_platform_specific_plugin_name(secondplugin=false) {
+  if (secondplugin) {
+    if (gIsWindows) return "npsecondtest.dll";
+    if (gIsOSX) return "SecondTest.plugin";
+    if (gIsLinux) return "libnpsecondtest.so";
+  } else {
+    if (gIsWindows) return "nptest.dll";
+    if (gIsOSX) return "Test.plugin";
+    if (gIsLinux) return "libnptest.so";
+  }
+  return null;
 }
 
 function get_platform_specific_plugin_suffix() {
   if (gIsWindows) return ".dll";
   else if (gIsOSX) return ".plugin";
   else if (gIsLinux) return ".so";
   else return null;
 }
--- a/dom/plugins/test/unit/test_plugin_default_state_xpi.js
+++ b/dom/plugins/test/unit/test_plugin_default_state_xpi.js
@@ -36,16 +36,21 @@ function run_test() {
   run_next_test();
 }
 
 add_task(function* test_state() {
   // Remove test so we will have only one "Test Plug-in" registered.
   // xpcshell tests have plugins in per-test profiles, so that's fine.
   let file = get_test_plugin();
   file.remove(true);
+  file = get_test_plugin(true);
+  file.remove(true);
+
+  Services.prefs.setIntPref("plugin.default.state", Ci.nsIPluginTag.STATE_CLICKTOPLAY);
+  Services.prefs.setIntPref("plugin.defaultXpi.state", Ci.nsIPluginTag.STATE_ENABLED);
 
   let success = yield installAddon(getTestaddonFilename());
   Assert.ok(success, "Should have installed addon.");
   let addonDir = getAddonRoot(gProfileDir, ADDON_ID);
 
   let provider = {
     classID: Components.ID("{0af6b2d7-a06c-49b7-babc-636d292b0dbb}"),
     QueryInterface: XPCOMUtils.generateQI([Ci.nsIDirectoryServiceProvider,
@@ -100,9 +105,13 @@ add_task(function* test_state() {
   Assert.ok(!addon.userDisabled, "Addon should not be user disabled");
 
   let testPlugin = get_test_plugintag();
   Assert.notEqual(testPlugin, null, "Test plugin should have been found");
   Assert.equal(testPlugin.enabledState, Ci.nsIPluginTag.STATE_ENABLED, "Test plugin from addon should have state enabled");
 
   pluginDir.append(testPlugin.filename);
   Assert.ok(pluginDir.exists(), "Plugin file should exist in addon directory: " + pluginDir.path);
+
+  testPlugin = get_test_plugintag("Second Test Plug-in");
+  Assert.notEqual(testPlugin, null, "Second test plugin should have been found");
+  Assert.equal(testPlugin.enabledState, Ci.nsIPluginTag.STATE_ENABLED, "Second test plugin from addon should have state enabled");
 });
--- a/mobile/android/base/BrowserApp.java
+++ b/mobile/android/base/BrowserApp.java
@@ -1915,17 +1915,17 @@ abstract public class BrowserApp extends
     }
 
     /**
      * Add the provided item to the provided menu, which should be
      * the root (mMenu).
      */
     private void addAddonMenuItemToMenu(final Menu menu, final MenuItemInfo info) {
         info.added = true;
-        
+
         final Menu destination;
         if (info.parent == 0) {
             destination = menu;
         } else if (info.parent == GECKO_TOOLS_MENU) {
             MenuItem tools = menu.findItem(R.id.tools);
             destination = tools != null ? tools.getSubMenu() : menu;
         } else {
             MenuItem parent = menu.findItem(info.parent);
@@ -2051,17 +2051,17 @@ abstract public class BrowserApp extends
         }
     }
 
     @Override
     public boolean onCreateOptionsMenu(Menu menu) {
         // Sets mMenu = menu.
         super.onCreateOptionsMenu(menu);
 
-        // Inform the menu about the action-items bar. 
+        // Inform the menu about the action-items bar.
         if (menu instanceof GeckoMenu &&
             HardwareUtils.isTablet()) {
             ((GeckoMenu) menu).setActionItemBarPresenter(mBrowserToolbar);
         }
 
         MenuInflater inflater = getMenuInflater();
         inflater.inflate(R.menu.browser_app_menu, mMenu);
 
@@ -2465,17 +2465,17 @@ abstract public class BrowserApp extends
             }
         }
         return super.onKeyLongPress(keyCode, event);
     }
 
     /*
      * If the app has been launched a certain number of times, and we haven't asked for feedback before,
      * open a new tab with about:feedback when launching the app from the icon shortcut.
-     */ 
+     */
     @Override
     protected void onNewIntent(Intent intent) {
         super.onNewIntent(intent);
 
         String action = intent.getAction();
 
         if (AppConstants.MOZ_ANDROID_BEAM && Build.VERSION.SDK_INT >= 10 && NfcAdapter.ACTION_NDEF_DISCOVERED.equals(action)) {
             String uri = intent.getDataString();
@@ -2567,17 +2567,17 @@ abstract public class BrowserApp extends
             }
         }).execute();
     }
 
     // HomePager.OnNewTabsListener
     @Override
     public void onNewTabs(String[] urls) {
         final EnumSet<OnUrlOpenListener.Flags> flags = EnumSet.of(OnUrlOpenListener.Flags.ALLOW_SWITCH_TO_TAB);
- 
+
         for (String url : urls) {
             if (!maybeSwitchToTab(url, flags)) {
                 openUrlAndStopEditing(url, true);
             }
         }
     }
 
     // HomePager.OnUrlOpenListener
--- a/mobile/android/base/gfx/TouchEventHandler.java
+++ b/mobile/android/base/gfx/TouchEventHandler.java
@@ -249,16 +249,18 @@ final class TouchEventHandler implements
 
         MotionEvent event = mEventQueue.poll();
         while (true) {
             // event being null here is valid and represents a block of events
             // that has already been dispatched.
 
             if (event != null) {
                 dispatchEvent(event, allowDefaultAction);
+                event.recycle();
+                event = null;
             }
             if (mEventQueue.isEmpty()) {
                 // we have processed the backlog of events, and are all caught up.
                 // now we can set clear the hold flag and set the dispatch flag so
                 // that the handleEvent() function can do the right thing for all
                 // remaining events in this block (which is still ongoing) without
                 // having to put them in the queue.
                 mHoldInQueue = false;
--- a/mobile/android/base/home/PanelItemView.java
+++ b/mobile/android/base/home/PanelItemView.java
@@ -73,16 +73,19 @@ class PanelItemView extends LinearLayout
                    .into(image);
         }
     }
 
     private static class ArticleItemView extends PanelItemView {
         private ArticleItemView(Context context) {
             super(context, R.layout.panel_article_item);
             setOrientation(LinearLayout.HORIZONTAL);
+
+            final int padding = getResources().getDimensionPixelSize(R.dimen.article_item_view_padding);
+            setPadding(0, padding, 0, padding);
         }
     }
 
     private static class ImageItemView extends PanelItemView {
         private ImageItemView(Context context) {
             super(context, R.layout.panel_image_item);
             setOrientation(LinearLayout.VERTICAL);
         }
--- a/mobile/android/base/home/TabMenuStrip.java
+++ b/mobile/android/base/home/TabMenuStrip.java
@@ -11,191 +11,90 @@ import android.content.Context;
 import android.content.res.TypedArray;
 import android.graphics.Canvas;
 import android.graphics.drawable.Drawable;
 import android.util.AttributeSet;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewTreeObserver;
 import android.view.accessibility.AccessibilityEvent;
+import android.widget.HorizontalScrollView;
 import android.widget.LinearLayout;
 import android.widget.TextView;
 
-public class TabMenuStrip extends LinearLayout
-                          implements HomePager.Decor,
-                                     View.OnFocusChangeListener {
-    private static final String LOGTAG = "GeckoTabMenuStrip";
+/**
+ * {@code TabMenuStrip} is the view used to display {@code HomePager} tabs
+ * on tablets. See {@code TabMenuStripLayout} for details about how the
+ * tabs are created and updated.
+ */
+public class TabMenuStrip extends HorizontalScrollView
+                          implements HomePager.Decor {
 
-    private HomePager.OnTitleClickListener mOnTitleClickListener;
-    private Drawable mStrip;
-    private View mSelectedView;
+    // Offset between the selected tab title and the edge of the screen,
+    // except for the first and last tab in the tab strip.
+    private static final int TITLE_OFFSET_DIPS = 24;
 
-    // Data associated with the scrolling of the strip drawable.
-    private View toTab;
-    private View fromTab;
-    private float progress;
-
-    // This variable is used to predict the direction of scroll.
-    private float mPrevProgress;
+    private final int titleOffset;
+    private final TabMenuStripLayout layout;
 
     public TabMenuStrip(Context context, AttributeSet attrs) {
         super(context, attrs);
 
-        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.TabMenuStrip);
-        final int stripResId = a.getResourceId(R.styleable.TabMenuStrip_strip, -1);
-        a.recycle();
+        // Disable the scroll bar.
+        setHorizontalScrollBarEnabled(false);
 
-        if (stripResId != -1) {
-            mStrip = getResources().getDrawable(stripResId);
-        }
+        titleOffset = (int) (TITLE_OFFSET_DIPS * getResources().getDisplayMetrics().density);
 
-        setWillNotDraw(false);
+        layout = new TabMenuStripLayout(context, attrs);
+        addView(layout, LayoutParams.FILL_PARENT, LayoutParams.FILL_PARENT);
     }
 
     @Override
     public void onAddPagerView(String title) {
-        final TextView button = (TextView) LayoutInflater.from(getContext()).inflate(R.layout.tab_menu_strip, this, false);
-        button.setText(title.toUpperCase());
-
-        addView(button);
-        button.setOnClickListener(new ViewClickListener(getChildCount() - 1));
-        button.setOnFocusChangeListener(this);
+        layout.onAddPagerView(title);
     }
 
     @Override
     public void removeAllPagerViews() {
-        removeAllViews();
+        layout.removeAllViews();
     }
 
     @Override
     public void onPageSelected(final int position) {
-        mSelectedView = getChildAt(position);
-
-        // Callback to measure and draw the strip after the view is visible.
-        ViewTreeObserver vto = mSelectedView.getViewTreeObserver();
-        if (vto.isAlive()) {
-            vto.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
-                @Override
-                public void onGlobalLayout() {
-                    mSelectedView.getViewTreeObserver().removeGlobalOnLayoutListener(this);
-
-                    if (mStrip != null) {
-                        mStrip.setBounds(mSelectedView.getLeft(),
-                                         mSelectedView.getTop(),
-                                         mSelectedView.getRight(),
-                                         mSelectedView.getBottom());
-                    }
-
-                    mPrevProgress = position;
-                }
-            });
-        }
+        layout.onPageSelected(position);
     }
 
-    // Page scroll animates the drawable and its bounds from the previous to next child view.
     @Override
     public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
-        if (mStrip == null) {
-            return;
-        }
+        layout.onPageScrolled(position, positionOffset, positionOffsetPixels);
 
-        setScrollingData(position, positionOffset);
-
-        if (fromTab == null || toTab == null) {
+        final View selectedTitle = layout.getChildAt(position);
+        if (selectedTitle == null) {
             return;
         }
 
-        final int fromTabLeft =  fromTab.getLeft();
-        final int fromTabRight = fromTab.getRight();
-
-        final int toTabLeft =  toTab.getLeft();
-        final int toTabRight = toTab.getRight();
-
-        mStrip.setBounds((int) (fromTabLeft + ((toTabLeft - fromTabLeft) * progress)),
-                         0,
-                         (int) (fromTabRight + ((toTabRight - fromTabRight) * progress)),
-                         getHeight());
-        invalidate();
-    }
+        final int selectedTitleOffset = (int) (positionOffset * selectedTitle.getWidth());
 
-    /*
-     * position + positionOffset goes from 0 to 2 as we scroll from page 1 to 3.
-     * Normalized progress is relative to the the direction the page is being scrolled towards.
-     * For this, we maintain direction of scroll with a state, and the child view we are moving towards and away from.
-     */
-    private void setScrollingData(int position, float positionOffset) {
-        if (position >= getChildCount() - 1) {
-            return;
-        }
-
-        final float currProgress = position + positionOffset;
-
-        if (mPrevProgress > currProgress) {
-            toTab = getChildAt(position);
-            fromTab = getChildAt(position + 1);
-            progress = 1 - positionOffset;
-        } else {
-            toTab = getChildAt(position + 1);
-            fromTab = getChildAt(position);
-            progress = positionOffset;
+        int titleLeft = selectedTitle.getLeft() + selectedTitleOffset;
+        if (position > 0) {
+            titleLeft -= titleOffset;
         }
 
-        mPrevProgress = currProgress;
-    }
-
-    @Override
-    public void onDraw(Canvas canvas) {
-        super.onDraw(canvas);
-
-        if (mStrip != null) {
-            mStrip.draw(canvas);
-        }
-    }
-
-    @Override
-    public void onFocusChange(View v, boolean hasFocus) {
-        if (v == this && hasFocus && getChildCount() > 0) {
-            mSelectedView.requestFocus();
-            return;
+        int titleRight = selectedTitle.getRight() + selectedTitleOffset;
+        if (position < layout.getChildCount() - 1) {
+            titleRight += titleOffset;
         }
 
-        if (!hasFocus) {
-            return;
-        }
-
-        int i = 0;
-        final int numTabs = getChildCount();
-
-        while (i < numTabs) {
-            View view = getChildAt(i);
-            if (view == v) {
-                view.requestFocus();
-                if (isShown()) {
-                    // A view is focused so send an event to announce the menu strip state.
-                    sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_FOCUSED);
-                }
-                break;
-            }
-
-            i++;
+        final int scrollX = getScrollX();
+        if (titleLeft < scrollX) {
+            // Tab strip overflows to the left.
+            scrollTo(titleLeft, 0);
+        } else if (titleRight > scrollX + getWidth()) {
+            // Tab strip overflows to the right.
+            scrollTo(titleRight - getWidth(), 0);
         }
     }
 
     @Override
     public void setOnTitleClickListener(HomePager.OnTitleClickListener onTitleClickListener) {
-        mOnTitleClickListener = onTitleClickListener;
-    }
-
-    private class ViewClickListener implements OnClickListener {
-        private final int mIndex;
-
-        public ViewClickListener(int index) {
-            mIndex = index;
-        }
-
-        @Override
-        public void onClick(View view) {
-            if (mOnTitleClickListener != null) {
-                mOnTitleClickListener.onTitleClicked(mIndex);
-            }
-        }
+        layout.setOnTitleClickListener(onTitleClickListener);
     }
 }
new file mode 100644
--- /dev/null
+++ b/mobile/android/base/home/TabMenuStripLayout.java
@@ -0,0 +1,194 @@
+/* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; 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.R;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.graphics.Canvas;
+import android.graphics.drawable.Drawable;
+import android.util.AttributeSet;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewTreeObserver;
+import android.view.accessibility.AccessibilityEvent;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+/**
+ * {@code TabMenuStripLayout} is the view that draws the {@code HomePager}
+ * tabs that are displayed in {@code TabMenuStrip}.
+ */
+class TabMenuStripLayout extends LinearLayout
+                         implements View.OnFocusChangeListener {
+
+    private HomePager.OnTitleClickListener onTitleClickListener;
+    private Drawable strip;
+    private View selectedView;
+
+    // Data associated with the scrolling of the strip drawable.
+    private View toTab;
+    private View fromTab;
+    private float progress;
+
+    // This variable is used to predict the direction of scroll.
+    private float prevProgress;
+
+    TabMenuStripLayout(Context context, AttributeSet attrs) {
+        super(context, attrs);
+
+        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.TabMenuStrip);
+        final int stripResId = a.getResourceId(R.styleable.TabMenuStrip_strip, -1);
+        a.recycle();
+
+        if (stripResId != -1) {
+            strip = getResources().getDrawable(stripResId);
+        }
+
+        setWillNotDraw(false);
+    }
+
+    void onAddPagerView(String title) {
+        final TextView button = (TextView) LayoutInflater.from(getContext()).inflate(R.layout.tab_menu_strip, this, false);
+        button.setText(title.toUpperCase());
+
+        addView(button);
+        button.setOnClickListener(new ViewClickListener(getChildCount() - 1));
+        button.setOnFocusChangeListener(this);
+    }
+
+    void onPageSelected(final int position) {
+        selectedView = getChildAt(position);
+
+        // Callback to measure and draw the strip after the view is visible.
+        ViewTreeObserver vto = selectedView.getViewTreeObserver();
+        if (vto.isAlive()) {
+            vto.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
+                @Override
+                public void onGlobalLayout() {
+                    selectedView.getViewTreeObserver().removeGlobalOnLayoutListener(this);
+
+                    if (strip != null) {
+                        strip.setBounds(selectedView.getLeft(),
+                                        selectedView.getTop(),
+                                        selectedView.getRight(),
+                                        selectedView.getBottom());
+                    }
+
+                    prevProgress = position;
+                }
+            });
+        }
+    }
+
+    // Page scroll animates the drawable and its bounds from the previous to next child view.
+    void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
+        if (strip == null) {
+            return;
+        }
+
+        setScrollingData(position, positionOffset);
+
+        if (fromTab == null || toTab == null) {
+            return;
+        }
+
+        final int fromTabLeft =  fromTab.getLeft();
+        final int fromTabRight = fromTab.getRight();
+
+        final int toTabLeft =  toTab.getLeft();
+        final int toTabRight = toTab.getRight();
+
+        strip.setBounds((int) (fromTabLeft + ((toTabLeft - fromTabLeft) * progress)),
+                         0,
+                         (int) (fromTabRight + ((toTabRight - fromTabRight) * progress)),
+                         getHeight());
+        invalidate();
+    }
+
+    /*
+     * position + positionOffset goes from 0 to 2 as we scroll from page 1 to 3.
+     * Normalized progress is relative to the the direction the page is being scrolled towards.
+     * For this, we maintain direction of scroll with a state, and the child view we are moving towards and away from.
+     */
+    void setScrollingData(int position, float positionOffset) {
+        if (position >= getChildCount() - 1) {
+            return;
+        }
+
+        final float currProgress = position + positionOffset;
+
+        if (prevProgress > currProgress) {
+            toTab = getChildAt(position);
+            fromTab = getChildAt(position + 1);
+            progress = 1 - positionOffset;
+        } else {
+            toTab = getChildAt(position + 1);
+            fromTab = getChildAt(position);
+            progress = positionOffset;
+        }
+
+        prevProgress = currProgress;
+    }
+
+    @Override
+    public void onDraw(Canvas canvas) {
+        super.onDraw(canvas);
+
+        if (strip != null) {
+            strip.draw(canvas);
+        }
+    }
+
+    @Override
+    public void onFocusChange(View v, boolean hasFocus) {
+        if (v == this && hasFocus && getChildCount() > 0) {
+            selectedView.requestFocus();
+            return;
+        }
+
+        if (!hasFocus) {
+            return;
+        }
+
+        int i = 0;
+        final int numTabs = getChildCount();
+
+        while (i < numTabs) {
+            View view = getChildAt(i);
+            if (view == v) {
+                view.requestFocus();
+                if (isShown()) {
+                    // A view is focused so send an event to announce the menu strip state.
+                    sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_FOCUSED);
+                }
+                break;
+            }
+
+            i++;
+        }
+    }
+
+    void setOnTitleClickListener(HomePager.OnTitleClickListener onTitleClickListener) {
+        this.onTitleClickListener = onTitleClickListener;
+    }
+
+    private class ViewClickListener implements OnClickListener {
+        private final int mIndex;
+
+        public ViewClickListener(int index) {
+            mIndex = index;
+        }
+
+        @Override
+        public void onClick(View view) {
+            if (onTitleClickListener != null) {
+                onTitleClickListener.onTitleClicked(mIndex);
+            }
+        }
+    }
+}
--- a/mobile/android/base/moz.build
+++ b/mobile/android/base/moz.build
@@ -274,16 +274,17 @@ gbjar.sources += [
     'home/ReadingListPanel.java',
     'home/ReadingListRow.java',
     'home/SearchEngine.java',
     'home/SearchEngineRow.java',
     'home/SearchLoader.java',
     'home/SimpleCursorLoader.java',
     'home/SuggestClient.java',
     'home/TabMenuStrip.java',
+    'home/TabMenuStripLayout.java',
     'home/TopSitesGridItemView.java',
     'home/TopSitesGridView.java',
     'home/TopSitesPanel.java',
     'home/TopSitesThumbnailView.java',
     'home/TwoLinePageRow.java',
     'InputMethods.java',
     'JavaAddonManager.java',
     'LightweightTheme.java',
--- a/mobile/android/base/resources/layout/panel_article_item.xml
+++ b/mobile/android/base/resources/layout/panel_article_item.xml
@@ -3,29 +3,24 @@
    - 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/. -->
 
 <merge xmlns:android="http://schemas.android.com/apk/res/android">
 
     <ImageView android:id="@+id/image"
                android:layout_width="54dp"
                android:layout_height="44dp"
-               android:layout_marginTop="10dip"
-               android:layout_marginBottom="10dip"
                android:layout_marginLeft="10dip"
                android:scaleType="centerCrop"/>
 
     <LinearLayout android:id="@+id/title_desc_container"
                   android:layout_width="fill_parent"
                   android:layout_height="wrap_content"
-                  android:paddingTop="15dip"
-                  android:paddingBottom="15dip"
                   android:paddingLeft="10dip"
                   android:paddingRight="10dip"
-                  android:minHeight="@dimen/page_row_height"
                   android:gravity="center_vertical"
                   android:orientation="vertical">
 
         <TextView android:id="@+id/title"
                   style="@style/Widget.PanelItemView.Title"
                   android:layout_width="fill_parent"
                   android:layout_height="wrap_content"/>
 
--- a/mobile/android/base/resources/values/dimens.xml
+++ b/mobile/android/base/resources/values/dimens.xml
@@ -102,9 +102,12 @@
     <dimen name="home_banner_height">72dp</dimen>
 
     <!-- Icon Grid -->
     <dimen name="icongrid_columnwidth">128dp</dimen>
     <dimen name="icongrid_padding">16dp</dimen>
 
     <!-- PanelGridView dimensions -->
     <dimen name="panel_grid_view_column_width">150dp</dimen>
+
+    <!-- ArticleItemView dimensions -->
+    <dimen name="article_item_view_padding">15dp</dimen>
 </resources>
--- a/mobile/android/base/tests/BaseTest.java
+++ b/mobile/android/base/tests/BaseTest.java
@@ -433,19 +433,18 @@ abstract class BaseTest extends BaseRobo
     public void selectMenuItemByPath(String[] listItems) {
         int listLength = listItems.length;
         if (listLength > 0) {
             selectMenuItem(listItems[0]);
         }
         if (listLength > 1) {
             for (int i = 1; i < listLength; i++) {
                 String itemName = "^" + listItems[i] + "$";
-                if (!waitForPreferencesText(itemName)) {
-                    mSolo.scrollDown();
-                }
+                mAsserter.ok(waitForPreferencesText(itemName), "Waiting for and scrolling once to find item " + itemName, itemName + " found");
+                mAsserter.ok(waitForEnabledText(itemName), "Waiting for enabled text " + itemName, itemName + " option is present and enabled");
                 mSolo.clickOnText(itemName);
             }
         }
     }
 
     public final void selectMenuItem(String menuItemName) {
         // build the item name ready to be used
         String itemName = "^" + menuItemName + "$";
--- a/mobile/android/base/tests/MotionEventHelper.java
+++ b/mobile/android/base/tests/MotionEventHelper.java
@@ -21,39 +21,54 @@ class MotionEventHelper {
         mSurfaceOffsetY = surfaceOffsetY;
         Log.i(LOGTAG, "Initialized using offset (" + mSurfaceOffsetX + "," + mSurfaceOffsetY + ")");
     }
 
     public long down(float x, float y) {
         Log.d(LOGTAG, "Triggering down at (" + x + "," + y + ")");
         long downTime = SystemClock.uptimeMillis();
         MotionEvent event = MotionEvent.obtain(downTime, downTime, MotionEvent.ACTION_DOWN, mSurfaceOffsetX + x, mSurfaceOffsetY + y, 0);
-        mInstrumentation.sendPointerSync(event);
+        try {
+            mInstrumentation.sendPointerSync(event);
+        } finally {
+            event.recycle();
+            event = null;
+        }
         return downTime;
     }
 
     public long move(long downTime, float x, float y) {
         return move(downTime, SystemClock.uptimeMillis(), x, y);
     }
 
     public long move(long downTime, long moveTime, float x, float y) {
         Log.d(LOGTAG, "Triggering move to (" + x + "," + y + ")");
         MotionEvent event = MotionEvent.obtain(downTime, moveTime, MotionEvent.ACTION_MOVE, mSurfaceOffsetX + x, mSurfaceOffsetY + y, 0);
-        mInstrumentation.sendPointerSync(event);
+        try {
+            mInstrumentation.sendPointerSync(event);
+        } finally {
+            event.recycle();
+            event = null;
+        }
         return downTime;
     }
 
     public long up(long downTime, float x, float y) {
         return up(downTime, SystemClock.uptimeMillis(), x, y);
     }
 
     public long up(long downTime, long upTime, float x, float y) {
         Log.d(LOGTAG, "Triggering up at (" + x + "," + y + ")");
         MotionEvent event = MotionEvent.obtain(downTime, upTime, MotionEvent.ACTION_UP, mSurfaceOffsetX + x, mSurfaceOffsetY + y, 0);
-        mInstrumentation.sendPointerSync(event);
+        try {
+            mInstrumentation.sendPointerSync(event);
+        } finally {
+            event.recycle();
+            event = null;
+        }
         return -1L;
     }
 
     public Thread dragAsync(final float startX, final float startY, final float endX, final float endY, final long durationMillis) {
         Thread t = new Thread() {
             @Override
             public void run() {
                 int numEvents = (int)(durationMillis * DRAG_EVENTS_PER_SECOND / 1000);
--- a/mobile/android/base/tests/MotionEventReplayer.java
+++ b/mobile/android/base/tests/MotionEventReplayer.java
@@ -198,18 +198,23 @@ class MotionEventReplayer {
                             long.class, long.class, int.class, int.class, pointerIds.getClass(),
                             pointerData.getClass(), int.class, float.class, float.class,
                             int.class, int.class);
                     }
                     event = (MotionEvent)mObtainNanoMethod.invoke(null, downTime, eventTime,
                             eventTime * 1000000, action, pointerCount, pointerIds, (float[])pointerData,
                             metaState, xPrecision, yPrecision, deviceId, edgeFlags);
                 }
-                Log.v(LOGTAG, "Injecting " + event.toString());
-                mInstrumentation.sendPointerSync(event);
+                try {
+                    Log.v(LOGTAG, "Injecting " + event.toString());
+                    mInstrumentation.sendPointerSync(event);
+                } finally {
+                    event.recycle();
+                    event = null;
+                }
 
                 eventProperties.clear();
             }
         } finally {
             br.close();
         }
     }
 }
--- a/mobile/android/base/toolbar/BrowserToolbar.java
+++ b/mobile/android/base/toolbar/BrowserToolbar.java
@@ -45,16 +45,17 @@ import android.text.TextUtils;
 import android.util.AttributeSet;
 import android.util.Log;
 import android.view.ContextMenu;
 import android.view.KeyEvent;
 import android.view.LayoutInflater;
 import android.view.MenuInflater;
 import android.view.MotionEvent;
 import android.view.View;
+import android.view.ViewGroup;
 import android.view.animation.AccelerateInterpolator;
 import android.view.animation.Interpolator;
 import android.view.inputmethod.InputMethodManager;
 import android.widget.Button;
 import android.widget.ImageButton;
 import android.widget.ImageView;
 import android.widget.LinearLayout;
 import android.widget.PopupWindow;
@@ -185,17 +186,20 @@ public class BrowserToolbar extends Them
         final Resources res = getResources();
         urlBarViewOffset = res.getDimensionPixelSize(R.dimen.url_bar_offset_left);
         defaultForwardMargin = res.getDimensionPixelSize(R.dimen.forward_default_offset);
         urlDisplayLayout = (ToolbarDisplayLayout) findViewById(R.id.display_layout);
         urlBarEntry = findViewById(R.id.url_bar_entry);
         urlEditLayout = (ToolbarEditLayout) findViewById(R.id.edit_layout);
 
         urlBarEntryDefaultLayoutParams = (RelativeLayout.LayoutParams) urlBarEntry.getLayoutParams();
-        urlBarEntryShrunkenLayoutParams = new RelativeLayout.LayoutParams(urlBarEntryDefaultLayoutParams);
+        // API level 19 adds a RelativeLayout.LayoutParams copy constructor, so we explicitly cast
+        // to ViewGroup.MarginLayoutParams to ensure consistency across platforms.
+        urlBarEntryShrunkenLayoutParams = new RelativeLayout.LayoutParams(
+                (ViewGroup.MarginLayoutParams) urlBarEntryDefaultLayoutParams);
         urlBarEntryShrunkenLayoutParams.addRule(RelativeLayout.ALIGN_RIGHT, R.id.edit_layout);
         urlBarEntryShrunkenLayoutParams.rightMargin = 0;
 
         // This will clip the translating edge's image at 60% of its width
         urlBarTranslatingEdge = (ImageView) findViewById(R.id.url_bar_translating_edge);
         if (urlBarTranslatingEdge != null) {
             urlBarTranslatingEdge.getDrawable().setLevel(6000);
         }
@@ -392,17 +396,17 @@ public class BrowserToolbar extends Them
     }
 
     public void refresh() {
         urlDisplayLayout.dismissSiteIdentityPopup();
     }
 
     public boolean onBackPressed() {
         if (isEditing()) {
-            stopEditing();
+            cancelEdit();
             return true;
         }
 
         return urlDisplayLayout.dismissSiteIdentityPopup();
     }
 
     public boolean onKey(int keyCode, KeyEvent event) {
         if (event.getAction() != KeyEvent.ACTION_DOWN) {