Merge mozilla-central to autoland. a=merge on a CLOSED TREE
authorAndreea Pavel <apavel@mozilla.com>
Thu, 15 Nov 2018 00:35:07 +0200
changeset 502853 682c6a11531bda7811aa573f86d4eb30cc2c43a1
parent 502852 ae26c60a0f50dc98534e7e2bd65ee62254cb71f6 (current diff)
parent 502838 b0a40093b6b7a0784a6f38b318f597419d86fd8e (diff)
child 502854 c61a46d741f9649de8cdca1a431c2a51cf05f564
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
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge mozilla-central to autoland. a=merge on a CLOSED TREE
browser/components/translation/jar.mn
browser/components/translation/microsoft-translator-attribution.png
browser/components/translation/translation-infobar.xml
modules/libpref/init/all.js
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",
+                "//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_encoder_L16.cc",
+                "//api/audio_codecs/L16/audio_encoder_L16.h"
+            ],
+            "type": "static_library"
+        },
+        "//api/audio_codecs/g711:audio_decoder_g711": {
+            "cf