Merge mozilla-inbound to mozilla-central a=merge
authorRazvan Maries <rmaries@mozilla.com>
Thu, 15 Nov 2018 00:16:46 +0200
changeset 502838 b0a40093b6b7a0784a6f38b318f597419d86fd8e
parent 502668 4e1b2b7e0c37fb3fcdb6795537af53510b9308ef (current diff)
parent 502837 fcbdfdfb077626ac424844baed754b9f7168ada2 (diff)
child 502839 ca9df8b302950643ea723b66945eb224a7542fc8
child 502853 682c6a11531bda7811aa573f86d4eb30cc2c43a1
child 502931 8affbbef5298262c208006fd22aa6ed650a30d81
push id10290
push userffxbld-merge
push dateMon, 03 Dec 2018 16:23:23 +0000
treeherdermozilla-beta@700bed2445e6 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone65.0a1
first release with
nightly linux32
b0a40093b6b7 / 65.0a1 / 20181114222927 / files
nightly linux64
b0a40093b6b7 / 65.0a1 / 20181114222927 / files
nightly mac
b0a40093b6b7 / 65.0a1 / 20181114222927 / files
nightly win32
b0a40093b6b7 / 65.0a1 / 20181114222927 / files
nightly win64
b0a40093b6b7 / 65.0a1 / 20181114222927 / files
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
releases
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge mozilla-inbound to mozilla-central a=merge
browser/base/content/browser.css
browser/components/translation/jar.mn
browser/components/translation/microsoft-translator-attribution.png
browser/components/translation/translation-infobar.xml
testing/web-platform/meta/css/css-filter/filtered-html-is-not-container.html.ini
testing/web-platform/meta/css/css-filter/filtered-inline-is-container.html.ini
testing/web-platform/meta/css/vendor-imports/mozilla/mozilla-central-reftests/transforms/individual-transform-1.html.ini
testing/web-platform/meta/css/vendor-imports/mozilla/mozilla-central-reftests/transforms/individual-transform-2a.html.ini
testing/web-platform/meta/css/vendor-imports/mozilla/mozilla-central-reftests/transforms/individual-transform-2b.html.ini
testing/web-platform/meta/css/vendor-imports/mozilla/mozilla-central-reftests/transforms/individual-transform-2c.html.ini
testing/web-platform/meta/css/vendor-imports/mozilla/mozilla-central-reftests/transforms/individual-transform-2d.html.ini
testing/web-platform/meta/css/vendor-imports/mozilla/mozilla-central-reftests/transforms/individual-transform-2e.html.ini
testing/web-platform/meta/webrtc/RTCRtpTransceiver-setDirection.html.ini
testing/web-platform/tests/css/css-filter/META.yml
testing/web-platform/tests/css/css-filter/blur-clip-stacking-context-001.html
testing/web-platform/tests/css/css-filter/blur-clip-stacking-context-002.html
testing/web-platform/tests/css/css-filter/blur-clip-stacking-context-ref.html
testing/web-platform/tests/css/css-filter/filtered-block-is-container-ref.html
testing/web-platform/tests/css/css-filter/filtered-block-is-container.html
testing/web-platform/tests/css/css-filter/filtered-html-is-not-container-ref.html
testing/web-platform/tests/css/css-filter/filtered-html-is-not-container.html
testing/web-platform/tests/css/css-filter/filtered-inline-is-container-ref.html
testing/web-platform/tests/css/css-filter/filtered-inline-is-container.html
testing/web-platform/tests/css/vendor-imports/mozilla/mozilla-central-reftests/transforms/individual-transform-1-ref.html
testing/web-platform/tests/css/vendor-imports/mozilla/mozilla-central-reftests/transforms/individual-transform-1.html
testing/web-platform/tests/css/vendor-imports/mozilla/mozilla-central-reftests/transforms/individual-transform-2-ref.html
testing/web-platform/tests/css/vendor-imports/mozilla/mozilla-central-reftests/transforms/individual-transform-2a.html
testing/web-platform/tests/css/vendor-imports/mozilla/mozilla-central-reftests/transforms/individual-transform-2b.html
testing/web-platform/tests/css/vendor-imports/mozilla/mozilla-central-reftests/transforms/individual-transform-2c.html
testing/web-platform/tests/css/vendor-imports/mozilla/mozilla-central-reftests/transforms/individual-transform-2d.html
testing/web-platform/tests/css/vendor-imports/mozilla/mozilla-central-reftests/transforms/individual-transform-2e.html
testing/web-platform/tests/tools/flake8.ini
testing/web-platform/tests/webrtc/RTCRtpTransceiver-setDirection.html
toolkit/themes/windows/global/tree/sort-asc.png
toolkit/themes/windows/global/tree/sort-dsc.png
tools/tryselect/test/test_fuzzy.py
--- a/accessible/tests/browser/e10s/browser.ini
+++ b/accessible/tests/browser/e10s/browser.ini
@@ -12,17 +12,17 @@ support-files =
   !/accessible/tests/mochitest/*.js
   !/accessible/tests/mochitest/letters.gif
   !/accessible/tests/mochitest/moz.png
 
 # Caching tests
 [browser_caching_attributes.js]
 [browser_caching_description.js]
 [browser_caching_name.js]
-skip-if = e10s && os == 'win' && debug # Bug 1338034, leaks
+skip-if = (e10s && os == 'win') || (os == 'mac' && debug) # Bug 1338034, leaks # Bug 1503084
 [browser_caching_relations.js]
 [browser_caching_states.js]
 [browser_caching_value.js]
 
 # Events tests
 [browser_events_caretmove.js]
 [browser_events_hide.js]
 [browser_events_show.js]
--- a/browser/base/content/browser-captivePortal.js
+++ b/browser/base/content/browser-captivePortal.js
@@ -210,17 +210,16 @@ var CaptivePortalWatcher = {
       {
         label: this._browserBundle.GetStringFromName("captivePortal.showLoginPage2"),
         callback: () => {
           this.ensureCaptivePortalTab();
 
           // Returning true prevents the notification from closing.
           return true;
         },
-        isDefault: true,
       },
     ];
 
     let message = this._browserBundle.GetStringFromName("captivePortal.infoMessage3");
 
     let closeHandler = (aEventName) => {
       if (aEventName != "removed") {
         return;
--- a/browser/base/content/browser-plugins.js
+++ b/browser/base/content/browser-plugins.js
@@ -477,14 +477,13 @@ var gPluginHandler = {
 
     // Add the "learn more" link.
     let link = notification.ownerDocument.createXULElement("label");
     link.className = "text-link";
     link.setAttribute("value", gNavigatorBundle.getString("crashedpluginsMessage.learnMore"));
     let crashurl = formatURL("app.support.baseURL", true);
     crashurl += "plugin-crashed-notificationbar";
     link.href = crashurl;
-    let description = notification.ownerDocument.getAnonymousElementByAttribute(notification, "anonid", "messageText");
-    description.appendChild(link);
+    notification.messageText.appendChild(link);
   },
 };
 
 gPluginHandler.init();
--- a/browser/base/content/browser.css
+++ b/browser/base/content/browser.css
@@ -1049,21 +1049,16 @@ browser[tabmodalPromptShowing] {
 }
 
 #statuspanel-inner {
   height: 3em;
   width: 100%;
   -moz-box-align: end;
 }
 
-/* Translation */
-notification[value="translation"] {
-  -moz-binding: url("chrome://browser/content/translation-infobar.xml#translationbar");
-}
-
 /*** Visibility of downloads indicator controls ***/
 
 /* Bug 924050: If we've loaded the indicator, for now we hide it in the menu panel,
    and just show the icon. This is a hack to side-step very weird layout bugs that
    seem to be caused by the indicator stack interacting with the menu panel. */
 #downloads-button[indicator]:not([cui-areatype="menu-panel"]) > .toolbarbutton-badge-stack > image.toolbarbutton-icon,
 #downloads-button[indicator][cui-areatype="menu-panel"] > #downloads-indicator-anchor {
   display: none;
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -237,16 +237,21 @@ XPCOMUtils.defineLazyGetter(this, "Win7F
         }
       },
       handledOpening: false,
     };
   }
   return null;
 });
 
+customElements.setElementCreationCallback("translation-notification", () => {
+  Services.scriptloader.loadSubScript(
+    "chrome://browser/content/translation-notification.js", window);
+});
+
 var gBrowser;
 var gLastValidURLStr = "";
 var gInPrintPreviewMode = false;
 var gContextMenu = null; // nsContextMenu instance
 var gMultiProcessBrowser =
   window.docShell
         .QueryInterface(Ci.nsILoadContext)
         .useRemoteTabs;
--- a/browser/base/content/test/general/browser_decoderDoctor.js
+++ b/browser/base/content/test/general/browser_decoderDoctor.js
@@ -42,20 +42,20 @@ async function test_decoder_doctor_notif
     try {
       notification = await awaitNotificationBar;
     } catch (ex) {
       ok(false, ex);
       return;
     }
     ok(notification, "Got decoder-doctor-notification notification");
 
-    is(notification.getAttribute("label"), notificationMessage,
+    is(notification.messageText.textContent, notificationMessage,
        "notification message should match expectation");
 
-    let button = notification.children[0];
+    let button = notification.querySelector("button");
     if (!label) {
       ok(!button, "There should not be button");
       return;
     }
 
     is(button.getAttribute("label"),
        label,
        `notification button should be '${label}'`);
--- a/browser/base/content/test/general/browser_refreshBlocker.js
+++ b/browser/base/content/test/general/browser_refreshBlocker.js
@@ -95,17 +95,17 @@ async function testRealRefresh(refreshPa
     await BrowserTestUtils.browserLoaded(browser);
 
     // Once browserLoaded resolves, all nsIWebProgressListener callbacks
     // should have fired, so the notification should be visible.
     let notificationBox = gBrowser.getNotificationBox(browser);
     let notification = notificationBox.currentNotification;
 
     ok(notification, "Notification should be visible");
-    is(notification.value, "refresh-blocked",
+    is(notification.getAttribute("value"), "refresh-blocked",
        "Should be showing the right notification");
 
     // Then click the button to allow the refresh.
     let buttons = notification.querySelectorAll(".notification-button");
     is(buttons.length, 1, "Should have one button.");
 
     // Prepare a Promise that should resolve when the refresh goes through
     let refreshPromise = BrowserTestUtils.browserLoaded(browser);
--- a/browser/base/content/test/plugins/browser_enable_DRM_prompt.js
+++ b/browser/base/content/test/plugins/browser_enable_DRM_prompt.js
@@ -40,17 +40,17 @@ add_task(async function() {
     });
     is(result.rejected, true, "EME request should be denied because EME disabled.");
 
     // Verify the UI prompt showed.
     let box = gBrowser.getNotificationBox(browser);
     let notification = box.currentNotification;
 
     ok(notification, "Notification should be visible");
-    is(notification.value, "drmContentDisabled",
+    is(notification.getAttribute("value"), "drmContentDisabled",
        "Should be showing the right notification");
 
     // Verify the "Enable DRM" button is there.
     let buttons = notification.querySelectorAll(".notification-button");
     is(buttons.length, 1, "Should have one button.");
 
     // Prepare a Promise that should resolve when the "Enable DRM" button's
     // page reload completes.
--- a/browser/base/content/test/plugins/browser_globalplugin_crashinfobar.js
+++ b/browser/base/content/test/plugins/browser_globalplugin_crashinfobar.js
@@ -22,13 +22,13 @@ add_task(async function() {
     });
 
     let notification = await waitForNotificationBar("plugin-crashed", browser);
 
     let notificationBox = gBrowser.getNotificationBox(browser);
     ok(notification, "Infobar was shown.");
     is(notification.priority, notificationBox.PRIORITY_WARNING_MEDIUM,
        "Correct priority.");
-    is(notification.getAttribute("label"),
+    is(notification.messageText.textContent,
        "The GlobalTestPlugin plugin has crashed.",
        "Correct message.");
   });
 });
--- a/browser/base/content/test/static/browser_all_files_referenced.js
+++ b/browser/base/content/test/static/browser_all_files_referenced.js
@@ -127,21 +127,16 @@ var whitelist = [
   {file: "resource://app/modules/translation/YandexTranslator.jsm"},
 
   // Starting from here, files in the whitelist are bugs that need fixing.
   // Bug 1339424 (wontfix?)
   {file: "chrome://browser/locale/taskbar.properties",
    platforms: ["linux", "macosx"]},
   // Bug 1356031 (only used by devtools)
   {file: "chrome://global/skin/icons/error-16.png"},
-  // Bug 1348526
-  {file: "chrome://global/skin/tree/sort-asc-classic.png", platforms: ["linux"]},
-  {file: "chrome://global/skin/tree/sort-asc.png", platforms: ["linux"]},
-  {file: "chrome://global/skin/tree/sort-dsc-classic.png", platforms: ["linux"]},
-  {file: "chrome://global/skin/tree/sort-dsc.png", platforms: ["linux"]},
   // Bug 1344267
   {file: "chrome://marionette/content/test_anonymous_content.xul"},
   {file: "chrome://marionette/content/test_dialog.properties"},
   {file: "chrome://marionette/content/test_dialog.xul"},
   // Bug 1348533
   {file: "chrome://mozapps/skin/downloads/buttons.png", platforms: ["macosx"]},
   {file: "chrome://mozapps/skin/downloads/downloadButtons.png", platforms: ["linux", "win"]},
   // Bug 1348558
--- a/browser/base/content/test/urlbar/browser_urlbarSearchSingleWordNotification.js
+++ b/browser/base/content/test/urlbar/browser_urlbarSearchSingleWordNotification.js
@@ -9,29 +9,18 @@ registerCleanupFunction(function() {
   }
 });
 
 function promiseNotification(aBrowser, value, expected, input) {
   return new Promise(resolve => {
     let notificationBox = aBrowser.getNotificationBox(aBrowser.selectedBrowser);
     if (expected) {
       info("Waiting for " + value + " notification");
-      let checkForNotification = function() {
-        if (notificationBox.getNotificationWithValue(value)) {
-          info("Saw the notification");
-          notificationObserver.disconnect();
-          notificationObserver = null;
-          resolve();
-        }
-      };
-      if (notificationObserver) {
-        notificationObserver.disconnect();
-      }
-      notificationObserver = new MutationObserver(checkForNotification);
-      notificationObserver.observe(notificationBox.stack, {childList: true});
+      resolve(BrowserTestUtils.waitForNotificationInNotificationBox(
+        notificationBox, value));
     } else {
       setTimeout(() => {
         is(notificationBox.getNotificationWithValue(value), null,
            `We are expecting to not get a notification for ${input}`);
         resolve();
       }, 1000);
     }
   });
@@ -147,17 +136,17 @@ function get_test_function_for_localhost
       expectSearch: true,
       expectNotification: true,
       aWindow: win,
     });
 
     let notificationBox = browser.getNotificationBox(tab.linkedBrowser);
     let notification = notificationBox.getNotificationWithValue("keyword-uri-fixup");
     let docLoadPromise = waitForDocLoadAndStopIt("http://" + hostName + "/", tab.linkedBrowser);
-    notification.querySelector(".notification-button-default").click();
+    notification.querySelector("button").click();
 
     // check pref value
     let prefValue = Services.prefs.getBoolPref(pref);
     is(prefValue, !isPrivate, "Pref should have the correct state.");
 
     await docLoadPromise;
     browser.removeTab(tab);
 
--- a/browser/components/extensions/test/browser/browser_ext_slow_script.js
+++ b/browser/components/extensions/test/browser/browser_ext_slow_script.js
@@ -44,17 +44,17 @@ add_task(async function test_slow_conten
     },
   });
 
   await extension.startup();
 
   let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, "http://example.com/");
 
   let notification = await BrowserTestUtils.waitForGlobalNotificationBar(window, "process-hang");
-  let text = document.getAnonymousElementByAttribute(notification, "anonid", "messageText").textContent;
+  let text = notification.messageText.textContent;
 
   ok(text.includes("\u201cSlow Script Extension\u201d"),
      "Label is correct");
 
   let stopButton = notification.querySelector("[label='Stop It']");
   stopButton.click();
 
   BrowserTestUtils.removeTab(tab);
--- a/browser/components/feeds/test/browser/browser_registerProtocolHandler_notification.js
+++ b/browser/components/feeds/test/browser/browser_registerProtocolHandler_notification.js
@@ -17,21 +17,20 @@ add_task(async function() {
 
   let notificationBox = window.gBrowser.getNotificationBox();
   let notification = notificationBox.getNotificationWithValue(notificationValue);
   ok(notification, "Notification box should be displayed");
   if (notification == null) {
     finish();
     return;
   }
-  is(notification.type, "info", "We expect this notification to have the type of 'info'.");
-  isnot(notification.image, null, "We expect this notification to have an icon.");
+  is(notification.getAttribute("type"), "info",
+     "We expect this notification to have the type of 'info'.");
+  ok(notification.messageImage.getAttribute("src"),
+     "We expect this notification to have an icon.");
 
-  let buttons = notification.getElementsByClassName("notification-button-default");
-  is(buttons.length, 1, "We expect see one default button.");
-
-  buttons = notification.getElementsByClassName("notification-button");
+  let buttons = notification.getElementsByClassName("notification-button");
   is(buttons.length, 1, "We expect see one button.");
 
   let button = buttons[0];
   isnot(button.label, null, "We expect the add button to have a label.");
   todo_isnot(button.accesskey, null, "We expect the add button to have a accesskey.");
 });
--- a/browser/components/tests/browser/browser_bug538331.js
+++ b/browser/components/tests/browser/browser_bug538331.js
@@ -328,17 +328,18 @@ function testShowNotification() {
 
     let updateBox = gHighPriorityNotificationBox.getNotificationWithValue(
                                                     "post-update-notification");
     if (testCase.actions && testCase.actions.includes("showNotification") &&
         !testCase.actions.includes("silent")) {
       ok(updateBox, "Update notification box should have been displayed");
       if (updateBox) {
         if (testCase.notificationText) {
-          is(updateBox.label, testCase.notificationText, "Update notification box " +
+          is(updateBox.messageText.textContent, testCase.notificationText,
+             "Update notification box " +
              "should have the label provided by the update");
         }
         if (testCase.notificationButtonLabel) {
           var button = updateBox.getElementsByTagName("button").item(0);
           is(button.label, testCase.notificationButtonLabel, "Update notification " +
              "box button should have the label provided by the update");
           if (testCase.notificationButtonAccessKey) {
             let accessKey = button.getAttribute("accesskey");
--- a/browser/components/translation/Translation.jsm
+++ b/browser/components/translation/Translation.jsm
@@ -223,17 +223,18 @@ TranslationUI.prototype = {
 
   get notificationBox() {
     return this.browser.ownerGlobal.gBrowser.getNotificationBox(this.browser);
   },
 
   showTranslationInfoBar() {
     let notificationBox = this.notificationBox;
     let notif = notificationBox.appendNotification("", "translation", null,
-                                                   notificationBox.PRIORITY_INFO_HIGH);
+      notificationBox.PRIORITY_INFO_HIGH, null, null,
+      "translation-notification");
     notif.init(this);
     return notif;
   },
 
   shouldShowInfoBar(aURI) {
     // Never show the infobar automatically while the translation
     // service is temporarily unavailable.
     if (Translation.serviceUnavailable)
copy from browser/components/customizableui/content/.eslintrc.js
copy to browser/components/translation/content/.eslintrc.js
rename from browser/components/translation/jar.mn
rename to browser/components/translation/content/jar.mn
--- a/browser/components/translation/jar.mn
+++ b/browser/components/translation/content/jar.mn
@@ -1,6 +1,6 @@
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 browser.jar:
-       content/browser/translation-infobar.xml
+       content/browser/translation-notification.js
        content/browser/microsoft-translator-attribution.png
rename from browser/components/translation/microsoft-translator-attribution.png
rename to browser/components/translation/content/microsoft-translator-attribution.png
copy from browser/components/customizableui/content/moz.build
copy to browser/components/translation/content/moz.build
rename from browser/components/translation/translation-infobar.xml
rename to browser/components/translation/content/translation-notification.js
--- a/browser/components/translation/translation-infobar.xml
+++ b/browser/components/translation/content/translation-notification.js
@@ -1,439 +1,349 @@
-<?xml version="1.0"?>
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
-   - License, v. 2.0. If a copy of the MPL was not distributed with this
-   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<!DOCTYPE bindings [
-<!ENTITY % notificationDTD SYSTEM "chrome://global/locale/notification.dtd">
-%notificationDTD;
-<!ENTITY % translationDTD SYSTEM "chrome://browser/locale/translation.dtd" >
-%translationDTD;
-]>
-
-<bindings id="translationBindings"
-          xmlns="http://www.mozilla.org/xbl"
-          xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
-          xmlns:xbl="http://www.mozilla.org/xbl">
-  <binding id="translationbar" extends="chrome://global/content/bindings/notification.xml#notification">
-    <content>
-      <xul:hbox anonid="details" align="center" flex="1">
-        <xul:image class="translate-infobar-element messageImage"
-                   anonid="messageImage"/>
-        <xul:panel anonid="welcomePanel" class="translation-welcome-panel"
-                   type="arrow" align="start">
-          <xul:image class="translation-welcome-logo"/>
-          <xul:vbox flex="1" class="translation-welcome-content">
-            <xul:description class="translation-welcome-headline"
-                             anonid="welcomeHeadline"/>
-            <xul:description class="translation-welcome-body" anonid="welcomeBody"/>
-            <xul:hbox align="center">
-              <xul:label anonid="learnMore" class="plain text-link"
-                         onclick="openTrustedLinkIn('https://support.mozilla.org/kb/automatic-translation', 'tab'); this.parentNode.parentNode.parentNode.hidePopup();"/>
-              <xul:spacer flex="1"/>
-              <xul:button class="translate-infobar-element" anonid="thanksButton"
-                          onclick="this.parentNode.parentNode.parentNode.hidePopup();"/>
-            </xul:hbox>
-          </xul:vbox>
-        </xul:panel>
-        <xul:deck anonid="translationStates" selectedIndex="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/. */
 
-          <!-- offer to translate -->
-          <xul:hbox class="translate-offer-box" align="center">
-            <xul:label class="translate-infobar-element" value="&translation.thisPageIsIn.label;"/>
-            <xul:menulist class="translate-infobar-element" anonid="detectedLanguage">
-              <xul:menupopup/>
-            </xul:menulist>
-            <xul:label class="translate-infobar-element" value="&translation.translateThisPage.label;"/>
-            <xul:button class="translate-infobar-element"
-                        label="&translation.translate.button;"
-                        anonid="translate"
-                        oncommand="document.getBindingParent(this).translate();"/>
-            <xul:button class="translate-infobar-element"
-                        label="&translation.notNow.button;" anonid="notNow"
-                        oncommand="document.getBindingParent(this).closeCommand();"/>
-          </xul:hbox>
-
-          <!-- translating -->
-          <xul:vbox class="translating-box" pack="center">
-            <xul:label class="translate-infobar-element"
-                       value="&translation.translatingContent.label;"/>
-          </xul:vbox>
-
-          <!-- translated -->
-          <xul:hbox class="translated-box" align="center">
-            <xul:label class="translate-infobar-element"
-                       value="&translation.translatedFrom.label;"/>
-            <xul:menulist class="translate-infobar-element"
-                          anonid="fromLanguage"
-                          oncommand="document.getBindingParent(this).translate()">
-              <xul:menupopup/>
-            </xul:menulist>
-            <xul:label class="translate-infobar-element"
-                       value="&translation.translatedTo.label;"/>
-            <xul:menulist class="translate-infobar-element"
-                          anonid="toLanguage"
-                          oncommand="document.getBindingParent(this).translate()">
-              <xul:menupopup/>
-            </xul:menulist>
-            <xul:label class="translate-infobar-element"
-                       value="&translation.translatedToSuffix.label;"/>
-            <xul:button anonid="showOriginal"
-                        class="translate-infobar-element"
-                        label="&translation.showOriginal.button;"
-                        oncommand="document.getBindingParent(this).showOriginal();"/>
-            <xul:button anonid="showTranslation"
-                        class="translate-infobar-element"
-                        label="&translation.showTranslation.button;"
-                        oncommand="document.getBindingParent(this).showTranslation();"/>
-          </xul:hbox>
-
-          <!-- error -->
-          <xul:hbox class="translation-error" align="center">
-            <xul:label class="translate-infobar-element"
-                       value="&translation.errorTranslating.label;"/>
-            <xul:button class="translate-infobar-element"
-                        label="&translation.tryAgain.button;"
-                        anonid="tryAgain"
-                        oncommand="document.getBindingParent(this).translate();"/>
-          </xul:hbox>
-
-          <!-- unavailable -->
-          <xul:vbox class="translation-unavailable" pack="center">
-            <xul:label class="translate-infobar-element"
-                       value="&translation.serviceUnavailable.label;"/>
-          </xul:vbox>
-
-        </xul:deck>
-        <xul:spacer flex="1"/>
+"use strict";
 
-        <xul:button type="menu"
-                    class="translate-infobar-element options-menu-button"
-                    anonid="options"
-                    label="&translation.options.menu;">
-          <xul:menupopup class="translation-menupopup cui-widget-panel cui-widget-panelview
-                                cui-widget-panelWithFooter PanelUI-subView"
-                         onpopupshowing="document.getBindingParent(this).optionsShowing();">
-            <xul:menuitem anonid="neverForLanguage"
-                          oncommand="document.getBindingParent(this).neverForLanguage();"/>
-            <xul:menuitem anonid="neverForSite"
-                          oncommand="document.getBindingParent(this).neverForSite();"
-                          label="&translation.options.neverForSite.label;"
-                          accesskey="&translation.options.neverForSite.accesskey;"/>
-            <xul:menuseparator/>
-            <xul:menuitem oncommand="openPreferences('paneGeneral', {origin:'translationInfobar'});"
-                          label="&translation.options.preferences.label;"
-                          accesskey="&translation.options.preferences.accesskey;"/>
-            <xul:menuitem class="subviewbutton panel-subview-footer"
-                          oncommand="document.getBindingParent(this).openProviderAttribution();">
-              <xul:deck anonid="translationEngine" selectedIndex="0">
-                <xul:hbox class="translation-attribution">
-                  <xul:label>&translation.options.attribution.beforeLogo;</xul:label>
-                  <xul:image src="chrome://browser/content/microsoft-translator-attribution.png"
-                             aria-label="Microsoft Translator"/>
-                  <xul:label>&translation.options.attribution.afterLogo;</xul:label>
-                </xul:hbox>
-                <xul:label class="translation-attribution">&translation.options.attribution.yandexTranslate;</xul:label>
-              </xul:deck>
-            </xul:menuitem>
-          </xul:menupopup>
-        </xul:button>
-
-      </xul:hbox>
-      <xul:toolbarbutton ondblclick="event.stopPropagation();"
-                         anonid="closeButton"
-                         class="messageCloseButton close-icon tabbable"
-                         xbl:inherits="hidden=hideclose"
-                         tooltiptext="&closeNotification.tooltip;"
-                         oncommand="document.getBindingParent(this).closeCommand();"/>
-    </content>
-    <implementation>
-      <property name="state"
-                onget="return this._getAnonElt('translationStates').selectedIndex;">
-        <setter>
-          <![CDATA[
-          let deck = this._getAnonElt("translationStates");
-
-          let activeElt = document.activeElement;
-          if (activeElt && deck.contains(activeElt))
-            activeElt.blur();
+class MozTranslationNotification extends MozElements.Notification {
+  connectedCallback() {
+    this.appendChild(MozXULElement.parseXULToFragment(`
+      <hbox anonid="details" align="center" flex="1">
+        <image class="translate-infobar-element messageImage"/>
+        <panel anonid="welcomePanel" class="translation-welcome-panel" type="arrow" align="start">
+          <image class="translation-welcome-logo"/>
+          <vbox flex="1" class="translation-welcome-content">
+            <description class="translation-welcome-headline" anonid="welcomeHeadline"/>
+            <description class="translation-welcome-body" anonid="welcomeBody"/>
+            <hbox align="center">
+              <label anonid="learnMore" class="plain text-link" onclick="openTrustedLinkIn('https://support.mozilla.org/kb/automatic-translation', 'tab'); this.parentNode.parentNode.parentNode.hidePopup();"/>
+              <spacer flex="1"/>
+              <button class="translate-infobar-element" anonid="thanksButton" onclick="this.parentNode.parentNode.parentNode.hidePopup();"/>
+            </hbox>
+          </vbox>
+        </panel>
+        <deck anonid="translationStates" selectedIndex="0">
+          <hbox class="translate-offer-box" align="center">
+            <label class="translate-infobar-element" value="&translation.thisPageIsIn.label;"/>
+            <menulist class="translate-infobar-element" anonid="detectedLanguage">
+              <menupopup/>
+            </menulist>
+            <label class="translate-infobar-element" value="&translation.translateThisPage.label;"/>
+            <button class="translate-infobar-element" label="&translation.translate.button;" anonid="translate" oncommand="this.closest('notification').translate();"/>
+            <button class="translate-infobar-element" label="&translation.notNow.button;" anonid="notNow" oncommand="this.closest('notification').closeCommand();"/>
+          </hbox>
+          <vbox class="translating-box" pack="center">
+            <label class="translate-infobar-element" value="&translation.translatingContent.label;"/>
+          </vbox>
+          <hbox class="translated-box" align="center">
+            <label class="translate-infobar-element" value="&translation.translatedFrom.label;"/>
+            <menulist class="translate-infobar-element" anonid="fromLanguage" oncommand="this.closest('notification').translate();">
+              <menupopup/>
+            </menulist>
+            <label class="translate-infobar-element" value="&translation.translatedTo.label;"/>
+            <menulist class="translate-infobar-element" anonid="toLanguage" oncommand="this.closest('notification').translate();">
+              <menupopup/>
+            </menulist>
+            <label class="translate-infobar-element" value="&translation.translatedToSuffix.label;"/>
+            <button anonid="showOriginal" class="translate-infobar-element" label="&translation.showOriginal.button;" oncommand="this.closest('notification').showOriginal();"/>
+            <button anonid="showTranslation" class="translate-infobar-element" label="&translation.showTranslation.button;" oncommand="this.closest('notification').showTranslation();"/>
+          </hbox>
+          <hbox class="translation-error" align="center">
+            <label class="translate-infobar-element" value="&translation.errorTranslating.label;"/>
+            <button class="translate-infobar-element" label="&translation.tryAgain.button;" anonid="tryAgain" oncommand="this.closest('notification').translate();"/>
+          </hbox>
+          <vbox class="translation-unavailable" pack="center">
+            <label class="translate-infobar-element" value="&translation.serviceUnavailable.label;"/>
+          </vbox>
+        </deck>
+        <spacer flex="1"/>
+        <button type="menu" class="translate-infobar-element options-menu-button" anonid="options" label="&translation.options.menu;">
+          <menupopup class="translation-menupopup cui-widget-panel cui-widget-panelview
+                                cui-widget-panelWithFooter PanelUI-subView" onpopupshowing="this.closest('notification').optionsShowing();">
+            <menuitem anonid="neverForLanguage" oncommand="this.closest('notification').neverForLanguage();"/>
+            <menuitem anonid="neverForSite" oncommand="this.closest('notification').neverForSite();" label="&translation.options.neverForSite.label;" accesskey="&translation.options.neverForSite.accesskey;"/>
+            <menuseparator/>
+            <menuitem oncommand="openPreferences('paneGeneral', {origin:'translationInfobar'});" label="&translation.options.preferences.label;" accesskey="&translation.options.preferences.accesskey;"/>
+            <menuitem class="subviewbutton panel-subview-footer" oncommand="this.closest('notification').openProviderAttribution();">
+              <deck anonid="translationEngine" selectedIndex="0">
+                <hbox class="translation-attribution">
+                  <label/>
+                  <image src="chrome://browser/content/microsoft-translator-attribution.png" aria-label="Microsoft Translator"/>
+                  <label/>
+                </hbox>
+                <label class="translation-attribution"/>
+              </deck>
+            </menuitem>
+          </menupopup>
+        </button>
+      </hbox>
+      <toolbarbutton anonid="closeButton" ondblclick="event.stopPropagation();"
+                     class="messageCloseButton close-icon tabbable"
+                     tooltiptext="&closeNotification.tooltip;"
+                     oncommand="this.parentNode.closeCommand();"/>
+    `, [
+      "chrome://global/locale/notification.dtd",
+      "chrome://browser/locale/translation.dtd",
+    ]));
 
-          let stateName;
-          for (let name of ["OFFER", "TRANSLATING", "TRANSLATED", "ERROR"]) {
-            if (Translation["STATE_" + name] == val) {
-              stateName = name.toLowerCase();
-              break;
-            }
-          }
-          this.setAttribute("state", stateName);
+    for (let [propertyName, selector] of [
+      ["details", "[anonid=details]"],
+      ["messageImage", ".messageImage"],
+      ["spacer", "[anonid=spacer]"],
+    ]) {
+      this[propertyName] = this.querySelector(selector);
+    }
+  }
+
+  set state(val) {
+    let deck = this._getAnonElt("translationStates");
+
+    let activeElt = document.activeElement;
+    if (activeElt && deck.contains(activeElt))
+      activeElt.blur();
 
-          if (val == Translation.STATE_TRANSLATED)
-            this._handleButtonHiding();
+    let stateName;
+    for (let name of ["OFFER", "TRANSLATING", "TRANSLATED", "ERROR"]) {
+      if (Translation["STATE_" + name] == val) {
+        stateName = name.toLowerCase();
+        break;
+      }
+    }
+    this.setAttribute("state", stateName);
 
-          deck.selectedIndex = val;
-          ]]>
-        </setter>
-      </property>
+    if (val == Translation.STATE_TRANSLATED)
+      this._handleButtonHiding();
 
-      <method name="init">
-        <parameter name="aTranslation"/>
-        <body>
-          <![CDATA[
-            this.translation = aTranslation;
+    deck.selectedIndex = val;
+  }
+
+  get state() {
+    return this._getAnonElt("translationStates").selectedIndex;
+  }
 
-            let sortByLocalizedName = function(aList) {
-              let names = Services.intl.getLanguageDisplayNames(undefined, aList);
-              return aList.map((code, i) => [code, names[i]])
-                          .sort((a, b) => a[1].localeCompare(b[1]));
-            };
+  init(aTranslation) {
+    this.translation = aTranslation;
 
-            // Fill the lists of supported source languages.
-            let detectedLanguage = this._getAnonElt("detectedLanguage");
-            let fromLanguage = this._getAnonElt("fromLanguage");
-            let sourceLanguages =
-              sortByLocalizedName(Translation.supportedSourceLanguages);
-            for (let [code, name] of sourceLanguages) {
-              detectedLanguage.appendItem(name, code);
-              fromLanguage.appendItem(name, code);
-            }
-            detectedLanguage.value = this.translation.detectedLanguage;
+    let sortByLocalizedName = function(aList) {
+      let names = Services.intl.getLanguageDisplayNames(undefined, aList);
+      return aList.map((code, i) => [code, names[i]])
+        .sort((a, b) => a[1].localeCompare(b[1]));
+    };
 
-            // translatedFrom is only set if we have already translated this page.
-            if (aTranslation.translatedFrom)
-              fromLanguage.value = aTranslation.translatedFrom;
+    // Fill the lists of supported source languages.
+    let detectedLanguage = this._getAnonElt("detectedLanguage");
+    let fromLanguage = this._getAnonElt("fromLanguage");
+    let sourceLanguages =
+      sortByLocalizedName(Translation.supportedSourceLanguages);
+    for (let [code, name] of sourceLanguages) {
+      detectedLanguage.appendItem(name, code);
+      fromLanguage.appendItem(name, code);
+    }
+    detectedLanguage.value = this.translation.detectedLanguage;
 
-            // Fill the list of supported target languages.
-            let toLanguage = this._getAnonElt("toLanguage");
-            let targetLanguages =
-              sortByLocalizedName(Translation.supportedTargetLanguages);
-            for (let [code, name] of targetLanguages)
-              toLanguage.appendItem(name, code);
+    // translatedFrom is only set if we have already translated this page.
+    if (aTranslation.translatedFrom)
+      fromLanguage.value = aTranslation.translatedFrom;
 
-            if (aTranslation.translatedTo)
-              toLanguage.value = aTranslation.translatedTo;
+    // Fill the list of supported target languages.
+    let toLanguage = this._getAnonElt("toLanguage");
+    let targetLanguages =
+      sortByLocalizedName(Translation.supportedTargetLanguages);
+    for (let [code, name] of targetLanguages)
+      toLanguage.appendItem(name, code);
 
-            if (aTranslation.state)
-              this.state = aTranslation.state;
+    if (aTranslation.translatedTo)
+      toLanguage.value = aTranslation.translatedTo;
+
+    if (aTranslation.state)
+      this.state = aTranslation.state;
 
-            // Show attribution for the preferred translator.
-            let engineIndex = Object.keys(Translation.supportedEngines)
-              .indexOf(Translation.translationEngine);
-            // We currently only have attribution for the Bing and Yandex engines.
-            if (engineIndex >= 0) {
-              --engineIndex;
-            }
-            let attributionNode = this._getAnonElt("translationEngine");
-            if (engineIndex != -1) {
-              attributionNode.selectedIndex = engineIndex;
-            } else {
-              // Hide the attribution menuitem
-              let footer = attributionNode.parentNode;
-              footer.hidden = true;
-              // Make the 'Translation preferences' item the new footer.
-              footer = footer.previousSibling;
-              footer.setAttribute("class", "subviewbutton panel-subview-footer");
-              // And hide the menuseparator.
-              footer.previousSibling.hidden = true;
-            }
+    // Show attribution for the preferred translator.
+    let engineIndex = Object.keys(Translation.supportedEngines)
+      .indexOf(Translation.translationEngine);
+    // We currently only have attribution for the Bing and Yandex engines.
+    if (engineIndex >= 0) {
+      --engineIndex;
+    }
+    let attributionNode = this._getAnonElt("translationEngine");
+    if (engineIndex != -1) {
+      attributionNode.selectedIndex = engineIndex;
+    } else {
+      // Hide the attribution menuitem
+      let footer = attributionNode.parentNode;
+      footer.hidden = true;
+      // Make the 'Translation preferences' item the new footer.
+      footer = footer.previousSibling;
+      footer.setAttribute("class", "subviewbutton panel-subview-footer");
+      // And hide the menuseparator.
+      footer.previousSibling.hidden = true;
+    }
 
-            const kWelcomePref = "browser.translation.ui.welcomeMessageShown";
-            if (Services.prefs.prefHasUserValue(kWelcomePref) ||
-                this.translation.browser != gBrowser.selectedBrowser)
-              return;
+    const kWelcomePref = "browser.translation.ui.welcomeMessageShown";
+    if (Services.prefs.prefHasUserValue(kWelcomePref) ||
+      this.translation.browser != gBrowser.selectedBrowser)
+      return;
 
-            this.addEventListener("transitionend", function() {
-              // These strings are hardcoded because they need to reach beta
-              // without riding the trains.
-              let localizedStrings = {
-                en: ["Hey look! It's something new!",
-                     "Now the Web is even more accessible with our new in-page translation feature. Click the translate button to try it!",
-                     "Learn more.",
-                     "Thanks"],
-                "es-AR": ["\xA1Mir\xE1! \xA1Hay algo nuevo!",
-                          "Ahora la web es a\xFAn m\xE1s accesible con nuestra nueva funcionalidad de traducci\xF3n integrada. \xA1Hac\xE9 clic en el bot\xF3n traducir para probarla!",
-                          "Conoc\xE9 m\xE1s.",
-                          "Gracias"],
-                "es-ES": ["\xA1Mira! \xA1Hay algo nuevo!",
-                          "Con la nueva funcionalidad de traducci\xF3n integrada, ahora la Web es a\xFAn m\xE1s accesible. \xA1Pulsa el bot\xF3n Traducir y pru\xE9bala!",
-                          "M\xE1s informaci\xF3n.",
-                          "Gracias"],
-                pl: ["Sp\xF3jrz tutaj! To co\u015B nowego!",
-                     "Sie\u0107 sta\u0142a si\u0119 w\u0142a\u015Bnie jeszcze bardziej dost\u0119pna dzi\u0119ki opcji bezpo\u015Bredniego t\u0142umaczenia stron. Kliknij przycisk t\u0142umaczenia, aby spr\xF3bowa\u0107!",
-                     "Dowiedz si\u0119 wi\u0119cej",
-                     "Dzi\u0119kuj\u0119"],
-                tr: ["Bak\u0131n, burada yeni bir \u015Fey var!",
-                     "Yeni sayfa i\xE7i \xE7eviri \xF6zelli\u011Fimiz sayesinde Web art\u0131k \xE7ok daha anla\u015F\u0131l\u0131r olacak. Denemek i\xE7in \xC7evir d\xFC\u011Fmesine t\u0131klay\u0131n!",
-                     "Daha fazla bilgi al\u0131n.",
-                     "Te\u015Fekk\xFCrler"],
-                vi: ["Nh\xECn n\xE0y! \u0110\u1ED3 m\u1EDBi!",
-                     "Gi\u1EDD \u0111\xE2y ch\xFAng ta c\xF3 th\u1EC3 ti\u1EBFp c\u1EADn web d\u1EC5 d\xE0ng h\u01A1n n\u1EEFa v\u1EDBi t\xEDnh n\u0103ng d\u1ECBch ngay trong trang.  Hay nh\u1EA5n n\xFAt d\u1ECBch \u0111\u1EC3 th\u1EED!",
-                     "T\xECm hi\u1EC3u th\xEAm.",
-                     "C\u1EA3m \u01A1n"],
-              };
-
-              let locale = Services.locale.appLocaleAsLangTag;
-              if (!(locale in localizedStrings))
-                locale = "en";
-              let strings = localizedStrings[locale];
+    this.addEventListener("transitionend", function() {
+      // These strings are hardcoded because they need to reach beta
+      // without riding the trains.
+      let localizedStrings = {
+        en: ["Hey look! It's something new!",
+          "Now the Web is even more accessible with our new in-page translation feature. Click the translate button to try it!",
+          "Learn more.",
+          "Thanks",
+        ],
+        "es-AR": ["\xA1Mir\xE1! \xA1Hay algo nuevo!",
+          "Ahora la web es a\xFAn m\xE1s accesible con nuestra nueva funcionalidad de traducci\xF3n integrada. \xA1Hac\xE9 clic en el bot\xF3n traducir para probarla!",
+          "Conoc\xE9 m\xE1s.",
+          "Gracias",
+        ],
+        "es-ES": ["\xA1Mira! \xA1Hay algo nuevo!",
+          "Con la nueva funcionalidad de traducci\xF3n integrada, ahora la Web es a\xFAn m\xE1s accesible. \xA1Pulsa el bot\xF3n Traducir y pru\xE9bala!",
+          "M\xE1s informaci\xF3n.",
+          "Gracias",
+        ],
+        pl: ["Sp\xF3jrz tutaj! To co\u015B nowego!",
+          "Sie\u0107 sta\u0142a si\u0119 w\u0142a\u015Bnie jeszcze bardziej dost\u0119pna dzi\u0119ki opcji bezpo\u015Bredniego t\u0142umaczenia stron. Kliknij przycisk t\u0142umaczenia, aby spr\xF3bowa\u0107!",
+          "Dowiedz si\u0119 wi\u0119cej",
+          "Dzi\u0119kuj\u0119",
+        ],
+        tr: ["Bak\u0131n, burada yeni bir \u015Fey var!",
+          "Yeni sayfa i\xE7i \xE7eviri \xF6zelli\u011Fimiz sayesinde Web art\u0131k \xE7ok daha anla\u015F\u0131l\u0131r olacak. Denemek i\xE7in \xC7evir d\xFC\u011Fmesine t\u0131klay\u0131n!",
+          "Daha fazla bilgi al\u0131n.",
+          "Te\u015Fekk\xFCrler",
+        ],
+        vi: ["Nh\xECn n\xE0y! \u0110\u1ED3 m\u1EDBi!",
+          "Gi\u1EDD \u0111\xE2y ch\xFAng ta c\xF3 th\u1EC3 ti\u1EBFp c\u1EADn web d\u1EC5 d\xE0ng h\u01A1n n\u1EEFa v\u1EDBi t\xEDnh n\u0103ng d\u1ECBch ngay trong trang.  Hay nh\u1EA5n n\xFAt d\u1ECBch \u0111\u1EC3 th\u1EED!",
+          "T\xECm hi\u1EC3u th\xEAm.",
+          "C\u1EA3m \u01A1n",
+        ],
+      };
 
-              this._getAnonElt("welcomeHeadline").setAttribute("value", strings[0]);
-              this._getAnonElt("welcomeBody").textContent = strings[1];
-              this._getAnonElt("learnMore").setAttribute("value", strings[2]);
-              this._getAnonElt("thanksButton").setAttribute("label", strings[3]);
+      let locale = Services.locale.appLocaleAsLangTag;
+      if (!(locale in localizedStrings))
+        locale = "en";
+      let strings = localizedStrings[locale];
 
-              let panel = this._getAnonElt("welcomePanel");
-              panel.openPopup(this._getAnonElt("messageImage"),
-                              "bottomcenter topleft");
+      this._getAnonElt("welcomeHeadline").setAttribute("value", strings[0]);
+      this._getAnonElt("welcomeBody").textContent = strings[1];
+      this._getAnonElt("learnMore").setAttribute("value", strings[2]);
+      this._getAnonElt("thanksButton").setAttribute("label", strings[3]);
 
-              Services.prefs.setBoolPref(kWelcomePref, true);
-            }, {once: true});
-          ]]>
-        </body>
-      </method>
+      let panel = this._getAnonElt("welcomePanel");
+      panel.openPopup(this._getAnonElt("messageImage"),
+        "bottomcenter topleft");
 
-      <method name="_getAnonElt">
-        <parameter name="aAnonId"/>
-        <body>
-          return document.getAnonymousElementByAttribute(this, "anonid", aAnonId);
-        </body>
-      </method>
+      Services.prefs.setBoolPref(kWelcomePref, true);
+    }, { once: true });
+  }
+
+  _getAnonElt(aAnonId) {
+    return this.querySelector("[anonid=" + aAnonId + "]");
+  }
 
-      <method name="translate">
-        <body>
-          <![CDATA[
-            if (this.state == Translation.STATE_OFFER) {
-              this._getAnonElt("fromLanguage").value =
-                this._getAnonElt("detectedLanguage").value;
-              this._getAnonElt("toLanguage").value =
-                Translation.defaultTargetLanguage;
-            }
-
-            this.translation.translate(this._getAnonElt("fromLanguage").value,
-                                       this._getAnonElt("toLanguage").value);
-          ]]>
-        </body>
-      </method>
+  translate() {
+    if (this.state == Translation.STATE_OFFER) {
+      this._getAnonElt("fromLanguage").value =
+        this._getAnonElt("detectedLanguage").value;
+      this._getAnonElt("toLanguage").value =
+        Translation.defaultTargetLanguage;
+    }
 
-      <!-- To be called when the infobar should be closed per user's wish (e.g.
-           by clicking the notification's close button -->
-      <method name="closeCommand">
-        <body>
-          <![CDATA[
-            this.close();
-            this.translation.infobarClosed();
-          ]]>
-        </body>
-      </method>
-      <method name="_handleButtonHiding">
-        <body>
-          <![CDATA[
-            let originalShown = this.translation.originalShown;
-            this._getAnonElt("showOriginal").hidden = originalShown;
-            this._getAnonElt("showTranslation").hidden = !originalShown;
-          ]]>
-        </body>
-      </method>
+    this.translation.translate(this._getAnonElt("fromLanguage").value,
+      this._getAnonElt("toLanguage").value);
+  }
+
+  /**
+   * To be called when the infobar should be closed per user's wish (e.g.
+   * by clicking the notification's close button
+   */
+  closeCommand() {
+    this.close();
+    this.translation.infobarClosed();
+  }
+
+  _handleButtonHiding() {
+    let originalShown = this.translation.originalShown;
+    this._getAnonElt("showOriginal").hidden = originalShown;
+    this._getAnonElt("showTranslation").hidden = !originalShown;
+  }
 
-      <method name="showOriginal">
-        <body>
-          <![CDATA[
-            this.translation.showOriginalContent();
-            this._handleButtonHiding();
-          ]]>
-        </body>
-      </method>
+  showOriginal() {
+    this.translation.showOriginalContent();
+    this._handleButtonHiding();
+  }
 
-      <method name="showTranslation">
-        <body>
-          <![CDATA[
-            this.translation.showTranslatedContent();
-            this._handleButtonHiding();
-          ]]>
-        </body>
-      </method>
+  showTranslation() {
+    this.translation.showTranslatedContent();
+    this._handleButtonHiding();
+  }
 
-      <method name="optionsShowing">
-        <body>
-          <![CDATA[
-            // Get the source language name.
-            let lang;
-            if (this.state == Translation.STATE_OFFER)
-              lang = this._getAnonElt("detectedLanguage").value;
-            else {
-              lang = this._getAnonElt("fromLanguage").value;
+  optionsShowing() {
+    // Get the source language name.
+    let lang;
+    if (this.state == Translation.STATE_OFFER)
+      lang = this._getAnonElt("detectedLanguage").value;
+    else {
+      lang = this._getAnonElt("fromLanguage").value;
 
-              // If we have never attempted to translate the page before the
-              // service became unavailable, "fromLanguage" isn't set.
-              if (!lang && this.state == Translation.STATE_UNAVAILABLE)
-                lang = this.translation.detectedLanguage;
-            }
+      // If we have never attempted to translate the page before the
+      // service became unavailable, "fromLanguage" isn't set.
+      if (!lang && this.state == Translation.STATE_UNAVAILABLE)
+        lang = this.translation.detectedLanguage;
+    }
 
-            let langName = Services.intl.getLanguageDisplayNames(undefined, [lang])[0];
+    let langName = Services.intl.getLanguageDisplayNames(undefined, [lang])[0];
 
-            // Set the label and accesskey on the menuitem.
-            let bundle =
-              Services.strings.createBundle("chrome://browser/locale/translation.properties");
-            let item = this._getAnonElt("neverForLanguage");
-            const kStrId = "translation.options.neverForLanguage";
-            item.setAttribute("label",
-                              bundle.formatStringFromName(kStrId + ".label",
-                                                          [langName], 1));
-            item.setAttribute("accesskey",
-                              bundle.GetStringFromName(kStrId + ".accesskey"));
-            item.langCode = lang;
+    // Set the label and accesskey on the menuitem.
+    let bundle =
+      Services.strings.createBundle("chrome://browser/locale/translation.properties");
+    let item = this._getAnonElt("neverForLanguage");
+    const kStrId = "translation.options.neverForLanguage";
+    item.setAttribute("label",
+      bundle.formatStringFromName(kStrId + ".label", [langName], 1));
+    item.setAttribute("accesskey",
+      bundle.GetStringFromName(kStrId + ".accesskey"));
+    item.langCode = lang;
 
-            // We may need to disable the menuitems if they have already been used.
-            // Check if translation is already disabled for this language:
-            let neverForLangs =
-              Services.prefs.getCharPref("browser.translation.neverForLanguages");
-            item.disabled = neverForLangs.split(",").includes(lang);
+    // We may need to disable the menuitems if they have already been used.
+    // Check if translation is already disabled for this language:
+    let neverForLangs =
+      Services.prefs.getCharPref("browser.translation.neverForLanguages");
+    item.disabled = neverForLangs.split(",").includes(lang);
 
-            // Check if translation is disabled for the domain:
-            let uri = this.translation.browser.currentURI;
-            let perms = Services.perms;
-            item = this._getAnonElt("neverForSite");
-            item.disabled =
-              perms.testExactPermission(uri, "translate") == perms.DENY_ACTION;
-          ]]>
-        </body>
-      </method>
-
-      <method name="neverForLanguage">
-        <body>
-          <![CDATA[
-            const kPrefName = "browser.translation.neverForLanguages";
+    // Check if translation is disabled for the domain:
+    let uri = this.translation.browser.currentURI;
+    let perms = Services.perms;
+    item = this._getAnonElt("neverForSite");
+    item.disabled =
+      perms.testExactPermission(uri, "translate") == perms.DENY_ACTION;
+  }
 
-            let val = Services.prefs.getCharPref(kPrefName);
-            if (val)
-              val += ",";
-            val += this._getAnonElt("neverForLanguage").langCode;
+  neverForLanguage() {
+    const kPrefName = "browser.translation.neverForLanguages";
 
-            Services.prefs.setCharPref(kPrefName, val);
+    let val = Services.prefs.getCharPref(kPrefName);
+    if (val)
+      val += ",";
+    val += this._getAnonElt("neverForLanguage").langCode;
 
-            this.closeCommand();
-          ]]>
-        </body>
-      </method>
+    Services.prefs.setCharPref(kPrefName, val);
+
+    this.closeCommand();
+  }
 
-      <method name="neverForSite">
-        <body>
-          <![CDATA[
-            let uri = this.translation.browser.currentURI;
-            let perms = Services.perms;
-            perms.add(uri, "translate", perms.DENY_ACTION);
+  neverForSite() {
+    let uri = this.translation.browser.currentURI;
+    let perms = Services.perms;
+    perms.add(uri, "translate", perms.DENY_ACTION);
 
-            this.closeCommand();
-          ]]>
-        </body>
-      </method>
+    this.closeCommand();
+  }
 
-      <method name="openProviderAttribution">
-        <body>
-          <![CDATA[
-            Translation.openProviderAttribution();
-          ]]>
-        </body>
-      </method>
+  openProviderAttribution() {
+    Translation.openProviderAttribution();
+  }
+}
 
-    </implementation>
-  </binding>
-</bindings>
+customElements.define("translation-notification", MozTranslationNotification,
+                      { extends: "notification" });
--- a/browser/components/translation/moz.build
+++ b/browser/components/translation/moz.build
@@ -1,28 +1,30 @@
 # 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/.
 
+DIRS += [
+    'content',
+]
+
 with Files("**"):
     BUG_COMPONENT = ("Firefox", "Translation")
 
 EXTRA_JS_MODULES.translation = [
     'BingTranslator.jsm',
     'cld2/cld-worker.js',
     'cld2/cld-worker.js.mem',
     'GoogleTranslator.jsm',
     'LanguageDetector.jsm',
     'Translation.jsm',
     'TranslationContentHandler.jsm',
     'TranslationDocument.jsm',
     'YandexTranslator.jsm'
 ]
 
-JAR_MANIFESTS += ['jar.mn']
-
 BROWSER_CHROME_MANIFESTS += [
     'test/browser.ini'
 ]
 
 XPCSHELL_TESTS_MANIFESTS += [
     'test/unit/xpcshell.ini'
 ]
--- a/browser/extensions/formautofill/skin/shared/editAddress.css
+++ b/browser/extensions/formautofill/skin/shared/editAddress.css
@@ -102,16 +102,20 @@
 /* End name field rules */
 
 #name-container,
 #street-address-container {
   /* Name and street address are always full-width */
   flex: 0 1 100%;
 }
 
+#street-address {
+  resize: vertical;
+}
+
 #country-warning-message {
   box-sizing: border-box;
   font-size: 1rem;
   align-items: center;
   text-align: start;
   color: #737373;
   padding-inline-start: 1em;
 }
--- a/browser/modules/test/browser/browser_UnsubmittedCrashHandler.js
+++ b/browser/modules/test/browser/browser_UnsubmittedCrashHandler.js
@@ -274,19 +274,17 @@ add_task(async function test_one_pending
  */
 add_task(async function test_other_ignored() {
   let toIgnore = await createPendingCrashReports(1);
   let notification =
     await UnsubmittedCrashHandler.checkForUnsubmittedCrashReports();
   Assert.ok(notification, "There should be a notification");
 
   // Dismiss notification, creating the .dmp.ignore file
-  let closeButton =
-    document.getAnonymousElementByAttribute(notification, "anonid", "close-button");
-  closeButton.click();
+  notification.querySelector(".messageCloseButton").click();
   gNotificationBox.removeNotification(notification, true);
   await waitForIgnoredReports(toIgnore);
 
   notification =
     await UnsubmittedCrashHandler.checkForUnsubmittedCrashReports();
   Assert.ok(!notification, "There should not be a notification");
 
   await createPendingCrashReports(1);
@@ -466,19 +464,17 @@ add_task(async function test_can_auto_su
  */
 add_task(async function test_can_ignore() {
   let reportIDs = await createPendingCrashReports(3);
   let notification =
     await UnsubmittedCrashHandler.checkForUnsubmittedCrashReports();
   Assert.ok(notification, "There should be a notification");
 
   // Dismiss the notification by clicking on the "X" button.
-  let closeButton =
-    document.getAnonymousElementByAttribute(notification, "anonid", "close-button");
-  closeButton.click();
+  notification.querySelector(".messageCloseButton").click();
   // We'll not wait for the notification to finish its transition -
   // we'll just remove it right away.
   gNotificationBox.removeNotification(notification, true);
   await waitForIgnoredReports(reportIDs);
 
   notification =
     await UnsubmittedCrashHandler.checkForUnsubmittedCrashReports();
   Assert.equal(notification, null, "There should be no notification");
@@ -540,19 +536,17 @@ add_task(async function test_shutdown_wh
  */
 add_task(async function test_shutdown_while_not_showing() {
   let reportIDs = await createPendingCrashReports(1);
   let notification =
     await UnsubmittedCrashHandler.checkForUnsubmittedCrashReports();
   Assert.ok(notification, "There should be a notification");
 
   // Dismiss the notification by clicking on the "X" button.
-  let closeButton =
-    document.getAnonymousElementByAttribute(notification, "anonid", "close-button");
-  closeButton.click();
+  notification.querySelector(".messageCloseButton").click();
   // We'll not wait for the notification to finish its transition -
   // we'll just remove it right away.
   gNotificationBox.removeNotification(notification, true);
 
   await waitForIgnoredReports(reportIDs);
 
   UnsubmittedCrashHandler.uninit();
   Assert.throws(() => {
--- a/browser/themes/shared/translation/infobar.inc.css
+++ b/browser/themes/shared/translation/infobar.inc.css
@@ -1,33 +1,33 @@
 %if 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/. */
 %endif
 notification[value="translation"] .messageImage {
-  list-style-image: url(chrome://browser/skin/translation-16.png);
+  list-style-image: url(chrome://browser/skin/translation-16.png) !important;
   -moz-image-region: rect(0, 32px, 16px, 16px);
 }
 
 @media (min-resolution: 1.25dppx) {
   notification[value="translation"] .messageImage {
-    list-style-image: url(chrome://browser/skin/translation-16@2x.png);
+    list-style-image: url(chrome://browser/skin/translation-16@2x.png) !important;
     -moz-image-region: rect(0, 64px, 32px, 32px);
   }
 }
 
 notification[value="translation"][state="translating"] .messageImage {
-  list-style-image: url(chrome://browser/skin/translating-16.png);
+  list-style-image: url(chrome://browser/skin/translating-16.png) !important;
   -moz-image-region: auto;
 }
 
 @media (min-resolution: 1.25dppx) {
   notification[value="translation"][state="translating"] .messageImage {
-    list-style-image: url(chrome://browser/skin/translating-16@2x.png);
+    list-style-image: url(chrome://browser/skin/translating-16@2x.png) !important;
   }
 }
 
 notification[value="translation"] hbox[anonid="details"] {
   overflow: hidden;
 }
 
 notification[value="translation"] button,
--- a/devtools/client/scratchpad/test/browser_scratchpad_open.js
+++ b/devtools/client/scratchpad/test/browser_scratchpad_open.js
@@ -77,18 +77,20 @@ function testOpenTestFile() {
       win.Scratchpad.importFromFile(
         "http://example.com/browser/devtools/client/scratchpad/test/NS_ERROR_ILLEGAL_INPUT.txt",
         "silent",
         function(aStatus, content) {
           const nb = win.Scratchpad.notificationBox;
           is(nb.allNotifications.length, 1, "There is just one notification");
           const cn = nb.currentNotification;
           is(cn.priority, nb.PRIORITY_WARNING_HIGH, "notification priority is correct");
-          is(cn.value, "file-import-convert-failed", "notification value is corrent");
-          is(cn.type, "warning", "notification type is correct");
+          is(cn.getAttribute("value"), "file-import-convert-failed",
+             "notification value is corrent");
+          is(cn.getAttribute("type"), "warning",
+             "notification type is correct");
           done();
         });
       ok(true, "importFromFile does not cause exception");
     } catch (exception) {
       ok(false, "importFromFile causes exception " + DevToolsUtils.safeErrorString(exception));
     }
   }, {noFocus: true});
 }
--- a/devtools/client/scratchpad/test/browser_scratchpad_recent_files.js
+++ b/devtools/client/scratchpad/test/browser_scratchpad_recent_files.js
@@ -147,17 +147,17 @@ function testOpenDeletedFile() {
   is(gScratchpad.getRecentFiles().length, 1,
      "The missing file was successfully removed from the list.");
   // The number of recent files stored, plus the separator and the
   // clearRecentMenuItems-item.
   is(popup.children.length, 3,
      "The missing file was successfully removed from the menu.");
   ok(gScratchpad.notificationBox.currentNotification,
      "The notification was successfully displayed.");
-  is(gScratchpad.notificationBox.currentNotification.label,
+  is(gScratchpad.notificationBox.currentNotification.messageText.textContent,
      gScratchpad.strings.GetStringFromName("fileNoLongerExists.notification"),
      "The notification label is correct.");
 
   gScratchpad.clearRecentFiles();
 }
 
 // We have cleared the last file. Test to see if the last file was removed,
 // the menu is empty and was disabled successfully.
--- a/devtools/shared/fronts/csscoverage.js
+++ b/devtools/shared/fronts/csscoverage.js
@@ -58,17 +58,17 @@ const CSSUsageFront = protocol.FrontClas
         notification = gnb.appendNotification(msg, "csscoverage-running",
                                               "",
                                               gnb.PRIORITY_INFO_HIGH,
                                               null,
                                               notifyStop);
       }
     } else {
       if (notification) {
-        notification.remove();
+        notification.close();
         notification = undefined;
       }
 
       gDevTools.showToolbox(target, "styleeditor");
       target = undefined;
     }
   }),
 
--- a/dom/html/nsHTMLDocument.cpp
+++ b/dom/html/nsHTMLDocument.cpp
@@ -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/. */
 
 #include "nsHTMLDocument.h"
 
 #include "nsIContentPolicy.h"
 #include "mozilla/DebugOnly.h"
 #include "mozilla/dom/HTMLAllCollection.h"
+#include "mozilla/dom/FeaturePolicyUtils.h"
 #include "nsCOMPtr.h"
 #include "nsGlobalWindow.h"
 #include "nsString.h"
 #include "nsPrintfCString.h"
 #include "nsReadableUtils.h"
 #include "nsUnicharUtils.h"
 #include "nsIHTMLContentSink.h"
 #include "nsIXMLContentSink.h"
@@ -1017,16 +1018,22 @@ void
 nsHTMLDocument::SetDomain(const nsAString& aDomain, ErrorResult& rv)
 {
   if (mSandboxFlags & SANDBOXED_DOMAIN) {
     // We're sandboxed; disallow setting domain
     rv.Throw(NS_ERROR_DOM_SECURITY_ERR);
     return;
   }
 
+  if (!FeaturePolicyUtils::IsFeatureAllowed(this,
+                                            NS_LITERAL_STRING("document-domain"))) {
+    rv.Throw(NS_ERROR_DOM_SECURITY_ERR);
+    return;
+  }
+
   if (aDomain.IsEmpty()) {
     rv.Throw(NS_ERROR_DOM_SECURITY_ERR);
     return;
   }
 
   nsCOMPtr<nsIURI> uri = GetDomainURI();
   if (!uri) {
     rv.Throw(NS_ERROR_FAILURE);
--- a/dom/payments/PaymentRequest.cpp
+++ b/dom/payments/PaymentRequest.cpp
@@ -60,34 +60,27 @@ NS_INTERFACE_MAP_END_INHERITING(DOMEvent
 
 NS_IMPL_ADDREF_INHERITED(PaymentRequest, DOMEventTargetHelper)
 NS_IMPL_RELEASE_INHERITED(PaymentRequest, DOMEventTargetHelper)
 
 bool
 PaymentRequest::PrefEnabled(JSContext* aCx, JSObject* aObj)
 {
 #if defined(NIGHTLY_BUILD)
-  const char* supportedRegions[] = { "US", "CA" };
-
   if (!XRE_IsContentProcess()) {
     return false;
   }
   if (!StaticPrefs::dom_payments_request_enabled()) {
     return false;
   }
+  RefPtr<PaymentRequestManager> manager = PaymentRequestManager::GetSingleton();
+  MOZ_ASSERT(manager);
   nsAutoString region;
   Preferences::GetString("browser.search.region", region);
-  bool regionIsSupported = false;
-  for (const char* each : supportedRegions) {
-    if (region.EqualsASCII(each)) {
-      regionIsSupported = true;
-      break;
-    }
-  }
-  if (!regionIsSupported) {
+  if (!manager->IsRegionSupported(region)) {
     return false;
   }
   nsAutoCString locale;
   LocaleService::GetInstance()->GetAppLocaleAsLangTag(locale);
   mozilla::intl::Locale loc = mozilla::intl::Locale(locale);
   if (!(loc.GetLanguage() == "en" && loc.GetRegion() == "US")) {
     return false;
   }
--- a/dom/payments/PaymentRequestManager.cpp
+++ b/dom/payments/PaymentRequestManager.cpp
@@ -299,16 +299,53 @@ ConvertResponseData(const IPCPaymentResp
     }
   }
 }
 } // end of namespace
 
 /* PaymentRequestManager */
 
 StaticRefPtr<PaymentRequestManager> gPaymentManager;
+const char kSupportedRegionsPref[] = "dom.payments.request.supportedRegions";
+
+void
+SupportedRegionsPrefChangedCallback(const char* aPrefName, nsTArray<nsString>* aRetval)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  MOZ_ASSERT(!strcmp(aPrefName, kSupportedRegionsPref));
+
+  nsAutoString supportedRegions;
+  Preferences::GetString(aPrefName, supportedRegions);
+  aRetval->Clear();
+  for (const nsAString& each : supportedRegions.Split(',')) {
+    aRetval->AppendElement(each);
+  }
+}
+
+PaymentRequestManager::PaymentRequestManager()
+{
+  Preferences::RegisterCallbackAndCall(SupportedRegionsPrefChangedCallback,
+                                       kSupportedRegionsPref,
+                                       &this->mSupportedRegions);
+}
+
+PaymentRequestManager::~PaymentRequestManager()
+{
+  MOZ_ASSERT(mActivePayments.Count() == 0);
+  Preferences::UnregisterCallback(SupportedRegionsPrefChangedCallback,
+                                  kSupportedRegionsPref,
+                                  &this->mSupportedRegions);
+  mSupportedRegions.Clear();
+}
+
+bool
+PaymentRequestManager::IsRegionSupported(const nsAString& region) const
+{
+  return mSupportedRegions.Contains(region);
+}
 
 PaymentRequestChild*
 PaymentRequestManager::GetPaymentChild(PaymentRequest* aRequest)
 {
   MOZ_ASSERT(aRequest);
 
   if (PaymentRequestChild* child = aRequest->GetIPC()) {
     return child;
--- a/dom/payments/PaymentRequestManager.h
+++ b/dom/payments/PaymentRequestManager.h
@@ -69,35 +69,36 @@ public:
   nsresult ChangeShippingOption(PaymentRequest* aRequest,
                                 const nsAString& aOption);
 
   nsresult ChangePayerDetail(PaymentRequest* aRequest,
                              const nsAString& aPayerName,
                              const nsAString& aPayerEmail,
                              const nsAString& aPayerPhone);
 
+  bool IsRegionSupported(const nsAString& region) const;
+
   // Called to ensure that we don't "leak" aRequest if we shut down while it had
   // an active request to the parent.
   void RequestIPCOver(PaymentRequest* aRequest);
 
 private:
-  PaymentRequestManager() = default;
-  ~PaymentRequestManager()
-  {
-    MOZ_ASSERT(mActivePayments.Count() == 0);
-  }
+  PaymentRequestManager();
+  ~PaymentRequestManager();
 
   PaymentRequestChild* GetPaymentChild(PaymentRequest* aRequest);
 
   nsresult SendRequestPayment(PaymentRequest* aRequest,
                               const IPCPaymentActionRequest& action,
                               bool aResponseExpected = true);
 
   void NotifyRequestDone(PaymentRequest* aRequest);
 
   // Strong pointer to requests with ongoing IPC messages to the parent.
   nsDataHashtable<nsRefPtrHashKey<PaymentRequest>, uint32_t> mActivePayments;
+
+  nsTArray<nsString> mSupportedRegions;
 };
 
 } // end of namespace dom
 } // end of namespace mozilla
 
 #endif
--- a/dom/security/featurepolicy/FeaturePolicyUtils.cpp
+++ b/dom/security/featurepolicy/FeaturePolicyUtils.cpp
@@ -25,16 +25,17 @@ static FeatureMap sSupportedFeatures[] =
   { "autoplay", FeaturePolicyUtils::FeaturePolicyValue::eAll },
   { "camera", FeaturePolicyUtils::FeaturePolicyValue::eSelf },
   { "encrypted-media", FeaturePolicyUtils::FeaturePolicyValue::eAll },
   { "fullscreen", FeaturePolicyUtils::FeaturePolicyValue::eAll },
   { "geolocation", FeaturePolicyUtils::FeaturePolicyValue::eAll },
   { "microphone", FeaturePolicyUtils::FeaturePolicyValue::eSelf },
   { "midi", FeaturePolicyUtils::FeaturePolicyValue::eSelf },
   { "payment", FeaturePolicyUtils::FeaturePolicyValue::eAll },
+  { "document-domain", FeaturePolicyUtils::FeaturePolicyValue::eAll },
   // TODO: not supported yet!!!
   { "speaker", FeaturePolicyUtils::FeaturePolicyValue::eSelf },
   { "vr", FeaturePolicyUtils::FeaturePolicyValue::eAll },
 };
 
 /* static */ bool
 FeaturePolicyUtils::IsSupportedFeature(const nsAString& aFeatureName)
 {
--- a/editor/libeditor/tests/browserscope/mochitest.ini
+++ b/editor/libeditor/tests/browserscope/mochitest.ini
@@ -49,11 +49,11 @@ support-files =
   lib/richtext/README.Mozilla
   lib/richtext/richtext/editable.html
   lib/richtext/richtext/richtext.html
   lib/richtext/richtext/js/range.js
   lib/richtext/currentStatus.js
 
 [test_richtext2.html]
 subsuite = clipboard
-skip-if = os == 'android' && debug # Bug 1202045
+skip-if = os == 'android' # Bug 1202045
 [test_richtext.html]
 
--- a/extensions/cookie/test/unit/test_permmanager_migrate_4-7.js
+++ b/extensions/cookie/test/unit/test_permmanager_migrate_4-7.js
@@ -11,16 +11,17 @@ function GetPermissionsFile(profile)
   let file = profile.clone();
   file.append(PERMISSIONS_FILE_NAME);
   return file;
 }
 
 add_task(async function test() {
   /* Create and set up the permissions database */
   let profile = do_get_profile();
+  Services.prefs.setCharPref("permissions.manager.defaultsUrl", "");
 
   let db = Services.storage.openDatabase(GetPermissionsFile(profile));
   db.schemaVersion = 4;
 
   db.executeSimpleSQL(
     "CREATE TABLE moz_hosts (" +
       " id INTEGER PRIMARY KEY" +
       ",host TEXT" +
--- a/extensions/cookie/test/unit/test_permmanager_migrate_4-7_no_history.js
+++ b/extensions/cookie/test/unit/test_permmanager_migrate_4-7_no_history.js
@@ -40,16 +40,17 @@ function GetPermissionsFile(profile)
 
 /*
  * Done nsINavHistoryService code
  */
 
 add_task(function test() {
   /* Create and set up the permissions database */
   let profile = do_get_profile();
+  Services.prefs.setCharPref("permissions.manager.defaultsUrl", "");
 
   // Make sure that we can't resolve the nsINavHistoryService
   try {
     Cc['@mozilla.org/browser/nav-history-service;1'].getService(Ci.nsINavHistoryService);
     Assert.ok(false, "There shouldn't have been a nsINavHistoryService");
   } catch (e) {
     Assert.ok(true, "There wasn't a nsINavHistoryService");
   }
--- a/extensions/cookie/test/unit/test_permmanager_migrate_5-7a.js
+++ b/extensions/cookie/test/unit/test_permmanager_migrate_5-7a.js
@@ -11,16 +11,17 @@ function GetPermissionsFile(profile)
   let file = profile.clone();
   file.append(PERMISSIONS_FILE_NAME);
   return file;
 }
 
 add_task(async function test() {
   /* Create and set up the permissions database */
   let profile = do_get_profile();
+  Services.prefs.setCharPref("permissions.manager.defaultsUrl", "");
 
   let db = Services.storage.openDatabase(GetPermissionsFile(profile));
   db.schemaVersion = 5;
 
   /*
    * V5 table
    */
   db.executeSimpleSQL(
--- a/extensions/cookie/test/unit/test_permmanager_migrate_5-7b.js
+++ b/extensions/cookie/test/unit/test_permmanager_migrate_5-7b.js
@@ -11,16 +11,17 @@ function GetPermissionsFile(profile)
   let file = profile.clone();
   file.append(PERMISSIONS_FILE_NAME);
   return file;
 }
 
 add_task(function test() {
   /* Create and set up the permissions database */
   let profile = do_get_profile();
+  Services.prefs.setCharPref("permissions.manager.defaultsUrl", "");
 
   let db = Services.storage.openDatabase(GetPermissionsFile(profile));
   db.schemaVersion = 5;
 
   /*
    * V5 table
    */
   db.executeSimpleSQL(
--- a/extensions/cookie/test/unit/test_permmanager_migrate_6-7a.js
+++ b/extensions/cookie/test/unit/test_permmanager_migrate_6-7a.js
@@ -11,16 +11,17 @@ function GetPermissionsFile(profile)
   let file = profile.clone();
   file.append(PERMISSIONS_FILE_NAME);
   return file;
 }
 
 add_task(async function test() {
   /* Create and set up the permissions database */
   let profile = do_get_profile();
+  Services.prefs.setCharPref("permissions.manager.defaultsUrl", "");
 
   let db = Services.storage.openDatabase(GetPermissionsFile(profile));
   db.schemaVersion = 6;
 
   /*
    * V5 table
    */
   db.executeSimpleSQL(
--- a/extensions/cookie/test/unit/test_permmanager_migrate_6-7b.js
+++ b/extensions/cookie/test/unit/test_permmanager_migrate_6-7b.js
@@ -11,16 +11,17 @@ function GetPermissionsFile(profile)
   let file = profile.clone();
   file.append(PERMISSIONS_FILE_NAME);
   return file;
 }
 
 add_task(function test() {
   /* Create and set up the permissions database */
   let profile = do_get_profile();
+  Services.prefs.setCharPref("permissions.manager.defaultsUrl", "");
 
   let db = Services.storage.openDatabase(GetPermissionsFile(profile));
   db.schemaVersion = 6;
 
   /*
    * V5 table
    */
   db.executeSimpleSQL(
--- a/extensions/cookie/test/unit/test_permmanager_migrate_7-8.js
+++ b/extensions/cookie/test/unit/test_permmanager_migrate_7-8.js
@@ -11,16 +11,17 @@ function GetPermissionsFile(profile)
   let file = profile.clone();
   file.append(PERMISSIONS_FILE_NAME);
   return file;
 }
 
 add_task(async function test() {
   /* Create and set up the permissions database */
   let profile = do_get_profile();
+  Services.prefs.setCharPref("permissions.manager.defaultsUrl", "");
 
   let db = Services.storage.openDatabase(GetPermissionsFile(profile));
   db.schemaVersion = 7;
 
   /*
    * V5 table
    */
   db.executeSimpleSQL(
--- a/extensions/cookie/test/unit/test_permmanager_notifications.js
+++ b/extensions/cookie/test/unit/test_permmanager_notifications.js
@@ -2,16 +2,17 @@
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 // Test that the permissionmanager 'added', 'changed', 'deleted', and 'cleared'
 // notifications behave as expected.
 
 var test_generator = do_run_test();
 
 function run_test() {
+  Services.prefs.setCharPref("permissions.manager.defaultsUrl", "");
   do_test_pending();
   test_generator.next();
 }
 
 function continue_test()
 {
   do_run_generator(test_generator);
 }
--- a/extensions/cookie/test/unit/test_permmanager_removebytype.js
+++ b/extensions/cookie/test/unit/test_permmanager_removebytype.js
@@ -1,12 +1,14 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 function run_test() {
+  Services.prefs.setCharPref("permissions.manager.defaultsUrl", "");
+
   // initialize the permission manager service
   let pm = Cc["@mozilla.org/permissionmanager;1"].
         getService(Ci.nsIPermissionManager);
 
   Assert.equal(perm_count(), 0);
 
   // add some permissions
   let uri = NetUtil.newURI("http://amazon.com:8080/foobarbaz");
--- a/gfx/layers/BufferTexture.cpp
+++ b/gfx/layers/BufferTexture.cpp
@@ -271,60 +271,41 @@ gfx::SurfaceFormat
 BufferTextureData::GetFormat() const
 {
   return ImageDataSerializer::FormatFromBufferDescriptor(mDescriptor);
 }
 
 already_AddRefed<gfx::DrawTarget>
 BufferTextureData::BorrowDrawTarget()
 {
-  if (mDrawTarget) {
-    mDrawTarget->SetTransform(gfx::Matrix());
-    RefPtr<gfx::DrawTarget> dt = mDrawTarget;
-    return dt.forget();
-  }
-
   if (mDescriptor.type() != BufferDescriptor::TRGBDescriptor) {
     return nullptr;
   }
 
   const RGBDescriptor& rgb = mDescriptor.get_RGBDescriptor();
 
   uint32_t stride = ImageDataSerializer::GetRGBStride(rgb);
+  RefPtr<gfx::DrawTarget> dt;
   if (gfx::Factory::DoesBackendSupportDataDrawtarget(mMoz2DBackend)) {
-    mDrawTarget = gfx::Factory::CreateDrawTargetForData(mMoz2DBackend,
-                                                        GetBuffer(), rgb.size(),
-                                                        stride, rgb.format(), true);
-  } else {
+    dt = gfx::Factory::CreateDrawTargetForData(mMoz2DBackend,
+                                               GetBuffer(), rgb.size(),
+                                               stride, rgb.format(), true);
+  }
+  if (!dt) {
     // Fall back to supported platform backend.  Note that mMoz2DBackend
     // does not match the draw target type.
-    mDrawTarget = gfxPlatform::CreateDrawTargetForData(GetBuffer(), rgb.size(),
-                                                       stride, rgb.format(),
-                                                       true);
+    dt = gfxPlatform::CreateDrawTargetForData(GetBuffer(), rgb.size(),
+                                              stride, rgb.format(),
+                                              true);
   }
 
-  if (mDrawTarget) {
-    RefPtr<gfx::DrawTarget> dt = mDrawTarget;
-    return dt.forget();
-  }
-
-  // TODO - should we warn? should we really fallback to cairo? perhaps
-  // at least update mMoz2DBackend...
-  if (mMoz2DBackend != gfx::BackendType::CAIRO) {
-    gfxCriticalNote << "Falling to CAIRO from " << (int)mMoz2DBackend;
-    mDrawTarget = gfx::Factory::CreateDrawTargetForData(gfx::BackendType::CAIRO,
-                                                        GetBuffer(), rgb.size(),
-                                                        stride, rgb.format(), true);
-  }
-
-  if (!mDrawTarget) {
+  if (!dt) {
     gfxCriticalNote << "BorrowDrawTarget failure, original backend " << (int)mMoz2DBackend;
   }
 
-  RefPtr<gfx::DrawTarget> dt = mDrawTarget;
   return dt.forget();
 }
 
 bool
 BufferTextureData::BorrowMappedData(MappedTextureData& aData)
 {
   if (GetFormat() == gfx::SurfaceFormat::YUV) {
     return false;
--- a/gfx/layers/BufferTexture.h
+++ b/gfx/layers/BufferTexture.h
@@ -82,17 +82,16 @@ protected:
   virtual uint8_t* GetBuffer() = 0;
   virtual size_t GetBufferSize() = 0;
 
   BufferTextureData(const BufferDescriptor& aDescriptor, gfx::BackendType aMoz2DBackend)
   : mDescriptor(aDescriptor)
   , mMoz2DBackend(aMoz2DBackend)
   {}
 
-  RefPtr<gfx::DrawTarget> mDrawTarget;
   BufferDescriptor mDescriptor;
   gfx::BackendType mMoz2DBackend;
 };
 
 } // namespace
 } // namespace
 
 #endif
--- a/image/AnimationSurfaceProvider.cpp
+++ b/image/AnimationSurfaceProvider.cpp
@@ -71,17 +71,17 @@ AnimationSurfaceProvider::DropImageRefer
                                     mImage.forget());
 }
 
 void
 AnimationSurfaceProvider::Reset()
 {
   // We want to go back to the beginning.
   bool mayDiscard;
-  bool restartDecoder;
+  bool restartDecoder = false;
 
   {
     MutexAutoLock lock(mFramesMutex);
 
     // If we have not crossed the threshold, we know we haven't discarded any
     // frames, and thus we know it is safe move our display index back to the
     // very beginning. It would be cleaner to let the frame buffer make this
     // decision inside the AnimationFrameBuffer::Reset method, but if we have
@@ -95,22 +95,33 @@ AnimationSurfaceProvider::Reset()
 
   if (mayDiscard) {
     // We are over the threshold and have started discarding old frames. In
     // that case we need to seize the decoding mutex. Thankfully we know that
     // we are in the process of decoding at most the batch size frames, so
     // this should not take too long to acquire.
     MutexAutoLock lock(mDecodingMutex);
 
-    // Recreate the decoder so we can regenerate the frames again.
-    mDecoder = DecoderFactory::CloneAnimationDecoder(mDecoder);
-    MOZ_ASSERT(mDecoder);
+    // We may have hit an error while redecoding. Because FrameAnimator is
+    // tightly coupled to our own state, that means we would need to go through
+    // some heroics to resume animating in those cases. The typical reason for
+    // a redecode to fail is out of memory, and recycling should prevent most of
+    // those errors. When image.animated.generate-full-frames has shipped
+    // enabled on a release or two, we can simply remove the old FrameAnimator
+    // blending code and simplify this quite a bit -- just always pop the next
+    // full frame and timeout off the stack.
+    if (mDecoder) {
+      mDecoder = DecoderFactory::CloneAnimationDecoder(mDecoder);
+      MOZ_ASSERT(mDecoder);
 
-    MutexAutoLock lock2(mFramesMutex);
-    restartDecoder = mFrames->Reset();
+      MutexAutoLock lock2(mFramesMutex);
+      restartDecoder = mFrames->Reset();
+    } else {
+      MOZ_ASSERT(mFrames->HasRedecodeError());
+    }
   }
 
   if (restartDecoder) {
     DecodePool::Singleton()->AsyncRun(this);
   }
 }
 
 void
--- a/js/src/builtin/TestingFunctions.cpp
+++ b/js/src/builtin/TestingFunctions.cpp
@@ -691,26 +691,47 @@ WasmBulkMemSupported(JSContext* cx, unsi
 #else
     bool isSupported = false;
 #endif
     args.rval().setBoolean(isSupported);
     return true;
 }
 
 static bool
-WasmGcEnabled(JSContext* cx, unsigned argc, Value* vp)
-{
-    CallArgs args = CallArgsFromVp(argc, vp);
+TestGCEnabled(JSContext* cx)
+{
 #ifdef ENABLE_WASM_GC
     bool isSupported = cx->options().wasmBaseline() && cx->options().wasmGc();
 # ifdef ENABLE_WASM_CRANELIFT
     if (cx->options().wasmForceCranelift()) {
         isSupported = false;
     }
 # endif
+    return isSupported;
+#else
+    return false;
+#endif
+}
+
+static bool
+WasmGcEnabled(JSContext* cx, unsigned argc, Value* vp)
+{
+    CallArgs args = CallArgsFromVp(argc, vp);
+    args.rval().setBoolean(TestGCEnabled(cx));
+    return true;
+}
+
+static bool
+WasmGeneralizedTables(JSContext* cx, unsigned argc, Value* vp)
+{
+    CallArgs args = CallArgsFromVp(argc, vp);
+#ifdef ENABLE_WASM_GENERALIZED_TABLES
+    // Generalized tables depend on anyref, though not currently on (ref T)
+    // types nor on structures or other GC-proposal features.
+    bool isSupported = TestGCEnabled(cx);
 #else
     bool isSupported = false;
 #endif
     args.rval().setBoolean(isSupported);
     return true;
 }
 
 static bool
@@ -6139,16 +6160,22 @@ gc::ZealModeHelpText),
 "wasmHasTier2CompilationCompleted(module)",
 "  Returns a boolean indicating whether a given module has finished compiled code for tier2. \n"
 "This will return true early if compilation isn't two-tiered. "),
 
     JS_FN_HELP("wasmGcEnabled", WasmGcEnabled, 1, 0,
 "wasmGcEnabled(bool)",
 "  Returns a boolean indicating whether the WebAssembly GC support is enabled."),
 
+    JS_FN_HELP("wasmGeneralizedTables", WasmGeneralizedTables, 1, 0,
+"wasmGeneralizedTables(bool)",
+"  Returns a boolean indicating whether generalized tables are available.\n"
+"  This feature set includes 'anyref' as a table type, and new instructions\n"
+"  including table.get, table.set, table.grow, and table.size."),
+
     JS_FN_HELP("isLazyFunction", IsLazyFunction, 1, 0,
 "isLazyFunction(fun)",
 "  True if fun is a lazy JSFunction."),
 
     JS_FN_HELP("isRelazifiableFunction", IsRelazifiableFunction, 1, 0,
 "isRelazifiableFunction(fun)",
 "  True if fun is a JSFunction with a relazifiable JSScript."),
 
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/ion/merge-phi-usage-analysis.js
@@ -0,0 +1,63 @@
+
+function expensive() {
+    with({}) {}
+}
+
+function phi_merge_0(i) {
+    // These computations can overflow, if the output is not truncated.
+    i = i | 0;
+    var a0 = i + i;
+    var a1 = i + i;
+
+    if ((a1 | 0) - ((2 * i) | 0)) {
+        // Good candidate for branch pruning, which marks only a1 as having
+        // removed uses.
+        expensive();
+        expensive();
+        expensive();
+        expensive();
+        expensive();
+    }
+
+    // Simple branch made to let GVN merge the Phi instructions.
+    if (a1 % 3 == 1) {
+        a1 = 2 * i;
+        a0 = 2 * i;
+    }
+
+    // a0 is never used, but a1 is truncated.
+    return a1 | 0;
+}
+
+function phi_merge_1(i) {
+    // These computations can overflow, if the output is not truncated.
+    i = i | 0;
+    var a1 = i + i;
+    var a0 = i + i;
+
+    if ((a1 | 0) - ((2 * i) | 0)) {
+        // Good candidate for branch pruning, which marks only a1 as having
+        // removed uses.
+        expensive();
+        expensive();
+        expensive();
+        expensive();
+        expensive();
+    }
+
+    // Simple branch made to let GVN merge the Phi instructions.
+    if (a1 % 3 == 1) {
+        a1 = 2 * i;
+        a0 = 2 * i;
+    }
+
+    // a0 is never used, but a1 is truncated.
+    return a1 | 0;
+}
+
+for (var j = 0; j < 300; j++) {
+    for (var i = 1; i == (i | 0); i = 2 * i + 1) {
+        assertEq(phi_merge_0(i) < 0x80000000, true);
+        assertEq(phi_merge_1(i) < 0x80000000, true);
+    }
+}
--- a/js/src/jit-test/tests/wasm/atomic.js
+++ b/js/src/jit-test/tests/wasm/atomic.js
@@ -50,40 +50,41 @@ for (let [type,view] of [['i32','8_u'],[
 for (let type of ['i32', 'i64']) {
     let text = (shared) => `(module (memory 1 1 ${shared})
 			     (func (result i32) (${type}.atomic.wait (i32.const 0) (${type}.const 1) (i64.const -1)))
 			     (export "" 0))`;
     assertEq(valText(text(SHARED)), true);
     assertEq(valText(text(UNSHARED)), false);
 }
 
-{
+// 'wake' remains a backwards-compatible alias for 'notify'
+for ( let notify of ['wake', 'notify']) {
     let text = (shared) => `(module (memory 1 1 ${shared})
-			     (func (result i32) (atomic.wake (i32.const 0) (i32.const 1)))
+			     (func (result i32) (atomic.${notify} (i32.const 0) (i32.const 1)))
 			     (export "" 0))`;
     assertEq(valText(text(SHARED)), true);
     assertEq(valText(text(UNSHARED)), false);
 }
 
 // Required explicit alignment for WAIT is the size of the datum
 
 for (let [type,align,good] of [['i32',1,false],['i32',2,false],['i32',4,true],['i32',8,false],
 			       ['i64',1,false],['i64',2,false],['i64',4,false],['i64',8,true]])
 {
     let text = `(module (memory 1 1 shared)
 		 (func (result i32) (${type}.atomic.wait align=${align} (i32.const 0) (${type}.const 1) (i64.const -1)))
 		 (export "" 0))`;
     assertEq(valText(text), good);
 }
 
-// Required explicit alignment for WAKE is 4
+// Required explicit alignment for NOTIFY is 4
 
 for (let align of [1, 2, 4, 8]) {
     let text = `(module (memory 1 1 shared)
-		 (func (result i32) (atomic.wake align=${align} (i32.const 0) (i32.const 1)))
+		 (func (result i32) (atomic.notify align=${align} (i32.const 0) (i32.const 1)))
 		 (export "" 0))`;
     assertEq(valText(text), align == 4);
 }
 
 function valText(text) {
     return WebAssembly.validate(wasmTextToBinary(text));
 }
 
@@ -461,17 +462,17 @@ var BoundsAndAlignment =
 		}
 	    }
 	}
     }
 }
 
 BoundsAndAlignment.run();
 
-// Bounds and alignment checks on wait and wake
+// Bounds and alignment checks on wait and notify
 
 assertErrorMessage(() => wasmEvalText(`(module (memory 1 1 shared)
 					(func (param i32) (result i32)
 					 (i32.atomic.wait (get_local 0) (i32.const 1) (i64.const -1)))
 					(export "" 0))`).exports[""](65536),
 		   RuntimeError, oob);
 
 assertErrorMessage(() => wasmEvalText(`(module (memory 1 1 shared)
@@ -489,25 +490,25 @@ assertErrorMessage(() => wasmEvalText(`(
 assertErrorMessage(() => wasmEvalText(`(module (memory 1 1 shared)
 					(func (param i32) (result i32)
 					 (i64.atomic.wait (get_local 0) (i64.const 1) (i64.const -1)))
 					(export "" 0))`).exports[""](65501),
 		   RuntimeError, unaligned);
 
 assertErrorMessage(() => wasmEvalText(`(module (memory 1 1 shared)
 					(func (param i32) (result i32)
-					 (atomic.wake (get_local 0) (i32.const 1)))
+					 (atomic.notify (get_local 0) (i32.const 1)))
 					(export "" 0))`).exports[""](65536),
 		   RuntimeError, oob);
 
-// Minimum run-time alignment for WAKE is 4
+// Minimum run-time alignment for NOTIFY is 4
 for (let addr of [1,2,3,5,6,7]) {
     assertErrorMessage(() => wasmEvalText(`(module (memory 1 1 shared)
 					    (func (export "f") (param i32) (result i32)
-					     (atomic.wake (get_local 0) (i32.const 1))))`).exports.f(addr),
+					     (atomic.notify (get_local 0) (i32.const 1))))`).exports.f(addr),
 		       RuntimeError, unaligned);
 }
 
 // Ensure alias analysis works even if atomic and non-atomic accesses are
 // mixed.
 assertErrorMessage(() => wasmEvalText(`(module
   (memory 0 1 shared)
   (func (export "main")
--- a/js/src/jit-test/tests/wasm/binary.js
+++ b/js/src/jit-test/tests/wasm/binary.js
@@ -110,30 +110,17 @@ wasmEval(moduleWithSections([tableSectio
 wasmEval(moduleWithSections([tableSection(1), elemSection([{offset:1, elems:[]}])]));
 assertErrorMessage(() => wasmEval(moduleWithSections([tableSection(1), elemSection([{offset:0, elems:[0]}])])), CompileError, /table element out of range/);
 wasmEval(moduleWithSections([v2vSigSection, declSection([0]), tableSection(1), elemSection([{offset:0, elems:[0]}]), bodySection([v2vBody])]));
 wasmEval(moduleWithSections([v2vSigSection, declSection([0]), tableSection(2), elemSection([{offset:0, elems:[0,0]}]), bodySection([v2vBody])]));
 assertErrorMessage(() => wasmEval(moduleWithSections([v2vSigSection, declSection([0]), tableSection(2), elemSection([{offset:0, elems:[0,1]}]), bodySection([v2vBody])])), CompileError, /table element out of range/);
 wasmEval(moduleWithSections([v2vSigSection, declSection([0,0,0]), tableSection(4), elemSection([{offset:0, elems:[0,1,0,2]}]), bodySection([v2vBody, v2vBody, v2vBody])]));
 wasmEval(moduleWithSections([sigSection([v2vSig,i2vSig]), declSection([0,0,1]), tableSection(3), elemSection([{offset:0,elems:[0,1,2]}]), bodySection([v2vBody, v2vBody, v2vBody])]));
 
-function invalidTableSection2() {
-    var body = [];
-    body.push(...varU32(2));           // number of tables
-    body.push(...varU32(AnyFuncCode));
-    body.push(...varU32(0x0));
-    body.push(...varU32(0));
-    body.push(...varU32(AnyFuncCode));
-    body.push(...varU32(0x0));
-    body.push(...varU32(0));
-    return { name: tableId, body };
-}
-
 wasmEval(moduleWithSections([tableSection0()]));
-assertErrorMessage(() => wasmEval(moduleWithSections([invalidTableSection2()])), CompileError, /number of tables must be at most one/);
 
 wasmEval(moduleWithSections([memorySection(0)]));
 
 function invalidMemorySection2() {
     var body = [];
     body.push(...varU32(2));           // number of memories
     body.push(...varU32(0x0));
     body.push(...varU32(0));
@@ -277,16 +264,18 @@ for (let i = 0x4f; i < 0x100; i++)
 
 // Illegal Misc opcodes
 
 var reservedMisc =
     { // Saturating conversions (standardized)
       0x00: true, 0x01: true, 0x02: true, 0x03: true, 0x04: true, 0x05: true, 0x06: true, 0x07: true,
       // Bulk memory (proposed)
       0x08: true, 0x09: true, 0x0a: true, 0x0b: true, 0x0c: true, 0x0d: true, 0x0e: true,
+      // Table (proposed)
+      0x0f: true, 0x10: true, 0x11: true, 0x12: true,
       // Structure operations (experimental, internal)
       0x50: true, 0x51: true, 0x52: true, 0x53: true };
 
 for (let i = 0; i < 256; i++) {
     if (reservedMisc.hasOwnProperty(i))
         continue;
     checkIllegalPrefixed(MiscPrefix, i);
 }
--- a/js/src/jit-test/tests/wasm/gc/gc-feature-opt-in.js
+++ b/js/src/jit-test/tests/wasm/gc/gc-feature-opt-in.js
@@ -13,25 +13,29 @@ var bad_order =
                     0x2a,                   // GcFeatureOptIn section
                     0x01,                   // Section size
                     0x01]);                 // Version
 
 assertErrorMessage(() => new WebAssembly.Module(bad_order),
                    WebAssembly.CompileError,
                    /expected custom section/);
 
-// Version numbers.  Version 1 is good, version 2 is bad.
+// Version numbers.  Version 1 and 2 are good, version 3 is bad.
+
+new WebAssembly.Module(wasmTextToBinary(
+    `(module
+      (gc_feature_opt_in 1))`));
 
 new WebAssembly.Module(wasmTextToBinary(
     `(module
       (gc_feature_opt_in 1))`));
 
 assertErrorMessage(() => new WebAssembly.Module(wasmTextToBinary(
     `(module
-      (gc_feature_opt_in 2))`)),
+      (gc_feature_opt_in 3))`)),
                    WebAssembly.CompileError,
                    /unsupported version of the gc feature/);
 
 // Struct types are only available if we opt in.
 
 new WebAssembly.Module(wasmTextToBinary(
     `(module
       (gc_feature_opt_in 1)
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/wasm/gc/tables-generalized-disabled.js
@@ -0,0 +1,6 @@
+// |jit-test| skip-if: wasmGeneralizedTables()
+
+assertErrorMessage(() => new WebAssembly.Table({element:"anyref", initial:10}),
+                   TypeError,
+                   /"element" property of table descriptor must be "anyfunc"/);
+
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/wasm/gc/tables-generalized.js
@@ -0,0 +1,439 @@
+// |jit-test| skip-if: !wasmGeneralizedTables()
+
+///////////////////////////////////////////////////////////////////////////
+//
+// General table management in wasm
+
+// Wasm: Create table-of-anyref
+
+new WebAssembly.Module(wasmTextToBinary(
+    `(module
+       (gc_feature_opt_in 1)
+       (table 10 anyref))`));
+
+// Wasm: Import table-of-anyref
+// JS: create table-of-anyref
+
+new WebAssembly.Instance(new WebAssembly.Module(wasmTextToBinary(
+    `(module
+       (gc_feature_opt_in 1)
+       (table (import "m" "t") 10 anyref))`)),
+                         {m:{t: new WebAssembly.Table({element:"anyref", initial:10})}});
+
+new WebAssembly.Instance(new WebAssembly.Module(wasmTextToBinary(
+    `(module
+       (gc_feature_opt_in 1)
+       (import "m" "t" (table 10 anyref)))`)),
+                         {m:{t: new WebAssembly.Table({element:"anyref", initial:10})}});
+
+// Wasm: Export table-of-anyref, initial values shall be null
+
+{
+    let ins = new WebAssembly.Instance(new WebAssembly.Module(wasmTextToBinary(
+    `(module
+       (gc_feature_opt_in 1)
+       (table (export "t") 10 anyref))`)));
+    let t = ins.exports.t;
+    assertEq(t.length, 10);
+    for (let i=0; i < t.length; i++)
+        assertEq(t.get(0), null);
+}
+
+// JS: Exported table can be grown, and values are preserved
+
+{
+    let ins = new WebAssembly.Instance(new WebAssembly.Module(wasmTextToBinary(
+    `(module
+       (gc_feature_opt_in 1)
+       (table (export "t") 10 anyref))`)));
+    let t = ins.exports.t;
+    let objs = [{}, {}, {}, {}, {}, {}, {}, {}, {}, {}];
+    for (let i in objs)
+        t.set(i, objs[i]);
+    ins.exports.t.grow(10);
+    assertEq(ins.exports.t.length, 20);
+    for (let i in objs)
+        assertEq(t.get(i), objs[i]);
+}
+
+// Wasm: table.copy between tables of anyref (currently source and destination
+// are both table zero)
+
+{
+    let ins = new WebAssembly.Instance(new WebAssembly.Module(wasmTextToBinary(
+    `(module
+       (gc_feature_opt_in 1)
+       (table (export "t") 10 anyref)
+       (func (export "f")
+         (table.copy (i32.const 5) (i32.const 0) (i32.const 3))))`)));
+    let t = ins.exports.t;
+    let objs = [{}, {}, {}, {}, {}, {}, {}, {}, {}, {}];
+    for (let i in objs)
+        t.set(i, objs[i]);
+    ins.exports.f();
+    assertEq(t.get(0), objs[0]);
+    assertEq(t.get(1), objs[1]);
+    assertEq(t.get(2), objs[2]);
+    assertEq(t.get(3), objs[3]);
+    assertEq(t.get(4), objs[4]);
+    assertEq(t.get(5), objs[0]);
+    assertEq(t.get(6), objs[1]);
+    assertEq(t.get(7), objs[2]);
+    assertEq(t.get(8), objs[8]);
+    assertEq(t.get(9), objs[9]);
+}
+
+// Wasm: element segments targeting table-of-anyref is forbidden
+
+assertErrorMessage(() => new WebAssembly.Module(wasmTextToBinary(
+    `(module
+       (gc_feature_opt_in 1)
+       (func $f1 (result i32) (i32.const 0))
+       (table 10 anyref)
+       (elem (i32.const 0) $f1))`)),
+                   WebAssembly.CompileError,
+                   /only tables of 'anyfunc' may have element segments/);
+
+// Wasm: table.init on table-of-anyref is forbidden
+
+assertErrorMessage(() => new WebAssembly.Module(wasmTextToBinary(
+    `(module
+       (gc_feature_opt_in 1)
+       (func $f1 (result i32) (i32.const 0))
+       (table 10 anyref)
+       (elem passive $f1)
+       (func
+         (table.init 0 (i32.const 0) (i32.const 0) (i32.const 0))))`)),
+                   WebAssembly.CompileError,
+                   /only tables of 'anyfunc' may have element segments/);
+
+// Wasm: table types must match at link time
+
+assertErrorMessage(
+    () => new WebAssembly.Instance(new WebAssembly.Module(wasmTextToBinary(
+    `(module
+       (gc_feature_opt_in 1)
+       (import "m" "t" (table 10 anyref)))`)),
+                                   {m:{t: new WebAssembly.Table({element:"anyfunc", initial:10})}}),
+    WebAssembly.LinkError,
+    /imported table type mismatch/);
+
+// call_indirect cannot reference table-of-anyref
+
+assertErrorMessage(() => new WebAssembly.Module(wasmTextToBinary(
+    `(module
+       (gc_feature_opt_in 1)
+       (table 10 anyref)
+       (type $t (func (param i32) (result i32)))
+       (func (result i32)
+         (call_indirect $t (i32.const 37))))`)),
+                   WebAssembly.CompileError,
+                   /indirect calls must go through a table of 'anyfunc'/);
+
+///////////////////////////////////////////////////////////////////////////
+//
+// additional js api tests
+
+{
+    let tbl = new WebAssembly.Table({element:"anyref", initial:10});
+
+    // Initial value.
+    assertEq(tbl.get(0), null);
+
+    // Identity preserving.
+    let x = {hi: 48};
+    tbl.set(0, x);
+    assertEq(tbl.get(0), x);
+    tbl.set(2, dummy);
+    assertEq(tbl.get(2), dummy);
+    tbl.set(2, null);
+    assertEq(tbl.get(2), null);
+
+    // Temporary semantics is to convert to object and leave as object; once we
+    // have a better wrapped anyref this will change, we won't be able to
+    // observe the boxing.
+    tbl.set(1, 42);
+    let y = tbl.get(1);
+    assertEq(typeof y, "object");
+    assertEq(y instanceof Number, true);
+    assertEq(y + 0, 42);
+
+    // Temporary semantics is to throw on undefined
+    assertErrorMessage(() => tbl.set(0, undefined),
+                       TypeError,
+                       /can't convert undefined to object/);
+}
+
+function dummy() { return 37 }
+
+///////////////////////////////////////////////////////////////////////////
+//
+// table.get and table.set
+
+// table.get in bounds - returns right value type & value
+// table.get out of bounds - fails
+
+{
+    let ins = wasmEvalText(
+        `(module
+           (gc_feature_opt_in 2)
+           (table (export "t") 10 anyref)
+           (func (export "f") (param i32) (result anyref)
+              (table.get (get_local 0))))`);
+    let x = {};
+    ins.exports.t.set(0, x);
+    assertEq(ins.exports.f(0), x);
+    assertEq(ins.exports.f(1), null);
+    assertErrorMessage(() => ins.exports.f(10), RangeError, /index out of bounds/);
+    assertErrorMessage(() => ins.exports.f(-5), RangeError, /index out of bounds/);
+}
+
+// table.get with non-i32 index - fails validation
+
+assertErrorMessage(() => new WebAssembly.Module(wasmTextToBinary(
+    `(module
+       (gc_feature_opt_in 2)
+       (table 10 anyref)
+       (func (export "f") (param f64) (result anyref)
+         (table.get (get_local 0))))`)),
+                   WebAssembly.CompileError,
+                   /type mismatch/);
+
+// table.get on table of anyfunc - fails validation because anyfunc is not expressible
+// Both with and without anyref support
+
+assertErrorMessage(() => new WebAssembly.Module(wasmTextToBinary(
+    `(module
+       (table 10 anyfunc)
+       (func (export "f") (param i32)
+         (drop (table.get (get_local 0)))))`)),
+                   WebAssembly.CompileError,
+                   /table.get only on tables of anyref/);
+
+assertErrorMessage(() => new WebAssembly.Module(wasmTextToBinary(
+    `(module
+       (gc_feature_opt_in 2)
+       (table 10 anyfunc)
+       (func (export "f") (param i32)
+         (drop (table.get (get_local 0)))))`)),
+                   WebAssembly.CompileError,
+                   /table.get only on tables of anyref/);
+
+// table.get when there are no tables - fails validation
+
+assertErrorMessage(() => new WebAssembly.Module(wasmTextToBinary(
+    `(module
+       (func (export "f") (param i32)
+         (drop (table.get (get_local 0)))))`)),
+                   WebAssembly.CompileError,
+                   /table index out of range for table.get/);
+
+// table.set in bounds with i32 x anyref - works, no value generated
+// table.set with (ref T) - works
+// table.set with null - works
+// table.set out of bounds - fails
+
+{
+    let ins = wasmEvalText(
+        `(module
+           (gc_feature_opt_in 2)
+           (table (export "t") 10 anyref)
+           (type $dummy (struct (field i32)))
+           (func (export "set_anyref") (param i32) (param anyref)
+             (table.set (get_local 0) (get_local 1)))
+           (func (export "set_null") (param i32)
+             (table.set (get_local 0) (ref.null anyref)))
+           (func (export "set_ref") (param i32) (param anyref)
+             (table.set (get_local 0) (struct.narrow anyref (ref $dummy) (get_local 1))))
+           (func (export "make_struct") (result anyref)
+             (struct.new $dummy (i32.const 37))))`);
+    let x = {};
+    ins.exports.set_anyref(3, x);
+    assertEq(ins.exports.t.get(3), x);
+    ins.exports.set_null(3);
+    assertEq(ins.exports.t.get(3), null);
+    let dummy = ins.exports.make_struct();
+    ins.exports.set_ref(5, dummy);
+    assertEq(ins.exports.t.get(5), dummy);
+
+    assertErrorMessage(() => ins.exports.set_anyref(10, x), RangeError, /index out of bounds/);
+    assertErrorMessage(() => ins.exports.set_anyref(-1, x), RangeError, /index out of bounds/);
+}
+
+// table.set with non-i32 index - fails validation
+
+assertErrorMessage(() => new WebAssembly.Module(wasmTextToBinary(
+    `(module
+       (gc_feature_opt_in 2)
+       (table 10 anyref)
+       (func (export "f") (param f64)
+         (table.set (get_local 0) (ref.null anyref))))`)),
+                   WebAssembly.CompileError,
+                   /type mismatch/);
+
+// table.set with non-anyref value - fails validation
+
+assertErrorMessage(() => new WebAssembly.Module(wasmTextToBinary(
+    `(module
+       (gc_feature_opt_in 2)
+       (table 10 anyref)
+       (func (export "f") (param f64)
+         (table.set (i32.const 0) (get_local 0))))`)),
+                   WebAssembly.CompileError,
+                   /type mismatch/);
+
+// table.set on table of anyfunc - fails validation
+// We need the gc_feature_opt_in here because of the anyref parameter; if we change
+// that to some other type, it's the validation of that type that fails us.
+
+assertErrorMessage(() => new WebAssembly.Module(wasmTextToBinary(
+    `(module
+      (gc_feature_opt_in 2)
+      (table 10 anyfunc)
+      (func (export "f") (param anyref)
+       (table.set (i32.const 0) (get_local 0))))`)),
+                   WebAssembly.CompileError,
+                   /table.set only on tables of anyref/);
+
+// table.set when there are no tables - fails validation
+
+assertErrorMessage(() => new WebAssembly.Module(wasmTextToBinary(
+    `(module
+      (gc_feature_opt_in 2)
+      (func (export "f") (param anyref)
+       (table.set (i32.const 0) (get_local 0))))`)),
+                   WebAssembly.CompileError,
+                   /table index out of range for table.set/);
+
+// we can grow table of anyref
+// table.grow with zero delta - always works even at maximum
+// table.grow with delta - works and returns correct old value
+// table.grow with delta at upper limit - fails
+// table.grow with negative delta - fails
+
+let ins = wasmEvalText(
+    `(module
+      (gc_feature_opt_in 2)
+      (table (export "t") 10 20 anyref)
+      (func (export "grow") (param i32) (result i32)
+       (table.grow (get_local 0) (ref.null anyref))))`);
+assertEq(ins.exports.grow(0), 10);
+assertEq(ins.exports.t.length, 10);
+assertEq(ins.exports.grow(1), 10);
+assertEq(ins.exports.t.length, 11);
+assertEq(ins.exports.t.get(10), null);
+assertEq(ins.exports.grow(9), 11);
+assertEq(ins.exports.t.length, 20);
+assertEq(ins.exports.t.get(19), null);
+assertEq(ins.exports.grow(0), 20);
+
+// The JS API throws if it can't grow
+assertErrorMessage(() => ins.exports.t.grow(1), RangeError, /failed to grow table/);
+assertErrorMessage(() => ins.exports.t.grow(-1), TypeError, /bad [Tt]able grow delta/);
+
+// The wasm API does not throw if it can't grow, but returns -1
+assertEq(ins.exports.grow(1), -1);
+assertEq(ins.exports.t.length, 20);
+assertEq(ins.exports.grow(-1), -1);
+assertEq(ins.exports.t.length, 20)
+
+// Special case for private tables without a maximum
+
+{
+    let ins = wasmEvalText(
+        `(module
+          (gc_feature_opt_in 2)
+          (table 10 anyref)
+          (func (export "grow") (param i32) (result i32)
+           (table.grow (get_local 0) (ref.null anyref))))`);
+    assertEq(ins.exports.grow(0), 10);
+    assertEq(ins.exports.grow(1), 10);
+    assertEq(ins.exports.grow(9), 11);
+    assertEq(ins.exports.grow(0), 20);
+}
+
+// Can't grow table of anyfunc yet
+
+assertErrorMessage(() => wasmEvalText(
+    `(module
+      (gc_feature_opt_in 2)     ;; Required because of the 'anyref' null value below
+      (table $t 2 anyfunc)
+      (func $f
+       (drop (table.grow (i32.const 1) (ref.null anyref)))))`),
+                   WebAssembly.CompileError,
+                   /table.grow only on tables of anyref/);
+
+// table.grow with non-i32 argument - fails validation
+
+assertErrorMessage(() => new WebAssembly.Module(wasmTextToBinary(
+    `(module
+       (gc_feature_opt_in 2)
+       (table 10 anyref)
+       (func (export "f") (param f64)
+        (table.grow (get_local 0) (ref.null anyref))))`)),
+                   WebAssembly.CompileError,
+                   /type mismatch/);
+
+// table.grow when there are no tables - fails validation
+
+assertErrorMessage(() => new WebAssembly.Module(wasmTextToBinary(
+    `(module
+       (gc_feature_opt_in 2)
+       (func (export "f") (param i32)
+        (table.grow (get_local 0) (ref.null anyref))))`)),
+                   WebAssembly.CompileError,
+                   /table index out of range for table.grow/);
+
+// table.grow on table of anyref with non-null ref value
+
+{
+    let ins = wasmEvalText(
+        `(module
+          (gc_feature_opt_in 2)
+          (type $S (struct (field i32) (field f64)))
+          (table (export "t") 2 anyref)
+          (func (export "f") (result i32)
+           (table.grow (i32.const 1) (struct.new $S (i32.const 0) (f64.const 3.14)))))`);
+    assertEq(ins.exports.t.length, 2);
+    assertEq(ins.exports.f(), 2);
+    assertEq(ins.exports.t.length, 3);
+    assertEq(typeof ins.exports.t.get(2), "object");
+}
+
+// table.size on table of anyref
+
+for (let visibility of ['', '(export "t")', '(import "m" "t")']) {
+    let exp = {m:{t: new WebAssembly.Table({element:"anyref",
+                                            initial: 10,
+                                            maximum: 20})}};
+    let ins = wasmEvalText(
+        `(module
+          (gc_feature_opt_in 2)
+          (table ${visibility} 10 20 anyref)
+          (func (export "grow") (param i32) (result i32)
+           (table.grow (get_local 0) (ref.null anyref)))
+          (func (export "size") (result i32)
+           (table.size)))`,
+        exp);
+    assertEq(ins.exports.grow(0), 10);
+    assertEq(ins.exports.size(), 10);
+    assertEq(ins.exports.grow(1), 10);
+    assertEq(ins.exports.size(), 11);
+    assertEq(ins.exports.grow(9), 11);
+    assertEq(ins.exports.size(), 20);
+    assertEq(ins.exports.grow(0), 20);
+    assertEq(ins.exports.size(), 20);
+}
+
+// table.size on table of anyfunc
+
+{
+    let ins = wasmEvalText(
+        `(module
+          (table (export "t") 2 anyfunc)
+          (func (export "f") (result i32)
+           (table.size)))`);
+    assertEq(ins.exports.f(), 2);
+    ins.exports.t.grow(1);
+    assertEq(ins.exports.f(), 3);
+}
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/wasm/gc/tables-multiple.js
@@ -0,0 +1,454 @@
+// |jit-test| skip-if: !wasmGeneralizedTables()
+
+// Note that negative tests not having to do with table indices have been taken
+// care of by tables-generalized.js.
+
+///////////////////////////////////////////////////////////////////////////
+//
+// Positive tests
+
+// - multiple local tables of misc type: syntax, validation, instantiation
+// - element segments can point to a table
+// - call-indirect can specify a table and will use it
+// - be sure to test table stuff w/o gc_feature_opt_in so that we get ion coverage too
+
+var ins = wasmEvalText(
+    `(module
+      (table $t1 2 anyfunc)
+      (table $t2 2 anyfunc)
+      (type $ftype (func (param i32) (result i32)))
+      (elem $t1 (i32.const 0) $f1 $f2)
+      (elem $t2 (i32.const 0) $f3 $f4)
+      (func $f1 (param $n i32) (result i32)
+       (i32.add (get_local $n) (i32.const 1)))
+      (func $f2 (param $n i32) (result i32)
+       (i32.add (get_local $n) (i32.const 2)))
+      (func $f3 (param $n i32) (result i32)
+       (i32.add (get_local $n) (i32.const 3)))
+      (func $f4 (param $n i32) (result i32)
+       (i32.add (get_local $n) (i32.const 4)))
+      (func (export "f") (param $fn i32) (param $n i32) (result i32)
+       (call_indirect $t1 $ftype (get_local $n) (get_local $fn)))
+      (func (export "g") (param $fn i32) (param $n i32) (result i32)
+       (call_indirect $t2 $ftype (get_local $n) (get_local $fn))))`).exports;
+
+assertEq(ins.f(0, 10), 11);
+assertEq(ins.f(1, 10), 12);
+assertEq(ins.g(0, 10), 13);
+assertEq(ins.g(1, 10), 14);
+
+// - export multiple tables.
+//   note the first and third tables make the export list not start at zero,
+//   and make it sparse
+
+var ins = wasmEvalText(
+    `(module
+      (gc_feature_opt_in 2)
+      (table $t0 (import "m" "t") 2 anyfunc)
+      (table $t1 (export "t1") 2 anyfunc)
+      (table 1 anyref)
+      (table $t2 (export "t2") 3 anyfunc))`,
+    {m:{t: new WebAssembly.Table({element:"anyfunc", initial:2})}}).exports;
+
+assertEq(ins.t1 instanceof WebAssembly.Table, true);
+assertEq(ins.t1.length, 2);
+assertEq(ins.t2 instanceof WebAssembly.Table, true);
+assertEq(ins.t2.length, 3);
+
+// - multiple imported tables of misc type
+// - table.get and table.set can point to a table
+
+var exp = {m:{t0: new WebAssembly.Table({element:"anyfunc", initial:2}),
+              t1: new WebAssembly.Table({element:"anyref", initial:3}),
+              t2: new WebAssembly.Table({element:"anyfunc", initial:4}),
+              t3: new WebAssembly.Table({element:"anyref", initial:5})}};
+var ins = wasmEvalText(
+    `(module
+      (gc_feature_opt_in 2)
+
+      (table $t0 (import "m" "t0") 2 anyfunc)
+      (type $id_i32_t (func (param i32) (result i32)))
+      (func $id_i32 (param i32) (result i32) (get_local 0))
+      (elem $t0 (i32.const 1) $id_i32)
+
+      (table $t1 (import "m" "t1") 3 anyref)
+
+      (table $t2 (import "m" "t2") 4 anyfunc)
+      (type $id_f64_t (func (param f64) (result f64)))
+      (func $id_f64 (param f64) (result f64) (get_local 0))
+      (elem $t2 (i32.const 3) $id_f64)
+
+      (table $t3 (import "m" "t3") 5 anyref)
+
+      (func (export "f0") (param i32) (result i32)
+       (call_indirect $t0 $id_i32_t (get_local 0) (i32.const 1)))
+
+      (func (export "f1") (param anyref)
+       (table.set $t1 (i32.const 2) (get_local 0)))
+
+      (func (export "f2") (param f64) (result f64)
+       (call_indirect $t2 $id_f64_t (get_local 0) (i32.const 3)))
+
+      (func (export "f3")
+       (table.set $t3 (i32.const 4) (table.get $t1 (i32.const 2)))))`,
+
+    exp).exports;
+
+assertEq(ins.f0(37), 37);
+
+var x = {}
+ins.f1(x);
+assertEq(exp.m.t1.get(2), x);
+
+assertEq(ins.f2(3.25), 3.25);
+
+ins.f3();
+assertEq(exp.m.t3.get(4), x);
+
+// - table.grow can point to a table
+// - growing a table grows the right table but not the others
+// - table.size on tables other than table 0
+
+var exp = {m:{t0: new WebAssembly.Table({element:"anyref", initial:2}),
+              t1: new WebAssembly.Table({element:"anyref", initial:3})}};
+var ins = wasmEvalText(
+    `(module
+      (gc_feature_opt_in 2)
+
+      (table $t0 (import "m" "t0") 2 anyref)
+      (table $t1 (import "m" "t1") 3 anyref)
+      (func (export "f") (result i32)
+       (table.grow $t1 (i32.const 5) (ref.null anyref)))
+      (func (export "size0") (result i32)
+       (table.size $t0))
+      (func (export "size1") (result i32)
+       (table.size $t1)))`,
+    exp);
+
+assertEq(ins.exports.f(), 3);
+assertEq(exp.m.t1.length, 8);
+assertEq(ins.exports.size0(), 2);
+assertEq(ins.exports.size1(), 8);
+
+// - table.copy can point to tables
+
+var exp = {m:{t0: new WebAssembly.Table({element:"anyref", initial:2}),
+              t1: new WebAssembly.Table({element:"anyref", initial:3})}};
+var ins = wasmEvalText(
+    `(module
+      (gc_feature_opt_in 2)
+
+      (table $t0 (import "m" "t0") 2 anyref)
+      (table $t1 (import "m" "t1") 3 anyref)
+      (func (export "f") (param $dest i32) (param $src i32) (param $len i32)
+       (table.copy $t1 (get_local $dest) $t0 (get_local $src) (get_local $len))))`,
+    exp);
+
+exp.m.t0.set(0, {x:0});
+exp.m.t0.set(1, {x:1});
+ins.exports.f(1, 0, 2);
+assertEq(exp.m.t1.get(1), exp.m.t0.get(0));
+assertEq(exp.m.t1.get(2), exp.m.t0.get(1));
+
+// - the table.copy syntax makes sense even in the non-parenthesized case
+
+var ins = wasmEvalText(
+    `(module
+      (gc_feature_opt_in 2)
+      (table $t0 2 anyref)
+      (table $t1 2 anyref)
+      (func (export "copy") (param $dest i32) (param $src i32) (param $len i32)
+       get_local $dest
+       get_local $src
+       get_local $len
+       table.copy $t1 $t0)
+      (func (export "set") (param $n i32) (param $v anyref)
+       (table.set $t0 (get_local $n) (get_local $v)))
+      (func (export "get") (param $n i32) (result anyref)
+       (table.get $t1 (get_local $n))))`,
+    exp);
+
+var values = [{x:0}, {x:1}];
+ins.exports.set(0, values[0]);
+ins.exports.set(1, values[1]);
+ins.exports.copy(0, 0, 2);
+assertEq(ins.exports.get(0), values[0]);
+assertEq(ins.exports.get(1), values[1]);
+
+// Copy beween an external table and a local table.  These cases are interesting
+// mostly because the representations of the tables must be compatible; the copy
+// in effect forces a representation for the function in the local table that
+// captures the function's instance, at the latest at the time the copy is
+// executed.
+//
+// In the case where the function is imported, it comes from a different module.
+//
+// Also tests:
+// - local tables can be exported and re-imported in another module
+// - table.copy between tables of anyfunc is possible without gc_feature_opt_in
+
+var arg = 4;
+for (let [x,y,result,init] of [['(export "t")', '', arg*13, true],
+                               ['', '(export "t")', arg*13, true],
+                               ['(import "m" "t")', '', arg*13, true],
+                               ['', '(import "m" "t")', arg-11, false]])
+{
+    var otherins = wasmEvalText(
+        `(module
+          (table $t (export "t") 2 anyfunc)
+          (type $fn1 (func (param i32) (result i32)))
+          (func $f1 (param $n i32) (result i32)
+           (i32.sub (get_local $n) (i32.const 11)))
+          (elem $t (i32.const 1) $f1))`);
+
+    let text =
+        `(module
+          (table $t0 ${x} 2 anyfunc)
+
+          (table $t1 ${y} 2 anyfunc)
+          (type $fn1 (func (param i32) (result i32)))
+          (func $f1 (param $n i32) (result i32)
+           (i32.mul (get_local $n) (i32.const 13)))
+          ${init ? "(elem $t1 (i32.const 1) $f1)" : ""}
+
+          (func (export "f") (param $n i32) (result i32)
+           (table.copy $t0 (i32.const 0) $t1 (i32.const 0) (i32.const 2))
+           (call_indirect $t0 $fn1 (get_local $n) (i32.const 1))))`;
+    var ins = wasmEvalText(text, {m: otherins.exports});
+
+    assertEq(ins.exports.f(arg), result);
+}
+
+// - a table can be imported multiple times, and when it is, and one of them is grown,
+//   they are all grown.
+// - test the (import "m" "t" (table ...)) syntax
+// - if table is grown from JS, wasm can observe the growth
+
+var tbl = new WebAssembly.Table({element:"anyref", initial:1});
+var exp = {m: {t0: tbl, t1:tbl}};
+
+var ins = wasmEvalText(
+    `(module
+      (gc_feature_opt_in 2)
+      (import $t0 "m" "t0" (table 1 anyref))
+      (import $t1 "m" "t1" (table 1 anyref))
+      (table $t2 (export "t2") 1 anyfunc)
+      (func (export "f") (result i32)
+       (table.grow $t0 (i32.const 1) (ref.null anyref)))
+      (func (export "g") (result i32)
+       (table.grow $t1 (i32.const 1) (ref.null anyref)))
+      (func (export "size") (result i32)
+       (table.size $t2)))`,
+    exp);
+
+assertEq(ins.exports.f(), 1);
+assertEq(ins.exports.g(), 2);
+assertEq(ins.exports.f(), 3);
+assertEq(ins.exports.g(), 4);
+assertEq(tbl.length, 5);
+ins.exports.t2.grow(3);
+assertEq(ins.exports.size(), 4);
+
+// - table.init on tables other than table 0
+// - gc_feature_opt_in not required for table.init on table(anyfunc) even with multiple tables
+
+var ins = wasmEvalText(
+    `(module
+      (table $t0 2 anyfunc)
+      (table $t1 2 anyfunc)
+      (elem passive $f0 $f1) ;; 0
+      (type $ftype (func (param i32) (result i32)))
+      (func $f0 (param i32) (result i32)
+       (i32.mul (get_local 0) (i32.const 13)))
+      (func $f1 (param i32) (result i32)
+       (i32.sub (get_local 0) (i32.const 11)))
+      (func (export "call") (param i32) (param i32) (result i32)
+       (call_indirect $t1 $ftype (get_local 1) (get_local 0)))
+      (func (export "init")
+       (table.init $t1 0 (i32.const 0) (i32.const 0) (i32.const 2))))`);
+
+ins.exports.init();
+assertEq(ins.exports.call(0, 10), 130);
+assertEq(ins.exports.call(1, 10), -1);
+
+// - [white-box] if a multi-imported table of anyfunc is grown and the grown
+//   part is properly initialized with functions then calls through both tables
+//   in the grown area should succeed, ie, bounds checks should pass.  this is
+//   an interesting case because we cache the table bounds for the benefit of
+//   call_indirect, so here we're testing that the caches are updated properly
+//   even when a table is observed multiple times (also by multiple modules).
+//   there's some extra hair here because a table of anyfunc can be grown only
+//   from JS at the moment.
+// - also test that bounds checking continues to catch OOB calls
+
+var tbl = new WebAssembly.Table({element:"anyfunc", initial:2});
+var exp = {m:{t0: tbl, t1: tbl}};
+var ins = wasmEvalText(
+    `(module
+      (import "m" "t0" (table $t0 2 anyfunc))
+      (import "m" "t1" (table $t1 2 anyfunc))
+      (type $ftype (func (param f64) (result f64)))
+      (func (export "f") (param $n f64) (result f64)
+       (f64.mul (get_local $n) (f64.const 3.25)))
+      (func (export "do0") (param $i i32) (param $n f64) (result f64)
+       (call_indirect $t0 $ftype (get_local $n) (get_local $i)))
+      (func (export "do1") (param $i i32) (param $n f64) (result f64)
+       (call_indirect $t1 $ftype (get_local $n) (get_local $i))))`,
+    exp);
+var ins2 = wasmEvalText(
+    `(module
+      (import "m" "t0" (table $t0 2 anyfunc))
+      (import "m" "t1" (table $t1 2 anyfunc))
+      (type $ftype (func (param f64) (result f64)))
+      (func (export "do0") (param $i i32) (param $n f64) (result f64)
+       (call_indirect $t0 $ftype (get_local $n) (get_local $i)))
+      (func (export "do1") (param $i i32) (param $n f64) (result f64)
+       (call_indirect $t1 $ftype (get_local $n) (get_local $i))))`,
+    exp);
+
+assertEq(tbl.grow(10), 2);
+tbl.set(11, ins.exports.f);
+assertEq(ins.exports.do0(11, 2.0), 6.5);
+assertEq(ins.exports.do1(11, 4.0), 13.0);
+assertEq(ins2.exports.do0(11, 2.0), 6.5);
+assertEq(ins2.exports.do1(11, 4.0), 13.0);
+assertErrorMessage(() => ins.exports.do0(12, 2.0),
+                   WebAssembly.RuntimeError,
+                   /index out of bounds/);
+assertErrorMessage(() => ins2.exports.do0(12, 2.0),
+                   WebAssembly.RuntimeError,
+                   /index out of bounds/);
+assertErrorMessage(() => ins.exports.do1(12, 2.0),
+                   WebAssembly.RuntimeError,
+                   /index out of bounds/);
+assertErrorMessage(() => ins2.exports.do1(12, 2.0),
+                   WebAssembly.RuntimeError,
+                   /index out of bounds/);
+
+///////////////////////////////////////////////////////////////////////////
+//
+// Negative tests
+
+// Table index (statically) out of bounds
+
+assertErrorMessage(() => wasmEvalText(
+    `(module
+      (gc_feature_opt_in 2)
+      (table $t0 2 anyref)
+      (table $t1 2 anyref)
+      (func $f (result anyref)
+       (table.get 2 (i32.const 0))))`),
+                   WebAssembly.CompileError,
+                   /table index out of range for table.get/);
+
+assertErrorMessage(() => wasmEvalText(
+    `(module
+      (gc_feature_opt_in 2)
+      (table $t0 2 anyref)
+      (table $t1 2 anyref)
+      (func $f (param anyref)
+       (table.set 2 (i32.const 0) (get_local 0))))`),
+                   WebAssembly.CompileError,
+                   /table index out of range for table.set/);
+
+assertErrorMessage(() => wasmEvalText(
+    `(module
+      (gc_feature_opt_in 2)
+      (table $t0 2 anyref)
+      (table $t1 2 anyref)
+      (func $f (param anyref)
+       (table.copy 0 (i32.const 0) 2 (i32.const 0) (i32.const 2))))`),
+                   WebAssembly.CompileError,
+                   /table index out of range for table.copy/);
+
+assertErrorMessage(() => wasmEvalText(
+    `(module
+      (gc_feature_opt_in 2)
+      (table $t0 2 anyref)
+      (table $t1 2 anyref)
+      (func $f (param anyref)
+       (table.copy 2 (i32.const 0) 0 (i32.const 0) (i32.const 2))))`),
+                   WebAssembly.CompileError,
+                   /table index out of range for table.copy/);
+
+assertErrorMessage(() => wasmEvalText(
+    `(module
+      (gc_feature_opt_in 2)
+      (table $t0 2 anyref)
+      (table $t1 2 anyref)
+      (func $f (result i32)
+       (table.size 2)))`),
+                   WebAssembly.CompileError,
+                   /table index out of range for table.size/);
+
+assertErrorMessage(() => wasmEvalText(
+    `(module
+      (gc_feature_opt_in 2)
+      (table $t0 2 anyref)
+      (table $t1 2 anyref)
+      (func $f (result i32)
+       (table.grow 2 (i32.const 1) (ref.null anyref))))`),
+                   WebAssembly.CompileError,
+                   /table index out of range for table.grow/);
+
+assertErrorMessage(() => wasmEvalText(
+    `(module
+      (table $t0 2 anyfunc)
+      (elem passive) ;; 0
+      (func $f (result i32)
+       (table.init 2 0 (i32.const 0) (i32.const 0) (i32.const 0))))`),
+                   WebAssembly.CompileError,
+                   /table index out of range for table.init/);
+
+assertErrorMessage(() => wasmEvalText(
+    `(module
+      (table $t0 2 anyfunc)
+      (elem 2 (i32.const 0)))`),
+                   WebAssembly.CompileError,
+                   /table index out of range for element segment/);
+
+assertErrorMessage(() => wasmEvalText(
+    `(module
+      (table $t0 2 anyfunc)
+      (type $ft (func (param f64) (result i32)))
+      (func $f (result i32)
+       (call_indirect 2 $ft (f64.const 3.14) (i32.const 0))))`),
+                   WebAssembly.CompileError,
+                   /table index out of range for call_indirect/);
+
+// Syntax errors when parsing text
+
+assertErrorMessage(() => wasmEvalText(
+    `(module
+      (table $t0 2 anyfunc)
+      (elem 0 passive (i32.const 0)))`),
+                   SyntaxError,
+                   /passive segment must not have a table/);
+
+assertErrorMessage(() => wasmEvalText(
+    `(module
+      (table $t0 2 anyfunc)
+      (elem passive) ;; 0
+      (func $f (result i32)
+       (table.init $t0 (i32.const 0) (i32.const 0) (i32.const 0))))`), // no segment
+                   SyntaxError,
+                   /expected element segment reference/);
+
+assertErrorMessage(() => wasmEvalText(
+    `(module
+      (table $t0 2 anyfunc)
+      (table $t1 2 anyfunc)
+      (func $f
+       (table.copy 0 (i32.const 0) (i32.const 0) (i32.const 2))))`), // target without source
+                   SyntaxError,
+                   /source is required if target is specified/);
+
+assertErrorMessage(() => wasmEvalText(
+    `(module
+      (table $t0 2 anyfunc)
+      (table $t1 2 anyfunc)
+      (func $f
+       (table.copy (i32.const 0) 0 (i32.const 0) (i32.const 2))))`), // source without target
+                   SyntaxError,
+                   /parsing wasm text/);
+
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/wasm/gc/tables-stress.js
@@ -0,0 +1,49 @@
+// |jit-test| skip-if: !wasmGeneralizedTables()
+
+for ( let prefix of ['', '(table $prefix 0 32 anyfunc)']) {
+    let mod = new WebAssembly.Module(wasmTextToBinary(
+    `(module
+       (gc_feature_opt_in 2)
+       ${prefix}
+       (table $tbl 0 anyref)
+       (import $item "m" "item" (func (result anyref)))
+       (func (export "run") (param $numiters i32)
+         (local $i i32)
+         (local $j i32)
+         (local $last i32)
+         (local $iters i32)
+         (local $tmp anyref)
+         (set_local $last (table.grow $tbl (i32.const 1) (ref.null anyref)))
+         (table.set $tbl (get_local $last) (call $item))
+         (loop $iter_continue
+           (set_local $i (i32.const 0))
+           (set_local $j (get_local $last))
+           (block $l_break
+             (loop $l_continue
+               (br_if $l_break (i32.ge_s (get_local $j) (get_local $i)))
+               (set_local $tmp (table.get $tbl (get_local $i)))
+               (if (i32.eqz (i32.rem_s (get_local $i) (i32.const 3)))
+                   (set_local $tmp (call $item)))
+               (table.set $tbl (get_local $i) (table.get $tbl (get_local $j)))
+               (table.set $tbl (get_local $j) (get_local $tmp))
+               (set_local $i (i32.add (get_local $i) (i32.const 1)))
+               (set_local $j (i32.sub (get_local $j) (i32.const 1)))
+               (br $l_continue))
+           (set_local $iters (i32.add (get_local $iters) (i32.const 1)))
+           (br_if $iter_continue (i32.lt_s (get_local $iters) (get_local $numiters)))))))`));
+
+    for (let [mode,freq] of [[14,100],  // Compact every 100 allocations
+                             [2,12],    // Collect every 12 allocations
+                             [7,100],   // Minor gc every 100 allocations
+                             [15,100]]) // Verify heap integrity
+    {
+        if (this.gczeal)
+            this.gczeal(mode,freq);
+        let k = 0;
+        let ins = new WebAssembly.Instance(mod, {m:{item:() => { return { x: k++ } }}}).exports;
+        for ( let i=0; i < 1000; i++ )
+            ins.run(1000);
+    }
+}
+
+
--- a/js/src/jit-test/tests/wasm/import-export.js
+++ b/js/src/jit-test/tests/wasm/import-export.js
@@ -448,18 +448,16 @@ wasmFailValidateText('(module (export "a
 wasmFailValidateText('(module (export "a" global 0))', /exported global index out of bounds/);
 wasmFailValidateText('(module (export "a" memory))', /exported memory index out of bounds/);
 wasmFailValidateText('(module (export "a" table))', /exported table index out of bounds/);
 
 // Default memory/table rules
 
 wasmFailValidateText('(module (import "a" "b" (memory 1 1)) (memory 1 1))', /already have default memory/);
 wasmFailValidateText('(module (import "a" "b" (memory 1 1)) (import "x" "y" (memory 2 2)))', /already have default memory/);
-wasmFailValidateText('(module (import "a" "b" (table 1 1 anyfunc)) (table 1 1 anyfunc))', /already have default table/);
-wasmFailValidateText('(module (import "a" "b" (table 1 1 anyfunc)) (import "x" "y" (table 2 2 anyfunc)))', /already have default table/);
 
 // Data segments on imports
 
 var m = new Module(wasmTextToBinary(`
     (module
         (import "a" "b" (memory 1 1))
         (data (i32.const 0) "\\0a\\0b")
         (data (i32.const 100) "\\0c\\0d")
--- a/js/src/jit-test/tests/wasm/passive-segs-boundary.js
+++ b/js/src/jit-test/tests/wasm/passive-segs-boundary.js
@@ -202,26 +202,26 @@ mem_test("(memory.init 1 (i32.const 1) (
 
 // drop with no table
 tab_test("table.drop 3", "",
          WebAssembly.CompileError, /can't table.drop without a table/,
          false);
 
 // init with no table
 tab_test("(table.init 1 (i32.const 12) (i32.const 1) (i32.const 1))", "",
-         WebAssembly.CompileError, /can't table.init without a table/,
+         WebAssembly.CompileError, /table index out of range/,
          false);
 
 // drop with elem seg ix out of range
 tab_test("table.drop 4", "",
-         WebAssembly.CompileError, /table.drop index out of range/);
+         WebAssembly.CompileError, /element segment index out of range for table.drop/);
 
 // init with elem seg ix out of range
 tab_test("(table.init 4 (i32.const 12) (i32.const 1) (i32.const 1))", "",
-         WebAssembly.CompileError, /table.init index out of range/);
+         WebAssembly.CompileError, /table.init segment index out of range/);
 
 // drop with elem seg ix indicating an active segment
 tab_test("table.drop 2", "",
          WebAssembly.RuntimeError, /use of invalid passive element segment/);
 
 // init with elem seg ix indicating an active segment
 tab_test("(table.init 2 (i32.const 12) (i32.const 1) (i32.const 1))", "",
          WebAssembly.RuntimeError, /use of invalid passive element segment/);
--- a/js/src/jit-test/tests/wasm/passive-segs-nonboundary.js
+++ b/js/src/jit-test/tests/wasm/passive-segs-nonboundary.js
@@ -366,17 +366,17 @@ function checkMiscPrefixed(opcode, expec
 }
 
 //-----------------------------------------------------------
 // Verification cases for memory.copy/fill opcode encodings
 
 checkMiscPrefixed([0x0a, 0x00], false); // memory.copy, flags=0
 checkMiscPrefixed([0x0b, 0x00], false); // memory.fill, flags=0
 checkMiscPrefixed([0x0b, 0x80, 0x00], false); // memory.fill, flags=0 (long encoding)
-checkMiscPrefixed([0x0f], true);        // table.copy+1, which is currently unassigned
+checkMiscPrefixed([0x13], true);        // table.size+1, which is currently unassigned
 
 //-----------------------------------------------------------
 // Verification cases for memory.copy/fill arguments
 
 // Invalid argument types
 {
     const tys = ['i32', 'f32', 'i64', 'f64'];
     const ops = ['copy', 'fill'];
--- a/js/src/jit-test/tests/wasm/spec/imports.wast.js
+++ b/js/src/jit-test/tests/wasm/spec/imports.wast.js
@@ -183,25 +183,16 @@ assert_return(() => call($14, "call", [1
 assert_return(() => call($14, "call", [2]), 22);
 
 // imports.wast:283
 assert_trap(() => call($14, "call", [3]));
 
 // imports.wast:284
 assert_trap(() => call($14, "call", [100]));
 
-// imports.wast:287
-assert_invalid("\x00\x61\x73\x6d\x01\x00\x00\x00\x02\x8d\x80\x80\x80\x00\x02\x00\x00\x01\x70\x00\x0a\x00\x00\x01\x70\x00\x0a");
-
-// imports.wast:291
-assert_invalid("\x00\x61\x73\x6d\x01\x00\x00\x00\x02\x87\x80\x80\x80\x00\x01\x00\x00\x01\x70\x00\x0a\x04\x84\x80\x80\x80\x00\x01\x70\x00\x0a");
-
-// imports.wast:295
-assert_invalid("\x00\x61\x73\x6d\x01\x00\x00\x00\x04\x87\x80\x80\x80\x00\x02\x70\x00\x0a\x70\x00\x0a");
-
 // imports.wast:300
 let $15 = instance("\x00\x61\x73\x6d\x01\x00\x00\x00\x02\x97\x80\x80\x80\x00\x01\x04\x74\x65\x73\x74\x0c\x74\x61\x62\x6c\x65\x2d\x31\x30\x2d\x69\x6e\x66\x01\x70\x00\x0a");
 
 // imports.wast:301
 let $16 = instance("\x00\x61\x73\x6d\x01\x00\x00\x00\x02\x97\x80\x80\x80\x00\x01\x04\x74\x65\x73\x74\x0c\x74\x61\x62\x6c\x65\x2d\x31\x30\x2d\x69\x6e\x66\x01\x70\x00\x05");
 
 // imports.wast:302
 let $17 = instance("\x00\x61\x73\x6d\x01\x00\x00\x00\x02\x97\x80\x80\x80\x00\x01\x04\x74\x65\x73\x74\x0c\x74\x61\x62\x6c\x65\x2d\x31\x30\x2d\x69\x6e\x66\x01\x70\x00\x00");
--- a/js/src/jit-test/tests/wasm/tables.js
+++ b/js/src/jit-test/tests/wasm/tables.js
@@ -261,10 +261,10 @@ assertEq(tbl.get(0).foo, 42);
     // Should fail because the kind is unknown
     assertErrorMessage(() => new WebAssembly.Module(makeIt(0x03, 0x00)),
                        WebAssembly.CompileError,
                        /invalid elem initializer-kind/);
 
     // Should fail because the table index is invalid
     assertErrorMessage(() => new WebAssembly.Module(makeIt(0x02, 0x01)),
                        WebAssembly.CompileError,
-                       /table index must be zero/);
+                       /table index out of range/);
 }
--- a/js/src/jit/CodeGenerator.cpp
+++ b/js/src/jit/CodeGenerator.cpp
@@ -7341,18 +7341,17 @@ CodeGenerator::emitWasmCallBase(MWasmCal
         switchRealm = false;
         break;
       case wasm::CalleeDesc::Import:
         masm.wasmCallImport(desc, callee);
         break;
       case wasm::CalleeDesc::AsmJSTable:
       case wasm::CalleeDesc::WasmTable:
         masm.wasmCallIndirect(desc, callee, needsBoundsCheck);
-        reloadRegs = switchRealm =
-            (callee.which() == wasm::CalleeDesc::WasmTable && callee.wasmTableIsExternal());
+        reloadRegs = switchRealm = callee.which() == wasm::CalleeDesc::WasmTable;
         break;
       case wasm::CalleeDesc::Builtin:
         masm.call(desc, callee.builtin());
         reloadRegs = false;
         switchRealm = false;
         break;
       case wasm::CalleeDesc::BuiltinInstanceMethod:
         masm.wasmCallBuiltinInstanceMethod(desc, mir->instanceArg(), callee.builtin());
--- a/js/src/jit/IonAnalysis.cpp
+++ b/js/src/jit/IonAnalysis.cpp
@@ -1,16 +1,18 @@
 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
  * vim: set ts=8 sts=4 et sw=4 tw=99:
  * This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "jit/IonAnalysis.h"
 
+#include <utility> // for ::std::pair
+
 #include "jit/AliasAnalysis.h"
 #include "jit/BaselineInspector.h"
 #include "jit/BaselineJIT.h"
 #include "jit/Ion.h"
 #include "jit/IonBuilder.h"
 #include "jit/IonOptimizationLevels.h"
 #include "jit/LIR.h"
 #include "jit/Lowering.h"
@@ -24,21 +26,126 @@
 #include "vm/JSScript-inl.h"
 #include "vm/TypeInference-inl.h"
 
 using namespace js;
 using namespace js::jit;
 
 using mozilla::DebugOnly;
 
-typedef Vector<MPhi*, 16, SystemAllocPolicy> MPhiVector;
+// Stack used by FlagPhiInputsAsHavingRemovedUses. It stores the Phi instruction
+// pointer and the MUseIterator which should be visited next.
+using MPhiUseIteratorStack = Vector<std::pair<MPhi*, MUseIterator>, 16, SystemAllocPolicy>;
+
+// Look for Phi uses with a depth-first search. If any uses are found the stack
+// of MPhi instructions is returned in the |worklist| argument.
+static bool
+DepthFirstSearchUse(MIRGenerator* mir, MPhiUseIteratorStack& worklist, MPhi* phi)
+{
+    // Push a Phi and the next use to iterate over in the worklist.
+    auto push = [&worklist](MPhi* phi, MUseIterator use) -> bool {
+        phi->setInWorklist();
+        return worklist.append(std::make_pair(phi, use));
+    };
+
+#ifdef DEBUG
+    // Used to assert that when we have no uses, we at least visited all the
+    // transitive uses.
+    size_t refUseCount = phi->useCount();
+    size_t useCount = 0;
+#endif
+    MOZ_ASSERT(worklist.empty());
+    if (!push(phi, phi->usesBegin())) {
+        return false;
+    }
+
+    while (!worklist.empty()) {
+        // Resume iterating over the last phi-use pair added by the next loop.
+        auto pair = worklist.popCopy();
+        MPhi* producer = pair.first;
+        MUseIterator use = pair.second;
+        MUseIterator end(producer->usesEnd());
+        producer->setNotInWorklist();
+
+        // Keep going down the tree of uses, skipping (continue)
+        // non-observable/unused cases and Phi which are already listed in the
+        // worklist. Stop (return) as soon as one use is found.
+        while (use != end) {
+            MNode* consumer = (*use)->consumer();
+            MUseIterator it = use;
+            use++;
+#ifdef DEBUG
+            useCount++;
+#endif
+            if (mir->shouldCancel("FlagPhiInputsAsHavingRemovedUses inner loop")) {
+                return false;
+            }
+
+            if (consumer->isResumePoint()) {
+                MResumePoint* rp = consumer->toResumePoint();
+                // Observable operands are similar to potential uses.
+                if (rp->isObservableOperand(*it)) {
+                    return push(producer, use);
+                }
+                continue;
+            }
+
+            MDefinition* cdef = consumer->toDefinition();
+            if (!cdef->isPhi()) {
+                // The producer is explicitly used by a definition.
+                return push(producer, use);
+            }
+
+            MPhi* cphi = cdef->toPhi();
+            if (cphi->getUsageAnalysis() == PhiUsage::Used || cphi->isUseRemoved()) {
+                // The information got cached on the Phi the last time it
+                // got visited, or when flagging operands of removed
+                // instructions.
+                return push(producer, use);
+            }
+
+            if (cphi->isInWorklist() || cphi == producer) {
+                // We are already iterating over the uses of this Phi
+                // instruction. Skip it.
+                continue;
+            }
+
+            if (cphi->getUsageAnalysis() == PhiUsage::Unused) {
+                // The instruction already got visited and is known to have
+                // no uses. Skip it.
+                continue;
+            }
+
+            // We found another Phi instruction, move the use iterator to
+            // the next use push it to the worklist stack. Then, continue
+            // with a depth search.
+            if (!push(producer, use)) {
+                return false;
+            }
+            producer = cphi;
+            use = producer->usesBegin();
+            end = producer->usesEnd();
+#ifdef DEBUG
+            refUseCount += producer->useCount();
+#endif
+        }
+
+        // When unused, we cannot bubble up this information without iterating
+        // over the rest of the previous Phi instruction consumers.
+        MOZ_ASSERT(use == end);
+        producer->setUsageAnalysis(PhiUsage::Unused);
+    }
+
+    MOZ_ASSERT(useCount == refUseCount);
+    return true;
+}
 
 static bool
 FlagPhiInputsAsHavingRemovedUses(MIRGenerator* mir, MBasicBlock* block, MBasicBlock* succ,
-                                 MPhiVector& worklist)
+                                 MPhiUseIteratorStack& worklist)
 {
     // When removing an edge between 2 blocks, we might remove the ability of
     // later phases to figure out that the uses of a Phi should be considered as
     // a use of all its inputs. Thus we need to mark the Phi inputs as having
     // removed uses iff the phi has any uses.
     //
     //
     //        +--------------------+         +---------------------+
@@ -89,22 +196,21 @@ FlagPhiInputsAsHavingRemovedUses(MIRGene
     //                     |70 MReturn 50           |
     //                     +------------------------+
     //
     //
     // If the inputs of the Phi are not flagged as having removed uses, then
     // later compilation phase might optimize them out. The problem is that a
     // bailout will use this value and give it back to baseline, which will then
     // use the OptimizedOut magic value in a computation.
-
-    // Conservative upper limit for the number of Phi instructions which are
-    // visited while looking for uses.
-    const size_t conservativeUsesLimit = 128;
-
-    MOZ_ASSERT(worklist.empty());
+    //
+    // Unfortunately, we cannot be too conservative about flagging Phi inputs as
+    // having removed uses, as this would prevent many optimizations from being
+    // used. Thus, the following code is in charge of flagging Phi instructions
+    // as Unused or Used, and setting UseRemoved accordingly.
     size_t predIndex = succ->getPredecessorIndex(block);
     MPhiIterator end = succ->phisEnd();
     MPhiIterator it = succ->phisBegin();
     for (; it != end; it++) {
         MPhi* phi = *it;
 
         if (mir->shouldCancel("FlagPhiInputsAsHavingRemovedUses outer loop")) {
             return false;
@@ -112,92 +218,46 @@ FlagPhiInputsAsHavingRemovedUses(MIRGene
 
         // We are looking to mark the Phi inputs which are used across the edge
         // between the |block| and its successor |succ|.
         MDefinition* def = phi->getOperand(predIndex);
         if (def->isUseRemoved()) {
             continue;
         }
 
-        phi->setInWorklist();
-        if (!worklist.append(phi)) {
+        // If the Phi is either Used or Unused, set the UseRemoved flag
+        // accordingly.
+        if (phi->getUsageAnalysis() == PhiUsage::Used || phi->isUseRemoved()) {
+            def->setUseRemoved();
+            continue;
+        } else if (phi->getUsageAnalysis() == PhiUsage::Unused) {
+            continue;
+        }
+
+        // We do not know if the Phi was Used or Unused, iterate over all uses
+        // with a depth-search of uses. Returns the matching stack in the
+        // worklist as soon as one use is found.
+        MOZ_ASSERT(worklist.empty());
+        if (!DepthFirstSearchUse(mir, worklist, phi)) {
             return false;
         }
 
-        // Fill the work list with all the Phi nodes uses until we reach either:
-        //  - A resume point which uses the Phi as an observable operand.
-        //  - An explicit use of the Phi instruction.
-        //  - An implicit use of the Phi instruction.
-        bool isUsed = false;
-        for (size_t idx = 0; !isUsed && idx < worklist.length(); idx++) {
-            phi = worklist[idx];
-
-            if (mir->shouldCancel("FlagPhiInputsAsHavingRemovedUses inner loop 1")) {
-                return false;
-            }
-
-            if (phi->isUseRemoved() || phi->isImplicitlyUsed()) {
-                // The phi is implicitly used.
-                isUsed = true;
-                break;
-            }
-
-            MUseIterator usesEnd(phi->usesEnd());
-            for (MUseIterator use(phi->usesBegin()); use != usesEnd; use++) {
-                MNode* consumer = (*use)->consumer();
-
-                if (mir->shouldCancel("FlagPhiInputsAsHavingRemovedUses inner loop 2")) {
-                    return false;
-                }
-
-                if (consumer->isResumePoint()) {
-                    MResumePoint* rp = consumer->toResumePoint();
-                    if (rp->isObservableOperand(*use)) {
-                        // The phi is observable via a resume point operand.
-                        isUsed = true;
-                        break;
-                    }
-                    continue;
-                }
-
-                MDefinition* cdef = consumer->toDefinition();
-                if (!cdef->isPhi()) {
-                    // The phi is explicitly used.
-                    isUsed = true;
-                    break;
-                }
-
-                phi = cdef->toPhi();
-                if (phi->isInWorklist()) {
-                    continue;
-                }
-
-                phi->setInWorklist();
-                if (!worklist.append(phi)) {
-                    return false;
-                }
-            }
-
-            // Use a conservative upper bound to avoid iterating too many times
-            // on very large graphs.
-            if (idx >= conservativeUsesLimit) {
-                isUsed = true;
-                break;
-            }
-        }
-
-        if (isUsed) {
+        MOZ_ASSERT_IF(worklist.empty(), phi->getUsageAnalysis() == PhiUsage::Unused);
+        if (!worklist.empty()) {
+            // One of the Phis is used, set Used flags on all the Phis which are
+            // in the use chain.
             def->setUseRemoved();
-        }
-
-        // Remove all the InWorklist flags.
-        while (!worklist.empty()) {
-            phi = worklist.popCopy();
-            phi->setNotInWorklist();
-        }
+            do {
+                auto pair = worklist.popCopy();
+                MPhi* producer = pair.first;
+                producer->setUsageAnalysis(PhiUsage::Used);
+                producer->setNotInWorklist();
+            } while (!worklist.empty());
+        }
+        MOZ_ASSERT(phi->getUsageAnalysis() != PhiUsage::Unknown);
     }
 
     return true;
 }
 
 static bool
 FlagAllOperandsAsHavingRemovedUses(MIRGenerator* mir, MBasicBlock* block)
 {
@@ -241,17 +301,17 @@ FlagAllOperandsAsHavingRemovedUses(MIRGe
                 rp->getOperand(i)->setUseRemovedUnchecked();
             }
         }
 
         rp = rp->caller();
     }
 
     // Flag Phi inputs of the successors has having removed uses.
-    MPhiVector worklist;
+    MPhiUseIteratorStack worklist;
     for (size_t i = 0, e = block->numSuccessors(); i < e; i++) {
         if (mir->shouldCancel("FlagAllOperandsAsHavingRemovedUses loop 3")) {
             return false;
         }
 
         if (!FlagPhiInputsAsHavingRemovedUses(mir, block, block->getSuccessor(i), worklist)) {
             return false;
         }
--- a/js/src/jit/MIR.cpp
+++ b/js/src/jit/MIR.cpp
@@ -2322,16 +2322,41 @@ MPhi::congruentTo(const MDefinition* ins
     // For now, consider phis in different blocks incongruent.
     if (ins->block() != block()) {
         return false;
     }
 
     return congruentIfOperandsEqual(ins);
 }
 
+bool
+MPhi::updateForReplacement(MDefinition* def)
+{
+    // This function is called to fix the current Phi flags using it as a
+    // replacement of the other Phi instruction |def|.
+    //
+    // When dealing with usage analysis, any Use will replace all other values,
+    // such as Unused and Unknown. Unless both are Unused, the merge would be
+    // Unknown.
+    MPhi* other = def->toPhi();
+    if (usageAnalysis_ == PhiUsage::Used || other->usageAnalysis_ == PhiUsage::Used) {
+        usageAnalysis_ = PhiUsage::Used;
+    } else if (usageAnalysis_ != other->usageAnalysis_) {
+        //    this == unused && other == unknown
+        // or this == unknown && other == unused
+        usageAnalysis_ = PhiUsage::Unknown;
+    } else {
+        //    this == unused && other == unused
+        // or this == unknown && other = unknown
+        MOZ_ASSERT(usageAnalysis_ == PhiUsage::Unused || usageAnalysis_ == PhiUsage::Unknown);
+        MOZ_ASSERT(usageAnalysis_ == other->usageAnalysis_);
+    }
+    return true;
+}
+
 static inline TemporaryTypeSet*
 MakeMIRTypeSet(TempAllocator& alloc, MIRType type)
 {
     MOZ_ASSERT(type != MIRType::Value);
     TypeSet::Type ntype = type == MIRType::Object
                           ? TypeSet::AnyObjectType()
                           : TypeSet::PrimitiveType(ValueTypeFromMIRType(type));
     return alloc.lifoAlloc()->new_<TemporaryTypeSet>(alloc.lifoAlloc(), ntype);
--- a/js/src/jit/MIR.h
+++ b/js/src/jit/MIR.h
@@ -6821,30 +6821,42 @@ class MArrowNewTarget
         return congruentIfOperandsEqual(ins);
     }
     AliasSet getAliasSet() const override {
         // An arrow function's lexical |this| value is immutable.
         return AliasSet::None();
     }
 };
 
+// This is a 3 state flag used by FlagPhiInputsAsHavingRemovedUses to record and
+// propagate the information about the consumers of a Phi instruction. This is
+// then used to set UseRemoved flags on the inputs of such Phi instructions.
+enum class PhiUsage : uint8_t {
+    Unknown,
+    Unused,
+    Used
+};
+
 class MPhi final
   : public MDefinition,
     public InlineListNode<MPhi>,
     public NoTypePolicy::Data
 {
     using InputVector = js::Vector<MUse, 2, JitAllocPolicy>;
     InputVector inputs_;
 
     TruncateKind truncateKind_;
     bool hasBackedgeType_;
     bool triedToSpecialize_;
     bool isIterator_;
     bool canProduceFloat32_;
     bool canConsumeFloat32_;
+    // Record the state of the data flow before any mutation made to the control
+    // flow, such that removing branches is properly accounted for.
+    PhiUsage usageAnalysis_;
 
 #if DEBUG
     bool specialized_;
 #endif
 
   protected:
     MUse* getUseFor(size_t index) override {
         // Note: after the initial IonBuilder pass, it is OK to change phi
@@ -6866,17 +6878,18 @@ class MPhi final
     MPhi(TempAllocator& alloc, MIRType resultType)
       : MDefinition(classOpcode),
         inputs_(alloc),
         truncateKind_(NoTruncate),
         hasBackedgeType_(false),
         triedToSpecialize_(false),
         isIterator_(false),
         canProduceFloat32_(false),
-        canConsumeFloat32_(false)
+        canConsumeFloat32_(false),
+        usageAnalysis_(PhiUsage::Unknown)
 #if DEBUG
         , specialized_(false)
 #endif
     {
         setResultType(resultType);
     }
 
     static MPhi* New(TempAllocator& alloc, MIRType resultType = MIRType::Value) {
@@ -6973,16 +6986,17 @@ class MPhi final
     // |*ptypeChange| to true if the type changed.
     bool checkForTypeChange(TempAllocator& alloc, MDefinition* ins, bool* ptypeChange);
 
     MDefinition* foldsTo(TempAllocator& alloc) override;
     MDefinition* foldsTernary(TempAllocator& alloc);
     MDefinition* foldsFilterTypeSet();
 
     bool congruentTo(const MDefinition* ins) const override;
+    bool updateForReplacement(MDefinition* def) override;
 
     bool isIterator() const {
         return isIterator_;
     }
     void setIterator() {
         isIterator_ = true;
     }
 
@@ -7007,16 +7021,23 @@ class MPhi final
 
     void setCanConsumeFloat32(bool can) {
         canConsumeFloat32_ = can;
     }
 
     TruncateKind operandTruncateKind(size_t index) const override;
     bool needTruncation(TruncateKind kind) override;
     void truncate() override;
+
+    PhiUsage getUsageAnalysis() const { return usageAnalysis_; }
+    void setUsageAnalysis(PhiUsage pu) {
+        MOZ_ASSERT(usageAnalysis_ == PhiUsage::Unknown);
+        usageAnalysis_ = pu;
+        MOZ_ASSERT(usageAnalysis_ != PhiUsage::Unknown);
+    }
 };
 
 // The goal of a Beta node is to split a def at a conditionally taken
 // branch, so that uses dominated by it have a different name.
 class MBeta
   : public MUnaryInstruction,
     public NoTypePolicy::Data
 {
--- a/js/src/jit/MacroAssembler.cpp
+++ b/js/src/jit/MacroAssembler.cpp
@@ -3598,21 +3598,34 @@ MacroAssembler::wasmCallBuiltinInstanceM
 
 void
 MacroAssembler::wasmCallIndirect(const wasm::CallSiteDesc& desc, const wasm::CalleeDesc& callee,
                                  bool needsBoundsCheck)
 {
     Register scratch = WasmTableCallScratchReg0;
     Register index = WasmTableCallIndexReg;
 
+    // Optimization opportunity: when offsetof(FunctionTableElem, code) == 0, as
+    // it is at present, we can probably generate better code here by folding
+    // the address computation into the load.
+
+    static_assert(sizeof(wasm::FunctionTableElem) == 8 || sizeof(wasm::FunctionTableElem) == 16,
+                  "elements of function tables are two words");
+
     if (callee.which() == wasm::CalleeDesc::AsmJSTable) {
-        // asm.js tables require no signature check, have had their index masked
-        // into range and thus need no bounds check and cannot be external.
-        loadWasmGlobalPtr(callee.tableBaseGlobalDataOffset(), scratch);
-        loadPtr(BaseIndex(scratch, index, ScalePointer), scratch);
+        // asm.js tables require no signature check, and have had their index
+        // masked into range and thus need no bounds check.
+        loadWasmGlobalPtr(callee.tableFunctionBaseGlobalDataOffset(), scratch);
+        if (sizeof(wasm::FunctionTableElem) == 8) {
+            computeEffectiveAddress(BaseIndex(scratch, index, TimesEight), scratch);
+        } else {
+            lshift32(Imm32(4), index);
+            addPtr(index, scratch);
+        }
+        loadPtr(Address(scratch, offsetof(wasm::FunctionTableElem, code)), scratch);
         call(desc, scratch);
         return;
     }
 
     MOZ_ASSERT(callee.which() == wasm::CalleeDesc::WasmTable);
 
     // Write the functype-id into the ABI functype-id register.
     wasm::FuncTypeIdDesc funcTypeId = callee.wasmTableSigId();
@@ -3635,49 +3648,38 @@ MacroAssembler::wasmCallIndirect(const w
 
         Label ok;
         branch32(Assembler::Condition::Below, index, scratch, &ok);
         wasmTrap(wasm::Trap::OutOfBounds, trapOffset);
         bind(&ok);
     }
 
     // Load the base pointer of the table.
-    loadWasmGlobalPtr(callee.tableBaseGlobalDataOffset(), scratch);
+    loadWasmGlobalPtr(callee.tableFunctionBaseGlobalDataOffset(), scratch);
 
     // Load the callee from the table.
-    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);
-
-        Label nonNull;
-        branchTest32(Assembler::NonZero, WasmTlsReg, WasmTlsReg, &nonNull);
-        wasmTrap(wasm::Trap::IndirectCallToNull, trapOffset);
-        bind(&nonNull);
-
-        loadWasmPinnedRegsFromTls();
-        switchToWasmTlsRealm(index, WasmTableCallScratchReg1);
-
-        loadPtr(Address(scratch, offsetof(wasm::ExternalTableElem, code)), scratch);
+    if (sizeof(wasm::FunctionTableElem) == 8) {
+        computeEffectiveAddress(BaseIndex(scratch, index, TimesEight), scratch);
     } else {
-        loadPtr(BaseIndex(scratch, index, ScalePointer), scratch);
-
-        Label nonNull;
-        branchTest32(Assembler::NonZero, scratch, scratch, &nonNull);
-        wasmTrap(wasm::Trap::IndirectCallToNull, trapOffset);
-        bind(&nonNull);
+        lshift32(Imm32(4), index);
+        addPtr(index, scratch);
     }
 
+    loadPtr(Address(scratch, offsetof(wasm::FunctionTableElem, tls)), WasmTlsReg);
+
+    Label nonNull;
+    branchTest32(Assembler::NonZero, WasmTlsReg, WasmTlsReg, &nonNull);
+    wasmTrap(wasm::Trap::IndirectCallToNull, trapOffset);
+    bind(&nonNull);
+
+    loadWasmPinnedRegsFromTls();
+    switchToWasmTlsRealm(index, WasmTableCallScratchReg1);
+
+    loadPtr(Address(scratch, offsetof(wasm::FunctionTableElem, code)), scratch);
+
     call(desc, scratch);
 }
 
 void
 MacroAssembler::emitPreBarrierFastPath(JSRuntime* rt, MIRType type, Register temp1, Register temp2,
                                        Register temp3, Label* noBarrier)
 {
     MOZ_ASSERT(temp1 != PreBarrierReg);
--- a/js/src/js-config.mozbuild
+++ b/js/src/js-config.mozbuild
@@ -5,16 +5,17 @@
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 # Nightly-only features
 if CONFIG['NIGHTLY_BUILD']:
     DEFINES['ENABLE_BINARYDATA'] = True
     DEFINES['ENABLE_WASM_BULKMEM_OPS'] = True
     DEFINES['ENABLE_WASM_THREAD_OPS'] = True
     DEFINES['ENABLE_WASM_GC'] = True
+    DEFINES['ENABLE_WASM_GENERALIZED_TABLES'] = True
     DEFINES['WASM_PRIVATE_REFTYPES'] = True
 
 # Some huge-mapping optimization instead of bounds checks on supported
 # platforms.
 if CONFIG['JS_CODEGEN_X64'] or CONFIG['JS_CODEGEN_ARM64']:
     DEFINES['WASM_HUGE_MEMORY'] = True
 
 # Enables CACHEIR_LOGS to diagnose IC coverage.
--- a/js/src/js.msg
+++ b/js/src/js.msg
@@ -379,38 +379,41 @@ MSG_DEF(JSMSG_WASM_BAD_IMPORT_TYPE,    2
 MSG_DEF(JSMSG_WASM_BAD_IMPORT_SIG,     2, JSEXN_WASMLINKERROR, "imported function '{0}.{1}' signature mismatch")
 MSG_DEF(JSMSG_WASM_BAD_IMP_SIZE,       1, JSEXN_WASMLINKERROR, "imported {0} with incompatible size")
 MSG_DEF(JSMSG_WASM_BAD_IMP_MAX,        1, JSEXN_WASMLINKERROR, "imported {0} with incompatible maximum size")
 MSG_DEF(JSMSG_WASM_IMP_SHARED_REQD,    0, JSEXN_WASMLINKERROR, "imported unshared memory but shared required")
 MSG_DEF(JSMSG_WASM_IMP_SHARED_BANNED,  0, JSEXN_WASMLINKERROR, "imported shared memory but unshared required")
 MSG_DEF(JSMSG_WASM_BAD_FIT,            2, JSEXN_WASMLINKERROR, "{0} segment does not fit in {1}")
 MSG_DEF(JSMSG_WASM_BAD_I64_LINK,       0, JSEXN_WASMLINKERROR, "cannot pass i64 to or from JS")
 MSG_DEF(JSMSG_WASM_NO_SHMEM_LINK,      0, JSEXN_WASMLINKERROR, "shared memory is disabled")
-MSG_DEF(JSMSG_WASM_BAD_MUT_LINK,       0, JSEXN_WASMLINKERROR, "imported global mutability mismatch")
-MSG_DEF(JSMSG_WASM_BAD_TYPE_LINK,      0, JSEXN_WASMLINKERROR, "imported global type mismatch")
+MSG_DEF(JSMSG_WASM_BAD_GLOB_MUT_LINK,  0, JSEXN_WASMLINKERROR, "imported global mutability mismatch")
+MSG_DEF(JSMSG_WASM_BAD_GLOB_TYPE_LINK, 0, JSEXN_WASMLINKERROR, "imported global type mismatch")
+MSG_DEF(JSMSG_WASM_BAD_TBL_TYPE_LINK,  0, JSEXN_WASMLINKERROR, "imported table type mismatch")
 MSG_DEF(JSMSG_WASM_IND_CALL_TO_NULL,   0, JSEXN_WASMRUNTIMEERROR, "indirect call to null")
 MSG_DEF(JSMSG_WASM_IND_CALL_BAD_SIG,   0, JSEXN_WASMRUNTIMEERROR, "indirect call signature mismatch")
 MSG_DEF(JSMSG_WASM_UNREACHABLE,        0, JSEXN_WASMRUNTIMEERROR, "unreachable executed")
 MSG_DEF(JSMSG_WASM_INTEGER_OVERFLOW,   0, JSEXN_WASMRUNTIMEERROR, "integer overflow")
 MSG_DEF(JSMSG_WASM_INVALID_CONVERSION, 0, JSEXN_WASMRUNTIMEERROR, "invalid conversion to integer")
 MSG_DEF(JSMSG_WASM_INT_DIVIDE_BY_ZERO, 0, JSEXN_WASMRUNTIMEERROR, "integer divide by zero")
 MSG_DEF(JSMSG_WASM_OUT_OF_BOUNDS,      0, JSEXN_WASMRUNTIMEERROR, "index out of bounds")
 MSG_DEF(JSMSG_WASM_UNALIGNED_ACCESS,   0, JSEXN_WASMRUNTIMEERROR, "unaligned memory access")
 MSG_DEF(JSMSG_WASM_WAKE_OVERFLOW,      0, JSEXN_WASMRUNTIMEERROR, "too many woken agents")
 MSG_DEF(JSMSG_WASM_INVALID_PASSIVE_DATA_SEG, 0, JSEXN_WASMRUNTIMEERROR, "use of invalid passive data segment")
 MSG_DEF(JSMSG_WASM_INVALID_PASSIVE_ELEM_SEG, 0, JSEXN_WASMRUNTIMEERROR, "use of invalid passive element segment")
 MSG_DEF(JSMSG_WASM_DEREF_NULL,         0, JSEXN_WASMRUNTIMEERROR, "dereferencing null pointer")
 MSG_DEF(JSMSG_WASM_BAD_RANGE ,         2, JSEXN_RANGEERR,    "bad {0} {1}")
 MSG_DEF(JSMSG_WASM_BAD_GROW,           1, JSEXN_RANGEERR,    "failed to grow {0}")
+MSG_DEF(JSMSG_WASM_TABLE_OUT_OF_BOUNDS, 0, JSEXN_RANGEERR,   "table index out of bounds")
 MSG_DEF(JSMSG_WASM_BAD_UINT32,         2, JSEXN_TYPEERR,     "bad {0} {1}")
 MSG_DEF(JSMSG_WASM_BAD_BUF_ARG,        0, JSEXN_TYPEERR,     "first argument must be an ArrayBuffer or typed array object")
 MSG_DEF(JSMSG_WASM_BAD_MOD_ARG,        0, JSEXN_TYPEERR,     "first argument must be a WebAssembly.Module")
 MSG_DEF(JSMSG_WASM_BAD_BUF_MOD_ARG,    0, JSEXN_TYPEERR,     "first argument must be a WebAssembly.Module, ArrayBuffer or typed array object")
 MSG_DEF(JSMSG_WASM_BAD_DESC_ARG,       1, JSEXN_TYPEERR,     "first argument must be a {0} descriptor")
 MSG_DEF(JSMSG_WASM_BAD_ELEMENT,        0, JSEXN_TYPEERR,     "\"element\" property of table descriptor must be \"anyfunc\"")
+MSG_DEF(JSMSG_WASM_BAD_ELEMENT_GENERALIZED, 0, JSEXN_TYPEERR, "\"element\" property of table descriptor must be \"anyfunc\" or \"anyref\"")
 MSG_DEF(JSMSG_WASM_BAD_IMPORT_ARG,     0, JSEXN_TYPEERR,     "second argument must be an object")
 MSG_DEF(JSMSG_WASM_BAD_IMPORT_FIELD,   1, JSEXN_TYPEERR,     "import object field '{0}' is not an Object")
 MSG_DEF(JSMSG_WASM_BAD_TABLE_VALUE,    0, JSEXN_TYPEERR,     "can only assign WebAssembly exported functions to Table")
 MSG_DEF(JSMSG_WASM_BAD_I64_TYPE,       0, JSEXN_TYPEERR,     "cannot pass i64 to or from JS")
 MSG_DEF(JSMSG_WASM_BAD_GLOBAL_TYPE,    0, JSEXN_TYPEERR,     "bad type for a WebAssembly.Global")
 MSG_DEF(JSMSG_WASM_NO_TRANSFER,        0, JSEXN_TYPEERR,     "cannot transfer WebAssembly/asm.js ArrayBuffer")
 MSG_DEF(JSMSG_WASM_STREAM_ERROR,       0, JSEXN_TYPEERR,     "stream error during WebAssembly compilation")
 MSG_DEF(JSMSG_WASM_TEXT_FAIL,          1, JSEXN_SYNTAXERR,   "wasm text error: {0}")
--- a/js/src/wasm/AsmJS.cpp
+++ b/js/src/wasm/AsmJS.cpp
@@ -6649,19 +6649,18 @@ TryInstantiate(JSContext* cx, CallArgs a
 
     RootedValVector valImports(cx);
     Rooted<FunctionVector> funcs(cx, FunctionVector(cx));
     if (!GetImports(cx, metadata, globalVal, importVal, &funcs, &valImports)) {
         return false;
     }
 
     Rooted<WasmGlobalObjectVector> globalObjs(cx);
-
-    RootedWasmTableObject table(cx);
-    if (!module.instantiate(cx, funcs, table, memory, valImports, globalObjs.get(), nullptr,
+    Rooted<WasmTableObjectVector> tables(cx);
+    if (!module.instantiate(cx, funcs, tables.get(), memory, valImports, globalObjs.get(), nullptr,
                             instanceObj))
         return false;
 
     exportObj.set(&instanceObj->exportsObj());
     return true;
 }
 
 static bool
--- a/js/src/wasm/WasmAST.h
+++ b/js/src/wasm/WasmAST.h
@@ -90,16 +90,19 @@ class AstRef
         MOZ_ASSERT(!isInvalid());
     }
     bool isInvalid() const {
         return name_.empty() && index_ == AstNoIndex;
     }
     AstName name() const {
         return name_;
     }
+    bool isIndex() const {
+        return index_ != AstNoIndex;
+    }
     size_t index() const {
         MOZ_ASSERT(index_ != AstNoIndex);
         return index_;
     }
     void setIndex(uint32_t index) {
         MOZ_ASSERT(index_ == AstNoIndex);
         index_ = index;
     }
@@ -491,16 +494,22 @@ enum class AstExprKind
     If,
     Load,
 #ifdef ENABLE_WASM_BULKMEM_OPS
     MemOrTableCopy,
     MemOrTableDrop,
     MemFill,
     MemOrTableInit,
 #endif
+#ifdef ENABLE_WASM_GENERALIZED_TABLES
+    TableGet,
+    TableGrow,
+    TableSet,
+    TableSize,
+#endif
 #ifdef ENABLE_WASM_GC
     StructNew,
     StructGet,
     StructSet,
     StructNarrow,
 #endif
     Nop,
     Pop,
@@ -735,25 +744,32 @@ class AstCall : public AstExpr
 
     Op op() const { return op_; }
     AstRef& func() { return func_; }
     const AstExprVector& args() const { return args_; }
 };
 
 class AstCallIndirect : public AstExpr
 {
+    AstRef targetTable_;
     AstRef funcType_;
     AstExprVector args_;
     AstExpr* index_;
 
   public:
     static const AstExprKind Kind = AstExprKind::CallIndirect;
-    AstCallIndirect(AstRef funcType, AstExprType type, AstExprVector&& args, AstExpr* index)
-      : AstExpr(Kind, type), funcType_(funcType), args_(std::move(args)), index_(index)
+    AstCallIndirect(AstRef targetTable, AstRef funcType, AstExprType type, AstExprVector&& args,
+                    AstExpr* index)
+      : AstExpr(Kind, type),
+        targetTable_(targetTable),
+        funcType_(funcType),
+        args_(std::move(args)),
+        index_(index)
     {}
+    AstRef& targetTable() { return targetTable_; }
     AstRef& funcType() { return funcType_; }
     const AstExprVector& args() const { return args_; }
     AstExpr* index() const { return index_; }
 };
 
 class AstReturn : public AstExpr
 {
     AstExpr* maybeExpr_;
@@ -968,32 +984,39 @@ class AstWake : public AstExpr
     const AstLoadStoreAddress& address() const { return address_; }
     AstExpr& count() const { return *count_; }
 };
 
 #ifdef ENABLE_WASM_BULKMEM_OPS
 class AstMemOrTableCopy : public AstExpr
 {
     bool     isMem_;
+    AstRef   destTable_;
     AstExpr* dest_;
+    AstRef   srcTable_;
     AstExpr* src_;
     AstExpr* len_;
 
   public:
     static const AstExprKind Kind = AstExprKind::MemOrTableCopy;
-    explicit AstMemOrTableCopy(bool isMem, AstExpr* dest, AstExpr* src, AstExpr* len)
+    explicit AstMemOrTableCopy(bool isMem, AstRef destTable, AstExpr* dest, AstRef srcTable,
+                               AstExpr* src, AstExpr* len)
       : AstExpr(Kind, ExprType::Void),
         isMem_(isMem),
+        destTable_(destTable),
         dest_(dest),
+        srcTable_(srcTable),
         src_(src),
         len_(len)
     {}
 
     bool     isMem() const { return isMem_; }
+    AstRef&  destTable()   { return destTable_; }
     AstExpr& dest()  const { return *dest_; }
+    AstRef&  srcTable()    { return srcTable_; }
     AstExpr& src()   const { return *src_; }
     AstExpr& len()   const { return *len_; }
 };
 
 class AstMemOrTableDrop : public AstExpr
 {
     bool     isMem_;
     uint32_t segIndex_;
@@ -1029,39 +1052,116 @@ class AstMemFill : public AstExpr
     AstExpr& val()   const { return *val_; }
     AstExpr& len()   const { return *len_; }
 };
 
 class AstMemOrTableInit : public AstExpr
 {
     bool     isMem_;
     uint32_t segIndex_;
+    AstRef   targetTable_;
     AstExpr* dst_;
     AstExpr* src_;
     AstExpr* len_;
 
   public:
     static const AstExprKind Kind = AstExprKind::MemOrTableInit;
-    explicit AstMemOrTableInit(bool isMem, uint32_t segIndex, AstExpr* dst, AstExpr* src, AstExpr* len)
+    explicit AstMemOrTableInit(bool isMem, uint32_t segIndex, AstRef targetTable, AstExpr* dst,
+                               AstExpr* src, AstExpr* len)
       : AstExpr(Kind, ExprType::Void),
         isMem_(isMem),
         segIndex_(segIndex),
+        targetTable_(targetTable),
         dst_(dst),
         src_(src),
         len_(len)
     {}
 
     bool     isMem()    const { return isMem_; }
     uint32_t segIndex() const { return segIndex_; }
+    AstRef&  targetTable()      { return targetTable_; }
     AstExpr& dst()      const { return *dst_; }
     AstExpr& src()      const { return *src_; }
     AstExpr& len()      const { return *len_; }
 };
 #endif
 
+#ifdef ENABLE_WASM_GENERALIZED_TABLES
+class AstTableGet : public AstExpr
+{
+    AstRef   targetTable_;
+    AstExpr* index_;
+
+  public:
+    static const AstExprKind Kind = AstExprKind::TableGet;
+    explicit AstTableGet(AstRef targetTable, AstExpr* index)
+      : AstExpr(Kind, ExprType::AnyRef),
+        targetTable_(targetTable),
+        index_(index)
+    {}
+
+    AstRef& targetTable() { return targetTable_; }
+    AstExpr& index() const { return *index_; }
+};
+
+class AstTableGrow : public AstExpr
+{
+    AstRef   targetTable_;
+    AstExpr* delta_;
+    AstExpr* initValue_;
+
+  public:
+    static const AstExprKind Kind = AstExprKind::TableGrow;
+    AstTableGrow(AstRef targetTable, AstExpr* delta, AstExpr* initValue)
+      : AstExpr(Kind, ExprType::I32),
+        targetTable_(targetTable),
+        delta_(delta),
+        initValue_(initValue)
+    {}
+
+    AstRef& targetTable() { return targetTable_; }
+    AstExpr& delta() const { return *delta_; }
+    AstExpr& initValue() const { return *initValue_; }
+};
+
+class AstTableSet : public AstExpr
+{
+    AstRef   targetTable_;
+    AstExpr* index_;
+    AstExpr* value_;
+
+  public:
+    static const AstExprKind Kind = AstExprKind::TableSet;
+    AstTableSet(AstRef targetTable, AstExpr* index, AstExpr* value)
+      : AstExpr(Kind, ExprType::Void),
+        targetTable_(targetTable),
+        index_(index),
+        value_(value)
+    {}
+
+    AstRef& targetTable() { return targetTable_; }
+    AstExpr& index() const { return *index_; }
+    AstExpr& value() const { return *value_; }
+};
+
+class AstTableSize : public AstExpr
+{
+    AstRef   targetTable_;
+
+  public:
+    static const AstExprKind Kind = AstExprKind::TableSize;
+    explicit AstTableSize(AstRef targetTable)
+      : AstExpr(Kind, ExprType::I32),
+        targetTable_(targetTable)
+    {}
+
+    AstRef& targetTable() { return targetTable_; }
+};
+#endif // ENABLE_WASM_GENERALIZED_TABLES
+
 #ifdef ENABLE_WASM_GC
 class AstStructNew : public AstExpr
 {
     AstRef structType_;
     AstExprVector fieldValues_;
 
   public:
     static const AstExprKind Kind = AstExprKind::StructNew;
@@ -1234,35 +1334,46 @@ class AstImport : public AstNode
 {
     AstName name_;
     AstName module_;
     AstName field_;
     DefinitionKind kind_;
 
     AstRef funcType_;
     Limits limits_;
+    TableKind tableKind_;
     AstGlobal global_;
 
   public:
     AstImport(AstName name, AstName module, AstName field, AstRef funcType)
       : name_(name), module_(module), field_(field), kind_(DefinitionKind::Function), funcType_(funcType)
     {}
     AstImport(AstName name, AstName module, AstName field, DefinitionKind kind,
               const Limits& limits)
       : name_(name), module_(module), field_(field), kind_(kind), limits_(limits)
+    {
+        MOZ_ASSERT(kind != DefinitionKind::Table, "A table must have a kind");
+    }
+    AstImport(AstName name, AstName module, AstName field, const Limits& limits, TableKind tableKind)
+      : name_(name), module_(module), field_(field), kind_(DefinitionKind::Table), limits_(limits),
+        tableKind_(tableKind)
     {}
     AstImport(AstName name, AstName module, AstName field, const AstGlobal& global)
       : name_(name), module_(module), field_(field), kind_(DefinitionKind::Global), global_(global)
     {}
 
     AstName name() const { return name_; }
     AstName module() const { return module_; }
     AstName field() const { return field_; }
 
     DefinitionKind kind() const { return kind_; }
+    TableKind tableKind() const {
+        MOZ_ASSERT(kind_ == DefinitionKind::Table);
+        return tableKind_;
+    }
     AstRef& funcType() {
         MOZ_ASSERT(kind_ == DefinitionKind::Function);
         return funcType_;
     }
     Limits limits() const {
         MOZ_ASSERT(kind_ == DefinitionKind::Memory || kind_ == DefinitionKind::Table);
         return limits_;
     }
@@ -1308,25 +1419,29 @@ class AstDataSegment : public AstNode
     AstExpr* offsetIfActive() const { return offsetIfActive_; }
     const AstNameVector& fragments() const { return fragments_; }
 };
 
 typedef AstVector<AstDataSegment*> AstDataSegmentVector;
 
 class AstElemSegment : public AstNode
 {
+    AstRef targetTable_;
     AstExpr* offsetIfActive_;
     AstRefVector elems_;
 
   public:
-    AstElemSegment(AstExpr* offsetIfActive, AstRefVector&& elems)
-      : offsetIfActive_(offsetIfActive),
+    AstElemSegment(AstRef targetTable, AstExpr* offsetIfActive, AstRefVector&& elems)
+      : targetTable_(targetTable),
+        offsetIfActive_(offsetIfActive),
         elems_(std::move(elems))
     {}
 
+    AstRef targetTable() const { return targetTable_; }
+    AstRef& targetTableRef() { return targetTable_; }
     AstExpr* offsetIfActive() const { return offsetIfActive_; }
     AstRefVector& elems() { return elems_; }
     const AstRefVector& elems() const { return elems_; }
 };
 
 typedef AstVector<AstElemSegment*> AstElemSegmentVector;
 
 class AstStartFunc : public AstNode
@@ -1338,49 +1453,65 @@ class AstStartFunc : public AstNode
       : func_(func)
     {}
 
     AstRef& func() {
         return func_;
     }
 };
 
-struct AstResizable
+struct AstMemory
 {
     AstName name;
     Limits limits;
     bool imported;
 
-    AstResizable(const Limits& limits, bool imported, AstName name = AstName())
+    AstMemory(const Limits& limits, bool imported, AstName name = AstName())
       : name(name),
         limits(limits),
         imported(imported)
     {}
 };
 
+struct AstTable
+{
+    AstName name;
+    Limits limits;
+    TableKind tableKind;
+    bool imported;
+
+    AstTable(const Limits& limits, TableKind tableKind, bool imported, AstName name = AstName())
+      : name(name),
+        limits(limits),
+        tableKind(tableKind),
+        imported(imported)
+    {}
+};
+
 class AstModule : public AstNode
 {
   public:
     typedef AstVector<AstFunc*> FuncVector;
     typedef AstVector<AstImport*> ImportVector;
     typedef AstVector<AstExport*> ExportVector;
     typedef AstVector<AstTypeDef*> TypeDefVector;
     typedef AstVector<AstName> NameVector;
-    typedef AstVector<AstResizable> AstResizableVector;
+    typedef AstVector<AstMemory> AstMemoryVector;
+    typedef AstVector<AstTable> AstTableVector;
 
   private:
     typedef AstHashMap<AstFuncType*, uint32_t, AstFuncType> FuncTypeMap;
 
     LifoAlloc&           lifo_;
     TypeDefVector        types_;
     FuncTypeMap          funcTypeMap_;
     ImportVector         imports_;
     NameVector           funcImportNames_;
-    AstResizableVector   tables_;
-    AstResizableVector   memories_;
+    AstTableVector       tables_;
+    AstMemoryVector      memories_;
 #ifdef ENABLE_WASM_GC
     uint32_t             gcFeatureOptIn_;
 #endif
     ExportVector         exports_;
     Maybe<AstStartFunc>  startFunc_;
     FuncVector           funcs_;
     AstDataSegmentVector dataSegments_;
     AstElemSegmentVector elemSegments_;
@@ -1403,40 +1534,40 @@ class AstModule : public AstNode
         exports_(lifo),
         funcs_(lifo),
         dataSegments_(lifo),
         elemSegments_(lifo),
         globals_(lifo),
         numGlobalImports_(0)
     {}
     bool addMemory(AstName name, const Limits& memory) {
-        return memories_.append(AstResizable(memory, false, name));
+        return memories_.append(AstMemory(memory, false, name));
     }
     bool hasMemory() const {
         return !!memories_.length();
     }
-    const AstResizableVector& memories() const {
+    const AstMemoryVector& memories() const {
         return memories_;
     }
 #ifdef ENABLE_WASM_GC
     bool addGcFeatureOptIn(uint32_t version) {
         gcFeatureOptIn_ = version;
         return true;
     }
     uint32_t gcFeatureOptIn() const {
         return gcFeatureOptIn_;
     }
 #endif
-    bool addTable(AstName name, const Limits& table) {
-        return tables_.append(AstResizable(table, false, name));
+    bool addTable(AstName name, const Limits& table, TableKind tableKind) {
+        return tables_.append(AstTable(table, tableKind, false, name));
     }
     bool hasTable() const {
         return !!tables_.length();
     }
-    const AstResizableVector& tables() const {
+    const AstTableVector& tables() const {
         return tables_;
     }
     bool append(AstDataSegment* seg) {
         return dataSegments_.append(seg);
     }
     const AstDataSegmentVector& dataSegments() const {
         return dataSegments_;
     }
@@ -1503,22 +1634,22 @@ class AstModule : public AstNode
     bool append(AstImport* imp) {
         switch (imp->kind()) {
           case DefinitionKind::Function:
             if (!funcImportNames_.append(imp->name())) {
                 return false;
             }
             break;
           case DefinitionKind::Table:
-            if (!tables_.append(AstResizable(imp->limits(), true))) {
+            if (!tables_.append(AstTable(imp->limits(), imp->tableKind(), true))) {
                 return false;
             }
             break;
           case DefinitionKind::Memory:
-            if (!memories_.append(AstResizable(imp->limits(), true))) {
+            if (!memories_.append(AstMemory(imp->limits(), true))) {
                 return false;
             }
             break;
           case DefinitionKind::Global:
             numGlobalImports_++;
             break;
         }
         return imports_.append(imp);
--- a/js/src/wasm/WasmBaselineCompile.cpp
+++ b/js/src/wasm/WasmBaselineCompile.cpp
@@ -1926,21 +1926,23 @@ class BaseCompiler final : public BaseCo
     bool                        deadCode_;       // Flag indicating we should decode & discard the opcode
     BCESet                      bceSafe_;        // Locals that have been bounds checked and not updated since
     ValTypeVector               SigD_;
     ValTypeVector               SigF_;
     MIRTypeVector               SigP_;
     MIRTypeVector               SigPI_;
     MIRTypeVector               SigPL_;
     MIRTypeVector               SigPII_;
+    MIRTypeVector               SigPIPI_;
     MIRTypeVector               SigPIII_;
     MIRTypeVector               SigPIIL_;
     MIRTypeVector               SigPIIP_;
     MIRTypeVector               SigPILL_;
     MIRTypeVector               SigPIIII_;
+    MIRTypeVector               SigPIIIII_;
     NonAssertingLabel           returnLabel_;
 
     LatentOp                    latentOp_;       // Latent operation for branch (seen next)
     ValType                     latentType_;     // Operand type, if latentOp_ is true
     Assembler::Condition        latentIntCmp_;   // Comparison operator, if latentOp_ == Compare, int types
     Assembler::DoubleCondition  latentDoubleCmp_;// Comparison operator, if latentOp_ == Compare, float types
 
     FuncOffsets                 offsets_;
@@ -3868,23 +3870,23 @@ class BaseCompiler final : public BaseCo
 
     void callSymbolic(SymbolicAddress callee, const FunctionCall& call) {
         CallSiteDesc desc(call.lineOrBytecode, CallSiteDesc::Symbolic);
         masm.call(desc, callee);
     }
 
     // Precondition: sync()
 
-    void callIndirect(uint32_t funcTypeIndex, const Stk& indexVal, const FunctionCall& call)
+    void callIndirect(uint32_t funcTypeIndex, uint32_t tableIndex, const Stk& indexVal,
+                      const FunctionCall& call)
     {
         const FuncTypeWithId& funcType = env_.types[funcTypeIndex].funcType();
         MOZ_ASSERT(funcType.id.kind() != FuncTypeIdDescKind::None);
 
-        MOZ_ASSERT(env_.tables.length() == 1);
-        const TableDesc& table = env_.tables[0];
+        const TableDesc& table = env_.tables[tableIndex];
 
         loadI32(indexVal, RegI32(WasmTableCallIndexReg));
 
         CallSiteDesc desc(call.lineOrBytecode, CallSiteDesc::Dynamic);
         CalleeDesc callee = CalleeDesc::wasmTable(table, funcType.id);
         masm.wasmCallIndirect(desc, callee, NeedsBoundsCheck(true));
     }
 
@@ -6164,16 +6166,20 @@ class BaseCompiler final : public BaseCo
     MOZ_MUST_USE bool emitAtomicXchg(ValType type, Scalar::Type viewType);
     void emitAtomicXchg64(MemoryAccessDesc* access, ValType type, WantResult wantResult);
 #ifdef ENABLE_WASM_BULKMEM_OPS
     MOZ_MUST_USE bool emitMemOrTableCopy(bool isMem);
     MOZ_MUST_USE bool emitMemOrTableDrop(bool isMem);
     MOZ_MUST_USE bool emitMemFill();
     MOZ_MUST_USE bool emitMemOrTableInit(bool isMem);
 #endif
+    MOZ_MUST_USE bool emitTableGet();
+    MOZ_MUST_USE bool emitTableGrow();
+    MOZ_MUST_USE bool emitTableSet();
+    MOZ_MUST_USE bool emitTableSize();
     MOZ_MUST_USE bool emitStructNew();
     MOZ_MUST_USE bool emitStructGet();
     MOZ_MUST_USE bool emitStructSet();
     MOZ_MUST_USE bool emitStructNarrow();
 };
 
 void
 BaseCompiler::emitAddI32()
@@ -8176,19 +8182,20 @@ BaseCompiler::emitCall()
 }
 
 bool
 BaseCompiler::emitCallIndirect()
 {
     uint32_t lineOrBytecode = readCallSiteLineOrBytecode();
 
     uint32_t funcTypeIndex;
+    uint32_t tableIndex;
     Nothing callee_;
     BaseOpIter::ValueVector args_;
-    if (!iter_.readCallIndirect(&funcTypeIndex, &callee_, &args_)) {
+    if (!iter_.readCallIndirect(&funcTypeIndex, &tableIndex, &callee_, &args_)) {
         return false;
     }
 
     if (deadCode_) {
         return true;
     }
 
     sync();
@@ -8208,17 +8215,17 @@ BaseCompiler::emitCallIndirect()
 
     FunctionCall baselineCall(lineOrBytecode);
     beginCall(baselineCall, UseABI::Wasm, InterModule::True);
 
     if (!emitCallArgs(funcType.args(), &baselineCall)) {
         return false;
     }
 
-    callIndirect(funcTypeIndex, callee, baselineCall);
+    callIndirect(funcTypeIndex, tableIndex, callee, baselineCall);
 
     endCall(baselineCall, stackSpace);
 
     popValueStackBy(numArgs);
 
     pushReturnedIfNonVoid(baselineCall, funcType.ret());
 
     return true;
@@ -9268,16 +9275,17 @@ BaseCompiler::emitGrowMemory()
     if (!iter_.readGrowMemory(&arg)) {
         return false;
     }
 
     if (deadCode_) {
         return true;
     }
 
+    // infallible
     emitInstanceCall(lineOrBytecode, SigPI_, ExprType::I32, SymbolicAddress::GrowMemory);
     return true;
 }
 
 bool
 BaseCompiler::emitCurrentMemory()
 {
     uint32_t lineOrBytecode = readCallSiteLineOrBytecode();
@@ -9285,16 +9293,17 @@ BaseCompiler::emitCurrentMemory()
     if (!iter_.readCurrentMemory()) {
         return false;
     }
 
     if (deadCode_) {
         return true;
     }
 
+    // infallible
     emitInstanceCall(lineOrBytecode, SigP_, ExprType::I32, SymbolicAddress::CurrentMemory);
     return true;
 }
 
 bool
 BaseCompiler::emitRefNull()
 {
     ValType type;
@@ -9610,16 +9619,17 @@ BaseCompiler::emitWait(ValType type, uin
     if (!iter_.readWait(&addr, type, byteSize, &nothing, &nothing)) {
         return false;
     }
 
     if (deadCode_) {
         return true;
     }
 
+    // Returns -1 on trap, otherwise nonnegative result.
     switch (type.code()) {
       case ValType::I32:
         emitInstanceCall(lineOrBytecode, SigPIIL_, ExprType::I32, SymbolicAddress::WaitI32);
         break;
       case ValType::I64:
         emitInstanceCall(lineOrBytecode, SigPILL_, ExprType::I32, SymbolicAddress::WaitI64);
         break;
       default:
@@ -9644,44 +9654,56 @@ BaseCompiler::emitWake()
     if (!iter_.readWake(&addr, &nothing)) {
         return false;
     }
 
     if (deadCode_) {
         return true;
     }
 
+    // Returns -1 on trap, otherwise nonnegative result.
     emitInstanceCall(lineOrBytecode, SigPII_, ExprType::I32, SymbolicAddress::Wake);
 
     Label ok;
     masm.branchTest32(Assembler::NotSigned, ReturnReg, ReturnReg, &ok);
     trap(Trap::ThrowReported);
     masm.bind(&ok);
 
     return true;
 }
 
 #ifdef ENABLE_WASM_BULKMEM_OPS
 bool
 BaseCompiler::emitMemOrTableCopy(bool isMem)
 {
     uint32_t lineOrBytecode = readCallSiteLineOrBytecode();
 
+    uint32_t dstMemOrTableIndex = 0;
+    uint32_t srcMemOrTableIndex = 0;
     Nothing nothing;
-    if (!iter_.readMemOrTableCopy(isMem, &nothing, &nothing, &nothing)) {
+    if (!iter_.readMemOrTableCopy(isMem, &dstMemOrTableIndex, &nothing, &srcMemOrTableIndex,
+                                  &nothing, &nothing))
+    {
         return false;
     }
 
     if (deadCode_) {
         return true;
     }
 
-    SymbolicAddress callee = isMem ? SymbolicAddress::MemCopy
-                                   : SymbolicAddress::TableCopy;
-    emitInstanceCall(lineOrBytecode, SigPIII_, ExprType::Void, callee);
+    // Returns -1 on trap, otherwise 0.
+    if (isMem) {
+        MOZ_ASSERT(srcMemOrTableIndex == 0);
+        MOZ_ASSERT(dstMemOrTableIndex == 0);
+        emitInstanceCall(lineOrBytecode, SigPIII_, ExprType::Void, SymbolicAddress::MemCopy);
+    } else {
+        pushI32(dstMemOrTableIndex);
+        pushI32(srcMemOrTableIndex);
+        emitInstanceCall(lineOrBytecode, SigPIIIII_, ExprType::Void, SymbolicAddress::TableCopy);
+    }
 
     Label ok;
     masm.branchTest32(Assembler::NotSigned, ReturnReg, ReturnReg, &ok);
     trap(Trap::ThrowReported);
     masm.bind(&ok);
 
     return true;
 }
@@ -9696,16 +9718,18 @@ BaseCompiler::emitMemOrTableDrop(bool is
         return false;
     }
 
     if (deadCode_) {
         return true;
     }
 
     // Despite the cast to int32_t, the callee regards the value as unsigned.
+    //
+    // Returns -1 on trap, otherwise 0.
     pushI32(int32_t(segIndex));
     SymbolicAddress callee = isMem ? SymbolicAddress::MemDrop
                                    : SymbolicAddress::TableDrop;
     emitInstanceCall(lineOrBytecode, SigPI_, ExprType::Void, callee);
 
     Label ok;
     masm.branchTest32(Assembler::NotSigned, ReturnReg, ReturnReg, &ok);
     trap(Trap::ThrowReported);
@@ -9723,55 +9747,156 @@ BaseCompiler::emitMemFill()
     if (!iter_.readMemFill(&nothing, &nothing, &nothing)) {
         return false;
     }
 
     if (deadCode_) {
         return true;
     }
 
+    // Returns -1 on trap, otherwise 0.
     emitInstanceCall(lineOrBytecode, SigPIII_, ExprType::Void, SymbolicAddress::MemFill);
 
     Label ok;
     masm.branchTest32(Assembler::NotSigned, ReturnReg, ReturnReg, &ok);
     trap(Trap::ThrowReported);
     masm.bind(&ok);
 
     return true;
 }
 
 bool
 BaseCompiler::emitMemOrTableInit(bool isMem)
 {
     uint32_t lineOrBytecode = readCallSiteLineOrBytecode();
 
     uint32_t segIndex = 0;
+    uint32_t dstTableIndex = 0;
     Nothing nothing;
-    if (!iter_.readMemOrTableInit(isMem, &segIndex, &nothing, &nothing, &nothing)) {
+    if (!iter_.readMemOrTableInit(isMem, &segIndex, &dstTableIndex, &nothing, &nothing, &nothing)) {
         return false;
     }
 
     if (deadCode_) {
         return true;
     }
 
+    // Returns -1 on trap, otherwise 0.
     pushI32(int32_t(segIndex));
-    SymbolicAddress callee = isMem ? SymbolicAddress::MemInit
-                                   : SymbolicAddress::TableInit;
-    emitInstanceCall(lineOrBytecode, SigPIIII_, ExprType::Void, callee);
+    if (isMem) {
+        emitInstanceCall(lineOrBytecode, SigPIIII_, ExprType::Void, SymbolicAddress::MemInit);
+    } else {
+        pushI32(dstTableIndex);
+        emitInstanceCall(lineOrBytecode, SigPIIIII_, ExprType::Void, SymbolicAddress::TableInit);
+    }
 
     Label ok;
     masm.branchTest32(Assembler::NotSigned, ReturnReg, ReturnReg, &ok);
     trap(Trap::ThrowReported);
     masm.bind(&ok);
 
     return true;
 }
 #endif
 
+MOZ_MUST_USE
+bool
+BaseCompiler::emitTableGet()
+{
+    uint32_t lineOrBytecode = readCallSiteLineOrBytecode();
+    Nothing index;
+    uint32_t tableIndex;
+    if (!iter_.readTableGet(&tableIndex, &index)) {
+        return false;
+    }
+    if (deadCode_) {
+        return true;
+    }
+    // get(index:u32, table:u32) -> anyref
+    //
+    // Returns (void*)-1 for error, this will not be confused with a real ref
+    // value.
+    pushI32(tableIndex);
+    emitInstanceCall(lineOrBytecode, SigPII_, ExprType::AnyRef, SymbolicAddress::TableGet);
+    Label noTrap;
+    masm.branchPtr(Assembler::NotEqual, ReturnReg, Imm32(-1), &noTrap);
+    trap(Trap::ThrowReported);
+    masm.bind(&noTrap);
+
+    return true;
+}
+
+MOZ_MUST_USE
+bool
+BaseCompiler::emitTableGrow()
+{
+    uint32_t lineOrBytecode = readCallSiteLineOrBytecode();
+    Nothing delta;
+    Nothing initValue;
+    uint32_t tableIndex;
+    if (!iter_.readTableGrow(&tableIndex, &delta, &initValue)) {
+        return false;
+    }
+    if (deadCode_) {
+        return true;
+    }
+    // grow(delta:u32, initValue:anyref, table:u32) -> u32
+    //
+    // infallible.
+    pushI32(tableIndex);
+    emitInstanceCall(lineOrBytecode, SigPIPI_, ExprType::I32, SymbolicAddress::TableGrow);
+
+    return true;
+}
+
+MOZ_MUST_USE
+bool
+BaseCompiler::emitTableSet()
+{
+    uint32_t lineOrBytecode = readCallSiteLineOrBytecode();
+    Nothing index, value;
+    uint32_t tableIndex;
+    if (!iter_.readTableSet(&tableIndex, &index, &value)) {
+        return false;
+    }
+    if (deadCode_) {
+        return true;
+    }
+    // set(index:u32, value:ref, table:u32) -> i32
+    //
+    // Returns -1 on range error, otherwise 0 (which is then ignored).
+    pushI32(tableIndex);
+    emitInstanceCall(lineOrBytecode, SigPIPI_, ExprType::Void, SymbolicAddress::TableSet);
+    Label noTrap;
+    masm.branchTest32(Assembler::NotSigned, ReturnReg, ReturnReg, &noTrap);
+    trap(Trap::ThrowReported);
+    masm.bind(&noTrap);
+    return true;
+}
+
+MOZ_MUST_USE
+bool
+BaseCompiler::emitTableSize()
+{
+    uint32_t lineOrBytecode = readCallSiteLineOrBytecode();
+    uint32_t tableIndex;
+    if (!iter_.readTableSize(&tableIndex)) {
+        return false;
+    }
+    if (deadCode_) {
+        return true;
+    }
+    // size(table:u32) -> u32
+    //
+    // infallible.
+    pushI32(tableIndex);
+    emitInstanceCall(lineOrBytecode, SigPI_, ExprType::I32, SymbolicAddress::TableSize);
+    return true;
+}
+
 bool
 BaseCompiler::emitStructNew()
 {
     uint32_t lineOrBytecode = readCallSiteLineOrBytecode();
 
     uint32_t typeIndex;
     BaseOpIter::ValueVector args;
     if (!iter_.readStructNew(&typeIndex, &args)) {
@@ -9779,16 +9904,18 @@ BaseCompiler::emitStructNew()
     }
 
     if (deadCode_) {
         return true;
     }
 
     // Allocate zeroed storage.  The parameter to StructNew is an index into a
     // descriptor table that the instance has.
+    //
+    // Returns null on OOM.
 
     const StructType& structType = env_.types[typeIndex].structType();
 
     pushI32(structType.moduleIndex_);
     emitInstanceCall(lineOrBytecode, SigPI_, ExprType::AnyRef, SymbolicAddress::StructNew);
 
     // Null pointer check.
 
@@ -10096,17 +10223,18 @@ BaseCompiler::emitStructNarrow()
 
     RegPtr rp = popRef();
 
     // AnyRef -> (ref T) must first unbox; leaves rp or null
 
     bool mustUnboxAnyref = inputType == ValType::AnyRef;
 
     // Dynamic downcast (ref T) -> (ref U), leaves rp or null
-
+    //
+    // Infallible.
     const StructType& outputStruct = env_.types[outputType.refTypeIndex()].structType();
 
     pushI32(mustUnboxAnyref);
     pushI32(outputStruct.moduleIndex_);
     pushRef(rp);
     emitInstanceCall(lineOrBytecode, SigPIIP_, ExprType::AnyRef, SymbolicAddress::StructNarrow);
 
     return true;
@@ -10758,16 +10886,26 @@ BaseCompiler::emitBody()
                 CHECK_NEXT(emitMemOrTableInit(/*isMem=*/true));
               case uint16_t(MiscOp::TableCopy):
                 CHECK_NEXT(emitMemOrTableCopy(/*isMem=*/false));
               case uint16_t(MiscOp::TableDrop):
                 CHECK_NEXT(emitMemOrTableDrop(/*isMem=*/false));
               case uint16_t(MiscOp::TableInit):
                 CHECK_NEXT(emitMemOrTableInit(/*isMem=*/false));
 #endif // ENABLE_WASM_BULKMEM_OPS
+#ifdef ENABLE_WASM_GENERALIZED_TABLES
+              case uint16_t(MiscOp::TableGet):
+                CHECK_NEXT(emitTableGet());
+              case uint16_t(MiscOp::TableGrow):
+                CHECK_NEXT(emitTableGrow());
+              case uint16_t(MiscOp::TableSet):
+                CHECK_NEXT(emitTableSet());
+              case uint16_t(MiscOp::TableSize):
+                CHECK_NEXT(emitTableSize());
+#endif
 #ifdef ENABLE_WASM_GC
               case uint16_t(MiscOp::StructNew):
                 if (env_.gcTypesEnabled() == HasGcTypes::False) {
                   return iter_.unrecognizedOpcode(&op);
                 }
                 CHECK_NEXT(emitStructNew());
               case uint16_t(MiscOp::StructGet):
                 if (env_.gcTypesEnabled() == HasGcTypes::False) {
@@ -11035,16 +11173,21 @@ BaseCompiler::init()
     if (!SigPL_.append(MIRType::Pointer) || !SigPL_.append(MIRType::Int64)) {
         return false;
     }
     if (!SigPII_.append(MIRType::Pointer) || !SigPII_.append(MIRType::Int32) ||
         !SigPII_.append(MIRType::Int32))
     {
         return false;
     }
+    if (!SigPIPI_.append(MIRType::Pointer) || !SigPIPI_.append(MIRType::Int32) ||
+        !SigPIPI_.append(MIRType::Pointer) || !SigPIPI_.append(MIRType::Int32))
+    {
+        return false;
+    }
     if (!SigPIII_.append(MIRType::Pointer) || !SigPIII_.append(MIRType::Int32) ||
         !SigPIII_.append(MIRType::Int32) || !SigPIII_.append(MIRType::Int32))
     {
         return false;
     }
     if (!SigPIIL_.append(MIRType::Pointer) || !SigPIIL_.append(MIRType::Int32) ||
         !SigPIIL_.append(MIRType::Int32) || !SigPIIL_.append(MIRType::Int64))
     {
@@ -11061,16 +11204,22 @@ BaseCompiler::init()
         return false;
     }
     if (!SigPIIII_.append(MIRType::Pointer) || !SigPIIII_.append(MIRType::Int32) ||
         !SigPIIII_.append(MIRType::Int32) || !SigPIIII_.append(MIRType::Int32) ||
         !SigPIIII_.append(MIRType::Int32))
     {
         return false;
     }
+    if (!SigPIIIII_.append(MIRType::Pointer) || !SigPIIIII_.append(MIRType::Int32) ||
+        !SigPIIIII_.append(MIRType::Int32) || !SigPIIIII_.append(MIRType::Int32) ||
+        !SigPIIIII_.append(MIRType::Int32) || !SigPIIIII_.append(MIRType::Int32))
+    {
+        return false;
+    }
 
     if (!fr.setupLocals(locals_, funcType().args(), env_.debugEnabled(), &localInfo_)) {
         return false;
     }
 
     return true;
 }
 
--- a/js/src/wasm/WasmBuiltins.cpp
+++ b/js/src/wasm/WasmBuiltins.cpp
@@ -675,24 +675,36 @@ AddressOf(SymbolicAddress imm, ABIFuncti
         return FuncCast(Instance::memDrop, *abiType);
       case SymbolicAddress::MemFill:
         *abiType = Args_General4;
         return FuncCast(Instance::memFill, *abiType);
       case SymbolicAddress::MemInit:
         *abiType = Args_General5;
         return FuncCast(Instance::memInit, *abiType);
       case SymbolicAddress::TableCopy:
-        *abiType = Args_General4;
+        *abiType = Args_General6;
         return FuncCast(Instance::tableCopy, *abiType);
       case SymbolicAddress::TableDrop:
         *abiType = Args_General2;
         return FuncCast(Instance::tableDrop, *abiType);
       case SymbolicAddress::TableInit:
-        *abiType = Args_General5;
+        *abiType = Args_General6;
         return FuncCast(Instance::tableInit, *abiType);
+      case SymbolicAddress::TableGet:
+        *abiType = Args_General3;
+        return FuncCast(Instance::tableGet, *abiType);
+      case SymbolicAddress::TableGrow:
+        *abiType = Args_General4;
+        return FuncCast(Instance::tableGrow, *abiType);
+      case SymbolicAddress::TableSet:
+        *abiType = Args_General4;
+        return FuncCast(Instance::tableSet, *abiType);
+      case SymbolicAddress::TableSize:
+        *abiType = Args_General2;
+        return FuncCast(Instance::tableSize, *abiType);
       case SymbolicAddress::PostBarrier:
         *abiType = Args_General2;
         return FuncCast(Instance::postBarrier, *abiType);
       case SymbolicAddress::StructNew:
         *abiType = Args_General2;
         return FuncCast(Instance::structNew, *abiType);
       case SymbolicAddress::StructNarrow:
         *abiType = Args_General4;
@@ -772,17 +784,21 @@ wasm::NeedsBuiltinThunk(SymbolicAddress 
       case SymbolicAddress::CoerceInPlace_JitEntry:
       case SymbolicAddress::ReportInt64JSCall:
       case SymbolicAddress::MemCopy:
       case SymbolicAddress::MemDrop:
       case SymbolicAddress::MemFill:
       case SymbolicAddress::MemInit:
       case SymbolicAddress::TableCopy:
       case SymbolicAddress::TableDrop:
+      case SymbolicAddress::TableGet:
+      case SymbolicAddress::TableGrow:
       case SymbolicAddress::TableInit:
+      case SymbolicAddress::TableSet:
+      case SymbolicAddress::TableSize:
       case SymbolicAddress::PostBarrier:
       case SymbolicAddress::StructNew:
       case SymbolicAddress::StructNarrow:
         return true;
       case SymbolicAddress::Limit:
         break;
     }
 
--- a/js/src/wasm/WasmConstants.h
+++ b/js/src/wasm/WasmConstants.h
@@ -136,17 +136,18 @@ enum class GlobalTypeImmediate
     IsMutable                            = 0x1,
     AllowedMask                          = 0x1
 };
 
 enum class MemoryTableFlags
 {
     Default                              = 0x0,
     HasMaximum                           = 0x1,
-    IsShared                             = 0x2
+    IsShared                             = 0x2,
+    HasTableIndex                        = 0x4, // UNOFFICIAL.  There will be a separate flag for memory.
 };
 
 enum class MemoryMasks
 {
     AllowUnshared                        = 0x1,
     AllowShared                          = 0x3
 };
 
@@ -395,16 +396,22 @@ enum class MiscOp
     MemInit                              = 0x08,
     MemDrop                              = 0x09,
     MemCopy                              = 0x0a,
     MemFill                              = 0x0b,
     TableInit                            = 0x0c,
     TableDrop                            = 0x0d,
     TableCopy                            = 0x0e,
 
+    // Generalized tables (reftypes proposal).  Note, these are unofficial.
+    TableGrow                            = 0x0f,
+    TableGet                             = 0x10,
+    TableSet                             = 0x11,
+    TableSize                            = 0x12,
+
     // Structure operations.  Note, these are unofficial.
     StructNew                            = 0x50,
     StructGet                            = 0x51,
     StructSet                            = 0x52,
     StructNarrow                         = 0x53,
 
     Limit
 };
@@ -577,16 +584,17 @@ enum class FieldFlags {
     Mutable     = 0x01,
     AllowedMask = 0x01
 };
 
 // These limits are agreed upon with other engines for consistency.
 
 static const unsigned MaxTypes               =  1000000;
 static const unsigned MaxFuncs               =  1000000;
+static const unsigned MaxTables              =   100000;  // TODO: get this into the shared limits spec
 static const unsigned MaxImports             =   100000;
 static const unsigned MaxExports             =   100000;
 static const unsigned MaxGlobals             =  1000000;
 static const unsigned MaxDataSegments        =   100000;
 static const unsigned MaxElemSegments        = 10000000;
 static const unsigned MaxTableMaximumLength  = 10000000;
 static const unsigned MaxLocals              =    50000;
 static const unsigned MaxParams              =     1000;
--- a/js/src/wasm/WasmCraneliftCompile.cpp
+++ b/js/src/wasm/WasmCraneliftCompile.cpp
@@ -416,17 +416,30 @@ size_t
 table_tlsOffset(const TableDesc* table)
 {
     return globalToTlsOffset(table->globalDataOffset);
 }
 
 bool
 table_isExternal(const TableDesc* table)
 {
-    return table->external;
+    // The external field was removed because it did not make sense in a
+    // multi-table world in the presence of table.copy - all function tables now
+    // use FunctionTableElem values, carrying both the code pointer and the
+    // instance.
+    //
+    // If you meant to ask whether the function is represented as
+    // 'FunctionTableElem' (what used to be called ExternalTableElem) then you
+    // want to see if
+    // table->kind==TableKind::AnyFunction || table->kind==TableKind::TypedFunction.
+    //
+    // If you meant to ask whether the table needs a JSObject representation,
+    // then you want to see if table->importedOrExported is true.
+
+    MOZ_CRASH("FIXME. This field has been removed.");
 }
 
 // Sig
 
 size_t
 funcType_numArgs(const FuncTypeWithId* funcType)
 {
     return funcType->args().length();
--- a/js/src/wasm/WasmFrameIter.cpp
+++ b/js/src/wasm/WasmFrameIter.cpp
@@ -1394,18 +1394,26 @@ ThunkedNativeToDescription(SymbolicAddre
       case SymbolicAddress::MemFill:
         return "call to native memory.fill function";
       case SymbolicAddress::MemInit:
         return "call to native memory.init function";
       case SymbolicAddress::TableCopy:
         return "call to native table.copy function";
       case SymbolicAddress::TableDrop:
         return "call to native table.drop function";
+      case SymbolicAddress::TableGet:
+        return "call to native table.get function";
+      case SymbolicAddress::TableGrow:
+        return "call to native table.grow function";
       case SymbolicAddress::TableInit:
         return "call to native table.init function";
+      case SymbolicAddress::TableSet:
+        return "call to native table.set function";
+      case SymbolicAddress::TableSize:
+        return "call to native table.size function";
       case SymbolicAddress::PostBarrier:
         return "call to native GC postbarrier (in wasm)";
       case SymbolicAddress::StructNew:
         return "call to native struct.new (in wasm)";
       case SymbolicAddress::StructNarrow:
         return "call to native struct.narrow (in wasm)";
 #if defined(JS_CODEGEN_MIPS32)
       case SymbolicAddress::js_jit_gAtomic64Lock:
--- a/js/src/wasm/WasmGenerator.cpp
+++ b/js/src/wasm/WasmGenerator.cpp
@@ -296,20 +296,20 @@ ModuleGenerator::init(Metadata* maybeAsm
         if (!allocateGlobalBytes(width, width, &globalDataOffset)) {
             return false;
         }
 
         global.setOffset(globalDataOffset);
     }
 
     // Accumulate all exported functions, whether by explicit export or
-    // implicitly by being an element of an external (imported or exported)
-    // table or by being the start function. The FuncExportVector stored in
-    // Metadata needs to be sorted (to allow O(log(n)) lookup at runtime) and
-    // deduplicated, so use an intermediate vector to sort and de-duplicate.
+    // implicitly by being an element of a function table or by being the start
+    // function. The FuncExportVector stored in Metadata needs to be sorted (to
+    // allow O(log(n)) lookup at runtime) and deduplicated, so use an
+    // intermediate vector to sort and de-duplicate.
 
     static_assert((uint64_t(MaxFuncs) << 1) < uint64_t(UINT32_MAX), "bit packing won't work");
 
     class ExportedFunc {
         uint32_t value;
       public:
         ExportedFunc(uint32_t index, bool isExplicit) : value((index << 1) | (isExplicit?1:0)) {}
         uint32_t index() const { return value >> 1; }
@@ -324,23 +324,31 @@ ModuleGenerator::init(Metadata* maybeAsm
         if (exp.kind() == DefinitionKind::Function) {
             if (!exportedFuncs.emplaceBack(exp.funcIndex(), true)) {
                 return false;
             }
         }
     }
 
     for (const ElemSegment* seg : env_->elemSegments) {
-        if (env_->tables[seg->tableIndex].external) {
+        TableKind kind = !seg->active() ? TableKind::AnyFunction : env_->tables[seg->tableIndex].kind;
+        switch (kind) {
+          case TableKind::AnyFunction:
             if (!exportedFuncs.reserve(exportedFuncs.length() + seg->length())) {
                 return false;
             }
             for (uint32_t funcIndex : seg->elemFuncIndices) {
                 exportedFuncs.infallibleEmplaceBack(funcIndex, false);
             }
+            break;
+          case TableKind::TypedFunction:
+            // asm.js functions are not exported.
+            break;
+          case TableKind::AnyRef:
+            break;
         }
     }
 
     if (env_->startFuncIndex && !exportedFuncs.emplaceBack(*env_->startFuncIndex, true)) {
         return false;
     }
 
     std::sort(exportedFuncs.begin(), exportedFuncs.end());
--- a/js/src/wasm/WasmInstance.cpp
+++ b/js/src/wasm/WasmInstance.cpp
@@ -230,45 +230,45 @@ Instance::callImport(JSContext* cx, uint
         return false;
     }
 
     import.code = jitExitCode;
     import.baselineScript = script->baselineScript();
     return true;
 }
 
-/* static */ int32_t
+/* static */ int32_t /* 0 to signal trap; 1 to signal OK */
 Instance::callImport_void(Instance* instance, int32_t funcImportIndex, int32_t argc, uint64_t* argv)
 {
     JSContext* cx = TlsContext.get();
     RootedValue rval(cx);
     return instance->callImport(cx, funcImportIndex, argc, argv, &rval);
 }
 
-/* static */ int32_t
+/* static */ int32_t /* 0 to signal trap; 1 to signal OK */
 Instance::callImport_i32(Instance* instance, int32_t funcImportIndex, int32_t argc, uint64_t* argv)
 {
     JSContext* cx = TlsContext.get();
     RootedValue rval(cx);
     if (!instance->callImport(cx, funcImportIndex, argc, argv, &rval)) {
         return false;
     }
 
     return ToInt32(cx, rval, (int32_t*)argv);
 }
 
-/* static */ int32_t
+/* static */ int32_t /* 0 to signal trap; 1 to signal OK */
 Instance::callImport_i64(Instance* instance, int32_t funcImportIndex, int32_t argc, uint64_t* argv)
 {
     JSContext* cx = TlsContext.get();
     JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, JSMSG_WASM_BAD_I64_TYPE);
     return false;
 }
 
-/* static */ int32_t
+/* static */ int32_t /* 0 to signal trap; 1 to signal OK */
 Instance::callImport_f64(Instance* instance, int32_t funcImportIndex, int32_t argc, uint64_t* argv)
 {
     JSContext* cx = TlsContext.get();
     RootedValue rval(cx);
     if (!instance->callImport(cx, funcImportIndex, argc, argv, &rval)) {
         return false;
     }
 
@@ -286,45 +286,45 @@ ToRef(JSContext* cx, HandleValue val, vo
     JSObject* obj = ToObject(cx, val);
     if (!obj) {
         return false;
     }
     *(JSObject**)addr = obj;
     return true;
 }
 
-/* static */ int32_t
+/* static */ int32_t /* 0 to signal trap; 1 to signal OK */
 Instance::callImport_ref(Instance* instance, int32_t funcImportIndex, int32_t argc, uint64_t* argv)
 {
     JSContext* cx = TlsContext.get();
     RootedValue rval(cx);
     if (!instance->callImport(cx, funcImportIndex, argc, argv, &rval)) {
         return false;
     }
     return ToRef(cx, rval, argv);
 }
 
-/* static */ uint32_t
+/* static */ uint32_t /* infallible */
 Instance::growMemory_i32(Instance* instance, uint32_t delta)
 {
     MOZ_ASSERT(!instance->isAsmJS());
 
     JSContext* cx = TlsContext.get();
     RootedWasmMemoryObject memory(cx, instance->memory_);
 
     uint32_t ret = WasmMemoryObject::grow(memory, delta, cx);
 
     // If there has been a moving grow, this Instance should have been notified.
     MOZ_RELEASE_ASSERT(instance->tlsData()->memoryBase ==
                        instance->memory_->buffer().dataPointerEither());
 
     return ret;
 }
 
-/* static */ uint32_t
+/* static */ uint32_t /* infallible */
 Instance::currentMemory_i32(Instance* instance)
 {
     // This invariant must hold when running Wasm code. Assert it here so we can
     // write tests for cross-realm calls.
     MOZ_ASSERT(TlsContext.get()->realm() == instance->realm());
 
     uint32_t byteLength = instance->memory()->volatileMemoryLength();
     MOZ_ASSERT(byteLength % wasm::PageSize == 0);
@@ -356,29 +356,29 @@ PerformWait(Instance* instance, uint32_t
       case FutexThread::WaitResult::OK:       return 0;
       case FutexThread::WaitResult::NotEqual: return 1;
       case FutexThread::WaitResult::TimedOut: return 2;
       case FutexThread::WaitResult::Error:    return -1;
       default: MOZ_CRASH();
     }
 }
 
-/* static */ int32_t
+/* static */ int32_t /* -1 to signal trap; nonnegative result for ok */
 Instance::wait_i32(Instance* instance, uint32_t byteOffset, int32_t value, int64_t timeout_ns)
 {
     return PerformWait<int32_t>(instance, byteOffset, value, timeout_ns);
 }
 
-/* static */ int32_t
+/* static */ int32_t /* -1 to signal trap; nonnegative result for ok */
 Instance::wait_i64(Instance* instance, uint32_t byteOffset, int64_t value, int64_t timeout_ns)
 {
     return PerformWait<int64_t>(instance, byteOffset, value, timeout_ns);
 }
 
-/* static */ int32_t
+/* static */ int32_t /* -1 to signal trap; nonnegative for ok */
 Instance::wake(Instance* instance, uint32_t byteOffset, int32_t count)
 {
     JSContext* cx = TlsContext.get();
 
     // The alignment guard is not in the wasm spec as of 2017-11-02, but is
     // considered likely to appear, as 4-byte alignment is required for WAKE by
     // the spec's validation algorithm.
 
@@ -397,17 +397,17 @@ Instance::wake(Instance* instance, uint3
     if (woken > INT32_MAX) {
         JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_WASM_WAKE_OVERFLOW);
         return -1;
     }
 
     return int32_t(woken);
 }
 
-/* static */ int32_t
+/* static */ int32_t /* -1 to signal trap; 0 for ok */
 Instance::memCopy(Instance* instance, uint32_t dstByteOffset, uint32_t srcByteOffset, uint32_t len)
 {
     WasmMemoryObject* mem = instance->memory();
     uint32_t memLen = mem->volatileMemoryLength();
 
     if (len == 0) {
         // Even though the length is zero, we must check for a valid offset.
         if (dstByteOffset < memLen && srcByteOffset < memLen) {
@@ -430,17 +430,17 @@ Instance::memCopy(Instance* instance, ui
         }
     }
 
     JSContext* cx = TlsContext.get();
     JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_WASM_OUT_OF_BOUNDS);
     return -1;
 }
 
-/* static */ int32_t
+/* static */ int32_t /* -1 to signal trap; 0 for ok */
 Instance::memDrop(Instance* instance, uint32_t segIndex)
 {
     MOZ_RELEASE_ASSERT(size_t(segIndex) < instance->passiveDataSegments_.length(),
                        "ensured by validation");
 
     if (!instance->passiveDataSegments_[segIndex]) {
        JS_ReportErrorNumberASCII(TlsContext.get(), GetErrorMessage, nullptr,
                                  JSMSG_WASM_INVALID_PASSIVE_DATA_SEG);
@@ -450,17 +450,17 @@ Instance::memDrop(Instance* instance, ui
     SharedDataSegment& segRefPtr = instance->passiveDataSegments_[segIndex];
     MOZ_RELEASE_ASSERT(!segRefPtr->active());
 
     // Drop this instance's reference to the DataSegment so it can be released.
     segRefPtr = nullptr;
     return 0;
 }
 
-/* static */ int32_t
+/* static */ int32_t /* -1 to signal trap; 0 for ok */
 Instance::memFill(Instance* instance, uint32_t byteOffset, uint32_t value, uint32_t len)
 {
     WasmMemoryObject* mem = instance->memory();
     uint32_t memLen = mem->volatileMemoryLength();
 
     if (len == 0) {
         // Even though the length is zero, we must check for a valid offset.
         if (byteOffset < memLen) {
@@ -479,17 +479,17 @@ Instance::memFill(Instance* instance, ui
         }
     }
 
     JSContext* cx = TlsContext.get();
     JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_WASM_OUT_OF_BOUNDS);
     return -1;
 }
 
-/* static */ int32_t
+/* static */ int32_t /* -1 to signal trap; 0 for ok */
 Instance::memInit(Instance* instance, uint32_t dstOffset, uint32_t srcOffset,
                   uint32_t len, uint32_t segIndex)
 {
     MOZ_RELEASE_ASSERT(size_t(segIndex) < instance->passiveDataSegments_.length(),
                        "ensured by validation");
 
     if (!instance->passiveDataSegments_[segIndex]) {
         JS_ReportErrorNumberASCII(TlsContext.get(), GetErrorMessage, nullptr,
@@ -533,59 +533,65 @@ Instance::memInit(Instance* instance, ui
             return 0;
         }
     }
 
     JS_ReportErrorNumberASCII(TlsContext.get(), GetErrorMessage, nullptr, JSMSG_WASM_OUT_OF_BOUNDS);
     return -1;
 }
 
-/* static */ int32_t
-Instance::tableCopy(Instance* instance, uint32_t dstOffset, uint32_t srcOffset, uint32_t len)
+/* static */ int32_t  /* -1 to signal trap; 0 for ok */
+Instance::tableCopy(Instance* instance, uint32_t dstOffset, uint32_t srcOffset, uint32_t len,
+                    uint32_t dstTableIndex, uint32_t srcTableIndex)
 {
-    const SharedTable& table = instance->tables()[0];
-    uint32_t tableLen = table->length();
+    const SharedTable& srcTable = instance->tables()[srcTableIndex];
+    uint32_t srcTableLen = srcTable->length();
+
+    const SharedTable& dstTable = instance->tables()[dstTableIndex];
+    uint32_t dstTableLen = dstTable->length();
 
     if (len == 0) {
         // Even though the number of items to copy is zero, we must check
         // for valid offsets.
-        if (dstOffset < tableLen && srcOffset < tableLen) {
+        if (dstOffset < dstTableLen && srcOffset < srcTableLen) {
             return 0;
         }
     } else {
         // Here, we know that |len - 1| cannot underflow.
         CheckedU32 lenMinus1 = CheckedU32(len - 1);
         CheckedU32 highestDstOffset = CheckedU32(dstOffset) + lenMinus1;
         CheckedU32 highestSrcOffset = CheckedU32(srcOffset) + lenMinus1;
         if (highestDstOffset.isValid() &&
             highestSrcOffset.isValid() &&
-            highestDstOffset.value() < tableLen &&
-            highestSrcOffset.value() < tableLen)
+            highestDstOffset.value() < dstTableLen &&
+            highestSrcOffset.value() < srcTableLen)
         {
             // Actually do the copy, taking care to handle overlapping cases
             // correctly.
-            if (dstOffset > srcOffset) {
+            if (&srcTable == &dstTable && dstOffset > srcOffset) {
                 for (uint32_t i = len; i > 0; i--) {
-                    table->copy(dstOffset + (i - 1), srcOffset + (i - 1));
+                    dstTable->copy(*srcTable, dstOffset + (i - 1), srcOffset + (i - 1));
                 }
-            } else if (dstOffset < srcOffset) {
+            } else if (&srcTable == &dstTable && dstOffset == srcOffset) {
+                // No-op
+            } else {
                 for (uint32_t i = 0; i < len; i++) {
-                    table->copy(dstOffset + i, srcOffset + i);
+                    dstTable->copy(*srcTable, dstOffset + i, srcOffset + i);
                 }
             }
 
             return 0;
         }
     }
 
     JS_ReportErrorNumberASCII(TlsContext.get(), GetErrorMessage, nullptr, JSMSG_WASM_OUT_OF_BOUNDS);
     return -1;
 }
 
-/* static */ int32_t
+/* static */ int32_t /* -1 to signal trap; 0 for ok */
 Instance::tableDrop(Instance* instance, uint32_t segIndex)
 {
     MOZ_RELEASE_ASSERT(size_t(segIndex) < instance->passiveElemSegments_.length(),
                        "ensured by validation");
 
     if (!instance->passiveElemSegments_[segIndex]) {
         JS_ReportErrorNumberASCII(TlsContext.get(), GetErrorMessage, nullptr,
                                   JSMSG_WASM_INVALID_PASSIVE_ELEM_SEG);
@@ -596,19 +602,20 @@ Instance::tableDrop(Instance* instance, 
     MOZ_RELEASE_ASSERT(!segRefPtr->active());
 
     // Drop this instance's reference to the ElemSegment so it can be released.
     segRefPtr = nullptr;
     return 0;
 }
 
 void
-Instance::initElems(const ElemSegment& seg, uint32_t dstOffset, uint32_t srcOffset, uint32_t len)
+Instance::initElems(uint32_t tableIndex, const ElemSegment& seg, uint32_t dstOffset,
+                    uint32_t srcOffset, uint32_t len)
 {
-    Table& table = *tables_[seg.tableIndex];
+    Table& table = *tables_[tableIndex];
     MOZ_ASSERT(dstOffset <= table.length());
     MOZ_ASSERT(len <= table.length() - dstOffset);
 
     Tier tier = code().bestTier();
     const MetadataTier& metadataTier = metadata(tier);
     const FuncImportVector& funcImports = metadataTier.funcImports;
     const CodeRangeVector& codeRanges = metadataTier.codeRanges;
     const Uint32Vector& funcToCodeRange = metadataTier.funcToCodeRange;
@@ -630,41 +637,45 @@ Instance::initElems(const ElemSegment& s
                 // Instance so that future Table.get()s produce the same
                 // function object as was imported.
                 WasmInstanceObject* calleeInstanceObj = ExportedFunctionToInstanceObject(fun);
                 Instance& calleeInstance = calleeInstanceObj->instance();
                 Tier calleeTier = calleeInstance.code().bestTier();
                 const CodeRange& calleeCodeRange =
                     calleeInstanceObj->getExportedFunctionCodeRange(fun, calleeTier);
                 void* code = calleeInstance.codeBase(calleeTier) + calleeCodeRange.funcTableEntry();
-                table.set(dstOffset + i, code, &calleeInstance);
+                table.setAnyFunc(dstOffset + i, code, &calleeInstance);
                 continue;
             }
         }
         void* code = codeBaseTier + codeRanges[funcToCodeRange[funcIndex]].funcTableEntry();
-        table.set(dstOffset + i, code, this);
+        table.setAnyFunc(dstOffset + i, code, this);
     }
 }
 
-/* static */ int32_t
+/* static */ int32_t /* -1 to signal trap; 0 for ok */
 Instance::tableInit(Instance* instance, uint32_t dstOffset, uint32_t srcOffset,
-                    uint32_t len, uint32_t segIndex)
+                    uint32_t len, uint32_t segIndex, uint32_t tableIndex)
 {
     MOZ_RELEASE_ASSERT(size_t(segIndex) < instance->passiveElemSegments_.length(),
                        "ensured by validation");
 
     if (!instance->passiveElemSegments_[segIndex]) {
         JS_ReportErrorNumberASCII(TlsContext.get(), GetErrorMessage, nullptr,
                                   JSMSG_WASM_INVALID_PASSIVE_ELEM_SEG);
         return -1;
     }
 
     const ElemSegment& seg = *instance->passiveElemSegments_[segIndex];
     MOZ_RELEASE_ASSERT(!seg.active());
-    const Table& table = *instance->tables()[0];
+    const Table& table = *instance->tables()[tableIndex];
+
+    // Element segments cannot currently contain arbitrary values, and anyref
+    // tables cannot be initialized from segments.
+    MOZ_ASSERT(table.kind() == TableKind::AnyFunction);
 
     // We are proposing to copy
     //
     //   seg[ srcOffset .. srcOffset + len - 1 ]
     // to
     //   tableBase[ dstOffset .. dstOffset + len - 1 ]
 
     if (len == 0) {
@@ -677,47 +688,95 @@ Instance::tableInit(Instance* instance, 
         CheckedU32 lenMinus1 = CheckedU32(len - 1);
         CheckedU32 highestDstOffset = CheckedU32(dstOffset) + lenMinus1;
         CheckedU32 highestSrcOffset = CheckedU32(srcOffset) + lenMinus1;
         if (highestDstOffset.isValid() &&
             highestSrcOffset.isValid() &&
             highestDstOffset.value() < table.length() &&
             highestSrcOffset.value() < seg.length())
         {
-            instance->initElems(seg, dstOffset, srcOffset, len);
+            instance->initElems(tableIndex, seg, dstOffset, srcOffset, len);
             return 0;
         }
     }
 
     JS_ReportErrorNumberASCII(TlsContext.get(), GetErrorMessage, nullptr, JSMSG_WASM_OUT_OF_BOUNDS);
     return -1;
 }
 
-/* static */ void
+/* static */ void* /* (void*)-1 to signal trap; other pointer value for ok */
+Instance::tableGet(Instance* instance, uint32_t index, uint32_t tableIndex)
+{
+    const Table& table = *instance->tables()[tableIndex];
+    MOZ_RELEASE_ASSERT(table.kind() == TableKind::AnyRef);
+    if (index >= table.length()) {
+        JS_ReportErrorNumberASCII(TlsContext.get(), GetErrorMessage, nullptr, JSMSG_WASM_TABLE_OUT_OF_BOUNDS);
+        return (void*)-1;
+    }
+    return table.getAnyRef(index);
+}
+
+/* static */ uint32_t /* infallible */
+Instance::tableGrow(Instance* instance, uint32_t delta, void* initValue, uint32_t tableIndex)
+{
+    RootedObject obj(TlsContext.get(), (JSObject*)initValue);
+    Table& table = *instance->tables()[tableIndex];
+    MOZ_RELEASE_ASSERT(table.kind() == TableKind::AnyRef);
+
+    uint32_t oldSize = table.grow(delta, TlsContext.get());
+    if (oldSize != uint32_t(-1) && initValue != nullptr) {
+        for (uint32_t i = 0; i < delta; i++) {
+            table.setAnyRef(oldSize + i, obj.get());
+        }
+    }
+    return oldSize;
+}
+
+/* static */ int32_t /* -1 to signal trap; 0 for ok */
+Instance::tableSet(Instance* instance, uint32_t index, void* value, uint32_t tableIndex)
+{
+    Table& table = *instance->tables()[tableIndex];
+    MOZ_RELEASE_ASSERT(table.kind() == TableKind::AnyRef);
+    if (index >= table.length()) {
+        JS_ReportErrorNumberASCII(TlsContext.get(), GetErrorMessage, nullptr, JSMSG_WASM_TABLE_OUT_OF_BOUNDS);
+        return -1;
+    }
+    table.setAnyRef(index, (JSObject*)value);
+    return 0;
+}
+
+/* static */ uint32_t /* infallible */
+Instance::tableSize(Instance* instance, uint32_t tableIndex)
+{
+    Table& table = *instance->tables()[tableIndex];
+    return table.length();
+}
+
+/* static */ void /* infallible */
 Instance::postBarrier(Instance* instance, gc::Cell** location)
 {
     MOZ_ASSERT(location);
     TlsContext.get()->runtime()->gc.storeBuffer().putCell(location);
 }
 
 // The typeIndex is an index into the structTypeDescrs_ table in the instance.
 // That table holds TypeDescr objects.
 //
 // When we fail to allocate we return a nullptr; the wasm side must check this
 // and propagate it as an error.
 
-/* static */ void*
+/* static */ void* /* null on OOM, otherwise a pointer */
 Instance::structNew(Instance* instance, uint32_t typeIndex)
 {
     JSContext* cx = TlsContext.get();
     Rooted<TypeDescr*> typeDescr(cx, instance->structTypeDescrs_[typeIndex]);
     return TypedObject::createZeroed(cx, typeDescr);
 }
 
-/* static */ void*
+/* static */ void* /* infallible */
 Instance::structNarrow(Instance* instance, uint32_t mustUnboxAnyref, uint32_t outputTypeIndex,
                        void* maybeNullPtr)
 {
     JSContext* cx = TlsContext.get();
 
     Rooted<TypedObject*> obj(cx);
     Rooted<StructTypeDescr*> typeDescr(cx);
 
@@ -842,17 +901,17 @@ Instance::Instance(JSContext* cx,
             import.baselineScript = nullptr;
         }
     }
 
     for (size_t i = 0; i < tables_.length(); i++) {
         const TableDesc& td = metadata().tables[i];
         TableTls& table = tableTls(td);
         table.length = tables_[i]->length();
-        table.base = tables_[i]->base();
+        table.functionBase = tables_[i]->functionBase();
     }
 
     for (size_t i = 0; i < metadata().globals.length(); i++) {
         const GlobalDesc& global = metadata().globals[i];
 
         // Constants are baked into the code, never stored in the global area.
         if (global.isConstant()) {
             continue;
@@ -1241,23 +1300,40 @@ Instance::onMovingGrowMemory(uint8_t* pr
     MOZ_ASSERT(!memory_->isShared());
 
     ArrayBufferObject& buffer = memory_->buffer().as<ArrayBufferObject>();
     tlsData()->memoryBase = buffer.dataPointer();
     tlsData()->boundsCheckLimit = buffer.wasmBoundsCheckLimit();
 }
 
 void
-Instance::onMovingGrowTable()
+Instance::onMovingGrowTable(const Table* theTable)
 {
     MOZ_ASSERT(!isAsmJS());
-    MOZ_ASSERT(tables_.length() == 1);
-    TableTls& table = tableTls(metadata().tables[0]);
-    table.length = tables_[0]->length();
-    table.base = tables_[0]->base();
+
+    // `theTable` has grown and we must update cached data for it.  Importantly,
+    // we can have cached those data in more than one location: we'll have
+    // cached them once for each time the table was imported into this instance.
+    //
+    // When an instance is registered as an observer of a table it is only
+    // registered once, regardless of how many times the table was imported.
+    // Thus when a table is grown, onMovingGrowTable() is only invoked once for
+    // the table.
+    //
+    // Ergo we must go through the entire list of tables in the instance here
+    // and check for the table in all the cached-data slots; we can't exit after
+    // the first hit.
+
+    for (uint32_t i = 0; i < tables_.length(); i++) {
+        if (tables_[i] == theTable) {
+            TableTls& table = tableTls(metadata().tables[i]);
+            table.length = tables_[i]->length();
+            table.functionBase = tables_[i]->functionBase();
+        }
+    }
 }
 
 void
 Instance::deoptimizeImportExit(uint32_t funcImportIndex)
 {
     Tier t = code().bestTier();
     const FuncImport& fi = metadata(t).funcImports[funcImportIndex];
     FuncImportTls& import = funcImportTls(fi);
--- a/js/src/wasm/WasmInstance.h
+++ b/js/src/wasm/WasmInstance.h
@@ -145,22 +145,23 @@ class Instance
     // directly into the JIT code. If the JIT code is released, the Instance must
     // be notified so it can go back to the generic callImport.
 
     void deoptimizeImportExit(uint32_t funcImportIndex);
 
     // Called by Wasm(Memory|Table)Object when a moving resize occurs:
 
     void onMovingGrowMemory(uint8_t* prevMemoryBase);
-    void onMovingGrowTable();
+    void onMovingGrowTable(const Table* theTable);
 
     // Called to apply a single ElemSegment at a given offset, assuming
     // that all bounds validation has already been performed.
 
-    void initElems(const ElemSegment& seg, uint32_t dstOffset, uint32_t srcOffset, uint32_t len);
+    void initElems(uint32_t tableIndex, const ElemSegment& seg, uint32_t dstOffset,
+                   uint32_t srcOffset, uint32_t len);
 
     // Debugger support:
 
     JSString* createDisplayURL(JSContext* cx);
 
     // about:memory reporting:
 
     void addSizeOfMisc(MallocSizeOf mallocSizeOf,
@@ -183,20 +184,25 @@ class Instance
     static int32_t wait_i32(Instance* instance, uint32_t byteOffset, int32_t value, int64_t timeout);
     static int32_t wait_i64(Instance* instance, uint32_t byteOffset, int64_t value, int64_t timeout);
     static int32_t wake(Instance* instance, uint32_t byteOffset, int32_t count);
     static int32_t memCopy(Instance* instance, uint32_t destByteOffset, uint32_t srcByteOffset, uint32_t len);
     static int32_t memDrop(Instance* instance, uint32_t segIndex);
     static int32_t memFill(Instance* instance, uint32_t byteOffset, uint32_t value, uint32_t len);
     static int32_t memInit(Instance* instance, uint32_t dstOffset,
                            uint32_t srcOffset, uint32_t len, uint32_t segIndex);
-    static int32_t tableCopy(Instance* instance, uint32_t dstOffset, uint32_t srcOffset, uint32_t len);
+    static int32_t tableCopy(Instance* instance, uint32_t dstOffset, uint32_t srcOffset, uint32_t len,
+                             uint32_t dstTableIndex, uint32_t srcTableIndex);
     static int32_t tableDrop(Instance* instance, uint32_t segIndex);
+    static void* tableGet(Instance* instance, uint32_t index, uint32_t tableIndex);
+    static uint32_t tableGrow(Instance* instance, uint32_t delta, void* initValue, uint32_t tableIndex);
+    static int32_t tableSet(Instance* instance, uint32_t index, void* value, uint32_t tableIndex);
+    static uint32_t tableSize(Instance* instance, uint32_t tableIndex);
     static int32_t tableInit(Instance* instance, uint32_t dstOffset,
-                             uint32_t srcOffset, uint32_t len, uint32_t segIndex);
+                             uint32_t srcOffset, uint32_t len, uint32_t segIndex, uint32_t tableIndex);
     static void postBarrier(Instance* instance, gc::Cell** location);
     static void* structNew(Instance* instance, uint32_t typeIndex);
     static void* structNarrow(Instance* instance, uint32_t mustUnboxAnyref, uint32_t outputTypeIndex,
                               void* maybeNullPtr);
 };
 
 typedef UniquePtr<Instance> UniqueInstance;
 
--- a/js/src/wasm/WasmIonCompile.cpp
+++ b/js/src/wasm/WasmIonCompile.cpp
@@ -1062,44 +1062,43 @@ class FunctionCompiler
             return false;
         }
 
         curBlock_->add(ins);
         *def = ins;
         return true;
     }
 
-    bool callIndirect(uint32_t funcTypeIndex, MDefinition* index, const CallCompileState& call,
-                      MDefinition** def)
+    bool callIndirect(uint32_t funcTypeIndex, uint32_t tableIndex, MDefinition* index,
+                      const CallCompileState& call, MDefinition** def)
     {
         if (inDeadCode()) {
             *def = nullptr;
             return true;
         }
 
         const FuncTypeWithId& funcType = env_.types[funcTypeIndex].funcType();
 
         CalleeDesc callee;
         if (env_.isAsmJS()) {
+            MOZ_ASSERT(tableIndex == 0);
             MOZ_ASSERT(funcType.id.kind() == FuncTypeIdDescKind::None);
             const TableDesc& table = env_.tables[env_.asmJSSigToTableIndex[funcTypeIndex]];
             MOZ_ASSERT(IsPowerOfTwo(table.limits.initial));
-            MOZ_ASSERT(!table.external);
 
             MConstant* mask = MConstant::New(alloc(), Int32Value(table.limits.initial - 1));
             curBlock_->add(mask);
             MBitAnd* maskedIndex = MBitAnd::New(alloc(), index, mask, MIRType::Int32);
             curBlock_->add(maskedIndex);
 
             index = maskedIndex;
             callee = CalleeDesc::asmJSTable(table);
         } else {
             MOZ_ASSERT(funcType.id.kind() != FuncTypeIdDescKind::None);
-            MOZ_ASSERT(env_.tables.length() == 1);
-            const TableDesc& table = env_.tables[0];
+            const TableDesc& table = env_.tables[tableIndex];
             callee = CalleeDesc::wasmTable(table, funcType.id);
         }
 
         CallSiteDesc desc(call.lineOrBytecode_, CallSiteDesc::Dynamic);
         auto* ins = MWasmCall::New(alloc(), desc, callee, call.regArgs_, ToMIRType(funcType.ret()),
                                    call.spIncrement_, index);
         if (!ins) {
             return false;
@@ -2099,41 +2098,43 @@ EmitCall(FunctionCompiler& f, bool asmJS
 }
 
 static bool
 EmitCallIndirect(FunctionCompiler& f, bool oldStyle)
 {
     uint32_t lineOrBytecode = f.readCallSiteLineOrBytecode();
 
     uint32_t funcTypeIndex;
+    uint32_t tableIndex;
     MDefinition* callee;
     DefVector args;
     if (oldStyle) {
+        tableIndex = 0;
         if (!f.iter().readOldCallIndirect(&funcTypeIndex, &callee, &args)) {
             return false;
         }
     } else {
-        if (!f.iter().readCallIndirect(&funcTypeIndex, &callee, &args)) {
+        if (!f.iter().readCallIndirect(&funcTypeIndex, &tableIndex, &callee, &args)) {
             return false;
         }
     }
 
     if (f.inDeadCode()) {
         return true;
     }
 
     const FuncType& funcType = f.env().types[funcTypeIndex].funcType();
 
     CallCompileState call(f, lineOrBytecode);
     if (!EmitCallArgs(f, funcType, args, &call)) {
         return false;
     }
 
     MDefinition* def;
-    if (!f.callIndirect(funcTypeIndex, callee, call, &def)) {
+    if (!f.callIndirect(funcTypeIndex, tableIndex, callee, call, &def)) {
         return false;
     }
 
     if (IsVoid(funcType.ret())) {
         return true;
     }
 
     f.iter().setResult(def);
@@ -2981,17 +2982,19 @@ EmitAtomicXchg(FunctionCompiler& f, ValT
 
 #endif // ENABLE_WASM_THREAD_OPS
 
 #ifdef ENABLE_WASM_BULKMEM_OPS
 static bool
 EmitMemOrTableCopy(FunctionCompiler& f, bool isMem)
 {
     MDefinition* dst, *src, *len;
-    if (!f.iter().readMemOrTableCopy(isMem, &dst, &src, &len)) {
+    uint32_t dstTableIndex;
+    uint32_t srcTableIndex;
+    if (!f.iter().readMemOrTableCopy(isMem, &dstTableIndex, &dst, &srcTableIndex, &src, &len)) {
         return false;
     }
 
     if (f.inDeadCode()) {
         return false;
     }
 
     uint32_t lineOrBytecode = f.readCallSiteLineOrBytecode();
@@ -3009,17 +3012,32 @@ EmitMemOrTableCopy(FunctionCompiler& f, 
         return false;
     }
     if (!f.passArg(src, ValType::I32, &args)) {
         return false;
     }
     if (!f.passArg(len, ValType::I32, &args)) {
         return false;
     }
-
+    if (!isMem) {
+        MDefinition* dti = f.constant(Int32Value(dstTableIndex), MIRType::Int32);
+        if (!dti) {
+            return false;
+        }
+        if (!f.passArg(dti, ValType::I32, &args)) {
+            return false;
+        }
+        MDefinition* sti = f.constant(Int32Value(srcTableIndex), MIRType::Int32);
+        if (!sti) {
+            return false;
+        }
+        if (!f.passArg(sti, ValType::I32, &args)) {
+            return false;
+        }
+    }
     if (!f.finishCall(&args)) {
         return false;
     }
 
     SymbolicAddress callee = isMem ? SymbolicAddress::MemCopy
                                    : SymbolicAddress::TableCopy;
     MDefinition* ret;
     if (!f.builtinInstanceMethodCall(callee, args, ValType::I32, &ret)) {
@@ -3126,19 +3144,19 @@ EmitMemFill(FunctionCompiler& f)
     }
 
     return true;
 }
 
 static bool
 EmitMemOrTableInit(FunctionCompiler& f, bool isMem)
 {
-    uint32_t segIndexVal = 0;
+    uint32_t segIndexVal = 0, dstTableIndex = 0;
     MDefinition* dstOff, *srcOff, *len;
-    if (!f.iter().readMemOrTableInit(isMem, &segIndexVal, &dstOff, &srcOff, &len)) {
+    if (!f.iter().readMemOrTableInit(isMem, &segIndexVal, &dstTableIndex, &dstOff, &srcOff, &len)) {
         return false;
     }
 
     if (f.inDeadCode()) {
         return false;
     }
 
     uint32_t lineOrBytecode = f.readCallSiteLineOrBytecode();
@@ -3161,17 +3179,25 @@ EmitMemOrTableInit(FunctionCompiler& f, 
     if (!f.passArg(len, ValType::I32, &args)) {
         return false;
     }
 
     MDefinition* segIndex = f.constant(Int32Value(int32_t(segIndexVal)), MIRType::Int32);
     if (!f.passArg(segIndex, ValType::I32, &args)) {
         return false;
     }
-
+    if (!isMem) {
+        MDefinition* dti = f.constant(Int32Value(dstTableIndex), MIRType::Int32);
+        if (!dti) {
+            return false;
+        }
+        if (!f.passArg(dti, ValType::I32, &args)) {
+            return false;
+        }
+    }
     if (!f.finishCall(&args)) {
         return false;
     }
 
     SymbolicAddress callee = isMem ? SymbolicAddress::MemInit
                                    : SymbolicAddress::TableInit;
     MDefinition* ret;
     if (!f.builtinInstanceMethodCall(callee, args, ValType::I32, &ret)) {
@@ -3181,16 +3207,107 @@ EmitMemOrTableInit(FunctionCompiler& f, 
     if (!f.checkI32NegativeMeansFailedResult(ret)) {
         return false;
     }
 
     return true;
 }
 #endif // ENABLE_WASM_BULKMEM_OPS
 
+#ifdef ENABLE_WASM_GENERALIZED_TABLES
+// About these implementations: table.{get,grow,set} on table(anyfunc) is
+// rejected by the verifier, while table.{get,grow,set} on table(anyref)
+// requires gc_feature_opt_in and will always be handled by the baseline
+// compiler; we should never get here in that case.
+//
+// table.size must however be handled properly here.
+
+static bool
+EmitTableGet(FunctionCompiler& f)
+{
+    uint32_t tableIndex;
+    MDefinition* index;
+    if (!f.iter().readTableGet(&tableIndex, &index)) {
+        return false;
+    }
+
+    MOZ_CRASH("Should not happen"); // See above
+}
+
+static bool
+EmitTableGrow(FunctionCompiler& f)
+{
+    uint32_t tableIndex;
+    MDefinition* delta;
+    MDefinition* initValue;
+    if (!f.iter().readTableGrow(&tableIndex, &delta, &initValue)) {
+        return false;
+    }
+
+    MOZ_CRASH("Should not happen"); // See above
+}
+
+static bool
+EmitTableSet(FunctionCompiler& f)
+{
+    uint32_t tableIndex;
+    MDefinition* index;
+    MDefinition* value;
+    if (!f.iter().readTableSet(&tableIndex, &index, &value)) {
+        return false;
+    }
+
+    MOZ_CRASH("Should not happen"); // See above
+}
+
+static bool
+EmitTableSize(FunctionCompiler& f)
+{
+    uint32_t tableIndex;
+    if (!f.iter().readTableSize(&tableIndex)) {
+        return false;
+    }
+
+    if (f.inDeadCode()) {
+        return false;
+    }
+
+    uint32_t lineOrBytecode = f.readCallSiteLineOrBytecode();
+
+    CallCompileState args(f, lineOrBytecode);
+    if (!f.startCall(&args)) {
+        return false;
+    }
+
+    if (!f.passInstance(&args)) {
+        return false;
+    }
+
+    MDefinition* tableIndexArg = f.constant(Int32Value(tableIndex), MIRType::Int32);
+    if (!tableIndexArg) {
+        return false;
+    }
+    if (!f.passArg(tableIndexArg, ValType::I32, &args)) {
+        return false;
+    }
+
+    if (!f.finishCall(&args)) {
+        return false;
+    }
+
+    MDefinition* ret;
+    if (!f.builtinInstanceMethodCall(SymbolicAddress::TableSize, args, ValType::I32, &ret)) {
+        return false;
+    }
+
+    f.iter().setResult(ret);
+    return true;
+}
+#endif  // ENABLE_WASM_GENERALIZED_TABLES
+
 static bool
 EmitBodyExprs(FunctionCompiler& f)
 {
     if (!f.iter().readFunctionStart(f.funcType().ret())) {
         return false;
     }
 
 #define CHECK(c)                                                              \
@@ -3621,16 +3738,26 @@ EmitBodyExprs(FunctionCompiler& f)
                 CHECK(EmitMemOrTableInit(f, /*isMem=*/true));
               case uint16_t(MiscOp::TableCopy):
                 CHECK(EmitMemOrTableCopy(f, /*isMem=*/false));
               case uint16_t(MiscOp::TableDrop):
                 CHECK(EmitMemOrTableDrop(f, /*isMem=*/false));
               case uint16_t(MiscOp::TableInit):
                 CHECK(EmitMemOrTableInit(f, /*isMem=*/false));
 #endif
+#ifdef ENABLE_WASM_GENERALIZED_TABLES
+              case uint16_t(MiscOp::TableGet):
+                CHECK(EmitTableGet(f));
+              case uint16_t(MiscOp::TableGrow):
+                CHECK(EmitTableGrow(f));
+              case uint16_t(MiscOp::TableSet):
+                CHECK(EmitTableSet(f));
+              case uint16_t(MiscOp::TableSize):
+                CHECK(EmitTableSize(f));
+#endif
 #ifdef ENABLE_WASM_GC
               case uint16_t(MiscOp::StructNew):
               case uint16_t(MiscOp::StructGet):
               case uint16_t(MiscOp::StructSet):
               case uint16_t(MiscOp::StructNarrow):
                 // Not yet supported
                 return f.iter().unrecognizedOpcode(&op);
 #endif
--- a/js/src/wasm/WasmJS.cpp
+++ b/js/src/wasm/WasmJS.cpp
@@ -235,30 +235,32 @@ GetProperty(JSContext* cx, HandleObject 
     return GetProperty(cx, obj, obj, id, v);
 }
 
 static bool
 GetImports(JSContext* cx,
            const Module& module,
            HandleObject importObj,
            MutableHandle<FunctionVector> funcImports,
-           MutableHandleWasmTableObject tableImport,
+           WasmTableObjectVector& tableImports,
            MutableHandleWasmMemoryObject memoryImport,
            WasmGlobalObjectVector& globalObjs,
            MutableHandleValVector globalImportValues)
 {
     const ImportVector& imports = module.imports();
     if (!imports.empty() && !importObj) {
         return ThrowBadImportArg(cx);
     }
 
     const Metadata& metadata = module.metadata();
 
     uint32_t globalIndex = 0;
     const GlobalDescVector& globals = metadata.globals;
+    uint32_t tableIndex = 0;
+    const TableDescVector& tables = metadata.tables;
     for (const Import& import : imports) {
         RootedValue v(cx);
         if (!GetProperty(cx, importObj, import.module.get(), &v)) {
             return false;
         }
 
         if (!v.isObject()) {
             JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, JSMSG_WASM_BAD_IMPORT_FIELD,
@@ -279,22 +281,30 @@ GetImports(JSContext* cx,
 
             if (!funcImports.append(&v.toObject().as<JSFunction>())) {
                 return false;
             }
 
             break;
           }
           case DefinitionKind::Table: {
+            const uint32_t index = tableIndex++;
             if (!v.isObject() || !v.toObject().is<WasmTableObject>()) {
                 return ThrowBadImportType(cx, import.field.get(), "Table");
             }
 
-            MOZ_ASSERT(!tableImport);
-            tableImport.set(&v.toObject().as<WasmTableObject>());
+            RootedWasmTableObject obj(cx, &v.toObject().as<WasmTableObject>());
+            if (obj->table().kind() != tables[index].kind) {
+                JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, JSMSG_WASM_BAD_TBL_TYPE_LINK);
+                return false;
+            }
+
+            if (!tableImports.append(obj)) {
+                return false;
+            }
             break;
           }
           case DefinitionKind::Memory: {
             if (!v.isObject() || !v.toObject().is<WasmMemoryObject>()) {
                 return ThrowBadImportType(cx, import.field.get(), "Memory");
             }
 
             MOZ_ASSERT(!memoryImport);
@@ -306,21 +316,21 @@ GetImports(JSContext* cx,
             const GlobalDesc& global = globals[index];
             MOZ_ASSERT(global.importIndex() == index);
 
             RootedVal val(cx);
             if (v.isObject() && v.toObject().is<WasmGlobalObject>()) {
                 RootedWasmGlobalObject obj(cx, &v.toObject().as<WasmGlobalObject>());
 
                 if (obj->isMutable() != global.isMutable()) {
-                    JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, JSMSG_WASM_BAD_MUT_LINK);
+                    JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, JSMSG_WASM_BAD_GLOB_MUT_LINK);
                     return false;
                 }
                 if (obj->type() != global.type()) {
-                    JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, JSMSG_WASM_BAD_TYPE_LINK);
+                    JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, JSMSG_WASM_BAD_GLOB_TYPE_LINK);
                     return false;
                 }
 
                 if (globalObjs.length() <= index && !globalObjs.resize(index + 1)) {
                     ReportOutOfMemory(cx);
                     return false;
                 }
                 globalObjs[index] = obj;
@@ -338,17 +348,17 @@ GetImports(JSContext* cx,
                 }
 
                 if (global.type() == ValType::I64) {
                     JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, JSMSG_WASM_BAD_I64_LINK);
                     return false;
                 }
 
                 if (global.isMutable()) {
-                    JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, JSMSG_WASM_BAD_MUT_LINK);
+                    JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, JSMSG_WASM_BAD_GLOB_MUT_LINK);
                     return false;
                 }
 
                 if (!ToWebAssemblyValue(cx, global.type(), v, &val)) {
                     return false;
                 }
             }
 
@@ -424,26 +434,26 @@ wasm::Eval(JSContext* cx, Handle<TypedAr
                                      error.get());
             return false;
         }
         ReportOutOfMemory(cx);
         return false;
     }
 
     Rooted<FunctionVector> funcs(cx, FunctionVector(cx));
-    RootedWasmTableObject table(cx);
+    Rooted<WasmTableObjectVector> tables(cx);
     RootedWasmMemoryObject memory(cx);
     Rooted<WasmGlobalObjectVector> globalObjs(cx);
 
     RootedValVector globals(cx);
-    if (!GetImports(cx, *module, importObj, &funcs, &table, &memory, globalObjs.get(), &globals)) {
+    if (!GetImports(cx, *module, importObj, &funcs, tables.get(), &memory, globalObjs.get(), &globals)) {
         return false;
     }
 
-    return module->instantiate(cx, funcs, table, memory, globals, globalObjs.get(), nullptr,
+    return module->instantiate(cx, funcs, tables.get(), memory, globals, globalObjs.get(), nullptr,
                                instanceObj);
 }
 
 bool
 wasm::CompileAndSerialize(const ShareableBytes& bytecode, Bytes* serialized)
 {
     MutableCompileArgs compileArgs = js_new<CompileArgs>(ScriptedCaller());
     if (!compileArgs) {
@@ -1330,26 +1340,26 @@ GetImportArg(JSContext* cx, CallArgs cal
 
 static bool
 Instantiate(JSContext* cx, const Module& module, HandleObject importObj,
             MutableHandleWasmInstanceObject instanceObj)
 {
     RootedObject instanceProto(cx, &cx->global()->getPrototype(JSProto_WasmInstance).toObject());
 
     Rooted<FunctionVector> funcs(cx, FunctionVector(cx));
-    RootedWasmTableObject table(cx);
+    Rooted<WasmTableObjectVector> tables(cx);
     RootedWasmMemoryObject memory(cx);
     Rooted<WasmGlobalObjectVector> globalObjs(cx);
 
     RootedValVector globals(cx);
-    if (!GetImports(cx, module, importObj, &funcs, &table, &memory, globalObjs.get(), &globals)) {
+    if (!GetImports(cx, module, importObj, &funcs, tables.get(), &memory, globalObjs.get(), &globals)) {
         return false;
     }
 
-    return module.instantiate(cx, funcs, table, memory, globals, globalObjs.get(), instanceProto,
+    return module.instantiate(cx, funcs, tables.get(), memory, globals, globalObjs.get(), instanceProto,
                               instanceObj);
 }
 
 /* static */ bool
 WasmInstanceObject::construct(JSContext* cx, unsigned argc, Value* vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
 
@@ -2060,33 +2070,29 @@ WasmTableObject::trace(JSTracer* trc, JS
 {
     WasmTableObject& tableObj = obj->as<WasmTableObject>();
     if (!tableObj.isNewborn()) {
         tableObj.table().tracePrivate(trc);
     }
 }
 
 /* static */ WasmTableObject*
-WasmTableObject::create(JSContext* cx, const Limits& limits)
+WasmTableObject::create(JSContext* cx, const Limits& limits, TableKind tableKind)
 {
     RootedObject proto(cx, &cx->global()->getPrototype(JSProto_WasmTable).toObject());
 
     AutoSetNewObjectMetadata metadata(cx);
     RootedWasmTableObject obj(cx, NewObjectWithGivenProto<WasmTableObject>(cx, proto));
     if (!obj) {
         return nullptr;
     }
 
     MOZ_ASSERT(obj->isNewborn());
 
-    TableDesc td(TableKind::AnyFunction, limits);
-    td.external = true;
-#ifdef WASM_PRIVATE_REFTYPES
-    td.importedOrExported = true;
-#endif
+    TableDesc td(tableKind, limits, /*importedOrExported=*/true);
 
     SharedTable table = Table::create(cx, td, obj);
     if (!table) {
         return nullptr;
     }
 
     obj->initReservedSlot(TABLE_SLOT, PrivateValue(table.forget().take()));
 
@@ -2130,29 +2136,44 @@ WasmTableObject::construct(JSContext* cx
         return false;
     }
 
     RootedLinearString elementLinearStr(cx, elementStr->ensureLinear(cx));
     if (!elementLinearStr) {
         return false;
     }
 
-    if (!StringEqualsAscii(elementLinearStr, "anyfunc")) {
+    TableKind tableKind;
+    if (StringEqualsAscii(elementLinearStr, "anyfunc")) {
+        tableKind = TableKind::AnyFunction;
+#ifdef ENABLE_WASM_GENERALIZED_TABLES
+    } else if (StringEqualsAscii(elementLinearStr, "anyref")) {
+        if (!cx->options().wasmGc()) {
+            JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, JSMSG_WASM_BAD_ELEMENT);
+            return false;
+        }
+        tableKind = TableKind::AnyRef;
+#endif
+    } else {
+#ifdef ENABLE_WASM_GENERALIZED_TABLES
+        JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, JSMSG_WASM_BAD_ELEMENT_GENERALIZED);
+#else
         JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, JSMSG_WASM_BAD_ELEMENT);
+#endif
         return false;
     }
 
     Limits limits;
     if (!GetLimits(cx, obj, MaxTableInitialLength, MaxTableMaximumLength, "Table", &limits,
                    Shareable::False))
     {
         return false;
     }
 
-    RootedWasmTableObject table(cx, WasmTableObject::create(cx, limits));
+    RootedWasmTableObject table(cx, WasmTableObject::create(cx, limits, tableKind));
     if (!table) {
         return false;
     }
 
     args.rval().setObject(*table);
     return true;
 }
 
@@ -2207,32 +2228,44 @@ WasmTableObject::getImpl(JSContext* cx, 
         return false;
     }
 
     uint32_t index;
     if (!ToTableIndex(cx, args.get(0), table, "get index", &index)) {
         return false;
     }
 
-    ExternalTableElem& elem = table.externalArray()[index];
-    if (!elem.code) {
-        args.rval().setNull();
-        return true;
-    }
-
-    Instance& instance = *elem.tls->instance;
-    const CodeRange& codeRange = *instance.code().lookupFuncRange(elem.code);
-
-    RootedWasmInstanceObject instanceObj(cx, instance.object());
-    RootedFunction fun(cx);
-    if (!instanceObj->getExportedFunction(cx, instanceObj, codeRange.funcIndex(), &fun)) {
-        return false;
-    }
-
-    args.rval().setObject(*fun);
+    switch (table.kind()) {
+      case TableKind::AnyFunction: {
+        const FunctionTableElem& elem = table.getAnyFunc(index);
+        if (!elem.code) {
+            args.rval().setNull();
+            return true;
+        }
+
+        Instance& instance = *elem.tls->instance;
+        const CodeRange& codeRange = *instance.code().lookupFuncRange(elem.code);
+
+        RootedWasmInstanceObject instanceObj(cx, instance.object());
+        RootedFunction fun(cx);
+        if (!instanceObj->getExportedFunction(cx, instanceObj, codeRange.funcIndex(), &fun)) {
+            return false;
+        }
+
+        args.rval().setObject(*fun);
+        break;
+      }
+      case TableKind::AnyRef: {
+        args.rval().setObjectOrNull(table.getAnyRef(index));
+        break;
+      }
+      default: {
+        MOZ_CRASH("Unexpected table kind");
+      }
+    }
     return true;
 }
 
 /* static */ bool
 WasmTableObject::get(JSContext* cx, unsigned argc, Value* vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
     return CallNonGenericMethod<IsTable, getImpl>(cx, args);
@@ -2248,40 +2281,60 @@ WasmTableObject::setImpl(JSContext* cx, 
         return false;
     }
 
     uint32_t index;
     if (!ToTableIndex(cx, args.get(0), table, "set index", &index)) {
         return false;
     }
 
-    RootedFunction value(cx);
-    if (!IsExportedFunction(args[1], &value) && !args[1].isNull()) {
-        JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, JSMSG_WASM_BAD_TABLE_VALUE);
-        return false;
-    }
-
-    if (value) {
-        RootedWasmInstanceObject instanceObj(cx, ExportedFunctionToInstanceObject(value));
-        uint32_t funcIndex = ExportedFunctionToFuncIndex(value);
+    switch (table.kind()) {
+      case TableKind::AnyFunction: {
+        RootedFunction value(cx);
+        if (!IsExportedFunction(args[1], &value) && !args[1].isNull()) {
+            JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, JSMSG_WASM_BAD_TABLE_VALUE);
+            return false;
+        }
+
+        if (value) {
+            RootedWasmInstanceObject instanceObj(cx, ExportedFunctionToInstanceObject(value));
+            uint32_t funcIndex = ExportedFunctionToFuncIndex(value);
 
 #ifdef DEBUG
-        RootedFunction f(cx);
-        MOZ_ASSERT(instanceObj->getExportedFunction(cx, instanceObj, funcIndex, &f));
-        MOZ_ASSERT(value == f);
+            RootedFunction f(cx);
+            MOZ_ASSERT(instanceObj->getExportedFunction(cx, instanceObj, funcIndex, &f));
+            MOZ_ASSERT(value == f);
 #endif
 
-        Instance& instance = instanceObj->instance();
-        Tier tier = instance.code().bestTier();
-        const MetadataTier& metadata = instance.metadata(tier);
-        const CodeRange& codeRange = metadata.codeRange(metadata.lookupFuncExport(funcIndex));
-        void* code = instance.codeBase(tier) + codeRange.funcTableEntry();
-        table.set(index, code, &instance);
-    } else {
-        table.setNull(index);
+            Instance& instance = instanceObj->instance();
+            Tier tier = instance.code().bestTier();
+            const MetadataTier& metadata = instance.metadata(tier);
+            const CodeRange& codeRange = metadata.codeRange(metadata.lookupFuncExport(funcIndex));
+            void* code = instance.codeBase(tier) + codeRange.funcTableEntry();
+            table.setAnyFunc(index, code, &instance);
+        } else {
+            table.setNull(index);
+        }
+        break;
+      }
+      case TableKind::AnyRef: {
+        if (args[1].isNull()) {
+            table.setNull(index);
+        } else {
+            RootedObject value(cx, ToObject(cx, args[1]));
+            if (!value) {
+                return false;
+            }
+            table.setAnyRef(index, value.get());
+        }
+        break;
+      }
+      default: {
+        MOZ_CRASH("Unexpected table kind");
+      }
     }
 
     args.rval().setUndefined();
     return true;
 }
 
 /* static */ bool
 WasmTableObject::set(JSContext* cx, unsigned argc, Value* vp)
--- a/js/src/wasm/WasmJS.h
+++ b/js/src/wasm/WasmJS.h
@@ -354,15 +354,16 @@ class WasmTableObject : public NativeObj
     static const JSPropertySpec properties[];
     static const JSFunctionSpec methods[];
     static const JSFunctionSpec static_methods[];
     static bool construct(JSContext*, unsigned, Value*);
 
     // Note that, after creation, a WasmTableObject's table() is not initialized
     // and must be initialized before use.
 
-    static WasmTableObject* create(JSContext* cx, const wasm::Limits& limits);
+    static WasmTableObject* create(JSContext* cx, const wasm::Limits& limits,
+                                   wasm::TableKind tableKind);
     wasm::Table& table() const;
 };
 
 } // namespace js
 
 #endif // wasm_js_h
--- a/js/src/wasm/WasmModule.cpp
+++ b/js/src/wasm/WasmModule.cpp
@@ -626,17 +626,17 @@ Module::initSegments(JSContext* cx,
     }
 
     // Now that initialization can't fail partway through, write data/elem
     // segments into memories/tables.
 
     for (const ElemSegment* seg : elemSegments_) {
         if (seg->active()) {
             uint32_t offset = EvaluateInitExpr(globalImportValues, seg->offset());
-            instance.initElems(*seg, offset, 0, seg->length());
+            instance.initElems(seg->tableIndex, *seg, offset, 0, seg->length());
         }
     }
 
     if (memoryObj) {
         uint8_t* memoryBase = memoryObj->buffer().dataPointerEither().unwrap(/* memcpy */);
 
         for (const DataSegment* seg : dataSegments_) {
             if (!seg->active()) {
@@ -798,63 +798,95 @@ Module::instantiateMemory(JSContext* cx,
             return false;
         }
     }
 
     return true;
 }
 
 bool
-Module::instantiateTable(JSContext* cx, MutableHandleWasmTableObject tableObj,
-                         SharedTableVector* tables) const
+Module::instantiateImportedTable(JSContext* cx, const TableDesc& td, Handle<WasmTableObject*> tableObj,
+                                 WasmTableObjectVector* tableObjs, SharedTableVector* tables) const
 {
-    if (tableObj) {
-        MOZ_ASSERT(!metadata().isAsmJS());
+    MOZ_ASSERT(tableObj);
+    MOZ_ASSERT(!metadata().isAsmJS());
+
+    Table& table = tableObj->table();
+    if (!CheckLimits(cx, td.limits.initial, td.limits.maximum, table.length(), table.maximum(),
+                     metadata().isAsmJS(), "Table"))
+    {
+        return false;
+    }
+
+    if (!tables->append(&table)) {
+        ReportOutOfMemory(cx);
+        return false;
+    }
 
-        MOZ_ASSERT(metadata().tables.length() == 1);
-        const TableDesc& td = metadata().tables[0];
-        MOZ_ASSERT(td.external);
+    if (!tableObjs->append(tableObj)) {
+        ReportOutOfMemory(cx);
+        return false;
+    }
+
+    return true;
+}
 
-        Table& table = tableObj->table();
-        if (!CheckLimits(cx, td.limits.initial, td.limits.maximum, table.length(), table.maximum(),
-                         metadata().isAsmJS(), "Table")) {
-            return false;
-        }
-
-        if (!tables->append(&table)) {
-            ReportOutOfMemory(cx);
+bool
+Module::instantiateLocalTable(JSContext* cx, const TableDesc& td, WasmTableObjectVector* tableObjs,
+                              SharedTableVector* tables) const
+{
+    SharedTable table;
+    Rooted<WasmTableObject*> tableObj(cx);
+    if (td.importedOrExported) {
+        tableObj.set(WasmTableObject::create(cx, td.limits, td.kind));
+        if (!tableObj) {
             return false;
         }
+        table = &tableObj->table();
     } else {
-        for (const TableDesc& td : metadata().tables) {
-            SharedTable table;
-            if (td.external) {
-                MOZ_ASSERT(!tableObj);
-                MOZ_ASSERT(td.kind == TableKind::AnyFunction);
+        table = Table::create(cx, td, /* HandleWasmTableObject = */ nullptr);
+        if (!table) {
+            return false;
+        }
+    }
+
+    // Note, appending a null pointer for non-exported local tables.
+    if (!tableObjs->append(tableObj.get())) {
+        ReportOutOfMemory(cx);
+        return false;
+    }
+
+    if (!tables->emplaceBack(table)) {
+        ReportOutOfMemory(cx);
+        return false;
+    }
 
-                tableObj.set(WasmTableObject::create(cx, td.limits));
-                if (!tableObj) {
-                    return false;
-                }
+    return true;
+}
 
-                table = &tableObj->table();
-            } else {
-                table = Table::create(cx, td, /* HandleWasmTableObject = */ nullptr);
-                if (!table) {
-                    return false;
-                }
+bool
+Module::instantiateTables(JSContext* cx,
+                          WasmTableObjectVector& tableImports,
+                          MutableHandle<WasmTableObjectVector> tableObjs,
+                          SharedTableVector* tables) const
+{
+    uint32_t tableIndex = 0;
+    for (const TableDesc& td : metadata().tables) {
+        if (tableIndex < tableImports.length()) {
+            Rooted<WasmTableObject*> tableObj(cx, tableImports[tableIndex]);
+            if (!instantiateImportedTable(cx, td, tableObj, &tableObjs.get(), tables)) {
+                return false;
             }
-
-            if (!tables->emplaceBack(table)) {
-                ReportOutOfMemory(cx);
+        } else {
+            if (!instantiateLocalTable(cx, td, &tableObjs.get(), tables)) {
                 return false;
             }
         }
+        tableIndex++;
     }
-
     return true;
 }
 
 static void
 ExtractGlobalValue(HandleValVector globalImportValues, uint32_t globalIndex,
                    const GlobalDesc& global, MutableHandleVal result)
 {
     switch (global.kind()) {
@@ -1027,17 +1059,17 @@ GetFunctionExport(JSContext* cx,
     val.setObject(*fun);
     return true;
 }
 
 static bool
 CreateExportObject(JSContext* cx,
                    HandleWasmInstanceObject instanceObj,
                    Handle<FunctionVector> funcImports,
-                   HandleWasmTableObject tableObj,
+                   const WasmTableObjectVector& tableObjs,
                    HandleWasmMemoryObject memoryObj,
                    const WasmGlobalObjectVector& globalObjs,
                    const ExportVector& exports)
 {
     const Instance& instance = instanceObj->instance();
     const Metadata& metadata = instance.metadata();
 
     if (metadata.isAsmJS() && exports.length() == 1 && strlen(exports[0].fieldName()) == 0) {
@@ -1069,17 +1101,17 @@ CreateExportObject(JSContext* cx,
         RootedValue val(cx);
         switch (exp.kind()) {
           case DefinitionKind::Function:
             if (!GetFunctionExport(cx, instanceObj, funcImports, exp, &val)) {
                 return false;
             }
             break;
           case DefinitionKind::Table:
-            val = ObjectValue(*tableObj);
+            val = ObjectValue(*tableObjs[exp.tableIndex()]);
             break;
           case DefinitionKind::Memory:
             val = ObjectValue(*memoryObj);
             break;
           case DefinitionKind::Global:
             val.setObject(*globalObjs[exp.globalIndex()]);
             break;
         }
@@ -1241,17 +1273,17 @@ Module::makeStructTypeDescrs(JSContext* 
     }
 
     return true;
 }
 
 bool
 Module::instantiate(JSContext* cx,
                     Handle<FunctionVector> funcImports,
-                    HandleWasmTableObject tableImport,
+                    WasmTableObjectVector& tableImports,
                     HandleWasmMemoryObject memoryImport,
                     HandleValVector globalImportValues,
                     WasmGlobalObjectVector& globalObjs,
                     HandleObject instanceProto,
                     MutableHandleWasmInstanceObject instance) const
 {
     MOZ_RELEASE_ASSERT(metadata().isAsmJS() || cx->wasmHaveSignalHandlers);
 
@@ -1259,19 +1291,22 @@ Module::instantiate(JSContext* cx,
         return false;
     }
 
     RootedWasmMemoryObject memory(cx, memoryImport);
     if (!instantiateMemory(cx, &memory)) {
         return false;
     }
 
-    RootedWasmTableObject table(cx, tableImport);
+    // Note that tableObjs is sparse: it will be null in slots that contain
+    // tables that are neither exported nor imported.
+
+    Rooted<WasmTableObjectVector> tableObjs(cx);
     SharedTableVector tables;
-    if (!instantiateTable(cx, &table, &tables)) {
+    if (!instantiateTables(cx, tableImports, &tableObjs, &tables)) {
         return false;
     }
 
     if (!instantiateGlobals(cx, globalImportValues, globalObjs)) {
         return false;
     }
 
     UniqueTlsData tlsData = CreateTlsData(metadata().globalDataLength);
@@ -1318,17 +1353,17 @@ Module::instantiate(JSContext* cx,
                                             globalImportValues,
                                             globalObjs,
                                             instanceProto,
                                             std::move(maybeDebug)));
     if (!instance) {
         return false;
     }
 
-    if (!CreateExportObject(cx, instance, funcImports, table, memory, globalObjs, exports_)) {
+    if (!CreateExportObject(cx, instance, funcImports, tableObjs.get(), memory, globalObjs, exports_)) {
         return false;
     }
 
     // Register the instance with the Realm so that it can find out about global
     // events like profiling being enabled in the realm. Registration does not
     // require a fully-initialized instance and must precede initSegments as the
     // final pre-requisite for a live instance.
 
--- a/js/src/wasm/WasmModule.h
+++ b/js/src/wasm/WasmModule.h
@@ -83,19 +83,28 @@ class Module : public JS::WasmModule
 
     // This flag is only used for testing purposes and is cleared on success or
     // failure. The field is racily polled from various threads.
 
     mutable Atomic<bool>    testingTier2Active_;
 
     bool instantiateFunctions(JSContext* cx, Handle<FunctionVector> funcImports) const;
     bool instantiateMemory(JSContext* cx, MutableHandleWasmMemoryObject memory) const;
-    bool instantiateTable(JSContext* cx,
-                          MutableHandleWasmTableObject table,
-                          SharedTableVector* tables) const;
+    bool instantiateImportedTable(JSContext* cx,
+                                  const TableDesc& td,
+                                  Handle<WasmTableObject*> table,
+                                  WasmTableObjectVector* tableObjs,
+                                  SharedTableVector* tables) const;
+    bool instantiateLocalTable(JSContext* cx,
+                               const TableDesc& td,
+                               WasmTableObjectVector* tableObjs,
+                               SharedTableVector* tables) const;
+    bool instantiateTables(JSContext* cx, WasmTableObjectVector& tableImports,
+                           MutableHandle<WasmTableObjectVector> tableObjs,
+                           SharedTableVector* tables) const;
     bool instantiateGlobals(JSContext* cx, HandleValVector globalImportValues,
                             WasmGlobalObjectVector& globalObjs) const;
     bool initSegments(JSContext* cx,
                       HandleWasmInstanceObject instance,
                       Handle<FunctionVector> funcImports,
                       HandleWasmMemoryObject memory,
                       HandleValVector globalImportValues) const;
     SharedCode getDebugEnabledCode() const;
@@ -140,17 +149,17 @@ class Module : public JS::WasmModule
     const Bytes& debugBytecode() const { return debugBytecode_->bytes; }
     uint32_t codeLength(Tier t) const { return code_->segment(t).length(); }
     const StructTypeVector& structTypes() const { return code_->structTypes(); }
 
     // Instantiate this module with the given imports:
 
     bool instantiate(JSContext* cx,
                      Handle<FunctionVector> funcImports,
-                     HandleWasmTableObject tableImport,
+                     WasmTableObjectVector& tableImport,
                      HandleWasmMemoryObject memoryImport,
                      HandleValVector globalImportValues,
                      WasmGlobalObjectVector& globalObjs,
                      HandleObject instanceProto,
                      MutableHandleWasmInstanceObject instanceObj) const;
 
     // Tier-2 compilation may be initiated after the Module is constructed at
     // most once. When tier-2 compilation completes, ModuleGenerator calls
--- a/js/src/wasm/WasmOpIter.cpp
+++ b/js/src/wasm/WasmOpIter.cpp
@@ -17,28 +17,41 @@
  */
 
 #include "wasm/WasmOpIter.h"
 
 using namespace js;
 using namespace js::jit;
 using namespace js::wasm;
 
+#ifdef ENABLE_WASM_GENERALIZED_TABLES
+// Actually we depend only on the reftypes proposal; this guard will change once
+// reftypes and GC are pried apart properly.
+#  ifndef ENABLE_WASM_GC
+#    error "Generalized tables require the GC feature"
+#  endif
+#endif
+
 #ifdef DEBUG
 
 # ifdef ENABLE_WASM_GC
 #  define WASM_GC_OP(code) return code
 # else
 #  define WASM_GC_OP(code) break
 # endif
 # ifdef ENABLE_WASM_BULKMEM_OPS
 #  define WASM_BULK_OP(code) return code
 # else
 #  define WASM_BULK_OP(code) break
 # endif
+# ifdef ENABLE_WASM_GENERALIZED_TABLES
+#  define WASM_TABLE_OP(code) return code
+# else
+#  define WASM_TABLE_OP(code) break
+# endif
 # ifdef ENABLE_WASM_THREAD_OPS
 #  define WASM_THREAD_OP(code) return code
 # else
 #  define WASM_THREAD_OP(code) break
 # endif
 
 OpKind
 wasm::Classify(OpBytes op)
@@ -279,16 +292,24 @@ wasm::Classify(OpBytes op)
             case MiscOp::MemDrop:
             case MiscOp::TableDrop:
               WASM_BULK_OP(OpKind::MemOrTableDrop);
             case MiscOp::MemFill:
               WASM_BULK_OP(OpKind::MemFill);
             case MiscOp::MemInit:
             case MiscOp::TableInit:
               WASM_BULK_OP(OpKind::MemOrTableInit);
+            case MiscOp::TableGet:
+              WASM_TABLE_OP(OpKind::TableGet);
+            case MiscOp::TableGrow:
+              WASM_TABLE_OP(OpKind::TableGrow);
+            case MiscOp::TableSet:
+              WASM_TABLE_OP(OpKind::TableSet);
+            case MiscOp::TableSize:
+              WASM_TABLE_OP(OpKind::TableSize);
             case MiscOp::StructNew:
               WASM_GC_OP(OpKind::StructNew);
             case MiscOp::StructGet:
               WASM_GC_OP(OpKind::StructGet);
             case MiscOp::StructSet:
               WASM_GC_OP(OpKind::StructSet);
             case MiscOp::StructNarrow:
               WASM_GC_OP(OpKind::StructNarrow);
@@ -423,11 +444,12 @@ wasm::Classify(OpBytes op)
           break;
       }
     }
     MOZ_MAKE_COMPILER_ASSUME_IS_UNREACHABLE("unimplemented opcode");
 }
 
 # undef WASM_GC_OP
 # undef WASM_BULK_OP
+# undef WASM_TABLE_OP
 # undef WASM_THREAD_OP
 
 #endif
--- a/js/src/wasm/WasmOpIter.h
+++ b/js/src/wasm/WasmOpIter.h
@@ -186,16 +186,20 @@ enum class OpKind {
     ReplaceLane,
     Swizzle,
     Shuffle,
     Splat,
     MemOrTableCopy,
     MemOrTableDrop,
     MemFill,
     MemOrTableInit,
+    TableGet,
+    TableGrow,
+    TableSet,
+    TableSize,
     RefNull,
     StructNew,
     StructGet,
     StructSet,
     StructNarrow,
 };
 
 // Return the OpKind for a given Op. This is used for sanity-checking that
@@ -537,17 +541,18 @@ class MOZ_STACK_CLASS OpIter : private P
     MOZ_MUST_USE bool readSetGlobal(uint32_t* id, Value* value);
     MOZ_MUST_USE bool readTeeGlobal(uint32_t* id, Value* value);
     MOZ_MUST_USE bool readI32Const(int32_t* i32);
     MOZ_MUST_USE bool readI64Const(int64_t* i64);
     MOZ_MUST_USE bool readF32Const(float* f32);
     MOZ_MUST_USE bool readF64Const(double* f64);
     MOZ_MUST_USE bool readRefNull(ValType* type);
     MOZ_MUST_USE bool readCall(uint32_t* calleeIndex, ValueVector* argValues);
-    MOZ_MUST_USE bool readCallIndirect(uint32_t* funcTypeIndex, Value* callee, ValueVector* argValues);
+    MOZ_MUST_USE bool readCallIndirect(uint32_t* funcTypeIndex, uint32_t* tableIndex, Value* callee,
+                                       ValueVector* argValues);
     MOZ_MUST_USE bool readOldCallDirect(uint32_t numFuncImports, uint32_t* funcIndex,
                                         ValueVector* argValues);
     MOZ_MUST_USE bool readOldCallIndirect(uint32_t* funcTypeIndex, Value* callee, ValueVector* argValues);
     MOZ_MUST_USE bool readWake(LinearMemoryAddress<Value>* addr, Value* count);
     MOZ_MUST_USE bool readWait(LinearMemoryAddress<Value>* addr,
                                ValType resultType,
                                uint32_t byteSize,
                                Value* value,
@@ -563,22 +568,26 @@ class MOZ_STACK_CLASS OpIter : private P
                                     ValType resultType,
                                     uint32_t byteSize,
                                     Value* value);
     MOZ_MUST_USE bool readAtomicCmpXchg(LinearMemoryAddress<Value>* addr,
                                         ValType resultType,
                                         uint32_t byteSize,
                                         Value* oldValue,
                                         Value* newValue);
-    MOZ_MUST_USE bool readMemOrTableCopy(bool isMem,
-                                         Value* dst, Value* src, Value* len);
+    MOZ_MUST_USE bool readMemOrTableCopy(bool isMem, uint32_t* dstMemOrTableIndex, Value* dst,
+                                         uint32_t* srcMemOrTableIndex, Value* src, Value* len);
     MOZ_MUST_USE bool readMemOrTableDrop(bool isMem, uint32_t* segIndex);
     MOZ_MUST_USE bool readMemFill(Value* start, Value* val, Value* len);
     MOZ_MUST_USE bool readMemOrTableInit(bool isMem, uint32_t* segIndex,
-                                         Value* dst, Value* src, Value* len);
+                                         uint32_t* dstTableIndex, Value* dst, Value* src, Value* len);
+    MOZ_MUST_USE bool readTableGet(uint32_t* tableIndex, Value* index);
+    MOZ_MUST_USE bool readTableGrow(uint32_t* tableIndex, Value* delta, Value* initValue);
+    MOZ_MUST_USE bool readTableSet(uint32_t* tableIndex, Value* index, Value* value);
+    MOZ_MUST_USE bool readTableSize(uint32_t* tableIndex);
     MOZ_MUST_USE bool readStructNew(uint32_t* typeIndex, ValueVector* argValues);
     MOZ_MUST_USE bool readStructGet(uint32_t* typeIndex, uint32_t* fieldIndex, Value* ptr);
     MOZ_MUST_USE bool readStructSet(uint32_t* typeIndex, uint32_t* fieldIndex, Value* ptr, Value* val);
     MOZ_MUST_USE bool readStructNarrow(ValType* inputType, ValType* outputType, Value* ptr);
     MOZ_MUST_USE bool readReferenceType(ValType* type, const char* const context);
 
     // At a location where readOp is allowed, peek at the next opcode
     // without consuming it or updating any internal state.
@@ -1775,53 +1784,66 @@ OpIter<Policy>::readCall(uint32_t* funcT
         return false;
     }
 
     return push(funcType.ret());
 }
 
 template <typename Policy>
 inline bool
-OpIter<Policy>::readCallIndirect(uint32_t* funcTypeIndex, Value* callee, ValueVector* argValues)
+OpIter<Policy>::readCallIndirect(uint32_t* funcTypeIndex, uint32_t* tableIndex, Value* callee, ValueVector* argValues)
 {
     MOZ_ASSERT(Classify(op_) == OpKind::CallIndirect);
-
-    if (!env_.tables.length()) {
-        return fail("can't call_indirect without a table");
-    }
+    MOZ_ASSERT(funcTypeIndex != tableIndex);
 
     if (!readVarU32(funcTypeIndex)) {
         return fail("unable to read call_indirect signature index");
     }
 
     if (*funcTypeIndex >= env_.numTypes()) {
         return fail("signature index out of range");
     }
 
     uint8_t flags;
     if (!readFixedU8(&flags)) {
         return false;
     }
 
-    if (flags != uint8_t(MemoryTableFlags::Default)) {
+    *tableIndex = 0;
+    if (flags == uint8_t(MemoryTableFlags::HasTableIndex)) {
+        if (!readVarU32(tableIndex))
+            return false;
+    } else if (flags != uint8_t(MemoryTableFlags::Default)) {
         return fail("unexpected flags");
     }
 
+    if (*tableIndex >= env_.tables.length()) {
+        // Special case this for improved user experience.
+        if (!env_.tables.length()) {
+            return fail("can't call_indirect without a table");
+        }
+        return fail("table index out of range for call_indirect");
+    }
+
+    if (env_.tables[*tableIndex].kind != TableKind::AnyFunction) {
+        return fail("indirect calls must go through a table of 'anyfunc'");
+    }
+
     if (!popWithType(ValType::I32, callee)) {
         return false;
     }
 
     if (!env_.types[*funcTypeIndex].isFuncType()) {
         return fail("expected signature type");
     }
 
     const FuncType& funcType = env_.types[*funcTypeIndex].funcType();
 
 #ifdef WASM_PRIVATE_REFTYPES
-    if (env_.tables[0].importedOrExported && funcType.exposesRef()) {
+    if (env_.tables[*tableIndex].importedOrExported && funcType.exposesRef()) {
         return fail("cannot expose reference type");
     }
 #endif
 
     if (!popCallArgs(funcType.args(), argValues)) {
         return false;
     }
 
@@ -2036,38 +2058,52 @@ OpIter<Policy>::readAtomicCmpXchg(Linear
     }
 
     infalliblePush(resultType);
     return true;
 }
 
 template <typename Policy>
 inline bool
-OpIter<Policy>::readMemOrTableCopy(bool isMem, Value* dst, Value* src, Value* len)
+OpIter<Policy>::readMemOrTableCopy(bool isMem, uint32_t* dstMemOrTableIndex, Value* dst,
+                                   uint32_t* srcMemOrTableIndex, Value* src, Value* len)
 {
     MOZ_ASSERT(Classify(op_) == OpKind::MemOrTableCopy);
+    MOZ_ASSERT(dstMemOrTableIndex != srcMemOrTableIndex);
+
+    *dstMemOrTableIndex = 0;
+    *srcMemOrTableIndex = 0;
+
+    uint32_t memOrTableFlags;
+    if (!readVarU32(&memOrTableFlags)) {
+        return fail(isMem ? "unable to read memory flags" : "unable to read table flags");
+    }
+    if (!isMem && (memOrTableFlags & uint32_t(MemoryTableFlags::HasTableIndex))) {
+        if (!readVarU32(dstMemOrTableIndex)) {
+            return false;
+        }
+        if (!readVarU32(srcMemOrTableIndex)) {
+            return false;
+        }
+        memOrTableFlags ^= uint32_t(MemoryTableFlags::HasTableIndex);
+    }
+    if (memOrTableFlags != uint32_t(MemoryTableFlags::Default)) {
+        return fail(isMem ? "unrecognized memory flags" : "unrecognized table flags");
+    }
 
     if (isMem) {
         if (!env_.usesMemory()) {
             return fail("can't touch memory without memory");
         }
     } else {
-        if (env_.tables.length() == 0) {
-            return fail("can't table.copy without a table");
+        if (*dstMemOrTableIndex >= env_.tables.length() || *srcMemOrTableIndex >= env_.tables.length()) {
+            return fail("table index out of range for table.copy");
         }
     }
 
-    uint32_t memOrTableFlags;
-    if (!readVarU32(&memOrTableFlags)) {
-        return fail(isMem ? "unable to read memory flags" : "unable to read table flags");
-    }
-    if (memOrTableFlags != 0) {
-        return fail(isMem ? "memory flags must be zero" : "table flags must be zero");
-    }
-
     if (!popWithType(ValType::I32, len)) {
         return false;
     }
 
     if (!popWithType(ValType::I32, src)) {
         return false;
     }
 
@@ -2100,17 +2136,17 @@ OpIter<Policy>::readMemOrTableDrop(bool 
 
     if (isMem) {
         // We can't range-check *segIndex at this point since we don't yet
         // know how many data segments the module has.  So note the index, but
         // defer the actual check for now.
         dvs_.lock()->notifyDataSegmentIndex(*segIndex, d_.currentOffset());
      } else {
         if (*segIndex >= env_.elemSegments.length()) {
-            return fail("table.drop index out of range");
+            return fail("element segment index out of range for table.drop");
         }
     }
 
     return true;
 }
 
 template <typename Policy>
 inline bool
@@ -2121,18 +2157,18 @@ OpIter<Policy>::readMemFill(Value* start
     if (!env_.usesMemory()) {
         return fail("can't touch memory without memory");
     }
 
     uint32_t memoryFlags;
     if (!readVarU32(&memoryFlags)) {
         return fail("unable to read memory flags");
     }
-    if (memoryFlags != 0) {
-        return fail("memory flags must be zero");
+    if (memoryFlags != uint32_t(MemoryTableFlags::Default)) {
+        return fail("unrecognized memory flags");
     }
 
     if (!popWithType(ValType::I32, len)) {
         return false;
     }
 
     if (!popWithType(ValType::I32, val)) {
         return false;
@@ -2143,68 +2179,236 @@ OpIter<Policy>::readMemFill(Value* start
     }
 
     return true;
 }
 
 template <typename Policy>
 inline bool
 OpIter<Policy>::readMemOrTableInit(bool isMem, uint32_t* segIndex,
-                                   Value* dst, Value* src, Value* len)
+                                   uint32_t* dstTableIndex, Value* dst, Value* src, Value* len)
 {
     MOZ_ASSERT(Classify(op_) == OpKind::MemOrTableInit);
-
-    if (isMem) {
-        if (!env_.usesMemory()) {
-            return fail("can't touch memory without memory");
-        }
-    } else {
-        if (env_.tables.length() == 0) {
-            return fail("can't table.init without a table");
-        }
-    }
+    MOZ_ASSERT(segIndex != dstTableIndex);
 
     if (!popWithType(ValType::I32, len)) {
         return false;
     }
 
     if (!popWithType(ValType::I32, src)) {
         return false;
     }
 
     if (!popWithType(ValType::I32, dst)) {
         return false;
     }
 
+    *dstTableIndex = 0;
+
     uint32_t memOrTableFlags;
     if (!readVarU32(&memOrTableFlags)) {
         return fail(isMem ? "unable to read memory flags" : "unable to read table flags");
     }
-    if (memOrTableFlags != 0) {
-        return fail(isMem ? "memory flags must be zero" : "table flags must be zero");
+    if (!isMem && (memOrTableFlags & uint32_t(MemoryTableFlags::HasTableIndex))) {
+        if (!readVarU32(dstTableIndex)) {
+            return false;
+        }
+        memOrTableFlags ^= uint32_t(MemoryTableFlags::HasTableIndex);
+    }
+    if (memOrTableFlags != uint32_t(MemoryTableFlags::Default)) {
+        return fail(isMem ? "unrecognized memory flags" : "unrecognized table flags");
+    }
+
+    if (isMem) {
+        if (!env_.usesMemory()) {
+            return fail("can't touch memory without memory");
+        }
+    } else {
+        if (*dstTableIndex >= env_.tables.length()) {
+            return fail("table index out of range for table.init");
+        }
     }
 
     if (!readVarU32(segIndex)) {
         return false;
     }
 
     if (isMem) {
         // Same comment as for readMemOrTableDrop.
         dvs_.lock()->notifyDataSegmentIndex(*segIndex, d_.currentOffset());
     } else {
+        // Element segments must carry functions exclusively and anyfunc is not
+        // yet a subtype of anyref.
+        if (env_.tables[*dstTableIndex].kind != TableKind::AnyFunction) {
+            return fail("only tables of 'anyfunc' may have element segments");
+        }
         if (*segIndex >= env_.elemSegments.length()) {
-            return fail("table.init index out of range");
+            return fail("table.init segment index out of range");
         }
     }
 
     return true;
 }
 
 template <typename Policy>
 inline bool
+OpIter<Policy>::readTableGet(uint32_t* tableIndex, Value* index)
+{
+    MOZ_ASSERT(Classify(op_) == OpKind::TableGet);
+
+    if (!popWithType(ValType::I32, index)) {
+        return false;
+    }
+
+    *tableIndex = 0;
+
+    uint8_t tableFlags;
+    if (!readFixedU8(&tableFlags)) {
+        return fail("unable to read table flags");
+    }
+    if (tableFlags & uint8_t(MemoryTableFlags::HasTableIndex)) {
+        if (!readVarU32(tableIndex)) {
+            return false;
+        }
+        tableFlags ^= uint8_t(MemoryTableFlags::HasTableIndex);
+    }
+    if (tableFlags != uint8_t(MemoryTableFlags::Default)) {
+        return fail("unrecognized table flags");
+    }
+
+    if (*tableIndex >= env_.tables.length()) {
+        return fail("table index out of range for table.get");
+    }
+    if (env_.tables[*tableIndex].kind != TableKind::AnyRef) {
+        return fail("table.get only on tables of anyref");
+    }
+    if (env_.gcTypesEnabled() == HasGcTypes::False) {
+        return fail("anyref support not enabled");
+    }
+
+    infalliblePush(ValType::AnyRef);
+    return true;
+}
+
+template <typename Policy>
+inline bool
+OpIter<Policy>::readTableGrow(uint32_t* tableIndex, Value* delta, Value* initValue)
+{
+    MOZ_ASSERT(Classify(op_) == OpKind::TableGrow);
+
+    if (!popWithType(ValType::AnyRef, initValue)) {
+        return false;
+    }
+    if (!popWithType(ValType::I32, delta)) {
+        return false;
+    }
+
+    *tableIndex = 0;
+
+    uint8_t tableFlags;
+    if (!readFixedU8(&tableFlags)) {
+        return fail("unable to read table flags");
+    }
+    if (tableFlags & uint8_t(MemoryTableFlags::HasTableIndex)) {
+        if (!readVarU32(tableIndex)) {
+            return false;
+        }
+        tableFlags ^= uint8_t(MemoryTableFlags::HasTableIndex);
+    }
+    if (tableFlags != uint8_t(MemoryTableFlags::Default)) {
+        return fail("unrecognized table flags");
+    }
+
+    if (*tableIndex >= env_.tables.length()) {
+        return fail("table index out of range for table.grow");
+    }
+    if (env_.tables[*tableIndex].kind != TableKind::AnyRef) {
+        return fail("table.grow only on tables of anyref");
+    }
+    if (env_.gcTypesEnabled() == HasGcTypes::False) {
+        return fail("anyref support not enabled");
+    }
+
+    infalliblePush(ValType::I32);
+    return true;
+}
+
+template <typename Policy>
+inline bool
+OpIter<Policy>::readTableSet(uint32_t* tableIndex, Value* index, Value* value)
+{
+    MOZ_ASSERT(Classify(op_) == OpKind::TableSet);
+
+    if (!popWithType(ValType::AnyRef, value)) {
+        return false;
+    }
+    if (!popWithType(ValType::I32, index)) {
+        return false;
+    }
+
+    *tableIndex = 0;
+
+    uint8_t tableFlags;
+    if (!readFixedU8(&tableFlags)) {
+        return fail("unable to read table flags");
+    }
+    if (tableFlags & uint8_t(MemoryTableFlags::HasTableIndex)) {
+        if (!readVarU32(tableIndex)) {
+            return false;
+        }
+        tableFlags ^= uint8_t(MemoryTableFlags::HasTableIndex);
+    }
+    if (tableFlags != uint8_t(MemoryTableFlags::Default)) {
+        return fail("unrecognized table flags");
+    }
+
+    if (*tableIndex >= env_.tables.length()) {
+        return fail("table index out of range for table.set");
+    }
+    if (env_.tables[*tableIndex].kind != TableKind::AnyRef) {
+        return fail("table.set only on tables of anyref");
+    }
+    if (env_.gcTypesEnabled() == HasGcTypes::False) {
+        return fail("anyref support not enabled");
+    }
+
+    return true;
+}
+
+template <typename Policy>
+inline bool
+OpIter<Policy>::readTableSize(uint32_t* tableIndex)
+{
+    MOZ_ASSERT(Classify(op_) == OpKind::TableSize);
+
+    *tableIndex = 0;
+
+    uint8_t tableFlags;
+    if (!readFixedU8(&tableFlags)) {
+        return fail("unable to read table flags");
+    }
+    if (tableFlags & uint8_t(MemoryTableFlags::HasTableIndex)) {
+        if (!readVarU32(tableIndex)) {
+            return false;
+        }
+        tableFlags ^= uint8_t(MemoryTableFlags::HasTableIndex);
+    }
+    if (tableFlags != uint8_t(MemoryTableFlags::Default)) {
+        return fail("unrecognized table flags");
+    }
+
+    if (*tableIndex >= env_.tables.length()) {
+        return fail("table index out of range for table.size");
+    }
+
+    return push(ValType::I32);
+}
+
+template <typename Policy>
+inline bool
 OpIter<Policy>::readStructTypeIndex(uint32_t* typeIndex)
 {
     if (!readVarU32(typeIndex)) {
         return fail("unable to read type index");
     }
 
     if (*typeIndex >= env_.types.length()) {
         return fail("type index out of range");
--- a/js/src/wasm/WasmTable.cpp
+++ b/js/src/wasm/WasmTable.cpp
@@ -25,67 +25,99 @@
 #include "wasm/WasmInstance.h"
 #include "wasm/WasmJS.h"
 
 using namespace js;
 using namespace js::wasm;
 using mozilla::CheckedInt;
 
 Table::Table(JSContext* cx, const TableDesc& desc, HandleWasmTableObject maybeObject,
-             UniqueByteArray array)
+             UniqueAnyFuncArray functions)
   : maybeObject_(maybeObject),
     observers_(cx->zone()),
-    array_(std::move(array)),
+    functions_(std::move(functions)),
     kind_(desc.kind),
     length_(desc.limits.initial),
-    maximum_(desc.limits.maximum),
-    external_(desc.external)
-{}
+    maximum_(desc.limits.maximum)
+{
+    MOZ_ASSERT(kind_ != TableKind::AnyRef);
+}
+
+Table::Table(JSContext* cx, const TableDesc& desc, HandleWasmTableObject maybeObject,
+             TableAnyRefVector&& objects)
+  : maybeObject_(maybeObject),
+    observers_(cx->zone()),
+    objects_(std::move(objects)),
+    kind_(desc.kind),
+    length_(desc.limits.initial),
+    maximum_(desc.limits.maximum)
+{
+    MOZ_ASSERT(kind_ == TableKind::AnyRef);
+}
 
 /* static */ SharedTable
 Table::create(JSContext* cx, const TableDesc& desc, HandleWasmTableObject maybeObject)
 {
-    // The raw element type of a Table depends on whether it is external: an
-    // external table can contain functions from multiple instances and thus
-    // must store an additional instance pointer in each element.
-    UniqueByteArray array;
-    if (desc.external) {
-        array.reset((uint8_t*)cx->pod_calloc<ExternalTableElem>(desc.limits.initial));
-    } else {
-        array.reset((uint8_t*)cx->pod_calloc<void*>(desc.limits.initial));
+    switch (desc.kind) {
+      case TableKind::AnyFunction:
+      case TableKind::TypedFunction: {
+        UniqueAnyFuncArray functions(cx->pod_calloc<FunctionTableElem>(desc.limits.initial));
+        if (!functions) {
+            return nullptr;
+        }
+        return SharedTable(cx->new_<Table>(cx, desc, maybeObject, std::move(functions)));
+      }
+      case TableKind::AnyRef: {
+        TableAnyRefVector objects;
+        if (!objects.resize(desc.limits.initial)) {
+            return nullptr;
+        }
+        return SharedTable(cx->new_<Table>(cx, desc, maybeObject, std::move(objects)));
+      }
+      default:
+        MOZ_CRASH();
     }
-    if (!array) {
-        return nullptr;
-    }
-
-    return SharedTable(cx->new_<Table>(cx, desc, maybeObject, std::move(array)));
 }
 
 void
 Table::tracePrivate(JSTracer* trc)
 {
     // If this table has a WasmTableObject, then this method is only called by
     // WasmTableObject's trace hook so maybeObject_ must already be marked.
     // TraceEdge is called so that the pointer can be updated during a moving
     // GC. TraceWeakEdge may sound better, but it is less efficient given that
     // we know object_ is already marked.
     if (maybeObject_) {
         MOZ_ASSERT(!gc::IsAboutToBeFinalized(&maybeObject_));
         TraceEdge(trc, &maybeObject_, "wasm table object");
     }
 
-    if (external_) {
-        ExternalTableElem* array = externalArray();
+    switch (kind_) {
+      case TableKind::AnyFunction: {
         for (uint32_t i = 0; i < length_; i++) {
-            if (array[i].tls) {
-                array[i].tls->instance->trace(trc);
+            if (functions_[i].tls) {
+                functions_[i].tls->instance->trace(trc);
             } else {
-                MOZ_ASSERT(!array[i].code);
+                MOZ_ASSERT(!functions_[i].code);
             }
         }
+        break;
+      }
+      case TableKind::AnyRef: {
+        objects_.trace(trc);
+        break;
+      }
+      case TableKind::TypedFunction: {
+#ifdef DEBUG
+        for (uint32_t i = 0; i < length_; i++) {
+            MOZ_ASSERT(!functions_[i].tls);
+        }
+#endif
+        break;
+      }
     }
 }
 
 void
 Table::trace(JSTracer* trc)
 {
     // The trace hook of WasmTableObject will call Table::tracePrivate at
     // which point we can mark the rest of the children. If there is no
@@ -94,84 +126,126 @@ Table::trace(JSTracer* trc)
     // edge (once per dependent Instance).
     if (maybeObject_) {
         TraceEdge(trc, &maybeObject_, "wasm table object");
     } else {
         tracePrivate(trc);
     }
 }
 
-void**
-Table::internalArray() const
+uint8_t*
+Table::functionBase() const
 {
-    MOZ_ASSERT(!external_);
-    return (void**)array_.get();
+    if (kind() == TableKind::AnyRef) {
+        return nullptr;
+    }
+    return (uint8_t*)functions_.get();
 }
 
-ExternalTableElem*
-Table::externalArray() const
+const FunctionTableElem&
+Table::getAnyFunc(uint32_t index) const
 {
-    MOZ_ASSERT(external_);
-    return (ExternalTableElem*)array_.get();
+    MOZ_ASSERT(isFunction());
+    return functions_[index];
+}
+
+JSObject*
+Table::getAnyRef(uint32_t index) const
+{
+    MOZ_ASSERT(!isFunction());
+    return objects_[index];
 }
 
 void
-Table::set(uint32_t index, void* code, const Instance* instance)
+Table::setAnyFunc(uint32_t index, void* code, const Instance* instance)
 {
-    if (external_) {
-        ExternalTableElem& elem = externalArray()[index];
-        if (elem.tls) {
-            JSObject::writeBarrierPre(elem.tls->instance->objectUnbarriered());
-        }
+    MOZ_ASSERT(isFunction());
 
+    FunctionTableElem& elem = functions_[index];
+    if (elem.tls) {
+        JSObject::writeBarrierPre(elem.tls->instance->objectUnbarriered());
+    }
+
+    switch (kind_) {
+      case TableKind::AnyFunction:
         elem.code = code;
         elem.tls = instance->tlsData();
-
         MOZ_ASSERT(elem.tls->instance->objectUnbarriered()->isTenured(),
                    "no writeBarrierPost (Table::set)");
-    } else {
-        internalArray()[index] = code;
+        break;
+      case TableKind::TypedFunction:
+        elem.code = code;
+        elem.tls = nullptr;
+        break;
+      case TableKind::AnyRef:
+        MOZ_CRASH("Bad table type");
     }
 }
 
 void
+Table::setAnyRef(uint32_t index, JSObject* new_obj)
+{
+    MOZ_ASSERT(!isFunction());
+    objects_[index] = new_obj;
+}
+
+void
 Table::setNull(uint32_t index)
 {
-    // Only external tables can set elements to null after initialization.
-    ExternalTableElem& elem = externalArray()[index];
-    if (elem.tls) {
-        JSObject::writeBarrierPre(elem.tls->instance->objectUnbarriered());
+    switch (kind_) {
+      case TableKind::AnyFunction: {
+        FunctionTableElem& elem = functions_[index];
+        if (elem.tls) {
+            JSObject::writeBarrierPre(elem.tls->instance->objectUnbarriered());
+        }
+
+        elem.code = nullptr;
+        elem.tls = nullptr;
+        break;
+      }
+      case TableKind::AnyRef: {
+        objects_[index] = nullptr;
+        break;
+      }
+      case TableKind::TypedFunction: {
+        MOZ_CRASH("Should not happen");
+      }
     }
-
-    elem.code = nullptr;
-    elem.tls = nullptr;
 }
 
 void
-Table::copy(uint32_t dstIndex, uint32_t srcIndex)
+Table::copy(const Table& srcTable, uint32_t dstIndex, uint32_t srcIndex)
 {
-    if (external_) {
-        ExternalTableElem& dst = externalArray()[dstIndex];
+    switch (kind_) {
+      case TableKind::AnyFunction: {
+        FunctionTableElem& dst = functions_[dstIndex];
         if (dst.tls) {
             JSObject::writeBarrierPre(dst.tls->instance->objectUnbarriered());
         }
 
-        ExternalTableElem& src = externalArray()[srcIndex];
+        FunctionTableElem& src = srcTable.functions_[srcIndex];
         dst.code = src.code;
         dst.tls = src.tls;
 
         if (dst.tls) {
             MOZ_ASSERT(dst.code);
             MOZ_ASSERT(dst.tls->instance->objectUnbarriered()->isTenured(),
                        "no writeBarrierPost (Table::copy)");
         } else {
             MOZ_ASSERT(!dst.code);
         }
-    } else {
-        internalArray()[dstIndex] = internalArray()[srcIndex];
+        break;
+      }
+      case TableKind::AnyRef: {
+        objects_[dstIndex] = srcTable.objects_[srcIndex];
+        break;
+      }
+      case TableKind::TypedFunction: {
+        MOZ_CRASH("Bad table type");
+      }
     }
 }
 
 uint32_t
 Table::grow(uint32_t delta, JSContext* cx)
 {
     // This isn't just an optimization: movingGrowable() assumes that
     // onMovingGrowTable does not fire when length == maximum.
@@ -190,52 +264,75 @@ Table::grow(uint32_t delta, JSContext* c
     if (maximum_ && newLength.value() > maximum_.value()) {
         return -1;
     }
 
     MOZ_ASSERT(movingGrowable());
 
     JSRuntime* rt = cx->runtime();  // Use JSRuntime's MallocProvider to avoid throwing.
 
-    // Note that realloc does not release array_'s pointee (which is returned by
-    // externalArray()) on failure which is exactly what we need here.
-    ExternalTableElem* newArray = rt->pod_realloc(externalArray(), length_, newLength.value());
-    if (!newArray) {
-        return -1;
+    switch (kind_) {
+      case TableKind::AnyFunction: {
+        // Note that realloc does not release functions_'s pointee on failure
+        // which is exactly what we need here.
+        FunctionTableElem* newFunctions = rt->pod_realloc<FunctionTableElem>(functions_.get(),
+                                                                             length_,
+                                                                             newLength.value());
+        if (!newFunctions) {
+            return -1;
+        }
+        Unused << functions_.release();
+        functions_.reset(newFunctions);
+
+        // Realloc does not zero the delta for us.
+        PodZero(newFunctions + length_, delta);
+        break;
+      }
+      case TableKind::AnyRef: {
+        if (!objects_.resize(newLength.value())) {
+            return -1;
+        }
+        break;
+      }
+      case TableKind::TypedFunction: {
+        MOZ_CRASH("Bad table type");
+      }
     }
-    Unused << array_.release();
-    array_.reset((uint8_t*)newArray);
 
-    // Realloc does not zero the delta for us.
-    PodZero(newArray + length_, delta);
     length_ = newLength.value();
 
     for (InstanceSet::Range r = observers_.all(); !r.empty(); r.popFront()) {
-        r.front()->instance().onMovingGrowTable();
+        r.front()->instance().onMovingGrowTable(this);
     }
 
     return oldLength;
 }
 
 bool
 Table::movingGrowable() const
 {
     return !maximum_ || length_ < maximum_.value();
 }
 
 bool
 Table::addMovingGrowObserver(JSContext* cx, WasmInstanceObject* instance)
 {
     MOZ_ASSERT(movingGrowable());
 
-    if (!observers_.putNew(instance)) {
+    // A table can be imported multiple times into an instance, but we only
+    // register the instance as an observer once.
+
+    if (!observers_.put(instance)) {
         ReportOutOfMemory(cx);
         return false;
     }
 
     return true;
 }
 
 size_t
 Table::sizeOfExcludingThis(MallocSizeOf mallocSizeOf) const
 {
-    return mallocSizeOf(array_.get());
+    if (isFunction()) {
+        return mallocSizeOf(functions_.get());
+    }
+    return objects_.sizeOfExcludingThis(mallocSizeOf);
 }
--- a/js/src/wasm/WasmTable.h
+++ b/js/src/wasm/WasmTable.h
@@ -23,60 +23,80 @@
 #include "wasm/WasmCode.h"
 
 namespace js {
 namespace wasm {
 
 // A Table is an indexable array of opaque values. Tables are first-class
 // stateful objects exposed to WebAssembly. asm.js also uses Tables to represent
 // its homogeneous function-pointer tables.
+//
+// A table of AnyFunction holds FunctionTableElems, which are (instance*,index)
+// pairs, where the instance must be traced.
+//
+// A table of AnyRef holds JSObject pointers, which must be traced.
+
+typedef GCVector<JS::Heap<JSObject*>, 0, SystemAllocPolicy> TableAnyRefVector;
 
 class Table : public ShareableBase<Table>
 {
     using InstanceSet = JS::WeakCache<GCHashSet<ReadBarrieredWasmInstanceObject,
                                                 MovableCellHasher<ReadBarrieredWasmInstanceObject>,
                                                 SystemAllocPolicy>>;
-    using UniqueByteArray = UniquePtr<uint8_t[], JS::FreePolicy>;
+    using UniqueAnyFuncArray = UniquePtr<FunctionTableElem[], JS::FreePolicy>;
 
     ReadBarrieredWasmTableObject maybeObject_;
     InstanceSet                  observers_;
-    UniqueByteArray              array_;
+    UniqueAnyFuncArray           functions_; // either functions_ has data
+    TableAnyRefVector            objects_;   //   or objects_, but not both
     const TableKind              kind_;
     uint32_t                     length_;
     const Maybe<uint32_t>        maximum_;
-    const bool                   external_;
 
     template <class> friend struct js::MallocProvider;
     Table(JSContext* cx, const TableDesc& td, HandleWasmTableObject maybeObject,
-          UniqueByteArray array);
+          UniqueAnyFuncArray functions);
+    Table(JSContext* cx, const TableDesc& td, HandleWasmTableObject maybeObject,
+          TableAnyRefVector&& objects);
 
     void tracePrivate(JSTracer* trc);
     friend class js::WasmTableObject;
 
   public:
     static RefPtr<Table> create(JSContext* cx, const TableDesc& desc,
                                 HandleWasmTableObject maybeObject);
     void trace(JSTracer* trc);
 
-    bool external() const { return external_; }
+    TableKind kind() const { return kind_; }
     bool isTypedFunction() const { return kind_ == TableKind::TypedFunction; }
+    bool isFunction() const {
+        return kind_ == TableKind::AnyFunction || kind_ == TableKind::TypedFunction;
+    }
     uint32_t length() const { return length_; }
     Maybe<uint32_t> maximum() const { return maximum_; }
-    uint8_t* base() const { return array_.get(); }
 
-    // All table updates must go through set() or setNull().
+    // Only for function values.  Raw pointer to the table.
+    uint8_t* functionBase() const;
 
-    void** internalArray() const;
-    ExternalTableElem* externalArray() const;
-    void set(uint32_t index, void* code, const Instance* instance);
+    // get/setAnyFunc is allowed only on table-of-anyfunc.
+    // get/setAnyRef is allowed only on table-of-anyref.
+    // setNull is allowed on either.
+    const FunctionTableElem& getAnyFunc(uint32_t index) const;
+    void setAnyFunc(uint32_t index, void* code, const Instance* instance);
+
+    JSObject* getAnyRef(uint32_t index) const;
+    void setAnyRef(uint32_t index, JSObject* obj);
+
     void setNull(uint32_t index);
 
-    // Copy entry at |srcIndex| to |dstIndex|.  Used by table.copy.
-    void copy(uint32_t dstIndex, uint32_t srcIndex);
+    // Copy entry from |srcTable| at |srcIndex| to this table at |dstIndex|.
+    // Used by table.copy.
+    void copy(const Table& srcTable, uint32_t dstIndex, uint32_t srcIndex);
 
+    // grow() returns (uint32_t)-1 if it could not grow.
     uint32_t grow(uint32_t delta, JSContext* cx);
     bool movingGrowable() const;
     bool addMovingGrowObserver(JSContext* cx, WasmInstanceObject* instance);
 
     // about:memory reporting:
 
     size_t sizeOfExcludingThis(MallocSizeOf mallocSizeOf) const;
 };
--- a/js/src/wasm/WasmTextToBinary.cpp
+++ b/js/src/wasm/WasmTextToBinary.cpp
@@ -16,16 +16,17 @@
  * limitations under the License.
  */
 
 #include "wasm/WasmTextToBinary.h"
 
 #include "mozilla/CheckedInt.h"
 #include "mozilla/MathAlgorithms.h"
 #include "mozilla/Maybe.h"
+#include "mozilla/Sprintf.h"
 
 #include "jsnum.h"
 
 #include "builtin/String.h"
 #include "ds/LifoAlloc.h"
 #include "js/CharacterEncoding.h"
 #include "js/HashTable.h"
 #include "js/Printf.h"
@@ -144,16 +145,22 @@ class WasmToken
         Struct,
         Store,
         Table,
 #ifdef ENABLE_WASM_BULKMEM_OPS
         TableCopy,
         TableDrop,
         TableInit,
 #endif
+#ifdef ENABLE_WASM_GENERALIZED_TABLES
+        TableGet,
+        TableGrow,
+        TableSet,
+        TableSize,
+#endif
         TeeLocal,
         TernaryOpcode,
         Text,
         Then,
         Type,
         UnaryOpcode,
         Unreachable,
         UnsignedInteger,
@@ -370,16 +377,22 @@ class WasmToken
           case SetGlobal:
           case SetLocal:
           case Store:
 #ifdef ENABLE_WASM_BULKMEM_OPS
           case TableCopy:
           case TableDrop:
           case TableInit:
 #endif
+#ifdef ENABLE_WASM_GENERALIZED_TABLES
+          case TableGet:
+          case TableGrow:
+          case TableSet:
+          case TableSize:
+#endif
           case TeeLocal:
           case TernaryOpcode:
           case UnaryOpcode:
           case Unreachable:
           case Wait:
           case Wake:
             return true;
           case Align:
@@ -988,18 +1001,21 @@ WasmTokenStream::next()
         }
         if (consume(u"anyfunc")) {
             return WasmToken(WasmToken::AnyFunc, begin, cur_);
         }
         if (consume(u"anyref")) {
             return WasmToken(WasmToken::ValueType, ValType::AnyRef, begin, cur_);
         }
 #ifdef ENABLE_WASM_THREAD_OPS
-        if (consume(u"atomic.wake")) {
-            return WasmToken(WasmToken::Wake, ThreadOp::Wake, begin, cur_);
+        if (consume(u"atomic.")) {
+            if (consume(u"wake") || consume(u"notify")) {
+                return WasmToken(WasmToken::Wake, ThreadOp::Wake, begin, cur_);
+            }
+            break;
         }
 #endif
         break;
 
       case 'b':
         if (consume(u"block")) {
             return WasmToken(WasmToken::Block, begin, cur_);
         }
@@ -2100,30 +2116,44 @@ WasmTokenStream::next()
                 return WasmToken(WasmToken::StructNarrow, begin, cur_);
             }
 #endif
             return WasmToken(WasmToken::Struct, begin, cur_);
         }
         break;
 
       case 't':
+        if (consume(u"table.")) {
 #ifdef ENABLE_WASM_BULKMEM_OPS
-        if (consume(u"table.")) {
             if (consume(u"copy")) {
                 return WasmToken(WasmToken::TableCopy, begin, cur_);
             }
             if (consume(u"drop")) {
                 return WasmToken(WasmToken::TableDrop, begin, cur_);
             }
             if (consume(u"init")) {
                 return WasmToken(WasmToken::TableInit, begin, cur_);
             }
+#endif
+#ifdef ENABLE_WASM_GENERALIZED_TABLES
+            if (consume(u"get")) {
+                return WasmToken(WasmToken::TableGet, begin, cur_);
+            }
+            if (consume(u"grow")) {
+                return WasmToken(WasmToken::TableGrow, begin, cur_);
+            }
+            if (consume(u"set")) {
+                return WasmToken(WasmToken::TableSet, begin, cur_);
+            }
+            if (consume(u"size")) {
+                return WasmToken(WasmToken::TableSize, begin, cur_);
+            }
+#endif
             break;
         }
-#endif
         if (consume(u"table")) {
             return WasmToken(WasmToken::Table, begin, cur_);
         }
         if (consume(u"tee_local")) {
             return WasmToken(WasmToken::TeeLocal, begin, cur_);
         }
         if (consume(u"then")) {
             return WasmToken(WasmToken::Then, begin, cur_);
@@ -2153,29 +2183,47 @@ namespace {
 
 struct WasmParseContext
 {
     WasmTokenStream ts;
     LifoAlloc& lifo;
     UniqueChars* error;
     DtoaState* dtoaState;
     uintptr_t stackLimit;
+    uint32_t nextSym;
 
     WasmParseContext(const char16_t* text, uintptr_t stackLimit, LifoAlloc& lifo,
                      UniqueChars* error)
       : ts(text),
         lifo(lifo),
         error(error),
         dtoaState(NewDtoaState()),
-        stackLimit(stackLimit)
+        stackLimit(stackLimit),
+        nextSym(0)
     {}
 
     ~WasmParseContext() {
         DestroyDtoaState(dtoaState);
     }
+
+    AstName gensym(const char* tag) {
+        char buf[128];
+        MOZ_ASSERT(strlen(tag) < sizeof(buf)-20);
+        SprintfLiteral(buf, ".%s.%u", tag, nextSym);
+        nextSym++;
+        size_t k = strlen(buf)+1;
+        char16_t* mem = (char16_t*)lifo.alloc(k * sizeof(char16_t));
+        if (!mem) {
+            return AstName();
+        }
+        for (size_t i = 0; i < k; i++) {
+            mem[i] = buf[i];
+        }
+        return AstName(mem, k-1);
+    }
 };
 
 } // end anonymous namespace
 
 static AstExpr*
 ParseExprInsideParens(WasmParseContext& c);
 
 static AstExpr*
@@ -2446,20 +2494,33 @@ ParseCall(WasmParseContext& c, bool inPa
     }
 
     return new(c.lifo) AstCall(Op::Call, ExprType::Void, func, std::move(args));
 }
 
 static AstCallIndirect*
 ParseCallIndirect(WasmParseContext& c, bool inParens)
 {
+    AstRef firstRef;
+    AstRef secondRef;
     AstRef funcType;
-    if (!c.ts.matchRef(&funcType, c.error)) {
+    AstRef targetTable = AstRef(0);
+
+    // (call_indirect table signature arg ... index)
+    // (call_indirect signature arg ... index)
+
+    if (!c.ts.matchRef(&firstRef, c.error)) {
         return nullptr;
     }
+    if (c.ts.getIfRef(&secondRef)) {
+        targetTable = firstRef;
+        funcType = secondRef;
+    } else {
+        funcType = firstRef;
+    }
 
     AstExprVector args(c.lifo);
     AstExpr* index;
     if (inParens) {
         if (!ParseArgs(c, &args)) {
             return nullptr;
         }
 
@@ -2471,17 +2532,17 @@ ParseCallIndirect(WasmParseContext& c, b
     } else {
         index = new(c.lifo) AstPop();
     }
 
     if (!index) {
         return nullptr;
     }
 
-    return new(c.lifo) AstCallIndirect(funcType, ExprType::Void, std::move(args), index);
+    return new(c.lifo) AstCallIndirect(targetTable, funcType, ExprType::Void, std::move(args), index);
 }
 
 static uint_fast8_t
 CountLeadingZeroes4(uint8_t x)
 {
     MOZ_ASSERT((x & -0x10) == 0);
     return CountLeadingZeroes32(x) - 28;
 }
@@ -3521,32 +3582,52 @@ ParseGrowMemory(WasmParseContext& c, boo
 
     return new(c.lifo) AstGrowMemory(operand);
 }
 
 #ifdef ENABLE_WASM_BULKMEM_OPS
 static AstMemOrTableCopy*
 ParseMemOrTableCopy(WasmParseContext& c, bool inParens, bool isMem)
 {
+    // (table.copy dest-table dest src-table src len)
+    // (table.copy dest src len)
+    // (memory.copy dest src len)
+
+    AstRef targetMemOrTable = AstRef(0);
+    bool requireSource = false;
+    if (!isMem) {
+        if (c.ts.getIfRef(&targetMemOrTable)) {
+            requireSource = true;
+        }
+    }
+
     AstExpr* dest = ParseExpr(c, inParens);
     if (!dest) {
         return nullptr;
     }
 
+    AstRef memOrTableSource = AstRef(0);
+    if (requireSource) {
+        if (!c.ts.getIfRef(&memOrTableSource)) {
+            c.ts.generateError(c.ts.peek(), "source is required if target is specified", c.error);
+            return nullptr;
+        }
+    }
+
     AstExpr* src = ParseExpr(c, inParens);
     if (!src) {
         return nullptr;
     }
 
     AstExpr* len = ParseExpr(c, inParens);
     if (!len) {
         return nullptr;
     }
 
-    return new(c.lifo) AstMemOrTableCopy(isMem, dest, src, len);
+    return new(c.lifo) AstMemOrTableCopy(isMem, targetMemOrTable, dest, memOrTableSource, src, len);
 }
 
 static AstMemOrTableDrop*
 ParseMemOrTableDrop(WasmParseContext& c, bool isMem)
 {
     WasmToken segIndexTok;
     if (!c.ts.getIf(WasmToken::Index, &segIndexTok)) {
         return nullptr;
@@ -3574,19 +3655,42 @@ ParseMemFill(WasmParseContext& c, bool i
     }
 
     return new(c.lifo) AstMemFill(start, val, len);
 }
 
 static AstMemOrTableInit*
 ParseMemOrTableInit(WasmParseContext& c, bool inParens, bool isMem)
 {
+    // (table.init table-index segment-index ...)
+    // (table.init segment-index ...)
+    // (memory.init segment-index ...)
+
+    AstRef targetMemOrTable = AstRef(0);
+    uint32_t segIndex = 0;
+
     WasmToken segIndexTok;
-    if (!c.ts.getIf(WasmToken::Index, &segIndexTok)) {
-        return nullptr;
+    if (isMem) {
+        if (!c.ts.getIf(WasmToken::Index, &segIndexTok)) {
+            return nullptr;
+        }
+        segIndex = segIndexTok.index();
+    } else {
+        // Slightly hairy to parse this for tables because the element index "0"
+        // could just as well be the table index "0".
+        c.ts.getIfRef(&targetMemOrTable);
+        if (c.ts.getIf(WasmToken::Index, &segIndexTok)) {
+            segIndex = segIndexTok.index();
+        } else if (targetMemOrTable.isIndex()) {
+            segIndex = targetMemOrTable.index();
+            targetMemOrTable = AstRef(0);
+        } else {
+            c.ts.generateError(c.ts.peek(), "expected element segment reference", c.error);
+            return nullptr;
+        }
     }
 
     AstExpr* dst = ParseExpr(c, inParens);
     if (!dst) {
         return nullptr;
     }
 
     AstExpr* src = ParseExpr(c, inParens);
@@ -3594,17 +3698,89 @@ ParseMemOrTableInit(WasmParseContext& c,
         return nullptr;
     }
 
     AstExpr* len = ParseExpr(c, inParens);
     if (!len) {
         return nullptr;
     }
 
-    return new(c.lifo) AstMemOrTableInit(isMem, segIndexTok.index(), dst, src, len);
+    return new(c.lifo) AstMemOrTableInit(isMem, segIndex, targetMemOrTable, dst, src, len);
+}
+#endif
+
+#ifdef ENABLE_WASM_GENERALIZED_TABLES
+static AstTableGet*
+ParseTableGet(WasmParseContext& c, bool inParens)
+{
+    // (table.get table index)
+    // (table.get index)
+
+    AstRef targetTable = AstRef(0);
+    c.ts.getIfRef(&targetTable);
+
+    AstExpr* index = ParseExpr(c, inParens);
+    if (!index) {
+        return nullptr;
+    }
+    return new(c.lifo) AstTableGet(targetTable, index);
+}
+
+static AstTableGrow*
+ParseTableGrow(WasmParseContext& c, bool inParens)
+{
+    // (table.grow table delta)
+    // (table.grow delta)
+
+    AstRef targetTable = AstRef(0);
+    c.ts.getIfRef(&targetTable);
+
+    AstExpr* delta = ParseExpr(c, inParens);
+    if (!delta) {
+        return nullptr;
+    }
+
+    AstExpr* initValue = ParseExpr(c, inParens);
+    if (!initValue) {
+        return nullptr;
+    }
+
+    return new(c.lifo) AstTableGrow(targetTable, delta, initValue);
+}
+
+static AstTableSet*
+ParseTableSet(WasmParseContext& c, bool inParens)
+{
+    // (table.set table index value)
+    // (table.set index value)
+
+    AstRef targetTable = AstRef(0);
+    c.ts.getIfRef(&targetTable);
+
+    AstExpr* index = ParseExpr(c, inParens);
+    if (!index) {
+        return nullptr;
+    }
+    AstExpr* value = ParseExpr(c, inParens);
+    if (!value) {
+        return nullptr;
+    }
+    return new(c.lifo) AstTableSet(targetTable, index, value);
+}
+
+static AstTableSize*
+ParseTableSize(WasmParseContext& c, bool inParens)
+{
+    // (table.size table)
+    // (table.size)
+
+    AstRef targetTable = AstRef(0);
+    c.ts.getIfRef(&targetTable);
+
+    return new(c.lifo) AstTableSize(targetTable);
 }
 #endif
 
 #ifdef ENABLE_WASM_GC
 static AstExpr*
 ParseStructNew(WasmParseContext& c, bool inParens)
 {
     AstRef typeDef;
@@ -3813,16 +3989,26 @@ ParseExprBody(WasmParseContext& c, WasmT
         return ParseMemOrTableInit(c, inParens, /*isMem=*/true);
       case WasmToken::TableCopy:
         return ParseMemOrTableCopy(c, inParens, /*isMem=*/false);
       case WasmToken::TableDrop:
         return ParseMemOrTableDrop(c, /*isMem=*/false);
       case WasmToken::TableInit:
         return ParseMemOrTableInit(c, inParens, /*isMem=*/false);
 #endif
+#ifdef ENABLE_WASM_GENERALIZED_TABLES
+      case WasmToken::TableGet:
+        return ParseTableGet(c, inParens);
+      case WasmToken::TableGrow:
+        return ParseTableGrow(c, inParens);
+      case WasmToken::TableSet:
+        return ParseTableSet(c, inParens);
+      case WasmToken::TableSize:
+        return ParseTableSize(c, inParens);
+#endif
 #ifdef ENABLE_WASM_GC
       case WasmToken::StructNew:
         return ParseStructNew(c, inParens);
       case WasmToken::StructGet:
         return ParseStructGet(c, inParens);
       case WasmToken::StructSet:
         return ParseStructSet(c, inParens);
       case WasmToken::StructNarrow:
@@ -4433,27 +4619,42 @@ ParseGlobalType(WasmParseContext& c, Ast
     if (!ParseValType(c, type)) {
         return false;
     }
 
     return true;
 }
 
 static bool
-ParseElemType(WasmParseContext& c)
-{
-    // Only AnyFunc is allowed at the moment.
-    return c.ts.match(WasmToken::AnyFunc, c.error);
-}
-
-static bool
-ParseTableSig(WasmParseContext& c, Limits* table)
+ParseElemType(WasmParseContext& c, TableKind* tableKind)
+{
+    WasmToken token;
+    if (c.ts.getIf(WasmToken::AnyFunc, &token)) {
+        *tableKind = TableKind::AnyFunction;
+        return true;
+    }
+#ifdef ENABLE_WASM_GENERALIZED_TABLES
+    if (c.ts.getIf(WasmToken::ValueType, &token) &&
+        token.valueType() == ValType::AnyRef)
+    {
+        *tableKind = TableKind::AnyRef;
+        return true;
+    }
+    c.ts.generateError(token, "'anyfunc' or 'anyref' required", c.error);
+#else
+    c.ts.generateError(token, "'anyfunc' required", c.error);
+#endif
+    return false;
+}
+
+static bool
+ParseTableSig(WasmParseContext& c, Limits* table, TableKind* tableKind)
 {
     return ParseLimits(c, table, Shareable::False) &&
-           ParseElemType(c);
+           ParseElemType(c, tableKind);
 }
 
 static AstImport*
 ParseImport(WasmParseContext& c, AstModule* module)
 {
     AstName name = c.ts.getIfName();
 
     WasmToken moduleName;
@@ -4484,25 +4685,27 @@ ParseImport(WasmParseContext& c, AstModu
             return new(c.lifo) AstImport(name, moduleName.text(), fieldName.text(),
                                          DefinitionKind::Memory, memory);
         }
         if (c.ts.getIf(WasmToken::Table)) {
             if (name.empty()) {
                 name = c.ts.getIfName();
             }
 
+            TableKind tableKind;
             Limits table;
-            if (!ParseTableSig(c, &table)) {
+            if (!ParseTableSig(c, &table, &tableKind)) {
                 return nullptr;
             }
             if (!c.ts.match(WasmToken::CloseParen, c.error)) {
                 return nullptr;
             }
+
             return new(c.lifo) AstImport(name, moduleName.text(), fieldName.text(),
-                                         DefinitionKind::Table, table);
+                                         table, tableKind);
         }
         if (c.ts.getIf(WasmToken::Global)) {
             if (name.empty()) {
                 name = c.ts.getIfName();
             }
 
             AstValType type;
             bool isMutable;
@@ -4652,23 +4855,24 @@ ParseTable(WasmParseContext& c, WasmToke
             InlineImport names;
             if (!ParseInlineImport(c, &names)) {
                 return false;
             }
             if (!c.ts.match(WasmToken::CloseParen, c.error)) {
                 return false;
             }
 
+            TableKind tableKind;
             Limits table;
-            if (!ParseTableSig(c, &table)) {
+            if (!ParseTableSig(c, &table, &tableKind)) {
                 return false;
             }
 
             auto* import = new(c.lifo) AstImport(name, names.module.text(), names.field.text(),
-                                                 DefinitionKind::Table, table);
+                                                 table, tableKind);
 
             return import && module->append(import);
         }
 
         if (!c.ts.match(WasmToken::Export, c.error)) {
             c.ts.generateError(token, c.error);
             return false;
         }
@@ -4679,35 +4883,45 @@ ParseTable(WasmParseContext& c, WasmToke
         }
         if (!c.ts.match(WasmToken::CloseParen, c.error)) {
             return false;
         }
     }
 
     // Either: min max? anyfunc
     if (c.ts.peek().kind() == WasmToken::Index) {
+        TableKind tableKind;
         Limits table;
-        if (!ParseTableSig(c, &table)) {
+        if (!ParseTableSig(c, &table, &tableKind)) {
             return false;
         }
-        return module->addTable(name, table);
+        return module->addTable(name, table, tableKind);
     }
 
     // Or: anyfunc (elem 1 2 ...)
-    if (!ParseElemType(c)) {
+    TableKind tableKind;
+    if (!ParseElemType(c, &tableKind)) {
         return false;
     }
 
     if (!c.ts.match(WasmToken::OpenParen, c.error)) {
         return false;
     }
     if (!c.ts.match(WasmToken::Elem, c.error)) {
         return false;
     }
 
+    if (name.empty()) {
+        // For inline elements we need a name, so synthesize one if there isn't
+        // one already.
+        name = c.gensym("elem");
+        if (name.empty())
+            return false;
+    }
+
     AstRefVector elems(c.lifo);
 
     AstRef elem;
     while (c.ts.getIfRef(&elem)) {
         if (!elems.append(elem)) {
             return false;
         }
     }
@@ -4716,51 +4930,61 @@ ParseTable(WasmParseContext& c, WasmToke
         return false;
     }
 
     uint32_t numElements = uint32_t(elems.length());
     if (numElements != elems.length()) {
         return false;
     }
 
-    if (!module->addTable(name, Limits(numElements, Some(numElements), Shareable::False))) {
+    if (!module->addTable(name, Limits(numElements, Some(numElements), Shareable::False),
+                          tableKind))
+    {
         return false;
     }
 
     auto* zero = new(c.lifo) AstConst(LitVal(uint32_t(0)));
     if (!zero) {
         return false;
     }
 
-    AstElemSegment* segment = new(c.lifo) AstElemSegment(zero, std::move(elems));
+    AstElemSegment* segment = new(c.lifo) AstElemSegment(AstRef(name), zero, std::move(elems));
     return segment && module->append(segment);
 }
 
 static AstElemSegment*
 ParseElemSegment(WasmParseContext& c)
 {
-    if (!MaybeParseOwnerIndex(c)) {
-        return nullptr;
-    }
+    // (elem table-name init-expr ref ...)
+    // (elem init-expr ref ...)
+    // (elem passive ref ...)
+
+    AstRef targetTable = AstRef(0);
+    bool hasTableName = c.ts.getIfRef(&targetTable);
 
     AstExpr* offsetIfActive;
     if (!ParseInitializerExpressionOrPassive(c, &offsetIfActive)) {
         return nullptr;
     }
 
+    if (hasTableName && !offsetIfActive) {
+        c.ts.generateError(c.ts.peek(), "passive segment must not have a table", c.error);
+        return nullptr;
+    }
+
     AstRefVector elems(c.lifo);
 
     AstRef elem;
     while (c.ts.getIfRef(&elem)) {
         if (!elems.append(elem)) {
             return nullptr;
         }
     }
 
-    return new(c.lifo) AstElemSegment(offsetIfActive, std::move(elems));
+    return new(c.lifo) AstElemSegment(targetTable, offsetIfActive, std::move(elems));
 }
 
 static bool
 ParseGlobal(WasmParseContext& c, AstModule* module)
 {
     AstName name = c.ts.getIfName();
 
     AstValType type;
@@ -5216,16 +5440,20 @@ ResolveCallIndirect(Resolver& r, AstCall
     if (!ResolveExpr(r, *c.index())) {
         return false;
     }
 
     if (!r.resolveSignature(c.funcType())) {
         return false;
     }
 
+    if (!r.resolveTable(c.targetTable())) {
+        return false;
+    }
+
     return true;
 }
 
 static bool
 ResolveFirst(Resolver& r, AstFirst& f)
 {
     return ResolveExprList(r, f.exprs());
 }
@@ -5443,33 +5671,66 @@ ResolveWake(Resolver& r, AstWake& s)
 }
 
 #ifdef ENABLE_WASM_BULKMEM_OPS
 static bool
 ResolveMemOrTableCopy(Resolver& r, AstMemOrTableCopy& s)
 {
     return ResolveExpr(r, s.dest()) &&
            ResolveExpr(r, s.src()) &&
-           ResolveExpr(r, s.len());
+           ResolveExpr(r, s.len()) &&
+           r.resolveTable(s.destTable()) &&
+           r.resolveTable(s.srcTable());
 }
 
 static bool
 ResolveMemFill(Resolver& r, AstMemFill& s)
 {
     return ResolveExpr(r, s.start()) &&
            ResolveExpr(r, s.val()) &&
            ResolveExpr(r, s.len());
 }
 
 static bool
 ResolveMemOrTableInit(Resolver& r, AstMemOrTableInit& s)
 {
     return ResolveExpr(r, s.dst()) &&
            ResolveExpr(r, s.src()) &&
-           ResolveExpr(r, s.len());
+           ResolveExpr(r, s.len()) &&
+           r.resolveTable(s.targetTable());
+}
+#endif
+
+#ifdef ENABLE_WASM_GENERALIZED_TABLES
+static bool
+ResolveTableGet(Resolver& r, AstTableGet& s)
+{
+    return ResolveExpr(r, s.index()) && r.resolveTable(s.targetTable());
+}
+
+static bool
+ResolveTableGrow(Resolver& r, AstTableGrow& s)
+{
+    return ResolveExpr(r, s.delta()) &&
+           ResolveExpr(r, s.initValue()) &&
+           r.resolveTable(s.targetTable());
+}
+
+static bool
+ResolveTableSet(Resolver& r, AstTableSet& s)
+{
+    return ResolveExpr(r, s.index()) &&
+           ResolveExpr(r, s.value()) &&
+           r.resolveTable(s.targetTable());
+}
+
+static bool
+ResolveTableSize(Resolver& r, AstTableSize& s)
+{
+    return r.resolveTable(s.targetTable());
 }
 #endif
 
 #ifdef ENABLE_WASM_GC
 static bool
 ResolveStructNew(Resolver& r, AstStructNew& s)
 {
     if (!ResolveArgs(r, s.fieldValues())) {
@@ -5608,16 +5869,26 @@ ResolveExpr(Resolver& r, AstExpr& expr)
         return ResolveMemOrTableCopy(r, expr.as<AstMemOrTableCopy>());
       case AstExprKind::MemOrTableDrop:
         return true;
       case AstExprKind::MemFill:
         return ResolveMemFill(r, expr.as<AstMemFill>());
       case AstExprKind::MemOrTableInit:
         return ResolveMemOrTableInit(r, expr.as<AstMemOrTableInit>());
 #endif
+#ifdef ENABLE_WASM_GENERALIZED_TABLES
+      case AstExprKind::TableGet:
+        return ResolveTableGet(r, expr.as<AstTableGet>());
+      case AstExprKind::TableGrow:
+        return ResolveTableGrow(r, expr.as<AstTableGrow>());
+      case AstExprKind::TableSet:
+        return ResolveTableSet(r, expr.as<AstTableSet>());
+      case AstExprKind::TableSize:
+        return ResolveTableSize(r, expr.as<AstTableSize>());
+#endif
 #ifdef ENABLE_WASM_GC
       case AstExprKind::StructNew:
         return ResolveStructNew(r, expr.as<AstStructNew>());
       case AstExprKind::StructGet:
         return ResolveStructGet(r, expr.as<AstStructGet>());
       case AstExprKind::StructSet:
         return ResolveStructSet(r, expr.as<AstStructSet>());
       case AstExprKind::StructNarrow:
@@ -5670,16 +5941,24 @@ ResolveStruct(Resolver& r, AstStructType
         if (!ResolveType(r, vt)) {
             return false;
         }
     }
     return true;
 }
 
 static bool
+ResolveElemSegment(Resolver& r, AstElemSegment& seg)
+{
+    if (!r.resolveTable(seg.targetTableRef()))
+        return false;
+    return true;
+}
+
+static bool
 ResolveModule(LifoAlloc& lifo, AstModule* module, UniqueChars* error)
 {
     Resolver r(lifo, error);
 
     size_t numTypes = module->types().length();
     for (size_t i = 0; i < numTypes; i++) {
         AstTypeDef* td = module->types()[i];
         if (td->isFuncType()) {
@@ -5773,26 +6052,26 @@ ResolveModule(LifoAlloc& lifo, AstModule
         if (!ResolveType(r, global->type())) {
             return false;
         }
         if (global->hasInit() && !ResolveExpr(r, global->init())) {
             return false;
         }
     }
 
-    for (const AstResizable& table : module->tables()) {
+    for (const AstTable& table : module->tables()) {
         if (table.imported) {
             continue;
         }
         if (!r.registerTableName(table.name, lastTableIndex++)) {
             return r.fail("duplicate import");
         }
     }
 
-    for (const AstResizable& memory : module->memories()) {
+    for (const AstMemory& memory : module->memories()) {
         if (memory.imported) {
             continue;
         }
         if (!r.registerMemoryName(memory.name, lastMemoryIndex++)) {
             return r.fail("duplicate import");
         }
     }
 
@@ -5816,16 +6095,22 @@ ResolveModule(LifoAlloc& lifo, AstModule
           case DefinitionKind::Memory:
             if (!r.resolveMemory(export_->ref())) {
                 return false;
             }
             break;
         }
     }
 
+    for (AstElemSegment* seg : module->elemSegments()) {
+        if (!ResolveElemSegment(r, *seg)) {
+            return false;
+        }
+    }
+
     for (AstFunc* func : module->funcs()) {
         if (!ResolveFunc(r, *func)) {
             return false;
         }
     }
 
     if (module->hasStartFunc()) {
         if (!r.resolveFunction(module->startFunc().func())) {
@@ -5952,16 +6237,26 @@ EncodeCall(Encoder& e, AstCall& c)
     if (!e.writeVarU32(c.func().index())) {
         return false;
     }
 
     return true;
 }
 
 static bool
+EncodeOneTableIndex(Encoder& e, uint32_t index)
+{
+    if (index) {
+        return e.writeVarU32(uint32_t(MemoryTableFlags::HasTableIndex)) &&
+               e.writeVarU32(index);
+    }
+    return e.writeVarU32(uint32_t(MemoryTableFlags::Default));
+}
+
+static bool
 EncodeCallIndirect(Encoder& e, AstCallIndirect& c)
 {
     if (!EncodeArgs(e, c.args())) {
         return false;
     }
 
     if (!EncodeExpr(e, *c.index())) {
         return false;
@@ -5970,21 +6265,17 @@ EncodeCallIndirect(Encoder& e, AstCallIn
     if (!e.writeOp(Op::CallIndirect)) {
         return false;
     }
 
     if (!e.writeVarU32(c.funcType().index())) {
         return false;
     }
 
-    if (!e.writeVarU32(uint32_t(MemoryTableFlags::Default))) {
-        return false;
-    }
-
-    return true;
+    return EncodeOneTableIndex(e, c.targetTable().index());
 }
 
 static bool
 EncodeConst(Encoder& e, AstConst& c)
 {
     switch (c.val().type().code()) {
       case ValType::I32:
         return e.writeOp(Op::I32Const) &&
@@ -6288,21 +6579,30 @@ EncodeWake(Encoder& e, AstWake& s)
            e.writeOp(ThreadOp::Wake) &&
            EncodeLoadStoreFlags(e, s.address());
 }
 
 #ifdef ENABLE_WASM_BULKMEM_OPS
 static bool
 EncodeMemOrTableCopy(Encoder& e, AstMemOrTableCopy& s)
 {
-    return EncodeExpr(e, s.dest()) &&
-           EncodeExpr(e, s.src()) &&
-           EncodeExpr(e, s.len()) &&
-           e.writeOp(s.isMem() ? MiscOp::MemCopy : MiscOp::TableCopy) &&
-           e.writeVarU32(uint32_t(MemoryTableFlags::Default));
+    bool result = EncodeExpr(e, s.dest()) &&
+                  EncodeExpr(e, s.src()) &&
+                  EncodeExpr(e, s.len()) &&
+                  e.writeOp(s.isMem() ? MiscOp::MemCopy : MiscOp::TableCopy);
+    if (s.destTable().index() == 0 && s.srcTable().index() == 0) {
+        result = result &&
+                 e.writeVarU32(uint32_t(MemoryTableFlags::Default));
+    } else {
+        result = result &&
+                 e.writeVarU32(uint32_t(MemoryTableFlags::HasTableIndex)) &&
+                 e.writeVarU32(s.destTable().index()) &&
+                 e.writeVarU32(s.srcTable().index());
+    }
+    return result;
 }
 
 static bool
 EncodeMemOrTableDrop(Encoder& e, AstMemOrTableDrop& s)
 {
     return e.writeOp(s.isMem() ? MiscOp::MemDrop : MiscOp::TableDrop) &&
            e.writeVarU32(s.segIndex());
 }
@@ -6319,21 +6619,56 @@ EncodeMemFill(Encoder& e, AstMemFill& s)
 
 static bool
 EncodeMemOrTableInit(Encoder& e, AstMemOrTableInit& s)
 {
     return EncodeExpr(e, s.dst()) &&
            EncodeExpr(e, s.src()) &&
            EncodeExpr(e, s.len()) &&
            e.writeOp(s.isMem() ? MiscOp::MemInit : MiscOp::TableInit) &&
-           e.writeVarU32(uint32_t(MemoryTableFlags::Default)) &&
+           EncodeOneTableIndex(e, s.targetTable().index()) &&
            e.writeVarU32(s.segIndex());
 }
 #endif
 
+#ifdef ENABLE_WASM_GENERALIZED_TABLES
+static bool
+EncodeTableGet(Encoder& e, AstTableGet& s)
+{
+    return EncodeExpr(e, s.index()) &&
+           e.writeOp(MiscOp::TableGet) &&
+           EncodeOneTableIndex(e, s.targetTable().index());
+}
+
+static bool
+EncodeTableGrow(Encoder& e, AstTableGrow& s)
+{
+    return EncodeExpr(e, s.delta()) &&
+           EncodeExpr(e, s.initValue()) &&
+           e.writeOp(MiscOp::TableGrow) &&
+           EncodeOneTableIndex(e, s.targetTable().index());
+}
+
+static bool
+EncodeTableSet(Encoder& e, AstTableSet& s)
+{
+    return EncodeExpr(e, s.index()) &&
+           EncodeExpr(e, s.value()) &&
+           e.writeOp(MiscOp::TableSet) &&
+           EncodeOneTableIndex(e, s.targetTable().index());
+}
+
+static bool
+EncodeTableSize(Encoder& e, AstTableSize& s)
+{
+    return e.writeOp(MiscOp::TableSize) &&
+           EncodeOneTableIndex(e, s.targetTable().index());
+}
+#endif
+
 #ifdef ENABLE_WASM_GC
 static bool
 EncodeStructNew(Encoder& e, AstStructNew& s)
 {
     if (!EncodeArgs(e, s.fieldValues())) {
         return false;
     }
 
@@ -6492,16 +6827,26 @@ EncodeExpr(Encoder& e, AstExpr& expr)
         return EncodeMemOrTableCopy(e, expr.as<AstMemOrTableCopy>());
       case AstExprKind::MemOrTableDrop:
         return EncodeMemOrTableDrop(e, expr.as<AstMemOrTableDrop>());
       case AstExprKind::MemFill:
         return EncodeMemFill(e, expr.as<AstMemFill>());
       case AstExprKind::MemOrTableInit:
         return EncodeMemOrTableInit(e, expr.as<AstMemOrTableInit>());
 #endif
+#ifdef ENABLE_WASM_GENERALIZED_TABLES
+      case AstExprKind::TableGet:
+        return EncodeTableGet(e, expr.as<AstTableGet>());
+      case AstExprKind::TableGrow:
+        return EncodeTableGrow(e, expr.as<AstTableGrow>());
+      case AstExprKind::TableSet:
+        return EncodeTableSet(e, expr.as<AstTableSet>());
+      case AstExprKind::TableSize:
+        return EncodeTableSize(e, expr.as<AstTableSize>());
+#endif
 #ifdef ENABLE_WASM_GC
       case AstExprKind::StructNew:
         return EncodeStructNew(e, expr.as<AstStructNew>());
       case AstExprKind::StructGet:
         return EncodeStructGet(e, expr.as<AstStructGet>());
       case AstExprKind::StructSet:
         return EncodeStructSet(e, expr.as<AstStructSet>());
       case AstExprKind::StructNarrow:
@@ -6665,20 +7010,31 @@ EncodeLimits(Encoder& e, const Limits& l
             return false;
         }
     }
 
     return true;
 }
 
 static bool
-EncodeTableLimits(Encoder& e, const Limits& limits)
-{
-    if (!e.writeVarU32(uint32_t(TypeCode::AnyFunc))) {
-        return false;
+EncodeTableLimits(Encoder& e, const Limits& limits, TableKind tableKind)
+{
+    switch (tableKind) {
+      case TableKind::AnyFunction:
+        if (!e.writeVarU32(uint32_t(TypeCode::AnyFunc))) {
+            return false;
+        }
+        break;
+      case TableKind::AnyRef:
+        if (!e.writeVarU32(uint32_t(TypeCode::AnyRef))) {
+            return false;
+        }
+        break;
+      default:
+        MOZ_CRASH("Unexpected table kind");
     }
 
     return EncodeLimits(e, limits);
 }
 
 static bool
 EncodeGlobalType(Encoder& e, const AstGlobal* global)
 {
@@ -6709,17 +7065,17 @@ EncodeImport(Encoder& e, AstImport& imp)
         break;
       case DefinitionKind::Global:
         MOZ_ASSERT(!imp.global().hasInit());
         if (!EncodeGlobalType(e, &imp.global())) {
             return false;
         }
         break;
       case DefinitionKind::Table:
-        if (!EncodeTableLimits(e, imp.limits())) {
+        if (!EncodeTableLimits(e, imp.limits(), imp.tableKind())) {
             return false;
         }
         break;
       case DefinitionKind::Memory:
         if (!EncodeLimits(e, imp.limits())) {
             return false;
         }
         break;
@@ -6753,17 +7109,17 @@ EncodeImportSection(Encoder& e, AstModul
     e.finishSection(offset);
     return true;
 }
 
 static bool
 EncodeMemorySection(Encoder& e, AstModule& module)
 {
     size_t numOwnMemories = 0;
-    for (const AstResizable& memory : module.memories()) {
+    for (const AstMemory& memory : module.memories()) {
         if (!memory.imported) {
             numOwnMemories++;
         }
     }
 
     if (!numOwnMemories) {
         return true;
     }
@@ -6772,17 +7128,17 @@ EncodeMemorySection(Encoder& e, AstModul
     if (!e.startSection(SectionId::Memory, &offset)) {
         return false;
     }
 
     if (!e.writeVarU32(numOwnMemories)) {
         return false;
     }
 
-    for (const AstResizable& memory : module.memories()) {
+    for (const AstMemory& memory : module.memories()) {
         if (memory.imported) {
             continue;
         }
         if (!EncodeLimits(e, memory.limits)) {
             return false;
         }
     }
 
@@ -6865,17 +7221,17 @@ EncodeExportSection(Encoder& e, AstModul
     e.finishSection(offset);
     return true;
 }
 
 static bool
 EncodeTableSection(Encoder& e, AstModule& module)
 {
     size_t numOwnTables = 0;
-    for (const AstResizable& table : module.tables()) {
+    for (const AstTable& table : module.tables()) {
         if (!table.imported) {
             numOwnTables++;
         }
     }
 
     if (!numOwnTables) {
         return true;
     }
@@ -6884,21 +7240,21 @@ EncodeTableSection(Encoder& e, AstModule
     if (!e.startSection(SectionId::Table, &offset)) {
         return false;
     }
 
     if (!e.writeVarU32(numOwnTables)) {
         return false;
     }
 
-    for (const AstResizable& table : module.tables()) {
+    for (const AstTable& table : module.tables()) {
         if (table.imported) {
             continue;
         }
-        if (!EncodeTableLimits(e, table.limits)) {
+        if (!EncodeTableLimits(e, table.limits, table.tableKind)) {
             return false;
         }
     }
 
     e.finishSection(offset);
     return true;
 }
 
@@ -6984,24 +7340,32 @@ EncodeCodeSection(Encoder& e, Uint32Vect
         }
     }
 
     e.finishSection(offset);
     return true;
 }
 
 static bool
-EncodeDestinationOffsetOrFlags(Encoder& e, AstExpr* offsetIfActive)
+EncodeDestinationOffsetOrFlags(Encoder& e, uint32_t index, AstExpr* offsetIfActive)
 {
     if (offsetIfActive) {
-        // In the MVP, the following VarU32 is the table or linear memory
-        // index and it must be zero.  In the bulk-mem-ops proposal, it is
-        // repurposed as a flag field.
-        if (!e.writeVarU32(uint32_t(InitializerKind::Active))) {
-            return false;
+        // In the MVP, the following VarU32 is the table or linear memory index
+        // and it must be zero.  In the bulk-mem-ops proposal, it is repurposed
+        // as a flag field, and if the index is not zero it must be present.
+        if (index) {
+            if (!e.writeVarU32(uint32_t(InitializerKind::ActiveWithIndex)) ||
+                !e.writeVarU32(index))
+            {
+                return false;
+            }
+        } else {
+            if (!e.writeVarU32(uint32_t(InitializerKind::Active))) {
+                return false;
+            }
         }
         if (!EncodeExpr(e, *offsetIfActive)) {
             return false;
         }
         if (!e.writeOp(Op::End)) {
             return false;
         }
     } else {
@@ -7011,17 +7375,17 @@ EncodeDestinationOffsetOrFlags(Encoder& 
     }
 
     return true;
 }
 
 static bool
 EncodeDataSegment(Encoder& e, const AstDataSegment& segment)
 {
-    if (!EncodeDestinationOffsetOrFlags(e, segment.offsetIfActive())) {
+    if (!EncodeDestinationOffsetOrFlags(e, 0, segment.offsetIfActive())) {
         return false;
     }
 
     size_t totalLength = 0;
     for (const AstName& fragment : segment.fragments()) {
         totalLength += fragment.length();
     }
 
@@ -7067,17 +7431,17 @@ EncodeDataSection(Encoder& e, AstModule&
 
     e.finishSection(offset);
     return true;
 }
 
 static bool
 EncodeElemSegment(Encoder& e, AstElemSegment& segment)
 {
-    if (!EncodeDestinationOffsetOrFlags(e, segment.offsetIfActive())) {
+    if (!EncodeDestinationOffsetOrFlags(e, segment.targetTable().index(), segment.offsetIfActive())) {
         return false;
     }
 
     if (!e.writeVarU32(segment.elems().length())) {
         return false;
     }
 
     for (const AstRef& elem : segment.elems()) {
--- a/js/src/wasm/WasmTypes.cpp
+++ b/js/src/wasm/WasmTypes.cpp
@@ -422,16 +422,23 @@ Export::funcIndex() const
 
 uint32_t
 Export::globalIndex() const
 {
     MOZ_ASSERT(pod.kind_ == DefinitionKind::Global);
     return pod.index_;
 }
 
+uint32_t
+Export::tableIndex() const
+{
+    MOZ_ASSERT(pod.kind_ == DefinitionKind::Table);
+    return pod.index_;
+}
+
 size_t
 Export::serializedSize() const
 {
     return fieldName_.serializedSize() +
            sizeof(pod);
 }
 
 uint8_t*
--- a/js/src/wasm/WasmTypes.h
+++ b/js/src/wasm/WasmTypes.h
@@ -61,16 +61,17 @@ typedef MutableHandle<WasmModuleObject*>
 
 class WasmInstanceObject;
 typedef GCVector<WasmInstanceObject*> WasmInstanceObjectVector;
 typedef Rooted<WasmInstanceObject*> RootedWasmInstanceObject;
 typedef Handle<WasmInstanceObject*> HandleWasmInstanceObject;
 typedef MutableHandle<WasmInstanceObject*> MutableHandleWasmInstanceObject;
 
 class WasmTableObject;
+typedef GCVector<WasmTableObject*, 0, SystemAllocPolicy> WasmTableObjectVector;
 typedef Rooted<WasmTableObject*> RootedWasmTableObject;
 typedef Handle<WasmTableObject*> HandleWasmTableObject;
 typedef MutableHandle<WasmTableObject*> MutableHandleWasmTableObject;
 
 class WasmGlobalObject;
 typedef GCVector<WasmGlobalObject*, 0, SystemAllocPolicy> WasmGlobalObjectVector;
 typedef Rooted<WasmGlobalObject*> RootedWasmGlobalObject;
 
@@ -997,16 +998,17 @@ class Export
     explicit Export(UniqueChars fieldName, uint32_t index, DefinitionKind kind);
     explicit Export(UniqueChars fieldName, DefinitionKind kind);
 
     const char* fieldName() const { return fieldName_.get(); }
 
     DefinitionKind kind() const { return pod.kind_; }
     uint32_t funcIndex() const;
     uint32_t globalIndex() const;
+    uint32_t tableIndex() const;
 
     WASM_DECLARE_SERIALIZABLE(Export)
 };
 
 typedef Vector<Export, 0, SystemAllocPolicy> ExportVector;
 
 // A GlobalDesc describes a single global variable.
 //
@@ -1912,17 +1914,21 @@ enum class SymbolicAddress
     WaitI64,
     Wake,
     MemCopy,
     MemDrop,
     MemFill,
     MemInit,
     TableCopy,
     TableDrop,
+    TableGet,
+    TableGrow,
     TableInit,
+    TableSet,
+    TableSize,
     PostBarrier,
     StructNew,
     StructNarrow,
 #if defined(JS_CODEGEN_MIPS32)
     js_jit_gAtomic64Lock,
 #endif
     Limit
 };
@@ -1950,40 +1956,31 @@ struct Limits
 
 // TableDesc describes a table as well as the offset of the table's base pointer
 // in global memory. Currently, wasm only has "any function" and asm.js only
 // "typed function".
 
 enum class TableKind
 {
     AnyFunction,
+    AnyRef,
     TypedFunction
 };
 
 struct TableDesc
 {
-    // If a table is marked 'external' it is because it can contain functions
-    // from multiple instances; a table is therefore marked external if it is
-    // imported or exported or if it is initialized with an imported function.
-
     TableKind kind;
-#ifdef WASM_PRIVATE_REFTYPES
     bool importedOrExported;
-#endif
-    bool external;
     uint32_t globalDataOffset;
     Limits limits;
 
     TableDesc() = default;
-    TableDesc(TableKind kind, const Limits& limits)
+    TableDesc(TableKind kind, const Limits& limits, bool importedOrExported = false)
      : kind(kind),
-#ifdef WASM_PRIVATE_REFTYPES
-       importedOrExported(false),
-#endif
-       external(false),
+       importedOrExported(importedOrExported),
        globalDataOffset(UINT32_MAX),
        limits(limits)
     {}
 };
 
 typedef Vector<TableDesc, 0, SystemAllocPolicy> TableDescVector;
 
 // TLS data for a single module instance.
@@ -2100,26 +2097,25 @@ struct FuncImportTls
 // instance's thread-local storage which is accessed directly from JIT code
 // to bounds-check and index the table.
 
 struct TableTls
 {
     // Length of the table in number of elements (not bytes).
     uint32_t length;
 
-    // Pointer to the array of elements (of type either ExternalTableElem or
-    // void*).
-    void* base;
+    // Pointer to the array of elements (which can have various representations).
+    // For tables of anyref this is null.
+    void* functionBase;
 };
 
-// When a table can contain functions from other instances (it is "external"),
-// the internal representation is an array of ExternalTableElem instead of just
-// an array of code pointers.
-
-struct ExternalTableElem
+// Table elements for TableKind::AnyFunctions carry both the code pointer and an
+// instance pointer.
+
+struct FunctionTableElem
 {
     // The code to call when calling this element. The table ABI is the system
     // ABI with the additional ABI requirements that:
     //  - WasmTlsReg and any pinned registers have been loaded appropriately
     //  - if this is a heterogeneous table that requires a signature check,
     //    WasmTableCallSigReg holds the signature id.
     void* code;
 
@@ -2163,17 +2159,16 @@ class CalleeDesc
         U() : funcIndex_(0) {}
         uint32_t funcIndex_;
         struct {
             uint32_t globalDataOffset_;
         } import;
         struct {
             uint32_t globalDataOffset_;
             uint32_t minLength_;
-            bool external_;
             FuncTypeIdDesc funcTypeId_;
         } table;
         SymbolicAddress builtin_;
     } u;
 
   public:
     CalleeDesc() {}
     static CalleeDesc function(uint32_t funcIndex) {
@@ -2188,17 +2183,16 @@ class CalleeDesc
         c.u.import.globalDataOffset_ = globalDataOffset;
         return c;
     }
     static CalleeDesc wasmTable(const TableDesc& desc, FuncTypeIdDesc funcTypeId) {
         CalleeDesc c;
         c.which_ = WasmTable;
         c.u.table.globalDataOffset_ = desc.globalDataOffset;
         c.u.table.minLength_ = desc.limits.initial;
-        c.u.table.external_ = desc.external;
         c.u.table.funcTypeId_ = funcTypeId;
         return c;
     }
     static CalleeDesc asmJSTable(const TableDesc& desc) {
         CalleeDesc c;
         c.which_ = AsmJSTable;
         c.u.table.globalDataOffset_ = desc.globalDataOffset;
         return c;
@@ -2228,23 +2222,19 @@ class CalleeDesc
     }
     bool isTable() const {
         return which_ == WasmTable || which_ == AsmJSTable;
     }
     uint32_t tableLengthGlobalDataOffset() const {
         MOZ_ASSERT(isTable());
         return u.table.globalDataOffset_ + offsetof(TableTls, length);
     }
-    uint32_t tableBaseGlobalDataOffset() const {
+    uint32_t tableFunctionBaseGlobalDataOffset() const {
         MOZ_ASSERT(isTable());
-        return u.table.globalDataOffset_ + offsetof(TableTls, base);
-    }
-    bool wasmTableIsExternal() const {
-        MOZ_ASSERT(which_ == WasmTable);
-        return u.table.external_;
+        return u.table.globalDataOffset_ + offsetof(TableTls, functionBase);
     }
     FuncTypeIdDesc wasmTableSigId() const {
         MOZ_ASSERT(which_ == WasmTable);
         return u.table.funcTypeId_;
     }
     uint32_t wasmTableMinLength() const {
         MOZ_ASSERT(which_ == WasmTable);
         return u.table.minLength_;
--- a/js/src/wasm/WasmValidate.cpp
+++ b/js/src/wasm/WasmValidate.cpp
@@ -574,19 +574,19 @@ DecodeFunctionBodyExprs(const ModuleEnvi
           case uint16_t(Op::Drop):
             CHECK(iter.readDrop());
           case uint16_t(Op::Call): {
             uint32_t unusedIndex;
             ValidatingOpIter::ValueVector unusedArgs;
             CHECK(iter.readCall(&unusedIndex, &unusedArgs));
           }
           case uint16_t(Op::CallIndirect): {
-            uint32_t unusedIndex;
+            uint32_t unusedIndex, unusedIndex2;
             ValidatingOpIter::ValueVector unusedArgs;
-            CHECK(iter.readCallIndirect(&unusedIndex, &nothing, &unusedArgs));
+            CHECK(iter.readCallIndirect(&unusedIndex, &unusedIndex2, &nothing, &unusedArgs));
           }
           case uint16_t(Op::I32Const): {
             int32_t unused;
             CHECK(iter.readI32Const(&unused));
           }
           case uint16_t(Op::I64Const): {
             int64_t unused;
             CHECK(iter.readI64Const(&unused));
@@ -899,41 +899,67 @@ DecodeFunctionBodyExprs(const ModuleEnvi
                 CHECK(iter.readConversion(ValType::F64, ValType::I32, &nothing));
               case uint16_t(MiscOp::I64TruncSSatF32):
               case uint16_t(MiscOp::I64TruncUSatF32):
                 CHECK(iter.readConversion(ValType::F32, ValType::I64, &nothing));
               case uint16_t(MiscOp::I64TruncSSatF64):
               case uint16_t(MiscOp::I64TruncUSatF64):
                 CHECK(iter.readConversion(ValType::F64, ValType::I64, &nothing));
 #ifdef ENABLE_WASM_BULKMEM_OPS
-              case uint16_t(MiscOp::MemCopy):
-                  CHECK(iter.readMemOrTableCopy(/*isMem=*/true,
-                                                &nothing, &nothing, &nothing));
+              case uint16_t(MiscOp::MemCopy): {
+                  uint32_t unusedDestMemIndex;
+                  uint32_t unusedSrcMemIndex;
+                  CHECK(iter.readMemOrTableCopy(/*isMem=*/true, &unusedDestMemIndex,
+                                                &nothing, &unusedSrcMemIndex, &nothing, &nothing));
+              }
               case uint16_t(MiscOp::MemDrop): {
                 uint32_t unusedSegIndex;
                 CHECK(iter.readMemOrTableDrop(/*isMem=*/true, &unusedSegIndex));
               }
               case uint16_t(MiscOp::MemFill):
                 CHECK(iter.readMemFill(&nothing, &nothing, &nothing));
               case uint16_t(MiscOp::MemInit): {
                 uint32_t unusedSegIndex;
+                uint32_t unusedTableIndex;
                 CHECK(iter.readMemOrTableInit(/*isMem=*/true,
-                                              &unusedSegIndex, &nothing, &nothing, &nothing));
+                                              &unusedSegIndex, &unusedTableIndex, &nothing, &nothing, &nothing));
               }
-              case uint16_t(MiscOp::TableCopy):
-                CHECK(iter.readMemOrTableCopy(/*isMem=*/false,
-                                              &nothing, &nothing, &nothing));
+              case uint16_t(MiscOp::TableCopy): {
+                uint32_t unusedDestTableIndex;
+                uint32_t unusedSrcTableIndex;
+                CHECK(iter.readMemOrTableCopy(/*isMem=*/false, &unusedDestTableIndex,
+                                              &nothing, &unusedSrcTableIndex, &nothing, &nothing));
+              }
               case uint16_t(MiscOp::TableDrop): {
                 uint32_t unusedSegIndex;
                 CHECK(iter.readMemOrTableDrop(/*isMem=*/false, &unusedSegIndex));
               }
               case uint16_t(MiscOp::TableInit): {
                 uint32_t unusedSegIndex;
+                uint32_t unusedTableIndex;
                 CHECK(iter.readMemOrTableInit(/*isMem=*/false,
-                                              &unusedSegIndex, &nothing, &nothing, &nothing));
+                                              &unusedSegIndex, &unusedTableIndex, &nothing, &nothing, &nothing));
+              }
+#endif
+#ifdef ENABLE_WASM_GENERALIZED_TABLES
+              case uint16_t(MiscOp::TableGet): {
+                uint32_t unusedTableIndex;
+                CHECK(iter.readTableGet(&unusedTableIndex, &nothing));
+              }
+              case uint16_t(MiscOp::TableGrow): {
+                uint32_t unusedTableIndex;
+                CHECK(iter.readTableGrow(&unusedTableIndex, &nothing, &nothing));
+              }
+              case uint16_t(MiscOp::TableSet): {
+                uint32_t unusedTableIndex;
+                CHECK(iter.readTableSet(&unusedTableIndex, &nothing, &nothing));
+              }
+              case uint16_t(MiscOp::TableSize): {
+                uint32_t unusedTableIndex;
+                CHECK(iter.readTableSize(&unusedTableIndex));
               }
 #endif
 #ifdef ENABLE_WASM_GC
               case uint16_t(MiscOp::StructNew): {
                 if (env.gcTypesEnabled() == HasGcTypes::False) {
                     return iter.unrecognizedOpcode(&op);
                 }
                 uint32_t unusedUint;
@@ -1424,20 +1450,24 @@ DecodeGCFeatureOptInSection(Decoder& d, 
     uint32_t version;
     if (!d.readVarU32(&version)) {
         return d.fail("expected gc feature version");
     }
 
     // For documentation of what's in the various versions, see
     // https://github.com/lars-t-hansen/moz-gc-experiments
     //
-    // When we evolve the engine to handle v2, we will continue to recognize v1
-    // here if v2 is fully backwards compatible with v1.
-
-    if (version != 1) {
+    // Version 1 is complete.
+    // Version 2 is in progress, currently backward compatible with version 1.
+
+    switch (version) {
+      case 1:
+      case 2:
+        break;
+      default:
         return d.fail("unsupported version of the gc feature");
     }
 
     env->gcFeatureOptIn = HasGcTypes::True;
     return d.finishSection(*range, "gcfeatureoptin");
 }
 #endif
 
@@ -1593,45 +1623,59 @@ DecodeLimits(Decoder& d, Limits* limits,
                        : Shareable::False;
     }
 #endif
 
     return true;
 }
 
 static bool
-DecodeTableLimits(Decoder& d, TableDescVector* tables)
+DecodeTableTypeAndLimits(Decoder& d, HasGcTypes gcTypesEnabled, TableDescVector* tables)
 {
     uint8_t elementType;
     if (!d.readFixedU8(&elementType)) {
         return d.fail("expected table element type");
     }
 
-    if (elementType != uint8_t(TypeCode::AnyFunc)) {
+    TableKind tableKind;
+    if (elementType == uint8_t(TypeCode::AnyFunc)) {
+        tableKind = TableKind::AnyFunction;
+#ifdef ENABLE_WASM_GENERALIZED_TABLES
+    } else if (elementType == uint8_t(TypeCode::AnyRef)) {
+        if (gcTypesEnabled == HasGcTypes::False) {
+            return d.fail("reference types not enabled");
+        }
+        tableKind = TableKind::AnyRef;
+#endif
+    } else {
+#ifdef ENABLE_WASM_GENERALIZED_TABLES
+        return d.fail("expected 'anyfunc' or 'anyref' element type");
+#else
         return d.fail("expected 'anyfunc' element type");
+#endif
     }
 
     Limits limits;
     if (!DecodeLimits(d, &limits)) {
         return false;
     }
 
     // If there's a maximum, check it is in range.  The check to exclude
     // initial > maximum is carried out by the DecodeLimits call above, so
     // we don't repeat it here.
     if (limits.initial > MaxTableInitialLength ||
         ((limits.maximum.isSome() &&
           limits.maximum.value() > MaxTableMaximumLength)))
         return d.fail("too many table elements");
 
-    if (tables->length()) {
-        return d.fail("already have default table");
+    if (tables->length() >= MaxTables) {
+        return d.fail("too many tables");
     }
 
-    return tables->emplaceBack(TableKind::AnyFunction, limits);
+    return tables->emplaceBack(tableKind, limits);
 }
 
 static bool
 GlobalIsJSCompatible(Decoder& d, ValType type, bool isMutable)
 {
     switch (type.code()) {
       case ValType::I32:
       case ValType::F32:
@@ -1770,23 +1814,20 @@ DecodeImport(Decoder& d, ModuleEnvironme
             return false;
         }
         if (env->funcTypes.length() > MaxFuncs) {
             return d.fail("too many functions");
         }
         break;
       }
       case DefinitionKind::Table: {
-        if (!DecodeTableLimits(d, &env->tables)) {
+        if (!DecodeTableTypeAndLimits(d, env->gcTypesEnabled(), &env->tables)) {
             return false;
         }
-        env->tables.back().external = true;
-#ifdef WASM_PRIVATE_REFTYPES
         env->tables.back().importedOrExported = true;
-#endif
         break;
       }
       case DefinitionKind::Memory: {
         if (!DecodeMemoryLimits(d, env)) {
             return false;
         }
         break;
       }
@@ -1900,22 +1941,18 @@ DecodeTableSection(Decoder& d, ModuleEnv
         return true;
     }
 
     uint32_t numTables;
     if (!d.readVarU32(&numTables)) {
         return d.fail("failed to read number of tables");
     }
 
-    if (numTables > 1) {
-        return d.fail("the number of tables must be at most one");
-    }
-
     for (uint32_t i = 0; i < numTables; ++i) {
-        if (!DecodeTableLimits(d, &env->tables)) {
+        if (!DecodeTableTypeAndLimits(d, env->gcTypesEnabled(), &env->tables)) {
             return false;
         }
     }
 
     return d.finishSection(*range, "table");
 }
 
 static bool
@@ -2146,24 +2183,18 @@ DecodeExport(Decoder& d, ModuleEnvironme
         uint32_t tableIndex;
         if (!d.readVarU32(&tableIndex)) {
             return d.fail("expected table index");
         }
 
         if (tableIndex >= env->tables.length()) {
             return d.fail("exported table index out of bounds");
         }
-
-        MOZ_ASSERT(env->tables.length() == 1);
-        env->tables[tableIndex].external = true;
-#ifdef WASM_PRIVATE_REFTYPES
         env->tables[tableIndex].importedOrExported = true;
-#endif
-
-        return env->exports.emplaceBack(std::move(fieldName), DefinitionKind::Table);
+        return env->exports.emplaceBack(std::move(fieldName), tableIndex, DefinitionKind::Table);
       }
       case DefinitionKind::Memory: {
         uint32_t memoryIndex;
         if (!d.readVarU32(&memoryIndex)) {
             return d.fail("expected memory index");
         }
 
         if (memoryIndex > 0 || !env->usesMemory()) {
@@ -2297,34 +2328,41 @@ DecodeElemSection(Decoder& d, ModuleEnvi
           case uint32_t(InitializerKind::ActiveWithIndex):
             break;
           default:
             return d.fail("invalid elem initializer-kind field");
         }
 
         InitializerKind initializerKind = InitializerKind(initializerKindVal);
 
-        MOZ_ASSERT(env->tables.length() <= 1);
         if (env->tables.length() == 0) {
             return d.fail("elem segment requires a table section");
         }
 
         MutableElemSegment seg = js_new<ElemSegment>();
         if (!seg) {
             return false;
         }
 
         uint32_t tableIndex = 0;
         if (initializerKind == InitializerKind::ActiveWithIndex) {
             if (!d.readVarU32(&tableIndex)) {
                 return d.fail("expected table index");
             }
-            if (tableIndex > 0) {
-                return d.fail("table index must be zero");
-            }
+        }
+        if (tableIndex >= env->tables.length()) {
+            return d.fail("table index out of range for element segment");
+        }
+        if (initializerKind == InitializerKind::Passive) {
+            // Too many bugs result from keeping this value zero.  For passive
+            // segments, there really is no segment index, and we should never
+            // touch the field.
+            tableIndex = (uint32_t)-1;
+        } else if (env->tables[tableIndex].kind != TableKind::AnyFunction) {
+            return d.fail("only tables of 'anyfunc' may have element segments");
         }
 
         seg->tableIndex = tableIndex;
 
         if (initializerKind == InitializerKind::Active ||
             initializerKind == InitializerKind::ActiveWithIndex)
         {
             InitExpr offset;
@@ -2344,37 +2382,37 @@ DecodeElemSection(Decoder& d, ModuleEnvi
         if (numElems > MaxTableInitialLength) {
             return d.fail("too many table elements");
         }
 
         if (!seg->elemFuncIndices.reserve(numElems)) {
             return false;
         }
 
+#ifdef WASM_PRIVATE_REFTYPES
+        // We assume that passive segments may be applied to external tables.
+        // We can do slightly better: if there are no external tables in the
+        // module then we don't need to worry about passive segments either.
+        // But this is a temporary restriction.
+        bool exportedTable = initializerKind == InitializerKind::Passive ||
+                             env->tables[tableIndex].importedOrExported;
+#endif
+
         for (uint32_t i = 0; i < numElems; i++) {
             uint32_t funcIndex;
             if (!d.readVarU32(&funcIndex)) {
                 return d.fail("failed to read element function index");
             }
 
             if (funcIndex >= env->numFuncs()) {
                 return d.fail("table element out of range");
             }
 
-            // If a table element function value is imported then the table can
-            // contain functions from multiple instances and must be marked
-            // external.
-            if (env->funcIsImport(funcIndex)) {
-                env->tables[0].external = true;
-            }
-
 #ifdef WASM_PRIVATE_REFTYPES
-            if (env->tables[0].importedOrExported &&
-                !FuncTypeIsJSCompatible(d, *env->funcTypes[funcIndex]))
-            {
+            if (exportedTable && !FuncTypeIsJSCompatible(d, *env->funcTypes[funcIndex])) {
                 return false;
             }
 #endif
 
             seg->elemFuncIndices.infallibleAppend(funcIndex);
         }
 
         env->elemSegments.infallibleAppend(std::move(seg));
new file mode 100644
--- /dev/null
+++ b/media/webrtc/gn-configs/x64_False_arm64_win.json
@@ -0,0 +1,16170 @@
+{
+    "gn_gen_args": {
+        "host_cpu": "x64",
+        "is_debug": false,
+        "target_cpu": "arm64",
+        "target_os": "win"
+    },
+    "mozbuild_args": {
+        "CPU_ARCH": "aarch64",
+        "HOST_CPU_ARCH": "x86_64",
+        "MOZ_DEBUG": null,
+        "OS_TARGET": "WINNT"
+    },
+    "sandbox_vars": {
+        "COMPILE_FLAGS": {
+            "WARNINGS_AS_ERRORS": []
+        },
+        "FINAL_LIBRARY": "webrtc"
+    },
+    "targets": {
+        "//:webrtc": {
+            "cflags": [
+                "/wd4117",
+                "/D__DATE__=",
+                "/D__TIME__=",
+                "/D__TIMESTAMP__=",
+                "/Gy",
+                "/FS",
+                "/bigobj",
+                "/d2FastFail",
+                "/Zc:sizedDealloc-",
+                "/W4",
+                "/WX",
+                "/utf-8",
+                "/wd4091",
+                "/wd4127",
+                "/wd4251",
+                "/wd4312",
+                "/wd4351",
+                "/wd4355",
+                "/wd4503",
+                "/wd4589",
+                "/wd4611",
+                "/wd4100",
+                "/wd4121",
+                "/wd4244",
+                "/wd4505",
+                "/wd4510",
+                "/wd4512",
+                "/wd4610",
+                "/wd4838",
+                "/wd4995",
+                "/wd4996",
+                "/wd4456",
+                "/wd4457",
+                "/wd4458",
+                "/wd4459",
+                "/O1",
+                "/Ob2",
+                "/Oy-",
+                "/d2Zi+",
+                "/Zc:inline",
+                "/Gw",
+                "/Oi",
+                "/Zi",
+                "/MT"
+            ],
+            "defines": [
+                "V8_DEPRECATION_WARNINGS",
+                "NO_TCMALLOC",
+                "CHROMIUM_BUILD",
+                "__STD_C",
+                "_CRT_RAND_S",
+                "_CRT_SECURE_NO_DEPRECATE",
+                "_HAS_EXCEPTIONS=0",
+                "_SCL_SECURE_NO_DEPRECATE",
+                "_ATL_NO_OPENGL",
+                "_WINDOWS",
+                "CERT_CHAIN_PARA_HAS_EXTRA_FIELDS",
+                "PSAPI_VERSION=1",
+                "WIN32",
+                "_SECURE_ATL",
+                "_USING_V110_SDK71_",
+                "WIN32_LEAN_AND_MEAN",
+                "NOMINMAX",
+                "_UNICODE",
+                "UNICODE",
+                "NTDDI_VERSION=0x0A000000",
+                "_WIN32_WINNT=0x0A00",
+                "WINVER=0x0A00",
+                "NDEBUG",
+                "NVALGRIND",
+                "DYNAMIC_ANNOTATIONS_ENABLED=0",
+                "WEBRTC_ENABLE_PROTOBUF=0",
+                "WEBRTC_RESTRICT_LOGGING",
+                "WEBRTC_ARCH_ARM64",
+                "WEBRTC_HAS_NEON",
+                "WEBRTC_MOZILLA_BUILD",
+                "WEBRTC_NON_STATIC_TRACE_EVENT_HANDLERS=0",
+                "WEBRTC_WIN",
+                "_CRT_SECURE_NO_WARNINGS",
+                "HAVE_WEBRTC_VIDEO",
+                "HAVE_WEBRTC_VOICE"
+            ],
+            "deps": [
+                "//:webrtc_common",
+                "//api:base_peerconnection_api",
+                "//api:transport_api",
+                "//api:video_frame_api",
+                "//audio:audio",
+                "//call:call",
+                "//common_audio:common_audio",
+                "//common_video:common_video",
+                "//media:media",
+                "//modules:modules",
+                "//modules/video_capture:video_capture_internal_impl",
+                "//rtc_base:rtc_base",
+                "//system_wrappers:field_trial_default",
+                "//system_wrappers:metrics_default",
+                "//system_wrappers:system_wrappers_default",
+                "//video:video",
+                "//video_engine:video_engine",
+                "//voice_engine:voice_engine"
+            ],
+            "include_dirs": [
+                "//",
+                "s:/orange/obj/arm/media/webrtc/trunk/webrtc/gn-output/gen/",
+                "//common_audio/resampler/include/",
+                "//common_audio/signal_processing/include/",
+                "//common_audio/vad/include/",
+                "//common_video/include/",
+                "/media/libyuv/libyuv/include/",
+                "//modules/audio_coding/include/",
+                "//modules/include/",
+                "//modules/include/",
+                "//modules/audio_device/include/",
+                "//modules/audio_device/dummy/"
+            ],
+            "libs": [
+                "winmm.lib",
+                "Strmiids.lib",
+                "d3d11.lib",
+                "dxgi.lib"
+            ],
+            "sources": [],
+            "type": "static_library"
+        },
+        "//:webrtc_common": {
+            "cflags": [
+                "/wd4117",
+                "/D__DATE__=",
+                "/D__TIME__=",
+                "/D__TIMESTAMP__=",
+                "/Gy",
+                "/FS",
+                "/bigobj",
+                "/d2FastFail",
+                "/Zc:sizedDealloc-",
+                "/W4",
+                "/WX",
+                "/utf-8",
+                "/wd4091",
+                "/wd4127",
+                "/wd4251",
+                "/wd4312",
+                "/wd4351",
+                "/wd4355",
+                "/wd4503",
+                "/wd4589",
+                "/wd4611",
+                "/wd4100",
+                "/wd4121",
+                "/wd4244",
+                "/wd4505",
+                "/wd4510",
+                "/wd4512",
+                "/wd4610",
+                "/wd4838",
+                "/wd4995",
+                "/wd4996",
+                "/wd4456",
+                "/wd4457",
+                "/wd4458",
+                "/wd4459",
+                "/O1",
+                "/Ob2",
+                "/Oy-",
+                "/d2Zi+",
+                "/Zc:inline",
+                "/Gw",
+                "/Oi",
+                "/Zi",
+                "/MT"
+            ],
+            "defines": [
+                "V8_DEPRECATION_WARNINGS",
+                "NO_TCMALLOC",
+                "CHROMIUM_BUILD",
+                "__STD_C",
+                "_CRT_RAND_S",
+                "_CRT_SECURE_NO_DEPRECATE",
+                "_HAS_EXCEPTIONS=0",
+                "_SCL_SECURE_NO_DEPRECATE",
+                "_ATL_NO_OPENGL",
+                "_WINDOWS",
+                "CERT_CHAIN_PARA_HAS_EXTRA_FIELDS",
+                "PSAPI_VERSION=1",
+                "WIN32",
+                "_SECURE_ATL",
+                "_USING_V110_SDK71_",
+                "WIN32_LEAN_AND_MEAN",
+                "NOMINMAX",
+                "_UNICODE",
+                "UNICODE",
+                "NTDDI_VERSION=0x0A000000",
+                "_WIN32_WINNT=0x0A00",
+                "WINVER=0x0A00",
+                "NDEBUG",
+                "NVALGRIND",
+                "DYNAMIC_ANNOTATIONS_ENABLED=0",
+                "WEBRTC_ENABLE_PROTOBUF=0",
+                "WEBRTC_RESTRICT_LOGGING",
+                "WEBRTC_ARCH_ARM64",
+                "WEBRTC_HAS_NEON",
+                "WEBRTC_MOZILLA_BUILD",
+                "WEBRTC_NON_STATIC_TRACE_EVENT_HANDLERS=0",
+                "WEBRTC_WIN",
+                "_CRT_SECURE_NO_WARNINGS"
+            ],
+            "deps": [],
+            "include_dirs": [
+                "//",
+                "s:/orange/obj/arm/media/webrtc/trunk/webrtc/gn-output/gen/"
+            ],
+            "libs": [],
+            "sources": [
+                "//common_types.cc",
+                "//common_types.h",
+                "//typedefs.h"
+            ],
+            "type": "static_library"
+        },
+        "//api/audio_codecs/L16:audio_decoder_L16": {
+            "cflags": [
+                "/wd4117",
+                "/D__DATE__=",
+                "/D__TIME__=",
+                "/D__TIMESTAMP__=",
+                "/Gy",
+                "/FS",
+                "/bigobj",
+                "/d2FastFail",
+                "/Zc:sizedDealloc-",
+                "/W4",
+                "/WX",
+                "/utf-8",
+                "/wd4091",
+                "/wd4127",
+                "/wd4251",
+                "/wd4312",
+                "/wd4351",
+                "/wd4355",
+                "/wd4503",
+                "/wd4589",
+                "/wd4611",
+                "/wd4100",
+                "/wd4121",
+                "/wd4244",
+                "/wd4505",
+                "/wd4510",
+                "/wd4512",
+                "/wd4610",
+                "/wd4838",
+                "/wd4995",
+                "/wd4996",
+                "/wd4456",
+                "/wd4457",
+                "/wd4458",
+                "/wd4459",
+                "/O1",
+                "/Ob2",
+                "/Oy-",
+                "/d2Zi+",
+                "/Zc:inline",
+                "/Gw",
+                "/Oi",
+                "/Zi",
+                "/MT"
+            ],
+            "defines": [
+                "V8_DEPRECATION_WARNINGS",
+                "NO_TCMALLOC",
+                "CHROMIUM_BUILD",
+                "__STD_C",
+                "_CRT_RAND_S",
+                "_CRT_SECURE_NO_DEPRECATE",
+                "_HAS_EXCEPTIONS=0",
+                "_SCL_SECURE_NO_DEPRECATE",
+                "_ATL_NO_OPENGL",
+                "_WINDOWS",
+                "CERT_CHAIN_PARA_HAS_EXTRA_FIELDS",
+                "PSAPI_VERSION=1",
+                "WIN32",
+                "_SECURE_ATL",
+                "_USING_V110_SDK71_",
+                "WIN32_LEAN_AND_MEAN",
+                "NOMINMAX",
+                "_UNICODE",
+                "UNICODE",
+                "NTDDI_VERSION=0x0A000000",
+                "_WIN32_WINNT=0x0A00",
+                "WINVER=0x0A00",
+                "NDEBUG",
+                "NVALGRIND",
+                "DYNAMIC_ANNOTATIONS_ENABLED=0",
+                "WEBRTC_ENABLE_PROTOBUF=0",
+                "WEBRTC_RESTRICT_LOGGING",
+                "WEBRTC_ARCH_ARM64",
+                "WEBRTC_HAS_NEON",
+                "WEBRTC_MOZILLA_BUILD",
+                "WEBRTC_NON_STATIC_TRACE_EVENT_HANDLERS=0",
+                "WEBRTC_WIN",
+                "_CRT_SECURE_NO_WARNINGS"
+            ],
+            "deps": [
+                "//:webrtc_common",
+                "//api:optional",
+                "//api/audio_codecs:audio_codecs_api",
+                "//modules/audio_coding:pcm16b",
+                "//rtc_base:rtc_base_approved"
+            ],
+            "include_dirs": [
+                "//",
+                "s:/orange/obj/arm/media/webrtc/trunk/webrtc/gn-output/gen/",
+                "//modules/audio_coding/codecs/pcm16b/include/"
+            ],
+            "libs": [],
+            "sources": [
+                "//api/audio_codecs/L16/audio_decoder_L16.cc",
+                "//api/audio_codecs/L16/audio_decoder_L16.h"
+            ],
+            "type": "static_library"
+        },
+        "//api/audio_codecs/L16:audio_encoder_L16": {
+            "cflags": [
+                "/wd4117",
+                "/D__DATE__=",
+                "/D__TIME__=",
+                "/D__TIMESTAMP__=",
+                "/Gy",
+                "/FS",
+                "/bigobj",
+                "/d2FastFail",
+                "/Zc:sizedDealloc-",
+                "/W4",
+                "/WX",
+                "/utf-8",
+                "/wd4091",
+                "/wd4127",
+                "/wd4251",
+                "/wd4312",
+                "/wd4351",
+                "/wd4355",
+                "/wd4503",
+                "/wd4589",
+                "/wd4611",
+                "/wd4100",
+                "/wd4121",
+                "/wd4244",
+                "/wd4505",
+                "/wd4510",
+                "/wd4512",
+                "/wd4610",
+                "/wd4838",
+                "/wd4995",
+                "/wd4996",
+                "/wd4456",
+                "/wd4457",
+                "/wd4458",
+                "/wd4459",
+                "/O1",
+                "/Ob2",
+                "/Oy-",
+                "/d2Zi+",
+                "/Zc:inline",
+                "/Gw",
+                "/Oi",
+                "/Zi",
+                "/MT"
+            ],
+            "defines": [
+                "V8_DEPRECATION_WARNINGS",
+                "NO_TCMALLOC",
+                "CHROMIUM_BUILD",
+                "__STD_C",
+                "_CRT_RAND_S",
+                "_CRT_SECURE_NO_DEPRECATE",
+                "_HAS_EXCEPTIONS=0",
+                "_SCL_SECURE_NO_DEPRECATE",
+                "_ATL_NO_OPENGL",
+                "_WINDOWS",
+                "CERT_CHAIN_PARA_HAS_EXTRA_FIELDS",
+                "PSAPI_VERSION=1",
+                "WIN32",
+                "_SECURE_ATL",
+                "_USING_V110_SDK71_",
+                "WIN32_LEAN_AND_MEAN",
+                "NOMINMAX",
+                "_UNICODE",
+                "UNICODE",
+                "NTDDI_VERSION=0x0A000000",
+                "_WIN32_WINNT=0x0A00",
+                "WINVER=0x0A00",
+                "NDEBUG",
+                "NVALGRIND",
+                "DYNAMIC_ANNOTATIONS_ENABLED=0",
+                "WEBRTC_ENABLE_PROTOBUF=0",
+                "WEBRTC_RESTRICT_LOGGING",
+                "WEBRTC_ARCH_ARM64",
+                "WEBRTC_HAS_NEON",
+                "WEBRTC_MOZILLA_BUILD",
+                "WEBRTC_NON_STATIC_TRACE_EVENT_HANDLERS=0",
+                "WEBRTC_WIN",
+                "_CRT_SECURE_NO_WARNINGS"
+            ],
+            "deps": [
+                "//:webrtc_common",
+                "//api:optional",
+                "//api/audio_codecs:audio_codecs_api",