merge fx-team to mozilla-central a=merge
authorCarsten "Tomcat" Book <cbook@mozilla.com>
Tue, 25 Nov 2014 12:51:23 +0100
changeset 241655 f3e841fb7358e9dc029dc7b3a575a7a2109aaf36
parent 241640 4631a7474d8a15d4f645861660b0c61579e2a37a (current diff)
parent 241654 c1890cc0359d5ad79d45453798a1d5fa71ddcbfd (diff)
child 241673 acde07cb4e4df582ea83ff0a7fd7b11c33479fad
push id4311
push userraliiev@mozilla.com
push dateMon, 12 Jan 2015 19:37:41 +0000
treeherdermozilla-beta@150c9fed433b [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone36.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
merge fx-team to mozilla-central a=merge
browser/base/content/test/general/browser_bug816527.js
browser/base/content/test/general/browser_tabMatchesInAwesomebar_perwindowpb.js
--- a/browser/app/profile/firefox.js
+++ b/browser/app/profile/firefox.js
@@ -178,17 +178,17 @@ pref("app.update.mode", 1);
 pref("app.update.metro.enabled", true);
 #endif
 #endif
 
 // If set to true, the Update Service will present no UI for any event.
 pref("app.update.silent", false);
 
 // If set to true, the hamburger button will show badges for update events.
-#ifdef MOZ_DEV_EDITION
+#ifndef RELEASE_BUILD
 pref("app.update.badge", true);
 #else
 pref("app.update.badge", false);
 #endif
 
 // If set to true, the Update Service will apply updates in the background
 // when it finishes downloading them.
 pref("app.update.staging.enabled", true);
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -7300,19 +7300,23 @@ let gPrivateBrowsingUI = {
             newWindow.command = newPrivateWindow.command;
           }
         });
       }
     }
 
     if (gURLBar &&
         !PrivateBrowsingUtils.permanentPrivateBrowsing) {
-      // Disable switch to tab autocompletion for private windows 
-      // (not for "Always use private browsing" mode)
-      gURLBar.setAttribute("autocompletesearchparam", "");
+      // Disable switch to tab autocompletion for private windows.
+      // We leave it enabled for permanent private browsing mode though.
+      let value = gURLBar.getAttribute("autocompletesearchparam") || "";
+      if (!value.contains("disable-private-actions")) {
+        gURLBar.setAttribute("autocompletesearchparam",
+                             value + " disable-private-actions");
+      }
     }
   }
 };
 
 let gRemoteTabsUI = {
   init: function() {
     if (window.location.href != getBrowserURL() &&
         // Also check hidden window for the Mac no-window case
--- a/browser/base/content/nsContextMenu.js
+++ b/browser/base/content/nsContextMenu.js
@@ -1768,41 +1768,28 @@ nsContextMenu.prototype = {
         this._telemetryClickID = (e.target.id || "unknown").replace(/^context-/i, "");
       }
     };
     aXulMenu.ownerDocument.addEventListener("command", activationHandler, true);
     aXulMenu.addEventListener("popuphiding", this._onPopupHiding, true);
   },
 
   _getTelemetryPageContextInfo: function() {
-    if (this.isContentSelected) {
-      return "selection";
-    }
-    if (this.onLink) {
-      if (this.onImage || this.onCanvas) {
-        return "image-link";
+    let rv = [];
+    for (let k of ["isContentSelected", "onLink", "onImage", "onCanvas", "onVideo", "onAudio",
+                   "onTextInput", "onSocial"]) {
+      if (this[k]) {
+        rv.push(k.replace(/^(?:is|on)(.)/, (match, firstLetter) => firstLetter.toLowerCase()));
       }
-      return "link";
-    }
-    if (this.onImage) {
-      return "image"
     }
-    if (this.onCanvas) {
-      return "canvas";
-    }
-    if (this.onVideo || this.onAudio) {
-      return "media";
+    if (!rv.length) {
+      rv.push('other');
     }
-    if (this.onTextInput) {
-      return "input";
-    }
-    if (this.onSocial) {
-      return "social";
-    }
-    return "other";
+
+    return JSON.stringify(rv);
   },
 
   _checkTelemetryForMenu: function(aXulMenu) {
     this._telemetryClickID = null;
     this._telemetryPageContext = this._getTelemetryPageContextInfo();
     this._telemetryHadCustomItems = this.hasPageMenu;
     this._getTelemetryClickInfo(aXulMenu);
   },
--- a/browser/base/content/test/general/browser.ini
+++ b/browser/base/content/test/general/browser.ini
@@ -271,18 +271,16 @@ skip-if = e10s # Bug 1093155 - tries to 
 [browser_bug735471.js]
 [browser_bug749738.js]
 skip-if = e10s # Bug 921935 - focusmanager issues with e10s
 [browser_bug763468_perwindowpb.js]
 skip-if = e10s
 [browser_bug767836_perwindowpb.js]
 [browser_bug771331.js]
 [browser_bug783614.js]
-[browser_bug816527.js]
-skip-if = e10s # Bug 1093373 - relies on browser.sessionHistory
 [browser_bug817947.js]
 [browser_bug822367.js]
 [browser_bug832435.js]
 [browser_bug839103.js]
 [browser_bug880101.js]
 [browser_bug882977.js]
 [browser_bug902156.js]
 [browser_bug906190.js]
@@ -407,18 +405,20 @@ skip-if = e10s
 support-files =
   searchSuggestionUI.html
   searchSuggestionUI.js
 [browser_selectTabAtIndex.js]
 [browser_star_hsts.js]
 [browser_subframe_favicons_not_used.js]
 [browser_tabDrop.js]
 skip-if = buildapp == 'mulet' || e10s
+[browser_tabMatchesInAwesomebar.js]
+skip-if = e10s # Bug 1093206 - need to re-enable tests relying on swapFrameLoaders et al for e10s (test calls gBrowser.swapBrowsersAndCloseOther)
 [browser_tabMatchesInAwesomebar_perwindowpb.js]
-skip-if = e10s # Bug 1093206 - need to re-enable tests relying on swapFrameLoaders et al for e10s (test calls gBrowser.swapBrowsersAndCloseOther)
+skip-if = e10s # Bug 1093373 - relies on browser.sessionHistory
 [browser_tab_drag_drop_perwindow.js]
 skip-if = buildapp == 'mulet'
 [browser_tab_dragdrop.js]
 skip-if = buildapp == 'mulet' || e10s # Bug 1093206 - need to re-enable tests relying on swapFrameLoaders et al for e10s (test calls gBrowser.swapBrowsersAndCloseOther)
 [browser_tab_dragdrop2.js]
 skip-if = buildapp == 'mulet' || e10s # Bug 1093206 - need to re-enable tests relying on swapFrameLoaders et al for e10s (test calls gBrowser.swapBrowsersAndCloseOther)
 [browser_tabbar_big_widgets.js]
 skip-if = os == "linux" || os == "mac" # No tabs in titlebar on linux
deleted file mode 100644
--- a/browser/base/content/test/general/browser_bug816527.js
+++ /dev/null
@@ -1,122 +0,0 @@
-/* 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/. */
-
-function test() {
-  waitForExplicitFinish();
-
-  let testURL = "http://example.org/browser/browser/base/content/test/general/dummy_page.html";
-
-  function testOnWindow(aOptions, aCallback) {
-    whenNewWindowLoaded(aOptions, function(aWin) {
-      // execute should only be called when need, like when you are opening
-      // web pages on the test. If calling executeSoon() is not necesary, then
-      // call whenNewWindowLoaded() instead of testOnWindow() on your test.
-      executeSoon(function() aCallback(aWin));
-    });
-  };
-
-  testOnWindow({}, function(aNormalWindow) {
-    testOnWindow({private: true}, function(aPrivateWindow) {
-      runTest(aNormalWindow, aPrivateWindow, false, function() {
-        aNormalWindow.close();
-        aPrivateWindow.close();
-        testOnWindow({}, function(aNormalWindow) {
-          testOnWindow({private: true}, function(aPrivateWindow) {
-            runTest(aPrivateWindow, aNormalWindow, false, function() {
-              aNormalWindow.close();
-              aPrivateWindow.close();
-              testOnWindow({private: true}, function(aPrivateWindow) {
-                runTest(aPrivateWindow, aPrivateWindow, false, function() {
-                  aPrivateWindow.close();
-                  testOnWindow({}, function(aNormalWindow) {
-                    runTest(aNormalWindow, aNormalWindow, true, function() {
-                      aNormalWindow.close();
-                      finish();
-                    });
-                  });
-                });
-              });
-            });
-          });
-        });
-      });
-    });
-  });
-
-  function runTest(aSourceWindow, aDestWindow, aExpectSuccess, aCallback) {
-    // Open the base tab
-    let baseTab = aSourceWindow.gBrowser.addTab(testURL);
-    baseTab.linkedBrowser.addEventListener("load", function() {
-      // Wait for the tab to be fully loaded so matching happens correctly
-      if (baseTab.linkedBrowser.currentURI.spec == "about:blank")
-        return;
-      baseTab.linkedBrowser.removeEventListener("load", arguments.callee, true);
-
-      let testTab = aDestWindow.gBrowser.addTab();
-
-      waitForFocus(function() {
-        // Select the testTab
-        aDestWindow.gBrowser.selectedTab = testTab;
-
-        // Ensure that this tab has no history entries
-        ok(testTab.linkedBrowser.sessionHistory.count < 2,
-           "The test tab has 1 or less history entries");
-        // Ensure that this tab is on about:blank
-        is(testTab.linkedBrowser.currentURI.spec, "about:blank",
-           "The test tab is on about:blank");
-        // Ensure that this tab's document has no child nodes
-        ok(!testTab.linkedBrowser.contentDocument.body.hasChildNodes(),
-           "The test tab has no child nodes");
-        ok(!testTab.hasAttribute("busy"),
-           "The test tab doesn't have the busy attribute");
-
-        // Set the urlbar to include the moz-action
-        aDestWindow.gURLBar.value = "moz-action:switchtab," + JSON.stringify({url: testURL});
-        // Focus the urlbar so we can press enter
-        aDestWindow.gURLBar.focus();
-
-        // We want to see if the switchtab action works.  If it does, the
-        // current tab will get closed, and that's what we detect with the
-        // TabClose handler.  If pressing enter triggers a load in that tab,
-        // then the load handler will get called.  Neither of these are
-        // the desired effect here.  So if the test goes successfully, it is
-        // the timeout handler which gets called.
-        //
-        // The reason that we can't avoid the timeout here is because we are
-        // trying to test something which should not happen, so we just need
-        // to wait for a while and then check whether any bad things have
-        // happened.
-
-        function onTabClose(aEvent) {
-          aDestWindow.gBrowser.tabContainer.removeEventListener("TabClose", onTabClose, false);
-          aDestWindow.gBrowser.removeEventListener("load", onLoad, false);
-          clearTimeout(timeout);
-          // Should only happen when we expect success
-          ok(aExpectSuccess, "Tab closed as expected");
-          aCallback();
-        }
-        function onLoad(aEvent) {
-          aDestWindow.gBrowser.tabContainer.removeEventListener("TabClose", onTabClose, false);
-          aDestWindow.gBrowser.removeEventListener("load", onLoad, false);
-          clearTimeout(timeout);
-          // Should only happen when we expect success
-          ok(aExpectSuccess, "Tab loaded as expected");
-          aCallback();
-        }
-
-        aDestWindow.gBrowser.tabContainer.addEventListener("TabClose", onTabClose, false);
-        aDestWindow.gBrowser.addEventListener("load", onLoad, false);
-        let timeout = setTimeout(function() {
-          aDestWindow.gBrowser.tabContainer.removeEventListener("TabClose", onTabClose, false);
-          aDestWindow.gBrowser.removeEventListener("load", onLoad, false);
-          aCallback();
-        }, 500);
-
-        // Press enter!
-        EventUtils.synthesizeKey("VK_RETURN", {});
-      }, aDestWindow);
-    }, true);
-  }
-}
-
copy from browser/base/content/test/general/browser_tabMatchesInAwesomebar_perwindowpb.js
copy to browser/base/content/test/general/browser_tabMatchesInAwesomebar.js
--- a/browser/base/content/test/general/browser_tabMatchesInAwesomebar_perwindowpb.js
+++ b/browser/base/content/test/general/browser_tabMatchesInAwesomebar_perwindowpb.js
@@ -1,249 +1,122 @@
-/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*-
- * vim:set ts=2 sw=2 sts=2 et:
- * This Source Code Form is subject to the terms of the Mozilla Public
+/* 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/. */
 
-const TEST_URL_BASES = [
-  "http://example.org/browser/browser/base/content/test/general/dummy_page.html#tabmatch",
-  "http://example.org/browser/browser/base/content/test/general/moz.png#tabmatch"
-];
-
-var gController = Cc["@mozilla.org/autocomplete/controller;1"].
-                  getService(Ci.nsIAutoCompleteController);
-
-var gTabWaitCount = 0;
-var gTabCounter = 0;
-
-var gTestSteps = [
-  function() {
-    info("Running step 1");
-    for (let i = 0; i < 10; i++) {
-      let tab = gBrowser.addTab();
-      loadTab(tab, TEST_URL_BASES[0] + (++gTabCounter));
-    }
-  },
-  function() {
-    info("Running step 2");
-    gBrowser.selectTabAtIndex(1);
-    gBrowser.removeCurrentTab();
-    gBrowser.selectTabAtIndex(1);
-    gBrowser.removeCurrentTab();
-    for (let i = 1; i < gBrowser.tabs.length; i++)
-      loadTab(gBrowser.tabs[i], TEST_URL_BASES[1] + (++gTabCounter));
-  },
-  function() {
-    info("Running step 3");
-    for (let i = 1; i < gBrowser.tabs.length; i++)
-      loadTab(gBrowser.tabs[i], TEST_URL_BASES[0] + gTabCounter);
-  },
-  function() {
-    info("Running step 4 - ensure we don't register subframes as open pages");
-    let tab = gBrowser.addTab();
-    tab.linkedBrowser.addEventListener("load", function () {
-      tab.linkedBrowser.removeEventListener("load", arguments.callee, true);
-      // Start the sub-document load.
-      executeSoon(function () {
-        tab.linkedBrowser.addEventListener("load", function (e) {
-          tab.linkedBrowser.removeEventListener("load", arguments.callee, true);
-            ensure_opentabs_match_db(nextStep);
-        }, true);
-        tab.linkedBrowser.contentDocument.querySelector("iframe").src = "http://test2.example.org/";
-      });
-    }, true);
-    tab.linkedBrowser.loadURI('data:text/html,<body><iframe src=""></iframe></body>');
-  },
-  function() {
-    info("Running step 5 - remove tab immediately");
-    let tab = gBrowser.addTab("about:logo");
-    gBrowser.removeTab(tab);
-    ensure_opentabs_match_db(nextStep);
-  },
-  function() {
-    info("Running step 6 - check swapBrowsersAndCloseOther preserves registered switch-to-tab result");
-    let tabToKeep = gBrowser.addTab();
-    let tab = gBrowser.addTab();
-    tab.linkedBrowser.addEventListener("load", function () {
-      tab.linkedBrowser.removeEventListener("load", arguments.callee, true);
-      gBrowser.swapBrowsersAndCloseOther(tabToKeep, tab);
-      ensure_opentabs_match_db(function () {
-        gBrowser.removeTab(tabToKeep);
-        ensure_opentabs_match_db(nextStep);
-      });
-    }, true);
-    tab.linkedBrowser.loadURI("about:mozilla");
-  },
-  function() {
-    info("Running step 7 - close all tabs");
-
-    Services.prefs.clearUserPref("browser.sessionstore.restore_on_demand");
-
-    gBrowser.addTab("about:blank", {skipAnimation: true});
-    while (gBrowser.tabs.length > 1) {
-      info("Removing tab: " + gBrowser.tabs[0].linkedBrowser.currentURI.spec);
-      gBrowser.selectTabAtIndex(0);
-      gBrowser.removeCurrentTab();
-    }
-    ensure_opentabs_match_db(nextStep);
-  }
-];
-
-
-
 function test() {
   waitForExplicitFinish();
-  nextStep();
-}
 
-function loadTab(tab, url) {
-  // Because adding visits is async, we will not be notified immediately.
-  let visited = false;
-  let loaded = false;
-
-  function maybeCheckResults() {
-    if (visited && loaded && --gTabWaitCount == 0) {
-      ensure_opentabs_match_db(nextStep);
-    }
-  }
-
-  tab.linkedBrowser.addEventListener("load", function () {
-    tab.linkedBrowser.removeEventListener("load", arguments.callee, true);
-    loaded = true;
-    maybeCheckResults();
-  }, true);
-
-  if (!visited) {
-    Services.obs.addObserver(
-      function (aSubject, aTopic, aData) {
-        if (url != aSubject.QueryInterface(Ci.nsIURI).spec)
-          return;
-        Services.obs.removeObserver(arguments.callee, aTopic);
-        visited = true;
-        maybeCheckResults();
-      },
-      "uri-visit-saved",
-      false
-    );
-  }
-
-  gTabWaitCount++;
-  info("Loading page: " + url);
-  tab.linkedBrowser.loadURI(url);
-}
-
-function waitForRestoredTab(tab) {
-  gTabWaitCount++;
-
-  tab.linkedBrowser.addEventListener("load", function () {
-    tab.linkedBrowser.removeEventListener("load", arguments.callee, true);
-    if (--gTabWaitCount == 0) {
-      ensure_opentabs_match_db(nextStep);
-    }
-  }, true);
-}
-
-
-function nextStep() {
-  if (gTestSteps.length == 0) {
-    while (gBrowser.tabs.length > 1) {
-      gBrowser.selectTabAtIndex(1);
-      gBrowser.removeCurrentTab();
-    }
-
-    waitForClearHistory(finish);
-
-    return;
-  }
-
-  var stepFunc = gTestSteps.shift();
-  stepFunc();
-}
-
-function ensure_opentabs_match_db(aCallback) {
-  var tabs = {};
+  let testURL = "http://example.org/browser/browser/base/content/test/general/dummy_page.html";
 
-  var winEnum = Services.wm.getEnumerator("navigator:browser");
-  while (winEnum.hasMoreElements()) {
-    let browserWin = winEnum.getNext();
-    // skip closed-but-not-destroyed windows
-    if (browserWin.closed)
-      continue;
-
-    for (let i = 0; i < browserWin.gBrowser.tabContainer.childElementCount; i++) {
-      let browser = browserWin.gBrowser.getBrowserAtIndex(i);
-      let url = browser.currentURI.spec;
-      if (browserWin.isBlankPageURL(url))
-        continue;
-      if (!(url in tabs))
-        tabs[url] = 1;
-      else
-        tabs[url]++;
-    }
-  }
-
-  checkAutocompleteResults(tabs, aCallback);
-}
-
-/**
- * Clears history invoking callback when done.
- */
-function waitForClearHistory(aCallback) {
-  const TOPIC_EXPIRATION_FINISHED = "places-expiration-finished";
-  let observer = {
-    observe: function(aSubject, aTopic, aData) {
-      Services.obs.removeObserver(this, TOPIC_EXPIRATION_FINISHED);
-      aCallback();
-    }
-  };
-  Services.obs.addObserver(observer, TOPIC_EXPIRATION_FINISHED, false);
-
-  PlacesUtils.bhistory.removeAllPages();
-}
-
-function checkAutocompleteResults(aExpected, aCallback)
-{
-  gController.input = {
-    timeout: 10,
-    textValue: "",
-    searches: ["history"],
-    searchParam: "enable-actions",
-    popupOpen: false,
-    minResultsForPopup: 0,
-    invalidate: function() {},
-    disableAutoComplete: false,
-    completeDefaultIndex: false,
-    get popup() { return this; },
-    onSearchBegin: function() {},
-    onSearchComplete:  function ()
-    {
-      info("Found " + gController.matchCount + " matches.");
-      // Check to see the expected uris and titles match up (in any order)
-      for (let i = 0; i < gController.matchCount; i++) {
-        let uri = gController.getValueAt(i).replace(/^moz-action:[^,]+,/i, "");
-
-        info("Search for '" + uri + "' in open tabs.");
-        let expected = uri in aExpected;
-        ok(expected, uri + " was found in autocomplete, was " + (expected ? "" : "not ") + "expected");
-        // Remove the found entry from expected results.
-        delete aExpected[uri];
-      }
-
-      // Make sure there is no reported open page that is not open.
-      for (let entry in aExpected) {
-        ok(false, "'" + entry + "' should be found in autocomplete");
-      }
-
-      executeSoon(aCallback);
-    },
-    setSelectedIndex: function() {},
-    get searchCount() { return this.searches.length; },
-    getSearchAt: function(aIndex) this.searches[aIndex],
-    QueryInterface: XPCOMUtils.generateQI([
-      Ci.nsIAutoCompleteInput,
-      Ci.nsIAutoCompletePopup,
-    ])
+  function testOnWindow(aOptions, aCallback) {
+    whenNewWindowLoaded(aOptions, function(aWin) {
+      // execute should only be called when need, like when you are opening
+      // web pages on the test. If calling executeSoon() is not necesary, then
+      // call whenNewWindowLoaded() instead of testOnWindow() on your test.
+      executeSoon(function() aCallback(aWin));
+    });
   };
 
-  info("Searching open pages.");
-  gController.startSearch(Services.prefs.getCharPref("browser.urlbar.restrict.openpage"));
+  testOnWindow({}, function(aNormalWindow) {
+    testOnWindow({private: true}, function(aPrivateWindow) {
+      runTest(aNormalWindow, aPrivateWindow, false, function() {
+        aNormalWindow.close();
+        aPrivateWindow.close();
+        testOnWindow({}, function(aNormalWindow) {
+          testOnWindow({private: true}, function(aPrivateWindow) {
+            runTest(aPrivateWindow, aNormalWindow, false, function() {
+              aNormalWindow.close();
+              aPrivateWindow.close();
+              testOnWindow({private: true}, function(aPrivateWindow) {
+                runTest(aPrivateWindow, aPrivateWindow, false, function() {
+                  aPrivateWindow.close();
+                  testOnWindow({}, function(aNormalWindow) {
+                    runTest(aNormalWindow, aNormalWindow, true, function() {
+                      aNormalWindow.close();
+                      finish();
+                    });
+                  });
+                });
+              });
+            });
+          });
+        });
+      });
+    });
+  });
+
+  function runTest(aSourceWindow, aDestWindow, aExpectSwitch, aCallback) {
+    // Open the base tab
+    let baseTab = aSourceWindow.gBrowser.addTab(testURL);
+    baseTab.linkedBrowser.addEventListener("load", function() {
+      // Wait for the tab to be fully loaded so matching happens correctly
+      if (baseTab.linkedBrowser.currentURI.spec == "about:blank")
+        return;
+      baseTab.linkedBrowser.removeEventListener("load", arguments.callee, true);
+
+      let testTab = aDestWindow.gBrowser.addTab();
+
+      waitForFocus(function() {
+        // Select the testTab
+        aDestWindow.gBrowser.selectedTab = testTab;
+
+        // Ensure that this tab has no history entries
+        ok(testTab.linkedBrowser.sessionHistory.count < 2,
+           "The test tab has 1 or less history entries");
+        // Ensure that this tab is on about:blank
+        is(testTab.linkedBrowser.currentURI.spec, "about:blank",
+           "The test tab is on about:blank");
+        // Ensure that this tab's document has no child nodes
+        ok(!testTab.linkedBrowser.contentDocument.body.hasChildNodes(),
+           "The test tab has no child nodes");
+        ok(!testTab.hasAttribute("busy"),
+           "The test tab doesn't have the busy attribute");
+
+        let urlbar = aDestWindow.gURLBar;
+        let controller = urlbar.controller;
+
+        // Focus URL bar, enter value, and start searching.
+        urlbar.focus();
+        urlbar.value = testURL;
+        controller.startSearch(testURL);
+
+        // Wait for the Awesomebar popup to appear.
+        promisePopupShown(aDestWindow.gURLBar.popup).then(() => {
+          function searchIsComplete() {
+            return controller.searchStatus ==
+              Ci.nsIAutoCompleteController.STATUS_COMPLETE_MATCH;
+          }
+
+          // Wait until the search is complete.
+          waitForCondition(searchIsComplete, function () {
+            if (aExpectSwitch) {
+              // If we expect a tab switch then the current tab
+              // will be closed and we switch to the other tab.
+              let tabContainer = aDestWindow.gBrowser.tabContainer;
+              tabContainer.addEventListener("TabClose", function onClose(event) {
+                if (event.target == testTab) {
+                  tabContainer.removeEventListener("TabClose", onClose);
+                  executeSoon(aCallback);
+                }
+              });
+            } else {
+              // If we don't expect a tab switch then wait for the tab to load.
+              testTab.addEventListener("load", function onLoad() {
+                testTab.removeEventListener("load", onLoad, true);
+                executeSoon(aCallback);
+              }, true);
+            }
+
+            // Select the second match, if any.
+            if (controller.matchCount > 1) {
+              controller.handleKeyNavigation(KeyEvent.DOM_VK_DOWN);
+            }
+
+            // Execute the selected action.
+            controller.handleEnter(true);
+          });
+        });
+
+      }, aDestWindow);
+    }, true);
+  }
 }
--- a/browser/components/loop/MozLoopService.jsm
+++ b/browser/components/loop/MozLoopService.jsm
@@ -323,18 +323,18 @@ let MozLoopServiceInternal = {
    * This should only be called from promiseRegisteredWithServers to prevent reentrancy.
    *
    * @param {LOOP_SESSION_TYPE} sessionType
    * @return {Promise} resolves with all push endpoints
    *                   rejects if any of the push registrations failed
    */
   promiseRegisteredWithPushServer: function(sessionType) {
     if (!this.deferredRegistrations.has(sessionType)) {
-      return Promise.reject("promiseRegisteredWithPushServer must be called while there is a " +
-                            "deferred in deferredRegistrations in order to prevent reentrancy");
+      return Promise.reject(new Error("promiseRegisteredWithPushServer must be called while there is a " +
+                            "deferred in deferredRegistrations in order to prevent reentrancy"));
     }
     // Wrap push notification registration call-back in a Promise.
     function registerForNotification(channelID, onNotification) {
       log.debug("registerForNotification", channelID);
       return new Promise((resolve, reject) => {
         function onRegistered(error, pushUrl) {
           log.debug("registerForNotification onRegistered:", error, pushUrl);
           if (error) {
@@ -370,17 +370,17 @@ let MozLoopServiceInternal = {
       let callsRegFxA = registerForNotification(MozLoopService.channelIDs.callsFxA,
                                                 LoopCalls.onNotification);
 
       let roomsRegFxA = registerForNotification(MozLoopService.channelIDs.roomsFxA,
                                                 roomsPushNotification);
       return Promise.all([callsRegFxA, roomsRegFxA]);
     }
 
-    return Promise.reject("promiseRegisteredWithPushServer: Invalid sessionType");
+    return Promise.reject(new Error("promiseRegisteredWithPushServer: Invalid sessionType"));
   },
 
   /**
    * Starts registration of Loop with the push server, and then will register
    * with the Loop server. It will return early if already registered.
    *
    * @param {LOOP_SESSION_TYPE} sessionType
    * @returns {Promise} a promise that is resolved with no params on completion, or
@@ -588,33 +588,33 @@ let MozLoopServiceInternal = {
       callsPushURL = this.pushHandler.registeredChannels[MozLoopService.channelIDs.callsFxA];
       roomsPushURL = this.pushHandler.registeredChannels[MozLoopService.channelIDs.roomsFxA];
     } else if (sessionType == LOOP_SESSION_TYPE.GUEST) {
       callsPushURL = this.pushHandler.registeredChannels[MozLoopService.channelIDs.callsGuest];
       roomsPushURL = this.pushHandler.registeredChannels[MozLoopService.channelIDs.roomsGuest];
     }
 
     if (!callsPushURL || !roomsPushURL) {
-      return Promise.reject("Invalid sessionType or missing push URLs for registerWithLoopServer: " + sessionType);
+      return Promise.reject(new Error("Invalid sessionType or missing push URLs for registerWithLoopServer: " + sessionType));
     }
 
     // create a registration payload with a backwards compatible attribute (simplePushURL)
     // that will register only the calls notification.
     let msg = {
         simplePushURL: callsPushURL,
         simplePushURLs: {
           calls: callsPushURL,
           rooms: roomsPushURL,
         },
     };
     return this.hawkRequestInternal(sessionType, "/registration", "POST", msg)
       .then((response) => {
         // If this failed we got an invalid token.
         if (!this.storeSessionToken(sessionType, response.headers)) {
-          return Promise.reject("session-token-wrong-size");
+          return Promise.reject(new Error("session-token-wrong-size"));
         }
 
         log.debug("Successfully registered with server for sessionType", sessionType);
         this.clearError("registration");
         return undefined;
       }, (error) => {
         // There's other errors than invalid auth token, but we should only do the reset
         // as a last resort.
@@ -1008,17 +1008,17 @@ this.MozLoopService = {
     // should be around Fx 39.
     Services.prefs.clearUserPref("loop.throttled");
     Services.prefs.clearUserPref("loop.throttled2");
     Services.prefs.clearUserPref("loop.soft_start_ticket_number");
     Services.prefs.clearUserPref("loop.soft_start_hostname");
 
     // Don't do anything if loop is not enabled.
     if (!Services.prefs.getBoolPref("loop.enabled")) {
-      return Promise.reject("loop is not enabled");
+      return Promise.reject(new Error("loop is not enabled"));
     }
 
     if (Services.prefs.getPrefType("loop.fxa.enabled") == Services.prefs.PREF_BOOL) {
       gFxAEnabled = Services.prefs.getBoolPref("loop.fxa.enabled");
       if (!gFxAEnabled) {
         yield this.logOutFromFxA();
       }
     }
--- a/browser/components/loop/content/shared/js/actions.js
+++ b/browser/components/loop/content/shared/js/actions.js
@@ -220,17 +220,20 @@ loop.shared.actions = (function() {
     GetAllRooms: Action.define("getAllRooms", {
     }),
 
     /**
      * An error occured while trying to fetch the room list.
      * XXX: should move to some roomActions module - refs bug 1079284
      */
     GetAllRoomsError: Action.define("getAllRoomsError", {
-      error: Error
+      // There's two types of error possible - one thrown by our code (and Error)
+      // and the other is an Object about the error codes from the server as
+      // returned by the Hawk request.
+      error: [Error, Object]
     }),
 
     /**
      * Updates room list.
      * XXX: should move to some roomActions module - refs bug 1079284
      */
     UpdateRoomList: Action.define("updateRoomList", {
       roomList: Array
--- a/browser/components/loop/test/mochitest/head.js
+++ b/browser/components/loop/test/mochitest/head.js
@@ -72,21 +72,16 @@ function promiseGetMozLoopAPI() {
 
 /**
  * Loads the loop panel by clicking the button and waits for its open to complete.
  * It also registers
  *
  * This assumes that the tests are running in a generatorTest.
  */
 function loadLoopPanel(aOverrideOptions = {}) {
-  Services.prefs.setBoolPref("loop.rooms.enabled", false);
-  registerCleanupFunction(function() {
-     Services.prefs.clearUserPref("loop.rooms.enabled");
-  });
-
   // Set prefs to ensure we don't access the network externally.
   Services.prefs.setCharPref("services.push.serverURL", aOverrideOptions.pushURL || "ws://localhost/");
   Services.prefs.setCharPref("loop.server", aOverrideOptions.loopURL || "http://localhost/");
 
   // Turn off the network for loop tests, so that we don't
   // try to access the remote servers. If we want to turn this
   // back on in future, be careful to check for intermittent
   // failures.
--- a/browser/components/loop/test/xpcshell/test_loopservice_token_validation.js
+++ b/browser/components/loop/test/xpcshell/test_loopservice_token_validation.js
@@ -15,17 +15,17 @@ add_test(function test_registration_hand
     response.processAsync();
     response.finish();
   });
 
   MozLoopService.promiseRegisteredWithServers().then(() => {
     do_throw("should not succeed with a bogus token");
   }, err => {
 
-    Assert.equal(err, "session-token-wrong-size", "Should cause an error to be" +
+    Assert.equal(err.message, "session-token-wrong-size", "Should cause an error to be" +
       " called back if the session-token is not 64 characters long");
 
     // for some reason, Assert.throw is misbehaving, so....
     var ex;
     try {
       Services.prefs.getCharPref("loop.hawk-session-token");
     } catch (e) {
       ex = e;
--- a/browser/devtools/styleinspector/computed-view.js
+++ b/browser/devtools/styleinspector/computed-view.js
@@ -1007,31 +1007,31 @@ PropertyView.prototype = {
   /**
    * Returns the className that should be assigned to the propertyView.
    * @return string
    */
   get propertyHeaderClassName()
   {
     if (this.visible) {
       let isDark = this.tree._darkStripe = !this.tree._darkStripe;
-      return isDark ? "property-view theme-bg-darker" : "property-view";
+      return isDark ? "property-view row-striped" : "property-view";
     }
     return "property-view-hidden";
   },
 
   /**
    * Returns the className that should be assigned to the propertyView content
    * container.
    * @return string
    */
   get propertyContentClassName()
   {
     if (this.visible) {
       let isDark = this.tree._darkStripe;
-      return isDark ? "property-content theme-bg-darker" : "property-content";
+      return isDark ? "property-content row-striped" : "property-content";
     }
     return "property-content-hidden";
   },
 
   /**
    * Build the markup for on computed style
    * @return Element
    */
--- a/browser/themes/shared/devtools/computedview.css
+++ b/browser/themes/shared/devtools/computedview.css
@@ -22,16 +22,20 @@ body {
 
 #propertyContainer {
   -moz-user-select: text;
   overflow: auto;
   min-height: 0;
   flex: 1;
 }
 
+.row-striped {
+  background: var(--theme-body-background);
+}
+
 .property-view-hidden,
 .property-content-hidden {
   display: none;
 }
 
 .property-view {
   clear: both;
   padding: 2px 0 2px 17px;
--- a/browser/themes/shared/devtools/dark-theme.css
+++ b/browser/themes/shared/devtools/dark-theme.css
@@ -10,16 +10,17 @@
   --theme-body-background: #14171a;
   --theme-sidebar-background: #181d20;
   --theme-contrast-background: #b28025;
 
   --theme-tab-toolbar-background: #252c33;
   --theme-toolbar-background: #343c45;
   --theme-selection-background: #1d4f73;
   --theme-selection-color: #f5f7fa;
+  --theme-selection-background-semitransparent: rgba(29, 79, 115, .5);
   --theme-splitter-color: black;
   --theme-comment: #5c6773;
 
   --theme-body-color: #a9bacb;
   --theme-body-color-alt: #b6babf;
   --theme-content-color1: #a9bacb;
   --theme-content-color2: #8fa1b2;
   --theme-content-color3: #667380;
@@ -45,17 +46,17 @@
 }
 
 ::-moz-selection {
   background-color: var(--theme-selection-background);
   color: var(--theme-selection-color);
 }
 
 .theme-bg-darker {
-  background-color: rgba(0,0,0,0.5);
+  background-color: var(--theme-selection-background-semitransparent);
 }
 
 .theme-selected,
 .CodeMirror-hint-active {
   background-color: var(--theme-selection-background);
   color: var(--theme-selection-color);
 }
 
--- a/browser/themes/shared/devtools/light-theme.css
+++ b/browser/themes/shared/devtools/light-theme.css
@@ -10,16 +10,17 @@
   --theme-body-background: #fcfcfc;
   --theme-sidebar-background: #f7f7f7;
   --theme-contrast-background: #e6b064;
 
   --theme-tab-toolbar-background: #ebeced;
   --theme-toolbar-background: #f0f1f2;
   --theme-selection-background: #4c9ed9;
   --theme-selection-color: #f5f7fa;
+  --theme-selection-background-semitransparent: rgba(76, 158, 217, .23);
   --theme-splitter-color: #aaaaaa;
   --theme-comment: hsl(90,2%,46%);
 
   --theme-body-color: #18191a;
   --theme-body-color-alt: #585959;
   --theme-content-color1: #292e33;
   --theme-content-color2: #8fa1b2;
   --theme-content-color3: #667380;
@@ -45,17 +46,17 @@
 }
 
 ::-moz-selection {
   background-color: var(--theme-selection-background);
   color: var(--theme-selection-color);
 }
 
 .theme-bg-darker {
-  background: #EFEFEF;
+  background: var(--theme-selection-background-semitransparent);
 }
 
 .theme-selected,
 .CodeMirror-hint-active {
   background-color: var(--theme-selection-background);
   color: var(--theme-selection-color);
 }
 
--- a/browser/themes/shared/devtools/webconsole.inc.css
+++ b/browser/themes/shared/devtools/webconsole.inc.css
@@ -194,22 +194,18 @@ a {
 }
 
 /* Network styles */
 .webconsole-filter-button[category="net"] > .toolbarbutton-menubutton-button:before {
   background-image: linear-gradient(#444444, #000000);
   border-color: #777;
 }
 
-.theme-light .message:hover {
-  background-color: rgba(76, 158, 217, 0.23) !important;
-}
-
-.theme-dark .message:hover {
-  background-color: rgba(29, 79, 115, 0.5) !important;
+.message:hover {
+  background-color: var(--theme-selection-background-semitransparent) !important;
 }
 
 .theme-light .message[severity=error] {
   background-color: rgba(255, 150, 150, 0.3);
 }
 
 .theme-dark .message[severity=error] {
   background-color: rgba(235, 83, 104, 0.17);
--- a/build/mach_bootstrap.py
+++ b/build/mach_bootstrap.py
@@ -83,16 +83,17 @@ MACH_MODULES = [
     'testing/mochitest/mach_commands.py',
     'testing/xpcshell/mach_commands.py',
     'testing/talos/mach_commands.py',
     'testing/web-platform/mach_commands.py',
     'testing/xpcshell/mach_commands.py',
     'tools/docs/mach_commands.py',
     'tools/mercurial/mach_commands.py',
     'tools/mach_commands.py',
+    'mobile/android/mach_commands.py',
 ]
 
 
 CATEGORIES = {
     'build': {
         'short': 'Build Commands',
         'long': 'Interact with the build system',
         'priority': 80,
--- a/mobile/android/base/docs/gradle.rst
+++ b/mobile/android/base/docs/gradle.rst
@@ -5,18 +5,17 @@
 ======================
 
 Instructions
 ============
 
 .. code-block:: shell
 
   ./mach build && ./mach package
-  cd $OBJDIR/mobile/android
-  ./gradlew build
+  ./mach gradle build
 
 The debug APK will be at
 ``$OBJDIR/mobile/android/gradle/app/build/outputs/apk/app-debug.apk``.
 
 The ``$OBJDIR/mobile/android/gradle`` directory can be imported into IntelliJ as
 follows:
 
 - File > Import Project
--- a/mobile/android/base/newtablet/res/layout-large-v11/new_tablet_browser_toolbar.xml
+++ b/mobile/android/base/newtablet/res/layout-large-v11/new_tablet_browser_toolbar.xml
@@ -57,16 +57,17 @@
                   android:layout_marginLeft="6dp"
                   android:orientation="horizontal"
                   android:layout_toLeftOf="@id/tabs"/>
 
     <org.mozilla.gecko.widget.ThemedImageButton
             android:id="@+id/tabs"
             style="@style/UrlBar.ImageButton.NewTablet"
             android:layout_toLeftOf="@id/menu"
+            android:layout_alignWithParentIfMissing="true"
             android:background="@drawable/new_tablet_action_bar_button"/>
 
     <!-- In a 56x60dp space, centering 24dp image will leave 16x18dp. -->
     <org.mozilla.gecko.toolbar.TabCounter android:id="@+id/tabs_counter"
                         style="@style/UrlBar.ImageButton.TabCount.NewTablet"
                         android:layout_alignLeft="@id/tabs"
                         android:layout_alignRight="@id/tabs"
                         android:layout_alignTop="@id/tabs"
@@ -76,17 +77,16 @@
                         android:layout_marginLeft="16dp"
                         android:layout_marginRight="16dp"
                         android:layout="@layout/new_tablet_tabs_counter"/>
 
     <org.mozilla.gecko.widget.ThemedImageButton
             android:id="@+id/menu"
             style="@style/UrlBar.ImageButton.NewTablet"
             android:layout_alignParentRight="true"
-            android:layout_marginRight="6dp"
             android:contentDescription="@string/menu"
             android:background="@drawable/new_tablet_action_bar_button"
             android:visibility="gone"/>
 
     <org.mozilla.gecko.widget.ThemedImageView
             android:id="@+id/menu_icon"
             style="@style/UrlBar.ImageButton.NewTablet"
             android:layout_alignLeft="@id/menu"
--- a/mobile/android/base/resources/values/dimens.xml
+++ b/mobile/android/base/resources/values/dimens.xml
@@ -28,16 +28,17 @@
     <dimen name="new_tablet_tab_strip_favicon_size">16dp</dimen>
     <dimen name="new_tablet_site_security_height">60dp</dimen>
     <dimen name="new_tablet_site_security_width">34dp</dimen>
     <!-- We primarily use padding (instead of margins) to increase the hit area. -->
     <dimen name="new_tablet_site_security_padding_vertical">21dp</dimen>
     <dimen name="new_tablet_site_security_padding_horizontal">8dp</dimen>
     <dimen name="new_tablet_site_security_right_margin">1dp</dimen>
     <dimen name="new_tablet_browser_toolbar_height">60dp</dimen>
+    <dimen name="new_tablet_browser_toolbar_menu_right_margin">6dp</dimen>
     <dimen name="new_tablet_browser_toolbar_menu_item_width">56dp</dimen>
     <!-- Padding combines with an 18dp image to form the menu item width and height. -->
     <dimen name="new_tablet_browser_toolbar_menu_item_padding_horizontal">19dp</dimen>
     <dimen name="new_tablet_browser_toolbar_menu_item_inset_vertical">5dp</dimen>
     <dimen name="new_tablet_browser_toolbar_menu_item_inset_horizontal">3dp</dimen>
     <dimen name="new_tablet_browser_toolbar_menu_item_corner_radius">5dp</dimen>
     <dimen name="new_tablet_tab_strip_button_inset">5dp</dimen>
     <dimen name="forward_default_offset">-13dip</dimen>
--- a/mobile/android/base/toolbar/BrowserToolbarNewTablet.java
+++ b/mobile/android/base/toolbar/BrowserToolbarNewTablet.java
@@ -5,17 +5,17 @@
 
 package org.mozilla.gecko.toolbar;
 
 import org.mozilla.gecko.R;
 import org.mozilla.gecko.animation.PropertyAnimator;
 import org.mozilla.gecko.animation.ViewHelper;
 
 import android.content.Context;
-import android.graphics.drawable.Drawable;
+import android.content.res.Resources;
 import android.util.AttributeSet;
 
 /**
  * A toolbar implementation for the tablet redesign (bug 1014156).
  * Expected to replace BrowserToolbarTablet once complete.
  */
 class BrowserToolbarNewTablet extends BrowserToolbarTabletBase {
 
@@ -46,16 +46,26 @@ class BrowserToolbarNewTablet extends Br
 
         // TODO: Move this to *TabletBase when old tablet is removed.
         // We don't want users clicking the forward button in transitions, but we don't want it to
         // look disabled to avoid flickering complications (e.g. disabled in editing mode), so undo
         // the work of the super class' constructor.
         setButtonEnabled(forwardButton, true);
 
         updateForwardButtonState(ForwardButtonState.HIDDEN);
+
+        setRightMargin();
+    }
+
+    private void setRightMargin() {
+        // TODO: Remove this hack in favor of resources when old tablet is removed.
+        final Resources res = getContext().getResources();
+        final int rightMargin =
+                res.getDimensionPixelOffset(R.dimen.new_tablet_browser_toolbar_menu_right_margin);
+        setPadding(getPaddingLeft(), getPaddingTop(), rightMargin, getPaddingBottom());
     }
 
     private void updateForwardButtonState(final ForwardButtonState state) {
         forwardButtonState = state;
         forwardButton.setEnabled(forwardButtonState == ForwardButtonState.DISPLAYED);
     }
 
     @Override
new file mode 100644
--- /dev/null
+++ b/mobile/android/mach_commands.py
@@ -0,0 +1,36 @@
+# 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/.
+
+from __future__ import print_function, unicode_literals
+
+import argparse
+import logging
+import mozpack.path as mozpath
+
+from mozbuild.base import (
+    MachCommandBase,
+    MachCommandConditions as conditions,
+)
+
+from mach.decorators import (
+    CommandArgument,
+    CommandProvider,
+    Command,
+)
+
+
+@CommandProvider
+class MachCommands(MachCommandBase):
+    @Command('gradle', category='devenv',
+        description='Run gradle.',
+        conditions=[conditions.is_android])
+    @CommandArgument('args', nargs=argparse.REMAINDER)
+    def gradle(self, args):
+        # Avoid logging the command
+        self.log_manager.terminal_handler.setLevel(logging.CRITICAL)
+
+        return self.run_process(['./gradlew'] + args,
+            pass_thru=True, # Allow user to run gradle interactively.
+            ensure_exit_code=False, # Don't throw on non-zero exit code.
+            cwd=mozpath.join(self.topobjdir, 'mobile', 'android', 'gradle'))
--- a/testing/mochitest/chunkifyTests.js
+++ b/testing/mochitest/chunkifyTests.js
@@ -75,16 +75,18 @@ function chunkifyTests(tests, totalChunk
 }
 
 function skipTests(tests, startTestPattern, endTestPattern) {
   var startIndex = 0, endIndex = tests.length - 1;
   for (var i = 0; i < tests.length; ++i) {
     var test_path;
     if ((tests[i] instanceof Object) && ('test' in tests[i])) {
       test_path = tests[i]['test']['url'];
+    } else if ((tests[i] instanceof Object) && ('url' in tests[i])) {
+      test_path = tests[i]['url'];
     } else {
       test_path = tests[i];
     }
     if (startTestPattern && test_path.endsWith(startTestPattern)) {
       startIndex = i;
     }
 
     if (endTestPattern && test_path.endsWith(endTestPattern)) {
--- a/toolkit/components/addoncompat/RemoteAddonsParent.jsm
+++ b/toolkit/components/addoncompat/RemoteAddonsParent.jsm
@@ -379,17 +379,17 @@ let EventTargetParent = {
         return target;
       } else if (target.localName == "tab") {
         return target.linkedBrowser;
       }
 
       // Check if |target| is somewhere on the patch from the
       // <tabbrowser> up to the root element.
       let window = target.ownerDocument.defaultView;
-      if (target.contains(window.gBrowser)) {
+      if (window && target.contains(window.gBrowser)) {
         return window;
       }
     }
 
     return null;
   },
 
   // When a given event fires in the child, we fire it on the
--- a/toolkit/components/places/UnifiedComplete.js
+++ b/toolkit/components/places/UnifiedComplete.js
@@ -563,17 +563,20 @@ function Search(searchString, searchPara
   this._originalSearchString = searchString;
   this._trimmedOriginalSearchString = searchString.trim();
   this._searchString = fixupSearchText(this._trimmedOriginalSearchString.toLowerCase());
 
   this._matchBehavior = Prefs.matchBehavior;
   // Set the default behavior for this search.
   this._behavior = this._searchString ? Prefs.defaultBehavior
                                       : Prefs.emptySearchDefaultBehavior;
-  this._enableActions = searchParam.split(" ").indexOf("enable-actions") != -1;
+
+  let params = new Set(searchParam.split(" "));
+  this._enableActions = params.has("enable-actions");
+  this._disablePrivateActions = params.has("disable-private-actions");
 
   this._searchTokens =
     this.filterTokens(getUnfilteredSearchTokens(this._searchString));
   // The protocol and the host are lowercased by nsIURI, so it's fine to
   // lowercase the typed prefix, to add it back to the results later.
   this._strippedPrefix = this._trimmedOriginalSearchString.slice(
     0, this._trimmedOriginalSearchString.length - this._searchString.length
   ).toLowerCase();
@@ -625,18 +628,24 @@ Search.prototype = {
   /**
    * Determines if the specified AutoComplete behavior is set.
    *
    * @param aType
    *        The behavior type to test for.
    * @return true if the behavior is set, false otherwise.
    */
   hasBehavior: function (type) {
-    return this._behavior &
-           Ci.mozIPlacesAutoComplete["BEHAVIOR_" + type.toUpperCase()];
+    let behavior = Ci.mozIPlacesAutoComplete["BEHAVIOR_" + type.toUpperCase()];
+
+    if (this._disablePrivateActions &&
+        behavior == Ci.mozIPlacesAutoComplete.BEHAVIOR_OPENPAGE) {
+      return false;
+    }
+
+    return this._behavior & behavior;
   },
 
   /**
    * Used to delay the most complex queries, to save IO while the user is
    * typing.
    */
   _sleepDeferred: null,
   _sleep: function (aTimeMs) {
--- a/toolkit/components/places/nsPlacesAutoComplete.js
+++ b/toolkit/components/places/nsPlacesAutoComplete.js
@@ -493,18 +493,19 @@ nsPlacesAutoComplete.prototype = {
 
     // We want to store the original string with no leading or trailing
     // whitespace for case sensitive searches.
     this._originalSearchString = aSearchString.trim();
 
     this._currentSearchString =
       fixupSearchText(this._originalSearchString.toLowerCase());
 
-    let searchParamParts = aSearchParam.split(" ");
-    this._enableActions = searchParamParts.indexOf("enable-actions") != -1;
+    let params = new Set(aSearchParam.split(" "));
+    this._enableActions = params.has("enable-actions");
+    this._disablePrivateActions = params.has("disable-private-actions");
 
     this._listener = aListener;
     let result = Cc["@mozilla.org/autocomplete/simple-result;1"].
                  createInstance(Ci.nsIAutoCompleteSimpleResult);
     result.setSearchString(aSearchString);
     result.setListener(this);
     this._result = result;
 
@@ -1288,18 +1289,24 @@ nsPlacesAutoComplete.prototype = {
    * Determines if the specified AutoComplete behavior is set.
    *
    * @param aType
    *        The behavior type to test for.
    * @return true if the behavior is set, false otherwise.
    */
   _hasBehavior: function PAC_hasBehavior(aType)
   {
-    return (this._behavior &
-            Ci.mozIPlacesAutoComplete["BEHAVIOR_" + aType.toUpperCase()]);
+    let behavior = Ci.mozIPlacesAutoComplete["BEHAVIOR_" + aType.toUpperCase()];
+
+    if (this._disablePrivateActions &&
+        behavior == Ci.mozIPlacesAutoComplete.BEHAVIOR_OPENPAGE) {
+      return false;
+    }
+
+    return this._behavior & behavior;
   },
 
   /**
    * Enables the desired AutoComplete behavior.
    *
    * @param aType
    *        The behavior type to set.
    */
--- a/toolkit/components/places/tests/unifiedcomplete/test_tabmatches.js
+++ b/toolkit/components/places/tests/unifiedcomplete/test_tabmatches.js
@@ -55,16 +55,33 @@ add_task(function* test_tab_matches() {
   yield check_autocomplete({
     search: "abc",
     searchParam: "enable-actions",
     matches: [ { uri: makeActionURI("searchengine", {engineName: "MozSearch", input: "abc", searchQuery: "abc"}), title: "MozSearch", style: [ "action", "searchengine" ] },
                { uri: makeActionURI("switchtab", {url: "http://abc.com/"}), title: "ABC rocks", style: [ "action", "switchtab" ] },
                { uri: makeActionURI("switchtab", {url: "http://xyz.net/"}), title: "xyz.net - we're better than ABC", style: [ "action", "switchtab" ] } ]
   });
 
+  do_log_info("three results, no tab matches (disable-private-actions)");
+  yield check_autocomplete({
+    search: "abc",
+    searchParam: "enable-actions disable-private-actions",
+    matches: [ { uri: makeActionURI("searchengine", {engineName: "MozSearch", input: "abc", searchQuery: "abc"}), title: "MozSearch", style: [ "action", "searchengine" ] },
+               { uri: uri1, title: "ABC rocks", style: [ "favicon" ] },
+               { uri: uri2, title: "xyz.net - we're better than ABC", style: [ "favicon" ] } ]
+  });
+
+  do_log_info("two results (actions disabled)");
+  yield check_autocomplete({
+    search: "abc",
+    searchParam: "",
+    matches: [ { uri: uri1, title: "ABC rocks", style: [ "favicon" ] },
+               { uri: uri2, title: "xyz.net - we're better than ABC", style: [ "favicon" ] } ]
+  });
+
   do_log_info("three results, no tab matches");
   removeOpenPages(uri1, 1);
   removeOpenPages(uri2, 6);
   yield check_autocomplete({
     search: "abc",
     searchParam: "enable-actions",
     matches: [ { uri: makeActionURI("searchengine", {engineName: "MozSearch", input: "abc", searchQuery: "abc"}), title: "MozSearch", style: [ "action", "searchengine" ] },
                { uri: uri1, title: "ABC rocks", style: [ "favicon" ] },
--- a/toolkit/content/browser-child.js
+++ b/toolkit/content/browser-child.js
@@ -42,16 +42,25 @@ let WebProgressListener = {
                      .createInstance(Ci.nsIWebProgress);
     this._filter.addProgressListener(this, Ci.nsIWebProgress.NOTIFY_ALL);
 
     let webProgress = docShell.QueryInterface(Ci.nsIInterfaceRequestor)
                               .getInterface(Ci.nsIWebProgress);
     webProgress.addProgressListener(this._filter, Ci.nsIWebProgress.NOTIFY_ALL);
   },
 
+  uninit() {
+    let webProgress = docShell.QueryInterface(Ci.nsIInterfaceRequestor)
+                              .getInterface(Ci.nsIWebProgress);
+    webProgress.removeProgressListener(this._filter);
+
+    this._filter.removeProgressListener(this);
+    this._filter = null;
+  },
+
   _requestSpec: function (aRequest, aPropertyName) {
     if (!aRequest || !(aRequest instanceof Ci.nsIChannel))
       return null;
     return aRequest.QueryInterface(Ci.nsIChannel)[aPropertyName].spec;
   },
 
   _setupJSON: function setupJSON(aWebProgress, aRequest) {
     if (aWebProgress) {
@@ -149,16 +158,19 @@ let WebProgressListener = {
         return this;
     }
 
     throw Components.results.NS_ERROR_NO_INTERFACE;
   }
 };
 
 WebProgressListener.init();
+addEventListener("unload", () => {
+  WebProgressListener.uninit();
+});
 
 let WebNavigation =  {
   init: function() {
     addMessageListener("WebNavigation:GoBack", this);
     addMessageListener("WebNavigation:GoForward", this);
     addMessageListener("WebNavigation:GotoIndex", this);
     addMessageListener("WebNavigation:LoadURI", this);
     addMessageListener("WebNavigation:Reload", this);