Merge inbound to mozilla-central. a=merge
authorBrindusan Cristian <cbrindusan@mozilla.com>
Mon, 18 Feb 2019 23:45:23 +0200
changeset 459806 ca3d40c83ae7
parent 459800 c590fd06f7cb (current diff)
parent 459805 4a7c9fc1c794 (diff)
child 459810 3d2830a2b70e
child 459826 10bef0ce2b5b
push id35573
push usercbrindusan@mozilla.com
push dateMon, 18 Feb 2019 21:45:58 +0000
treeherdermozilla-central@ca3d40c83ae7 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone67.0a1
first release with
nightly linux32
ca3d40c83ae7 / 67.0a1 / 20190218214558 / files
nightly linux64
ca3d40c83ae7 / 67.0a1 / 20190218214558 / files
nightly mac
ca3d40c83ae7 / 67.0a1 / 20190218214558 / files
nightly win32
ca3d40c83ae7 / 67.0a1 / 20190218214558 / files
nightly win64
ca3d40c83ae7 / 67.0a1 / 20190218214558 / files
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
releases
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge inbound to mozilla-central. a=merge
--- 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");