Merge mozilla-central to autoland. a=merge CLOSED TREE
authorBrindusan Cristian <cbrindusan@mozilla.com>
Mon, 18 Feb 2019 23:47:49 +0200
changeset 459810 3d2830a2b70e2157ca78b0099e9dc5c83a1dd897
parent 459809 c2cfcb1247ecffc86c52a42e2d2ebc7d7c8e92f5 (current diff)
parent 459806 ca3d40c83ae79d08ce38d9d6dc61f3da30faf126 (diff)
child 459811 2299664d9a0b83229ed527ca3e5c43452e69ab2a
push id35574
push usercbrindusan@mozilla.com
push dateTue, 19 Feb 2019 04:38:09 +0000
treeherdermozilla-central@8231f78228bc [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone67.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 CLOSED TREE
--- a/browser/components/aboutconfig/content/aboutconfig.css
+++ b/browser/components/aboutconfig/content/aboutconfig.css
@@ -1,16 +1,20 @@
 /* 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/. */
 
 html {
   height: 100%;
 }
 
+.hidden {
+  display: none;
+}
+
 body.config-background {
   min-height: 100%;
   background-image: url("chrome://browser/content/aboutconfig/background.svg");
   background-repeat: no-repeat;
   background-position: center center;
   background-size: 450px;
 }
 
@@ -45,31 +49,38 @@ body.config-warning {
   padding-inline-start: 30px;
   z-index: 1;
 }
 
 #about-config-search:dir(rtl) {
   background-position-x: right 9px;
 }
 
+#show-all:not(.hidden) {
+  display: block;
+  margin: 10px auto;
+}
+
+/* Ghost the button when it's not selected. */
+#show-all:not(:focus):not(:hover):not(:active) {
+  background-color: transparent;
+  opacity: .7;
+}
+
 #prefs {
   background-color: var(--in-content-box-background);
   color: var(--in-content-text-color);
   margin: 10px;
   min-width: 644px;
   /* To stay consistent with about:preferences (664px - 20px margin). */
   border: 1px solid var(--in-content-box-border-color);
   border-radius: 2px;
   border-spacing: 0;
 }
 
-#prefs > tr.hidden {
-  display: none;
-}
-
 #prefs > tr.odd {
   background-color: var(--in-content-box-background-odd);
 }
 
 #prefs > tr:hover {
   background-color: var(--in-content-item-hover);
 }
 
--- a/browser/components/aboutconfig/content/aboutconfig.html
+++ b/browser/components/aboutconfig/content/aboutconfig.html
@@ -42,15 +42,17 @@
       </div>
     </div>
 
     <template id="main">
       <div id="search-container">
         <!-- Use a unique ID to prevent showing autocomplete results from other
              browser pages with similarly named fields. -->
         <input type="text" id="about-config-search"
-               data-l10n-id="about-config-search">
+               data-l10n-id="about-config-search-input">
       </div>
 
       <table id="prefs"></table>
+
+      <button id="show-all" data-l10n-id="about-config-show-all"></button>
     </template>
   </body>
 </html>
--- a/browser/components/aboutconfig/content/aboutconfig.js
+++ b/browser/components/aboutconfig/content/aboutconfig.js
@@ -1,32 +1,33 @@
 /* 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/. */
 
 const {DeferredTask} = ChromeUtils.import("resource://gre/modules/DeferredTask.jsm");
 const {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm");
 const {Preferences} = ChromeUtils.import("resource://gre/modules/Preferences.jsm");
 
-const SEARCH_TIMEOUT_MS = 500;
+const SEARCH_TIMEOUT_MS = 100;
+const SEARCH_AUTO_MIN_CRARACTERS = 3;
 
 const GETTERS_BY_PREF_TYPE = {
   [Ci.nsIPrefBranch.PREF_BOOL]: "getBoolPref",
   [Ci.nsIPrefBranch.PREF_INT]: "getIntPref",
   [Ci.nsIPrefBranch.PREF_STRING]: "getStringPref",
 };
 
 const STRINGS_ADD_BY_TYPE = {
   Boolean: "about-config-pref-add-type-boolean",
   Number: "about-config-pref-add-type-number",
   String: "about-config-pref-add-type-string",
 };
 
 let gDefaultBranch = Services.prefs.getDefaultBranch("");
-let gFilterPrefsTask = new DeferredTask(() => filterPrefs(), SEARCH_TIMEOUT_MS);
+let gFilterPrefsTask = new DeferredTask(() => filterPrefs(), SEARCH_TIMEOUT_MS, 0);
 
 /**
  * Maps the name of each preference in the back-end to its PrefRow object,
  * separating the preferences that actually exist. This is as an optimization to
  * avoid querying the preferences service each time the list is filtered.
  */
 let gExistingPrefs = new Map();
 let gDeletedPrefs = new Map();
@@ -43,16 +44,21 @@ let gPrefsTable = null;
  */
 let gPrefInEdit = null;
 
 /**
  * Lowercase substring that should be contained in the preference name.
  */
 let gFilterString = null;
 
+/**
+ * True if we were requested to show all preferences.
+ */
+let gFilterShowAll = false;
+
 class PrefRow {
   constructor(name) {
     this.name = name;
     this.value = true;
     this.hidden = false;
     this.odd = false;
     this.editing = false;
     this.refreshValue();
@@ -109,17 +115,18 @@ class PrefRow {
     return this.value.constructor.name;
   }
 
   get exists() {
     return this.hasDefaultValue || this.hasUserValue;
   }
 
   get matchesFilter() {
-    return !gFilterString || this.name.toLowerCase().includes(gFilterString);
+    return gFilterShowAll ||
+           (gFilterString && this.name.toLowerCase().includes(gFilterString));
   }
 
   /**
    * Returns a reference to the table row element to be added to the document,
    * constructing and initializing it the first time this method is called.
    */
   getElement() {
     if (this._element) {
@@ -362,37 +369,52 @@ function loadPrefs() {
   [...document.styleSheets].find(s => s.title == "infop").disabled = true;
 
   let { content } = document.getElementById("main");
   document.body.textContent = "";
   document.body.appendChild(content);
 
   let search = gSearchInput = document.getElementById("about-config-search");
   let prefs = gPrefsTable = document.getElementById("prefs");
+  let showAll = document.getElementById("show-all");
   search.focus();
 
   for (let name of Services.prefs.getChildList("")) {
     new PrefRow(name);
   }
 
   search.addEventListener("keypress", event => {
-    switch (event.key) {
-      case "Escape":
-        search.value = "";
-        // Fall through.
-      case "Enter":
-        gFilterPrefsTask.disarm();
-        filterPrefs();
+    if (event.key == "Escape") {
+      // The ESC key returns immediately to the initial empty page.
+      search.value = "";
+      gFilterPrefsTask.disarm();
+      filterPrefs();
+    } else if (event.key == "Enter") {
+      // The Enter key filters immediately even if the search string is short.
+      gFilterPrefsTask.disarm();
+      filterPrefs({ shortString: true });
     }
   });
 
   search.addEventListener("input", () => {
     // We call "disarm" to restart the timer at every input.
     gFilterPrefsTask.disarm();
-    gFilterPrefsTask.arm();
+    if (search.value.trim().length < SEARCH_AUTO_MIN_CRARACTERS) {
+      // Return immediately to the empty page if the search string is short.
+      filterPrefs();
+    } else {
+      gFilterPrefsTask.arm();
+    }
+  });
+
+  showAll.addEventListener("click", event => {
+    search.focus();
+    search.value = "";
+    gFilterPrefsTask.disarm();
+    filterPrefs({ showAll: true });
   });
 
   prefs.addEventListener("click", event => {
     if (event.target.localName != "button") {
       return;
     }
     let pref = event.target.closest("tr")._pref;
     let button = event.target.closest("button");
@@ -411,42 +433,54 @@ function loadPrefs() {
       // This is "button-reset" or "button-delete".
       pref.editing = false;
       Services.prefs.clearUserPref(pref.name);
       pref.editButton.focus();
     }
   });
 }
 
-function filterPrefs() {
+function filterPrefs(options = {}) {
   if (gPrefInEdit) {
     gPrefInEdit.endEdit();
   }
   gDeletedPrefs.clear();
 
   let searchName = gSearchInput.value.trim();
+  if (searchName.length < SEARCH_AUTO_MIN_CRARACTERS && !options.shortString) {
+    searchName = "";
+  }
+
   gFilterString = searchName.toLowerCase();
+  gFilterShowAll = !!options.showAll;
 
-  if (!gSortedExistingPrefs) {
-    gSortedExistingPrefs = [...gExistingPrefs.values()];
-    gSortedExistingPrefs.sort((a, b) => a.name > b.name);
+  let showResults = gFilterString || gFilterShowAll;
+  document.getElementById("show-all").classList.toggle("hidden", showResults);
+
+  let prefArray = [];
+  if (showResults) {
+    if (!gSortedExistingPrefs) {
+      gSortedExistingPrefs = [...gExistingPrefs.values()];
+      gSortedExistingPrefs.sort((a, b) => a.name > b.name);
+    }
+    prefArray = gSortedExistingPrefs;
   }
 
   // The slowest operations tend to be the addition and removal of DOM nodes, so
   // this algorithm tries to reduce removals by hiding nodes instead. This
   // happens frequently when the set narrows while typing preference names. We
   // iterate the nodes already in the table in parallel to those we want to
   // show, because the two lists are sorted and they will often match already.
   let fragment = null;
   let indexInArray = 0;
   let elementInTable = gPrefsTable.firstElementChild;
   let odd = false;
-  while (indexInArray < gSortedExistingPrefs.length || elementInTable) {
+  while (indexInArray < prefArray.length || elementInTable) {
     // For efficiency, filter the array while we are iterating.
-    let prefInArray = gSortedExistingPrefs[indexInArray];
+    let prefInArray = prefArray[indexInArray];
     if (prefInArray) {
       if (!prefInArray.matchesFilter) {
         indexInArray++;
         continue;
       }
       prefInArray.hidden = false;
       prefInArray.odd = odd;
     }
--- a/browser/components/aboutconfig/test/browser/browser_edit.js
+++ b/browser/components/aboutconfig/test/browser/browser_edit.js
@@ -72,19 +72,19 @@ add_task(async function test_delete_user
                    Ci.nsIPrefBranch.PREF_INVALID);
 
       // Re-adding the preference should keep the same value.
       Assert.ok(row.element.querySelectorAll("input")[radioIndex].checked);
       row.editColumnButton.click();
       Assert.ok(!row.hasClass("deleted"));
       Assert.ok(Preferences.get(PREF_NEW) === testValue);
 
-      // Searching again after deleting should remove the row.
+      // Filtering again after deleting should remove the row.
       row.resetColumnButton.click();
-      this.search();
+      this.showAll();
       Assert.ok(!this.getRow(PREF_NEW));
     });
   }
 });
 
 add_task(async function test_reset_user_pref() {
   await SpecialPowers.pushPrefEnv({
     "set": [
@@ -98,18 +98,18 @@ add_task(async function test_reset_user_
     let row = this.getRow(PREF_BOOLEAN_DEFAULT_TRUE);
     row.resetColumnButton.click();
     // Check new layout and reset.
     Assert.ok(!row.hasClass("has-user-value"));
     Assert.ok(!row.resetColumnButton);
     Assert.ok(!Services.prefs.prefHasUserValue(PREF_BOOLEAN_DEFAULT_TRUE));
     Assert.equal(this.getRow(PREF_BOOLEAN_DEFAULT_TRUE).value, "true");
 
-    // Search for nothing to test gPrefArray
-    this.search();
+    // Filter again to test the preference cache.
+    this.showAll();
     row = this.getRow(PREF_BOOLEAN_DEFAULT_TRUE);
     Assert.ok(!row.hasClass("has-user-value"));
     Assert.ok(!row.resetColumnButton);
     Assert.equal(this.getRow(PREF_BOOLEAN_DEFAULT_TRUE).value, "true");
 
     // Clicking reset on a localized preference without a corresponding value.
     row = this.getRow(PREF_STRING_LOCALIZED_MISSING);
     Assert.equal(row.value, "user-value");
--- a/browser/components/aboutconfig/test/browser/browser_observe.js
+++ b/browser/components/aboutconfig/test/browser/browser_observe.js
@@ -51,19 +51,19 @@ add_task(async function test_observe_add
       // Re-adding the preference from the interface should restore its value.
       row.editColumnButton.click();
       if (value.constructor.name != "Boolean") {
         row.editColumnButton.click();
       }
       Assert.equal(row.value, "" + value);
       Assert.ok(Preferences.get(PREF_NEW) === value);
 
-      // Searching again after deleting should remove the row.
+      // Filtering again after deleting should remove the row.
       Preferences.reset(PREF_NEW);
-      this.search();
+      this.showAll();
       Assert.ok(!this.getRow(PREF_NEW));
 
       // Searching for the preference name should give the ability to add it.
       Preferences.reset(PREF_NEW);
       this.search(PREF_NEW);
       row = this.getRow(PREF_NEW);
       Assert.ok(row.hasClass("deleted"));
 
@@ -74,37 +74,37 @@ add_task(async function test_observe_add
       // If a new preference does not match the filter it is not displayed.
       Preferences.reset(PREF_NEW);
       this.search(PREF_NEW + ".extra");
       Assert.ok(!this.getRow(PREF_NEW));
       Preferences.set(PREF_NEW, value);
       Assert.ok(!this.getRow(PREF_NEW));
 
       // Resetting the filter should display the new preference.
-      this.search("");
+      this.showAll();
       Assert.equal(this.getRow(PREF_NEW).value, "" + value);
 
       // Reset the preference, then continue by adding a different value.
       Preferences.reset(PREF_NEW);
-      this.search("");
+      this.showAll();
     }
   });
 });
 
 add_task(async function test_observe_delete_user_pref() {
   for (let value of [true, "value", -10]) {
     Preferences.set(PREF_NEW, value);
     await AboutConfigTest.withNewTab(async function() {
       // Deleting the preference should keep the row.
       let row = this.getRow(PREF_NEW);
       Preferences.reset(PREF_NEW);
       Assert.ok(row.hasClass("deleted"));
 
-      // Searching again should remove the row.
-      this.search();
+      // Filtering again should remove the row.
+      this.showAll();
       Assert.ok(!this.getRow(PREF_NEW));
     });
   }
 });
 
 add_task(async function test_observe_reset_user_pref() {
   await SpecialPowers.pushPrefEnv({
     "set": [
--- a/browser/components/aboutconfig/test/browser/browser_search.js
+++ b/browser/components/aboutconfig/test/browser/browser_search.js
@@ -29,45 +29,74 @@ add_task(async function test_search() {
 
     let filteredPrefArray =
         prefArray.filter(pref => pref.includes("wser.down"));
     // Adding +1 to the list since button does not match an exact
     // preference name then a row is added for the user to add a
     // new button preference if desired
     Assert.equal(this.rows.length, filteredPrefArray.length + 1);
 
-    // Test empty search returns all preferences.
-    this.search();
+    // Show all preferences again after filtering.
+    this.showAll();
+    Assert.equal(this.searchInput.value, "");
 
     // The total number of preferences may change at any time because of
     // operations running in the background, so we only test approximately.
     // The change in count would be because of one or two added preferences,
     // but we tolerate a difference of up to 50 preferences just to be safe.
     // We want thousands of prefs instead of a few dozen that are filtered.
     Assert.greater(this.rows.length, prefArray.length - 50);
 
+    // Pressing ESC while showing all preferences returns to the initial page.
+    EventUtils.sendKey("escape");
+    Assert.equal(this.rows.length, 0);
+
     // Test invalid search returns no preferences.
     // Expecting 1 row to be returned since it offers the ability to add.
     this.search("aJunkValueasdf");
     Assert.equal(this.rows.length, 1);
 
+    // Pressing ESC clears the field and returns to the initial page.
+    EventUtils.sendKey("escape");
+    Assert.equal(this.searchInput.value, "");
+    Assert.equal(this.rows.length, 0);
+
     // Two preferences match this filter, and one of those matches exactly.
     this.search("test.aboutconfig.a");
     Assert.equal(this.rows.length, 2);
 
     // When searching case insensitively, there is an additional row to add a
     // new preference with the same name but a different case.
     this.search("TEST.aboutconfig.a");
     Assert.equal(this.rows.length, 3);
+
+    // Entering an empty string returns to the initial page.
+    this.search("");
+    Assert.equal(this.rows.length, 0);
   });
 });
 
 add_task(async function test_search_delayed() {
   await AboutConfigTest.withNewTab(async function() {
-    // Prepare the table and the search field for the test.
+    // Start with the initial empty page.
+    this.search("");
+
+    // We need to wait more than the search typing timeout to make sure that
+    // nothing happens when entering a short string.
+    EventUtils.synthesizeKey("t");
+    EventUtils.synthesizeKey("e");
+    // eslint-disable-next-line mozilla/no-arbitrary-setTimeout
+    await new Promise(resolve => setTimeout(resolve, 500));
+    Assert.equal(this.rows.length, 0);
+
+    // Pressing Enter will force a search to occur anyways.
+    EventUtils.sendKey("return");
+    Assert.greater(this.rows.length, 0);
+
+    // Prepare the table and the search field for the next test.
     this.search("test.aboutconfig.a");
     Assert.equal(this.rows.length, 2);
 
     // The table is updated in a single microtask, so we don't need to wait for
     // specific mutations, we can just continue when any of the children or
     // their "hidden" attributes are updated.
     let prefsTableChanged = new Promise(resolve => {
       let observer = new MutationObserver(() => {
--- a/browser/components/aboutconfig/test/browser/browser_warning.js
+++ b/browser/components/aboutconfig/test/browser/browser_warning.js
@@ -25,14 +25,14 @@ add_task(async function test_showWarning
         this.bypassWarningButton.click();
       }
 
       // No results are shown after the warning page is dismissed or bypassed.
       this.assertWarningPage(false);
       Assert.ok(!this.prefsTable.firstElementChild);
       Assert.equal(this.document.activeElement, this.searchInput);
 
-      // Pressing ESC shows all results immediately.
-      EventUtils.sendKey("escape");
+      // The show all button should be present and show all results immediately.
+      this.showAll();
       Assert.ok(this.prefsTable.firstElementChild);
     }, { dontBypassWarning: true });
   }
 });
--- a/browser/components/aboutconfig/test/browser/head.js
+++ b/browser/components/aboutconfig/test/browser/head.js
@@ -93,17 +93,17 @@ class AboutConfigTest {
     this.document = browser.contentDocument;
     this.window = browser.contentWindow;
   }
 
   async setupNewTab(options) {
     await this.document.l10n.ready;
     if (!options.dontBypassWarning) {
       this.bypassWarningButton.click();
-      this.search();
+      this.showAll();
     }
   }
 
   get showWarningNextTimeInput() {
     return this.document.getElementById("showWarningNextTime");
   }
 
   get bypassWarningButton() {
@@ -130,20 +130,28 @@ class AboutConfigTest {
    * Returns the AboutConfigRowTest object for the row in the main table which
    * corresponds to the given preference name, or undefined if none is present.
    */
   getRow(name) {
     return this.rows.find(row => row.name == name);
   }
 
   /**
+   * Shows all preferences using the dedicated button.
+   */
+  showAll() {
+    this.search("");
+    this.document.getElementById("show-all").click();
+  }
+
+  /**
    * Performs a new search using the dedicated textbox. This also makes sure
    * that the list of preferences displayed is up to date.
    */
-  search(value = "") {
+  search(value) {
     this.searchInput.value = value;
     this.searchInput.focus();
     EventUtils.sendKey("return");
   }
 
   /**
    * Checks whether or not the initial warning page is displayed.
    */
--- a/browser/locales/en-US/browser/aboutConfig.ftl
+++ b/browser/locales/en-US/browser/aboutConfig.ftl
@@ -5,18 +5,19 @@
 # An old map warning, see https://en.wikipedia.org/wiki/Here_be_dragons
 about-config-warning-title = Here be dragons!
 about-config-warning-text = Changing these advanced settings can be harmful to the stability, security, and performance of this application. You should only continue if you are sure of what you are doing.
 about-config-warning-checkbox = Annoy me again, please!
 about-config-warning-button = I accept the risk
 
 about-config-title = about:config
 
-about-config-search =
-    .placeholder = Search or press ESC to show all
+about-config-search-input =
+    .placeholder = Search
+about-config-show-all = Show All
 
 about-config-pref-add = Add
 about-config-pref-toggle = Toggle
 about-config-pref-edit = Edit
 about-config-pref-save = Save
 about-config-pref-reset = Reset
 about-config-pref-delete = Delete
 
--- a/js/src/jit-test/tests/wasm/gc/tables-generalized.js
+++ b/js/src/jit-test/tests/wasm/gc/tables-generalized.js
@@ -401,8 +401,59 @@ for (let visibility of ['', '(export "t"
         `(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);
 }
+
+// JS API for growing the table can take a fill argument, defaulting to null
+
+let VALUES = [null,
+              undefined,
+              true,
+              false,
+              {x:1337},
+              ["abracadabra"],
+              1337,
+              13.37,
+              "hi",
+              Symbol("status"),
+              () => 1337];
+
+{
+    let t = new WebAssembly.Table({element:"anyref", initial:0});
+    t.grow(1);
+    assertEq(t.get(t.length-1), null);
+    let prev = null;
+    for (let v of VALUES) {
+        t.grow(2, v);
+        assertEq(t.get(t.length-3), prev);
+        assertEq(t.get(t.length-2), v);
+        assertEq(t.get(t.length-1), v);
+        prev = v;
+    }
+}
+
+{
+    let t = new WebAssembly.Table({element:"anyfunc", initial:0});
+    let ins = wasmEvalText(
+        `(module
+           (func (export "f") (param i32) (result i32)
+             (get_local 0)))`);
+    t.grow(1);
+    assertEq(t.get(t.length-1), null);
+    t.grow(2, ins.exports.f);
+    assertEq(t.get(t.length-3), null);
+    assertEq(t.get(t.length-2), ins.exports.f);
+    assertEq(t.get(t.length-1), ins.exports.f);
+}
+
+// If growing by zero elements there are no spurious writes
+
+{
+    let t = new WebAssembly.Table({element:"anyref", initial:1});
+    t.set(0, 1337);
+    t.grow(0, 1789);
+    assertEq(t.get(0), 1337);
+}
--- a/js/src/js.msg
+++ b/js/src/js.msg
@@ -396,16 +396,17 @@ MSG_DEF(JSMSG_WASM_INT_DIVIDE_BY_ZERO, 0
 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_DROPPED_DATA_SEG,   0, JSEXN_WASMRUNTIMEERROR, "use of dropped data segment")
 MSG_DEF(JSMSG_WASM_DROPPED_ELEM_SEG,   0, JSEXN_WASMRUNTIMEERROR, "use of dropped 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_BAD_TBL_GROW_INIT,  1, JSEXN_TYPEERR,     "bad initializer to {0} table")
 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\"")
--- a/js/src/tests/jstests.py
+++ b/js/src/tests/jstests.py
@@ -375,16 +375,17 @@ def load_wpt_tests(xul_tester, requested
             if tests:
                 yield item_type, path, tests
 
     run_info_extras = products.load_product(kwargs["config"], "firefox")[-1](**kwargs)
     run_info = wpttest.get_run_info(kwargs["test_paths"]["/"]["metadata_path"],
                                     "firefox",
                                     debug=xul_tester.test("isDebugBuild"),
                                     extras=run_info_extras)
+    run_info["release_or_beta"] = xul_tester.test("getBuildConfiguration().release_or_beta")
 
     path_filter = testloader.TestFilter(test_manifests,
                                         include=requested_paths,
                                         exclude=excluded_paths)
     loader = testloader.TestLoader(test_manifests,
                                    ["testharness"],
                                    run_info,
                                    manifest_filters=[path_filter, filter_jsshell_tests])
--- a/js/src/wasm/WasmConstants.h
+++ b/js/src/wasm/WasmConstants.h
@@ -559,17 +559,17 @@ 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 MaxTableLength = 10000000;
 static const unsigned MaxLocals = 50000;
 static const unsigned MaxParams = 1000;
 static const unsigned MaxStructFields = 1000;
 static const unsigned MaxMemoryMaximumPages = 65536;
 static const unsigned MaxStringBytes = 100000;
 static const unsigned MaxModuleBytes = 1024 * 1024 * 1024;
 static const unsigned MaxFunctionBytes = 7654321;
 
--- a/js/src/wasm/WasmJS.cpp
+++ b/js/src/wasm/WasmJS.cpp
@@ -2086,17 +2086,17 @@ bool WasmTableObject::isNewborn() const 
 #else
     JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
                              JSMSG_WASM_BAD_ELEMENT);
 #endif
     return false;
   }
 
   Limits limits;
-  if (!GetLimits(cx, obj, MaxTableInitialLength, MaxTableMaximumLength, "Table",
+  if (!GetLimits(cx, obj, MaxTableInitialLength, MaxTableLength, "Table",
                  &limits, Shareable::False)) {
     return false;
   }
 
   RootedWasmTableObject table(cx,
                               WasmTableObject::create(cx, limits, tableKind));
   if (!table) {
     return false;
@@ -2188,74 +2188,88 @@ static bool ToTableIndex(JSContext* cx, 
 }
 
 /* static */ bool WasmTableObject::get(JSContext* cx, unsigned argc,
                                        Value* vp) {
   CallArgs args = CallArgsFromVp(argc, vp);
   return CallNonGenericMethod<IsTable, getImpl>(cx, args);
 }
 
+static void TableFunctionFill(JSContext* cx, Table* table, HandleFunction value,
+                              uint32_t index, uint32_t limit)
+{
+  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);
+#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();
+  while (index < limit) {
+    table->setAnyFunc(index++, code, &instance);
+  }
+}
+
 /* static */ bool WasmTableObject::setImpl(JSContext* cx,
                                            const CallArgs& args) {
   RootedWasmTableObject tableObj(
       cx, &args.thisv().toObject().as<WasmTableObject>());
   Table& table = tableObj->table();
 
   if (!args.requireAtLeast(cx, "WebAssembly.Table.set", 2)) {
     return false;
   }
 
   uint32_t index;
   if (!ToTableIndex(cx, args.get(0), table, "set index", &index)) {
     return false;
   }
 
+  RootedValue fillValue(cx, args[1]);
   switch (table.kind()) {
     case TableKind::AnyFunction: {
       RootedFunction value(cx);
-      if (!IsExportedFunction(args[1], &value) && !args[1].isNull()) {
+      if (!IsExportedFunction(fillValue, &value) && !fillValue.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);
-#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.setAnyFunc(index, code, &instance);
+        MOZ_ASSERT(index < MaxTableLength);
+        static_assert(MaxTableLength < UINT32_MAX, "Invariant");
+        TableFunctionFill(cx, &table, value, index, index + 1);
       } else {
         table.setNull(index);
       }
       break;
     }
     case TableKind::AnyRef: {
       RootedAnyRef tmp(cx, AnyRef::null());
-      if (!BoxAnyRef(cx, args[1], &tmp)) {
+      if (!BoxAnyRef(cx, fillValue, &tmp)) {
         return false;
       }
       table.setAnyRef(index, tmp);
       break;
     }
-    default: { MOZ_CRASH("Unexpected table kind"); }
-  }
+    default: {
+      MOZ_CRASH("Unexpected table kind");
+    }
+  }
+
 
   args.rval().setUndefined();
   return true;
 }
 
 /* static */ bool WasmTableObject::set(JSContext* cx, unsigned argc,
                                        Value* vp) {
   CallArgs args = CallArgsFromVp(argc, vp);
@@ -2271,25 +2285,78 @@ static bool ToTableIndex(JSContext* cx, 
     return false;
   }
 
   uint32_t delta;
   if (!EnforceRangeU32(cx, args.get(0), "Table", "grow delta", &delta)) {
     return false;
   }
 
-  uint32_t ret = table->table().grow(delta, cx);
-
-  if (ret == uint32_t(-1)) {
+  uint32_t oldLength = table->table().grow(delta, cx);
+
+  if (oldLength == uint32_t(-1)) {
     JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, JSMSG_WASM_BAD_GROW,
                              "table");
     return false;
   }
 
-  args.rval().setInt32(ret);
+  RootedValue fillValue(cx);
+  fillValue.setNull();
+  if (args.length() > 1) {
+    fillValue = args[1];
+  }
+
+  MOZ_ASSERT(delta <= MaxTableLength); // grow() should ensure this
+  MOZ_ASSERT(oldLength <= MaxTableLength - delta); // ditto
+
+  static_assert(MaxTableLength < UINT32_MAX, "Invariant");
+
+  switch (table->table().kind()) {
+    case TableKind::AnyFunction: {
+      RootedFunction value(cx);
+      if (fillValue.isNull()) {
+#ifdef DEBUG
+        for (uint32_t index = oldLength; index < oldLength + delta; index++) {
+          MOZ_ASSERT(table->table().getAnyFunc(index).code == nullptr);
+        }
+#endif
+      } else if (IsExportedFunction(fillValue, &value)) {
+        TableFunctionFill(cx, &table->table(), value, oldLength,
+                          oldLength + delta);
+      } else {
+        JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
+                                 JSMSG_WASM_BAD_TBL_GROW_INIT, "anyfunc");
+        return false;
+      }
+      break;
+    }
+    case TableKind::AnyRef: {
+      RootedAnyRef tmp(cx, AnyRef::null());
+      if (!BoxAnyRef(cx, fillValue, &tmp)) {
+        return false;
+      }
+      if (!tmp.get().isNull()) {
+        for (uint32_t index = oldLength; index < oldLength + delta; index++) {
+          table->table().setAnyRef(index, tmp);
+        }
+      } else {
+#ifdef DEBUG
+        for (uint32_t index = oldLength; index < oldLength + delta; index++) {
+          MOZ_ASSERT(table->table().getAnyRef(index).isNull());
+        }
+#endif
+      }
+      break;
+    }
+    default: {
+      MOZ_CRASH("Unexpected table kind");
+    }
+  }
+
+  args.rval().setInt32(oldLength);
   return true;
 }
 
 /* static */ bool WasmTableObject::grow(JSContext* cx, unsigned argc,
                                         Value* vp) {
   CallArgs args = CallArgsFromVp(argc, vp);
   return CallNonGenericMethod<IsTable, growImpl>(cx, args);
 }
--- a/js/src/wasm/WasmTable.cpp
+++ b/js/src/wasm/WasmTable.cpp
@@ -243,17 +243,17 @@ uint32_t Table::grow(uint32_t delta, JSC
   if (!delta) {
     return length_;
   }
 
   uint32_t oldLength = length_;
 
   CheckedInt<uint32_t> newLength = oldLength;
   newLength += delta;
-  if (!newLength.isValid()) {
+  if (!newLength.isValid() || newLength.value() > MaxTableLength) {
     return -1;
   }
 
   if (maximum_ && newLength.value() > maximum_.value()) {
     return -1;
   }
 
   MOZ_ASSERT(movingGrowable());
--- a/js/src/wasm/WasmValidate.cpp
+++ b/js/src/wasm/WasmValidate.cpp
@@ -1582,18 +1582,19 @@ static bool DecodeTableTypeAndLimits(Dec
     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)))
+        limits.maximum.value() > MaxTableLength))) {
     return d.fail("too many table elements");
+  }
 
   if (tables->length() >= MaxTables) {
     return d.fail("too many tables");
   }
 
   return tables->emplaceBack(tableKind, limits);
 }
 
--- a/testing/web-platform/tests/wasm/jsapi/table/grow.any.js
+++ b/testing/web-platform/tests/wasm/jsapi/table/grow.any.js
@@ -85,12 +85,12 @@ for (const value of outOfRangeValues) {
   }, `Out-of-range argument: ${format_value(value)}`);
 }
 
 test(() => {
   const argument = { "element": "anyfunc", "initial": 5 };
   const table = new WebAssembly.Table(argument);
   assert_equal_to_array(table, nulls(5), "before");
 
-  const result = table.grow(3, {});
+  const result = table.grow(3, null, {});
   assert_equals(result, 5);
   assert_equal_to_array(table, nulls(8), "after");
 }, "Stray argument");