Merge inbound to mozilla-central. a=merge
authorNarcis Beleuzu <nbeleuzu@mozilla.com>
Fri, 16 Feb 2018 11:49:59 +0200
changeset 404133 9eaebbcc33fd3824876db1b8b33750e997c02f7b
parent 404132 6ba349d419dd67074dcfffc56ed8fa38337511da (current diff)
parent 404058 2d086c21b2a17715efed3b8d08350b26deacb150 (diff)
child 404134 5f2344531e28852f2daf6cf5a5871cc5a94e4040
child 404143 f01d1def46fb53a6523768c6e9188e66b89e664e
child 404203 e78ab9a72984a252764331675cf1afed7347020c
push id99938
push usercbrindusan@mozilla.com
push dateFri, 16 Feb 2018 09:57:26 +0000
treeherdermozilla-inbound@5f2344531e28 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone60.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge inbound to mozilla-central. a=merge
dom/base/test/jsmodules/mochitest.ini
dom/base/test/jsmodules/test_moduleScriptsNotRun.html
dom/canvas/DocumentRendererChild.cpp
dom/canvas/DocumentRendererChild.h
dom/canvas/DocumentRendererParent.cpp
dom/canvas/DocumentRendererParent.h
dom/html/nsGenericHTMLElement.cpp
dom/ipc/PDocumentRenderer.ipdl
layout/base/nsLayoutUtils.cpp
layout/generic/nsFrame.cpp
modules/fdlibm/patches/15_use_safer_strict_assign_on_visual_studio.patch
modules/fdlibm/update.sh
modules/libpref/init/all.js
testing/web-platform/meta/html/semantics/scripting-1/the-script-element/module/__dir__.ini
testing/web-platform/meta/html/semantics/scripting-1/the-script-element/nomodule-reflect.html.ini
testing/web-platform/meta/html/semantics/scripting-1/the-script-element/nomodule-set-on-async-classic-script.html.ini
testing/web-platform/meta/html/semantics/scripting-1/the-script-element/nomodule-set-on-external-module-script.html.ini
testing/web-platform/meta/html/semantics/scripting-1/the-script-element/nomodule-set-on-inline-classic-scripts.html.ini
testing/web-platform/meta/html/semantics/scripting-1/the-script-element/nomodule-set-on-inline-module-script.html.ini
testing/web-platform/meta/html/semantics/scripting-1/the-script-element/nomodule-set-on-synchronously-loaded-classic-scripts.html.ini
--- a/browser/components/extensions/ExtensionPopups.jsm
+++ b/browser/components/extensions/ExtensionPopups.jsm
@@ -73,16 +73,17 @@ class BasePopup {
 
     extension.callOnClose(this);
 
     this.contentReady = new Promise(resolve => {
       this._resolveContentReady = resolve;
     });
 
     this.viewNode.addEventListener(this.DESTROY_EVENT, this);
+    this.panel.addEventListener("popuppositioned", this, {once: true, capture: true});
 
     this.browser = null;
     this.browserLoaded = new Promise((resolve, reject) => {
       this.browserLoadedDeferred = {resolve, reject};
     });
     this.browserReady = this.createBrowser(viewNode, popupURL);
 
     BasePopup.instances.get(this.window).set(extension, this);
@@ -206,16 +207,28 @@ class BasePopup {
 
   handleEvent(event) {
     switch (event.type) {
       case this.DESTROY_EVENT:
         if (!this.destroyed) {
           this.destroy();
         }
         break;
+      case "popuppositioned":
+        if (!this.destroyed) {
+          this.browserLoaded.then(() => {
+            if (this.destroyed) {
+              return;
+            }
+            this.browser.messageManager.sendAsyncMessage("Extension:GrabFocus", {});
+          }).catch(() => {
+            // If the panel closes too fast an exception is raised here and tests will fail.
+          });
+        }
+        break;
     }
   }
 
   createBrowser(viewNode, popupURL = null) {
     let document = viewNode.ownerDocument;
 
     let stack = document.createElementNS(XUL_NS, "stack");
     stack.setAttribute("class", "webextension-popup-stack");
@@ -433,16 +446,17 @@ class ViewPopup extends BasePopup {
    *        browser was destroyed before it was fully loaded, and the popup
    *        should be closed, or `true` otherwise.
    */
   async attach(viewNode) {
     this.viewNode = viewNode;
     this.viewNode.addEventListener(this.DESTROY_EVENT, this);
     this.viewNode.setAttribute("closemenu", "none");
 
+    this.panel.addEventListener("popuppositioned", this, {once: true, capture: true});
     if (this.extension.remote) {
       this.panel.setAttribute("remote", "true");
     }
 
     // Wait until the browser element is fully initialized, and give it at least
     // a short grace period to finish loading its initial content, if necessary.
     //
     // In practice, the browser that was created by the mousdown handler should
--- a/browser/components/extensions/test/browser/browser-common.ini
+++ b/browser/components/extensions/test/browser/browser-common.ini
@@ -111,16 +111,17 @@ skip-if = (os == 'win' && ccov) # Bug 14
 [browser_ext_pageAction_popup_resize.js]
 [browser_ext_pageAction_show_matches.js]
 [browser_ext_pageAction_simple.js]
 [browser_ext_pageAction_telemetry.js]
 [browser_ext_pageAction_title.js]
 [browser_ext_popup_api_injection.js]
 [browser_ext_popup_background.js]
 [browser_ext_popup_corners.js]
+[browser_ext_popup_focus.js]
 [browser_ext_popup_sendMessage.js]
 [browser_ext_popup_shutdown.js]
 [browser_ext_runtime_openOptionsPage.js]
 [browser_ext_runtime_openOptionsPage_uninstall.js]
 [browser_ext_runtime_setUninstallURL.js]
 [browser_ext_sessions_forgetClosedTab.js]
 [browser_ext_sessions_forgetClosedWindow.js]
 [browser_ext_sessions_getRecentlyClosed.js]
--- a/browser/components/extensions/test/browser/browser_ext_currentWindow.js
+++ b/browser/components/extensions/test/browser/browser_ext_currentWindow.js
@@ -99,39 +99,35 @@ add_task(async function() {
     extension.sendMessage(kind + "-check-current1");
     is((await extension.awaitMessage("result")), winId, `${name} is on top (check 1) [${kind}]`);
     extension.sendMessage(kind + "-check-current2");
     is((await extension.awaitMessage("result")), winId, `${name} is on top (check 2) [${kind}]`);
     extension.sendMessage(kind + "-check-current3");
     is((await extension.awaitMessage("result")), winId, `${name} is on top (check 3) [${kind}]`);
   }
 
-  await focusWindow(win1);
-  await checkWindow("background", winId1, "win1");
-  await focusWindow(win2);
-  await checkWindow("background", winId2, "win2");
-
   async function triggerPopup(win, callback) {
     await clickBrowserAction(extension, win);
     await awaitExtensionPanel(extension, win);
 
     await extension.awaitMessage("popup-ready");
 
     await callback();
 
     closeBrowserAction(extension, win);
   }
 
-  // Set focus to some other window.
-  await focusWindow(window);
-
+  await focusWindow(win1);
+  await checkWindow("background", winId1, "win1");
   await triggerPopup(win1, async function() {
     await checkWindow("popup", winId1, "win1");
   });
 
+  await focusWindow(win2);
+  await checkWindow("background", winId2, "win2");
   await triggerPopup(win2, async function() {
     await checkWindow("popup", winId2, "win2");
   });
 
   async function triggerPage(winId, name) {
     extension.sendMessage("background-open-page", winId);
     await extension.awaitMessage("page-ready");
     await checkWindow("page", winId, name);
--- a/browser/components/extensions/test/browser/browser_ext_getViews.js
+++ b/browser/components/extensions/test/browser/browser_ext_getViews.js
@@ -143,32 +143,28 @@ add_task(async function() {
 
   let tabId2 = await openTab(winId2);
 
   await checkViews("background", 2, 0, 0);
   await checkViewsWithFilter({windowId: winId2}, 1);
   await checkViewsWithFilter({tabId: tabId2}, 1);
 
   async function triggerPopup(win, callback) {
+    // Window needs focus to open popups.
+    await focusWindow(win);
     await clickBrowserAction(extension, win);
     await awaitExtensionPanel(extension, win);
 
     await extension.awaitMessage("popup-ready");
 
     await callback();
 
     closeBrowserAction(extension, win);
   }
 
-  // The popup occasionally closes prematurely if we open it immediately here.
-  // I'm not sure what causes it to close (it's something internal, and seems to
-  // be focus-related, but it's not caused by JS calling hidePopup), but even a
-  // short timeout seems to consistently fix it.
-  await new Promise(resolve => win1.setTimeout(resolve, 10));
-
   await triggerPopup(win1, async function() {
     await checkViews("background", 2, 1, 0);
     await checkViews("popup", 2, 1, 1);
     await checkViewsWithFilter({windowId: winId1}, 2);
     await checkViewsWithFilter({type: "popup", tabId: -1}, 1);
   });
 
   await triggerPopup(win2, async function() {
new file mode 100644
--- /dev/null
+++ b/browser/components/extensions/test/browser/browser_ext_popup_focus.js
@@ -0,0 +1,71 @@
+/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set sts=2 sw=2 et tw=80: */
+"use strict";
+
+const DUMMY_PAGE = "http://example.com/browser/browser/components/extensions/test/browser/file_dummy.html";
+
+add_task(async function testPageActionFocus() {
+  let extension = ExtensionTestUtils.loadExtension({
+    manifest: {
+      "page_action": {
+        "default_popup": "popup.html",
+        "show_matches": ["<all_urls>"],
+      },
+    },
+    files: {
+      "popup.html": `<!DOCTYPE html><html><head><meta charset="utf-8">
+        <script src="popup.js"></script>
+        </head><body>
+        </body></html>
+      `,
+      "popup.js": function() {
+        window.addEventListener("focus", (event) => {
+          browser.test.assertEq(true, document.hasFocus(), "document should be focused");
+          browser.test.notifyPass("focused");
+        }, {once: true});
+      },
+    },
+  });
+  let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, DUMMY_PAGE);
+
+  await extension.startup();
+  let finish = extension.awaitFinish("focused");
+  await clickPageAction(extension);
+  await finish;
+  await closePageAction(extension);
+
+  await BrowserTestUtils.removeTab(tab);
+  await extension.unload();
+});
+
+add_task(async function testBrowserActionFocus() {
+  let extension = ExtensionTestUtils.loadExtension({
+    manifest: {
+      "browser_action": {"default_popup": "popup.html"},
+    },
+    files: {
+      "popup.html": `<!DOCTYPE html><html><head><meta charset="utf-8">
+        <script src="popup.js"></script>
+        </head><body>
+        </body></html>
+      `,
+      "popup.js": function() {
+        window.addEventListener("focus", (event) => {
+          browser.test.assertEq(true, document.hasFocus(), "document should be focused");
+          browser.test.notifyPass("focused");
+        }, {once: true});
+      },
+    },
+  });
+  await extension.startup();
+
+  let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, DUMMY_PAGE);
+  let finish = extension.awaitFinish("focused");
+  await clickBrowserAction(extension);
+  await finish;
+
+  await closeBrowserAction(extension);
+
+  await BrowserTestUtils.removeTab(tab);
+  await extension.unload();
+});
--- a/browser/components/migration/tests/marionette/manifest.ini
+++ b/browser/components/migration/tests/marionette/manifest.ini
@@ -1,5 +1,5 @@
 [DEFAULT]
 run-if = buildapp == 'browser'
 
 [test_refresh_firefox.py]
-
+skip-if = (os == 'win' && !debug) #bug 1425323
--- a/build/moz.configure/windows.configure
+++ b/build/moz.configure/windows.configure
@@ -242,22 +242,29 @@ def valid_ucrt_sdk_dir(windows_sdk_dir, 
         lib=sdk.lib,
         version=version,
     )
 
 
 @depends(c_compiler)
 @imports('os')
 def vc_path(c_compiler):
-    if c_compiler.type != 'msvc':
+    if c_compiler.type not in ('msvc', 'clang-cl'):
         return
+
     # Normally, we'd start from c_compiler.compiler, but for now, it's not the
     # ideal full path to the compiler. At least, we're guaranteed find_program
     # will get us the one we found in toolchain.configure.
-    cl = find_program(c_compiler.compiler)
+    vc_program = c_compiler.compiler
+
+    # In clang-cl builds, we use the headers and libraries from an MSVC installation.
+    if c_compiler.type == 'clang-cl':
+        vc_program = 'cl.exe'
+
+    cl = find_program(vc_program)
     result = os.path.dirname(cl)
     while True:
         next, p = os.path.split(result)
         if next == result:
             die('Cannot determine the Visual C++ directory the compiler (%s) '
                 'is in' % cl)
         result = next
         if p.lower() == 'bin':
--- a/build/sanitizers/ubsan_signed_overflow_blacklist.txt
+++ b/build/sanitizers/ubsan_signed_overflow_blacklist.txt
@@ -45,25 +45,16 @@ src:*bits/basic_string.h
 src:*/CheckedInt.h
 
 # Exclude bignum
 src:*/mfbt/double-conversion/source/bignum.cc
 
 # Exclude anything within gtests
 src:*/gtest/*
 
-# The JS engine has a lot of code doing all sorts of overflows. This code
-# is pretty well tested though and excluding it here will allow us to go
-# for other, less tested code. Ideally, we would include the JS engine here
-# at some point.
-src:*/js/src/*
-src:*/js/public/*
-src:*/js/*.h
-src:*/jsfriendapi.h
-
 # Atomics can overflow, but without a full stack we can't trace these back
 # to what is actually causing the overflow. Ignoring these for now, as it will
 # be too much effort to determine every single source here.
 src:*/mfbt/Atomics.h
 
 # No reason to instrument certain parts of NSS that explicitely deal with
 # arithmetics and crypto.
 src:*/security/nss/lib/freebl/mpi/*
--- a/devtools/server/actors/source.js
+++ b/devtools/server/actors/source.js
@@ -902,33 +902,56 @@ let SourceActor = ActorClassWithSpec(sou
       // This is a line breakpoint, so we are interested in all offsets
       // that correspond to the given line number.
       for (let script of scripts) {
         let offsets = script.getLineOffsets(generatedLine);
         if (offsets.length > 0) {
           entryPoints.push({ script, offsets });
         }
       }
-    } else {
-      // This is a column breakpoint, so we are interested in all column
-      // offsets that correspond to the given line *and* column number.
+
+      if (entryPoints.length > 0) {
+        setBreakpointAtEntryPoints(actor, entryPoints);
+        return true;
+      }
+
+      return false;
+    }
+
+    // This is a column breakpoint, so we are interested in all column
+    // offsets that correspond to the given line *and* column number.
+    for (let script of scripts) {
+      let columnToOffsetMap = script.getAllColumnOffsets()
+                                    .filter(({ lineNumber }) => {
+                                      return lineNumber === generatedLine;
+                                    });
+      for (let { columnNumber: column, offset } of columnToOffsetMap) {
+        if (column >= generatedColumn && column <= generatedLastColumn) {
+          entryPoints.push({ script, offsets: [offset] });
+        }
+      }
+    }
+
+    // If we don't find any matching entrypoints, then
+    // we should check to see if the breakpoint is to the left of the first offset.
+    if (entryPoints.length === 0) {
       for (let script of scripts) {
-        let columnToOffsetMap = script.getAllColumnOffsets()
-                                      .filter(({ lineNumber }) => {
-                                        return lineNumber === generatedLine;
-                                      });
-        for (let { columnNumber: column, offset } of columnToOffsetMap) {
-          if (column >= generatedColumn && column <= generatedLastColumn) {
+        let columnToOffsetMap = script
+          .getAllColumnOffsets()
+          .filter(({ lineNumber }) => {
+            return lineNumber === generatedLine;
+          });
+
+        if (columnToOffsetMap.length > 0) {
+          let { columnNumber: column, offset } = columnToOffsetMap[0];
+          if (generatedColumn < column) {
             entryPoints.push({ script, offsets: [offset] });
           }
         }
       }
     }
 
-    if (entryPoints.length === 0) {
-      return false;
-    }
     setBreakpointAtEntryPoints(actor, entryPoints);
     return true;
   }
 });
 
 exports.SourceActor = SourceActor;
new file mode 100644
--- /dev/null
+++ b/devtools/server/tests/unit/test_setBreakpoint-at-the-beginning-of-a-line.js
@@ -0,0 +1,64 @@
+"use strict";
+
+var SOURCE_URL = getFileUrl("setBreakpoint-on-column.js");
+
+function run_test() {
+  return Task.spawn(function* () {
+    do_test_pending();
+
+    DebuggerServer.registerModule("xpcshell-test/testactors");
+    DebuggerServer.init(() => true);
+
+    let global = createTestGlobal("test");
+    DebuggerServer.addTestGlobal(global);
+
+    let client = new DebuggerClient(DebuggerServer.connectPipe());
+    yield connect(client);
+
+    let { tabs } = yield listTabs(client);
+    let tab = findTab(tabs, "test");
+    let [, tabClient] = yield attachTab(client, tab);
+    let [, threadClient] = yield attachThread(tabClient);
+    yield resume(threadClient);
+
+    let promise = waitForNewSource(threadClient, SOURCE_URL);
+    loadSubScript(SOURCE_URL, global);
+    let { source } = yield promise;
+    let sourceClient = threadClient.source(source);
+
+    let location = { line: 4, column: 2 };
+    let [packet, breakpointClient] = yield setBreakpoint(
+      sourceClient,
+      location
+    );
+
+    Assert.ok(!packet.isPending);
+    Assert.equal(false, "actualLocation" in packet);
+
+    packet = yield executeOnNextTickAndWaitForPause(function () {
+      Cu.evalInSandbox("f()", global);
+    }, client);
+
+    Assert.equal(packet.type, "paused");
+    let why = packet.why;
+    Assert.equal(why.type, "breakpoint");
+    Assert.equal(why.actors.length, 1);
+    Assert.equal(why.actors[0], breakpointClient.actor);
+
+    let frame = packet.frame;
+    let where = frame.where;
+    Assert.equal(where.source.actor, source.actor);
+    Assert.equal(where.line, location.line);
+    Assert.equal(where.column, 6);
+
+    let variables = frame.environment.bindings.variables;
+    Assert.equal(variables.a.value.type, "undefined");
+    Assert.equal(variables.b.value.type, "undefined");
+    Assert.equal(variables.c.value.type, "undefined");
+
+    yield resume(threadClient);
+    yield close(client);
+
+    do_test_finished();
+  });
+}
--- a/devtools/server/tests/unit/xpcshell.ini
+++ b/devtools/server/tests/unit/xpcshell.ini
@@ -217,16 +217,17 @@ reason = bug 937197
 [test_registerClient.js]
 [test_client_request.js]
 [test_symbols-01.js]
 [test_symbols-02.js]
 [test_get-executable-lines.js]
 [test_get-executable-lines-source-map.js]
 [test_xpcshell_debugging.js]
 support-files = xpcshell_debugging_script.js
+[test_setBreakpoint-at-the-beginning-of-a-line.js]
 [test_setBreakpoint-on-column.js]
 [test_setBreakpoint-on-column-in-gcd-script.js]
 [test_setBreakpoint-on-column-with-no-offsets-at-end-of-line.js]
 [test_setBreakpoint-on-line.js]
 [test_setBreakpoint-on-line-in-gcd-script.js]
 [test_setBreakpoint-on-line-with-multiple-offsets.js]
 [test_setBreakpoint-on-line-with-multiple-statements.js]
 [test_setBreakpoint-on-line-with-no-offsets.js]
--- a/devtools/shared/builtin-modules.js
+++ b/devtools/shared/builtin-modules.js
@@ -13,17 +13,17 @@
  * they would also miss them.
  */
 
 const { Cu, CC, Cc, Ci } = require("chrome");
 const promise = require("resource://gre/modules/Promise.jsm").Promise;
 const jsmScope = require("resource://gre/modules/Services.jsm");
 const { Services } = jsmScope;
 // Steal various globals only available in JSM scope (and not Sandbox one)
-const { PromiseDebugging, ChromeUtils, HeapSnapshot,
+const { ChromeUtils, HeapSnapshot,
         atob, btoa, TextEncoder, TextDecoder } = Cu.getGlobalForObject(jsmScope);
 
 // Create a single Sandbox to access global properties needed in this module.
 // Sandbox are memory expensive, so we should create as little as possible.
 const { CSS, CSSRule, FileReader, indexedDB, InspectorUtils, URL } =
     Cu.Sandbox(CC("@mozilla.org/systemprincipal;1", "nsIPrincipal")(), {
       wantGlobalProperties: [
         "CSS", "CSSRule", "FileReader", "indexedDB", "InspectorUtils", "URL",
@@ -173,17 +173,16 @@ function lazyRequireGetter(obj, property
 // List of pseudo modules exposed to all devtools modules.
 exports.modules = {
   "Services": Object.create(Services),
   promise,
   // Expose "chrome" Promise, which aren't related to any document
   // and so are never frozen, even if the browser loader module which
   // pull it is destroyed. See bug 1402779.
   Promise,
-  PromiseDebugging,
   ChromeUtils,
   HeapSnapshot,
   InspectorUtils,
   FileReader,
 };
 
 defineLazyGetter(exports.modules, "Debugger", () => {
   // addDebuggerToGlobal only allows adding the Debugger object to a global. The
--- a/dom/base/nsRange.cpp
+++ b/dom/base/nsRange.cpp
@@ -3468,17 +3468,18 @@ nsRange::GetClientRectsAndTexts(
 
   nsLayoutUtils::RectListBuilder builder(aResult.mRectList);
 
   CollectClientRectsAndText(&builder, &aResult.mTextList, this,
     mStart.Container(), mStart.Offset(), mEnd.Container(), mEnd.Offset(), true, true);
 }
 
 nsresult
-nsRange::GetUsedFontFaces(nsTArray<nsAutoPtr<InspectorFontFace>>& aResult)
+nsRange::GetUsedFontFaces(nsTArray<nsAutoPtr<InspectorFontFace>>& aResult,
+                          uint32_t aMaxRanges)
 {
   NS_ENSURE_TRUE(mStart.Container(), NS_ERROR_UNEXPECTED);
 
   nsCOMPtr<nsINode> startContainer = do_QueryInterface(mStart.Container());
   nsCOMPtr<nsINode> endContainer = do_QueryInterface(mEnd.Container());
 
   // Flush out layout so our frames are up to date.
   nsIDocument* doc = mStart.Container()->OwnerDoc();
@@ -3512,27 +3513,27 @@ nsRange::GetUsedFontFaces(nsTArray<nsAut
       continue;
     }
 
     if (content->IsNodeOfType(nsINode::eTEXT)) {
        if (node == startContainer) {
          int32_t offset = startContainer == endContainer ?
            mEnd.Offset() : content->GetText()->GetLength();
          nsLayoutUtils::GetFontFacesForText(frame, mStart.Offset(), offset,
-                                            true, fontFaces);
+                                            true, fontFaces, aMaxRanges);
          continue;
        }
        if (node == endContainer) {
          nsLayoutUtils::GetFontFacesForText(frame, 0, mEnd.Offset(),
-                                            true, fontFaces);
+                                            true, fontFaces, aMaxRanges);
          continue;
        }
     }
 
-    nsLayoutUtils::GetFontFacesForFrames(frame, fontFaces);
+    nsLayoutUtils::GetFontFacesForFrames(frame, fontFaces, aMaxRanges);
   }
 
   // Take ownership of the InspectorFontFaces in the table and move them into
   // the aResult outparam.
   for (auto iter = fontFaces.Iter(); !iter.Done(); iter.Next()) {
     aResult.AppendElement(Move(iter.Data()));
   }
 
--- a/dom/base/nsRange.h
+++ b/dom/base/nsRange.h
@@ -276,18 +276,22 @@ public:
     int32_t indexInParent = parentNode->ComputeIndexOf(aNode);
     if (NS_WARN_IF(indexInParent < 0)) {
       return nullptr;
     }
     *aOffset = static_cast<uint32_t>(indexInParent);
     return parentNode;
   }
 
+  // aMaxRanges is the maximum number of text ranges to record for each face
+  // (pass 0 to just get the list of faces, without recording exact ranges
+  // where each face was used).
   nsresult GetUsedFontFaces(
-      nsTArray<nsAutoPtr<mozilla::dom::InspectorFontFace>>& aResult);
+      nsTArray<nsAutoPtr<mozilla::dom::InspectorFontFace>>& aResult,
+      uint32_t aMaxRanges);
 
   // nsIMutationObserver methods
   NS_DECL_NSIMUTATIONOBSERVER_CHARACTERDATACHANGED
   NS_DECL_NSIMUTATIONOBSERVER_CONTENTINSERTED
   NS_DECL_NSIMUTATIONOBSERVER_CONTENTREMOVED
   NS_DECL_NSIMUTATIONOBSERVER_PARENTCHAINCHANGED
   NS_DECL_NSIMUTATIONOBSERVER_CONTENTAPPENDED
 
deleted file mode 100644
--- a/dom/base/test/jsmodules/mochitest.ini
+++ /dev/null
@@ -1,6 +0,0 @@
-[DEFAULT]
-support-files =
-  module_setRan.js
-
-[test_moduleScriptsNotRun.html]
-skip-if = nightly_build
--- a/dom/base/test/jsmodules/moz.build
+++ b/dom/base/test/jsmodules/moz.build
@@ -1,13 +1,9 @@
 # -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
 # vim: set filetype=python:
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
-MOCHITEST_MANIFESTS += [
-    'mochitest.ini'
-]
-
 MOCHITEST_CHROME_MANIFESTS += [
     'chrome.ini'
 ]
deleted file mode 100644
--- a/dom/base/test/jsmodules/test_moduleScriptsNotRun.html
+++ /dev/null
@@ -1,19 +0,0 @@
-<!DOCTYPE html>
-<meta charset=utf-8>
-<title>Test script elements with type="module" are not run for content HTML</title>
-<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
-<script>
-  var inlineModuleRan = false;
-  var moduleRan = false;
-
-  SimpleTest.waitForExplicitFinish();
-
-  function testLoaded() {
-    ok(!moduleRan, "Check module script was not run");
-    ok(!inlineModuleRan, "Check inline module script was not run");
-    SimpleTest.finish();
-  }
-</script>
-<script type="module" src="module_setRan.js"></script>
-<script type="module">inlineModuleRan = true;</script>
-<body onload='testLoaded()'></body>
--- a/dom/canvas/CanvasRenderingContext2D.cpp
+++ b/dom/canvas/CanvasRenderingContext2D.cpp
@@ -90,18 +90,16 @@
 #include "mozilla/EndianUtils.h"
 #include "mozilla/gfx/2D.h"
 #include "mozilla/gfx/Helpers.h"
 #include "mozilla/gfx/Tools.h"
 #include "mozilla/gfx/PathHelpers.h"
 #include "mozilla/gfx/DataSurfaceHelpers.h"
 #include "mozilla/gfx/PatternHelpers.h"
 #include "mozilla/gfx/Swizzle.h"
-#include "mozilla/ipc/DocumentRendererParent.h"
-#include "mozilla/ipc/PDocumentRendererParent.h"
 #include "mozilla/layers/PersistentBufferProvider.h"
 #include "mozilla/MathAlgorithms.h"
 #include "mozilla/Preferences.h"
 #include "mozilla/ServoBindings.h"
 #include "mozilla/Telemetry.h"
 #include "mozilla/TimeStamp.h"
 #include "mozilla/UniquePtr.h"
 #include "mozilla/Unused.h"
--- a/dom/canvas/CanvasRenderingContextHelper.cpp
+++ b/dom/canvas/CanvasRenderingContextHelper.cpp
@@ -4,16 +4,17 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "CanvasRenderingContextHelper.h"
 #include "ImageBitmapRenderingContext.h"
 #include "ImageEncoder.h"
 #include "mozilla/dom/CanvasRenderingContext2D.h"
 #include "mozilla/Telemetry.h"
 #include "mozilla/UniquePtr.h"
+#include "MozFramebuffer.h"
 #include "nsContentUtils.h"
 #include "nsDOMJSUtils.h"
 #include "nsIScriptContext.h"
 #include "nsJSUtils.h"
 #include "WebGL1Context.h"
 #include "WebGL2Context.h"
 
 namespace mozilla {
deleted file mode 100644
--- a/dom/canvas/DocumentRendererChild.cpp
+++ /dev/null
@@ -1,110 +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/. */
-
-#include "mozilla/ipc/DocumentRendererChild.h"
-
-#include "base/basictypes.h"
-
-#include "gfx2DGlue.h"
-#include "gfxContext.h"
-#include "gfxPattern.h"
-#include "mozilla/gfx/2D.h"
-#include "mozilla/RefPtr.h"
-#include "nsPIDOMWindow.h"
-#include "nsIDOMWindow.h"
-#include "nsIDocShell.h"
-#include "nsIInterfaceRequestorUtils.h"
-#include "nsComponentManagerUtils.h"
-#include "nsCSSParser.h"
-#include "nsPresContext.h"
-#include "nsCOMPtr.h"
-#include "nsColor.h"
-#include "nsLayoutUtils.h"
-#include "nsContentUtils.h"
-#include "nsCSSValue.h"
-#ifdef MOZ_OLD_STYLE
-#include "nsRuleNode.h"
-#endif
-#include "mozilla/gfx/Matrix.h"
-#include "mozilla/ServoCSSParser.h"
-
-using namespace mozilla;
-using namespace mozilla::gfx;
-using namespace mozilla::ipc;
-
-DocumentRendererChild::DocumentRendererChild()
-{}
-
-DocumentRendererChild::~DocumentRendererChild()
-{}
-
-bool
-DocumentRendererChild::RenderDocument(nsPIDOMWindowOuter* window,
-                                      const nsRect& documentRect,
-                                      const mozilla::gfx::Matrix& transform,
-                                      const nsString& aBGColor,
-                                      uint32_t renderFlags,
-                                      bool flushLayout,
-                                      const nsIntSize& renderSize,
-                                      nsCString& data)
-{
-    if (flushLayout)
-        nsContentUtils::FlushLayoutForTree(window);
-
-    RefPtr<nsPresContext> presContext;
-    if (window) {
-        nsIDocShell* docshell = window->GetDocShell();
-        if (docshell) {
-            docshell->GetPresContext(getter_AddRefs(presContext));
-        }
-    }
-    if (!presContext)
-        return false;
-
-    nscolor bgColor;
-
-    ServoStyleSet* servoStyleSet = presContext->StyleSet()
-      ? presContext->StyleSet()->GetAsServo()
-      : nullptr;
-
-    if (servoStyleSet) {
-      if (!ServoCSSParser::ComputeColor(servoStyleSet, NS_RGB(0, 0, 0),
-                                        aBGColor, &bgColor)) {
-        return false;
-      }
-    } else {
-#ifdef MOZ_OLD_STYLE
-      nsCSSParser parser;
-      nsCSSValue bgColorValue;
-      if (!parser.ParseColorString(aBGColor, nullptr, 0, bgColorValue) ||
-          !nsRuleNode::ComputeColor(bgColorValue, presContext, nullptr, bgColor)) {
-        return false;
-      }
-#else
-      MOZ_CRASH("old style system disabled");
-#endif
-    }
-
-    // Draw directly into the output array.
-    data.SetLength(renderSize.width * renderSize.height * 4);
-
-    RefPtr<DrawTarget> dt =
-        Factory::CreateDrawTargetForData(gfxPlatform::GetPlatform()->GetSoftwareBackend(),
-                                         reinterpret_cast<uint8_t*>(data.BeginWriting()),
-                                         IntSize(renderSize.width, renderSize.height),
-                                         4 * renderSize.width,
-                                         SurfaceFormat::B8G8R8A8);
-    if (!dt || !dt->IsValid()) {
-        gfxWarning() << "DocumentRendererChild::RenderDocument failed to Factory::CreateDrawTargetForData";
-        return false;
-    }
-    RefPtr<gfxContext> ctx = gfxContext::CreateOrNull(dt);
-    MOZ_ASSERT(ctx); // already checked the draw target above
-    ctx->SetMatrix(transform);
-
-    nsCOMPtr<nsIPresShell> shell = presContext->PresShell();
-    shell->RenderDocument(documentRect, renderFlags, bgColor, ctx);
-
-    return true;
-}
deleted file mode 100644
--- a/dom/canvas/DocumentRendererChild.h
+++ /dev/null
@@ -1,37 +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/. */
-
-#ifndef mozilla_dom_DocumentRendererChild
-#define mozilla_dom_DocumentRendererChild
-
-#include "gfxContext.h"
-#include "mozilla/ipc/PDocumentRendererChild.h"
-#include "nsString.h"
-
-class nsIDOMWindow;
-
-namespace mozilla {
-namespace ipc {
-
-class DocumentRendererChild : public PDocumentRendererChild
-{
-public:
-    DocumentRendererChild();
-    virtual ~DocumentRendererChild();
-
-    bool RenderDocument(nsPIDOMWindowOuter* window,
-                        const nsRect& documentRect, const gfx::Matrix& transform,
-                        const nsString& bgcolor,
-                        uint32_t renderFlags, bool flushLayout,
-                        const nsIntSize& renderSize, nsCString& data);
-
-private:
-
-    DISALLOW_EVIL_CONSTRUCTORS(DocumentRendererChild);
-};
-
-} // namespace ipc
-} // namespace mozilla
-
-#endif
deleted file mode 100644
--- a/dom/canvas/DocumentRendererParent.cpp
+++ /dev/null
@@ -1,63 +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/. */
-
-#include "mozilla/ipc/DocumentRendererParent.h"
-
-#include "gfx2DGlue.h"
-#include "mozilla/gfx/2D.h"
-#include "mozilla/gfx/PathHelpers.h"
-#include "mozilla/RefPtr.h"
-#include "nsICanvasRenderingContextInternal.h"
-
-using namespace mozilla;
-using namespace mozilla::gfx;
-using namespace mozilla::ipc;
-
-DocumentRendererParent::DocumentRendererParent()
-{}
-
-DocumentRendererParent::~DocumentRendererParent()
-{}
-
-void DocumentRendererParent::SetCanvasContext(nsICanvasRenderingContextInternal* aCanvas,
-                                              gfxContext* ctx)
-{
-    mCanvas = aCanvas;
-    mCanvasContext = ctx;
-}
-
-void DocumentRendererParent::DrawToCanvas(const nsIntSize& aSize,
-                                          const nsCString& aData)
-{
-    if (!mCanvas || !mCanvasContext)
-        return;
-
-    DrawTarget* drawTarget = mCanvasContext->GetDrawTarget();
-    Rect rect(0, 0, aSize.width, aSize.height);
-    MaybeSnapToDevicePixels(rect, *drawTarget, true);
-    RefPtr<DataSourceSurface> dataSurface =
-        Factory::CreateWrappingDataSourceSurface(reinterpret_cast<uint8_t*>(const_cast<nsCString&>(aData).BeginWriting()),
-                                                 aSize.width * 4,
-                                                 IntSize(aSize.width, aSize.height),
-                                                 SurfaceFormat::B8G8R8A8);
-    SurfacePattern pattern(dataSurface, ExtendMode::CLAMP);
-    drawTarget->FillRect(rect, pattern);
-
-    gfxRect damageRect = mCanvasContext->UserToDevice(ThebesRect(rect));
-    mCanvas->Redraw(damageRect);
-}
-
-void
-DocumentRendererParent::ActorDestroy(ActorDestroyReason aWhy)
-{
-  // Implement me! Bug 1005139
-}
-
-mozilla::ipc::IPCResult
-DocumentRendererParent::Recv__delete__(const nsIntSize& renderedSize,
-                                       const nsCString& data)
-{
-    DrawToCanvas(renderedSize, data);
-    return IPC_OK();
-}
deleted file mode 100644
--- a/dom/canvas/DocumentRendererParent.h
+++ /dev/null
@@ -1,44 +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/. */
-
-#ifndef mozilla_dom_DocumentRendererParent
-#define mozilla_dom_DocumentRendererParent
-
-#include "gfxContext.h"
-#include "mozilla/ipc/PDocumentRendererParent.h"
-#include "nsCOMPtr.h"
-#include "nsString.h"
-
-class nsICanvasRenderingContextInternal;
-
-namespace mozilla {
-namespace ipc {
-
-class DocumentRendererParent : public PDocumentRendererParent
-{
-public:
-    DocumentRendererParent();
-    virtual ~DocumentRendererParent();
-
-    void SetCanvasContext(nsICanvasRenderingContextInternal* aCanvas,
-			  gfxContext* ctx);
-    void DrawToCanvas(const nsIntSize& renderedSize,
-		      const nsCString& aData);
-
-    virtual void ActorDestroy(ActorDestroyReason aWhy) override;
-
-    virtual mozilla::ipc::IPCResult Recv__delete__(const nsIntSize& renderedSize,
-                                                   const nsCString& data) override;
-
-private:
-    nsCOMPtr<nsICanvasRenderingContextInternal> mCanvas;
-    RefPtr<gfxContext> mCanvasContext;
-
-    DISALLOW_EVIL_CONSTRUCTORS(DocumentRendererParent);
-};
-
-} // namespace ipc
-} // namespace mozilla
-
-#endif
--- a/dom/canvas/WebGLContextState.cpp
+++ b/dom/canvas/WebGLContextState.cpp
@@ -5,16 +5,17 @@
 
 #include "WebGLContext.h"
 
 #include "GLContext.h"
 #include "GLScreenBuffer.h"
 #include "mozilla/dom/ToJSValue.h"
 #include "mozilla/Maybe.h"
 #include "mozilla/Preferences.h"
+#include "MozFramebuffer.h"
 #include "nsString.h"
 #include "WebGLBuffer.h"
 #include "WebGLContextUtils.h"
 #include "WebGLFramebuffer.h"
 #include "WebGLProgram.h"
 #include "WebGLRenderbuffer.h"
 #include "WebGLShader.h"
 #include "WebGLTexture.h"
--- a/dom/canvas/moz.build
+++ b/dom/canvas/moz.build
@@ -44,21 +44,16 @@ MOCHITEST_MANIFESTS += [
 ]
 
 MOCHITEST_CHROME_MANIFESTS += ['test/chrome/chrome.ini']
 
 EXPORTS += [
     'nsICanvasRenderingContextInternal.h',
 ]
 
-EXPORTS.mozilla.ipc += [
-    'DocumentRendererChild.h',
-    'DocumentRendererParent.h',
-]
-
 EXPORTS.mozilla.dom += [
     'BasicRenderingContext2D.h',
     'CanvasGradient.h',
     'CanvasPath.h',
     'CanvasPattern.h',
     'CanvasRenderingContext2D.h',
     'CanvasRenderingContextHelper.h',
     'CanvasUtils.h',
@@ -73,18 +68,16 @@ EXPORTS.mozilla.dom += [
 ]
 
 # Canvas 2D and common sources
 UNIFIED_SOURCES += [
     'CanvasImageCache.cpp',
     'CanvasRenderingContext2D.cpp',
     'CanvasRenderingContextHelper.cpp',
     'CanvasUtils.cpp',
-    'DocumentRendererChild.cpp',
-    'DocumentRendererParent.cpp',
     'ImageBitmap.cpp',
     'ImageBitmapColorUtils.cpp',
     'ImageBitmapRenderingContext.cpp',
     'ImageBitmapUtils.cpp',
     'ImageData.cpp',
     'OffscreenCanvas.cpp',
 ]
 
--- a/dom/html/ImageDocument.cpp
+++ b/dom/html/ImageDocument.cpp
@@ -360,22 +360,30 @@ ImageDocument::ShrinkToFit()
       classList->Add(NS_LITERAL_STRING("overflowingVertical"), ignored);
     } else {
       classList->Remove(NS_LITERAL_STRING("overflowingVertical"), ignored);
     }
     ignored.SuppressException();
     return;
   }
 
+  uint32_t newWidth = std::max(1, NSToCoordFloor(GetRatio() * mImageWidth));
+  uint32_t newHeight = std::max(1, NSToCoordFloor(GetRatio() * mImageHeight));
+
   // Keep image content alive while changing the attributes.
   RefPtr<HTMLImageElement> image = HTMLImageElement::FromContent(mImageContent);
-  image->SetWidth(std::max(1, NSToCoordFloor(GetRatio() * mImageWidth)),
-                  IgnoreErrors());
-  image->SetHeight(std::max(1, NSToCoordFloor(GetRatio() * mImageHeight)),
-                   IgnoreErrors());
+
+  if (mImageIsResized &&
+      newWidth == image->Width() && newHeight == image->Height()) {
+    // Image has already been resized.
+    return;
+  }
+
+  image->SetWidth(newWidth, IgnoreErrors());
+  image->SetHeight(newHeight, IgnoreErrors());
 
   // The view might have been scrolled when zooming in, scroll back to the
   // origin now that we're showing a shrunk-to-window version.
   ScrollImageTo(0, 0, false);
 
   if (!mImageContent) {
     // ScrollImageTo flush destroyed our content.
     return;
--- a/dom/ipc/ContentParent.cpp
+++ b/dom/ipc/ContentParent.cpp
@@ -2000,46 +2000,60 @@ ContentParent::LaunchSubprocess(ProcessP
   nsAutoCStringN<1024> boolPrefs;
   nsAutoCStringN<1024> intPrefs;
   nsAutoCStringN<1024> stringPrefs;
 
   size_t prefsLen;
   ContentPrefs::GetEarlyPrefs(&prefsLen);
 
   for (unsigned int i = 0; i < prefsLen; i++) {
-    MOZ_ASSERT(i == 0 || strcmp(ContentPrefs::GetEarlyPref(i), ContentPrefs::GetEarlyPref(i - 1)) > 0);
-    switch (Preferences::GetType(ContentPrefs::GetEarlyPref(i))) {
+    const char* prefName = ContentPrefs::GetEarlyPref(i);
+    MOZ_ASSERT_IF(i > 0,
+                  strcmp(prefName, ContentPrefs::GetEarlyPref(i - 1)) > 0);
+
+    if (!Preferences::MustSendToContentProcesses(prefName)) {
+      continue;
+    }
+
+    switch (Preferences::GetType(prefName)) {
     case nsIPrefBranch::PREF_INT:
-      intPrefs.Append(nsPrintfCString("%u:%d|", i, Preferences::GetInt(ContentPrefs::GetEarlyPref(i))));
+      intPrefs.Append(nsPrintfCString("%u:%d|", i, Preferences::GetInt(prefName)));
       break;
     case nsIPrefBranch::PREF_BOOL:
-      boolPrefs.Append(nsPrintfCString("%u:%d|", i, Preferences::GetBool(ContentPrefs::GetEarlyPref(i))));
+      boolPrefs.Append(nsPrintfCString("%u:%d|", i, Preferences::GetBool(prefName)));
       break;
     case nsIPrefBranch::PREF_STRING: {
       nsAutoCString value;
-      Preferences::GetCString(ContentPrefs::GetEarlyPref(i), value);
+      Preferences::GetCString(prefName, value);
       stringPrefs.Append(nsPrintfCString("%u:%d;%s|", i, value.Length(), value.get()));
       }
       break;
     case nsIPrefBranch::PREF_INVALID:
       break;
     default:
-      printf("preference type: %x\n", Preferences::GetType(ContentPrefs::GetEarlyPref(i)));
+      printf("preference type: %x\n", Preferences::GetType(prefName));
       MOZ_CRASH();
     }
   }
 
   nsCString schedulerPrefs = Scheduler::GetPrefs();
 
-  extraArgs.push_back("-intPrefs");
-  extraArgs.push_back(intPrefs.get());
-  extraArgs.push_back("-boolPrefs");
-  extraArgs.push_back(boolPrefs.get());
-  extraArgs.push_back("-stringPrefs");
-  extraArgs.push_back(stringPrefs.get());
+  // Only do these ones if they're non-empty.
+  if (!intPrefs.IsEmpty()) {
+    extraArgs.push_back("-intPrefs");
+    extraArgs.push_back(intPrefs.get());
+  }
+  if (!boolPrefs.IsEmpty()) {
+    extraArgs.push_back("-boolPrefs");
+    extraArgs.push_back(boolPrefs.get());
+  }
+  if (!stringPrefs.IsEmpty()) {
+    extraArgs.push_back("-stringPrefs");
+    extraArgs.push_back(stringPrefs.get());
+  }
 
   // Scheduler prefs need to be handled differently because the scheduler needs
   // to start up in the content process before the normal preferences service.
   extraArgs.push_back("-schedulerPrefs");
   extraArgs.push_back(schedulerPrefs.get());
 
   if (gSafeMode) {
     extraArgs.push_back("-safeMode");
--- a/dom/ipc/PBrowser.ipdl
+++ b/dom/ipc/PBrowser.ipdl
@@ -4,17 +4,16 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 include protocol PColorPicker;
 include protocol PContent;
 include protocol PContentBridge;
 include protocol PDocAccessible;
-include protocol PDocumentRenderer;
 include protocol PFilePicker;
 include protocol PIndexedDBPermissionRequest;
 include protocol PRenderFrame;
 include protocol PPluginWidget;
 include protocol PRemotePrintJob;
 include protocol PChildToParentStream;
 include protocol PParentToChildStream;
 include protocol PFileDescriptorSet;
@@ -105,17 +104,16 @@ union OptionalShmem
 };
 
 nested(upto inside_cpow) sync protocol PBrowser
 {
     manager PContent or PContentBridge;
 
     manages PColorPicker;
     manages PDocAccessible;
-    manages PDocumentRenderer;
     manages PFilePicker;
     manages PIndexedDBPermissionRequest;
     manages PRenderFrame;
     manages PPluginWidget;
     manages PPaymentRequest;
 
 both:
     async AsyncMessage(nsString aMessage, CpowEntry[] aCpows,
@@ -727,34 +725,16 @@ child:
     /**
      * Activate event forwarding from client to parent.
      */
     async ActivateFrameEvent(nsString aType, bool capture);
 
     async LoadRemoteScript(nsString aURL, bool aRunInGlobalScope);
 
     /**
-     * Create a asynchronous request to render whatever document is
-     * loaded in the child when this message arrives.  When the
-     * request finishes, PDocumentRenderer:__delete__ is sent back to
-     * this side to notify completion.
-     *
-     * |documentRect| is the area of the remote document to draw,
-     * transformed by |transform|.  The rendered area will have the
-     * default background color |bgcolor|.  |renderFlags| are the
-     * nsIPresShell::RenderDocument() flags to use on the remote side,
-     * and if true, |flushLayout| will do just that before rendering
-     * the document.  The rendered image will be of size |renderSize|.
-     */
-    async PDocumentRenderer(nsRect documentRect, Matrix transform,
-                            nsString bgcolor,
-                            uint32_t renderFlags, bool flushLayout,
-                            IntSize renderSize);
-
-    /**
      * Sent by the chrome process when it no longer wants this remote
      * <browser>.  The child side cleans up in response, then
      * finalizing its death by sending back __delete__() to the
      * parent.
      */
     async Destroy();
 
     /**
deleted file mode 100644
--- a/dom/ipc/PDocumentRenderer.ipdl
+++ /dev/null
@@ -1,25 +0,0 @@
-/* -*- Mode: C++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 8 -*- */
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-include protocol PBrowser;
-
-include "mozilla/GfxMessageUtils.h";
-
-using nsIntSize from "nsSize.h";
-
-namespace mozilla {
-namespace ipc {
-
-protocol PDocumentRenderer
-{
-  manager PBrowser;
-
-parent:
-    // Returns the width and height, in pixels, of the returned ARGB32 data.
-    async __delete__(nsIntSize renderedSize, nsCString data);
-};
-
-} // namespace ipc
-} // namespace mozilla
--- a/dom/ipc/TabChild.cpp
+++ b/dom/ipc/TabChild.cpp
@@ -18,17 +18,16 @@
 #include "mozilla/Preferences.h"
 #include "mozilla/BrowserElementParent.h"
 #include "mozilla/ClearOnShutdown.h"
 #include "mozilla/EventListenerManager.h"
 #include "mozilla/dom/indexedDB/PIndexedDBPermissionRequestChild.h"
 #include "mozilla/dom/PaymentRequestChild.h"
 #include "mozilla/dom/TelemetryScrollProbe.h"
 #include "mozilla/IMEStateManager.h"
-#include "mozilla/ipc/DocumentRendererChild.h"
 #include "mozilla/ipc/URIUtils.h"
 #include "mozilla/layers/APZChild.h"
 #include "mozilla/layers/APZCCallbackHelper.h"
 #include "mozilla/layers/APZCTreeManager.h"
 #include "mozilla/layers/APZCTreeManagerChild.h"
 #include "mozilla/layers/APZEventState.h"
 #include "mozilla/layers/ContentProcessController.h"
 #include "mozilla/layers/CompositorBridgeChild.h"
@@ -2212,70 +2211,16 @@ bool
 TabChild::DeallocPDocAccessibleChild(a11y::PDocAccessibleChild* aChild)
 {
 #ifdef ACCESSIBILITY
   delete static_cast<mozilla::a11y::DocAccessibleChild*>(aChild);
 #endif
   return true;
 }
 
-PDocumentRendererChild*
-TabChild::AllocPDocumentRendererChild(const nsRect& documentRect,
-                                      const mozilla::gfx::Matrix& transform,
-                                      const nsString& bgcolor,
-                                      const uint32_t& renderFlags,
-                                      const bool& flushLayout,
-                                      const nsIntSize& renderSize)
-{
-    return new DocumentRendererChild();
-}
-
-bool
-TabChild::DeallocPDocumentRendererChild(PDocumentRendererChild* actor)
-{
-    delete actor;
-    return true;
-}
-
-mozilla::ipc::IPCResult
-TabChild::RecvPDocumentRendererConstructor(PDocumentRendererChild* actor,
-                                           const nsRect& documentRect,
-                                           const mozilla::gfx::Matrix& transform,
-                                           const nsString& bgcolor,
-                                           const uint32_t& renderFlags,
-                                           const bool& flushLayout,
-                                           const nsIntSize& renderSize)
-{
-    DocumentRendererChild *render = static_cast<DocumentRendererChild *>(actor);
-
-    nsCOMPtr<nsIWebBrowser> browser = do_QueryInterface(WebNavigation());
-    if (!browser)
-        return IPC_OK(); // silently ignore
-    nsCOMPtr<mozIDOMWindowProxy> window;
-    if (NS_FAILED(browser->GetContentDOMWindow(getter_AddRefs(window))) ||
-        !window)
-    {
-        return IPC_OK(); // silently ignore
-    }
-
-    nsCString data;
-    bool ret = render->RenderDocument(nsPIDOMWindowOuter::From(window),
-                                      documentRect, transform,
-                                      bgcolor,
-                                      renderFlags, flushLayout,
-                                      renderSize, data);
-    if (!ret)
-        return IPC_OK(); // silently ignore
-
-    if (!PDocumentRendererChild::Send__delete__(actor, renderSize, data)) {
-      return IPC_FAIL_NO_REASON(this);
-    }
-    return IPC_OK();
-}
-
 PColorPickerChild*
 TabChild::AllocPColorPickerChild(const nsString&, const nsString&)
 {
   MOZ_CRASH("unused");
   return nullptr;
 }
 
 bool
--- a/dom/ipc/TabChild.h
+++ b/dom/ipc/TabChild.h
@@ -470,37 +470,16 @@ public:
   RecvSwappedWithOtherRemoteLoader(const IPCTabContext& aContext) override;
 
   virtual PDocAccessibleChild*
   AllocPDocAccessibleChild(PDocAccessibleChild*, const uint64_t&,
                            const uint32_t&, const IAccessibleHolder&) override;
 
   virtual bool DeallocPDocAccessibleChild(PDocAccessibleChild*) override;
 
-  virtual PDocumentRendererChild*
-  AllocPDocumentRendererChild(const nsRect& aDocumentRect,
-                              const gfx::Matrix& aTransform,
-                              const nsString& aBggcolor,
-                              const uint32_t& aRenderFlags,
-                              const bool& aFlushLayout,
-                              const nsIntSize& arenderSize) override;
-
-  virtual bool
-  DeallocPDocumentRendererChild(PDocumentRendererChild* aCctor) override;
-
-  virtual mozilla::ipc::IPCResult
-  RecvPDocumentRendererConstructor(PDocumentRendererChild* aActor,
-                                   const nsRect& aDocumentRect,
-                                   const gfx::Matrix& aTransform,
-                                   const nsString& aBgcolor,
-                                   const uint32_t& aRenderFlags,
-                                   const bool& aFlushLayout,
-                                   const nsIntSize& aRenderSize) override;
-
-
   virtual PColorPickerChild*
   AllocPColorPickerChild(const nsString& aTitle,
                          const nsString& aInitialColor) override;
 
   virtual bool DeallocPColorPickerChild(PColorPickerChild* aActor) override;
 
   virtual PFilePickerChild*
   AllocPFilePickerChild(const nsString& aTitle, const int16_t& aMode) override;
--- a/dom/ipc/TabParent.cpp
+++ b/dom/ipc/TabParent.cpp
@@ -22,17 +22,16 @@
 #include "mozilla/dom/IPCBlobUtils.h"
 #include "mozilla/dom/PaymentRequestParent.h"
 #include "mozilla/EventStateManager.h"
 #include "mozilla/gfx/2D.h"
 #include "mozilla/gfx/DataSurfaceHelpers.h"
 #include "mozilla/gfx/GPUProcessManager.h"
 #include "mozilla/Hal.h"
 #include "mozilla/IMEStateManager.h"
-#include "mozilla/ipc/DocumentRendererParent.h"
 #include "mozilla/jsipc/CrossProcessObjectWrappers.h"
 #include "mozilla/layers/AsyncDragMetrics.h"
 #include "mozilla/layers/InputAPZContext.h"
 #include "mozilla/layout/RenderFrameParent.h"
 #include "mozilla/plugins/PPluginWidgetParent.h"
 #include "mozilla/LookAndFeel.h"
 #include "mozilla/MouseEvents.h"
 #include "mozilla/net/NeckoChild.h"
@@ -999,34 +998,16 @@ TabParent::GetTopLevelDocAccessible() co
   }
 
   MOZ_ASSERT(docs.Count() == 0, "If there isn't a top level accessible doc "
                                 "there shouldn't be an accessible doc at all!");
 #endif
   return nullptr;
 }
 
-PDocumentRendererParent*
-TabParent::AllocPDocumentRendererParent(const nsRect& documentRect,
-                                        const gfx::Matrix& transform,
-                                        const nsString& bgcolor,
-                                        const uint32_t& renderFlags,
-                                        const bool& flushLayout,
-                                        const nsIntSize& renderSize)
-{
-    return new DocumentRendererParent();
-}
-
-bool
-TabParent::DeallocPDocumentRendererParent(PDocumentRendererParent* actor)
-{
-    delete actor;
-    return true;
-}
-
 PFilePickerParent*
 TabParent::AllocPFilePickerParent(const nsString& aTitle, const int16_t& aMode)
 {
   return new FilePickerParent(aTitle, aMode);
 }
 
 bool
 TabParent::DeallocPFilePickerParent(PFilePickerParent* actor)
--- a/dom/ipc/TabParent.h
+++ b/dom/ipc/TabParent.h
@@ -461,27 +461,16 @@ public:
   bool SendSelectionEvent(mozilla::WidgetSelectionEvent& aEvent);
 
   bool SendHandleTap(TapType aType,
                      const LayoutDevicePoint& aPoint,
                      Modifiers aModifiers,
                      const ScrollableLayerGuid& aGuid,
                      uint64_t aInputBlockId);
 
-  virtual PDocumentRendererParent*
-  AllocPDocumentRendererParent(const nsRect& documentRect,
-                               const gfx::Matrix& transform,
-                               const nsString& bgcolor,
-                               const uint32_t& renderFlags,
-                               const bool& flushLayout,
-                               const nsIntSize& renderSize) override;
-
-  virtual bool
-  DeallocPDocumentRendererParent(PDocumentRendererParent* actor) override;
-
   virtual PFilePickerParent*
   AllocPFilePickerParent(const nsString& aTitle,
                          const int16_t& aMode) override;
 
   virtual bool DeallocPFilePickerParent(PFilePickerParent* actor) override;
 
   virtual PIndexedDBPermissionRequestParent*
   AllocPIndexedDBPermissionRequestParent(const Principal& aPrincipal) override;
--- a/dom/ipc/moz.build
+++ b/dom/ipc/moz.build
@@ -89,17 +89,16 @@ IPDL_SOURCES += [
     'PBrowser.ipdl',
     'PBrowserOrId.ipdlh',
     'PColorPicker.ipdl',
     'PContent.ipdl',
     'PContentBridge.ipdl',
     'PContentPermission.ipdlh',
     'PContentPermissionRequest.ipdl',
     'PCycleCollectWithLogs.ipdl',
-    'PDocumentRenderer.ipdl',
     'PFilePicker.ipdl',
     'PLoginReputation.ipdl',
     'PPluginWidget.ipdl',
     'PProcessHangMonitor.ipdl',
     'PTabContext.ipdlh',
     'PURLClassifier.ipdl',
     'PURLClassifierInfo.ipdlh',
     'PURLClassifierLocal.ipdl',
--- a/dom/webidl/InspectorUtils.webidl
+++ b/dom/webidl/InspectorUtils.webidl
@@ -59,17 +59,26 @@ namespace InspectorUtils {
                                           boolean showingAnonymousContent);
   sequence<DOMString> getBindingURLs(Element element);
   [Throws] boolean setContentState(Element element, unsigned long long state);
   [Throws] boolean removeContentState(
       Element element,
       unsigned long long state,
       optional boolean clearActiveDocument = false);
   unsigned long long getContentState(Element element);
-  [NewObject, Throws] sequence<InspectorFontFace> getUsedFontFaces(Range range);
+
+  // Get the font face(s) actually used to render the text in /range/,
+  // as a collection of InspectorFontFace objects (below).
+  // If /maxRanges/ is greater than zero, each InspectorFontFace will record
+  // up to /maxRanges/ fragments of content that used the face, for the caller
+  // to access via its .ranges attribute.
+  [NewObject, Throws] sequence<InspectorFontFace> getUsedFontFaces(
+      Range range,
+      optional unsigned long maxRanges = 0);
+
   sequence<DOMString> getCSSPseudoElementNames();
   void addPseudoClassLock(Element element,
                           DOMString pseudoClass,
                           optional boolean enabled = true);
   void removePseudoClassLock(Element element, DOMString pseudoClass);
   boolean hasPseudoClassLock(Element element, DOMString pseudoClass);
   void clearPseudoClassLocks(Element element);
   [Throws] void parseStyleSheet(CSSStyleSheet sheet, DOMString input);
@@ -130,16 +139,24 @@ interface InspectorFontFace {
   readonly attribute DOMString CSSFamilyName; // a family name that could be used in CSS font-family
                                               // (not necessarily the actual name that was used,
                                               // due to aliases, generics, localized names, etc)
 
   [NewObject,Throws] sequence<InspectorVariationAxis> getVariationAxes();
   [NewObject,Throws] sequence<InspectorVariationInstance> getVariationInstances();
   [NewObject,Throws] sequence<InspectorFontFeature> getFeatures();
 
+  // A list of Ranges of text rendered with this face.
+  // This will list the first /maxRanges/ ranges found when InspectorUtils.getUsedFontFaces
+  // was called (so it will be empty unless a non-zero maxRanges argument was passed).
+  // Note that this indicates how the document was rendered at the time of calling
+  // getUsedFontFaces; it does not reflect any subsequent modifications, so if styles
+  // have been modified since calling getUsedFontFaces, it may no longer be accurate.
+  [Constant,Cached]  readonly attribute sequence<Range> ranges;
+
   // meaningful only when the font is a user font defined using @font-face
   readonly attribute CSSFontFaceRule? rule; // null if no associated @font-face rule
   readonly attribute long srcIndex; // index in the rule's src list, -1 if no @font-face rule
   readonly attribute DOMString URI; // empty string if not a downloaded font, i.e. local
   readonly attribute DOMString localName; // empty string  if not a src:local(...) rule
   readonly attribute DOMString format; // as per http://www.w3.org/TR/css3-webfonts/#referencing
   readonly attribute DOMString metadata; // XML metadata from WOFF file (if any)
 };
--- a/gfx/ipc/GPUParent.cpp
+++ b/gfx/ipc/GPUParent.cpp
@@ -457,17 +457,19 @@ GPUParent::ActorDestroy(ActorDestroyReas
 
   if (mVsyncBridge) {
     mVsyncBridge->Shutdown();
     mVsyncBridge = nullptr;
   }
   dom::VideoDecoderManagerParent::ShutdownVideoBridge();
   CompositorThreadHolder::Shutdown();
   VRListenerThreadHolder::Shutdown();
-  if (gfxVars::UseWebRender()) {
+  // There is a case that RenderThread exists when gfxVars::UseWebRender() is false.
+  // This could happen when WebRender was fallbacked to compositor.
+  if (wr::RenderThread::Get()) {
     wr::RenderThread::ShutDown();
 
     wr::WebRenderAPI::ShutdownExternalLogHandler();
   }
   Factory::ShutDown();
 #if defined(XP_WIN)
   DeviceManagerDx::Shutdown();
 #endif
--- a/gfx/layers/client/TiledContentClient.cpp
+++ b/gfx/layers/client/TiledContentClient.cpp
@@ -554,16 +554,18 @@ TileClient::ValidateBackBufferFromFront(
       const IntRect rectToCopy = regionToCopy.GetBounds();
       gfx::IntRect gfxRectToCopy(rectToCopy.X(), rectToCopy.Y(), rectToCopy.Width(), rectToCopy.Height());
       if (CopyFrontToBack(mFrontBuffer, mBackBuffer, gfxRectToCopy, aFlags, aCopies, aClients)) {
         if (mBackBufferOnWhite) {
           MOZ_ASSERT(mFrontBufferOnWhite);
           if (CopyFrontToBack(mFrontBufferOnWhite, mBackBufferOnWhite, gfxRectToCopy, aFlags, aCopies, aClients)) {
             mInvalidBack.SetEmpty();
           }
+        } else {
+          mInvalidBack.SetEmpty();
         }
       }
     }
   }
 }
 
 void
 TileClient::DiscardFrontBuffer()
--- a/gfx/thebes/gfxPlatform.cpp
+++ b/gfx/thebes/gfxPlatform.cpp
@@ -1047,17 +1047,19 @@ gfxPlatform::ShutdownLayersIPC()
         }
     } else if (XRE_IsParentProcess()) {
         gfx::VRManagerChild::ShutDown();
         layers::CompositorManagerChild::Shutdown();
         layers::ImageBridgeChild::ShutDown();
         // This has to happen after shutting down the child protocols.
         layers::CompositorThreadHolder::Shutdown();
         gfx::VRListenerThreadHolder::Shutdown();
-        if (gfxVars::UseWebRender()) {
+        // There is a case that RenderThread exists when gfxVars::UseWebRender() is false.
+        // This could happen when WebRender was fallbacked to compositor.
+        if (wr::RenderThread::Get()) {
           wr::RenderThread::ShutDown();
 
           Preferences::UnregisterCallback(WebRenderDebugPrefChangeCallback, WR_DEBUG_PREF);
         }
 
     } else {
       // TODO: There are other kind of processes and we should make sure gfx
       // stuff is either not created there or shut down properly.
--- a/js/public/ProfilingStack.h
+++ b/js/public/ProfilingStack.h
@@ -48,17 +48,17 @@ class PseudoStack;
 //  (3) Thread A is resumed.
 //
 // Thread suspension is achieved using platform-specific APIs; refer to each
 // platform's Sampler::SuspendAndSampleAndResumeThread implementation in
 // platform-*.cpp for details.
 //
 // When the thread is suspended, the values in pseudoStack->stackPointer and in
 // the entry range pseudoStack->entries[0..pseudoStack->stackPointer] need to
-// be in a consistent state, so that thread A does not read partially-
+// be in a consistent state, so that thread S does not read partially-
 // constructed profile entries. More specifically, we have two requirements:
 //  (1) When adding a new entry at the top of the stack, its ProfileEntry data
 //      needs to be put in place *before* the stackPointer is incremented, and
 //      the compiler + CPU need to know that this order matters.
 //  (2) When popping an entry from the stack and then preparing the
 //      ProfileEntry data for the next frame that is about to be pushed, the
 //      decrement of the stackPointer in pop() needs to happen *before* the
 //      ProfileEntry for the new frame is being popuplated, and the compiler +
--- a/js/src/builtin/SIMD.cpp
+++ b/js/src/builtin/SIMD.cpp
@@ -750,17 +750,25 @@ struct Identity {
 };
 template<typename T>
 struct Abs {
     static T apply(T x) { return mozilla::Abs(x); }
 };
 template<typename T>
 struct Neg {
     using MaybeUnsignedT = typename detail::MaybeMakeUnsigned<T>::Type;
-    static T apply(T x) { return MaybeUnsignedT(-1) * MaybeUnsignedT(x); }
+    static T apply(T x) {
+        // Prepend |1U| to force integral promotion through *unsigned* types.
+        // Otherwise when |T = uint16_t| and |int| is 32-bit, we could have
+        // |uint16_t(-1) * uint16_t(65535)| which would really be
+        // |int(65535) * int(65535)|, but as |4294836225 > 2147483647| would
+        // perform signed integer overflow.
+        // https://stackoverflow.com/questions/24795651/whats-the-best-c-way-to-multiply-unsigned-integers-modularly-safely
+        return static_cast<MaybeUnsignedT>(1U * MaybeUnsignedT(-1) * MaybeUnsignedT(x));
+    }
 };
 template<typename T>
 struct Not {
     static T apply(T x) { return ~x; }
 };
 template<typename T>
 struct LogicalNot {
     static T apply(T x) { return !x; }
--- a/js/src/builtin/TestingFunctions.cpp
+++ b/js/src/builtin/TestingFunctions.cpp
@@ -561,16 +561,29 @@ WasmSignExtensionSupported(JSContext* cx
 #else
     bool isSupported = false;
 #endif
     args.rval().setBoolean(isSupported);
     return true;
 }
 
 static bool
+WasmSaturatingTruncationSupported(JSContext* cx, unsigned argc, Value* vp)
+{
+    CallArgs args = CallArgsFromVp(argc, vp);
+#ifdef ENABLE_WASM_SATURATING_TRUNC_OPS
+    bool isSupported = true;
+#else
+    bool isSupported = false;
+#endif
+    args.rval().setBoolean(isSupported);
+    return true;
+}
+
+static bool
 WasmCompileMode(JSContext* cx, unsigned argc, Value* vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
 
     // We default to ion if nothing is enabled, as does the Wasm compiler.
     JSString* result;
     if (!wasm::HasSupport(cx))
         result = JS_NewStringCopyZ(cx, "disabled");
@@ -5294,16 +5307,21 @@ gc::ZealModeHelpText),
 "  Returns a boolean indicating whether the WebAssembly threads proposal is\n"
 "  supported on the current device."),
 
     JS_FN_HELP("wasmSignExtensionSupported", WasmSignExtensionSupported, 0, 0,
 "wasmSignExtensionSupported()",
 "  Returns a boolean indicating whether the WebAssembly sign extension opcodes are\n"
 "  supported on the current device."),
 
+    JS_FN_HELP("wasmSaturatingTruncationSupported", WasmSaturatingTruncationSupported, 0, 0,
+"wasmSaturatingTruncationSupported()",
+"  Returns a boolean indicating whether the WebAssembly saturating truncates opcodes are\n"
+"  supported on the current device."),
+
     JS_FN_HELP("wasmCompileMode", WasmCompileMode, 0, 0,
 "wasmCompileMode()",
 "  Returns a string indicating the available compile policy: 'baseline', 'ion',\n"
 "  'baseline-or-ion', or 'disabled' (if wasm is not available at all)."),
 
     JS_FN_HELP("wasmTextToBinary", WasmTextToBinary, 1, 0,
 "wasmTextToBinary(str)",
 "  Translates the given text wasm module into its binary encoding."),
--- a/js/src/jit-test/lib/wasm-binary.js
+++ b/js/src/jit-test/lib/wasm-binary.js
@@ -90,17 +90,19 @@ const I64TruncUF32Code = 0xaf;
 const I64TruncSF64Code = 0xb0;
 const I64TruncUF64Code = 0xb1;
 const I64DivSCode      = 0x7f;
 const I64DivUCode      = 0x80;
 const I64RemSCode      = 0x81;
 const I64RemUCode      = 0x82;
 
 const FirstInvalidOpcode = wasmThreadsSupported() ? 0xc5 : 0xc0;
-const LastInvalidOpcode = 0xfd;
+const LastInvalidOpcode = 0xfb;
+const NumericPrefix = 0xfc;
+const SimdPrefix = 0xfd;
 const AtomicPrefix = 0xfe;
 const MozPrefix = 0xff;
 
 // DefinitionKind
 const FunctionCode     = 0x00;
 const TableCode        = 0x01;
 const MemoryCode       = 0x02;
 const GlobalCode       = 0x03;
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/self-test/cacheEntry.js
@@ -0,0 +1,23 @@
+// These tests are checking that CacheEntry_getBytecode properly set an error
+// when there is no bytecode registered.
+var caught = 0;
+var code = cacheEntry("");
+try {
+    offThreadDecodeScript(code);
+}
+catch (e) {
+    // offThreadDecodeScript does not work with the --no-thread command line option.
+    assertEq(e.message.includes("CacheEntry") || e.message.includes("offThreadDecodeScript"), true);
+    caught++;
+}
+
+code = cacheEntry("");
+try {
+    evaluate(code, {loadBytecode: true});
+}
+catch (e) {
+    assertEq(e.message.includes("CacheEntry"), true);
+    caught++;
+}
+
+assertEq(caught, 2);
--- a/js/src/jit-test/tests/wasm/binary.js
+++ b/js/src/jit-test/tests/wasm/binary.js
@@ -459,21 +459,34 @@ function checkIllegalPrefixed(prefix, op
 //  0x10 .. 0x4f are primitive atomic ops
 
 for (let i = 3; i < 0x10; i++)
     checkIllegalPrefixed(AtomicPrefix, i);
 
 for (let i = 0x4f; i < 0x100; i++)
     checkIllegalPrefixed(AtomicPrefix, i);
 
+// Illegal Numeric opcodes
+//
+// Feb 2018 numeric draft:
+//
+//  0x00 .. 0x07 are saturating truncation ops
+
+for (let i = 0x08; i < 256; i++)
+    checkIllegalPrefixed(NumericPrefix, i);
+
+// Illegal SIMD opcodes (all of them, for now)
+for (let i = 0; i < 256; i++)
+    checkIllegalPrefixed(SimdPrefix, i);
+
 // Illegal MozPrefix opcodes (all of them)
 for (let i = 0; i < 256; i++)
     checkIllegalPrefixed(MozPrefix, i);
 
-for (let prefix of [AtomicPrefix, MozPrefix]) {
+for (let prefix of [AtomicPrefix, NumericPrefix, SimdPrefix, MozPrefix]) {
     // Prefix without a subsequent opcode
     let binary = moduleWithSections([v2vSigSection, declSection([0]), bodySection([funcBody({locals:[], body:[prefix]})])]);
     assertErrorMessage(() => wasmEval(binary), CompileError, /unrecognized opcode/);
     assertEq(WebAssembly.validate(binary), false);
 }
 
 // Checking stack trace.
 function runStackTraceTest(moduleName, funcNames, expectedName) {
--- a/js/src/jit-test/tests/wasm/conversion.js
+++ b/js/src/jit-test/tests/wasm/conversion.js
@@ -50,17 +50,17 @@ function testConversion0(resultType, opc
 function testConversion(resultType, opcode, paramType, op, expect) {
   testConversion0(resultType, `${resultType}.${opcode}/${paramType}`, paramType, op, expect);
 }
 
 function testSignExtension(resultType, opcode, paramType, op, expect) {
   testConversion0(resultType, `${resultType}.${opcode}`, paramType, op, expect);
 }
 
-function testTrap(resultType, opcode, paramType, op, expect) {
+function testTrap(resultType, opcode, paramType, op) {
     let func = wasmEvalText(`(module
         (func
             (param ${paramType})
             (result ${resultType})
             (${resultType}.${opcode}/${paramType} (get_local 0))
         )
         (func
             (param ${paramType})
@@ -71,16 +71,18 @@ function testTrap(resultType, opcode, pa
         (export "" 1)
     )`).exports[""];
 
     let expectedError = op === 'nan' ? /invalid conversion to integer/ : /integer overflow/;
 
     assertErrorMessage(() => func(jsify(op)), Error, expectedError);
 }
 
+var p = Math.pow;
+
 testConversion('i32', 'wrap', 'i64', '0x100000028', 40);
 testConversion('i32', 'wrap', 'i64', -10, -10);
 testConversion('i32', 'wrap', 'i64', "0xffffffff7fffffff", 0x7fffffff);
 testConversion('i32', 'wrap', 'i64', "0xffffffff00000000", 0);
 testConversion('i32', 'wrap', 'i64', "0xfffffffeffffffff", -1);
 testConversion('i32', 'wrap', 'i64', "0x1234567801abcdef", 0x01abcdef);
 testConversion('i32', 'wrap', 'i64', "0x8000000000000002", 2);
 
@@ -98,19 +100,19 @@ testConversion('i64', 'extend_u', 'i32',
 testConversion('i64', 'extend_u', 'i32', 0x80000000, "0x0000000080000000");
 
 testConversion('f32', 'convert_s', 'i64', 1, 1.0);
 testConversion('f32', 'convert_s', 'i64', -1, -1.0);
 testConversion('f32', 'convert_s', 'i64', 0, 0.0);
 testConversion('f32', 'convert_s', 'i64', "0x7fffffffffffffff", 9223372036854775807.0);
 testConversion('f32', 'convert_s', 'i64', "0x8000000000000000", -9223372036854775808.0);
 testConversion('f32', 'convert_s', 'i64', "0x11db9e76a2483", 314159275180032.0);
-testConversion('f32', 'convert_s', 'i64', "0x7fffffff", 2147483648.0); // closesth approx.
+testConversion('f32', 'convert_s', 'i64', "0x7fffffff", 2147483648.0); // closest approx.
 testConversion('f32', 'convert_s', 'i64', "0x80000000", 2147483648.0);
-testConversion('f32', 'convert_s', 'i64', "0x80000001", 2147483648.0); // closesth approx.
+testConversion('f32', 'convert_s', 'i64', "0x80000001", 2147483648.0); // closest approx.
 
 // Interesting values at the boundaries.
 testConversion('f32', 'convert_s', 'i64', "0x358a09a000000002", 3857906751034621952);
 testConversion('f32', 'convert_s', 'i64', "0x8000004000000001", -9223371487098961920);
 testConversion('f32', 'convert_s', 'i64', "0xffdfffffdfffffff", -9007200328482816);
 testConversion('f32', 'convert_s', 'i64', "0x0020000020000001", 9007200328482816);
 testConversion('f32', 'convert_s', 'i64', "0x7fffff4000000001", 9223371487098961920);
 
@@ -233,32 +235,87 @@ testTrap('i64', 'trunc_u', 'f32', 184467
 testTrap('i64', 'trunc_u', 'f32', -1);
 testTrap('i64', 'trunc_u', 'f32', "nan");
 testTrap('i64', 'trunc_u', 'f32', "infinity");
 testTrap('i64', 'trunc_u', 'f32', "-infinity");
 
 testConversion('i64', 'reinterpret', 'f64', 40.09999999999968, "0x40440ccccccccca0");
 testConversion('f64', 'reinterpret', 'i64', "0x40440ccccccccca0", 40.09999999999968);
 
+if (wasmSaturatingTruncationSupported()) {
+    var u64max = '0xffffffffffffffff';
+    var s64max = '0x7fffffffffffffff';
+    var s64min = '-0x8000000000000000';
+    var s32max = 2147483647;
+    var s32min = -2147483648;
+
+    testConversion('i32', 'trunc_s:sat', 'f32', NaN, 0);
+    testConversion('i32', 'trunc_s:sat', 'f32', Infinity, s32max);
+    testConversion('i32', 'trunc_s:sat', 'f32', -Infinity, s32min);
+    testConversion('i32', 'trunc_s:sat', 'f32', p(2, 31), s32max);
+    testConversion('i32', 'trunc_s:sat', 'f32', -p(2, 31) - 256, s32min);
+
+    testConversion('i32', 'trunc_s:sat', 'f64', NaN, 0);
+    testConversion('i32', 'trunc_s:sat', 'f64', Infinity, s32max);
+    testConversion('i32', 'trunc_s:sat', 'f64', -Infinity, s32min);
+    testConversion('i32', 'trunc_s:sat', 'f64', p(2, 31), s32max);
+    testConversion('i32', 'trunc_s:sat', 'f64', -p(2, 31) - 1, s32min);
+
+    testConversion('i32', 'trunc_u:sat', 'f32', NaN, 0);
+    testConversion('i32', 'trunc_u:sat', 'f32', Infinity, -1);
+    testConversion('i32', 'trunc_u:sat', 'f32', -Infinity, 0);
+    testConversion('i32', 'trunc_u:sat', 'f32', -1, 0);
+    testConversion('i32', 'trunc_u:sat', 'f32', p(2, 32), -1);
+
+    testConversion('i32', 'trunc_u:sat', 'f64', NaN, 0);
+    testConversion('i32', 'trunc_u:sat', 'f64', Infinity, -1);
+    testConversion('i32', 'trunc_u:sat', 'f64', -Infinity, 0);
+    testConversion('i32', 'trunc_u:sat', 'f64', -1, 0);
+    testConversion('i32', 'trunc_u:sat', 'f64', p(2, 32), -1);
+
+    testConversion('i64', 'trunc_s:sat', 'f64', 9223372036854776000.0, s64max);
+    testConversion('i64', 'trunc_s:sat', 'f64', -9223372036854778000.0, s64min);
+    testConversion('i64', 'trunc_s:sat', 'f64', 'nan', '0');
+    testConversion('i64', 'trunc_s:sat', 'f64', 'infinity', s64max);
+    testConversion('i64', 'trunc_s:sat', 'f64', '-infinity', s64min);
+
+    testConversion('i64', 'trunc_u:sat', 'f64', -1, '0');
+    testConversion('i64', 'trunc_u:sat', 'f64', 18446744073709551616.0, u64max);
+    testConversion('i64', 'trunc_u:sat', 'f64', 'nan', '0');
+    testConversion('i64', 'trunc_u:sat', 'f64', 'infinity', u64max);
+    testConversion('i64', 'trunc_u:sat', 'f64', '-infinity', '0');
+
+    testConversion('i64', 'trunc_s:sat', 'f32', 9223372036854776000.0, s64max);
+    testConversion('i64', 'trunc_s:sat', 'f32', -9223372586610630000.0, s64min);
+    testConversion('i64', 'trunc_s:sat', 'f32', 'nan', '0');
+    testConversion('i64', 'trunc_s:sat', 'f32', 'infinity', s64max);
+    testConversion('i64', 'trunc_s:sat', 'f32', '-infinity', s64min);
+
+    testConversion('i64', 'trunc_u:sat', 'f32', 18446744073709551616.0, u64max);
+    testConversion('i64', 'trunc_u:sat', 'f32', -1, '0');
+    testConversion('i64', 'trunc_u:sat', 'f32', 'nan', '0');
+    testConversion('i64', 'trunc_u:sat', 'f32', 'infinity', u64max);
+    testConversion('i64', 'trunc_u:sat', 'f32', '-infinity', '0');
+}
+
 if (wasmSignExtensionSupported()) {
     testSignExtension('i32', 'extend8_s', 'i32', 0x7F, 0x7F);
     testSignExtension('i32', 'extend8_s', 'i32', 0x80, -0x80);
     testSignExtension('i32', 'extend16_s', 'i32', 0x7FFF, 0x7FFF);
     testSignExtension('i32', 'extend16_s', 'i32', 0x8000, -0x8000);
     testSignExtension('i64', 'extend8_s', 'i64', 0x7F, 0x7F);
     testSignExtension('i64', 'extend8_s', 'i64', 0x80, -0x80);
     testSignExtension('i64', 'extend16_s', 'i64', 0x7FFF, 0x7FFF);
     testSignExtension('i64', 'extend16_s', 'i64', 0x8000, -0x8000);
     testSignExtension('i64', 'extend32_s', 'i64', 0x7FFFFFFF, 0x7FFFFFFF);
     testSignExtension('i64', 'extend32_s', 'i64', "0x80000000", "0xFFFFFFFF80000000");
 }
 
 // i32.trunc_s* : all values in ] -2**31 - 1; 2**31 [ are acceptable.
 // f32:
-var p = Math.pow;
 testConversion('i32', 'trunc_s', 'f32', 40.1, 40);
 testConversion('i32', 'trunc_s', 'f32', p(2, 31) - 128, p(2, 31) - 128); // last f32 value exactly representable < 2**31.
 testConversion('i32', 'trunc_s', 'f32', -p(2, 31), -p(2,31)); // last f32 value exactly representable > -2**31 - 1.
 
 testTrap('i32', 'trunc_s', 'f32', 'nan');
 testTrap('i32', 'trunc_s', 'f32', 'infinity');
 testTrap('i32', 'trunc_s', 'f32', '-infinity');
 testTrap('i32', 'trunc_s', 'f32', p(2, 31));
@@ -288,21 +345,21 @@ testTrap('i32', 'trunc_u', 'f32', '-infi
 testTrap('i32', 'trunc_u', 'f32', -1);
 testTrap('i32', 'trunc_u', 'f32', p(2,32));
 
 // f64:
 testConversion('i32', 'trunc_u', 'f64', 40.1, 40);
 testConversion('i32', 'trunc_u', 'f64', p(2,32) - 0.001, (p(2,32) - 1)|0); // example value near the top.
 testConversion('i32', 'trunc_u', 'f64', -0.99999, 0); // example value near the bottom.
 
-testTrap('i32', 'trunc_u', 'f32', 'nan');
-testTrap('i32', 'trunc_u', 'f32', 'infinity');
-testTrap('i32', 'trunc_u', 'f32', '-infinity');
-testTrap('i32', 'trunc_u', 'f32', -1);
-testTrap('i32', 'trunc_u', 'f32', p(2,32));
+testTrap('i32', 'trunc_u', 'f64', 'nan');
+testTrap('i32', 'trunc_u', 'f64', 'infinity');
+testTrap('i32', 'trunc_u', 'f64', '-infinity');
+testTrap('i32', 'trunc_u', 'f64', -1);
+testTrap('i32', 'trunc_u', 'f64', p(2,32));
 
 // Other opcodes.
 testConversion('i32', 'reinterpret', 'f32', 40.1, 1109419622);
 testConversion('f32', 'reinterpret', 'i32', 40, 5.605193857299268e-44);
 
 testConversion('f32', 'convert_s', 'i32', 40, 40);
 testConversion('f32', 'convert_u', 'i32', 40, 40);
 
--- a/js/src/jit/IonTypes.h
+++ b/js/src/jit/IonTypes.h
@@ -922,12 +922,17 @@ enum class RoundingMode {
     TowardsZero
 };
 
 // If a function contains no calls, we can assume the caller has checked the
 // stack limit up to this maximum frame size. This works because the jit stack
 // limit has a generous buffer before the real end of the native stack.
 static const uint32_t MAX_UNCHECKED_LEAF_FRAME_SIZE = 64;
 
+// Truncating conversion modifiers.
+typedef uint32_t TruncFlags;
+static const TruncFlags TRUNC_UNSIGNED   = TruncFlags(1) << 0;
+static const TruncFlags TRUNC_SATURATING = TruncFlags(1) << 1;
+
 } // namespace jit
 } // namespace js
 
 #endif /* jit_IonTypes_h */
--- a/js/src/jit/MIR.cpp
+++ b/js/src/jit/MIR.cpp
@@ -4397,32 +4397,32 @@ MWasmTruncateToInt32::foldsTo(TempAlloca
     if (input->type() == MIRType::Int32)
         return input;
 
     if (input->type() == MIRType::Double && input->isConstant()) {
         double d = input->toConstant()->toDouble();
         if (IsNaN(d))
             return this;
 
-        if (!isUnsigned_ && d <= double(INT32_MAX) && d >= double(INT32_MIN))
+        if (!isUnsigned() && d <= double(INT32_MAX) && d >= double(INT32_MIN))
             return MConstant::New(alloc, Int32Value(ToInt32(d)));
 
-        if (isUnsigned_ && d <= double(UINT32_MAX) && d >= 0)
+        if (isUnsigned() && d <= double(UINT32_MAX) && d >= 0)
             return MConstant::New(alloc, Int32Value(ToInt32(d)));
     }
 
     if (input->type() == MIRType::Float32 && input->isConstant()) {
         double f = double(input->toConstant()->toFloat32());
         if (IsNaN(f))
             return this;
 
-        if (!isUnsigned_ && f <= double(INT32_MAX) && f >= double(INT32_MIN))
+        if (!isUnsigned() && f <= double(INT32_MAX) && f >= double(INT32_MIN))
             return MConstant::New(alloc, Int32Value(ToInt32(f)));
 
-        if (isUnsigned_ && f <= double(UINT32_MAX) && f >= 0)
+        if (isUnsigned() && f <= double(UINT32_MAX) && f >= 0)
             return MConstant::New(alloc, Int32Value(ToInt32(f)));
     }
 
     return this;
 }
 
 MDefinition*
 MWrapInt64ToInt32::foldsTo(TempAllocator& alloc)
--- a/js/src/jit/MIR.h
+++ b/js/src/jit/MIR.h
@@ -5590,78 +5590,85 @@ class MExtendInt32ToInt64
         return AliasSet::None();
     }
 };
 
 class MWasmTruncateToInt64
   : public MUnaryInstruction,
     public NoTypePolicy::Data
 {
-    bool isUnsigned_;
+    TruncFlags flags_;
     wasm::BytecodeOffset bytecodeOffset_;
 
-    MWasmTruncateToInt64(MDefinition* def, bool isUnsigned, wasm::BytecodeOffset bytecodeOffset)
+    MWasmTruncateToInt64(MDefinition* def, TruncFlags flags, wasm::BytecodeOffset bytecodeOffset)
       : MUnaryInstruction(classOpcode, def),
-        isUnsigned_(isUnsigned),
+        flags_(flags),
         bytecodeOffset_(bytecodeOffset)
     {
         setResultType(MIRType::Int64);
         setGuard(); // neither removable nor movable because of possible side-effects.
     }
 
   public:
     INSTRUCTION_HEADER(WasmTruncateToInt64)
     TRIVIAL_NEW_WRAPPERS
 
-    bool isUnsigned() const { return isUnsigned_; }
+    bool isUnsigned() const { return flags_ & TRUNC_UNSIGNED; }
+    bool isSaturating() const { return flags_ & TRUNC_SATURATING; }
+    TruncFlags flags() const { return flags_; }
     wasm::BytecodeOffset bytecodeOffset() const { return bytecodeOffset_; }
 
     bool congruentTo(const MDefinition* ins) const override {
         return congruentIfOperandsEqual(ins) &&
-               ins->toWasmTruncateToInt64()->isUnsigned() == isUnsigned_;
+               ins->toWasmTruncateToInt64()->flags() == flags_;
     }
     AliasSet getAliasSet() const override {
         return AliasSet::None();
     }
 };
 
 // Truncate a value to an int32, with wasm semantics: this will trap when the
 // value is out of range.
 class MWasmTruncateToInt32
   : public MUnaryInstruction,
     public NoTypePolicy::Data
 {
-    bool isUnsigned_;
+    TruncFlags flags_;
     wasm::BytecodeOffset bytecodeOffset_;
 
-    explicit MWasmTruncateToInt32(MDefinition* def, bool isUnsigned,
+    explicit MWasmTruncateToInt32(MDefinition* def, TruncFlags flags,
                                   wasm::BytecodeOffset bytecodeOffset)
-      : MUnaryInstruction(classOpcode, def),
-        isUnsigned_(isUnsigned), bytecodeOffset_(bytecodeOffset)
+      : MUnaryInstruction(classOpcode, def), flags_(flags), bytecodeOffset_(bytecodeOffset)
     {
         setResultType(MIRType::Int32);
         setGuard(); // neither removable nor movable because of possible side-effects.
     }
 
   public:
     INSTRUCTION_HEADER(WasmTruncateToInt32)
     TRIVIAL_NEW_WRAPPERS
 
     bool isUnsigned() const {
-        return isUnsigned_;
+        return flags_ & TRUNC_UNSIGNED;
+    }
+    bool isSaturating() const {
+        return flags_ & TRUNC_SATURATING;
+    }
+    TruncFlags flags() const {
+        return flags_;
     }
     wasm::BytecodeOffset bytecodeOffset() const {
         return bytecodeOffset_;
     }
 
     MDefinition* foldsTo(TempAllocator& alloc) override;
 
     bool congruentTo(const MDefinition* ins) const override {
         return congruentIfOperandsEqual(ins) &&
-               ins->toWasmTruncateToInt32()->isUnsigned() == isUnsigned_;
+               ins->toWasmTruncateToInt32()->flags() == flags_;
     }
 
     AliasSet getAliasSet() const override {
         return AliasSet::None();
     }
 };
 
 class MInt64ToFloatingPoint
--- a/js/src/jit/MacroAssembler.cpp
+++ b/js/src/jit/MacroAssembler.cpp
@@ -3234,36 +3234,43 @@ MacroAssembler::wasmCallIndirect(const w
         wasm::OldTrapDesc oobTrap(trapOffset, wasm::Trap::OutOfBounds, framePushed());
         branch32(Assembler::Condition::AboveOrEqual, index, scratch, oobTrap);
     }
 
     // Load the base pointer of the table.
     loadWasmGlobalPtr(callee.tableBaseGlobalDataOffset(), scratch);
 
     // Load the callee from the table.
-    wasm::OldTrapDesc nullTrap(trapOffset, wasm::Trap::IndirectCallToNull, framePushed());
     if (callee.wasmTableIsExternal()) {
         static_assert(sizeof(wasm::ExternalTableElem) == 8 || sizeof(wasm::ExternalTableElem) == 16,
                       "elements of external tables are two words");
         if (sizeof(wasm::ExternalTableElem) == 8) {
             computeEffectiveAddress(BaseIndex(scratch, index, TimesEight), scratch);
         } else {
             lshift32(Imm32(4), index);
             addPtr(index, scratch);
         }
 
         loadPtr(Address(scratch, offsetof(wasm::ExternalTableElem, tls)), WasmTlsReg);
-        branchTest32(Assembler::Zero, WasmTlsReg, WasmTlsReg, nullTrap);
+
+        Label nonNull;
+        branchTest32(Assembler::NonZero, WasmTlsReg, WasmTlsReg, &nonNull);
+        wasmTrap(wasm::Trap::IndirectCallToNull, trapOffset);
+        bind(&nonNull);
 
         loadWasmPinnedRegsFromTls();
 
         loadPtr(Address(scratch, offsetof(wasm::ExternalTableElem, code)), scratch);
     } else {
         loadPtr(BaseIndex(scratch, index, ScalePointer), scratch);
-        branchTest32(Assembler::Zero, scratch, scratch, nullTrap);
+
+        Label nonNull;
+        branchTest32(Assembler::NonZero, scratch, scratch, &nonNull);
+        wasmTrap(wasm::Trap::IndirectCallToNull, trapOffset);
+        bind(&nonNull);
     }
 
     call(desc, scratch);
 }
 
 void
 MacroAssembler::wasmEmitOldTrapOutOfLineCode()
 {
--- a/js/src/jit/MacroAssembler.h
+++ b/js/src/jit/MacroAssembler.h
@@ -1364,42 +1364,42 @@ class MacroAssembler : public MacroAssem
     template <typename T, class L>
     inline void branchTestMagicImpl(Condition cond, const T& t, L label)
         DEFINED_ON(arm, arm64, x86_shared);
 
   public:
 
     inline void cmp32Move32(Condition cond, Register lhs, Register rhs, Register src,
                             Register dest)
-        DEFINED_ON(arm, arm64, x86_shared);
+        DEFINED_ON(arm, arm64, mips_shared, x86_shared);
 
     inline void cmp32Move32(Condition cond, Register lhs, const Address& rhs, Register src,
                             Register dest)
-        DEFINED_ON(arm, arm64, x86_shared);
+        DEFINED_ON(arm, arm64, mips_shared, x86_shared);
 
     inline void cmp32MovePtr(Condition cond, Register lhs, Imm32 rhs, Register src,
                              Register dest)
-        DEFINED_ON(arm, arm64, x86, x64);
+        DEFINED_ON(arm, arm64, mips_shared, x86, x64);
 
     inline void test32LoadPtr(Condition cond, const Address& addr, Imm32 mask, const Address& src,
                               Register dest)
-        DEFINED_ON(arm, arm64, x86, x64);
+        DEFINED_ON(arm, arm64, mips_shared, x86, x64);
 
     inline void test32MovePtr(Condition cond, const Address& addr, Imm32 mask, Register src,
                               Register dest)
-        DEFINED_ON(arm, arm64, x86, x64);
+        DEFINED_ON(arm, arm64, mips_shared, x86, x64);
 
     // Performs a bounds check and zeroes the index register if out-of-bounds
     // (to mitigate Spectre).
     inline void boundsCheck32ForLoad(Register index, Register length, Register scratch,
                                      Label* failure)
-        DEFINED_ON(arm, arm64, x86_shared);
+        DEFINED_ON(arm, arm64, mips_shared, x86_shared);
     inline void boundsCheck32ForLoad(Register index, const Address& length, Register scratch,
                                      Label* failure)
-        DEFINED_ON(arm, arm64, x86_shared);
+        DEFINED_ON(arm, arm64, mips_shared, x86_shared);
 
     // ========================================================================
     // Canonicalization primitives.
     inline void canonicalizeDouble(FloatRegister reg);
     inline void canonicalizeDoubleIfDeterministic(FloatRegister reg);
 
     inline void canonicalizeFloat(FloatRegister reg);
     inline void canonicalizeFloatIfDeterministic(FloatRegister reg);
@@ -1556,47 +1556,51 @@ class MacroAssembler : public MacroAssem
                                Register memoryBase, Register ptr, Register ptrScratch,
                                Register tmp)
         DEFINED_ON(arm);
 
     // wasm specific methods, used in both the wasm baseline compiler and ion.
 
     // The truncate-to-int32 methods do not bind the rejoin label; clients must
     // do so if oolWasmTruncateCheckF64ToI32() can jump to it.
-    void wasmTruncateDoubleToUInt32(FloatRegister input, Register output, Label* oolEntry) PER_ARCH;
-    void wasmTruncateDoubleToInt32(FloatRegister input, Register output, Label* oolEntry) PER_SHARED_ARCH;
-    void oolWasmTruncateCheckF64ToI32(FloatRegister input, bool isUnsigned,
+    void wasmTruncateDoubleToUInt32(FloatRegister input, Register output, bool isSaturating,
+                                    Label* oolEntry) PER_ARCH;
+    void wasmTruncateDoubleToInt32(FloatRegister input, Register output, bool isSaturating,
+                                   Label* oolEntry) PER_SHARED_ARCH;
+    void oolWasmTruncateCheckF64ToI32(FloatRegister input, Register output, TruncFlags flags,
                                       wasm::BytecodeOffset off, Label* rejoin)
         DEFINED_ON(arm, arm64, x86_shared);
 
-    void wasmTruncateFloat32ToUInt32(FloatRegister input, Register output, Label* oolEntry) PER_ARCH;
-    void wasmTruncateFloat32ToInt32(FloatRegister input, Register output, Label* oolEntry) PER_SHARED_ARCH;
-    void oolWasmTruncateCheckF32ToI32(FloatRegister input, bool isUnsigned,
+    void wasmTruncateFloat32ToUInt32(FloatRegister input, Register output, bool isSaturating,
+                                     Label* oolEntry) PER_ARCH;
+    void wasmTruncateFloat32ToInt32(FloatRegister input, Register output, bool isSaturating,
+                                    Label* oolEntry) PER_SHARED_ARCH;
+    void oolWasmTruncateCheckF32ToI32(FloatRegister input, Register output, TruncFlags flags,
                                       wasm::BytecodeOffset off, Label* rejoin)
         DEFINED_ON(arm, arm64, x86_shared);
 
     // The truncate-to-int64 methods will always bind the `oolRejoin` label
     // after the last emitted instruction.
-    void wasmTruncateDoubleToInt64(FloatRegister input, Register64 output, Label* oolEntry,
-                                   Label* oolRejoin, FloatRegister tempDouble)
+    void wasmTruncateDoubleToInt64(FloatRegister input, Register64 output, bool isSaturating,
+                                   Label* oolEntry, Label* oolRejoin, FloatRegister tempDouble)
         DEFINED_ON(arm64, x86, x64);
-    void wasmTruncateDoubleToUInt64(FloatRegister input, Register64 output, Label* oolEntry,
-                                    Label* oolRejoin, FloatRegister tempDouble)
+    void wasmTruncateDoubleToUInt64(FloatRegister input, Register64 output, bool isSaturating,
+                                    Label* oolEntry, Label* oolRejoin, FloatRegister tempDouble)
         DEFINED_ON(arm64, x86, x64);
-    void oolWasmTruncateCheckF64ToI64(FloatRegister input, bool isUnsigned,
+    void oolWasmTruncateCheckF64ToI64(FloatRegister input, Register64 output, TruncFlags flags,
                                       wasm::BytecodeOffset off, Label* rejoin)
         DEFINED_ON(arm, arm64, x86_shared);
 
-    void wasmTruncateFloat32ToInt64(FloatRegister input, Register64 output, Label* oolEntry,
-                                    Label* oolRejoin, FloatRegister tempDouble)
+    void wasmTruncateFloat32ToInt64(FloatRegister input, Register64 output, bool isSaturating,
+                                    Label* oolEntry, Label* oolRejoin, FloatRegister tempDouble)
         DEFINED_ON(arm64, x86, x64);
-    void wasmTruncateFloat32ToUInt64(FloatRegister input, Register64 output, Label* oolEntry,
-                                     Label* oolRejoin, FloatRegister tempDouble)
+    void wasmTruncateFloat32ToUInt64(FloatRegister input, Register64 output, bool isSaturating,
+                                     Label* oolEntry, Label* oolRejoin, FloatRegister tempDouble)
         DEFINED_ON(arm64, x86, x64);
-    void oolWasmTruncateCheckF32ToI64(FloatRegister input, bool isUnsigned,
+    void oolWasmTruncateCheckF32ToI64(FloatRegister input, Register64 output, TruncFlags flags,
                                       wasm::BytecodeOffset off, Label* rejoin)
         DEFINED_ON(arm, arm64, x86_shared);
 
     // This function takes care of loading the callee's TLS and pinned regs but
     // it is the caller's responsibility to save/restore TLS or pinned regs.
     void wasmCallImport(const wasm::CallSiteDesc& desc, const wasm::CalleeDesc& callee);
 
     // WasmTableCallIndexReg must contain the index of the indirect call.
--- a/js/src/jit/ValueNumbering.cpp
+++ b/js/src/jit/ValueNumbering.cpp
@@ -577,17 +577,17 @@ ValueNumberer::removePredecessorAndClean
                 return false;
             if (MResumePoint* outer = block->outerResumePoint()) {
                 if (!releaseResumePointOperands(outer) || !processDeadDefs())
                     return false;
             }
             MOZ_ASSERT(nextDef_ == nullptr);
             for (MInstructionIterator iter(block->begin()), end(block->end()); iter != end; ) {
                 MInstruction* ins = *iter++;
-                nextDef_ = *iter;
+                nextDef_ = iter != end ? *iter : nullptr;
                 if (MResumePoint* resume = ins->resumePoint()) {
                     if (!releaseResumePointOperands(resume) || !processDeadDefs())
                         return false;
                 }
             }
             nextDef_ = nullptr;
         } else {
 #ifdef DEBUG
@@ -953,17 +953,17 @@ ValueNumberer::visitUnreachableBlock(MBa
 
     // Discard any instructions with no uses. The remaining instructions will be
     // discarded when their last use is discarded.
     MOZ_ASSERT(nextDef_ == nullptr);
     for (MDefinitionIterator iter(block); iter; ) {
         MDefinition* def = *iter++;
         if (def->hasUses())
             continue;
-        nextDef_ = *iter;
+        nextDef_ = iter ? *iter : nullptr;
         if (!discardDefsRecursively(def))
             return false;
     }
 
     nextDef_ = nullptr;
     MControlInstruction* control = block->lastIns();
     return discardDefsRecursively(control);
 }
@@ -980,17 +980,17 @@ ValueNumberer::visitBlock(MBasicBlock* b
     // Visit the definitions in the block top-down.
     MOZ_ASSERT(nextDef_ == nullptr);
     for (MDefinitionIterator iter(block); iter; ) {
         if (!graph_.alloc().ensureBallast())
             return false;
         MDefinition* def = *iter++;
 
         // Remember where our iterator is so that we don't invalidate it.
-        nextDef_ = *iter;
+        nextDef_ = iter ? *iter : nullptr;
 
         // If the definition is dead, discard it.
         if (IsDiscardable(def)) {
             if (!discardDefsRecursively(def))
                 return false;
             continue;
         }
 
--- a/js/src/jit/arm/CodeGenerator-arm.cpp
+++ b/js/src/jit/arm/CodeGenerator-arm.cpp
@@ -2557,66 +2557,97 @@ void
 CodeGeneratorARM::visitWasmTruncateToInt32(LWasmTruncateToInt32* lir)
 {
     auto input = ToFloatRegister(lir->input());
     auto output = ToRegister(lir->output());
 
     MWasmTruncateToInt32* mir = lir->mir();
     MIRType fromType = mir->input()->type();
 
-    auto* ool = new(alloc()) OutOfLineWasmTruncateCheck(mir, input);
-    addOutOfLineCode(ool, mir);
-    masm.wasmTruncateToInt32(input, output, fromType, mir->isUnsigned(), ool->entry());
-    masm.bind(ool->rejoin());
+    OutOfLineWasmTruncateCheck* ool = nullptr;
+    Label* oolEntry = nullptr;
+    if (!lir->mir()->isSaturating()) {
+        ool = new(alloc()) OutOfLineWasmTruncateCheck(mir, input, Register::Invalid());
+        addOutOfLineCode(ool, mir);
+        oolEntry = ool->entry();
+    }
+
+    masm.wasmTruncateToInt32(input, output, fromType, mir->isUnsigned(), mir->isSaturating(),
+                             oolEntry);
+
+    if (!lir->mir()->isSaturating()) {
+        masm.bind(ool->rejoin());
+    }
 }
 
 void
 CodeGeneratorARM::visitWasmTruncateToInt64(LWasmTruncateToInt64* lir)
 {
     FloatRegister input = ToFloatRegister(lir->input());
     FloatRegister inputDouble = input;
     Register64 output = ToOutRegister64(lir);
 
     MWasmTruncateToInt64* mir = lir->mir();
     MIRType fromType = mir->input()->type();
 
-    auto* ool = new(alloc()) OutOfLineWasmTruncateCheck(mir, input);
-    addOutOfLineCode(ool, mir);
+    OutOfLineWasmTruncateCheck* ool = nullptr;
+    if (!lir->mir()->isSaturating()) {
+        ool = new(alloc()) OutOfLineWasmTruncateCheck(mir, input, Register64::Invalid());
+        addOutOfLineCode(ool, mir);
+    }
 
     ScratchDoubleScope scratchScope(masm);
     if (fromType == MIRType::Float32) {
         inputDouble = ScratchDoubleReg;
         masm.convertFloat32ToDouble(input, inputDouble);
     }
 
     masm.Push(input);
 
     masm.setupWasmABICall();
     masm.passABIArg(inputDouble, MoveOp::DOUBLE);
 
-    if (lir->mir()->isUnsigned())
-        masm.callWithABI(mir->bytecodeOffset(), wasm::SymbolicAddress::TruncateDoubleToUint64);
-    else
-        masm.callWithABI(mir->bytecodeOffset(), wasm::SymbolicAddress::TruncateDoubleToInt64);
+    if (lir->mir()->isSaturating()) {
+        if (lir->mir()->isUnsigned())
+            masm.callWithABI(mir->bytecodeOffset(), wasm::SymbolicAddress::SaturatingTruncateDoubleToUint64);
+        else
+            masm.callWithABI(mir->bytecodeOffset(), wasm::SymbolicAddress::SaturatingTruncateDoubleToInt64);
+    } else {
+        if (lir->mir()->isUnsigned())
+            masm.callWithABI(mir->bytecodeOffset(), wasm::SymbolicAddress::TruncateDoubleToUint64);
+        else
+            masm.callWithABI(mir->bytecodeOffset(), wasm::SymbolicAddress::TruncateDoubleToInt64);
+    }
 
     masm.Pop(input);
 
-    ScratchRegisterScope scratch(masm);
-    masm.ma_cmp(output.high, Imm32(0x80000000), scratch);
-    masm.as_cmp(output.low, Imm8(0x00000000), Assembler::Equal);
-    masm.ma_b(ool->entry(), Assembler::Equal);
-
-    masm.bind(ool->rejoin());
+    // TruncateDoubleTo{UI,I}nt64 returns 0x8000000000000000 to indicate
+    // exceptional results, so check for that and produce the appropriate
+    // traps. The Saturating form always returns a normal value and never
+    // needs traps.
+    if (!lir->mir()->isSaturating()) {
+        ScratchRegisterScope scratch(masm);
+        masm.ma_cmp(output.high, Imm32(0x80000000), scratch);
+        masm.as_cmp(output.low, Imm8(0x00000000), Assembler::Equal);
+        masm.ma_b(ool->entry(), Assembler::Equal);
+
+        masm.bind(ool->rejoin());
+    }
 
     MOZ_ASSERT(ReturnReg64 == output);
 }
 
 void
 CodeGeneratorARM::visitOutOfLineWasmTruncateCheck(OutOfLineWasmTruncateCheck* ool)
 {
+    // On ARM, saturating truncation codegen handles saturating itself rather than
+    // relying on out-of-line fixup code.
+    if (ool->isSaturating())
+        return;
+
     masm.outOfLineWasmTruncateToIntCheck(ool->input(), ool->fromType(), ool->toType(),
                                          ool->isUnsigned(), ool->rejoin(),
                                          ool->bytecodeOffset());
 }
 
 void
 CodeGeneratorARM::visitInt64ToFloatingPointCall(LInt64ToFloatingPointCall* lir)
 {
--- a/js/src/jit/arm/MacroAssembler-arm.cpp
+++ b/js/src/jit/arm/MacroAssembler-arm.cpp
@@ -4921,69 +4921,74 @@ MacroAssembler::storeUnboxedValue(const 
 
 CodeOffset
 MacroAssembler::wasmTrapInstruction()
 {
     return CodeOffset(as_illegal_trap().getOffset());
 }
 
 void
-MacroAssembler::wasmTruncateDoubleToUInt32(FloatRegister input, Register output, Label* oolEntry)
-{
-    wasmTruncateToInt32(input, output, MIRType::Double, /* isUnsigned= */ true, oolEntry);
-}
-
-void
-MacroAssembler::wasmTruncateDoubleToInt32(FloatRegister input, Register output, Label* oolEntry)
-{
-    wasmTruncateToInt32(input, output, MIRType::Double, /* isUnsigned= */ false, oolEntry);
-}
-
-void
-MacroAssembler::wasmTruncateFloat32ToUInt32(FloatRegister input, Register output, Label* oolEntry)
-{
-    wasmTruncateToInt32(input, output, MIRType::Float32, /* isUnsigned= */ true, oolEntry);
-}
-
-void
-MacroAssembler::wasmTruncateFloat32ToInt32(FloatRegister input, Register output, Label* oolEntry)
-{
-    wasmTruncateToInt32(input, output, MIRType::Float32, /* isUnsigned= */ false, oolEntry);
-}
-
-void
-MacroAssembler::oolWasmTruncateCheckF32ToI32(FloatRegister input, bool isUnsigned,
+MacroAssembler::wasmTruncateDoubleToUInt32(FloatRegister input, Register output,
+                                           bool isSaturating, Label* oolEntry)
+{
+    wasmTruncateToInt32(input, output, MIRType::Double, /* isUnsigned= */ true, isSaturating,
+                        oolEntry);
+}
+
+void
+MacroAssembler::wasmTruncateDoubleToInt32(FloatRegister input, Register output,
+                                          bool isSaturating, Label* oolEntry)
+{
+    wasmTruncateToInt32(input, output, MIRType::Double, /* isUnsigned= */ false,isSaturating,
+                         oolEntry);
+}
+
+void
+MacroAssembler::wasmTruncateFloat32ToUInt32(FloatRegister input, Register output,
+                                            bool isSaturating, Label* oolEntry)
+{
+    wasmTruncateToInt32(input, output, MIRType::Float32, /* isUnsigned= */ true,isSaturating,
+                         oolEntry);
+}
+
+void
+MacroAssembler::wasmTruncateFloat32ToInt32(FloatRegister input, Register output,
+                                           bool isSaturating, Label* oolEntry)
+{
+    wasmTruncateToInt32(input, output, MIRType::Float32, /* isUnsigned= */ false,isSaturating,
+                         oolEntry);
+}
+
+void
+MacroAssembler::oolWasmTruncateCheckF32ToI32(FloatRegister input, Register output, TruncFlags flags,
                                              wasm::BytecodeOffset off, Label* rejoin)
 {
-    outOfLineWasmTruncateToIntCheck(input, MIRType::Float32, MIRType::Int32, isUnsigned,
-                                    rejoin, off);
-}
-
-void
-MacroAssembler::oolWasmTruncateCheckF64ToI32(FloatRegister input, bool isUnsigned,
+    outOfLineWasmTruncateToIntCheck(input, MIRType::Float32, MIRType::Int32, flags, rejoin, off);
+}
+
+void
+MacroAssembler::oolWasmTruncateCheckF64ToI32(FloatRegister input, Register output, TruncFlags flags,
                                              wasm::BytecodeOffset off, Label* rejoin)
 {
-    outOfLineWasmTruncateToIntCheck(input, MIRType::Double, MIRType::Int32, isUnsigned,
-                                    rejoin, off);
-}
-
-void
-MacroAssembler::oolWasmTruncateCheckF32ToI64(FloatRegister input, bool isUnsigned,
+    outOfLineWasmTruncateToIntCheck(input, MIRType::Double, MIRType::Int32, flags, rejoin, off);
+}
+
+void
+MacroAssembler::oolWasmTruncateCheckF32ToI64(FloatRegister input, Register64 output, TruncFlags flags,
                                              wasm::BytecodeOffset off, Label* rejoin)
 {
-    outOfLineWasmTruncateToIntCheck(input, MIRType::Float32, MIRType::Int64, isUnsigned,
-                                    rejoin, off);
-}
-
-void
-MacroAssembler::oolWasmTruncateCheckF64ToI64(FloatRegister input, bool isUnsigned,
-                                             wasm::BytecodeOffset off, Label* rejoin)
-{
-    outOfLineWasmTruncateToIntCheck(input, MIRType::Double, MIRType::Int64, isUnsigned,
-                                    rejoin, off);
+    outOfLineWasmTruncateToIntCheck(input, MIRType::Float32, MIRType::Int64, flags, rejoin, off);
+}
+
+void
+MacroAssembler::oolWasmTruncateCheckF64ToI64(FloatRegister input, Register64 output, TruncFlags flags,
+                                             wasm::BytecodeOffset off,
+                                             Label* rejoin)
+{
+    outOfLineWasmTruncateToIntCheck(input, MIRType::Double, MIRType::Int64, flags, rejoin, off);
 }
 
 void
 MacroAssembler::wasmLoad(const wasm::MemoryAccessDesc& access, Register memoryBase, Register ptr,
                          Register ptrScratch, AnyRegister output)
 {
     wasmLoadImpl(access, memoryBase, ptr, ptrScratch, output, Register64::Invalid());
 }
@@ -5790,20 +5795,20 @@ MacroAssembler::convertUInt64ToDouble(Re
     convertUInt32ToDouble(src.low, scratchDouble);
     addDouble(scratchDouble, dest);
 }
 
 //}}} check_macroassembler_style
 
 void
 MacroAssemblerARM::wasmTruncateToInt32(FloatRegister input, Register output, MIRType fromType,
-                                       bool isUnsigned, Label* oolEntry)
+                                       bool isUnsigned, bool isSaturating, Label* oolEntry)
 {
     // vcvt* converts NaN into 0, so check for NaNs here.
-    {
+    if (!isSaturating) {
         if (fromType == MIRType::Double)
             asMasm().compareDouble(input, input);
         else if (fromType == MIRType::Float32)
             asMasm().compareFloat(input, input);
         else
             MOZ_CRASH("unexpected type in visitWasmTruncateToInt32");
 
         ma_b(oolEntry, Assembler::VFP_Unordered);
@@ -5821,44 +5826,55 @@ MacroAssemblerARM::wasmTruncateToInt32(F
             ma_vcvt_F64_U32(input, scratch);
         else if (fromType == MIRType::Float32)
             ma_vcvt_F32_U32(input, scratch);
         else
             MOZ_CRASH("unexpected type in visitWasmTruncateToInt32");
 
         ma_vxfer(scratch, output);
 
-        // int32_t(UINT32_MAX) == -1.
-        ma_cmp(output, Imm32(-1), scratchReg);
-        as_cmp(output, Imm8(0), Assembler::NotEqual);
-        ma_b(oolEntry, Assembler::Equal);
+        if (!isSaturating) {
+            // int32_t(UINT32_MAX) == -1.
+            ma_cmp(output, Imm32(-1), scratchReg);
+            as_cmp(output, Imm8(0), Assembler::NotEqual);
+            ma_b(oolEntry, Assembler::Equal);
+        }
 
         return;
     }
 
     scratch = scratchScope.sintOverlay();
 
     if (fromType == MIRType::Double)
         ma_vcvt_F64_I32(input, scratch);
     else if (fromType == MIRType::Float32)
         ma_vcvt_F32_I32(input, scratch);
     else
         MOZ_CRASH("unexpected type in visitWasmTruncateToInt32");
 
     ma_vxfer(scratch, output);
-    ma_cmp(output, Imm32(INT32_MAX), scratchReg);
-    ma_cmp(output, Imm32(INT32_MIN), scratchReg, Assembler::NotEqual);
-    ma_b(oolEntry, Assembler::Equal);
+
+    if (!isSaturating) {
+        ma_cmp(output, Imm32(INT32_MAX), scratchReg);
+        ma_cmp(output, Imm32(INT32_MIN), scratchReg, Assembler::NotEqual);
+        ma_b(oolEntry, Assembler::Equal);
+    }
 }
 
 void
 MacroAssemblerARM::outOfLineWasmTruncateToIntCheck(FloatRegister input, MIRType fromType,
-                                                   MIRType toType, bool isUnsigned, Label* rejoin,
-                                                   wasm::BytecodeOffset trapOffset)
-{
+                                                   MIRType toType, TruncFlags flags,
+                                                   Label* rejoin, wasm::BytecodeOffset trapOffset)
+{
+    // On ARM, saturating truncation codegen handles saturating itself rather
+    // than relying on out-of-line fixup code.
+    if (flags & TRUNC_SATURATING)
+        return;
+
+    bool isUnsigned = flags & TRUNC_UNSIGNED;
     ScratchDoubleScope scratchScope(asMasm());
     FloatRegister scratch;
 
     // Eagerly take care of NaNs.
     Label inputIsNaN;
     if (fromType == MIRType::Double)
         asMasm().branchDouble(Assembler::DoubleUnordered, input, input, &inputIsNaN);
     else if (fromType == MIRType::Float32)
--- a/js/src/jit/arm/MacroAssembler-arm.h
+++ b/js/src/jit/arm/MacroAssembler-arm.h
@@ -94,20 +94,20 @@ class MacroAssemblerARM : public Assembl
     void convertFloat32ToInt32(FloatRegister src, Register dest, Label* fail,
                                bool negativeZeroCheck = true);
 
     void convertFloat32ToDouble(FloatRegister src, FloatRegister dest);
     void convertInt32ToFloat32(Register src, FloatRegister dest);
     void convertInt32ToFloat32(const Address& src, FloatRegister dest);
 
     void wasmTruncateToInt32(FloatRegister input, Register output, MIRType fromType,
-                             bool isUnsigned, Label* oolEntry);
+                             bool isUnsigned, bool isSaturating, Label* oolEntry);
     void outOfLineWasmTruncateToIntCheck(FloatRegister input, MIRType fromType,
-                                         MIRType toType, bool isUnsigned, Label* rejoin,
-                                         wasm::BytecodeOffset trapOffset);
+                                         MIRType toType, TruncFlags flags,
+                                         Label* rejoin, wasm::BytecodeOffset trapOffset);
 
     // Somewhat direct wrappers for the low-level assembler funcitons
     // bitops. Attempt to encode a virtual alu instruction using two real
     // instructions.
   private:
     bool alu_dbl(Register src1, Imm32 imm, Register dest, ALUOp op,
                  SBit s, Condition c);
 
--- a/js/src/jit/arm64/MacroAssembler-arm64.cpp
+++ b/js/src/jit/arm64/MacroAssembler-arm64.cpp
@@ -908,91 +908,101 @@ MacroAssembler::comment(const char* msg)
 
 CodeOffset
 MacroAssembler::wasmTrapInstruction()
 {
     MOZ_CRASH("NYI");
 }
 
 void
-MacroAssembler::wasmTruncateDoubleToUInt32(FloatRegister input, Register output, Label* oolEntry)
+MacroAssembler::wasmTruncateDoubleToUInt32(FloatRegister input, Register output,
+                                           bool isSaturating, Label* oolEntry)
 {
     MOZ_CRASH("NYI");
 }
 
 void
-MacroAssembler::wasmTruncateDoubleToInt32(FloatRegister input, Register output, Label* oolEntry)
+MacroAssembler::wasmTruncateDoubleToInt32(FloatRegister input, Register output,
+                                          bool isSaturating, Label* oolEntry)
 {
     MOZ_CRASH("NYI");
 }
 
 void
-MacroAssembler::wasmTruncateFloat32ToUInt32(FloatRegister input, Register output, Label* oolEntry)
+MacroAssembler::wasmTruncateFloat32ToUInt32(FloatRegister input, Register output,
+                                            bool isSaturating, Label* oolEntry)
 {
     MOZ_CRASH("NYI");
 }
 
 void
-MacroAssembler::wasmTruncateFloat32ToInt32(FloatRegister input, Register output, Label* oolEntry)
+MacroAssembler::wasmTruncateFloat32ToInt32(FloatRegister input, Register output,
+                                           bool isSaturating, Label* oolEntry)
 {
     MOZ_CRASH("NYI");
 }
 
 void
-MacroAssembler::wasmTruncateDoubleToInt64(FloatRegister input, Register64 output, Label* oolEntry,
+MacroAssembler::wasmTruncateDoubleToInt64(FloatRegister input, Register64 output,
+                                          bool isSaturating, Label* oolEntry,
                                           Label* oolRejoin, FloatRegister tempDouble)
 {
     MOZ_CRASH("NYI");
 }
 
 void
-MacroAssembler::wasmTruncateDoubleToUInt64(FloatRegister input, Register64 output, Label* oolEntry,
+MacroAssembler::wasmTruncateDoubleToUInt64(FloatRegister input, Register64 output,
+                                           bool isSaturating, Label* oolEntry,
                                            Label* oolRejoin, FloatRegister tempDouble)
 {
     MOZ_CRASH("NYI");
 }
 
 void
-MacroAssembler::wasmTruncateFloat32ToInt64(FloatRegister input, Register64 output, Label* oolEntry,
+MacroAssembler::wasmTruncateFloat32ToInt64(FloatRegister input, Register64 output,
+                                           bool isSaturating, Label* oolEntry,
                                            Label* oolRejoin, FloatRegister tempDouble)
 {
     MOZ_CRASH("NYI");
 }
 
 void
-MacroAssembler::wasmTruncateFloat32ToUInt64(FloatRegister input, Register64 output, Label* oolEntry,
+MacroAssembler::wasmTruncateFloat32ToUInt64(FloatRegister input, Register64 output,
+                                            bool isSaturating, Label* oolEntry,
                                             Label* oolRejoin, FloatRegister tempDouble)
 {
     MOZ_CRASH("NYI");
 }
 
 void
-MacroAssembler::oolWasmTruncateCheckF32ToI32(FloatRegister input, bool isUnsigned,
+MacroAssembler::oolWasmTruncateCheckF32ToI32(FloatRegister input, Register output, TruncFlags flags,
                                              wasm::BytecodeOffset off, Label* rejoin)
 {
     MOZ_CRASH("NYI");
 }
 
 void
-MacroAssembler::oolWasmTruncateCheckF64ToI32(FloatRegister input, bool isUnsigned,
+MacroAssembler::oolWasmTruncateCheckF64ToI32(FloatRegister input, Register output, TruncFlags flags,
                                              wasm::BytecodeOffset off, Label* rejoin)
 {
     MOZ_CRASH("NYI");
 }
 
 void
-MacroAssembler::oolWasmTruncateCheckF32ToI64(FloatRegister input, bool isUnsigned,
-                                             wasm::BytecodeOffset off, Label* rejoin)
+MacroAssembler::oolWasmTruncateCheckF32ToI64(FloatRegister input, Register64 output,
+                                             TruncFlags flags, wasm::BytecodeOffset off,
+                                             Label* rejoin)
 {
     MOZ_CRASH("NYI");
 }
 
 void
-MacroAssembler::oolWasmTruncateCheckF64ToI64(FloatRegister input, bool isUnsigned,
-                                             wasm::BytecodeOffset off, Label* rejoin)
+MacroAssembler::oolWasmTruncateCheckF64ToI64(FloatRegister input, Register64 output,
+                                             TruncFlags flags, wasm::BytecodeOffset off,
+                                             Label* rejoin)
 {
     MOZ_CRASH("NYI");
 }
 
 // ========================================================================
 // Convert floating point.
 
 bool
--- a/js/src/jit/mips-shared/CodeGenerator-mips-shared.cpp
+++ b/js/src/jit/mips-shared/CodeGenerator-mips-shared.cpp
@@ -1481,19 +1481,19 @@ CodeGeneratorMIPSShared::visitWasmTrunca
         else if (fromType == MIRType::Float32)
             masm.wasmTruncateFloat32ToUInt32(input, output, oolEntry);
         else
             MOZ_CRASH("unexpected type");
         return;
     }
 
     if (fromType == MIRType::Double)
-        masm.wasmTruncateDoubleToInt32(input, output, oolEntry);
+        masm.wasmTruncateDoubleToInt32(input, output, mir->isSaturating(), oolEntry);
     else if (fromType == MIRType::Float32)
-        masm.wasmTruncateFloat32ToInt32(input, output, oolEntry);
+        masm.wasmTruncateFloat32ToInt32(input, output, mir->isSaturating(), oolEntry);
     else
         MOZ_CRASH("unexpected type");
 
     masm.bind(ool->rejoin());
 }
 
 void
 CodeGeneratorMIPSShared::visitOutOfLineWasmTruncateCheck(OutOfLineWasmTruncateCheck* ool)
--- a/js/src/jit/mips-shared/MacroAssembler-mips-shared-inl.h
+++ b/js/src/jit/mips-shared/MacroAssembler-mips-shared-inl.h
@@ -989,16 +989,71 @@ MacroAssembler::branchTestMagic(Conditio
 void
 MacroAssembler::branchTestMagic(Condition cond, const BaseIndex& address, Label* label)
 {
     SecondScratchRegisterScope scratch2(*this);
     extractTag(address, scratch2);
     branchTestMagic(cond, scratch2, label);
 }
 
+void
+MacroAssembler::cmp32Move32(Condition cond, Register lhs, Register rhs, Register src,
+                            Register dest)
+{
+    MOZ_CRASH();
+}
+
+void
+MacroAssembler::cmp32MovePtr(Condition cond, Register lhs, Imm32 rhs, Register src,
+                             Register dest)
+{
+    MOZ_CRASH();
+}
+
+void
+MacroAssembler::cmp32Move32(Condition cond, Register lhs, const Address& rhs, Register src,
+                            Register dest)
+{
+    MOZ_CRASH();
+}
+
+void
+MacroAssembler::test32LoadPtr(Condition cond, const Address& addr, Imm32 mask, const Address& src,
+                              Register dest)
+{
+    MOZ_RELEASE_ASSERT(!JitOptions.spectreStringMitigations);
+    Label skip;
+    branchTest32(Assembler::InvertCondition(cond), addr, mask, &skip);
+    loadPtr(src, dest);
+    bind(&skip);
+}
+
+void
+MacroAssembler::test32MovePtr(Condition cond, const Address& addr, Imm32 mask, Register src,
+                              Register dest)
+{
+    MOZ_CRASH();
+}
+
+void
+MacroAssembler::boundsCheck32ForLoad(Register index, Register length, Register scratch,
+                                     Label* failure)
+{
+    MOZ_RELEASE_ASSERT(!JitOptions.spectreIndexMasking);
+    branch32(Assembler::BelowOrEqual, length, index, failure);
+}
+
+void
+MacroAssembler::boundsCheck32ForLoad(Register index, const Address& length, Register scratch,
+                                     Label* failure)
+{
+    MOZ_RELEASE_ASSERT(!JitOptions.spectreIndexMasking);
+    branch32(Assembler::BelowOrEqual, length, index, failure);
+}
+
 // ========================================================================
 // Memory access primitives.
 void
 MacroAssembler::storeFloat32x3(FloatRegister src, const Address& dest)
 {
     MOZ_CRASH("NYI");
 }
 void
--- a/js/src/jit/mips-shared/MacroAssembler-mips-shared.cpp
+++ b/js/src/jit/mips-shared/MacroAssembler-mips-shared.cpp
@@ -1615,28 +1615,30 @@ CodeOffset
 MacroAssembler::wasmTrapInstruction()
 {
     CodeOffset offset(currentOffset());
     as_teq(zero, zero, WASM_TRAP);
     return offset;
 }
 
 void
-MacroAssembler::wasmTruncateDoubleToInt32(FloatRegister input, Register output, Label* oolEntry)
+MacroAssembler::wasmTruncateDoubleToInt32(FloatRegister input, Register output, bool isSaturating,
+                                          Label* oolEntry)
 {
     as_truncwd(ScratchFloat32Reg, input);
     as_cfc1(ScratchRegister, Assembler::FCSR);
     moveFromFloat32(ScratchFloat32Reg, output);
     ma_ext(ScratchRegister, ScratchRegister, 6, 1);
     ma_b(ScratchRegister, Imm32(0), oolEntry, Assembler::NotEqual);
 }
 
 
 void
-MacroAssembler::wasmTruncateFloat32ToInt32(FloatRegister input, Register output, Label* oolEntry)
+MacroAssembler::wasmTruncateFloat32ToInt32(FloatRegister input, Register output, bool isSaturating,
+                                           Label* oolEntry)
 {
     as_truncws(ScratchFloat32Reg, input);
     as_cfc1(ScratchRegister, Assembler::FCSR);
     moveFromFloat32(ScratchFloat32Reg, output);
     ma_ext(ScratchRegister, ScratchRegister, 6, 1);
     ma_b(ScratchRegister, Imm32(0), oolEntry, Assembler::NotEqual);
 }
 
--- a/js/src/jit/mips32/Assembler-mips32.h
+++ b/js/src/jit/mips32/Assembler-mips32.h
@@ -46,16 +46,20 @@ class ABIArgGenerator
     }
 };
 
 // These registers may be volatile or nonvolatile.
 static constexpr Register ABINonArgReg0 = t0;
 static constexpr Register ABINonArgReg1 = t1;
 static constexpr Register ABINonArgReg2 = t2;
 
+// This register may be volatile or nonvolatile. Avoid f18 which is the
+// ScratchDoubleReg.
+static constexpr FloatRegister ABINonArgDoubleReg { FloatRegisters::f16, FloatRegister::Double };
+
 // These registers may be volatile or nonvolatile.
 // Note: these three registers are all guaranteed to be different
 static constexpr Register ABINonArgReturnReg0 = t0;
 static constexpr Register ABINonArgReturnReg1 = t1;
 
 // This register is guaranteed to be clobberable during the prologue and
 // epilogue of an ABI call which must preserve both ABI argument, return
 // and non-volatile registers.
--- a/js/src/jit/mips32/MacroAssembler-mips32.cpp
+++ b/js/src/jit/mips32/MacroAssembler-mips32.cpp
@@ -2347,18 +2347,20 @@ template void
 MacroAssembler::storeUnboxedValue(const ConstantOrRegister& value, MIRType valueType,
                                   const Address& dest, MIRType slotType);
 template void
 MacroAssembler::storeUnboxedValue(const ConstantOrRegister& value, MIRType valueType,
                                   const BaseIndex& dest, MIRType slotType);
 
 
 void
-MacroAssembler::wasmTruncateDoubleToUInt32(FloatRegister input, Register output, Label* oolEntry)
+MacroAssembler::wasmTruncateDoubleToUInt32(FloatRegister input, Register output, bool isSaturating,
+                                           Label* oolEntry)
 {
+    MOZ_ASSERT(!isSaturating, "NYI");
 
     loadConstantDouble(double(-1.0), ScratchDoubleReg);
     branchDouble(Assembler::DoubleLessThanOrEqual, input, ScratchDoubleReg, oolEntry);
 
     loadConstantDouble(double(UINT32_MAX) + 1.0, ScratchDoubleReg);
     branchDouble(Assembler::DoubleGreaterThanOrEqualOrUnordered, input, ScratchDoubleReg, oolEntry);
     Label done, simple;
     loadConstantDouble(double(0x80000000UL), ScratchDoubleReg);
@@ -2371,18 +2373,21 @@ MacroAssembler::wasmTruncateDoubleToUInt
     ma_b(&done);
     bind(&simple);
     as_truncwd(ScratchDoubleReg, input);
     moveFromFloat32(ScratchDoubleReg, output);
     bind(&done);
 }
 
 void
-MacroAssembler::wasmTruncateFloat32ToUInt32(FloatRegister input, Register output, Label* oolEntry)
+MacroAssembler::wasmTruncateFloat32ToUInt32(FloatRegister input, Register output, bool isSaturating,
+                                            Label* oolEntry)
 {
+    MOZ_ASSERT(!isSaturating, "NYI");
+
     loadConstantFloat32(double(-1.0), ScratchDoubleReg);
     branchFloat(Assembler::DoubleLessThanOrEqualOrUnordered, input, ScratchDoubleReg, oolEntry);
 
     loadConstantFloat32(double(UINT32_MAX) + 1.0, ScratchDoubleReg);
     branchFloat(Assembler::DoubleGreaterThanOrEqualOrUnordered, input, ScratchDoubleReg, oolEntry);
     Label done, simple;
     loadConstantFloat32(double(0x80000000UL), ScratchDoubleReg);
     branchFloat(Assembler::DoubleLessThan, input, ScratchDoubleReg, &simple);
--- a/js/src/jit/mips64/Assembler-mips64.h
+++ b/js/src/jit/mips64/Assembler-mips64.h
@@ -39,16 +39,20 @@ class ABIArgGenerator
     }
 };
 
 // These registers may be volatile or nonvolatile.
 static constexpr Register ABINonArgReg0 = t0;
 static constexpr Register ABINonArgReg1 = t1;
 static constexpr Register ABINonArgReg2 = t2;
 
+// This register may be volatile or nonvolatile. Avoid f23 which is the
+// ScratchDoubleReg.
+static constexpr FloatRegister ABINonArgDoubleReg { FloatRegisters::f21, FloatRegisters::Double };
+
 // These registers may be volatile or nonvolatile.
 // Note: these three registers are all guaranteed to be different
 static constexpr Register ABINonArgReturnReg0 = t0;
 static constexpr Register ABINonArgReturnReg1 = t1;
 
 // This register is guaranteed to be clobberable during the prologue and
 // epilogue of an ABI call which must preserve both ABI argument, return
 // and non-volatile registers.
--- a/js/src/jit/mips64/MacroAssembler-mips64.cpp
+++ b/js/src/jit/mips64/MacroAssembler-mips64.cpp
@@ -1562,17 +1562,17 @@ MacroAssemblerMIPS64Compat::unboxValue(c
     } else {
         unboxNonDouble(src, dest.gpr(), type);
     }
 }
 
 void
 MacroAssemblerMIPS64Compat::unboxPrivate(const ValueOperand& src, Register dest)
 {
-    ma_dsrl(dest, src.valueReg(), Imm32(1));
+    ma_dsll(dest, src.valueReg(), Imm32(1));
 }
 
 void
 MacroAssemblerMIPS64Compat::boxDouble(FloatRegister src, const ValueOperand& dest, FloatRegister)
 {
     as_dmfc1(dest.valueReg(), src);
 }
 
@@ -2433,32 +2433,38 @@ template void
 MacroAssembler::storeUnboxedValue(const ConstantOrRegister& value, MIRType valueType,
                                   const Address& dest, MIRType slotType);
 template void
 MacroAssembler::storeUnboxedValue(const ConstantOrRegister& value, MIRType valueType,
                                   const BaseIndex& dest, MIRType slotType);
 
 
 void
-MacroAssembler::wasmTruncateDoubleToUInt32(FloatRegister input, Register output, Label* oolEntry)
+MacroAssembler::wasmTruncateDoubleToUInt32(FloatRegister input, Register output, bool isSaturating,
+                                           Label* oolEntry)
 {
+    MOZ_ASSERT(!isSaturating, "NYI");
+
     as_truncld(ScratchDoubleReg, input);
     moveFromDoubleHi(ScratchDoubleReg, output);
     as_cfc1(ScratchRegister, Assembler::FCSR);
     ma_ext(ScratchRegister, ScratchRegister, 6, 1);
     ma_or(ScratchRegister, output);
     moveFromFloat32(ScratchDoubleReg, output);
     ma_b(ScratchRegister, Imm32(0), oolEntry, Assembler::NotEqual);
 
 
 }
 
 void
-MacroAssembler::wasmTruncateFloat32ToUInt32(FloatRegister input, Register output, Label* oolEntry)
+MacroAssembler::wasmTruncateFloat32ToUInt32(FloatRegister input, Register output, bool isSaturating,
+                                            Label* oolEntry)
 {
+    MOZ_ASSERT(!isSaturating, "NYI");
+
     as_truncls(ScratchDoubleReg, input);
     moveFromDoubleHi(ScratchDoubleReg, output);
     as_cfc1(ScratchRegister, Assembler::FCSR);
     ma_ext(ScratchRegister, ScratchRegister, 6, 1);
     ma_or(ScratchRegister, output);
     moveFromFloat32(ScratchDoubleReg, output);
     ma_b(ScratchRegister, Imm32(0), oolEntry, Assembler::NotEqual);
 
--- a/js/src/jit/shared/CodeGenerator-shared.h
+++ b/js/src/jit/shared/CodeGenerator-shared.h
@@ -849,37 +849,45 @@ CodeGeneratorShared::visitOutOfLineCallV
     masm.jump(ool->rejoin());
 }
 
 class OutOfLineWasmTruncateCheck : public OutOfLineCodeBase<CodeGeneratorShared>
 {
     MIRType fromType_;
     MIRType toType_;
     FloatRegister input_;
-    bool isUnsigned_;
+    Register output_;
+    Register64 output64_;
+    TruncFlags flags_;
     wasm::BytecodeOffset bytecodeOffset_;
 
   public:
-    OutOfLineWasmTruncateCheck(MWasmTruncateToInt32* mir, FloatRegister input)
-      : fromType_(mir->input()->type()), toType_(MIRType::Int32), input_(input),
-        isUnsigned_(mir->isUnsigned()), bytecodeOffset_(mir->bytecodeOffset())
+    OutOfLineWasmTruncateCheck(MWasmTruncateToInt32* mir, FloatRegister input, Register output)
+      : fromType_(mir->input()->type()), toType_(MIRType::Int32), input_(input), output_(output),
+        output64_(Register64::Invalid()), flags_(mir->flags()),
+        bytecodeOffset_(mir->bytecodeOffset())
     { }
 
-    OutOfLineWasmTruncateCheck(MWasmTruncateToInt64* mir, FloatRegister input)
+    OutOfLineWasmTruncateCheck(MWasmTruncateToInt64* mir, FloatRegister input, Register64 output)
       : fromType_(mir->input()->type()), toType_(MIRType::Int64), input_(input),
-        isUnsigned_(mir->isUnsigned()), bytecodeOffset_(mir->bytecodeOffset())
+        output_(Register::Invalid()), output64_(output), flags_(mir->flags()),
+        bytecodeOffset_(mir->bytecodeOffset())
     { }
 
     void accept(CodeGeneratorShared* codegen) override {
         codegen->visitOutOfLineWasmTruncateCheck(this);
     }
 
     FloatRegister input() const { return input_; }
+    Register output() const { return output_; }
+    Register64 output64() const { return output64_; }
     MIRType toType() const { return toType_; }
     MIRType fromType() const { return fromType_; }
-    bool isUnsigned() const { return isUnsigned_; }
+    bool isUnsigned() const { return flags_ & TRUNC_UNSIGNED; }
+    bool isSaturating() const { return flags_ & TRUNC_SATURATING; }
+    TruncFlags flags() const { return flags_; }
     wasm::BytecodeOffset bytecodeOffset() const { return bytecodeOffset_; }
 };
 
 } // namespace jit
 } // namespace js
 
 #endif /* jit_shared_CodeGenerator_shared_h */
--- a/js/src/jit/x64/CodeGenerator-x64.cpp
+++ b/js/src/jit/x64/CodeGenerator-x64.cpp
@@ -735,33 +735,38 @@ CodeGeneratorX64::visitWasmTruncateToInt
     FloatRegister input = ToFloatRegister(lir->input());
     Register64 output = ToOutRegister64(lir);
 
     MWasmTruncateToInt64* mir = lir->mir();
     MIRType inputType = mir->input()->type();
 
     MOZ_ASSERT(inputType == MIRType::Double || inputType == MIRType::Float32);
 
-    auto* ool = new(alloc()) OutOfLineWasmTruncateCheck(mir, input);
+    auto* ool = new(alloc()) OutOfLineWasmTruncateCheck(mir, input, output);
     addOutOfLineCode(ool, mir);
 
     FloatRegister temp = mir->isUnsigned() ? ToFloatRegister(lir->temp()) : InvalidFloatReg;
 
     Label* oolEntry = ool->entry();
     Label* oolRejoin = ool->rejoin();
+    bool isSaturating = mir->isSaturating();
     if (inputType == MIRType::Double) {
         if (mir->isUnsigned())
-            masm.wasmTruncateDoubleToUInt64(input, output, oolEntry, oolRejoin, temp);
+            masm.wasmTruncateDoubleToUInt64(input, output, isSaturating,
+                                            oolEntry, oolRejoin, temp);
         else
-            masm.wasmTruncateDoubleToInt64(input, output, oolEntry, oolRejoin, temp);
+            masm.wasmTruncateDoubleToInt64(input, output, isSaturating,
+                                           oolEntry, oolRejoin, temp);
     } else {
         if (mir->isUnsigned())
-            masm.wasmTruncateFloat32ToUInt64(input, output, oolEntry, oolRejoin, temp);
+            masm.wasmTruncateFloat32ToUInt64(input, output, isSaturating,
+                                             oolEntry, oolRejoin, temp);
         else
-            masm.wasmTruncateFloat32ToInt64(input, output, oolEntry, oolRejoin, temp);
+            masm.wasmTruncateFloat32ToInt64(input, output, isSaturating,
+                                            oolEntry, oolRejoin, temp);
     }
 }
 
 void
 CodeGeneratorX64::visitInt64ToFloatingPoint(LInt64ToFloatingPoint* lir)
 {
     Register64 input = ToRegister64(lir->getInt64Operand(0));
     FloatRegister output = ToFloatRegister(lir->output());
--- a/js/src/jit/x64/MacroAssembler-x64.cpp
+++ b/js/src/jit/x64/MacroAssembler-x64.cpp
@@ -765,62 +765,64 @@ MacroAssembler::wasmStore(const wasm::Me
         MOZ_CRASH("unexpected array type");
     }
     append(access, storeOffset, framePushed());
 
     memoryBarrierAfter(access.sync());
 }
 
 void
-MacroAssembler::wasmTruncateDoubleToUInt32(FloatRegister input, Register output, Label* oolEntry)
+MacroAssembler::wasmTruncateDoubleToUInt32(FloatRegister input, Register output, bool isSaturating,
+                                           Label* oolEntry)
 {
     vcvttsd2sq(input, output);
 
     // Check that the result is in the uint32_t range.
     ScratchRegisterScope scratch(*this);
     move32(Imm32(0xffffffff), scratch);
     cmpq(scratch, output);
     j(Assembler::Above, oolEntry);
 }
 
 void
-MacroAssembler::wasmTruncateFloat32ToUInt32(FloatRegister input, Register output, Label* oolEntry)
+MacroAssembler::wasmTruncateFloat32ToUInt32(FloatRegister input, Register output, bool isSaturating,
+                                            Label* oolEntry)
 {
     vcvttss2sq(input, output);
 
     // Check that the result is in the uint32_t range.
     ScratchRegisterScope scratch(*this);
     move32(Imm32(0xffffffff), scratch);
     cmpq(scratch, output);
     j(Assembler::Above, oolEntry);
 }
 
 void
-MacroAssembler::wasmTruncateDoubleToInt64(FloatRegister input, Register64 output, Label* oolEntry,
-                                          Label* oolRejoin, FloatRegister tempReg)
+MacroAssembler::wasmTruncateDoubleToInt64(FloatRegister input, Register64 output, bool isSaturating,
+                                          Label* oolEntry, Label* oolRejoin, FloatRegister tempReg)
 {
     vcvttsd2sq(input, output.reg);
     cmpq(Imm32(1), output.reg);
     j(Assembler::Overflow, oolEntry);
     bind(oolRejoin);
 }
 
 void
-MacroAssembler::wasmTruncateFloat32ToInt64(FloatRegister input, Register64 output, Label* oolEntry,
-                                           Label* oolRejoin, FloatRegister tempReg)
+MacroAssembler::wasmTruncateFloat32ToInt64(FloatRegister input, Register64 output, bool isSaturating,
+                                           Label* oolEntry, Label* oolRejoin, FloatRegister tempReg)
 {
     vcvttss2sq(input, output.reg);
     cmpq(Imm32(1), output.reg);
     j(Assembler::Overflow, oolEntry);
     bind(oolRejoin);
 }
 
 void
-MacroAssembler::wasmTruncateDoubleToUInt64(FloatRegister input, Register64 output, Label* oolEntry,
-                                           Label* oolRejoin, FloatRegister tempReg)
+MacroAssembler::wasmTruncateDoubleToUInt64(FloatRegister input, Register64 output, bool isSaturating,
+                                           Label* oolEntry, Label* oolRejoin, FloatRegister tempReg)
 {
     // If the input < INT64_MAX, vcvttsd2sq will do the right thing, so
     // we use it directly. Else, we subtract INT64_MAX, convert to int64,
     // and then add INT64_MAX to the result.
 
     Label isLarge;
 
     ScratchDoubleScope scratch(*this);
@@ -839,17 +841,18 @@ MacroAssembler::wasmTruncateDoubleToUInt
     testq(output.reg, output.reg);
     j(Assembler::Signed, oolEntry);
     or64(Imm64(0x8000000000000000), output);
 
     bind(oolRejoin);
 }
 
 void
-MacroAssembler::wasmTruncateFloat32ToUInt64(FloatRegister input, Register64 output, Label* oolEntry,
+MacroAssembler::wasmTruncateFloat32ToUInt64(FloatRegister input, Register64 output,
+                                            bool isSaturating, Label* oolEntry,
                                             Label* oolRejoin, FloatRegister tempReg)
 {
     // If the input < INT64_MAX, vcvttss2sq will do the right thing, so
     // we use it directly. Else, we subtract INT64_MAX, convert to int64,
     // and then add INT64_MAX to the result.
 
     Label isLarge;
 
--- a/js/src/jit/x86-shared/CodeGenerator-x86-shared.cpp
+++ b/js/src/jit/x86-shared/CodeGenerator-x86-shared.cpp
@@ -444,34 +444,36 @@ CodeGeneratorX86Shared::visitWasmTruncat
     FloatRegister input = ToFloatRegister(lir->input());
     Register output = ToRegister(lir->output());
 
     MWasmTruncateToInt32* mir = lir->mir();
     MIRType inputType = mir->input()->type();
 
     MOZ_ASSERT(inputType == MIRType::Double || inputType == MIRType::Float32);
 
-    auto* ool = new (alloc()) OutOfLineWasmTruncateCheck(mir, input);
+    auto* ool = new (alloc()) OutOfLineWasmTruncateCheck(mir, input, output);
     addOutOfLineCode(ool, mir);
 
     Label* oolEntry = ool->entry();
     if (mir->isUnsigned()) {
         if (inputType == MIRType::Double)
-            masm.wasmTruncateDoubleToUInt32(input, output, oolEntry);
+            masm.wasmTruncateDoubleToUInt32(input, output, mir->isSaturating(), oolEntry);
         else if (inputType == MIRType::Float32)
-            masm.wasmTruncateFloat32ToUInt32(input, output, oolEntry);
+            masm.wasmTruncateFloat32ToUInt32(input, output, mir->isSaturating(), oolEntry);
         else
             MOZ_CRASH("unexpected type");
+        if (mir->isSaturating())
+            masm.bind(ool->rejoin());
         return;
     }
 
     if (inputType == MIRType::Double)
-        masm.wasmTruncateDoubleToInt32(input, output, oolEntry);
+        masm.wasmTruncateDoubleToInt32(input, output, mir->isSaturating(), oolEntry);
     else if (inputType == MIRType::Float32)
-        masm.wasmTruncateFloat32ToInt32(input, output, oolEntry);
+        masm.wasmTruncateFloat32ToInt32(input, output, mir->isSaturating(), oolEntry);
     else
         MOZ_CRASH("unexpected type");
 
     masm.bind(ool->rejoin());
 }
 
 bool
 CodeGeneratorX86Shared::generateOutOfLineCode()
@@ -2531,22 +2533,21 @@ CodeGeneratorX86Shared::visitOutOfLineSi
     masm.loadConstantSimd128Float(Int32MaxX4, scratch);
     masm.vcmpleps(Operand(input), scratch, scratch);
     masm.vmovmskps(scratch, temp);
     masm.cmp32(temp, Imm32(0));
     masm.j(Assembler::NotEqual, &onConversionError);
 
     masm.jump(ool->rejoin());
 
-    if (gen->compilingWasm()) {
-        masm.bindLater(&onConversionError, oldTrap(ool, wasm::Trap::ImpreciseSimdConversion));
-    } else {
-        masm.bind(&onConversionError);
+    masm.bind(&onConversionError);
+    if (gen->compilingWasm())
+        masm.wasmTrap(wasm::Trap::ImpreciseSimdConversion, ool->bytecodeOffset());
+    else
         bailout(ool->ins()->snapshot());
-    }
 }
 
 // Convert Float32x4 to Uint32x4.
 //
 // If any input lane value is out of range or NaN, bail out.
 void
 CodeGeneratorX86Shared::visitFloat32x4ToUint32x4(LFloat32x4ToUint32x4* ins)
 {
@@ -2611,20 +2612,24 @@ CodeGeneratorX86Shared::visitFloat32x4To
     masm.bitwiseOrSimd128(Operand(scratch), out);
 
     // We still need to filter out the V-lanes. They would show up as 0x80000000
     // in both A and B. Since we cleared the valid A-lanes in B, the V-lanes are
     // the remaining negative lanes in B.
     masm.vmovmskps(scratch, temp);
     masm.cmp32(temp, Imm32(0));
 
-    if (gen->compilingWasm())
-        masm.j(Assembler::NotEqual, oldTrap(mir, wasm::Trap::ImpreciseSimdConversion));
-    else
+    if (gen->compilingWasm()) {
+        Label ok;
+        masm.j(Assembler::Equal, &ok);
+        masm.wasmTrap(wasm::Trap::ImpreciseSimdConversion, mir->bytecodeOffset());
+        masm.bind(&ok);
+    } else {
         bailoutIf(Assembler::NotEqual, ins->snapshot());
+    }
 }
 
 void
 CodeGeneratorX86Shared::visitSimdValueInt32x4(LSimdValueInt32x4* ins)
 {
     MOZ_ASSERT(ins->mir()->type() == MIRType::Int32x4 || ins->mir()->type() == MIRType::Bool32x4);
 
     FloatRegister output = ToFloatRegister(ins->output());
@@ -4359,34 +4364,36 @@ CodeGeneratorX86Shared::setReturnDoubleR
     regs->add(ReturnDoubleReg);
     regs->add(ReturnSimd128Reg);
 }
 
 void
 CodeGeneratorX86Shared::visitOutOfLineWasmTruncateCheck(OutOfLineWasmTruncateCheck* ool)
 {
     FloatRegister input = ool->input();
+    Register output = ool->output();
+    Register64 output64 = ool->output64();
     MIRType fromType = ool->fromType();
     MIRType toType = ool->toType();
     Label* oolRejoin = ool->rejoin();
-    bool isUnsigned = ool->isUnsigned();
+    TruncFlags flags = ool->flags();
     wasm::BytecodeOffset off = ool->bytecodeOffset();
 
     if (fromType == MIRType::Float32) {
         if (toType == MIRType::Int32)
-            masm.oolWasmTruncateCheckF32ToI32(input, isUnsigned, off, oolRejoin);
+            masm.oolWasmTruncateCheckF32ToI32(input, output, flags, off, oolRejoin);
         else if (toType == MIRType::Int64)
-            masm.oolWasmTruncateCheckF32ToI64(input, isUnsigned, off, oolRejoin);
+            masm.oolWasmTruncateCheckF32ToI64(input, output64, flags, off, oolRejoin);
         else
             MOZ_CRASH("unexpected type");
     } else if (fromType == MIRType::Double) {
         if (toType == MIRType::Int32)
-            masm.oolWasmTruncateCheckF64ToI32(input, isUnsigned, off, oolRejoin);
+            masm.oolWasmTruncateCheckF64ToI32(input, output, flags, off, oolRejoin);
         else if (toType == MIRType::Int64)
-            masm.oolWasmTruncateCheckF64ToI64(input, isUnsigned, off, oolRejoin);
+            masm.oolWasmTruncateCheckF64ToI64(input, output64, flags, off, oolRejoin);
         else
             MOZ_CRASH("unexpected type");
     } else {
         MOZ_CRASH("unexpected type");
     }
 }
 
 void
--- a/js/src/jit/x86-shared/MacroAssembler-x86-shared.cpp
+++ b/js/src/jit/x86-shared/MacroAssembler-x86-shared.cpp
@@ -697,62 +697,130 @@ struct MOZ_RAII AutoHandleWasmTruncateTo
         masm.wasmTrap(wasm::Trap::IntegerOverflow, off);
 
         masm.bind(&inputIsNaN);
         masm.wasmTrap(wasm::Trap::InvalidConversionToInteger, off);
     }
 };
 
 void
-MacroAssembler::wasmTruncateDoubleToInt32(FloatRegister input, Register output, Label* oolEntry)
+MacroAssembler::wasmTruncateDoubleToInt32(FloatRegister input, Register output,
+                                          bool isSaturating, Label* oolEntry)
 {
     vcvttsd2si(input, output);
     cmp32(output, Imm32(1));
     j(Assembler::Overflow, oolEntry);
 }
 
 void
-MacroAssembler::wasmTruncateFloat32ToInt32(FloatRegister input, Register output, Label* oolEntry)
+MacroAssembler::wasmTruncateFloat32ToInt32(FloatRegister input, Register output,
+                                           bool isSaturating, Label* oolEntry)
 {
     vcvttss2si(input, output);
     cmp32(output, Imm32(1));
     j(Assembler::Overflow, oolEntry);
 }
 
 void
-MacroAssembler::oolWasmTruncateCheckF64ToI32(FloatRegister input, bool isUnsigned,
-                                             wasm::BytecodeOffset off, Label* rejoin)
+MacroAssembler::oolWasmTruncateCheckF64ToI32(FloatRegister input, Register output,
+                                             TruncFlags flags, wasm::BytecodeOffset off,
+                                             Label* rejoin)
 {
+    bool isUnsigned = flags & TRUNC_UNSIGNED;
+    bool isSaturating = flags & TRUNC_SATURATING;
+
     AutoHandleWasmTruncateToIntErrors traps(*this, off);
 
+    if (isSaturating) {
+        if (isUnsigned) {
+            // Negative overflow and NaN both are converted to 0, and the only other case
+            // is positive overflow which is converted to UINT32_MAX.
+            Label nonNegative;
+            loadConstantDouble(0.0, ScratchDoubleReg);
+            branchDouble(Assembler::DoubleGreaterThanOrEqual, input, ScratchDoubleReg, &nonNegative);
+            move32(Imm32(0), output);
+            jump(rejoin);
+            bind(&nonNegative);
+
+            move32(Imm32(UINT32_MAX), output);
+        } else {
+            // Negative overflow is already saturated to INT32_MIN, so we only have
+            // to handle NaN and positive overflow here.
+            Label notNaN;
+            branchDouble(Assembler::DoubleOrdered, input, input, &notNaN);
+            move32(Imm32(0), output);
+            jump(rejoin);
+            bind(&notNaN);
+
+            loadConstantDouble(0.0, ScratchDoubleReg);
+            branchDouble(Assembler::DoubleLessThan, input, ScratchDoubleReg, rejoin);
+            sub32(Imm32(1), output);
+        }
+        jump(rejoin);
+        return;
+    }
+
     // Eagerly take care of NaNs.
     branchDouble(Assembler::DoubleUnordered, input, input, &traps.inputIsNaN);
 
     // For unsigned, fall through to intOverflow failure case.
     if (isUnsigned)
         return;
 
     // Handle special values.
 
     // We've used vcvttsd2si. The only valid double values that can
     // truncate to INT32_MIN are in ]INT32_MIN - 1; INT32_MIN].
     loadConstantDouble(double(INT32_MIN) - 1.0, ScratchDoubleReg);
     branchDouble(Assembler::DoubleLessThanOrEqual, input, ScratchDoubleReg, &traps.intOverflow);
 
-    loadConstantDouble(double(INT32_MIN), ScratchDoubleReg);
+    loadConstantDouble(0.0, ScratchDoubleReg);
     branchDouble(Assembler::DoubleGreaterThan, input, ScratchDoubleReg, &traps.intOverflow);
     jump(rejoin);
 }
 
 void
-MacroAssembler::oolWasmTruncateCheckF32ToI32(FloatRegister input, bool isUnsigned,
-                                             wasm::BytecodeOffset off, Label* rejoin)
+MacroAssembler::oolWasmTruncateCheckF32ToI32(FloatRegister input, Register output,
+                                             TruncFlags flags, wasm::BytecodeOffset off,
+                                             Label* rejoin)
 {
+    bool isUnsigned = flags & TRUNC_UNSIGNED;
+    bool isSaturating = flags & TRUNC_SATURATING;
+
     AutoHandleWasmTruncateToIntErrors traps(*this, off);
 
+    if (isSaturating) {
+        if (isUnsigned) {
+            // Negative overflow and NaN both are converted to 0, and the only other case
+            // is positive overflow which is converted to UINT32_MAX.
+            Label nonNegative;
+            loadConstantFloat32(0.0f, ScratchDoubleReg);
+            branchFloat(Assembler::DoubleGreaterThanOrEqual, input, ScratchDoubleReg, &nonNegative);
+            move32(Imm32(0), output);
+            jump(rejoin);
+            bind(&nonNegative);
+
+            move32(Imm32(UINT32_MAX), output);
+        } else {
+            // Negative overflow is already saturated to INT32_MIN, so we only have
+            // to handle NaN and positive overflow here.
+            Label notNaN;
+            branchFloat(Assembler::DoubleOrdered, input, input, &notNaN);
+            move32(Imm32(0), output);
+            jump(rejoin);
+            bind(&notNaN);
+
+            loadConstantFloat32(0.0f, ScratchFloat32Reg);
+            branchFloat(Assembler::DoubleLessThan, input, ScratchFloat32Reg, rejoin);
+            sub32(Imm32(1), output);
+        }
+        jump(rejoin);
+        return;
+    }
+
     // Eagerly take care of NaNs.
     branchFloat(Assembler::DoubleUnordered, input, input, &traps.inputIsNaN);
 
     // For unsigned, fall through to intOverflow failure case.
     if (isUnsigned)
         return;
 
     // Handle special values.
@@ -761,54 +829,120 @@ MacroAssembler::oolWasmTruncateCheckF32T
     // float(INT32_MIN), which is the only legimitate input that
     // would truncate to INT32_MIN.
     loadConstantFloat32(float(INT32_MIN), ScratchFloat32Reg);
     branchFloat(Assembler::DoubleNotEqual, input, ScratchFloat32Reg, &traps.intOverflow);
     jump(rejoin);
 }
 
 void
-MacroAssembler::oolWasmTruncateCheckF64ToI64(FloatRegister input, bool isUnsigned,
-                                             wasm::BytecodeOffset off, Label* rejoin)
+MacroAssembler::oolWasmTruncateCheckF64ToI64(FloatRegister input, Register64 output,
+                                             TruncFlags flags, wasm::BytecodeOffset off,
+                                             Label* rejoin)
 {
+    bool isUnsigned = flags & TRUNC_UNSIGNED;
+    bool isSaturating = flags & TRUNC_SATURATING;
+
     AutoHandleWasmTruncateToIntErrors traps(*this, off);
 
+    if (isSaturating) {
+        if (isUnsigned) {
+            // Negative overflow and NaN both are converted to 0, and the only other case
+            // is positive overflow which is converted to UINT64_MAX.
+            Label nonNegative;
+            loadConstantDouble(0.0, ScratchDoubleReg);
+            branchDouble(Assembler::DoubleGreaterThanOrEqual, input, ScratchDoubleReg, &nonNegative);
+            move64(Imm64(0), output);
+            jump(rejoin);
+            bind(&nonNegative);
+
+            move64(Imm64(UINT64_MAX), output);
+        } else {
+            // Negative overflow is already saturated to INT64_MIN, so we only have
+            // to handle NaN and positive overflow here.
+            Label notNaN;
+            branchDouble(Assembler::DoubleOrdered, input, input, &notNaN);
+            move64(Imm64(0), output);
+            jump(rejoin);
+            bind(&notNaN);
+
+            loadConstantDouble(0.0, ScratchDoubleReg);
+            branchDouble(Assembler::DoubleLessThan, input, ScratchDoubleReg, rejoin);
+            sub64(Imm64(1), output);
+        }
+        jump(rejoin);
+        return;
+    }
+
     // Eagerly take care of NaNs.
     branchDouble(Assembler::DoubleUnordered, input, input, &traps.inputIsNaN);
 
     // Handle special values.
     if (isUnsigned) {
-        loadConstantDouble(-0.0, ScratchDoubleReg);
+        loadConstantDouble(0.0, ScratchDoubleReg);
         branchDouble(Assembler::DoubleGreaterThan, input, ScratchDoubleReg, &traps.intOverflow);
         loadConstantDouble(-1.0, ScratchDoubleReg);
         branchDouble(Assembler::DoubleLessThanOrEqual, input, ScratchDoubleReg, &traps.intOverflow);
         jump(rejoin);
         return;
     }
 
     // We've used vcvtsd2sq. The only legit value whose i64
     // truncation is INT64_MIN is double(INT64_MIN): exponent is so
     // high that the highest resolution around is much more than 1.
     loadConstantDouble(double(int64_t(INT64_MIN)), ScratchDoubleReg);
     branchDouble(Assembler::DoubleNotEqual, input, ScratchDoubleReg, &traps.intOverflow);
     jump(rejoin);
 }
 
 void
-MacroAssembler::oolWasmTruncateCheckF32ToI64(FloatRegister input, bool isUnsigned,
-                                             wasm::BytecodeOffset off, Label* rejoin)
+MacroAssembler::oolWasmTruncateCheckF32ToI64(FloatRegister input, Register64 output,
+                                             TruncFlags flags, wasm::BytecodeOffset off,
+                                             Label* rejoin)
 {
+    bool isUnsigned = flags & TRUNC_UNSIGNED;
+    bool isSaturating = flags & TRUNC_SATURATING;
+
     AutoHandleWasmTruncateToIntErrors traps(*this, off);
 
+    if (isSaturating) {
+        if (isUnsigned) {
+            // Negative overflow and NaN both are converted to 0, and the only other case
+            // is positive overflow which is converted to UINT64_MAX.
+            Label nonNegative;
+            loadConstantFloat32(0.0f, ScratchDoubleReg);
+            branchFloat(Assembler::DoubleGreaterThanOrEqual, input, ScratchDoubleReg, &nonNegative);
+            move64(Imm64(0), output);
+            jump(rejoin);
+            bind(&nonNegative);
+
+            move64(Imm64(UINT64_MAX), output);
+        } else {
+            // Negative overflow is already saturated to INT64_MIN, so we only have
+            // to handle NaN and positive overflow here.
+            Label notNaN;
+            branchFloat(Assembler::DoubleOrdered, input, input, &notNaN);
+            move64(Imm64(0), output);
+            jump(rejoin);
+            bind(&notNaN);
+
+            loadConstantFloat32(0.0f, ScratchFloat32Reg);
+            branchFloat(Assembler::DoubleLessThan, input, ScratchFloat32Reg, rejoin);
+            sub64(Imm64(1), output);
+        }
+        jump(rejoin);
+        return;
+    }
+
     // Eagerly take care of NaNs.
     branchFloat(Assembler::DoubleUnordered, input, input, &traps.inputIsNaN);
 
     // Handle special values.
     if (isUnsigned) {
-        loadConstantFloat32(-0.0f, ScratchFloat32Reg);
+        loadConstantFloat32(0.0f, ScratchFloat32Reg);
         branchFloat(Assembler::DoubleGreaterThan, input, ScratchFloat32Reg, &traps.intOverflow);
         loadConstantFloat32(-1.0f, ScratchFloat32Reg);
         branchFloat(Assembler::DoubleLessThanOrEqual, input, ScratchFloat32Reg, &traps.intOverflow);
         jump(rejoin);
         return;
     }
 
     // We've used vcvtss2sq. See comment in outOfLineWasmTruncateDoubleToInt64.
--- a/js/src/jit/x86/CodeGenerator-x86.cpp
+++ b/js/src/jit/x86/CodeGenerator-x86.cpp
@@ -1260,29 +1260,34 @@ CodeGeneratorX86::visitWasmTruncateToInt
 
     MWasmTruncateToInt64* mir = lir->mir();
     FloatRegister floatTemp = ToFloatRegister(lir->temp());
 
     Label fail, convert;
 
     MOZ_ASSERT (mir->input()->type() == MIRType::Double || mir->input()->type() == MIRType::Float32);
 
-    auto* ool = new(alloc()) OutOfLineWasmTruncateCheck(mir, input);
+    auto* ool = new(alloc()) OutOfLineWasmTruncateCheck(mir, input, output);
     addOutOfLineCode(ool, mir);
 
+    bool isSaturating = mir->isSaturating();
     if (mir->input()->type() == MIRType::Float32) {
         if (mir->isUnsigned())
-            masm.wasmTruncateFloat32ToUInt64(input, output, ool->entry(), ool->rejoin(), floatTemp);
+            masm.wasmTruncateFloat32ToUInt64(input, output, isSaturating,
+                                             ool->entry(), ool->rejoin(), floatTemp);
         else
-            masm.wasmTruncateFloat32ToInt64(input, output, ool->entry(), ool->rejoin(), floatTemp);
+            masm.wasmTruncateFloat32ToInt64(input, output, isSaturating,
+                                            ool->entry(), ool->rejoin(), floatTemp);
     } else {
         if (mir->isUnsigned())
-            masm.wasmTruncateDoubleToUInt64(input, output, ool->entry(), ool->rejoin(), floatTemp);
+            masm.wasmTruncateDoubleToUInt64(input, output, isSaturating,
+                                            ool->entry(), ool->rejoin(), floatTemp);
         else
-            masm.wasmTruncateDoubleToInt64(input, output, ool->entry(), ool->rejoin(), floatTemp);
+            masm.wasmTruncateDoubleToInt64(input, output, isSaturating,
+                                           ool->entry(), ool->rejoin(), floatTemp);
     }
 }
 
 void
 CodeGeneratorX86::visitInt64ToFloatingPoint(LInt64ToFloatingPoint* lir)
 {
     Register64 input = ToRegister64(lir->getInt64Operand(0));
     FloatRegister output = ToFloatRegister(lir->output());
--- a/js/src/jit/x86/MacroAssembler-x86.cpp
+++ b/js/src/jit/x86/MacroAssembler-x86.cpp
@@ -980,52 +980,54 @@ MacroAssembler::atomicFetchOp64(const Sy
 void
 MacroAssembler::atomicFetchOp64(const Synchronization&, AtomicOp op, const Address& value,
                                 const BaseIndex& mem, Register64 temp, Register64 output)
 {
     AtomicFetchOp64(*this, op, value, mem, temp, output);
 }
 
 void
-MacroAssembler::wasmTruncateDoubleToUInt32(FloatRegister input, Register output, Label* oolEntry)
+MacroAssembler::wasmTruncateDoubleToUInt32(FloatRegister input, Register output, bool isSaturating,
+                                           Label* oolEntry)
 {
     Label done;
     vcvttsd2si(input, output);
     branch32(Assembler::Condition::NotSigned, output, Imm32(0), &done);
 
     loadConstantDouble(double(int32_t(0x80000000)), ScratchDoubleReg);
     addDouble(input, ScratchDoubleReg);
     vcvttsd2si(ScratchDoubleReg, output);
 
     branch32(Assembler::Condition::Signed, output, Imm32(0), oolEntry);
     or32(Imm32(0x80000000), output);
 
     bind(&done);
 }
 
 void
-MacroAssembler::wasmTruncateFloat32ToUInt32(FloatRegister input, Register output, Label* oolEntry)
+MacroAssembler::wasmTruncateFloat32ToUInt32(FloatRegister input, Register output, bool isSaturating,
+                                            Label* oolEntry)
 {
     Label done;
     vcvttss2si(input, output);
     branch32(Assembler::Condition::NotSigned, output, Imm32(0), &done);
 
     loadConstantFloat32(float(int32_t(0x80000000)), ScratchFloat32Reg);
     addFloat32(input, ScratchFloat32Reg);
     vcvttss2si(ScratchFloat32Reg, output);
 
     branch32(Assembler::Condition::Signed, output, Imm32(0), oolEntry);
     or32(Imm32(0x80000000), output);
 
     bind(&done);
 }
 
 void
-MacroAssembler::wasmTruncateDoubleToInt64(FloatRegister input, Register64 output, Label* oolEntry,
-                                          Label* oolRejoin, FloatRegister tempReg)
+MacroAssembler::wasmTruncateDoubleToInt64(FloatRegister input, Register64 output, bool isSaturating,
+                                          Label* oolEntry, Label* oolRejoin, FloatRegister tempReg)
 {
     Label fail, convert;
     Register temp = output.high;
 
     // Make sure input fits in (u)int64.
     reserveStack(2 * sizeof(int32_t));
     storeDouble(input, Operand(esp, 0));
     branchDoubleNotInInt64Range(Address(esp, 0), temp, &fail);
@@ -1044,18 +1046,19 @@ MacroAssembler::wasmTruncateDoubleToInt6
     truncateDoubleToInt64(Address(esp, 0), Address(esp, 0), temp);
 
     // Load value into int64 register.
     load64(Address(esp, 0), output);
     freeStack(2 * sizeof(int32_t));
 }
 
 void
-MacroAssembler::wasmTruncateFloat32ToInt64(FloatRegister input, Register64 output, Label* oolEntry,
-                                           Label* oolRejoin, FloatRegister tempReg)
+MacroAssembler::wasmTruncateFloat32ToInt64(FloatRegister input, Register64 output,
+                                           bool isSaturating,
+                                           Label* oolEntry, Label* oolRejoin, FloatRegister tempReg)
 {
     Label fail, convert;
     Register temp = output.high;
 
     // Make sure input fits in (u)int64.
     reserveStack(2 * sizeof(int32_t));
     storeFloat32(input, Operand(esp, 0));
     branchFloat32NotInInt64Range(Address(esp, 0), temp, &fail);
@@ -1074,17 +1077,18 @@ MacroAssembler::wasmTruncateFloat32ToInt
     truncateFloat32ToInt64(Address(esp, 0), Address(esp, 0), temp);
 
     // Load value into int64 register.
     load64(Address(esp, 0), output);
     freeStack(2 * sizeof(int32_t));
 }
 
 void
-MacroAssembler::wasmTruncateDoubleToUInt64(FloatRegister input, Register64 output, Label* oolEntry,
+MacroAssembler::wasmTruncateDoubleToUInt64(FloatRegister input, Register64 output,
+                                           bool isSaturating, Label* oolEntry,
                                            Label* oolRejoin, FloatRegister tempReg)
 {
     Label fail, convert;
     Register temp = output.high;
 
     // Make sure input fits in (u)int64.
     reserveStack(2 * sizeof(int32_t));
     storeDouble(input, Operand(esp, 0));
@@ -1104,17 +1108,18 @@ MacroAssembler::wasmTruncateDoubleToUInt
     truncateDoubleToUInt64(Address(esp, 0), Address(esp, 0), temp, tempReg);
 
     // Load value into int64 register.
     load64(Address(esp, 0), output);
     freeStack(2 * sizeof(int32_t));
 }
 
 void
-MacroAssembler::wasmTruncateFloat32ToUInt64(FloatRegister input, Register64 output, Label* oolEntry,
+MacroAssembler::wasmTruncateFloat32ToUInt64(FloatRegister input, Register64 output,
+                                            bool isSaturating, Label* oolEntry,
                                             Label* oolRejoin, FloatRegister tempReg)
 {
     Label fail, convert;
     Register temp = output.high;
 
     // Make sure input fits in (u)int64.
     reserveStack(2 * sizeof(int32_t));
     storeFloat32(input, Operand(esp, 0));
--- a/js/src/jsmath.cpp
+++ b/js/src/jsmath.cpp
@@ -650,20 +650,20 @@ js::minmax_impl(JSContext* cx, bool max,
         res.setNumber(math_max_impl(x, y));
     else
         res.setNumber(math_min_impl(x, y));
 
     return true;
 }
 
 double
-js::powi(double x, int y)
+js::powi(double x, int32_t y)
 {
     AutoUnsafeCallWithABI unsafe;
-    unsigned n = (y < 0) ? -y : y;
+    uint32_t n = Abs(y);
     double m = x;
     double p = 1;
     while (true) {
         if ((n & 1) != 0) p *= m;
         n >>= 1;
         if (n == 0) {
             if (y < 0) {
                 // Unfortunately, we have to be careful when p has reached
--- a/js/src/jsmath.h
+++ b/js/src/jsmath.h
@@ -5,16 +5,17 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef jsmath_h
 #define jsmath_h
 
 #include "mozilla/MemoryReporting.h"
 
 #include <cmath>
+#include <stdint.h>
 
 #include "NamespaceImports.h"
 
 namespace js {
 
 typedef double (*UnaryFunType)(double);
 
 class MathCache
@@ -338,17 +339,17 @@ math_round(JSContext* cx, unsigned argc,
 
 extern double
 math_round_impl(double x);
 
 extern float
 math_roundf_impl(float x);
 
 extern double
-powi(double x, int y);
+powi(double x, int32_t y);
 
 extern double
 ecmaPow(double x, double y);
 
 extern bool
 math_imul(JSContext* cx, unsigned argc, Value* vp);
 
 extern double
--- a/js/src/moz.build
+++ b/js/src/moz.build
@@ -651,16 +651,17 @@ DIRS += [
     'build',
 ]
 
 FINAL_LIBRARY = 'js'
 
 if CONFIG['NIGHTLY_BUILD']:
     DEFINES['ENABLE_BINARYDATA'] = True
     DEFINES['ENABLE_SIMD'] = True
+    DEFINES['ENABLE_WASM_SATURATING_TRUNC_OPS'] = True
     DEFINES['ENABLE_WASM_SIGNEXTEND_OPS'] = True
     DEFINES['ENABLE_WASM_THREAD_OPS'] = True
     # An experiment we want to run on Nightly: Can we change the JS
     # representation of an exported global from the global's value
     # to an instance of WebAssembly.Global without breaking existing
     # wasm content?
     DEFINES['ENABLE_WASM_GLOBAL'] = True
 
--- a/js/src/shell/js.cpp
+++ b/js/src/shell/js.cpp
@@ -1546,33 +1546,37 @@ CacheEntry(JSContext* cx, unsigned argc,
 
 static bool
 CacheEntry_isCacheEntry(JSObject* cache)
 {
     return JS_GetClass(cache) == &CacheEntry_class;
 }
 
 static JSString*
-CacheEntry_getSource(HandleObject cache)
+CacheEntry_getSource(JSContext* cx, HandleObject cache)
 {
     MOZ_ASSERT(CacheEntry_isCacheEntry(cache));
     Value v = JS_GetReservedSlot(cache, CacheEntry_SOURCE);
-    if (!v.isString())
+    if (!v.isString()) {
+        JS_ReportErrorASCII(cx, "CacheEntry_getSource: Unexpected type of source reserved slot.");
         return nullptr;
+    }
 
     return v.toString();
 }
 
 static uint8_t*
-CacheEntry_getBytecode(HandleObject cache, uint32_t* length)
+CacheEntry_getBytecode(JSContext* cx, HandleObject cache, uint32_t* length)
 {
     MOZ_ASSERT(CacheEntry_isCacheEntry(cache));
     Value v = JS_GetReservedSlot(cache, CacheEntry_BYTECODE);
-    if (!v.isObject() || !v.toObject().is<ArrayBufferObject>())
+    if (!v.isObject() || !v.toObject().is<ArrayBufferObject>()) {
+        JS_ReportErrorASCII(cx, "CacheEntry_getBytecode: Unexpected type of bytecode reserved slot.");
         return nullptr;
+    }
 
     ArrayBufferObject* arrayBuffer = &v.toObject().as<ArrayBufferObject>();
     *length = arrayBuffer->byteLength();
     return arrayBuffer->dataPointer();
 }
 
 static bool
 CacheEntry_setBytecode(JSContext* cx, HandleObject cache, uint8_t* buffer, uint32_t length)
@@ -1649,17 +1653,19 @@ Evaluate(JSContext* cx, unsigned argc, V
     }
 
     RootedString code(cx, nullptr);
     RootedObject cacheEntry(cx, nullptr);
     if (args[0].isString()) {
         code = args[0].toString();
     } else if (args[0].isObject() && CacheEntry_isCacheEntry(&args[0].toObject())) {
         cacheEntry = &args[0].toObject();
-        code = CacheEntry_getSource(cacheEntry);
+        code = CacheEntry_getSource(cx, cacheEntry);
+        if (!code)
+            return false;
     }
 
     if (!code || (args.length() == 2 && args[1].isPrimitive())) {
         JS_ReportErrorNumberASCII(cx, my_GetErrorMessage, nullptr, JSSMSG_INVALID_ARGS, "evaluate");
         return false;
     }
 
     CompileOptions options(cx);
@@ -1832,17 +1838,17 @@ Evaluate(JSContext* cx, unsigned argc, V
         return false;
 
     JS::TranscodeBuffer loadBuffer;
     JS::TranscodeBuffer saveBuffer;
 
     if (loadBytecode) {
         uint32_t loadLength = 0;
         uint8_t* loadData = nullptr;
-        loadData = CacheEntry_getBytecode(cacheEntry, &loadLength);
+        loadData = CacheEntry_getBytecode(cx, cacheEntry, &loadLength);
         if (!loadData)
             return false;
         if (!loadBuffer.append(loadData, loadLength)) {
             JS_ReportOutOfMemory(cx);
             return false;
         }
     }
 
@@ -4910,17 +4916,17 @@ OffThreadDecodeScript(JSContext* cx, uns
 
     // We assume the caller wants caching if at all possible, ignoring
     // heuristics that make sense for a real browser.
     options.forceAsync = true;
 
     JS::TranscodeBuffer loadBuffer;
     uint32_t loadLength = 0;
     uint8_t* loadData = nullptr;
-    loadData = CacheEntry_getBytecode(cacheEntry, &loadLength);
+    loadData = CacheEntry_getBytecode(cx, cacheEntry, &loadLength);
     if (!loadData)
         return false;
     if (!loadBuffer.append(loadData, loadLength)) {
         JS_ReportOutOfMemory(cx);
         return false;
     }
 
     if (!JS::CanDecodeOffThread(cx, options, loadLength)) {
--- a/js/src/vm/JSFunction.cpp
+++ b/js/src/vm/JSFunction.cpp
@@ -2033,20 +2033,20 @@ js::CanReuseScriptForClone(JSCompartment
     // in that case we have some actual scope objects on our scope chain and
     // whatnot; whoever put them there should be responsible for setting our
     // script's flags appropriately.  We hit this case for JSOP_LAMBDA, for
     // example.
     if (IsSyntacticEnvironment(newParent))
         return true;
 
     // We need to clone the script if we're not already marked as having a
-    // non-syntactic scope. If we're lazy, go ahead and clone the script; see
-    // the big comment at the end of CopyScriptInternal for the explanation of
-    // what's going on there.
-    return fun->hasScript() && fun->nonLazyScript()->hasNonSyntacticScope();
+    // non-syntactic scope.
+    return fun->hasScript()
+        ? fun->nonLazyScript()->hasNonSyntacticScope()
+        : fun->lazyScript()->enclosingScope()->hasOnChain(ScopeKind::NonSyntactic);
 }
 
 static inline JSFunction*
 NewFunctionClone(JSContext* cx, HandleFunction fun, NewObjectKind newKind,
                  gc::AllocKind allocKind, HandleObject proto)
 {
     RootedObject cloneProto(cx, proto);
     if (!proto && (fun->isGenerator() || fun->isAsync())) {
--- a/js/src/vm/JSScript.cpp
+++ b/js/src/vm/JSScript.cpp
@@ -3669,33 +3669,16 @@ js::detail::CopyScript(JSContext* cx, Ha
         dst->trynotes()->vector = Rebase<JSTryNote>(dst, src, src->trynotes()->vector);
     if (nscopenotes != 0)
         dst->scopeNotes()->vector = Rebase<ScopeNote>(dst, src, src->scopeNotes()->vector);
     if (nyieldoffsets != 0) {
         dst->yieldAndAwaitOffsets().vector_ =
             Rebase<uint32_t>(dst, src, src->yieldAndAwaitOffsets().vector_);
     }
 
-    /*
-     * Function delazification assumes that their script does not have a
-     * non-syntactic global scope.  We ensure that as follows:
-     *
-     * 1) Initial parsing only creates lazy functions if
-     *    !hasNonSyntacticScope.
-     * 2) Cloning a lazy function into a non-global scope will always require
-     *    that its script be cloned.  See comments in
-     *    CloneFunctionObjectUseSameScript.
-     * 3) Cloning a script never sets a lazyScript on the clone, so the function
-     *    cannot be relazified.
-     *
-     * If you decide that lazy functions should be supported with a
-     * non-syntactic global scope, make sure delazification can deal.
-     */
-    MOZ_ASSERT_IF(dst->hasNonSyntacticScope(), !dst->maybeLazyScript());
-    MOZ_ASSERT_IF(dst->hasNonSyntacticScope(), !dst->isRelazifiable());
     return true;
 }
 
 static JSScript*
 CreateEmptyScriptForClone(JSContext* cx, HandleScript src)
 {
     /*
      * Wrap the script source object as needed. Self-hosted scripts may be
--- a/js/src/wasm/WasmAST.h
+++ b/js/src/wasm/WasmAST.h
@@ -193,16 +193,17 @@ enum class AstExprKind
     BranchTable,
     Call,
     CallIndirect,
     ComparisonOperator,
     Const,
     ConversionOperator,
     CurrentMemory,
     Drop,
+    ExtraConversionOperator,
     First,
     GetGlobal,
     GetLocal,
     GrowMemory,
     If,
     Load,
     Nop,
     Pop,
@@ -1136,16 +1137,35 @@ class AstConversionOperator final : publ
       : AstExpr(Kind, ExprType::Limit),
         op_(op), operand_(operand)
     {}
 
     Op op() const { return op_; }
     AstExpr* operand() const { return operand_; }
 };
 
+#ifdef ENABLE_WASM_SATURATING_TRUNC_OPS
+// Like AstConversionOperator, but for opcodes encoded with the Numeric prefix.
+class AstExtraConversionOperator final : public AstExpr
+{
+    NumericOp op_;
+    AstExpr* operand_;
+
+  public:
+    static const AstExprKind Kind = AstExprKind::ExtraConversionOperator;
+    explicit AstExtraConversionOperator(NumericOp op, AstExpr* operand)
+      : AstExpr(Kind, ExprType::Limit),
+        op_(op), operand_(operand)
+    {}
+
+    NumericOp op() const { return op_; }
+    AstExpr* operand() const { return operand_; }
+};
+#endif
+
 // This is an artificial AST node which can fill operand slots in an AST
 // constructed from parsing or decoding stack-machine code that doesn't have
 // an inherent AST structure.
 class AstPop final : public AstExpr
 {
   public:
     static const AstExprKind Kind = AstExprKind::Pop;
     AstPop()
--- a/js/src/wasm/WasmBaselineCompile.cpp
+++ b/js/src/wasm/WasmBaselineCompile.cpp
@@ -3575,125 +3575,142 @@ class BaseCompiler final : public BaseCo
 #else
         MOZ_CRASH("BaseCompiler platform hook: needPopcnt64Temp");
 #endif
     }
 
     class OutOfLineTruncateCheckF32OrF64ToI32 : public OutOfLineCode
     {
         AnyReg src;
-        bool isUnsigned;
+        RegI32 dest;
+        TruncFlags flags;
         BytecodeOffset off;
 
       public:
-        OutOfLineTruncateCheckF32OrF64ToI32(AnyReg src, bool isUnsigned, BytecodeOffset off)
+        OutOfLineTruncateCheckF32OrF64ToI32(AnyReg src, RegI32 dest, TruncFlags flags,
+                                            BytecodeOffset off)
           : src(src),
-            isUnsigned(isUnsigned),
+            dest(dest),
+            flags(flags),
             off(off)
         {}
 
         virtual void generate(MacroAssembler* masm) override {
             if (src.tag == AnyReg::F32)
-                masm->oolWasmTruncateCheckF32ToI32(src.f32(), isUnsigned, off, rejoin());
+                masm->oolWasmTruncateCheckF32ToI32(src.f32(), dest, flags, off, rejoin());
             else if (src.tag == AnyReg::F64)
-                masm->oolWasmTruncateCheckF64ToI32(src.f64(), isUnsigned, off, rejoin());
+                masm->oolWasmTruncateCheckF64ToI32(src.f64(), dest, flags, off, rejoin());
             else
                 MOZ_CRASH("unexpected type");
         }
     };
 
-    MOZ_MUST_USE bool truncateF32ToI32(RegF32 src, RegI32 dest, bool isUnsigned) {
+    MOZ_MUST_USE bool truncateF32ToI32(RegF32 src, RegI32 dest, TruncFlags flags) {
         BytecodeOffset off = bytecodeOffset();
         OutOfLineCode* ool =
             addOutOfLineCode(new(alloc_) OutOfLineTruncateCheckF32OrF64ToI32(AnyReg(src),
-                                                                             isUnsigned,
+                                                                             dest,
+                                                                             flags,
                                                                              off));
         if (!ool)
             return false;
-        if (isUnsigned)
-            masm.wasmTruncateFloat32ToUInt32(src, dest, ool->entry());
+        bool isSaturating = flags & TRUNC_SATURATING;
+        if (flags & TRUNC_UNSIGNED)
+            masm.wasmTruncateFloat32ToUInt32(src, dest, isSaturating, ool->entry());
         else
-            masm.wasmTruncateFloat32ToInt32(src, dest, ool->entry());
+            masm.wasmTruncateFloat32ToInt32(src, dest, isSaturating, ool->entry());
         masm.bind(ool->rejoin());
         return true;
     }
 
-    MOZ_MUST_USE bool truncateF64ToI32(RegF64 src, RegI32 dest, bool isUnsigned) {
+    MOZ_MUST_USE bool truncateF64ToI32(RegF64 src, RegI32 dest, TruncFlags flags) {
         BytecodeOffset off = bytecodeOffset();
         OutOfLineCode* ool =
             addOutOfLineCode(new(alloc_) OutOfLineTruncateCheckF32OrF64ToI32(AnyReg(src),
-                                                                             isUnsigned,
+                                                                             dest,
+                                                                             flags,
                                                                              off));
         if (!ool)
             return false;
-        if (isUnsigned)
-            masm.wasmTruncateDoubleToUInt32(src, dest, ool->entry());
+        bool isSaturating = flags & TRUNC_SATURATING;
+        if (flags & TRUNC_UNSIGNED)
+            masm.wasmTruncateDoubleToUInt32(src, dest, isSaturating, ool->entry());
         else
-            masm.wasmTruncateDoubleToInt32(src, dest, ool->entry());
+            masm.wasmTruncateDoubleToInt32(src, dest, isSaturating, ool->entry());
         masm.bind(ool->rejoin());
         return true;
     }
 
     class OutOfLineTruncateCheckF32OrF64ToI64 : public OutOfLineCode
     {
         AnyReg src;
-        bool isUnsigned;
+        RegI64 dest;
+        TruncFlags flags;
         BytecodeOffset off;
 
       public:
-        OutOfLineTruncateCheckF32OrF64ToI64(AnyReg src, bool isUnsigned, BytecodeOffset off)
+        OutOfLineTruncateCheckF32OrF64ToI64(AnyReg src, RegI64 dest, TruncFlags flags, BytecodeOffset off)
           : src(src),
-            isUnsigned(isUnsigned),
+            dest(dest),
+            flags(flags),
             off(off)
         {}
 
         virtual void generate(MacroAssembler* masm) override {
             if (src.tag == AnyReg::F32)
-                masm->oolWasmTruncateCheckF32ToI64(src.f32(), isUnsigned, off, rejoin());
+                masm->oolWasmTruncateCheckF32ToI64(src.f32(), dest, flags, off, rejoin());
             else if (src.tag == AnyReg::F64)
-                masm->oolWasmTruncateCheckF64ToI64(src.f64(), isUnsigned, off, rejoin());
+                masm->oolWasmTruncateCheckF64ToI64(src.f64(), dest, flags, off, rejoin());
             else
                 MOZ_CRASH("unexpected type");
         }
     };
 
 #ifndef RABALDR_FLOAT_TO_I64_CALLOUT
-    MOZ_MUST_USE RegF64 needTempForFloatingToI64(bool isUnsigned) {
+    MOZ_MUST_USE RegF64 needTempForFloatingToI64(TruncFlags flags) {
 # if defined(JS_CODEGEN_X86) || defined(JS_CODEGEN_X64)
-        if (isUnsigned)
+        if (flags & TRUNC_UNSIGNED)
             return needF64();
 # endif
         return RegF64::Invalid();
     }
 
-    MOZ_MUST_USE bool truncateF32ToI64(RegF32 src, RegI64 dest, bool isUnsigned, RegF64 temp) {
+    MOZ_MUST_USE bool truncateF32ToI64(RegF32 src, RegI64 dest, TruncFlags flags, RegF64 temp) {
         OutOfLineCode* ool = addOutOfLineCode(
             new (alloc_) OutOfLineTruncateCheckF32OrF64ToI64(AnyReg(src),
-                                                             isUnsigned,
+                                                             dest,
+                                                             flags,
                                                              bytecodeOffset()));
         if (!ool)
             return false;
-        if (isUnsigned)
-            masm.wasmTruncateFloat32ToUInt64(src, dest, ool->entry(), ool->rejoin(), temp);
+        bool isSaturating = flags & TRUNC_SATURATING;
+        if (flags & TRUNC_UNSIGNED)
+            masm.wasmTruncateFloat32ToUInt64(src, dest, isSaturating, ool->entry(),
+                                             ool->rejoin(), temp);
         else
-            masm.wasmTruncateFloat32ToInt64(src, dest, ool->entry(), ool->rejoin(), temp);
+            masm.wasmTruncateFloat32ToInt64(src, dest, isSaturating, ool->entry(),
+                                            ool->rejoin(), temp);
         return true;
     }
 
-    MOZ_MUST_USE bool truncateF64ToI64(RegF64 src, RegI64 dest, bool isUnsigned, RegF64 temp) {
+    MOZ_MUST_USE bool truncateF64ToI64(RegF64 src, RegI64 dest, TruncFlags flags, RegF64 temp) {
         OutOfLineCode* ool = addOutOfLineCode(
             new (alloc_) OutOfLineTruncateCheckF32OrF64ToI64(AnyReg(src),
-                                                             isUnsigned,
+                                                             dest,
+                                                             flags,
                                                              bytecodeOffset()));
         if (!ool)
             return false;
-        if (isUnsigned)
-            masm.wasmTruncateDoubleToUInt64(src, dest, ool->entry(), ool->rejoin(), temp);
+        bool isSaturating = flags & TRUNC_SATURATING;
+        if (flags & TRUNC_UNSIGNED)
+            masm.wasmTruncateDoubleToUInt64(src, dest, isSaturating, ool->entry(),
+                                            ool->rejoin(), temp);
         else
-            masm.wasmTruncateDoubleToInt64(src, dest, ool->entry(), ool->rejoin(), temp);
+            masm.wasmTruncateDoubleToInt64(src, dest, isSaturating, ool->entry(),
+                                           ool->rejoin(), temp);
         return true;
     }
 #endif // RABALDR_FLOAT_TO_I64_CALLOUT
 
 #ifndef RABALDR_I64_TO_FLOAT_CALLOUT
     RegI32 needConvertI64ToFloatTemp(ValType to, bool isUnsigned) {
         bool needs = false;
         if (to == ValType::F64) {
@@ -5082,24 +5099,24 @@ class BaseCompiler final : public BaseCo
     void emitPopcntI32();
     void emitPopcntI64();
     void emitAbsF32();
     void emitAbsF64();
     void emitNegateF32();
     void emitNegateF64();
     void emitSqrtF32();
     void emitSqrtF64();
-    template<bool isUnsigned> MOZ_MUST_USE bool emitTruncateF32ToI32();
-    template<bool isUnsigned> MOZ_MUST_USE bool emitTruncateF64ToI32();
+    template<TruncFlags flags> MOZ_MUST_USE bool emitTruncateF32ToI32();
+    template<TruncFlags flags> MOZ_MUST_USE bool emitTruncateF64ToI32();
 #ifdef RABALDR_FLOAT_TO_I64_CALLOUT
     MOZ_MUST_USE bool emitConvertFloatingToInt64Callout(SymbolicAddress callee, ValType operandType,
                                                         ValType resultType);
 #else
-    template<bool isUnsigned> MOZ_MUST_USE bool emitTruncateF32ToI64();
-    template<bool isUnsigned> MOZ_MUST_USE bool emitTruncateF64ToI64();
+    template<TruncFlags flags> MOZ_MUST_USE bool emitTruncateF32ToI64();
+    template<TruncFlags flags> MOZ_MUST_USE bool emitTruncateF64ToI64();
 #endif
     void emitWrapI64ToI32();
     void emitExtendI32_8();
     void emitExtendI32_16();
     void emitExtendI64_8();
     void emitExtendI64_16();
     void emitExtendI64_32();
     void emitExtendI32ToI64();
@@ -6067,66 +6084,66 @@ BaseCompiler::emitSqrtF32()
 void
 BaseCompiler::emitSqrtF64()
 {
     RegF64 r = popF64();
     masm.sqrtDouble(r, r);
     pushF64(r);
 }
 
-template<bool isUnsigned>
+template<TruncFlags flags>
 bool
 BaseCompiler::emitTruncateF32ToI32()
 {
     RegF32 rs = popF32();
     RegI32 rd = needI32();
-    if (!truncateF32ToI32(rs, rd, isUnsigned))
+    if (!truncateF32ToI32(rs, rd, flags))
         return false;
     freeF32(rs);
     pushI32(rd);
     return true;
 }
 
-template<bool isUnsigned>
+template<TruncFlags flags>
 bool
 BaseCompiler::emitTruncateF64ToI32()
 {
     RegF64 rs = popF64();
     RegI32 rd = needI32();
-    if (!truncateF64ToI32(rs, rd, isUnsigned))
+    if (!truncateF64ToI32(rs, rd, flags))
         return false;
     freeF64(rs);
     pushI32(rd);
     return true;
 }
 
 #ifndef RABALDR_FLOAT_TO_I64_CALLOUT
-template<bool isUnsigned>
+template<TruncFlags flags>
 bool
 BaseCompiler::emitTruncateF32ToI64()
 {
     RegF32 rs = popF32();
     RegI64 rd = needI64();
-    RegF64 temp = needTempForFloatingToI64(isUnsigned);
-    if (!truncateF32ToI64(rs, rd, isUnsigned, temp))
+    RegF64 temp = needTempForFloatingToI64(flags);
+    if (!truncateF32ToI64(rs, rd, flags, temp))
         return false;
     maybeFreeF64(temp);
     freeF32(rs);
     pushI64(rd);
     return true;
 }
 
-template<bool isUnsigned>
+template<TruncFlags flags>
 bool
 BaseCompiler::emitTruncateF64ToI64()
 {
     RegF64 rs = popF64();
     RegI64 rd = needI64();
-    RegF64 temp = needTempForFloatingToI64(isUnsigned);
-    if (!truncateF64ToI64(rs, rd, isUnsigned, temp))
+    RegF64 temp = needTempForFloatingToI64(flags);
+    if (!truncateF64ToI64(rs, rd, flags, temp))
         return false;
     maybeFreeF64(temp);
     freeF64(rs);
     pushI64(rd);
     return true;
 }
 #endif // RABALDR_FLOAT_TO_I64_CALLOUT
 
@@ -7279,28 +7296,39 @@ BaseCompiler::emitConvertFloatingToInt64
     masm.callWithABI(bytecodeOffset(), callee);
 
     freeF64(doubleInput);
 
     RegI64 rv = captureReturnedI64();
 
     RegF64 inputVal = popF64();
 
-    bool isUnsigned = callee == SymbolicAddress::TruncateDoubleToUint64;
-
-    // The OOL check just succeeds or fails, it does not generate a value.
-    OutOfLineCode* ool =
-        addOutOfLineCode(new (alloc_) OutOfLineTruncateCheckF32OrF64ToI64(AnyReg(inputVal),
-                                                                          isUnsigned,
-                                                                          bytecodeOffset()));
-    if (!ool)
-        return false;
-
-    masm.branch64(Assembler::Equal, rv, Imm64(0x8000000000000000), ool->entry());
-    masm.bind(ool->rejoin());
+    TruncFlags flags = 0;
+    if (callee == SymbolicAddress::TruncateDoubleToUint64)
+        flags |= TRUNC_UNSIGNED;
+    if (callee == SymbolicAddress::SaturatingTruncateDoubleToInt64 ||
+        callee == SymbolicAddress::SaturatingTruncateDoubleToUint64) {
+        flags |= TRUNC_SATURATING;
+    }
+
+    // If we're saturating, the callout will always produce the final result
+    // value. Otherwise, the callout value will return 0x8000000000000000
+    // and we need to produce traps.
+    OutOfLineCode* ool = nullptr;
+    if (!(flags & TRUNC_SATURATING)) {
+        // The OOL check just succeeds or fails, it does not generate a value.
+        ool = addOutOfLineCode(new (alloc_) OutOfLineTruncateCheckF32OrF64ToI64(AnyReg(inputVal),
+                                                                                rv, flags,
+                                                                                bytecodeOffset()));
+        if (!ool)
+            return false;
+
+        masm.branch64(Assembler::Equal, rv, Imm64(0x8000000000000000), ool->entry());
+        masm.bind(ool->rejoin());
+    }
 
     pushI64(rv);
     freeF64(inputVal);
 
     return true;
 }
 #endif // RABALDR_FLOAT_TO_I64_CALLOUT
 
@@ -8324,17 +8352,21 @@ BaseCompiler::emitWait(ValType type, uin
         emitInstanceCall(lineOrBytecode, SigPIIL_, ExprType::I32, SymbolicAddress::WaitI32);
         break;
       case ValType::I64:
         emitInstanceCall(lineOrBytecode, SigPILL_, ExprType::I32, SymbolicAddress::WaitI64);
         break;
       default:
         MOZ_CRASH();
     }
-    masm.branchTest32(Assembler::Signed, ReturnReg, ReturnReg, oldTrap(Trap::ThrowReported));
+
+    Label ok;
+    masm.branchTest32(Assembler::NotSigned, ReturnReg, ReturnReg, &ok);
+    trap(Trap::ThrowReported);
+    masm.bind(&ok);
 
     return true;
 }
 
 bool
 BaseCompiler::emitWake()
 {
     uint32_t lineOrBytecode = readCallSiteLineOrBytecode();
@@ -8343,17 +8375,21 @@ BaseCompiler::emitWake()
     LinearMemoryAddress<Nothing> addr;
     if (!iter_.readWake(&addr, &nothing))
         return false;
 
     if (deadCode_)
         return true;
 
     emitInstanceCall(lineOrBytecode, SigPII_, ExprType::I32, SymbolicAddress::Wake);
-    masm.branchTest32(Assembler::Signed, ReturnReg, ReturnReg, oldTrap(Trap::ThrowReported));
+
+    Label ok;
+    masm.branchTest32(Assembler::NotSigned, ReturnReg, ReturnReg, &ok);
+    trap(Trap::ThrowReported);
+    masm.bind(&ok);
 
     return true;
 }
 
 bool
 BaseCompiler::emitBody()
 {
     if (!iter_.readFunctionStart(sig().ret()))
@@ -8514,23 +8550,23 @@ BaseCompiler::emitBody()
             CHECK_NEXT(emitBinary(emitQuotientU32, ValType::I32));
           case uint16_t(Op::I32RemS):
             CHECK_NEXT(emitBinary(emitRemainderI32, ValType::I32));
           case uint16_t(Op::I32RemU):
             CHECK_NEXT(emitBinary(emitRemainderU32, ValType::I32));
           case uint16_t(Op::I32Eqz):
             CHECK_NEXT(emitConversion(emitEqzI32, ValType::I32, ValType::I32));
           case uint16_t(Op::I32TruncSF32):
-            CHECK_NEXT(emitConversionOOM(emitTruncateF32ToI32<false>, ValType::F32, ValType::I32));
+            CHECK_NEXT(emitConversionOOM(emitTruncateF32ToI32<0>, ValType::F32, ValType::I32));
           case uint16_t(Op::I32TruncUF32):
-            CHECK_NEXT(emitConversionOOM(emitTruncateF32ToI32<true>, ValType::F32, ValType::I32));
+            CHECK_NEXT(emitConversionOOM(emitTruncateF32ToI32<TRUNC_UNSIGNED>, ValType::F32, ValType::I32));
           case uint16_t(Op::I32TruncSF64):
-            CHECK_NEXT(emitConversionOOM(emitTruncateF64ToI32<false>, ValType::F64, ValType::I32));
+            CHECK_NEXT(emitConversionOOM(emitTruncateF64ToI32<0>, ValType::F64, ValType::I32));
           case uint16_t(Op::I32TruncUF64):
-            CHECK_NEXT(emitConversionOOM(emitTruncateF64ToI32<true>, ValType::F64, ValType::I32));
+            CHECK_NEXT(emitConversionOOM(emitTruncateF64ToI32<TRUNC_UNSIGNED>, ValType::F64, ValType::I32));
           case uint16_t(Op::I32WrapI64):
             CHECK_NEXT(emitConversion(emitWrapI64ToI32, ValType::I64, ValType::I32));
           case uint16_t(Op::I32ReinterpretF32):
             CHECK_NEXT(emitConversion(emitReinterpretF32AsI32, ValType::F32, ValType::I32));
           case uint16_t(Op::I32Clz):
             CHECK_NEXT(emitUnary(emitClzI32, ValType::I32));
           case uint16_t(Op::I32Ctz):
             CHECK_NEXT(emitUnary(emitCtzI32, ValType::I32));
@@ -8612,41 +8648,41 @@ BaseCompiler::emitBody()
             CHECK_NEXT(emitBinary(emitRemainderU64, ValType::I64));
 #endif
           case uint16_t(Op::I64TruncSF32):
 #ifdef RABALDR_FLOAT_TO_I64_CALLOUT
             CHECK_NEXT(emitCalloutConversionOOM(emitConvertFloatingToInt64Callout,
                                                 SymbolicAddress::TruncateDoubleToInt64,
                                                 ValType::F32, ValType::I64));
 #else
-            CHECK_NEXT(emitConversionOOM(emitTruncateF32ToI64<false>, ValType::F32, ValType::I64));
+            CHECK_NEXT(emitConversionOOM(emitTruncateF32ToI64<0>, ValType::F32, ValType::I64));
 #endif
           case uint16_t(Op::I64TruncUF32):
 #ifdef RABALDR_FLOAT_TO_I64_CALLOUT
             CHECK_NEXT(emitCalloutConversionOOM(emitConvertFloatingToInt64Callout,
                                                 SymbolicAddress::TruncateDoubleToUint64,
                                                 ValType::F32, ValType::I64));
 #else
-            CHECK_NEXT(emitConversionOOM(emitTruncateF32ToI64<true>, ValType::F32, ValType::I64));
+            CHECK_NEXT(emitConversionOOM(emitTruncateF32ToI64<TRUNC_UNSIGNED>, ValType::F32, ValType::I64));
 #endif
           case uint16_t(Op::I64TruncSF64):
 #ifdef RABALDR_FLOAT_TO_I64_CALLOUT
             CHECK_NEXT(emitCalloutConversionOOM(emitConvertFloatingToInt64Callout,
                                                 SymbolicAddress::TruncateDoubleToInt64,
                                                 ValType::F64, ValType::I64));
 #else
-            CHECK_NEXT(emitConversionOOM(emitTruncateF64ToI64<false>, ValType::F64, ValType::I64));
+            CHECK_NEXT(emitConversionOOM(emitTruncateF64ToI64<0>, ValType::F64, ValType::I64));
 #endif
           case uint16_t(Op::I64TruncUF64):
 #ifdef RABALDR_FLOAT_TO_I64_CALLOUT
             CHECK_NEXT(emitCalloutConversionOOM(emitConvertFloatingToInt64Callout,
                                                 SymbolicAddress::TruncateDoubleToUint64,
                                                 ValType::F64, ValType::I64));
 #else
-            CHECK_NEXT(emitConversionOOM(emitTruncateF64ToI64<true>, ValType::F64, ValType::I64));
+            CHECK_NEXT(emitConversionOOM(emitTruncateF64ToI64<TRUNC_UNSIGNED>, ValType::F64, ValType::I64));
 #endif
           case uint16_t(Op::I64ExtendSI32):
             CHECK_NEXT(emitConversion(emitExtendI32ToI64, ValType::I32, ValType::I64));
           case uint16_t(Op::I64ExtendUI32):
             CHECK_NEXT(emitConversion(emitExtendU32ToI64, ValType::I32, ValType::I64));
           case uint16_t(Op::I64ReinterpretF64):
             CHECK_NEXT(emitConversion(emitReinterpretF64AsI64, ValType::F64, ValType::I64));
           case uint16_t(Op::I64Or):
@@ -8907,16 +8943,77 @@ BaseCompiler::emitBody()
 #endif
 
           // Memory Related
           case uint16_t(Op::GrowMemory):
             CHECK_NEXT(emitGrowMemory());
           case uint16_t(Op::CurrentMemory):
             CHECK_NEXT(emitCurrentMemory());
 
+          // Numeric operations
+          case uint16_t(Op::NumericPrefix): {
+#ifdef ENABLE_WASM_SATURATING_TRUNC_OPS
+            switch (op.b1) {
+              case uint16_t(NumericOp::I32TruncSSatF32):
+                CHECK_NEXT(emitConversionOOM(emitTruncateF32ToI32<TRUNC_SATURATING>,
+                                             ValType::F32, ValType::I32));
+              case uint16_t(NumericOp::I32TruncUSatF32):
+                CHECK_NEXT(emitConversionOOM(emitTruncateF32ToI32<TRUNC_UNSIGNED | TRUNC_SATURATING>,
+                                             ValType::F32, ValType::I32));
+              case uint16_t(NumericOp::I32TruncSSatF64):
+                CHECK_NEXT(emitConversionOOM(emitTruncateF64ToI32<TRUNC_SATURATING>,
+                                             ValType::F64, ValType::I32));
+              case uint16_t(NumericOp::I32TruncUSatF64):
+                CHECK_NEXT(emitConversionOOM(emitTruncateF64ToI32<TRUNC_UNSIGNED | TRUNC_SATURATING>,
+                                             ValType::F64, ValType::I32));
+              case uint16_t(NumericOp::I64TruncSSatF32):
+#ifdef RABALDR_FLOAT_TO_I64_CALLOUT
+                CHECK_NEXT(emitCalloutConversionOOM(emitConvertFloatingToInt64Callout,
+                                                    SymbolicAddress::SaturatingTruncateDoubleToInt64,
+                                                    ValType::F32, ValType::I64));
+#else
+                CHECK_NEXT(emitConversionOOM(emitTruncateF32ToI64<TRUNC_SATURATING>,
+                                             ValType::F32, ValType::I64));
+#endif
+              case uint16_t(NumericOp::I64TruncUSatF32):
+#ifdef RABALDR_FLOAT_TO_I64_CALLOUT
+                CHECK_NEXT(emitCalloutConversionOOM(emitConvertFloatingToInt64Callout,
+                                                    SymbolicAddress::SaturatingTruncateDoubleToUint64,
+                                                    ValType::F32, ValType::I64));
+#else
+                CHECK_NEXT(emitConversionOOM(emitTruncateF32ToI64<TRUNC_UNSIGNED | TRUNC_SATURATING>,
+                                             ValType::F32, ValType::I64));
+#endif
+              case uint16_t(NumericOp::I64TruncSSatF64):
+#ifdef RABALDR_FLOAT_TO_I64_CALLOUT
+                CHECK_NEXT(emitCalloutConversionOOM(emitConvertFloatingToInt64Callout,
+                                                    SymbolicAddress::SaturatingTruncateDoubleToInt64,
+                                                    ValType::F64, ValType::I64));
+#else
+                CHECK_NEXT(emitConversionOOM(emitTruncateF64ToI64<TRUNC_SATURATING>,
+                                             ValType::F64, ValType::I64));
+#endif
+              case uint16_t(NumericOp::I64TruncUSatF64):
+#ifdef RABALDR_FLOAT_TO_I64_CALLOUT
+                CHECK_NEXT(emitCalloutConversionOOM(emitConvertFloatingToInt64Callout,
+                                                    SymbolicAddress::SaturatingTruncateDoubleToUint64,
+                                                    ValType::F64, ValType::I64));
+#else
+                CHECK_NEXT(emitConversionOOM(emitTruncateF64ToI64<TRUNC_UNSIGNED | TRUNC_SATURATING>,
+                                             ValType::F64, ValType::I64));
+#endif
+              default:
+                return iter_.unrecognizedOpcode(&op);
+            }
+            break;
+#else
+            return iter_.unrecognizedOpcode(&op);
+#endif
+          }
+
           // Thread operations
           case uint16_t(Op::ThreadPrefix): {
 #ifdef ENABLE_WASM_THREAD_OPS
             switch (op.b1) {
               case uint16_t(ThreadOp::Wake):
                 CHECK_NEXT(emitWake());
 
               case uint16_t(ThreadOp::I32Wait):
--- a/js/src/wasm/WasmBinaryConstants.h
+++ b/js/src/wasm/WasmBinaryConstants.h
@@ -318,28 +318,46 @@ enum class Op
     // Sign extension
     I32Extend8S                          = 0xc0,
     I32Extend16S                         = 0xc1,
     I64Extend8S                          = 0xc2,
     I64Extend16S                         = 0xc3,
     I64Extend32S                         = 0xc4,
 #endif
 
+    FirstPrefix                          = 0xfc,
+    NumericPrefix                        = 0xfc,
     ThreadPrefix                         = 0xfe,
     MozPrefix                            = 0xff,
 
     Limit                                = 0x100
 };
 
 inline bool
 IsPrefixByte(uint8_t b)
 {
-    return b >= uint8_t(Op::ThreadPrefix);
+    return b >= uint8_t(Op::FirstPrefix);
 }
 
+// Opcodes in the "numeric" opcode space.
+enum class NumericOp
+{
+    // Saturating float-to-int conversions
+    I32TruncSSatF32                      = 0x00,
+    I32TruncUSatF32                      = 0x01,
+    I32TruncSSatF64                      = 0x02,
+    I32TruncUSatF64                      = 0x03,
+    I64TruncSSatF32                      = 0x04,
+    I64TruncUSatF32                      = 0x05,
+    I64TruncSSatF64                      = 0x06,
+    I64TruncUSatF64                      = 0x07,
+
+    Limit
+};
+
 // Opcodes from threads proposal as of June 30, 2017
 enum class ThreadOp
 {
     // Wait and wake
     Wake                                 = 0x00,
     I32Wait                              = 0x01,
     I64Wait                              = 0x02,
 
--- a/js/src/wasm/WasmBinaryIterator.cpp
+++ b/js/src/wasm/WasmBinaryIterator.cpp
@@ -235,16 +235,34 @@ wasm::Classify(OpBytes op)
       case Op::Else:
         return OpKind::Else;
       case Op::End:
         return OpKind::End;
       case Op::CurrentMemory:
         return OpKind::CurrentMemory;
       case Op::GrowMemory:
         return OpKind::GrowMemory;
+      case Op::NumericPrefix: {
+#ifdef ENABLE_WASM_SATURATING_TRUNC_OPS
+          switch (NumericOp(op.b1)) {
+            case NumericOp::I32TruncSSatF32:
+            case NumericOp::I32TruncUSatF32:
+            case NumericOp::I32TruncSSatF64:
+            case NumericOp::I32TruncUSatF64:
+            case NumericOp::I64TruncSSatF32:
+            case NumericOp::I64TruncUSatF32:
+            case NumericOp::I64TruncSSatF64:
+            case NumericOp::I64TruncUSatF64:
+              return OpKind::Conversion;
+            default:
+              break;
+          }
+#endif
+          break;
+      }
       case Op::ThreadPrefix: {
 #ifdef ENABLE_WASM_THREAD_OPS
           switch (ThreadOp(op.b1)) {
             case ThreadOp::Limit:
               // Reject Limit for ThreadPrefix encoding
               break;
             case ThreadOp::Wake:
               return OpKind::Wake;
--- a/js/src/wasm/WasmBinaryToAST.cpp
+++ b/js/src/wasm/WasmBinaryToAST.cpp
@@ -700,16 +700,37 @@ AstDecodeConversion(AstDecodeContext& c,
         return false;
 
     if (!c.push(AstDecodeStackItem(conversion)))
         return false;
 
     return true;
 }
 
+#ifdef ENABLE_WASM_SATURATING_TRUNC_OPS
+static bool
+AstDecodeExtraConversion(AstDecodeContext& c, ValType fromType, ValType toType, NumericOp op)
+{
+    if (!c.iter().readConversion(fromType, toType, nullptr))
+        return false;
+
+    AstDecodeStackItem operand = c.popCopy();
+
+    AstExtraConversionOperator* conversion =
+        new(c.lifo) AstExtraConversionOperator(op, operand.expr);
+    if (!conversion)
+        return false;
+
+    if (!c.push(AstDecodeStackItem(conversion)))
+        return false;
+
+    return true;
+}
+#endif
+
 static AstLoadStoreAddress
 AstDecodeLoadStoreAddress(const LinearMemoryAddress<Nothing>& addr, const AstDecodeStackItem& item)
 {
     uint32_t flags = FloorLog2(addr.align);
     return AstLoadStoreAddress(item.expr, flags, addr.offset);
 }
 
 static bool
@@ -1635,16 +1656,44 @@ AstDecodeExpr(AstDecodeContext& c)
         if (!c.iter().readUnreachable())
             return false;
         tmp = new(c.lifo) AstUnreachable();
         if (!tmp)
             return false;
         if (!c.push(AstDecodeStackItem(tmp)))
             return false;
         break;
+#ifdef ENABLE_WASM_SATURATING_TRUNC_OPS
+      case uint16_t(Op::NumericPrefix):
+        switch (op.b1) {
+          case uint16_t(NumericOp::I32TruncSSatF32):
+          case uint16_t(NumericOp::I32TruncUSatF32):
+            if (!AstDecodeExtraConversion(c, ValType::F32, ValType::I32, NumericOp(op.b1)))
+                return false;
+            break;
+          case uint16_t(NumericOp::I32TruncSSatF64):
+          case uint16_t(NumericOp::I32TruncUSatF64):
+            if (!AstDecodeExtraConversion(c, ValType::F64, ValType::I32, NumericOp(op.b1)))
+                return false;
+            break;
+          case uint16_t(NumericOp::I64TruncSSatF32):
+          case uint16_t(NumericOp::I64TruncUSatF32):
+            if (!AstDecodeExtraConversion(c, ValType::F32, ValType::I64, NumericOp(op.b1)))
+                return false;
+            break;
+          case uint16_t(NumericOp::I64TruncSSatF64):
+          case uint16_t(NumericOp::I64TruncUSatF64):
+            if (!AstDecodeExtraConversion(c, ValType::F64, ValType::I64, NumericOp(op.b1)))
+                return false;
+            break;
+          default:
+            return c.iter().unrecognizedOpcode(&op);
+        }
+        break;
+#endif
       case uint16_t(Op::ThreadPrefix):
         switch (op.b1) {
           case uint16_t(ThreadOp::Wake):
             if (!AstDecodeWake(c))
                 return false;
             break;
           case uint16_t(ThreadOp::I32Wait):
           case uint16_t(ThreadOp::I64Wait):
--- a/js/src/wasm/WasmBinaryToText.cpp
+++ b/js/src/wasm/WasmBinaryToText.cpp
@@ -746,16 +746,43 @@ RenderConversionOperator(WasmRenderConte
       case Op::I32Eqz:            opStr = "i32.eqz"; break;
       case Op::I64Eqz:            opStr = "i64.eqz"; break;
       default:                      return Fail(c, "unexpected conversion operator");
     }
     return c.buffer.append(opStr, strlen(opStr));
 }
 
 static bool
+RenderExtraConversionOperator(WasmRenderContext& c, AstExtraConversionOperator& conv)
+{
+    if (!RenderExpr(c, *conv.operand()))
+        return false;
+
+    if (!RenderIndent(c))
+        return false;
+
+    MAP_AST_EXPR(c, conv);
+    const char* opStr;
+    switch (conv.op()) {
+#ifdef ENABLE_WASM_SATURATING_TRUNC_OPS
+      case NumericOp::I32TruncSSatF32:   opStr = "i32.trunc_s:sat/f32"; break;
+      case NumericOp::I32TruncUSatF32:   opStr = "i32.trunc_u:sat/f32"; break;
+      case NumericOp::I32TruncSSatF64:   opStr = "i32.trunc_s:sat/f64"; break;
+      case NumericOp::I32TruncUSatF64:   opStr = "i32.trunc_u:sat/f64"; break;
+      case NumericOp::I64TruncSSatF32:   opStr = "i64.trunc_s:sat/f32"; break;
+      case NumericOp::I64TruncUSatF32:   opStr = "i64.trunc_u:sat/f32"; break;
+      case NumericOp::I64TruncSSatF64:   opStr = "i64.trunc_s:sat/f64"; break;
+      case NumericOp::I64TruncUSatF64:   opStr = "i64.trunc_u:sat/f64"; break;
+#endif
+      default:                      return Fail(c, "unexpected extra conversion operator");
+    }
+    return c.buffer.append(opStr, strlen(opStr));
+}
+
+static bool
 RenderIf(WasmRenderContext& c, AstIf& if_)
 {
     if (!RenderExpr(c, if_.cond()))
         return false;
 
     if (!RenderIndent(c))
         return false;
 
@@ -1322,16 +1349,22 @@ RenderExpr(WasmRenderContext& c, AstExpr
       case AstExprKind::ComparisonOperator:
         if (!RenderComparisonOperator(c, expr.as<AstComparisonOperator>()))
             return false;
         break;
       case AstExprKind::ConversionOperator:
         if (!RenderConversionOperator(c, expr.as<AstConversionOperator>()))
             return false;
         break;
+#ifdef ENABLE_WASM_SATURATING_TRUNC_OPS
+      case AstExprKind::ExtraConversionOperator:
+        if (!RenderExtraConversionOperator(c, expr.as<AstExtraConversionOperator>()))
+            return false;
+        break;
+#endif
       case AstExprKind::Load:
         if (!RenderLoad(c, expr.as<AstLoad>()))
             return false;
         break;
       case AstExprKind::Store:
         if (!RenderStore(c, expr.as<AstStore>()))
             return false;
         break;
--- a/js/src/wasm/WasmBuiltins.cpp
+++ b/js/src/wasm/WasmBuiltins.cpp
@@ -436,16 +436,45 @@ TruncateDoubleToUint64(double input)
 {
     // Note: UINT64_MAX is not representable in double. It is actually UINT64_MAX + 1.
     // Therefore also sending the failure value.
     if (input >= double(UINT64_MAX) || input <= -1.0 || IsNaN(input))
         return 0x8000000000000000;
     return uint64_t(input);
 }
 
+static int64_t
+SaturatingTruncateDoubleToInt64(double input)
+{
+    // Handle in-range values (except INT64_MIN).
+    if (fabs(input) < -double(INT64_MIN))
+        return int64_t(input);
+    // Handle NaN.
+    if (IsNaN(input))
+        return 0;
+    // Handle positive overflow.
+    if (input > 0)
+        return INT64_MAX;
+    // Handle negative overflow.
+    return INT64_MIN;
+}
+
+static uint64_t
+SaturatingTruncateDoubleToUint64(double input)
+{
+    // Handle positive overflow.
+    if (input >= -double(INT64_MIN) * 2.0)
+        return UINT64_MAX;
+    // Handle in-range values.
+    if (input >= -1.0)
+        return uint64_t(input);
+    // Handle NaN and negative overflow.
+    return 0;
+}
+
 static double
 Int64ToDouble(int32_t x_hi, uint32_t x_lo)
 {
     int64_t x = int64_t((uint64_t(x_hi) << 32)) + int64_t(x_lo);
     return double(x);
 }
 
 static float
@@ -545,16 +574,22 @@ AddressOf(SymbolicAddress imm, ABIFuncti
         *abiType = Args_General4;
         return FuncCast(UModI64, *abiType);
       case SymbolicAddress::TruncateDoubleToUint64:
         *abiType = Args_Int64_Double;
         return FuncCast(TruncateDoubleToUint64, *abiType);
       case SymbolicAddress::TruncateDoubleToInt64:
         *abiType = Args_Int64_Double;
         return FuncCast(TruncateDoubleToInt64, *abiType);
+      case SymbolicAddress::SaturatingTruncateDoubleToUint64:
+        *abiType = Args_Int64_Double;
+        return FuncCast(SaturatingTruncateDoubleToUint64, *abiType);
+      case SymbolicAddress::SaturatingTruncateDoubleToInt64:
+        *abiType = Args_Int64_Double;
+        return FuncCast(SaturatingTruncateDoubleToInt64, *abiType);
       case SymbolicAddress::Uint64ToDouble:
         *abiType = Args_Double_IntInt;
         return FuncCast(Uint64ToDouble, *abiType);
       case SymbolicAddress::Uint64ToFloat32:
         *abiType = Args_Float32_IntInt;
         return FuncCast(Uint64ToFloat32, *abiType);
       case SymbolicAddress::Int64ToDouble:
         *abiType = Args_Double_IntInt;
@@ -678,16 +713,18 @@ wasm::NeedsBuiltinThunk(SymbolicAddress 
         return false;
       case SymbolicAddress::ToInt32:
       case SymbolicAddress::DivI64:
       case SymbolicAddress::UDivI64:
       case SymbolicAddress::ModI64:
       case SymbolicAddress::UModI64:
       case SymbolicAddress::TruncateDoubleToUint64:
       case SymbolicAddress::TruncateDoubleToInt64:
+      case SymbolicAddress::SaturatingTruncateDoubleToUint64:
+      case SymbolicAddress::SaturatingTruncateDoubleToInt64:
       case SymbolicAddress::Uint64ToDouble:
       case SymbolicAddress::Uint64ToFloat32:
       case SymbolicAddress::Int64ToDouble:
       case SymbolicAddress::Int64ToFloat32:
 #if defined(JS_CODEGEN_ARM)
       case SymbolicAddress::aeabi_idivmod:
       case SymbolicAddress::aeabi_uidivmod:
 #endif
--- a/js/src/wasm/WasmFrameIter.cpp
+++ b/js/src/wasm/WasmFrameIter.cpp
@@ -621,16 +621,19 @@ wasm::GenerateJitEntryPrologue(MacroAsse
     masm.haltingAlign(CodeAlignment);
 
     {
 #if defined(JS_CODEGEN_ARM)
         AutoForbidPools afp(&masm, /* number of instructions in scope = */ 2);
         offsets->begin = masm.currentOffset();
         MOZ_ASSERT(BeforePushRetAddr == 0);
         masm.push(lr);
+#elif defined(JS_CODEGEN_MIPS32) || defined(JS_CODEGEN_MIPS64)
+        offsets->begin = masm.currentOffset();
+        masm.push(ra);
 #else
         // The x86/x64 call instruction pushes the return address.
         offsets->begin = masm.currentOffset();
 #endif
         MOZ_ASSERT_IF(!masm.oom(), PushedRetAddr == masm.currentOffset() - offsets->begin);
 
         // Save jit frame pointer, so unwinding from wasm to jit frames is trivial.
         masm.moveStackPtrTo(FramePointer);
@@ -1110,16 +1113,20 @@ ThunkedNativeToDescription(SymbolicAddre
       case SymbolicAddress::ModI64:
         return "call to native i64.rem_s (in wasm)";
       case SymbolicAddress::UModI64:
         return "call to native i64.rem_u (in wasm)";
       case SymbolicAddress::TruncateDoubleToUint64:
         return "call to native i64.trunc_u/f64 (in wasm)";
       case SymbolicAddress::TruncateDoubleToInt64:
         return "call to native i64.trunc_s/f64 (in wasm)";
+      case SymbolicAddress::SaturatingTruncateDoubleToUint64:
+        return "call to native i64.trunc_u:sat/f64 (in wasm)";
+      case SymbolicAddress::SaturatingTruncateDoubleToInt64:
+        return "call to native i64.trunc_s:sat/f64 (in wasm)";
       case SymbolicAddress::Uint64ToDouble:
         return "call to native f64.convert_u/i64 (in wasm)";
       case SymbolicAddress::Uint64ToFloat32:
         return "call to native f32.convert_u/i64 (in wasm)";
       case SymbolicAddress::Int64ToDouble:
         return "call to native f64.convert_s/i64 (in wasm)";
       case SymbolicAddress::Int64ToFloat32:
         return "call to native f32.convert_s/i64 (in wasm)";
--- a/js/src/wasm/WasmIonCompile.cpp
+++ b/js/src/wasm/WasmIonCompile.cpp
@@ -720,21 +720,21 @@ class FunctionCompiler
         if (inDeadCode())
             return nullptr;
         auto* ins = MRotate::New(alloc(), input, count, type, left);
         curBlock_->add(ins);
         return ins;
     }
 
     template <class T>
-    MDefinition* truncate(MDefinition* op, bool isUnsigned)
+    MDefinition* truncate(MDefinition* op, TruncFlags flags)
     {
         if (inDeadCode())
             return nullptr;
-        auto* ins = T::New(alloc(), op, isUnsigned, bytecodeOffset());
+        auto* ins = T::New(alloc(), op, flags, bytecodeOffset());
         curBlock_->add(ins);
         return ins;
     }
 
     MDefinition* compare(MDefinition* lhs, MDefinition* rhs, JSOp op, MCompare::CompareType type)
     {
         if (inDeadCode())
             return nullptr;
@@ -2408,31 +2408,36 @@ EmitConversionWithType(FunctionCompiler&
         return false;
 
     f.iter().setResult(f.unary<MIRClass>(input, mirType));
     return true;
 }
 
 static bool
 EmitTruncate(FunctionCompiler& f, ValType operandType, ValType resultType,
-             bool isUnsigned)
+             bool isUnsigned, bool isSaturating)
 {
     MDefinition* input;
     if (!f.iter().readConversion(operandType, resultType, &input))
         return false;
 
+    TruncFlags flags = 0;
+    if (isUnsigned)
+        flags |= TRUNC_UNSIGNED;
+    if (isSaturating)
+        flags |= TRUNC_SATURATING;
     if (resultType == ValType::I32) {
         if (f.env().isAsmJS())
             f.iter().setResult(f.unary<MTruncateToInt32>(input));
         else
-            f.iter().setResult(f.truncate<MWasmTruncateToInt32>(input, isUnsigned));
+            f.iter().setResult(f.truncate<MWasmTruncateToInt32>(input, flags));
     } else {
         MOZ_ASSERT(resultType == ValType::I64);
         MOZ_ASSERT(!f.env().isAsmJS());
-        f.iter().setResult(f.truncate<MWasmTruncateToInt64>(input, isUnsigned));
+        f.iter().setResult(f.truncate<MWasmTruncateToInt64>(input, flags));
     }
     return true;
 }
 
 #ifdef ENABLE_WASM_SIGNEXTEND_OPS
 static bool
 EmitSignExtend(FunctionCompiler& f, uint32_t srcSize, uint32_t targetSize)
 {
@@ -3919,29 +3924,29 @@ EmitBodyExprs(FunctionCompiler& f)
           case uint16_t(Op::F64CopySign):
             CHECK(EmitCopySign(f, ValType::F64));
 
           // Conversions
           case uint16_t(Op::I32WrapI64):
             CHECK(EmitConversion<MWrapInt64ToInt32>(f, ValType::I64, ValType::I32));
           case uint16_t(Op::I32TruncSF32):
           case uint16_t(Op::I32TruncUF32):
-            CHECK(EmitTruncate(f, ValType::F32, ValType::I32, Op(op.b0) == Op::I32TruncUF32));
+            CHECK(EmitTruncate(f, ValType::F32, ValType::I32, Op(op.b0) == Op::I32TruncUF32, false));
           case uint16_t(Op::I32TruncSF64):
           case uint16_t(Op::I32TruncUF64):
-            CHECK(EmitTruncate(f, ValType::F64, ValType::I32, Op(op.b0) == Op::I32TruncUF64));
+            CHECK(EmitTruncate(f, ValType::F64, ValType::I32, Op(op.b0) == Op::I32TruncUF64, false));
           case uint16_t(Op::I64ExtendSI32):
           case uint16_t(Op::I64ExtendUI32):
             CHECK(EmitExtendI32(f, Op(op.b0) == Op::I64ExtendUI32));
           case uint16_t(Op::I64TruncSF32):
           case uint16_t(Op::I64TruncUF32):
-            CHECK(EmitTruncate(f, ValType::F32, ValType::I64, Op(op.b0) == Op::I64TruncUF32));
+            CHECK(EmitTruncate(f, ValType::F32, ValType::I64, Op(op.b0) == Op::I64TruncUF32, false));
           case uint16_t(Op::I64TruncSF64):
           case uint16_t(Op::I64TruncUF64):
-            CHECK(EmitTruncate(f, ValType::F64, ValType::I64, Op(op.b0) == Op::I64TruncUF64));
+            CHECK(EmitTruncate(f, ValType::F64, ValType::I64, Op(op.b0) == Op::I64TruncUF64, false));
           case uint16_t(Op::F32ConvertSI32):
             CHECK(EmitConversion<MToFloat32>(f, ValType::I32, ValType::F32));
           case uint16_t(Op::F32ConvertUI32):
             CHECK(EmitConversion<MWasmUnsignedToFloat32>(f, ValType::I32, ValType::F32));
           case uint16_t(Op::F32ConvertSI64):
           case uint16_t(Op::F32ConvertUI64):
             CHECK(EmitConvertI64ToFloatingPoint(f, ValType::F32, MIRType::Float32, Op(op.b0) == Op::F32ConvertUI64));
           case uint16_t(Op::F32DemoteF64):
@@ -3975,16 +3980,45 @@ EmitBodyExprs(FunctionCompiler& f)
           case uint16_t(Op::I64Extend8S):
             CHECK(EmitSignExtend(f, 1, 8));
           case uint16_t(Op::I64Extend16S):
             CHECK(EmitSignExtend(f, 2, 8));
           case uint16_t(Op::I64Extend32S):
             CHECK(EmitSignExtend(f, 4, 8));
 #endif
 
+          // Numeric operations
+          case uint16_t(Op::NumericPrefix): {
+#ifdef ENABLE_WASM_SATURATING_TRUNC_OPS
+            switch (op.b1) {
+              case uint16_t(NumericOp::I32TruncSSatF32):
+              case uint16_t(NumericOp::I32TruncUSatF32):
+                CHECK(EmitTruncate(f, ValType::F32, ValType::I32,
+                                   NumericOp(op.b1) == NumericOp::I32TruncUSatF32, true));
+              case uint16_t(NumericOp::I32TruncSSatF64):
+              case uint16_t(NumericOp::I32TruncUSatF64):
+                CHECK(EmitTruncate(f, ValType::F64, ValType::I32,
+                                   NumericOp(op.b1) == NumericOp::I32TruncUSatF64, true));
+              case uint16_t(NumericOp::I64TruncSSatF32):
+              case uint16_t(NumericOp::I64TruncUSatF32):
+                CHECK(EmitTruncate(f, ValType::F32, ValType::I64,
+                                   NumericOp(op.b1) == NumericOp::I64TruncUSatF32, true));
+              case uint16_t(NumericOp::I64TruncSSatF64):
+              case uint16_t(NumericOp::I64TruncUSatF64):
+                CHECK(EmitTruncate(f, ValType::F64, ValType::I64,
+                                   NumericOp(op.b1) == NumericOp::I64TruncUSatF64, true));
+              default:
+                return f.iter().unrecognizedOpcode(&op);
+            }
+            break;
+#else
+            return f.iter().unrecognizedOpcode(&op);
+#endif
+          }
+
           // Thread operations
           case uint16_t(Op::ThreadPrefix): {
 #ifdef ENABLE_WASM_THREAD_OPS
             switch (op.b1) {
               case uint16_t(ThreadOp::Wake):
                 CHECK(EmitWake(f));
 
               case uint16_t(ThreadOp::I32Wait):
--- a/js/src/wasm/WasmStubs.cpp
+++ b/js/src/wasm/WasmStubs.cpp
@@ -1724,25 +1724,25 @@ wasm::GenerateStubs(const ModuleEnvironm
     JitSpew(JitSpew_Codegen, "# Emitting wasm trap stubs");
 
     for (Trap trap : MakeEnumeratedRange(Trap::Limit)) {
         switch (trap) {
           case Trap::Unreachable:
           case Trap::IntegerOverflow:
           case Trap::InvalidConversionToInteger:
           case Trap::IntegerDivideByZero:
+          case Trap::IndirectCallToNull:
+          case Trap::ImpreciseSimdConversion:
           case Trap::StackOverflow:
+          case Trap::ThrowReported:
             break;
           // The TODO list of "old" traps to convert to new traps:
           case Trap::OutOfBounds:
           case Trap::UnalignedAccess:
-          case Trap::IndirectCallToNull:
-          case Trap::IndirectCallBadSig:
-          case Trap::ImpreciseSimdConversion:
-          case Trap::ThrowReported: {
+          case Trap::IndirectCallBadSig: {
             CallableOffsets offsets;
             if (!GenerateOldTrapExit(masm, trap, &throwLabel, &offsets))
                 return false;
             if (!code->codeRanges.emplaceBack(trap, offsets))
                 return false;
             break;
           }
           case Trap::Limit:
--- a/js/src/wasm/WasmTextToBinary.cpp
+++ b/js/src/wasm/WasmTextToBinary.cpp
@@ -87,16 +87,17 @@ class WasmToken
         Drop,
         Elem,
         Else,
         End,
         EndOfFile,
         Equal,
         Error,
         Export,
+        ExtraConversionOpcode,
         Float,
         Func,
         GetGlobal,
         GetLocal,
         Global,
         GrowMemory,
         If,
         Import,
@@ -141,16 +142,17 @@ class WasmToken
     const char16_t* end_;
     union {
         uint32_t index_;
         uint64_t uint_;
         int64_t sint_;
         FloatLiteralKind floatLiteralKind_;
         ValType valueType_;
         Op op_;
+        NumericOp numericOp_;
         ThreadOp threadOp_;
     } u;
   public:
     WasmToken()
       : kind_(Kind::Invalid),
         begin_(nullptr),
         end_(nullptr),
         u()
@@ -212,16 +214,25 @@ class WasmToken
         end_(end)
     {
         MOZ_ASSERT(begin != end);
         MOZ_ASSERT(kind_ == UnaryOpcode || kind_ == BinaryOpcode || kind_ == TernaryOpcode ||
                    kind_ == ComparisonOpcode || kind_ == ConversionOpcode ||
                    kind_ == Load || kind_ == Store);
         u.op_ = op;
     }
+    explicit WasmToken(Kind kind, NumericOp op, const char16_t* begin, const char16_t* end)
+      : kind_(kind),
+        begin_(begin),
+        end_(end)
+    {
+        MOZ_ASSERT(begin != end);
+        MOZ_ASSERT(kind_ == ExtraConversionOpcode);
+        u.numericOp_ = op;
+    }
     explicit WasmToken(Kind kind, ThreadOp op, const char16_t* begin, const char16_t* end)
       : kind_(kind),
         begin_(begin),
         end_(end)
     {
         MOZ_ASSERT(begin != end);
         MOZ_ASSERT(kind_ == AtomicCmpXchg || kind_ == AtomicLoad || kind_ == AtomicRMW ||
                    kind_ == AtomicStore || kind_ == Wait || kind_ == Wake);
@@ -273,16 +284,20 @@ class WasmToken
         return u.valueType_;
     }
     Op op() const {
         MOZ_ASSERT(kind_ == UnaryOpcode || kind_ == BinaryOpcode || kind_ == TernaryOpcode ||
                    kind_ == ComparisonOpcode || kind_ == ConversionOpcode ||
                    kind_ == Load || kind_ == Store);
         return u.op_;
     }
+    NumericOp numericOp() const {
+        MOZ_ASSERT(kind_ == ExtraConversionOpcode);
+        return u.numericOp_;
+    }
     ThreadOp threadOp() const {
         MOZ_ASSERT(kind_ == AtomicCmpXchg || kind_ == AtomicLoad || kind_ == AtomicRMW ||
                    kind_ == AtomicStore || kind_ == Wait || kind_ == Wake);
         return u.threadOp_;
     }
     bool isOpcode() const {
         switch (kind_) {
           case AtomicCmpXchg:
@@ -294,16 +309,19 @@ class WasmToken
           case Br:
           case BrIf:
           case BrTable:
           case Call:
           case CallIndirect:
           case ComparisonOpcode:
           case Const:
           case ConversionOpcode:
+#ifdef ENABLE_WASM_SATURATING_TRUNC_OPS
+          case ExtraConversionOpcode:
+#endif
           case CurrentMemory:
           case Drop:
           case GetGlobal:
           case GetLocal:
           case GrowMemory:
           case If:
           case Load:
           case Loop:
@@ -1317,16 +1335,30 @@ WasmTokenStream::next()
                     return WasmToken(WasmToken::ConversionOpcode, Op::I32TruncSF64,
                                      begin, cur_);
                 if (consume(u"trunc_u/f32"))
                     return WasmToken(WasmToken::ConversionOpcode, Op::I32TruncUF32,
                                      begin, cur_);
                 if (consume(u"trunc_u/f64"))
                     return WasmToken(WasmToken::ConversionOpcode, Op::I32TruncUF64,
                                      begin, cur_);
+#ifdef ENABLE_WASM_SATURATING_TRUNC_OPS
+                if (consume(u"trunc_s:sat/f32"))
+                    return WasmToken(WasmToken::ExtraConversionOpcode, NumericOp::I32TruncSSatF32,
+                                     begin, cur_);
+                if (consume(u"trunc_s:sat/f64"))
+                    return WasmToken(WasmToken::ExtraConversionOpcode, NumericOp::I32TruncSSatF64,
+                                     begin, cur_);
+                if (consume(u"trunc_u:sat/f32"))
+                    return WasmToken(WasmToken::ExtraConversionOpcode, NumericOp::I32TruncUSatF32,
+                                     begin, cur_);
+                if (consume(u"trunc_u:sat/f64"))
+                    return WasmToken(WasmToken::ExtraConversionOpcode, NumericOp::I32TruncUSatF64,
+                                     begin, cur_);
+#endif
                 break;
               case 'w':
                 if (consume(u"wrap/i64"))
                     return WasmToken(WasmToken::ConversionOpcode, Op::I32WrapI64,
                                      begin, cur_);
                 break;
               case 'x':
                 if (consume(u"xor"))
@@ -1553,16 +1585,30 @@ WasmTokenStream::next()
                     return WasmToken(WasmToken::ConversionOpcode, Op::I64TruncSF64,
                                      begin, cur_);
                 if (consume(u"trunc_u/f32"))
                     return WasmToken(WasmToken::ConversionOpcode, Op::I64TruncUF32,
                                      begin, cur_);
                 if (consume(u"trunc_u/f64"))
                     return WasmToken(WasmToken::ConversionOpcode, Op::I64TruncUF64,
                                      begin, cur_);
+#ifdef ENABLE_WASM_SATURATING_TRUNC_OPS
+                if (consume(u"trunc_s:sat/f32"))
+                    return WasmToken(WasmToken::ExtraConversionOpcode, NumericOp::I64TruncSSatF32,
+                                     begin, cur_);
+                if (consume(u"trunc_s:sat/f64"))
+                    return WasmToken(WasmToken::ExtraConversionOpcode, NumericOp::I64TruncSSatF64,
+                                     begin, cur_);
+                if (consume(u"trunc_u:sat/f32"))
+                    return WasmToken(WasmToken::ExtraConversionOpcode, NumericOp::I64TruncUSatF32,
+                                     begin, cur_);
+                if (consume(u"trunc_u:sat/f64"))
+                    return WasmToken(WasmToken::ExtraConversionOpcode, NumericOp::I64TruncUSatF64,
+                                     begin, cur_);
+#endif
                 break;
               case 'w':
                 break;
               case 'x':
                 if (consume(u"xor"))
                     return WasmToken(WasmToken::BinaryOpcode, Op::I64Xor, begin, cur_);
                 break;
             }
@@ -2365,16 +2411,28 @@ ParseConversionOperator(WasmParseContext
 {
     AstExpr* operand = ParseExpr(c, inParens);
     if (!operand)
         return nullptr;
 
     return new(c.lifo) AstConversionOperator(op, operand);
 }
 
+#ifdef ENABLE_WASM_SATURATING_TRUNC_OPS
+static AstExtraConversionOperator*
+ParseExtraConversionOperator(WasmParseContext& c, NumericOp op, bool inParens)
+{
+    AstExpr* operand = ParseExpr(c, inParens);
+    if (!operand)
+        return nullptr;
+
+    return new(c.lifo) AstExtraConversionOperator(op, operand);
+}
+#endif
+
 static AstDrop*
 ParseDrop(WasmParseContext& c, bool inParens)
 {
     AstExpr* value = ParseExpr(c, inParens);
     if (!value)
         return nullptr;
 
     return new(c.lifo) AstDrop(*value);
@@ -2904,16 +2962,20 @@ ParseExprBody(WasmParseContext& c, WasmT
       case WasmToken::CallIndirect:
         return ParseCallIndirect(c, inParens);
       case WasmToken::ComparisonOpcode:
         return ParseComparisonOperator(c, token.op(), inParens);
       case WasmToken::Const:
         return ParseConst(c, token);
       case WasmToken::ConversionOpcode:
         return ParseConversionOperator(c, token.op(), inParens);
+#ifdef ENABLE_WASM_SATURATING_TRUNC_OPS
+      case WasmToken::ExtraConversionOpcode:
+        return ParseExtraConversionOperator(c, token.numericOp(), inParens);
+#endif
       case WasmToken::Drop:
         return ParseDrop(c, inParens);
       case WasmToken::If:
         return ParseIf(c, inParens);
       case WasmToken::GetGlobal:
         return ParseGetGlobal(c);
       case WasmToken::GetLocal:
         return ParseGetLocal(c);
@@ -4135,16 +4197,24 @@ ResolveComparisonOperator(Resolver& r, A
 }
 
 static bool
 ResolveConversionOperator(Resolver& r, AstConversionOperator& b)
 {
     return ResolveExpr(r, *b.operand());
 }
 
+#ifdef ENABLE_WASM_SATURATING_TRUNC_OPS
+static bool
+ResolveExtraConversionOperator(Resolver& r, AstExtraConversionOperator& b)
+{
+    return ResolveExpr(r, *b.operand());
+}
+#endif
+
 static bool
 ResolveIfElse(Resolver& r, AstIf& i)
 {
     if (!ResolveExpr(r, i.cond()))
         return false;
     if (!r.pushTarget(i.name()))
         return false;
     if (!ResolveExprList(r, i.thenExprs()))
@@ -4264,16 +4334,20 @@ ResolveExpr(Resolver& r, AstExpr& expr)
       case AstExprKind::CallIndirect:
         return ResolveCallIndirect(r, expr.as<AstCallIndirect>());
       case AstExprKind::ComparisonOperator:
         return ResolveComparisonOperator(r, expr.as<AstComparisonOperator>());
       case AstExprKind::Const:
         return true;
       case AstExprKind::ConversionOperator:
         return ResolveConversionOperator(r, expr.as<AstConversionOperator>());
+#ifdef ENABLE_WASM_SATURATING_TRUNC_OPS
+      case AstExprKind::ExtraConversionOperator:
+        return ResolveExtraConversionOperator(r, expr.as<AstExtraConversionOperator>());
+#endif
       case AstExprKind::First:
         return ResolveFirst(r, expr.as<AstFirst>());
       case AstExprKind::GetGlobal:
         return ResolveGetGlobal(r, expr.as<AstGetGlobal>());
       case AstExprKind::GetLocal:
         return ResolveGetLocal(r, expr.as<AstGetLocal>());
       case AstExprKind::If:
         return ResolveIfElse(r, expr.as<AstIf>());
@@ -4660,16 +4734,25 @@ EncodeComparisonOperator(Encoder& e, Ast
 
 static bool
 EncodeConversionOperator(Encoder& e, AstConversionOperator& b)
 {
     return EncodeExpr(e, *b.operand()) &&
            e.writeOp(b.op());
 }
 
+#ifdef ENABLE_WASM_SATURATING_TRUNC_OPS
+static bool
+EncodeExtraConversionOperator(Encoder& e, AstExtraConversionOperator& b)
+{
+    return EncodeExpr(e, *b.operand()) &&
+           e.writeOp(b.op());
+}
+#endif
+
 static bool
 EncodeIf(Encoder& e, AstIf& i)
 {
     if (!EncodeExpr(e, i.cond()) || !e.writeOp(Op::If))
         return false;
 
     if (!e.writeBlockType(i.type()))
         return false;
@@ -4864,16 +4947,20 @@ EncodeExpr(Encoder& e, AstExpr& expr)
       case AstExprKind::ComparisonOperator:
         return EncodeComparisonOperator(e, expr.as<AstComparisonOperator>());
       case AstExprKind::Const:
         return EncodeConst(e, expr.as<AstConst>());
       case AstExprKind::ConversionOperator:
         return EncodeConversionOperator(e, expr.as<AstConversionOperator>());
       case AstExprKind::Drop:
         return EncodeDrop(e, expr.as<AstDrop>());
+#ifdef ENABLE_WASM_SATURATING_TRUNC_OPS
+      case AstExprKind::ExtraConversionOperator:
+        return EncodeExtraConversionOperator(e, expr.as<AstExtraConversionOperator>());
+#endif
       case AstExprKind::First:
         return EncodeFirst(e, expr.as<AstFirst>());
       case AstExprKind::GetLocal:
         return EncodeGetLocal(e, expr.as<AstGetLocal>());
       case AstExprKind::GetGlobal:
         return EncodeGetGlobal(e, expr.as<AstGetGlobal>());
       case AstExprKind::If:
         return EncodeIf(e, expr.as<AstIf>());
--- a/js/src/wasm/WasmTypes.h
+++ b/js/src/wasm/WasmTypes.h
@@ -1389,16 +1389,18 @@ enum class SymbolicAddress
     CoerceInPlace_ToNumber,
     CoerceInPlace_JitEntry,
     DivI64,
     UDivI64,
     ModI64,
     UModI64,
     TruncateDoubleToInt64,
     TruncateDoubleToUint64,
+    SaturatingTruncateDoubleToInt64,
+    SaturatingTruncateDoubleToUint64,
     Uint64ToFloat32,
     Uint64ToDouble,
     Int64ToFloat32,
     Int64ToDouble,
     GrowMemory,
     CurrentMemory,
     WaitI32,
     WaitI64,
--- a/js/src/wasm/WasmValidate.cpp
+++ b/js/src/wasm/WasmValidate.cpp
@@ -715,16 +715,39 @@ DecodeFunctionBodyExprs(const ModuleEnvi
             uint32_t unusedDefault;
             ExprType unusedType;
             CHECK(iter.readBrTable(&unusedDepths, &unusedDefault, &unusedType, &nothing, &nothing));
           }
           case uint16_t(Op::Return):
             CHECK(iter.readReturn(&nothing));
           case uint16_t(Op::Unreachable):
             CHECK(iter.readUnreachable());
+          case uint16_t(Op::NumericPrefix): {
+#ifdef ENABLE_WASM_SATURATING_TRUNC_OPS
+            switch (op.b1) {
+              case uint16_t(NumericOp::I32TruncSSatF32):
+              case uint16_t(NumericOp::I32TruncUSatF32):
+                CHECK(iter.readConversion(ValType::F32, ValType::I32, &nothing));
+              case uint16_t(NumericOp::I32TruncSSatF64):
+              case uint16_t(NumericOp::I32TruncUSatF64):
+                CHECK(iter.readConversion(ValType::F64, ValType::I32, &nothing));
+              case uint16_t(NumericOp::I64TruncSSatF32):
+              case uint16_t(NumericOp::I64TruncUSatF32):
+                CHECK(iter.readConversion(ValType::F32, ValType::I64, &nothing));
+              case uint16_t(NumericOp::I64TruncSSatF64):
+              case uint16_t(NumericOp::I64TruncUSatF64):
+                CHECK(iter.readConversion(ValType::F64, ValType::I64, &nothing));
+              default:
+                return iter.unrecognizedOpcode(&op);
+            }
+            break;
+#else
+            return iter.unrecognizedOpcode(&op);
+#endif
+          }
           case uint16_t(Op::ThreadPrefix): {
 #ifdef ENABLE_WASM_THREAD_OPS
             switch (op.b1) {
               case uint16_t(ThreadOp::Wake): {
                 LinearMemoryAddress<Nothing> addr;
                 CHECK(iter.readWake(&addr, &nothing));
               }
               case uint16_t(ThreadOp::I32Wait): {
--- a/js/src/wasm/WasmValidate.h
+++ b/js/src/wasm/WasmValidate.h
@@ -279,16 +279,22 @@ class Encoder
         return writeFixedU8(uint8_t(op));
     }
     MOZ_MUST_USE bool writeOp(MozOp op) {
         static_assert(size_t(MozOp::Limit) <= 256, "fits");
         MOZ_ASSERT(size_t(op) < size_t(MozOp::Limit));
         return writeFixedU8(uint8_t(Op::MozPrefix)) &&
                writeFixedU8(uint8_t(op));
     }
+    MOZ_MUST_USE bool writeOp(NumericOp op) {
+        static_assert(size_t(NumericOp::Limit) <= 256, "fits");
+        MOZ_ASSERT(size_t(op) < size_t(NumericOp::Limit));
+        return writeFixedU8(uint8_t(Op::NumericPrefix)) &&
+               writeFixedU8(uint8_t(op));
+    }
     MOZ_MUST_USE bool writeOp(ThreadOp op) {
         static_assert(size_t(ThreadOp::Limit) <= 256, "fits");
         MOZ_ASSERT(size_t(op) < size_t(ThreadOp::Limit));
         return writeFixedU8(uint8_t(Op::ThreadPrefix)) &&
                writeFixedU8(uint8_t(op));
     }
 
     // Fixed-length encodings that allow back-patching.
--- a/layout/base/nsLayoutUtils.cpp
+++ b/layout/base/nsLayoutUtils.cpp
@@ -8030,85 +8030,97 @@ nsLayoutUtils::AssertTreeOnlyEmptyNextIn
       nsLayoutUtils::AssertTreeOnlyEmptyNextInFlows(childFrames.get());
     }
   }
 }
 #endif
 
 static void
 GetFontFacesForFramesInner(nsIFrame* aFrame,
-                           nsLayoutUtils::UsedFontFaceTable& aFontFaces)
+                           nsLayoutUtils::UsedFontFaceTable& aFontFaces,
+                           uint32_t aMaxRanges)
 {
   NS_PRECONDITION(aFrame, "NULL frame pointer");
 
   if (aFrame->IsTextFrame()) {
     if (!aFrame->GetPrevContinuation()) {
       nsLayoutUtils::GetFontFacesForText(aFrame, 0, INT32_MAX, true,
-                                         aFontFaces);
+                                         aFontFaces, aMaxRanges);
     }
     return;
   }
 
   nsIFrame::ChildListID childLists[] = { nsIFrame::kPrincipalList,
                                          nsIFrame::kPopupList };
   for (size_t i = 0; i < ArrayLength(childLists); ++i) {
     nsFrameList children(aFrame->GetChildList(childLists[i]));
     for (nsFrameList::Enumerator e(children); !e.AtEnd(); e.Next()) {
       nsIFrame* child = e.get();
       child = nsPlaceholderFrame::GetRealFrameFor(child);
-      GetFontFacesForFramesInner(child, aFontFaces);
+      GetFontFacesForFramesInner(child, aFontFaces, aMaxRanges);
     }
   }
 }
 
 /* static */ nsresult
 nsLayoutUtils::GetFontFacesForFrames(nsIFrame* aFrame,
-                                     UsedFontFaceTable& aFontFaces)
+                                     UsedFontFaceTable& aFontFaces,
+                                     uint32_t aMaxRanges)
 {
   NS_PRECONDITION(aFrame, "NULL frame pointer");
 
   while (aFrame) {
-    GetFontFacesForFramesInner(aFrame, aFontFaces);
+    GetFontFacesForFramesInner(aFrame, aFontFaces, aMaxRanges);
     aFrame = GetNextContinuationOrIBSplitSibling(aFrame);
   }
 
   return NS_OK;
 }
 
 static void
 AddFontsFromTextRun(gfxTextRun* aTextRun,
+                    nsIContent* aContent,
+                    gfxSkipCharsIterator& aSkipIter,
                     uint32_t aOffset,
                     uint32_t aLength,
-                    nsLayoutUtils::UsedFontFaceTable& aFontFaces)
+                    nsLayoutUtils::UsedFontFaceTable& aFontFaces,
+                    uint32_t aMaxRanges)
 {
   gfxTextRun::Range range(aOffset, aOffset + aLength);
   gfxTextRun::GlyphRunIterator iter(aTextRun, range);
   while (iter.NextRun()) {
     gfxFontEntry *fe = iter.GetGlyphRun()->mFont->GetFontEntry();
     // if we have already listed this face, just make sure the match type is
     // recorded
-    InspectorFontFace* existingFace = aFontFaces.Get(fe);
-    if (existingFace) {
-      existingFace->AddMatchType(iter.GetGlyphRun()->mMatchType);
+    InspectorFontFace* fontFace = aFontFaces.Get(fe);
+    if (fontFace) {
+      fontFace->AddMatchType(iter.GetGlyphRun()->mMatchType);
     } else {
       // A new font entry we haven't seen before
-      InspectorFontFace* ff =
-        new InspectorFontFace(fe, aTextRun->GetFontGroup(),
-                              iter.GetGlyphRun()->mMatchType);
-      aFontFaces.Put(fe, ff);
+      fontFace = new InspectorFontFace(fe, aTextRun->GetFontGroup(),
+                                       iter.GetGlyphRun()->mMatchType);
+      aFontFaces.Put(fe, fontFace);
+    }
+    if (fontFace->RangeCount() < aMaxRanges) {
+      uint32_t start = aSkipIter.ConvertSkippedToOriginal(iter.GetStringStart());
+      uint32_t end = aSkipIter.ConvertSkippedToOriginal(iter.GetStringEnd());
+      RefPtr<nsRange> range;
+      nsRange::CreateRange(aContent, start, aContent, end, getter_AddRefs(range));
+      fontFace->AddRange(range);
     }
   }
 }
 
 /* static */ nsresult
 nsLayoutUtils::GetFontFacesForText(nsIFrame* aFrame,
                                    int32_t aStartOffset,
                                    int32_t aEndOffset,
                                    bool aFollowContinuations,
-                                   UsedFontFaceTable& aFontFaces)
+                                   UsedFontFaceTable& aFontFaces,
+                                   uint32_t aMaxRanges)
 {
   NS_PRECONDITION(aFrame, "NULL frame pointer");
 
   if (!aFrame->IsTextFrame()) {
     return NS_OK;
   }
 
   nsTextFrame* curr = static_cast<nsTextFrame*>(aFrame);
@@ -8133,17 +8145,18 @@ nsLayoutUtils::GetFontFacesForText(nsIFr
         fend = std::min(next->GetContentEnd(), aEndOffset);
         next = fend < aEndOffset ?
           static_cast<nsTextFrame*>(next->GetNextContinuation()) : nullptr;
       }
     }
 
     uint32_t skipStart = iter.ConvertOriginalToSkipped(fstart);
     uint32_t skipEnd = iter.ConvertOriginalToSkipped(fend);
-    AddFontsFromTextRun(textRun, skipStart, skipEnd - skipStart, aFontFaces);
+    AddFontsFromTextRun(textRun, aFrame->GetContent(), iter,
+                        skipStart, skipEnd - skipStart, aFontFaces, aMaxRanges);
     curr = next;
   } while (aFollowContinuations && curr);
 
   return NS_OK;
 }
 
 /* static */
 size_t
--- a/layout/base/nsLayoutUtils.h
+++ b/layout/base/nsLayoutUtils.h
@@ -2273,31 +2273,35 @@ public:
   static bool NeedsPrintPreviewBackground(nsPresContext* aPresContext);
 
   typedef nsClassHashtable<nsPtrHashKey<gfxFontEntry>,
                            mozilla::dom::InspectorFontFace> UsedFontFaceTable;
 
   /**
    * Adds all font faces used in the frame tree starting from aFrame
    * to the list aFontFaceList.
+   * aMaxRanges: maximum number of text ranges to record for each face.
    */
   static nsresult GetFontFacesForFrames(nsIFrame* aFrame,
-                                        UsedFontFaceTable& aResult);
+                                        UsedFontFaceTable& aResult,
+                                        uint32_t aMaxRanges);
 
   /**
    * Adds all font faces used within the specified range of text in aFrame,
    * and optionally its continuations, to the list in aFontFaceList.
    * Pass 0 and INT32_MAX for aStartOffset and aEndOffset to specify the
    * entire text is to be considered.
+   * aMaxRanges: maximum number of text ranges to record for each face.
    */
   static nsresult GetFontFacesForText(nsIFrame* aFrame,
                                       int32_t aStartOffset,
                                       int32_t aEndOffset,
                                       bool aFollowContinuations,
-                                      UsedFontFaceTable& aResult);
+                                      UsedFontFaceTable& aResult,
+                                      uint32_t aMaxRanges);
 
   /**
    * Walks the frame tree starting at aFrame looking for textRuns.
    * If |clear| is true, just clears the TEXT_RUN_MEMORY_ACCOUNTED flag
    * on each textRun found (and |aMallocSizeOf| is not used).
    * If |clear| is false, adds the storage used for each textRun to the
    * total, and sets the TEXT_RUN_MEMORY_ACCOUNTED flag to avoid double-
    * accounting. (Runs with this flag already set will be skipped.)
new file mode 100644
--- /dev/null
+++ b/layout/forms/crashtests/1432853.html
@@ -0,0 +1,8 @@
+<style>
+.c1 { height: 10ch; position: fixed }
+#a { column-width: 0em }
+.c2 { -webkit-transform-style: preserve-3d }
+</style>
+<details id="a" open="">
+<h4 class="c2">
+<select class="c1">
--- a/layout/forms/crashtests/crashtests.list
+++ b/layout/forms/crashtests/crashtests.list
@@ -66,8 +66,9 @@ load 1140216.html
 load 1182414.html
 load 1212688.html
 load 1228670.xhtml
 load 1279354.html
 load 1388230-1.html
 load 1388230-2.html
 load 1405830.html
 load 1418477.html
+load 1432853.html
--- a/layout/generic/nsAbsoluteContainingBlock.cpp
+++ b/layout/generic/nsAbsoluteContainingBlock.cpp
@@ -162,27 +162,21 @@ nsAbsoluteContainingBlock::Reflow(nsCont
         }
       }
     }
     if (kidNeedsReflow && !aPresContext->HasPendingInterrupt()) {
       // Reflow the frame
       nsReflowStatus kidStatus;
       ReflowAbsoluteFrame(aDelegatingFrame, aPresContext, aReflowInput, cb,
                           aFlags, kidFrame, kidStatus, aOverflowAreas);
+      MOZ_ASSERT(!kidStatus.IsInlineBreakBefore(),
+                 "ShouldAvoidBreakInside should prevent this from happening");
       nsIFrame* nextFrame = kidFrame->GetNextInFlow();
-      if ((kidStatus.IsInlineBreakBefore() ||
-           !kidStatus.IsFullyComplete()) &&
+      if (!kidStatus.IsFullyComplete() &&
           aDelegatingFrame->IsFrameOfType(nsIFrame::eCanContainOverflowContainers)) {
-        // XXX it's unclear how we should handle 'page-break-inside:avoid' on
-        // abs.pos. boxes -- ignore it for now by setting the status to
-        // Incomplete (which will probably fragment it).
-        if (kidStatus.IsInlineBreakBefore()) {
-          kidStatus.Reset();
-          kidStatus.SetIncomplete();
-        }
         // Need a continuation
         if (!nextFrame) {
           nextFrame =
             aPresContext->PresShell()->FrameConstructor()->
               CreateContinuingFrame(aPresContext, kidFrame, aDelegatingFrame);
         }
         // Add it as an overflow container.
         //XXXfr This is a hack to fix some of our printing dataloss.
--- a/layout/generic/nsFrame.cpp
+++ b/layout/generic/nsFrame.cpp
@@ -9712,16 +9712,26 @@ nsIFrame::GetDepthInFrameTree() const
 void
 nsFrame::ConsiderChildOverflow(nsOverflowAreas& aOverflowAreas,
                                nsIFrame* aChildFrame)
 {
   aOverflowAreas.UnionWith(aChildFrame->GetOverflowAreas() +
                            aChildFrame->GetPosition());
 }
 
+bool
+nsFrame::ShouldAvoidBreakInside(const ReflowInput& aReflowInput) const
+{
+  const auto* disp = StyleDisplay();
+  return !aReflowInput.mFlags.mIsTopOfPage &&
+    NS_STYLE_PAGE_BREAK_AVOID == disp->mBreakInside &&
+    !(HasAnyStateBits(NS_FRAME_OUT_OF_FLOW) && IsAbsolutelyPositioned(disp)) &&
+    !GetPrevInFlow();
+}
+
 /**
  * This function takes a frame that is part of a block-in-inline split,
  * and _if_ that frame is an anonymous block created by an ib split it
  * returns the block's preceding inline.  This is needed because the
  * split inline's style context is the parent of the anonymous block's
  * style context.
  *
  * If aFrame is not an anonymous block, null is returned.
--- a/layout/generic/nsFrame.h
+++ b/layout/generic/nsFrame.h
@@ -468,21 +468,17 @@ public:
   // Incorporate the child overflow areas into aOverflowAreas.
   // If the child does not have a overflow, use the child area.
   void ConsiderChildOverflow(nsOverflowAreas& aOverflowAreas,
                              nsIFrame* aChildFrame);
 
   /**
    * @return true if we should avoid a page/column break in this frame.
    */
-  bool ShouldAvoidBreakInside(const ReflowInput& aReflowInput) const {
-    return !aReflowInput.mFlags.mIsTopOfPage &&
-           NS_STYLE_PAGE_BREAK_AVOID == StyleDisplay()->mBreakInside &&
-           !GetPrevInFlow();
-  }
+  bool ShouldAvoidBreakInside(const ReflowInput& aReflowInput) const;
 
 #ifdef DEBUG
   /**
    * Tracing method that writes a method enter/exit routine to the
    * nspr log using the nsIFrame log module. The tracing is only
    * done when the NS_FRAME_TRACE_CALLS bit is set in the log module's
    * level field.
    */
--- a/layout/inspector/InspectorFontFace.cpp
+++ b/layout/inspector/InspectorFontFace.cpp
@@ -289,10 +289,22 @@ InspectorFontFace::GetFeatures(nsTArray<
   for (auto& f : features) {
     InspectorFontFeature& feat = *aResult.AppendElement();
     AppendTagAsASCII(feat.mTag, f.mTag);
     AppendTagAsASCII(feat.mScript, f.mScript);
     AppendTagAsASCII(feat.mLanguageSystem, f.mLangSys);
   }
 }
 
+void
+InspectorFontFace::GetRanges(nsTArray<RefPtr<nsRange>>& aResult)
+{
+  aResult = mRanges;
+}
+
+void
+InspectorFontFace::AddRange(nsRange* aRange)
+{
+  mRanges.AppendElement(aRange);
+}
+
 } // namespace dom
 } // namespace mozilla
--- a/layout/inspector/InspectorFontFace.h
+++ b/layout/inspector/InspectorFontFace.h
@@ -4,16 +4,17 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef mozilla_InspectorFontFace_h
 #define mozilla_InspectorFontFace_h
 
 #include "mozilla/dom/InspectorUtilsBinding.h"
 #include "mozilla/dom/NonRefcountedDOMObject.h"
+#include "nsRange.h"
 
 class gfxFontEntry;
 class gfxFontGroup;
 class nsCSSFontFaceRule;
 
 namespace mozilla {
 namespace dom {
 
@@ -37,16 +38,21 @@ public:
   ~InspectorFontFace()
   {
     MOZ_COUNT_DTOR(InspectorFontFace);
   }
 
   gfxFontEntry* GetFontEntry() const { return mFontEntry; }
   void AddMatchType(uint8_t aMatchType) { mMatchType |= aMatchType; }
 
+  void AddRange(nsRange* aRange);
+  size_t RangeCount() const {
+    return mRanges.Length();
+  }
+
   // Web IDL
   bool FromFontGroup();
   bool FromLanguagePrefs();
   bool FromSystemFallback();
   void GetName(nsAString& aName);
   void GetCSSFamilyName(nsAString& aCSSFamilyName);
   nsCSSFontFaceRule* GetRule();
   int32_t SrcIndex();
@@ -57,25 +63,29 @@ public:
 
   void GetVariationAxes(nsTArray<InspectorVariationAxis>& aResult,
                         ErrorResult& aRV);
   void GetVariationInstances(nsTArray<InspectorVariationInstance>& aResult,
                              ErrorResult& aRV);
   void GetFeatures(nsTArray<InspectorFontFeature>& aResult,
                    ErrorResult& aRV);
 
+  void GetRanges(nsTArray<RefPtr<nsRange>>& aResult);
+
   bool WrapObject(JSContext* aCx,
                   JS::Handle<JSObject*> aGivenProto,
                   JS::MutableHandle<JSObject*> aReflector)
   {
     return InspectorFontFaceBinding::Wrap(aCx, this, aGivenProto, aReflector);
   }
 
 protected:
   RefPtr<gfxFontEntry> mFontEntry;
   RefPtr<gfxFontGroup> mFontGroup;
   uint8_t mMatchType;
+
+  nsTArray<RefPtr<nsRange>> mRanges;
 };
 
 } // namespace dom
 } // namespace mozilla
 
 #endif // mozilla_InspectorFontFace_h
--- a/layout/inspector/InspectorUtils.cpp
+++ b/layout/inspector/InspectorUtils.cpp
@@ -1025,20 +1025,21 @@ InspectorUtils::GetCleanStyleContextForE
   RefPtr<nsStyleContext> styleContext =
     nsComputedDOMStyle::GetStyleContext(aElement, aPseudo, presShell);
   return styleContext.forget();
 }
 
 /* static */ void
 InspectorUtils::GetUsedFontFaces(GlobalObject& aGlobalObject,
                                  nsRange& aRange,
+                                 uint32_t aMaxRanges,
                                  nsTArray<nsAutoPtr<InspectorFontFace>>& aResult,
                                  ErrorResult& aRv)
 {
-  nsresult rv = aRange.GetUsedFontFaces(aResult);
+  nsresult rv = aRange.GetUsedFontFaces(aResult, aMaxRanges);
   if (NS_FAILED(rv)) {
     aRv.Throw(rv);
   }
 }
 
 static EventStates
 GetStatesForPseudoClass(const nsAString& aStatePseudo)
 {
--- a/layout/inspector/InspectorUtils.h
+++ b/layout/inspector/InspectorUtils.h
@@ -217,16 +217,18 @@ public:
                                  Element& aElement,
                                  uint64_t aState,
                                  bool aClearActiveDocument,
                                  ErrorResult& aRv);
   static uint64_t GetContentState(GlobalObject& aGlobal, Element& aElement);
 
   static void GetUsedFontFaces(GlobalObject& aGlobal,
                                nsRange& aRange,
+                               uint32_t aMaxRanges, // max number of ranges to
+                                                    // record for each face
                                nsTArray<nsAutoPtr<InspectorFontFace>>& aResult,
                                ErrorResult& aRv);
 
   /**
    * Get the names of all the supported pseudo-elements.
    * Pseudo-elements which are only accepted in UA style sheets are
    * not included.
    */
--- a/layout/inspector/tests/chrome/chrome.ini
+++ b/layout/inspector/tests/chrome/chrome.ini
@@ -12,16 +12,19 @@ support-files =
 support-files =
   test_bug708874.css
 [test_bug727834.xul]
 support-files =
   test_bug727834.css
 [test_parseStyleSheetObservers.html]
 support-files =
   imported_no_op.css
+[test_fontFaceRanges.xul]
+support-files =
+  test_fontFaceRanges.css
 [test_fontFeaturesAPI.xul]
 support-files =
   test_fontFeaturesAPI.css
   DejaVuSans.ttf
 [test_fontVariationsAPI.xul]
 skip-if = os == 'win' # bug 1433438
 support-files =
   test_fontVariationsAPI.css
new file mode 100644
--- /dev/null
+++ b/layout/inspector/tests/chrome/test_fontFaceRanges.css
@@ -0,0 +1,17 @@
+@font-face {
+  font-family: capitals;
+  src: url(GentiumPlus-R.woff) format("woff");
+  unicode-range: U+0041-005A;
+}
+@font-face {
+  font-family: lowercase;
+  src: url(GentiumPlus-R.woff) format("woff");
+  unicode-range: U+0061-007A;
+}
+@font-face {
+  font-family: gentium;
+  src: url(GentiumPlus-R.woff) format("woff");
+}
+.gentium {
+  font-family: capitals, lowercase, gentium, sans-serif;
+}
new file mode 100644
--- /dev/null
+++ b/layout/inspector/tests/chrome/test_fontFaceRanges.xul
@@ -0,0 +1,189 @@
+<?xml version="1.0"?>
+<?xml-stylesheet type="text/css" href="chrome://global/skin"?>
+<?xml-stylesheet type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"?>
+<?xml-stylesheet type="text/css" href="test_fontFaceRanges.css"?>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1435989
+-->
+<window title="Mozilla Bug 1435989"
+        xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+        onload="RunTest();">
+  <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
+
+  <script type="application/javascript">
+  <![CDATA[
+  /** Test for Bug 1435989 **/
+
+SimpleTest.waitForExplicitFinish();
+
+function expectRanges(f, ranges) {
+  var r = f.ranges;
+  is(r.length, ranges.length, "number of ranges");
+  for (var i = 0; i < Math.min(r.length, ranges.length); i++) {
+    is(r[i].toString(), ranges[i], "text of range");
+  }
+}
+
+function RunTest() {
+  var rng = document.createRange();
+  var elem, fonts;
+  var familyNames = new Map();
+
+  // Test element contains Latin & Chinese
+  elem = document.getElementById("test1");
+  rng.selectNode(elem);
+  fonts = InspectorUtils.getUsedFontFaces(rng, 10);
+  is(fonts.length, 4, "number of font faces");
+  for (var i = 0; i < fonts.length; i++) {
+    var f = fonts[i];
+    familyNames.set(f.CSSFamilyName, true);
+    switch (true) {
+    case f.CSSFamilyName == "capitals":
+      expectRanges(f, ["H", "W"]);
+      break;
+    case f.CSSFamilyName == "lowercase":
+      expectRanges(f, ["ello", "orld"]);
+      break;
+    case f.CSSFamilyName == "gentium":
+      expectRanges(f, [" ", " ", " ", "!"]);
+      break;
+    default:
+      // We'll get a platform-dependent font for the Chinese characters,
+      // so we don't attempt to check its name.
+      expectRanges(f, ["\u4F60", "\u597D"]);
+      break;
+    }
+  }
+  is(familyNames.size, 4, "found all expected families");
+
+  // Test element #test1 again, but limit maxRanges to 1 per face
+  elem = document.getElementById("test1");
+  rng.selectNode(elem);
+  fonts = InspectorUtils.getUsedFontFaces(rng, 1);
+  is(fonts.length, 4, "number of font faces");
+  familyNames.clear();
+  for (var i = 0; i < fonts.length; i++) {
+    var f = fonts[i];
+    familyNames.set(f.CSSFamilyName, true);
+    switch (true) {
+    case f.CSSFamilyName == "capitals":
+      expectRanges(f, ["H"]);
+      break;
+    case f.CSSFamilyName == "lowercase":
+      expectRanges(f, ["ello"]);
+      break;
+    case f.CSSFamilyName == "gentium":
+      expectRanges(f, [" "]);
+      break;
+    default:
+      // We'll get a platform-dependent font for the Chinese characters,
+      // so we don't attempt to check its name.
+      expectRanges(f, ["\u4F60"]);
+      break;
+    }
+  }
+  is(familyNames.size, 4, "found all expected families");
+
+  // Test element contains Latin & Arabic (for bidi)
+  elem = document.getElementById("test2");
+  rng.selectNode(elem);
+  fonts = InspectorUtils.getUsedFontFaces(rng, 10);
+  is(fonts.length, 4, "number of font faces");
+  familyNames.clear();
+  for (var i = 0; i < fonts.length; i++) {
+    var f = fonts[i];
+    familyNames.set(f.CSSFamilyName, true);
+    switch (true) {
+    case f.CSSFamilyName == "capitals":
+      expectRanges(f, ["H", "W"]);
+      break;
+    case f.CSSFamilyName == "lowercase":
+      expectRanges(f, ["ello", "orld"]);
+      break;
+    case f.CSSFamilyName == "gentium":
+      expectRanges(f, [" ", " ", "!"]);
+      break;
+    default:
+      // We'll get a platform-dependent font for the Arabic characters,
+      // so we don't attempt to check its name.
+      expectRanges(f, ["العربي"]);
+      break;
+    }
+  }
+  is(familyNames.size, 4, "found all expected families");
+
+  // Test element wrapped across multiple lines, including soft hyphens,
+  // one of which will be used as a line-break.
+  elem = document.getElementById("test3");
+  rng.selectNode(elem);
+  fonts = InspectorUtils.getUsedFontFaces(rng, 10);
+  is(fonts.length, 3, "number of font faces");
+  familyNames.clear();
+  for (var i = 0; i < fonts.length; i++) {
+    var f = fonts[i];
+    familyNames.set(f.CSSFamilyName, true);
+    switch (true) {
+    case f.CSSFamilyName == "capitals":
+      expectRanges(f, ["H", "W"]);
+      break;
+    case f.CSSFamilyName == "lowercase":
+      expectRanges(f, ["ello", "mul\u00ADti\u00ADline", "orld"]);
+      break;
+    case f.CSSFamilyName == "gentium":
+      expectRanges(f, [" ", " ", "!"]);
+      break;
+    default:
+      // There shouldn't be any other font used
+      ok(false, "unexpected font: " + f.CSSFamilyName);
+      break;
+    }
+  }
+  is(familyNames.size, 3, "found all expected families");
+
+  // Test where textrun should continue across inline element boundaries
+  // (but we expect to get separate Range objects).
+  elem = document.getElementById("test4");
+  rng.selectNode(elem);
+  fonts = InspectorUtils.getUsedFontFaces(rng, 10);
+  is(fonts.length, 3, "number of font faces");
+  familyNames.clear();
+  for (var i = 0; i < fonts.length; i++) {
+    var f = fonts[i];
+    familyNames.set(f.CSSFamilyName, true);
+    switch (true) {
+    case f.CSSFamilyName == "capitals":
+      expectRanges(f, ["H"]);
+      break;
+    case f.CSSFamilyName == "lowercase":
+      expectRanges(f, ["ello", "cruel", "world"]);
+      break;
+    case f.CSSFamilyName == "gentium":
+      expectRanges(f, ["!"]);
+      break;
+    default:
+      // There shouldn't be any other font used
+      ok(false, "unexpected font: " + f.CSSFamilyName);
+      break;
+    }
+  }
+  is(familyNames.size, 3, "found all expected families");
+
+  SimpleTest.finish();
+}
+
+  ]]>
+  </script>
+
+  <body xmlns="http://www.w3.org/1999/xhtml">
+  <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=1435989"
+     target="_blank">Mozilla Bug 1435989</a>
+  <!-- We use @font-face with unicode-range to force different font faces to be used
+       for uppercase, lowercase, and non-letter Latin characters; and then the Chinese
+       and Arabic characters will result in font fallback being applied. -->
+  <div class="gentium" id="test1">Hello &#x4F60; <b>World</b> &#x597D;!</div>
+  <div class="gentium" id="test2">Hello العربي World!</div>
+  <div class="gentium" id="test3" style="width:3em">Hello mul&#xAD;ti&#xAD;line World!</div>
+  <div class="gentium" id="test4"><span>Hello</span><span>cruel</span>world!</div>
+  </body>
+
+</window>
--- a/layout/painting/RetainedDisplayListBuilder.cpp
+++ b/layout/painting/RetainedDisplayListBuilder.cpp
@@ -814,20 +814,16 @@ ProcessFrame(nsIFrame* aFrame, nsDisplay
 
     // Convert 'aOverflow' into the coordinate space of the nearest stacking context
     // or display port ancestor and update 'currentFrame' to point to that frame.
     aOverflow = nsLayoutUtils::TransformFrameRectToAncestor(currentFrame, aOverflow, aStopAtFrame,
                                                            nullptr, nullptr,
                                                            /* aStopAtStackingContextAndDisplayPortAndOOFFrame = */ true,
                                                            &currentFrame);
     MOZ_ASSERT(currentFrame);
-    aOverflow.IntersectRect(aOverflow, currentFrame->GetVisualOverflowRectRelativeToSelf());
-    if (aOverflow.IsEmpty()) {
-      break;
-    }
 
     if (nsLayoutUtils::FrameHasDisplayPort(currentFrame)) {
       CRR_LOG("Frame belongs to displayport frame %p\n", currentFrame);
       nsIScrollableFrame* sf = do_QueryFrame(currentFrame);
       MOZ_ASSERT(sf);
       nsRect displayPort;
       DebugOnly<bool> hasDisplayPort =
         nsLayoutUtils::GetDisplayPort(currentFrame->GetContent(), &displayPort,
@@ -851,18 +847,23 @@ ProcessFrame(nsIFrame* aFrame, nsDisplay
 
         // TODO: Can we just use MarkFrameForDisplayIfVisible, plus MarkFramesForDifferentAGR to
         // ensure that this displayport, plus any items that move relative to it get rebuilt,
         // and then not contribute to the root dirty area?
         aOverflow = sf->GetScrollPortRect();
       } else {
         // Don't contribute to the root dirty area at all.
         aOverflow.SetEmpty();
-        break;
       }
+    } else {
+      aOverflow.IntersectRect(aOverflow, currentFrame->GetVisualOverflowRectRelativeToSelf());
+    }
+
+    if (aOverflow.IsEmpty()) {
+      break;
     }
 
     if (currentFrame != aBuilder.RootReferenceFrame() &&
         currentFrame->IsStackingContext()) {
       CRR_LOG("Frame belongs to stacking context frame %p\n", currentFrame);
       // If we found an intermediate stacking context with an existing display item
       // then we can store the dirty rect there and stop. If we couldn't find one then
       // we need to keep bubbling up to the next stacking context.
new file mode 100644
--- /dev/null
+++ b/layout/reftests/display-list/1437374-1-ref.html
@@ -0,0 +1,8 @@
+<!DOCTYPE HTML>
+<html>
+<body>
+  <div style="width:400px; height:500px; border:2px solid black">
+    <div style="height:450px"></div>
+    <div style="height:50px; background:purple"></div>
+  </div>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/display-list/1437374-1.html
@@ -0,0 +1,23 @@
+<!DOCTYPE HTML>
+<html reftest-async-scroll class="reftest-wait reftest-snapshot-all">
+<body>
+<div style="width:400px; height:500px; overflow:hidden; border:2px solid black"
+     reftest-displayport-x="0" reftest-displayport-y="0"
+     reftest-displayport-w="800" reftest-displayport-h="2000"
+     reftest-async-scroll-x="0" reftest-async-scroll-y="50">
+  <div style="height:500px"></div>
+  <div style="height:100px; background-color:red" id="scrolled"></div>
+</div>
+<script>
+
+function doTest()
+{
+  var scrolled = document.getElementById("scrolled");
+  scrolled.style.backgroundColor = "purple";
+  document.documentElement.setAttribute("class", "reftest-snapshot-all");
+}
+
+document.addEventListener("MozReftestInvalidate", doTest);
+</script>
+</body>
+</html>
--- a/layout/reftests/display-list/reftest.list
+++ b/layout/reftests/display-list/reftest.list
@@ -16,9 +16,10 @@ fuzzy(1,235200) == 1413073.html 1413073-
 == 1418945-1.html 1418945-1-ref.html
 skip-if(Android) == 1428993-1.html 1428993-1-ref.html
 == 1420480-1.html 1420480-1-ref.html
 == 1428993-2.html 1428993-2-ref.html
 needs-focus == 1429027-1.html 1429027-1-ref.html
 == 1432553-1.html 1432553-1-ref.html
 == 1432553-2.html 1432553-2-ref.html
 == 1436189-1.html 1436189-1-ref.html
+skip-if(!asyncPan) == 1437374-1.html 1437374-1-ref.html
 
--- a/layout/xul/tree/TreeBoxObject.cpp
+++ b/layout/xul/tree/TreeBoxObject.cpp
@@ -160,16 +160,24 @@ TreeBoxObject::GetView(nsITreeView * *aV
 already_AddRefed<nsITreeView>
 TreeBoxObject::GetView(CallerType /* unused */)
 {
   nsCOMPtr<nsITreeView> view;
   GetView(getter_AddRefs(view));
   return view.forget();
 }
 
+NS_IMETHODIMP
+TreeBoxObject::SetView(nsITreeView* aView)
+{
+  ErrorResult rv;
+  SetView(aView, CallerType::System, rv);
+  return rv.StealNSResult();
+}
+
 void
 TreeBoxObject::SetView(nsITreeView* aView, CallerType aCallerType,
                        ErrorResult& aRv)
 {
   if (aCallerType != CallerType::System) {
     // Don't trust views coming from random places.
     aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
     return;
--- a/layout/xul/tree/nsITreeBoxObject.idl
+++ b/layout/xul/tree/nsITreeBoxObject.idl
@@ -7,31 +7,33 @@
 
 interface nsIDOMElement;
 interface nsITreeView;
 interface nsITreeSelection;
 interface nsITreeColumn;
 interface nsITreeColumns;
 interface nsIScriptableRegion;
 
+/**
+ * This interface cannot become builtinclass until bug 1438525 is fixed.
+ */
 [scriptable, uuid(f3da0c5e-51f5-45f0-b2cd-6be3ab6847ae)]
 interface nsITreeBoxObject : nsISupports
 {
   /**
    * Obtain the columns.
    */
   readonly attribute nsITreeColumns columns;
 
   /**
-   * The view that backs the tree and that supplies it with its data.  This can
-   * be set from script (via the TreeBoxObject.webidl bits), read from C++ from
-   * an nsITreeBoxObject, or set in C++ on a mozilla::dom::TreeBoxObject.  In
-   * general, this attribute, along with this whole interface, should go away.
+   * The view that backs the tree and that supplies it with its data.
+   * It is dynamically settable, either using a view attribute on the
+   * tree tag or by setting this attribute to a new value.
    */
-  readonly attribute nsITreeView view;
+  attribute nsITreeView view;
 
   /**
    * Whether or not we are currently focused.
    */
   attribute boolean focused;
 
   /**
    * Obtain the treebody content node
--- a/mobile/android/chrome/geckoview/GeckoViewContent.js
+++ b/mobile/android/chrome/geckoview/GeckoViewContent.js
@@ -132,17 +132,18 @@ class GeckoViewContent extends GeckoView
             node = node.parentNode;
           }
           return node && node.href;
         }
 
         let node = aEvent.target;
         let hrefNode = nearestParentHref(node);
         let isImageNode = (ChromeUtils.getClassName(node) === "HTMLImageElement");
-        let isMediaNode = (ChromeUtils.getClassName(node) === "HTMLMediaElement");
+        let isMediaNode = (ChromeUtils.getClassName(node) === "HTMLVideoElement" ||
+                           ChromeUtils.getClassName(node) === "HTMLAudioElement");
 
         if (hrefNode || isImageNode || isMediaNode) {
           this.eventDispatcher.sendRequest({
             type: "GeckoView:ContextMenu",
             screenX: aEvent.screenX,
             screenY: aEvent.screenY,
             uri: hrefNode,
             elementSrc: isImageNode || isMediaNode
--- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/GeckoSession.java
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/GeckoSession.java
@@ -228,20 +228,38 @@ public class GeckoSession extends LayerS
                             new PermissionCallback("android", callback));
                 } else if ("GeckoView:ContentPermission".equals(event)) {
                     final String type = message.getString("perm");
                     listener.requestContentPermission(
                             GeckoSession.this, message.getString("uri"),
                             type, message.getString("access"),
                             new PermissionCallback(type, callback));
                 } else if ("GeckoView:MediaPermission".equals(event)) {
+                    GeckoBundle[] videoBundles = message.getBundleArray("video");
+                    GeckoBundle[] audioBundles = message.getBundleArray("audio");
+                    PermissionDelegate.MediaSource[] videos = null;
+                    PermissionDelegate.MediaSource[] audios = null;
+
+                    if (videoBundles != null) {
+                        videos = new PermissionDelegate.MediaSource[videoBundles.length];
+                        for (int i = 0; i < videoBundles.length; i++) {
+                            videos[i] = new PermissionDelegate.MediaSource(videoBundles[i]);
+                        }
+                    }
+
+                    if (audioBundles != null) {
+                        audios = new PermissionDelegate.MediaSource[audioBundles.length];
+                        for (int i = 0; i < audioBundles.length; i++) {
+                            audios[i] = new PermissionDelegate.MediaSource(audioBundles[i]);
+                        }
+                    }
+
                     listener.requestMediaPermission(
                             GeckoSession.this, message.getString("uri"),
-                            message.getBundleArray("video"), message.getBundleArray("audio"),
-                            new PermissionCallback("media", callback));
+                            videos, audios, new PermissionCallback("media", callback));
                 }
             }
         };
 
     private static class PermissionCallback implements
         PermissionDelegate.Callback, PermissionDelegate.MediaCallback {
 
         private final String mType;
@@ -279,19 +297,19 @@ public class GeckoSession extends LayerS
             }
             final GeckoBundle response = new GeckoBundle(2);
             response.putString("video", video);
             response.putString("audio", audio);
             submit(response);
         }
 
         @Override // PermissionDelegate.MediaCallback
-        public void grant(final GeckoBundle video, final GeckoBundle audio) {
-            grant(video != null ? video.getString("id") : null,
-                  audio != null ? audio.getString("id") : null);
+        public void grant(final PermissionDelegate.MediaSource video, final PermissionDelegate.MediaSource audio) {
+            grant(video != null ? video.id : null,
+                  audio != null ? audio.id : null);
         }
     }
 
     /**
      * Get the current prompt delegate for this GeckoSession.
      * @return PromptDelegate instance or null if using default delegate.
      */
     public PermissionDelegate getPermissionDelegate() {
@@ -891,39 +909,39 @@ public class GeckoSession extends LayerS
                 ensureResult().putStringArray("choices", values);
             } else {
                 throw new UnsupportedOperationException();
             }
             submit();
         }
 
         @Override // ChoiceCallback
-        public void confirm(GeckoBundle item) {
+        public void confirm(PromptDelegate.Choice item) {
             if ("choice".equals(mType)) {
-                confirm(item == null ? null : item.getString("id"));
+                confirm(item == null ? null : item.id);
                 return;
             } else {
                 throw new UnsupportedOperationException();
             }
         }
 
         @Override // ChoiceCallback
-        public void confirm(GeckoBundle[] items) {
+        public void confirm(PromptDelegate.Choice[] items) {
             if (("menu".equals(mMode) || "single".equals(mMode)) &&
                 (items == null || items.length != 1)) {
                 throw new IllegalArgumentException();
             }
             if ("choice".equals(mType)) {
                 if (items == null) {
                     confirm((String[]) null);
                     return;
                 }
                 final String[] ids = new String[items.length];
                 for (int i = 0; i < ids.length; i++) {
-                    ids[i] = (items[i] == null) ? null : items[i].getString("id");
+                    ids[i] = (items[i] == null) ? null : items[i].id;
                 }
                 confirm(ids);
                 return;
             } else {
                 throw new UnsupportedOperationException();
             }
         }
 
@@ -1032,33 +1050,44 @@ public class GeckoSession extends LayerS
                 delegate.promptForButton(session, title, msg, btnCustomTitle, cb);
                 break;
             }
             case "text": {
                 delegate.promptForText(session, title, msg, message.getString("value"), cb);
                 break;
             }
             case "auth": {
-                delegate.promptForAuth(session, title, msg, message.getBundle("options"), cb);
+                delegate.promptForAuth(session, title, msg, new PromptDelegate.AuthenticationOptions(message.getBundle("options")), cb);
                 break;
             }
             case "choice": {
                 final int intMode;
                 if ("menu".equals(mode)) {
-                    intMode = PromptDelegate.CHOICE_TYPE_MENU;
+                    intMode = PromptDelegate.Choice.CHOICE_TYPE_MENU;
                 } else if ("single".equals(mode)) {
-                    intMode = PromptDelegate.CHOICE_TYPE_SINGLE;
+                    intMode = PromptDelegate.Choice.CHOICE_TYPE_SINGLE;
                 } else if ("multiple".equals(mode)) {
-                    intMode = PromptDelegate.CHOICE_TYPE_MULTIPLE;
+                    intMode = PromptDelegate.Choice.CHOICE_TYPE_MULTIPLE;
                 } else {
                     callback.sendError("Invalid mode");
                     return;
                 }
+
+                GeckoBundle[] choiceBundles = message.getBundleArray("choices");
+                PromptDelegate.Choice choices[];
+                if (choiceBundles == null || choiceBundles.length == 0) {
+                    choices = null;
+                } else {
+                    choices = new PromptDelegate.Choice[choiceBundles.length];
+                    for (int i = 0; i < choiceBundles.length; i++) {
+                        choices[i] = new PromptDelegate.Choice(choiceBundles[i]);
+                    }
+                }
                 delegate.promptForChoice(session, title, msg, intMode,
-                                         message.getBundleArray("choices"), cb);
+                                         choices, cb);
                 break;
             }
             case "color": {
                 delegate.promptForColor(session, title, message.getString("value"), cb);
                 break;
             }
             case "datetime": {
                 final int intMode;
@@ -1488,66 +1517,176 @@ public class GeckoSession extends LayerS
              * confirmed by the user.
              *
              * @param username Entered username.
              * @param password Entered password.
              */
             void confirm(String username, String password);
         }
 
-        /**
-         * The auth prompt is for a network host.
-         */
-        static final int AUTH_FLAG_HOST = 1;
-        /**
-         * The auth prompt is for a proxy.
-         */
-        static final int AUTH_FLAG_PROXY = 2;
-        /**
-         * The auth prompt should only request a password.
-         */
-        static final int AUTH_FLAG_ONLY_PASSWORD = 8;
-        /**
-         * The auth prompt is the result of a previous failed login.
-         */
-        static final int AUTH_FLAG_PREVIOUS_FAILED = 16;
-        /**
-         * The auth prompt is for a cross-origin sub-resource.
-         */
-        static final int AUTH_FLAG_CROSS_ORIGIN_SUB_RESOURCE = 32;
+        class AuthenticationOptions {
+            /**
+             * The auth prompt is for a network host.
+             */
+            public static final int AUTH_FLAG_HOST = 1;
+            /**
+             * The auth prompt is for a proxy.
+             */
+            public static final int AUTH_FLAG_PROXY = 2;
+            /**
+             * The auth prompt should only request a password.
+             */
+            public static final int AUTH_FLAG_ONLY_PASSWORD = 8;
+            /**
+             * The auth prompt is the result of a previous failed login.
+             */
+            public static final int AUTH_FLAG_PREVIOUS_FAILED = 16;
+            /**
+             * The auth prompt is for a cross-origin sub-resource.
+             */
+            public static final int AUTH_FLAG_CROSS_ORIGIN_SUB_RESOURCE = 32;
 
-        /**
-         * The auth request is unencrypted or the encryption status is unknown.
-         */
-        static final int AUTH_LEVEL_NONE = 0;
-        /**
-         * The auth request only encrypts password but not data.
-         */
-        static final int AUTH_LEVEL_PW_ENCRYPTED = 1;
-        /**
-         * The auth request encrypts both password and data.
-         */
-        static final int AUTH_LEVEL_SECURE = 2;
+            /**
+             * The auth request is unencrypted or the encryption status is unknown.
+             */
+            public static final int AUTH_LEVEL_NONE = 0;
+            /**
+             * The auth request only encrypts password but not data.
+             */
+            public static final int AUTH_LEVEL_PW_ENCRYPTED = 1;
+            /**
+             * The auth request encrypts both password and data.
+             */
+            public static final int AUTH_LEVEL_SECURE = 2;
+
+            /**
+             * An int bit-field of AUTH_FLAG_* flags.
+             */
+            public int flags;
+
+            /**
+             * A string containing the URI for the auth request or null if unknown.
+             */
+            public String uri;
+
+            /**
+             * An int, one of AUTH_LEVEL_*, indicating level of encryption.
+             */
+            public int level;
+
+            /**
+             * A string containing the initial username or null if password-only.
+             */
+            public String username;
+
+            /**
+             * A string containing the initial password.
+             */
+            public String password;
+
+            /* package */ AuthenticationOptions(GeckoBundle options) {
+                flags = options.getInt("flags");
+                uri = options.getString("uri");
+                level = options.getInt("level");
+                username = options.getString("username");
+                password = options.getString("password");
+            }
+        }
 
         /**
          * Display a prompt for authentication credentials.
          *
          * @param session GeckoSession that triggered the prompt
          * @param title Title for the prompt dialog.
          * @param msg Message for the prompt dialog.
          * @param options Bundle containing options for the prompt with keys,
          *                "flags": int, bit field of AUTH_FLAG_* flags;
          *                "uri": String, URI for the auth request or null if unknown;
          *                "level": int, one of AUTH_LEVEL_* indicating level of encryption;
          *                "username": String, initial username or null if password-only;
          *                "password": String, intiial password;
          * @param callback Callback interface.
          */
         void promptForAuth(GeckoSession session, String title, String msg,
-                           GeckoBundle options, AuthCallback callback);
+                           AuthenticationOptions options, AuthCallback callback);
+
+        class Choice {
+            /**
+             * Display choices in a menu that dismisses as soon as an item is chosen.
+             */
+            public static final int CHOICE_TYPE_MENU = 1;
+
+            /**
+             * Display choices in a list that allows a single selection.
+             */
+            public static final int CHOICE_TYPE_SINGLE = 2;
+
+            /**
+             * Display choices in a list that allows multiple selections.
+             */
+            public static final int CHOICE_TYPE_MULTIPLE = 3;
+
+            /**
+             * A boolean indicating if the item is disabled. Item should not be
+             * selectable if this is true.
+             */
+            public boolean disabled;
+
+            /**
+             * A String giving the URI of the item icon, or null if none exists
+             * (only valid for menus)
+             */
+            public String icon;
+
+            /**
+             * A String giving the ID of the item or group
+             */
+            public String id;
+
+            /**
+             * A Choice array of sub-items in a group, or null if not a group
+             */
+            public Choice[] items;
+
+            /**
+             * A string giving the label for displaying the item or group
+             */
+            public String label;
+
+            /**
+             * A boolean indicating if the item should be pre-selected
+             * (pre-checked for menu items)
+             */
+            public boolean selected;
+
+            /**
+             * A boolean indicating if the item should be a menu separator
+             * (only valid for menus)
+             */
+            public boolean separator;
+
+            /* package */ Choice(GeckoBundle choice) {
+                disabled = choice.getBoolean("disabled");
+                icon = choice.getString("icon");
+                id = choice.getString("id");
+                label = choice.getString("label");
+                selected = choice.getBoolean("label");
+                separator = choice.getBoolean("separator");
+
+                GeckoBundle[] choices = choice.getBundleArray("items");
+                if (choices == null) {
+                    items = null;
+                } else {
+                    items = new Choice[choices.length];
+                    for (int i = 0; i < choices.length; i++) {
+                        items[i] = new Choice(choices[i]);
+                    }
+                }
+            }
+        }
 
         /**
          * Callback interface for notifying the result of menu or list choice.
          */
         interface ChoiceCallback extends AlertCallback {
             /**
              * Called by the prompt implementation when the menu or single-choice list is
              * dismissed by the user.
@@ -1563,69 +1702,55 @@ public class GeckoSession extends LayerS
              * @param ids IDs of the selected items.
              */
             void confirm(String[] ids);
 
             /**
              * Called by the prompt implementation when the menu or single-choice list is
              * dismissed by the user.
              *
-             * @param item Bundle representing the selected item; must be an original
-             *             GeckoBundle object that was passed to the implementation.
+             * @param item Choice representing the selected item; must be an original
+             *             Choice object that was passed to the implementation.
              */
-            void confirm(GeckoBundle item);
+            void confirm(Choice item);
 
             /**
              * Called by the prompt implementation when the multiple-choice list is
              * dismissed by the user.
              *
-             * @param items Bundle array representing the selected items; must be original
-             *             GeckoBundle objects that were passed to the implementation.
+             * @param items Choice array representing the selected items; must be original
+             *              Choice objects that were passed to the implementation.
              */
-            void confirm(GeckoBundle[] items);
+            void confirm(Choice[] items);
         }
 
-        /**
-         * Display choices in a menu that dismisses as soon as an item is chosen.
-         */
-        static final int CHOICE_TYPE_MENU = 1;
-
-        /**
-         * Display choices in a list that allows a single selection.
-         */
-        static final int CHOICE_TYPE_SINGLE = 2;
-
-        /**
-         * Display choices in a list that allows multiple selections.
-         */
-        static final int CHOICE_TYPE_MULTIPLE = 3;
 
         /**
          * Display a menu prompt or list prompt.
          *
          * @param session GeckoSession that triggered the prompt
          * @param title Title for the prompt dialog, or null for no title.
          * @param msg Message for the prompt dialog, or null for no message.
          * @param type One of CHOICE_TYPE_* indicating the type of prompt.
          * @param choices Array of bundles each representing an item or group, with keys,
          *                "disabled": boolean, true if the item should not be selectable;
          *                "icon": String, URI of the item icon or null if none
          *                        (only valid for menus);
          *                "id": String, ID of the item or group;
-         *                "items": GeckoBundle[], array of sub-items in a group or null
+         *                "items": Choice[], array of sub-items in a group or null
          *                         if not a group.
          *                "label": String, label for displaying the item or group;
          *                "selected": boolean, true if the item should be pre-selected
          *                            (pre-checked for menu items);
          *                "separator": boolean, true if the item should be a menu separator
          *                             (only valid for menus);
          * @param callback Callback interface.
          */
         void promptForChoice(GeckoSession session, String title, String msg, int type,
-                             GeckoBundle[] choices, ChoiceCallback callback);
+                             Choice[] choices, ChoiceCallback callback);
 
         /**
          * Display a color prompt.
          *
          * @param session GeckoSession that triggered the prompt
          * @param title Title for the prompt dialog.
          * @param value Initial color value in HTML color format.
          * @param callback Callback interface; the result passed to confirm() must be in
@@ -1821,16 +1946,141 @@ public class GeckoSession extends LayerS
          *             "geolocation": permission for using the geolocation API
          *             "desktop-notification": permission for using the notifications API
          * @param access Not used.
          * @param callback Callback interface.
          */
         void requestContentPermission(GeckoSession session, String uri, String type,
                                       String access, Callback callback);
 
+        class MediaSource {
+            /**
+             * The media source is a camera.
+             */
+            public static final int SOURCE_CAMERA = 0;
+
+            /**
+             * The media source is the screen.
+             */
+            public static final int SOURCE_SCREEN  = 1;
+
+            /**
+             * The media source is an application.
+             */
+            public static final int SOURCE_APPLICATION = 2;
+
+            /**
+             * The media source is a window.
+             */
+            public static final int SOURCE_WINDOW = 3;
+
+            /**
+             * The media source is the browser.
+             */
+            public static final int SOURCE_BROWSER = 4;
+
+            /**
+             * The media source is a microphone.
+             */
+            public static final int SOURCE_MICROPHONE = 5;
+
+            /**
+             * The media source is audio capture.
+             */
+            public static final int SOURCE_AUDIOCAPTURE = 6;
+
+            /**
+             * The media source does not fall into any of the other categories.
+             */
+            public static final int SOURCE_OTHER = 7;
+
+            /**
+             * The media type is video.
+             */
+            public static final int TYPE_VIDEO = 0;
+
+            /**
+             * The media type is audio.
+             */
+            public static final int TYPE_AUDIO = 1;
+
+            /**
+             * A string giving the origin-specific source identifier.
+             */
+            public String id;
+
+            /**
+             * A string giving the non-origin-specific source identifier.
+             */
+            public String rawId;
+
+            /**
+             * A string giving the name of the video source from the system
+             * (for example, "Camera 0, Facing back, Orientation 90").
+             * May be empty.
+             */
+            public String name;
+
+            /**
+             * An int giving the media source type.
+             * Possible values for a video source are:
+             * SOURCE_CAMERA, SOURCE_SCREEN, SOURCE_APPLICATION, SOURCE_WINDOW, SOURCE_BROWSER, and SOURCE_OTHER.
+             * Possible values for an audio source are:
+             * SOURCE_MICROPHONE, SOURCE_AUDIOCAPTURE, and SOURCE_OTHER.
+             */
+            public int source;
+
+            /**
+             * An int giving the type of media, must be either TYPE_VIDEO or TYPE_AUDIO.
+             */
+            public int type;
+
+            private static int getSourceFromString(String src) {
+                // The strings here should match those in MediaSourceEnum in MediaStreamTrack.webidl
+                if ("camera".equals(src)) {
+                    return SOURCE_CAMERA;
+                } else if ("screen".equals(src)) {
+                    return SOURCE_SCREEN;
+                } else if ("application".equals(src)) {
+                    return SOURCE_APPLICATION;
+                } else if ("window".equals(src)) {
+                    return SOURCE_WINDOW;
+                } else if ("browser".equals(src)) {
+                    return SOURCE_BROWSER;
+                } else if ("microphone".equals(src)) {
+                    return SOURCE_MICROPHONE;
+                } else if ("audioCapture".equals(src)) {
+                    return SOURCE_AUDIOCAPTURE;
+                } else if ("other".equals(src)) {
+                    return SOURCE_OTHER;
+                } else {
+                    throw new IllegalArgumentException("String: " + src + " is not a valid media source string");
+                }
+            }
+
+            private static int getTypeFromString(String type) {
+                // The strings here should match the possible types in MediaDevice::MediaDevice in MediaManager.cpp
+                if ("video".equals(type)) {
+                    return TYPE_VIDEO;
+                } else if ("audio".equals(type)) {
+                    return TYPE_AUDIO;
+                } else {
+                    throw new IllegalArgumentException("String: " + type + " is not a valid media type string");
+                }
+            }
+
+            public MediaSource(GeckoBundle media) {
+                id = media.getString("id");
+                rawId = media.getString("id");
+                name = media.getString("name");
+                source = getSourceFromString(media.getString("source"));
+                type = getTypeFromString(media.getString("type"));
+            }
+        }
+
         /**
          * Callback interface for notifying the result of a media permission request,
          * including which media source(s) to use.
          */
         interface MediaCallback {
             /**
              * Called by the implementation after permissions are granted; the
              * implementation must call one of grant() or reject() for every request.
@@ -1841,53 +2091,38 @@ public class GeckoSession extends LayerS
              *              or null when audio is not requested.
              */
             void grant(final String video, final String audio);
 
             /**
              * Called by the implementation after permissions are granted; the
              * implementation must call one of grant() or reject() for every request.
              *
-             * @param video Bundle for the video source to use (must be an original
-             *              GeckoBundle object that was passed to the implementation);
+             * @param video MediaSource for the video source to use (must be an original
+             *              MediaSource object that was passed to the implementation);
              *              or null when video is not requested.
-             * @param audio Bundle for the audio source to use (must be an original
-             *              GeckoBundle object that was passed to the implementation);
+             * @param audio MediaSource for the audio source to use (must be an original
+             *              MediaSource object that was passed to the implementation);
              *              or null when audio is not requested.
              */
-            void grant(final GeckoBundle video, final GeckoBundle audio);
+            void grant(final MediaSource video, final MediaSource audio);
 
             /**
              * Called by the implementation when permissions are not granted; the
              * implementation must call one of grant() or reject() for every request.
              */
             void reject();
         }
 
         /**
          * Request content media permissions, including request for which video and/or
          * audio source to use.
          *
          * @param session GeckoSession instance requesting the permission.
          * @param uri The URI of the content requesting the permission.
          * @param video List of video sources, or null if not requesting video.
-         *              Each bundle represents a video source, with keys,
-         *              "id": String, the origin-specific source identifier;
-         *              "rawId": String, the non-origin-specific source identifier;
-         *              "name": String, the name of the video source from the system
-         *                      (for example, "Camera 0, Facing back, Orientation 90");
-         *                      may be empty;
-         *              "mediaSource": String, the media source type; possible values are,
-         *                             "camera", "screen", "application", "window",
-         *                             "browser", and "other";
-         *              "type": String, always "video";
          * @param audio List of audio sources, or null if not requesting audio.
-         *              Each bundle represents an audio source with same keys and possible
-         *              values as video source bundles above, except for:
-         *              "mediaSource", String; possible values are "microphone",
-         *                             "audioCapture", and "other";
-         *              "type", String, always "audio";
          * @param callback Callback interface.
          */
-        void requestMediaPermission(GeckoSession session, String uri, GeckoBundle[] video,
-                                    GeckoBundle[] audio, MediaCallback callback);
+        void requestMediaPermission(GeckoSession session, String uri, MediaSource[] video,
+                                    MediaSource[] audio, MediaCallback callback);
     }
 }
--- a/mobile/android/geckoview_example/src/main/java/org/mozilla/geckoview_example/BasicGeckoViewPrompt.java
+++ b/mobile/android/geckoview_example/src/main/java/org/mozilla/geckoview_example/BasicGeckoViewPrompt.java
@@ -44,17 +44,17 @@ import android.widget.TimePicker;
 import java.text.ParseException;
 import java.text.SimpleDateFormat;
 import java.util.ArrayList;
 import java.util.Calendar;
 import java.util.Date;
 import java.util.Locale;
 
 import org.mozilla.gecko.GeckoSession;
-import org.mozilla.gecko.util.GeckoBundle;
+import org.mozilla.gecko.GeckoSession.PermissionDelegate.MediaSource;
 
 final class BasicGeckoViewPrompt implements GeckoSession.PromptDelegate {
     protected static final String LOGTAG = "BasicGeckoViewPrompt";
 
     private final Activity mActivity;
     public int filePickerRequestCode = 1;
     private int mFileType;
     private FileCallback mFileCallback;
@@ -204,110 +204,110 @@ final class BasicGeckoViewPrompt impleme
                         callback.confirm(editText.getText().toString());
                     }
                 });
 
         createStandardDialog(addCheckbox(builder, container, callback), callback).show();
     }
 
     public void promptForAuth(final GeckoSession session, final String title,
-                              final String msg, final GeckoBundle options,
+                              final String msg, final AuthenticationOptions options,
                               final AuthCallback callback) {
         final Activity activity = mActivity;
         if (activity == null) {
             callback.dismiss();
             return;
         }
         final AlertDialog.Builder builder = new AlertDialog.Builder(activity);
         final LinearLayout container = addStandardLayout(builder, title, msg);
 
-        final int flags = options.getInt("flags");
-        final int level = options.getInt("level");
+        final int flags = options.flags;
+        final int level = options.level;
         final EditText username;
-        if ((flags & AUTH_FLAG_ONLY_PASSWORD) == 0) {
+        if ((flags & AuthenticationOptions.AUTH_FLAG_ONLY_PASSWORD) == 0) {
             username = new EditText(builder.getContext());
             username.setHint(R.string.username);
-            username.setText(options.getString("username"));
+            username.setText(options.username);
             container.addView(username);
         } else {
             username = null;
         }
 
         final EditText password = new EditText(builder.getContext());
         password.setHint(R.string.password);
-        password.setText(options.getString("password"));
+        password.setText(options.password);
         password.setInputType(InputType.TYPE_CLASS_TEXT |
                               InputType.TYPE_TEXT_VARIATION_PASSWORD);
         container.addView(password);
 
-        if (level != AUTH_LEVEL_NONE) {
+        if (level != AuthenticationOptions.AUTH_LEVEL_NONE) {
             final ImageView secure = new ImageView(builder.getContext());
             secure.setImageResource(android.R.drawable.ic_lock_lock);
             container.addView(secure);
         }
 
         builder.setNegativeButton(android.R.string.cancel, /* listener */ null)
                .setPositiveButton(android.R.string.ok,
                                   new DialogInterface.OnClickListener() {
                     @Override
                     public void onClick(final DialogInterface dialog, final int which) {
-                        if ((flags & AUTH_FLAG_ONLY_PASSWORD) == 0) {
+                        if ((flags & AuthenticationOptions.AUTH_FLAG_ONLY_PASSWORD) == 0) {
                             callback.confirm(username.getText().toString(),
                                              password.getText().toString());
                         } else {
                             callback.confirm(password.getText().toString());
                         }
                     }
                 });
         createStandardDialog(addCheckbox(builder, container, callback), callback).show();
     }
 
-    private void addChoiceItems(final int type, final ArrayAdapter<GeckoBundle> list,
-                                final GeckoBundle[] items, final String indent) {
-        if (type == CHOICE_TYPE_MENU) {
+    private void addChoiceItems(final int type, final ArrayAdapter<Choice> list,
+                                final Choice[] items, final String indent) {
+        if (type == Choice.CHOICE_TYPE_MENU) {
             list.addAll(items);
             return;
         }
 
-        for (final GeckoBundle item : items) {
-            final GeckoBundle[] children = item.getBundleArray("items");
+        for (final Choice item : items) {
+            final Choice[] children = item.items;
             if (indent != null && children == null) {
-                item.putString("label", indent + item.getString("label", ""));
+                item.label = indent + item.label;
             }
             list.add(item);
 
             if (children != null) {
                 final String newIndent;
-                if (type == CHOICE_TYPE_SINGLE || type == CHOICE_TYPE_MULTIPLE) {
+                if (type == Choice.CHOICE_TYPE_SINGLE || type == Choice.CHOICE_TYPE_MULTIPLE) {
                     newIndent = (indent != null) ? indent + '\t' : "\t";
                 } else {
                     newIndent = null;
                 }
                 addChoiceItems(type, list, children, newIndent);
             }
         }
     }
 
     public void promptForChoice(final GeckoSession session, final String title,
                                 final String msg, final int type,
-                                final GeckoBundle[] choices, final ChoiceCallback callback) {
+                                final Choice[] choices, final ChoiceCallback callback) {
         final Activity activity = mActivity;
         if (activity == null) {
             callback.dismiss();
             return;
         }
         final AlertDialog.Builder builder = new AlertDialog.Builder(activity);
         addStandardLayout(builder, title, msg);
 
         final ListView list = new ListView(builder.getContext());
-        if (type == CHOICE_TYPE_MULTIPLE) {
+        if (type == Choice.CHOICE_TYPE_MULTIPLE) {
             list.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE);
         }
 
-        final ArrayAdapter<GeckoBundle> adapter = new ArrayAdapter<GeckoBundle>(
+        final ArrayAdapter<Choice> adapter = new ArrayAdapter<Choice>(
                 builder.getContext(), android.R.layout.simple_list_item_1) {
             private static final int TYPE_MENU_ITEM = 0;
             private static final int TYPE_MENU_CHECK = 1;
             private static final int TYPE_SEPARATOR = 2;
             private static final int TYPE_GROUP = 3;
             private static final int TYPE_SINGLE = 4;
             private static final int TYPE_MULTIPLE = 5;
             private static final int TYPE_COUNT = 6;
@@ -317,38 +317,38 @@ final class BasicGeckoViewPrompt impleme
 
             @Override
             public int getViewTypeCount() {
                 return TYPE_COUNT;
             }
 
             @Override
             public int getItemViewType(final int position) {
-                final GeckoBundle item = getItem(position);
-                if (item.getBoolean("separator")) {
+                final Choice item = getItem(position);
+                if (item.separator) {
                     return TYPE_SEPARATOR;
-                } else if (type == CHOICE_TYPE_MENU) {
-                    return item.getBoolean("selected") ? TYPE_MENU_CHECK : TYPE_MENU_ITEM;
-                } else if (item.containsKey("items")) {
+                } else if (type == Choice.CHOICE_TYPE_MENU) {
+                    return item.selected ? TYPE_MENU_CHECK : TYPE_MENU_ITEM;
+                } else if (item.items != null) {
                     return TYPE_GROUP;
-                } else if (type == CHOICE_TYPE_SINGLE) {
+                } else if (type == Choice.CHOICE_TYPE_SINGLE) {
                     return TYPE_SINGLE;
-                } else if (type == CHOICE_TYPE_MULTIPLE) {
+                } else if (type == Choice.CHOICE_TYPE_MULTIPLE) {
                     return TYPE_MULTIPLE;
                 } else {
                     throw new UnsupportedOperationException();
                 }
             }
 
             @Override
             public boolean isEnabled(final int position) {
-                final GeckoBundle item = getItem(position);
-                return !item.getBoolean("separator") && !item.getBoolean("disabled") &&
-                        ((type != CHOICE_TYPE_SINGLE && type != CHOICE_TYPE_MULTIPLE) ||
-                         !item.containsKey("items"));
+                final Choice item = getItem(position);
+                return !item.separator && !item.disabled &&
+                        ((type != Choice.CHOICE_TYPE_SINGLE && type != Choice.CHOICE_TYPE_MULTIPLE) ||
+                         item.items != null);
             }
 
             @Override
             public View getView(final int position, View view,
                                 final ViewGroup parent) {
                 final int itemType = getItemViewType(position);
                 final int layoutId;
                 if (itemType == TYPE_SEPARATOR) {
@@ -378,80 +378,80 @@ final class BasicGeckoViewPrompt impleme
 
                 if (view == null) {
                     if (mInflater == null) {
                         mInflater = LayoutInflater.from(builder.getContext());
                     }
                     view = mInflater.inflate(layoutId, parent, false);
                 }
 
-                final GeckoBundle item = getItem(position);
+                final Choice item = getItem(position);
                 final TextView text = (TextView) view;
-                text.setEnabled(!item.getBoolean("disabled"));
-                text.setText(item.getString("label"));
+                text.setEnabled(!item.disabled);
+                text.setText(item.label);
                 if (view instanceof CheckedTextView) {
-                    final boolean selected = item.getBoolean("selected");
+                    final boolean selected = item.selected;
                     if (itemType == TYPE_MULTIPLE) {
                         list.setItemChecked(position, selected);
                     } else {
                         ((CheckedTextView) view).setChecked(selected);
                     }
                 }
                 return view;
             }
         };
         addChoiceItems(type, adapter, choices, /* indent */ null);
 
         list.setAdapter(adapter);
         builder.setView(list);
 
         final AlertDialog dialog;
-        if (type == CHOICE_TYPE_SINGLE || type == CHOICE_TYPE_MENU) {
+        if (type == Choice.CHOICE_TYPE_SINGLE || type == Choice.CHOICE_TYPE_MENU) {
             dialog = createStandardDialog(builder, callback);
             list.setOnItemClickListener(new AdapterView.OnItemClickListener() {
                 @Override
                 public void onItemClick(final AdapterView<?> parent, final View v,
                                         final int position, final long id) {
-                    final GeckoBundle item = adapter.getItem(position);
-                    if (type == CHOICE_TYPE_MENU) {
-                        final GeckoBundle[] children = item.getBundleArray("items");
+                    final Choice item = adapter.getItem(position);
+                    if (type == Choice.CHOICE_TYPE_MENU) {
+                        final Choice[] children = item.items;
                         if (children != null) {
                             // Show sub-menu.
                             dialog.setOnDismissListener(null);
                             dialog.dismiss();
-                            promptForChoice(session, item.getString("label"), /* msg */ null,
+                            promptForChoice(session, item.label, /* msg */ null,
                                             type, children, callback);
                             return;
                         }
                     }
                     callback.confirm(item);
                     dialog.dismiss();
                 }
             });
-        } else if (type == CHOICE_TYPE_MULTIPLE) {
+        } else if (type == Choice.CHOICE_TYPE_MULTIPLE) {
             list.setOnItemClickListener(new AdapterView.OnItemClickListener() {
                 @Override
                 public void onItemClick(final AdapterView<?> parent, final View v,
                                         final int position, final long id) {
-                    final GeckoBundle item = adapter.getItem(position);
-                    item.putBoolean("selected", ((CheckedTextView) v).isChecked());
+                    final Choice item = adapter.getItem(position);
+                    item.selected = ((CheckedTextView) v).isChecked();
                 }
             });
             builder.setNegativeButton(android.R.string.cancel, /* listener */ null)
                    .setPositiveButton(android.R.string.ok,
                                       new DialogInterface.OnClickListener() {
                 @Override
                 public void onClick(final DialogInterface dialog,
                                     final int which) {
                     final int len = adapter.getCount();
                     ArrayList<String> items = new ArrayList<>(len);
                     for (int i = 0; i < len; i++) {
-                        final GeckoBundle item = adapter.getItem(i);
-                        if (item.getBoolean("selected")) {
-                            items.add(item.getString("id"));
+                        final Choice item = adapter.getItem(i);
+                        if (item.selected) {
+                            items.add(item.id);
                         }
                     }
                     callback.confirm(items.toArray(new String[items.size()]));
                 }
             });
             dialog = createStandardDialog(builder, callback);
         } else {
             throw new UnsupportedOperationException();
@@ -804,23 +804,23 @@ final class BasicGeckoViewPrompt impleme
                    public void onDismiss(final DialogInterface dialog) {
                        callback.reject();
                    }
                });
         dialog.show();
     }
 
     private Spinner addMediaSpinner(final Context context, final ViewGroup container,
-                                    final GeckoBundle[] sources) {
-        final ArrayAdapter<GeckoBundle> adapter = new ArrayAdapter<GeckoBundle>(
+                                    final MediaSource[] sources) {
+        final ArrayAdapter<MediaSource> adapter = new ArrayAdapter<MediaSource>(
                 context, android.R.layout.simple_spinner_item) {
             private View convertView(final int position, final View view) {
                 if (view != null) {
-                    final GeckoBundle item = getItem(position);
-                    ((TextView) view).setText(item.getString("name"));
+                    final MediaSource item = getItem(position);
+                    ((TextView) view).setText(item.name);
                 }
                 return view;
             }
 
             @Override
             public View getView(final int position, View view,
                                 final ViewGroup parent) {
                 return convertView(position, super.getView(position, view, parent));
@@ -838,17 +838,17 @@ final class BasicGeckoViewPrompt impleme
         final Spinner spinner = new Spinner(context);
         spinner.setAdapter(adapter);
         spinner.setSelection(0);
         container.addView(spinner);
         return spinner;
     }
 
     public void promptForMedia(final GeckoSession session, final String title,
-                               final GeckoBundle[] video, final GeckoBundle[] audio,
+                               final MediaSource[] video, final MediaSource[] audio,
                                final GeckoSession.PermissionDelegate.MediaCallback callback) {
         final Activity activity = mActivity;
         if (activity == null || (video == null && audio == null)) {
             callback.reject();
             return;
         }
         final AlertDialog.Builder builder = new AlertDialog.Builder(activity);
         final LinearLayout container = addStandardLayout(builder, title, /* msg */ null);
@@ -867,20 +867,20 @@ final class BasicGeckoViewPrompt impleme
             audioSpinner = null;
         }
 
         builder.setNegativeButton(android.R.string.cancel, /* listener */ null)
                .setPositiveButton(android.R.string.ok,
                                   new DialogInterface.OnClickListener() {
                     @Override
                     public void onClick(final DialogInterface dialog, final int which) {
-                        final GeckoBundle video = (videoSpinner != null)
-                                ? (GeckoBundle) videoSpinner.getSelectedItem() : null;
-                        final GeckoBundle audio = (audioSpinner != null)
-                                ? (GeckoBundle) audioSpinner.getSelectedItem() : null;
+                        final MediaSource video = (videoSpinner != null)
+                                ? (MediaSource) videoSpinner.getSelectedItem() : null;
+                        final MediaSource audio = (audioSpinner != null)
+                                ? (MediaSource) audioSpinner.getSelectedItem() : null;
                         callback.grant(video, audio);
                     }
                 });
 
         final AlertDialog dialog = builder.create();
         dialog.setOnDismissListener(new DialogInterface.OnDismissListener() {
                     @Override
                     public void onDismiss(final DialogInterface dialog) {
--- a/mobile/android/geckoview_example/src/main/java/org/mozilla/geckoview_example/GeckoViewActivity.java
+++ b/mobile/android/geckoview_example/src/main/java/org/mozilla/geckoview_example/GeckoViewActivity.java
@@ -17,16 +17,17 @@ import android.util.Log;
 import android.view.WindowManager;
 
 import java.util.Locale;
 
 import org.mozilla.gecko.GeckoSession;
 import org.mozilla.gecko.GeckoSessionSettings;
 import org.mozilla.gecko.GeckoThread;
 import org.mozilla.gecko.GeckoView;
+import org.mozilla.gecko.GeckoSession.PermissionDelegate.MediaSource;
 import org.mozilla.gecko.GeckoSession.TrackingProtectionDelegate;
 import org.mozilla.gecko.util.GeckoBundle;
 
 public class GeckoViewActivity extends Activity {
     private static final String LOGTAG = "GeckoViewActivity";
     private static final String DEFAULT_URL = "https://mozilla.org";
     private static final String USE_MULTIPROCESS_EXTRA = "use_multiprocess";
     private static final String USE_REMOTE_DEBUGGER_EXTRA = "use_remote_debugger";
@@ -277,44 +278,43 @@ public class GeckoViewActivity extends A
             }
 
             final String title = getString(resId, Uri.parse(uri).getAuthority());
             final BasicGeckoViewPrompt prompt = (BasicGeckoViewPrompt)
                     mGeckoSession.getPromptDelegate();
             prompt.promptForPermission(session, title, callback);
         }
 
-        private void normalizeMediaName(final GeckoBundle[] sources) {
+        private void normalizeMediaName(final MediaSource[] sources) {
             if (sources == null) {
                 return;
             }
-            for (final GeckoBundle source : sources) {
-                final String mediaSource = source.getString("mediaSource");
-                String name = source.getString("name");
-                if ("camera".equals(mediaSource)) {
+            for (final MediaSource source : sources) {
+                final int mediaSource = source.source;
+                String name = source.name;
+                if (MediaSource.SOURCE_CAMERA == mediaSource) {
                     if (name.toLowerCase(Locale.ENGLISH).contains("front")) {
                         name = getString(R.string.media_front_camera);
                     } else {
                         name = getString(R.string.media_back_camera);
                     }
                 } else if (!name.isEmpty()) {
                     continue;
-                } else if ("microphone".equals(mediaSource)) {
+                } else if (MediaSource.SOURCE_MICROPHONE == mediaSource) {
                     name = getString(R.string.media_microphone);
                 } else {
                     name = getString(R.string.media_other);
                 }
-                source.putString("name", name);
+                source.name = name;
             }
         }
 
         @Override
         public void requestMediaPermission(final GeckoSession session, final String uri,
-                                           final GeckoBundle[] video,
-                                           final GeckoBundle[] audio,
+                                           final MediaSource[] video, final MediaSource[] audio,
                                            final MediaCallback callback) {
             final String host = Uri.parse(uri).getAuthority();
             final String title;
             if (audio == null) {
                 title = getString(R.string.request_video, host);
             } else if (video == null) {
                 title = getString(R.string.request_audio, host);
             } else {
rename from modules/fdlibm/patches/15_use_safer_strict_assign_on_visual_studio.patch
rename to modules/fdlibm/patches/16_use_safer_strict_assign_on_visual_studio.patch
new file mode 100644
--- /dev/null
+++ b/modules/fdlibm/patches/17_compare_atan2_x_to_1_no_signed_overflow.patch
@@ -0,0 +1,22 @@
+diff --git a/modules/fdlibm/src/e_atan2.cpp b/modules/fdlibm/src/e_atan2.cpp
+--- a/modules/fdlibm/src/e_atan2.cpp
++++ b/modules/fdlibm/src/e_atan2.cpp
+@@ -65,17 +65,17 @@ double
+ 
+ 	EXTRACT_WORDS(hx,lx,x);
+ 	ix = hx&0x7fffffff;
+ 	EXTRACT_WORDS(hy,ly,y);
+ 	iy = hy&0x7fffffff;
+ 	if(((ix|((lx|-lx)>>31))>0x7ff00000)||
+ 	   ((iy|((ly|-ly)>>31))>0x7ff00000))	/* x or y is NaN */
+ 	   return x+y;
+-	if((hx-0x3ff00000|lx)==0) return atan(y);   /* x=1.0 */
++	if(hx==0x3ff00000&&lx==0) return atan(y);   /* x=1.0 */
+ 	m = ((hy>>31)&1)|((hx>>30)&2);	/* 2*sign(x)+sign(y) */
+ 
+     /* when y = 0 */
+ 	if((iy|ly)==0) {
+ 	    switch(m) {
+ 		case 0: 
+ 		case 1: return y; 	/* atan(+-0,+anything)=+-0 */
+ 		case 2: return  pi+tiny;/* atan(+0,-anything) = pi */
--- a/modules/fdlibm/src/e_atan2.cpp
+++ b/modules/fdlibm/src/e_atan2.cpp
@@ -65,17 +65,17 @@ double
 
 	EXTRACT_WORDS(hx,lx,x);
 	ix = hx&0x7fffffff;
 	EXTRACT_WORDS(hy,ly,y);
 	iy = hy&0x7fffffff;
 	if(((ix|((lx|-lx)>>31))>0x7ff00000)||
 	   ((iy|((ly|-ly)>>31))>0x7ff00000))	/* x or y is NaN */
 	   return x+y;
-	if((hx-0x3ff00000|lx)==0) return atan(y);   /* x=1.0 */
+	if(hx==0x3ff00000&&lx==0) return atan(y);   /* x=1.0 */
 	m = ((hy>>31)&1)|((hx>>30)&2);	/* 2*sign(x)+sign(y) */
 
     /* when y = 0 */
 	if((iy|ly)==0) {
 	    switch(m) {
 		case 0: 
 		case 1: return y; 	/* atan(+-0,+anything)=+-0 */
 		case 2: return  pi+tiny;/* atan(+0,-anything) = pi */
old mode 100644
new mode 100755
--- a/modules/libpref/Preferences.cpp
+++ b/modules/libpref/Preferences.cpp
@@ -328,16 +328,17 @@ class Pref
 public:
   explicit Pref(const char* aName)
     : mName(ArenaStrdup(aName, gPrefNameArena))
     , mType(static_cast<uint32_t>(PrefType::None))
     , mIsSticky(false)
     , mIsLocked(false)
     , mHasDefaultValue(false)
     , mHasUserValue(false)
+    , mHasChangedSinceInit(false)
     , mDefaultValue()
     , mUserValue()
   {
   }
 
   ~Pref()
   {
     // There's no need to free mName because it's allocated in memory owned by
@@ -358,21 +359,51 @@ public:
   bool IsTypeNone() const { return IsType(PrefType::None); }
   bool IsTypeString() const { return IsType(PrefType::String); }
   bool IsTypeInt() const { return IsType(PrefType::Int); }
   bool IsTypeBool() const { return IsType(PrefType::Bool); }
 
   // Other properties.
 
   bool IsLocked() const { return mIsLocked; }
-  void SetIsLocked(bool aValue) { mIsLocked = aValue; }
+  void SetIsLocked(bool aValue)
+  {
+    mIsLocked = aValue;
+    mHasChangedSinceInit = true;
+  }
 
   bool HasDefaultValue() const { return mHasDefaultValue; }
   bool HasUserValue() const { return mHasUserValue; }
 
+  // When a content process is created we could tell it about every pref. But
+  // the content process also initializes prefs from file, so we save a lot of
+  // IPC if we only tell it about prefs that have changed since initialization.
+  //
+  // Specifically, we send a pref if any of the following conditions are met.
+  //
+  // - If the pref has changed in any way (default value, user value, or other
+  //   attribute, such as whether it is locked) since being initialized from
+  //   file.
+  //
+  // - If the pref has a user value. (User values are more complicated than
+  //   default values, because they can be loaded from file after
+  //   initialization with Preferences::ReadUserPrefsFromFile(), so we are
+  //   conservative with them.)
+  //
+  // In other words, prefs that only have a default value and haven't changed
+  // need not be sent. One could do better with effort, but it's ok to be
+  // conservative and this still greatly reduces the number of prefs sent.
+  //
+  // Note: This function is only useful in the parent process.
+  bool MustSendToContentProcesses() const
+  {
+    MOZ_ASSERT(XRE_IsParentProcess());
+    return mHasUserValue || mHasChangedSinceInit;
+  }
+
   // Other operations.
 
   bool MatchEntry(const char* aPrefName)
   {
     if (!mName || !aPrefName) {
       return false;
     }
 
@@ -502,16 +533,18 @@ public:
         mHasUserValue = true;
         userValueChanged = true;
       }
     } else if (mHasUserValue) {
       ClearUserValue();
       userValueChanged = true;
     }
 
+    mHasChangedSinceInit = true;
+
     if (userValueChanged || (defaultValueChanged && !mHasUserValue)) {
       *aValueChanged = true;
     }
   }
 
   bool HasAdvisablySizedValues()
   {
     MOZ_ASSERT(XRE_IsParentProcess());
@@ -551,33 +584,38 @@ public:
   void ClearUserValue()
   {
     if (Type() == PrefType::String) {
       free(const_cast<char*>(mUserValue.mStringVal));
       mUserValue.mStringVal = nullptr;
     }
 
     mHasUserValue = false;
+    mHasChangedSinceInit = true;
   }
 
   nsresult SetDefaultValue(PrefType aType,
                            PrefValue aValue,
+                           bool aFromFile,
                            bool aIsSticky,
                            bool* aValueChanged)
   {
     // Types must always match when setting the default value.
     if (!IsType(aType)) {
       return NS_ERROR_UNEXPECTED;
     }
 
     // Should we set the default value? Only if the pref is not locked, and
     // doing so would change the default value.
     if (!IsLocked() && !ValueMatches(PrefValueKind::Default, aType, aValue)) {
       mDefaultValue.Replace(Type(), aType, aValue);
       mHasDefaultValue = true;
+      if (!aFromFile) {
+        mHasChangedSinceInit = true;
+      }
       if (aIsSticky) {
         mIsSticky = true;
       }
       if (!mHasUserValue) {
         *aValueChanged = true;
       }
       // What if we change the default to be the same as the user value?
       // Should we clear the user value? Currently we don't.
@@ -609,16 +647,19 @@ public:
       }
 
       // Otherwise, should we set the user value? Only if doing so would
       // change the user value.
     } else if (!ValueMatches(PrefValueKind::User, aType, aValue)) {
       mUserValue.Replace(Type(), aType, aValue);
       SetType(aType); // needed because we may have changed the type
       mHasUserValue = true;
+      if (!aFromFile) {
+        mHasChangedSinceInit = true;
+      }
       if (!IsLocked()) {
         *aValueChanged = true;
       }
     }
     return NS_OK;
   }
 
   // Returns false if this pref doesn't have a user value worth saving.
@@ -662,16 +703,17 @@ public:
 private:
   const char* mName; // allocated in gPrefNameArena
 
   uint32_t mType : 2;
   uint32_t mIsSticky : 1;
   uint32_t mIsLocked : 1;
   uint32_t mHasDefaultValue : 1;
   uint32_t mHasUserValue : 1;
+  uint32_t mHasChangedSinceInit : 1;
 
   PrefValue mDefaultValue;
   PrefValue mUserValue;
 };
 
 class PrefEntry : public PLDHashEntryHdr
 {
 public:
@@ -879,17 +921,18 @@ pref_SetPref(const char* aPrefName,
   if (pref->IsTypeNone()) {
     // New entry. Set the type.
     pref->SetType(aType);
   }
 
   bool valueChanged = false;
   nsresult rv;
   if (aKind == PrefValueKind::Default) {
-    rv = pref->SetDefaultValue(aType, aValue, aIsSticky, &valueChanged);
+    rv =
+      pref->SetDefaultValue(aType, aValue, aFromFile, aIsSticky, &valueChanged);
   } else {
     rv = pref->SetUserValue(aType, aValue, aFromFile, &valueChanged);
   }
   if (NS_FAILED(rv)) {
     NS_WARNING(
       nsPrintfCString(
         "Rejected attempt to change type of pref %s's %s value from %s to %s",
         aPrefName,
@@ -3229,16 +3272,23 @@ Preferences::GetPreferences(InfallibleTA
 {
   MOZ_ASSERT(XRE_IsParentProcess());
   MOZ_ASSERT(NS_IsMainThread());
 
   aDomPrefs->SetCapacity(gHashTable->EntryCount());
   for (auto iter = gHashTable->Iter(); !iter.Done(); iter.Next()) {
     Pref* pref = static_cast<PrefEntry*>(iter.Get())->mPref;
 
+    if (!pref->MustSendToContentProcesses()) {
+      // The pref value hasn't changed since it was initialized at startup.
+      // Don't bother sending it, because the content process will initialize
+      // it the same way.
+      continue;
+    }
+
     if (pref->HasAdvisablySizedValues()) {
       dom::Pref* setting = aDomPrefs->AppendElement();
       pref->ToDomPref(setting);
     }
   }
 }
 
 #ifdef DEBUG
@@ -4158,16 +4208,25 @@ Preferences::ClearUser(const char* aPref
 Preferences::HasUserValue(const char* aPrefName)
 {
   NS_ENSURE_TRUE(InitStaticMembers(), false);
 
   Pref* pref = pref_HashTableLookup(aPrefName);
   return pref && pref->HasUserValue();
 }
 
+/* static */ bool
+Preferences::MustSendToContentProcesses(const char* aPrefName)
+{
+  NS_ENSURE_TRUE(InitStaticMembers(), false);
+
+  Pref* pref = pref_HashTableLookup(aPrefName);
+  return pref && pref->MustSendToContentProcesses();
+}
+
 /* static */ int32_t
 Preferences::GetType(const char* aPrefName)
 {
   NS_ENSURE_TRUE(InitStaticMembers(), nsIPrefBranch::PREF_INVALID);
 
   Pref* pref;
   if (!gHashTable || !(pref = pref_HashTableLookup(aPrefName))) {
     return PREF_INVALID;
--- a/modules/libpref/Preferences.h
+++ b/modules/libpref/Preferences.h
@@ -225,16 +225,19 @@ public:
   static bool IsLocked(const char* aPrefName);
 
   // Clears user set pref. Fails if run outside the parent process.
   static nsresult ClearUser(const char* aPrefName);
 
   // Whether the pref has a user value or not.
   static bool HasUserValue(const char* aPref);
 
+  // Must the pref be sent to content processes when they start?
+  static bool MustSendToContentProcesses(const char* aPref);
+
   // Adds/Removes the observer for the root pref branch. See nsIPrefBranch.idl
   // for details.
   static nsresult AddStrongObserver(nsIObserver* aObserver, const char* aPref);
   static nsresult AddWeakObserver(nsIObserver* aObserver, const char* aPref);
   static nsresult RemoveObserver(nsIObserver* aObserver, const char* aPref);
 
   // Adds/Removes two or more observers for the root pref branch. Pass to
   // aPrefs an array of const char* whose last item is nullptr.
--- a/modules/libpref/init/all.js
+++ b/modules/libpref/init/all.js
@@ -5893,21 +5893,17 @@ pref("browser.storageManager.pressureNot
 // a single web page in a row, all following authentication dialogs will
 // be blocked (automatically canceled) for that page. The counter resets
 // when the page is reloaded. To turn this feature off, just set the limit to 0.
 pref("prompts.authentication_dialog_abuse_limit", 3);
 
 pref("dom.IntersectionObserver.enabled", true);
 
 // Whether module scripts (<script type="module">) are enabled for content.
-#ifdef NIGHTLY_BUILD
 pref("dom.moduleScripts.enabled", true);
-#else
-pref("dom.moduleScripts.enabled", false);
-#endif
 
 // Maximum amount of time in milliseconds consecutive setTimeout()/setInterval()
 // callback are allowed to run before yielding the event loop.
 pref("dom.timeout.max_consecutive_callbacks_ms", 4);
 
 // Use this preference to house "Payment Request API" during development
 pref("dom.payments.request.enabled", false);
 pref("dom.payments.loglevel", "Warn");
--- a/python/mozboot/mozboot/base.py
+++ b/python/mozboot/mozboot/base.py
@@ -145,17 +145,17 @@ ac_add_options --enable-artifact-builds
 # This should match OLDEST_NON_LEGACY_VERSION from
 # the hg setup wizard in version-control-tools.
 MODERN_MERCURIAL_VERSION = LooseVersion('4.2.3')
 
 # Upgrade Python older than this.
 MODERN_PYTHON_VERSION = LooseVersion('2.7.3')
 
 # Upgrade rust older than this.
-MODERN_RUST_VERSION = LooseVersion('1.23.0')
+MODERN_RUST_VERSION = LooseVersion('1.24.0')
 
 
 class BaseBootstrapper(object):
     """Base class for system bootstrappers."""
 
     def __init__(self, no_interactive=False):
         self.package_manager_updated = False
         self.no_interactive = no_interactive
--- a/security/sandbox/win/src/sandboxbroker/sandboxBroker.cpp
+++ b/security/sandbox/win/src/sandboxbroker/sandboxBroker.cpp
@@ -771,21 +771,37 @@ SandboxBroker::SetSecurityLevelForPlugin
   SANDBOX_ENSURE_SUCCESS(result,
                          "SetDelayedIntegrityLevel should never fail, what happened?");
 
   sandbox::MitigationFlags mitigations =
     sandbox::MITIGATION_BOTTOM_UP_ASLR |
     sandbox::MITIGATION_HEAP_TERMINATE |
     sandbox::MITIGATION_SEHOP |
     sandbox::MITIGATION_DEP_NO_ATL_THUNK |
-    sandbox::MITIGATION_DEP;
-
-  result = mPolicy->SetProcessMitigations(mitigations);
-  SANDBOX_ENSURE_SUCCESS(result,
-                         "Invalid flags for SetProcessMitigations.");
+    sandbox::MITIGATION_DEP |
+    sandbox::MITIGATION_HARDEN_TOKEN_IL_POLICY |
+    sandbox::MITIGATION_EXTENSION_POINT_DISABLE |
+    sandbox::MITIGATION_NONSYSTEM_FONT_DISABLE |
+    sandbox::MITIGATION_IMAGE_LOAD_PREFER_SYS32;
+
+  if (!sRunningFromNetworkDrive) {
+    mitigations |= sandbox::MITIGATION_IMAGE_LOAD_NO_REMOTE |
+                   sandbox::MITIGATION_IMAGE_LOAD_NO_LOW_LABEL;
+  }
+
+  result = mPolicy->SetProcessMitigations(mitigations);
+  SANDBOX_ENSURE_SUCCESS(result,
+                         "Invalid flags for SetProcessMitigations.");
+
+  sandbox::MitigationFlags delayedMitigations =
+    sandbox::MITIGATION_DLL_SEARCH_ORDER;
+
+  result = mPolicy->SetDelayedProcessMitigations(delayedMitigations);
+  SANDBOX_ENSURE_SUCCESS(result,
+                         "Invalid flags for SetDelayedProcessMitigations.");
 
   if (aSandboxLevel >= 2) {
     // Level 2 and above uses low integrity, so we need to give write access to
     // the Flash directories.
     AddCachedDirRule(mPolicy, sandbox::TargetPolicy::FILES_ALLOW_ANY,
                      sRoamingAppDataDir,
                      NS_LITERAL_STRING("\\Macromedia\\Flash Player\\*"));
     AddCachedDirRule(mPolicy, sandbox::TargetPolicy::FILES_ALLOW_ANY,
--- a/taskcluster/ci/build/linux.yml
+++ b/taskcluster/ci/build/linux.yml
@@ -720,17 +720,17 @@ linux64-jsdcov/opt:
     description: "Linux64-JSDCov Opt"
     index:
         product: firefox
         job-name: linux64-jsdcov-opt
     treeherder:
         platform: linux64-jsdcov/opt
         symbol: B
         tier: 2
-    run-on-projects: []
+    run-on-projects: ['mozilla-central', 'try']
     worker-type: aws-provisioner-v1/gecko-{level}-b-linux
     worker:
         max-run-time: 36000
     run:
         using: mozharness
         actions: [get-secrets build check-test update]
         config:
             - builds/releng_base_firefox.py
@@ -749,17 +749,17 @@ linux64-ccov/opt:
     description: "Linux64-CCov Opt"
     index:
         product: firefox
         job-name: linux64-ccov-opt
     treeherder:
         platform: linux64-ccov/opt
         symbol: B
         tier: 2
-    run-on-projects: []
+    run-on-projects: ['mozilla-central', 'try']
     worker-type: aws-provisioner-v1/gecko-{level}-b-linux
     worker:
         max-run-time: 36000
     run:
         using: mozharness
         actions: [get-secrets build check-test update]
         config:
             - builds/releng_base_firefox.py
--- a/taskcluster/ci/build/windows.yml
+++ b/taskcluster/ci/build/windows.yml
@@ -494,17 +494,17 @@ win64-ccov/debug:
         using: mozharness
         options: [append-env-variables-from-configs]
         script: mozharness/scripts/fx_desktop_build.py
         config:
             - builds/releng_base_firefox.py
             - builds/taskcluster_base_windows.py
             - builds/taskcluster_base_win64.py
             - builds/taskcluster_sub_win64/ccov_debug.py
-    run-on-projects: []
+    run-on-projects: ['mozilla-central', 'try']
     toolchains:
         - win64-clang-cl
         - win64-rust
         - win64-sccache
 
 win64-asan/debug:
     description: "Win64 Debug ASAN"
     index:
--- a/taskcluster/taskgraph/transforms/tests.py
+++ b/taskcluster/taskgraph/transforms/tests.py
@@ -706,38 +706,30 @@ def handle_suite_category(config, tests)
 @transforms.add
 def enable_code_coverage(config, tests):
     """Enable code coverage for the linux64-ccov/opt & linux64-jsdcov/opt & win64-ccov/debug
     build-platforms"""
     for test in tests:
         if 'ccov' in test['build-platform'] and not test['test-name'].startswith('test-verify'):
             test['mozharness'].setdefault('extra-options', []).append('--code-coverage')
             test['instance-size'] = 'xlarge'
-            # Ensure we don't run on inbound/autoland/beta, but if the test is try only, ignore it
-            if 'mozilla-central' in test['run-on-projects'] or \
-                    test['run-on-projects'] == 'built-projects':
-                test['run-on-projects'] = ['mozilla-central', 'try']
 
             if 'talos' in test['test-name']:
                 test['max-run-time'] = 7200
                 if 'linux' in test['build-platform']:
                     test['docker-image'] = {"in-tree": "desktop1604-test"}
                 test['mozharness']['extra-options'].append('--add-option')
                 test['mozharness']['extra-options'].append('--cycles,1')
                 test['mozharness']['extra-options'].append('--add-option')
                 test['mozharness']['extra-options'].append('--tppagecycles,1')
                 test['mozharness']['extra-options'].append('--add-option')
                 test['mozharness']['extra-options'].append('--no-upload-results')
                 test['mozharness']['extra-options'].append('--add-option')
                 test['mozharness']['extra-options'].append('--tptimeout,15000')
         elif test['build-platform'] == 'linux64-jsdcov/opt':
-            # Ensure we don't run on inbound/autoland/beta, but if the test is try only, ignore it
-            if 'mozilla-central' in test['run-on-projects'] or \
-                    test['run-on-projects'] == 'built-projects':
-                test['run-on-projects'] = ['mozilla-central', 'try']
             test['mozharness'].setdefault('extra-options', []).append('--jsd-code-coverage')
         yield test
 
 
 @transforms.add
 def handle_run_on_projects(config, tests):
     """Handle translating `built-projects` appropriately"""
     for test in tests:
--- a/testing/mochitest/BrowserTestUtils/BrowserTestUtils.jsm
+++ b/testing/mochitest/BrowserTestUtils/BrowserTestUtils.jsm
@@ -99,16 +99,20 @@ this.BrowserTestUtils = {
         url: options
       }
     }
     let tab = await BrowserTestUtils.openNewForegroundTab(options);
     let originalWindow = tab.ownerGlobal;
     let result = await taskFn(tab.linkedBrowser);
     let finalWindow = tab.ownerGlobal;
     if (originalWindow == finalWindow && !tab.closing && tab.linkedBrowser) {
+      // taskFn may resolve within a tick after opening a new tab.
+      // We shouldn't remove the newly opened tab in the same tick.
+      // Wait for the next tick here.
+      await TestUtils.waitForTick();
       await BrowserTestUtils.removeTab(tab);
     } else {
       Services.console.logStringMessage(
         "BrowserTestUtils.withNewTab: Tab was already closed before " +
         "removeTab would have been called");
     }
     return Promise.resolve(result);
   },
--- a/testing/talos/talos.json
+++ b/testing/talos/talos.json
@@ -61,17 +61,17 @@
             "pagesets_name": "tp5n.zip"
         },
         "g5-profiling-e10s": {
             "tests": ["ts_paint_webext", "tp5o_webext"],
             "talos_options": ["--geckoProfile"],
             "pagesets_name": "tp5n.zip"
         },
         "motionmark-e10s": {
-            "tests": ["motionmark_animometer", "motionmark_htmlsuite"]
+            "tests": ["motionmark_animometer", "motionmark_htmlsuite", "ARES6"]
         },
         "svgr-e10s": {
             "tests": ["tsvgx", "tsvgr_opacity", "tart", "tscrollx", "tsvg_static"]
         },
         "svgr-profiling-e10s": {
             "tests": ["tsvgx", "tsvgr_opacity", "tart", "tscrollx", "tsvg_static"],
             "talos_options": ["--geckoProfile"]
         },
--- a/testing/talos/talos/output.py
+++ b/testing/talos/talos/output.py
@@ -251,16 +251,24 @@ class Output(object):
         if len(results) != 160:
             raise Exception("Speedometer has 160 subtests, found: %s instead" % len(results))
 
         results = results[9::10]
         score = 60 * 1000 / filter.geometric_mean(results) / correctionFactor
         return score
 
     @classmethod
+    def ares6_score(cls, val_list):
+        """
+        ares6_score: reported as 'geomean'
+        """
+        results = [i for i, j in val_list if j == 'geomean']
+        return filter.mean(results)
+
+    @classmethod
     def stylebench_score(cls, val_list):
         """
         stylebench_score: https://bug-172968-attachments.webkit.org/attachment.cgi?id=319888
         """
         correctionFactor = 3
         results = [i for i, j in val_list]
         # stylebench has 4 tests, each of these are made of up 12 subtests
         # and a sum of the 12 values.  We receive 52 values, and want to use
@@ -274,16 +282,18 @@ class Output(object):
 
     def construct_results(self, vals, testname):
         if 'responsiveness' in testname:
             return filter.responsiveness_Metric([val for (val, page) in vals])
         elif testname.startswith('v8_7'):
             return self.v8_Metric(vals)
         elif testname.startswith('kraken'):
             return self.JS_Metric(vals)
+        elif testname.startswith('ares6'):
+            return self.ares6_score(vals)
         elif testname.startswith('speedometer'):
             return self.speedometer_score(vals)
         elif testname.startswith('stylebench'):
             return self.stylebench_score(vals)
         elif len(vals) > 1:
             return filter.geometric_mean([i for i, j in vals])
         else:
             return filter.mean([i for i, j in vals])
--- a/testing/talos/talos/test.py
+++ b/testing/talos/talos/test.py
@@ -811,16 +811,23 @@ class stylebench(WebkitBenchmark):
 
 @register_test()
 class motionmark_animometer(WebkitBenchmark):
     # MotionMark benchmark used by many browser vendors (from webkit)
     tpmanifest = '${talos}/tests/motionmark/animometer.manifest'
 
 
 @register_test()
+class ARES6(WebkitBenchmark):
+    # ARES-6 benchmark used by many browser vendors (from webkit)
+    tpmanifest = '${talos}/tests/ares6/ares6.manifest'
+    tppagecycles = 1
+
+
+@register_test()
 class motionmark_htmlsuite(WebkitBenchmark):
     # MotionMark benchmark used by many browser vendors (from webkit)
     tpmanifest = '${talos}/tests/motionmark/htmlsuite.manifest'
 
 
 @register_test()
 class perf_reftest(PageloaderTest):
     """
new file mode 100644
--- /dev/null
+++ b/testing/talos/talos/tests/ares6/ares6.manifest
@@ -0,0 +1,1 @@
+% http://localhost/tests/webkit/PerformanceTests/ARES-6/index.html?gecko
--- a/testing/web-platform/meta/html/dom/interfaces.html.ini
+++ b/testing/web-platform/meta/html/dom/interfaces.html.ini
@@ -1,10 +1,10 @@
 [interfaces.html]
-  prefs: [dom.forms.inputmode:true, dom.dialog_element.enabled:true, dom.forms.autocomplete.formautofill:true, dom.webcomponents.shadowdom.enabled:true, dom.moduleScripts.enabled:true, browser.cache.offline.insecure.enable:true]
+  prefs: [dom.forms.inputmode:true, dom.dialog_element.enabled:true, dom.forms.autocomplete.formautofill:true, dom.webcomponents.shadowdom.enabled:true, browser.cache.offline.insecure.enable:true]
   [Document interface: attribute domain]
     expected: FAIL
 
   [Document interface: attribute cookie]
     expected: FAIL
 
   [Document interface: attribute head]
     expected: FAIL
--- a/testing/web-platform/meta/html/dom/reflection-misc.html.ini
+++ b/testing/web-platform/meta/html/dom/reflection-misc.html.ini
@@ -1,10 +1,10 @@
 [reflection-misc.html]
-  prefs: [dom.dialog_element.enabled: true, dom.webcomponents.shadowdom.enabled:true, dom.moduleScripts.enabled:true]
+  prefs: [dom.dialog_element.enabled: true, dom.webcomponents.shadowdom.enabled:true]
   [html.tabIndex: setAttribute() to object "3" followed by getAttribute()]
     expected: FAIL
 
   [html.tabIndex: setAttribute() to object "3" followed by IDL get]
     expected: FAIL
 
   [script.tabIndex: setAttribute() to object "3" followed by getAttribute()]
     expected: FAIL
deleted file mode 100644
--- a/testing/web-platform/meta/html/semantics/scripting-1/the-script-element/module/__dir__.ini
+++ /dev/null
@@ -1,1 +0,0 @@
-prefs: ["dom.moduleScripts.enabled:true"]
\ No newline at end of file
deleted file mode 100644
--- a/testing/web-platform/meta/html/semantics/scripting-1/the-script-element/nomodule-reflect.html.ini
+++ /dev/null
@@ -1,2 +0,0 @@
-[nomodule-reflect.html]
-  prefs: [dom.moduleScripts.enabled:true]
deleted file mode 100644
--- a/testing/web-platform/meta/html/semantics/scripting-1/the-script-element/nomodule-set-on-async-classic-script.html.ini
+++ /dev/null
@@ -1,2 +0,0 @@
-[nomodule-set-on-async-classic-script.html]
-  prefs: [dom.moduleScripts.enabled:true]
deleted file mode 100644
--- a/testing/web-platform/meta/html/semantics/scripting-1/the-script-element/nomodule-set-on-external-module-script.html.ini
+++ /dev/null
@@ -1,2 +0,0 @@
-[nomodule-set-on-external-module-script.html]
-  prefs: [dom.moduleScripts.enabled:true]
deleted file mode 100644
--- a/testing/web-platform/meta/html/semantics/scripting-1/the-script-element/nomodule-set-on-inline-classic-scripts.html.ini
+++ /dev/null
@@ -1,2 +0,0 @@
-[nomodule-set-on-inline-classic-scripts.html]
-  prefs: [dom.moduleScripts.enabled:true]
deleted file mode 100644
--- a/testing/web-platform/meta/html/semantics/scripting-1/the-script-element/nomodule-set-on-inline-module-script.html.ini
+++ /dev/null
@@ -1,2 +0,0 @@
-[nomodule-set-on-inline-module-script.html]
-  prefs: [dom.moduleScripts.enabled:true]
deleted file mode 100644
--- a/testing/web-platform/meta/html/semantics/scripting-1/the-script-element/nomodule-set-on-synchronously-loaded-classic-scripts.html.ini
+++ /dev/null
@@ -1,2 +0,0 @@
-[nomodule-set-on-synchronously-loaded-classic-scripts.html]
-  prefs: [dom.moduleScripts.enabled:true]
new file mode 100644
--- /dev/null
+++ b/third_party/webkit/PerformanceTests/ARES-6/ARES-6.svg
@@ -0,0 +1,4 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 335 74">
+    <title>ARES-6</title>
+    <path fill="#9B9B9B" d="M 12.837 13.785 C 6.052 13.785 0.59 19.247 0.59 26.032 L 0.59 73.366 L 13.416 73.366 L 13.416 54.168 L 47.179 54.168 L 47.179 73.366 L 60.171 73.366 L 60.171 26.032 C 60.171 19.247 54.627 13.785 47.841 13.785 L 12.837 13.785 Z M 13.416 41.259 L 13.416 26.694 L 47.179 26.694 L 47.179 41.259 L 13.416 41.259 Z M 124.854 26.115 C 124.854 19.412 119.309 13.868 112.524 13.868 L 65.273 13.868 L 65.273 73.366 L 78.099 73.366 L 78.099 54.251 C 78.182 54.333 78.347 54.333 78.347 54.333 C 78.347 54.002 78.265 53.837 78.099 53.837 L 95.146 53.754 L 111.613 73.366 L 124.854 73.366 L 124.854 68.98 C 120.716 64.015 116.165 58.719 111.944 53.754 L 112.524 53.754 C 119.309 53.754 124.854 48.21 124.854 41.507 L 124.854 26.115 Z M 78.099 26.694 L 111.862 26.694 L 111.862 40.845 L 78.099 40.845 L 78.099 26.694 Z"/><path fill="#E7B135" d="M 313.955 73.053 C 317.167 71.389 319.353 68.037 319.353 64.158 L 319.353 49.842 C 319.353 44.215 314.885 39.83 309.34 39.83 L 269.206 39.83 C 267.468 39.83 265.979 38.34 265.979 36.519 L 265.979 24.603 C 265.979 22.865 267.468 21.376 269.206 21.376 L 308.927 21.376 L 308.927 14.508 L 269.206 14.508 C 263.579 14.508 259.193 18.976 259.193 24.603 L 259.193 64.158 C 259.193 68.037 261.339 71.389 264.555 73.053 L 234.069 73.053 C 239.473 71.802 243.566 66.949 243.566 61.119 L 243.566 49.368 C 243.566 42.583 238.022 37.121 231.319 37.121 L 196.812 37.121 L 196.812 26.694 L 230.574 26.694 L 230.574 31.577 L 243.566 31.577 L 243.566 26.032 C 243.566 19.247 238.022 13.785 231.319 13.785 L 196.232 13.785 C 189.447 13.785 183.985 19.247 183.985 26.032 L 183.985 37.783 C 183.985 44.569 189.447 50.03 196.232 50.03 L 230.574 50.03 L 230.574 60.457 L 196.812 60.457 L 196.812 55.575 L 183.985 55.575 L 183.985 61.119 C 183.985 66.949 188.017 71.802 193.457 73.053 L 179.747 73.053 L 179.747 60.457 L 137.709 60.457 L 137.709 50.03 L 171.555 50.03 L 171.555 37.121 L 137.709 37.121 L 137.709 26.694 L 179.747 26.694 L 179.747 13.785 L 124.717 13.785 L 124.717 43.419 L 124.717 0.366 L 334.951 0.366 L 334.951 73.053 L 313.955 73.053 Z M 247.722 40.547 L 247.722 47.333 L 254.507 47.333 L 254.507 40.547 L 247.722 40.547 Z M 312.568 64.158 C 312.568 65.896 311.078 67.386 309.34 67.386 L 269.206 67.386 C 267.468 67.386 265.979 65.896 265.979 64.158 L 265.979 46.615 L 309.34 46.615 C 311.078 46.615 312.568 48.105 312.568 49.842 L 312.568 64.158 Z"/>
+</svg>
new file mode 100644
--- /dev/null
+++ b/third_party/webkit/PerformanceTests/ARES-6/Air/README.md
@@ -0,0 +1,152 @@
+# All about Air.js
+
+Air.js is an ES6 benchmark. It tries to faithfully use new features like arrow
+functions, classes, for-of, and Map/Set, among others. Air.js doesn't avoid any
+features out of fear that they might be slow, in the hope that we might learn
+how to make those features fast by looking at how Air.js and other benchmarks
+use them.
+
+This documents the motivation, design, and license of Air.js.
+
+To run Air.js, simply open "[Air.js/test.html](test.html)" in your browser. It
+will only run correctly if your browser supports ES6.
+
+## Motivation
+
+At the time that Air.js was written, most JavaScript benchmarks used ES5 or
+older versions of the language. ES6 testing mostly relied on microbenchmarks or
+conversions of existing tests to ES6. We try to use larger benchmarks to avoid
+over-optimizing for small pieces of code, and we avoid making changes to
+existing benchmarks because that approach has no limiting principle: if it's OK
+to change a benchmark to use a feature, does that mean we can also change it to
+remove the use of a feature we don't like? We feel that the best way to avoid
+falling into the trap of creating benchmarks that reinforce what some JS engine
+is already good at is to create a new benchmark from first principles.
+
+We only recently completed our new JavaScript compiler, called
+[B3](https://webkit.org/blog/5852/introducing-the-b3-jit-compiler/). B3's
+backend, called
+[Air](https://webkit.org/docs/b3/assembly-intermediate-representation.html), is
+very CPU-intensive and uses a combination of object-oriented and functional
+idioms in C++. Additionally, it relies heavily on high speed maps and sets. It
+goes so far as to use customized map/set implementations - even more so than
+the rest of WebKit. This makes Air a great candidate for ES6 benchmarking.
+Air.js is a faithful ES6 implementation of Air. It pulls no punches: just as
+the original C++ Air was written with expressiveness as a top priority, Air.js
+is liberal in its use of modern ES6 idioms whenever this helps make the code
+more readable. Unlike the original C++ Air, Air.js doesn't exploit a deep
+understanding of compilers to make the code easy to compile.
+
+## Design
+
+Air.js runs one of the more expensive Air phases, Air::allocateStack(). This
+turns abstract stack references into concrete stack references, by selecting
+how to lay out stack slots in the stack frame. This requires liveness analysis
+and an interference graph.
+
+Air.js relies on three major ES6 features more so than most of the others:
+
+- Arrow functions. Like the C++ Air, Air.js uses a functional style of
+  iterating most non-trivial data-structures:
+
+        inst.forEachArg((arg, role, type, width) => ...)
+  
+  This is because the functional style allows the callbacks to mutate the data
+  being iterated: if the callback returns a non-null value, forEachArg() will
+  replace the argument with that value. This would not have been possible with
+  for-of.
+
+- For-of. Many Air data structures are amenable to for-of iteration. While the
+  innermost loops tend to use functional iteration, pretty much all of the
+  outer logic uses for-of heavily. For example:
+
+        for (let block of code) // Iterate over the basic blocks
+            for (let inst of block) // Iterate over the instructions in a block
+                ...
+
+- Map/Set. The liveness analysis and Air::allocateStack() rely on maps and
+  sets. For example, we use a liveAtHead map that is keyed by basic block. Its
+  values are sets of live stack slots. This is a relatively crude way of doing
+  liveness, but it is exactly how the original Air::LivenessAnalysis worked, so
+  we view it as being quite faithful to how a sensible programmer might use Map
+  and Set.
+
+Air.js also uses some other ES6 features. For example, it uses a Proxy
+in one place, though we doubt that it's on a critical path. Air.js uses classes
+and let/const extensively, as well a symbols. Symbols are used as enumeration
+elements, and so they frequently show up as cases in switch statements.
+
+The workflow of an Air.js run is pretty simple: we do 150 runs of allocateStack
+on four IR payloads.
+
+Each IR payload is a large piece of ES6 code that constructs an Air.js Code
+object, complete with blocks, temporaries, stack slots, and instructions. These
+payloads are generated by running Air::dumpAsJS() phase just prior to the
+native allocateStack phase on the largest hot function in four major JS
+benchmarks according to JavaScriptCore's internal profiling:
+
+- Octane/GBEmu, the executeIteration function.
+- Kraken/imaging-gaussian-blur, the gaussianBlur function.
+- Octane/Typescript, the scanIdentifier function,
+- Air.js, an anonymous closure identified by our profiler as ACLj8C.
+
+These payloads allow Air.js to precisely replay allocateStack on those actual
+functions.
+
+It was an a priori goal of Air.js to spend most of the time in the
+allocateStack phase. This is a faithful reproduction of the C++ allocateStack
+phase, including its use of an abstract liveness analysis. It's abstract in the
+sense that the same liveness algorithm can be reused for temporaries,
+registers, or stack slots. In C++ this meant using templates, while in ES6 it
+means more run-time dynamic dispatch.
+
+Each IR payload is executable code that allocates the IR, and about 15% of
+benchmark execution time is spent in that code. This is significant, but having
+learned this, we don't feel that it would be honest to try to change the
+efficiency of payload initialization. What if the payload initialization was
+more expensive on our engine than others? If it was, then such a change would
+not be fair.
+
+Air.js validates its results. We added a Code hashing capability to both the
+C++ Air and Air.js, and we assert each payload looks identical after
+allocateStack to what it would have looked like after the original C++
+allocateStack. We also validate that payloads hash properly before
+allcoateStack, to help catch bugs during payload initialization. We have not
+measured how long hashing takes, but it's a O(N) operation, while allocateStack
+is closer to O(N^2). We suspect that barring some engine pathologies, hashing
+should be much faster than allocateStack, and allocateStack should be where the
+bulk of time is spent.
+
+## License
+
+Copyright (C) 2016 Apple Inc. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions
+are met:
+
+1. Redistributions of source code must retain the above copyright
+   notice, this list of conditions and the following disclaimer.
+
+2. Redistributions in binary form must reproduce the above copyright
+   notice, this list of conditions and the following disclaimer in the
+   documentation and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
+EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE INC. OR
+CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 
+
+## Summary
+
+At the time that Air.js was written, we weren't happy with the ES6 benchmarks
+that were available to us. Air.js uses some ES6 features in anger, in the hope
+that we can learn about possible optimization strategies by looking at this and
+other benchmarks.
new file mode 100644
--- /dev/null
+++ b/third_party/webkit/PerformanceTests/ARES-6/Air/airjs-tests.yaml
@@ -0,0 +1,28 @@
+# Copyright (C) 2016 Apple Inc. All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+#
+# 1.  Redistributions of source code must retain the above copyright
+#     notice, this list of conditions and the following disclaimer. 
+# 2.  Redistributions in binary form must reproduce the above copyright
+#     notice, this list of conditions and the following disclaimer in the
+#     documentation and/or other materials provided with the distribution. 
+#
+# THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
+# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+# DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
+# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+- path: .
+  tests:
+    - stress-test.js
+  cmd: defaultRunNoisyTest unless parseRunCommands
+
new file mode 100644
--- /dev/null
+++ b/third_party/webkit/PerformanceTests/ARES-6/Air/all.js
@@ -0,0 +1,18 @@
+"use strict";
+
+load("symbols.js");
+load("tmp_base.js");
+load("arg.js");
+load("basic_block.js");
+load("code.js");
+load("frequented_block.js");
+load("inst.js");
+load("opcode.js");
+load("reg.js");
+load("stack_slot.js");
+load("tmp.js");
+load("util.js");
+load("custom.js");
+load("liveness.js");
+load("insertion_set.js");
+load("allocate_stack.js");
new file mode 100644
--- /dev/null
+++ b/third_party/webkit/PerformanceTests/ARES-6/Air/allocate_stack.js
@@ -0,0 +1,252 @@
+/*
+ * Copyright (C) 2016 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE INC. OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 
+ */
+"use strict";
+
+function allocateStack(code)
+{
+    if (code.frameSize)
+        throw new Error("Frame size already determined");
+    
+    function attemptAssignment(slot, offsetFromFP, otherSlots)
+    {
+        if (offsetFromFP > 0)
+            throw new Error("Expect negative offset");
+        
+        offsetFromFP = -roundUpToMultipleOf(slot.alignment, -offsetFromFP);
+        
+        for (let otherSlot of otherSlots) {
+            if (!otherSlot.offsetFromFP)
+                continue;
+            let overlap = rangesOverlap(
+                offsetFromFP,
+                offsetFromFP + slot.byteSize,
+                otherSlot.offsetFromFP,
+                otherSlot.offsetFromFP + otherSlot.byteSize);
+            if (overlap)
+                return false;
+        }
+        
+        slot.setOffsetFromFP(offsetFromFP);
+        return true;
+    }
+    
+    function assign(slot, otherSlots)
+    {
+        if (attemptAssignment(slot, -slot.byteSize, otherSlots))
+            return;
+        
+        for (let otherSlot of otherSlots) {
+            if (!otherSlot.offsetFromFP)
+                continue;
+            if (attemptAssignment(slot, otherSlot.offsetFromFP - slot.byteSize, otherSlots))
+                return;
+        }
+        
+        throw new Error("Assignment failed");
+    }
+    
+    // Allocate all of the escaped slots in order. This is kind of a crazy algorithm to allow for
+    // the possibility of stack slots being assigned frame offsets before we even get here.
+    let assignedEscapedStackSlots = [];
+    let escapedStackSlotsWorklist = [];
+    for (let slot of code.stackSlots) {
+        if (slot.isLocked) {
+            if (slot.offsetFromFP)
+                assignedEscapedStackSlots.push(slot);
+            else
+                escapedStackSlotsWorklist.push(slot);
+        } else {
+            if (slot.offsetFromFP)
+                throw new Error("Offset already assigned");
+        }
+    }
+    
+    // This is a fairly espensive loop, but it's OK because we'll usually only have a handful of
+    // escaped stack slots.
+    while (escapedStackSlotsWorklist.length) {
+        let slot = escapedStackSlotsWorklist.pop();
+        assign(slot, assignedEscapedStackSlots);
+        assignedEscapedStackSlots.push(slot);
+    }
+    
+    // Now we handle the spill slots.
+    let liveness = new Liveness(StackSlot, code);
+    let interference = new Map();
+    for (let slot of code.stackSlots)
+        interference.set(slot, new Set());
+    let slots = [];
+    
+    for (let block of code) {
+        let localCalc = liveness.localCalc(block);
+        
+        function interfere(instIndex)
+        {
+            Inst.forEachDef(
+                StackSlot, block.get(instIndex), block.get(instIndex + 1),
+                (slot, role, type, width) => {
+                    if (!slot.isSpill)
+                        return;
+                    
+                    for (let otherSlot of localCalc.liveSet) {
+                        interference.get(slot).add(otherSlot);
+                        interference.get(otherSlot).add(slot);
+                    }
+                });
+        }
+        
+        for (let instIndex = block.size; instIndex--;) {
+            // Kill dead stores. For simplicity we say that a store is killable if it has only late
+            // defs and those late defs are to things that are dead right now. We only do that
+            // because that's the only kind of dead stack store we will see here.
+            let inst = block.at(instIndex);
+            if (!inst.hasNonArgEffects) {
+                let ok = true;
+                inst.forEachArg((arg, role, type, width) => {
+                    if (Arg.isEarlyDef(role)) {
+                        ok = false;
+                        return;
+                    }
+                    if (!Arg.isLateDef(role))
+                        return;
+                    if (!arg.isStack) {
+                        ok = false;
+                        return;
+                    }
+                    
+                    let slot = arg.stackSlot;
+                    if (!slot.isSpill) {
+                        ok = false;
+                        return;
+                    }
+                    
+                    if (localCalc.liveSet.has(slot)) {
+                        ok = false;
+                        return;
+                    }
+                });
+                if (ok)
+                    inst.clear();
+            }
+            
+            interfere(instIndex);
+            localCalc.execute(instIndex);
+        }
+        interfere(-1);
+        
+        removeAllMatching(block.insts, inst => inst.opcode == Nop);
+    }
+    
+    // Now we assign stack locations. At its heart this algorithm is just first-fit. For each
+    // StackSlot we just want to find the offsetFromFP that is closest to zero while ensuring no
+    // overlap with other StackSlots that this overlaps with.
+    for (let slot of code.stackSlots) {
+        if (slot.offsetFromFP)
+            continue;
+        
+        assign(slot, assignedEscapedStackSlots.concat(Array.from(interference.get(slot))));
+    }
+    
+    // Figure out how much stack we're using for stack slots.
+    let frameSizeForStackSlots = 0;
+    for (let slot of code.stackSlots) {
+        frameSizeForStackSlots = Math.max(
+            frameSizeForStackSlots,
+            -slot.offsetFromFP);
+    }
+    
+    frameSizeForStackSlots = roundUpToMultipleOf(stackAlignmentBytes, frameSizeForStackSlots);
+
+    // No we need to deduce how much argument area we need.
+    for (let block of code) {
+        for (let inst of block) {
+            for (let arg of inst.args) {
+                if (arg.isCallArg) {
+                    // For now, we assume that we use 8 bytes of the call arg. But that's not
+                    // such an awesome assumption.
+                    // FIXME: https://bugs.webkit.org/show_bug.cgi?id=150454
+                    if (arg.offset < 0)
+                        throw new Error("Did not expect negative offset for callArg");
+                    code.requestCallArgAreaSize(arg.offset + 8);
+                }
+            }
+        }
+    }
+    
+    code.setFrameSize(frameSizeForStackSlots + code.callArgAreaSize);
+    
+    // Finally transform the code to use Addrs instead of StackSlots. This is a lossless
+    // transformation since we can search the StackSlots array to figure out which StackSlot any
+    // offset-from-FP refers to.
+
+    // FIXME: This may produce addresses that aren't valid if we end up with a ginormous stack frame.
+    // We would have to scavenge for temporaries if this happened. Fortunately, this case will be
+    // extremely rare so we can do crazy things when it arises.
+    // https://bugs.webkit.org/show_bug.cgi?id=152530
+    
+    let insertionSet = new InsertionSet();
+    for (let block of code) {
+        for (let instIndex = 0; instIndex < block.size; ++instIndex) {
+            let inst = block.at(instIndex);
+            inst.forEachArg((arg, role, type, width) => {
+                function stackAddr(offset)
+                {
+                    return Arg.createStackAddr(offset, code.frameSize, width);
+                }
+                
+                switch (arg.kind) {
+                case Arg.Stack: {
+                    let slot = arg.stackSlot;
+                    if (Arg.isZDef(role)
+                        && slot.isSpill
+                        && slot.byteSize > Arg.bytes(width)) {
+                        // Currently we only handle this simple case because it's the only one
+                        // that arises: ZDef's are only 32-bit right now. So, when we hit these
+                        // assertions it means that we need to implement those other kinds of
+                        // zero fills.
+                        if (slot.byteSize != 8) {
+                            throw new Error(
+                                `Bad spill slot size for ZDef: ${slot.byteSize}, width is ${width}`);
+                        }
+                        if (width != 32)
+                            throw new Error("Bad width for ZDef");
+                        
+                        insertionSet.insert(
+                            instIndex + 1,
+                            new Inst(
+                                StoreZero32,
+                                [stackAddr(arg.offset + 4 + slot.offsetFromFP)]));
+                    }
+                    return stackAddr(arg.offset + slot.offsetFromFP);
+                }
+                case Arg.CallArg:
+                    return stackAddr(arg.offset - code.frameSize);
+                default:
+                    break;
+                }
+            });
+        }
+        insertionSet.execute(block.insts);
+    }
+}
new file mode 100644
--- /dev/null
+++ b/third_party/webkit/PerformanceTests/ARES-6/Air/arg.js
@@ -0,0 +1,1064 @@
+/*
+ * Copyright (C) 2016 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE INC. OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 
+ */
+"use strict";
+
+class Arg {
+    constructor()
+    {
+        this._kind = Arg.Invalid;
+    }
+    
+    static isAnyUse(role)
+    {
+        switch (role) {
+        case Arg.Use:
+        case Arg.ColdUse:
+        case Arg.UseDef:
+        case Arg.UseZDef:
+        case Arg.LateUse:
+        case Arg.LateColdUse:
+        case Arg.Scratch:
+            return true;
+        case Arg.Def:
+        case Arg.ZDef:
+        case Arg.UseAddr:
+        case Arg.EarlyDef:
+            return false;
+        default:
+            throw new Error("Bad role");
+        }
+    }
+    
+    static isColdUse(role)
+    {
+        switch (role) {
+        case Arg.ColdUse:
+        case Arg.LateColdUse:
+            return true;
+        case Arg.Use:
+        case Arg.UseDef:
+        case Arg.UseZDef:
+        case Arg.LateUse:
+        case Arg.Def:
+        case Arg.ZDef:
+        case Arg.UseAddr:
+        case Arg.Scratch:
+        case Arg.EarlyDef:
+            return false;
+        default:
+            throw new Error("Bad role");
+        }
+    }
+    
+    static isWarmUse(role)
+    {
+        return Arg.isAnyUse(role) && !Arg.isColdUse(role);
+    }
+    
+    static cooled(role)
+    {
+        switch (role) {
+        case Arg.ColdUse:
+        case Arg.LateColdUse:
+        case Arg.UseDef:
+        case Arg.UseZDef:
+        case Arg.Def:
+        case Arg.ZDef:
+        case Arg.UseAddr:
+        case Arg.Scratch:
+        case Arg.EarlyDef:
+            return role;
+        case Arg.Use:
+            return Arg.ColdUse;
+        case Arg.LateUse:
+            return Arg.LateColdUse;
+        default:
+            throw new Error("Bad role");
+        }
+    }
+
+    static isEarlyUse(role)
+    {
+        switch (role) {
+        case Arg.Use:
+        case Arg.ColdUse:
+        case Arg.UseDef:
+        case Arg.UseZDef:
+            return true;
+        case Arg.Def:
+        case Arg.ZDef:
+        case Arg.UseAddr:
+        case Arg.LateUse:
+        case Arg.LateColdUse:
+        case Arg.Scratch:
+        case Arg.EarlyDef:
+            return false;
+        default:
+            throw new Error("Bad role");
+        }
+    }
+    
+    static isLateUse(role)
+    {
+        switch (role) {
+        case Arg.LateUse:
+        case Arg.LateColdUse:
+        case Arg.Scratch:
+            return true;
+        case Arg.ColdUse:
+        case Arg.Use:
+        case Arg.UseDef:
+        case Arg.UseZDef:
+        case Arg.Def:
+        case Arg.ZDef:
+        case Arg.UseAddr:
+        case Arg.EarlyDef:
+            return false;
+        default:
+            throw new Error("Bad role");
+        }
+    }
+    
+    static isAnyDef(role)
+    {
+        switch (role) {
+        case Arg.Use:
+        case Arg.ColdUse:
+        case Arg.UseAddr:
+        case Arg.LateUse:
+        case Arg.LateColdUse:
+            return false;
+        case Arg.Def:
+        case Arg.UseDef:
+        case Arg.ZDef:
+        case Arg.UseZDef:
+        case Arg.EarlyDef:
+        case Arg.Scratch:
+            return true;
+        default:
+            throw new Error("Bad role");
+        }
+    }
+    
+    static isEarlyDef(role)
+    {
+        switch (role) {
+        case Arg.Use:
+        case Arg.ColdUse:
+        case Arg.UseAddr:
+        case Arg.LateUse:
+        case Arg.Def:
+        case Arg.UseDef:
+        case Arg.ZDef:
+        case Arg.UseZDef:
+        case Arg.LateColdUse:
+            return false;
+        case Arg.EarlyDef:
+        case Arg.Scratch:
+            return true;
+        default:
+            throw new Error("Bad role");
+        }
+    }
+    
+    static isLateDef(role)
+    {
+        switch (role) {
+        case Arg.Use:
+        case Arg.ColdUse:
+        case Arg.UseAddr:
+        case Arg.LateUse:
+        case Arg.EarlyDef:
+        case Arg.Scratch:
+        case Arg.LateColdUse:
+            return false;
+        case Arg.Def:
+        case Arg.UseDef:
+        case Arg.ZDef:
+        case Arg.UseZDef:
+            return true;
+        default:
+            throw new Error("Bad role");
+        }
+    }
+    
+    static isZDef(role)
+    {
+        switch (role) {
+        case Arg.Use:
+        case Arg.ColdUse:
+        case Arg.UseAddr:
+        case Arg.LateUse:
+        case Arg.Def:
+        case Arg.UseDef:
+        case Arg.EarlyDef:
+        case Arg.Scratch:
+        case Arg.LateColdUse:
+            return false;
+        case Arg.ZDef:
+        case Arg.UseZDef:
+            return true;
+        default:
+            throw new Error("Bad role");
+        }
+    }
+    
+    static typeForB3Type(type)
+    {
+        switch (type) {
+        case Int32:
+        case Int64:
+            return GP;
+        case Float:
+        case Double:
+            return FP;
+        default:
+            throw new Error("Bad B3 type");
+        }
+    }
+    
+    static widthForB3Type(type)
+    {
+        switch (type) {
+        case Int32:
+        case Float:
+            return 32;
+        case Int64:
+        case Double:
+            return 64;
+        default:
+            throw new Error("Bad B3 type");
+        }
+    }
+    
+    static conservativeWidth(type)
+    {
+        return type == GP ? Ptr : 64;
+    }
+    
+    static minimumWidth(type)
+    {
+        return type == GP ? 8 : 32;
+    }
+    
+    static bytes(width)
+    {
+        return width / 8;
+    }
+    
+    static widthForBytes(bytes)
+    {
+        switch (bytes) {
+        case 0:
+        case 1:
+            return 8;
+        case 2:
+            return 16;
+        case 3:
+        case 4:
+            return 32;
+        default:
+            if (bytes > 8)
+                throw new Error("Bad number of bytes");
+            return 64;
+        }
+    }
+    
+    static createTmp(tmp)
+    {
+        let result = new Arg();
+        result._kind = Arg.Tmp;
+        result._tmp = tmp;
+        return result;
+    }
+    
+    static fromReg(reg)
+    {
+        return Arg.createTmp(reg);
+    }
+    
+    static createImm(value)
+    {
+        let result = new Arg();
+        result._kind = Arg.Imm;
+        result._value = value;
+        return result;
+    }
+    
+    static createBigImm(lowValue, highValue = 0)
+    {
+        let result = new Arg();
+        result._kind = Arg.BigImm;
+        result._lowValue = lowValue;
+        result._highValue = highValue;
+        return result;
+    }
+    
+    static createBitImm(value)
+    {
+        let result = new Arg();
+        result._kind = Arg.BitImm;
+        result._value = value;
+        return result;
+    }
+    
+    static createBitImm64(lowValue, highValue = 0)
+    {
+        let result = new Arg();
+        result._kind = Arg.BitImm64;
+        result._lowValue = lowValue;
+        result._highValue = highValue;
+        return result;
+    }
+    
+    static createAddr(base, offset = 0)
+    {
+        let result = new Arg();
+        result._kind = Arg.Addr;
+        result._base = base;
+        result._offset = offset;
+        return result;
+    }
+    
+    static createStack(slot, offset = 0)
+    {
+        let result = new Arg();
+        result._kind = Arg.Stack;
+        result._slot = slot;
+        result._offset = offset;
+        return result;
+    }
+    
+    static createCallArg(offset)
+    {
+        let result = new Arg();
+        result._kind = Arg.CallArg;
+        result._offset = offset;
+        return result;
+    }
+    
+    static createStackAddr(offsetFromFP, frameSize, width)
+    {
+        let result = Arg.createAddr(Reg.callFrameRegister, offsetFromFP);
+        if (!result.isValidForm(width))
+            result = Arg.createAddr(Reg.stackPointerRegister, offsetFromFP + frameSize);
+        return result;
+    }
+    
+    static isValidScale(scale, width)
+    {
+        switch (scale) {
+        case 1:
+        case 2:
+        case 4:
+        case 8:
+            return true;
+        default:
+            return false;
+        }
+    }
+    
+    static logScale(scale)
+    {
+        switch (scale) {
+        case 1:
+            return 0;
+        case 2:
+            return 1;
+        case 4:
+            return 2;
+        case 8:
+            return 3;
+        default:
+            throw new Error("Bad scale");
+        }
+    }
+    
+    static createIndex(base, index, scale = 1, offset = 0)
+    {
+        let result = new Arg();
+        result._kind = Arg.Index;
+        result._base = base;
+        result._index = index;
+        result._scale = scale;
+        result._offset = offset;
+        return result;
+    }
+    
+    static createRelCond(condition)
+    {
+        let result = new Arg();
+        result._kind = Arg.RelCond;
+        result._condition = condition;
+        return result;
+    }
+    
+    static createResCond(condition)
+    {
+        let result = new Arg();
+        result._kind = Arg.ResCond;
+        result._condition = condition;
+        return result;
+    }
+    
+    static createDoubleCond(condition)
+    {
+        let result = new Arg();
+        result._kind = Arg.DoubleCond;
+        result._condition = condition;
+        return result;
+    }
+    
+    static createWidth(width)
+    {
+        let result = new Arg();
+        result._kind = Arg.Width;
+        result._width = width;
+        return result;
+    }
+    
+    static createSpecial()
+    {
+        let result = new Arg();
+        result._kind = Arg.Special;
+        return result;
+    }
+    
+    get kind() { return this._kind; }
+    get isTmp() { return this._kind == Arg.Tmp; }
+    get isImm() { return this._kind == Arg.Imm; }
+    get isBigImm() { return this._kind == Arg.BigImm; }
+    get isBitImm() { return this._kind == Arg.BitImm; }
+    get isBitImm64() { return this._kind == Arg.BitImm64; }
+    get isSomeImm()
+    {
+        switch (this._kind) {
+        case Arg.Imm:
+        case Arg.BitImm:
+            return true;
+        default:
+            return false;
+        }
+    }
+    get isSomeBigImm()
+    {
+        switch (this._kind) {
+        case Arg.BigImm:
+        case Arg.BitImm64:
+            return true;
+        default:
+            return false;
+        }
+    }
+    get isAddr() { return this._kind == Arg.Addr; }
+    get isStack() { return this._kind == Arg.Stack; }
+    get isCallArg() { return this._kind == Arg.CallArg; }
+    get isIndex() { return this._kind == Arg.Index; }
+    get isMemory()
+    {
+        switch (this._kind) {
+        case Arg.Addr:
+        case Arg.Stack:
+        case Arg.CallArg:
+        case Arg.Index:
+            return true;
+        default:
+            return false;
+        }
+    }
+    get isStackMemory()
+    {
+        switch (this._kind) {
+        case Arg.Addr:
+            return this._base == Reg.callFrameRegister
+                || this._base == Reg.stackPointerRegister;
+        case Arg.Stack:
+        case Arg.CallArg:
+            return true;
+        default:
+            return false;
+        }
+    }
+    get isRelCond() { return this._kind == Arg.RelCond; }
+    get isResCond() { return this._kind == Arg.ResCond; }
+    get isDoubleCond() { return this._kind == Arg.DoubleCond; }
+    get isCondition()
+    {
+        switch (this._kind) {
+        case Arg.RelCond:
+        case Arg.ResCond:
+        case Arg.DoubleCond:
+            return true;
+        default:
+            return false;
+        }
+    }
+    get isWidth() { return this._kind == Arg.Width; }
+    get isSpecial() { return this._kind == Arg.Special; }
+    get isAlive() { return this.isTmp || this.isStack; }
+    
+    get tmp()
+    {
+        if (this._kind != Arg.Tmp)
+            throw new Error("Called .tmp for non-tmp");
+        return this._tmp;
+    }
+    
+    get value()
+    {
+        if (!this.isSomeImm)
+            throw new Error("Called .value for non-imm");
+        return this._value;
+    }
+    
+    get lowValue()
+    {
+        if (!this.isSomeBigImm)
+            throw new Error("Called .lowValue for non-big-imm");
+        return this._lowValue;
+    }
+    
+    get highValue()
+    {
+        if (!this.isSomeBigImm)
+            throw new Error("Called .highValue for non-big-imm");
+        return this._highValue;
+    }
+    
+    get base()
+    {
+        switch (this._kind) {
+        case Arg.Addr:
+        case Arg.Index:
+            return this._base;
+        default:
+            throw new Error("Called .base for non-address");
+        }
+    }
+    
+    get hasOffset() { return this.isMemory; }
+    
+    get offset()
+    {
+        switch (this._kind) {
+        case Arg.Addr:
+        case Arg.Index:
+        case Arg.Stack:
+        case Arg.CallArg:
+            return this._offset;
+        default:
+            throw new Error("Called .offset for non-address");
+        }
+    }
+    
+    get stackSlot()
+    {
+        if (this._kind != Arg.Stack)
+            throw new Error("Called .stackSlot for non-address");
+        return this._slot;
+    }
+    
+    get index()
+    {
+        if (this._kind != Arg.Index)
+            throw new Error("Called .index for non-Index");
+        return this._index;
+    }
+    
+    get scale()
+    {
+        if (this._kind != Arg.Index)
+            throw new Error("Called .scale for non-Index");
+        return this._scale;
+    }
+    
+    get logScale()
+    {
+        return Arg.logScale(this.scale);
+    }
+    
+    get width()
+    {
+        if (this._kind != Arg.Width)
+            throw new Error("Called .width for non-Width");
+        return this._width;
+    }
+    
+    get isGPTmp() { return this.isTmp && this.tmp.isGP; }
+    get isFPTmp() { return this.isTmp && this.tmp.isFP; }
+    
+    get isGP()
+    {
+        switch (this._kind) {
+        case Arg.Imm:
+        case Arg.BigImm:
+        case Arg.BitImm:
+        case Arg.BitImm64:
+        case Arg.Addr:
+        case Arg.Index:
+        case Arg.Stack:
+        case Arg.CallArg:
+        case Arg.RelCond:
+        case Arg.ResCond:
+        case Arg.DoubleCond:
+        case Arg.Width:
+        case Arg.Special:
+            return true;
+        case Arg.Tmp:
+            return this.isGPTmp;
+        case Arg.Invalid:
+            return false;
+        default:
+            throw new Error("Bad kind");
+        }
+    }
+    
+    get isFP()
+    {
+        switch (this._kind) {
+        case Arg.Imm:
+        case Arg.BitImm:
+        case Arg.BitImm64:
+        case Arg.RelCond:
+        case Arg.ResCond:
+        case Arg.DoubleCond:
+        case Arg.Width:
+        case Arg.Special:
+        case Arg.Invalid:
+            return false;
+        case Arg.Addr:
+        case Arg.Index:
+        case Arg.Stack:
+        case Arg.CallArg:
+        case Arg.BigImm:
+            return true;
+        case Arg.Tmp:
+            return this.isFPTmp;
+        default:
+            throw new Error("Bad kind");
+        }
+    }
+    
+    get hasType()
+    {
+        switch (this._kind) {
+        case Arg.Imm:
+        case Arg.BitImm:
+        case Arg.BitImm64:
+        case Arg.Tmp:
+            return true;
+        default:
+            return false;
+        }
+    }
+    
+    get type()
+    {
+        return this.isGP ? GP : FP;
+    }
+    
+    isType(type)
+    {
+        switch (type) {
+        case Arg.GP:
+            return this.isGP;
+        case Arg.FP:
+            return this.isFP;
+        default:
+            throw new Error("Bad type");
+        }
+    }
+    
+    isCompatibleType(other)
+    {
+        if (this.hasType)
+            return other.isType(this.type);
+        if (other.hasType)
+            return this.isType(other.type);
+        return true;
+    }
+    
+    get isGPR() { return this.isTmp && this.tmp.isGPR; }
+    get gpr() { return this.tmp.gpr; }
+    get isFPR() { return this.isTmp && this.tmp.isFPR; }
+    get fpr() { return this.tmp.fpr; }
+    get isReg() { return this.isTmp && this.tmp.isReg; }
+    get reg() { return this.tmp.reg; }
+    
+    static isValidImmForm(value)
+    {
+        return isRepresentableAsInt32(value);
+    }
+    static isValidBitImmForm(value)
+    {
+        return isRepresentableAsInt32(value);
+    }
+    static isValidBitImm64Form(value)
+    {
+        return isRepresentableAsInt32(value);
+    }
+    
+    static isValidAddrForm(offset, width)
+    {
+        return true;
+    }
+    
+    static isValidIndexForm(scale, offset, width)
+    {
+        if (!isValidScale(scale, width))
+            return false;
+        return true;
+    }
+    
+    isValidForm(width)
+    {
+        switch (this._kind) {
+        case Arg.Invalid:
+            return false;
+        case Arg.Tmp:
+            return true;
+        case Arg.Imm:
+            return Arg.isValidImmForm(this.value);
+        case Arg.BigImm:
+            return true;
+        case Arg.BitImm:
+            return Arg.isValidBitImmForm(this.value);
+        case Arg.BitImm64:
+            return Arg.isValidBitImm64Form(this.value);
+        case Arg.Addr:
+        case Arg.Stack:
+        case Arg.CallArg:
+            return Arg.isValidAddrForm(this.offset, width);
+        case Arg.Index:
+            return Arg.isValidIndexForm(this.scale, this.offset, width);
+        case Arg.RelCond:
+        case Arg.ResCond:
+        case Arg.DoubleCond:
+        case Arg.Width:
+        case Arg.Special:
+            return true;
+        default:
+            throw new Error("Bad kind");
+        }
+    }
+    
+    forEachTmpFast(func)
+    {
+        switch (this._kind) {
+        case Arg.Tmp: {
+            let replacement;
+            if (replacement = func(this._tmp))
+                return Arg.createTmp(replacement);
+            break;
+        }
+        case Arg.Addr: {
+            let replacement;
+            if (replacement = func(this._base))
+                return Arg.createAddr(replacement, this._offset);
+            break;
+        }
+        case Arg.Index: {
+            let baseReplacement = func(this._base);
+            let indexReplacement = func(this._index);
+            if (baseReplacement || indexReplacement) {
+                return Arg.createIndex(
+                    baseReplacement ? baseReplacement : this._base,
+                    indexReplacement ? indexReplacement : this._index,
+                    this._scale, this._offset);
+            }
+            break;
+        }
+        default:
+            break;
+        }
+    }
+    
+    usesTmp(expectedTmp)
+    {
+        let usesTmp = false;
+        forEachTmpFast(tmp => {
+            usesTmp |= tmp == expectedTmp;
+        });
+        return usesTmp;
+    }
+    
+    forEachTmp(role, type, width, func)
+    {
+        switch (this._kind) {
+        case Arg.Tmp: {
+            let replacement;
+            if (replacement = func(this._tmp, role, type, width))
+                return Arg.createTmp(replacement);
+            break;
+        }
+        case Arg.Addr: {
+            let replacement;
+            if (replacement = func(this._base, Arg.Use, GP, role == Arg.UseAddr ? width : Ptr))
+                return Arg.createAddr(replacement, this._offset);
+            break;
+        }
+        case Arg.Index: {
+            let baseReplacement = func(this._base, Arg.Use, GP, role == Arg.UseAddr ? width : Ptr);
+            let indexReplacement = func(this._index, Arg.Use, GP, role == Arg.UseAddr ? width : Ptr);
+            if (baseReplacement || indexReplacement) {
+                return Arg.createIndex(
+                    baseReplacement ? baseReplacement : this._base,
+                    indexReplacement ? indexReplacement : this._index,
+                    this._scale, this._offset);
+            }
+            break;
+        }
+        default:
+            break;
+        }
+    }
+    
+    is(thing) { return !!thing.extract(this); }
+    as(thing) { return thing.extract(this); }
+    
+    // This lets you say things like:
+    // arg.forEach(Tmp | Reg | Arg | StackSlot, ...)
+    //
+    // It's used for abstract liveness analysis.
+    forEachFast(thing, func)
+    {
+        return thing.forEachFast(this, func);
+    }
+    forEach(thing, role, type, width, func)
+    {
+        return thing.forEach(this, role, type, width, func);
+    }
+    
+    static extract(arg) { return arg; }
+    static forEachFast(arg, func) { return func(arg); }
+    static forEach(arg, role, type, width, func) { return func(arg, role, type, width); }
+
+    get condition()
+    {
+        switch (this._kind) {
+        case Arg.RelCond:
+        case Arg.ResCond:
+        case Arg.DoubleCond:
+            return this._condition;
+        default:
+            throw new Error("Called .condition for non-condition");
+        }
+    }
+    
+    get isInvertible()
+    {
+        switch (this._kind) {
+        case Arg.RelCond:
+        case Arg.DoubleCold:
+            return true;
+        case Arg.ResCond:
+            switch (this._condition) {
+            case Zero:
+            case NonZero:
+            case Signed:
+            case PositiveOrZero:
+                return true;
+            default:
+                return false;
+            }
+        default:
+            return false;
+        }
+    }
+    
+    static kindCode(kind)
+    {
+        switch (kind) {
+        case Arg.Invalid:
+            return 0;
+        case Arg.Tmp:
+            return 1;
+        case Arg.Imm:
+            return 2;
+        case Arg.BigImm:
+            return 3;
+        case Arg.BitImm:
+            return 4;
+        case Arg.BitImm64:
+            return 5;
+        case Arg.Addr:
+            return 6;
+        case Arg.Stack:
+            return 7;
+        case Arg.CallArg:
+            return 8;
+        case Arg.Index:
+            return 9;
+        case Arg.RelCond:
+            return 10;
+        case Arg.ResCond:
+            return 11;
+        case Arg.DoubleCond:
+            return 12;
+        case Arg.Special:
+            return 13;
+        case Arg.WidthArg:
+            return 14;
+        default:
+            throw new Error("Bad kind");
+        }
+    }
+    
+    hash()
+    {
+        let result = Arg.kindCode(this._kind);
+        
+        switch (this._kind) {
+        case Arg.Invalid:
+        case Arg.Special:
+            break;
+        case Arg.Tmp:
+            result += this._tmp.hash();
+            result |= 0;
+            break;
+        case Arg.Imm:
+        case Arg.BitImm:
+            result += this._value;
+            result |= 0;
+            break;
+        case Arg.BigImm:
+        case Arg.BitImm64:
+            result += this._lowValue;
+            result |= 0;
+            result += this._highValue;
+            result |= 0;
+            break;
+        case Arg.CallArg:
+            result += this._offset;
+            result |= 0;
+            break;
+        case Arg.RelCond:
+            result += relCondCode(this._condition);
+            result |= 0;
+            break;
+        case Arg.ResCond:
+            result += resCondCode(this._condition);
+            result |= 0;
+            break;
+        case Arg.DoubleCond:
+            result += doubleCondCode(this._condition);
+            result |= 0;
+            break;
+        case Arg.WidthArg:
+            result += this._width;
+            result |= 0;
+            break;
+        case Arg.Addr:
+            result += this._offset;
+            result |= 0;
+            result += this._base.hash();
+            result |= 0;
+            break;
+        case Arg.Index:
+            result += this._offset;
+            result |= 0;
+            result += this._scale;
+            result |= 0;
+            result += this._base.hash();
+            result |= 0;
+            result += this._index.hash();
+            result |= 0;
+            break;
+        case Arg.Stack:
+            result += this._offset;
+            result |= 0;
+            result += this.stackSlot.index;
+            result |= 0;
+            break;
+        }
+        
+        return result >>> 0;
+    }
+    
+    toString()
+    {
+        switch (this._kind) {
+        case Arg.Invalid:
+            return "<invalid>";
+        case Arg.Tmp:
+            return this._tmp.toString();
+        case Arg.Imm:
+            return "$" + this._value;
+        case Arg.BigImm:
+        case Arg.BitImm64:
+            return "$0x" + this._highValue.toString(16) + ":" + this._lowValue.toString(16);
+        case Arg.Addr:
+            return "" + (this._offset ? this._offset : "") + "(" + this._base + ")";
+        case Arg.Index:
+            return "" + (this._offset ? this._offset : "") + "(" + this._base +
+                "," + this._index + (this._scale == 1 ? "" : "," + this._scale) + ")";
+        case Arg.Stack:
+            return "" + (this._offset ? this._offset : "") + "(" + this._slot + ")";
+        case Arg.CallArg:
+            return "" + (this._offset ? this._offset : "") + "(callArg)";
+        case Arg.RelCond:
+        case Arg.ResCond:
+        case Arg.DoubleCond:
+            return symbolName(this._condition);
+        case Arg.Special:
+            return "special";
+        case Arg.Width:
+            return "" + this._value;
+        default:
+            throw new Error("Bad kind");
+        }
+    }
+}
+
+// Arg kinds
+Arg.Invalid = Symbol("Invalid");
+Arg.Tmp = Symbol("Tmp");
+Arg.Imm = Symbol("Imm");
+Arg.BigImm = Symbol("BigImm");
+Arg.BitImm = Symbol("BitImm");
+Arg.BitImm64 = Symbol("BitImm64");
+Arg.Addr = Symbol("Addr");
+Arg.Stack = Symbol("Stack");
+Arg.CallArg = Symbol("CallArg");
+Arg.Index = Symbol("Index");
+Arg.RelCond = Symbol("RelCond");
+Arg.ResCond = Symbol("ResCond");
+Arg.DoubleCond = Symbol("DoubleCond");
+Arg.Special = Symbol("Special");
+Arg.Width = Symbol("Width");
+
+// Arg roles
+Arg.Use = Symbol("Use");
+Arg.ColdUse = Symbol("ColdUse");
+Arg.LateUse = Symbol("LateUse");
+Arg.LateColdUse = Symbol("LateColdUse");
+Arg.Def = Symbol("Def");
+Arg.ZDef = Symbol("ZDef");
+Arg.UseDef = Symbol("UseDef");
+Arg.UseZDef = Symbol("UseZDef");
+Arg.EarlyDef = Symbol("EarlyDef");
+Arg.Scratch = Symbol("Scratch");
+Arg.UseAddr = Symbol("UseAddr");
+
new file mode 100644
--- /dev/null
+++ b/third_party/webkit/PerformanceTests/ARES-6/Air/basic_block.js
@@ -0,0 +1,135 @@
+/*
+ * Copyright (C) 2016 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE INC. OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 
+ */
+"use strict";
+
+class BasicBlock {
+    constructor(index, frequency)
+    {
+        this._index = index;
+        this._frequency = frequency;
+        this._insts = [];
+        this._successors = [];
+        this._predecessors = [];
+    }
+    
+    get index() { return this._index; }
+    get size() { return this._insts.length; }
+    
+    [Symbol.iterator]()
+    {
+        return this._insts[Symbol.iterator]();
+    }
+    
+    at(index)
+    {
+        if (index >= this._insts.length)
+            throw new Error("Out of bounds access");
+        return this._insts[index];
+    }
+    
+    get(index)
+    {
+        if (index < 0 || index >= this._insts.length)
+            return null;
+        return this._insts[index];
+    }
+    
+    get last()
+    {
+        return this._insts[this._insts.length - 1];
+    }
+    
+    get insts() { return this._insts; }
+    
+    append(inst) { this._insts.push(inst); }
+    
+    get numSuccessors() { return this._successors.length; }
+    successor(index) { return this._successors[index]; }
+    get successors() { return this._successors; }
+    
+    successorBlock(index) { return this._successors[index].block; }
+    get successorBlocks()
+    {
+        return new Proxy(this._successors, {
+            get(target, property) {
+                if (typeof property == "string"
+                    && (property | 0) == property)
+                    return target[property].block;
+                return target[property];
+            },
+            
+            set(target, property, value) {
+                if (typeof property == "string"
+                    && (property | 0) == property) {
+                    var oldValue = target[property];
+                    target[property] = new FrequentedBlock(
+                        value, oldValue ? oldValue.frequency : Normal);
+                    return;
+                }
+                
+                target[property] = value;
+            }
+        });
+    }
+    
+    get numPredecessors() { return this._predecessors.length; }
+    predecessor(index) { return this._predecessors[index]; }
+    get predecessors() { return this._predecessors; }
+    
+    get frequency() { return this._frequency; }
+
+    toString()
+    {
+        return "#" + this._index;
+    }
+    
+    get headerString()
+    {
+        let result = "";
+        result += `BB${this}: ; frequency = ${this._frequency}\n`;
+        if (this._predecessors.length)
+            result += "  Predecessors: " + this._predecessors.join(", ") + "\n";
+        return result;
+    }
+    
+    get footerString()
+    {
+        let result = "";
+        if (this._successors.length)
+            result += "  Successors: " + this._successors.join(", ") + "\n";
+        return result;
+    }
+    
+    toStringDeep()
+    {
+        let result = "";
+        result += this.headerString;
+        for (let inst of this)
+            result += `    ${inst}\n`;
+        result += this.footerString;
+        return result;
+    }
+}
+
new file mode 100644
--- /dev/null
+++ b/third_party/webkit/PerformanceTests/ARES-6/Air/benchmark.js
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2016 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE INC. OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 
+ */
+"use strict";
+
+class AirBenchmark {
+    constructor(verbose = 0)
+    {
+        this._verbose = verbose;
+        
+        this._payloads = [
+            {generate: createPayloadGbemuExecuteIteration, earlyHash: 632653144, lateHash: 372715518},
+            {generate: createPayloadImagingGaussianBlurGaussianBlur, earlyHash: 3677819581, lateHash: 1252116304},
+            {generate: createPayloadTypescriptScanIdentifier, earlyHash: 1914852601, lateHash: 837339551},
+            {generate: createPayloadAirJSACLj8C, earlyHash: 1373599940, lateHash: 3981283600}
+        ];
+    }
+    
+    runIteration()
+    {
+        for (let payload of this._payloads) {
+            // Sadly about 17% of our time is in generate. I don't think that's really avoidable,
+            // and I don't mind testing VMs' ability to run such "data definition" code quickly. I
+            // would not have expected it to be so slow from first principles!
+            let code = payload.generate();
+            
+            if (this._verbose) {
+                print("Before allocateStack:");
+                print(code);
+            }
+            
+            let hash = code.hash();
+            if (hash != payload.earlyHash)
+                throw new Error(`Wrong early hash for ${payload.generate.name}: ${hash}`);
+            
+            allocateStack(code);
+            
+            if (this._verbose) {
+                print("After allocateStack:");
+                print(code);
+            }
+
+            hash = code.hash();
+            if (hash != payload.lateHash)
+                throw new Error(`Wrong late hash for ${payload.generate.name}: ${hash}`);
+        }
+    }
+}
+
+function runBenchmark()
+{
+    const verbose = 0;
+    const numIterations = 150;
+    
+    let before = currentTime();
+    
+    let benchmark = new AirBenchmark(verbose);
+    
+    for (let iteration = 0; iteration < numIterations; ++iteration)
+        benchmark.runIteration();
+    
+    let after = currentTime();
+    return after - before;
+}
new file mode 100644
--- /dev/null
+++ b/third_party/webkit/PerformanceTests/ARES-6/Air/code.js
@@ -0,0 +1,122 @@
+/*
+ * Copyright (C) 2016 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE INC. OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE O