Merge mozilla-central to mozilla-inbound
authorCarsten "Tomcat" Book <cbook@mozilla.com>
Thu, 25 Feb 2016 11:59:05 +0100
changeset 321893 3b913f81cb98b75061bb8c90f7780f88ac4b7dbb
parent 321892 5a5874ed903337de47dff5c76ed7cf541e3f3788 (current diff)
parent 321875 c1e0d1890cfee9d86c8d566b0490053f21e0afc6 (diff)
child 321894 2f43bc2f7ffdec7157f47793397110f9e8651cdf
push id5913
push userjlund@mozilla.com
push dateMon, 25 Apr 2016 16:57:49 +0000
treeherdermozilla-beta@dcaf0a6fa115 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
milestone47.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 mozilla-central to mozilla-inbound
devtools/client/devtools-clhandler.js
devtools/client/devtools-clhandler.manifest
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -1286,19 +1286,16 @@ var gBrowserInit = {
     gSyncUI.init();
     gFxAccounts.init();
 
     if (AppConstants.MOZ_DATA_REPORTING)
       gDataNotificationInfoBar.init();
 
     gBrowserThumbnails.init();
 
-    // Add Devtools menuitems and listeners
-    gDevToolsBrowser.registerBrowserWindow(window);
-
     gMenuButtonBadgeManager.init();
 
     gMenuButtonUpdateBadge.init();
 
     window.addEventListener("mousemove", MousePosTracker, false);
     window.addEventListener("dragover", MousePosTracker, false);
 
     gNavToolbox.addEventListener("customizationstarting", CustomizationHandler);
@@ -1402,18 +1399,16 @@ var gBrowserInit = {
 
   onUnload: function() {
     // In certain scenarios it's possible for unload to be fired before onload,
     // (e.g. if the window is being closed after browser.js loads but before the
     // load completes). In that case, there's nothing to do here.
     if (!this._loadHandled)
       return;
 
-    gDevToolsBrowser.forgetBrowserWindow(window);
-
     let desc = Object.getOwnPropertyDescriptor(window, "DeveloperToolbar");
     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).
 
@@ -2697,18 +2692,17 @@ var BrowserOnClick = {
         if (Services.io.offline) {
           // Reset network state and refresh the page.
           Services.io.offline = false;
           msg.target.reload();
         }
       break;
       case "Browser:SendSSLErrorReport":
         this.onSSLErrorReport(msg.target,
-                              msg.data.documentURI,
-                              msg.data.location,
+                              msg.data.uri,
                               msg.data.securityInfo);
       break;
       case "Browser:SetSSLErrorReportAuto":
         Services.prefs.setBoolPref("security.ssl.errorReporting.automatic", msg.json.automatic);
         let bin = TLS_ERROR_REPORT_TELEMETRY_AUTO_UNCHECKED;
         if (msg.json.automatic) {
           bin = TLS_ERROR_REPORT_TELEMETRY_AUTO_CHECKED;
         }
@@ -2718,43 +2712,40 @@ var BrowserOnClick = {
         let reportStatus = msg.data.reportStatus;
         Services.telemetry.getHistogramById("TLS_ERROR_REPORT_UI")
           .add(reportStatus);
       break;
       case "Browser:OverrideWeakCrypto":
         let weakCryptoOverride = Cc["@mozilla.org/security/weakcryptooverride;1"]
                                    .getService(Ci.nsIWeakCryptoOverride);
         weakCryptoOverride.addWeakCryptoOverride(
-          msg.data.location.hostname,
+          msg.data.uri.host,
           PrivateBrowsingUtils.isBrowserPrivate(gBrowser.selectedBrowser));
       break;
       case "Browser:SSLErrorGoBack":
         goBackFromErrorPage();
       break;
     }
   },
 
-  onSSLErrorReport: function(browser, documentURI, location, securityInfo) {
+  onSSLErrorReport: function(browser, uri, securityInfo) {
     if (!Services.prefs.getBoolPref("security.ssl.errorReporting.enabled")) {
       Cu.reportError("User requested certificate error report sending, but certificate error reporting is disabled");
       return;
     }
 
     let serhelper = Cc["@mozilla.org/network/serialization-helper;1"]
                            .getService(Ci.nsISerializationHelper);
     let transportSecurityInfo = serhelper.deserializeObject(securityInfo);
     transportSecurityInfo.QueryInterface(Ci.nsITransportSecurityInfo)
 
     let errorReporter = Cc["@mozilla.org/securityreporter;1"]
                           .getService(Ci.nsISecurityReporter);
-    // if location.port is the empty string, set to -1 (for consistency with
-    // port values from nsIURI)
-    let port = location.port === "" ? -1 : location.port;
     errorReporter.reportTLSError(transportSecurityInfo,
-                                 location.hostname, port);
+                                 uri.host, uri.port);
   },
 
   onAboutCertError: function (browser, elementId, isTopFrame, location, securityInfoAsString) {
     let secHistogram = Services.telemetry.getHistogramById("SECURITY_UI");
 
     switch (elementId) {
       case "exceptionDialogButton":
         if (isTopFrame) {
@@ -6538,16 +6529,20 @@ var gIdentityHandler = {
   get _isMixedActiveContentBlocked() {
     return this._state & Ci.nsIWebProgressListener.STATE_BLOCKED_MIXED_ACTIVE_CONTENT;
   },
 
   get _isMixedPassiveContentLoaded() {
     return this._state & Ci.nsIWebProgressListener.STATE_LOADED_MIXED_DISPLAY_CONTENT;
   },
 
+  get _isCertUserOverridden() {
+    return this._state & Ci.nsIWebProgressListener.STATE_CERT_USER_OVERRIDDEN;
+  },
+
   get _hasInsecureLoginForms() {
     // checks if the page has been flagged for an insecure login. Also checks
     // if the pref to degrade the UI is set to true
     return LoginManagerParent.hasInsecureLoginForms(gBrowser.selectedBrowser) &&
            Services.prefs.getBoolPref("security.insecure_password.ui.enabled");
   },
 
   // smart getters
@@ -6827,33 +6822,24 @@ var gIdentityHandler = {
       icon_labels_dir = /^[\u0590-\u08ff\ufb1d-\ufdff\ufe70-\ufefc]/.test(icon_label) ?
                         "rtl" : "ltr";
 
     } else if (this._uriHasHost && this._isSecure) {
       this._identityBox.className = "verifiedDomain";
       if (this._isMixedActiveContentBlocked) {
         this._identityBox.classList.add("mixedActiveBlocked");
       }
-
-      let iData = this.getIdentityData();
-
-      // Verifier is either the CA Org, for a normal cert, or a special string
-      // for certs that are trusted because of a security exception.
-      tooltip = gNavigatorBundle.getFormattedString("identity.identified.verifier",
-                                                    [iData.caOrg]);
-
-      let host = this._uri.host;
-      let port = 443;
-      try {
-        if (this._uri.port > 0)
-          port = this._uri.port;
-      } catch (e) {}
-
-      if (this._overrideService.hasMatchingOverride(host, port, iData.cert, {}, {})) {
+      if (this._isCertUserOverridden) {
+        this._identityBox.classList.add("certUserOverridden");
+        // Cert is trusted because of a security exception, verifier is a special string.
         tooltip = gNavigatorBundle.getString("identity.identified.verified_by_you");
+      } else {
+        // It's a normal cert, verifier is the CA Org.
+        tooltip = gNavigatorBundle.getFormattedString("identity.identified.verifier",
+                                                      [this.getIdentityData().caOrg]);
       }
     } else {
       this._identityBox.className = "unknownIdentity";
       if (this._isBroken) {
         if (this._isMixedActiveContentLoaded) {
           this._identityBox.classList.add("mixedActiveContent");
         } else if (this._isMixedActiveContentBlocked) {
           this._identityBox.classList.add("mixedDisplayContentLoadedActiveBlocked");
@@ -6947,16 +6933,18 @@ var gIdentityHandler = {
     // Determine connection security information.
     let connection = "not-secure";
     if (this._isSecureInternalUI) {
       connection = "chrome";
     } else if (this._isURILoadedFromFile) {
       connection = "file";
     } else if (this._isEV) {
       connection = "secure-ev";
+    } else if (this._isCertUserOverridden) {
+      connection = "secure-cert-user-overridden";
     } else if (this._isSecure) {
       connection = "secure";
     }
 
     // Determine if there are insecure login forms.
     let loginforms = "secure";
     if (this._hasInsecureLoginForms) {
       loginforms = "insecure";
--- a/browser/base/content/content.js
+++ b/browser/base/content/content.js
@@ -94,17 +94,18 @@ var handleContentContextMenu = function 
   let subject = {
     event: event,
     addonInfo: addonInfo,
   };
   subject.wrappedJSObject = subject;
   Services.obs.notifyObservers(subject, "content-contextmenu", null);
 
   let doc = event.target.ownerDocument;
-  let docLocation = doc.location ? doc.location.href : undefined;
+  let docLocation = doc.mozDocumentURIIfNotForErrorPages;
+  docLocation = docLocation && docLocation.spec;
   let charSet = doc.characterSet;
   let baseURI = doc.baseURI;
   let referrer = doc.referrer;
   let referrerPolicy = doc.referrerPolicy;
   let frameOuterWindowID = doc.defaultView.QueryInterface(Ci.nsIInterfaceRequestor)
                                           .getInterface(Ci.nsIDOMWindowUtils)
                                           .outerWindowID;
   let loginFillInfo = LoginManagerContent.getFieldContext(event.target);
@@ -271,31 +272,28 @@ var AboutCertErrorListener = {
 
   onSetAutomatic(event) {
     sendAsyncMessage("Browser:SetSSLErrorReportAuto", {
       automatic: event.detail
     });
 
     // if we're enabling reports, send a report for this failure
     if (event.detail) {
-      let doc = content.document;
-      let location = doc.location.href;
-
       let serhelper = Cc["@mozilla.org/network/serialization-helper;1"]
           .getService(Ci.nsISerializationHelper);
 
       let serializable =  docShell.failedChannel.securityInfo
           .QueryInterface(Ci.nsITransportSecurityInfo)
           .QueryInterface(Ci.nsISerializable);
 
       let serializedSecurityInfo = serhelper.serializeToString(serializable);
 
+      let {host, port} = content.document.mozDocumentURIIfNotForErrorPages;
       sendAsyncMessage("Browser:SendSSLErrorReport", {
-        documentURI: doc.documentURI,
-        location: {hostname: doc.location.hostname, port: doc.location.port},
+        uri: { host, port },
         securityInfo: serializedSecurityInfo
       });
     }
   },
 };
 
 AboutCertErrorListener.init(this);
 
@@ -344,49 +342,37 @@ var AboutNetErrorListener = {
 
   onSetAutomatic: function(evt) {
     sendAsyncMessage("Browser:SetSSLErrorReportAuto", {
       automatic: evt.detail
     });
 
     // if we're enabling reports, send a report for this failure
     if (evt.detail) {
-      let contentDoc = content.document;
-
-      let location = contentDoc.location.href;
-
       let serhelper = Cc["@mozilla.org/network/serialization-helper;1"]
                         .getService(Ci.nsISerializationHelper);
 
       let serializable = docShell.failedChannel.securityInfo
           .QueryInterface(Ci.nsITransportSecurityInfo)
           .QueryInterface(Ci.nsISerializable);
 
       let serializedSecurityInfo = serhelper.serializeToString(serializable);
 
+      let {host, port} = content.document.mozDocumentURIIfNotForErrorPages;
       sendAsyncMessage("Browser:SendSSLErrorReport", {
-        documentURI: contentDoc.documentURI,
-        location: {
-          hostname: contentDoc.location.hostname,
-          port: contentDoc.location.port
-        },
+        uri: { host, port },
         securityInfo: serializedSecurityInfo
       });
 
     }
   },
 
   onOverride: function(evt) {
-    let contentDoc = content.document;
-    let location = contentDoc.location;
-
-    sendAsyncMessage("Browser:OverrideWeakCrypto", {
-      documentURI: contentDoc.documentURI,
-      location: {hostname: location.hostname, port: location.port}
-    });
+    let {host, port} = content.document.mozDocumentURIIfNotForErrorPages;
+    sendAsyncMessage("Browser:OverrideWeakCrypto", { uri: {host, port} });
   }
 }
 
 AboutNetErrorListener.init(this);
 
 
 var ClickEventHandler = {
   init: function init() {
--- a/browser/base/content/sanitize.js
+++ b/browser/base/content/sanitize.js
@@ -683,18 +683,19 @@ Sanitizer.PREF_DOMAIN = "privacy.sanitiz
 // Whether we should sanitize on shutdown.
 Sanitizer.PREF_SANITIZE_ON_SHUTDOWN = "privacy.sanitize.sanitizeOnShutdown";
 // During a sanitization this is set to a json containing the array of items
 // being sanitized, then cleared once the sanitization is complete.
 // This allows to retry a sanitization on startup in case it was interrupted
 // by a crash.
 Sanitizer.PREF_SANITIZE_IN_PROGRESS = "privacy.sanitize.sanitizeInProgress";
 // Whether the previous shutdown sanitization completed successfully.
-// Note that PREF_SANITIZE_IN_PROGRESS would be enough to detect an interrupted
-// sanitization, but this is still supported for backwards compatibility.
+// This is used to detect cases where we were supposed to sanitize on shutdown
+// but due to a crash we were unable to.  In such cases there may not be any
+// sanitization in progress, cause we didn't have a chance to start it yet.
 Sanitizer.PREF_SANITIZE_DID_SHUTDOWN = "privacy.sanitize.didShutdownSanitize";
 
 // Time span constants corresponding to values of the privacy.sanitize.timeSpan
 // pref.  Used to determine how much history to clear, for various items
 Sanitizer.TIMESPAN_EVERYTHING = 0;
 Sanitizer.TIMESPAN_HOUR       = 1;
 Sanitizer.TIMESPAN_2HOURS     = 2;
 Sanitizer.TIMESPAN_4HOURS     = 3;
@@ -774,20 +775,24 @@ Sanitizer.sanitize = function(aParentWin
   Sanitizer.showUI(aParentWindow);
 };
 
 Sanitizer.onStartup = Task.async(function*() {
   // Check if we were interrupted during the last shutdown sanitization.
   let shutownSanitizationWasInterrupted =
     Preferences.get(Sanitizer.PREF_SANITIZE_ON_SHUTDOWN, false) &&
     !Preferences.has(Sanitizer.PREF_SANITIZE_DID_SHUTDOWN);
-  // Regardless, reset the pref, since we want to check it at the next startup
-  // even if the browser exits abruptly.
-  Preferences.reset(Sanitizer.PREF_SANITIZE_DID_SHUTDOWN);
-  Services.prefs.savePrefFile(null);
+
+  if (Preferences.has(Sanitizer.PREF_SANITIZE_DID_SHUTDOWN)) {
+    // Reset the pref, so that if we crash before having a chance to
+    // sanitize on shutdown, we will do at the next startup.
+    // Flushing prefs has a cost, so do this only if necessary.
+    Preferences.reset(Sanitizer.PREF_SANITIZE_DID_SHUTDOWN);
+    Services.prefs.savePrefFile(null);
+  }
 
   // Make sure that we are triggered during shutdown, at the right time,
   // and only once.
   let placesClient = Cc["@mozilla.org/browser/nav-history-service;1"]
                        .getService(Ci.nsPIPlacesDatabase)
                        .shutdownClient
                        .jsclient;
 
@@ -799,29 +804,27 @@ Sanitizer.onStartup = Task.async(functio
       Sanitizer.onShutdown().catch(er => {Promise.reject(er) /* Do not return rejected promise */;}).then(() =>
         deferredSanitization.resolve()
       );
     }
     return deferredSanitization.promise;
   }
   placesClient.addBlocker("sanitize.js: Sanitize on shutdown", doSanitize);
 
-  // Check if Firefox crashed before completing a sanitization.
+  // Check if Firefox crashed during a sanitization.
   let lastInterruptedSanitization = Preferences.get(Sanitizer.PREF_SANITIZE_IN_PROGRESS, "");
   if (lastInterruptedSanitization) {
     let s = new Sanitizer();
     // If the json is invalid this will just throw and reject the Task.
     let itemsToClear = JSON.parse(lastInterruptedSanitization);
     yield s.sanitize(itemsToClear);
   } else if (shutownSanitizationWasInterrupted) {
-    // Ideally lastInterruptedSanitization should always be set when a
-    // sanitization is interrupted, but some add-ons or Firefox previous
-    // versions may not set the pref.
-    // In such a case, we can still detect an interrupted shutdown sanitization,
-    // and just redo it.
+    // Otherwise, could be we were supposed to sanitize on shutdown but we
+    // didn't have a chance, due to an earlier crash.
+    // In such a case, just redo a shutdown sanitize now, during startup.
     yield Sanitizer.onShutdown();
   }
 });
 
 Sanitizer.onShutdown = Task.async(function*() {
   if (!Preferences.get(Sanitizer.PREF_SANITIZE_ON_SHUTDOWN)) {
     return;
   }
--- a/browser/base/content/social-content.js
+++ b/browser/base/content/social-content.js
@@ -59,16 +59,21 @@ SocialErrorListener = {
     addMessageListener("WaitForDOMContentLoaded", this);
     let webProgress = docShell.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
                               .getInterface(Components.interfaces.nsIWebProgress);
     webProgress.addProgressListener(this, Ci.nsIWebProgress.NOTIFY_STATE_REQUEST |
                                           Ci.nsIWebProgress.NOTIFY_LOCATION);
   },
 
   receiveMessage(message) {
+    if (!content) {
+      Cu.reportError("Message received whilst `content` is null: " + message.name);
+      return;
+    }
+
     let document = content.document;
 
     switch (message.name) {
       case "Loop:GetAllWebrtcStats":
         content.WebrtcGlobalInformation.getAllStats(allStats => {
           content.WebrtcGlobalInformation.getLogging("", logs => {
             sendAsyncMessage("Loop:GetAllWebrtcStats", {
               allStats: allStats,
--- a/browser/base/content/test/chat/browser_chatwindow.js
+++ b/browser/base/content/test/chat/browser_chatwindow.js
@@ -1,14 +1,16 @@
 /* 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/. */
 
 Components.utils.import("resource://gre/modules/Promise.jsm", this);
 
+requestLongerTimeout(2);
+
 var chatbar = document.getElementById("pinnedchats");
 
 add_chat_task(function* testOpenCloseChat() {
   let chatbox = yield promiseOpenChat("http://example.com");
   Assert.strictEqual(chatbox, chatbar.selectedChat);
   // we requested a "normal" chat, so shouldn't be minimized
   Assert.ok(!chatbox.minimized, "chat is not minimized");
   Assert.equal(chatbar.childNodes.length, 1, "should be 1 chat open");
--- a/browser/base/content/test/chat/browser_focus.js
+++ b/browser/base/content/test/chat/browser_focus.js
@@ -2,16 +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/. */
 
 // Tests the focus functionality.
 
 Components.utils.import("resource://gre/modules/Promise.jsm", this);
 const CHAT_URL = "https://example.com/browser/browser/base/content/test/chat/chat.html";
 
+requestLongerTimeout(2);
+
 // Is the currently opened tab focused?
 function isTabFocused() {
   let tabb = gBrowser.getBrowserForTab(gBrowser.selectedTab);
   // focus sucks in tests - our window may have lost focus.
   let elt = Services.focus.getFocusedElementForWindow(window, false, {});
   return elt == tabb;
 }
 
--- a/browser/base/content/test/general/browser.ini
+++ b/browser/base/content/test/general/browser.ini
@@ -150,16 +150,17 @@ skip-if = e10s # Bug 1101993 - times out
 [browser_autocomplete_autoselect.js]
 [browser_autocomplete_oldschool_wrap.js]
 [browser_autocomplete_tag_star_visibility.js]
 [browser_backButtonFitts.js]
 skip-if = os == "mac" # The Fitt's Law back button is not supported on OS X
 [browser_beforeunload_duplicate_dialogs.js]
 [browser_blob-channelname.js]
 [browser_bookmark_popup.js]
+skip-if = (os == "linux" && debug) # mouseover not reliable on linux debug builds
 [browser_bookmark_titles.js]
 skip-if = buildapp == 'mulet' || toolkit == "windows" # Disabled on Windows due to frequent failures (bugs 825739, 841341)
 [browser_bug304198.js]
 [browser_bug321000.js]
 skip-if = true # browser_bug321000.js is disabled because newline handling is shaky (bug 592528)
 [browser_bug329212.js]
 skip-if = e10s # Bug 1236991 - Update or remove tests that use fillInPageTooltip
 [browser_bug331772_xul_tooltiptext_in_html.js]
--- a/browser/base/content/test/general/browser_addCertException.js
+++ b/browser/base/content/test/general/browser_addCertException.js
@@ -3,17 +3,17 @@
 /* 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/. */
 
 // Test adding a certificate exception by attempting to browse to a site with
 // a bad certificate, being redirected to the internal about:certerror page,
 // using the button contained therein to load the certificate exception
 // dialog, using that to add an exception, and finally successfully visiting
-// the site.
+// the site, including showing the right identity box and control center icons.
 function test() {
   waitForExplicitFinish();
   whenNewTabLoaded(window, loadBadCertPage);
 }
 
 // Attempt to load https://expired.example.com (which has an expired cert).
 function loadBadCertPage() {
   gBrowser.addProgressListener(certErrorProgressListener);
@@ -62,24 +62,54 @@ var certExceptionDialogObserver = {
     }
   }
 };
 
 // Finally, we should successfully load https://expired.example.com.
 var successfulLoadListener = {
   handleEvent: function() {
     gBrowser.selectedBrowser.removeEventListener("load", this, true);
+    checkControlPanelIcons();
     let certOverrideService = Cc["@mozilla.org/security/certoverride;1"]
                                 .getService(Ci.nsICertOverrideService);
     certOverrideService.clearValidityOverride("expired.example.com", -1);
     gBrowser.removeTab(gBrowser.selectedTab);
     finish();
   }
 };
 
+// Check for the correct icons in the identity box and control center.
+function checkControlPanelIcons() {
+  let { gIdentityHandler } = gBrowser.ownerGlobal;
+  gIdentityHandler._identityBox.click();
+  document.getElementById("identity-popup-security-expander").click();
+
+  is_element_visible(document.getElementById("connection-icon"));
+  let connectionIconImage = gBrowser.ownerGlobal
+        .getComputedStyle(document.getElementById("connection-icon"), "")
+        .getPropertyValue("list-style-image");
+  let securityViewBG = gBrowser.ownerGlobal
+        .getComputedStyle(document.getElementById("identity-popup-securityView"), "")
+        .getPropertyValue("background-image");
+  let securityContentBG = gBrowser.ownerGlobal
+        .getComputedStyle(document.getElementById("identity-popup-security-content"), "")
+        .getPropertyValue("background-image");
+  is(connectionIconImage,
+     "url(\"chrome://browser/skin/identity-mixed-passive-loaded.svg\")",
+     "Using expected icon image in the identity block");
+  is(securityViewBG,
+     "url(\"chrome://browser/skin/identity-mixed-passive-loaded.svg\")",
+     "Using expected icon image in the Control Center main view");
+  is(securityContentBG,
+     "url(\"chrome://browser/skin/identity-mixed-passive-loaded.svg\")",
+     "Using expected icon image in the Control Center subview");
+
+  gIdentityHandler._identityPopup.hidden = true;
+}
+
 // Utility function to get a handle on the certificate exception dialog.
 // Modified from toolkit/components/passwordmgr/test/prompt_common.js
 function getDialog(aLocation) {
   let wm = Cc["@mozilla.org/appshell/window-mediator;1"]
              .getService(Ci.nsIWindowMediator);
   let enumerator = wm.getXULWindowEnumerator(null);
 
   while (enumerator.hasMoreElements()) {
--- a/browser/components/controlcenter/content/panel.inc.xul
+++ b/browser/components/controlcenter/content/panel.inc.xul
@@ -20,17 +20,17 @@
     <panelview id="identity-popup-mainView" flex="1">
 
       <!-- Security Section -->
       <hbox id="identity-popup-security" class="identity-popup-section">
         <vbox id="identity-popup-security-content" flex="1">
           <label observes="identity-popup-content-host"/>
           <description class="identity-popup-connection-not-secure"
                        value="&identity.connectionNotSecure;"
-                       when-connection="not-secure"/>
+                       when-connection="not-secure secure-cert-user-overridden"/>
           <description class="identity-popup-connection-secure"
                        value="&identity.connectionSecure;"
                        when-connection="secure secure-ev"/>
           <description value="&identity.connectionInternal;"
                        when-connection="chrome"/>
           <description value="&identity.connectionFile;"
                        when-connection="file"/>
 
@@ -42,24 +42,24 @@
             <description when-mixedcontent="active-loaded">&identity.activeLoaded;</description>
             <description class="identity-popup-warning-yellow"
                          when-ciphers="weak">&identity.weakEncryption;</description>
             <description when-loginforms="insecure">&identity.insecureLoginForms2;</description>
           </vbox>
         </vbox>
         <button id="identity-popup-security-expander"
                 class="identity-popup-expander"
-                when-connection="not-secure secure secure-ev"
+                when-connection="not-secure secure secure-ev secure-cert-user-overridden"
                 oncommand="gIdentityHandler.toggleSubView('security', this)"/>
       </hbox>
 
       <!-- Tracking Protection Section -->
       <hbox id="tracking-protection-container"
             class="identity-popup-section"
-            when-connection="not-secure secure secure-ev file">
+            when-connection="not-secure secure secure-ev secure-cert-user-overridden file">
         <vbox id="tracking-protection-content" flex="1">
           <description class="identity-popup-headline"
                        crop="end"
                        value="&trackingProtection.title;" />
 
           <label id="tracking-blocked"
                  crop="end">&trackingProtection.detectedBlocked3;</label>
           <label id="tracking-loaded"
@@ -94,33 +94,33 @@
     </panelview>
 
     <!-- Security SubView -->
     <panelview id="identity-popup-securityView" flex="1">
       <vbox id="identity-popup-securityView-header">
         <label observes="identity-popup-content-host"/>
         <description class="identity-popup-connection-not-secure"
                      value="&identity.connectionNotSecure;"
-                     when-connection="not-secure"/>
+                     when-connection="not-secure secure-cert-user-overridden"/>
         <description class="identity-popup-connection-secure"
                      value="&identity.connectionSecure;"
                      when-connection="secure secure-ev"/>
       </vbox>
 
       <vbox id="identity-popup-securityView-body">
         <!-- (EV) Certificate Information -->
         <description id="identity-popup-content-verified-by"
                      when-connection="secure-ev">&identity.connectionVerified1;</description>
         <description id="identity-popup-content-owner"
                      when-connection="secure-ev"
                      class="header"/>
         <description id="identity-popup-content-supplemental"
                      when-connection="secure-ev"/>
         <description id="identity-popup-content-verifier"
-                     when-connection="secure secure-ev"/>
+                     when-connection="secure secure-ev secure-cert-user-overridden"/>
 
         <!-- Connection is Not Secure -->
         <description when-connection="not-secure"
                      and-when-loginforms="secure">&identity.description.insecure;</description>
 
         <!-- Insecure login forms -->
         <description when-loginforms="insecure">&identity.description.insecureLoginForms; <label observes="identity-popup-insecure-login-forms-learn-more"/></description>
 
--- a/browser/components/distribution.js
+++ b/browser/components/distribution.js
@@ -235,19 +235,24 @@ DistributionCustomizer.prototype = {
           }
         }
 
         break;
       }
     }
   }),
 
+  _newProfile: false,
   _customizationsApplied: false,
   applyCustomizations: function DIST_applyCustomizations() {
     this._customizationsApplied = true;
+
+    if (!Services.prefs.prefHasUserValue("browser.migration.version"))
+      this._newProfile = true;
+
     if (!this._ini)
       return this._checkCustomizationComplete();
 
     // nsPrefService loads very early.  Reload prefs so we can set
     // distribution defaults during the prefservice:after-app-defaults
     // notification (see applyPrefDefaults below)
     this._prefSvc.QueryInterface(Ci.nsIObserver);
     this._prefSvc.observe(null, "reload-default-prefs", null);
@@ -403,16 +408,35 @@ DistributionCustomizer.prototype = {
         } catch (e) { /* ignore bad prefs and move on */ }
       }
     }
 
     return this._checkCustomizationComplete();
   },
 
   _checkCustomizationComplete: function DIST__checkCustomizationComplete() {
+    const BROWSER_DOCURL = "chrome://browser/content/browser.xul";
+
+    if (this._newProfile) {
+      let xulStore = Cc["@mozilla.org/xul/xulstore;1"].getService(Ci.nsIXULStore);
+
+      try {
+        var showPersonalToolbar = Services.prefs.getBoolPref("browser.showPersonalToolbar");
+        if (showPersonalToolbar) {
+          xulStore.setValue(BROWSER_DOCURL, "PersonalToolbar", "collapsed", "false");
+        }
+      } catch(e) {}
+      try {
+        var showMenubar = Services.prefs.getBoolPref("browser.showMenubar");
+        if (showMenubar) {
+          xulStore.setValue(BROWSER_DOCURL, "toolbar-menubar", "collapsed", "false");
+        }
+      } catch(e) {}
+    }
+
     let prefDefaultsApplied = this._prefDefaultsApplied || !this._ini;
     if (this._customizationsApplied && this._bookmarksApplied &&
         prefDefaultsApplied) {
       let os = Cc["@mozilla.org/observer-service;1"].
                getService(Ci.nsIObserverService);
       os.notifyObservers(null, DISTRIBUTION_CUSTOMIZATION_COMPLETE_TOPIC, null);
     }
   }
--- a/browser/components/extensions/test/browser/browser_ext_webNavigation_getFrames.js
+++ b/browser/components/extensions/test/browser/browser_ext_webNavigation_getFrames.js
@@ -134,17 +134,21 @@ add_task(function* testWebNavigationFram
     getFrameResults,
   } = yield extension.awaitMessage("webNavigationFrames.done");
 
   is(getAllFramesDetails.length, 3, "expected number of frames found");
   is(getAllFramesDetails.length, collectedDetails.length,
      "number of frames found should equal the number onCompleted events collected");
 
   // ordered by frameId
-  let sortByFrameId = (el) => el ? el.frameId : -1;
+  let sortByFrameId = (el1, el2) => {
+    let val1 = el1 ? el1.frameId : -1;
+    let val2 = el2 ? el2.frameId : -1;
+    return val1 - val2;
+  };
 
   collectedDetails = collectedDetails.sort(sortByFrameId);
   getAllFramesDetails = getAllFramesDetails.sort(sortByFrameId);
   getFrameResults = getFrameResults.sort(sortByFrameId);
 
   info("check frame details content");
 
   is(getFrameResults.length, getAllFramesDetails.length,
--- a/browser/components/sessionstore/SessionCookies.jsm
+++ b/browser/components/sessionstore/SessionCookies.jsm
@@ -112,19 +112,26 @@ var SessionCookiesInternal = {
   },
 
   /**
    * Restores a given list of session cookies.
    */
   restore(cookies) {
     for (let cookie of cookies) {
       let expiry = "expiry" in cookie ? cookie.expiry : MAX_EXPIRY;
-      Services.cookies.add(cookie.host, cookie.path || "", cookie.name || "",
-                           cookie.value, !!cookie.secure, !!cookie.httponly,
-                           /* isSession = */ true, expiry);
+      let cookieObj = {
+        host: cookie.host,
+        path: cookie.path || "",
+        name: cookie.name || ""
+      };
+      if (!Services.cookies.cookieExists(cookieObj)) {
+        Services.cookies.add(cookie.host, cookie.path || "", cookie.name || "",
+                             cookie.value, !!cookie.secure, !!cookie.httponly,
+                             /* isSession = */ true, expiry);
+      }
     }
   },
 
   /**
    * Handles observers notifications that are sent whenever cookies are added,
    * changed, or removed. Ensures that the storage is updated accordingly.
    */
   observe: function (subject, topic, data) {
@@ -236,16 +243,18 @@ var SessionCookiesInternal = {
   /**
    * Updates or adds a given cookie to the store.
    */
   _updateCookie: function (cookie) {
     cookie.QueryInterface(Ci.nsICookie2);
 
     if (cookie.isSession) {
       CookieStore.set(cookie);
+    } else {
+      CookieStore.delete(cookie);
     }
   },
 
   /**
    * Removes a given cookie from the store.
    */
   _removeCookie: function (cookie) {
     cookie.QueryInterface(Ci.nsICookie2);
--- a/browser/components/shell/nsWindowsShellService.cpp
+++ b/browser/components/shell/nsWindowsShellService.cpp
@@ -653,16 +653,25 @@ nsWindowsShellService::LaunchModernSetti
                                 (void**)&pActivator);
 
   if (SUCCEEDED(hr)) {
     DWORD pid;
     hr = pActivator->ActivateApplication(
            L"windows.immersivecontrolpanel_cw5n1h2txyewy"
            L"!microsoft.windows.immersivecontrolpanel",
            L"page=SettingsPageAppsDefaults", AO_NONE, &pid);
+    if (SUCCEEDED(hr)) {
+      // Do not check error because we could at least open
+      // the "Default apps" setting.
+      pActivator->ActivateApplication(
+             L"windows.immersivecontrolpanel_cw5n1h2txyewy"
+             L"!microsoft.windows.immersivecontrolpanel",
+             L"page=SettingsPageAppsDefaults"
+             L"&target=SystemSettings_DefaultApps_Browser", AO_NONE, &pid);
+    }
     pActivator->Release();
     return SUCCEEDED(hr) ? NS_OK : NS_ERROR_FAILURE;
   }
   return NS_OK;
 }
 
 nsresult
 nsWindowsShellService::InvokeHTTPOpenAsVerb()
--- a/browser/extensions/loop/bootstrap.js
+++ b/browser/extensions/loop/bootstrap.js
@@ -951,16 +951,21 @@ function createLoopButton() {
 /**
  * Loads the default preferences from the prefs file. This loads the preferences
  * into the default branch, so they don't appear as user preferences.
  */
 function loadDefaultPrefs() {
   var branch = Services.prefs.getDefaultBranch("");
   Services.scriptloader.loadSubScript("chrome://loop/content/preferences/prefs.js", {
     pref: (key, val) => {
+      // If a previously set default pref exists don't overwrite it.  This can
+      // happen for ESR or distribution.ini.
+      if (branch.getPrefType(key) != branch.PREF_INVALID) {
+        return;
+      }
       switch (typeof val) {
         case "boolean":
           branch.setBoolPref(key, val);
           break;
         case "number":
           branch.setIntPref(key, val);
           break;
         case "string":
@@ -974,16 +979,19 @@ function loadDefaultPrefs() {
 /**
  * Called when the add-on is started, e.g. when installed or when Firefox starts.
  */
 function startup(data) {
   // Record the add-on version for when the UI is initialised.
   WindowListener.addonVersion = data.version;
 
   loadDefaultPrefs();
+  if (!Services.prefs.getBoolPref("loop.enabled")) {
+    return;
+  }
 
   createLoopButton();
 
   // Attach to hidden window (for OS X).
   if (AppConstants.platform == "macosx") {
     try {
       WindowListener.setupBrowserUI(Services.appShell.hiddenDOMWindow);
     } catch (ex) {
--- a/browser/extensions/loop/chrome/content/modules/MozLoopAPI.jsm
+++ b/browser/extensions/loop/chrome/content/modules/MozLoopAPI.jsm
@@ -817,16 +817,32 @@ const kMessageHandlers = {
    *                           the senders' channel.
    */
   OpenGettingStartedTour: function(message, reply) {
     MozLoopService.openGettingStartedTour();
     reply();
   },
 
   /**
+   * Retrieves the Getting Started tour url.
+   *
+   * @param {Object}   message Message meant for the handler function, containing
+   *                           the following parameters in its `data` property:
+   *                           [aSrc, aAdditionalParams]
+   * @param {Function} reply   Callback function, invoked with the result of this
+   *                           message handler. The result will be sent back to
+   *                           the senders' channel.
+   */
+  GettingStartedURL: function(message, reply) {
+    let aSrc = message.data[0] || null;
+    let aAdditionalParams = message.data[1] || {};
+    reply(MozLoopService.getTourURL(aSrc, aAdditionalParams).href);
+  },
+
+  /**
    * Open the FxA profile/ settings page.
    *
    * @param {Object}   message Message meant for the handler function, containing
    *                           the following parameters in its `data` property:
    *                           [ ]
    * @param {Function} reply   Callback function, invoked with the result of this
    *                           message handler. The result will be sent back to
    *                           the senders' channel.
--- a/browser/extensions/loop/chrome/content/panels/js/panel.js
+++ b/browser/extensions/loop/chrome/content/panels/js/panel.js
@@ -462,19 +462,25 @@ loop.panel = function (_, mozL10n) {
     handleClickEntry: function (event) {
       event.preventDefault();
 
       this.props.dispatcher.dispatch(new sharedActions.OpenRoom({
         roomToken: this.props.room.roomToken
       }));
 
       // Open url if needed.
-      loop.request("getSelectedTabMetadata").then(function (metadata) {
+      loop.requestMulti(
+        ["getSelectedTabMetadata"],
+        ["GettingStartedURL", null, {}]
+      ).then(function(results) {
         var contextURL = this.props.room.decryptedContext.urls && this.props.room.decryptedContext.urls[0].location;
-        if (contextURL && metadata.url !== contextURL) {
+
+        contextURL = contextURL || (results[1] + "?noopenpanel=1");
+
+        if (results[0].url !== contextURL) {
           loop.request("OpenURL", contextURL);
         }
         this.closeWindow();
       }.bind(this));
     },
 
     handleClick: function (e) {
       e.preventDefault();
--- a/browser/extensions/loop/chrome/content/panels/test/panel_test.js
+++ b/browser/extensions/loop/chrome/content/panels/test/panel_test.js
@@ -71,16 +71,17 @@ describe("loop.panel", function() {
       Confirm: sinon.stub(),
       GetHasEncryptionKey: function() { return true; },
       HangupAllChatWindows: function() {},
       IsMultiProcessActive: sinon.stub(),
       LoginToFxA: sinon.stub(),
       LogoutFromFxA: sinon.stub(),
       NotifyUITour: sinon.stub(),
       OpenURL: sinon.stub(),
+      GettingStartedURL: sinon.stub().returns("http://fakeFTUUrl.com"),
       GetSelectedTabMetadata: sinon.stub().returns({}),
       GetUserProfile: function() { return null; }
     });
 
     loop.storedRequests = {
       GetFxAEnabled: true,
       GetHasEncryptionKey: true,
       GetUserProfile: null,
@@ -824,16 +825,50 @@ describe("loop.panel", function() {
         });
 
         it("should open a new tab with the room context if it is not the same as the currently open tab", function() {
           TestUtils.Simulate.click(roomEntry.refs.roomEntry.getDOMNode());
           sinon.assert.calledOnce(openURLStub);
           sinon.assert.calledWithExactly(openURLStub, "http://testurl.com");
         });
 
+        it("should open a new tab with the FTU Getting Started URL if the room context is blank", function() {
+          var roomDataNoURL = {
+            roomToken: "QzBbvGmIZWU",
+            roomUrl: "http://sample/QzBbvGmIZWU",
+            decryptedContext: {
+              roomName: roomName,
+              urls: [{
+                location: ""
+              }]
+            },
+            maxSize: 2,
+            participants: [{
+              displayName: "Alexis",
+              account: "alexis@example.com",
+              roomConnectionId: "2a1787a6-4a73-43b5-ae3e-906ec1e763cb"
+            }, {
+              displayName: "Adam",
+              roomConnectionId: "781f012b-f1ea-4ce1-9105-7cfc36fb4ec7"
+            }],
+            ctime: 1405517418
+          };
+          roomEntry = mountRoomEntry({
+            deleteRoom: sandbox.stub(),
+            isOpenedRoom: false,
+            room: new loop.store.Room(roomDataNoURL)
+          });
+          var ftuURL = requestStubs.GettingStartedURL() + "?noopenpanel=1";
+
+          TestUtils.Simulate.click(roomEntry.refs.roomEntry.getDOMNode());
+
+          sinon.assert.calledOnce(openURLStub);
+          sinon.assert.calledWithExactly(openURLStub, ftuURL);
+        });
+
         it("should not open a new tab if the context is the same as the currently open tab", function() {
           LoopMochaUtils.stubLoopRequest({
             GetSelectedTabMetadata: function() {
               return {
                 url: "http://testurl.com",
                 description: "fakeSite"
               };
             }
new file mode 100644
--- /dev/null
+++ b/browser/extensions/loop/chrome/test/xpcshell/test_loopapi_ftu_url.js
@@ -0,0 +1,14 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+"use strict";
+
+const { LoopAPI } = Cu.import("chrome://loop/content/modules/MozLoopAPI.jsm", {});
+var [, gHandlers] = LoopAPI.inspect();
+
+add_task(function* test_mozLoop_gettingStartedURL() {
+  let expectedURL = MozLoopService.getTourURL().href;
+  // Test gettingStartedURL
+  gHandlers.GettingStartedURL({ data: [] }, result => {
+    Assert.equal(result, expectedURL, "should get mozLoopService GettingStartedURL value correctly");
+  });
+});
--- a/browser/extensions/loop/chrome/test/xpcshell/xpcshell.ini
+++ b/browser/extensions/loop/chrome/test/xpcshell/xpcshell.ini
@@ -1,15 +1,16 @@
 [DEFAULT]
 head = head.js
 tail =
 firefox-appdir = browser
 skip-if = toolkit == 'gonk'
 
 [test_loopapi_doNotDisturb.js]
+[test_loopapi_ftu_url.js]
 [test_loopapi_internal.js]
 [test_loopapi_prefs.js]
 [test_looppush_initialize.js]
 [test_looprooms.js]
 [test_looprooms_encryption_in_fxa.js]
 [test_looprooms_first_notification.js]
 [test_looprooms_getall.js]
 [test_looprooms_upgrade_to_encryption.js]
--- a/browser/extensions/pocket/bootstrap.js
+++ b/browser/extensions/pocket/bootstrap.js
@@ -36,16 +36,20 @@ const PREFS = {
   api: "api.getpocket.com",
   site: "getpocket.com",
   oAuthConsumerKey: "40249-e88c401e1b1f2242d9e441c4"
 };
 
 function setDefaultPrefs() {
   let branch = Services.prefs.getDefaultBranch(PREF_BRANCH);
   for (let [key, val] in Iterator(PREFS)) {
+    // If someone beat us to setting a default, don't overwrite it.  This can
+    // happen if distribution.ini sets the default first.
+    if (branch.getPrefType(key) != branch.PREF_INVALID)
+      continue;
     switch (typeof val) {
       case "boolean":
         branch.setBoolPref(key, val);
         break;
       case "number":
         branch.setIntPref(key, val);
         break;
       case "string":
@@ -514,18 +518,17 @@ function startup(data, reason) {
     setDefaultPrefs();
     // migrate enabled pref
     if (Services.prefs.prefHasUserValue("browser.pocket.enabled")) {
       Services.prefs.setBoolPref("extensions.pocket.enabled", Services.prefs.getBoolPref("browser.pocket.enabled"));
       Services.prefs.clearUserPref("browser.pocket.enabled");
     }
     // watch pref change and enable/disable if necessary
     Services.prefs.addObserver("extensions.pocket.enabled", prefObserver, false);
-    if (Services.prefs.prefHasUserValue("extensions.pocket.enabled") &&
-        !Services.prefs.getBoolPref("extensions.pocket.enabled"))
+    if (!Services.prefs.getBoolPref("extensions.pocket.enabled"))
       return;
     PocketOverlay.startup(reason);
   });
 }
 
 function shutdown(data, reason) {
   // For speed sake, we should only do a shutdown if we're being disabled.
   // On an app shutdown, just let it fade away...
--- a/browser/installer/package-manifest.in
+++ b/browser/installer/package-manifest.in
@@ -370,18 +370,18 @@
 @RESPATH@/browser/components/FeedConverter.js
 @RESPATH@/browser/components/FeedWriter.js
 @RESPATH@/browser/components/WebContentConverter.js
 @RESPATH@/browser/components/BrowserComponents.manifest
 @RESPATH@/browser/components/nsBrowserContentHandler.js
 @RESPATH@/browser/components/nsBrowserGlue.js
 @RESPATH@/browser/components/nsSetDefaultBrowser.manifest
 @RESPATH@/browser/components/nsSetDefaultBrowser.js
-@RESPATH@/browser/components/devtools-clhandler.manifest
-@RESPATH@/browser/components/devtools-clhandler.js
+@RESPATH@/browser/components/devtools-startup.manifest
+@RESPATH@/browser/components/devtools-startup.js
 @RESPATH@/browser/components/webideCli.js
 @RESPATH@/browser/components/webideComponents.manifest
 @RESPATH@/browser/components/Experiments.manifest
 @RESPATH@/browser/components/ExperimentsService.js
 @RESPATH@/browser/components/browser-newtab.xpt
 @RESPATH@/browser/components/aboutNewTabService.js
 @RESPATH@/browser/components/NewTabComponents.manifest
 @RESPATH@/components/Downloads.manifest
--- a/browser/modules/test/browser.ini
+++ b/browser/modules/test/browser.ini
@@ -1,17 +1,16 @@
 [DEFAULT]
 support-files =
   head.js
 
 [browser_BrowserUITelemetry_buckets.js]
 [browser_ProcessHangNotifications.js]
 skip-if = !e10s
 [browser_ContentSearch.js]
-skip-if = e10s
 support-files =
   contentSearch.js
   contentSearchBadImage.xml
   contentSearchSuggestions.sjs
   contentSearchSuggestions.xml
 [browser_NetworkPrioritizer.js]
 skip-if = e10s # Bug 666804 - Support NetworkPrioritizer in e10s
 [browser_SelfSupportBackend.js]
--- a/browser/modules/test/browser_ContentSearch.js
+++ b/browser/modules/test/browser_ContentSearch.js
@@ -92,51 +92,55 @@ add_task(function* search() {
   yield addTab();
   let engine = Services.search.currentEngine;
   let data = {
     engineName: engine.name,
     searchString: "ContentSearchTest",
     healthReportKey: "ContentSearchTest",
     searchPurpose: "ContentSearchTest",
   };
+  let submissionURL =
+    engine.getSubmission(data.searchString, "", data.whence).uri.spec;
   gMsgMan.sendAsyncMessage(TEST_MSG, {
     type: "Search",
     data: data,
+    expectedURL: submissionURL,
   });
-  let submissionURL =
-    engine.getSubmission(data.searchString, "", data.whence).uri.spec;
-  yield waitForLoadAndStopIt(gBrowser.selectedBrowser, submissionURL);
+  let msg = yield waitForTestMsg("loadStopped");
+  Assert.equal(msg.data.url, submissionURL, "Correct search page loaded");
 });
 
 add_task(function* searchInBackgroundTab() {
   // This test is like search(), but it opens a new tab after starting a search
   // in another.  In other words, it performs a search in a background tab.  The
   // search page should be loaded in the same tab that performed the search, in
   // the background tab.
   yield addTab();
   let searchBrowser = gBrowser.selectedBrowser;
   let engine = Services.search.currentEngine;
   let data = {
     engineName: engine.name,
     searchString: "ContentSearchTest",
     healthReportKey: "ContentSearchTest",
     searchPurpose: "ContentSearchTest",
   };
+  let submissionURL =
+    engine.getSubmission(data.searchString, "", data.whence).uri.spec;
   gMsgMan.sendAsyncMessage(TEST_MSG, {
     type: "Search",
     data: data,
+    expectedURL: submissionURL,
   });
 
   let newTab = gBrowser.addTab();
   gBrowser.selectedTab = newTab;
   registerCleanupFunction(() => gBrowser.removeTab(newTab));
 
-  let submissionURL =
-    engine.getSubmission(data.searchString, "", data.whence).uri.spec;
-  yield waitForLoadAndStopIt(searchBrowser, submissionURL);
+  let msg = yield waitForTestMsg("loadStopped");
+  Assert.equal(msg.data.url, submissionURL, "Correct search page loaded");
 });
 
 add_task(function* badImage() {
   yield addTab();
   // If the bad image URI caused an exception to be thrown within ContentSearch,
   // then we'll hang waiting for the CurrentState responses triggered by the new
   // engine.  That's what we're testing, and obviously it shouldn't happen.
   let vals = yield waitForNewEngine("contentSearchBadImage.xml", 1);
@@ -326,43 +330,16 @@ function waitForNewEngine(basename, numI
       ok(false, "addEngine failed with error code " + errCode);
       addDeferred.reject();
     },
   });
 
   return Promise.all([addDeferred.promise].concat(eventPromises));
 }
 
-function waitForLoadAndStopIt(browser, expectedURL) {
-  let deferred = Promise.defer();
-  let listener = {
-    onStateChange: function (webProg, req, flags, status) {
-      if (req instanceof Ci.nsIChannel) {
-        let url = req.originalURI.spec;
-        info("onStateChange " + url);
-        let docStart = Ci.nsIWebProgressListener.STATE_IS_DOCUMENT |
-                       Ci.nsIWebProgressListener.STATE_START;
-        if ((flags & docStart) && webProg.isTopLevel && url == expectedURL) {
-          browser.removeProgressListener(listener);
-          ok(true, "Expected URL loaded");
-          req.cancel(Components.results.NS_ERROR_FAILURE);
-          deferred.resolve();
-        }
-      }
-    },
-    QueryInterface: XPCOMUtils.generateQI([
-      Ci.nsIWebProgressListener,
-      Ci.nsISupportsWeakReference,
-    ]),
-  };
-  browser.addProgressListener(listener);
-  info("Waiting for URL to load: " + expectedURL);
-  return deferred.promise;
-}
-
 function addTab() {
   let deferred = Promise.defer();
   let tab = gBrowser.addTab();
   gBrowser.selectedTab = tab;
   tab.linkedBrowser.addEventListener("load", function load() {
     tab.linkedBrowser.removeEventListener("load", load, true);
     let url = getRootDirectory(gTestPath) + TEST_CONTENT_SCRIPT_BASENAME;
     gMsgMan = tab.linkedBrowser.messageManager;
--- a/browser/modules/test/contentSearch.js
+++ b/browser/modules/test/contentSearch.js
@@ -18,9 +18,47 @@ content.addEventListener(SERVICE_EVENT_T
 
 // Forward messages from the test to the in-content service.
 addMessageListener(TEST_MSG, msg => {
   content.dispatchEvent(
     new content.CustomEvent(CLIENT_EVENT_TYPE, {
       detail: msg.data,
     })
   );
+
+  // If the message is a search, stop the page from loading and then tell the
+  // test that it loaded.
+  if (msg.data.type == "Search") {
+    waitForLoadAndStopIt(msg.data.expectedURL, url => {
+      sendAsyncMessage(TEST_MSG, {
+        type: "loadStopped",
+        url: url,
+      });
+    });
+  }
 });
+
+function waitForLoadAndStopIt(expectedURL, callback) {
+  let Ci = Components.interfaces;
+  let webProgress = docShell.QueryInterface(Ci.nsIInterfaceRequestor)
+                            .getInterface(Ci.nsIWebProgress);
+  let listener = {
+    onStateChange: function (webProg, req, flags, status) {
+      if (req instanceof Ci.nsIChannel) {
+        let url = req.originalURI.spec;
+        dump("waitForLoadAndStopIt: onStateChange " + url + "\n");
+        let docStart = Ci.nsIWebProgressListener.STATE_IS_DOCUMENT |
+                       Ci.nsIWebProgressListener.STATE_START;
+        if ((flags & docStart) && webProg.isTopLevel && url == expectedURL) {
+          webProgress.removeProgressListener(listener);
+          req.cancel(Components.results.NS_ERROR_FAILURE);
+          callback(url);
+        }
+      }
+    },
+    QueryInterface: XPCOMUtils.generateQI([
+      Ci.nsIWebProgressListener,
+      Ci.nsISupportsWeakReference,
+    ]),
+  };
+  webProgress.addProgressListener(listener, Ci.nsIWebProgress.NOTIFY_ALL);
+  dump("waitForLoadAndStopIt: Waiting for URL to load: " + expectedURL + "\n");
+}
--- a/browser/themes/shared/controlcenter/panel.inc.css
+++ b/browser/themes/shared/controlcenter/panel.inc.css
@@ -6,16 +6,17 @@
 
 /* Hide all conditional elements by default. */
 :-moz-any([when-connection],[when-mixedcontent],[when-ciphers],[when-loginforms]) {
   display: none;
 }
 
 /* Show the right elements for the right connection states. */
 #identity-popup[connection=not-secure] [when-connection~=not-secure],
+#identity-popup[connection=secure-cert-user-overridden] [when-connection~=secure-cert-user-overridden],
 #identity-popup[connection=secure-ev] [when-connection~=secure-ev],
 #identity-popup[connection=secure] [when-connection~=secure],
 #identity-popup[connection=chrome] [when-connection~=chrome],
 #identity-popup[connection=file] [when-connection~=file],
 /* Show insecure login forms messages when needed. */
 #identity-popup[loginforms=insecure] [when-loginforms=insecure],
 /* Show weak cipher messages when needed. */
 #identity-popup[ciphers=weak] [when-ciphers~=weak],
@@ -229,16 +230,21 @@
 /* Use [isbroken] to make sure we don't show a lock on an http page. See Bug 1192162. */
 #identity-popup[ciphers=weak] #identity-popup-securityView,
 #identity-popup[ciphers=weak] #identity-popup-security-content,
 #identity-popup[mixedcontent~=passive-loaded][isbroken] #identity-popup-securityView,
 #identity-popup[mixedcontent~=passive-loaded][isbroken] #identity-popup-security-content {
   background-image: url(chrome://browser/skin/controlcenter/conn-degraded.svg);
 }
 
+#identity-popup[connection=secure-cert-user-overridden] #identity-popup-securityView,
+#identity-popup[connection=secure-cert-user-overridden] #identity-popup-security-content {
+  background-image: url(chrome://browser/skin/identity-mixed-passive-loaded.svg);
+}
+
 #identity-popup[loginforms=insecure] #identity-popup-securityView,
 #identity-popup[loginforms=insecure] #identity-popup-security-content,
 #identity-popup[mixedcontent~=active-loaded][isbroken] #identity-popup-securityView,
 #identity-popup[mixedcontent~=active-loaded][isbroken] #identity-popup-security-content {
   background-image: url(chrome://browser/skin/controlcenter/mcb-disabled.svg);
 }
 
 #identity-popup-security-descriptions > description {
--- a/browser/themes/shared/customizableui/panelUIOverlay.inc.css
+++ b/browser/themes/shared/customizableui/panelUIOverlay.inc.css
@@ -193,16 +193,17 @@ panelmultiview[nosubviews=true] > .panel
 
 #PanelUI-popup > arrowscrollbox > autorepeatbutton {
   display: none;
 }
 #PanelUI-popup > arrowscrollbox > scrollbox {
   overflow: visible;
 }
 
+.cui-widget-panel > .panel-arrowcontainer > .panel-arrowcontent,
 #PanelUI-popup > .panel-arrowcontainer > .panel-arrowcontent {
   overflow: hidden;
 }
 
 #PanelUI-popup > .panel-arrowcontainer > .panel-arrowcontent,
 .cui-widget-panel > .panel-arrowcontainer > .panel-arrowcontent > .popup-internal-box {
   padding: 0;
 }
--- a/browser/themes/shared/identity-block/identity-block.inc.css
+++ b/browser/themes/shared/identity-block/identity-block.inc.css
@@ -161,16 +161,17 @@
 
 #urlbar[pageproxystate="valid"] > #identity-box.insecureLoginForms > #connection-icon,
 #urlbar[pageproxystate="valid"] > #identity-box.mixedActiveContent > #connection-icon {
   list-style-image: url(chrome://browser/skin/identity-mixed-active-loaded.svg);
   visibility: visible;
 }
 
 #urlbar[pageproxystate="valid"] > #identity-box.weakCipher > #connection-icon,
+#urlbar[pageproxystate="valid"] > #identity-box.certUserOverridden > #connection-icon,
 #urlbar[pageproxystate="valid"] > #identity-box.mixedDisplayContent > #connection-icon,
 #urlbar[pageproxystate="valid"] > #identity-box.mixedDisplayContentLoadedActiveBlocked > #connection-icon {
   list-style-image: url(chrome://browser/skin/identity-mixed-passive-loaded.svg);
   visibility: visible;
 }
 
 #urlbar[pageproxystate="valid"] > #identity-box.mixedActiveBlocked > #connection-icon {
   list-style-image: url(chrome://browser/skin/identity-mixed-active-blocked.svg);
--- a/browser/tools/mozscreenshots/head.js
+++ b/browser/tools/mozscreenshots/head.js
@@ -28,17 +28,16 @@ function shouldCapture() {
     ok(true, "MOZSCREENSHOTS_SETS was specified so only capture what was " +
        "requested (in browser_screenshots.js)");
     return false;
   }
 
   // Automation isn't able to schedule test jobs to only run on nightlies so we handle it here
   // (see also: bug 1116275).
   let capture = AppConstants.MOZ_UPDATE_CHANNEL == "nightly" ||
-                AppConstants.SOURCE_REVISION_URL == "" ||
-                AppConstants.SOURCE_REVISION_URL == "1"; // bug 1248027
+                AppConstants.SOURCE_REVISION_URL == "";
   if (!capture) {
     ok(true, "Capturing is disabled for this MOZ_UPDATE_CHANNEL or REPO");
   }
   return capture;
 }
 
 add_task(setup);
--- a/browser/tools/mozscreenshots/mozscreenshots/extension/TestRunner.jsm
+++ b/browser/tools/mozscreenshots/mozscreenshots/extension/TestRunner.jsm
@@ -39,17 +39,17 @@ XPCOMUtils.defineLazyGetter(this, "log",
 this.TestRunner = {
   combos: null,
   completedCombos: 0,
   currentComboIndex: 0,
   _lastCombo: null,
   _libDir: null,
 
   init(extensionPath) {
-    log.info("init");
+    log.debug("init");
     this._extensionPath = extensionPath;
   },
 
   /**
    * Load specified sets, execute all combinations of them, and capture screenshots.
    */
   start: Task.async(function*(setNames = null) {
     setNames = setNames || defaultSetNames;
--- a/devtools/client/animationinspector/animation-controller.js
+++ b/devtools/client/animationinspector/animation-controller.js
@@ -121,16 +121,17 @@ var getServerTraits = Task.async(functio
  * function onPlayers() {
  *   for (let player of AnimationsController.animationPlayers) {
  *     // do something with player
  *   }
  * }
  */
 var AnimationsController = {
   PLAYERS_UPDATED_EVENT: "players-updated",
+  ALL_ANIMATIONS_TOGGLED_EVENT: "all-animations-toggled",
 
   initialize: Task.async(function*() {
     if (this.initialized) {
       yield this.initialized.promise;
       return;
     }
     this.initialized = promise.defer();
 
@@ -250,17 +251,19 @@ var AnimationsController = {
   /**
    * Toggle (pause/play) all animations in the current target.
    */
   toggleAll: function() {
     if (!this.traits.hasToggleAll) {
       return promise.resolve();
     }
 
-    return this.animationsFront.toggleAll().catch(e => console.error(e));
+    return this.animationsFront.toggleAll()
+      .then(() => this.emit(this.ALL_ANIMATIONS_TOGGLED_EVENT, this))
+      .catch(e => console.error(e));
   },
 
   /**
    * Similar to toggleAll except that it only plays/pauses the currently known
    * animations (those listed in this.animationPlayers).
    * @param {Boolean} shouldPause True if the animations should be paused, false
    * if they should be played.
    * @return {Promise} Resolves when the playState has been changed.
--- a/devtools/client/animationinspector/animation-panel.js
+++ b/devtools/client/animationinspector/animation-panel.js
@@ -47,20 +47,20 @@ var AnimationsPanel = {
 
     // If the server doesn't support toggling all animations at once, hide the
     // whole global toolbar.
     if (!AnimationsController.traits.hasToggleAll) {
       $("#global-toolbar").style.display = "none";
     }
 
     // Binding functions that need to be called in scope.
-    for (let functionName of ["onPickerStarted", "onPickerStopped",
-      "refreshAnimationsUI", "toggleAll", "onTabNavigated",
-      "onTimelineDataChanged", "playPauseTimeline", "rewindTimeline",
-      "onRateChanged"]) {
+    for (let functionName of ["onKeyDown", "onPickerStarted",
+      "onPickerStopped", "refreshAnimationsUI", "onToggleAllClicked",
+      "onTabNavigated", "onTimelineDataChanged", "onTimelinePlayClicked",
+      "onTimelineRewindClicked", "onRateChanged"]) {
       this[functionName] = this[functionName].bind(this);
     }
     let hUtils = gToolbox.highlighterUtils;
     this.togglePicker = hUtils.togglePicker.bind(hUtils);
 
     this.animationsTimelineComponent = new AnimationsTimeline(gInspector);
     this.animationsTimelineComponent.init(this.playersEl);
 
@@ -109,19 +109,23 @@ var AnimationsPanel = {
   startListeners: function() {
     AnimationsController.on(AnimationsController.PLAYERS_UPDATED_EVENT,
       this.refreshAnimationsUI);
 
     this.pickerButtonEl.addEventListener("click", this.togglePicker);
     gToolbox.on("picker-started", this.onPickerStarted);
     gToolbox.on("picker-stopped", this.onPickerStopped);
 
-    this.toggleAllButtonEl.addEventListener("click", this.toggleAll);
-    this.playTimelineButtonEl.addEventListener("click", this.playPauseTimeline);
-    this.rewindTimelineButtonEl.addEventListener("click", this.rewindTimeline);
+    this.toggleAllButtonEl.addEventListener("click", this.onToggleAllClicked);
+    this.playTimelineButtonEl.addEventListener(
+      "click", this.onTimelinePlayClicked);
+    this.rewindTimelineButtonEl.addEventListener(
+      "click", this.onTimelineRewindClicked);
+
+    document.addEventListener("keydown", this.onKeyDown, false);
 
     gToolbox.target.on("navigate", this.onTabNavigated);
 
     this.animationsTimelineComponent.on("timeline-data-changed",
       this.onTimelineDataChanged);
 
     if (this.rateSelectorComponent) {
       this.rateSelectorComponent.on("rate-changed", this.onRateChanged);
@@ -131,30 +135,50 @@ var AnimationsPanel = {
   stopListeners: function() {
     AnimationsController.off(AnimationsController.PLAYERS_UPDATED_EVENT,
       this.refreshAnimationsUI);
 
     this.pickerButtonEl.removeEventListener("click", this.togglePicker);
     gToolbox.off("picker-started", this.onPickerStarted);
     gToolbox.off("picker-stopped", this.onPickerStopped);
 
-    this.toggleAllButtonEl.removeEventListener("click", this.toggleAll);
-    this.playTimelineButtonEl.removeEventListener("click", this.playPauseTimeline);
-    this.rewindTimelineButtonEl.removeEventListener("click", this.rewindTimeline);
+    this.toggleAllButtonEl.removeEventListener("click", this.onToggleAllClicked);
+    this.playTimelineButtonEl.removeEventListener(
+      "click", this.onTimelinePlayClicked);
+    this.rewindTimelineButtonEl.removeEventListener(
+      "click", this.onTimelineRewindClicked);
+
+    document.removeEventListener("keydown", this.onKeyDown, false);
 
     gToolbox.target.off("navigate", this.onTabNavigated);
 
     this.animationsTimelineComponent.off("timeline-data-changed",
       this.onTimelineDataChanged);
 
     if (this.rateSelectorComponent) {
       this.rateSelectorComponent.off("rate-changed", this.onRateChanged);
     }
   },
 
+  onKeyDown: function(event) {
+    let keyEvent = Ci.nsIDOMKeyEvent;
+
+    // If the space key is pressed, it should toggle the play state of
+    // the animations displayed in the panel, or of all the animations on
+    // the page if the selected node does not have any animation on it.
+    if (event.keyCode === keyEvent.DOM_VK_SPACE) {
+      if (AnimationsController.animationPlayers.length > 0) {
+        this.playPauseTimeline().catch(ex => console.error(ex));
+      } else {
+        this.toggleAll().catch(ex => console.error(ex));
+      }
+      event.preventDefault();
+    }
+  },
+
   togglePlayers: function(isVisible) {
     if (isVisible) {
       document.body.removeAttribute("empty");
       document.body.setAttribute("timeline", "true");
     } else {
       document.body.setAttribute("empty", "true");
       document.body.removeAttribute("timeline");
     }
@@ -163,52 +187,73 @@ var AnimationsPanel = {
   onPickerStarted: function() {
     this.pickerButtonEl.setAttribute("checked", "true");
   },
 
   onPickerStopped: function() {
     this.pickerButtonEl.removeAttribute("checked");
   },
 
+  onToggleAllClicked: function() {
+    this.toggleAll().catch(ex => console.error(ex));
+  },
+
+  /**
+   * Toggle (pause/play) all animations in the current target
+   * and update the UI the toggleAll button.
+   */
   toggleAll: Task.async(function*() {
     this.toggleAllButtonEl.classList.toggle("paused");
     yield AnimationsController.toggleAll();
   }),
 
+  onTimelinePlayClicked: function() {
+    this.playPauseTimeline().catch(ex => console.error(ex));
+  },
+
   /**
    * Depending on the state of the timeline either pause or play the animations
    * displayed in it.
    * If the animations are finished, this will play them from the start again.
    * If the animations are playing, this will pause them.
    * If the animations are paused, this will resume them.
+   *
+   * @return {Promise} Resolves when the playState is changed and the UI
+   * is refreshed
    */
   playPauseTimeline: function() {
-    AnimationsController.toggleCurrentAnimations(this.timelineData.isMoving)
-                        .then(() => this.refreshAnimationsStateAndUI())
-                        .catch(e => console.error(e));
+    return AnimationsController
+      .toggleCurrentAnimations(this.timelineData.isMoving)
+      .then(() => this.refreshAnimationsStateAndUI());
+  },
+
+  onTimelineRewindClicked: function() {
+    this.rewindTimeline().catch(ex => console.error(ex));
   },
 
   /**
    * Reset the startTime of all current animations shown in the timeline and
    * pause them.
+   *
+   * @return {Promise} Resolves when currentTime is set and the UI is refreshed
    */
   rewindTimeline: function() {
-    AnimationsController.setCurrentTimeAll(0, true)
-                        .then(() => this.refreshAnimationsStateAndUI())
-                        .catch(e => console.error(e));
+    return AnimationsController
+      .setCurrentTimeAll(0, true)
+      .then(() => this.refreshAnimationsStateAndUI());
   },
 
   /**
    * Set the playback rate of all current animations shown in the timeline to
    * the value of this.rateSelectorEl.
    */
   onRateChanged: function(e, rate) {
     AnimationsController.setPlaybackRateAll(rate)
                         .then(() => this.refreshAnimationsStateAndUI())
-                        .catch(e => console.error(e));
+                        .catch(ex => console.error(ex));
   },
 
   onTabNavigated: function() {
     this.toggleAllButtonEl.classList.remove("paused");
   },
 
   onTimelineDataChanged: function(e, data) {
     this.timelineData = data;
--- a/devtools/client/animationinspector/test/browser.ini
+++ b/devtools/client/animationinspector/test/browser.ini
@@ -26,16 +26,18 @@ skip-if = os == "linux" && !debug # Bug 
 [browser_animation_playerWidgets_target_nodes.js]
 [browser_animation_refresh_on_added_animation.js]
 [browser_animation_refresh_on_removed_animation.js]
 skip-if = os == "linux" && !debug # Bug 1227792
 [browser_animation_refresh_when_active.js]
 [browser_animation_running_on_compositor.js]
 [browser_animation_same_nb_of_playerWidgets_and_playerFronts.js]
 [browser_animation_shows_player_on_valid_node.js]
+[browser_animation_spacebar_toggles_animations.js]
+[browser_animation_spacebar_toggles_node_animations.js]
 [browser_animation_target_highlight_select.js]
 [browser_animation_target_highlighter_lock.js]
 [browser_animation_timeline_currentTime.js]
 [browser_animation_timeline_header.js]
 [browser_animation_timeline_pause_button.js]
 skip-if = os == "linux" && bits == 32 # Bug 1220974
 [browser_animation_timeline_rate_selector.js]
 [browser_animation_timeline_rewind_button.js]
new file mode 100644
--- /dev/null
+++ b/devtools/client/animationinspector/test/browser_animation_spacebar_toggles_animations.js
@@ -0,0 +1,43 @@
+/* vim: set ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test that the spacebar key press toggles the toggleAll button state
+// when a node with no animation is selected.
+// This test doesn't need to test if animations actually pause/resume
+// because there's an other test that does this :
+// browser_animation_toggle_button_toggles_animation.js
+
+add_task(function*() {
+  yield addTab(TEST_URL_ROOT + "doc_simple_animation.html");
+  let {panel, inspector, window, controller} = yield openAnimationInspector();
+  let {toggleAllButtonEl} = panel;
+
+  // select a node without animations
+  yield selectNode(".still", inspector);
+
+  // ensure the focus is on the animation panel
+  window.focus();
+
+  info("Simulate spacebar stroke and check toggleAll button" +
+       " is in paused state");
+
+  // sending the key will lead to a ALL_ANIMATIONS_TOGGLED_EVENT
+  let onToggled = once(controller, controller.ALL_ANIMATIONS_TOGGLED_EVENT);
+  EventUtils.sendKey("SPACE", window);
+  yield onToggled;
+  ok(toggleAllButtonEl.classList.contains("paused"),
+   "The toggle all button is in its paused state");
+
+  info("Simulate spacebar stroke and check toggleAll button" +
+       " is in playing state");
+
+  // sending the key will lead to a ALL_ANIMATIONS_TOGGLED_EVENT
+  onToggled = once(controller, controller.ALL_ANIMATIONS_TOGGLED_EVENT);
+  EventUtils.sendKey("SPACE", window);
+  yield onToggled;
+  ok(!toggleAllButtonEl.classList.contains("paused"),
+   "The toggle all button is in its playing state again");
+});
new file mode 100644
--- /dev/null
+++ b/devtools/client/animationinspector/test/browser_animation_spacebar_toggles_node_animations.js
@@ -0,0 +1,40 @@
+/* vim: set ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test that the spacebar key press toggles the play/resume button state.
+// This test doesn't need to test if animations actually pause/resume
+// because there's an other test that does this.
+// There are animations in the test page and since, by default, the <body> node
+// is selected, animations will be displayed in the timeline, so the timeline
+// play/resume button will be displayed
+add_task(function*() {
+  yield addTab(TEST_URL_ROOT + "doc_simple_animation.html");
+  let {panel, window} = yield openAnimationInspector();
+  let {playTimelineButtonEl} = panel;
+
+  // ensure the focus is on the animation panel
+  window.focus();
+
+  info("Simulate spacebar stroke and check playResume button" +
+       " is in paused state");
+
+  // sending the key will lead to a UI_UPDATE_EVENT
+  let onUpdated = panel.once(panel.UI_UPDATED_EVENT);
+  EventUtils.sendKey("SPACE", window);
+  yield onUpdated;
+  ok(playTimelineButtonEl.classList.contains("paused"),
+    "The play/resume button is in its paused state");
+
+  info("Simulate spacebar stroke and check playResume button" +
+       " is in playing state");
+
+  // sending the key will lead to a UI_UPDATE_EVENT
+  onUpdated = panel.once(panel.UI_UPDATED_EVENT);
+  EventUtils.sendKey("SPACE", window);
+  yield onUpdated;
+  ok(!playTimelineButtonEl.classList.contains("paused"),
+    "The play/resume button is in its play state again");
+});
rename from devtools/client/devtools-clhandler.js
rename to devtools/client/devtools-startup.js
--- a/devtools/client/devtools-clhandler.js
+++ b/devtools/client/devtools-startup.js
@@ -1,92 +1,111 @@
 /* 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/. */
 
 /* FIXME: remove this globals comment and replace with import-globals-from when
    bug 1242893 is fixed */
 /* globals BrowserToolboxProcess */
 
+/**
+ * This XPCOM component is loaded very early.
+ * It handles command line arguments like -jsconsole, but also ensures starting
+ * core modules like devtools/devtools-browser that listen for application
+ * startup.
+ *
+ * Be careful to lazy load dependencies as much as possible.
+ **/
+
 "use strict";
 
 const { interfaces: Ci, utils: Cu } = Components;
 const kDebuggerPrefs = [
   "devtools.debugger.remote-enabled",
   "devtools.chrome.enabled"
 ];
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "Services", "resource://gre/modules/Services.jsm");
 
-function devtoolsCommandlineHandler() {}
+function DevToolsStartup() {}
 
-devtoolsCommandlineHandler.prototype = {
+DevToolsStartup.prototype = {
   handle: function(cmdLine) {
     let consoleFlag = cmdLine.handleFlag("jsconsole", false);
     let debuggerFlag = cmdLine.handleFlag("jsdebugger", false);
     let devtoolsFlag = cmdLine.handleFlag("devtools", false);
 
     if (consoleFlag) {
       this.handleConsoleFlag(cmdLine);
     }
     if (debuggerFlag) {
       this.handleDebuggerFlag(cmdLine);
     }
-    if (devtoolsFlag) {
-      this.handleDevToolsFlag();
-    }
     let debuggerServerFlag;
     try {
       debuggerServerFlag =
         cmdLine.handleFlagWithParam("start-debugger-server", false);
     } catch (e) {
       // We get an error if the option is given but not followed by a value.
       // By catching and trying again, the value is effectively optional.
       debuggerServerFlag = cmdLine.handleFlag("start-debugger-server", false);
     }
     if (debuggerServerFlag) {
       this.handleDebuggerServerFlag(cmdLine, debuggerServerFlag);
     }
+
+    let onStartup = function(window) {
+      Services.obs.removeObserver(onStartup,
+                                  "browser-delayed-startup-finished");
+      // Ensure loading core module once firefox is ready
+      this.initDevTools();
+
+      if (devtoolsFlag) {
+        this.handleDevToolsFlag(window);
+      }
+    }.bind(this);
+    Services.obs.addObserver(onStartup, "browser-delayed-startup-finished",
+                             false);
+  },
+
+  initDevTools: function() {
+    let { require } = Cu.import("resource://devtools/shared/Loader.jsm", {});
+    // Ensure loading main devtools module that hooks up into browser UI
+    // and initialize all devtools machinery.
+    // browser.xul or main top-level document used to load this module,
+    // but this code may be called without/before it.
+    require("devtools/client/framework/devtools-browser");
   },
 
   handleConsoleFlag: function(cmdLine) {
     let window = Services.wm.getMostRecentWindow("devtools:webconsole");
     if (!window) {
+      this.initDevTools();
+
       let { require } = Cu.import("resource://devtools/shared/Loader.jsm", {});
-      // Ensure loading main devtools module that hooks up into browser UI
-      // and initialize all devtools machinery.
-      // browser.xul or main top-level document used to load this module,
-      // but this code may be called without/before it.
-      // Bug 1247203 should ease handling this.
-      require("devtools/client/framework/devtools-browser");
-
       let hudservice = require("devtools/client/webconsole/hudservice");
       let { console } = Cu.import("resource://gre/modules/Console.jsm", {});
       hudservice.toggleBrowserConsole().then(null, console.error);
     } else {
       // the Browser Console was already open
       window.focus();
     }
 
     if (cmdLine.state == Ci.nsICommandLine.STATE_REMOTE_AUTO) {
       cmdLine.preventDefault = true;
     }
   },
 
   // Open the toolbox on the selected tab once the browser starts up.
-  handleDevToolsFlag: function() {
-    Services.obs.addObserver(function onStartup(window) {
-      Services.obs.removeObserver(onStartup,
-                                  "browser-delayed-startup-finished");
-      const {require} = Cu.import("resource://devtools/shared/Loader.jsm", {});
-      const {gDevTools} = require("devtools/client/framework/devtools");
-      const {TargetFactory} = require("devtools/client/framework/target");
-      let target = TargetFactory.forTab(window.gBrowser.selectedTab);
-      gDevTools.showToolbox(target);
-    }, "browser-delayed-startup-finished", false);
+  handleDevToolsFlag: function(window) {
+    const {require} = Cu.import("resource://devtools/shared/Loader.jsm", {});
+    const {gDevTools} = require("devtools/client/framework/devtools");
+    const {TargetFactory} = require("devtools/client/framework/target");
+    let target = TargetFactory.forTab(window.gBrowser.selectedTab);
+    gDevTools.showToolbox(target);
   },
 
   _isRemoteDebuggingEnabled() {
     let remoteDebuggingEnabled = false;
     try {
       remoteDebuggingEnabled = kDebuggerPrefs.every(pref => {
         return Services.prefs.getBoolPref(pref);
       });
@@ -163,9 +182,9 @@ devtoolsCommandlineHandler.prototype = {
             "Start the debugger server on a TCP port or " +
             "Unix domain socket path.  Defaults to TCP port 6000.\n",
 
   classID: Components.ID("{9e9a9283-0ce9-4e4a-8f1c-ba129a032c32}"),
   QueryInterface: XPCOMUtils.generateQI([Ci.nsICommandLineHandler]),
 };
 
 this.NSGetFactory = XPCOMUtils.generateNSGetFactory(
-  [devtoolsCommandlineHandler]);
+  [DevToolsStartup]);
rename from devtools/client/devtools-clhandler.manifest
rename to devtools/client/devtools-startup.manifest
--- a/devtools/client/devtools-clhandler.manifest
+++ b/devtools/client/devtools-startup.manifest
@@ -1,2 +1,2 @@
-component {9e9a9283-0ce9-4e4a-8f1c-ba129a032c32} devtools-clhandler.js
+component {9e9a9283-0ce9-4e4a-8f1c-ba129a032c32} devtools-startup.js
 contract @mozilla.org/toolkit/console-clh;1 {9e9a9283-0ce9-4e4a-8f1c-ba129a032c32}
--- a/devtools/client/framework/devtools-browser.js
+++ b/devtools/client/framework/devtools-browser.js
@@ -1,14 +1,22 @@
 /* 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/. */
 
 "use strict";
 
+/**
+ * This is the main module loaded in Firefox desktop that handles browser
+ * windows and coordinates devtools around each window.
+ *
+ * This module is loaded lazily by devtools-clhandler.js, once the first
+ * browser window is ready (i.e. fired browser-delayed-startup-finished event)
+ **/
+
 const {Cc, Ci, Cu} = require("chrome");
 const Services = require("Services");
 const promise = require("promise");
 const {gDevTools} = require("./devtools");
 
 // Load target and toolbox lazily as they need gDevTools to be fully initialized
 loader.lazyRequireGetter(this, "TargetFactory", "devtools/client/framework/target", true);
 loader.lazyRequireGetter(this, "Toolbox", "devtools/client/framework/toolbox", true);
@@ -111,20 +119,27 @@ var gDevToolsBrowser = exports.gDevTools
     let consoleEnabled = Services.prefs.getBoolPref("devtools.errorconsole.enabled");
     toggleCmd("Tools:ErrorConsole", consoleEnabled);
 
     // Enable DevTools connection screen, if the preference allows this.
     toggleCmd("Tools:DevToolsConnect", devtoolsRemoteEnabled);
   },
 
   observe: function(subject, topic, prefName) {
-    if (prefName.endsWith("enabled")) {
-      for (let win of this._trackedBrowserWindows) {
-        this.updateCommandAvailability(win);
-      }
+    switch (topic) {
+      case "browser-delayed-startup-finished":
+        this._registerBrowserWindow(subject);
+        break;
+      case "nsPref:changed":
+        if (prefName.endsWith("enabled")) {
+          for (let win of this._trackedBrowserWindows) {
+            this.updateCommandAvailability(win);
+          }
+        }
+        break;
     }
   },
 
   _prefObserverRegistered: false,
 
   ensurePrefObserver: function() {
     if (!this._prefObserverRegistered) {
       this._prefObserverRegistered = true;
@@ -314,21 +329,21 @@ var gDevToolsBrowser = exports.gDevTools
   },
 
   /**
    * Add this DevTools's presence to a browser window's document
    *
    * @param {XULDocument} doc
    *        The document to which menuitems and handlers are to be added
    */
-  // Used by browser.js
-  registerBrowserWindow: function DT_registerBrowserWindow(win) {
+  _registerBrowserWindow: function(win) {
     this.updateCommandAvailability(win);
     this.ensurePrefObserver();
     gDevToolsBrowser._trackedBrowserWindows.add(win);
+    win.addEventListener("unload", this);
     gDevToolsBrowser._addAllToolsToMenu(win.document);
 
     if (this._isFirebugInstalled()) {
       let broadcaster = win.document.getElementById("devtoolsMenuBroadcaster_DevToolbox");
       broadcaster.removeAttribute("key");
     }
 
     let tabContainer = win.gBrowser.tabContainer;
@@ -744,18 +759,19 @@ var gDevToolsBrowser = exports.gDevTools
 
   /**
    * Called on browser unload to remove menu entries, toolboxes and event
    * listeners from the closed browser window.
    *
    * @param  {XULWindow} win
    *         The window containing the menu entry
    */
-  forgetBrowserWindow: function DT_forgetBrowserWindow(win) {
+  _forgetBrowserWindow: function(win) {
     gDevToolsBrowser._trackedBrowserWindows.delete(win);
+    win.removeEventListener("unload", this);
 
     // Destroy toolboxes for closed window
     for (let [target, toolbox] of gDevTools._toolboxes) {
       if (toolbox.frame && toolbox.frame.ownerDocument.defaultView == win) {
         toolbox.destroy();
       }
     }
 
@@ -787,25 +803,35 @@ var gDevToolsBrowser = exports.gDevTools
 
         this._tabStats.histOpen.push(open);
         this._tabStats.histPinned.push(pinned);
         this._tabStats.peakOpen = Math.max(open, this._tabStats.peakOpen);
         this._tabStats.peakPinned = Math.max(pinned, this._tabStats.peakPinned);
       break;
       case "TabSelect":
         gDevToolsBrowser._updateMenuCheckbox();
+      break;
+      case "unload":
+        // top-level browser window unload
+        gDevToolsBrowser._forgetBrowserWindow(event.target.defaultView);
+      break;
     }
   },
 
   /**
    * All browser windows have been closed, tidy up remaining objects.
    */
   destroy: function() {
     Services.prefs.removeObserver("devtools.", gDevToolsBrowser);
+    Services.obs.removeObserver(gDevToolsBrowser, "browser-delayed-startup-finished");
     Services.obs.removeObserver(gDevToolsBrowser.destroy, "quit-application");
+
+    for (let win of gDevToolsBrowser._trackedBrowserWindows) {
+      gDevToolsBrowser._forgetBrowserWindow(win);
+    }
   },
 }
 
 gDevTools.on("tool-registered", function(ev, toolId) {
   let toolDefinition = gDevTools._tools.get(toolId);
   gDevToolsBrowser._addToolToWindows(toolDefinition);
 });
 
@@ -815,14 +841,24 @@ gDevTools.on("tool-unregistered", functi
   }
   gDevToolsBrowser._removeToolFromWindows(toolId);
 });
 
 gDevTools.on("toolbox-ready", gDevToolsBrowser._updateMenuCheckbox);
 gDevTools.on("toolbox-destroyed", gDevToolsBrowser._updateMenuCheckbox);
 
 Services.obs.addObserver(gDevToolsBrowser.destroy, "quit-application", false);
+Services.obs.addObserver(gDevToolsBrowser, "browser-delayed-startup-finished", false);
+// Fake end of browser window load event for all already opened windows
+// that is already fully loaded.
+let enumerator = Services.wm.getEnumerator("navigator:browser");
+while (enumerator.hasMoreElements()) {
+  let win = enumerator.getNext();
+  if (win.gBrowserInit && win.gBrowserInit.delayedStartupFinished) {
+    gDevToolsBrowser._registerBrowserWindow(win);
+  }
+}
 
 // Load the browser devtools main module as the loader's main module.
 // This is done precisely here as main.js ends up dispatching the
 // tool-registered events we are listening in this module.
 loader.main("devtools/client/main");
 
--- a/devtools/client/framework/toolbox.xul
+++ b/devtools/client/framework/toolbox.xul
@@ -134,17 +134,17 @@
       <hbox id="toolbox-option-container"/>
       <hbox id="toolbox-controls">
         <hbox id="toolbox-dock-buttons"/>
         <toolbarbutton id="toolbox-close"
                        class="devtools-closebutton"
                        tooltiptext="&toolboxCloseButton.tooltip;"/>
       </hbox>
     </toolbar>
-    <vbox flex="1">
+    <vbox flex="1" class="theme-body">
       <!-- Set large flex to allow the toolbox-panel-webconsole to have a
            height set to a small value without flexing to fill up extra
            space. There must be a flex on both to ensure that the console
            panel itself is sized properly -->
       <deck id="toolbox-deck" flex="1000" minheight="75" />
       <splitter id="toolbox-console-splitter" class="devtools-horizontal-splitter" hidden="true" />
       <box minheight="75" flex="1" id="toolbox-panel-webconsole" collapsed="true" />
     </vbox>
--- a/devtools/client/moz.build
+++ b/devtools/client/moz.build
@@ -39,18 +39,18 @@ DIRS += [
     'webide',
 ]
 
 # Shim old theme paths used by DevTools add-ons
 if CONFIG['MOZ_BUILD_APP'] == 'browser':
     DIRS += ['themes/shims']
 
 EXTRA_COMPONENTS += [
-    'devtools-clhandler.js',
-    'devtools-clhandler.manifest',
+    'devtools-startup.js',
+    'devtools-startup.manifest',
 ]
 
 JAR_MANIFESTS += ['jar.mn']
 
 DevToolsModules(
     'definitions.js',
     'main.js',
 )
--- a/devtools/client/themes/floating-scrollbars-dark-theme.css
+++ b/devtools/client/themes/floating-scrollbars-dark-theme.css
@@ -22,34 +22,25 @@ xul|scrollbar[orient="vertical"] {
 }
 
 xul|scrollbar[orient="horizontal"] {
   margin-top: -10px;
   min-height: 10px;
   max-height: 10px;
 }
 
-:root[platform="mac"] xul|slider {
-  -moz-appearance: none !important;
-}
-
-:root[platform="mac"] xul|thumb {
-  -moz-appearance: none !important;
-  border-radius: 3px;
-}
-
 xul|scrollbar xul|thumb {
   background-color: rgba(170,170,170,0.2) !important;
-}
-
-:root[platform="win"] xul|thumb,
-:root[platform="linux"] xul|thumb {
   -moz-appearance: none !important;
   border-width: 0px !important;
   border-radius: 3px !important;
 }
 
+:root[platform="mac"] xul|slider {
+  -moz-appearance: none !important;
+}
+
 :root[platform="win"] xul|scrollbar xul|scrollbarbutton,
 :root[platform="linux"] xul|scrollbar xul|scrollbarbutton,
 :root[platform="win"] xul|scrollbar xul|gripper,
 :root[platform="linux"] xul|scrollbar xul|gripper {
   display: none;
 }
--- a/devtools/client/themes/toolbars.css
+++ b/devtools/client/themes/toolbars.css
@@ -827,17 +827,17 @@
 }
 
 .devtools-tab {
   -moz-appearance: none;
   -moz-binding: url("chrome://global/content/bindings/general.xml#control-item");
   -moz-box-align: center;
   min-width: 32px;
   min-height: 24px;
-  max-width: 95px;
+  max-width: 100px;
   margin: 0;
   padding: 0;
   border-style: solid;
   border-width: 0;
   -moz-border-start-width: 1px;
   -moz-box-align: center;
 }
 
--- a/devtools/client/webconsole/console-output.js
+++ b/devtools/client/webconsole/console-output.js
@@ -1344,17 +1344,17 @@ Messages.Extended.prototype = Heritage.e
 Messages.JavaScriptEvalOutput = function(evalResponse, errorMessage)
 {
   let severity = "log", msg, quoteStrings = true;
 
   // Store also the response packet from the back end. It might
   // be useful to extensions customizing the console output.
   this.response = evalResponse;
 
-  if (errorMessage) {
+  if (typeof(errorMessage) !== "undefined") {
     severity = "error";
     msg = errorMessage;
     quoteStrings = false;
   } else {
     msg = evalResponse.result;
   }
 
   let options = {
--- a/devtools/client/webconsole/jsterm.js
+++ b/devtools/client/webconsole/jsterm.js
@@ -303,16 +303,21 @@ JSTerm.prototype = {
       return;
     }
     if (response.error) {
       Cu.reportError("Evaluation error " + response.error + ": " +
                      response.message);
       return;
     }
     let errorMessage = response.exceptionMessage;
+    // Wrap thrown strings in Error objects, so `throw "foo"` outputs
+    // "Error: foo"
+    if (typeof(response.exception) === "string") {
+      errorMessage = new Error(errorMessage).toString();
+    }
     let result = response.result;
     let helperResult = response.helperResult;
     let helperHasRawOutput = !!(helperResult || {}).rawOutput;
 
     if (helperResult && helperResult.type) {
       switch (helperResult.type) {
         case "clearOutput":
           this.clearOutput();
--- a/devtools/client/webconsole/test/browser_webconsole_exception_stackframe.js
+++ b/devtools/client/webconsole/test/browser_webconsole_exception_stackframe.js
@@ -6,17 +6,17 @@
 "use strict";
 
 // Test that the console receive exceptions include a stackframe.
 // See bug 1184172.
 
 // On e10s, the exception is triggered in child process
 // and is ignored by test harness
 if (!Services.appinfo.browserTabsRemoteAutostart) {
-  expectUncaughtException();
+  SimpleTest.ignoreAllUncaughtExceptions();
 }
 
 function test() {
   let hud;
 
   const TEST_URI = "http://example.com/browser/devtools/client/webconsole/" +
                    "test/test-exception-stackframe.html";
   const TEST_FILE = TEST_URI.substr(TEST_URI.lastIndexOf("/"));
@@ -44,16 +44,47 @@ function test() {
     let results = yield waitForMessages({
       webconsole: hud,
       messages: [{
         text: "nonExistingMethodCall is not defined",
         category: CATEGORY_JS,
         severity: SEVERITY_ERROR,
         collapsible: true,
         stacktrace: stack,
+      }, {
+        text: "An invalid or illegal string was specified",
+        category: CATEGORY_JS,
+        severity: SEVERITY_ERROR,
+        collapsible: true,
+        stacktrace: [{
+          file: TEST_FILE,
+            fn: "domAPI",
+            line: 25,
+          }, {
+            file: TEST_FILE,
+            fn: "onLoadDomAPI",
+            line: 33,
+          }
+        ]
+      }, {
+        text: "DOMException",
+        category: CATEGORY_JS,
+        severity: SEVERITY_ERROR,
+        collapsible: true,
+        stacktrace: [{
+            file: TEST_FILE,
+            fn: "domException",
+            line: 29,
+          }, {
+            file: TEST_FILE,
+            fn: "onLoadDomException",
+            line: 36,
+          },
+
+        ]
       }],
     });
 
     let elem = [...results[0].matched][0];
     ok(elem, "message element");
 
     let msg = elem._messageObject;
     ok(msg, "message object");
--- a/devtools/client/webconsole/test/browser_webconsole_jsterm.js
+++ b/devtools/client/webconsole/test/browser_webconsole_jsterm.js
@@ -137,9 +137,33 @@ function* testJSTerm(hud) {
   // check that an evaluated null produces "null", bug 650780
   jsterm.clearOutput();
   yield jsterm.execute("null");
   yield checkResult("null", "null is null");
 
   jsterm.clearOutput();
   yield jsterm.execute("undefined");
   yield checkResult("undefined", "undefined is printed");
+
+  // check that thrown strings produce error messages,
+  // and the message text matches that of a stringified error object
+  // bug 1099071
+  jsterm.clearOutput();
+  yield jsterm.execute("throw '';");
+  yield checkResult((node) => {
+    return node.parentNode.getAttribute("severity") === "error" &&
+      node.textContent === new Error("").toString();
+  }, "thrown empty string generates error message");
+
+  jsterm.clearOutput();
+  yield jsterm.execute("throw 'tomatoes';");
+  yield checkResult((node) => {
+    return node.parentNode.getAttribute("severity") === "error" &&
+      node.textContent === new Error("tomatoes").toString();
+  }, "thrown non-empty string generates error message");
+
+  jsterm.clearOutput();
+  yield jsterm.execute("throw { foo: 'bar' };");
+  yield checkResult((node) => {
+    return node.parentNode.getAttribute("severity") === "error" &&
+      node.textContent === Object.prototype.toString();
+  }, "thrown object generates error message");
 }
--- a/devtools/client/webconsole/test/test-exception-stackframe.html
+++ b/devtools/client/webconsole/test/test-exception-stackframe.html
@@ -16,15 +16,28 @@
       var secondCall = function () {
         thirdCall();
       }
 
       function thirdCall() {
         nonExistingMethodCall();
       }
 
-      window.onload = firstCall;
+      function domAPI() {
+        document.querySelector("buggy;selector");
+      }
+
+      function domException() {
+        throw new DOMException("DOMException");
+      }
+      window.addEventListener("load", firstCall);
+      window.addEventListener("load", function onLoadDomAPI() {
+        domAPI();
+      });
+      window.addEventListener("load", function onLoadDomException() {
+        domException();
+      });
     </script>
   </head>
   <body>
     <p>Hello world!</p>
   </body>
 </html>
--- a/devtools/client/webconsole/webconsole.js
+++ b/devtools/client/webconsole/webconsole.js
@@ -1,10 +1,10 @@
 /* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
-/* vim: set ft= javascript ts=2 et sw=2 tw=80: */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
 /* 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/. */
 
 "use strict";
 
 const {Cc, Ci, Cu} = require("chrome");
 
--- a/devtools/server/actors/script.js
+++ b/devtools/server/actors/script.js
@@ -2,17 +2,17 @@
 /* vim: set ft=javascript ts=2 et sw=2 tw=80: */
 /* 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/. */
 
 "use strict";
 
 const Services = require("Services");
-const { Cc, Ci, Cu, components, ChromeWorker } = require("chrome");
+const { Cc, Ci, Cu, Cr, components, ChromeWorker } = require("chrome");
 const { ActorPool, OriginalLocation, GeneratedLocation } = require("devtools/server/actors/common");
 const { BreakpointActor } = require("devtools/server/actors/breakpoint");
 const { FrameActor } = require("devtools/server/actors/frame");
 const { ObjectActor, createValueGrip, longStringGrip } = require("devtools/server/actors/object");
 const { DebuggerServer } = require("devtools/server/main");
 const DevToolsUtils = require("devtools/shared/DevToolsUtils");
 const { assert, dumpn, update, fetch } = DevToolsUtils;
 const { dirname, joinURI } = require("devtools/shared/path");
@@ -1841,20 +1841,27 @@ ThreadActor.prototype = {
         break;
       }
     }
 
     if (willBeCaught && this._options.ignoreCaughtExceptions) {
       return undefined;
     }
 
+    // NS_ERROR_NO_INTERFACE exceptions are a special case in browser code,
+    // since they're almost always thrown by QueryInterface functions, and
+    // handled cleanly by native code.
+    if (aValue == Cr.NS_ERROR_NO_INTERFACE) {
+      return undefined;
+    }
+
     const generatedLocation = this.sources.getFrameLocation(aFrame);
-    const { sourceActor } = this.unsafeSynchronize(this.sources.getOriginalLocation(
+    const { originalSourceActor } = this.unsafeSynchronize(this.sources.getOriginalLocation(
       generatedLocation));
-    const url = sourceActor ? sourceActor.url : null;
+    const url = originalSourceActor ? originalSourceActor.url : null;
 
     if (this.sources.isBlackBoxed(url)) {
       return undefined;
     }
 
     try {
       let packet = this._paused(aFrame);
       if (!packet) {
--- a/devtools/server/tests/unit/test_blackboxing-05.js
+++ b/devtools/server/tests/unit/test_blackboxing-05.js
@@ -68,17 +68,17 @@ function test_black_box_exception() {
     do_check_true(!error, "Should not get an error: " + error);
     let sourceClient = gThreadClient.source(sources.filter(s => s.url == BLACK_BOXED_URL)[0]);
 
     sourceClient.blackBox(function ({error}) {
       do_check_true(!error, "Should not get an error: " + error);
       gThreadClient.pauseOnExceptions(true);
 
       gClient.addOneTimeListener("paused", function (aEvent, aPacket) {
-        do_check_neq(aPacket.frame.where.url, BLACK_BOXED_URL,
-                     "We shouldn't pause while in the black boxed source.");
+        do_check_eq(aPacket.frame.where.source.url, SOURCE_URL,
+                    "We shouldn't pause while in the black boxed source.");
         finishClient(gClient);
       });
 
       gThreadClient.resume();
     });
   });
 }
copy from devtools/server/tests/unit/test_pause_exceptions-02.js
copy to devtools/server/tests/unit/test_ignore_no_interface_exceptions.js
--- a/devtools/server/tests/unit/test_pause_exceptions-02.js
+++ b/devtools/server/tests/unit/test_ignore_no_interface_exceptions.js
@@ -1,27 +1,28 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 /**
- * Test that setting pauseOnExceptions to true when the debugger isn't in a
- * paused state will cause the debuggee to pause when an exceptions is thrown.
+ * Test that the debugger automatically ignores NS_ERROR_NO_INTERFACE
+ * exceptions, but not normal ones.
  */
 
+
 var gDebuggee;
 var gClient;
 var gThreadClient;
 
 function run_test()
 {
   initTestDebuggerServer();
-  gDebuggee = addTestGlobal("test-stack");
+  gDebuggee = addTestGlobal("test-no-interface");
   gClient = new DebuggerClient(DebuggerServer.connectPipe());
   gClient.connect().then(function() {
-    attachTestTabAndResume(gClient, "test-stack", function(aResponse, aTabClient, aThreadClient) {
+    attachTestTabAndResume(gClient, "test-no-interface", function(aResponse, aTabClient, aThreadClient) {
       gThreadClient = aThreadClient;
       test_pause_frame();
     });
   });
   do_test_pending();
 }
 
 function test_pause_frame()
@@ -31,17 +32,23 @@ function test_pause_frame()
       do_check_eq(aPacket.why.type, "exception");
       do_check_eq(aPacket.why.exception, 42);
       gThreadClient.resume(function () {
         finishClient(gClient);
       });
     });
 
     gDebuggee.eval("(" + function() {
+      function QueryInterface() {
+        throw Components.results.NS_ERROR_NO_INTERFACE;
+      }
       function stopMe() {
         throw 42;
       };
       try {
+        QueryInterface();
+      } catch (e) {}
+      try {
         stopMe();
       } catch (e) {}
     } + ")()");
   });
 }
--- a/devtools/server/tests/unit/xpcshell.ini
+++ b/devtools/server/tests/unit/xpcshell.ini
@@ -234,16 +234,17 @@ reason = bug 820380
 [test_profiler_events-01.js]
 [test_profiler_events-02.js]
 [test_profiler_getbufferinfo.js]
 [test_profiler_getfeatures.js]
 [test_profiler_getsharedlibraryinformation.js]
 [test_unsafeDereference.js]
 [test_add_actors.js]
 [test_ignore_caught_exceptions.js]
+[test_ignore_no_interface_exceptions.js]
 [test_requestTypes.js]
 reason = bug 937197
 [test_layout-reflows-observer.js]
 [test_protocolSpec.js]
 [test_registerClient.js]
 [test_client_request.js]
 [test_monitor_actor.js]
 [test_symbols-01.js]
--- a/dom/base/nsJSEnvironment.cpp
+++ b/dom/base/nsJSEnvironment.cpp
@@ -47,16 +47,18 @@
 #include "nsIArray.h"
 #include "nsIObjectInputStream.h"
 #include "nsIObjectOutputStream.h"
 #include "prmem.h"
 #include "WrapperFactory.h"
 #include "nsGlobalWindow.h"
 #include "nsScriptNameSpaceManager.h"
 #include "mozilla/AutoRestore.h"
+#include "mozilla/dom/DOMException.h"
+#include "mozilla/dom/DOMExceptionBinding.h"
 #include "mozilla/dom/ErrorEvent.h"
 #include "nsAXPCNativeCallContext.h"
 #include "mozilla/CycleCollectedJSRuntime.h"
 
 #include "nsJSPrincipals.h"
 
 #ifdef XP_MACOSX
 // AssertMacros.h defines 'check' and conflicts with AccessCheck.h
@@ -218,16 +220,51 @@ static const int32_t kPokesBetweenExpens
 
 static const char*
 ProcessNameForCollectorLog()
 {
   return XRE_GetProcessType() == GeckoProcessType_Default ?
     "default" : "content";
 }
 
+// This handles JS Exceptions (via ExceptionStackOrNull), as well as DOM and XPC Exceptions.
+//
+// Note that the returned object is _not_ wrapped into the compartment of cx.
+static JSObject*
+FindExceptionStack(JSContext* cx, JS::HandleObject exceptionObject)
+{
+  JSAutoCompartment ac(cx, exceptionObject);
+  JS::RootedObject stackObject(cx, ExceptionStackOrNull(cx, exceptionObject));
+  if (stackObject) {
+    return stackObject;
+  }
+
+  // It is not a JS Exception, try DOM Exception.
+  RefPtr<Exception> exception;
+  UNWRAP_OBJECT(DOMException, exceptionObject, exception);
+  if (!exception) {
+    // Not a DOM Exception, try XPC Exception.
+    UNWRAP_OBJECT(Exception, exceptionObject, exception);
+    if (!exception) {
+      return nullptr;
+    }
+  }
+
+  nsCOMPtr<nsIStackFrame> stack = exception->GetLocation();
+  if (!stack) {
+    return nullptr;
+  }
+  JS::RootedValue value(cx);
+  stack->GetNativeSavedFrame(&value);
+  if (value.isObject()) {
+    stackObject = &value.toObject();
+  }
+  return stackObject;
+}
+
 static PRTime
 GetCollectionTimeDelta()
 {
   PRTime now = PR_Now();
   if (sFirstCollectionTime) {
     return now - sFirstCollectionTime;
   }
   sFirstCollectionTime = now;
@@ -421,17 +458,17 @@ public:
       if (mError.isObject()) {
         AutoJSAPI jsapi;
         if (NS_WARN_IF(!jsapi.Init(mError.toObjectOrNull()))) {
           mReport->LogToConsole();
           return NS_OK;
         }
         JSContext* cx = jsapi.cx();
         JS::Rooted<JSObject*> exObj(cx, mError.toObjectOrNull());
-        JS::RootedObject stack(cx, ExceptionStackOrNull(cx, exObj));
+        JS::RootedObject stack(cx, FindExceptionStack(cx, exObj));
         mReport->LogToConsoleWithStack(stack);
       } else {
         mReport->LogToConsole();
       }
 
     }
 
     return NS_OK;
@@ -507,17 +544,17 @@ SystemErrorReporter(JSContext *cx, const
     // directly. This includes the case where the error was an OOM, because
     // triggering a scripted event handler is likely to generate further OOMs.
     if (!win || JSREPORT_IS_WARNING(xpcReport->mFlags) ||
         report->errorNumber == JSMSG_OUT_OF_MEMORY)
     {
       if (exception.isObject()) {
         JS::RootedObject exObj(cx, exception.toObjectOrNull());
         JSAutoCompartment ac(cx, exObj);
-        JS::RootedObject stackVal(cx, ExceptionStackOrNull(cx, exObj));
+        JS::RootedObject stackVal(cx, FindExceptionStack(cx, exObj));
         xpcReport->LogToConsoleWithStack(stackVal);
       } else {
         xpcReport->LogToConsole();
       }
       return;
     }
 
     // Otherwise, we need to asynchronously invoke onerror before we can decide
--- a/mobile/android/app/build.gradle
+++ b/mobile/android/app/build.gradle
@@ -233,17 +233,19 @@ task syncPreprocessedCode(type: Sync, de
     from("${topobjdir}/mobile/android/base/generated/preprocessed")
 }
 
 // The localization system uses the moz.build preprocessor to interpolate a .dtd
 // file of XML entity definitions into an XML file of elements referencing those
 // entities.  (Each locale produces its own .dtd file, backstopped by the en-US
 // .dtd file in tree.)  Android Studio (and IntelliJ) don't handle these inline
 // entities smoothly.  This filter merely expands the entities in place, making
-// them appear properly throughout the IDE.
+// them appear properly throughout the IDE.  Be aware that this assumes that the
+// JVM's file.encoding is utf-8.  See comments in
+// mobile/android/mach_commands.py.
 class ExpandXMLEntitiesFilter extends FilterReader {
     ExpandXMLEntitiesFilter(Reader input) {
         // Extremely inefficient, but whatever.
         super(new StringReader(groovy.xml.XmlUtil.serialize(new XmlParser(false, false, true).parse(input))))
     }
 }
 
 task syncPreprocessedResources(type: Sync, dependsOn: rootProject.generateCodeAndResources) {
--- a/mobile/android/base/Makefile.in
+++ b/mobile/android/base/Makefile.in
@@ -206,17 +206,16 @@ endif # MOZ_INSTALL_TRACKING
 library_jars := $(subst $(NULL) ,:,$(strip $(library_jars)))
 
 gradle_dir := $(topobjdir)/gradle/build/mobile/android
 
 ifdef MOZ_BUILD_MOBILE_ANDROID_WITH_GRADLE
 .gradle.deps: .aapt.deps FORCE
 	@$(TOUCH) $@
 	$(topsrcdir)/mach gradle \
-		$(if $(MOZILLA_OFFICIAL),--no-daemon --offline --info) \
 		app:assembleAutomationDebug app:assembleAutomationDebugAndroidTest -x lint
 
 classes.dex: .gradle.deps
 	$(REPORT_BUILD)
 	cp $(gradle_dir)/app/intermediates/transforms/dex/automation/debug/folders/1000/1f/main/classes.dex $@
 else
 classes.dex: .proguard.deps
 	$(REPORT_BUILD)
--- a/mobile/android/base/java/org/mozilla/gecko/BrowserApp.java
+++ b/mobile/android/base/java/org/mozilla/gecko/BrowserApp.java
@@ -156,16 +156,17 @@ import java.io.IOException;
 import java.lang.reflect.Method;
 import java.net.URLEncoder;
 import java.util.EnumSet;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Locale;
 import java.util.UUID;
 import java.util.Vector;
+import java.util.regex.Pattern;
 
 public class BrowserApp extends GeckoApp
                         implements TabsPanel.TabsLayoutChangeListener,
                                    PropertyAnimator.PropertyAnimationListener,
                                    View.OnKeyListener,
                                    LayerView.DynamicToolbarListener,
                                    BrowserSearch.OnSearchListener,
                                    BrowserSearch.OnEditSuggestionListener,
@@ -2428,16 +2429,21 @@ public class BrowserApp extends GeckoApp
      * @param query
      *        a search query to store. We won't store empty queries.
      */
     private void storeSearchQuery(final String query) {
         if (TextUtils.isEmpty(query)) {
             return;
         }
 
+        // Filter out URLs and long suggestions
+        if (query.length() > 50 || Pattern.matches("^(https?|ftp|file)://.*", query)) {
+            return;
+        }
+
         final GeckoProfile profile = getProfile();
         // Don't bother storing search queries in guest mode
         if (profile.inGuestMode()) {
             return;
         }
 
         final BrowserDB db = profile.getDB();
         ThreadUtils.postToBackgroundThread(new Runnable() {
--- a/mobile/android/base/java/org/mozilla/gecko/home/SearchEngineRow.java
+++ b/mobile/android/base/java/org/mozilla/gecko/home/SearchEngineRow.java
@@ -34,17 +34,19 @@ import android.view.LayoutInflater;
 import android.view.View;
 import android.view.animation.AlphaAnimation;
 import android.widget.ImageView;
 import android.widget.LinearLayout;
 import android.widget.TextView;
 
 import java.util.ArrayList;
 import java.util.EnumSet;
+import java.util.Iterator;
 import java.util.List;
+import java.util.regex.Pattern;
 
 class SearchEngineRow extends AnimatedHeightLayout {
     // Duration for fade-in animation
     private static final int ANIMATION_DURATION = 250;
 
     // Inner views
     private final FlowLayout mSuggestionView;
     private final FaviconView mIconView;
@@ -378,16 +380,28 @@ class SearchEngineRow extends AnimatedHe
         setDescriptionOnSuggestion(mUserEnteredTextView, mUserEnteredTextView.getText().toString());
 
         final int recycledSuggestionCount = mSuggestionView.getChildCount();
         final SharedPreferences prefs = GeckoSharedPrefs.forApp(getContext());
         final boolean savedSearchesEnabled = prefs.getBoolean(GeckoPreferences.PREFS_HISTORY_SAVED_SEARCH, true);
 
         // Remove duplicates of search engine suggestions from saved searches.
         List<String> searchHistorySuggestions = (rawSearchHistorySuggestions != null) ? rawSearchHistorySuggestions : new ArrayList<String>();
+
+        // Filter out URLs and long search suggestions
+        Iterator<String> searchistoryIterator = searchHistorySuggestions.iterator();
+        while (searchistoryIterator.hasNext()) {
+            final String currentSearchHistory = searchistoryIterator.next();
+
+            if (currentSearchHistory.length() > 50 || Pattern.matches("^(https?|ftp|file)://.*", currentSearchHistory)) {
+                searchHistorySuggestions.remove(currentSearchHistory);
+            }
+        }
+
+
         List<String> searchEngineSuggestions = new ArrayList<String>();
         for (String suggestion : searchEngine.getSuggestions()) {
             searchHistorySuggestions.remove(suggestion);
             searchEngineSuggestions.add(suggestion);
         }
         // Make sure the search term itself isn't duplicated. This is more important on phones than tablets where screen
         // space is more precious.
         searchHistorySuggestions.remove(getSuggestionTextFromView(mUserEnteredView));
--- a/mobile/android/chrome/content/browser.js
+++ b/mobile/android/chrome/content/browser.js
@@ -2278,16 +2278,21 @@ var NativeWindow = {
    * @param aCategory
    *        Doorhanger type to display (e.g., LOGIN)
    */
     show: function(aMessage, aValue, aButtons, aTabID, aOptions, aCategory) {
       if (aButtons == null) {
         aButtons = [];
       }
 
+      if (aButtons.length > 2) {
+        console.log("Doorhanger can have a maximum of two buttons!");
+        aButtons.length = 2;
+      }
+
       aButtons.forEach((function(aButton) {
         this._callbacks[this._callbacksId] = { cb: aButton.callback, prompt: this._promptId };
         aButton.callback = this._callbacksId;
         this._callbacksId++;
       }).bind(this));
 
       this._promptId++;
       let json = {
--- a/mobile/android/config/tooltool-manifests/android-frontend/releng.manifest
+++ b/mobile/android/config/tooltool-manifests/android-frontend/releng.manifest
@@ -33,10 +33,18 @@
 },
 {
 "algorithm": "sha512",
 "visibility": "public",
 "filename": "gradle.tar.xz",
 "unpack": true,
 "digest": "ef1d0038da879cc6840fced87671f8f6a18c51375498804f64d21fa48d7089ded4da2be36bd06a1457083e9110e59c0884f1e074dc609d29617c131caea8f234",
 "size": 50542140
+},
+{
+"algorithm": "sha512",
+"visibility": "public",
+"filename": "dotgradle.tar.xz",
+"unpack": true,
+"digest": "9f082ccd71ad18991eb71fcad355c6990f50a72a09ab9b79696521485656083a72faf5a8d4714de9c4b901ee2319b6786a51964846bb7075061642a8505501c2",
+"size": 512
 }
 ]
--- a/mobile/android/mach_commands.py
+++ b/mobile/android/mach_commands.py
@@ -52,27 +52,36 @@ 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)
 
+
+        # In automation, JAVA_HOME is set via mozconfig, which needs
+        # to be specially handled in each mach command. This turns
+        # $JAVA_HOME/bin/java into $JAVA_HOME.
+        java_home = os.path.dirname(os.path.dirname(self.substs['JAVA']))
+
         # We force the Gradle JVM to run with the UTF-8 encoding, since we
         # filter strings.xml, which is really UTF-8; the ellipsis character is
         # replaced with ??? in some encodings (including ASCII).  It's not yet
         # possible to filter with encodings in Gradle
         # (https://github.com/gradle/gradle/pull/520) and it's challenging to
         # do our filtering with Gradle's Ant support.  Moreover, all of the
         # Android tools expect UTF-8: see
         # http://tools.android.com/knownissues/encoding.  See
         # http://stackoverflow.com/a/21267635 for discussion of this approach.
         return self.run_process([self.substs['GRADLE']] + args,
-            append_env={'GRADLE_OPTS': '-Dfile.encoding=utf-8'},
+            append_env={
+                'GRADLE_OPTS': '-Dfile.encoding=utf-8',
+                'JAVA_HOME': java_home,
+            },
             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.topsrcdir))
 
     @Command('gradle-install', category='devenv',
         conditions=[REMOVED])
     def gradle_install(self):
         pass
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/fxa/authenticator/AndroidFxAccount.java
+++ b/mobile/android/services/src/main/java/org/mozilla/gecko/fxa/authenticator/AndroidFxAccount.java
@@ -288,28 +288,39 @@ public class AndroidFxAccount {
     return accountManager.getUserData(account, ACCOUNT_KEY_IDP_SERVER);
   }
 
   public String getTokenServerURI() {
     return accountManager.getUserData(account, ACCOUNT_KEY_TOKEN_SERVER);
   }
 
   public String getProfileServerURI() {
-    return accountManager.getUserData(account, ACCOUNT_KEY_PROFILE_SERVER);
+    String profileURI = accountManager.getUserData(account, ACCOUNT_KEY_PROFILE_SERVER);
+    if (profileURI == null) {
+      if (isStaging()) {
+        return FxAccountConstants.STAGE_PROFILE_SERVER_ENDPOINT;
+      }
+      return FxAccountConstants.DEFAULT_PROFILE_SERVER_ENDPOINT;
+    }
+    return profileURI;
   }
 
   public String getOAuthServerURI() {
     // Allow testing against stage.
-    if (FxAccountConstants.STAGE_AUTH_SERVER_ENDPOINT.equals(getAccountServerURI())) {
+    if (isStaging()) {
       return FxAccountConstants.STAGE_OAUTH_SERVER_ENDPOINT;
     } else {
       return FxAccountConstants.DEFAULT_OAUTH_SERVER_ENDPOINT;
     }
   }
 
+  private boolean isStaging() {
+    return FxAccountConstants.STAGE_AUTH_SERVER_ENDPOINT.equals(getAccountServerURI());
+  }
+
   private String constructPrefsPath(String product, long version, String extra) throws GeneralSecurityException, UnsupportedEncodingException {
     String profile = getProfile();
     String username = account.name;
 
     if (profile == null) {
       throw new IllegalStateException("Missing profile. Cannot fetch prefs.");
     }
 
--- a/security/manager/ssl/nsNSSCallbacks.cpp
+++ b/security/manager/ssl/nsNSSCallbacks.cpp
@@ -1,31 +1,33 @@
 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
  *
  * 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 "nsNSSCallbacks.h"
-#include "pkix/pkixtypes.h"
+
 #include "mozilla/Telemetry.h"
 #include "mozilla/TimeStamp.h"
+#include "nsContentUtils.h"
+#include "nsICertOverrideService.h"
+#include "nsIHttpChannelInternal.h"
+#include "nsIPrompt.h"
+#include "nsISupportsPriority.h"
+#include "nsITokenDialogs.h"
+#include "nsIUploadChannel.h"
+#include "nsIWebProgressListener.h"
+#include "nsNetUtil.h"
 #include "nsNSSComponent.h"
 #include "nsNSSIOLayer.h"
-#include "nsIWebProgressListener.h"
 #include "nsProtectedAuthThread.h"
-#include "nsITokenDialogs.h"
-#include "nsIUploadChannel.h"
-#include "nsIPrompt.h"
 #include "nsProxyRelease.h"
+#include "pkix/pkixtypes.h"
 #include "PSMRunnable.h"
-#include "nsContentUtils.h"
-#include "nsIHttpChannelInternal.h"
-#include "nsISupportsPriority.h"
-#include "nsNetUtil.h"
 #include "SharedSSLState.h"
 #include "ssl.h"
 #include "sslproto.h"
 
 using namespace mozilla;
 using namespace mozilla::psm;
 
 extern PRLogModuleInfo* gPIPNSSLog;
@@ -1230,16 +1232,27 @@ void HandshakeCallback(PRFileDesc* fd, v
                                         &siteSupportsSafeRenego);
   MOZ_ASSERT(rv == SECSuccess);
   if (rv != SECSuccess) {
     siteSupportsSafeRenego = false;
   }
   bool renegotiationUnsafe = !siteSupportsSafeRenego &&
                              ioLayerHelpers.treatUnsafeNegotiationAsBroken();
 
+
+  /* Set the SSL Status information */
+  RefPtr<nsSSLStatus> status(infoObject->SSLStatus());
+  if (!status) {
+    status = new nsSSLStatus();
+    infoObject->SetSSLStatus(status);
+  }
+
+  RememberCertErrorsTable::GetInstance().LookupCertErrorBits(infoObject,
+                                                             status);
+
   uint32_t state;
   if (usesWeakCipher || renegotiationUnsafe) {
     state = nsIWebProgressListener::STATE_IS_BROKEN;
     if (usesWeakCipher) {
       state |= nsIWebProgressListener::STATE_USES_WEAK_CRYPTO;
     }
   } else {
     state = nsIWebProgressListener::STATE_IS_SECURE |
@@ -1247,16 +1260,49 @@ void HandshakeCallback(PRFileDesc* fd, v
     SSLVersionRange defVersion;
     rv = SSL_VersionRangeGetDefault(ssl_variant_stream, &defVersion);
     if (rv == SECSuccess && versions.max >= defVersion.max) {
       // we know this site no longer requires a weak cipher
       ioLayerHelpers.removeInsecureFallbackSite(infoObject->GetHostName(),
                                                 infoObject->GetPort());
     }
   }
+
+  if (status->HasServerCert()) {
+    MOZ_LOG(gPIPNSSLog, LogLevel::Debug,
+           ("HandshakeCallback KEEPING existing cert\n"));
+  } else {
+    ScopedCERTCertificate serverCert(SSL_PeerCertificate(fd));
+    RefPtr<nsNSSCertificate> nssc(nsNSSCertificate::Create(serverCert.get()));
+    MOZ_LOG(gPIPNSSLog, LogLevel::Debug,
+           ("HandshakeCallback using NEW cert %p\n", nssc.get()));
+    status->SetServerCert(nssc, nsNSSCertificate::ev_status_unknown);
+  }
+
+  nsCOMPtr<nsICertOverrideService> overrideService =
+      do_GetService(NS_CERTOVERRIDE_CONTRACTID);
+
+  if (overrideService) {
+    bool haveOverride;
+    uint32_t overrideBits = 0; // Unused.
+    bool isTemporaryOverride; // Unused.
+    const nsACString& hostString(infoObject->GetHostName());
+    const int32_t port(infoObject->GetPort());
+    nsCOMPtr<nsIX509Cert> cert;
+    status->GetServerCert(getter_AddRefs(cert));
+    nsresult nsrv = overrideService->HasMatchingOverride(hostString, port,
+                                                         cert,
+                                                         &overrideBits,
+                                                         &isTemporaryOverride,
+                                                         &haveOverride);
+    if (NS_SUCCEEDED(nsrv) && haveOverride) {
+      state |= nsIWebProgressListener::STATE_CERT_USER_OVERRIDDEN;
+    }
+  }
+
   infoObject->SetSecurityState(state);
 
   // XXX Bug 883674: We shouldn't be formatting messages here in PSM; instead,
   // we should set a flag on the channel that higher (UI) level code can check
   // to log the warning. In particular, these warnings should go to the web
   // console instead of to the error console. Also, the warning is not
   // localized.
   if (!siteSupportsSafeRenego) {
@@ -1265,32 +1311,11 @@ void HandshakeCallback(PRFileDesc* fd, v
 
     nsAutoString msg;
     msg.Append(NS_ConvertASCIItoUTF16(hostName));
     msg.AppendLiteral(" : server does not support RFC 5746, see CVE-2009-3555");
 
     nsContentUtils::LogSimpleConsoleError(msg, "SSL");
   }
 
-  /* Set the SSL Status information */
-  RefPtr<nsSSLStatus> status(infoObject->SSLStatus());
-  if (!status) {
-    status = new nsSSLStatus();
-    infoObject->SetSSLStatus(status);
-  }
-
-  RememberCertErrorsTable::GetInstance().LookupCertErrorBits(infoObject,
-                                                             status);
-
-  if (status->HasServerCert()) {
-    MOZ_LOG(gPIPNSSLog, LogLevel::Debug,
-           ("HandshakeCallback KEEPING existing cert\n"));
-  } else {
-    ScopedCERTCertificate serverCert(SSL_PeerCertificate(fd));
-    RefPtr<nsNSSCertificate> nssc(nsNSSCertificate::Create(serverCert.get()));
-    MOZ_LOG(gPIPNSSLog, LogLevel::Debug,
-           ("HandshakeCallback using NEW cert %p\n", nssc.get()));
-    status->SetServerCert(nssc, nsNSSCertificate::ev_status_unknown);
-  }
-
   infoObject->NoteTimeUntilReady();
   infoObject->SetHandshakeCompleted();
 }
--- a/security/manager/ssl/nsNSSComponent.cpp
+++ b/security/manager/ssl/nsNSSComponent.cpp
@@ -1112,16 +1112,24 @@ nsNSSComponent::InitializeNSS()
   // Initialize the site security service
   nsCOMPtr<nsISiteSecurityService> sssService =
     do_GetService(NS_SSSERVICE_CONTRACTID);
   if (!sssService) {
     MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("Cannot initialize site security service\n"));
     return NS_ERROR_FAILURE;
   }
 
+  // Initialize the cert override service
+  nsCOMPtr<nsICertOverrideService> coService =
+    do_GetService(NS_CERTOVERRIDE_CONTRACTID);
+  if (!coService) {
+    MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("Cannot initialize cert override service\n"));
+    return NS_ERROR_FAILURE;
+  }
+
   if (PK11_IsFIPS()) {
     Telemetry::Accumulate(Telemetry::FIPS_ENABLED, true);
   }
 
   MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("NSS Initialization done\n"));
   return NS_OK;
 }
 
--- a/security/manager/ssl/nsSecureBrowserUIImpl.cpp
+++ b/security/manager/ssl/nsSecureBrowserUIImpl.cpp
@@ -102,16 +102,17 @@ nsSecureBrowserUIImpl::nsSecureBrowserUI
   : mNotifiedSecurityState(lis_no_security)
   , mNotifiedToplevelIsEV(false)
   , mNewToplevelSecurityState(STATE_IS_INSECURE)
   , mNewToplevelIsEV(false)
   , mNewToplevelSecurityStateKnown(true)
   , mIsViewSource(false)
   , mSubRequestsBrokenSecurity(0)
   , mSubRequestsNoSecurity(0)
+  , mCertUserOverridden(false)
   , mRestoreSubrequests(false)
   , mOnLocationChangeSeen(false)
 #ifdef DEBUG
   , mOnStateLocationChangeReentranceDetection(0)
 #endif
   , mTransferringRequests(&gMapOps, sizeof(RequestHashEntry))
 {
   MOZ_ASSERT(NS_IsMainThread());
@@ -228,16 +229,20 @@ nsSecureBrowserUIImpl::MapInternalToExte
     case lis_no_security:
       *aState = STATE_IS_INSECURE;
       break;
   }
 
   if (ev && (*aState & STATE_IS_SECURE))
     *aState |= nsIWebProgressListener::STATE_IDENTITY_EV_TOPLEVEL;
 
+  if (mCertUserOverridden && (*aState & STATE_IS_SECURE)) {
+    *aState |= nsIWebProgressListener::STATE_CERT_USER_OVERRIDDEN;
+  }
+
   nsCOMPtr<nsIDocShell> docShell = do_QueryReferent(mDocShell);
   if (!docShell)
     return NS_OK;
 
   // For content docShell's, the mixed content security state is set on the root docShell.
   if (docShell->ItemType() == nsIDocShellTreeItem::typeContent) {
     nsCOMPtr<nsIDocShellTreeItem> docShellTreeItem(do_QueryInterface(docShell));
     nsCOMPtr<nsIDocShellTreeItem> sameTypeRoot;
@@ -255,16 +260,19 @@ nsSecureBrowserUIImpl::MapInternalToExte
       !docShell->GetHasMixedActiveContentLoaded() &&
       !docShell->GetHasMixedDisplayContentLoaded() &&
       !docShell->GetHasMixedActiveContentBlocked() &&
       !docShell->GetHasMixedDisplayContentBlocked()) {
     *aState = STATE_IS_SECURE;
     if (ev) {
       *aState |= nsIWebProgressListener::STATE_IDENTITY_EV_TOPLEVEL;
     }
+    if (mCertUserOverridden) {
+      *aState |= nsIWebProgressListener::STATE_CERT_USER_OVERRIDDEN;
+    }
   }
   // * If so, the state should be broken or insecure; overriding the previous
   // state set by the lock parameter.
   uint32_t tempState = STATE_IS_BROKEN;
   if (lock == lis_no_security) {
       // this is to ensure that http: pages with mixed content in nested
       // iframes don't get marked as broken instead of insecure
       tempState = STATE_IS_INSECURE;
@@ -788,16 +796,21 @@ nsSecureBrowserUIImpl::OnStateChange(nsI
     f -= nsIWebProgressListener::STATE_IS_SECURE;
     info.AppendLiteral("IS_SECURE ");
   }
   if (f & nsIWebProgressListener::STATE_SECURE_HIGH)
   {
     f -= nsIWebProgressListener::STATE_SECURE_HIGH;
     info.AppendLiteral("SECURE_HIGH ");
   }
+  if (f & nsIWebProgressListener::STATE_CERT_USER_OVERRIDDEN)
+  {
+    f -= nsIWebProgressListener::STATE_CERT_USER_OVERRIDDEN;
+    info.AppendLiteral("STATE_CERT_USER_OVERRIDDEN ");
+  }
   if (f & nsIWebProgressListener::STATE_RESTORING)
   {
     f -= nsIWebProgressListener::STATE_RESTORING;
     info.AppendLiteral("STATE_RESTORING ");
   }
 
   if (f > 0)
   {
@@ -1126,16 +1139,19 @@ nsSecureBrowserUIImpl::UpdateSecuritySta
       newSecurityState = lis_high_security;
     }
   }
 
   if (mNewToplevelSecurityState & STATE_IS_BROKEN) {
     newSecurityState = lis_broken_security;
   }
 
+  mCertUserOverridden =
+    mNewToplevelSecurityState & STATE_CERT_USER_OVERRIDDEN;
+
   MOZ_LOG(gSecureDocLog, LogLevel::Debug,
          ("SecureUI:%p: UpdateSecurityState:  old-new  %d - %d\n", this,
           mNotifiedSecurityState, newSecurityState));
 
   bool flagsChanged = false;
   if (mNotifiedSecurityState != newSecurityState) {
     // Something changed since the last time.
     flagsChanged = true;
--- a/security/manager/ssl/nsSecureBrowserUIImpl.h
+++ b/security/manager/ssl/nsSecureBrowserUIImpl.h
@@ -69,16 +69,17 @@ protected:
   uint32_t mNewToplevelSecurityState;
   bool mNewToplevelIsEV;
   bool mNewToplevelSecurityStateKnown;
   bool mIsViewSource;
 
   int32_t mDocumentRequestsInProgress;
   int32_t mSubRequestsBrokenSecurity;
   int32_t mSubRequestsNoSecurity;
+  bool mCertUserOverridden;
   bool mRestoreSubrequests;
   bool mOnLocationChangeSeen;
 #ifdef DEBUG
   mozilla::Atomic<int32_t> mOnStateLocationChangeReentranceDetection;
 #endif
 
   static already_AddRefed<nsISupports> ExtractSecurityInfo(nsIRequest* aRequest);
   nsresult MapInternalToExternalState(uint32_t* aState, lockIconState lock, bool ev);
--- a/security/manager/ssl/tests/unit/head_psm.js
+++ b/security/manager/ssl/tests/unit/head_psm.js
@@ -657,17 +657,21 @@ function add_cert_override(aHost, aExpec
 
 // Given a host, expected error bits (see nsICertOverrideService.idl), and
 // an expected error code, tests that an initial connection to the host fails
 // with the expected errors and that adding an override results in a subsequent
 // connection succeeding.
 function add_cert_override_test(aHost, aExpectedBits, aExpectedError) {
   add_connection_test(aHost, aExpectedError, null,
                       add_cert_override.bind(this, aHost, aExpectedBits));
-  add_connection_test(aHost, PRErrorCodeSuccess);
+  add_connection_test(aHost, PRErrorCodeSuccess, null, aSecurityInfo => {
+    Assert.ok(aSecurityInfo.securityState &
+              Ci.nsIWebProgressListener.STATE_CERT_USER_OVERRIDDEN,
+              "Cert override flag should be set on the security state");
+  });
 }
 
 // Helper function for add_prevented_cert_override_test. This is much like
 // add_cert_override except it may not be the case that the connection has an
 // SSLStatus set on it. In this case, the error was not overridable anyway, so
 // we consider it a success.
 function attempt_adding_cert_override(aHost, aExpectedBits, aSecurityInfo) {
   let sslstatus = aSecurityInfo.QueryInterface(Ci.nsISSLStatusProvider)
--- a/testing/mozharness/configs/builds/releng_sub_android_configs/64_api_15_frontend.py
+++ b/testing/mozharness/configs/builds/releng_sub_android_configs/64_api_15_frontend.py
@@ -1,8 +1,12 @@
 config = {
     'base_name': 'Android armv7 API 15+ frontend %(branch)s',
     'stage_platform': 'android-api-15-frontend',
     'build_type': 'api-15-opt',
     'src_mozconfig': 'mobile/android/config/mozconfigs/android-api-15-frontend/nightly',
     'tooltool_manifest_src': 'mobile/android/config/tooltool-manifests/android-frontend/releng.manifest',
     'multi_locale_config_platform': 'android',
+    'postflight_build_mach_commands': [
+        ['gradle', 'app:lintAutomationDebug'],
+        ['gradle', 'app:testAutomationDebugUnitTest'],
+    ],
 }
--- a/testing/mozharness/mozharness/mozilla/building/buildbase.py
+++ b/testing/mozharness/mozharness/mozilla/building/buildbase.py
@@ -1876,16 +1876,36 @@ or run without that action (ie: --no-{ac
 
     def postflight_build(self, console_output=True):
         """grabs properties from post build and calls ccache -s"""
         self.generate_build_props(console_output=console_output,
                                   halt_on_failure=True)
         if self.config.get('enable_ccache'):
             self._ccache_s()
 
+        # A list of argument lists.  Better names gratefully accepted!
+        mach_commands = self.config.get('postflight_build_mach_commands', [])
+        for mach_command in mach_commands:
+            self._execute_postflight_build_mach_command(mach_command)
+
+    def _execute_postflight_build_mach_command(self, mach_command_args):
+        env = self.query_build_env()
+        env.update(self.query_mach_build_env())
+        python = self.query_exe('python2.7')
+
+        command = [python, 'mach', '--log-no-times']
+        command.extend(mach_command_args)
+
+        self.run_command_m(
+            command=command,
+            cwd=self.query_abs_dirs()['abs_src_dir'],
+            env=env, output_timeout=self.config.get('max_build_output_timeout', 60 * 20),
+            halt_on_failure=True,
+        )
+
     def preflight_package_source(self):
         self._get_mozconfig()
 
     def package_source(self):
         """generates source archives and uploads them"""
         env = self.query_build_env()
         env.update(self.query_mach_build_env())
         python = self.query_exe('python2.7')
--- a/testing/taskcluster/tasks/builds/android_api_15_frontend.yml
+++ b/testing/taskcluster/tasks/builds/android_api_15_frontend.yml
@@ -34,27 +34,50 @@ task:
       # TODO: make these additional configuration files go away
       MOZHARNESS_CONFIG: >
           builds/releng_base_android_64_builds.py
           disable_signing.py
           platform_supports_post_upload_to_latest.py
       MH_CUSTOM_BUILD_VARIANT_CFG: api-15-frontend
       MH_BRANCH: {{project}}
       MH_BUILD_POOL: taskcluster
+      GRADLE_USER_HOME: '/home/worker/workspace/build/src/dotgradle'
 
     maxRunTime: 36000
 
     command: ["/bin/bash", "bin/build.sh"]
 
+    artifacts:
+      'public/android/unittest':
+        type: directory
+        path: '/home/worker/workspace/build/src/obj-firefox/gradle/build/mobile/android/app/reports/tests'
+        expires: '{{#from_now}}1 year{{/from_now}}'
+      'public/android/lint/lint-results-automationDebug.html':
+        type: file
+        path: '/home/worker/workspace/build/src/obj-firefox/gradle/build/mobile/android/app/outputs/lint-results-automationDebug.html'
+        expires: '{{#from_now}}1 year{{/from_now}}'
+      'public/android/lint/lint-results-automationDebug.xml':
+        type: file
+        path: '/home/worker/workspace/build/src/obj-firefox/gradle/build/mobile/android/app/outputs/lint-results-automationDebug.xml'
+        expires: '{{#from_now}}1 year{{/from_now}}'
+      'public/android/lint/lint-results-automationDebug_files':
+        type: directory
+        path: '/home/worker/workspace/build/src/obj-firefox/gradle/build/mobile/android/app/outputs/lint-results-automationDebug_files'
+        expires: '{{#from_now}}1 year{{/from_now}}'
+
   extra:
     treeherderEnv:
       - production
       - staging
     treeherder:
       machine:
         # see https://github.com/mozilla/treeherder/blob/master/ui/js/values.js
-        platform: android-4-0-armv7-api15-frontend
+        platform: android-4-0-armv7-api15
+      groupSymbol: tc
+      groupName: Submitted by taskcluster
+      symbol: Unit
+      tier: 2
     # Rather then enforcing particular conventions we require that all build
     # tasks provide the "build" extra field to specify where the build and tests
     # files are located.
     locations:
       build: 'public/build/target.linux-x86_64.tar.bz2'
       tests: 'public/build/target.tests.zip'
--- a/toolkit/components/extensions/ExtensionContent.jsm
+++ b/toolkit/components/extensions/ExtensionContent.jsm
@@ -41,16 +41,17 @@ Cu.import("resource://gre/modules/Extens
 var {
   runSafeSyncWithoutClone,
   BaseContext,
   LocaleData,
   MessageBroker,
   Messenger,
   injectAPI,
   flushJarCache,
+  detectLanguage,
 } = ExtensionUtils;
 
 function isWhenBeforeOrSame(when1, when2) {
   let table = {"document_start": 0,
                "document_end": 1,
                "document_idle": 2};
   return table[when1] <= table[when2];
 }
@@ -117,16 +118,21 @@ var api = context => {
     i18n: {
       getMessage: function(messageName, substitutions) {
         return context.extension.localizeMessage(messageName, substitutions);
       },
 
       getUILanguage: function() {
         return context.extension.localeData.uiLocale;
       },
+
+      detectLanguage: function(text, callback) {
+        let result = detectLanguage(text);
+        return context.wrapPromise(result, callback);
+      },
     },
   };
 };
 
 // Represents a content script.
 function Script(options, deferred = PromiseUtils.defer()) {
   this.options = options;
   this.run_at = this.options.run_at;
--- a/toolkit/components/extensions/ExtensionUtils.jsm
+++ b/toolkit/components/extensions/ExtensionUtils.jsm
@@ -11,17 +11,18 @@ const Cc = Components.classes;
 const Cu = Components.utils;
 const Cr = Components.results;
 
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource://gre/modules/Services.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "AppConstants",
                                   "resource://gre/modules/AppConstants.jsm");
-
+XPCOMUtils.defineLazyModuleGetter(this, "LanguageDetector",
+                                  "resource:///modules/translation/LanguageDetector.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "Locale",
                                   "resource://gre/modules/Locale.jsm");
 
 function filterStack(error) {
   return String(error.stack).replace(/(^.*(Task\.jsm|Promise-backend\.js).*\n)+/gm, "<Promise Chain>\n");
 }
 
 // Run a function and report exceptions.
@@ -428,17 +429,16 @@ LocaleData.prototype = {
     return result;
   },
 
   get uiLocale() {
     // Return the browser locale, but convert it to a Chrome-style
     // locale code.
     return Locale.getLocale().replace(/-/g, "_");
   },
-
 };
 
 // This is a generic class for managing event listeners. Example usage:
 //
 // new EventManager(context, "api.subAPI", fire => {
 //   let listener = (...) => {
 //     // Fire any listeners registered with addListener.
 //     fire(arg1, arg2);
@@ -956,16 +956,28 @@ const PlatformInfo = Object.freeze({
       arch = "x86-32";
     } else if (arch == "x86_64") {
       arch = "x86-64";
     }
     return arch;
   })(),
 });
 
+function detectLanguage(text) {
+  return LanguageDetector.detectLanguage(text).then(result => ({
+    isReliable: result.confident,
+    languages: result.languages.map(lang => {
+      return {
+        language: lang.languageCode,
+        percentage: lang.percent,
+      };
+    }),
+  }));
+}
+
 this.ExtensionUtils = {
   runSafeWithoutClone,
   runSafeSyncWithoutClone,
   runSafe,
   runSafeSync,
   BaseContext,
   DefaultWeakMap,
   EventManager,
@@ -975,9 +987,10 @@ this.ExtensionUtils = {
   injectAPI,
   MessageBroker,
   Messenger,
   PlatformInfo,
   SpreadArgs,
   extend,
   flushJarCache,
   instanceOf,
+  detectLanguage,
 };
--- a/toolkit/components/extensions/ext-i18n.js
+++ b/toolkit/components/extensions/ext-i18n.js
@@ -1,15 +1,24 @@
 "use strict";
 
+Cu.import("resource://gre/modules/ExtensionUtils.jsm");
+var {
+  detectLanguage,
+} = ExtensionUtils;
+
 extensions.registerSchemaAPI("i18n", null, (extension, context) => {
   return {
     i18n: {
       getMessage: function(messageName, substitutions) {
         return extension.localizeMessage(messageName, substitutions);
       },
 
       getUILanguage: function() {
         return extension.localeData.uiLocale;
       },
+
+      detectLanguage: function(text) {
+        return detectLanguage(text);
+      },
     },
   };
 });
--- a/toolkit/components/extensions/schemas/i18n.json
+++ b/toolkit/components/extensions/schemas/i18n.json
@@ -73,17 +73,16 @@
         "parameters": [],
         "returns": {
           "type": "string",
           "description": "The browser UI language code such as en-US or fr-FR."
         }
       },
       {
         "name": "detectLanguage",
-        "unsupported": true,
         "type": "function",
         "description": "Detects the language of the provided text using CLD.",
         "async": "callback",
         "parameters": [
           {
             "type": "string",
             "name": "text",
             "description": "User input string to be translated."
--- a/toolkit/components/extensions/test/mochitest/test_ext_i18n.html
+++ b/toolkit/components/extensions/test/mochitest/test_ext_i18n.html
@@ -237,12 +237,125 @@ add_task(function* test_get_ui_language(
   extension.sendMessage(["expect-results", "he"]);
   yield extension.awaitMessage("done");
 
   win.close();
 
   yield extension.unload();
 });
 
+
+add_task(function* test_detect_language() {
+  const af_string = " aam skukuza die naam beteken hy wat skoonvee of hy wat alles onderstebo keer wysig " +
+    "bosveldkampe boskampe is kleiner afgeleë ruskampe wat oor min fasiliteite beskik daar is geen restaurante " +
+    "of winkels nie en slegs oornagbesoekers word toegelaat bateleur";
+  // String with intermixed French/English text
+  const fr_en_string = "France is the largest country in Western Europe and the third-largest in Europe as a whole. " +
+    "A accès aux chiens et aux frontaux qui lui ont été il peut consulter et modifier ses collections et exporter " +
+    "Cet article concerne le pays européen aujourd’hui appelé République française. Pour d’autres usages du nom France, " +
+    "Pour une aide rapide et effective, veuiller trouver votre aide dans le menu ci-dessus." +
+    "Motoring events began soon after the construction of the first successful gasoline-fueled automobiles. The quick brown fox jumped over the lazy dog";
+
+  function backgroundScript() {
+    function checkResult(source, result, expected) {
+      browser.test.assertEq(expected.isReliable, result.isReliable, "result.confident is true");
+      browser.test.assertEq(
+        expected.languages.length,
+        result.languages.length,
+        `result.languages contains the expected number of languages in ${source}`);
+      expected.languages.forEach((lang, index) => {
+        browser.test.assertEq(
+          lang.percentage,
+          result.languages[index].percentage,
+          `element ${index} of result.languages array has the expected percentage in ${source}`);
+        browser.test.assertEq(
+          lang.language,
+          result.languages[index].language,
+          `element ${index} of result.languages array has the expected language in ${source}`);
+      });
+    }
+
+    let tabId;
+
+    browser.tabs.query({currentWindow: true, active: true}, tabs => {
+      tabId = tabs[0].id;
+      browser.test.sendMessage("ready");
+    });
+
+    browser.test.onMessage.addListener(([msg, expected]) => {
+      Promise.all([
+        browser.i18n.detectLanguage(msg),
+        new Promise(
+          resolve => browser.tabs.sendMessage(tabId, msg, resolve)),
+      ]).then(([backgroundResults, contentResults]) => {
+        checkResult("background", backgroundResults, expected);
+        checkResult("contentScript", contentResults, expected);
+
+        browser.test.sendMessage("done");
+      });
+    });
+  }
+
+  function content() {
+    browser.runtime.onMessage.addListener((msg, sender, respond) => {
+      browser.i18n.detectLanguage(msg, respond);
+      return true;
+    });
+  }
+
+  let extension = ExtensionTestUtils.loadExtension({
+    manifest: {
+      "content_scripts": [{
+        "matches": ["http://mochi.test/*/file_sample.html"],
+        "run_at": "document_start",
+        "js": ["content_script.js"],
+      }],
+    },
+
+    background: `(${backgroundScript})()`,
+
+    files: {
+      "content_script.js": `(${content})()`,
+    },
+  });
+
+  let win = window.open("file_sample.html");
+
+  yield extension.startup();
+  yield extension.awaitMessage("ready");
+
+  let expected = {
+    isReliable: true,
+    languages: [
+      {
+        language: "fr",
+        percentage: 67,
+      },
+      {
+        language: "en",
+        percentage: 32,
+      },
+    ],
+  };
+  extension.sendMessage([fr_en_string, expected]);
+  yield extension.awaitMessage("done");
+
+  expected = {
+    isReliable: true,
+    languages: [
+      {
+        language: "af",
+        percentage: 99,
+      },
+    ],
+  };
+  extension.sendMessage([af_string, expected]);
+  yield extension.awaitMessage("done");
+
+  win.close();
+
+  yield extension.unload();
+});
+
 </script>
 
 </body>
 </html>
--- a/toolkit/components/places/Database.cpp
+++ b/toolkit/components/places/Database.cpp
@@ -459,17 +459,17 @@ Database::Init()
     rv = updateSQLiteStatistics(MainConn());
     NS_ENSURE_SUCCESS(rv, rv);
   }
 
   // Initialize here all the items that are not part of the on-disk database,
   // like views, temp triggers or temp tables.  The database should not be
   // considered corrupt if any of the following fails.
 
-  rv = InitTempTriggers();
+  rv = InitTempEntities();
   NS_ENSURE_SUCCESS(rv, rv);
 
   // Notify we have finished database initialization.
   // Enqueue the notification, so if we init another service that requires
   // nsNavHistoryService we don't recursive try to get it.
   RefPtr<PlacesEvent> completeEvent =
     new PlacesEvent(TOPIC_PLACES_INIT_COMPLETE);
   rv = NS_DispatchToMainThread(completeEvent);
@@ -1010,28 +1010,32 @@ Database::InitFunctions()
   NS_ENSURE_SUCCESS(rv, rv);
   rv = FrecencyNotificationFunction::create(mMainConn);
   NS_ENSURE_SUCCESS(rv, rv);
 
   return NS_OK;
 }
 
 nsresult
-Database::InitTempTriggers()
+Database::InitTempEntities()
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   nsresult rv = mMainConn->ExecuteSimpleSQL(CREATE_HISTORYVISITS_AFTERINSERT_TRIGGER);
   NS_ENSURE_SUCCESS(rv, rv);
   rv = mMainConn->ExecuteSimpleSQL(CREATE_HISTORYVISITS_AFTERDELETE_TRIGGER);
   NS_ENSURE_SUCCESS(rv, rv);
 
   // Add the triggers that update the moz_hosts table as necessary.
   rv = mMainConn->ExecuteSimpleSQL(CREATE_PLACES_AFTERINSERT_TRIGGER);
   NS_ENSURE_SUCCESS(rv, rv);
+  rv = mMainConn->ExecuteSimpleSQL(CREATE_UPDATEHOSTS_TEMP);
+  NS_ENSURE_SUCCESS(rv, rv);
+  rv = mMainConn->ExecuteSimpleSQL(CREATE_UPDATEHOSTS_AFTERDELETE_TRIGGER);
+  NS_ENSURE_SUCCESS(rv, rv);
   rv = mMainConn->ExecuteSimpleSQL(CREATE_PLACES_AFTERDELETE_TRIGGER);
   NS_ENSURE_SUCCESS(rv, rv);
   rv = mMainConn->ExecuteSimpleSQL(CREATE_PLACES_AFTERUPDATE_FRECENCY_TRIGGER);
   NS_ENSURE_SUCCESS(rv, rv);
   rv = mMainConn->ExecuteSimpleSQL(CREATE_PLACES_AFTERUPDATE_TYPED_TRIGGER);
   NS_ENSURE_SUCCESS(rv, rv);
 
   rv = mMainConn->ExecuteSimpleSQL(CREATE_BOOKMARKS_FOREIGNCOUNT_AFTERDELETE_TRIGGER);
--- a/toolkit/components/places/Database.h
+++ b/toolkit/components/places/Database.h
@@ -237,19 +237,19 @@ protected:
   nsresult CreateBookmarkRoots();
 
   /**
    * Initializes additionale SQLite functions, defined in SQLFunctions.h
    */
   nsresult InitFunctions();
 
   /**
-   * Initializes triggers defined in nsPlacesTriggers.h
+   * Initializes temp entities, like triggers, tables, views...
    */
-  nsresult InitTempTriggers();
+  nsresult InitTempEntities();
 
   /**
    * Helpers used by schema upgrades.
    */
   nsresult MigrateV13Up();
   nsresult MigrateV15Up();
   nsresult MigrateV17Up();
   nsresult MigrateV18Up();
--- a/toolkit/components/places/History.cpp
+++ b/toolkit/components/places/History.cpp
@@ -1879,26 +1879,39 @@ private:
       NS_ENSURE_STATE(stmt);
       mozStorageStatementScoper scoper(stmt);
       bool hasResult;
       MOZ_ASSERT(NS_SUCCEEDED(stmt->ExecuteStep(&hasResult)) && !hasResult,
                  "Trying to remove a non-oprhan place from the database");
     }
 #endif
 
-    nsCString query("DELETE FROM moz_places "
-                    "WHERE id IN (");
-    query.Append(placeIdsToRemove);
-    query.Append(')');
-
-    nsCOMPtr<mozIStorageStatement> stmt = mHistory->GetStatement(query);
-    NS_ENSURE_STATE(stmt);
-    mozStorageStatementScoper scoper(stmt);
-    nsresult rv = stmt->Execute();
-    NS_ENSURE_SUCCESS(rv, rv);
+    {
+      nsCString query("DELETE FROM moz_places "
+                      "WHERE id IN (");
+      query.Append(placeIdsToRemove);
+      query.Append(')');
+
+      nsCOMPtr<mozIStorageStatement> stmt = mHistory->GetStatement(query);
+      NS_ENSURE_STATE(stmt);
+      mozStorageStatementScoper scoper(stmt);
+      nsresult rv = stmt->Execute();
+      NS_ENSURE_SUCCESS(rv, rv);
+    }
+
+    {
+      // Hosts accumulated during the places delete are updated through a trigger
+      // (see nsPlacesTriggers.h).
+      nsAutoCString query("DELETE FROM moz_updatehosts_temp");
+      nsCOMPtr<mozIStorageStatement> stmt = mHistory->GetStatement(query);
+      NS_ENSURE_STATE(stmt);
+      mozStorageStatementScoper scoper(stmt);
+      nsresult rv = stmt->Execute();
+      NS_ENSURE_SUCCESS(rv, rv);
+    }
 
     return NS_OK;
   }
 
   mozIStorageConnection* mDBConn;
   bool mHasTransitionType;
   nsCString mWhereClause;
 
--- a/toolkit/components/places/History.jsm
+++ b/toolkit/components/places/History.jsm
@@ -496,18 +496,22 @@ var clear = Task.async(function* (db) {
  * @param idList: (Array of integers)
  *      The `moz_places` identifiers for the places to remove.
  * @return (Promise)
  */
 var removePagesById = Task.async(function*(db, idList) {
   if (idList.length == 0) {
     return;
   }
+  // Note, we are already in a transaction, since callers create it.
   yield db.execute(`DELETE FROM moz_places
                     WHERE id IN ( ${ sqlList(idList) } )`);
+  // Hosts accumulated during the places delete are updated through a trigger
+  // (see nsPlacesTriggers.h).
+  yield db.execute(`DELETE FROM moz_updatehosts_temp`);
 });
 
 /**
  * Clean up pages whose history has been modified, by either
  * removing them entirely (if they are marked for removal,
  * typically because all visits have been removed and there
  * are no more foreign keys such as bookmarks) or updating
  * their frecency (otherwise).
--- a/toolkit/components/places/nsNavHistory.cpp
+++ b/toolkit/components/places/nsNavHistory.cpp
@@ -2450,16 +2450,23 @@ nsNavHistory::CleanupPlacesOnVisitsDelet
     NS_LITERAL_CSTRING(
       "DELETE FROM moz_places WHERE id IN ( "
         ) + filteredPlaceIds + NS_LITERAL_CSTRING(
       ") "
     )
   );
   NS_ENSURE_SUCCESS(rv, rv);
 
+  // Hosts accumulated during the places delete are updated through a trigger
+  // (see nsPlacesTriggers.h).
+  rv = mDB->MainConn()->ExecuteSimpleSQL(
+    NS_LITERAL_CSTRING("DELETE FROM moz_updatehosts_temp")
+  );
+  NS_ENSURE_SUCCESS(rv, rv);
+
   // Invalidate frecencies of touched places, since they need recalculation.
   rv = invalidateFrecencies(aPlaceIdsQueryString);
   NS_ENSURE_SUCCESS(rv, rv);
 
   // Finally notify about the removed URIs.
   for (int32_t i = 0; i < URIs.Count(); ++i) {
     NOTIFY_OBSERVERS(mCanNotify, mCacheObservers, mObservers,
                      nsINavHistoryObserver,
--- a/toolkit/components/places/nsPlacesExpiration.js
+++ b/toolkit/components/places/nsPlacesExpiration.js
@@ -236,16 +236,25 @@ const EXPIRATION_QUERIES = {
             WHERE h.last_visit_date IS NULL
               AND h.foreign_count = 0
               AND v.id IS NULL
             LIMIT :limit_uris
           )`,
     actions: ACTION.CLEAR_HISTORY
   },
 
+  // Hosts accumulated during the places delete are updated through a trigger
+  // (see nsPlacesTriggers.h).
+  QUERY_UPDATE_HOSTS: {
+    sql: `DELETE FROM moz_updatehosts_temp`,
+    actions: ACTION.CLEAR_HISTORY | ACTION.TIMED | ACTION.TIMED_OVERLIMIT |
+             ACTION.SHUTDOWN_DIRTY | ACTION.IDLE_DIRTY | ACTION.IDLE_DAILY |
+             ACTION.DEBUG
+  },
+
   // Expire orphan icons from the database.
   QUERY_EXPIRE_FAVICONS: {
     sql: `DELETE FROM moz_favicons WHERE id IN (
             SELECT f.id FROM moz_favicons f
             LEFT JOIN moz_places h ON f.id = h.favicon_id
             WHERE h.favicon_id IS NULL
             LIMIT :limit_favicons
           )`,
--- a/toolkit/components/places/nsPlacesTables.h
+++ b/toolkit/components/places/nsPlacesTables.h
@@ -139,9 +139,20 @@
 //       nsPlacesAutoComplete.js.
 #define CREATE_MOZ_OPENPAGES_TEMP NS_LITERAL_CSTRING( \
   "CREATE TEMP TABLE moz_openpages_temp (" \
     "  url TEXT PRIMARY KEY" \
     ", open_count INTEGER" \
   ")" \
 )
 
+// This table is used, along with moz_places_afterdelete_trigger, to update
+// hosts after places removals. During a DELETE FROM moz_places, hosts are
+// accumulated into this table, then a DELETE FROM moz_updatehosts_temp will
+// take care of updating the moz_hosts table for every modified host.
+// See CREATE_PLACES_AFTERDELETE_TRIGGER in nsPlacestriggers.h for details.
+#define CREATE_UPDATEHOSTS_TEMP NS_LITERAL_CSTRING( \
+  "CREATE TEMP TABLE moz_updatehosts_temp (" \
+    "  host TEXT PRIMARY KEY " \
+  ") WITHOUT ROWID " \
+)
+
 #endif // __nsPlacesTables_h__
--- a/toolkit/components/places/nsPlacesTriggers.h
+++ b/toolkit/components/places/nsPlacesTriggers.h
@@ -105,30 +105,49 @@
        "FROM ( " \
           "SELECT fixup_url(get_unreversed_host(NEW.rev_host)) AS host " \
         ") AS match " \
       ") " \
     "); " \
   "END" \
 )
 
+// This is a hack to workaround the lack of FOR EACH STATEMENT in Sqlite, until
+// bug 871908 can be fixed properly.
+// We store the modified hosts in a temp table, and after every DELETE FROM
+// moz_places, we issue a DELETE FROM moz_updatehosts_temp.  The AFTER DELETE
+// trigger will then take care of updating the moz_hosts table.
+// Note this way we lose atomicity, crashing between the 2 queries may break the
+// hosts table coherency. So it's better to run those DELETE queries in a single
+// transaction.
+// Regardless, this is still better than hanging the browser for several minutes
+// on a fast machine.
 #define CREATE_PLACES_AFTERDELETE_TRIGGER NS_LITERAL_CSTRING( \
   "CREATE TEMP TRIGGER moz_places_afterdelete_trigger " \
   "AFTER DELETE ON moz_places FOR EACH ROW " \
   "BEGIN " \
+    "INSERT OR IGNORE INTO moz_updatehosts_temp (host)" \
+    "VALUES (fixup_url(get_unreversed_host(OLD.rev_host)));" \
+  "END" \
+)
+
+#define CREATE_UPDATEHOSTS_AFTERDELETE_TRIGGER NS_LITERAL_CSTRING( \
+  "CREATE TEMP TRIGGER moz_updatehosts_afterdelete_trigger " \
+  "AFTER DELETE ON moz_updatehosts_temp FOR EACH ROW " \
+  "BEGIN " \
     "DELETE FROM moz_hosts " \
-    "WHERE host = fixup_url(get_unreversed_host(OLD.rev_host)) " \
+    "WHERE host = OLD.host " \
       "AND NOT EXISTS(" \
         "SELECT 1 FROM moz_places " \
           "WHERE rev_host = get_unreversed_host(host || '.') || '.' " \
              "OR rev_host = get_unreversed_host(host || '.') || '.www.' " \
       "); " \
     "UPDATE moz_hosts " \
     "SET prefix = (" HOSTS_PREFIX_PRIORITY_FRAGMENT ") " \
-    "WHERE host = fixup_url(get_unreversed_host(OLD.rev_host)); " \
+    "WHERE host = OLD.host; " \
   "END" \
 )
 
 // For performance reasons the host frecency is updated only when the page
 // frecency changes by a meaningful percentage.  This is because the frecency
 // decay algorithm requires to update all the frecencies at once, causing a
 // too high overhead, while leaving the ordering unchanged.
 #define CREATE_PLACES_AFTERUPDATE_FRECENCY_TRIGGER NS_LITERAL_CSTRING( \
--- a/toolkit/components/telemetry/TelemetrySession.jsm
+++ b/toolkit/components/telemetry/TelemetrySession.jsm
@@ -655,23 +655,25 @@ var Impl = {
   // Null if this is the first run, or the previous build ID is unknown.
   _previousBuildId: null,
   // Telemetry payloads sent by child processes.
   // Each element is in the format {source: <weak-ref>, payload: <object>},
   // where source is a weak reference to the child process,
   // and payload is the telemetry payload from that child process.
   _childTelemetry: [],
   // Thread hangs from child processes.
+  // Used for TelemetrySession.getChildThreadHangs(); not sent with Telemetry pings.
+  // TelemetrySession.getChildThreadHangs() is used by extensions such as Statuser (https://github.com/chutten/statuser).
   // Each element is in the format {source: <weak-ref>, payload: <object>},
   // where source is a weak reference to the child process,
   // and payload contains the thread hang stats from that child process.
   _childThreadHangs: [],
-  // Array of the resolve functions of all the promises that are waiting for the child thread hang stats to arrive, used to resolve all those promises at once
+  // Array of the resolve functions of all the promises that are waiting for the child thread hang stats to arrive, used to resolve all those promises at once.
   _childThreadHangsResolveFunctions: [],
-  // Timeout function for child thread hang stats retrieval
+  // Timeout function for child thread hang stats retrieval.
   _childThreadHangsTimeout: null,
   // Unique id that identifies this session so the server can cope with duplicate
   // submissions, orphaning and other oddities. The id is shared across subsessions.
   _sessionId: null,
   // Random subsession id.
   _subsessionId: null,
   // Session id of the previous session, null on first run.
   _previousSessionId: null,
--- a/toolkit/mozapps/extensions/content/extensions.js
+++ b/toolkit/mozapps/extensions/content/extensions.js
@@ -200,18 +200,16 @@ function loadView(aViewId) {
 }
 
 function isCorrectlySigned(aAddon) {
   // temporary add-ons do not require signing
   if (aAddon.scope == AddonManager.SCOPE_TEMPORARY)
       return true;
   if (aAddon.signedState <= AddonManager.SIGNEDSTATE_MISSING)
     return false;
-  if (aAddon.foreignInstall && aAddon.signedState < AddonManager.SIGNEDSTATE_SIGNED)
-    return false;
   return true;
 }
 
 function isDiscoverEnabled() {
   if (Services.prefs.getPrefType(PREF_DISCOVERURL) == Services.prefs.PREF_INVALID)
     return false;
 
   try {
--- a/toolkit/mozapps/extensions/internal/XPIProvider.jsm
+++ b/toolkit/mozapps/extensions/internal/XPIProvider.jsm
@@ -688,18 +688,16 @@ function isUsableAddon(aAddon) {
     return false;
   }
   // temporary and system add-ons do not require signing
   if ((aAddon._installLocation.name != KEY_APP_SYSTEM_DEFAULTS &&
        aAddon._installLocation.name != KEY_APP_TEMPORARY) &&
        mustSign(aAddon.type)) {
     if (aAddon.signedState <= AddonManager.SIGNEDSTATE_MISSING)
       return false;
-    if (aAddon.foreignInstall && aAddon.signedState < AddonManager.SIGNEDSTATE_SIGNED)
-      return false;
   }
 
   if (aAddon.blocklistState == Blocklist.STATE_BLOCKED)
     return false;
 
   // Experiments are installed through an external mechanism that
   // limits target audience to compatible clients. We trust it knows what
   // it's doing and skip compatibility checks.
@@ -8010,16 +8008,22 @@ Object.assign(SystemAddonInstallLocation
   },
 
   /**
    * Removes any directories not currently in use or pending use after a
    * restart. Any errors that happen here don't really matter as we'll attempt
    * to cleanup again next time.
    */
   cleanDirectories: Task.async(function*() {
+
+    // System add-ons directory does not exist
+    if (!(yield OS.File.exists(this._baseDir.path))) {
+      return;
+    }
+
     let iterator;
     try {
       iterator = new OS.File.DirectoryIterator(this._baseDir.path);
     }
     catch (e) {
       logger.error("Failed to clean updated system add-ons directories.", e);
       return;
     }
--- a/toolkit/mozapps/extensions/test/browser/browser_details.js
+++ b/toolkit/mozapps/extensions/test/browser/browser_details.js
@@ -863,23 +863,21 @@ add_test(function() {
       open_details("addon11@tests.mozilla.org", "extension", function() {
         is(get("detail-name").textContent, "Test add-on 11", "Name should be correct");
 
         is_element_hidden(get("detail-prefs-btn"), "Preferences button should be hidden");
         is_element_hidden(get("detail-enable-btn"), "Enable button should be hidden");
         is_element_hidden(get("detail-disable-btn"), "Disable button should be hidden");
         is_element_visible(get("detail-uninstall-btn"), "Remove button should be visible");
 
-        is_element_hidden(get("detail-warning"), "Warning message should be hidden");
+        is_element_visible(get("detail-warning"), "Warning message should be visible");
+        is(get("detail-warning").textContent, "Test add-on 11 is incompatible with " + gApp + " " + gVersion + ".", "Warning message should be correct");
         is_element_hidden(get("detail-warning-link"), "Warning link should be hidden");
-        is_element_visible(get("detail-error"), "Error message should be visible");
-        is(get("detail-error").textContent, "Test add-on 11 could not be verified for use in " + gApp + " and has been disabled.", "Error message should be correct");
-        is_element_visible(get("detail-error-link"), "Error link should be visible");
-        is(get("detail-error-link").value, "More Information", "Error link text should be correct");
-        is(get("detail-error-link").href, infoURL, "Error link should be correct");
+        is_element_hidden(get("detail-error"), "Error message should be hidden");
+        is_element_hidden(get("detail-error-link"), "Error link should be hidden");
 
         close_manager(gManagerWindow, function() {
           Services.prefs.setBoolPref("xpinstall.signatures.required", false);
           open_manager(null, function(aWindow) {
             gManagerWindow = aWindow;
             gCategoryUtilities = new CategoryUtilities(gManagerWindow);
 
             run_next_test();
--- a/toolkit/mozapps/extensions/test/browser/browser_list.js
+++ b/toolkit/mozapps/extensions/test/browser/browser_list.js
@@ -105,18 +105,16 @@ add_task(function*() {
     signedState: AddonManager.SIGNEDSTATE_MISSING,
     isActive: false,
     isCompatible: false,
     appDisabled: true,
   }, {
     id: "addon12@tests.mozilla.org",
     name: "Test add-on 12",
     signedState: AddonManager.SIGNEDSTATE_PRELIMINARY,
-    isActive: false,
-    appDisabled: true,
     foreignInstall: true,
   }, {
     id: "addon13@tests.mozilla.org",
     name: "Test add-on 13",
     signedState: AddonManager.SIGNEDSTATE_SIGNED,
     foreignInstall: true,
   }, {
     id: "addon15@tests.mozilla.org",
@@ -887,26 +885,23 @@ add_task(function*() {
   addon = items["Test add-on 12"];
   addon.parentNode.ensureElementIsVisible(addon);
   ({ name, version } = yield get_tooltip_info(addon))
   is(get_node(addon, "name").value, "Test add-on 12", "Name should be correct");
   is(name, "Test add-on 12", "Tooltip name should be correct");
 
   is_element_hidden(get_node(addon, "preferences-btn"), "Preferences button should be hidden");
   is_element_hidden(get_node(addon, "enable-btn"), "Enable button should be hidden");
-  is_element_hidden(get_node(addon, "disable-btn"), "Disable button should be hidden");
+  is_element_visible(get_node(addon, "disable-btn"), "Disable button should be visible");
   is_element_visible(get_node(addon, "remove-btn"), "Remove button should be visible");
 
   is_element_hidden(get_node(addon, "warning"), "Warning message should be hidden");
   is_element_hidden(get_node(addon, "warning-link"), "Warning link should be hidden");
-  is_element_visible(get_node(addon, "error"), "Error message should be visible");
-  is(get_node(addon, "error").textContent, "Test add-on 12 could not be verified for use in " + gApp + " and has been disabled.", "Error message should be correct");
-  is_element_visible(get_node(addon, "error-link"), "Error link should be visible");
-  is(get_node(addon, "error-link").value, "More Information", "Error link text should be correct");
-  is(get_node(addon, "error-link").href, infoURL, "Error link should be correct");
+  is_element_hidden(get_node(addon, "error"), "Error message should be hidden");
+  is_element_hidden(get_node(addon, "error-link"), "Error link should be hidden");
 
   info("Addon 13");
   addon = items["Test add-on 13"];
   addon.parentNode.ensureElementIsVisible(addon);
   ({ name, version } = yield get_tooltip_info(addon));
   is(get_node(addon, "name").value, "Test add-on 13", "Name should be correct");
   is(name, "Test add-on 13", "Tooltip name should be correct");
 
@@ -931,20 +926,19 @@ add_task(function*() {
 
   yield new Promise(resolve => wait_for_view_load(gManagerWindow, resolve));
 
   is_element_hidden(filterButton, "Button for showing disabled unsigned extensions should be hidden");
   is_element_visible(showAllButton, "Button for showing all extensions should be visible");
   is_element_visible(signingInfoUI, "Signing info UI should be visible");
 
   items = get_test_items();
-  is(Object.keys(items).length, 3, "Two add-ons should be shown");
+  is(Object.keys(items).length, 2, "Two add-ons should be shown");
   is(Object.keys(items)[0], "Test add-on 10", "The disabled unsigned extension should be shown");
   is(Object.keys(items)[1], "Test add-on 11", "The disabled unsigned extension should be shown");
-  is(Object.keys(items)[2], "Test add-on 12", "The disabled foreign installed extension should be shown");
 
   showAllButton.click();
 
   yield new Promise(resolve => wait_for_view_load(gManagerWindow, resolve));
 
   items = get_test_items();
   is(Object.keys(items).length, EXPECTED_ADDONS, "All add-ons should be shown again");
   is_element_visible(filterButton, "Button for showing disabled unsigned extensions should be visible again");
--- a/toolkit/mozapps/extensions/test/xpcshell/test_signed_inject.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_signed_inject.js
@@ -331,29 +331,29 @@ add_task(function*() {
 
   do_check_false(file.exists());
   clearCache(file);
 
   yield promiseShutdownManager();
   resetPrefs();
 });
 
-// Only fully-signed sideloaded add-ons should work
+// Preliminarily-signed sideloaded add-ons should work
 add_task(function*() {
   let file = manuallyInstall(do_get_file(DATA + ADDONS.bootstrap.preliminary), profileDir, ID);
 
   startupManager();
 
   // Currently we leave the sideloaded add-on there but just don't run it
   let addon = yield promiseAddonByID(ID);
   do_check_neq(addon, null);
-  do_check_true(addon.appDisabled);
-  do_check_false(addon.isActive);
+  do_check_false(addon.appDisabled);
+  do_check_true(addon.isActive);
   do_check_eq(addon.signedState, AddonManager.SIGNEDSTATE_PRELIMINARY);
-  do_check_eq(getActiveVersion(), -1);
+  do_check_eq(getActiveVersion(), 2);
 
   addon.uninstall();
   yield promiseShutdownManager();
   resetPrefs();
 
   do_check_false(file.exists());
   clearCache(file);
 });
--- a/uriloader/base/nsIWebProgressListener.idl
+++ b/uriloader/base/nsIWebProgressListener.idl
@@ -257,19 +257,23 @@ interface nsIWebProgressListener : nsISu
     *
     * These flags describe the reason of the broken state.
     *
     * STATE_USES_SSL_3
     *   The topmost document uses SSL 3.0.
     *
     * STATE_USES_WEAK_CRYPTO
     *   The topmost document uses a weak cipher suite such as RC4.
+    *
+    * STATE_CERT_USER_OVERRIDDEN
+    *   The user has added a security exception for the site.
     */
   const unsigned long STATE_USES_SSL_3                = 0x01000000;
   const unsigned long STATE_USES_WEAK_CRYPTO          = 0x02000000;
+  const unsigned long STATE_CERT_USER_OVERRIDDEN      = 0x04000000;
 
   /**
    * Notification indicating the state has changed for one of the requests
    * associated with aWebProgress.
    *
    * @param aWebProgress
    *        The nsIWebProgress instance that fired the notification
    * @param aRequest