Merge inbound to mozilla-central. a=merge
authorNarcis Beleuzu <nbeleuzu@mozilla.com>
Sat, 31 Mar 2018 00:49:19 +0300
changeset 410815 f4fcdaef616807d1e31a849db0c60a3a4bdc8778
parent 410795 10c662d8416e84b44931d767ea1be2f4d0cc92ce (current diff)
parent 410814 280491d65d24816f1d8beb1e38b738e000ca3df8 (diff)
child 410827 8059b8b2abe9aee49ae69c804b8d9fef6ae42df5
push id33740
push usernbeleuzu@mozilla.com
push dateFri, 30 Mar 2018 21:49:44 +0000
treeherdermozilla-central@f4fcdaef6168 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone61.0a1
first release with
nightly linux32
f4fcdaef6168 / 61.0a1 / 20180330220126 / files
nightly linux64
f4fcdaef6168 / 61.0a1 / 20180330220126 / files
nightly mac
f4fcdaef6168 / 61.0a1 / 20180330220126 / files
nightly win32
f4fcdaef6168 / 61.0a1 / 20180330220126 / files
nightly win64
f4fcdaef6168 / 61.0a1 / 20180330220126 / files
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
releases
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge inbound to mozilla-central. a=merge
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -6122,16 +6122,21 @@ function handleLinkClick(event, href, li
     params.userContextId = doc.nodePrincipal.originAttributes.userContextId;
   }
 
   openLinkIn(href, where, params);
   event.preventDefault();
   return true;
 }
 
+/**
+ * Handles paste on middle mouse clicks.
+ *
+ * @param event {Event | Object} Event or JSON object.
+ */
 function middleMousePaste(event) {
   let clipboard = readFromClipboard();
   if (!clipboard)
     return;
 
   // Strip embedded newlines and surrounding whitespace, to match the URL
   // bar's behavior (stripsurroundingwhitespace)
   clipboard = clipboard.replace(/\s*\n\s*/g, "");
@@ -6165,17 +6170,19 @@ function middleMousePaste(event) {
     if (where != "current" ||
         lastLocationChange == gBrowser.selectedBrowser.lastLocationChange) {
       openUILink(data.url, event,
                  { ignoreButton: true,
                    disallowInheritPrincipal: !data.mayInheritPrincipal });
     }
   });
 
-  event.stopPropagation();
+  if (event instanceof Event) {
+    event.stopPropagation();
+  }
 }
 
 function stripUnsafeProtocolOnPaste(pasteData) {
   // Don't allow pasting javascript URIs since we don't support
   // LOAD_FLAGS_DISALLOW_INHERIT_PRINCIPAL for those.
   while (true) {
     let scheme = "";
     try {
--- a/browser/base/content/utilityOverlay.js
+++ b/browser/base/content/utilityOverlay.js
@@ -78,20 +78,30 @@ function getBoolPref(prefname, def) {
 function doGetProtocolFlags(aURI) {
   let handler = Services.io.getProtocolHandler(aURI.scheme);
   // see DoGetProtocolFlags in nsIProtocolHandler.idl
   return handler instanceof Ci.nsIProtocolHandlerWithDynamicFlags ?
          handler.QueryInterface(Ci.nsIProtocolHandlerWithDynamicFlags).getFlagsForURI(aURI) :
          handler.protocolFlags;
 }
 
-/* openUILink handles clicks on UI elements that cause URLs to load.
+/**
+ * openUILink handles clicks on UI elements that cause URLs to load.
  *
  * As the third argument, you may pass an object with the same properties as
  * accepted by openUILinkIn, plus "ignoreButton" and "ignoreAlt".
+ *
+ * @param url {string}
+ * @param event {Event | Object} Event or JSON object representing an Event
+ * @param {Boolean | Object} aIgnoreButton
+ * @param {Boolean} aIgnoreButton
+ * @param {Boolean} aIgnoreAlt
+ * @param {Boolean} aAllowThirdPartyFixup
+ * @param {Object} aPostData
+ * @param {nsIURI} aReferrerURI
  */
 function openUILink(url, event, aIgnoreButton, aIgnoreAlt, aAllowThirdPartyFixup,
                     aPostData, aReferrerURI) {
   let params;
 
   if (aIgnoreButton && typeof aIgnoreButton == "object") {
     params = aIgnoreButton;
 
@@ -110,17 +120,18 @@ function openUILink(url, event, aIgnoreB
     };
   }
 
   let where = whereToOpenLink(event, aIgnoreButton, aIgnoreAlt);
   openUILinkIn(url, where, params);
 }
 
 
-/* whereToOpenLink() looks at an event to decide where to open a link.
+/**
+ * whereToOpenLink() looks at an event to decide where to open a link.
  *
  * The event may be a mouse event (click, double-click, middle-click) or keypress event (enter).
  *
  * On Windows, the modifiers are:
  * Ctrl        new tab, selected
  * Shift       new window
  * Ctrl+Shift  new tab, in background
  * Alt         save
@@ -128,16 +139,21 @@ function openUILink(url, event, aIgnoreB
  * Middle-clicking is the same as Ctrl+clicking (it opens a new tab).
  *
  * Exceptions:
  * - Alt is ignored for menu items selected using the keyboard so you don't accidentally save stuff.
  *    (Currently, the Alt isn't sent here at all for menu items, but that will change in bug 126189.)
  * - Alt is hard to use in context menus, because pressing Alt closes the menu.
  * - Alt can't be used on the bookmarks toolbar because Alt is used for "treat this as something draggable".
  * - The button is ignored for the middle-click-paste-URL feature, since it's always a middle-click.
+ *
+ * @param e {Event|Object} Event or JSON Object
+ * @param ignoreButton {Boolean}
+ * @param ignoreAlt {Boolean}
+ * @returns {"current" | "tabshifted" | "tab" | "save" | "window"}
  */
 function whereToOpenLink(e, ignoreButton, ignoreAlt) {
   // This method must treat a null event like a left click without modifier keys (i.e.
   // e = { shiftKey:false, ctrlKey:false, metaKey:false, altKey:false, button:0 })
   // for compatibility purposes.
   if (!e)
     return "current";
 
--- a/browser/components/extensions/test/browser/browser_ext_tabs_events.js
+++ b/browser/components/extensions/test/browser/browser_ext_tabs_events.js
@@ -298,8 +298,60 @@ add_task(async function testTabRemovalEv
 
     background,
   });
 
   await extension.startup();
   await extension.awaitFinish("tabs-events");
   await extension.unload();
 });
+
+add_task(async function testTabCreateRelated() {
+  await SpecialPowers.pushPrefEnv({set: [
+    ["browser.tabs.opentabfor.middleclick", true],
+    ["browser.tabs.insertRelatedAfterCurrent", true],
+  ]});
+
+  async function background() {
+    let created;
+    browser.tabs.onCreated.addListener(tab => {
+      browser.test.log(`tabs.onCreated, index=${tab.index}`);
+      browser.test.assertEq(1, tab.index, "expecting tab index of 1");
+      created = tab.id;
+    });
+    browser.tabs.onMoved.addListener((id, info) => {
+      browser.test.log(`tabs.onMoved, from ${info.fromIndex} to ${info.toIndex}`);
+      browser.test.fail("tabMoved was received");
+    });
+    browser.tabs.onRemoved.addListener((tabId, info) => {
+      browser.test.assertEq(created, tabId, "removed id same as created");
+      browser.test.sendMessage("tabRemoved");
+    });
+  }
+
+  let extension = ExtensionTestUtils.loadExtension({
+    manifest: {
+      "permissions": ["tabs"],
+    },
+
+    background,
+  });
+
+  // Create a *opener* tab page which has a link to "example.com".
+  let pageURL = "http://example.com/browser/browser/components/extensions/test/browser/file_dummy.html";
+  let openerTab = await BrowserTestUtils.openNewForegroundTab(gBrowser, pageURL);
+  gBrowser.moveTabTo(openerTab, 0);
+
+  await extension.startup();
+
+  let newTabPromise = BrowserTestUtils.waitForNewTab(gBrowser, "http://example.com/#linkclick", true);
+  await BrowserTestUtils.synthesizeMouseAtCenter("#link_to_example_com",
+                                                 {button: 1}, gBrowser.selectedBrowser);
+  let openTab = await newTabPromise;
+  is(openTab.linkedBrowser.currentURI.spec, "http://example.com/#linkclick",
+     "Middle click should open site to correct url.");
+  BrowserTestUtils.removeTab(openTab);
+
+  await extension.awaitMessage("tabRemoved");
+  await extension.unload();
+
+  BrowserTestUtils.removeTab(openerTab);
+});
--- a/browser/components/extensions/test/browser/file_dummy.html
+++ b/browser/components/extensions/test/browser/file_dummy.html
@@ -1,9 +1,10 @@
 <html>
 <head>
 <title>Dummy test page</title>
 <meta http-equiv="Content-Type" content="text/html;charset=utf-8"></meta>
 </head>
 <body>
 <p>Dummy test page</p>
+<a id="link_to_example_com" href="http://example.com/#linkclick">link</a>
 </body>
 </html>
--- a/browser/components/nsBrowserContentHandler.js
+++ b/browser/components/nsBrowserContentHandler.js
@@ -168,165 +168,167 @@ function getPostUpdateOverridePage(defau
   // The existence of silent or the non-existence of showURL in the actions both
   // mean that an override page should not be displayed.
   if (actions.includes("silent") || !actions.includes("showURL"))
     return "";
 
   return update.getProperty("openURL") || defaultOverridePage;
 }
 
-// Flag used to indicate that the arguments to openWindow can be passed directly.
-const NO_EXTERNAL_URIS = 1;
-
-function openWindow(parent, url, target, features, args, noExternalArgs) {
-  if (noExternalArgs == NO_EXTERNAL_URIS) {
-    // Just pass in the defaultArgs directly
-    var argstring;
-    if (args) {
-      argstring = Cc["@mozilla.org/supports-string;1"]
-                    .createInstance(nsISupportsString);
-      argstring.data = args;
-    }
-
-    return Services.ww.openWindow(parent, url, target, features, argstring);
-  }
+/**
+ * Open a browser window. If this is the initial launch, this function will
+ * attempt to use the navigator:blank window opened by nsBrowserGlue.js during
+ * early startup.
+ *
+ * @param cmdLine
+ *        The nsICommandLine object given to nsICommandLineHandler's handle
+ *        method.
+ *        Used to check if we are processing the command line for the initial launch.
+ * @param urlOrUrlList (optional)
+ *        When omitted, the browser window will be opened with the default
+ *        arguments, which will usually load the homepage.
+ *        This can be a JS array of urls provided as strings, each url will be
+ *        loaded in a tab. postData will be ignored in this case.
+ *        This can be a single url to load in the new window, provided as a string.
+ *        postData will be used in this case if provided.
+ * @param postData (optional)
+ *        An nsIInputStream object to use as POST data when loading the provided
+ *        url, or null.
+ * @param forcePrivate (optional)
+ *        Boolean. If set to true, the new window will be a private browsing one.
+ */
+function openBrowserWindow(cmdLine, urlOrUrlList, postData = null,
+                           forcePrivate = false) {
+  let chromeURL = Services.prefs.getCharPref("browser.chromeURL");
 
-  // Pass an array to avoid the browser "|"-splitting behavior.
-  var argArray = Cc["@mozilla.org/array;1"]
-                    .createInstance(Ci.nsIMutableArray);
-
-  // add args to the arguments array
-  var stringArgs = null;
-  if (args instanceof Array) // array
-    stringArgs = args;
-  else if (args) // string
-    stringArgs = [args];
-
-  if (stringArgs) {
-    // put the URIs into argArray
-    var uriArray = Cc["@mozilla.org/array;1"]
-                       .createInstance(Ci.nsIMutableArray);
-    stringArgs.forEach(function(uri) {
+  let args;
+  if (!urlOrUrlList) {
+    // Just pass in the defaultArgs directly
+    args = [gBrowserContentHandler.defaultArgs];
+  } else if (Array.isArray(urlOrUrlList)) {
+    // Passing an nsIArray for the url disables the "|"-splitting behavior.
+    let uriArray = Cc["@mozilla.org/array;1"]
+                     .createInstance(Ci.nsIMutableArray);
+    urlOrUrlList.forEach(function(uri) {
       var sstring = Cc["@mozilla.org/supports-string;1"]
                       .createInstance(nsISupportsString);
       sstring.data = uri;
       uriArray.appendElement(sstring);
     });
-    argArray.appendElement(uriArray);
+    args = [uriArray];
   } else {
-    argArray.appendElement(null);
+    // Always pass at least 3 arguments to avoid the "|"-splitting behavior,
+    // ie. avoid the loadOneOrMoreURIs function.
+    args = [urlOrUrlList,
+            null, // charset
+            null, // referer
+            postData];
   }
 
-  // Pass these as null to ensure that we always trigger the "single URL"
-  // behavior in browser.js's gBrowserInit.onLoad (which handles the window
-  // arguments)
-  argArray.appendElement(null); // charset
-  argArray.appendElement(null); // referer
-  argArray.appendElement(null); // postData
-  argArray.appendElement(null); // allowThirdPartyFixup
+  if (cmdLine.state == nsICommandLine.STATE_INITIAL_LAUNCH) {
+    let win = Services.wm.getMostRecentWindow("navigator:blank");
+    if (win) {
+      // Remove the windowtype of our blank window so that we don't close it
+      // later on when seeing cmdLine.preventDefault is true.
+      win.document.documentElement.removeAttribute("windowtype");
+
+      if (forcePrivate) {
+        // This causes a "Only internal code is allowed to set the
+        // usePrivateBrowsing attribute" warning in the Browser Console.
+        // Still better than having a white window that flickers.
+        win.QueryInterface(Ci.nsIInterfaceRequestor)
+           .getInterface(Ci.nsIWebNavigation)
+           .QueryInterface(Ci.nsILoadContext)
+           .usePrivateBrowsing = true;
+      }
+
+      win.location = chromeURL;
+      win.arguments = args; // <-- needs to be a plain JS array here.
+
+      return;
+    }
+  }
 
-  return Services.ww.openWindow(parent, url, target, features, argArray);
+  // We can't provide arguments to openWindow as a JS array.
+  if (!urlOrUrlList) {
+    // If we have a single string guaranteed to not contain '|' we can simply
+    // wrap it in an nsISupportsString object.
+    let [url] = args;
+    args = Cc["@mozilla.org/supports-string;1"]
+             .createInstance(nsISupportsString);
+    args.data = url;
+  } else {
+    // Otherwise, pass an nsIArray.
+    if (args.length > 1) {
+      let string = Cc["@mozilla.org/supports-string;1"]
+                     .createInstance(Ci.nsISupportsString);
+      string.data = args[0];
+      args[0] = string;
+    }
+    let array = Cc["@mozilla.org/array;1"].createInstance(Ci.nsIMutableArray);
+    args.forEach(a => { array.appendElement(a); });
+    args = array;
+  }
+
+  let features = "chrome,dialog=no,all" + gBrowserContentHandler.getFeatures(cmdLine);
+  if (forcePrivate)
+    features += ",private";
+
+  Services.ww.openWindow(null, chromeURL, "_blank", features, args);
 }
 
-function openPreferences(extraArgs) {
+function openPreferences(cmdLine, extraArgs) {
   if (extraArgs && extraArgs.origin) {
     Services.telemetry.getHistogramById("FX_PREFERENCES_OPENED_VIA").add(extraArgs.origin);
   } else {
     Services.telemetry.getHistogramById("FX_PREFERENCES_OPENED_VIA").add("other");
   }
-  var args = Cc["@mozilla.org/array;1"]
-                     .createInstance(Ci.nsIMutableArray);
-
-  var wuri = Cc["@mozilla.org/supports-string;1"]
-               .createInstance(Ci.nsISupportsString);
-  wuri.data = "about:preferences";
-
-  args.appendElement(wuri);
-
-  Services.ww.openWindow(null, gBrowserContentHandler.chromeURL,
-                         "_blank",
-                         "chrome,dialog=no,all",
-                         args);
+  openBrowserWindow(cmdLine, "about:preferences");
 }
 
 function logSystemBasedSearch(engine) {
   var countId = (engine.identifier || ("other-" + engine.name)) + ".system";
   var count = Services.telemetry.getKeyedHistogramById("SEARCH_COUNTS");
   count.add(countId);
 }
 
 function doSearch(searchTerm, cmdLine) {
   var engine = Services.search.defaultEngine;
   logSystemBasedSearch(engine);
 
   var submission = engine.getSubmission(searchTerm, null, "system");
 
-  // fill our nsIMutableArray with uri-as-wstring, null, null, postData
-  var args = Cc["@mozilla.org/array;1"]
-                     .createInstance(Ci.nsIMutableArray);
-
-  var wuri = Cc["@mozilla.org/supports-string;1"]
-               .createInstance(Ci.nsISupportsString);
-  wuri.data = submission.uri.spec;
-
-  args.appendElement(wuri);
-  args.appendElement(null);
-  args.appendElement(null);
-  args.appendElement(submission.postData);
-
   // XXXbsmedberg: use handURIToExistingBrowser to obey tabbed-browsing
   // preferences, but need nsIBrowserDOMWindow extensions
-
-  return Services.ww.openWindow(null, gBrowserContentHandler.chromeURL,
-                                "_blank",
-                                "chrome,dialog=no,all" +
-                                gBrowserContentHandler.getFeatures(cmdLine),
-                                args);
+  openBrowserWindow(cmdLine, submission.uri.spec, submission.postData);
 }
 
 function nsBrowserContentHandler() {
 }
 nsBrowserContentHandler.prototype = {
   classID: Components.ID("{5d0ce354-df01-421a-83fb-7ead0990c24e}"),
 
   _xpcom_factory: {
     createInstance: function bch_factory_ci(outer, iid) {
       if (outer)
         throw Cr.NS_ERROR_NO_AGGREGATION;
       return gBrowserContentHandler.QueryInterface(iid);
     }
   },
 
-  /* helper functions */
-
-  mChromeURL: null,
-
-  get chromeURL() {
-    if (this.mChromeURL) {
-      return this.mChromeURL;
-    }
-
-    this.mChromeURL = Services.prefs.getCharPref("browser.chromeURL");
-
-    return this.mChromeURL;
-  },
-
   /* nsISupports */
   QueryInterface: XPCOMUtils.generateQI([nsICommandLineHandler,
                                          nsIBrowserHandler,
                                          nsIContentHandler,
                                          nsICommandLineValidator]),
 
   /* nsICommandLineHandler */
   handle: function bch_handle(cmdLine) {
     if (cmdLine.handleFlag("browser", false)) {
-      // Passing defaultArgs, so use NO_EXTERNAL_URIS
-      openWindow(null, this.chromeURL, "_blank",
-                 "chrome,dialog=no,all" + this.getFeatures(cmdLine),
-                 this.defaultArgs, NO_EXTERNAL_URIS);
+      openBrowserWindow(cmdLine);
       cmdLine.preventDefault = true;
     }
 
     // In the past, when an instance was not already running, the -remote
     // option returned an error code. Any script or application invoking the
     // -remote option is expected to be handling this case, otherwise they
     // wouldn't be doing anything when there is no Firefox already running.
     // Making the -remote option always return an error code makes those
@@ -337,19 +339,17 @@ nsBrowserContentHandler.prototype = {
     }
 
     var uriparam;
     try {
       while ((uriparam = cmdLine.handleFlagWithParam("new-window", false))) {
         let uri = resolveURIInternal(cmdLine, uriparam);
         if (!shouldLoadURI(uri))
           continue;
-        openWindow(null, this.chromeURL, "_blank",
-                   "chrome,dialog=no,all" + this.getFeatures(cmdLine),
-                   uri.spec);
+        openBrowserWindow(cmdLine, uri.spec);
         cmdLine.preventDefault = true;
       }
     } catch (e) {
       Cu.reportError(e);
     }
 
     try {
       while ((uriparam = cmdLine.handleFlagWithParam("new-tab", false))) {
@@ -363,42 +363,42 @@ nsBrowserContentHandler.prototype = {
     }
 
     var chromeParam = cmdLine.handleFlagWithParam("chrome", false);
     if (chromeParam) {
 
       // Handle old preference dialog URLs.
       if (chromeParam == "chrome://browser/content/pref/pref.xul" ||
           chromeParam == "chrome://browser/content/preferences/preferences.xul") {
-        openPreferences({origin: "commandLineLegacy"});
+        openPreferences(cmdLine, {origin: "commandLineLegacy"});
         cmdLine.preventDefault = true;
       } else try {
         let resolvedURI = resolveURIInternal(cmdLine, chromeParam);
         let isLocal = uri => {
           let localSchemes = new Set(["chrome", "file", "resource"]);
           if (uri instanceof Ci.nsINestedURI) {
             uri = uri.QueryInterface(Ci.nsINestedURI).innerMostURI;
           }
           return localSchemes.has(uri.scheme);
         };
         if (isLocal(resolvedURI)) {
           // If the URI is local, we are sure it won't wrongly inherit chrome privs
           let features = "chrome,dialog=no,all" + this.getFeatures(cmdLine);
-          openWindow(null, resolvedURI.spec, "_blank", features);
+          Services.ww.openWindow(null, resolvedURI.spec, "_blank", features, null);
           cmdLine.preventDefault = true;
         } else {
           dump("*** Preventing load of web URI as chrome\n");
           dump("    If you're trying to load a webpage, do not pass --chrome.\n");
         }
       } catch (e) {
         Cu.reportError(e);
       }
     }
     if (cmdLine.handleFlag("preferences", false)) {
-      openPreferences({origin: "commandLineLegacy"});
+      openPreferences(cmdLine, {origin: "commandLineLegacy"});
       cmdLine.preventDefault = true;
     }
     if (cmdLine.handleFlag("silent", false))
       cmdLine.preventDefault = true;
 
     try {
       var privateWindowParam = cmdLine.handleFlagWithParam("private-window", false);
       if (privateWindowParam) {
@@ -417,23 +417,18 @@ nsBrowserContentHandler.prototype = {
         cmdLine.preventDefault = true;
       }
     } catch (e) {
       if (e.result != Cr.NS_ERROR_INVALID_ARG) {
         throw e;
       }
       // NS_ERROR_INVALID_ARG is thrown when flag exists, but has no param.
       if (cmdLine.handleFlag("private-window", false)) {
-        let features = "chrome,dialog=no,all";
-        if (PrivateBrowsingUtils.enabled) {
-          features += ",private";
-        }
-        openWindow(null, this.chromeURL, "_blank",
-          features + this.getFeatures(cmdLine),
-          "about:privatebrowsing");
+        openBrowserWindow(cmdLine, "about:privatebrowsing", null,
+                          PrivateBrowsingUtils.enabled);
         cmdLine.preventDefault = true;
       }
     }
 
     var searchParam = cmdLine.handleFlagWithParam("search", false);
     if (searchParam) {
       doSearch(searchParam, cmdLine);
       cmdLine.preventDefault = true;
@@ -444,19 +439,17 @@ nsBrowserContentHandler.prototype = {
     if (cmdLine.handleFlag("private", false) && PrivateBrowsingUtils.enabled) {
       PrivateBrowsingUtils.enterTemporaryAutoStartMode();
     }
 
     var fileParam = cmdLine.handleFlagWithParam("file", false);
     if (fileParam) {
       var file = cmdLine.resolveFile(fileParam);
       var fileURI = Services.io.newFileURI(file);
-      openWindow(null, this.chromeURL, "_blank",
-                 "chrome,dialog=no,all" + this.getFeatures(cmdLine),
-                 fileURI.spec);
+      openBrowserWindow(cmdLine, fileURI.spec);
       cmdLine.preventDefault = true;
     }
 
     if (AppConstants.platform == "win") {
       // Handle "? searchterm" for Windows Vista start menu integration
       for (var i = cmdLine.length - 1; i >= 0; --i) {
         var param = cmdLine.getArgument(i);
         if (param.match(/^\? /)) {
@@ -685,21 +678,17 @@ function handURIToExistingBrowser(uri, l
     return;
 
   // Unless using a private window is forced, open external links in private
   // windows only if we're in perma-private mode.
   var allowPrivate = forcePrivate || PrivateBrowsingUtils.permanentPrivateBrowsing;
   var navWin = RecentWindow.getMostRecentBrowserWindow({private: allowPrivate});
   if (!navWin) {
     // if we couldn't load it in an existing window, open a new one
-    var features = "chrome,dialog=no,all" + gBrowserContentHandler.getFeatures(cmdLine);
-    if (forcePrivate) {
-      features += ",private";
-    }
-    openWindow(null, gBrowserContentHandler.chromeURL, "_blank", features, uri.spec);
+    openBrowserWindow(cmdLine, uri.spec, null, forcePrivate);
     return;
   }
 
   var navNav = navWin.QueryInterface(nsIInterfaceRequestor)
                      .getInterface(nsIWebNavigation);
   var rootItem = navNav.QueryInterface(nsIDocShellTreeItem).rootTreeItem;
   var rootWin = rootItem.QueryInterface(nsIInterfaceRequestor)
                         .getInterface(nsIDOMWindow);
@@ -789,45 +778,31 @@ nsDefaultCommandLineHandler.prototype = 
                                    Services.scriptSecurityManager.getSystemPrincipal());
           return;
         } catch (e) {
         }
       }
 
       var URLlist = urilist.filter(shouldLoadURI).map(u => u.spec);
       if (URLlist.length) {
-        openWindow(null, gBrowserContentHandler.chromeURL, "_blank",
-                   "chrome,dialog=no,all" + gBrowserContentHandler.getFeatures(cmdLine),
-                   URLlist);
+        openBrowserWindow(cmdLine, URLlist);
       }
 
     } else if (!cmdLine.preventDefault) {
       if (AppConstants.isPlatformAndVersionAtLeast("win", "10") &&
           cmdLine.state != nsICommandLine.STATE_INITIAL_LAUNCH &&
           WindowsUIUtils.inTabletMode) {
         // In windows 10 tablet mode, do not create a new window, but reuse the existing one.
         let win = RecentWindow.getMostRecentBrowserWindow();
         if (win) {
           win.focus();
           return;
         }
       }
-      if (cmdLine.state == nsICommandLine.STATE_INITIAL_LAUNCH) {
-        let win = Services.wm.getMostRecentWindow("navigator:blank");
-        if (win) {
-          win.location = gBrowserContentHandler.chromeURL;
-          win.arguments = [gBrowserContentHandler.defaultArgs];
-          return;
-        }
-      }
-
-      // Passing defaultArgs, so use NO_EXTERNAL_URIS
-      openWindow(null, gBrowserContentHandler.chromeURL, "_blank",
-                 "chrome,dialog=no,all" + gBrowserContentHandler.getFeatures(cmdLine),
-                 gBrowserContentHandler.defaultArgs, NO_EXTERNAL_URIS);
+      openBrowserWindow(cmdLine);
     } else {
       // Need a better solution in the future to avoid opening the blank window
       // when command line parameters say we are not going to show a browser
       // window, but for now the blank window getting closed quickly (and
       // causing only a slight flicker) is better than leaving it open.
       let win = Services.wm.getMostRecentWindow("navigator:blank");
       if (win)
         win.close();
--- a/browser/modules/ContentClick.jsm
+++ b/browser/modules/ContentClick.jsm
@@ -20,16 +20,22 @@ var ContentClick = {
   receiveMessage(message) {
     switch (message.name) {
       case "Content:Click":
         this.contentAreaClick(message.json, message.target);
         break;
     }
   },
 
+  /**
+   * Handles clicks in the content area.
+   *
+   * @param json {Object} JSON object that looks like an Event
+   * @param browser {Element<browser>}
+   */
   contentAreaClick(json, browser) {
     // This is heavily based on contentAreaClick from browser.js (Bug 903016)
     // The json is set up in a way to look like an Event.
     let window = browser.ownerGlobal;
 
     if (!json.href) {
       // Might be middle mouse navigation.
       if (Services.prefs.getBoolPref("middlemouse.contentLoadURL") &&
--- a/build/valgrind/x86_64-pc-linux-gnu.sup
+++ b/build/valgrind/x86_64-pc-linux-gnu.sup
@@ -344,16 +344,189 @@
 {
    Bug 1437796 SmallVec::grow in Servo_InvalidateStyleForDocStateChanges, January 2018
    Memcheck:Cond
    fun:_ZN36_$LT$smallvec..SmallVec$LT$A$GT$$GT$4grow*
    fun:Servo_InvalidateStyleForDocStateChanges
    fun:_ZN7mozilla13ServoStyleSet38InvalidateStyleForDocumentStateChangesENS_11EventStatesE
 }
 
+
+##############################################################################
+## BEGIN suppressions for Stylo as compiled by rustc 1.25.0
+# Even more similar issues, resulting from transitioning to rustc 1.25.0.
+# See bug 1447137.
+
+
+# Suppressions rooted at *style*values*specified*color*Color*style*parser*Parse*parse*
+
+{
+   bug1447137-01
+   Memcheck:Cond
+   fun:*style*values*specified*color*Color*style*parser*Parse*parse*
+   fun:*style*SimpleShadow*
+   fun:*style*parse_value*
+   fun:*style*parse_into*
+}
+
+{
+   bug1447137-02
+   Memcheck:Cond
+   fun:*style*values*specified*color*Color*style*parser*Parse*parse*
+   fun:*style*UrlPaintServer*
+   fun:*style*parse_value*
+   fun:*style*parse_into*
+}
+
+{
+   bug1447137-03
+   Memcheck:Cond
+   fun:*style*values*specified*color*Color*style*parser*Parse*parse*
+   fun:*style*parse_fallback*
+   fun:*style*UrlPaintServer*
+   fun:*style*parse_value*
+}
+
+{
+   bug1447137-04
+   Memcheck:Cond
+   fun:*style*values*specified*color*Color*style*parser*Parse*parse*
+   fun:*style*parse_border*
+   fun:*style*parse_into*
+   fun:*style*parse_value*
+}
+
+{
+   bug1447137-05
+   Memcheck:Cond
+   fun:*style*values*specified*color*Color*style*parser*Parse*parse*
+   fun:*style*parse_quirky*
+   fun:*style*parse_value*
+   fun:*style*parse_into*
+}
+
+{
+   bug1447137-06
+   Memcheck:Cond
+   fun:*style*values*specified*color*Color*style*parser*Parse*parse*
+   fun:*style*parse_value*
+   fun:*style*parse_into*
+   fun:*style*parse_value*
+}
+
+{
+   bug1447137-07
+   Memcheck:Cond
+   fun:*style*values*specified*color*Color*style*parser*Parse*parse*
+   fun:*style*parse_border*
+   fun:*style*parse_value*
+   fun:*style*parse_into*
+}
+
+{
+   bug1447137-08
+   Memcheck:Cond
+   fun:*style*values*specified*color*Color*style*parser*Parse*parse*
+   fun:*style*parse_quirky*
+   fun:*style*parse_value*
+   fun:*style*substitute_variables*
+}
+
+{
+   bug1447137-09
+   Memcheck:Cond
+   fun:*style*values*specified*color*Color*style*parser*Parse*parse*
+   fun:*style*Gradient*
+   fun:*style*Parse*
+   fun:*style*parse_value*
+}
+
+{
+   bug1447137-10
+   Memcheck:Cond
+   fun:*style*values*specified*color*Color*style*parser*Parse*parse*
+   fun:*style*SimpleShadow*
+   fun:*style*parse*closure*
+   fun:*style*parse_value*
+}
+
+{
+   bug1447137-11
+   Memcheck:Cond
+   fun:*style*values*specified*color*Color*style*parser*Parse*parse*
+   fun:*style*parse_border*
+   fun:*style*substitute_variables*
+   fun:*style*substitute_variables*
+}
+
+{
+   bug1447137-12
+   Memcheck:Cond
+   fun:*style*values*specified*color*Color*style*parser*Parse*parse*
+   fun:*style*parse_border*
+   fun:*style*parse_value*
+   fun:*style*substitute_variables*
+}
+
+{
+   bug1447137-13
+   Memcheck:Cond
+   fun:*style*values*specified*color*Color*style*parser*Parse*parse*
+   fun:*style*parse_value*
+   fun:*style*substitute_variables*
+   fun:*style*substitute_variables*
+}
+
+
+{
+   bug1447137-14
+   Memcheck:Cond
+   fun:*style*values*specified*color*Color*style*parser*Parse*parse*
+   fun:*style*parse_value*
+   fun:*style*parse_into*
+   fun:*geckoservo*parse_property_into*
+}
+
+##########################################
+# Suppressions rooted at *selectors*parser*SelectorList*Impl*parse*
+
+{
+   bug1447137-15
+   Memcheck:Cond
+   fun:*selectors*parser*SelectorList*Impl*parse*
+   fun:*style*parse_nested_rules*
+   fun:*style*parse_block*
+   fun:Servo_StyleSheet_FromUTF8Bytes
+}
+
+{
+   bug1447137-16
+   Memcheck:Cond
+   fun:*selectors*parser*SelectorList*Impl*parse*
+   fun:Servo_StyleSheet_FromUTF8Bytes
+   fun:*mozilla*ServoStyleSheet*ParseSheet*
+   fun:*mozilla*css*Loader*DoParseSheetServo*
+}
+
+##########################################
+# Suppressions rooted at *style*properties*shorthands*
+
+{
+   bug1447137-17
+   Memcheck:Cond
+   fun:*style*properties*shorthands*
+   fun:*style*properties*PropertyDeclaration*parse_into*
+   fun:*style*parse_value*
+   fun:*cssparser*Iterator*next*
+}
+
+## END suppressions for Stylo as compiled by rustc 1.25.0
+##############################################################################
+
+
 ###################################################
 #  For valgrind-mochitest ("tc-M-V [tier 2]") runs on taskcluster.
 #  See bug 1248365.
 #  These are specific to Ubuntu 12.04.5, 64-bit.
 ###################################################
 
 
 # Not sure what this is.  Is it the well-known
--- a/ipc/testshell/XPCShellEnvironment.cpp
+++ b/ipc/testshell/XPCShellEnvironment.cpp
@@ -127,18 +127,20 @@ Dump(JSContext *cx, unsigned argc, JS::V
 
 static bool
 Load(JSContext *cx,
      unsigned argc,
      JS::Value *vp)
 {
     JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
 
-    JS::RootedValue thisv(cx, args.computeThis(cx));
-    if (!thisv.isObject() || !JS_IsGlobalObject(&thisv.toObject())) {
+    JS::RootedObject thisObject(cx);
+    if (!args.computeThis(cx, &thisObject))
+        return false;
+    if (!JS_IsGlobalObject(thisObject)) {
         JS_ReportErrorASCII(cx, "Trying to load() into a non-global object");
         return false;
     }
 
     for (unsigned i = 0; i < args.length(); i++) {
         JS::Rooted<JSString*> str(cx, JS::ToString(cx, args[i]));
         if (!str)
             return false;
--- a/js/public/CallArgs.h
+++ b/js/public/CallArgs.h
@@ -82,18 +82,18 @@ namespace JS {
 extern JS_PUBLIC_DATA(const HandleValue) UndefinedHandleValue;
 
 namespace detail {
 
 /*
  * Compute |this| for the |vp| inside a JSNative, either boxing primitives or
  * replacing with the global object as necessary.
  */
-extern JS_PUBLIC_API(Value)
-ComputeThis(JSContext* cx, JS::Value* vp);
+extern JS_PUBLIC_API(bool)
+ComputeThis(JSContext* cx, JS::Value* vp, MutableHandleObject thisObject);
 
 #ifdef JS_DEBUG
 extern JS_PUBLIC_API(void)
 CheckIsValidConstructible(const Value& v);
 #endif
 
 class MOZ_STACK_CLASS IncludeUsedRval
 {
@@ -194,21 +194,23 @@ class MOZ_STACK_CLASS CallArgsBase
      */
     HandleValue thisv() const {
         // Some internal code uses thisv() in constructing cases, so don't do
         // this yet.
         // MOZ_ASSERT(!argv_[-1].isMagic(JS_IS_CONSTRUCTING));
         return HandleValue::fromMarkedLocation(&argv_[-1]);
     }
 
-    Value computeThis(JSContext* cx) const {
-        if (thisv().isObject())
-            return thisv();
+    bool computeThis(JSContext* cx, MutableHandleObject thisObject) const {
+        if (thisv().isObject()) {
+            thisObject.set(&thisv().toObject());
+            return true;
+        }
 
-        return ComputeThis(cx, base());
+        return ComputeThis(cx, base(), thisObject);
     }
 
     // ARGUMENTS
 
         /* Returns the number of arguments. */
     unsigned length() const { return argc_; }
 
     /* Returns the i-th zero-indexed argument. */
--- a/js/src/frontend/NameFunctions.cpp
+++ b/js/src/frontend/NameFunctions.cpp
@@ -820,18 +820,18 @@ class NameResolver
 
         // It would be nice to common up the repeated |parents[initialParents]|
         // in a single variable, but the #if condition required to prevent an
         // unused-variable warning across three separate conditionally-expanded
         // macros would be super-ugly.  :-(
         MOZ_ASSERT(parents[initialParents] == cur,
                    "pushed child shouldn't change underneath us");
 
-        JS_POISON(&parents[initialParents], 0xFF, sizeof(parents[initialParents]));
-        MOZ_MAKE_MEM_UNDEFINED(&parents[initialParents], sizeof(parents[initialParents]));
+        JS_POISON(&parents[initialParents], 0xFF, sizeof(parents[initialParents]),
+                  MemCheckKind::MakeUndefined);
 
         return true;
     }
 };
 
 } /* anonymous namespace */
 
 bool
--- a/js/src/gc/Allocator.cpp
+++ b/js/src/gc/Allocator.cpp
@@ -673,17 +673,24 @@ Chunk::allocate(JSRuntime* rt)
         return nullptr;
     rt->gc.stats().count(gcstats::STAT_NEW_CHUNK);
     return chunk;
 }
 
 void
 Chunk::init(JSRuntime* rt)
 {
-    JS_POISON(this, JS_FRESH_TENURED_PATTERN, ChunkSize);
+    /* The chunk may still have some regions marked as no-access. */
+    MOZ_MAKE_MEM_UNDEFINED(this, ChunkSize);
+
+    /*
+     * Poison the chunk. Note that decommitAllArenas() below will mark the
+     * arenas as inaccessible (for memory sanitizers).
+     */
+    JS_POISON(this, JS_FRESH_TENURED_PATTERN, ChunkSize, MemCheckKind::MakeUndefined);
 
     /*
      * We clear the bitmap to guard against JS::GCThingIsMarkedGray being called
      * on uninitialized data, which would happen before the first GC cycle.
      */
     bitmap.clear();
 
     /*
--- a/js/src/gc/Barrier.h
+++ b/js/src/gc/Barrier.h
@@ -467,17 +467,17 @@ class GCPtr : public WriteBarrieredBase<
         // and cleared the pointer.
         //
         // If you get a crash here, you may need to make the containing object
         // use GCManagedDeletePolicy and use JS::DeletePolicy to destroy it.
         //
         // Note that when sweeping the wrapped pointer may already have been
         // freed by this point.
         MOZ_ASSERT(CurrentThreadIsGCSweeping() || this->value == JS::GCPolicy<T>::initial());
-        Poison(this, JS_FREED_HEAP_PTR_PATTERN, sizeof(*this));
+        Poison(this, JS_FREED_HEAP_PTR_PATTERN, sizeof(*this), MemCheckKind::MakeNoAccess);
     }
 #endif
 
     void init(const T& v) {
         CheckTargetIsNotGray(v);
         this->value = v;
         this->post(JS::GCPolicy<T>::initial(), v);
     }
--- a/js/src/gc/GC.cpp
+++ b/js/src/gc/GC.cpp
@@ -583,25 +583,25 @@ Arena::finalize(FreeOp* fop, AllocKind t
                 newListTail->initBounds(firstThingOrSuccessorOfLastMarkedThing,
                                         thing - thingSize, this);
                 newListTail = newListTail->nextSpanUnchecked(this);
             }
             firstThingOrSuccessorOfLastMarkedThing = thing + thingSize;
             nmarked++;
         } else {
             t->finalize(fop);
-            JS_POISON(t, JS_SWEPT_TENURED_PATTERN, thingSize);
+            JS_POISON(t, JS_SWEPT_TENURED_PATTERN, thingSize, MemCheckKind::MakeUndefined);
             TraceTenuredFinalize(t);
         }
     }
 
     if (nmarked == 0) {
         // Do nothing. The caller will update the arena appropriately.
         MOZ_ASSERT(newListTail == &newListHead);
-        JS_EXTRA_POISON(data, JS_SWEPT_TENURED_PATTERN, sizeof(data));
+        JS_EXTRA_POISON(data, JS_SWEPT_TENURED_PATTERN, sizeof(data), MemCheckKind::MakeUndefined);
         return nmarked;
     }
 
     MOZ_ASSERT(firstThingOrSuccessorOfLastMarkedThing != firstThing);
     uint_fast16_t lastMarkedThing = firstThingOrSuccessorOfLastMarkedThing - thingSize;
     if (lastThing == lastMarkedThing) {
         // If the last thing was marked, we will have already set the bounds of
         // the final span, and we just need to terminate the list.
@@ -2956,17 +2956,18 @@ GCRuntime::releaseRelocatedArenasWithout
         // Clear the mark bits
         arena->unmarkAll();
 
         // Mark arena as empty
         arena->setAsFullyUnused();
 
 #if defined(JS_CRASH_DIAGNOSTICS) || defined(JS_GC_ZEAL)
         JS_POISON(reinterpret_cast<void*>(arena->thingsStart()),
-                  JS_MOVED_TENURED_PATTERN, arena->getThingsSpan());
+                  JS_MOVED_TENURED_PATTERN, arena->getThingsSpan(),
+                  MemCheckKind::MakeNoAccess);
 #endif
 
         releaseArena(arena, lock);
         ++count;
     }
 }
 
 // In debug mode we don't always release relocated arenas straight away.
--- a/js/src/gc/Heap-inl.h
+++ b/js/src/gc/Heap-inl.h
@@ -10,16 +10,18 @@
 #include "gc/Heap.h"
 
 #include "gc/StoreBuffer.h"
 #include "gc/Zone.h"
 
 inline void
 js::gc::Arena::init(JS::Zone* zoneArg, AllocKind kind, const AutoLockGC& lock)
 {
+    MOZ_MAKE_MEM_UNDEFINED(this, ArenaSize);
+
     MOZ_ASSERT(firstFreeSpan.isEmpty());
     MOZ_ASSERT(!zone);
     MOZ_ASSERT(!allocated());
     MOZ_ASSERT(!hasDelayedMarking);
     MOZ_ASSERT(!markOverflow);
     MOZ_ASSERT(!auxNextLink);
 
     zone = zoneArg;
--- a/js/src/gc/Heap.h
+++ b/js/src/gc/Heap.h
@@ -169,17 +169,18 @@ class FreeSpan
             // The last space points to the next free span (which may be empty).
             const FreeSpan* next = nextSpan(arena);
             first = next->first;
             last = next->last;
         } else {
             return nullptr; // The span is empty.
         }
         checkSpan(arena);
-        JS_EXTRA_POISON(reinterpret_cast<void*>(thing), JS_ALLOCATED_TENURED_PATTERN, thingSize);
+        JS_EXTRA_POISON(reinterpret_cast<void*>(thing), JS_ALLOCATED_TENURED_PATTERN, thingSize,
+                        MemCheckKind::MakeUndefined);
         return reinterpret_cast<TenuredCell*>(thing);
     }
 
     inline void checkSpan(const Arena* arena) const;
     inline void checkRange(uintptr_t first, uintptr_t last, const Arena* arena) const;
 };
 
 /*
--- a/js/src/gc/Memory.cpp
+++ b/js/src/gc/Memory.cpp
@@ -246,33 +246,40 @@ GetNewChunk(void** aAddress, void** aRet
     } while (!retainedAddr);
     *aAddress = address;
     *aRetainedAddr = retainedAddr;
 }
 
 void
 UnmapPages(void* p, size_t size)
 {
+    // ASan does not automatically unpoison memory, so we have to do this here.
+    MOZ_MAKE_MEM_UNDEFINED(p, size);
+
     MOZ_ALWAYS_TRUE(VirtualFree(p, 0, MEM_RELEASE));
 }
 
 bool
 MarkPagesUnused(void* p, size_t size)
 {
+    MOZ_MAKE_MEM_NOACCESS(p, size);
+
     if (!DecommitEnabled())
         return true;
 
     MOZ_ASSERT(OffsetFromAligned(p, pageSize) == 0);
     LPVOID p2 = MapMemoryAt(p, size, MEM_RESET);
     return p2 == p;
 }
 
 void
 MarkPagesInUse(void* p, size_t size)
 {
+    MOZ_MAKE_MEM_UNDEFINED(p, size);
+
     if (!DecommitEnabled())
         return;
 
     MOZ_ASSERT(OffsetFromAligned(p, pageSize) == 0);
 }
 
 size_t
 GetPageFaultCount()
@@ -365,29 +372,34 @@ static void*
 MapAlignedPagesLastDitch(size_t size, size_t alignment)
 {
     return nullptr;
 }
 
 void
 UnmapPages(void* p, size_t size)
 {
+    // ASan does not automatically unpoison memory, so we have to do this here.
+    MOZ_MAKE_MEM_UNDEFINED(p, size);
+
     _aligned_free(p);
 }
 
 bool
 MarkPagesUnused(void* p, size_t size)
 {
+    MOZ_MAKE_MEM_NOACCESS(p, size);
     MOZ_ASSERT(OffsetFromAligned(p, pageSize) == 0);
     return true;
 }
 
 bool
 MarkPagesInUse(void* p, size_t size)
 {
+    MOZ_MAKE_MEM_UNDEFINED(p, size);
     MOZ_ASSERT(OffsetFromAligned(p, pageSize) == 0);
 }
 
 size_t
 GetPageFaultCount()
 {
     // GetProcessMemoryInfo is unavailable.
     return 0;
@@ -445,29 +457,35 @@ static void*
 MapAlignedPagesLastDitch(size_t size, size_t alignment)
 {
     return nullptr;
 }
 
 void
 UnmapPages(void* p, size_t size)
 {
+    // ASan does not automatically unpoison memory, so we have to do this here.
+    MOZ_MAKE_MEM_UNDEFINED(p, size);
+
     MOZ_ALWAYS_TRUE(0 == munmap((caddr_t)p, size));
 }
 
 bool
 MarkPagesUnused(void* p, size_t size)
 {
+    MOZ_MAKE_MEM_NOACCESS(p, size);
     MOZ_ASSERT(OffsetFromAligned(p, pageSize) == 0);
     return true;
 }
 
 bool
 MarkPagesInUse(void* p, size_t size)
 {
+    MOZ_MAKE_MEM_UNDEFINED(p, size);
+
     if (!DecommitEnabled())
         return;
 
     MOZ_ASSERT(OffsetFromAligned(p, pageSize) == 0);
 }
 
 size_t
 GetPageFaultCount()
@@ -750,38 +768,45 @@ GetNewChunk(void** aAddress, void** aRet
     }
     *aAddress = address;
     *aRetainedAddr = retainedAddr;
 }
 
 void
 UnmapPages(void* p, size_t size)
 {
+    // ASan does not automatically unpoison memory, so we have to do this here.
+    MOZ_MAKE_MEM_UNDEFINED(p, size);
+
     if (munmap(p, size))
         MOZ_ASSERT(errno == ENOMEM);
 }
 
 bool
 MarkPagesUnused(void* p, size_t size)
 {
+    MOZ_MAKE_MEM_NOACCESS(p, size);
+
     if (!DecommitEnabled())
         return false;
 
     MOZ_ASSERT(OffsetFromAligned(p, pageSize) == 0);
 #if defined(XP_SOLARIS)
     int result = posix_madvise(p, size, POSIX_MADV_DONTNEED);
 #else
     int result = madvise(p, size, MADV_DONTNEED);
 #endif
     return result != -1;
 }
 
 void
 MarkPagesInUse(void* p, size_t size)
 {
+    MOZ_MAKE_MEM_UNDEFINED(p, size);
+
     if (!DecommitEnabled())
         return;
 
     MOZ_ASSERT(OffsetFromAligned(p, pageSize) == 0);
 }
 
 size_t
 GetPageFaultCount()
--- a/js/src/gc/Nursery.cpp
+++ b/js/src/gc/Nursery.cpp
@@ -80,25 +80,31 @@ struct NurseryChunk {
 static_assert(sizeof(js::NurseryChunk) == gc::ChunkSize,
               "Nursery chunk size must match gc::Chunk size.");
 
 } /* namespace js */
 
 inline void
 js::NurseryChunk::poisonAndInit(JSRuntime* rt)
 {
-    JS_POISON(this, JS_FRESH_NURSERY_PATTERN, ChunkSize);
+    MOZ_MAKE_MEM_UNDEFINED(this, ChunkSize);
+
+    JS_POISON(this, JS_FRESH_NURSERY_PATTERN, ChunkSize, MemCheckKind::MakeUndefined);
 
     new (&trailer) gc::ChunkTrailer(rt, &rt->gc.storeBuffer());
 }
 
 inline void
 js::NurseryChunk::poisonAfterSweep()
 {
-    JS_POISON(this, JS_SWEPT_NURSERY_PATTERN, ChunkSize);
+    // We can poison the same chunk more than once, so first make sure memory
+    // sanitizers will let us poison it.
+    MOZ_MAKE_MEM_UNDEFINED(this, ChunkSize);
+
+    JS_POISON(this, JS_SWEPT_NURSERY_PATTERN, ChunkSize, MemCheckKind::MakeNoAccess);
 }
 
 /* static */ inline js::NurseryChunk*
 js::NurseryChunk::fromChunk(Chunk* chunk)
 {
     return reinterpret_cast<NurseryChunk*>(chunk);
 }
 
@@ -376,17 +382,17 @@ js::Nursery::allocate(size_t size)
             MOZ_ASSERT(chunkno < allocatedChunkCount());
         }
         setCurrentChunk(chunkno);
     }
 
     void* thing = (void*)position();
     position_ = position() + size;
 
-    JS_EXTRA_POISON(thing, JS_ALLOCATED_NURSERY_PATTERN, size);
+    JS_EXTRA_POISON(thing, JS_ALLOCATED_NURSERY_PATTERN, size, MemCheckKind::MakeUndefined);
 
 #ifdef JS_GC_ZEAL
     if (runtime()->gc.hasZealMode(ZealMode::CheckNursery)) {
         auto canary = reinterpret_cast<Canary*>(position() - CanarySize);
         canary->magicValue = CanaryMagicValue;
         canary->next = nullptr;
         if (lastCanary_) {
             MOZ_ASSERT(!lastCanary_->next);
--- a/js/src/jit/ExecutableAllocator.cpp
+++ b/js/src/jit/ExecutableAllocator.cpp
@@ -77,16 +77,17 @@ void*
 ExecutablePool::alloc(size_t n, CodeKind kind)
 {
     MOZ_ASSERT(n <= available());
     void* result = m_freePtr;
     m_freePtr += n;
 
     m_codeBytes[kind] += n;
 
+    MOZ_MAKE_MEM_UNDEFINED(result, n);
     return result;
 }
 
 size_t
 ExecutablePool::available() const
 {
     MOZ_ASSERT(m_end >= m_freePtr);
     return m_end - m_freePtr;
@@ -338,34 +339,39 @@ ExecutableAllocator::poisonCode(JSRuntim
 
         // Use the pool's mark bit to indicate we made the pool writable.
         // This avoids reprotecting a pool multiple times.
         if (!pool->isMarked()) {
             reprotectPool(rt, pool, ProtectionSetting::Writable);
             pool->mark();
         }
 
+        // Note: we use memset instead of JS_POISON because we want to poison
+        // JIT code in release builds too. Furthermore, we don't want the
+        // invalid-ObjectValue poisoning JS_POISON does in debug builds.
         memset(ranges[i].start, JS_SWEPT_CODE_PATTERN, ranges[i].size);
+        MOZ_MAKE_MEM_NOACCESS(ranges[i].start, ranges[i].size);
     }
 
     // Make the pools executable again and drop references.
     for (size_t i = 0; i < ranges.length(); i++) {
         ExecutablePool* pool = ranges[i].pool;
         if (pool->isMarked()) {
             reprotectPool(rt, pool, ProtectionSetting::Executable);
             pool->unmark();
         }
         pool->release();
     }
 }
 
 ExecutablePool::Allocation
 ExecutableAllocator::systemAlloc(size_t n)
 {
-    void* allocation = AllocateExecutableMemory(n, ProtectionSetting::Executable);
+    void* allocation =
+        AllocateExecutableMemory(n, ProtectionSetting::Executable, MemCheckKind::MakeNoAccess);
     ExecutablePool::Allocation alloc = { reinterpret_cast<char*>(allocation), n };
     return alloc;
 }
 
 void
 ExecutableAllocator::systemRelease(const ExecutablePool::Allocation& alloc)
 {
     DeallocateExecutableMemory(alloc.pages, alloc.size);
--- a/js/src/jit/ProcessExecutableMemory.cpp
+++ b/js/src/jit/ProcessExecutableMemory.cpp
@@ -497,22 +497,23 @@ class ProcessExecutableMemory
         MOZ_ASSERT(!initialized());
     }
 
     void assertValidAddress(void* p, size_t bytes) const {
         MOZ_RELEASE_ASSERT(p >= base_ &&
                            uintptr_t(p) + bytes <= uintptr_t(base_) + MaxCodeBytesPerProcess);
     }
 
-    void* allocate(size_t bytes, ProtectionSetting protection);
+    void* allocate(size_t bytes, ProtectionSetting protection, MemCheckKind checkKind);
     void deallocate(void* addr, size_t bytes, bool decommit);
 };
 
 void*
-ProcessExecutableMemory::allocate(size_t bytes, ProtectionSetting protection)
+ProcessExecutableMemory::allocate(size_t bytes, ProtectionSetting protection,
+                                  MemCheckKind checkKind)
 {
     MOZ_ASSERT(initialized());
     MOZ_ASSERT(bytes > 0);
     MOZ_ASSERT((bytes % ExecutableCodePageSize) == 0);
 
     size_t numPages = bytes / ExecutableCodePageSize;
 
     // Take the lock and try to allocate.
@@ -568,16 +569,18 @@ ProcessExecutableMemory::allocate(size_t
     }
 
     // Commit the pages after releasing the lock.
     if (!CommitPages(p, bytes, protection)) {
         deallocate(p, bytes, /* decommit = */ false);
         return nullptr;
     }
 
+    SetMemCheckKind(p, bytes, checkKind);
+
     return p;
 }
 
 void
 ProcessExecutableMemory::deallocate(void* addr, size_t bytes, bool decommit)
 {
     MOZ_ASSERT(initialized());
     MOZ_ASSERT(addr);
@@ -586,16 +589,17 @@ ProcessExecutableMemory::deallocate(void
     MOZ_ASSERT((bytes % ExecutableCodePageSize) == 0);
 
     assertValidAddress(addr, bytes);
 
     size_t firstPage = (static_cast<uint8_t*>(addr) - base_) / ExecutableCodePageSize;
     size_t numPages = bytes / ExecutableCodePageSize;
 
     // Decommit before taking the lock.
+    MOZ_MAKE_MEM_NOACCESS(addr, bytes);
     if (decommit)
         DecommitPages(addr, bytes);
 
     LockGuard<Mutex> guard(lock_);
     MOZ_ASSERT(numPages <= pagesAllocated_);
     pagesAllocated_ -= numPages;
 
     for (size_t i = 0; i < numPages; i++)
@@ -605,19 +609,19 @@ ProcessExecutableMemory::deallocate(void
     // whole region.
     if (firstPage < cursor_)
         cursor_ = firstPage;
 }
 
 static ProcessExecutableMemory execMemory;
 
 void*
-js::jit::AllocateExecutableMemory(size_t bytes, ProtectionSetting protection)
+js::jit::AllocateExecutableMemory(size_t bytes, ProtectionSetting protection, MemCheckKind checkKind)
 {
-    return execMemory.allocate(bytes, protection);
+    return execMemory.allocate(bytes, protection, checkKind);
 }
 
 void
 js::jit::DeallocateExecutableMemory(void* addr, size_t bytes)
 {
     execMemory.deallocate(addr, bytes, /* decommit = */ true);
 }
 
--- a/js/src/jit/ProcessExecutableMemory.h
+++ b/js/src/jit/ProcessExecutableMemory.h
@@ -4,16 +4,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/. */
 
 #ifndef jit_ProcessExecutableMemory_h
 #define jit_ProcessExecutableMemory_h
 
 #include "mozilla/Attributes.h"
 
+#include "jsutil.h"
+
 namespace js {
 namespace jit {
 
 // Limit on the number of bytes of executable memory to prevent JIT spraying
 // attacks.
 #if JS_BITS_PER_WORD == 32
 static const size_t MaxCodeBytesPerProcess = 140 * 1024 * 1024;
 #else
@@ -34,17 +36,18 @@ enum class ProtectionSetting {
 extern MOZ_MUST_USE bool ReprotectRegion(void* start, size_t size, ProtectionSetting protection);
 
 // Functions called at process start-up/shutdown to initialize/release the
 // executable memory region.
 extern MOZ_MUST_USE bool InitProcessExecutableMemory();
 extern void ReleaseProcessExecutableMemory();
 
 // Allocate/deallocate executable pages.
-extern void* AllocateExecutableMemory(size_t bytes, ProtectionSetting protection);
+extern void* AllocateExecutableMemory(size_t bytes, ProtectionSetting protection,
+                                      MemCheckKind checkKind);
 extern void DeallocateExecutableMemory(void* addr, size_t bytes);
 
 // Returns true if we can allocate a few more MB of executable code without
 // hitting our code limit. This function can be used to stop compiling things
 // that are optional (like Baseline and Ion code) when we're about to reach the
 // limit, so we are less likely to OOM or crash. Note that the limit is
 // per-process, so other threads can also allocate code after we call this
 // function.
--- a/js/src/jsapi.cpp
+++ b/js/src/jsapi.cpp
@@ -1334,27 +1334,28 @@ JS::CurrentGlobalOrNull(JSContext* cx)
 {
     AssertHeapIsIdleOrIterating();
     CHECK_REQUEST(cx);
     if (!cx->compartment())
         return nullptr;
     return cx->global();
 }
 
-JS_PUBLIC_API(Value)
-JS::detail::ComputeThis(JSContext* cx, Value* vp)
+JS_PUBLIC_API(bool)
+JS::detail::ComputeThis(JSContext* cx, Value* vp, MutableHandleObject thisObject)
 {
     AssertHeapIsIdle();
     assertSameCompartment(cx, JSValueArray(vp, 2));
 
     MutableHandleValue thisv = MutableHandleValue::fromMarkedLocation(&vp[1]);
     if (!BoxNonStrictThis(cx, thisv, thisv))
-        return NullValue();
-
-    return thisv;
+        return false;
+
+    thisObject.set(&thisv.toObject());
+    return true;
 }
 
 JS_PUBLIC_API(void*)
 JS_malloc(JSContext* cx, size_t nbytes)
 {
     AssertHeapIsIdle();
     CHECK_REQUEST(cx);
     return static_cast<void*>(cx->zone()->pod_malloc<uint8_t>(nbytes));
--- a/js/src/jsutil.h
+++ b/js/src/jsutil.h
@@ -9,16 +9,17 @@
  */
 
 #ifndef jsutil_h
 #define jsutil_h
 
 #include "mozilla/Assertions.h"
 #include "mozilla/HashFunctions.h"
 #include "mozilla/MathAlgorithms.h"
+#include "mozilla/MemoryChecking.h"
 #include "mozilla/PodOperations.h"
 
 #include <limits.h>
 
 #include "js/Initialization.h"
 #include "js/Utility.h"
 #include "js/Value.h"
 
@@ -278,18 +279,43 @@ PodSet(T* aDst, const T& aSrc, size_t aN
 #elif defined(JS_CODEGEN_ARM) || defined(JS_CODEGEN_ARM64)
 # define JS_SWEPT_CODE_PATTERN 0xA3 // undefined instruction
 #elif defined(JS_CODEGEN_MIPS32) || defined(JS_CODEGEN_MIPS64)
 # define JS_SWEPT_CODE_PATTERN 0x01 // undefined instruction
 #else
 # error "JS_SWEPT_CODE_PATTERN not defined for this platform"
 #endif
 
+enum class MemCheckKind : uint8_t {
+    // Marks a region as poisoned. Memory sanitizers like ASan will crash when
+    // accessing it (both reads and writes).
+    MakeNoAccess,
+
+    // Marks a region as having undefined contents. In ASan builds this just
+    // unpoisons the memory. MSan and Valgrind can also use this to find
+    // reads of uninitialized memory.
+    MakeUndefined,
+};
+
+static MOZ_ALWAYS_INLINE void
+SetMemCheckKind(void* ptr, size_t bytes, MemCheckKind kind)
+{
+    switch (kind) {
+      case MemCheckKind::MakeUndefined:
+        MOZ_MAKE_MEM_UNDEFINED(ptr, bytes);
+        return;
+      case MemCheckKind::MakeNoAccess:
+        MOZ_MAKE_MEM_NOACCESS(ptr, bytes);
+        return;
+    }
+    MOZ_CRASH("Invalid kind");
+}
+
 static inline void*
-Poison(void* ptr, uint8_t value, size_t num)
+Poison(void* ptr, uint8_t value, size_t num, MemCheckKind kind)
 {
     static bool disablePoison = bool(getenv("JSGC_DISABLE_POISONING"));
     if (disablePoison)
         return ptr;
 
     // Without a valid Value tag, a poisoned Value may look like a valid
     // floating point number. To ensure that we crash more readily when
     // observing a poisoned Value, we make the poison an invalid ObjectValue.
@@ -309,32 +335,34 @@ Poison(void* ptr, uint8_t value, size_t 
     if (byte_count) {
         uint8_t* bytes = static_cast<uint8_t*>(ptr);
         uint8_t* end = bytes + num;
         mozilla::PodSet(end - byte_count, value, byte_count);
     }
 #else // !DEBUG
     memset(ptr, value, num);
 #endif // !DEBUG
+
+    SetMemCheckKind(ptr, num, kind);
     return ptr;
 }
 
 /* Crash diagnostics by default in debug and on nightly channel. */
 #if defined(DEBUG) || defined(NIGHTLY_BUILD)
 # define JS_CRASH_DIAGNOSTICS 1
 #endif
 
 /* Enable poisoning in crash-diagnostics and zeal builds. */
 #if defined(JS_CRASH_DIAGNOSTICS) || defined(JS_GC_ZEAL)
-# define JS_POISON(p, val, size) Poison(p, val, size)
+# define JS_POISON(p, val, size, kind) Poison(p, val, size, kind)
 # define JS_GC_POISONING 1
 #else
-# define JS_POISON(p, val, size) ((void) 0)
+# define JS_POISON(p, val, size, kind) ((void) 0)
 #endif
 
 /* Enable even more poisoning in purely debug builds. */
 #if defined(DEBUG)
-# define JS_EXTRA_POISON(p, val, size) Poison(p, val, size)
+# define JS_EXTRA_POISON(p, val, size, kind) Poison(p, val, size, kind)
 #else
-# define JS_EXTRA_POISON(p, val, size) ((void) 0)
+# define JS_EXTRA_POISON(p, val, size, kind) ((void) 0)
 #endif
 
 #endif /* jsutil_h */
--- a/js/src/threading/ExclusiveData.h
+++ b/js/src/threading/ExclusiveData.h
@@ -2,17 +2,16 @@
  * vim: set ts=8 sts=4 et sw=4 tw=99:
  * 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/. */
 
 #ifndef threading_ExclusiveData_h
 #define threading_ExclusiveData_h
 
-#include "mozilla/Alignment.h"
 #include "mozilla/Maybe.h"
 #include "mozilla/Move.h"
 #include "mozilla/OperatorNewExtensions.h"
 
 #include "threading/ConditionVariable.h"
 #include "threading/Mutex.h"
 
 namespace js {
@@ -79,57 +78,49 @@ namespace js {
  *      foot-gun, prefer using the guard directly! Do not store raw references
  *      to the protected value in other structures!
  */
 template <typename T>
 class ExclusiveData
 {
   protected:
     mutable Mutex lock_;
-    mutable mozilla::AlignedStorage2<T> value_;
+    mutable T value_;
 
     ExclusiveData(const ExclusiveData&) = delete;
     ExclusiveData& operator=(const ExclusiveData&) = delete;
 
     void acquire() const { lock_.lock(); }
     void release() const { lock_.unlock(); }
 
   public:
     /**
      * Create a new `ExclusiveData`, with perfect forwarding of the protected
      * value.
      */
     template <typename U>
     explicit ExclusiveData(const MutexId& id, U&& u)
-      : lock_(id)
-    {
-        new (mozilla::KnownNotNull, value_.addr()) T(mozilla::Forward<U>(u));
-    }
+      : lock_(id),
+        value_(mozilla::Forward<U>(u))
+    {}
 
     /**
      * Create a new `ExclusiveData`, constructing the protected value in place.
      */
     template <typename... Args>
     explicit ExclusiveData(const MutexId& id, Args&&... args)
-      : lock_(id)
-    {
-        new (mozilla::KnownNotNull, value_.addr()) T(mozilla::Forward<Args>(args)...);
-    }
-
-    ~ExclusiveData() {
-        acquire();
-        value_.addr()->~T();
-        release();
-    }
+      : lock_(id),
+        value_(mozilla::Forward<Args>(args)...)
+    {}
 
     ExclusiveData(ExclusiveData&& rhs)
-      : lock_(mozilla::Move(rhs.lock))
+      : lock_(mozilla::Move(rhs.lock)),
+        value_(Move(rhs.value_))
     {
         MOZ_ASSERT(&rhs != this, "self-move disallowed!");
-        new (mozilla::KnownNotNull, value_.addr()) T(mozilla::Move(*rhs.value_.addr()));
     }
 
     ExclusiveData& operator=(ExclusiveData&& rhs) {
         this->~ExclusiveData();
         new (mozilla::KnownNotNull, this) ExclusiveData(mozilla::Move(rhs));
         return *this;
     }
 
@@ -165,17 +156,17 @@ class ExclusiveData
         Guard& operator=(Guard&& rhs) {
             this->~Guard();
             new (this) Guard(mozilla::Move(rhs));
             return *this;
         }
 
         T& get() const {
             MOZ_ASSERT(parent_);
-            return *parent_->value_.addr();
+            return parent_->value_;
         }
 
         operator T& () const { return get(); }
         T* operator->() const { return &get(); }
 
         const ExclusiveData<T>* parent() const {
             MOZ_ASSERT(parent_);
             return parent_;
--- a/js/src/vm/Initialization.cpp
+++ b/js/src/vm/Initialization.cpp
@@ -100,18 +100,16 @@ JS::detail::InitWithFailureDiagnostic(bo
 #if defined(DEBUG) || defined(JS_OOM_BREAKPOINT)
     RETURN_IF_FAIL(js::oom::InitThreadType());
 #endif
 
     js::InitMallocAllocator();
 
     RETURN_IF_FAIL(js::Mutex::Init());
 
-    RETURN_IF_FAIL(js::wasm::Init());
-
     js::gc::InitMemorySubsystem(); // Ensure gc::SystemPageSize() works.
 
     RETURN_IF_FAIL(js::jit::InitProcessExecutableMemory());
 
     RETURN_IF_FAIL(js::MemoryProtectionExceptionHandler::install());
 
     RETURN_IF_FAIL(js::jit::InitializeIon());
 
--- a/js/src/vm/JSScript.cpp
+++ b/js/src/vm/JSScript.cpp
@@ -3135,17 +3135,17 @@ JSScript::finalize(FreeOp* fop)
         types_->destroy();
 
     jit::DestroyJitScripts(fop, this);
 
     destroyScriptCounts();
     destroyDebugScript(fop);
 
     if (data) {
-        JS_POISON(data, 0xdb, computedSizeOfData());
+        JS_POISON(data, 0xdb, computedSizeOfData(), MemCheckKind::MakeNoAccess);
         fop->free_(data);
     }
 
     if (scriptData_)
         scriptData_->decRefCount();
 
     // In most cases, our LazyScript's script pointer will reference this
     // script, and thus be nulled out by normal weakref processing. However, if
--- a/js/src/vm/TypeInference.cpp
+++ b/js/src/vm/TypeInference.cpp
@@ -4148,17 +4148,18 @@ ConstraintTypeSet::trace(Zone* zone, JST
             if (!pentry)
                 oomUnsafe.crash("ConstraintTypeSet::trace");
 
             *pentry = key;
         }
         MOZ_RELEASE_ASSERT(oldObjectCount == oldObjectsFound);
         setBaseObjectCount(objectCount);
         // Note: -1/+1 to also poison the capacity field.
-        JS_POISON(oldArray - 1, JS_SWEPT_TI_PATTERN, (oldCapacity + 1) * sizeof(oldArray[0]));
+        JS_POISON(oldArray - 1, JS_SWEPT_TI_PATTERN, (oldCapacity + 1) * sizeof(oldArray[0]),
+                  MemCheckKind::MakeUndefined);
     } else if (objectCount == 1) {
         ObjectKey* key = (ObjectKey*) objectSet;
         TraceObjectKey(trc, &key);
         objectSet = reinterpret_cast<ObjectKey**>(key);
     } else {
         MOZ_RELEASE_ASSERT(!objectSet);
     }
 }
@@ -4226,17 +4227,18 @@ ConstraintTypeSet::sweep(Zone* zone, Aut
                 flags |= TYPE_FLAG_ANYOBJECT;
                 clearObjects();
                 objectCount = 0;
                 break;
             }
         }
         setBaseObjectCount(objectCount);
         // Note: -1/+1 to also poison the capacity field.
-        JS_POISON(oldArray - 1, JS_SWEPT_TI_PATTERN, (oldCapacity + 1) * sizeof(oldArray[0]));
+        JS_POISON(oldArray - 1, JS_SWEPT_TI_PATTERN, (oldCapacity + 1) * sizeof(oldArray[0]),
+                  MemCheckKind::MakeUndefined);
     } else if (objectCount == 1) {
         ObjectKey* key = (ObjectKey*) objectSet;
         if (!IsObjectKeyAboutToBeFinalized(&key)) {
             objectSet = reinterpret_cast<ObjectKey**>(key);
         } else {
             // As above, mark type sets containing objects with unknown
             // properties as unknown.
             if (key->isGroup() && key->groupNoBarrier()->unknownPropertiesDontCheckGeneration())
@@ -4260,17 +4262,18 @@ ConstraintTypeSet::sweep(Zone* zone, Aut
                 MOZ_ASSERT(zone->types.typeLifoAlloc().contains(copy));
                 copy->setNext(constraintList_);
                 constraintList_ = copy;
             } else {
                 oom.setOOM();
             }
         }
         TypeConstraint* next = constraint->next();
-        JS_POISON(constraint, JS_SWEPT_TI_PATTERN, sizeof(TypeConstraint));
+        JS_POISON(constraint, JS_SWEPT_TI_PATTERN, sizeof(TypeConstraint),
+                  MemCheckKind::MakeUndefined);
         constraint = next;
     }
 }
 
 inline void
 ObjectGroup::clearProperties()
 {
     setBasePropertyCount(0);
@@ -4360,22 +4363,24 @@ ObjectGroup::sweep(AutoClearTypeInferenc
                 prop->types.checkMagic();
                 if (singleton() && !prop->types.constraintList() && !zone()->isPreservingCode()) {
                     /*
                      * Don't copy over properties of singleton objects when their
                      * presence will not be required by jitcode or type constraints
                      * (i.e. for the definite properties analysis). The contents of
                      * these type sets will be regenerated as necessary.
                      */
-                    JS_POISON(prop, JS_SWEPT_TI_PATTERN, sizeof(Property));
+                    JS_POISON(prop, JS_SWEPT_TI_PATTERN, sizeof(Property),
+                              MemCheckKind::MakeUndefined);
                     continue;
                 }
 
                 Property* newProp = typeLifoAlloc.new_<Property>(*prop);
-                JS_POISON(prop, JS_SWEPT_TI_PATTERN, sizeof(Property));
+                JS_POISON(prop, JS_SWEPT_TI_PATTERN, sizeof(Property),
+                          MemCheckKind::MakeUndefined);
                 if (newProp) {
                     Property** pentry = TypeHashSet::Insert<jsid, Property, Property>
                                       (typeLifoAlloc, propertySet, propertyCount, newProp->id);
                     if (pentry) {
                         *pentry = newProp;
                         newProp->types.sweep(zone(), *oom);
                         continue;
                     }
@@ -4389,21 +4394,21 @@ ObjectGroup::sweep(AutoClearTypeInferenc
         }
         MOZ_RELEASE_ASSERT(oldPropertyCount == oldPropertiesFound);
         setBasePropertyCount(propertyCount);
     } else if (propertyCount == 1) {
         Property* prop = (Property*) propertySet;
         prop->types.checkMagic();
         if (singleton() && !prop->types.constraintList() && !zone()->isPreservingCode()) {
             // Skip, as above.
-            JS_POISON(prop, JS_SWEPT_TI_PATTERN, sizeof(Property));
+            JS_POISON(prop, JS_SWEPT_TI_PATTERN, sizeof(Property), MemCheckKind::MakeUndefined);
             clearProperties();
         } else {
             Property* newProp = typeLifoAlloc.new_<Property>(*prop);
-            JS_POISON(prop, JS_SWEPT_TI_PATTERN, sizeof(Property));
+            JS_POISON(prop, JS_SWEPT_TI_PATTERN, sizeof(Property), MemCheckKind::MakeUndefined);
             if (newProp) {
                 propertySet = (Property**) newProp;
                 newProp->types.sweep(zone(), *oom);
             } else {
                 oom->setOOM();
                 addFlags(OBJECT_FLAG_DYNAMIC_MASK | OBJECT_FLAG_UNKNOWN_PROPERTIES);
                 clearProperties();
                 return;
--- a/js/src/wasm/WasmBuiltins.cpp
+++ b/js/src/wasm/WasmBuiltins.cpp
@@ -969,17 +969,18 @@ wasm::EnsureBuiltinThunksInitialized()
 
     masm.finish();
     if (masm.oom())
         return false;
 
     size_t allocSize = AlignBytes(masm.bytesNeeded(), ExecutableCodePageSize);
 
     thunks->codeSize = allocSize;
-    thunks->codeBase = (uint8_t*)AllocateExecutableMemory(allocSize, ProtectionSetting::Writable);
+    thunks->codeBase = (uint8_t*)AllocateExecutableMemory(allocSize, ProtectionSetting::Writable,
+                                                          MemCheckKind::MakeUndefined);
     if (!thunks->codeBase)
         return false;
 
     masm.executableCopy(thunks->codeBase, /* flushICache = */ false);
     memset(thunks->codeBase + masm.bytesNeeded(), 0, allocSize - masm.bytesNeeded());
 
     masm.processCodeLabels(thunks->codeBase);
 
--- a/js/src/wasm/WasmCode.cpp
+++ b/js/src/wasm/WasmCode.cpp
@@ -66,25 +66,27 @@ RoundupCodeLength(uint32_t codeLength)
 CodeSegment::AllocateCodeBytes(uint32_t codeLength)
 {
     if (codeLength > MaxCodeBytesPerProcess)
         return nullptr;
 
     static_assert(MaxCodeBytesPerProcess <= INT32_MAX, "rounding won't overflow");
     uint32_t roundedCodeLength = RoundupCodeLength(codeLength);
 
-    void* p = AllocateExecutableMemory(roundedCodeLength, ProtectionSetting::Writable);
+    void* p = AllocateExecutableMemory(roundedCodeLength, ProtectionSetting::Writable,
+                                       MemCheckKind::MakeUndefined);
 
     // If the allocation failed and the embedding gives us a last-ditch attempt
     // to purge all memory (which, in gecko, does a purging GC/CC/GC), do that
     // then retry the allocation.
     if (!p) {
         if (OnLargeAllocationFailure) {
             OnLargeAllocationFailure();
-            p = AllocateExecutableMemory(roundedCodeLength, ProtectionSetting::Writable);
+            p = AllocateExecutableMemory(roundedCodeLength, ProtectionSetting::Writable,
+                                         MemCheckKind::MakeUndefined);
         }
     }
 
     if (!p)
         return nullptr;
 
     // Zero the padding.
     memset(((uint8_t*)p) + codeLength, 0, roundedCodeLength - codeLength);
--- a/js/src/wasm/WasmInstance.cpp
+++ b/js/src/wasm/WasmInstance.cpp
@@ -79,33 +79,17 @@ class SigIdSet
         p->value()--;
         if (!p->value()) {
             js_delete(p->key());
             map_.remove(p);
         }
     }
 };
 
-ExclusiveData<SigIdSet>* sigIdSet = nullptr;
-
-bool
-js::wasm::InitSignatureSet()
-{
-    MOZ_ASSERT(!sigIdSet);
-    sigIdSet = js_new<ExclusiveData<SigIdSet>>(mutexid::WasmSigIdSet);
-    return sigIdSet != nullptr;
-}
-
-void
-js::wasm::ReleaseSignatureSet()
-{
-    MOZ_ASSERT(sigIdSet);
-    js_delete(sigIdSet);
-    sigIdSet = nullptr;
-}
+ExclusiveData<SigIdSet> sigIdSet(mutexid::WasmSigIdSet);
 
 const void**
 Instance::addressOfSigId(const SigIdDesc& sigId) const
 {
     return (const void**)(globalData() + sigId.globalDataOffset());
 }
 
 FuncImportTls&
@@ -505,17 +489,17 @@ Instance::init(JSContext* cx)
         return false;
 
     for (const SharedTable& table : tables_) {
         if (table->movingGrowable() && !table->addMovingGrowObserver(cx, object_))
             return false;
     }
 
     if (!metadata().sigIds.empty()) {
-        ExclusiveData<SigIdSet>::Guard lockedSigIdSet = sigIdSet->lock();
+        ExclusiveData<SigIdSet>::Guard lockedSigIdSet = sigIdSet.lock();
 
         if (!lockedSigIdSet->ensureInitialized(cx))
             return false;
 
         for (const SigWithId& sig : metadata().sigIds) {
             const void* sigId;
             if (!lockedSigIdSet->allocateSigId(cx, sig, &sigId))
                 return false;
@@ -540,17 +524,17 @@ Instance::~Instance()
 
     for (unsigned i = 0; i < funcImports.length(); i++) {
         FuncImportTls& import = funcImportTls(funcImports[i]);
         if (import.baselineScript)
             import.baselineScript->removeDependentWasmImport(*this, i);
     }
 
     if (!metadata().sigIds.empty()) {
-        ExclusiveData<SigIdSet>::Guard lockedSigIdSet = sigIdSet->lock();
+        ExclusiveData<SigIdSet>::Guard lockedSigIdSet = sigIdSet.lock();
 
         for (const SigWithId& sig : metadata().sigIds) {
             if (const void* sigId = *addressOfSigId(sig.id))
                 lockedSigIdSet->deallocateSigId(sig, sigId);
         }
     }
 }
 
--- a/js/src/wasm/WasmInstance.h
+++ b/js/src/wasm/WasmInstance.h
@@ -168,15 +168,12 @@ class Instance
     static uint32_t currentMemory_i32(Instance* instance);
     static int32_t wait_i32(Instance* instance, uint32_t byteOffset, int32_t value, int64_t timeout);
     static int32_t wait_i64(Instance* instance, uint32_t byteOffset, int64_t value, int64_t timeout);
     static int32_t wake(Instance* instance, uint32_t byteOffset, int32_t count);
 };
 
 typedef UniquePtr<Instance> UniqueInstance;
 
-bool InitSignatureSet();
-void ReleaseSignatureSet();
-
 } // namespace wasm
 } // namespace js
 
 #endif // wasm_instance_h
--- a/js/src/wasm/WasmProcess.cpp
+++ b/js/src/wasm/WasmProcess.cpp
@@ -242,27 +242,20 @@ wasm::LookupCodeSegment(const void* pc, 
 
 const Code*
 wasm::LookupCode(const void* pc, const CodeRange** cr /* = nullptr */)
 {
     const CodeSegment* found = LookupCodeSegment(pc, cr);
     return found ? &found->code() : nullptr;
 }
 
-bool
-wasm::Init()
-{
-    return InitSignatureSet();
-}
-
 void
 wasm::ShutDown()
 {
     // If there are live runtimes then we are already pretty much leaking the
     // world, so to avoid spurious assertions (which are valid and valuable when
     // there are not live JSRuntimes), don't bother releasing anything here.
     if (JSRuntime::hasLiveRuntimes())
         return;
 
-    ReleaseSignatureSet();
     ReleaseBuiltinThunks();
     processCodeSegmentMap.freeAll();
 }
--- a/js/src/wasm/WasmProcess.h
+++ b/js/src/wasm/WasmProcess.h
@@ -50,18 +50,15 @@ bool
 RegisterCodeSegment(const CodeSegment* cs);
 
 void
 UnregisterCodeSegment(const CodeSegment* cs);
 
 // Called once before/after the last VM execution which could execute or compile
 // wasm.
 
-bool
-Init();
-
 void
 ShutDown();
 
 } // namespace wasm
 } // namespace js
 
 #endif // wasm_process_h
--- a/js/xpconnect/src/ExportHelpers.cpp
+++ b/js/xpconnect/src/ExportHelpers.cpp
@@ -285,17 +285,20 @@ FunctionForwarder(JSContext* cx, unsigne
         return false;
 
     // Grab and unwrap the underlying callable.
     RootedValue v(cx, js::GetFunctionNativeReserved(&args.callee(), 0));
     RootedObject unwrappedFun(cx, js::UncheckedUnwrap(&v.toObject()));
 
     RootedValue thisVal(cx, NullValue());
     if (!args.isConstructing()) {
-        thisVal.set(args.computeThis(cx));
+        RootedObject thisObject(cx);
+        if (!args.computeThis(cx, &thisObject))
+            return false;
+        thisVal.setObject(*thisObject);
     }
 
     {
         // We manually implement the contents of CrossCompartmentWrapper::call
         // here, because certain function wrappers (notably content->nsEP) are
         // not callable.
         JSAutoCompartment ac(cx, unwrappedFun);
         if (!CheckSameOriginArg(cx, options, thisVal) || !JS_WrapValue(cx, &thisVal))
--- a/js/xpconnect/src/Sandbox.cpp
+++ b/js/xpconnect/src/Sandbox.cpp
@@ -211,22 +211,20 @@ SandboxImport(JSContext* cx, unsigned ar
 
     RootedId id(cx);
     if (!JS_StringToId(cx, funname, &id))
         return false;
 
     // We need to resolve the this object, because this function is used
     // unbound and should still work and act on the original sandbox.
 
-    RootedValue thisv(cx, args.computeThis(cx));
-    if (!thisv.isObject()) {
-        XPCThrower::Throw(NS_ERROR_UNEXPECTED, cx);
+    RootedObject thisObject(cx);
+    if (!args.computeThis(cx, &thisObject))
         return false;
-    }
-    RootedObject thisObject(cx, &thisv.toObject());
+
     if (!JS_SetPropertyById(cx, thisObject, id, args[0]))
         return false;
 
     args.rval().setUndefined();
     return true;
 }
 
 static bool
@@ -557,17 +555,25 @@ xpc::SandboxCallableProxyHandler::call(J
     //
     // Luckily, the intent of Xrays is to provide a vanilla view of a foreign
     // DOM interface, which means that we don't care about script-enacted
     // strictness in the prototype's home compartment. Indeed, since DOM
     // methods are always non-strict, we can just assume non-strict semantics
     // if the sandboxPrototype is an Xray Wrapper, which lets us appropriately
     // remap |this|.
     bool isXray = WrapperFactory::IsXrayWrapper(sandboxProxy);
-    RootedValue thisVal(cx, isXray ? args.computeThis(cx) : args.thisv());
+    RootedValue thisVal(cx, args.thisv());
+    if (isXray) {
+        RootedObject thisObject(cx);
+        if (!args.computeThis(cx, &thisObject)) {
+            return false;
+        }
+        thisVal.setObject(*thisObject);
+    }
+
     if (thisVal == ObjectValue(*sandboxGlobal)) {
         thisVal = ObjectValue(*js::GetProxyTargetObject(sandboxProxy));
     }
 
     RootedValue func(cx, js::GetProxyPrivate(proxy));
     return JS::Call(cx, thisVal, func, args, args.rval());
 }
 
--- a/js/xpconnect/src/XPCShellImpl.cpp
+++ b/js/xpconnect/src/XPCShellImpl.cpp
@@ -332,18 +332,20 @@ Dump(JSContext* cx, unsigned argc, Value
     return true;
 }
 
 static bool
 Load(JSContext* cx, unsigned argc, Value* vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
 
-    RootedValue thisv(cx, args.computeThis(cx));
-    if (!thisv.isObject() || !JS_IsGlobalObject(&thisv.toObject())) {
+    JS::RootedObject thisObject(cx);
+    if (!args.computeThis(cx, &thisObject))
+        return false;
+    if (!JS_IsGlobalObject(thisObject)) {
         JS_ReportErrorASCII(cx, "Trying to load() into a non-global object");
         return false;
     }
 
     RootedString str(cx);
     for (unsigned i = 0; i < args.length(); i++) {
         str = ToString(cx, args[i]);
         if (!str)
--- a/js/xpconnect/src/XPCWrappedNativeJSOps.cpp
+++ b/js/xpconnect/src/XPCWrappedNativeJSOps.cpp
@@ -65,22 +65,19 @@ ToStringGuts(XPCCallContext& ccx)
 
 /***************************************************************************/
 
 static bool
 XPC_WN_Shared_ToString(JSContext* cx, unsigned argc, Value* vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
 
-    RootedValue thisv(cx, args.computeThis(cx));
-    if (!thisv.isObject()) {
-        JS_ReportErrorASCII(cx, "Called on incompatible |this|");
+    RootedObject obj(cx);
+    if (!args.computeThis(cx, &obj))
         return false;
-    }
-    RootedObject obj(cx, &thisv.toObject());
 
     XPCCallContext ccx(cx, obj);
     if (!ccx.IsValid())
         return Throw(NS_ERROR_XPC_BAD_OP_ON_WN_PROTO, cx);
     ccx.SetName(ccx.GetContext()->GetStringID(XPCJSContext::IDX_TO_STRING));
     ccx.SetArgsAndResultPtr(args.length(), args.array(), vp);
     return ToStringGuts(ccx);
 }
@@ -890,22 +887,19 @@ FixUpThisIfBroken(JSObject* obj, JSObjec
 
 bool
 XPC_WN_CallMethod(JSContext* cx, unsigned argc, Value* vp)
 {
     JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
     MOZ_ASSERT(JS_TypeOfValue(cx, args.calleev()) == JSTYPE_FUNCTION, "bad function");
     RootedObject funobj(cx, &args.callee());
 
-    RootedValue thisv(cx, args.computeThis(cx));
-    if (!thisv.isObject()) {
-        JS_ReportErrorASCII(cx, "Called on incompatible |this|");
+    RootedObject obj(cx);
+    if (!args.computeThis(cx, &obj))
         return false;
-    }
-    RootedObject obj(cx, &thisv.toObject());
 
     obj = FixUpThisIfBroken(obj, funobj);
     XPCCallContext ccx(cx, obj, funobj, JSID_VOIDHANDLE, args.length(),
                        args.array(), vp);
     XPCWrappedNative* wrapper = ccx.GetWrapper();
     THROW_AND_RETURN_IF_BAD_WRAPPER(cx, wrapper);
 
     RefPtr<XPCNativeInterface> iface;
--- a/mobile/android/base/java/org/mozilla/gecko/GeckoApp.java
+++ b/mobile/android/base/java/org/mozilla/gecko/GeckoApp.java
@@ -897,16 +897,20 @@ public abstract class GeckoApp extends G
     }
 
     @Override
     public void onContextMenu(final GeckoSession session, final int screenX,
                               final int screenY, final String uri,
                               int elementType, final String elementSrc) {
     }
 
+    @Override
+    public void onExternalResponse(final GeckoSession session, final GeckoSession.WebResponseInfo request) {
+    }
+
     protected void setFullScreen(final boolean fullscreen) {
         ThreadUtils.postToUiThread(new Runnable() {
             @Override
             public void run() {
                 onFullScreen(mLayerView.getSession(), fullscreen);
             }
         });
     }
--- a/mobile/android/base/java/org/mozilla/gecko/customtabs/CustomTabsActivity.java
+++ b/mobile/android/base/java/org/mozilla/gecko/customtabs/CustomTabsActivity.java
@@ -719,16 +719,19 @@ public class CustomTabsActivity extends 
         runOnUiThread(new Runnable() {
             @Override
             public void run() {
                 WebApps.openInFennec(validUri, CustomTabsActivity.this);
             }
         });
     }
 
+    @Override
+    public void onExternalResponse(final GeckoSession session, final GeckoSession.WebResponseInfo request) {
+    }
 
     @Override // ActionModePresenter
     public void startActionMode(final ActionMode.Callback callback) {
         endActionMode();
         mActionMode = startSupportActionMode(callback);
     }
 
     @Override // ActionModePresenter
--- a/mobile/android/base/java/org/mozilla/gecko/webapps/WebAppActivity.java
+++ b/mobile/android/base/java/org/mozilla/gecko/webapps/WebAppActivity.java
@@ -360,16 +360,20 @@ public class WebAppActivity extends AppC
         if (validUri == null) {
             return;
         }
 
         WebApps.openInFennec(validUri, WebAppActivity.this);
     }
 
     @Override // GeckoSession.ContentDelegate
+    public void onExternalResponse(final GeckoSession session, final GeckoSession.WebResponseInfo request) {
+    }
+
+    @Override // GeckoSession.ContentDelegate
     public void onFullScreen(GeckoSession session, boolean fullScreen) {
         updateFullScreenContent(fullScreen);
     }
 
     @Override
     public void onLoadRequest(final GeckoSession session, final String urlStr,
                               final int target,
                               final GeckoSession.Response<Boolean> response) {
--- a/mobile/android/components/geckoview/GeckoView.manifest
+++ b/mobile/android/components/geckoview/GeckoView.manifest
@@ -14,8 +14,12 @@ contract @mozilla.org/content-permission
 # GeckoViewPrompt.js
 component {076ac188-23c1-4390-aa08-7ef1f78ca5d9} GeckoViewPrompt.js
 contract @mozilla.org/embedcomp/prompt-service;1 {076ac188-23c1-4390-aa08-7ef1f78ca5d9}
 contract @mozilla.org/prompter;1 {076ac188-23c1-4390-aa08-7ef1f78ca5d9}
 component {aa0dd6fc-73dd-4621-8385-c0b377e02cee} GeckoViewPrompt.js process=main
 contract @mozilla.org/colorpicker;1 {aa0dd6fc-73dd-4621-8385-c0b377e02cee} process=main
 component {e4565e36-f101-4bf5-950b-4be0887785a9} GeckoViewPrompt.js process=main
 contract @mozilla.org/filepicker;1 {e4565e36-f101-4bf5-950b-4be0887785a9} process=main
+
+# GeckoViewExternalAppService.js
+component {a89eeec6-6608-42ee-a4f8-04d425992f45} GeckoViewExternalAppService.js
+contract @mozilla.org/uriloader/external-helper-app-service;1 {a89eeec6-6608-42ee-a4f8-04d425992f45}
\ No newline at end of file
new file mode 100644
--- /dev/null
+++ b/mobile/android/components/geckoview/GeckoViewExternalAppService.js
@@ -0,0 +1,55 @@
+/* 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";
+
+ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
+
+ChromeUtils.defineModuleGetter(this, "EventDispatcher",
+  "resource://gre/modules/Messaging.jsm");
+
+XPCOMUtils.defineLazyGetter(this, "dump", () =>
+    ChromeUtils.import("resource://gre/modules/AndroidLog.jsm",
+                       {}).AndroidLog.d.bind(null, "ViewContent"));
+
+function debug(aMsg) {
+  // dump(aMsg);
+}
+
+function ExternalAppService() {
+  this.wrappedJSObject = this;
+}
+
+ExternalAppService.prototype = {
+  classID: Components.ID("{a89eeec6-6608-42ee-a4f8-04d425992f45}"),
+  QueryInterface: XPCOMUtils.generateQI([Ci.nsIExternalHelperAppService]),
+
+  doContent(mimeType, request, context, forceSave) {
+    const channel = request.QueryInterface(Ci.nsIChannel);
+    const mm = context.QueryInterface(Ci.nsIDocShell).tabChild.messageManager;
+
+    debug(`doContent() URI=${channel.URI.displaySpec}, contentType=${channel.contentType}`);
+
+    EventDispatcher.forMessageManager(mm).sendRequest({
+      type: "GeckoView:ExternalResponse",
+      uri: channel.URI.displaySpec,
+      contentType: channel.contentType,
+      contentLength: channel.contentLength,
+      filename: channel.contentDispositionFilename
+    });
+
+    request.cancel(Cr.NS_ERROR_ABORT);
+    Components.returnCode = Cr.NS_ERROR_ABORT;
+  },
+
+  applyDecodingForExtension(ext, encoding) {
+    debug(`applyDecodingForExtension() extension=${ext}, encoding=${encoding}`);
+
+    // This doesn't matter for us right now because
+    // we shouldn't end up reading the stream.
+    return true;
+  }
+};
+
+this.NSGetFactory = XPCOMUtils.generateNSGetFactory([ExternalAppService]);
--- a/mobile/android/components/geckoview/moz.build
+++ b/mobile/android/components/geckoview/moz.build
@@ -1,12 +1,13 @@
 # -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
 # vim: set filetype=python:
 # 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/.
 
 EXTRA_COMPONENTS += [
     'GeckoView.manifest',
+    'GeckoViewExternalAppService.js',
     'GeckoViewPermission.js',
     'GeckoViewPrompt.js',
     'GeckoViewStartup.js',
 ]
new file mode 100644
--- /dev/null
+++ b/mobile/android/geckoview/src/androidTest/assets/www/download.html
@@ -0,0 +1,13 @@
+<html>
+<body>
+    <script>
+        const data = "Downloaded Data";
+        const element = document.createElement("a");
+        element.setAttribute("href", "data:text/plain;charset=utf-8," + encodeURIComponent(data));
+        element.setAttribute("download", "download.txt");
+        element.style.display = "none";
+        document.body.appendChild(element);
+        element.click();
+    </script>
+</body>
+</html>
\ No newline at end of file
new file mode 100644
--- /dev/null
+++ b/mobile/android/geckoview/src/androidTest/assets/www/titleChange.html
@@ -0,0 +1,12 @@
+<html>
+<header><title>Title1</title></header>
+    <body>
+        <script>
+            addEventListener("load", function() {
+                setTimeout(function() {
+                    document.title = "Title2";
+                }, 100);
+            });
+        </script>
+    </body>
+</html>
\ No newline at end of file
--- a/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/BaseSessionTest.kt
+++ b/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/BaseSessionTest.kt
@@ -22,18 +22,20 @@ import kotlin.reflect.KClass
  * providing the test rule and other utilities.
  */
 open class BaseSessionTest(noErrorCollector: Boolean = false) {
     companion object {
         const val INVALID_URI = "http://www.test.invalid/"
         const val HELLO_HTML_PATH = "/assets/www/hello.html"
         const val HELLO2_HTML_PATH = "/assets/www/hello2.html"
         const val NEW_SESSION_HTML_PATH = "/assets/www/newSession.html";
-        const val NEW_SESSION_CHILD_HTML_PATH = "/assets/www/newSession_child.html";
-        const val CLICK_TO_RELOAD_HTML_PATH = "/assets/www/clickToReload.html";
+        const val NEW_SESSION_CHILD_HTML_PATH = "/assets/www/newSession_child.html"
+        const val CLICK_TO_RELOAD_HTML_PATH = "/assets/www/clickToReload.html"
+        const val TITLE_CHANGE_HTML_PATH = "/assets/www/titleChange.html"
+        const val DOWNLOAD_HTML_PATH = "/assets/www/download.html"
     }
 
     @get:Rule val sessionRule = GeckoSessionTestRule()
 
     @get:Rule val errors = ErrorCollector()
     fun <T> assertThat(reason: String, v: T, m: Matcher<T>) = sessionRule.assertThat(reason, v, m)
 
     init {
new file mode 100644
--- /dev/null
+++ b/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/ContentDelegateTest.kt
@@ -0,0 +1,59 @@
+/* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+package org.mozilla.geckoview.test
+
+import org.mozilla.geckoview.GeckoSession
+import org.mozilla.geckoview.test.rule.GeckoSessionTestRule.AssertCalled
+import org.mozilla.geckoview.test.util.Callbacks
+
+import android.support.test.filters.MediumTest
+import android.support.test.runner.AndroidJUnit4
+import org.hamcrest.Matchers.*
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+@MediumTest
+class ContentDelegateTest : BaseSessionTest() {
+
+    @Test fun titleChange() {
+        sessionRule.session.loadTestPath(TITLE_CHANGE_HTML_PATH)
+
+        val titles = mutableListOf("Title1", "Title2")
+        sessionRule.waitUntilCalled(object : Callbacks.ContentDelegate {
+            @AssertCalled(count = 2)
+            override fun onTitleChange(session: GeckoSession, title: String) {
+                assertThat("Title should match", title,
+                           equalTo(titles.removeAt(0)))
+            }
+        })
+    }
+
+    @Test fun download() {
+        sessionRule.session.loadTestPath(DOWNLOAD_HTML_PATH)
+
+        sessionRule.waitUntilCalled(object : Callbacks.NavigationDelegate, Callbacks.ContentDelegate {
+
+            @AssertCalled(count = 2)
+            override fun onLoadRequest(session: GeckoSession, uri: String, where: Int, response: GeckoSession.Response<Boolean>) {
+                response.respond(false)
+            }
+
+            @AssertCalled(false)
+            override fun onNewSession(session: GeckoSession, uri: String, response: GeckoSession.Response<GeckoSession>) {
+            }
+
+            @AssertCalled(count = 1)
+            override fun onExternalResponse(session: GeckoSession, response: GeckoSession.WebResponseInfo) {
+                assertThat("Uri should start with data:", response.uri, startsWith("data:"))
+                assertThat("Content type should match", response.contentType, equalTo("text/plain"))
+                assertThat("Content length should be non-zero", response.contentLength, greaterThan(0L))
+                assertThat("Filename should match", response.filename, equalTo("download.txt"))
+            }
+        })
+    }
+
+}
\ No newline at end of file
--- a/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/TestRunnerActivity.java
+++ b/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/TestRunnerActivity.java
@@ -1,17 +1,15 @@
 /* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*-
  * This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 package org.mozilla.geckoview.test;
 
-import org.mozilla.gecko.mozglue.GeckoLoader;
-import org.mozilla.gecko.mozglue.SafeIntent;
 import org.mozilla.geckoview.GeckoSession;
 import org.mozilla.geckoview.GeckoSessionSettings;
 import org.mozilla.geckoview.GeckoView;
 
 import android.app.Activity;
 import android.content.Intent;
 import android.net.Uri;
 import android.os.Bundle;
@@ -71,16 +69,20 @@ public class TestRunnerActivity extends 
         public void onFullScreen(GeckoSession session, boolean fullScreen) {
 
         }
 
         @Override
         public void onContextMenu(GeckoSession session, int screenX, int screenY, String uri, int elementType, String elementSrc) {
 
         }
+
+        @Override
+        public void onExternalResponse(GeckoSession session, GeckoSession.WebResponseInfo request) {
+        }
     };
 
     private GeckoSession createSession() {
         return createSession(null);
     }
 
     private GeckoSession createSession(GeckoSessionSettings settings) {
         if (settings == null) {
--- a/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/util/Callbacks.kt
+++ b/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/util/Callbacks.kt
@@ -23,16 +23,19 @@ class Callbacks private constructor() {
         override fun onCloseRequest(session: GeckoSession) {
         }
 
         override fun onFullScreen(session: GeckoSession, fullScreen: Boolean) {
         }
 
         override fun onContextMenu(session: GeckoSession, screenX: Int, screenY: Int, uri: String, elementType: Int, elementSrc: String) {
         }
+
+        override fun onExternalResponse(session: GeckoSession, response: GeckoSession.WebResponseInfo) {
+        }
     }
 
     interface NavigationDelegate : GeckoSession.NavigationDelegate {
         override fun onLocationChange(session: GeckoSession, url: String) {
         }
 
         override fun onCanGoBack(session: GeckoSession, canGoBack: Boolean) {
         }
--- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/util/GeckoBundle.java
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/util/GeckoBundle.java
@@ -30,16 +30,17 @@ import java.util.Iterator;
 @RobocopTarget
 public final class GeckoBundle implements Parcelable {
     private static final String LOGTAG = "GeckoBundle";
     private static final boolean DEBUG = false;
 
     @WrapForJNI(calledFrom = "gecko")
     private static final boolean[] EMPTY_BOOLEAN_ARRAY = new boolean[0];
     private static final int[] EMPTY_INT_ARRAY = new int[0];
+    private static final long[] EMPTY_LONG_ARRAY = new long[0];
     private static final double[] EMPTY_DOUBLE_ARRAY = new double[0];
     private static final String[] EMPTY_STRING_ARRAY = new String[0];
     private static final GeckoBundle[] EMPTY_BUNDLE_ARRAY = new GeckoBundle[0];
 
     @WrapForJNI(calledFrom = "gecko")
     private static Object box(boolean b) { return b; }
     @WrapForJNI(calledFrom = "gecko")
     private static Object box(int i) { return i; }
@@ -243,16 +244,62 @@ public final class GeckoBundle implement
      */
     public int[] getIntArray(final String key) {
         final Object value = mMap.get(key);
         return value == null ? null : Array.getLength(value) == 0 ? EMPTY_INT_ARRAY :
                value instanceof double[] ? getIntArray((double[]) value) : (int[]) value;
     }
 
     /**
+     * Returns the value associated with a long mapping, or defaultValue if the mapping
+     * does not exist.
+     *
+     * @param key Key to look for.
+     * @param defaultValue Value to return if mapping does not exist.
+     * @return long value
+     */
+    public long getLong(final String key, final long defaultValue) {
+        final Object value = mMap.get(key);
+        return value == null ? defaultValue : ((Number) value).longValue();
+    }
+
+    /**
+     * Returns the value associated with a long mapping, or defaultValue if the mapping
+     * does not exist.
+     *
+     * @param key Key to look for.
+     * @return long value
+     */
+    public long getLong(final String key) {
+        return getLong(key, 0L);
+    }
+
+    private static long[] getLongArray(final double[] array) {
+        final int len = array.length;
+        final long[] ret = new long[len];
+        for (int i = 0; i < len; i++) {
+            ret[i] = (long) array[i];
+        }
+        return ret;
+    }
+
+    /**
+     * Returns the value associated with a long array mapping, or null if the mapping does
+     * not exist.
+     *
+     * @param key Key to look for.
+     * @return long array value
+     */
+    public long[] getLongArray(final String key) {
+        final Object value = mMap.get(key);
+        return value == null ? null : Array.getLength(value) == 0 ? EMPTY_LONG_ARRAY :
+                value instanceof double[] ? getLongArray((double[]) value) : (long[]) value;
+    }
+
+    /**
      * Returns the value associated with a String mapping, or defaultValue if the mapping
      * does not exist.
      *
      * @param key Key to look for.
      * @param defaultValue Value to return if mapping value is null or mapping does not exist.
      * @return String value
      */
     public String getString(final String key, final String defaultValue) {
--- a/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoSession.java
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoSession.java
@@ -94,16 +94,17 @@ public class GeckoSession extends LayerS
     private final GeckoSessionHandler<ContentDelegate> mContentHandler =
         new GeckoSessionHandler<ContentDelegate>(
             "GeckoViewContent", this,
             new String[]{
                 "GeckoView:ContextMenu",
                 "GeckoView:DOMTitleChanged",
                 "GeckoView:DOMWindowFocus",
                 "GeckoView:DOMWindowClose",
+                "GeckoView:ExternalResponse",
                 "GeckoView:FullScreenEnter",
                 "GeckoView:FullScreenExit"
             }
         ) {
             @Override
             public void handleMessage(final ContentDelegate delegate,
                                       final String event,
                                       final GeckoBundle message,
@@ -125,16 +126,18 @@ public class GeckoSession extends LayerS
                 } else if ("GeckoView:DOMWindowFocus".equals(event)) {
                     delegate.onFocusRequest(GeckoSession.this);
                 } else if ("GeckoView:DOMWindowClose".equals(event)) {
                     delegate.onCloseRequest(GeckoSession.this);
                 } else if ("GeckoView:FullScreenEnter".equals(event)) {
                     delegate.onFullScreen(GeckoSession.this, true);
                 } else if ("GeckoView:FullScreenExit".equals(event)) {
                     delegate.onFullScreen(GeckoSession.this, false);
+                } else if ("GeckoView:ExternalResponse".equals(event)) {
+                    delegate.onExternalResponse(GeckoSession.this, new WebResponseInfo(message));
                 }
             }
         };
 
     private final GeckoSessionHandler<NavigationDelegate> mNavigationHandler =
         new GeckoSessionHandler<NavigationDelegate>(
             "GeckoViewNavigation", this,
             new String[]{
@@ -1609,16 +1612,53 @@ public class GeckoSession extends LayerS
         } else if ("HTMLVideoElement".equals(name)) {
             return ContentDelegate.ELEMENT_TYPE_VIDEO;
         } else if ("HTMLAudioElement".equals(name)) {
             return ContentDelegate.ELEMENT_TYPE_AUDIO;
         }
         return ContentDelegate.ELEMENT_TYPE_NONE;
     }
 
+    /**
+     * WebResponseInfo contains information about a single web response.
+     */
+    public class WebResponseInfo {
+        /**
+         * The URI of the response. Cannot be null.
+         */
+        public final String uri;
+
+        /**
+         * The content type (mime type) of the response. May be null.
+         */
+        public final String contentType;
+
+        /**
+         * The content length of the response. May be 0 if unknokwn.
+         */
+        public final long contentLength;
+
+        /**
+         * The filename obtained from the content disposition, if any.
+         * May be null.
+         */
+        public final String filename;
+
+        /* package */ WebResponseInfo(GeckoBundle message) {
+            uri = message.getString("uri");
+            if (uri == null) {
+                throw new IllegalArgumentException("URI cannot be null");
+            }
+
+            contentType = message.getString("contentType");
+            contentLength = message.getLong("contentLength");
+            filename = message.getString("filename");
+        }
+    }
+
     public interface ContentDelegate {
         @IntDef({ELEMENT_TYPE_NONE, ELEMENT_TYPE_IMAGE, ELEMENT_TYPE_VIDEO,
                  ELEMENT_TYPE_AUDIO})
         public @interface ElementType {}
         static final int ELEMENT_TYPE_NONE = 0;
         static final int ELEMENT_TYPE_IMAGE = 1;
         static final int ELEMENT_TYPE_VIDEO = 2;
         static final int ELEMENT_TYPE_AUDIO = 3;
@@ -1668,16 +1708,25 @@ public class GeckoSession extends LayerS
          * @param elementType The type of the pressed element.
          *                    One of the {@link ContentDelegate#ELEMENT_TYPE_NONE} flags.
          * @param elementSrc The source URI of the pressed element, set for
          *                   (nested) images and media elements.
          */
         void onContextMenu(GeckoSession session, int screenX, int screenY,
                            String uri, @ElementType int elementType,
                            String elementSrc);
+
+        /**
+         * This is fired when there is a response that cannot be handled
+         * by Gecko (e.g., a download).
+         *
+         * @param session the GeckoSession that received the external response.
+         * @param response the WebResponseInfo for the external response
+         */
+        void onExternalResponse(GeckoSession session, WebResponseInfo response);
     }
 
     /**
      * This is used to send responses in delegate methods that have asynchronous responses.
      */
     public interface Response<T> {
         /**
          * @param val The value contained in the response
--- a/mobile/android/geckoview_example/src/main/java/org/mozilla/geckoview_example/GeckoViewActivity.java
+++ b/mobile/android/geckoview_example/src/main/java/org/mozilla/geckoview_example/GeckoViewActivity.java
@@ -7,17 +7,16 @@ package org.mozilla.geckoview_example;
 
 import android.app.Activity;
 import android.content.Intent;
 import android.content.pm.PackageManager;
 import android.net.Uri;
 import android.os.Build;
 import android.os.Bundle;
 import android.os.SystemClock;
-import android.text.TextUtils;
 import android.util.Log;
 import android.view.WindowManager;
 
 import java.util.Locale;
 
 import org.mozilla.gecko.GeckoThread;
 import org.mozilla.geckoview.GeckoSession;
 import org.mozilla.geckoview.GeckoSessionSettings;
@@ -191,16 +190,20 @@ public class GeckoViewActivity extends A
         @Override
         public void onContextMenu(GeckoSession session, int screenX, int screenY,
                                   String uri, int elementType, String elementSrc) {
             Log.d(LOGTAG, "onContextMenu screenX=" + screenX +
                           " screenY=" + screenY + " uri=" + uri +
                           " elementType=" + elementType +
                           " elementSrc=" + elementSrc);
         }
+
+        @Override
+        public void onExternalResponse(GeckoSession session, GeckoSession.WebResponseInfo request) {
+        }
     }
 
     private class MyGeckoViewProgress implements GeckoSession.ProgressDelegate {
         private MyTrackingProtection mTp;
 
         private MyGeckoViewProgress(final MyTrackingProtection tp) {
             mTp = tp;
         }
--- a/mobile/android/installer/package-manifest.in
+++ b/mobile/android/installer/package-manifest.in
@@ -505,16 +505,17 @@
 @BINPATH@/components/remotebrowserutils.manifest
 
 [mobile]
 @BINPATH@/chrome/geckoview@JAREXT@
 @BINPATH@/chrome/geckoview.manifest
 
 #ifdef MOZ_GECKOVIEW_JAR
 @BINPATH@/components/GeckoView.manifest
+@BINPATH@/components/GeckoViewExternalAppService.js
 @BINPATH@/components/GeckoViewPrompt.js
 @BINPATH@/components/GeckoViewPermission.js
 @BINPATH@/components/GeckoViewStartup.js
 #else
 @BINPATH@/chrome/chrome@JAREXT@
 @BINPATH@/chrome/chrome.manifest
 @BINPATH@/components/AboutRedirector.js
 @BINPATH@/components/AddonUpdateService.js
--- a/security/nss/TAG-INFO
+++ b/security/nss/TAG-INFO
@@ -1,1 +1,1 @@
-dedf5290c679
+6ae3ab8a1e7b
--- a/security/nss/coreconf/coreconf.dep
+++ b/security/nss/coreconf/coreconf.dep
@@ -5,9 +5,8 @@
 
 /*
  * A dummy header file that is a dependency for all the object files.
  * Used to force a full recompilation of NSS in Mozilla's Tinderbox
  * depend builds.  See comments in rules.mk.
  */
 
 #error "Do not include this header file."
-
--- a/security/nss/lib/dev/devslot.c
+++ b/security/nss/lib/dev/devslot.c
@@ -91,20 +91,26 @@ nssSlot_ResetDelay(
     NSSSlot *slot)
 {
     PZ_Lock(slot->isPresentLock);
     slot->lastTokenPingState = nssSlotLastPingState_Reset;
     PZ_Unlock(slot->isPresentLock);
 }
 
 static PRBool
-within_token_delay_period(const NSSSlot *slot)
+token_status_checked(const NSSSlot *slot)
 {
     PRIntervalTime time;
     int lastPingState = slot->lastTokenPingState;
+    /* When called from the same thread, that means
+     * nssSlot_IsTokenPresent() is called recursively through
+     * nssSlot_Refresh(). Return immediately in that case. */
+    if (slot->isPresentThread == PR_GetCurrentThread()) {
+        return PR_TRUE;
+    }
     /* Set the delay time for checking the token presence */
     if (s_token_delay_time == 0) {
         s_token_delay_time = PR_SecondsToInterval(NSSSLOT_TOKEN_DELAY_TIME);
     }
     time = PR_IntervalNow();
     if ((lastPingState == nssSlotLastPingState_Valid) && ((time - slot->lastTokenPingTime) < s_token_delay_time)) {
         return PR_TRUE;
     }
@@ -125,46 +131,46 @@ nssSlot_IsTokenPresent(
 
     /* permanent slots are always present unless they're disabled */
     if (nssSlot_IsPermanent(slot)) {
         return !PK11_IsDisabled(slot->pk11slot);
     }
 
     /* avoid repeated calls to check token status within set interval */
     PZ_Lock(slot->isPresentLock);
-    if (within_token_delay_period(slot)) {
+    if (token_status_checked(slot)) {
         CK_FLAGS ckFlags = slot->ckFlags;
         PZ_Unlock(slot->isPresentLock);
         return ((ckFlags & CKF_TOKEN_PRESENT) != 0);
     }
     PZ_Unlock(slot->isPresentLock);
 
     /* First obtain the slot epv before we set up the condition
      * variable, so we can just return if we couldn't get it. */
     epv = slot->epv;
     if (!epv) {
         return PR_FALSE;
     }
 
     /* set up condition so only one thread is active in this part of the code at a time */
     PZ_Lock(slot->isPresentLock);
-    while (slot->inIsPresent) {
+    while (slot->isPresentThread) {
         PR_WaitCondVar(slot->isPresentCondition, 0);
     }
     /* if we were one of multiple threads here, the first thread will have
      * given us the answer, no need to make more queries of the token. */
-    if (within_token_delay_period(slot)) {
+    if (token_status_checked(slot)) {
         CK_FLAGS ckFlags = slot->ckFlags;
         PZ_Unlock(slot->isPresentLock);
         return ((ckFlags & CKF_TOKEN_PRESENT) != 0);
     }
     /* this is the winning thread, block all others until we've determined
      * if the token is present and that it needs initialization. */
     slot->lastTokenPingState = nssSlotLastPingState_Update;
-    slot->inIsPresent = PR_TRUE;
+    slot->isPresentThread = PR_GetCurrentThread();
 
     PZ_Unlock(slot->isPresentLock);
 
     nssSlot_EnterMonitor(slot);
     ckrv = CKAPI(epv)->C_GetSlotInfo(slot->slotID, &slotInfo);
     nssSlot_ExitMonitor(slot);
     if (ckrv != CKR_OK) {
         slot->token->base.name[0] = 0; /* XXX */
@@ -252,17 +258,17 @@ done:
      */
     PZ_Lock(slot->isPresentLock);
     /* don't update the time if we were reset while we were
      * getting the token state */
     if (slot->lastTokenPingState == nssSlotLastPingState_Update) {
         slot->lastTokenPingTime = PR_IntervalNow();
         slot->lastTokenPingState = nssSlotLastPingState_Valid;
     }
-    slot->inIsPresent = PR_FALSE;
+    slot->isPresentThread = NULL;
     PR_NotifyAllCondVar(slot->isPresentCondition);
     PZ_Unlock(slot->isPresentLock);
     return isPresent;
 }
 
 NSS_IMPLEMENT void *
 nssSlot_GetCryptokiEPV(
     NSSSlot *slot)
--- a/security/nss/lib/dev/devt.h
+++ b/security/nss/lib/dev/devt.h
@@ -87,17 +87,17 @@ struct NSSSlotStr {
     struct nssSlotAuthInfoStr authInfo;
     PRIntervalTime lastTokenPingTime;
     nssSlotLastPingState lastTokenPingState;
     PZLock *lock;
     void *epv;
     PK11SlotInfo *pk11slot;
     PZLock *isPresentLock;
     PRCondVar *isPresentCondition;
-    PRBool inIsPresent;
+    PRThread *isPresentThread;
 };
 
 struct nssSessionStr {
     PZLock *lock;
     CK_SESSION_HANDLE handle;
     NSSSlot *slot;
     PRBool isRW;
     PRBool ownLock;
--- a/security/nss/lib/pk11wrap/dev3hack.c
+++ b/security/nss/lib/pk11wrap/dev3hack.c
@@ -117,17 +117,17 @@ nssSlot_CreateFromPK11SlotInfo(NSSTrustD
     rvSlot->pk11slot = PK11_ReferenceSlot(nss3slot);
     rvSlot->epv = nss3slot->functionList;
     rvSlot->slotID = nss3slot->slotID;
     /* Grab the slot name from the PKCS#11 fixed-length buffer */
     rvSlot->base.name = nssUTF8_Duplicate(nss3slot->slot_name, td->arena);
     rvSlot->lock = (nss3slot->isThreadSafe) ? NULL : nss3slot->sessionLock;
     rvSlot->isPresentLock = PZ_NewLock(nssiLockOther);
     rvSlot->isPresentCondition = PR_NewCondVar(rvSlot->isPresentLock);
-    rvSlot->inIsPresent = PR_FALSE;
+    rvSlot->isPresentThread = NULL;
     rvSlot->lastTokenPingState = nssSlotLastPingState_Reset;
     return rvSlot;
 }
 
 NSSToken *
 nssToken_CreateFromPK11SlotInfo(NSSTrustDomain *td, PK11SlotInfo *nss3slot)
 {
     NSSToken *rvToken;
--- a/taskcluster/ci/test/reftest.yml
+++ b/taskcluster/ci/test/reftest.yml
@@ -87,37 +87,36 @@ jsreftest:
             linux64-qr/.*: 1
             windows10-64-asan.*: 3
             default: default
 
 reftest:
     description: "Reftest run"
     suite: reftest/reftest
     treeherder-symbol: R(R)
-    worker-type:
-        by-test-platform:
-            windows10-64.*: buildbot-bridge/buildbot-bridge
-            default: null
     run-on-projects:
         by-test-platform:
             .*-qr/.*: ['trunk', 'try']  # exclude beta and project branches
             default: built-projects
     instance-size:
         by-test-platform:
             android.*: xlarge
             default: default
-    virtualization: virtual-with-gpu
+    virtualization:
+        by-test-platform:
+            windows10.*: hardware
+            default: virtual-with-gpu
     chunks:
         by-test-platform:
             android-4.3-arm7-api-16/debug: 56
             android.*: 28
             macosx64.*/opt: 1
             macosx64.*/debug: 3
-            windows10-64.*/opt: 1
-            windows10-64.*/debug: 2
+            windows.*/opt: 2
+            windows.*/debug: 4
             default: 8
     e10s:
         by-test-platform:
             linux32/debug: both
             default: true
     max-run-time:
         by-test-platform:
             android.*: 7200
@@ -134,17 +133,20 @@ reftest:
             linux64-qr/.*: 1
             windows10-64-asan.*: 3
             default: default
 
 reftest-gpu:
     description: "Reftest GPU run"
     suite: reftest/reftest-gpu
     treeherder-symbol: R(Rg)
-    chunks: 8
+    chunks:
+        by-test-platform:
+            windows.*/opt: 2
+            default: 4
     run-on-projects:
         by-test-platform:
             windows10.*: []
             default: built-projects
     instance-size: default
     virtualization: virtual-with-gpu
     max-run-time: 3600
     mozharness:
@@ -158,16 +160,17 @@ reftest-no-accel:
     virtualization: virtual-with-gpu
     run-on-projects:
         by-test-platform:
             windows10.*: []
             default: built-projects
     chunks:
         by-test-platform:
             macosx.*: 1
+            windows.*: 4
             default: 8
     e10s:
         by-test-platform:
             linux64-jsdcov/opt: false
             default: true
     mozharness:
         chunked:
             by-test-platform:
--- a/taskcluster/ci/test/test-sets.yml
+++ b/taskcluster/ci/test/test-sets.yml
@@ -199,16 +199,18 @@ windows-talos:
     - talos-other
     - talos-perf-reftest
     - talos-perf-reftest-singletons
     - talos-svgr
     - talos-tp5o
     - talos-xperf
     - talos-speedometer
     - talos-tp6
+    - talos-motionmark
+    - talos-h1
 
 macosx64-tests:
     - cppunit
     - crashtest
     - firefox-ui-functional-local
     - firefox-ui-functional-remote
     - gtest
     - jittest
--- a/taskcluster/ci/toolchain/linux.yml
+++ b/taskcluster/ci/toolchain/linux.yml
@@ -360,81 +360,104 @@ linux64-android-gradle-dependencies:
             - 'mobile/android/config/mozconfigs/common*'
             - 'mobile/android/gradle.configure'
         toolchain-artifact: public/build/android-gradle-dependencies.tar.xz
         toolchain-alias: android-gradle-dependencies
     toolchains:
         # Aliases aren't allowed for toolchains depending on toolchains.
         - linux64-android-sdk-linux-repack
 
+linux64-rust-1.25:
+    description: "rust repack"
+    treeherder:
+        kind: build
+        platform: toolchains/opt
+        symbol: TL(rust)
+        tier: 1
+    worker-type: aws-provisioner-v1/gecko-{level}-b-linux
+    worker:
+        max-run-time: 7200
+        env:
+            UPLOAD_DIR: artifacts
+    run:
+        using: toolchain-script
+        script: repack_rust.py
+        arguments: [
+            '--channel', '1.25.0',
+            '--host', 'x86_64-unknown-linux-gnu',
+            '--target', 'x86_64-unknown-linux-gnu',
+            '--target', 'i686-unknown-linux-gnu',
+        ]
+        toolchain-alias: linux64-rust
+        toolchain-artifact: public/build/rustc.tar.xz
+
 linux64-rust-1.24:
     description: "rust repack"
     treeherder:
         kind: build
         platform: toolchains/opt
-        symbol: TL(rust)
+        symbol: TL(rust-1.24)
         tier: 1
     worker-type: aws-provisioner-v1/gecko-{level}-b-linux
     worker:
         max-run-time: 7200
         env:
             UPLOAD_DIR: artifacts
     run:
         using: toolchain-script
         script: repack_rust.py
         arguments: [
             '--channel', '1.24.0',
             '--host', 'x86_64-unknown-linux-gnu',
             '--target', 'x86_64-unknown-linux-gnu',
             '--target', 'i686-unknown-linux-gnu',
         ]
-        toolchain-alias: linux64-rust
         toolchain-artifact: public/build/rustc.tar.xz
 
-linux64-rust-macos-1.24:
+linux64-rust-macos-1.25:
     description: "rust repack with macos-cross support"
     treeherder:
         kind: build
         platform: toolchains/opt
         symbol: TL(rust-macos)
         tier: 1
     worker-type: aws-provisioner-v1/gecko-{level}-b-linux
     worker:
         max-run-time: 7200
         env:
             UPLOAD_DIR: artifacts
     run:
         using: toolchain-script
         script: repack_rust.py
         arguments: [
-            '--channel', '1.24.0',
+            '--channel', '1.25.0',
             '--host', 'x86_64-unknown-linux-gnu',
             '--target', 'x86_64-unknown-linux-gnu',
             '--target', 'x86_64-apple-darwin',
         ]
         toolchain-alias: linux64-rust-macos
         toolchain-artifact: public/build/rustc.tar.xz
 
-linux64-rust-android-1.24:
+linux64-rust-android-1.25:
     description: "rust repack with android-cross support"
     treeherder:
         kind: build
         platform: toolchains/opt
         symbol: TL(rust-android)
         tier: 1
     worker-type: aws-provisioner-v1/gecko-{level}-b-linux
     worker:
         max-run-time: 7200
         env:
             UPLOAD_DIR: artifacts
     run:
         using: toolchain-script
         script: repack_rust.py
         arguments: [
-            '--channel', '1.24.0',
+            '--channel', '1.25.0',
             '--host', 'x86_64-unknown-linux-gnu',
             '--target', 'x86_64-unknown-linux-gnu',
             '--target', 'armv7-linux-androideabi',
             '--target', 'aarch64-linux-android',
             '--target', 'i686-linux-android',
         ]
         toolchain-alias: linux64-rust-android
         toolchain-artifact: public/build/rustc.tar.xz
@@ -451,17 +474,17 @@ linux64-sccache:
         max-run-time: 36000
     run:
         using: toolchain-script
         script: build-sccache.sh
         resources:
             - 'taskcluster/scripts/misc/tooltool-download.sh'
         toolchain-artifact: public/build/sccache2.tar.xz
     toolchains:
-        - linux64-rust-1.24
+        - linux64-rust-1.25
 
 linux64-gn:
     description: "gn toolchain build"
     treeherder:
         kind: build
         platform: toolchains/opt
         symbol: TL(gn)
         tier: 1
--- a/taskcluster/ci/toolchain/windows.yml
+++ b/taskcluster/ci/toolchain/windows.yml
@@ -113,83 +113,83 @@ win64-clang-tidy:
         script: build-clang-tidy64-windows.sh
         resources:
             - 'build/clang-plugin/**'
             - 'build/build-clang/build-clang.py'
             - 'build/build-clang/clang-tidy-win32.json'
             - 'taskcluster/scripts/misc/build-clang-windows-helper64.sh'
         toolchain-artifact: public/build/clang-tidy.tar.bz2
 
-win64-rust-1.24:
+win64-rust-1.25:
     description: "rust repack"
     treeherder:
         kind: build
         platform: toolchains/opt
         symbol: TW64(rust)
         tier: 1
     worker-type: aws-provisioner-v1/gecko-{level}-b-linux
     worker:
         docker-image: {in-tree: toolchain-build}
         max-run-time: 7200
         env:
             UPLOAD_DIR: artifacts
     run:
         using: toolchain-script
         script: repack_rust.py
         arguments: [
-            '--channel', '1.24.0',
+            '--channel', '1.25.0',
             '--host', 'x86_64-pc-windows-msvc',
             '--target', 'x86_64-pc-windows-msvc',
             '--target', 'i686-pc-windows-msvc',
         ]
         toolchain-alias: win64-rust
         toolchain-artifact: public/build/rustc.tar.bz2
 
-win32-rust-1.24:
+win32-rust-1.25:
     description: "rust repack"
     treeherder:
         kind: build
         platform: toolchains/opt
         symbol: TW32(rust)
         tier: 1
     worker-type: aws-provisioner-v1/gecko-{level}-b-linux
     worker:
         docker-image: {in-tree: toolchain-build}
         max-run-time: 7200
         env:
             UPLOAD_DIR: artifacts
     run:
         using: toolchain-script
         script: repack_rust.py
         arguments: [
-            '--channel', '1.24.0',
+            '--channel', '1.25.0',
             '--host', 'i686-pc-windows-msvc',
             '--target', 'i686-pc-windows-msvc',
         ]
         toolchain-alias: win32-rust
         toolchain-artifact: public/build/rustc.tar.bz2
 
-mingw32-rust-1.24:
+mingw32-rust-1.25:
     description: "rust repack"
     treeherder:
         kind: build
         platform: toolchains/opt
         symbol: TMW(rust)
         tier: 1
     worker-type: aws-provisioner-v1/gecko-{level}-b-linux
     worker:
         docker-image: {in-tree: toolchain-build}
         max-run-time: 7200
         env:
             UPLOAD_DIR: artifacts
     run:
         using: toolchain-script
         script: repack_rust.py
         arguments: [
-            '--channel', '1.24.0',
+            '--channel', '1.25.0',
             '--host', 'i686-unknown-linux-gnu',
             '--target', 'i686-pc-windows-gnu',
             '--target', 'x86_64-unknown-linux-gnu',
             '--target', 'i686-unknown-linux-gnu',
         ]
         toolchain-alias: mingw32-rust
         toolchain-artifact: public/build/rustc.tar.xz
 
@@ -207,17 +207,17 @@ win64-sccache:
             TOOLTOOL_MANIFEST: "browser/config/tooltool-manifests/win64/sccache-build.manifest"
     run:
         using: toolchain-script
         script: build-sccache.sh
         resources:
             - 'taskcluster/scripts/misc/tooltool-download.sh'
         toolchain-artifact: public/build/sccache2.tar.bz2
     toolchains:
-        - win64-rust-1.24
+        - win64-rust-1.25
 
 win32-gn:
     description: "gn toolchain build"
     treeherder:
         kind: build
         platform: toolchains/opt
         symbol: TW32(gn)
         tier: 1
--- a/taskcluster/taskgraph/transforms/coalesce.py
+++ b/taskcluster/taskgraph/transforms/coalesce.py
@@ -30,15 +30,16 @@ def enable_coalescing(config, jobs):
         if int(config.params['level']) > 1 and job['worker-type'] not in [
             # These worker types don't currently support coalescing.
             # This list can be removed when bug 1399401 is fixed:
             #   https://bugzilla.mozilla.org/show_bug.cgi?id=1399401
             'aws-provisioner-v1/gecko-t-win7-32',
             'aws-provisioner-v1/gecko-t-win7-32-gpu',
             'aws-provisioner-v1/gecko-t-win10-64',
             'aws-provisioner-v1/gecko-t-win10-64-gpu',
+            'releng-hardware/gecko-t-win10-64-hw',
         ]:
             job['coalesce'] = {
                 'job-identifier': sha256(job["label"]).hexdigest()[:20],
                 'age': 3600,
                 'size': 5,
             }
         yield job
--- a/taskcluster/taskgraph/transforms/tests.py
+++ b/taskcluster/taskgraph/transforms/tests.py
@@ -47,32 +47,32 @@ LINUX_WORKER_TYPES = {
     'default': 'aws-provisioner-v1/gecko-t-linux-large',
 }
 
 # windows worker types keyed by test-platform and virtualization
 WINDOWS_WORKER_TYPES = {
     'windows7-32': {
       'virtual': 'aws-provisioner-v1/gecko-t-win7-32',
       'virtual-with-gpu': 'aws-provisioner-v1/gecko-t-win7-32-gpu',
-      'hardware': 'releng-hardware/gecko-t-win7-32-hw',
+      'hardware': 'releng-hardware/gecko-t-win10-64-hw',
     },
     'windows7-32-pgo': {
       'virtual': 'aws-provisioner-v1/gecko-t-win7-32',
       'virtual-with-gpu': 'aws-provisioner-v1/gecko-t-win7-32-gpu',
-      'hardware': 'releng-hardware/gecko-t-win7-32-hw',
+      'hardware': 'releng-hardware/gecko-t-win10-64-hw',
     },
     'windows7-32-nightly': {
       'virtual': 'aws-provisioner-v1/gecko-t-win7-32',
       'virtual-with-gpu': 'aws-provisioner-v1/gecko-t-win7-32-gpu',
-      'hardware': 'releng-hardware/gecko-t-win7-32-hw',
+      'hardware': 'releng-hardware/gecko-t-win10-64-hw',
     },
     'windows7-32-devedition': {
       'virtual': 'aws-provisioner-v1/gecko-t-win7-32',
       'virtual-with-gpu': 'aws-provisioner-v1/gecko-t-win7-32-gpu',
-      'hardware': 'releng-hardware/gecko-t-win7-32-hw',
+      'hardware': 'releng-hardware/gecko-t-win10-64-hw',
     },
     'windows10-64': {
       'virtual': 'aws-provisioner-v1/gecko-t-win10-64',
       'virtual-with-gpu': 'aws-provisioner-v1/gecko-t-win10-64-gpu',
       'hardware': 'releng-hardware/gecko-t-win10-64-hw',
     },
     'windows10-64-ccov': {
       'virtual': 'aws-provisioner-v1/gecko-t-win10-64',
@@ -761,25 +761,16 @@ def split_chunks(config, tests):
     them and assigning 'this-chunk' appropriately and updating the treeherder
     symbol."""
     for test in tests:
         if test['chunks'] == 1:
             test['this-chunk'] = 1
             yield test
             continue
 
-        # HACK: Bug 1373578 appears to pass with more chunks, non-e10s only though
-        if test['test-platform'] == 'windows7-32/debug' and test['test-name'] == 'reftest':
-            test['chunks'] = 32
-
-        if (test['test-platform'] == 'windows7-32/opt' or
-            test['test-platform'] == 'windows7-32-pgo/opt') and \
-                test['test-name'] in ['reftest-e10s', 'reftest-no-accel-e10s', 'reftest-gpu-e10s']:
-            test['chunks'] = 32
-
         for this_chunk in range(1, test['chunks'] + 1):
             # copy the test and update with the chunk number
             chunked = copy.deepcopy(test)
             chunked['this-chunk'] = this_chunk
 
             # add the chunk number to the TH symbol
             group, symbol = split_symbol(chunked['treeherder-symbol'])
             symbol += str(this_chunk)
@@ -876,59 +867,45 @@ def single_stylo_traversal_tests(config,
 
 @transforms.add
 def set_worker_type(config, tests):
     """Set the worker type based on the test platform."""
     for test in tests:
         # during the taskcluster migration, this is a bit tortured, but it
         # will get simpler eventually!
         test_platform = test['test-platform']
-        try_options = config.params['try_options'] if config.params['try_options'] else {}
         if test.get('worker-type'):
             # This test already has its worker type defined, so just use that (yields below)
             pass
         elif test_platform.startswith('macosx'):
             test['worker-type'] = MACOSX_WORKER_TYPES['macosx64']
         elif test_platform.startswith('win'):
-            win_worker_type_platform = WINDOWS_WORKER_TYPES[
-                test_platform.split('/')[0]
-            ]
-            if test.get('suite', '') == 'talos' and 'ccov' not in test['build-platform']:
-                if try_options.get('taskcluster_worker'):
-                    test['worker-type'] = win_worker_type_platform['hardware']
-                elif test['virtualization'] == 'virtual':
-                    test['worker-type'] = win_worker_type_platform[test['virtualization']]
-                else:
-                    test['worker-type'] = 'buildbot-bridge/buildbot-bridge'
+            # figure out what platform the job needs to run on
+            if test['virtualization'] == 'hardware':
+                # some jobs like talos and reftest run on real h/w - those are all win10
+                win_worker_type_platform = WINDOWS_WORKER_TYPES['windows10-64']
             else:
-                test['worker-type'] = win_worker_type_platform[test['virtualization']]
+                # the other jobs run on a vm which may or may not be a win10 vm
+                win_worker_type_platform = WINDOWS_WORKER_TYPES[
+                    test_platform.split('/')[0]
+                ]
+            # now we have the right platform set the worker type accordingly
+            test['worker-type'] = win_worker_type_platform[test['virtualization']]
         elif test_platform.startswith('linux') or test_platform.startswith('android'):
             if test.get('suite', '') == 'talos' and test['build-platform'] != 'linux64-ccov/opt':
                 test['worker-type'] = 'releng-hardware/gecko-t-linux-talos'
             else:
                 test['worker-type'] = LINUX_WORKER_TYPES[test['instance-size']]
         else:
             raise Exception("unknown test_platform {}".format(test_platform))
 
         yield test
 
 
 @transforms.add
-def skip_win10_hardware(config, tests):
-    """Windows 10 hardware isn't ready yet, don't even bother scheduling
-    unless we're on try"""
-    for test in tests:
-        if 'releng-hardware/gecko-t-win10-64-hw' not in test['worker-type']:
-            yield test
-        if config.params == 'try':
-            yield test
-        # Silently drop the test on the floor if its win10 hardware and we're not try
-
-
-@transforms.add
 def make_job_description(config, tests):
     """Convert *test* descriptions to *job* descriptions (input to
     taskgraph.transforms.job)"""
 
     for test in tests:
         label = '{}-{}-{}'.format(config.kind, test['test-platform'], test['test-name'])
         if test['chunks'] > 1:
             label += '-{}'.format(test['this-chunk'])
--- a/testing/mozharness/configs/talos/windows_config.py
+++ b/testing/mozharness/configs/talos/windows_config.py
@@ -1,12 +1,13 @@
 import os
 import socket
+import sys
 
-PYTHON = 'c:/mozilla-build/python27/python.exe'
+PYTHON = sys.executable
 PYTHON_DLL = 'c:/mozilla-build/python27/python27.dll'
 VENV_PATH = os.path.join(os.getcwd(), 'build/venv')
 
 config = {
     "log_name": "talos",
     "buildbot_json_path": "buildprops.json",
     "installer_path": "installer.exe",
     "virtualenv_path": VENV_PATH,
@@ -17,17 +18,18 @@ config = {
     ],
     "virtualenv_modules": ['pywin32', 'talos', 'mozinstall'],
     "exes": {
         'python': PYTHON,
         'easy_install': ['%s/scripts/python' % VENV_PATH,
                          '%s/scripts/easy_install-2.7-script.py' % VENV_PATH],
         'mozinstall': ['%s/scripts/python' % VENV_PATH,
                        '%s/scripts/mozinstall-script.py' % VENV_PATH],
-        'hg': 'c:/mozilla-build/hg/hg',
+        'hg': os.path.join(os.environ['PROGRAMFILES'], 'Mercurial', 'hg'),
+        'tooltool.py': [PYTHON, os.path.join(os.environ['MOZILLABUILD'], 'tooltool.py')],
     },
     "title": socket.gethostname().split('.')[0],
     "default_actions": [
         "clobber",
         "read-buildbot-config",
         "download-and-extract",
         "populate-webroot",
         "create-virtualenv",
--- a/testing/mozharness/configs/unittests/win_taskcluster_unittest.py
+++ b/testing/mozharness/configs/unittests/win_taskcluster_unittest.py
@@ -123,16 +123,17 @@ config = {
                 "--appname=%(binary_path)s",
                 "--utility-path=tests/bin",
                 "--extra-profile-file=tests/bin/plugins",
                 "--symbols-path=%(symbols_path)s",
                 "--log-raw=%(raw_log_file)s",
                 "--log-errorsummary=%(error_summary_file)s",
                 "--cleanup-crashes",
                 "--marionette-startup-timeout=180",
+                "--sandbox-read-whitelist=%(abs_work_dir)s",
             ],
             "run_filename": "runreftest.py",
             "testsdir": "reftest"
         },
         "xpcshell": {
             "options": [
                 "--symbols-path=%(symbols_path)s",
                 "--test-plugin-path=%(test_plugin_path)s",
--- a/testing/talos/talos/mitmproxy/mitmproxy_requirements.txt
+++ b/testing/talos/talos/mitmproxy/mitmproxy_requirements.txt
@@ -1,14 +1,15 @@
 argh==0.26.2
 asn1crypto==0.22.0
 blinker==1.4
+pycparser==2.17
+cffi==1.10.0
 brotlipy==0.6.0
 certifi==2017.4.17
-cffi==1.10.0
 click==6.7
 construct==2.8.12
 cryptography==1.8.2
 cssutils==1.0.2
 EditorConfig==0.12.1
 h2==2.6.2
 hpack==3.0.0
 html2text==2016.9.19
@@ -16,17 +17,16 @@ hyperframe==4.0.2
 idna==2.5
 jsbeautifier==1.6.12
 kaitaistruct==0.6
 mitmproxy==2.0.2
 packaging==16.8
 passlib==1.7.1
 pathtools==0.1.2
 pyasn1==0.2.3
-pycparser==2.17
 pyOpenSSL==16.2.0
 pyparsing==2.2.0
 pyperclip==1.5.27
 PyYAML==3.12
 requests==2.13.0
 ruamel.yaml==0.13.14
 six==1.10.0
 sortedcontainers==1.5.7
--- a/testing/talos/talos/startup_test/tresize/addon/bootstrap.js
+++ b/testing/talos/talos/startup_test/tresize/addon/bootstrap.js
@@ -9,43 +9,25 @@
 // Please do not update one withput updating all.
 
 // Reads the chrome.manifest from a legacy non-restartless extension and loads
 // its overlays into the appropriate top-level windows.
 
 ChromeUtils.import("resource://gre/modules/NetUtil.jsm");
 ChromeUtils.import("resource://gre/modules/Services.jsm");
 
-const windowTracker = {
-  init() {
-    Services.ww.registerNotification(this);
-  },
-
-  async observe(window, topic, data) {
-    if (topic === "domwindowopened") {
-      await new Promise(resolve =>
-        window.addEventListener("DOMWindowCreated", resolve, {once: true}));
-
-      let {document} = window;
-      let {documentURI} = document;
-
-      if (documentURI !== "chrome://browser/content/browser.xul") {
-        return;
-      }
-      initializeBrowser(window);
-    }
-  },
-};
-
 function readSync(uri) {
   let channel = NetUtil.newChannel({uri, loadUsingSystemPrincipal: true});
   let buffer = NetUtil.readInputStream(channel.open2());
   return new TextDecoder().decode(buffer);
 }
 
 function startup(data, reason) {
   Services.scriptloader.loadSubScript(data.resourceURI.resolve("content/initialize_browser.js"));
-  windowTracker.init();
+  Services.obs.addObserver(function observer(window) {
+    Services.obs.removeObserver(observer, "browser-delayed-startup-finished");
+    initializeBrowser(window);
+  }, "browser-delayed-startup-finished");
 }
 
 function shutdown(data, reason) {}
 function install(data, reason) {}
 function uninstall(data, reason) {}