Merge autoland to mozilla-central. a=merge
authorCsoregi Natalia <ncsoregi@mozilla.com>
Sat, 05 Oct 2019 00:36:39 +0300
changeset 496408 74c62117e3e5215f69a07e5ba0adddae33773060
parent 496407 e966603209bba385cac292c937cd74d9426a3412 (current diff)
parent 496335 9b47750842e6f8df0d31828f8f5bbdf2aceb9b04 (diff)
child 496409 02bc638506eca7607515df60dd72b9621e92f476
push id114143
push userrgurzau@mozilla.com
push dateMon, 07 Oct 2019 09:35:08 +0000
treeherdermozilla-inbound@3955e0a93047 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone71.0a1
first release with
nightly linux32
74c62117e3e5 / 71.0a1 / 20191004213811 / files
nightly linux64
74c62117e3e5 / 71.0a1 / 20191004213811 / files
nightly mac
74c62117e3e5 / 71.0a1 / 20191004213811 / files
nightly win32
74c62117e3e5 / 71.0a1 / 20191004213811 / files
nightly win64
74c62117e3e5 / 71.0a1 / 20191004213811 / 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 autoland to mozilla-central. a=merge
browser/base/content/fxaDisconnect.js
browser/base/content/fxaDisconnect.xul
browser/components/newtab/content-src/asrouter/templates/StartupOverlay/StartupOverlay.jsx
browser/components/newtab/content-src/asrouter/templates/StartupOverlay/_StartupOverlay.scss
browser/components/newtab/data/content/assets/fox-tail.png
browser/components/newtab/data/content/assets/sync-devices.svg
browser/components/newtab/test/unit/content-src/components/StartupOverlay.test.jsx
browser/locales/en-US/browser/fxaDisconnect.ftl
testing/web-platform/meta/mathml/relations/html5-tree/clipboard-event-handlers.tentative.html.ini
--- a/browser/base/content/browser-sync.js
+++ b/browser/base/content/browser-sync.js
@@ -45,23 +45,16 @@ var gSync = {
     delete this.syncStrings;
     // XXXzpao these strings should probably be moved from /services to /browser... (bug 583381)
     //        but for now just make it work
     return (this.syncStrings = Services.strings.createBundle(
       "chrome://weave/locale/sync.properties"
     ));
   },
 
-  get brandStrings() {
-    delete this.brandStrings;
-    return (this.brandStrings = Services.strings.createBundle(
-      "chrome://branding/locale/brand.properties"
-    ));
-  },
-
   // Returns true if FxA is configured, but the send tab targets list isn't
   // ready yet.
   get sendTabConfiguredAndLoading() {
     return (
       UIState.get().status == UIState.STATUS_SIGNED_IN &&
       !fxAccounts.device.recentDeviceList
     );
   },
@@ -148,16 +141,18 @@ var gSync = {
 
     this._definePrefGetters();
 
     if (!this.FXA_ENABLED) {
       this.onFxaDisabled();
       return;
     }
 
+    MozXULElement.insertFTLIfNeeded("browser/sync.ftl");
+
     this._generateNodeGetters();
 
     // Label for the sync buttons.
     if (!this.appMenuLabel) {
       // We are in a window without our elements - just abort now, without
       // setting this._initialized, so we don't attempt to remove observers.
       return;
     }
@@ -397,18 +392,19 @@ var gSync = {
     }
   },
 
   updateFxAPanel(state = {}) {
     const mainWindowEl = document.documentElement;
 
     // The Firefox Account toolbar currently handles 3 different states for
     // users. The default `not_configured` state shows an empty avatar, `unverified`
-    // state shows an avatar with an email icon and the `verified` state will show
-    // the users custom profile image or a filled avatar.
+    // state shows an avatar with an email icon, `login-failed` state shows an avatar
+    // with a danger icon and the `verified` state will show the users
+    // custom profile image or a filled avatar.
     let stateValue = "not_configured";
 
     const menuHeaderTitleEl = document.getElementById("fxa-menu-header-title");
     const menuHeaderDescriptionEl = document.getElementById(
       "fxa-menu-header-description"
     );
 
     const cadButtonEl = document.getElementById(
@@ -447,17 +443,17 @@ var gSync = {
     fxaMenuAccountButtonEl.classList.remove("subviewbutton-nav");
     fxaMenuAccountButtonEl.removeAttribute("closemenu");
     syncPrefsButtonEl.setAttribute("hidden", true);
     syncSetupButtonEl.removeAttribute("hidden");
 
     if (state.status === UIState.STATUS_NOT_CONFIGURED) {
       mainWindowEl.style.removeProperty("--avatar-image-url");
     } else if (state.status === UIState.STATUS_LOGIN_FAILED) {
-      stateValue = "unverified";
+      stateValue = "login-failed";
       headerTitle = this.fxaStrings.GetStringFromName("account.reconnectToFxA");
       headerDescription = state.email;
       mainWindowEl.style.removeProperty("--avatar-image-url");
     } else if (state.status === UIState.STATUS_NOT_VERIFIED) {
       stateValue = "unverified";
       headerTitle = this.fxaStrings.GetStringFromName(
         "account.finishAccountSetup"
       );
@@ -993,19 +989,17 @@ var gSync = {
       fragment,
       createDeviceNodeFn,
       notVerified,
       actions
     );
   },
 
   _appendSendTabUnconfigured(fragment, createDeviceNodeFn) {
-    const brandProductName = this.brandStrings.GetStringFromName(
-      "brandProductName"
-    );
+    const brandProductName = gBrandBundle.GetStringFromName("brandProductName");
     const notConnected = this.fxaStrings.GetStringFromName(
       "sendTabToDevice.unconfigured.label2"
     );
     const learnMore = this.fxaStrings.GetStringFromName(
       "sendTabToDevice.unconfigured"
     );
     const actions = [
       { label: learnMore, command: () => this.openSendToDevicePromo() },
@@ -1221,36 +1215,60 @@ var gSync = {
       this._onActivityStop();
     }
   },
 
   // Disconnect from sync, and optionally disconnect from the FxA account.
   // Returns true if the disconnection happened (ie, if the user didn't decline
   // when asked to confirm)
   async disconnect({ confirm = true, disconnectAccount = true } = {}) {
-    if (confirm) {
-      let args = { disconnectAccount, confirmed: false };
-      window.openDialog(
-        "chrome://browser/content/fxaDisconnect.xul",
-        "_blank",
-        "chrome,modal,centerscreen,resizable=no",
-        args
-      );
-      if (!args.confirmed) {
-        return false;
-      }
+    if (confirm && !(await this._confirmDisconnect(disconnectAccount))) {
+      return false;
     }
     await Weave.Service.promiseInitialized;
     await Weave.Service.startOver();
     if (disconnectAccount) {
       await fxAccounts.signOut();
     }
     return true;
   },
 
+  /**
+   * Prompts the user whether or not they want to proceed with
+   * disconnecting from their Firefox Account or Sync.
+   * @param {Boolean} disconnectAccount True if we are disconnecting both Sync and FxA.
+   * @returns {Boolean} True if the user confirmed.
+   */
+  async _confirmDisconnect(disconnectAccount) {
+    const l10nPrefix = `${
+      disconnectAccount ? "fxa" : "sync"
+    }-disconnect-dialog`;
+    const [title, body, button] = await document.l10n.formatValues([
+      { id: `${l10nPrefix}-title` },
+      { id: `${l10nPrefix}-body` },
+      { id: "sync-disconnect-dialog-button" },
+    ]);
+    // buttonPressed will be 0 for disconnect, 1 for cancel.
+    const flags =
+      Services.prompt.BUTTON_TITLE_IS_STRING * Services.prompt.BUTTON_POS_0 +
+      Services.prompt.BUTTON_TITLE_CANCEL * Services.prompt.BUTTON_POS_1;
+    const buttonPressed = Services.prompt.confirmEx(
+      window,
+      title,
+      body,
+      flags,
+      button,
+      null,
+      null,
+      null,
+      {}
+    );
+    return buttonPressed == 0;
+  },
+
   // doSync forces a sync - it *does not* return a promise as it is called
   // via the various UI components.
   doSync() {
     if (!UIState.isReady()) {
       return;
     }
     // Note we don't bother checking if sync is actually enabled - none of the
     // UI which calls this function should be visible in that case.
--- a/browser/base/content/browser.xhtml
+++ b/browser/base/content/browser.xhtml
@@ -479,22 +479,20 @@
                 accesskey="&dontShowMessage.accesskey;"
                 type="checkbox"
                 oncommand="gPopupBlockerObserver.dontShowMessage();"/>
       <menuseparator id="blockedPopupsSeparator"/>
     </menupopup>
 
     <menupopup id="autohide-context"
            onpopupshowing="FullScreen.getAutohide(this.firstChild);">
-      <menuitem type="checkbox" label="&fullScreenAutohide.label;"
-                accesskey="&fullScreenAutohide.accesskey;"
+      <menuitem type="checkbox" data-l10n-id="full-screen-autohide"
                 oncommand="FullScreen.setAutohide();"/>
       <menuseparator/>
-      <menuitem label="&fullScreenExit.label;"
-                accesskey="&fullScreenExit.accesskey;"
+      <menuitem data-l10n-id="full-screen-exit"
                 oncommand="BrowserFullScreen();"/>
     </menupopup>
 
     <menupopup id="contentAreaContextMenu" pagemenu="#page-menu-separator"
                onpopupshowing="if (event.target != this)
                                  return true;
                                gContextMenu = new nsContextMenu(this, event.shiftKey);
                                if (gContextMenu.shouldDisplay)
deleted file mode 100644
--- a/browser/base/content/fxaDisconnect.js
+++ /dev/null
@@ -1,15 +0,0 @@
-// This Source Code Form is subject to the terms of the Mozilla Public
-// License, v. 2.0. If a copy of the MPL was not distributed with this
-// file, You can obtain one at http://mozilla.org/MPL/2.0/.
-
-function onLoad() {
-  if (window.arguments[0].disconnectAccount) {
-    document.getElementById("disconnectSync").hidden = true;
-  } else {
-    document.getElementById("disconnectAcct").hidden = true;
-  }
-  document.addEventListener(
-    "dialogaccept",
-    () => (window.arguments[0].confirmed = true)
-  );
-}
deleted file mode 100644
--- a/browser/base/content/fxaDisconnect.xul
+++ /dev/null
@@ -1,29 +0,0 @@
-<?xml version="1.0"?>
-
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
-   - License, v. 2.0. If a copy of the MPL was not distributed with this
-   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<?xml-stylesheet href="chrome://global/skin/"?>
-<?xml-stylesheet href="chrome://browser/skin/preferences/in-content/preferences.css" type="text/css"?>
-
-<dialog id="fxaDisconnectDialog"
-        xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
-        xmlns:html="http://www.w3.org/1999/xhtml"
-        onload="onLoad();"
-        buttons="accept,cancel"
-        data-l10n-id="fxa-disconnect-dialog"
-        data-l10n-attrs="title, style, buttonlabelaccept, buttonaccesskeyaccept">
-
-  <linkset>
-    <html:link rel="localization" href="branding/brand.ftl"/>
-    <html:link rel="localization" href="browser/branding/sync-brand.ftl"/>
-    <html:link rel="localization" href="browser/fxaDisconnect.ftl"/>
-  </linkset>
-  <script src="chrome://browser/content/fxaDisconnect.js"/>
-
-  <vbox id="deleteOptionsContent">
-    <label id="disconnectAcct" data-l10n-id="disconnect-account"/>
-    <label id="disconnectSync" data-l10n-id="disconnect-sync"/>
-  </vbox>
-</dialog>
--- a/browser/base/content/test/sync/browser_sync.js
+++ b/browser/base/content/test/sync/browser_sync.js
@@ -260,17 +260,17 @@ add_task(async function test_ui_state_lo
       "PanelUI-fxa-menu-send-button",
     ],
     disabledItems: ["PanelUI-fxa-menu-connect-device-button"],
     hiddenItems: [
       "PanelUI-fxa-menu-syncnow-button",
       "PanelUI-fxa-menu-sync-prefs-button",
     ],
   });
-  checkFxAAvatar("unverified");
+  checkFxAAvatar("login-failed");
 });
 
 function checkPanelUIStatusBar({ label, fxastatus, syncing }) {
   let labelNode = document.getElementById("appMenu-fxa-label");
   is(labelNode.getAttribute("label"), label, "fxa label has the right value");
 }
 
 function checkRemoteTabsPanel(expectedShownItemId, syncing, syncNowTooltip) {
@@ -381,29 +381,30 @@ async function checkFxABadged() {
   await BrowserTestUtils.waitForCondition(() => {
     return button.querySelector("label.feature-callout");
   });
   const badge = button.querySelector("label.feature-callout");
   ok(badge, "expected feature-callout style badge");
   ok(BrowserTestUtils.is_visible(badge), "expected the badge to be visible");
 }
 
-// fxaStatus is one of 'not_configured', 'unverified', or 'signedin'.
+// fxaStatus is one of 'not_configured', 'unverified', 'login-failed', or 'signedin'.
 function checkFxAAvatar(fxaStatus) {
   const avatarContainers = [
     document.getElementById("fxa-menu-avatar"),
     document.getElementById("fxa-avatar-image"),
   ];
   for (const avatar of avatarContainers) {
     const avatarURL = getComputedStyle(avatar).listStyleImage;
     const expected = {
       not_configured:
         'url("chrome://browser/skin/fxa/avatar-empty-badged.svg")',
       unverified: 'url("chrome://browser/skin/fxa/avatar-confirm.svg")',
       signedin: 'url("chrome://browser/skin/fxa/avatar.svg")',
+      "login-failed": 'url("chrome://browser/skin/fxa/avatar-alert.svg")',
     };
     ok(
       avatarURL == expected[fxaStatus],
       `expected avatar URL to be ${expected[fxaStatus]}, got ${avatarURL}`
     );
   }
 }
 
--- a/browser/base/jar.mn
+++ b/browser/base/jar.mn
@@ -73,18 +73,16 @@ browser.jar:
         content/browser/defaultthemes/3.icon.png      (content/defaultthemes/3.icon.png)
         content/browser/defaultthemes/3.preview.png   (content/defaultthemes/3.preview.png)
         content/browser/defaultthemes/4.header.png    (content/defaultthemes/4.header.png)
         content/browser/defaultthemes/4.icon.png      (content/defaultthemes/4.icon.png)
         content/browser/defaultthemes/4.preview.png   (content/defaultthemes/4.preview.png)
         content/browser/defaultthemes/5.header.png    (content/defaultthemes/5.header.png)
         content/browser/defaultthemes/5.icon.jpg      (content/defaultthemes/5.icon.jpg)
         content/browser/defaultthemes/5.preview.jpg   (content/defaultthemes/5.preview.jpg)
-        content/browser/fxaDisconnect.js              (content/fxaDisconnect.js)
-        content/browser/fxaDisconnect.xul             (content/fxaDisconnect.xul)
         content/browser/history-swipe-arrow.svg       (content/history-swipe-arrow.svg)
 *       content/browser/pageinfo/pageInfo.xul         (content/pageinfo/pageInfo.xul)
         content/browser/pageinfo/pageInfo.js          (content/pageinfo/pageInfo.js)
         content/browser/pageinfo/pageInfo.css         (content/pageinfo/pageInfo.css)
         content/browser/pageinfo/permissions.js       (content/pageinfo/permissions.js)
         content/browser/pageinfo/security.js          (content/pageinfo/security.js)
         content/browser/content-refreshblocker.js     (content/content-refreshblocker.js)
         content/browser/robot.ico                     (content/robot.ico)
--- a/browser/components/customizableui/test/browser.ini
+++ b/browser/components/customizableui/test/browser.ini
@@ -166,17 +166,16 @@ tags = fullscreen
 [browser_reset_builtin_widget_currentArea.js]
 [browser_switch_to_customize_mode.js]
 [browser_synced_tabs_menu.js]
 [browser_backfwd_enabled_post_customize.js]
 [browser_check_tooltips_in_navbar.js]
 [browser_editcontrols_update.js]
 tags = clipboard
 [browser_customization_context_menus.js]
-skip-if = fission # Crashes: @ __poll_nocancel + 0x24
 [browser_newtab_button_customizemode.js]
 [browser_open_from_popup.js]
 [browser_open_in_lazy_tab.js]
 [browser_PanelMultiView_focus.js]
 [browser_PanelMultiView_keyboard.js]
 [browser_reload_tab.js]
 [browser_sidebar_toggle.js]
 skip-if = verify
--- a/browser/extensions/webcompat/data/injections.js
+++ b/browser/extensions/webcompat/data/injections.js
@@ -198,26 +198,30 @@ const AVAILABLE_INJECTIONS = [
       urls: ["https://*/*/tpPdk.js", "https://*/*/pdk/js/*/*.js"],
       types: ["script"],
     },
     customFunc: "pdk5fix",
   },
   {
     id: "bug1577870",
     platform: "desktop",
-    domain: "slideshare.net",
+    domain: "Download prompt for files with no content-type",
     bug: "1577870",
     data: {
-      urls: ["https://*.linkedin.com/tscp-serving/dtag*"],
+      urls: [
+        "https://*.linkedin.com/tscp-serving/dtag*",
+        "https://ads-us.rd.linksynergy.com/as.php*",
+        "https://www.office.com/logout?sid*",
+      ],
       contentType: {
         name: "content-type",
         value: "text/html; charset=utf-8",
       },
     },
-    customFunc: "dtagFix",
+    customFunc: "noSniffFix",
   },
   {
     id: "bug1305028",
     platform: "desktop",
     domain: "gaming.youtube.com",
     bug: "1305028",
     contentScripts: {
       matches: ["*://gaming.youtube.com/*"],
--- a/browser/extensions/webcompat/data/ua_overrides.js
+++ b/browser/extensions/webcompat/data/ua_overrides.js
@@ -142,16 +142,38 @@ const AVAILABLE_UA_OVERRIDES = [
           UAHelpers.getPrefix(originalUA) +
           " AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.142 Safari/537.36"
         );
       },
     },
   },
   {
     /*
+     * Bug 1582582 - sling.com - UA override for sling.com
+     * WebCompat issue #17804 - https://webcompat.com/issues/17804
+     *
+     * sling.com blocks Firefox users showing unsupported browser message.
+     * When spoofing as Chrome playing content works fine
+     */
+    id: "bug1582582",
+    platform: "desktop",
+    domain: "sling.com",
+    bug: "1582582",
+    config: {
+      matches: ["https://watch.sling.com/*", "https://www.sling.com/*"],
+      uaTransformer: originalUA => {
+        return (
+          UAHelpers.getPrefix(originalUA) +
+          " AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.132 Safari/537.36"
+        );
+      },
+    },
+  },
+  {
+    /*
      * Bug 1480710 - m.imgur.com - Build UA override
      * WebCompat issue #13154 - https://webcompat.com/issues/13154
      *
      * imgur returns a 404 for requests to CSS and JS file if requested with a Fennec
      * User Agent. By removing the Fennec identifies and adding Chrome Mobile's, we
      * receive the correct CSS and JS files.
      */
     id: "bug1480710",
--- a/browser/extensions/webcompat/lib/custom_functions.js
+++ b/browser/extensions/webcompat/lib/custom_functions.js
@@ -31,29 +31,29 @@ const replaceStringInRequest = (
     if (carryover.length) {
       filter.write(encoder.encode(carryover));
     }
     filter.close();
   };
 };
 
 const CUSTOM_FUNCTIONS = {
-  dtagFix: injection => {
+  noSniffFix: injection => {
     const { urls, contentType } = injection.data;
     const listener = (injection.data.listener = e => {
       e.responseHeaders.push(contentType);
       return { responseHeaders: e.responseHeaders };
     });
 
     browser.webRequest.onHeadersReceived.addListener(listener, { urls }, [
       "blocking",
       "responseHeaders",
     ]);
   },
-  dtagFixDisable: injection => {
+  noSniffFixDisable: injection => {
     const { listener } = injection.data;
     browser.webRequest.onHeadersReceived.removeListener(listener);
     delete injection.data.listener;
   },
   pdk5fix: injection => {
     const { urls, types } = injection.data;
     const listener = (injection.data.listener = ({ requestId }) => {
       replaceStringInRequest(
--- a/browser/extensions/webcompat/manifest.json
+++ b/browser/extensions/webcompat/manifest.json
@@ -1,13 +1,13 @@
 {
   "manifest_version": 2,
   "name": "Web Compat",
   "description": "Urgent post-release fixes for web compatibility.",
-  "version": "6.1.0",
+  "version": "6.2.0",
 
   "applications": {
     "gecko": {
       "id": "webcompat@mozilla.org",
       "strict_min_version": "59.0b5"
     }
   },
 
--- a/browser/locales/en-US/browser/browser.ftl
+++ b/browser/locales/en-US/browser/browser.ftl
@@ -51,8 +51,17 @@ urlbar-addons-notification-anchor =
 ## Page Action Context Menu
 
 page-action-add-to-urlbar =
     .label = Add to Address Bar
 page-action-manage-extension =
     .label = Manage Extension…
 page-action-remove-from-urlbar =
     .label = Remove from Address Bar
+
+## Auto-hide Context Menu
+
+full-screen-autohide =
+    .label = Hide Toolbars
+    .accesskey = H
+full-screen-exit =
+    .label = Exit Full Screen Mode
+    .accesskey = F
deleted file mode 100644
--- a/browser/locales/en-US/browser/fxaDisconnect.ftl
+++ /dev/null
@@ -1,13 +0,0 @@
-# This Source Code Form is subject to the terms of the Mozilla Public
-# License, v. 2.0. If a copy of the MPL was not distributed with this
-# file, You can obtain one at http://mozilla.org/MPL/2.0/.
-
-fxa-disconnect-dialog =
-    .title = Disconnect { -sync-brand-short-name }?
-    .style = max-width: 400px
-    .buttonlabelaccept = Disconnect
-    .buttonaccesskeyaccept = D
-
-disconnect-account = { -brand-short-name } will disconnect from your account but won’t delete any of your browsing data on this device.
-disconnect-sync = { -brand-short-name } will stop syncing your account but won’t delete any of your browsing data on this device.
-
new file mode 100644
--- /dev/null
+++ b/browser/locales/en-US/browser/sync.ftl
@@ -0,0 +1,9 @@
+# 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/.
+
+sync-disconnect-dialog-title = Disconnect { -sync-brand-short-name }?
+sync-disconnect-dialog-body = { -brand-product-name } will stop syncing your account but won’t delete any of your browsing data on this device.
+fxa-disconnect-dialog-title = Disconnect { -brand-product-name }?
+fxa-disconnect-dialog-body = { -brand-product-name } will disconnect from your account but won’t delete any of your browsing data on this device.
+sync-disconnect-dialog-button = Disconnect
--- a/browser/locales/en-US/chrome/browser/browser.dtd
+++ b/browser/locales/en-US/chrome/browser/browser.dtd
@@ -77,20 +77,16 @@ convenience of Safari and Chrome users o
 <!ENTITY toggleReaderMode.win.keycode "VK_F9">
 
 <!ENTITY togglePictureInPicture.key2 "}">
 <!ENTITY togglePictureInPicture.key "]"> <!-- } is above this key on many keyboards -->
 
 <!ENTITY fullScreenMinimize.tooltip "Minimize">
 <!ENTITY fullScreenRestore.tooltip "Restore">
 <!ENTITY fullScreenClose.tooltip "Close">
-<!ENTITY fullScreenAutohide.label "Hide Toolbars">
-<!ENTITY fullScreenAutohide.accesskey "H">
-<!ENTITY fullScreenExit.label "Exit Full Screen Mode">
-<!ENTITY fullScreenExit.accesskey "F">
 
 <!ENTITY pictureInPictureHideToggle.label "Hide Picture-in-Picture Toggle">
 <!ENTITY pictureInPictureHideToggle.accesskey "H">
 
 <!-- LOCALIZATION NOTE (fxa.menu) Used to define the different labels
      for the Firefox Account toolbar menu screen. The `Signed in as` text is
      followed by the user's email. -->
 <!ENTITY fxa.menu.syncSettings2.label "&syncBrand.shortName.label; Settings">
--- a/browser/modules/HomePage.jsm
+++ b/browser/modules/HomePage.jsm
@@ -232,17 +232,17 @@ let HomePage = {
    *   A string that is the url or urls to be ignored.
    * @returns {boolean}
    *   True if the url should be ignored.
    */
   async shouldIgnore(url) {
     await this.init();
 
     const lowerURL = url.toLowerCase();
-    return this._ignoreList.some(code => lowerURL.includes(code));
+    return this._ignoreList.some(code => lowerURL.includes(code.toLowerCase()));
   },
 
   /**
    * Handles updates of the ignore list, checking the existing preference and
    * correcting it as necessary.
    *
    * @param {Object} eventData
    *   The event data as received from RemoteSettings.
--- a/browser/modules/test/unit/test_HomePage_ignore.js
+++ b/browser/modules/test/unit/test_HomePage_ignore.js
@@ -21,17 +21,17 @@ const HOMEPAGE_IGNORELIST = "homepage-ur
 /**
  * Provides a basic set of remote settings for use in tests.
  */
 async function setupRemoteSettings() {
   const settings = await RemoteSettings("hijack-blocklists");
   sinon.stub(settings, "get").returns([
     {
       id: HOMEPAGE_IGNORELIST,
-      matches: ["ignore=me"],
+      matches: ["ignore=me", "ignoreCASE=ME"],
       _status: "synced",
     },
   ]);
 }
 
 add_task(async function setup() {
   await setupRemoteSettings();
 });
@@ -75,17 +75,17 @@ add_task(async function test_updateIgnor
   // Simulate an ignore list update.
   await RemoteSettings("hijack-blocklists").emit("sync", {
     data: {
       current: [
         {
           id: HOMEPAGE_IGNORELIST,
           schema: 1553857697843,
           last_modified: 1553859483588,
-          matches: ["ignore=me", "new=ignore"],
+          matches: ["ignore=me", "ignoreCASE=ME", "new=ignore"],
         },
       ],
     },
   });
 
   Assert.ok(
     !HomePage.overridden,
     "Should no longer be overriding the homepage."
@@ -99,29 +99,35 @@ add_task(async function test_updateIgnor
     [{ object: "ignore", value: "saved_reset" }],
     {
       category: "homepage",
       method: "preference",
     }
   );
 });
 
-add_task(async function test_setIgnoredUrl() {
+async function testSetIgnoredUrl(url) {
   Assert.ok(!HomePage.overriden, "Should not be overriding the homepage");
 
-  await HomePage.set("http://bad/?ignore=me");
+  await HomePage.set(url);
 
   Assert.equal(
     HomePage.get(),
     HomePage.getDefault(),
     "Should still have the default homepage."
   );
   Assert.ok(!HomePage.overriden, "Should not be overriding the homepage.");
   TelemetryTestUtils.assertEvents(
     [{ object: "ignore", value: "set_blocked" }],
     {
       category: "homepage",
       method: "preference",
     }
   );
+}
+
+add_task(async function test_setIgnoredUrl() {
+  await testSetIgnoredUrl("http://bad/?ignore=me");
 });
 
-// Also need an integration mochitest with an extension (if we can).
+add_task(async function test_setIgnoredUrl_case() {
+  await testSetIgnoredUrl("http://bad/?Ignorecase=me");
+});
--- a/browser/themes/shared/browser.inc.css
+++ b/browser/themes/shared/browser.inc.css
@@ -205,16 +205,17 @@ menupopup::part(drop-indicator) {
 #addon-webext-perm-info {
   margin-inline-start: 0;
 }
 
 /* Contextual Feature Recommendation popup-notification */
 
 #cfr-notification-header {
   width: 100%;
+  display: block;
   text-align: center;
   box-shadow: 0px 1px 0px rgba(0, 0, 0, 0.2);
 }
 
 #cfr-notification-header-stack {
   width: 100%;
 }
 
@@ -244,16 +245,20 @@ menupopup::part(drop-indicator) {
   background-color: hsla(0,0%,70%,.2);
   border-radius: 2px;
 }
 
 #contextual-feature-recommendation-notification {
   width: 343px;
 }
 
+#contextual-feature-recommendation-notification:not([hidden]) {
+  display: block;
+}
+
 #contextual-feature-recommendation-notification[data-notification-bucket="CFR_SOCIAL_TRACKING_PROTECTION"] {
   width: 386px;
 }
 
 #contextual-feature-recommendation-notification .popup-notification-icon {
   margin-inline-end: 4px;
 }
 
--- a/browser/themes/shared/customizableui/panelUI.inc.css
+++ b/browser/themes/shared/customizableui/panelUI.inc.css
@@ -592,31 +592,34 @@ toolbarbutton[constrain-size="true"][cui
 #appMenu-fxa-status[fxastatus="unverified"] > #appMenu-fxa-label,
 #appMenu-fxa-status[fxastatus="login-failed"] > #appMenu-fxa-label {
   list-style-image: url(chrome://browser/skin/warning.svg);
   -moz-image-region: rect(0, 16px, 16px, 0);
 }
 
 #appMenu-fxa-status[fxastatus="login-failed"],
 #appMenu-fxa-status[fxastatus="unverified"],
+:root[fxastatus="login-failed"] .fxa-menu-header,
 :root[fxastatus="unverified"] .fxa-menu-header {
   background-color: @appmenuWarningBackgroundColor@;
   color: @appmenuWarningColor@;
   border-top: 1px solid @appmenuWarningBorderColor@;
   border-bottom: 1px solid @appmenuWarningBorderColor@;
 }
 
 #appMenu-fxa-status[fxastatus="login-failed"]:hover,
 #appMenu-fxa-status[fxastatus="unverified"]:hover,
+:root[fxastatus="login-failed"] .fxa-menu-header:hover,
 :root[fxastatus="unverified"] .fxa-menu-header:hover {
   background-color: @appmenuWarningBackgroundColorHover@;
 }
 
 #appMenu-fxa-status[fxastatus="login-failed"]:hover:active,
 #appMenu-fxa-status[fxastatus="unverified"]:hover:active,
+:root[fxastatus="login-failed"] .fxa-menu-header:hover:active,
 :root[fxastatus="unverified"] .fxa-menu-header:hover:active {
   background-color: @appmenuWarningBackgroundColorActive@;
 }
 
 /* Tracking Protection Button & Toggle */
 
 #appMenu-protection-report-text {
   -moz-context-properties: fill;
@@ -667,29 +670,32 @@ toolbarbutton[constrain-size="true"][cui
 
 :root[lwt-popup-brighttext] .addon-banner-item:hover:active,
 :root[lwt-popup-brighttext] .addon-banner-item:focus:active {
   background: @appmenuWarningBackgroundColorActiveBrightText@;
 }
 
 :root[lwt-popup-brighttext] #appMenu-fxa-status[fxastatus="login-failed"],
 :root[lwt-popup-brighttext] #appMenu-fxa-status[fxastatus="unverified"],
+:root[lwt-popup-brighttext][fxastatus="login-failed"] .fxa-menu-header,
 :root[lwt-popup-brighttext][fxastatus="unverified"] .fxa-menu-header {
   background-color: @appmenuWarningBackgroundColorBrightText@;
   color: @appmenuWarningColorBrightText@;
 }
 
 :root[lwt-popup-brighttext] #appMenu-fxa-status[fxastatus="login-failed"]:hover,
 :root[lwt-popup-brighttext] #appMenu-fxa-status[fxastatus="unverified"]:hover,
+:root[lwt-popup-brighttext][fxastatus="login-failed"] .fxa-menu-header:hover,
 :root[lwt-popup-brighttext][fxastatus="unverified"] .fxa-menu-header:hover {
   background-color: @appmenuWarningBackgroundColorHoverBrightText@;
 }
 
 :root[lwt-popup-brighttext] #appMenu-fxa-status[fxastatus="login-failed"]:hover:active,
 :root[lwt-popup-brighttext] #appMenu-fxa-status[fxastatus="unverified"]:hover:active,
+:root[lwt-popup-brighttext][fxastatus="login-failed"] .fxa-menu-header:hover:active,
 :root[lwt-popup-brighttext][fxastatus="unverified"] .fxa-menu-header:hover:active {
   background-color: @appmenuWarningBackgroundColorActiveBrightText@;
 }
 
 /* Firefox Account Toolbar Panel */
 
 #fxa-avatar-image {
   width: 16px;
@@ -699,16 +705,20 @@ toolbarbutton[constrain-size="true"][cui
 :root {
   --avatar-image-url: url(chrome://browser/skin/fxa/avatar.svg);
 }
 
 :root[fxastatus="unverified"] {
   --avatar-image-url: url(chrome://browser/skin/fxa/avatar-confirm.svg);
 }
 
+:root[fxastatus="login-failed"] {
+  --avatar-image-url: url(chrome://browser/skin/fxa/avatar-alert.svg);
+}
+
 :root[fxastatus="not_configured"] {
   --avatar-image-url: url(chrome://browser/skin/fxa/avatar-empty.svg);
 }
 
 :root[fxastatus="not_configured"][fxa_avatar_badged="badged"] {
   --avatar-image-url: url(chrome://browser/skin/fxa/avatar-empty-badged.svg);
 }
 
new file mode 100644
--- /dev/null
+++ b/browser/themes/shared/fxa/avatar-alert.svg
@@ -0,0 +1,6 @@
+<!-- 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/. -->
+<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16">
+    <path fill="context-fill" d="M15.82 14.13l-3.19-6.41a1.28 1.28 0 0 0-2.3 0l-3.19 6.41A1.29 1.29 0 0 0 8.29 16h6.38a1.29 1.29 0 0 0 1.15-1.87zM11 9.5a0.5 0.5 0 0 1 1 0v2a0.5 0.5 0 0 1-1 0zm0.5 4.69a0.69 0.69 0 1 1 0.69-0.69 0.69 0.69 0 0 1-0.69 0.69zm-7.61-3.38A1 1 0 0 1 4 9.58C5 8.36 6.48 10 8 10h0.12l0.53-1.07A2.82 2.82 0 0 1 8 9a3 3 0 1 1 3-3v0.07A2.33 2.33 0 0 1 11.52 6a2.28 2.28 0 0 1 2.05 1.27l1.21 2.44A6.91 6.91 0 0 0 15 8a7 7 0 1 0-8.95 6.72 2.26 2.26 0 0 1 0.24-1l0.42-0.85a5 5 0 0 1-2.82-2.06z"/>
+</svg>
--- a/browser/themes/shared/jar.inc.mn
+++ b/browser/themes/shared/jar.inc.mn
@@ -139,16 +139,17 @@
 * skin/classic/browser/preferences/in-content/containers.css   (../shared/incontentprefs/containers.css)
 * skin/classic/browser/preferences/containers.css              (../shared/preferences/containers.css)
   skin/classic/browser/fxa/fxa-spinner.svg                     (../shared/fxa/fxa-spinner.svg)
   skin/classic/browser/fxa/sync-illustration.svg               (../shared/fxa/sync-illustration.svg)
   skin/classic/browser/fxa/sync-illustration-issue.svg         (../shared/fxa/sync-illustration-issue.svg)
   skin/classic/browser/fxa/send-to-device.svg                  (../shared/fxa/send-to-device.svg)
 
   skin/classic/browser/fxa/avatar.svg                          (../shared/fxa/avatar.svg)
+  skin/classic/browser/fxa/avatar-alert.svg                    (../shared/fxa/avatar-alert.svg)
   skin/classic/browser/fxa/avatar-color.svg                    (../shared/fxa/avatar-color.svg)
   skin/classic/browser/fxa/avatar-confirm.svg                  (../shared/fxa/avatar-confirm.svg)
   skin/classic/browser/fxa/avatar-empty.svg                    (../shared/fxa/avatar-empty.svg)
   skin/classic/browser/fxa/avatar-empty-badged.svg             (../shared/fxa/avatar-empty-badged.svg)
   skin/classic/browser/fxa/sync-devices.svg                    (../shared/fxa/sync-devices.svg)
   skin/classic/browser/fxa/send.svg                            (../shared/fxa/send.svg)
   skin/classic/browser/fxa/monitor.svg                         (../shared/fxa/monitor.svg)
   skin/classic/browser/fxa/add-device.svg                      (../shared/fxa/add-device.svg)
--- a/browser/themes/shared/toolbarbuttons.inc.css
+++ b/browser/themes/shared/toolbarbuttons.inc.css
@@ -348,16 +348,17 @@ toolbarbutton.bookmark-item {
 .bookmark-item > .toolbarbutton-text {
   display: -moz-box !important;
 }
 
 #PlacesToolbarItems > .bookmark-item > .toolbarbutton-icon {
   margin-inline-end: 0;
 }
 
+#bookmarks-toolbar-placeholder > .toolbarbutton-icon,
 #PlacesToolbarItems > .bookmark-item > .toolbarbutton-icon[label]:not([label=""]) {
   margin-inline-end: 4px;
 }
 
 /* The bookmarks toolbar is smaller than the other toolbars, so we
  * need to override the badge position to not be cut off. */
 #PersonalToolbar .toolbarbutton-badge {
   margin-top: -1px !important;
--- a/devtools/client/debugger/packages/devtools-reps/src/object-inspector/components/ObjectInspectorItem.js
+++ b/devtools/client/debugger/packages/devtools-reps/src/object-inspector/components/ObjectInspectorItem.js
@@ -172,34 +172,23 @@ class ObjectInspectorItem extends Compon
           open: nodeHasFullText(item) && expanded,
         };
       }
 
       if (nodeHasGetter(item)) {
         const targetGrip = getParentGripValue(item);
         const receiverGrip = getNonPrototypeParentGripValue(item);
         if (targetGrip && receiverGrip) {
-          let propertyName = item.name;
-          // If we're dealing with a property that can't be accessed
-          // with the dot notation, for example: x["hello-world"]
-          if (propertyName.startsWith(`"`) && propertyName.endsWith(`"`)) {
-            // We remove the quotes wrapping the property name, and we replace any
-            // "double" escaped quotes (\\\") by simple escaped ones (\").
-            propertyName = propertyName
-              .substring(1, propertyName.length - 1)
-              .replace(/\\\"/g, `\"`);
-          }
-
           Object.assign(repProps, {
             onInvokeGetterButtonClick: () =>
               this.props.invokeGetter(
                 item,
                 targetGrip,
                 receiverGrip.actor,
-                propertyName
+                item.propertyName || item.name
               ),
           });
         }
       }
 
       return {
         label,
         value: Utils.renderRep(item, repProps),
--- a/devtools/client/debugger/packages/devtools-reps/src/object-inspector/tests/utils/__snapshots__/promises.js.snap
+++ b/devtools/client/debugger/packages/devtools-reps/src/object-inspector/tests/utils/__snapshots__/promises.js.snap
@@ -20,16 +20,17 @@ Array [
             "state": "rejected",
           },
           "type": "object",
         },
       },
       "path": "root",
     },
     "path": "root◦<state>",
+    "propertyName": undefined,
     "type": Symbol(<state>),
   },
   Object {
     "contents": Object {
       "value": Object {
         "type": "3",
       },
     },
@@ -47,12 +48,13 @@ Array [
             "state": "rejected",
           },
           "type": "object",
         },
       },
       "path": "root",
     },
     "path": "root◦<reason>",
+    "propertyName": undefined,
     "type": Symbol(<reason>),
   },
 ]
 `;
--- a/devtools/client/debugger/packages/devtools-reps/src/object-inspector/utils/node.js
+++ b/devtools/client/debugger/packages/devtools-reps/src/object-inspector/utils/node.js
@@ -517,16 +517,17 @@ function makeDefaultPropsBucket(
       contents: null,
       type: NODE_TYPES.DEFAULT_PROPERTIES,
     });
 
     const defaultNodes = defaultProperties.map((name, index) =>
       createNode({
         parent: defaultPropertiesNode,
         name: maybeEscapePropertyName(name),
+        propertyName: name,
         path: createPath(index, name),
         contents: ownProperties[name],
       })
     );
     nodes.push(setNodeChildren(defaultPropertiesNode, defaultNodes));
   }
   return nodes;
 }
@@ -535,16 +536,17 @@ function makeNodesForOwnProps(
   propertiesNames: Array<string>,
   parent: Node,
   ownProperties: Object
 ): Array<Node> {
   return propertiesNames.map(name =>
     createNode({
       parent,
       name: maybeEscapePropertyName(name),
+      propertyName: name,
       contents: ownProperties[name],
     })
   );
 }
 
 function makeNodesForProperties(
   objProps: GripProperties,
   parent: Node
@@ -655,20 +657,22 @@ function makeNodeForPrototype(objProps: 
 
 function createNode(options: {
   parent: Node,
   name: string,
   contents: any,
   path?: string,
   type?: Symbol,
   meta?: Object,
+  propertyName?: string,
 }): ?Node {
   const {
     parent,
     name,
+    propertyName,
     path,
     contents,
     type = NODE_TYPES.GRIP,
     meta,
   } = options;
 
   if (contents === undefined) {
     return null;
@@ -676,16 +680,18 @@ function createNode(options: {
 
   // The path is important to uniquely identify the item in the entire
   // tree. This helps debugging & optimizes React's rendering of large
   // lists. The path will be separated by property name.
 
   return {
     parent,
     name,
+    // `name` can be escaped; propertyName contains the original property name.
+    propertyName,
     path: createPath(parent && parent.path, path || name),
     contents,
     type,
     meta,
   };
 }
 
 function createGetterNode({ parent, property, name }) {
--- a/devtools/client/netmonitor/test/browser.ini
+++ b/devtools/client/netmonitor/test/browser.ini
@@ -5,16 +5,17 @@ support-files =
   dropmarker.svg
   file_ws_backend_wsh.py
   head.js
   html_cause-test-page.html
   html_content-type-without-cache-test-page.html
   html_brotli-test-page.html
   html_image-tooltip-test-page.html
   html_cors-test-page.html
+  html_csp-resend-test-page.html
   html_csp-test-page.html
   html_custom-get-page.html
   html_cyrillic-test-page.html
   html_frame-test-page.html
   html_frame-subdocument.html
   html_filter-test-page.html
   html_infinite-get-page.html
   html_json-b64.html
@@ -190,16 +191,17 @@ skip-if = (verify && debug && (os == 'wi
 [browser_net_prefs-reload.js]
 skip-if = os == 'win' # bug 1391264
 [browser_net_raw_headers.js]
 [browser_net_reload-button.js]
 [browser_net_reload-markers.js]
 [browser_net_response-ui-limit.js]
 [browser_net_req-resp-bodies.js]
 [browser_net_resend_cors.js]
+[browser_net_resend_csp.js]
 [browser_net_resend_headers.js]
 [browser_net_resend_xhr.js]
 [browser_net_resend.js]
 [browser_net_security-details.js]
 [browser_net_security-error.js]
 [browser_net_security-icon-click.js]
 [browser_net_security-redirect.js]
 [browser_net_security-state.js]
new file mode 100644
--- /dev/null
+++ b/devtools/client/netmonitor/test/browser_net_resend_csp.js
@@ -0,0 +1,54 @@
+/* Any copyright is dedicated to the Public Domain.
+ *  http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/**
+ * Tests if resending an image request uses the same content type
+ * and hence is not blocked by the CSP of the page.
+ */
+
+add_task(async function() {
+  const { tab, monitor } = await initNetMonitor(CSP_RESEND_URL);
+  const { document, store, windowRequire } = monitor.panelWin;
+  const Actions = windowRequire("devtools/client/netmonitor/src/actions/index");
+  store.dispatch(Actions.batchEnable(false));
+
+  // Executes 1 request
+  await performRequests(monitor, tab, 1);
+
+  // Select the image request
+  const imgRequest = document.querySelectorAll(".request-list-item")[0];
+  EventUtils.sendMouseEvent({ type: "mousedown" }, imgRequest);
+
+  // Stores original request for comparison of values later
+  const { getSelectedRequest } = windowRequire(
+    "devtools/client/netmonitor/src/selectors/index"
+  );
+  const origReq = getSelectedRequest(store.getState());
+
+  // Context Menu > "Resend"
+  EventUtils.sendMouseEvent({ type: "contextmenu" }, imgRequest);
+  getContextMenuItem(monitor, "request-list-context-resend-only").click();
+
+  // Selects request that was resent
+  const selReq = getSelectedRequest(store.getState());
+
+  // Finally, some sanity checks
+  ok(selReq.url.endsWith("test-image.png"), "Correct request selected");
+  ok(origReq.url === selReq.url, "Orig and Sel url match");
+
+  ok(selReq.cause.type === "img", "Correct type of selected");
+  ok(origReq.cause.type === selReq.cause.type, "Orig and Sel type match");
+
+  const cspOBJ = await ContentTask.spawn(tab.linkedBrowser, {}, async () => {
+    return JSON.parse(content.document.cspJSON);
+  });
+
+  const policies = cspOBJ["csp-policies"];
+  is(policies.length, 1, "CSP: should be one policy");
+  const policy = policies[0];
+  is(policy["img-src"], "*", "CSP: img-src should be *");
+
+  return teardown(monitor);
+});
--- a/devtools/client/netmonitor/test/head.js
+++ b/devtools/client/netmonitor/test/head.js
@@ -87,16 +87,17 @@ const SINGLE_GET_URL = EXAMPLE_URL + "ht
 const STATISTICS_URL = EXAMPLE_URL + "html_statistics-test-page.html";
 const CURL_URL = EXAMPLE_URL + "html_copy-as-curl.html";
 const CURL_UTILS_URL = EXAMPLE_URL + "html_curl-utils.html";
 const SEND_BEACON_URL = EXAMPLE_URL + "html_send-beacon.html";
 const CORS_URL = EXAMPLE_URL + "html_cors-test-page.html";
 const PAUSE_URL = EXAMPLE_URL + "html_pause-test-page.html";
 const OPEN_REQUEST_IN_TAB_URL = EXAMPLE_URL + "html_open-request-in-tab.html";
 const CSP_URL = EXAMPLE_URL + "html_csp-test-page.html";
+const CSP_RESEND_URL = EXAMPLE_URL + "html_csp-resend-test-page.html";
 
 const SIMPLE_SJS = EXAMPLE_URL + "sjs_simple-test-server.sjs";
 const SIMPLE_UNSORTED_COOKIES_SJS =
   EXAMPLE_URL + "sjs_simple-unsorted-cookies-test-server.sjs";
 const CONTENT_TYPE_SJS = EXAMPLE_URL + "sjs_content-type-test-server.sjs";
 const WS_CONTENT_TYPE_SJS = WS_HTTP_URL + "sjs_content-type-test-server.sjs";
 const HTTPS_CONTENT_TYPE_SJS =
   HTTPS_EXAMPLE_URL + "sjs_content-type-test-server.sjs";
new file mode 100644
--- /dev/null
+++ b/devtools/client/netmonitor/test/html_csp-resend-test-page.html
@@ -0,0 +1,25 @@
+<!DOCTYPE HTML>
+<html>
+  <head>
+    <meta charset=utf-8>
+    <meta http-equiv="Content-Security-Policy" content="default-src 'none'; img-src *; script-src 'unsafe-inline'">
+    <meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate" />
+    <meta http-equiv="Pragma" content="no-cache" />
+    <meta http-equiv="Expires" content="0" />
+    <title>Tests 'Edit and Resend' requests re-uses the same content-type</title>
+  </head>
+  <body>
+    The image will be allowed to load by the CSP.<br/>
+    <img id="test-img"></img><br/>
+    <script type="text/javascript">
+      /* exported performRequests */
+      "use strict";
+
+      function performRequests() {
+        const testImg = document.getElementById("test-img");
+        testImg.src = "test-image.png";
+      }
+
+    </script>
+  </body>
+</html>
--- a/devtools/client/shared/components/reps/reps.js
+++ b/devtools/client/shared/components/reps/reps.js
@@ -2048,29 +2048,31 @@ function makeDefaultPropsBucket(properti
       parent,
       name: "<default properties>",
       contents: null,
       type: NODE_TYPES.DEFAULT_PROPERTIES
     });
     const defaultNodes = defaultProperties.map((name, index) => createNode({
       parent: defaultPropertiesNode,
       name: maybeEscapePropertyName(name),
+      propertyName: name,
       path: createPath(index, name),
       contents: ownProperties[name]
     }));
     nodes.push(setNodeChildren(defaultPropertiesNode, defaultNodes));
   }
 
   return nodes;
 }
 
 function makeNodesForOwnProps(propertiesNames, parent, ownProperties) {
   return propertiesNames.map(name => createNode({
     parent,
     name: maybeEscapePropertyName(name),
+    propertyName: name,
     contents: ownProperties[name]
   }));
 }
 
 function makeNodesForProperties(objProps, parent) {
   const {
     ownProperties = {},
     ownSymbols,
@@ -2182,32 +2184,35 @@ function makeNodeForPrototype(objProps, 
 
   return null;
 }
 
 function createNode(options) {
   const {
     parent,
     name,
+    propertyName,
     path,
     contents,
     type = NODE_TYPES.GRIP,
     meta
   } = options;
 
   if (contents === undefined) {
     return null;
   } // The path is important to uniquely identify the item in the entire
   // tree. This helps debugging & optimizes React's rendering of large
   // lists. The path will be separated by property name.
 
 
   return {
     parent,
     name,
+    // `name` can be escaped; propertyName contains the original property name.
+    propertyName,
     path: createPath(parent && parent.path, path || name),
     contents,
     type,
     meta
   };
 }
 
 function createGetterNode({
@@ -8257,27 +8262,18 @@ class ObjectInspectorItem extends Compon
         };
       }
 
       if (nodeHasGetter(item)) {
         const targetGrip = getParentGripValue(item);
         const receiverGrip = getNonPrototypeParentGripValue(item);
 
         if (targetGrip && receiverGrip) {
-          let propertyName = item.name; // If we're dealing with a property that can't be accessed
-          // with the dot notation, for example: x["hello-world"]
-
-          if (propertyName.startsWith(`"`) && propertyName.endsWith(`"`)) {
-            // We remove the quotes wrapping the property name, and we replace any
-            // "double" escaped quotes (\\\") by simple escaped ones (\").
-            propertyName = propertyName.substring(1, propertyName.length - 1).replace(/\\\"/g, `\"`);
-          }
-
           Object.assign(repProps, {
-            onInvokeGetterButtonClick: () => this.props.invokeGetter(item, targetGrip, receiverGrip.actor, propertyName)
+            onInvokeGetterButtonClick: () => this.props.invokeGetter(item, targetGrip, receiverGrip.actor, item.propertyName || item.name)
           });
         }
       }
 
       return {
         label,
         value: Utils.renderRep(item, repProps)
       };
--- a/devtools/client/webconsole/test/browser/browser_webconsole_object_inspector_getters.js
+++ b/devtools/client/webconsole/test/browser/browser_webconsole_object_inspector_getters.js
@@ -69,16 +69,19 @@ add_task(async function() {
             return longString;
           },
           get ["hyphen-getter"]() {
             return "---";
           },
           get [`"quoted-getter"`]() {
             return "quoted";
           },
+          get [`"'\``]() {
+            return "quoted2";
+          },
         })
       )
     );
   });
 
   const node = await waitFor(() => findMessage(hud, "oi-test"));
   const oi = node.querySelector(".tree");
 
@@ -95,17 +98,17 @@ add_task(async function() {
   await testTrueGetter(oi);
   await testObjectGetter(oi);
   await testArrayGetter(oi);
   await testMapGetter(oi);
   await testProxyGetter(oi);
   await testThrowingGetter(oi);
   await testLongStringGetter(oi, LONGSTRING);
   await testHypgenGetter(oi);
-  await testQuotedGetter(oi);
+  await testQuotedGetters(oi);
 });
 
 async function testStringGetter(oi) {
   let node = findObjectInspectorNode(oi, "myStringGetter");
   is(
     isObjectInspectorNodeExpandable(node),
     false,
     "The node can't be expanded"
@@ -594,43 +597,57 @@ async function testHypgenGetter(oi) {
   );
   is(
     isObjectInspectorNodeExpandable(node),
     false,
     "The node can't be expanded"
   );
 }
 
-async function testQuotedGetter(oi) {
-  const findQuotedGetterNode = () =>
-    findObjectInspectorNode(oi, `"\\"quoted-getter\\""`);
-  let node = findQuotedGetterNode();
+async function testQuotedGetters(oi) {
+  const nodes = [
+    {
+      name: `"\\"quoted-getter\\""`,
+      expected: `"quoted"`,
+      expandable: false,
+    },
+    {
+      name: `"\\"'\`"`,
+      expected: `"quoted2"`,
+      expandable: false,
+    },
+  ];
 
+  for (const { name, expected, expandable } of nodes) {
+    await testGetter(oi, name, expected, expandable);
+  }
+}
+
+async function testGetter(oi, propertyName, expectedResult, resultExpandable) {
+  info(`Check «${propertyName}» getter`);
+  const findNode = () => findObjectInspectorNode(oi, propertyName);
+
+  let node = findNode();
   is(
     isObjectInspectorNodeExpandable(node),
     false,
-    "The node can't be expanded"
+    `«${propertyName}» can't be expanded`
   );
-  const invokeButton = getObjectInspectorInvokeGetterButton(node);
-  ok(invokeButton, "There is an invoke button as expected");
+  getObjectInspectorInvokeGetterButton(node).click();
+  await waitFor(() => !getObjectInspectorInvokeGetterButton(findNode()));
 
-  invokeButton.click();
-  await waitFor(
-    () => !getObjectInspectorInvokeGetterButton(findQuotedGetterNode())
-  );
-
-  node = findQuotedGetterNode();
+  node = findNode();
   ok(
-    node.textContent.includes(`"\\"quoted-getter\\"": "quoted"`),
-    "Node now has the expected text content"
+    node.textContent.includes(`${propertyName}: ${expectedResult}`),
+    `«${propertyName}» now has the expected text content («${expectedResult}»)`
   );
   is(
     isObjectInspectorNodeExpandable(node),
-    false,
-    "The node can't be expanded"
+    resultExpandable,
+    `«${propertyName}» ${resultExpandable ? "now can" : "can't"} be expanded`
   );
 }
 
 function checkChildren(node, expectedChildren) {
   const children = getObjectInspectorChildrenNodes(node);
   is(
     children.length,
     expectedChildren.length,
--- a/dom/base/test/mochitest.ini
+++ b/dom/base/test/mochitest.ini
@@ -825,17 +825,16 @@ support-files =
 [test_setInterval_uncatchable_exception.html]
 skip-if = debug == false
 [test_settimeout_extra_arguments.html]
 [test_settimeout_inner.html]
 [test_setTimeoutWith0.html]
 [test_setting_opener.html]
 [test_shared_compartment1.html]
 [test_shared_compartment2.html]
-fail-if = fission
 [test_structuredclone_backref.html]
 [test_style_cssText.html]
 [test_text_wholeText.html]
 [test_textnode_normalize_in_selection.html]
 [test_textnode_split_in_selection.html]
 [test_timeout_clamp.html]
 skip-if = debug == true && toolkit == 'android' # Timing dependent, skip slow debug android builds
 [test_timer_flood.html]
--- a/dom/base/test/test_shared_compartment2.html
+++ b/dom/base/test/test_shared_compartment2.html
@@ -11,17 +11,17 @@ https://bugzilla.mozilla.org/show_bug.cg
   <script type="application/javascript">
 
   /** Test for Bug 1530608 **/
   SimpleTest.waitForExplicitFinish();
 
   // We have the following origins:
   //
   // 1: this page:    mochi.test:8888
-  // 2: iframe:       example.org
+  // 2: iframe:       test1.mochi.test:8888
   // 3: inner iframe: mochi.test:8888
   //
   // Test that 1 and 2 are cross-compartment (because cross-origin), but 1 and 3
   // are same-compartment.
 
   function go(innerWin) {
     var Cu = SpecialPowers.Cu;
     var isSameCompartment = Cu.getJSTestingFunctions().isSameCompartment;
@@ -36,12 +36,12 @@ https://bugzilla.mozilla.org/show_bug.cg
     SimpleTest.finish();
   }
 
   </script>
 </head>
 <body>
 <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1530608">Mozilla Bug 1530608</a>
 
-<iframe id="frame" src="http://example.org/tests/dom/base/test/iframe_shared_compartment2a.html"></iframe>
+<iframe id="frame" src="http://test1.mochi.test:8888/tests/dom/base/test/iframe_shared_compartment2a.html"></iframe>
 
 </body>
 </html>
--- a/gfx/cairo/cairo/src/cairo-truetype-subset.c
+++ b/gfx/cairo/cairo/src/cairo-truetype-subset.c
@@ -1165,17 +1165,17 @@ static cairo_int_status_t
 _cairo_truetype_reverse_cmap (cairo_scaled_font_t *scaled_font,
 			      unsigned long        table_offset,
 			      unsigned long        index,
 			      uint32_t            *ucs4)
 {
     cairo_status_t status;
     const cairo_scaled_font_backend_t *backend;
     tt_segment_map_t *map;
-    char buf[4];
+    tt_segment_map_t buf;
     unsigned int num_segments, i;
     unsigned long size;
     uint16_t *start_code;
     uint16_t *end_code;
     uint16_t *delta;
     uint16_t *range_offset;
     uint16_t *glyph_array;
     uint16_t  c;
@@ -1185,17 +1185,17 @@ static cairo_int_status_t
     status = backend->load_truetype_table (scaled_font,
                                            TT_TAG_cmap, table_offset,
 					   (unsigned char *) &buf,
 					   &size);
     if (unlikely (status))
 	return status;
 
     /* All table formats have the same first two words */
-    map = (tt_segment_map_t *) buf;
+    map = &buf;
     if (be16_to_cpu (map->format) != 4)
 	return CAIRO_INT_STATUS_UNSUPPORTED;
 
     size = be16_to_cpu (map->length);
     map = malloc (size);
     if (unlikely (map == NULL))
 	return _cairo_error (CAIRO_STATUS_NO_MEMORY);
 
--- a/js/src/gc/Zone.cpp
+++ b/js/src/gc/Zone.cpp
@@ -372,16 +372,20 @@ void Zone::discardJitCode(JSFreeOp* fop,
 
     // Try to release the script's JitScript. This should happen after
     // releasing JIT code because we can't do this when the script still has
     // JIT code.
     if (discardJitScripts) {
       script->maybeReleaseJitScript(fop);
       jitScript = script->maybeJitScript();
       if (!jitScript) {
+        // Try to discard the ScriptCounts too.
+        if (!script->realm()->collectCoverageForDebug()) {
+          script->destroyScriptCounts();
+        }
         continue;
       }
     }
 
     // If we did not release the JitScript, we need to purge optimized IC
     // stubs because the optimizedStubSpace will be purged below.
     if (discardBaselineCode) {
       jitScript->purgeOptimizedStubs(script);
--- a/js/src/jit-test/tests/wasm/binary.js
+++ b/js/src/jit-test/tests/wasm/binary.js
@@ -225,20 +225,29 @@ assertNoWarning(() => wasmEval(moduleWit
 assertErrorMessage(() => wasmEval(moduleWithSections([
     v2vSigSection,
     declSection([0]),
     exportSection([{funcIndex: 0, name: "f"}]),
     bodySection([funcBody({locals:[], body:[UnreachableCode]})]),
     nameSection([moduleNameSubsection('hi')])])
 ).f(), RuntimeError, /unreachable/);
 
-// Diagnose nonstandard block signature types.
-for (var bad of [0xff, 0, 1, 0x3f])
+// Diagnose invalid block signature types.
+for (var bad of [0xff, 1, 0x3f])
     assertErrorMessage(() => wasmEval(moduleWithSections([sigSection([v2vSig]), declSection([0]), bodySection([funcBody({locals:[], body:[BlockCode, bad, EndCode]})])])), CompileError, /invalid .*block type/);
 
+if (wasmMultiValueEnabled()) {
+    // In this test module, 0 denotes a void-to-void block type.
+    let binary = moduleWithSections([sigSection([v2vSig]), declSection([0]), bodySection([funcBody({locals:[], body:[BlockCode, 0, EndCode]})])]);
+    assertEq(WebAssembly.validate(binary), true);
+} else {
+    const bad = 0;
+    assertErrorMessage(() => wasmEval(moduleWithSections([sigSection([v2vSig]), declSection([0]), bodySection([funcBody({locals:[], body:[BlockCode, bad, EndCode]})])])), CompileError, /invalid .*block type/);
+}
+
 // Ensure all invalid opcodes rejected
 for (let op of undefinedOpcodes) {
     let binary = moduleWithSections([v2vSigSection, declSection([0]), bodySection([funcBody({locals:[], body:[op]})])]);
     assertErrorMessage(() => wasmEval(binary), CompileError, /unrecognized opcode/);
     assertEq(WebAssembly.validate(binary), false);
 }
 
 // Prefixed opcodes
--- a/js/src/jit-test/tests/wasm/gc/binary.js
+++ b/js/src/jit-test/tests/wasm/gc/binary.js
@@ -13,17 +13,17 @@ function checkInvalid(body, errorMessage
 }
 
 const invalidRefBlockType = funcBody({locals:[], body:[
     BlockCode,
     RefCode,
     0x42,
     EndCode,
 ]});
-checkInvalid(invalidRefBlockType, /invalid inline block type/);
+checkInvalid(invalidRefBlockType, /ref/);
 
 const invalidTooBigRefType = funcBody({locals:[], body:[
     BlockCode,
     RefCode,
     varU32(1000000),
     EndCode,
 ]});
-checkInvalid(invalidTooBigRefType, /invalid inline block type/);
+checkInvalid(invalidTooBigRefType, /ref/);
--- a/js/src/jit-test/tests/wasm/regress/misc-control-flow.js
+++ b/js/src/jit-test/tests/wasm/regress/misc-control-flow.js
@@ -197,27 +197,27 @@ wasmEvalText(`
 wasmFailValidateText(`
 (module
     (func (result i32)
       (loop
         (i32.const 0)
         (br_table 1 0 (i32.const 15))
       )
     )
-)`, /br_table operand must be subtype of all target types/);
+)`, /br_table targets must all have the same arity/);
 
 wasmFailValidateText(`
 (module
   (func (result i32)
     (loop i32
       (i32.const 0)
       (br_table 1 0 (i32.const 15))
     )
   )
-)`, /br_table operand must be subtype of all target types/);
+)`, /br_table targets must all have the same arity/);
 
 wasmValidateText(`
 (module
     (func
         (loop
           (i32.const 0)
           (br_table 1 0 (i32.const 15))
         )
--- a/js/src/jit/IonCode.h
+++ b/js/src/jit/IonCode.h
@@ -593,21 +593,20 @@ struct IonScriptCounts {
 
   void setPrevious(IonScriptCounts* previous) { previous_ = previous; }
 
   IonScriptCounts* previous() const { return previous_; }
 
   size_t sizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf) const {
     size_t size = 0;
     auto currCounts = this;
-    while (currCounts) {
-      const IonScriptCounts* currCount = currCounts;
-      currCounts = currCount->previous_;
-      size += currCount->sizeOfOneIncludingThis(mallocSizeOf);
-    }
+    do {
+      size += currCounts->sizeOfOneIncludingThis(mallocSizeOf);
+      currCounts = currCounts->previous_;
+    } while (currCounts);
     return size;
   }
 
   size_t sizeOfOneIncludingThis(mozilla::MallocSizeOf mallocSizeOf) const {
     size_t size = mallocSizeOf(this) + mallocSizeOf(blocks_);
     for (size_t i = 0; i < numBlocks_; i++) {
       blocks_[i].sizeOfExcludingThis(mallocSizeOf);
     }
--- a/js/src/vm/JSScript.cpp
+++ b/js/src/vm/JSScript.cpp
@@ -1494,19 +1494,23 @@ js::PCCounts* ScriptCounts::getThrowCoun
       std::lower_bound(throwCounts_.begin(), throwCounts_.end(), searched);
   if (elem == throwCounts_.end() || elem->pcOffset() != offset) {
     elem = throwCounts_.insert(elem, searched);
   }
   return elem;
 }
 
 size_t ScriptCounts::sizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf) {
-  return mallocSizeOf(this) + pcCounts_.sizeOfExcludingThis(mallocSizeOf) +
-         throwCounts_.sizeOfExcludingThis(mallocSizeOf) +
-         ionCounts_->sizeOfIncludingThis(mallocSizeOf);
+  size_t size = mallocSizeOf(this);
+  size += pcCounts_.sizeOfExcludingThis(mallocSizeOf);
+  size += throwCounts_.sizeOfExcludingThis(mallocSizeOf);
+  if (ionCounts_) {
+    size += ionCounts_->sizeOfIncludingThis(mallocSizeOf);
+  }
+  return size;
 }
 
 js::PCCounts* JSScript::maybeGetPCCounts(jsbytecode* pc) {
   MOZ_ASSERT(containsPC(pc));
   return getScriptCounts().maybeGetPCCounts(pcToOffset(pc));
 }
 
 const js::PCCounts* JSScript::maybeGetThrowCounts(jsbytecode* pc) {
--- a/js/src/wasm/WasmBaselineCompile.cpp
+++ b/js/src/wasm/WasmBaselineCompile.cpp
@@ -2436,21 +2436,31 @@ class BaseCompiler final : public BaseCo
         : stackHeight(StackHeight::Invalid()),
           stackSize(UINT32_MAX),
           bceSafeOnEntry(0),
           bceSafeOnExit(~BCESet(0)),
           deadOnArrival(false),
           deadThenBranch(false) {}
   };
 
+  class NothingVector {
+    Nothing unused_;
+
+   public:
+    bool resize(size_t length) { return true; }
+    Nothing& operator[](size_t) { return unused_; }
+    Nothing& back() { return unused_; }
+  };
+
   struct BaseCompilePolicy {
     // The baseline compiler tracks values on a stack of its own -- it
     // needs to scan that stack for spilling -- and thus has no need
     // for the values maintained by the iterator.
     using Value = Nothing;
+    using ValueVector = NothingVector;
 
     // The baseline compiler uses the iterator's control stack, attaching
     // its own control information.
     using ControlItem = Control;
   };
 
   using BaseOpIter = OpIter<BaseCompilePolicy>;
 
@@ -2772,99 +2782,115 @@ class BaseCompiler final : public BaseCo
   }
 
   void moveF32(RegF32 src, RegF32 dest) {
     if (src != dest) {
       masm.moveFloat32(src, dest);
     }
   }
 
-  void maybeReserveJoinRegI(ExprType type) {
-    switch (type.code()) {
-      case ExprType::I32:
-        needI32(joinRegI32_);
-        break;
-      case ExprType::I64:
-        needI64(joinRegI64_);
-        break;
-      case ExprType::FuncRef:
-      case ExprType::AnyRef:
-      case ExprType::NullRef:
-      case ExprType::Ref:
-        needRef(joinRegPtr_);
-        break;
-      default:;
-    }
-  }
-
-  void maybeUnreserveJoinRegI(ExprType type) {
-    switch (type.code()) {
-      case ExprType::I32:
-        freeI32(joinRegI32_);
-        break;
-      case ExprType::I64:
-        freeI64(joinRegI64_);
-        break;
-      case ExprType::FuncRef:
-      case ExprType::AnyRef:
-      case ExprType::NullRef:
-      case ExprType::Ref:
-        freeRef(joinRegPtr_);
-        break;
-      default:;
-    }
-  }
-
-  void maybeReserveJoinReg(ExprType type) {
-    switch (type.code()) {
-      case ExprType::I32:
+  void maybeReserveJoinRegI(ResultType type) {
+    if (type.empty()) {
+      return;
+    }
+    MOZ_ASSERT(type.length() == 1, "multi-value joins unimplemented");
+    switch (type[0].code()) {
+      case ValType::I32:
         needI32(joinRegI32_);
         break;
-      case ExprType::I64:
+      case ValType::I64:
         needI64(joinRegI64_);
         break;
-      case ExprType::F32:
-        needF32(joinRegF32_);
-        break;
-      case ExprType::F64:
-        needF64(joinRegF64_);
-        break;
-      case ExprType::Ref:
-      case ExprType::NullRef:
-      case ExprType::FuncRef:
-      case ExprType::AnyRef:
+      case ValType::F32:
+      case ValType::F64:
+        break;
+      case ValType::FuncRef:
+      case ValType::AnyRef:
+      case ValType::NullRef:
+      case ValType::Ref:
         needRef(joinRegPtr_);
         break;
-      default:
-        break;
-    }
-  }
-
-  void maybeUnreserveJoinReg(ExprType type) {
-    switch (type.code()) {
-      case ExprType::I32:
+    }
+  }
+
+  void maybeUnreserveJoinRegI(ResultType type) {
+    if (type.empty()) {
+      return;
+    }
+    MOZ_ASSERT(type.length() == 1, "multi-value joins unimplemented");
+    switch (type[0].code()) {
+      case ValType::I32:
         freeI32(joinRegI32_);
         break;
-      case ExprType::I64:
+      case ValType::I64:
         freeI64(joinRegI64_);
         break;
-      case ExprType::F32:
+      case ValType::F32:
+      case ValType::F64:
+        break;
+      case ValType::FuncRef:
+      case ValType::AnyRef:
+      case ValType::NullRef:
+      case ValType::Ref:
+        freeRef(joinRegPtr_);
+        break;
+    }
+  }
+
+  void maybeReserveJoinReg(ResultType type) {
+    if (type.empty()) {
+      return;
+    }
+    MOZ_ASSERT(type.length() == 1, "multi-value joins unimplemented");
+    switch (type[0].code()) {
+      case ValType::I32:
+        needI32(joinRegI32_);
+        break;
+      case ValType::I64:
+        needI64(joinRegI64_);
+        break;
+      case ValType::F32:
+        needF32(joinRegF32_);
+        break;
+      case ValType::F64:
+        needF64(joinRegF64_);
+        break;
+      case ValType::Ref:
+      case ValType::NullRef:
+      case ValType::FuncRef:
+      case ValType::AnyRef:
+        needRef(joinRegPtr_);
+        break;
+    }
+  }
+
+  void maybeUnreserveJoinReg(ResultType type) {
+    if (type.empty()) {
+      return;
+    }
+    MOZ_ASSERT(type.length() == 1, "multi-value joins unimplemented");
+    switch (type[0].code()) {
+      case ValType::I32:
+        freeI32(joinRegI32_);
+        break;
+      case ValType::I64:
+        freeI64(joinRegI64_);
+        break;
+      case ValType::F32:
         freeF32(joinRegF32_);
         break;
-      case ExprType::F64:
+      case ValType::F64:
         freeF64(joinRegF64_);
         break;
-      case ExprType::Ref:
-      case ExprType::NullRef:
-      case ExprType::FuncRef:
-      case ExprType::AnyRef:
+      case ValType::Ref:
+      case ValType::NullRef:
+      case ValType::FuncRef:
+      case ValType::AnyRef:
         freeRef(joinRegPtr_);
         break;
-      default:
-        break;
     }
   }
 
   ////////////////////////////////////////////////////////////
   //
   // Value stack and spilling.
   //
   // The value stack facilitates some on-the-fly register allocation
@@ -3748,96 +3774,96 @@ class BaseCompiler final : public BaseCo
   // On the other hand, we sync() before every block and only the
   // JoinReg is live out of the block.  But on the way out, we
   // currently pop the JoinReg before freeing regs to be discarded,
   // so there is a real risk of some pointless shuffling there.  If
   // we instead integrate the popping of the join reg into the
   // popping of the stack we can just use the JoinReg as it will
   // become available in that process.
 
-  MOZ_MUST_USE Maybe<AnyReg> popJoinRegUnlessVoid(ExprType type) {
-    switch (type.code()) {
-      case ExprType::Void: {
-        return Nothing();
-      }
-      case ExprType::I32: {
+  MOZ_MUST_USE Maybe<AnyReg> popJoinRegUnlessVoid(ResultType type) {
+    if (type.empty()) {
+      return Nothing();
+    }
+    MOZ_ASSERT(type.length() == 1, "multi-value return unimplemented");
+    switch (type[0].code()) {
+      case ValType::I32: {
         DebugOnly<Stk::Kind> k(stk_.back().kind());
         MOZ_ASSERT(k == Stk::RegisterI32 || k == Stk::ConstI32 ||
                    k == Stk::MemI32 || k == Stk::LocalI32);
         return Some(AnyReg(popI32(joinRegI32_)));
       }
-      case ExprType::I64: {
+      case ValType::I64: {
         DebugOnly<Stk::Kind> k(stk_.back().kind());
         MOZ_ASSERT(k == Stk::RegisterI64 || k == Stk::ConstI64 ||
                    k == Stk::MemI64 || k == Stk::LocalI64);
         return Some(AnyReg(popI64(joinRegI64_)));
       }
-      case ExprType::F64: {
+      case ValType::F64: {
         DebugOnly<Stk::Kind> k(stk_.back().kind());
         MOZ_ASSERT(k == Stk::RegisterF64 || k == Stk::ConstF64 ||
                    k == Stk::MemF64 || k == Stk::LocalF64);
         return Some(AnyReg(popF64(joinRegF64_)));
       }
-      case ExprType::F32: {
+      case ValType::F32: {
         DebugOnly<Stk::Kind> k(stk_.back().kind());
         MOZ_ASSERT(k == Stk::RegisterF32 || k == Stk::ConstF32 ||
                    k == Stk::MemF32 || k == Stk::LocalF32);
         return Some(AnyReg(popF32(joinRegF32_)));
       }
-      case ExprType::Ref:
-      case ExprType::NullRef:
-      case ExprType::FuncRef:
-      case ExprType::AnyRef: {
+      case ValType::Ref:
+      case ValType::NullRef:
+      case ValType::FuncRef:
+      case ValType::AnyRef: {
         DebugOnly<Stk::Kind> k(stk_.back().kind());
         MOZ_ASSERT(k == Stk::RegisterRef || k == Stk::ConstRef ||
                    k == Stk::MemRef || k == Stk::LocalRef);
         return Some(AnyReg(popRef(joinRegPtr_)));
       }
-      default: {
-        MOZ_CRASH("Compiler bug: unexpected expression type");
-      }
-    }
+    }
+    MOZ_CRASH("Compiler bug: unexpected expression type");
   }
 
   // If we ever start not sync-ing on entry to Block (but instead try to sync
   // lazily) then this may start asserting because it does not spill the
   // joinreg if the joinreg is already allocated.  Note, it *can't* spill the
   // joinreg in the contexts it's being used, so some other solution will need
   // to be found.
 
-  MOZ_MUST_USE Maybe<AnyReg> captureJoinRegUnlessVoid(ExprType type) {
-    switch (type.code()) {
-      case ExprType::I32:
+  MOZ_MUST_USE Maybe<AnyReg> captureJoinRegUnlessVoid(ResultType type) {
+    if (type.empty()) {
+      return Nothing();
+    }
+    MOZ_ASSERT(type.length() == 1, "multi-value return unimplemented");
+    switch (type[0].code()) {
+      case ValType::I32:
         MOZ_ASSERT(isAvailableI32(joinRegI32_));
         needI32(joinRegI32_);
         return Some(AnyReg(joinRegI32_));
-      case ExprType::I64:
+      case ValType::I64:
         MOZ_ASSERT(isAvailableI64(joinRegI64_));
         needI64(joinRegI64_);
         return Some(AnyReg(joinRegI64_));
-      case ExprType::F32:
+      case ValType::F32:
         MOZ_ASSERT(isAvailableF32(joinRegF32_));
         needF32(joinRegF32_);
         return Some(AnyReg(joinRegF32_));
-      case ExprType::F64:
+      case ValType::F64:
         MOZ_ASSERT(isAvailableF64(joinRegF64_));
         needF64(joinRegF64_);
         return Some(AnyReg(joinRegF64_));
-      case ExprType::Ref:
-      case ExprType::NullRef:
-      case ExprType::FuncRef:
-      case ExprType::AnyRef:
+      case ValType::Ref:
+      case ValType::NullRef:
+      case ValType::FuncRef:
+      case ValType::AnyRef:
         MOZ_ASSERT(isAvailableRef(joinRegPtr_));
         needRef(joinRegPtr_);
         return Some(AnyReg(joinRegPtr_));
-      case ExprType::Void:
-        return Nothing();
-      default:
-        MOZ_CRASH("Compiler bug: unexpected type");
-    }
+    }
+    MOZ_CRASH("Compiler bug: unexpected type");
   }
 
   void pushJoinRegUnlessVoid(const Maybe<AnyReg>& r) {
     if (!r) {
       return;
     }
     switch (r->tag) {
       case AnyReg::I32:
@@ -6566,33 +6592,32 @@ class BaseCompiler final : public BaseCo
         RegF64 rhs;
       } f64;
     };
 
     Label* const label;             // The target of the branch, never NULL
     const StackHeight stackHeight;  // The value to pop to along the taken edge,
                                     // unless !hasPop()
     const bool invertBranch;        // If true, invert the sense of the branch
-    const ExprType
-        resultType;  // The result propagated along the edges, or Void
+    const ResultType resultType;    // The result propagated along the edges
 
     explicit BranchState(Label* label)
         : label(label),
           stackHeight(StackHeight::Invalid()),
           invertBranch(false),
-          resultType(ExprType::Void) {}
+          resultType(ResultType::Empty()) {}
 
     BranchState(Label* label, bool invertBranch)
         : label(label),
           stackHeight(StackHeight::Invalid()),
           invertBranch(invertBranch),
-          resultType(ExprType::Void) {}
+          resultType(ResultType::Empty()) {}
 
     BranchState(Label* label, StackHeight stackHeight, bool invertBranch,
-                ExprType resultType)
+                ResultType resultType)
         : label(label),
           stackHeight(stackHeight),
           invertBranch(invertBranch),
           resultType(resultType) {}
 
     bool hasPop() const { return stackHeight.isValid(); }
   };
 
@@ -6714,19 +6739,20 @@ class BaseCompiler final : public BaseCo
   MOZ_MUST_USE bool loadCommon(MemoryAccessDesc* access, ValType type);
   MOZ_MUST_USE bool emitStore(ValType resultType, Scalar::Type viewType);
   MOZ_MUST_USE bool storeCommon(MemoryAccessDesc* access, ValType resultType);
   MOZ_MUST_USE bool emitSelect(bool typed);
 
   template <bool isSetLocal>
   MOZ_MUST_USE bool emitSetOrTeeLocal(uint32_t slot);
 
-  void endBlock(ExprType type);
+  void endBlock(ResultType type);
+  void endLoop(ResultType type);
   void endIfThen();
-  void endIfThenElse(ExprType type);
+  void endIfThenElse(ResultType type);
 
   void doReturn(bool popStack);
   void pushReturnValueOfCall(const FunctionCall& call, ValType type);
   void pushReturnValueOfCall(const FunctionCall& call, MIRType type);
 
   void emitCompareI32(Assembler::Condition compareOp, ValType compareType);
   void emitCompareI64(Assembler::Condition compareOp, ValType compareType);
   void emitCompareF32(Assembler::DoubleCondition compareOp,
@@ -8129,17 +8155,17 @@ bool BaseCompiler::emitBlock() {
     sync();  // Simplifies branching out from block
   }
 
   initControl(controlItem());
 
   return true;
 }
 
-void BaseCompiler::endBlock(ExprType type) {
+void BaseCompiler::endBlock(ResultType type) {
   Control& block = controlItem();
 
   // Save the value.
   Maybe<AnyReg> r;
   if (!deadCode_) {
     r = popJoinRegUnlessVoid(type);
     block.bceSafeOnExit &= bceSafe_;
   }
@@ -8246,20 +8272,20 @@ void BaseCompiler::endIfThen() {
   }
 
   deadCode_ = ifThen.deadOnArrival;
 
   bceSafe_ = ifThen.bceSafeOnExit & ifThen.bceSafeOnEntry;
 }
 
 bool BaseCompiler::emitElse() {
-  ExprType thenType;
-  Nothing unused_thenValue;
-
-  if (!iter_.readElse(&thenType, &unused_thenValue)) {
+  ResultType thenType;
+  NothingVector unused_thenValues;
+
+  if (!iter_.readElse(&thenType, &unused_thenValues)) {
     return false;
   }
 
   Control& ifThenElse = controlItem(0);
 
   // See comment in endIfThenElse, below.
 
   // Exit the "then" branch.
@@ -8290,17 +8316,17 @@ bool BaseCompiler::emitElse() {
   }
 
   deadCode_ = ifThenElse.deadOnArrival;
   bceSafe_ = ifThenElse.bceSafeOnEntry;
 
   return true;
 }
 
-void BaseCompiler::endIfThenElse(ExprType type) {
+void BaseCompiler::endIfThenElse(ResultType type) {
   Control& ifThenElse = controlItem();
 
   // The expression type is not a reliable guide to what we'll find
   // on the stack, we could have (if E (i32.const 1) (unreachable))
   // in which case the "else" arm is AnyType but the type of the
   // full expression is I32.  So restore whatever's there, not what
   // we want to find there.  The "then" arm has the same constraint.
 
@@ -8334,19 +8360,19 @@ void BaseCompiler::endIfThenElse(ExprTyp
 
   if (!deadCode_) {
     pushJoinRegUnlessVoid(r);
   }
 }
 
 bool BaseCompiler::emitEnd() {
   LabelKind kind;
-  ExprType type;
-  Nothing unused_value;
-  if (!iter_.readEnd(&kind, &type, &unused_value)) {
+  ResultType type;
+  NothingVector unused_values;
+  if (!iter_.readEnd(&kind, &type, &unused_values)) {
     return false;
   }
 
   switch (kind) {
     case LabelKind::Body:
       endBlock(type);
       iter_.popEnd();
       MOZ_ASSERT(iter_.controlStackEmpty());
@@ -8369,19 +8395,19 @@ bool BaseCompiler::emitEnd() {
 
   iter_.popEnd();
 
   return true;
 }
 
 bool BaseCompiler::emitBr() {
   uint32_t relativeDepth;
-  ExprType type;
-  Nothing unused_value;
-  if (!iter_.readBr(&relativeDepth, &type, &unused_value)) {
+  ResultType type;
+  NothingVector unused_values;
+  if (!iter_.readBr(&relativeDepth, &type, &unused_values)) {
     return false;
   }
 
   if (deadCode_) {
     return true;
   }
 
   Control& target = controlItem(relativeDepth);
@@ -8402,19 +8428,20 @@ bool BaseCompiler::emitBr() {
 
   deadCode_ = true;
 
   return true;
 }
 
 bool BaseCompiler::emitBrIf() {
   uint32_t relativeDepth;
-  ExprType type;
-  Nothing unused_value, unused_condition;
-  if (!iter_.readBrIf(&relativeDepth, &type, &unused_value,
+  ResultType type;
+  NothingVector unused_values;
+  Nothing unused_condition;
+  if (!iter_.readBrIf(&relativeDepth, &type, &unused_values,
                       &unused_condition)) {
     return false;
   }
 
   if (deadCode_) {
     resetLatentOp();
     return true;
   }
@@ -8427,36 +8454,43 @@ bool BaseCompiler::emitBrIf() {
   emitBranchPerform(&b);
 
   return true;
 }
 
 bool BaseCompiler::emitBrTable() {
   Uint32Vector depths;
   uint32_t defaultDepth;
-  ExprType branchValueType;
-  Nothing unused_value, unused_index;
-  if (!iter_.readBrTable(&depths, &defaultDepth, &branchValueType,
-                         &unused_value, &unused_index)) {
+  ResultType type;
+  NothingVector unused_values;
+  Nothing unused_index;
+  // N.B., `type' gets set to the type of the default branch target.  In the
+  // presence of subtyping, it could be that the different branch targets have
+  // different types.  Here we rely on the assumption that the value
+  // representations (e.g. Stk value types) of all branch target types are the
+  // same, in the baseline compiler.  Notably, this means that all Ref types
+  // should be represented the same.
+  if (!iter_.readBrTable(&depths, &defaultDepth, &type, &unused_values,
+                         &unused_index)) {
     return false;
   }
 
   if (deadCode_) {
     return true;
   }
 
   // Don't use joinReg for rc
-  maybeReserveJoinRegI(branchValueType);
+  maybeReserveJoinRegI(type);
 
   // Table switch value always on top.
   RegI32 rc = popI32();
 
-  maybeUnreserveJoinRegI(branchValueType);
-
-  Maybe<AnyReg> r = popJoinRegUnlessVoid(branchValueType);
+  maybeUnreserveJoinRegI(type);
+
+  Maybe<AnyReg> r = popJoinRegUnlessVoid(type);
 
   Label dispatchCode;
   masm.branch32(Assembler::Below, rc, Imm32(depths.length()), &dispatchCode);
 
   // This is the out-of-range stub.  rc is dead here but we don't need it.
 
   fr.popStackBeforeBranch(controlItem(defaultDepth).stackHeight);
   controlItem(defaultDepth).bceSafeOnExit &= bceSafe_;
@@ -8560,18 +8594,18 @@ void BaseCompiler::doReturn(bool popStac
     }
     default: {
       MOZ_CRASH("Function return type");
     }
   }
 }
 
 bool BaseCompiler::emitReturn() {
-  Nothing unused_value;
-  if (!iter_.readReturn(&unused_value)) {
+  NothingVector unused_values;
+  if (!iter_.readReturn(&unused_values)) {
     return false;
   }
 
   if (deadCode_) {
     return true;
   }
 
   doReturn(PopStack(true));
@@ -8648,17 +8682,17 @@ void BaseCompiler::pushReturnValueOfCall
 // by the call, because then what we want is parallel assignment to the argument
 // registers or onto the stack for outgoing arguments.  A sync() is just
 // simpler.
 
 bool BaseCompiler::emitCall() {
   uint32_t lineOrBytecode = readCallSiteLineOrBytecode();
 
   uint32_t funcIndex;
-  BaseOpIter::ValueVector args_;
+  NothingVector args_;
   if (!iter_.readCall(&funcIndex, &args_)) {
     return false;
   }
 
   if (deadCode_) {
     return true;
   }
 
@@ -8702,17 +8736,17 @@ bool BaseCompiler::emitCall() {
 }
 
 bool BaseCompiler::emitCallIndirect() {
   uint32_t lineOrBytecode = readCallSiteLineOrBytecode();
 
   uint32_t funcTypeIndex;
   uint32_t tableIndex;
   Nothing callee_;
-  BaseOpIter::ValueVector args_;
+  NothingVector args_;
   if (!iter_.readCallIndirect(&funcTypeIndex, &tableIndex, &callee_, &args_)) {
     return false;
   }
 
   if (deadCode_) {
     return true;
   }
 
@@ -8807,17 +8841,16 @@ bool BaseCompiler::emitUnaryMathBuiltinC
   if (!createStackMap("emitUnaryMathBuiltin[..]", raOffset)) {
     return false;
   }
 
   endCall(baselineCall, stackSpace);
 
   popValueStackBy(numArgs);
 
-  // We know retType isn't ExprType::Void here, so there's no need to check it.
   pushReturnValueOfCall(baselineCall, retType);
 
   return true;
 }
 
 #ifdef RABALDR_INT_DIV_I64_CALLOUT
 bool BaseCompiler::emitDivOrModI64BuiltinCall(SymbolicAddress callee,
                                               ValType operandType) {
@@ -10436,17 +10469,17 @@ bool BaseCompiler::emitTableSize() {
   return emitInstanceCall(lineOrBytecode, SASigTableSize);
 }
 #endif
 
 bool BaseCompiler::emitStructNew() {
   uint32_t lineOrBytecode = readCallSiteLineOrBytecode();
 
   uint32_t typeIndex;
-  BaseOpIter::ValueVector args;
+  NothingVector args;
   if (!iter_.readStructNew(&typeIndex, &args)) {
     return false;
   }
 
   if (deadCode_) {
     return true;
   }
 
@@ -10457,20 +10490,16 @@ bool BaseCompiler::emitStructNew() {
 
   const StructType& structType = env_.types[typeIndex].structType();
 
   pushI32(structType.moduleIndex_);
   if (!emitInstanceCall(lineOrBytecode, SASigStructNew)) {
     return false;
   }
 
-  // As many arguments as there are fields.
-
-  MOZ_ASSERT(args.length() == structType.fields_.length());
-
   // Optimization opportunity: Iterate backward to pop arguments off the
   // stack.  This will generate more instructions than we want, since we
   // really only need to pop the stack once at the end, not for every element,
   // but to do better we need a bit more machinery to load elements off the
   // stack into registers.
 
   RegPtr rp = popRef();
   RegPtr rdata = rp;
--- a/js/src/wasm/WasmConstants.h
+++ b/js/src/wasm/WasmConstants.h
@@ -37,17 +37,23 @@ enum class SectionId {
   Start = 8,
   Elem = 9,
   Code = 10,
   Data = 11,
   DataCount = 12,
   GcFeatureOptIn = 42  // Arbitrary, but fits in 7 bits
 };
 
+// WebAssembly type encodings are all single-byte negative SLEB128s, hence:
+//  forall tc:TypeCode. ((tc & SLEB128SignMask) == SLEB128SignBit
+static const uint8_t SLEB128SignMask = 0xc0;
+static const uint8_t SLEB128SignBit = 0x40;
+
 enum class TypeCode {
+
   I32 = 0x7f,  // SLEB128(-0x01)
   I64 = 0x7e,  // SLEB128(-0x02)
   F32 = 0x7d,  // SLEB128(-0x03)
   F64 = 0x7c,  // SLEB128(-0x04)
 
   // A function pointer with any signature
   FuncRef = 0x70,  // SLEB128(-0x10)
 
@@ -58,17 +64,17 @@ enum class TypeCode {
   Ref = 0x6e,
 
   // Type constructor for function types
   Func = 0x60,  // SLEB128(-0x20)
 
   // Type constructor for structure types - unofficial
   Struct = 0x50,  // SLEB128(-0x30)
 
-  // Special code representing the block signature ()->()
+  // The 'empty' case of blocktype.
   BlockVoid = 0x40,  // SLEB128(-0x40)
 
   // Type designator for null - unofficial, will not appear in the binary format
   NullRef = 0x39,
 
   Limit = 0x80
 };
 
--- a/js/src/wasm/WasmIonCompile.cpp
+++ b/js/src/wasm/WasmIonCompile.cpp
@@ -38,20 +38,22 @@ using namespace js::wasm;
 using mozilla::IsPowerOfTwo;
 using mozilla::Maybe;
 using mozilla::Nothing;
 using mozilla::Some;
 
 namespace {
 
 typedef Vector<MBasicBlock*, 8, SystemAllocPolicy> BlockVector;
+typedef Vector<MDefinition*, 8, SystemAllocPolicy> DefVector;
 
 struct IonCompilePolicy {
   // We store SSA definitions in the value stack.
   typedef MDefinition* Value;
+  typedef DefVector ValueVector;
 
   // We store loop headers and then/else blocks in the control flow stack.
   typedef MBasicBlock* ControlItem;
 };
 
 typedef OpIter<IonCompilePolicy> IonOpIter;
 
 class FunctionCompiler;
@@ -1146,81 +1148,79 @@ class FunctionCompiler {
     }
     return true;
   }
 
   /*********************************************** Control flow generation */
 
   inline bool inDeadCode() const { return curBlock_ == nullptr; }
 
-  void returnExpr(MDefinition* operand) {
+  void returnValues(const DefVector& values) {
     if (inDeadCode()) {
       return;
     }
 
-    MWasmReturn* ins = MWasmReturn::New(alloc(), operand);
-    curBlock_->end(ins);
-    curBlock_ = nullptr;
-  }
-
-  void returnVoid() {
-    if (inDeadCode()) {
-      return;
+    MOZ_ASSERT(values.length() <= 1, "until multi-return");
+
+    if (values.empty()) {
+      curBlock_->end(MWasmReturnVoid::New(alloc()));
+    } else {
+      curBlock_->end(MWasmReturn::New(alloc(), values[0]));
     }
-
-    MWasmReturnVoid* ins = MWasmReturnVoid::New(alloc());
-    curBlock_->end(ins);
     curBlock_ = nullptr;
   }
 
   void unreachableTrap() {
     if (inDeadCode()) {
       return;
     }
 
     auto* ins =
         MWasmTrap::New(alloc(), wasm::Trap::Unreachable, bytecodeOffset());
     curBlock_->end(ins);
     curBlock_ = nullptr;
   }
 
  private:
-  static bool hasPushed(MBasicBlock* block) {
-    uint32_t numPushed = block->stackDepth() - block->info().firstStackSlot();
-    MOZ_ASSERT(numPushed == 0 || numPushed == 1);
-    return numPushed;
+  static uint32_t numPushed(MBasicBlock* block) {
+    return block->stackDepth() - block->info().firstStackSlot();
   }
 
  public:
-  void pushDef(MDefinition* def) {
+  void pushDefs(const DefVector& defs) {
     if (inDeadCode()) {
       return;
     }
-    MOZ_ASSERT(!hasPushed(curBlock_));
-    if (def && def->type() != MIRType::None) {
+    MOZ_ASSERT(numPushed(curBlock_) == 0);
+    for (MDefinition* def : defs) {
+      MOZ_ASSERT(def->type() != MIRType::None);
       curBlock_->push(def);
     }
   }
 
-  MDefinition* popDefIfPushed() {
-    if (!hasPushed(curBlock_)) {
-      return nullptr;
+  bool popPushedDefs(DefVector* defs) {
+    size_t n = numPushed(curBlock_);
+    if (!defs->resizeUninitialized(n)) {
+      return false;
     }
-    MDefinition* def = curBlock_->pop();
-    MOZ_ASSERT(def->type() != MIRType::Value);
-    return def;
+    for (; n > 0; n--) {
+      MDefinition* def = curBlock_->pop();
+      MOZ_ASSERT(def->type() != MIRType::Value);
+      (*defs)[n - 1] = def;
+    }
+    return true;
   }
 
  private:
-  void addJoinPredecessor(MDefinition* def, MBasicBlock** joinPred) {
+  void addJoinPredecessor(const DefVector& defs, MBasicBlock** joinPred) {
     *joinPred = curBlock_;
     if (inDeadCode()) {
       return;
     }
-    pushDef(def);
+    pushDefs(defs);
   }
 
  public:
   bool branchAndStartThen(MDefinition* cond, MBasicBlock** elseBlock) {
     if (inDeadCode()) {
       *elseBlock = nullptr;
     } else {
       MBasicBlock* thenBlock;
@@ -1236,87 +1236,84 @@ class FunctionCompiler {
       curBlock_ = thenBlock;
       mirGraph().moveBlockToEnd(curBlock_);
     }
 
     return startBlock();
   }
 
   bool switchToElse(MBasicBlock* elseBlock, MBasicBlock** thenJoinPred) {
-    MDefinition* ifDef;
-    if (!finishBlock(&ifDef)) {
+    DefVector values;
+    if (!finishBlock(&values)) {
       return false;
     }
 
     if (!elseBlock) {
       *thenJoinPred = nullptr;
     } else {
-      addJoinPredecessor(ifDef, thenJoinPred);
+      addJoinPredecessor(values, thenJoinPred);
 
       curBlock_ = elseBlock;
       mirGraph().moveBlockToEnd(curBlock_);
     }
 
     return startBlock();
   }
 
-  bool joinIfElse(MBasicBlock* thenJoinPred, MDefinition** def) {
-    MDefinition* elseDef;
-    if (!finishBlock(&elseDef)) {
+  bool joinIfElse(MBasicBlock* thenJoinPred, DefVector* defs) {
+    DefVector values;
+    if (!finishBlock(&values)) {
       return false;
     }
 
     if (!thenJoinPred && inDeadCode()) {
-      *def = nullptr;
-    } else {
-      MBasicBlock* elseJoinPred;
-      addJoinPredecessor(elseDef, &elseJoinPred);
-
-      mozilla::Array<MBasicBlock*, 2> blocks;
-      size_t numJoinPreds = 0;
-      if (thenJoinPred) {
-        blocks[numJoinPreds++] = thenJoinPred;
-      }
-      if (elseJoinPred) {
-        blocks[numJoinPreds++] = elseJoinPred;
-      }
-
-      if (numJoinPreds == 0) {
-        *def = nullptr;
-        return true;
-      }
-
-      MBasicBlock* join;
-      if (!goToNewBlock(blocks[0], &join)) {
+      return true;
+    }
+
+    MBasicBlock* elseJoinPred;
+    addJoinPredecessor(values, &elseJoinPred);
+
+    mozilla::Array<MBasicBlock*, 2> blocks;
+    size_t numJoinPreds = 0;
+    if (thenJoinPred) {
+      blocks[numJoinPreds++] = thenJoinPred;
+    }
+    if (elseJoinPred) {
+      blocks[numJoinPreds++] = elseJoinPred;
+    }
+
+    if (numJoinPreds == 0) {
+      return true;
+    }
+
+    MBasicBlock* join;
+    if (!goToNewBlock(blocks[0], &join)) {
+      return false;
+    }
+    for (size_t i = 1; i < numJoinPreds; ++i) {
+      if (!goToExistingBlock(blocks[i], join)) {
         return false;
       }
-      for (size_t i = 1; i < numJoinPreds; ++i) {
-        if (!goToExistingBlock(blocks[i], join)) {
-          return false;
-        }
-      }
-
-      curBlock_ = join;
-      *def = popDefIfPushed();
     }
 
-    return true;
+    curBlock_ = join;
+    return popPushedDefs(defs);
   }
 
   bool startBlock() {
     MOZ_ASSERT_IF(blockDepth_ < blockPatches_.length(),
                   blockPatches_[blockDepth_].empty());
     blockDepth_++;
     return true;
   }
 
-  bool finishBlock(MDefinition** def) {
+  bool finishBlock(DefVector* defs) {
     MOZ_ASSERT(blockDepth_);
     uint32_t topLabel = --blockDepth_;
-    return bindBranches(topLabel, def);
+    return bindBranches(topLabel, defs);
   }
 
   bool startLoop(MBasicBlock** loopHeader) {
     *loopHeader = nullptr;
 
     blockDepth_++;
     loopDepth_++;
 
@@ -1396,53 +1393,52 @@ class FunctionCompiler {
       loopEntry->discardPhi(entryDef);
       mirGraph().addPhiToFreeList(entryDef);
     }
 
     return true;
   }
 
  public:
-  bool closeLoop(MBasicBlock* loopHeader, MDefinition** loopResult) {
+  bool closeLoop(MBasicBlock* loopHeader, DefVector* loopResults) {
     MOZ_ASSERT(blockDepth_ >= 1);
     MOZ_ASSERT(loopDepth_);
 
     uint32_t headerLabel = blockDepth_ - 1;
 
     if (!loopHeader) {
       MOZ_ASSERT(inDeadCode());
       MOZ_ASSERT(headerLabel >= blockPatches_.length() ||
                  blockPatches_[headerLabel].empty());
       blockDepth_--;
       loopDepth_--;
-      *loopResult = nullptr;
       return true;
     }
 
     // Op::Loop doesn't have an implicit backedge so temporarily set
     // aside the end of the loop body to bind backedges.
     MBasicBlock* loopBody = curBlock_;
     curBlock_ = nullptr;
 
     // As explained in bug 1253544, Ion apparently has an invariant that
     // there is only one backedge to loop headers. To handle wasm's ability
     // to have multiple backedges to the same loop header, we bind all those
     // branches as forward jumps to a single backward jump. This is
     // unfortunate but the optimizer is able to fold these into single jumps
     // to backedges.
-    MDefinition* _;
+    DefVector _;
     if (!bindBranches(headerLabel, &_)) {
       return false;
     }
 
     MOZ_ASSERT(loopHeader->loopDepth() == loopDepth_);
 
     if (curBlock_) {
       // We're on the loop backedge block, created by bindBranches.
-      if (hasPushed(curBlock_)) {
+      for (size_t i = 0, n = numPushed(curBlock_); i != n; i++) {
         curBlock_->pop();
       }
 
       MOZ_ASSERT(curBlock_->loopDepth() == loopDepth_);
       curBlock_->end(MGoto::New(alloc(), loopHeader));
       if (!setLoopBackedge(loopHeader, loopBody, curBlock_)) {
         return false;
       }
@@ -1457,75 +1453,74 @@ class FunctionCompiler {
       MBasicBlock* out;
       if (!goToNewBlock(curBlock_, &out)) {
         return false;
       }
       curBlock_ = out;
     }
 
     blockDepth_ -= 1;
-    *loopResult = inDeadCode() ? nullptr : popDefIfPushed();
-    return true;
+    return inDeadCode() || popPushedDefs(loopResults);
   }
 
   bool addControlFlowPatch(MControlInstruction* ins, uint32_t relative,
                            uint32_t index) {
     MOZ_ASSERT(relative < blockDepth_);
     uint32_t absolute = blockDepth_ - 1 - relative;
 
     if (absolute >= blockPatches_.length() &&
         !blockPatches_.resize(absolute + 1)) {
       return false;
     }
 
     return blockPatches_[absolute].append(ControlFlowPatch(ins, index));
   }
 
-  bool br(uint32_t relativeDepth, MDefinition* maybeValue) {
+  bool br(uint32_t relativeDepth, const DefVector& values) {
     if (inDeadCode()) {
       return true;
     }
 
     MGoto* jump = MGoto::New(alloc());
     if (!addControlFlowPatch(jump, relativeDepth, MGoto::TargetIndex)) {
       return false;
     }
 
-    pushDef(maybeValue);
+    pushDefs(values);
 
     curBlock_->end(jump);
     curBlock_ = nullptr;
     return true;
   }
 
-  bool brIf(uint32_t relativeDepth, MDefinition* maybeValue,
+  bool brIf(uint32_t relativeDepth, const DefVector& values,
             MDefinition* condition) {
     if (inDeadCode()) {
       return true;
     }
 
     MBasicBlock* joinBlock = nullptr;
     if (!newBlock(curBlock_, &joinBlock)) {
       return false;
     }
 
     MTest* test = MTest::New(alloc(), condition, joinBlock);
     if (!addControlFlowPatch(test, relativeDepth, MTest::TrueBranchIndex)) {
       return false;
     }
 
-    pushDef(maybeValue);
+    pushDefs(values);
 
     curBlock_->end(test);
     curBlock_ = joinBlock;
     return true;
   }
 
   bool brTable(MDefinition* operand, uint32_t defaultDepth,
-               const Uint32Vector& depths, MDefinition* maybeValue) {
+               const Uint32Vector& depths, const DefVector& values) {
     if (inDeadCode()) {
       return true;
     }
 
     size_t numCases = depths.length();
     MOZ_ASSERT(numCases <= INT32_MAX);
     MOZ_ASSERT(numCases);
 
@@ -1568,17 +1563,17 @@ class FunctionCompiler {
         caseIndex = p->value();
       }
 
       if (!table->addCase(caseIndex)) {
         return false;
       }
     }
 
-    pushDef(maybeValue);
+    pushDefs(values);
 
     curBlock_->end(table);
     curBlock_ = nullptr;
 
     return true;
   }
 
   /************************************************************ DECODING ***/
@@ -1616,20 +1611,19 @@ class FunctionCompiler {
 
   bool goToExistingBlock(MBasicBlock* prev, MBasicBlock* next) {
     MOZ_ASSERT(prev);
     MOZ_ASSERT(next);
     prev->end(MGoto::New(alloc(), next));
     return next->addPredecessor(alloc(), prev);
   }
 
-  bool bindBranches(uint32_t absolute, MDefinition** def) {
+  bool bindBranches(uint32_t absolute, DefVector* defs) {
     if (absolute >= blockPatches_.length() || blockPatches_[absolute].empty()) {
-      *def = inDeadCode() ? nullptr : popDefIfPushed();
-      return true;
+      return inDeadCode() || popPushedDefs(defs);
     }
 
     ControlFlowPatchVector& patches = blockPatches_[absolute];
     MControlInstruction* ins = patches[0].ins;
     MBasicBlock* pred = ins->block();
 
     MBasicBlock* join = nullptr;
     if (!newBlock(pred, &join)) {
@@ -1659,17 +1653,19 @@ class FunctionCompiler {
     }
 
     if (curBlock_ && !goToExistingBlock(curBlock_, join)) {
       return false;
     }
 
     curBlock_ = join;
 
-    *def = popDefIfPushed();
+    if (!popPushedDefs(defs)) {
+      return false;
+    }
 
     patches.clear();
     return true;
   }
 };
 
 template <>
 MDefinition* FunctionCompiler::unary<MToFloat32>(MDefinition* op) {
@@ -1784,196 +1780,158 @@ static bool EmitIf(FunctionCompiler& f) 
     return false;
   }
 
   f.iter().controlItem() = elseBlock;
   return true;
 }
 
 static bool EmitElse(FunctionCompiler& f) {
-  ExprType thenType;
-  MDefinition* thenValue;
-  if (!f.iter().readElse(&thenType, &thenValue)) {
+  ResultType thenType;
+  DefVector thenValues;
+  if (!f.iter().readElse(&thenType, &thenValues)) {
     return false;
   }
 
-  if (!IsVoid(thenType)) {
-    f.pushDef(thenValue);
-  }
+  f.pushDefs(thenValues);
 
   if (!f.switchToElse(f.iter().controlItem(), &f.iter().controlItem())) {
     return false;
   }
 
   return true;
 }
 
 static bool EmitEnd(FunctionCompiler& f) {
   LabelKind kind;
-  ExprType type;
-  MDefinition* value;
-  if (!f.iter().readEnd(&kind, &type, &value)) {
+  ResultType type;
+  DefVector preJoinDefs;
+  if (!f.iter().readEnd(&kind, &type, &preJoinDefs)) {
     return false;
   }
 
   MBasicBlock* block = f.iter().controlItem();
-
   f.iter().popEnd();
 
-  if (!IsVoid(type)) {
-    f.pushDef(value);
-  }
-
-  MDefinition* def = nullptr;
+  f.pushDefs(preJoinDefs);
+
+  DefVector postJoinDefs;
   switch (kind) {
     case LabelKind::Body:
       MOZ_ASSERT(f.iter().controlStackEmpty());
-      if (!f.finishBlock(&def)) {
+      if (!f.finishBlock(&postJoinDefs)) {
         return false;
       }
-      if (f.inDeadCode() || IsVoid(type)) {
-        f.returnVoid();
-      } else {
-        f.returnExpr(def);
-      }
+      f.returnValues(postJoinDefs);
       return f.iter().readFunctionEnd(f.iter().end());
     case LabelKind::Block:
-      if (!f.finishBlock(&def)) {
+      if (!f.finishBlock(&postJoinDefs)) {
         return false;
       }
       break;
     case LabelKind::Loop:
-      if (!f.closeLoop(block, &def)) {
+      if (!f.closeLoop(block, &postJoinDefs)) {
         return false;
       }
       break;
     case LabelKind::Then:
       // If we didn't see an Else, create a trivial else block so that we create
       // a diamond anyway, to preserve Ion invariants.
       if (!f.switchToElse(block, &block)) {
         return false;
       }
 
-      if (!f.joinIfElse(block, &def)) {
+      if (!f.joinIfElse(block, &postJoinDefs)) {
         return false;
       }
       break;
     case LabelKind::Else:
-      if (!f.joinIfElse(block, &def)) {
+      if (!f.joinIfElse(block, &postJoinDefs)) {
         return false;
       }
       break;
   }
 
-  if (!IsVoid(type)) {
-    MOZ_ASSERT_IF(!f.inDeadCode(), def);
-    f.iter().setResult(def);
-  }
+  MOZ_ASSERT_IF(!f.inDeadCode(), postJoinDefs.length() == type.length());
+  f.iter().setResults(postJoinDefs.length(), postJoinDefs);
 
   return true;
 }
 
 static bool EmitBr(FunctionCompiler& f) {
   uint32_t relativeDepth;
-  ExprType type;
-  MDefinition* value;
-  if (!f.iter().readBr(&relativeDepth, &type, &value)) {
+  ResultType type;
+  DefVector values;
+  if (!f.iter().readBr(&relativeDepth, &type, &values)) {
     return false;
   }
 
-  if (IsVoid(type)) {
-    if (!f.br(relativeDepth, nullptr)) {
-      return false;
-    }
-  } else {
-    if (!f.br(relativeDepth, value)) {
-      return false;
-    }
-  }
-
-  return true;
+  return f.br(relativeDepth, values);
 }
 
 static bool EmitBrIf(FunctionCompiler& f) {
   uint32_t relativeDepth;
-  ExprType type;
-  MDefinition* value;
+  ResultType type;
+  DefVector values;
   MDefinition* condition;
-  if (!f.iter().readBrIf(&relativeDepth, &type, &value, &condition)) {
+  if (!f.iter().readBrIf(&relativeDepth, &type, &values, &condition)) {
     return false;
   }
 
-  if (IsVoid(type)) {
-    if (!f.brIf(relativeDepth, nullptr, condition)) {
-      return false;
-    }
-  } else {
-    if (!f.brIf(relativeDepth, value, condition)) {
-      return false;
-    }
-  }
-
-  return true;
+  return f.brIf(relativeDepth, values, condition);
 }
 
 static bool EmitBrTable(FunctionCompiler& f) {
   Uint32Vector depths;
   uint32_t defaultDepth;
-  ExprType branchValueType;
-  MDefinition* branchValue;
+  ResultType branchValueType;
+  DefVector branchValues;
   MDefinition* index;
   if (!f.iter().readBrTable(&depths, &defaultDepth, &branchValueType,
-                            &branchValue, &index)) {
+                            &branchValues, &index)) {
     return false;
   }
 
   // If all the targets are the same, or there are no targets, we can just
   // use a goto. This is not just an optimization: MaybeFoldConditionBlock
   // assumes that tables have more than one successor.
   bool allSameDepth = true;
   for (uint32_t depth : depths) {
     if (depth != defaultDepth) {
       allSameDepth = false;
       break;
     }
   }
 
   if (allSameDepth) {
-    return f.br(defaultDepth, branchValue);
-  }
-
-  return f.brTable(index, defaultDepth, depths, branchValue);
+    return f.br(defaultDepth, branchValues);
+  }
+
+  return f.brTable(index, defaultDepth, depths, branchValues);
 }
 
 static bool EmitReturn(FunctionCompiler& f) {
-  MDefinition* value;
-  if (!f.iter().readReturn(&value)) {
+  DefVector values;
+  if (!f.iter().readReturn(&values)) {
     return false;
   }
 
-  if (f.funcType().results().length() == 0) {
-    f.returnVoid();
-    return true;
-  }
-
-  f.returnExpr(value);
+  f.returnValues(values);
   return true;
 }
 
 static bool EmitUnreachable(FunctionCompiler& f) {
   if (!f.iter().readUnreachable()) {
     return false;
   }
 
   f.unreachableTrap();
   return true;
 }
 
-typedef IonOpIter::ValueVector DefVector;
-
 static bool EmitCallArgs(FunctionCompiler& f, const FuncType& funcType,
                          const DefVector& args, CallCompileState* call) {
   for (size_t i = 0, n = funcType.args().length(); i < n; ++i) {
     if (!f.mirGen().ensureBallast()) {
       return false;
     }
     if (!f.passArg(args[i], funcType.args()[i], call)) {
       return false;
--- a/js/src/wasm/WasmOpIter.h
+++ b/js/src/wasm/WasmOpIter.h
@@ -24,16 +24,326 @@
 
 #include "jit/AtomicOp.h"
 #include "js/Printf.h"
 #include "wasm/WasmValidate.h"
 
 namespace js {
 namespace wasm {
 
+template <typename PointerType>
+class TaggedValue {
+ public:
+  enum Kind {
+    ImmediateKind1 = 0,
+    ImmediateKind2 = 1,
+    PointerKind1 = 2,
+    PointerKind2 = 3
+  };
+
+ private:
+  uintptr_t bits_;
+
+  static constexpr uintptr_t PayloadShift = 2;
+  static constexpr uintptr_t KindMask = 0x3;
+  static constexpr uintptr_t PointerKindBit = 0x2;
+
+  constexpr static bool IsPointerKind(Kind kind) {
+    return uintptr_t(kind) & PointerKindBit;
+  }
+  constexpr static bool IsImmediateKind(Kind kind) {
+    return !IsPointerKind(kind);
+  }
+
+  static_assert(IsImmediateKind(ImmediateKind1), "immediate kind 1");
+  static_assert(IsImmediateKind(ImmediateKind2), "immediate kind 2");
+  static_assert(IsPointerKind(PointerKind1), "pointer kind 1");
+  static_assert(IsPointerKind(PointerKind2), "pointer kind 2");
+
+  static uintptr_t PackImmediate(Kind kind, uint32_t imm) {
+    MOZ_ASSERT(IsImmediateKind(kind));
+    MOZ_ASSERT((uintptr_t(kind) & KindMask) == kind);
+    MOZ_ASSERT((imm & (uint32_t(KindMask) << (32 - PayloadShift))) == 0);
+    return uintptr_t(kind) | (uintptr_t(imm) << PayloadShift);
+  }
+
+  static uintptr_t PackPointer(Kind kind, PointerType* ptr) {
+    uintptr_t ptrBits = reinterpret_cast<uintptr_t>(ptr);
+    MOZ_ASSERT(IsPointerKind(kind));
+    MOZ_ASSERT((uintptr_t(kind) & KindMask) == kind);
+    MOZ_ASSERT((ptrBits & KindMask) == 0);
+    return uintptr_t(kind) | ptrBits;
+  }
+
+ public:
+  TaggedValue(Kind kind, uint32_t imm) : bits_(PackImmediate(kind, imm)) {}
+  TaggedValue(Kind kind, PointerType* ptr) : bits_(PackPointer(kind, ptr)) {}
+
+  uintptr_t bits() const { return bits_; }
+  Kind kind() const { return Kind(bits() & KindMask); }
+  uint32_t immediate() const {
+    MOZ_ASSERT(IsImmediateKind(kind()));
+    return mozilla::AssertedCast<uint32_t>(bits() >> PayloadShift);
+  }
+  PointerType* pointer() const {
+    MOZ_ASSERT(IsPointerKind(kind()));
+    return reinterpret_cast<PointerType*>(bits() & ~KindMask);
+  }
+};
+
+// ResultType represents the WebAssembly spec's `resulttype`. Semantically, a
+// result type is just a vec(valtype).  For effiency, though, the ResultType
+// value is packed into a word, with separate encodings for these 3 cases:
+//  []
+//  [valtype]
+//  pointer to ValTypeVector
+//
+// Additionally there is an encoding indicating uninitialized ResultType
+// values.
+//
+// Generally in the latter case the ValTypeVector is the args() or results() of
+// a FuncType in the compilation unit, so as long as the lifetime of the
+// ResultType value is less than the OpIter, we can just borrow the pointer
+// without ownership or copying.
+class ResultType {
+  typedef TaggedValue<const ValTypeVector> Tagged;
+  Tagged tagged_;
+
+  enum Kind {
+    EmptyKind = Tagged::ImmediateKind1,
+    SingleKind = Tagged::ImmediateKind2,
+#ifdef ENABLE_WASM_MULTI_VALUE
+    VectorKind = Tagged::PointerKind1,
+#endif
+    InvalidKind = Tagged::PointerKind2,
+  };
+
+  ResultType(Kind kind, uint32_t imm) : tagged_(Tagged::Kind(kind), imm) {}
+#ifdef ENABLE_WASM_MULTI_VALUE
+  explicit ResultType(const ValTypeVector* ptr)
+      : tagged_(Tagged::Kind(VectorKind), ptr) {}
+#endif
+
+  Kind kind() const { return Kind(tagged_.kind()); }
+
+  ValType singleValType() const {
+    MOZ_ASSERT(kind() == SingleKind);
+    return ValType(PackedTypeCodeFromBits(tagged_.immediate()));
+  }
+
+#ifdef ENABLE_WASM_MULTI_VALUE
+  const ValTypeVector& values() const {
+    MOZ_ASSERT(kind() == VectorKind);
+    return *tagged_.pointer();
+  }
+#endif
+
+ public:
+  ResultType() : tagged_(Tagged::Kind(InvalidKind), nullptr) {}
+
+  static ResultType Empty() { return ResultType(EmptyKind, uint32_t(0)); }
+  static ResultType Single(ValType vt) {
+    return ResultType(SingleKind, vt.bitsUnsafe());
+  }
+  static ResultType Vector(const ValTypeVector& vals) {
+    switch (vals.length()) {
+      case 0:
+        return Empty();
+      case 1:
+        return Single(vals[0]);
+      default:
+#ifdef ENABLE_WASM_MULTI_VALUE
+        return ResultType(&vals);
+#else
+        MOZ_CRASH("multi-value returns not supported");
+#endif
+    }
+  }
+
+  bool empty() const { return kind() == EmptyKind; }
+
+  size_t length() const {
+    switch (kind()) {
+      case EmptyKind:
+        return 0;
+      case SingleKind:
+        return 1;
+#ifdef ENABLE_WASM_MULTI_VALUE
+      case VectorKind:
+        return values().length();
+#endif
+      default:
+        MOZ_CRASH("bad resulttype");
+    }
+  }
+
+  ValType operator[](size_t i) const {
+    switch (kind()) {
+      case SingleKind:
+        MOZ_ASSERT(i == 0);
+        return singleValType();
+#ifdef ENABLE_WASM_MULTI_VALUE
+      case VectorKind:
+        return values()[i];
+#endif
+      default:
+        MOZ_CRASH("bad resulttype");
+    }
+  }
+
+  bool operator==(ResultType rhs) const {
+    switch (kind()) {
+      case EmptyKind:
+      case SingleKind:
+      case InvalidKind:
+        return tagged_.bits() == rhs.tagged_.bits();
+#ifdef ENABLE_WASM_MULTI_VALUE
+      case VectorKind: {
+        if (rhs.kind() == EmptyKind || rhs.kind() == SingleKind) {
+          return false;
+        }
+        return EqualContainers(values(), rhs.values());
+      }
+#endif
+      default:
+        MOZ_CRASH("bad resulttype");
+    }
+  }
+  bool operator!=(ResultType rhs) const { return !(*this == rhs); }
+};
+
+// BlockType represents the WebAssembly spec's `blocktype`. Semantically, a
+// block type is just a (vec(valtype) -> vec(valtype)) with four special
+// encodings which are represented explicitly in BlockType:
+//  [] -> []
+//  [] -> [valtype]
+//  [params] -> [results] via pointer to FuncType
+//  [] -> [results] via pointer to FuncType (ignoring [params])
+
+class BlockType {
+  typedef TaggedValue<const FuncType> Tagged;
+  Tagged tagged_;
+
+  enum Kind {
+    VoidToVoidKind = Tagged::ImmediateKind1,
+    VoidToSingleKind = Tagged::ImmediateKind2,
+#ifdef ENABLE_WASM_MULTI_VALUE
+    FuncKind = Tagged::PointerKind1,
+    FuncResultsKind = Tagged::PointerKind2
+#endif
+  };
+
+  BlockType(Kind kind, uint32_t imm) : tagged_(Tagged::Kind(kind), imm) {}
+#ifdef ENABLE_WASM_MULTI_VALUE
+  BlockType(Kind kind, const FuncType& type)
+      : tagged_(Tagged::Kind(kind), &type) {}
+#endif
+
+  Kind kind() const { return Kind(tagged_.kind()); }
+  ValType singleValType() const {
+    MOZ_ASSERT(kind() == VoidToSingleKind);
+    return ValType(PackedTypeCodeFromBits(tagged_.immediate()));
+  }
+
+#ifdef ENABLE_WASM_MULTI_VALUE
+  const FuncType& funcType() const { return *tagged_.pointer(); }
+#endif
+
+ public:
+  BlockType()
+      : tagged_(Tagged::Kind(VoidToVoidKind),
+                uint32_t(InvalidPackedTypeCode())) {}
+
+  static BlockType VoidToVoid() {
+    return BlockType(VoidToVoidKind, uint32_t(0));
+  }
+  static BlockType VoidToSingle(ValType vt) {
+    return BlockType(VoidToSingleKind, vt.bitsUnsafe());
+  }
+  static BlockType Func(const FuncType& type) {
+#ifdef ENABLE_WASM_MULTI_VALUE
+    if (type.args().length() == 0) {
+      return FuncResults(type);
+    }
+    return BlockType(FuncKind, type);
+#else
+    MOZ_ASSERT(type.args().length() == 0);
+    return FuncResults(type);
+#endif
+  }
+  static BlockType FuncResults(const FuncType& type) {
+    switch (type.results().length()) {
+      case 0:
+        return VoidToVoid();
+      case 1:
+        return VoidToSingle(type.results()[0]);
+      default:
+#ifdef ENABLE_WASM_MULTI_VALUE
+        return BlockType(FuncResultsKind, type);
+#else
+        MOZ_CRASH("multi-value returns not supported");
+#endif
+    }
+  }
+
+  ResultType params() const {
+    switch (kind()) {
+      case VoidToVoidKind:
+      case VoidToSingleKind:
+#ifdef ENABLE_WASM_MULTI_VALUE
+      case FuncResultsKind:
+#endif
+        return ResultType::Empty();
+#ifdef ENABLE_WASM_MULTI_VALUE
+      case FuncKind:
+        return ResultType::Vector(funcType().args());
+#endif
+      default:
+        MOZ_CRASH("unexpected kind");
+    }
+  }
+
+  ResultType results() const {
+    switch (kind()) {
+      case VoidToVoidKind:
+        return ResultType::Empty();
+      case VoidToSingleKind:
+        return ResultType::Single(singleValType());
+#ifdef ENABLE_WASM_MULTI_VALUE
+      case FuncKind:
+      case FuncResultsKind:
+        return ResultType::Vector(funcType().results());
+#endif
+      default:
+        MOZ_CRASH("unexpected kind");
+    }
+  }
+
+  bool operator==(BlockType rhs) const {
+    if (kind() != rhs.kind()) {
+      return false;
+    }
+    switch (kind()) {
+      case VoidToVoidKind:
+      case VoidToSingleKind:
+        return tagged_.bits() == rhs.tagged_.bits();
+#ifdef ENABLE_WASM_MULTI_VALUE
+      case FuncKind:
+        return funcType() == rhs.funcType();
+      case FuncResultsKind:
+        return EqualContainers(funcType().results(), rhs.funcType().results());
+#endif
+      default:
+        MOZ_CRASH("unexpected kind");
+    }
+  }
+
+  bool operator!=(BlockType rhs) const { return !(*this == rhs); }
+};
+
 // The kind of a control-flow stack item.
 enum class LabelKind : uint8_t { Body, Block, Loop, Then, Else };
 
 // The type of values on the operand stack during validation. The Any type
 // represents the type of a value produced by an unconditional branch.
 
 class StackType {
   PackedTypeCode tc_;
@@ -204,80 +514,94 @@ struct LinearMemoryAddress {
   LinearMemoryAddress() : offset(0), align(0) {}
   LinearMemoryAddress(Value base, uint32_t offset, uint32_t align)
       : base(base), offset(offset), align(align) {}
 };
 
 template <typename ControlItem>
 class ControlStackEntry {
   // Use a Pair to optimize away empty ControlItem.
-  mozilla::Pair<LabelKind, ControlItem> kindAndItem_;
+  mozilla::Pair<BlockType, ControlItem> typeAndItem_;
+
+  // The "base" of a control stack entry is valueStack_.length() minus
+  // type().params().length(), i.e., the size of the value stack "below"
+  // this block.
+  uint32_t valueStackBase_;
   bool polymorphicBase_;
-  ExprType type_;
-  size_t valueStackStart_;
+
+  LabelKind kind_;
 
  public:
-  ControlStackEntry(LabelKind kind, ExprType type, size_t valueStackStart)
-      : kindAndItem_(kind, ControlItem()),
+  ControlStackEntry(LabelKind kind, BlockType type, uint32_t valueStackBase)
+      : typeAndItem_(type, ControlItem()),
+        valueStackBase_(valueStackBase),
         polymorphicBase_(false),
-        type_(type),
-        valueStackStart_(valueStackStart) {
-    MOZ_ASSERT(type != ExprType::Limit);
+        kind_(kind) {
+    MOZ_ASSERT(type != BlockType());
   }
 
-  LabelKind kind() const { return kindAndItem_.first(); }
-  ExprType resultType() const { return type_; }
-  ExprType branchTargetType() const {
-    return kind() == LabelKind::Loop ? ExprType::Void : type_;
+  LabelKind kind() const { return kind_; }
+  BlockType type() const { return typeAndItem_.first(); }
+  ResultType resultType() const { return type().results(); }
+  ResultType branchTargetType() const {
+    return kind_ == LabelKind::Loop ? type().params() : type().results();
   }
-  size_t valueStackStart() const { return valueStackStart_; }
-  ControlItem& controlItem() { return kindAndItem_.second(); }
+  uint32_t valueStackBase() const { return valueStackBase_; }
+  ControlItem& controlItem() { return typeAndItem_.second(); }
   void setPolymorphicBase() { polymorphicBase_ = true; }
   bool polymorphicBase() const { return polymorphicBase_; }
 
   void switchToElse() {
     MOZ_ASSERT(kind() == LabelKind::Then);
-    kindAndItem_.first() = LabelKind::Else;
+    kind_ = LabelKind::Else;
     polymorphicBase_ = false;
   }
 };
 
 template <typename Value>
-class TypeAndValue {
+class TypeAndValueT {
   // Use a Pair to optimize away empty Value.
   mozilla::Pair<StackType, Value> tv_;
 
  public:
-  TypeAndValue() : tv_(StackType::Bottom, Value()) {}
-  explicit TypeAndValue(StackType type) : tv_(type, Value()) {}
-  explicit TypeAndValue(ValType type) : tv_(StackType(type), Value()) {}
-  TypeAndValue(StackType type, Value value) : tv_(type, value) {}
-  TypeAndValue(ValType type, Value value) : tv_(StackType(type), value) {}
+  TypeAndValueT() : tv_(StackType::Bottom, Value()) {}
+  explicit TypeAndValueT(StackType type) : tv_(type, Value()) {}
+  explicit TypeAndValueT(ValType type) : tv_(StackType(type), Value()) {}
+  TypeAndValueT(StackType type, Value value) : tv_(type, value) {}
+  TypeAndValueT(ValType type, Value value) : tv_(StackType(type), value) {}
   StackType type() const { return tv_.first(); }
   StackType& typeRef() { return tv_.first(); }
   Value value() const { return tv_.second(); }
   void setValue(Value value) { tv_.second() = value; }
 };
 
 // An iterator over the bytes of a function body. It performs validation
 // and unpacks the data into a usable form.
 //
 // The MOZ_STACK_CLASS attribute here is because of the use of DebugOnly.
 // There's otherwise nothing inherent in this class which would require
 // it to be used on the stack.
 template <typename Policy>
 class MOZ_STACK_CLASS OpIter : private Policy {
+ public:
   typedef typename Policy::Value Value;
+  typedef typename Policy::ValueVector ValueVector;
+  typedef TypeAndValueT<Value> TypeAndValue;
+  typedef Vector<TypeAndValue, 8, SystemAllocPolicy> TypeAndValueStack;
   typedef typename Policy::ControlItem ControlItem;
-
+  typedef ControlStackEntry<ControlItem> Control;
+  typedef Vector<Control, 8, SystemAllocPolicy> ControlStack;
+
+ private:
   Decoder& d_;
   const ModuleEnvironment& env_;
 
-  Vector<TypeAndValue<Value>, 8, SystemAllocPolicy> valueStack_;
-  Vector<ControlStackEntry<ControlItem>, 8, SystemAllocPolicy> controlStack_;
+  TypeAndValueStack valueStack_;
+  TypeAndValueStack thenParamStack_;
+  ControlStack controlStack_;
 
 #ifdef DEBUG
   OpBytes op_;
 #endif
   size_t offsetOfLastReadOp_;
 
   MOZ_MUST_USE bool readFixedU8(uint8_t* out) { return d_.readFixedU8(out); }
   MOZ_MUST_USE bool readFixedU32(uint32_t* out) { return d_.readFixedU32(out); }
@@ -288,78 +612,66 @@ class MOZ_STACK_CLASS OpIter : private P
   MOZ_MUST_USE bool readFixedF32(float* out) { return d_.readFixedF32(out); }
   MOZ_MUST_USE bool readFixedF64(double* out) { return d_.readFixedF64(out); }
 
   MOZ_MUST_USE bool readMemOrTableIndex(bool isMem, uint32_t* index);
   MOZ_MUST_USE bool readLinearMemoryAddress(uint32_t byteSize,
                                             LinearMemoryAddress<Value>* addr);
   MOZ_MUST_USE bool readLinearMemoryAddressAligned(
       uint32_t byteSize, LinearMemoryAddress<Value>* addr);
-  MOZ_MUST_USE bool readBlockType(ExprType* expr);
+  MOZ_MUST_USE bool readBlockType(BlockType* type);
   MOZ_MUST_USE bool readStructTypeIndex(uint32_t* typeIndex);
   MOZ_MUST_USE bool readFieldIndex(uint32_t* fieldIndex,
                                    const StructType& structType);
 
   MOZ_MUST_USE bool popCallArgs(const ValTypeVector& expectedTypes,
-                                Vector<Value, 8, SystemAllocPolicy>* values);
+                                ValueVector* values);
 
   MOZ_MUST_USE bool failEmptyStack();
   MOZ_MUST_USE bool popStackType(StackType* type, Value* value);
-  MOZ_MUST_USE bool popWithType(ValType valType, Value* value);
-  MOZ_MUST_USE bool popWithType(ExprType expectedType, Value* value);
-  MOZ_MUST_USE bool topWithType(ExprType expectedType, Value* value);
-  MOZ_MUST_USE bool topWithType(ValType valType, Value* value);
-  MOZ_MUST_USE bool topIsType(ValType expectedType, StackType* actualType,
-                              Value* value);
-
-  MOZ_MUST_USE bool pushControl(LabelKind kind, ExprType type);
-  MOZ_MUST_USE bool checkStackAtEndOfBlock(ExprType* type, Value* value);
-  MOZ_MUST_USE bool getControl(uint32_t relativeDepth,
-                               ControlStackEntry<ControlItem>** controlEntry);
-  MOZ_MUST_USE bool checkBranchValue(uint32_t relativeDepth, ExprType* type,
-                                     Value* value);
+  MOZ_MUST_USE bool popWithType(ValType expected, Value* value);
+  MOZ_MUST_USE bool popWithType(ResultType expected, ValueVector* values);
+  MOZ_MUST_USE bool popThenPushType(ResultType expected, ValueVector* values);
+  MOZ_MUST_USE bool ensureTopHasType(ResultType expected, ValueVector* values);
+
+  MOZ_MUST_USE bool pushControl(LabelKind kind, BlockType type);
+  MOZ_MUST_USE bool checkStackAtEndOfBlock(ResultType* type,
+                                           ValueVector* values);
+  MOZ_MUST_USE bool getControl(uint32_t relativeDepth, Control** controlEntry);
+  MOZ_MUST_USE bool checkBranchValue(uint32_t relativeDepth, ResultType* type,
+                                     ValueVector* values);
   MOZ_MUST_USE bool checkBrTableEntry(uint32_t* relativeDepth,
-                                      uint32_t* branchValueArity,
-                                      ExprType* branchValueType,
-                                      Value* branchValue);
-
-  MOZ_MUST_USE bool push(StackType t) { return valueStack_.emplaceBack(t); }
+                                      ResultType prevBranchType,
+                                      ResultType* branchType,
+                                      ValueVector* branchValues);
+
   MOZ_MUST_USE bool push(ValType t) { return valueStack_.emplaceBack(t); }
-  MOZ_MUST_USE bool push(ExprType t) {
-    return IsVoid(t) || push(NonVoidToValType(t));
-  }
-  MOZ_MUST_USE bool push(const Maybe<ValType>& t) {
-    return t.isNothing() || push(t.ref());
-  }
-  MOZ_MUST_USE bool push(TypeAndValue<Value> tv) {
-    return valueStack_.append(tv);
+  MOZ_MUST_USE bool push(TypeAndValue tv) { return valueStack_.append(tv); }
+  MOZ_MUST_USE bool push(ResultType t) {
+    for (size_t i = 0; i < t.length(); i++) {
+      if (!push(t[i])) {
+        return false;
+      }
+    }
+    return true;
   }
   void infalliblePush(StackType t) { valueStack_.infallibleEmplaceBack(t); }
   void infalliblePush(ValType t) {
     valueStack_.infallibleEmplaceBack(StackType(t));
   }
-  void infalliblePush(TypeAndValue<Value> tv) {
-    valueStack_.infallibleAppend(tv);
-  }
+  void infalliblePush(TypeAndValue tv) { valueStack_.infallibleAppend(tv); }
 
   void afterUnconditionalBranch() {
-    valueStack_.shrinkTo(controlStack_.back().valueStackStart());
+    valueStack_.shrinkTo(controlStack_.back().valueStackBase());
     controlStack_.back().setPolymorphicBase();
   }
 
-  // Compute a type that is a supertype of one and two. This type is not
-  // guaranteed to be minimal; there may be a more specific supertype of one
-  // and two that this type is a supertype of.
-  inline bool weakMeet(ExprType one, ExprType two, ExprType* result) const;
-
   inline bool checkIsSubtypeOf(ValType lhs, ValType rhs);
 
  public:
-  typedef Vector<Value, 8, SystemAllocPolicy> ValueVector;
-
 #ifdef DEBUG
   explicit OpIter(const ModuleEnvironment& env, Decoder& decoder)
       : d_(decoder),
         env_(env),
         op_(OpBytes(Op::Limit)),
         offsetOfLastReadOp_(0) {}
 #else
   explicit OpIter(const ModuleEnvironment& env, Decoder& decoder)
@@ -402,30 +714,31 @@ class MOZ_STACK_CLASS OpIter : private P
   }
 
   // ------------------------------------------------------------------------
   // Decoding and validation interface.
 
   MOZ_MUST_USE bool readOp(OpBytes* op);
   MOZ_MUST_USE bool readFunctionStart(uint32_t funcIndex);
   MOZ_MUST_USE bool readFunctionEnd(const uint8_t* bodyEnd);
-  MOZ_MUST_USE bool readReturn(Value* value);
+  MOZ_MUST_USE bool readReturn(ValueVector* values);
   MOZ_MUST_USE bool readBlock();
   MOZ_MUST_USE bool readLoop();
   MOZ_MUST_USE bool readIf(Value* condition);
-  MOZ_MUST_USE bool readElse(ExprType* thenType, Value* thenValue);
-  MOZ_MUST_USE bool readEnd(LabelKind* kind, ExprType* type, Value* value);
+  MOZ_MUST_USE bool readElse(ResultType* thenType, ValueVector* thenValues);
+  MOZ_MUST_USE bool readEnd(LabelKind* kind, ResultType* type,
+                            ValueVector* values);
   void popEnd();
-  MOZ_MUST_USE bool readBr(uint32_t* relativeDepth, ExprType* type,
-                           Value* value);
-  MOZ_MUST_USE bool readBrIf(uint32_t* relativeDepth, ExprType* type,
-                             Value* value, Value* condition);
+  MOZ_MUST_USE bool readBr(uint32_t* relativeDepth, ResultType* type,
+                           ValueVector* values);
+  MOZ_MUST_USE bool readBrIf(uint32_t* relativeDepth, ResultType* type,
+                             ValueVector* values, Value* condition);
   MOZ_MUST_USE bool readBrTable(Uint32Vector* depths, uint32_t* defaultDepth,
-                                ExprType* branchValueType, Value* branchValue,
-                                Value* index);
+                                ResultType* defaultBranchValueType,
+                                ValueVector* branchValues, Value* index);
   MOZ_MUST_USE bool readUnreachable();
   MOZ_MUST_USE bool readDrop();
   MOZ_MUST_USE bool readUnary(ValType operandType, Value* input);
   MOZ_MUST_USE bool readConversion(ValType operandType, ValType resultType,
                                    Value* input);
   MOZ_MUST_USE bool readBinary(ValType operandType, Value* lhs, Value* rhs);
   MOZ_MUST_USE bool readComparison(ValType operandType, Value* lhs, Value* rhs);
   MOZ_MUST_USE bool readLoad(ValType resultType, uint32_t byteSize,
@@ -508,16 +821,25 @@ class MOZ_STACK_CLASS OpIter : private P
   // At a location where readOp is allowed, peek at the next opcode
   // without consuming it or updating any internal state.
   // Never fails: returns uint16_t(Op::Limit) in op->b0 if it can't read.
   void peekOp(OpBytes* op);
 
   // ------------------------------------------------------------------------
   // Stack management.
 
+  // Set the top N result values.
+  void setResults(size_t count, const ValueVector& values) {
+    MOZ_ASSERT(valueStack_.length() >= count);
+    size_t base = valueStack_.length() - count;
+    for (size_t i = 0; i < count; i++) {
+      valueStack_[base + i].setValue(values[i]);
+    }
+  }
+
   // Set the result value of the current top-of-value-stack expression.
   void setResult(Value value) { valueStack_.back().setValue(value); }
 
   // Return the result value of the current top-of-value-stack expression.
   Value getResult() { return valueStack_.back().value(); }
 
   // Return a reference to the top of the control stack.
   ControlItem& controlItem() { return controlStack_.back().controlItem(); }
@@ -532,32 +854,16 @@ class MOZ_STACK_CLASS OpIter : private P
   ControlItem& controlOutermost() { return controlStack_[0].controlItem(); }
 
   // Test whether the control-stack is empty, meaning we've consumed the final
   // end of the function body.
   bool controlStackEmpty() const { return controlStack_.empty(); }
 };
 
 template <typename Policy>
-inline bool OpIter<Policy>::weakMeet(ExprType one, ExprType two,
-                                     ExprType* result) const {
-  if (MOZ_LIKELY(one == two)) {
-    *result = one;
-    return true;
-  }
-
-  if (one.isReference() && two.isReference()) {
-    *result = ExprType::AnyRef;
-    return true;
-  }
-
-  return false;
-}
-
-template <typename Policy>
 inline bool OpIter<Policy>::checkIsSubtypeOf(ValType actual, ValType expected) {
   if (actual == expected) {
     return true;
   }
 
   if (actual.isReference() && expected.isReference() &&
       env_.isRefSubtypeOf(actual, expected)) {
     return true;
@@ -601,233 +907,283 @@ inline bool OpIter<Policy>::fail_ctx(con
 template <typename Policy>
 inline bool OpIter<Policy>::failEmptyStack() {
   return valueStack_.empty() ? fail("popping value from empty stack")
                              : fail("popping value from outside block");
 }
 
 // This function pops exactly one value from the stack, yielding Bottom types in
 // various cases and therefore making it the caller's responsibility to do the
-// right thing for StackType::Bottom. Prefer (pop|top)WithType.
+// right thing for StackType::Bottom. Prefer (pop|top)WithType.  This is an
+// optimization for the super-common case where the caller is statically
+// expecting the resulttype `[valtype]`.
 template <typename Policy>
 inline bool OpIter<Policy>::popStackType(StackType* type, Value* value) {
-  ControlStackEntry<ControlItem>& block = controlStack_.back();
-
-  MOZ_ASSERT(valueStack_.length() >= block.valueStackStart());
-  if (MOZ_UNLIKELY(valueStack_.length() == block.valueStackStart())) {
+  Control& block = controlStack_.back();
+
+  MOZ_ASSERT(valueStack_.length() >= block.valueStackBase());
+  if (MOZ_UNLIKELY(valueStack_.length() == block.valueStackBase())) {
     // If the base of this block's stack is polymorphic, then we can pop a
     // dummy value of any type; it won't be used since we're in unreachable
     // code.
     if (block.polymorphicBase()) {
       *type = StackType::Bottom;
       *value = Value();
 
       // Maintain the invariant that, after a pop, there is always memory
       // reserved to push a value infallibly.
       return valueStack_.reserve(valueStack_.length() + 1);
     }
 
     return failEmptyStack();
   }
 
-  TypeAndValue<Value>& tv = valueStack_.back();
+  TypeAndValue& tv = valueStack_.back();
   *type = tv.type();
   *value = tv.value();
   valueStack_.popBack();
   return true;
 }
 
 // This function pops exactly one value from the stack, checking that it has the
 // expected type which can either be a specific value type or a type variable.
 template <typename Policy>
 inline bool OpIter<Policy>::popWithType(ValType expectedType, Value* value) {
-  StackType stackType(expectedType);
+  StackType stackType;
   if (!popStackType(&stackType, value)) {
     return false;
   }
 
   return stackType == StackType::Bottom ||
          checkIsSubtypeOf(NonBottomToValType(stackType), expectedType);
 }
 
-// This function pops as many types from the stack as determined by the given
-// signature. Currently, all signatures are limited to 0 or 1 types, with
-// ExprType::Void meaning 0 and all other ValTypes meaning 1, but this will be
-// generalized in the future.
+// Pops each of the given expected types (in reverse, because it's a stack).
 template <typename Policy>
-inline bool OpIter<Policy>::popWithType(ExprType expectedType, Value* value) {
-  if (IsVoid(expectedType)) {
-    *value = Value();
+inline bool OpIter<Policy>::popWithType(ResultType expected,
+                                        ValueVector* values) {
+  size_t expectedLength = expected.length();
+  if (!values->resize(expectedLength)) {
+    return false;
+  }
+  for (size_t i = 0; i < expectedLength; i++) {
+    size_t reverseIndex = expectedLength - i - 1;
+    ValType expectedType = expected[reverseIndex];
+    Value* value = &(*values)[reverseIndex];
+    if (!popWithType(expectedType, value)) {
+      return false;
+    }
+  }
+  return true;
+}
+
+// This function is an optimization of the sequence:
+//   popWithType(ResultType, tmp)
+//   push(ResultType, tmp)
+template <typename Policy>
+inline bool OpIter<Policy>::popThenPushType(ResultType expected,
+                                            ValueVector* values) {
+  if (expected.empty()) {
     return true;
   }
 
-  return popWithType(NonVoidToValType(expectedType), value);
-}
-
-// This function is equivalent to: popWithType(expectedType);
-// push(expectedType);
-template <typename Policy>
-inline bool OpIter<Policy>::topWithType(ValType expectedType, Value* value) {
-  ControlStackEntry<ControlItem>& block = controlStack_.back();
-
-  MOZ_ASSERT(valueStack_.length() >= block.valueStackStart());
-  if (valueStack_.length() == block.valueStackStart()) {
-    // If the base of this block's stack is polymorphic, then we can just
-    // pull out a dummy value of the expected type; it won't be used since
-    // we're in unreachable code. We must however push this value onto the
-    // stack since it is now fixed to a specific type by this type
-    // constraint.
-    if (block.polymorphicBase()) {
-      if (!valueStack_.emplaceBack(expectedType, Value())) {
+  Control& block = controlStack_.back();
+
+  size_t expectedLength = expected.length();
+  if (!values->resize(expectedLength)) {
+    return false;
+  }
+
+  for (size_t i = 0; i != expectedLength; i++) {
+    // We're iterating as-if we were popping each expected/actual type one by
+    // one, which means iterating the array of expected results backwards.
+    // The "current" value stack length refers to what the value stack length
+    // would have been if we were popping it.
+    size_t reverseIndex = expectedLength - i - 1;
+    ValType expectedType = expected[reverseIndex];
+    Value* value = &(*values)[reverseIndex];
+    size_t currentValueStackLength = valueStack_.length() - i;
+
+    MOZ_ASSERT(currentValueStackLength >= block.valueStackBase());
+    if (currentValueStackLength == block.valueStackBase()) {
+      if (!block.polymorphicBase()) {
+        return failEmptyStack();
+      }
+
+      // If the base of this block's stack is polymorphic, then we can just
+      // pull out as many fake values as we need to validate; they won't be used
+      // since we're in unreachable code. We must however push these types on
+      // the operand stack since they are now fixed by this constraint.
+      if (!valueStack_.insert(valueStack_.begin() + currentValueStackLength,
+                              TypeAndValue(expectedType))) {
         return false;
       }
 
       *value = Value();
-      return true;
+    } else {
+      TypeAndValue& observed = valueStack_[currentValueStackLength - 1];
+
+      if (observed.type() == StackType::Bottom) {
+        observed.typeRef() = StackType(expectedType);
+        *value = Value();
+      } else {
+        if (!checkIsSubtypeOf(NonBottomToValType(observed.type()),
+                              expectedType)) {
+          return false;
+        }
+
+        *value = observed.value();
+      }
     }
-
-    return failEmptyStack();
   }
-
-  TypeAndValue<Value>& observed = valueStack_.back();
-
-  if (observed.type() == StackType::Bottom) {
-    observed.typeRef() = StackType(expectedType);
-    *value = Value();
+  return true;
+}
+
+// This function checks that the top of the stack is a subtype of expected.
+// Like topWithType, it may insert synthetic StackType::Bottom entries if the
+// block's stack is polymorphic, which happens during unreachable code.  However
+// unlike popThenPushType, it doesn't otherwise modify the value stack to update
+// stack types.  Finally, ensureTopHasType allows passing |nullptr| as |values|
+// to avoid collecting values.
+
+template <typename Policy>
+inline bool OpIter<Policy>::ensureTopHasType(ResultType expected,
+                                             ValueVector* values) {
+  if (expected.empty()) {
     return true;
   }
 
-  if (!checkIsSubtypeOf(NonBottomToValType(observed.type()), expectedType)) {
+  Control& block = controlStack_.back();
+
+  size_t expectedLength = expected.length();
+  if (values && !values->resize(expectedLength)) {
     return false;
   }
 
-  *value = observed.value();
+  for (size_t i = 0; i != expectedLength; i++) {
+    // We're iterating as-if we were popping each expected/actual type one by
+    // one, which means iterating the array of expected results backwards.
+    // The "current" value stack length refers to what the value stack length
+    // would have been if we were popping it.
+    size_t reverseIndex = expectedLength - i - 1;
+    ValType expectedType = expected[reverseIndex];
+    auto collectValue = [&](const Value& v) {
+      if (values) {
+        (*values)[reverseIndex] = v;
+      }
+    };
+    size_t currentValueStackLength = valueStack_.length() - i;
+
+    MOZ_ASSERT(currentValueStackLength >= block.valueStackBase());
+    if (currentValueStackLength == block.valueStackBase()) {
+      if (!block.polymorphicBase()) {
+        return failEmptyStack();
+      }
+
+      // Fill missing values with StackType::Bottom.
+      if (!valueStack_.insert(valueStack_.begin() + currentValueStackLength,
+                              TypeAndValue(StackType::Bottom))) {
+        return false;
+      }
+
+      collectValue(Value());
+    } else {
+      TypeAndValue& observed = valueStack_[currentValueStackLength - 1];
+
+      if (observed.type() == StackType::Bottom) {
+        collectValue(Value());
+      } else {
+        if (!checkIsSubtypeOf(NonBottomToValType(observed.type()),
+                              expectedType)) {
+          return false;
+        }
+
+        collectValue(observed.value());
+      }
+    }
+  }
+
   return true;
 }
 
 template <typename Policy>
-inline bool OpIter<Policy>::topWithType(ExprType expectedType, Value* value) {
-  if (IsVoid(expectedType)) {
-    *value = Value();
-    return true;
-  }
-
-  return topWithType(NonVoidToValType(expectedType), value);
-}
-
-// This function checks that the top of the stack is a subtype of expectedType
-// and returns the value if so.
-template <typename Policy>
-inline bool OpIter<Policy>::topIsType(ValType expectedType,
-                                      StackType* actualType, Value* value) {
-  ControlStackEntry<ControlItem>& block = controlStack_.back();
-
-  MOZ_ASSERT(valueStack_.length() >= block.valueStackStart());
-  if (valueStack_.length() == block.valueStackStart()) {
-    // If the base of this block's stack is polymorphic, then we can just
-    // pull out a dummy value of the expected type; it won't be used since
-    // we're in unreachable code.
-    if (block.polymorphicBase()) {
-      *actualType = StackType::Bottom;
-      *value = Value();
-      return true;
-    }
-
-    return failEmptyStack();
-  }
-
-  TypeAndValue<Value>& observed = valueStack_.back();
-
-  if (observed.type() == StackType::Bottom) {
-    *actualType = StackType::Bottom;
-    *value = Value();
-    return true;
-  }
-
-  if (!checkIsSubtypeOf(NonBottomToValType(observed.type()), expectedType)) {
+inline bool OpIter<Policy>::pushControl(LabelKind kind, BlockType type) {
+  ResultType paramType = type.params();
+
+  ValueVector values;
+  if (!popThenPushType(paramType, &values)) {
     return false;
   }
-
-  *actualType = observed.type();
-  *value = observed.value();
-  return true;
+  MOZ_ASSERT(valueStack_.length() >= paramType.length());
+  uint32_t valueStackBase = valueStack_.length() - paramType.length();
+  return controlStack_.emplaceBack(kind, type, valueStackBase);
 }
 
 template <typename Policy>
-inline bool OpIter<Policy>::pushControl(LabelKind kind, ExprType type) {
-  return controlStack_.emplaceBack(kind, type, valueStack_.length());
+inline bool OpIter<Policy>::checkStackAtEndOfBlock(ResultType* expectedType,
+                                                   ValueVector* values) {
+  Control& block = controlStack_.back();
+  *expectedType = block.type().results();
+
+  MOZ_ASSERT(valueStack_.length() >= block.valueStackBase());
+  if (expectedType->length() < valueStack_.length() - block.valueStackBase()) {
+    return fail("unused values not explicitly dropped by end of block");
+  }
+
+  return popThenPushType(*expectedType, values);
 }
 
 template <typename Policy>
-inline bool OpIter<Policy>::checkStackAtEndOfBlock(ExprType* type,
-                                                   Value* value) {
-  ControlStackEntry<ControlItem>& block = controlStack_.back();
-
-  MOZ_ASSERT(valueStack_.length() >= block.valueStackStart());
-  size_t pushed = valueStack_.length() - block.valueStackStart();
-  if (pushed > (IsVoid(block.resultType()) ? 0u : 1u)) {
-    return fail("unused values not explicitly dropped by end of block");
-  }
-
-  if (!topWithType(block.resultType(), value)) {
-    return false;
-  }
-
-  *type = block.resultType();
-  return true;
-}
-
-template <typename Policy>
-inline bool OpIter<Policy>::getControl(
-    uint32_t relativeDepth, ControlStackEntry<ControlItem>** controlEntry) {
+inline bool OpIter<Policy>::getControl(uint32_t relativeDepth,
+                                       Control** controlEntry) {
   if (relativeDepth >= controlStack_.length()) {
     return fail("branch depth exceeds current nesting level");
   }
 
   *controlEntry = &controlStack_[controlStack_.length() - 1 - relativeDepth];
   return true;
 }
 
 template <typename Policy>
-inline bool OpIter<Policy>::readBlockType(ExprType* type) {
-  uint8_t uncheckedCode;
-  uint32_t uncheckedRefTypeIndex;
-  if (!d_.readBlockType(&uncheckedCode, &uncheckedRefTypeIndex)) {
-    return fail("unable to read block signature");
+inline bool OpIter<Policy>::readBlockType(BlockType* type) {
+  uint8_t nextByte;
+  if (!d_.peekByte(&nextByte)) {
+    return fail("unable to read block type");
+  }
+
+  if (nextByte == uint8_t(TypeCode::BlockVoid)) {
+    d_.uncheckedReadFixedU8();
+    *type = BlockType::VoidToVoid();
+    return true;
   }
 
-  bool known = false;
-  switch (uncheckedCode) {
-    case uint8_t(ExprType::Void):
-    case uint8_t(ExprType::I32):
-    case uint8_t(ExprType::I64):
-    case uint8_t(ExprType::F32):
-    case uint8_t(ExprType::F64):
-      known = true;
-      break;
-    case uint8_t(ExprType::FuncRef):
-    case uint8_t(ExprType::AnyRef):
-#ifdef ENABLE_WASM_REFTYPES
-      known = env_.refTypesEnabled();
+  if ((nextByte & SLEB128SignMask) == SLEB128SignBit) {
+    ValType v;
+    if (!readValType(&v)) {
+      return false;
+    }
+    *type = BlockType::VoidToSingle(v);
+    return true;
+  }
+
+#ifdef ENABLE_WASM_MULTI_VALUE
+  int32_t x;
+  if (!d_.readVarS32(&x) || x < 0 || uint32_t(x) >= env_.types.length()) {
+    return fail("invalid block type type index");
+  }
+
+  if (!env_.types[x].isFuncType()) {
+    return fail("block type type index must be func type");
+  }
+
+  *type = BlockType::Func(env_.types[x].funcType());
+  return true;
+#else
+  return fail("invalid block type reference");
 #endif
-      break;
-    case uint8_t(ExprType::Ref):
-      known = env_.gcTypesEnabled() && uncheckedRefTypeIndex < MaxTypes &&
-              uncheckedRefTypeIndex < env_.types.length();
-      break;
-    case uint8_t(ExprType::Limit):
-      break;
-  }
-
-  if (!known) {
-    return fail("invalid inline block type");
-  }
-
-  *type = ExprType(ExprType::Code(uncheckedCode), uncheckedRefTypeIndex);
-  return true;
 }
 
 template <typename Policy>
 inline bool OpIter<Policy>::readOp(OpBytes* op) {
   MOZ_ASSERT(!controlStack_.empty());
 
   offsetOfLastReadOp_ = d_.currentOffset();
 
@@ -850,271 +1206,240 @@ inline void OpIter<Policy>::peekOp(OpByt
     op->b0 = uint16_t(Op::Limit);
   }
 
   d_.rollbackPosition(pos);
 }
 
 template <typename Policy>
 inline bool OpIter<Policy>::readFunctionStart(uint32_t funcIndex) {
+  MOZ_ASSERT(thenParamStack_.empty());
   MOZ_ASSERT(valueStack_.empty());
   MOZ_ASSERT(controlStack_.empty());
   MOZ_ASSERT(op_.b0 == uint16_t(Op::Limit));
-  const ValTypeVector& results = env_.funcTypes[funcIndex]->results();
-  ExprType ret = ExprType::Void;
-  if (results.length()) {
-    MOZ_ASSERT(results.length() == 1, "multi-value return unimplemented");
-    ret = ExprType(results[0]);
-  }
-  return pushControl(LabelKind::Body, ret);
+  BlockType type = BlockType::FuncResults(*env_.funcTypes[funcIndex]);
+  return pushControl(LabelKind::Body, type);
 }
 
 template <typename Policy>
 inline bool OpIter<Policy>::readFunctionEnd(const uint8_t* bodyEnd) {
   if (d_.currentPosition() != bodyEnd) {
     return fail("function body length mismatch");
   }
 
   if (!controlStack_.empty()) {
     return fail("unbalanced function body control flow");
   }
+  MOZ_ASSERT(thenParamStack_.empty());
 
 #ifdef DEBUG
   op_ = OpBytes(Op::Limit);
 #endif
   valueStack_.clear();
   return true;
 }
 
 template <typename Policy>
-inline bool OpIter<Policy>::readReturn(Value* value) {
+inline bool OpIter<Policy>::readReturn(ValueVector* values) {
   MOZ_ASSERT(Classify(op_) == OpKind::Return);
 
-  ControlStackEntry<ControlItem>& body = controlStack_[0];
+  Control& body = controlStack_[0];
   MOZ_ASSERT(body.kind() == LabelKind::Body);
 
-  if (!popWithType(body.resultType(), value)) {
+  if (!popWithType(body.resultType(), values)) {
     return false;
   }
 
   afterUnconditionalBranch();
   return true;
 }
 
 template <typename Policy>
 inline bool OpIter<Policy>::readBlock() {
   MOZ_ASSERT(Classify(op_) == OpKind::Block);
 
-  ExprType type = ExprType::Limit;
+  BlockType type;
   if (!readBlockType(&type)) {
     return false;
   }
 
   return pushControl(LabelKind::Block, type);
 }
 
 template <typename Policy>
 inline bool OpIter<Policy>::readLoop() {
   MOZ_ASSERT(Classify(op_) == OpKind::Loop);
 
-  ExprType type = ExprType::Limit;
+  BlockType type;
   if (!readBlockType(&type)) {
     return false;
   }
 
   return pushControl(LabelKind::Loop, type);
 }
 
 template <typename Policy>
 inline bool OpIter<Policy>::readIf(Value* condition) {
   MOZ_ASSERT(Classify(op_) == OpKind::If);
 
-  ExprType type = ExprType::Limit;
+  BlockType type;
   if (!readBlockType(&type)) {
     return false;
   }
 
   if (!popWithType(ValType::I32, condition)) {
     return false;
   }
 
-  return pushControl(LabelKind::Then, type);
+  if (!pushControl(LabelKind::Then, type)) {
+    return false;
+  }
+
+  size_t paramsLength = type.params().length();
+  return thenParamStack_.append(valueStack_.end() - paramsLength, paramsLength);
 }
 
 template <typename Policy>
-inline bool OpIter<Policy>::readElse(ExprType* type, Value* value) {
+inline bool OpIter<Policy>::readElse(ResultType* thenType,
+                                     ValueVector* values) {
   MOZ_ASSERT(Classify(op_) == OpKind::Else);
 
-  // Finish checking the then-block.
-
-  if (!checkStackAtEndOfBlock(type, value)) {
-    return false;
-  }
-
-  ControlStackEntry<ControlItem>& block = controlStack_.back();
-
+  Control& block = controlStack_.back();
   if (block.kind() != LabelKind::Then) {
     return fail("else can only be used within an if");
   }
 
-  // Switch to the else-block.
-
-  if (!IsVoid(block.resultType())) {
-    valueStack_.popBack();
+  if (!checkStackAtEndOfBlock(thenType, values)) {
+    return false;
   }
 
-  MOZ_ASSERT(valueStack_.length() == block.valueStackStart());
+  // Restore to the entry state of the then block. Since the then block may
+  // clobbered any value in the block's params, we must restore from a
+  // snapshot.
+  valueStack_.shrinkTo(block.valueStackBase());
+  size_t thenParamsLength = block.type().params().length();
+  MOZ_ASSERT(thenParamStack_.length() >= thenParamsLength);
+  valueStack_.infallibleAppend(thenParamStack_.end() - thenParamsLength,
+                               thenParamsLength);
+  thenParamStack_.shrinkBy(thenParamsLength);
 
   block.switchToElse();
   return true;
 }
 
 template <typename Policy>
-inline bool OpIter<Policy>::readEnd(LabelKind* kind, ExprType* type,
-                                    Value* value) {
+inline bool OpIter<Policy>::readEnd(LabelKind* kind, ResultType* type,
+                                    ValueVector* values) {
   MOZ_ASSERT(Classify(op_) == OpKind::End);
 
-  if (!checkStackAtEndOfBlock(type, value)) {
+  if (!checkStackAtEndOfBlock(type, values)) {
     return false;
   }
 
-  ControlStackEntry<ControlItem>& block = controlStack_.back();
+  Control& block = controlStack_.back();
 
   // If an `if` block ends with `end` instead of `else`, then we must
   // additionally validate that the then-block doesn't push anything.
-  if (block.kind() == LabelKind::Then && !IsVoid(block.resultType())) {
+  if (block.kind() == LabelKind::Then && !block.resultType().empty()) {
     return fail("if without else with a result value");
   }
 
   *kind = block.kind();
   return true;
 }
 
 template <typename Policy>
 inline void OpIter<Policy>::popEnd() {
   MOZ_ASSERT(Classify(op_) == OpKind::End);
 
   controlStack_.popBack();
 }
 
 template <typename Policy>
 inline bool OpIter<Policy>::checkBranchValue(uint32_t relativeDepth,
-                                             ExprType* type, Value* value) {
-  ControlStackEntry<ControlItem>* block = nullptr;
+                                             ResultType* type,
+                                             ValueVector* values) {
+  Control* block = nullptr;
   if (!getControl(relativeDepth, &block)) {
     return false;
   }
 
   *type = block->branchTargetType();
-  return topWithType(*type, value);
+  return popThenPushType(*type, values);
 }
 
 template <typename Policy>
-inline bool OpIter<Policy>::readBr(uint32_t* relativeDepth, ExprType* type,
-                                   Value* value) {
+inline bool OpIter<Policy>::readBr(uint32_t* relativeDepth, ResultType* type,
+                                   ValueVector* values) {
   MOZ_ASSERT(Classify(op_) == OpKind::Br);
 
   if (!readVarU32(relativeDepth)) {
     return fail("unable to read br depth");
   }
 
-  if (!checkBranchValue(*relativeDepth, type, value)) {
+  if (!checkBranchValue(*relativeDepth, type, values)) {
     return false;
   }
 
   afterUnconditionalBranch();
   return true;
 }
 
 template <typename Policy>
-inline bool OpIter<Policy>::readBrIf(uint32_t* relativeDepth, ExprType* type,
-                                     Value* value, Value* condition) {
+inline bool OpIter<Policy>::readBrIf(uint32_t* relativeDepth, ResultType* type,
+                                     ValueVector* values, Value* condition) {
   MOZ_ASSERT(Classify(op_) == OpKind::BrIf);
 
   if (!readVarU32(relativeDepth)) {
     return fail("unable to read br_if depth");
   }
 
   if (!popWithType(ValType::I32, condition)) {
     return false;
   }
 
-  return checkBranchValue(*relativeDepth, type, value);
+  return checkBranchValue(*relativeDepth, type, values);
 }
 
 #define UNKNOWN_ARITY UINT32_MAX
 
 template <typename Policy>
 inline bool OpIter<Policy>::checkBrTableEntry(uint32_t* relativeDepth,
-                                              uint32_t* branchValueArity,
-                                              ExprType* branchValueType,
-                                              Value* branchValue) {
+                                              ResultType prevType,
+                                              ResultType* type,
+                                              ValueVector* branchValues) {
   if (!readVarU32(relativeDepth)) {
     return false;
   }
 
-  ControlStackEntry<ControlItem>* block = nullptr;
+  Control* block = nullptr;
   if (!getControl(*relativeDepth, &block)) {
     return false;
   }
 
-  // For the first encountered branch target, do a normal branch value type
-  // check which will change *branchValueArity and *branchValueType to a
-  // non-sentinel value. For all subsequent branch targets, check that the
-  // branch target arity and type matches the now-known branch value arity
-  // and type. This will need to change with multi-value.
-  uint32_t labelTypeArity = IsVoid(block->branchTargetType()) ? 0 : 1;
-
-  if (*branchValueArity == UNKNOWN_ARITY) {
-    *branchValueArity = labelTypeArity;
-  } else if (*branchValueArity != labelTypeArity) {
-    return fail("br_table operand must be subtype of all target types");
-  }
-
-  // If the label types are void, no need to check type on the stack
-  if (labelTypeArity == 0) {
-    *branchValueType = ExprType::Void;
-    *branchValue = Value();
-    return true;
+  *type = block->branchTargetType();
+
+  if (prevType != ResultType()) {
+    if (prevType.length() != type->length()) {
+      return fail("br_table targets must all have the same arity");
+    }
+
+    // Avoid re-collecting the same values for subsequent branch targets.
+    branchValues = nullptr;
   }
 
-  // Check that the value on the stack is a subtype of the label
-  StackType actualBranchValueType;
-  if (!topIsType(NonVoidToValType(block->branchTargetType()),
-                 &actualBranchValueType, branchValue)) {
-    return false;
-  }
-
-  // If the value on the stack is the bottom type, it will by definition be a
-  // subtype of every possible label type. This also implies that the label
-  // types may not have a subtype relation, and so we cannot report a branch
-  // value type. Fortunately this only happens in unreachable code, where we
-  // don't use the branch value type.
-  if (actualBranchValueType == StackType::Bottom) {
-    *branchValueType = ExprType::Limit;
-    return true;
-  }
-
-  // Compute the branch value type in all other cases
-
-  if (*branchValueType == ExprType::Limit) {
-    *branchValueType = block->branchTargetType();
-  } else if (!weakMeet(*branchValueType, block->branchTargetType(),
-                       branchValueType)) {
-    return fail("br_table operand must be subtype of all target types");
-  }
-
-  return true;
+  return ensureTopHasType(*type, branchValues);
 }
 
 template <typename Policy>
 inline bool OpIter<Policy>::readBrTable(Uint32Vector* depths,
                                         uint32_t* defaultDepth,
-                                        ExprType* branchValueType,
-                                        Value* branchValue, Value* index) {
+                                        ResultType* defaultBranchType,
+                                        ValueVector* branchValues,
+                                        Value* index) {
   MOZ_ASSERT(Classify(op_) == OpKind::BrTable);
 
   uint32_t tableLength;
   if (!readVarU32(&tableLength)) {
     return fail("unable to read br_table table length");
   }
 
   if (tableLength > MaxBrTableElems) {
@@ -1124,32 +1449,32 @@ inline bool OpIter<Policy>::readBrTable(
   if (!popWithType(ValType::I32, index)) {
     return false;
   }
 
   if (!depths->resize(tableLength)) {
     return false;
   }
 
-  uint32_t branchValueArity = UNKNOWN_ARITY;
-  *branchValueType = ExprType::Limit;
-
+  ResultType prevBranchType;
   for (uint32_t i = 0; i < tableLength; i++) {
-    if (!checkBrTableEntry(&(*depths)[i], &branchValueArity, branchValueType,
-                           branchValue)) {
+    ResultType branchType;
+    if (!checkBrTableEntry(&(*depths)[i], prevBranchType, &branchType,
+                           branchValues)) {
       return false;
     }
+    prevBranchType = branchType;
   }
 
-  if (!checkBrTableEntry(defaultDepth, &branchValueArity, branchValueType,
-                         branchValue)) {
+  if (!checkBrTableEntry(defaultDepth, prevBranchType, defaultBranchType,
+                         branchValues)) {
     return false;
   }
 
-  MOZ_ASSERT(branchValueArity != UNKNOWN_ARITY);
+  MOZ_ASSERT(*defaultBranchType != ResultType());
 
   afterUnconditionalBranch();
   return true;
 }
 
 #undef UNKNOWN_ARITY
 
 template <typename Policy>
@@ -1340,17 +1665,17 @@ inline bool OpIter<Policy>::readTeeStore
   if (!popWithType(resultType, value)) {
     return false;
   }
 
   if (!readLinearMemoryAddress(byteSize, addr)) {
     return false;
   }
 
-  infalliblePush(TypeAndValue<Value>(resultType, *value));
+  infalliblePush(TypeAndValue(resultType, *value));
   return true;
 }
 
 template <typename Policy>
 inline bool OpIter<Policy>::readNop() {
   MOZ_ASSERT(Classify(op_) == OpKind::Nop);
 
   return true;
@@ -1506,17 +1831,23 @@ inline bool OpIter<Policy>::readTeeLocal
   if (!readVarU32(id)) {
     return false;
   }
 
   if (*id >= locals.length()) {
     return fail("local.set index out of range");
   }
 
-  return topWithType(locals[*id], value);
+  ValueVector single;
+  if (!popThenPushType(ResultType::Single(locals[*id]), &single)) {
+    return false;
+  }
+
+  *value = single[0];
+  return true;
 }
 
 template <typename Policy>
 inline bool OpIter<Policy>::readGetGlobal(uint32_t* id) {
   MOZ_ASSERT(Classify(op_) == OpKind::GetGlobal);
 
   if (!readVarU32(id)) {
     return false;
@@ -1559,17 +1890,24 @@ inline bool OpIter<Policy>::readTeeGloba
   if (*id >= env_.globals.length()) {
     return fail("global.set index out of range");
   }
 
   if (!env_.globals[*id].isMutable()) {
     return fail("can't write an immutable global");
   }
 
-  return topWithType(env_.globals[*id].type(), value);
+  ValueVector single;
+  if (!popThenPushType(ResultType::Single(env_.globals[*id].type()), &single)) {
+    return false;
+  }
+
+  MOZ_ASSERT(single.length() == 1);
+  *value = single[0];
+  return true;
 }
 
 template <typename Policy>
 inline bool OpIter<Policy>::readI32Const(int32_t* i32) {
   MOZ_ASSERT(Classify(op_) == OpKind::I32);
 
   return readVarS32(i32) && push(ValType::I32);
 }
@@ -1603,24 +1941,24 @@ inline bool OpIter<Policy>::readRefFunc(
     return fail("unable to read function index");
   }
   if (*funcTypeIndex >= env_.funcTypes.length()) {
     return fail("function index out of range");
   }
   if (!env_.validForRefFunc.getBit(*funcTypeIndex)) {
     return fail("function index is not in an element segment");
   }
-  return push(StackType(ValType::FuncRef));
+  return push(ValType::FuncRef);
 }
 
 template <typename Policy>
 inline bool OpIter<Policy>::readRefNull() {
   MOZ_ASSERT(Classify(op_) == OpKind::RefNull);
 
-  return push(StackType(ValType::NullRef));
+  return push(ValType::NullRef);
 }
 
 template <typename Policy>
 inline bool OpIter<Policy>::readValType(ValType* type) {
   return d_.readValType(env_.types, env_.refTypesEnabled(),
                         env_.gcTypesEnabled(), type);
 }
 
@@ -1667,17 +2005,17 @@ inline bool OpIter<Policy>::readCall(uin
   }
 
   const FuncType& funcType = *env_.funcTypes[*funcTypeIndex];
 
   if (!popCallArgs(funcType.args(), argValues)) {
     return false;
   }
 
-  return push(funcType.ret());
+  return push(ResultType::Vector(funcType.results()));
 }
 
 template <typename Policy>
 inline bool OpIter<Policy>::readCallIndirect(uint32_t* funcTypeIndex,
                                              uint32_t* tableIndex,
                                              Value* callee,
                                              ValueVector* argValues) {
   MOZ_ASSERT(Classify(op_) == OpKind::CallIndirect);
@@ -1720,17 +2058,17 @@ inline bool OpIter<Policy>::readCallIndi
     return fail("cannot expose reference type");
   }
 #endif
 
   if (!popCallArgs(funcType.args(), argValues)) {
     return false;
   }
 
-  return push(funcType.ret());
+  return push(ResultType::Vector(funcType.results()));
 }
 
 template <typename Policy>
 inline bool OpIter<Policy>::readOldCallDirect(uint32_t numFuncImports,
                                               uint32_t* funcTypeIndex,
                                               ValueVector* argValues) {
   MOZ_ASSERT(Classify(op_) == OpKind::OldCallDirect);
 
@@ -1750,17 +2088,17 @@ inline bool OpIter<Policy>::readOldCallD
   }
 
   const FuncType& funcType = *env_.funcTypes[*funcTypeIndex];
 
   if (!popCallArgs(funcType.args(), argValues)) {
     return false;
   }
 
-  return push(funcType.ret());
+  return push(ResultType::Vector(funcType.results()));
 }
 
 template <typename Policy>
 inline bool OpIter<Policy>::readOldCallIndirect(uint32_t* funcTypeIndex,
                                                 Value* callee,
                                                 ValueVector* argValues) {
   MOZ_ASSERT(Classify(op_) == OpKind::OldCallIndirect);
 
@@ -1781,21 +2119,17 @@ inline bool OpIter<Policy>::readOldCallI
   if (!popCallArgs(funcType.args(), argValues)) {
     return false;
   }
 
   if (!popWithType(ValType::I32, callee)) {
     return false;
   }
 
-  if (!push(funcType.ret())) {
-    return false;
-  }
-
-  return true;
+  return push(ResultType::Vector(funcType.results()));
 }
 
 template <typename Policy>
 inline bool OpIter<Policy>::readWake(LinearMemoryAddress<Value>* addr,
                                      Value* count) {
   MOZ_ASSERT(Classify(op_) == OpKind::Wake);
 
   if (!env_.usesSharedMemory()) {
@@ -2378,15 +2712,15 @@ inline bool OpIter<Policy>::readStructNa
 
 }  // namespace wasm
 }  // namespace js
 
 namespace mozilla {
 
 // Specialize IsPod for the Nothing specializations.
 template <>
-struct IsPod<js::wasm::TypeAndValue<Nothing>> : TrueType {};
+struct IsPod<js::wasm::TypeAndValueT<Nothing>> : TrueType {};
 template <>
 struct IsPod<js::wasm::ControlStackEntry<Nothing>> : TrueType {};
 
 }  // namespace mozilla
 
 #endif  // wasm_op_iter_h
--- a/js/src/wasm/WasmTextToBinary.cpp
+++ b/js/src/wasm/WasmTextToBinary.cpp
@@ -6173,22 +6173,33 @@ static bool EncodeExprList(Encoder& e, c
   for (size_t i = 0; i < v.length(); i++) {
     if (!EncodeExpr(e, *v[i])) {
       return false;
     }
   }
   return true;
 }
 
+static bool EncodeBlockType(Encoder& e, AstExprType& t) {
+  ExprType type = t.type();
+  static_assert(size_t(TypeCode::Limit) <= UINT8_MAX, "fits");
+  MOZ_ASSERT(size_t(type.code()) < size_t(TypeCode::Limit));
+  if (type.isRef()) {
+    return e.writeFixedU8(uint8_t(ExprType::Ref)) &&
+           e.writeVarU32(type.refTypeIndex());
+  }
+  return e.writeFixedU8(uint8_t(type.code()));
+}
+
 static bool EncodeBlock(Encoder& e, AstBlock& b) {
   if (!e.writeOp(b.op())) {
     return false;
   }
 
-  if (!e.writeBlockType(b.type().type())) {
+  if (!EncodeBlockType(e, b.type())) {
     return false;
   }
 
   if (!EncodeExprList(e, b.exprs())) {
     return false;
   }
 
   if (!e.writeOp(Op::End)) {
@@ -6363,17 +6374,17 @@ static bool EncodeExtraConversionOperato
   return EncodeExpr(e, *b.operand()) && e.writeOp(b.op());
 }
 
 static bool EncodeIf(Encoder& e, AstIf& i) {
   if (!EncodeExpr(e, i.cond()) || !e.writeOp(Op::If)) {
     return false;
   }
 
-  if (!e.writeBlockType(i.type().type())) {
+  if (!EncodeBlockType(e, i.type())) {
     return false;
   }
 
   if (!EncodeExprList(e, i.thenExprs())) {
     return false;
   }
 
   if (i.hasElse()) {
--- a/js/src/wasm/WasmTypes.cpp
+++ b/js/src/wasm/WasmTypes.cpp
@@ -222,31 +222,16 @@ size_t FuncType::serializedSize() const 
 }
 
 uint8_t* FuncType::serialize(uint8_t* cursor) const {
   cursor = SerializePodVector(cursor, results_);
   cursor = SerializePodVector(cursor, args_);
   return cursor;
 }
 
-namespace js {
-namespace wasm {
-
-// ExprType is not POD while ReadScalar requires POD, so specialize.
-template <>
-inline const uint8_t* ReadScalar<ExprType>(const uint8_t* src, ExprType* dst) {
-  static_assert(sizeof(PackedTypeCode) == sizeof(ExprType),
-                "ExprType must carry only a PackedTypeCode");
-  memcpy(dst->packedPtr(), src, sizeof(PackedTypeCode));
-  return src + sizeof(*dst);
-}
-
-}  // namespace wasm
-}  // namespace js
-
 const uint8_t* FuncType::deserialize(const uint8_t* cursor) {
   cursor = DeserializePodVector(cursor, &results_);
   if (!cursor) {
     return nullptr;
   }
   return DeserializePodVector(cursor, &args_);
 }
 
--- a/js/src/wasm/WasmTypes.h
+++ b/js/src/wasm/WasmTypes.h
@@ -208,36 +208,38 @@ typedef RefPtr<const ShareableBytes> Sha
 // DO NOT use PackedTypeCode as a cast.  ALWAYS go via PackTypeCode().
 
 enum class PackedTypeCode : uint32_t {};
 
 static_assert(std::is_pod<PackedTypeCode>::value,
               "must be POD to be simply serialized/deserialized");
 
 const uint32_t NoTypeCode = 0xFF;          // Only use these
-const uint32_t NoRefTypeIndex = 0xFFFFFF;  //   with PackedTypeCode
-
-static inline PackedTypeCode InvalidPackedTypeCode() {
-  return PackedTypeCode((NoRefTypeIndex << 8) | NoTypeCode);
-}
-
-static inline PackedTypeCode PackTypeCode(TypeCode tc) {
-  MOZ_ASSERT(uint32_t(tc) <= 0xFF);
-  MOZ_ASSERT(tc != TypeCode::Ref);
-  return PackedTypeCode((NoRefTypeIndex << 8) | uint32_t(tc));
-}
+const uint32_t NoRefTypeIndex = 0x3FFFFF;  //   with PackedTypeCode
 
 static inline PackedTypeCode PackTypeCode(TypeCode tc, uint32_t refTypeIndex) {
   MOZ_ASSERT(uint32_t(tc) <= 0xFF);
   MOZ_ASSERT_IF(tc != TypeCode::Ref, refTypeIndex == NoRefTypeIndex);
   MOZ_ASSERT_IF(tc == TypeCode::Ref, refTypeIndex <= MaxTypes);
-  static_assert(MaxTypes < (1 << (32 - 8)), "enough bits");
+  // A PackedTypeCode should be representable in a single word, so in the
+  // smallest case, 32 bits.  However sometimes 2 bits of the word may be taken
+  // by a pointer tag; for that reason, limit to 30 bits; and then there's the
+  // 8-bit typecode, so 22 bits left for the type index.
+  static_assert(MaxTypes < (1 << (30 - 8)), "enough bits");
   return PackedTypeCode((refTypeIndex << 8) | uint32_t(tc));
 }
 
+static inline PackedTypeCode PackTypeCode(TypeCode tc) {
+  return PackTypeCode(tc, NoRefTypeIndex);
+}
+
+static inline PackedTypeCode InvalidPackedTypeCode() {
+  return PackedTypeCode(NoTypeCode);
+}
+
 static inline PackedTypeCode PackedTypeCodeFromBits(uint32_t bits) {
   return PackTypeCode(TypeCode(bits & 255), bits >> 8);
 }
 
 static inline bool IsValid(PackedTypeCode ptc) {
   return (uint32_t(ptc) & 255) != NoTypeCode;
 }
 
--- a/js/src/wasm/WasmValidate.cpp
+++ b/js/src/wasm/WasmValidate.cpp
@@ -439,25 +439,34 @@ bool wasm::DecodeValidatedLocalEntries(D
     }
   }
 
   return true;
 }
 
 // Function body validation.
 
+class NothingVector {
+  Nothing unused_;
+
+ public:
+  bool resize(size_t length) { return true; }
+  Nothing& operator[](size_t) { return unused_; }
+  Nothing& back() { return unused_; }
+};
+
 struct ValidatingPolicy {
   typedef Nothing Value;
+  typedef NothingVector ValueVector;
   typedef Nothing ControlItem;
 };
 
 typedef OpIter<ValidatingPolicy> ValidatingOpIter;
 
 static bool DecodeFunctionBodyExprs(const ModuleEnvironment& env,
-                                    // FIXME(1401675): Replace with BlockType.
                                     uint32_t funcIndex,
                                     const ValTypeVector& locals,
                                     const uint8_t* bodyEnd, Decoder* d) {
   ValidatingOpIter iter(env, *d);
 
   if (!iter.readFunctionStart(funcIndex)) {
     return false;
   }
@@ -468,42 +477,43 @@ static bool DecodeFunctionBodyExprs(cons
 
   while (true) {
     OpBytes op;
     if (!iter.readOp(&op)) {
       return false;
     }
 
     Nothing nothing;
+    NothingVector nothings;
+    ResultType unusedType;
 
     switch (op.b0) {
       case uint16_t(Op::End): {
         LabelKind unusedKind;
-        ExprType unusedType;
-        if (!iter.readEnd(&unusedKind, &unusedType, &nothing)) {
+        if (!iter.readEnd(&unusedKind, &unusedType, &nothings)) {
           return false;
         }
         iter.popEnd();
         if (iter.controlStackEmpty()) {
           return iter.readFunctionEnd(bodyEnd);
         }
         break;
       }
       case uint16_t(Op::Nop):
         CHECK(iter.readNop());
       case uint16_t(Op::Drop):
         CHECK(iter.readDrop());
       case uint16_t(Op::Call): {
         uint32_t unusedIndex;
-        ValidatingOpIter::ValueVector unusedArgs;
+        NothingVector unusedArgs;
         CHECK(iter.readCall(&unusedIndex, &unusedArgs));
       }
       case uint16_t(Op::CallIndirect): {
         uint32_t unusedIndex, unusedIndex2;
-        ValidatingOpIter::ValueVector unusedArgs;
+        NothingVector unusedArgs;
         CHECK(iter.readCallIndirect(&unusedIndex, &unusedIndex2, &nothing,
                                     &unusedArgs));
       }
       case uint16_t(Op::I32Const): {
         int32_t unused;
         CHECK(iter.readI32Const(&unused));
       }
       case uint16_t(Op::I64Const): {
@@ -568,20 +578,18 @@ static bool DecodeFunctionBodyExprs(cons
                               &nothing));
       }
       case uint16_t(Op::Block):
         CHECK(iter.readBlock());
       case uint16_t(Op::Loop):
         CHECK(iter.readLoop());
       case uint16_t(Op::If):
         CHECK(iter.readIf(&nothing));
-      case uint16_t(Op::Else): {
-        ExprType type;
-        CHECK(iter.readElse(&type, &nothing));
-      }
+      case uint16_t(Op::Else):
+        CHECK(iter.readElse(&unusedType, &nothings));
       case uint16_t(Op::I32Clz):
       case uint16_t(Op::I32Ctz):
       case uint16_t(Op::I32Popcnt):
         CHECK(iter.readUnary(ValType::I32, &nothing));
       case uint16_t(Op::I64Clz):
       case uint16_t(Op::I64Ctz):
       case uint16_t(Op::I64Popcnt):
         CHECK(iter.readUnary(ValType::I64, &nothing));
@@ -810,33 +818,30 @@ static bool DecodeFunctionBodyExprs(cons
         CHECK(iter.readStore(ValType::F64, 8, &addr, &nothing));
       }
       case uint16_t(Op::MemoryGrow):
         CHECK(iter.readMemoryGrow(&nothing));
       case uint16_t(Op::MemorySize):
         CHECK(iter.readMemorySize());
       case uint16_t(Op::Br): {
         uint32_t unusedDepth;
-        ExprType unusedType;
-        CHECK(iter.readBr(&unusedDepth, &unusedType, &nothing));
+        CHECK(iter.readBr(&unusedDepth, &unusedType, &nothings));
       }
       case uint16_t(Op::BrIf): {
         uint32_t unusedDepth;
-        ExprType unusedType;
-        CHECK(iter.readBrIf(&unusedDepth, &unusedType, &nothing, &nothing));
+        CHECK(iter.readBrIf(&unusedDepth, &unusedType, &nothings, &nothing));
       }
       case uint16_t(Op::BrTable): {
         Uint32Vector unusedDepths;
         uint32_t unusedDefault;
-        ExprType unusedType;
         CHECK(iter.readBrTable(&unusedDepths, &unusedDefault, &unusedType,
-                               &nothing, &nothing));
+                               &nothings, &nothing));
       }
       case uint16_t(Op::Return):
-        CHECK(iter.readReturn(&nothing));
+        CHECK(iter.readReturn(&nothings));
       case uint16_t(Op::Unreachable):
         CHECK(iter.readUnreachable());
       case uint16_t(Op::MiscPrefix): {
         switch (op.b1) {
           case uint32_t(MiscOp::I32TruncSSatF32):
           case uint32_t(MiscOp::I32TruncUSatF32):
             CHECK(iter.readConversion(ValType::F32, ValType::I32, &nothing));
           case uint32_t(MiscOp::I32TruncSSatF64):
@@ -953,17 +958,17 @@ static bool DecodeFunctionBodyExprs(cons
           }
 #endif
 #ifdef ENABLE_WASM_GC
           case uint32_t(MiscOp::StructNew): {
             if (!env.gcTypesEnabled()) {
               return iter.unrecognizedOpcode(&op);
             }
             uint32_t unusedUint;
-            ValidatingOpIter::ValueVector unusedArgs;
+            NothingVector unusedArgs;
             CHECK(iter.readStructNew(&unusedUint, &unusedArgs));
           }
           case uint32_t(MiscOp::StructGet): {
             if (!env.gcTypesEnabled()) {
               return iter.unrecognizedOpcode(&op);
             }
             uint32_t unusedUint1, unusedUint2;
             CHECK(iter.readStructGet(&unusedUint1, &unusedUint2, &nothing));
@@ -1205,20 +1210,18 @@ static bool DecodeFunctionBodyExprs(cons
   MOZ_CRASH("unreachable");
 
 #undef CHECK
 }
 
 bool wasm::ValidateFunctionBody(const ModuleEnvironment& env,
                                 uint32_t funcIndex, uint32_t bodySize,
                                 Decoder& d) {
-  const FuncType& funcType = *env.funcTypes[funcIndex];
-
   ValTypeVector locals;
-  if (!locals.appendAll(funcType.args())) {
+  if (!locals.appendAll(env.funcTypes[funcIndex]->args())) {
     return false;
   }
 
   const uint8_t* bodyBegin = d.currentPosition();
 
   if (!DecodeLocalEntries(d, env.types, env.refTypesEnabled(),
                           env.gcTypesEnabled(), &locals)) {
     return false;
--- a/js/src/wasm/WasmValidate.h
+++ b/js/src/wasm/WasmValidate.h
@@ -391,25 +391,16 @@ class Encoder {
     static_assert(size_t(TypeCode::Limit) <= UINT8_MAX, "fits");
     MOZ_ASSERT(size_t(type.code()) < size_t(TypeCode::Limit));
     if (type.isRef()) {
       return writeFixedU8(uint8_t(TypeCode::Ref)) &&
              writeVarU32(type.refTypeIndex());
     }
     return writeFixedU8(uint8_t(type.code()));
   }
-  MOZ_MUST_USE bool writeBlockType(ExprType type) {
-    static_assert(size_t(TypeCode::Limit) <= UINT8_MAX, "fits");
-    MOZ_ASSERT(size_t(type.code()) < size_t(TypeCode::Limit));
-    if (type.isRef()) {
-      return writeFixedU8(uint8_t(ExprType::Ref)) &&
-             writeVarU32(type.refTypeIndex());
-    }
-    return writeFixedU8(uint8_t(type.code()));
-  }
   MOZ_MUST_USE bool writeOp(Op op) {
     static_assert(size_t(Op::Limit) == 256, "fits");
     MOZ_ASSERT(size_t(op) < size_t(Op::Limit));
     return writeFixedU8(uint8_t(op));
   }
   MOZ_MUST_USE bool writeOp(MiscOp op) {
     MOZ_ASSERT(size_t(op) < size_t(MiscOp::Limit));
     return writeFixedU8(uint8_t(Op::MiscPrefix)) && writeVarU32(uint32_t(op));
@@ -621,16 +612,26 @@ class Decoder {
   }
   // pos must be a value previously returned from currentPosition.
   void rollbackPosition(const uint8_t* pos) { cur_ = pos; }
   const uint8_t* currentPosition() const { return cur_; }
   size_t currentOffset() const { return offsetInModule_ + (cur_ - beg_); }
   const uint8_t* begin() const { return beg_; }
   const uint8_t* end() const { return end_; }
 
+  // Peek at the next byte, if it exists, without advancing the position.
+
+  bool peekByte(uint8_t* byte) {
+    if (done()) {
+      return false;
+    }
+    *byte = *cur_;
+    return true;
+  }
+
   // Fixed-size encoding operations simply copy the literal bytes (without
   // attempting to align).
 
   MOZ_MUST_USE bool readFixedU8(uint8_t* i) { return read<uint8_t>(i); }
   MOZ_MUST_USE bool readFixedU32(uint32_t* u) { return read<uint32_t>(u); }
   MOZ_MUST_USE bool readFixedF32(float* f) { return read<float>(f); }
   MOZ_MUST_USE bool readFixedF64(double* d) { return read<double>(d); }
 
@@ -703,30 +704,16 @@ class Decoder {
     if (!readValType(types.length(), refTypesEnabled, gcTypesEnabled, type)) {
       return false;
     }
     if (type->isRef() && !types[type->refTypeIndex()].isStructType()) {
       return fail("ref does not reference a struct type");
     }
     return true;
   }
-  MOZ_MUST_USE bool readBlockType(uint8_t* code, uint32_t* refTypeIndex) {
-    static_assert(size_t(TypeCode::Limit) <= UINT8_MAX, "fits");
-    if (!readFixedU8(code)) {
-      return false;
-    }
-    if (*code == uint8_t(TypeCode::Ref)) {
-      if (!readVarU32(refTypeIndex)) {
-        return false;
-      }
-    } else {
-      *refTypeIndex = NoRefTypeIndex;
-    }
-    return true;
-  }
   MOZ_MUST_USE bool readOp(OpBytes* op) {
     static_assert(size_t(Op::Limit) == 256, "fits");
     uint8_t u8;
     if (!readFixedU8(&u8)) {
       return false;
     }
     op->b0 = u8;
     if (MOZ_LIKELY(!IsPrefixByte(u8))) {
--- a/mobile/android/extensions/webcompat/data/injections.js
+++ b/mobile/android/extensions/webcompat/data/injections.js
@@ -198,26 +198,30 @@ const AVAILABLE_INJECTIONS = [
       urls: ["https://*/*/tpPdk.js", "https://*/*/pdk/js/*/*.js"],
       types: ["script"],
     },
     customFunc: "pdk5fix",
   },
   {
     id: "bug1577870",
     platform: "desktop",
-    domain: "slideshare.net",
+    domain: "Download prompt for files with no content-type",
     bug: "1577870",
     data: {
-      urls: ["https://*.linkedin.com/tscp-serving/dtag*"],
+      urls: [
+        "https://*.linkedin.com/tscp-serving/dtag*",
+        "https://ads-us.rd.linksynergy.com/as.php*",
+        "https://www.office.com/logout?sid*",
+      ],
       contentType: {
         name: "content-type",
         value: "text/html; charset=utf-8",
       },
     },
-    customFunc: "dtagFix",
+    customFunc: "noSniffFix",
   },
   {
     id: "bug1305028",
     platform: "desktop",
     domain: "gaming.youtube.com",
     bug: "1305028",
     contentScripts: {
       matches: ["*://gaming.youtube.com/*"],
--- a/mobile/android/extensions/webcompat/data/ua_overrides.js
+++ b/mobile/android/extensions/webcompat/data/ua_overrides.js
@@ -142,16 +142,38 @@ const AVAILABLE_UA_OVERRIDES = [
           UAHelpers.getPrefix(originalUA) +
           " AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.142 Safari/537.36"
         );
       },
     },
   },
   {
     /*
+     * Bug 1582582 - sling.com - UA override for sling.com
+     * WebCompat issue #17804 - https://webcompat.com/issues/17804
+     *
+     * sling.com blocks Firefox users showing unsupported browser message.
+     * When spoofing as Chrome playing content works fine
+     */
+    id: "bug1582582",
+    platform: "desktop",
+    domain: "sling.com",
+    bug: "1582582",
+    config: {
+      matches: ["https://watch.sling.com/*", "https://www.sling.com/*"],
+      uaTransformer: originalUA => {
+        return (
+          UAHelpers.getPrefix(originalUA) +
+          " AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.132 Safari/537.36"
+        );
+      },
+    },
+  },
+  {
+    /*
      * Bug 1480710 - m.imgur.com - Build UA override
      * WebCompat issue #13154 - https://webcompat.com/issues/13154
      *
      * imgur returns a 404 for requests to CSS and JS file if requested with a Fennec
      * User Agent. By removing the Fennec identifies and adding Chrome Mobile's, we
      * receive the correct CSS and JS files.
      */
     id: "bug1480710",
--- a/mobile/android/extensions/webcompat/lib/custom_functions.js
+++ b/mobile/android/extensions/webcompat/lib/custom_functions.js
@@ -31,29 +31,29 @@ const replaceStringInRequest = (
     if (carryover.length) {
       filter.write(encoder.encode(carryover));
     }
     filter.close();
   };
 };
 
 const CUSTOM_FUNCTIONS = {
-  dtagFix: injection => {
+  noSniffFix: injection => {
     const { urls, contentType } = injection.data;
     const listener = (injection.data.listener = e => {
       e.responseHeaders.push(contentType);
       return { responseHeaders: e.responseHeaders };
     });
 
     browser.webRequest.onHeadersReceived.addListener(listener, { urls }, [
       "blocking",
       "responseHeaders",
     ]);
   },
-  dtagFixDisable: injection => {
+  noSniffFixDisable: injection => {
     const { listener } = injection.data;
     browser.webRequest.onHeadersReceived.removeListener(listener);
     delete injection.data.listener;
   },
   pdk5fix: injection => {
     const { urls, types } = injection.data;
     const listener = (injection.data.listener = ({ requestId }) => {
       replaceStringInRequest(
--- a/mobile/android/extensions/webcompat/manifest.json
+++ b/mobile/android/extensions/webcompat/manifest.json
@@ -1,13 +1,13 @@
 {
   "manifest_version": 2,
   "name": "Web Compat",
   "description": "Urgent post-release fixes for web compatibility.",
-  "version": "6.1.0",
+  "version": "6.2.0",
 
   "applications": {
     "gecko": {
       "id": "webcompat@mozilla.org",
       "strict_min_version": "59.0b5"
     }
   },
 
new file mode 100644
--- /dev/null
+++ b/python/l10n/fluent_migrations/bug_1586246_migration.py
@@ -0,0 +1,26 @@
+# coding=utf8
+
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+from __future__ import absolute_import
+import fluent.syntax.ast as FTL
+from fluent.migrate.helpers import transforms_from
+from fluent.migrate.helpers import COPY
+
+
+def migrate(ctx):
+    """Bug 1586246 - Convert autohide-context to Fluent, part {index}"""
+
+    ctx.add_transforms(
+        "browser/browser/browser.ftl",
+        "browser/browser/browser.ftl",
+        transforms_from(
+            """
+full-screen-autohide =
+    .label = { COPY(path1, "fullScreenAutohide.label") }
+    .accesskey = { COPY(path1, "fullScreenAutohide.accesskey") }
+full-screen-exit =
+    .label = { COPY(path1, "fullScreenExit.label") }
+    .accesskey = { COPY(path1, "fullScreenExit.accesskey") }
+""", path1="browser/chrome/browser/browser.dtd"))
--- a/remote/test/browser/browser.ini
+++ b/remote/test/browser/browser.ini
@@ -22,16 +22,15 @@ support-files =
 [browser_page_javascriptDialog_beforeunload.js]
 [browser_page_javascriptDialog_confirm.js]
 [browser_page_javascriptDialog_otherTarget.js]
 [browser_page_javascriptDialog_prompt.js]
 [browser_page_runtime_events.js]
 [browser_runtime_callFunctionOn.js]
 [browser_runtime_evaluate.js]
 [browser_runtime_executionContext.js]
-skip-if = os == "mac" || (verify && os == 'win') # bug 1547961
 [browser_runtime_get_properties.js]
 [browser_runtime_remote_objects.js]
 [browser_session.js]
 [browser_tabs.js]
 [browser_target.js]
 [browser_target_browserContext.js]
 [browser_target_close.js]
--- a/security/sandbox/mac/SandboxPolicyFlash.h
+++ b/security/sandbox/mac/SandboxPolicyFlash.h
@@ -268,16 +268,21 @@ static const char SandboxPolicyFlash[] =
       (global-name "com.apple.tccd.system")
       (global-name "com.apple.cmio.AppleCameraAssistant")
       (global-name "com.apple.cmio.IIDCVideoAssistant")
       (global-name "com.apple.cmio.AVCAssistant")
       (global-name "com.apple.cmio.VDCAssistant"))
   ; bug 1475707
   (if (= macosMinorVersion 9)
      (allow mach-lookup (global-name "com.apple.xpcd")))
+  (if (>= macosMinorVersion 15)
+     (allow mach-lookup
+      (global-name "com.apple.ViewBridgeAuxiliary")
+      (global-name "com.apple.appkit.xpc.openAndSavePanelService")
+      (global-name "com.apple.MTLCompilerService")))
 
   ; Fonts
   (allow file-read*
     (subpath "/Library/Fonts")
     (subpath "/Library/Application Support/Apple/Fonts")
     (home-library-subpath "/Fonts")
     ; Allow read access to paths allowed via sandbox extensions.
     ; This is needed for fonts in non-standard locations normally
--- a/taskcluster/ci/test/xpcshell.yml
+++ b/taskcluster/ci/test/xpcshell.yml
@@ -55,17 +55,17 @@ xpcshell:
             linux64(-qr)?/debug: 6
             windows(7-32|10-64)(-shippable|-devedition|-asan|.*-qr)?/.*: 2
             windows10-aarch64/opt: 3
             default: 5
     max-run-time:
         by-test-platform:
             macosx.*(?!-ccov)...../.*: 3600
             windows10-aarch64/.*: 3600
-            windows7-32(-shippable)?/opt: 3600
+            windows7-32(-shippable)?/opt: 5400
             .*-ccov/debug: 5400
             default: 2700
     allow-software-gl-layers: false
     tier:
         by-test-platform:
             windows10-64-asan.*: 3
             windows10-aarch64.*: 2
             default: default
--- a/testing/geckodriver/src/command.rs
+++ b/testing/geckodriver/src/command.rs
@@ -229,244 +229,165 @@ pub struct XblLocatorParameters {
 
 #[derive(Default, Debug, PartialEq)]
 pub struct LogOptions {
     pub level: Option<logging::Level>,
 }
 
 #[cfg(test)]
 mod tests {
+    use serde_json::json;
+
     use super::*;
-    use crate::test::check_deserialize;
-    use std::fs::File;
-    use std::io::Read;
+    use crate::test::assert_de;
 
     #[test]
-    fn test_json_addon_install_parameters_null() {
-        let json = r#""#;
-
-        assert!(serde_json::from_str::<AddonInstallParameters>(&json).is_err());
+    fn test_json_addon_install_parameters_invalid() {
+        assert!(serde_json::from_str::<AddonInstallParameters>("").is_err());
+        assert!(serde_json::from_value::<AddonInstallParameters>(json!(null)).is_err());
+        assert!(serde_json::from_value::<AddonInstallParameters>(json!({})).is_err());
     }
 
     #[test]
-    fn test_json_addon_install_parameters_empty() {
-        let json = r#"{}"#;
-
-        assert!(serde_json::from_str::<AddonInstallParameters>(&json).is_err());
+    fn test_json_addon_install_parameters_with_path_and_temporary() {
+        let params = AddonInstallParameters {
+            path: "/path/to.xpi".to_string(),
+            temporary: Some(true),
+        };
+        assert_de(&params, json!({"path": "/path/to.xpi", "temporary": true}));
     }
 
     #[test]
     fn test_json_addon_install_parameters_with_path() {
-        let json = r#"{"path": "/path/to.xpi", "temporary": true}"#;
-        let data = AddonInstallParameters {
-            path: "/path/to.xpi".to_string(),
-            temporary: Some(true),
-        };
-
-        check_deserialize(&json, &data);
-    }
-
-    #[test]
-    fn test_json_addon_install_parameters_with_path_only() {
-        let json = r#"{"path": "/path/to.xpi"}"#;
-        let data = AddonInstallParameters {
+        let params = AddonInstallParameters {
             path: "/path/to.xpi".to_string(),
             temporary: None,
         };
-
-        check_deserialize(&json, &data);
+        assert_de(&params, json!({"path": "/path/to.xpi"}));
     }
 
     #[test]
     fn test_json_addon_install_parameters_with_path_invalid_type() {
-        let json = r#"{"path": true, "temporary": true}"#;
-
-        assert!(serde_json::from_str::<AddonInstallParameters>(&json).is_err());
+        let json = json!({"path": true, "temporary": true});
+        assert!(serde_json::from_value::<AddonInstallParameters>(json).is_err());
     }
 
     #[test]
     fn test_json_addon_install_parameters_with_path_and_temporary_invalid_type() {
-        let json = r#"{"path": "/path/to.xpi", "temporary": "foo"}"#;
-
-        assert!(serde_json::from_str::<AddonInstallParameters>(&json).is_err());
+        let json = json!({"path": "/path/to.xpi", "temporary": "foo"});
+        assert!(serde_json::from_value::<AddonInstallParameters>(json).is_err());
     }
 
     #[test]
     fn test_json_addon_install_parameters_with_addon() {
-        let json = r#"{"addon": "aGVsbG8=", "temporary": true}"#;
-        let data = serde_json::from_str::<AddonInstallParameters>(&json).unwrap();
+        let json = json!({"addon": "aGVsbG8=", "temporary": true});
+        let data = serde_json::from_value::<AddonInstallParameters>(json).unwrap();
 
         assert_eq!(data.temporary, Some(true));
         let mut file = File::open(data.path).unwrap();
         let mut contents = String::new();
         file.read_to_string(&mut contents).unwrap();
         assert_eq!(contents, "hello");
     }
 
     #[test]
     fn test_json_addon_install_parameters_with_addon_only() {
-        let json = r#"{"addon": "aGVsbG8="}"#;
-        let data = serde_json::from_str::<AddonInstallParameters>(&json).unwrap();
+        let json = json!({"addon": "aGVsbG8="});
+        let data = serde_json::from_value::<AddonInstallParameters>(json).unwrap();
 
         assert_eq!(data.temporary, None);
         let mut file = File::open(data.path).unwrap();
         let mut contents = String::new();
         file.read_to_string(&mut contents).unwrap();
         assert_eq!(contents, "hello");
     }
 
     #[test]
     fn test_json_addon_install_parameters_with_addon_invalid_type() {
-        let json = r#"{"addon": true, "temporary": true}"#;
-
-        assert!(serde_json::from_str::<AddonInstallParameters>(&json).is_err());
+        let json = json!({"addon": true, "temporary": true});
+        assert!(serde_json::from_value::<AddonInstallParameters>(json).is_err());
     }
 
     #[test]
     fn test_json_addon_install_parameters_with_addon_and_temporary_invalid_type() {
-        let json = r#"{"addon": "aGVsbG8=", "temporary": "foo"}"#;
-
-        assert!(serde_json::from_str::<AddonInstallParameters>(&json).is_err());
+        let json = json!({"addon": "aGVsbG8=", "temporary": "foo"});
+        assert!(serde_json::from_value::<AddonInstallParameters>(json).is_err());
     }
 
     #[test]
     fn test_json_install_parameters_with_temporary_only() {
-        let json = r#"{"temporary": true}"#;
-
-        assert!(serde_json::from_str::<AddonInstallParameters>(&json).is_err());
+        let json = json!({"temporary": true});
+        assert!(serde_json::from_value::<AddonInstallParameters>(json).is_err());
     }
 
     #[test]
     fn test_json_addon_install_parameters_with_both_path_and_addon() {
-        let json = r#"{
-            "path":"/path/to.xpi",
-            "addon":"aGVsbG8=",
-            "temporary":true
-        }"#;
-
-        assert!(serde_json::from_str::<AddonInstallParameters>(&json).is_err());
+        let json = json!({
+            "path": "/path/to.xpi",
+            "addon": "aGVsbG8=",
+            "temporary": true,
+        });
+        assert!(serde_json::from_value::<AddonInstallParameters>(json).is_err());
     }
 
     #[test]
-    fn test_json_addon_uninstall_parameters_null() {
-        let json = r#""#;
-
-        assert!(serde_json::from_str::<AddonUninstallParameters>(&json).is_err());
-    }
-
-    #[test]
-    fn test_json_addon_uninstall_parameters_empty() {
-        let json = r#"{}"#;
-
-        assert!(serde_json::from_str::<AddonUninstallParameters>(&json).is_err());
+    fn test_json_addon_uninstall_parameters_invalid() {
+        assert!(serde_json::from_str::<AddonUninstallParameters>("").is_err());
+        assert!(serde_json::from_value::<AddonUninstallParameters>(json!(null)).is_err());
+        assert!(serde_json::from_value::<AddonUninstallParameters>(json!({})).is_err());
     }
 
     #[test]
     fn test_json_addon_uninstall_parameters() {
-        let json = r#"{"id": "foo"}"#;
-        let data = AddonUninstallParameters {
+        let params = AddonUninstallParameters {
             id: "foo".to_string(),
         };
-
-        check_deserialize(&json, &data);
+        assert_de(&params, json!({"id": "foo"}));
     }
 
     #[test]
     fn test_json_addon_uninstall_parameters_id_invalid_type() {
-        let json = r#"{"id": true}"#;
-
-        assert!(serde_json::from_str::<AddonUninstallParameters>(&json).is_err());
+        let json = json!({"id": true});
+        assert!(serde_json::from_value::<AddonUninstallParameters>(json).is_err());
     }
 
     #[test]
     fn test_json_gecko_context_parameters_content() {
-        let json = r#"{"context": "content"}"#;
-        let data = GeckoContextParameters {
+        let params = GeckoContextParameters {
             context: GeckoContext::Content,
         };
-
-        check_deserialize(&json, &data);
+        assert_de(&params, json!({"context": "content"}));
     }
 
     #[test]
     fn test_json_gecko_context_parameters_chrome() {
-        let json = r#"{"context": "chrome"}"#;
-        let data = GeckoContextParameters {
+        let params = GeckoContextParameters {
             context: GeckoContext::Chrome,
         };
-
-        check_deserialize(&json, &data);
+        assert_de(&params, json!({"context": "chrome"}));
     }
 
     #[test]
-    fn test_json_gecko_context_parameters_context_missing() {
-        let json = r#"{}"#;
-
-        assert!(serde_json::from_str::<GeckoContextParameters>(&json).is_err());
-    }
-
-    #[test]
-    fn test_json_gecko_context_parameters_context_null() {
-        let json = r#"{"context": null}"#;
-
-        assert!(serde_json::from_str::<GeckoContextParameters>(&json).is_err());
-    }
-
-    #[test]
-    fn test_json_gecko_context_parameters_context_invalid_value() {
-        let json = r#"{"context": "foo"}"#;
-
-        assert!(serde_json::from_str::<GeckoContextParameters>(&json).is_err());
+    fn test_json_gecko_context_parameters_context_invalid() {
+        type P = GeckoContextParameters;
+        assert!(serde_json::from_value::<P>(json!({})).is_err());
+        assert!(serde_json::from_value::<P>(json!({ "context": null })).is_err());
+        assert!(serde_json::from_value::<P>(json!({"context": "foo"})).is_err());
     }
 
     #[test]
     fn test_json_xbl_anonymous_by_attribute() {
-        let json = r#"{
-            "name": "foo",
-            "value": "bar"
-        }"#;
-
-        let data = XblLocatorParameters {
+        let locator = XblLocatorParameters {
             name: "foo".to_string(),
             value: "bar".to_string(),
         };
-
-        check_deserialize(&json, &data);
-    }
-
-    #[test]
-    fn test_json_xbl_anonymous_by_attribute_with_name_missing() {
-        let json = r#"{
-            "value": "bar"
-        }"#;
-
-        assert!(serde_json::from_str::<XblLocatorParameters>(&json).is_err());
+        assert_de(&locator, json!({"name": "foo", "value": "bar"}));
     }
 
     #[test]
-    fn test_json_xbl_anonymous_by_attribute_with_name_invalid_type() {
-        let json = r#"{
-            "name": null,
-            "value": "bar"
-        }"#;
-
-        assert!(serde_json::from_str::<XblLocatorParameters>(&json).is_err());
-    }
-
-    #[test]
-    fn test_json_xbl_anonymous_by_attribute_with_value_missing() {
-        let json = r#"{
-            "name": "foo",
-        }"#;
-
-        assert!(serde_json::from_str::<XblLocatorParameters>(&json).is_err());
-    }
-
-    #[test]
-    fn test_json_xbl_anonymous_by_attribute_with_value_invalid_type() {
-        let json = r#"{
-            "name": "foo",
-            "value": null
-        }"#;
-
-        assert!(serde_json::from_str::<XblLocatorParameters>(&json).is_err());
+    fn test_json_xbl_anonymous_by_attribute_with_name_invalid() {
+        type P = XblLocatorParameters;
+        assert!(serde_json::from_value::<P>(json!({"value": "bar"})).is_err());
+        assert!(serde_json::from_value::<P>(json!({"name": null, "value": "bar"})).is_err());
+        assert!(serde_json::from_value::<P>(json!({"name": "foo"})).is_err());
+        assert!(serde_json::from_value::<P>(json!({"name": "foo", "value": null})).is_err());
     }
 }
--- a/testing/geckodriver/src/test.rs
+++ b/testing/geckodriver/src/test.rs
@@ -1,19 +1,8 @@
-use regex::Regex;
-use serde;
-use serde_json;
-use std;
-
-lazy_static! {
-    static ref MIN_REGEX: Regex = Regex::new(r"[\n\t]|\s{4}").unwrap();
-}
-
-pub fn check_deserialize<T>(json: &str, data: &T)
+pub fn assert_de<T>(data: &T, json: serde_json::Value)
 where
     T: std::fmt::Debug,
     T: std::cmp::PartialEq,
     T: serde::de::DeserializeOwned,
 {
-    let min_json = MIN_REGEX.replace_all(json, "");
-
-    assert_eq!(serde_json::from_str::<T>(&min_json).unwrap(), *data);
+    assert_eq!(data, &serde_json::from_value::<T>(json).unwrap());
 }
--- a/testing/web-platform/tests/tools/wptrunner/wptrunner/browsers/firefox.py
+++ b/testing/web-platform/tests/tools/wptrunner/wptrunner/browsers/firefox.py
@@ -176,17 +176,17 @@ def run_info_browser_version(binary):
         version_info = None
     if version_info:
         return {"browser_build_id": version_info.get("application_buildid", None),
                 "browser_changeset": version_info.get("application_changeset", None)}
     return {}
 
 
 def update_properties():
-    return (["os", "debug", "webrender", "e10s", "sw-e10s", "processor"],
+    return (["os", "debug", "webrender", "fisson", "e10s", "sw-e10s", "processor"],
             {"os": ["version"], "processor": ["bits"]})
 
 
 class FirefoxBrowser(Browser):
     init_timeout = 70
     shutdown_timeout = 70
 
     def __init__(self, logger, binary, prefs_root, test_type, extra_prefs=None, debug_info=None,
--- a/toolkit/components/search/SearchService.jsm
+++ b/toolkit/components/search/SearchService.jsm
@@ -2268,16 +2268,25 @@ SearchService.prototype = {
     }
     if (!name) {
       SearchUtils.fail("Invalid name passed to addEngineWithDetails!");
     }
     if (!params.template) {
       SearchUtils.fail("Invalid template passed to addEngineWithDetails!");
     }
     let existingEngine = this._engines.get(name);
+    if (
+      existingEngine &&
+      existingEngine._loadPath.startsWith("[distribution]")
+    ) {
+      SearchUtils.fail(
+        "Not loading engine due to having a distribution engine with the same name",
+        Cr.NS_ERROR_FILE_ALREADY_EXISTS
+      );
+    }
     if (!isReload && existingEngine) {
       if (
         params.extensionID &&
         existingEngine._loadPath.startsWith(
           `jar:[profile]/extensions/${params.extensionID}`
         )
       ) {
         // This is a legacy extension engine that needs to be migrated to WebExtensions.
--- a/toolkit/components/search/tests/xpcshell/head_opensearch.js
+++ b/toolkit/components/search/tests/xpcshell/head_opensearch.js
@@ -34,19 +34,20 @@ function installAddonEngine(name = "engi
 
 /**
  * Copy the engine-distribution.xml engine to a fake distribution
  * created in the profile, and registered with the directory service.
  */
 function installDistributionEngine() {
   const XRE_APP_DISTRIBUTION_DIR = "XREAppDist";
 
-  const profD = do_get_profile().QueryInterface(Ci.nsIFile);
-
-  let dir = profD.clone();
+  // Use a temp directory rather than the profile or app directory, as then the
+  // engine gets registered as a proper [distribution] load path rather than
+  // something else.
+  let dir = do_get_tempdir();
   dir.append("distribution");
   dir.create(dir.DIRECTORY_TYPE, FileUtils.PERMS_DIRECTORY);
   let distDir = dir.clone();
 
   dir.append("searchplugins");
   dir.create(dir.DIRECTORY_TYPE, FileUtils.PERMS_DIRECTORY);
 
   dir.append("common");
new file mode 100644
--- /dev/null
+++ b/toolkit/components/search/tests/xpcshell/test_maybereloadengine_update_distro.js
@@ -0,0 +1,72 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const SEARCH_SERVICE_TOPIC = "browser-search-service";
+
+add_task(async function setup() {
+  useTestEngines("search-extensions");
+  const defaultBranch = Services.prefs.getDefaultBranch(null);
+
+  defaultBranch.setCharPref("distribution.id", "partner-test");
+  defaultBranch.setCharPref(
+    kDefaultenginenamePref,
+    "data:text/plain,browser.search.defaultenginename=bug645970"
+  );
+
+  installDistributionEngine();
+
+  await AddonTestUtils.promiseStartupManager();
+});
+
+add_task(async function test_maybereloadengine_update_distro() {
+  let reloadObserved = false;
+  let obs = (subject, topic, data) => {
+    if (data == "engines-reloaded") {
+      reloadObserved = true;
+    }
+  };
+  Services.obs.addObserver(obs, SEARCH_SERVICE_TOPIC);
+
+  let initPromise = Services.search.init(true);
+
+  async function cont(requests) {
+    await Promise.all([
+      initPromise,
+      SearchTestUtils.promiseSearchNotification("ensure-known-region-done"),
+      promiseAfterCache(),
+    ]);
+
+    Assert.ok(
+      reloadObserved,
+      "Engines should be reloaded during test, because region fetch succeeded"
+    );
+
+    let defaultEngine = await Services.search.getDefault();
+    Assert.equal(
+      defaultEngine._shortName,
+      "bug645970",
+      "Should have kept the same name"
+    );
+    Assert.equal(
+      defaultEngine._loadPath,
+      "[distribution]/searchplugins/common/bug645970.xml",
+      "Should have kept the distribution engine"
+    );
+    Assert.equal(
+      defaultEngine
+        ._getURLOfType("text/html")
+        .getSubmission("", defaultEngine, "searchbar").uri.spec,
+      "http://searchtest.local/?search=",
+      "Should have kept the same submission URL"
+    );
+
+    Services.obs.removeObserver(obs, SEARCH_SERVICE_TOPIC);
+  }
+
+  await withGeoServer(cont, {
+    geoLookupData: { country_code: "FR" },
+    preGeolookupPromise: initPromise,
+  });
+});
--- a/toolkit/components/search/tests/xpcshell/xpcshell-legacyconfig.ini
+++ b/toolkit/components/search/tests/xpcshell/xpcshell-legacyconfig.ini
@@ -72,12 +72,13 @@ support-files =
   data1/engine2/manifest.json
   data1/list.json
 [test_require_engines_in_cache.js]
 
 # Testing with list.json specifically - Bug 1582942
 
 # Use geoSpecificDefaults - Bug 1542269
 [test_maybereloadengine_update.js]
+[test_maybereloadengine_update_distro.js]
 [test_reloadEngines.js]
 [test_geodefaults.js]
 [test_hidden.js]
 [test_webextensions_install.js]