Merge mozilla-central to autoland. a=merge CLOSED TREE
authorBogdan Tara <btara@mozilla.com>
Sat, 22 Sep 2018 01:04:59 +0300
changeset 493487 6c1f0d449eca8d1903d6878c2aa5d987f1d790a4
parent 493486 df8cf3e01392d1c2ef27ae0a37fe76ffcb025a4e (current diff)
parent 493472 eddbfdc38cbc0d25be44ea92588126db3d0b9a78 (diff)
child 493488 fb218472a84096406d16026ee8e9eebbeb789534
push id9984
push userffxbld-merge
push dateMon, 15 Oct 2018 21:07:35 +0000
treeherdermozilla-beta@183d27ea8570 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone64.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/payments/res/containers/address-form.css
+++ b/browser/components/payments/res/containers/address-form.css
@@ -1,55 +1,51 @@
 /* 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/. */
 
-.error-text:not(:empty) {
+.error-text {
   color: #fff;
   background-color: #d70022;
   border-radius: 2px;
-  /* The padding-top and padding-bottom are referenced by address-form.js */
+  margin: 5px 3px 0 3px;
+  /* The padding-top and padding-bottom are referenced by address-form.js */ /* TODO */
   padding: 5px 12px;
   position: absolute;
   z-index: 1;
   pointer-events: none;
-}
-
-body[dir="ltr"] .error-text {
-  left: 3px;
+  top: 100%;
+  visibility: hidden;
 }
 
-body[dir="rtl"] .error-text {
-  right: 3px;
-}
-
-:-moz-any(input, textarea, select):focus ~ .error-text:not(:empty)::before {
+/* ::before is the error on the error text panel */
+:-moz-any(input, textarea, select) ~ .error-text::before {
   background-color: #d70022;
   top: -7px;
   content: '.';
   height: 16px;
   position: absolute;
   text-indent: -999px;
   transform: rotate(45deg);
   white-space: nowrap;
   width: 16px;
   z-index: -1
 }
 
-body[dir=ltr] .error-text::before {
+/* Position the arrow */
+.error-text:dir(ltr)::before {
   left: 12px
 }
 
-body[dir=rtl] .error-text::before {
+.error-text:dir(rtl)::before {
   right: 12px
 }
 
-:-moz-any(input, textarea, select):not(:focus) ~ .error-text,
-:-moz-any(input, textarea, select):valid ~ .error-text {
-  display: none;
+:-moz-any(input, textarea, select):-moz-ui-invalid:focus ~ .error-text {
+  visibility: visible;
 }
 
 address-form > footer > .cancel-button {
   /* When cancel is shown (during onboarding), it should always be on the left with a space after it */
   margin-right: auto;
 }
 
 address-form > footer > .back-button {
--- a/browser/components/payments/res/containers/address-form.js
+++ b/browser/components/payments/res/containers/address-form.js
@@ -1,14 +1,15 @@
 /* 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/. */
 
 /* import-globals-from ../../../../../browser/extensions/formautofill/content/autofillEditForms.js*/
 import LabelledCheckbox from "../components/labelled-checkbox.js";
+import PaymentDialog from "./payment-dialog.js";
 import PaymentRequestPage from "../components/payment-request-page.js";
 import PaymentStateSubscriberMixin from "../mixins/PaymentStateSubscriberMixin.js";
 import paymentRequest from "../paymentRequest.js";
 /* import-globals-from ../unprivileged-fallbacks.js */
 
 /**
  * <address-form></address-form>
  *
@@ -92,16 +93,22 @@ export default class AddressForm extends
 
       // The EditAddress constructor adds `input` event listeners on the same element,
       // which update field validity. By adding our event listeners after this constructor,
       // validity will be updated before our handlers get the event
       this.form.addEventListener("input", this);
       this.form.addEventListener("invalid", this);
       this.form.addEventListener("change", this);
 
+      // The "invalid" event does not bubble and needs to be listened for on each
+      // form element.
+      for (let field of this.form.elements) {
+        field.addEventListener("invalid", this);
+      }
+
       this.body.appendChild(this.persistCheckbox);
       this.body.appendChild(this.genericErrorText);
 
       this.footer.appendChild(this.cancelButton);
       this.footer.appendChild(this.backButton);
       this.footer.appendChild(this.saveButton);
       // Only call the connected super callback(s) once our markup is fully
       // connected, including the shared form fetched asynchronously.
@@ -171,50 +178,21 @@ export default class AddressForm extends
     // Add validation to some address fields
     this.updateRequiredState();
 
     let shippingAddressErrors = request.paymentDetails.shippingAddressErrors;
     for (let [errorName, errorSelector] of Object.entries(this._errorFieldMap)) {
       let container = this.form.querySelector(errorSelector + "-container");
       let field = this.form.querySelector(errorSelector);
       let errorText = (shippingAddressErrors && shippingAddressErrors[errorName]) || "";
-      container.classList.toggle("error", !!errorText);
       field.setCustomValidity(errorText);
-      let span = container.querySelector(".error-text");
-      if (!span) {
-        span = document.createElement("span");
-        span.className = "error-text";
-        container.appendChild(span);
-      }
+      let span = PaymentDialog.maybeCreateFieldErrorElement(container);
       span.textContent = errorText;
     }
 
-    // Position the error messages all at once so layout flushes only once.
-    let formRect = this.form.getBoundingClientRect();
-    let errorSpanData = [...this.form.querySelectorAll(".error-text:not(:empty)")].map(span => {
-      let relatedInput = span.parentNode.querySelector("input, textarea, select");
-      let relatedRect = relatedInput.getBoundingClientRect();
-      return {
-        span,
-        top: relatedRect.height,
-        left: relatedRect.left - formRect.left,
-        right: formRect.right - relatedRect.right,
-      };
-    });
-    let isRTL = this.form.matches(":dir(rtl)");
-    for (let data of errorSpanData) {
-      // Add 10px for the padding-top and padding-bottom.
-      data.span.style.top = (data.top + 10) + "px";
-      if (isRTL) {
-        data.span.style.right = data.right + "px";
-      } else {
-        data.span.style.left = data.left + "px";
-      }
-    }
-
     this.updateSaveButtonState();
   }
 
   handleEvent(event) {
     switch (event.type) {
       case "change": {
         this.updateSaveButtonState();
         break;
@@ -223,17 +201,22 @@ export default class AddressForm extends
         this.onClick(event);
         break;
       }
       case "input": {
         this.onInput(event);
         break;
       }
       case "invalid": {
-        this.onInvalid(event);
+        if (event.target instanceof HTMLFormElement) {
+          this.onInvalidForm(event);
+          break;
+        }
+
+        this.onInvalidField(event);
         break;
       }
     }
   }
 
   onClick(evt) {
     switch (evt.target) {
       case this.cancelButton: {
@@ -264,30 +247,32 @@ export default class AddressForm extends
       }
       default: {
         throw new Error("Unexpected click target");
       }
     }
   }
 
   onInput(event) {
-    let container = event.target.closest(`#${event.target.id}-container`);
-    if (container) {
-      container.classList.remove("error");
-      event.target.setCustomValidity("");
-
-      let span = container.querySelector(".error-text");
-      if (span) {
-        span.textContent = "";
-      }
-    }
+    event.target.setCustomValidity("");
     this.updateSaveButtonState();
   }
 
-  onInvalid(event) {
+  /**
+   * @param {Event} event - "invalid" event
+   * Note: Keep this in-sync with the equivalent version in basic-card-form.js
+   */
+  onInvalidField(event) {
+    let field = event.target;
+    let container = field.closest(`#${field.id}-container`);
+    let errorTextSpan = PaymentDialog.maybeCreateFieldErrorElement(container);
+    errorTextSpan.textContent = field.validationMessage;
+  }
+
+  onInvalidForm() {
     this.saveButton.disabled = true;
   }
 
   updateRequiredState() {
     for (let field of this.form.elements) {
       let container = field.closest(`#${field.id}-container`);
       if (field.localName == "button" || !container) {
         continue;
--- a/browser/components/payments/res/containers/basic-card-form.js
+++ b/browser/components/payments/res/containers/basic-card-form.js
@@ -1,15 +1,16 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 /* import-globals-from ../../../../../browser/extensions/formautofill/content/autofillEditForms.js*/
 import AcceptedCards from "../components/accepted-cards.js";
 import LabelledCheckbox from "../components/labelled-checkbox.js";
+import PaymentDialog from "./payment-dialog.js";
 import PaymentRequestPage from "../components/payment-request-page.js";
 import PaymentStateSubscriberMixin from "../mixins/PaymentStateSubscriberMixin.js";
 import paymentRequest from "../paymentRequest.js";
 
 /* import-globals-from ../unprivileged-fallbacks.js */
 
 /**
  * <basic-card-form></basic-card-form>
@@ -92,16 +93,22 @@ export default class BasicCardForm exten
 
       // The EditCreditCard constructor adds `change` and `input` event listeners on the same
       // element, which update field validity. By adding our event listeners after this
       // constructor, validity will be updated before our handlers get the event
       form.addEventListener("change", this);
       form.addEventListener("input", this);
       form.addEventListener("invalid", this);
 
+      // The "invalid" event does not bubble and needs to be listened for on each
+      // form element.
+      for (let field of this.form.elements) {
+        field.addEventListener("invalid", this);
+      }
+
       let fragment = document.createDocumentFragment();
       fragment.append(this.addressAddLink);
       fragment.append(" ");
       fragment.append(this.addressEditLink);
       let billingAddressRow = this.form.querySelector(".billingAddressRow");
       billingAddressRow.appendChild(fragment);
 
       this.body.appendChild(this.persistCheckbox);
@@ -217,17 +224,22 @@ export default class BasicCardForm exten
         this.onClick(event);
         break;
       }
       case "input": {
         this.onInput(event);
         break;
       }
       case "invalid": {
-        this.onInvalid(event);
+        if (event.target instanceof HTMLFormElement) {
+          this.onInvalidForm(event);
+          break;
+        }
+
+        this.onInvalidField(event);
         break;
       }
     }
   }
 
   onChange(evt) {
     this.updateSaveButtonState();
   }
@@ -314,20 +326,32 @@ export default class BasicCardForm exten
       }
       default: {
         throw new Error("Unexpected click target");
       }
     }
   }
 
   onInput(event) {
+    event.target.setCustomValidity("");
     this.updateSaveButtonState();
   }
 
-  onInvalid(event) {
+  /**
+   * @param {Event} event - "invalid" event
+   * Note: Keep this in-sync with the equivalent version in address-form.js
+   */
+  onInvalidField(event) {
+    let field = event.target;
+    let container = field.closest(`#${field.id}-container`);
+    let errorTextSpan = PaymentDialog.maybeCreateFieldErrorElement(container);
+    errorTextSpan.textContent = field.validationMessage;
+  }
+
+  onInvalidForm() {
     this.saveButton.disabled = true;
   }
 
   updateSaveButtonState() {
     this.saveButton.disabled = !this.form.checkValidity();
   }
 
   updateRequiredState() {
--- a/browser/components/payments/res/containers/payment-dialog.js
+++ b/browser/components/payments/res/containers/payment-dialog.js
@@ -377,11 +377,21 @@ export default class PaymentDialog exten
     if (state.changesPrevented) {
       this.setAttribute("changes-prevented", "");
     } else {
       this.removeAttribute("changes-prevented");
     }
     this.setAttribute("complete-status", request.completeStatus);
     this._disabledOverlay.hidden = !state.changesPrevented;
   }
+
+  static maybeCreateFieldErrorElement(container) {
+    let span = container.querySelector(".error-text");
+    if (!span) {
+      span = document.createElement("span");
+      span.className = "error-text";
+      container.appendChild(span);
+    }
+    return span;
+  }
 }
 
 customElements.define("payment-dialog", PaymentDialog);
--- a/browser/components/payments/res/containers/payment-method-picker.js
+++ b/browser/components/payments/res/containers/payment-method-picker.js
@@ -15,16 +15,19 @@ import paymentRequest from "../paymentRe
 export default class PaymentMethodPicker extends RichPicker {
   constructor() {
     super();
     this.dropdown.setAttribute("option-type", "basic-card-option");
     this.securityCodeInput = document.createElement("input");
     this.securityCodeInput.autocomplete = "off";
     this.securityCodeInput.placeholder = this.dataset.cvvPlaceholder;
     this.securityCodeInput.size = 3;
+    this.securityCodeInput.required = true;
+    // 3 or more digits
+    this.securityCodeInput.pattern = "[0-9]{3,}";
     this.securityCodeInput.classList.add("security-code");
     this.securityCodeInput.addEventListener("change", this);
   }
 
   connectedCallback() {
     super.connectedCallback();
     this.dropdown.after(this.securityCodeInput);
   }
--- a/browser/components/payments/res/containers/rich-picker.css
+++ b/browser/components/payments/res/containers/rich-picker.css
@@ -23,17 +23,17 @@
 }
 
 .rich-picker > .add-link {
   grid-area: add;
 }
 
 .rich-picker > .edit-link {
   grid-area: edit;
-  border-right: 1px solid #0C0C0D33;
+  border-inline-end: 1px solid #0C0C0D33;
 }
 
 .rich-picker > rich-select {
   grid-area: dropdown;
 }
 
 .invalid-selected-option > rich-select > select {
   border: 1px solid #c70011;
@@ -55,13 +55,15 @@ payment-method-picker.rich-picker {
   grid-template-areas:
     "label    spacer edit add"
     "dropdown cvv    cvv  cvv"
     "invalid  invalid invalid invalid";
 }
 
 payment-method-picker > input {
   border: 1px solid #0C0C0D33;
-  border-left: none;
+  border-inline-start: none;
   grid-area: cvv;
   margin: 14px 0; /* Has to be same as rich-select */
   padding: 8px;
+  /* So the error outline appears above the adjacent dropdown */
+  z-index: 1;
 }
--- a/browser/components/payments/res/debugging.html
+++ b/browser/components/payments/res/debugging.html
@@ -11,16 +11,17 @@
   </head>
   <body>
     <div>
       <section class="group">
         <button id="refresh">Refresh</button>
         <button id="rerender">Re-render</button>
         <button id="logState">Log state</button>
         <button id="debugFrame" hidden>Debug frame</button>
+        <button id="toggleDirectionality">Toggle :dir</button>
       </section>
       <section class="group">
         <h1>Requests</h1>
         <button id="setRequest1">Request 1</button>
         <button id="setRequest2">Request 2</button>
         <fieldset id="paymentOptions">
           <legend>Payment Options</legend>
           <label><input type="checkbox" autocomplete="off" name="requestPayerName" id="setRequestPayerName">requestPayerName</label>
--- a/browser/components/payments/res/debugging.js
+++ b/browser/components/payments/res/debugging.js
@@ -509,16 +509,21 @@ let buttonActions = {
   setCompleteStatus() {
     let input = document.querySelector("[name='setCompleteStatus']:checked");
     let completeStatus = input.value;
     let request = requestStore.getState().request;
     paymentDialog.setStateFromParent({
       request: Object.assign({}, request, { completeStatus }),
     });
   },
+
+  toggleDirectionality() {
+    let body = paymentDialog.ownerDocument.body;
+    body.dir = body.dir == "rtl" ? "ltr" : "rtl";
+  },
 };
 
 window.addEventListener("click", function onButtonClick(evt) {
   let id = evt.target.id || evt.target.name;
   if (!id || typeof(buttonActions[id]) != "function") {
     return;
   }
 
--- a/browser/components/payments/res/paymentRequest.css
+++ b/browser/components/payments/res/paymentRequest.css
@@ -140,17 +140,17 @@ payment-dialog > header > .page-error {
   flex: 0 1 auto;
 }
 
 payment-dialog #pay::before {
   -moz-context-properties: fill;
   content: url(chrome://browser/skin/connection-secure.svg);
   fill: currentColor;
   height: 16px;
-  margin-right: 0.5em;
+  margin-inline-end: 0.5em;
   vertical-align: text-bottom;
   width: 16px;
 }
 
 payment-dialog[changes-prevented][complete-status="fail"] #pay,
 payment-dialog[changes-prevented][complete-status="unknown"] #pay,
 payment-dialog[changes-prevented][complete-status="processing"] #pay,
 payment-dialog[changes-prevented][complete-status="success"] #pay {
--- a/browser/components/payments/test/browser/browser_card_edit.js
+++ b/browser/components/payments/test/browser/browser_card_edit.js
@@ -1,12 +1,14 @@
 /* eslint-disable no-shadow */
 
 "use strict";
 
+requestLongerTimeout(2);
+
 async function setup(addresses = [], cards = []) {
   await setupFormAutofillStorage();
   await cleanupFormAutofillStorage();
   let prefilledGuids = await addSampleAddressesAndBasicCard(addresses, cards);
   return prefilledGuids;
 }
 
 async function add_link(aOptions = {}) {
--- a/browser/components/payments/test/mochitest/mochitest.ini
+++ b/browser/components/payments/test/mochitest/mochitest.ini
@@ -9,16 +9,17 @@ support-files =
    ../../../../../testing/modules/sinon-2.3.2.js
    ../../res/**
    payments_common.js
 skip-if = !e10s
 
 [test_accepted_cards.html]
 [test_address_form.html]
 [test_address_option.html]
+skip-if = os == "linux" # Bug 1490077 comment 7
 [test_address_picker.html]
 [test_basic_card_form.html]
 [test_basic_card_option.html]
 [test_completion_error_page.html]
 [test_currency_amount.html]
 [test_labelled_checkbox.html]
 [test_order_details.html]
 [test_payer_address_picker.html]
--- a/browser/components/payments/test/mochitest/test_address_form.html
+++ b/browser/components/payments/test/mochitest/test_address_form.html
@@ -513,12 +513,53 @@ add_task(async function test_field_valid
   ok(!addressLevel1Input.value, "address-level1 should be empty by default");
   ok(postalCodeInput.checkValidity(),
      "postal-code should be valid by default when it is not visible");
   ok(addressLevel1Input.checkValidity(),
      "address-level1 should be valid by default when it is not visible");
 
   form.remove();
 });
+
+add_task(async function test_field_validation_dom_errors() {
+  let form = new AddressForm();
+  await form.promiseReady;
+  const state = {
+    page: {
+      id: "address-page",
+    },
+    "address-page": {
+      title: "Sample page title",
+    },
+  };
+  await form.requestStore.setState(state);
+  display.appendChild(form);
+  await asyncElementRendered();
+
+  const BAD_POSTAL_CODE = "hi mom";
+  let postalCode = document.getElementById("postal-code");
+  postalCode.focus();
+  sendString(BAD_POSTAL_CODE, window);
+  postalCode.blur();
+  let errorTextSpan = postalCode.parentNode.querySelector(".error-text");
+  is(errorTextSpan.textContent, "Please match the requested format.",
+     "DOM validation messages should be reflected in the error-text #1");
+
+  postalCode.focus();
+  while (postalCode.value) {
+    sendKey("BACK_SPACE", window);
+  }
+  postalCode.blur();
+  is(errorTextSpan.textContent, "Please fill out this field.",
+     "DOM validation messages should be reflected in the error-text #2");
+
+  postalCode.focus();
+  sendString("12345", window);
+  is(errorTextSpan.innerText, "", "DOM validation message should be removed when no error");
+  postalCode.blur();
+
+  form.remove();
+});
+
 </script>
 
 </body>
 </html>
--- a/browser/components/payments/test/mochitest/test_basic_card_form.html
+++ b/browser/components/payments/test/mochitest/test_basic_card_form.html
@@ -15,17 +15,19 @@ Test the basic-card-form element
   <script src="../../res/unprivileged-fallbacks.js"></script>
   <script src="autofillEditForms.js"></script>
 
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
   <link rel="stylesheet" type="text/css" href="../../res/paymentRequest.css"/>
   <link rel="stylesheet" type="text/css" href="../../res/components/accepted-cards.css"/>
 </head>
 <body>
-  <p id="display">
+  <p id="display" style="height: 100vh; margin: 0;">
+    <iframe id="templateFrame" src="../../res/paymentRequest.xhtml" width="0" height="0"
+            style="float: left;"></iframe>
   </p>
 <div id="content" style="display: none">
 
 </div>
 <pre id="test">
 </pre>
 <script type="module">
 /** Test the basic-card-form element **/
@@ -48,16 +50,23 @@ function checkCCForm(customEl, expectedC
   for (let propName of CC_PROPERTY_NAMES) {
     let expectedVal = expectedCard[propName] || "";
     is(document.getElementById(propName).value,
        expectedVal.toString(),
        `Check ${propName}`);
   }
 }
 
+add_task(async function setup_once() {
+  let templateFrame = document.getElementById("templateFrame");
+  await SimpleTest.promiseFocus(templateFrame.contentWindow);
+  let displayEl = document.getElementById("display");
+  importDialogDependencies(templateFrame, displayEl);
+});
+
 add_task(async function test_initialState() {
   let form = new BasicCardForm();
   let {page} = form.requestStore.getState();
   is(page.id, "payment-summary", "Check initial page");
   await form.promiseReady;
   display.appendChild(form);
   await asyncElementRendered();
   is(page.id, "payment-summary", "Check initial page after appending");
@@ -138,16 +147,17 @@ add_task(async function test_saveButton(
   is(fieldsVisiblyInvalid[0].id, "cc-number", "Check #cc-number is visibly invalid");
 
   fillField(form.form.querySelector("#cc-number"), "4111 1111-1111 1111");
   is(form.querySelectorAll(":-moz-ui-invalid").length, 0, "Check no fields visibly invalid");
   ok(!form.saveButton.disabled, "Save button is enabled after re-filling cc-number");
 
   let messagePromise = promiseContentToChromeMessage("updateAutofillRecord");
   is(form.saveButton.textContent, "Add", "Check label");
+  form.saveButton.scrollIntoView();
   synthesizeMouseAtCenter(form.saveButton, {});
 
   let details = await messagePromise;
   ok(typeof(details.messageID) == "number" && details.messageID > 0, "Check messageID type");
   delete details.messageID;
   is(details.collectionName, "creditCards", "Check collectionName");
   isDeeply(details, {
     collectionName: "creditCards",
--- a/browser/components/urlbar/UrlbarInput.jsm
+++ b/browser/components/urlbar/UrlbarInput.jsm
@@ -29,21 +29,22 @@ class UrlbarInput {
    *   The <panel> element.
    * @param {UrlbarController} [options.controller]
    *   Optional fake controller to override the built-in UrlbarController.
    *   Intended for use in unit tests only.
    */
   constructor(options = {}) {
     this.textbox = options.textbox;
     this.panel = options.panel;
+    this.window = this.textbox.ownerGlobal;
     this.controller = options.controller || new UrlbarController();
     this.view = new UrlbarView(this);
     this.valueIsTyped = false;
     this.userInitiatedFocus = false;
-    this.isPrivate = PrivateBrowsingUtils.isWindowPrivate(this.panel.ownerGlobal);
+    this.isPrivate = PrivateBrowsingUtils.isWindowPrivate(this.window);
 
     const METHODS = ["addEventListener", "removeEventListener",
       "setAttribute", "hasAttribute", "removeAttribute", "getAttribute",
       "focus", "blur", "select"];
     const READ_ONLY_PROPERTIES = ["focused", "inputField", "editor"];
     const READ_WRITE_PROPERTIES = ["value", "placeholder", "readOnly",
       "selectionStart", "selectionEnd"];
 
@@ -70,16 +71,19 @@ class UrlbarInput {
         },
         set(val) {
           return this.textbox[property] = val;
         },
       });
     }
 
     this.addEventListener("input", this);
+    this.inputField.addEventListener("overflow", this);
+    this.inputField.addEventListener("underflow", this);
+    this.inputField.addEventListener("scrollend", this);
   }
 
   formatValue() {
   }
 
   closePopup() {
     this.view.close();
   }
@@ -99,18 +103,65 @@ class UrlbarInput {
       this[methodName](event);
     } else {
       throw "Unrecognized urlbar event: " + event.type;
     }
   }
 
   // Private methods below.
 
+  _updateTextOverflow() {
+    if (!this._inOverflow) {
+      this.removeAttribute("textoverflow");
+      return;
+    }
+
+    this.window.promiseDocumentFlushed(() => {
+      // Check overflow again to ensure it didn't change in the meantime.
+      let input = this.inputField;
+      if (input && this._inOverflow) {
+        let side = input.scrollLeft &&
+                   input.scrollLeft == input.scrollLeftMax ? "start" : "end";
+        this.setAttribute("textoverflow", side);
+      }
+    });
+  }
+
+  // Event handlers below.
+
   _oninput(event) {
     // XXX Fill in lastKey & maxResults, and add anything else we need.
     this.controller.handleQuery(new QueryContext({
       searchString: event.target.value,
       lastKey: "",
       maxResults: 12,
       isPrivate: this.isPrivate,
     }));
   }
+
+  _onoverflow(event) {
+    const targetIsPlaceholder =
+      !event.originalTarget.classList.contains("anonymous-div");
+    // We only care about the non-placeholder text.
+    // This shouldn't be needed, see bug 1487036.
+    if (targetIsPlaceholder) {
+      return;
+    }
+    this._inOverflow = true;
+    this._updateTextOverflow();
+  }
+
+  _onunderflow(event) {
+    const targetIsPlaceholder =
+      !event.originalTarget.classList.contains("anonymous-div");
+    // We only care about the non-placeholder text.
+    // This shouldn't be needed, see bug 1487036.
+    if (targetIsPlaceholder) {
+      return;
+    }
+    this._inOverflow = false;
+    this._updateTextOverflow();
+  }
+
+  _onscrollend(event) {
+    this._updateTextOverflow();
+  }
 }
--- a/browser/components/urlbar/tests/unit/test_UrlbarInput_unit.js
+++ b/browser/components/urlbar/tests/unit/test_UrlbarInput_unit.js
@@ -71,18 +71,20 @@ function checkHandleQueryCall(stub, expe
 add_task(function setup() {
   sandbox = sinon.sandbox.create();
 
   fakeController = new UrlbarController();
 
   sandbox.stub(fakeController, "handleQuery");
   sandbox.stub(PrivateBrowsingUtils, "isWindowPrivate").returns(false);
 
+  let textbox = createFakeElement();
+  textbox.inputField = createFakeElement();
   inputOptions = {
-    textbox: createFakeElement(),
+    textbox,
     panel: {
       ownerDocument: {},
       querySelector() {
         return createFakeElement();
       },
     },
     controller: fakeController,
   };
--- a/browser/themes/shared/tabs.inc.css
+++ b/browser/themes/shared/tabs.inc.css
@@ -697,17 +697,17 @@
   content: "";
   display: -moz-box;
 }
 
 /* Tab bar scroll arrows */
 
 .tabbrowser-arrowscrollbox > .scrollbutton-up,
 .tabbrowser-arrowscrollbox > .scrollbutton-down {
-  list-style-image: url(chrome://browser/skin/arrow-left.svg);
+  list-style-image: url(chrome://browser/skin/arrow-left.svg) !important;
   -moz-context-properties: fill, fill-opacity;
   fill: currentColor;
   fill-opacity: var(--toolbarbutton-icon-fill-opacity);
   color: inherit;
 }
 
 .tabbrowser-arrowscrollbox > .scrollbutton-up:-moz-locale-dir(rtl),
 .tabbrowser-arrowscrollbox > .scrollbutton-down:-moz-locale-dir(ltr) {
--- a/browser/themes/shared/toolbarbutton-icons.inc.css
+++ b/browser/themes/shared/toolbarbutton-icons.inc.css
@@ -8,17 +8,17 @@
 
 :root[lwt-popup-brighttext] panel,
 toolbar[brighttext] {
   --toolbarbutton-icon-fill-attention: var(--lwt-toolbarbutton-icon-fill-attention, #45a1ff);
 }
 
 .toolbarbutton-animatable-box,
 .toolbarbutton-1 {
-  color: inherit;
+  color: inherit !important;
   -moz-context-properties: fill, fill-opacity;
   fill: var(--lwt-toolbarbutton-icon-fill, currentColor);
   fill-opacity: var(--toolbarbutton-icon-fill-opacity);
 }
 
 #back-button:-moz-locale-dir(rtl) > .toolbarbutton-icon,
 #forward-button:-moz-locale-dir(rtl) > .toolbarbutton-icon,
 #reload-button:-moz-locale-dir(rtl) > .toolbarbutton-icon,
--- a/browser/themes/shared/toolbarbuttons.inc.css
+++ b/browser/themes/shared/toolbarbuttons.inc.css
@@ -58,34 +58,34 @@ toolbar[brighttext] {
 :root:not([customizing]) .toolbarbutton-1[disabled=true],
 /* specialcase the overflow and the hamburger button so they show up disabled in customize mode. */
 #nav-bar-overflow-button[disabled=true],
 #PanelUI-menu-button[disabled=true] {
   opacity: 0.4;
 }
 
 .toolbarbutton-1 > .toolbarbutton-icon {
-  margin-inline-end: 0;
+  margin-inline-end: 0 !important;
 }
 
 .toolbarbutton-1 > .toolbarbutton-icon,
 .toolbarbutton-1 > .toolbarbutton-badge-stack > .toolbarbutton-icon {
   width: 16px;
 }
 
 #TabsToolbar .toolbarbutton-1,
 .tabbrowser-arrowscrollbox > .scrollbutton-up,
 .tabbrowser-arrowscrollbox > .scrollbutton-down {
-  margin: 0 0 @navbarTabsShadowSize@;
+  margin: 0 0 @navbarTabsShadowSize@ !important;
 }
 
 .tabbrowser-arrowscrollbox > .scrollbutton-up,
 .tabbrowser-arrowscrollbox > .scrollbutton-down {
   -moz-appearance: none;
-  padding: 0 var(--toolbarbutton-inner-padding);
+  padding: 0 var(--toolbarbutton-inner-padding) !important;
 }
 
 #navigator-toolbox:not(:hover) > #TabsToolbar > #tabbrowser-tabs > .tabbrowser-arrowscrollbox > .scrollbutton-down:not([highlight]) {
   transition: 1s background-color ease-out;
 }
 
 .tabbrowser-arrowscrollbox > .scrollbutton-down[highlight] {
   background-color: Highlight;
@@ -95,23 +95,23 @@ toolbar[brighttext] {
   -moz-appearance: none;
   padding: 0;
   color: inherit;
 }
 
 toolbar .toolbarbutton-1 {
   -moz-appearance: none;
   margin: 0;
-  padding: 0 var(--toolbarbutton-outer-padding);
+  padding: 0 var(--toolbarbutton-outer-padding) !important;
   -moz-box-pack: center;
 }
 
 :root:not([uidensity=compact]) #PanelUI-menu-button {
-  padding-inline-start: 5px;
-  padding-inline-end: 5px;
+  padding-inline-start: 5px !important;
+  padding-inline-end: 5px !important;
 }
 
 toolbar .toolbarbutton-1 > menupopup {
   margin-top: -3px;
 }
 
 .findbar-button > .toolbarbutton-text,
 toolbarbutton.bookmark-item:not(.subviewbutton),
@@ -144,18 +144,18 @@ toolbar .toolbarbutton-1 > .toolbarbutto
 }
 
 toolbar .toolbaritem-combined-buttons {
   margin-left: 2px;
   margin-right: 2px;
 }
 
 toolbar .toolbaritem-combined-buttons > .toolbarbutton-1 {
-  padding-left: 0;
-  padding-right: 0;
+  padding-left: 0 !important;
+  padding-right: 0 !important;
 }
 
 toolbar .toolbaritem-combined-buttons:not(:hover) > separator {
   content: "";
   display: -moz-box;
   width: 1px;
   height: 16px;
   margin-inline-end: -1px;
@@ -194,19 +194,19 @@ toolbar .toolbarbutton-1:not([disabled=t
 }
 
 toolbar .toolbarbutton-1[checked]:not(:active):hover > .toolbarbutton-icon {
   background-color: var(--toolbarbutton-hover-background);
   transition: background-color .4s;
 }
 
 :root:not([uidensity=compact]) #back-button {
-  padding-top: 3px;
-  padding-bottom: 3px;
-  padding-inline-start: 3px;
+  padding-top: 3px !important;
+  padding-bottom: 3px !important;
+  padding-inline-start: 3px !important;
   padding-inline-end: 0 !important;
   position: relative !important;
   z-index: 1 !important;
   border-radius: 0 10000px 10000px 0;
 }
 
 :root:not([uidensity=compact]) #back-button:-moz-locale-dir(rtl) {
   border-radius: 10000px 0 0 10000px;
@@ -225,19 +225,19 @@ toolbar .toolbarbutton-1[checked]:not(:a
   width: 34px;
   height: 34px;
   padding: 8px;
   transition-property: box-shadow;
   transition-duration: var(--toolbarbutton-hover-transition-duration);
 }
 
 :root[uidensity=touch] #back-button {
-  padding-top: 1px;
-  padding-bottom: 1px;
-  padding-inline-start: 1px;
+  padding-top: 1px !important;
+  padding-bottom: 1px !important;
+  padding-inline-start: 1px !important;
 }
 
 :root[uidensity=touch] #back-button > .toolbarbutton-icon {
   width: 38px;
   height: 38px;
   padding: 10px;
 }
 
@@ -338,11 +338,11 @@ toolbarbutton.bookmark-item {
 #PersonalToolbar .toolbarbutton-1 > .toolbarbutton-text,
 #PersonalToolbar .toolbarbutton-1 > .toolbarbutton-badge-stack {
   padding: 0 !important;
   background: none !important;
   min-height: 16px;
 }
 
 #PersonalToolbar .toolbarbutton-1 {
-  padding: 1px var(--toolbarbutton-inner-padding);
+  padding: 1px var(--toolbarbutton-inner-padding) !important;
   border-radius: var(--toolbarbutton-border-radius);
 }
--- a/dom/base/nsFocusManager.cpp
+++ b/dom/base/nsFocusManager.cpp
@@ -3289,27 +3289,28 @@ nsFocusManager::GetNextTabbableContentIn
         }
         iterContent = contentTraversal.GetCurrent();
       }
       if (!iterContent) {
         // Reach the end
         break;
       }
 
-      // Get the tab index of the next element. For NAC we rely on frames.
-      //XXXsmaug we should probably use frames also for Shadow DOM and special
-      //         case only display:contents elements.
       int32_t tabIndex = 0;
       if (iterContent->IsInNativeAnonymousSubtree() &&
           iterContent->GetPrimaryFrame()) {
         iterContent->GetPrimaryFrame()->IsFocusable(&tabIndex);
       } else if (IsHostOrSlot(iterContent)) {
         tabIndex = HostOrSlotTabIndexValue(iterContent);
       } else {
-        iterContent->IsFocusable(&tabIndex);
+        nsIFrame* frame = iterContent->GetPrimaryFrame();
+        if (!frame) {
+          continue;
+        }
+        frame->IsFocusable(&tabIndex, 0);
       }
       if (tabIndex < 0 || !(aIgnoreTabIndex || tabIndex == aCurrentTabIndex)) {
         // If the element has native anonymous content, we may need to
         // focus some NAC element, even if the element itself isn't focusable.
         // This happens for example with <input type="date">.
         // So, try to find NAC and then traverse the frame tree to find elements
         // to focus.
         nsIFrame* possibleAnonOwnerFrame = iterContent->GetPrimaryFrame();
--- a/dom/base/test/file_bug1453693.html
+++ b/dom/base/test/file_bug1453693.html
@@ -120,16 +120,20 @@
         var shadowAnchor = anchor.cloneNode(false);
         shadowAnchor.onfocus = focusLogger;
         shadowAnchor.textContent = "in shadow DOM";
         sr.appendChild(shadowAnchor);
         var shadowInput = document.createElement("input");
         shadowInput.onfocus = focusLogger;
         sr.appendChild(shadowInput);
 
+        var hiddenShadowButton = document.createElement("button");
+        hiddenShadowButton.setAttribute("style", "display: none;");
+        sr.appendChild(hiddenShadowButton);
+
         var input = document.createElement("input");
         input.onfocus = focusLogger;
         document.body.appendChild(input);
 
         var input2 = document.createElement("input");
         input2.onfocus = focusLogger;
         document.body.appendChild(input2);
 
--- a/dom/bindings/CallbackObject.h
+++ b/dom/bindings/CallbackObject.h
@@ -143,21 +143,16 @@ public:
     return CallbackPreserveColor();
   }
 
   nsIGlobalObject* IncumbentGlobalOrNull() const
   {
     return mIncumbentGlobal;
   }
 
-  void Reset()
-  {
-    ClearJSReferences();
-  }
-
   enum ExceptionHandling {
     // Report any exception and don't throw it to the caller code.
     eReportExceptions,
     // Throw an exception to the caller code if the thrown exception is a
     // binding object for a DOMException from the caller's scope, otherwise
     // report it.
     eRethrowContentExceptions,
     // Throw exceptions to the caller code, unless the caller realm is
--- a/js/src/builtin/TypedObject.cpp
+++ b/js/src/builtin/TypedObject.cpp
@@ -287,16 +287,55 @@ ScalarTypeDescr::call(JSContext* cx, uns
 #undef SCALARTYPE_CALL
       case Scalar::Int64:
       case Scalar::MaxTypedArrayViewType:
         MOZ_CRASH();
     }
     return true;
 }
 
+/* static */ TypeDescr*
+GlobalObject::getOrCreateScalarTypeDescr(JSContext* cx, Handle<GlobalObject*> global,
+                                         Scalar::Type scalarType)
+{
+    int32_t slot = 0;
+    switch (scalarType) {
+      case Scalar::Int32:   slot = TypedObjectModuleObject::Int32Desc; break;
+      case Scalar::Int64:   MOZ_CRASH("No Int64 support yet");
+      case Scalar::Float32: slot = TypedObjectModuleObject::Float32Desc; break;
+      case Scalar::Float64: slot = TypedObjectModuleObject::Float64Desc; break;
+      default:              MOZ_CRASH("NYI");
+    }
+
+    Rooted<TypedObjectModuleObject*> module(cx,
+        &GlobalObject::getOrCreateTypedObjectModule(cx, global)->as<TypedObjectModuleObject>());
+    if (!module) {
+       return nullptr;
+    }
+    return &module->getReservedSlot(slot).toObject().as<TypeDescr>();
+}
+
+/* static */ TypeDescr*
+GlobalObject::getOrCreateReferenceTypeDescr(JSContext* cx, Handle<GlobalObject*> global,
+                                            ReferenceType type)
+{
+    int32_t slot = 0;
+    switch (type) {
+      case ReferenceType::TYPE_OBJECT: slot = TypedObjectModuleObject::ObjectDesc; break;
+      default:                         MOZ_CRASH("NYI");
+    }
+
+    Rooted<TypedObjectModuleObject*> module(cx,
+        &GlobalObject::getOrCreateTypedObjectModule(cx, global)->as<TypedObjectModuleObject>());
+    if (!module) {
+       return nullptr;
+    }
+    return &module->getReservedSlot(slot).toObject().as<TypeDescr>();
+}
+
 /***************************************************************************
  * Reference type objects
  *
  * Reference type objects like `Any` or `Object` basically work the
  * same way that the scalar type objects do. There is one class with
  * many instances, and each instance has a reserved slot with a
  * TypeRepresentation object, which is used to distinguish which
  * reference type object this actually is.
@@ -791,17 +830,17 @@ StructMetaTypeDescr::create(JSContext* c
     }
 
     // Iterate through each field. Collect values for the various
     // vectors below and also track total size and alignment. Be wary
     // of overflow!
     AutoValueVector fieldTypeObjs(cx); // Type descriptor of each field.
     bool opaque = false;               // Opacity of struct.
 
-    Vector<bool> fieldMutabilities(cx);
+    Vector<StructFieldProps> fieldProps(cx);
 
     RootedValue fieldTypeVal(cx);
     RootedId id(cx);
     Rooted<TypeDescr*> fieldType(cx);
 
     for (unsigned int i = 0; i < ids.length(); i++) {
         id = ids[i];
 
@@ -825,43 +864,45 @@ StructMetaTypeDescr::create(JSContext* c
         }
 
         // Collect field type object
         if (!fieldTypeObjs.append(ObjectValue(*fieldType))) {
             return nullptr;
         }
 
         // Along this path everything is mutable
-        if (!fieldMutabilities.append(true)) {
+        StructFieldProps props;
+        props.isMutable = true;
+        if (!fieldProps.append(props)) {
             return nullptr;
         }
 
         // Struct is opaque if any field is opaque
         if (fieldType->opaque()) {
             opaque = true;
         }
     }
 
     RootedObject structTypePrototype(cx, GetPrototype(cx, metaTypeDescr));
     if (!structTypePrototype) {
         return nullptr;
     }
 
     return createFromArrays(cx, structTypePrototype, opaque, /* allowConstruct= */ true, ids,
-                            fieldTypeObjs, fieldMutabilities);
+                            fieldTypeObjs, fieldProps);
 }
 
 /* static */ StructTypeDescr*
 StructMetaTypeDescr::createFromArrays(JSContext* cx,
                                       HandleObject structTypePrototype,
                                       bool opaque,
                                       bool allowConstruct,
                                       AutoIdVector& ids,
                                       AutoValueVector& fieldTypeObjs,
-                                      Vector<bool>& fieldMutabilities)
+                                      Vector<StructFieldProps>& fieldProps)
 {
     StringBuffer stringBuffer(cx);     // Canonical string repr
     AutoValueVector fieldNames(cx);    // Name of each field.
     AutoValueVector fieldOffsets(cx);  // Offset of each field field.
     AutoValueVector fieldMuts(cx);     // Mutability of each field.
     RootedObject userFieldOffsets(cx); // User-exposed {f:offset} object
     RootedObject userFieldTypes(cx);   // User-exposed {f:descr} object.
     Layout layout;                     // Field offsetter
@@ -910,27 +951,29 @@ StructMetaTypeDescr::createFromArrays(JS
         }
         if (!stringBuffer.append(": ")) {
             return nullptr;
         }
         if (!stringBuffer.append(&fieldType->stringRepr())) {
             return nullptr;
         }
 
-        CheckedInt32 offset = layout.addField(fieldType->alignment(), fieldType->size());
+        CheckedInt32 offset = layout.addField(fieldProps[i].alignAsInt64
+                                              ? ScalarTypeDescr::alignment(Scalar::Int64)
+                                              : fieldType->alignment(),
+                                              fieldType->size());
         if (!offset.isValid()) {
             JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_TYPEDOBJECT_TOO_BIG);
             return nullptr;
         }
         MOZ_ASSERT(offset.value() >= 0);
         if (!fieldOffsets.append(Int32Value(offset.value()))) {
             return nullptr;
         }
-
-        if (!fieldMuts.append(BooleanValue(fieldMutabilities[i]))) {
+        if (!fieldMuts.append(BooleanValue(fieldProps[i].isMutable))) {
             return nullptr;
         }
 
         // userFieldOffsets[id] = offset
         RootedValue offsetValue(cx, Int32Value(offset.value()));
         if (!DefineDataProperty(cx, userFieldOffsets, id, offsetValue,
                                 JSPROP_READONLY | JSPROP_PERMANENT))
         {
@@ -1366,16 +1409,32 @@ GlobalObject::initTypedObjectModule(JSCo
 
 #define BINARYDATA_REFERENCE_DEFINE(constant_, type_, name_)                    \
     if (!DefineSimpleTypeDescr<ReferenceTypeDescr>(cx, global, module, constant_,   \
                                                cx->names().name_))              \
         return false;
     JS_FOR_EACH_REFERENCE_TYPE_REPR(BINARYDATA_REFERENCE_DEFINE)
 #undef BINARYDATA_REFERENCE_DEFINE
 
+    // Tuck away descriptors we will use for wasm.
+
+    RootedValue typeDescr(cx);
+
+    MOZ_ALWAYS_TRUE(JS_GetProperty(cx, module, "int32", &typeDescr));
+    module->initReservedSlot(TypedObjectModuleObject::Int32Desc, typeDescr);
+
+    MOZ_ALWAYS_TRUE(JS_GetProperty(cx, module, "float32", &typeDescr));
+    module->initReservedSlot(TypedObjectModuleObject::Float32Desc, typeDescr);
+
+    MOZ_ALWAYS_TRUE(JS_GetProperty(cx, module, "float64", &typeDescr));
+    module->initReservedSlot(TypedObjectModuleObject::Float64Desc, typeDescr);
+
+    MOZ_ALWAYS_TRUE(JS_GetProperty(cx, module, "Object", &typeDescr));
+    module->initReservedSlot(TypedObjectModuleObject::ObjectDesc, typeDescr);
+
     // ArrayType.
 
     RootedObject arrayType(cx);
     arrayType = DefineMetaTypeDescr<ArrayMetaTypeDescr>(
         cx, "ArrayType", global, module, TypedObjectModuleObject::ArrayTypePrototype);
     if (!arrayType) {
         return false;
     }
--- a/js/src/builtin/TypedObject.h
+++ b/js/src/builtin/TypedObject.h
@@ -398,16 +398,23 @@ class ArrayTypeDescr : public ComplexTyp
         return uint32_t(i);
     }
 
     static int32_t offsetOfLength() {
         return getFixedSlotOffset(JS_DESCR_SLOT_ARRAY_LENGTH);
     }
 };
 
+struct StructFieldProps
+{
+    StructFieldProps() : isMutable(0), alignAsInt64(0) {}
+    uint32_t isMutable:1;
+    uint32_t alignAsInt64:1;
+};
+
 /*
  * Properties and methods of the `StructType` meta type object. There
  * is no `class_` field because `StructType` is just a native
  * constructor function.
  */
 class StructMetaTypeDescr : public NativeObject
 {
   private:
@@ -419,17 +426,17 @@ class StructMetaTypeDescr : public Nativ
     // The names in `ids` must all be non-numeric.
     // The type objects in `fieldTypeObjs` must all be TypeDescr objects.
     static StructTypeDescr* createFromArrays(JSContext* cx,
                                              HandleObject structTypePrototype,
                                              bool opaque,
                                              bool allowConstruct,
                                              AutoIdVector& ids,
                                              AutoValueVector& fieldTypeObjs,
-                                             Vector<bool>& fieldMutabilities);
+                                             Vector<StructFieldProps>& fieldProps);
 
     // Properties and methods to be installed on StructType.prototype,
     // and hence inherited by all struct type objects:
     static const JSPropertySpec typeObjectProperties[];
     static const JSFunctionSpec typeObjectMethods[];
 
     // Properties and methods to be installed on StructType.prototype.prototype,
     // and hence inherited by all struct *typed* objects:
@@ -501,16 +508,21 @@ typedef Handle<StructTypeDescr*> HandleS
  * somewhat, rather than sticking them all into the global object.
  * Eventually it will go away and become a module.
  */
 class TypedObjectModuleObject : public NativeObject {
   public:
     enum Slot {
         ArrayTypePrototype,
         StructTypePrototype,
+        Int32Desc,
+        Int64Desc,
+        Float32Desc,
+        Float64Desc,
+        ObjectDesc,
         SlotCount
     };
 
     static const Class class_;
 };
 
 /* Base type for transparent and opaque typed objects. */
 class TypedObject : public ShapedObject
--- a/js/src/jit-test/lib/wasm-binary.js
+++ b/js/src/jit-test/lib/wasm-binary.js
@@ -35,16 +35,17 @@ const nameTypeFunction = 1;
 const nameTypeLocal    = 2;
 
 // Type codes
 const I32Code          = 0x7f;
 const I64Code          = 0x7e;
 const F32Code          = 0x7d;
 const F64Code          = 0x7c;
 const AnyFuncCode      = 0x70;
+const AnyrefCode       = 0x6f;
 const RefCode          = 0x6e;
 const FuncCode         = 0x60;
 const VoidCode         = 0x40;
 
 // Opcodes
 const UnreachableCode  = 0x00
 const BlockCode        = 0x02;
 const EndCode          = 0x0b;
@@ -101,16 +102,22 @@ const RefNull          = 0xd0;
 
 const FirstInvalidOpcode = 0xc5;
 const LastInvalidOpcode = 0xfb;
 const MiscPrefix = 0xfc;
 const SimdPrefix = 0xfd;
 const ThreadPrefix = 0xfe;
 const MozPrefix = 0xff;
 
+// Secondary opcode bytes for misc prefix
+const StructNew = 0x50;         // UNOFFICIAL
+const StructGet = 0x51;         // UNOFFICIAL
+const StructSet = 0x52;         // UNOFFICIAL
+const StructNarrow = 0x53;      // UNOFFICIAL
+
 // DefinitionKind
 const FunctionCode     = 0x00;
 const TableCode        = 0x01;
 const MemoryCode       = 0x02;
 const GlobalCode       = 0x03;
 
 // ResizableFlags
 const HasMaximumFlag   = 0x1;
--- a/js/src/jit-test/tests/wasm/binary.js
+++ b/js/src/jit-test/tests/wasm/binary.js
@@ -270,26 +270,28 @@ function checkIllegalPrefixed(prefix, op
 //  0x10 .. 0x4f are primitive atomic ops
 
 for (let i = 3; i < 0x10; i++)
     checkIllegalPrefixed(ThreadPrefix, i);
 
 for (let i = 0x4f; i < 0x100; i++)
     checkIllegalPrefixed(ThreadPrefix, i);
 
-// Illegal Numeric opcodes
-//
-// Feb 2018 numeric draft:
-//
-//  0x00 .. 0x07 are saturating truncation ops.  0x08 .. 0x0e are from the
-//  bulk memory proposal.  0x08 .. 0x0e are unofficial values, until such
-//  time as there is an official assignment for memory.copy/fill subopcodes.
+// 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,
+      // Structure operations (experimental, internal)
+      0x50: true, 0x51: true, 0x52: true, 0x53: true };
 
 for (let i = 0; i < 256; i++) {
-    if (i <= 0x07 || (i >= 0x08 && i <= 0x0e))
+    if (reservedMisc.hasOwnProperty(i))
         continue;
     checkIllegalPrefixed(MiscPrefix, i);
 }
 
 // Illegal SIMD opcodes (all of them, for now)
 for (let i = 0; i < 256; i++)
     checkIllegalPrefixed(SimdPrefix, i);
 
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/wasm/gc/TypedObject.js
@@ -0,0 +1,150 @@
+if (!wasmGcEnabled())
+    quit(0);
+
+// We can read the object fields from JS, and write them if they are mutable.
+
+{
+    let ins = wasmEvalText(`(module
+                             (gc_feature_opt_in 1)
+
+                             (type $p (struct (field f64) (field (mut i32))))
+
+                             (func (export "mkp") (result anyref)
+                              (struct.new $p (f64.const 1.5) (i32.const 33))))`).exports;
+
+    let p = ins.mkp();
+    assertEq(p._0, 1.5);
+    assertEq(p._1, 33);
+    assertEq(p._2, undefined);
+
+    p._1 = 44;
+    assertEq(p._1, 44);
+}
+
+// Writing an immutable field from JS throws.
+
+{
+    let ins = wasmEvalText(`(module
+                             (gc_feature_opt_in 1)
+
+                             (type $p (struct (field f64)))
+
+                             (func (export "mkp") (result anyref)
+                              (struct.new $p (f64.const 1.5))))`).exports;
+
+    let p = ins.mkp();
+    assertErrorMessage(() => p._0 = 5.7,
+                       Error,
+                       /setting immutable field/);
+}
+
+// MVA v1 restriction: structs that expose ref-typed fields should not be
+// constructible from JS.
+//
+// However, if the fields are anyref the structs can be constructed from JS.
+
+{
+    let ins = wasmEvalText(`(module
+                             (gc_feature_opt_in 1)
+
+                             (type $q (struct (field (mut f64))))
+                             (type $p (struct (field (mut (ref $q)))))
+
+                             (type $r (struct (field (mut anyref))))
+
+                             (func (export "mkp") (result anyref)
+                              (struct.new $p (ref.null (ref $q))))
+
+                             (func (export "mkr") (result anyref)
+                              (struct.new $r (ref.null anyref))))`).exports;
+
+    assertEq(typeof ins.mkp().constructor, "function");
+    assertErrorMessage(() => new (ins.mkp().constructor)({_0:null}),
+                       TypeError,
+                       /not constructible/);
+
+    assertEq(typeof ins.mkr().constructor, "function");
+    let r = new (ins.mkr().constructor)({_0:null});
+    assertEq(typeof r, "object");
+}
+
+// MVA v1 restriction: structs that expose ref-typed fields make those fields
+// immutable from JS even if we're trying to store the correct type.
+//
+// However, anyref fields are mutable from JS.
+
+{
+    let ins = wasmEvalText(`(module
+                             (gc_feature_opt_in 1)
+
+                             (type $q (struct (field (mut f64))))
+                             (type $p (struct (field (mut (ref $q))) (field (mut anyref))))
+
+                             (func (export "mkq") (result anyref)
+                              (struct.new $q (f64.const 1.5)))
+
+                             (func (export "mkp") (result anyref)
+                              (struct.new $p (ref.null (ref $q)) (ref.null anyref))))`).exports;
+    let q = ins.mkq();
+    assertEq(typeof q, "object");
+    assertEq(q._0, 1.5);
+    let p = ins.mkp();
+    assertEq(typeof p, "object");
+    assertEq(p._0, null);
+
+    assertErrorMessage(() => { p._0 = q },
+                       Error,
+                       /setting immutable field/);
+
+    p._1 = q;
+    assertEq(p._1, q);
+}
+
+// MVA v1 restriction: structs that expose i64 fields make those fields
+// immutable from JS, and the structs are not constructible from JS.
+
+{
+    let ins = wasmEvalText(`(module
+                             (gc_feature_opt_in 1)
+                             (type $p (struct (field (mut i64))))
+                             (func (export "mkp") (result anyref)
+                              (struct.new $p (i64.const 0x1234567887654321))))`).exports;
+
+    let p = ins.mkp();
+    assertEq(typeof p, "object");
+    assertEq(p._0_high, 0x12345678)
+    assertEq(p._0_low, 0x87654321|0);
+
+    assertEq(typeof p.constructor, "function");
+    assertErrorMessage(() => new (p.constructor)({_0_high:0, _0_low:0}),
+                       TypeError,
+                       /not constructible/);
+
+    assertErrorMessage(() => { p._0_low = 0 },
+                       Error,
+                       /setting immutable field/);
+    assertErrorMessage(() => { p._0_high = 0 },
+                       Error,
+                       /setting immutable field/);
+}
+
+// A consequence of the current mapping of i64 as two i32 fields is that we run
+// a risk of struct.narrow not recognizing the difference.  So check this.
+
+{
+    let ins = wasmEvalText(
+        `(module
+          (gc_feature_opt_in 1)
+          (type $p (struct (field i64)))
+          (type $q (struct (field i32) (field i32)))
+          (func $f (param anyref) (result i32)
+           (ref.is_null (struct.narrow anyref (ref $q) (get_local 0))))
+          (func $g (param anyref) (result i32)
+           (ref.is_null (struct.narrow anyref (ref $p) (get_local 0))))
+          (func (export "t1") (result i32)
+           (call $f (struct.new $p (i64.const 0))))
+          (func (export "t2") (result i32)
+           (call $g (struct.new $q (i32.const 0) (i32.const 0)))))`).exports;
+    assertEq(ins.t1(), 1);
+    assertEq(ins.t2(), 1);
+}
--- a/js/src/jit-test/tests/wasm/gc/binary.js
+++ b/js/src/jit-test/tests/wasm/gc/binary.js
@@ -25,17 +25,17 @@ const invalidRefNullBody = funcBody({loc
 
     // Condition code;
     I32ConstCode,
     0x10,
 
     SelectCode,
     DropCode
 ]});
-checkInvalid(invalidRefNullBody, /invalid nullref type/);
+checkInvalid(invalidRefNullBody, /invalid reference type for ref.null/);
 
 const invalidRefBlockType = funcBody({locals:[], body:[
     BlockCode,
     RefCode,
     0x42,
     EndCode,
 ]});
 checkInvalid(invalidRefBlockType, /invalid inline block type/);
--- 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
@@ -119,8 +119,32 @@ assertErrorMessage(() => new WebAssembly
                    WebAssembly.CompileError,
                    /unrecognized opcode/);
 
 assertErrorMessage(() => new WebAssembly.Module(wasmTextToBinary(
     `(module
       (func ref.eq))`)),
                    WebAssembly.CompileError,
                    /unrecognized opcode/);
+
+assertErrorMessage(() => new WebAssembly.Module(wasmTextToBinary(
+    `(module
+      (func (struct.new 0)))`)),
+                   WebAssembly.CompileError,
+                   /unrecognized opcode/);
+
+assertErrorMessage(() => new WebAssembly.Module(wasmTextToBinary(
+    `(module
+      (func (struct.get 0 0)))`)),
+                   WebAssembly.CompileError,
+                   /unrecognized opcode/);
+
+assertErrorMessage(() => new WebAssembly.Module(wasmTextToBinary(
+    `(module
+      (func (struct.set 0 0)))`)),
+                   WebAssembly.CompileError,
+                   /unrecognized opcode/);
+
+assertErrorMessage(() => new WebAssembly.Module(wasmTextToBinary(
+    `(module
+      (func (struct.narrow anyref anyref)))`)),
+                   WebAssembly.CompileError,
+                   /unrecognized opcode/);
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/wasm/gc/ref-struct.js
@@ -0,0 +1,489 @@
+if (!wasmGcEnabled())
+    quit(0);
+
+// We'll be running some binary-format tests shortly.
+
+load(libdir + "wasm-binary.js");
+
+const v2vSigSection = sigSection([{args:[], ret:VoidCode}]);
+
+function checkInvalid(body, errorMessage) {
+    assertErrorMessage(() => new WebAssembly.Module(
+        moduleWithSections([gcFeatureOptInSection(1),
+                            v2vSigSection,
+                            declSection([0]),
+                            bodySection([body])])),
+                       WebAssembly.CompileError,
+                       errorMessage);
+}
+
+// General test case for struct.new, struct.get, and struct.set: binary tree
+// manipulation.
+
+{
+    let bin = wasmTextToBinary(
+        `(module
+          (gc_feature_opt_in 1)
+
+          (import $print_lp "" "print_lp" (func))
+          (import $print_rp "" "print_rp" (func))
+          (import $print_int "" "print_int" (func (param i32)))
+
+          (type $wabbit (struct
+                         (field $x (mut i32))
+                         (field $left (mut (ref $wabbit)))
+                         (field $right (mut (ref $wabbit)))))
+
+          (global $g (mut (ref $wabbit)) (ref.null (ref $wabbit)))
+
+          (global $k (mut i32) (i32.const 0))
+
+          (func (export "init") (param $n i32)
+                (set_global $g (call $make (get_local $n))))
+
+          (func $make (param $n i32) (result (ref $wabbit))
+                (local $tmp i32)
+                (set_local $tmp (get_global $k))
+                (set_global $k (i32.add (get_local $tmp) (i32.const 1)))
+                (if (ref $wabbit) (i32.le_s (get_local $n) (i32.const 2))
+                    (struct.new $wabbit (get_local $tmp) (ref.null (ref $wabbit)) (ref.null (ref $wabbit)))
+                    (block (ref $wabbit)
+                      (struct.new $wabbit
+                                  (get_local $tmp)
+                                  (call $make (i32.sub (get_local $n) (i32.const 1)))
+                                  (call $make (i32.sub (get_local $n) (i32.const 2)))))))
+
+          (func (export "accumulate") (result i32)
+                (call $accum (get_global $g)))
+
+          (func $accum (param $w (ref $wabbit)) (result i32)
+                (if i32 (ref.is_null (get_local $w))
+                    (i32.const 0)
+                    (i32.add (struct.get $wabbit 0 (get_local $w))
+                             (i32.sub (call $accum (struct.get $wabbit 1 (get_local $w)))
+                                      (call $accum (struct.get $wabbit 2 (get_local $w)))))))
+
+          (func (export "reverse")
+                (call $reverse (get_global $g)))
+
+          (func $reverse (param $w (ref $wabbit))
+                (local $tmp (ref $wabbit))
+                (if (i32.eqz (ref.is_null (get_local $w)))
+                    (block
+                     (struct.set $wabbit 0 (get_local $w) (i32.mul (i32.const 2) (struct.get $wabbit 0 (get_local $w))))
+                     (set_local $tmp (struct.get $wabbit 1 (get_local $w)))
+                     (struct.set $wabbit 1 (get_local $w) (struct.get $wabbit 2 (get_local $w)))
+                     (struct.set $wabbit 2 (get_local $w) (get_local $tmp))
+                     (call $reverse (struct.get $wabbit 1 (get_local $w)))
+                     (call $reverse (struct.get $wabbit 2 (get_local $w))))))
+
+          (func (export "print")
+                (call $pr (get_global $g)))
+
+          (func $pr (param $w (ref $wabbit))
+                (if (i32.eqz (ref.is_null (get_local $w)))
+                    (block
+                     (call $print_lp)
+                     (call $print_int (struct.get $wabbit 0 (get_local $w)))
+                     (call $pr (struct.get $wabbit 1 (get_local $w)))
+                     (call $pr (struct.get $wabbit 2 (get_local $w)))
+                     (call $print_rp))))
+         )`);
+
+    let s = "";
+    function pr_int(k) { s += k + " "; }
+    function pr_lp() { s += "(" };
+    function pr_rp() { s += ")" }
+
+    let mod = new WebAssembly.Module(bin);
+    let ins = new WebAssembly.Instance(mod, {"":{print_int:pr_int,print_lp:pr_lp,print_rp:pr_rp}}).exports;
+
+    ins.init(6);
+    s = ""; ins.print(); assertEq(s, "(0 (1 (2 (3 (4 )(5 ))(6 ))(7 (8 )(9 )))(10 (11 (12 )(13 ))(14 )))");
+    assertEq(ins.accumulate(), -13);
+
+    ins.reverse();
+    s = ""; ins.print(); assertEq(s, "(0 (20 (28 )(22 (26 )(24 )))(2 (14 (18 )(16 ))(4 (12 )(6 (10 )(8 )))))");
+    assertEq(ins.accumulate(), 14);
+
+    for (let i=10; i < 22; i++ ) {
+        ins.init(i);
+        ins.reverse();
+        gc();
+        ins.reverse();
+    }
+}
+
+// Sanity check for struct.set: we /can/ store a (ref T) into a (ref U) field
+// with struct.set if T <: U; this should fall out of normal coercion but good
+// to test.
+
+wasmEvalText(
+    `(module
+      (gc_feature_opt_in 1)
+      (type $node (struct (field (mut (ref $node)))))
+      (type $nix (struct (field (mut (ref $node))) (field i32)))
+      (func $f (param $p (ref $node)) (param $q (ref $nix))
+       (struct.set $node 0 (get_local $p) (get_local $q))))`);
+
+// struct.narrow: if the pointer's null we get null
+
+assertEq(wasmEvalText(
+    `(module
+      (gc_feature_opt_in 1)
+      (type $node (struct (field i32)))
+      (type $node2 (struct (field i32) (field f32)))
+      (func $f (param $p (ref $node)) (result (ref $node2))
+       (struct.narrow (ref $node) (ref $node2) (get_local $p)))
+      (func (export "test") (result anyref)
+       (call $f (ref.null (ref $node)))))`).exports.test(),
+         null);
+
+// struct.narrow: if the downcast succeeds we get the original pointer
+
+assertEq(wasmEvalText(
+    `(module
+      (gc_feature_opt_in 1)
+      (type $node (struct (field i32)))
+      (type $node2 (struct (field i32) (field f32)))
+      (func $f (param $p (ref $node)) (result (ref $node2))
+       (struct.narrow (ref $node) (ref $node2) (get_local $p)))
+      (func (export "test") (result i32)
+       (local $n (ref $node))
+       (set_local $n (struct.new $node2 (i32.const 0) (f32.const 12)))
+       (ref.eq (call $f (get_local $n)) (get_local $n))))`).exports.test(),
+         1);
+
+// And once more with mutable fields
+
+assertEq(wasmEvalText(
+    `(module
+      (gc_feature_opt_in 1)
+      (type $node (struct (field (mut i32))))
+      (type $node2 (struct (field (mut i32)) (field f32)))
+      (func $f (param $p (ref $node)) (result (ref $node2))
+       (struct.narrow (ref $node) (ref $node2) (get_local $p)))
+      (func (export "test") (result i32)
+       (local $n (ref $node))
+       (set_local $n (struct.new $node2 (i32.const 0) (f32.const 12)))
+       (ref.eq (call $f (get_local $n)) (get_local $n))))`).exports.test(),
+         1);
+
+// A more subtle case: the downcast is to a struct that looks like the original
+// struct should succeed because struct.narrow is a structural cast with nominal
+// per-field type equality.
+//
+// We use ref-typed fields here because they have the trickiest equality rules,
+// and we have two cases: one where the ref types are the same, and one where
+// they reference different structures that look the same; this latter case
+// should fail because our structural compatibility is shallow.
+
+assertEq(wasmEvalText(
+    `(module
+      (gc_feature_opt_in 1)
+
+      (type $node (struct (field i32)))
+      (type $node2a (struct (field i32) (field (ref $node))))
+      (type $node2b (struct (field i32) (field (ref $node))))
+
+      (func $f (param $p (ref $node)) (result (ref $node2b))
+       (struct.narrow (ref $node) (ref $node2b) (get_local $p)))
+
+      (func (export "test") (result i32)
+       (local $n (ref $node))
+       (set_local $n (struct.new $node2a (i32.const 0) (ref.null (ref $node))))
+       (ref.eq (call $f (get_local $n)) (get_local $n))))`).exports.test(),
+         1);
+
+assertEq(wasmEvalText(
+    `(module
+      (gc_feature_opt_in 1)
+
+      (type $node (struct (field i32)))
+      (type $nodeCopy (struct (field i32)))
+      (type $node2a (struct (field i32) (field (ref $node))))
+      (type $node2b (struct (field i32) (field (ref $nodeCopy))))
+
+      (func $f (param $p (ref $node)) (result (ref $node2b))
+       (struct.narrow (ref $node) (ref $node2b) (get_local $p)))
+
+      (func (export "test") (result i32)
+       (local $n (ref $node))
+       (set_local $n (struct.new $node2a (i32.const 0) (ref.null (ref $node))))
+       (ref.eq (call $f (get_local $n)) (get_local $n))))`).exports.test(),
+         0);
+
+// Another subtle case: struct.narrow can target a type that is not the concrete
+// type of the object, but a prefix of that concrete type.
+
+assertEq(wasmEvalText(
+    `(module
+      (gc_feature_opt_in 1)
+      (type $node (struct (field i32)))
+      (type $node2 (struct (field i32) (field f32)))
+      (type $node3 (struct (field i32) (field f32) (field f64)))
+      (func $f (param $p (ref $node)) (result (ref $node2))
+       (struct.narrow (ref $node) (ref $node2) (get_local $p)))
+      (func (export "test") (result i32)
+       (local $n (ref $node))
+       (set_local $n (struct.new $node3 (i32.const 0) (f32.const 12) (f64.const 17)))
+       (ref.eq (call $f (get_local $n)) (get_local $n))))`).exports.test(),
+         1);
+
+// struct.narrow: if the downcast fails we get null
+
+assertEq(wasmEvalText(
+    `(module
+      (gc_feature_opt_in 1)
+      (type $node (struct (field i32)))
+      (type $node2 (struct (field i32) (field f32)))
+      (type $snort (struct (field i32) (field f64)))
+      (func $f (param $p (ref $node)) (result (ref $node2))
+       (struct.narrow (ref $node) (ref $node2) (get_local $p)))
+      (func (export "test") (result anyref)
+       (call $f (struct.new $snort (i32.const 0) (f64.const 12)))))`).exports.test(),
+         null);
+
+// struct.narrow: anyref -> struct when the anyref is the right struct;
+// special case since anyref requires unboxing
+
+assertEq(wasmEvalText(
+    `(module
+      (gc_feature_opt_in 1)
+      (type $node (struct (field i32)))
+      (func $f (param $p anyref) (result (ref $node))
+       (struct.narrow anyref (ref $node) (get_local $p)))
+      (func (export "test") (result i32)
+       (local $n (ref $node))
+       (set_local $n (struct.new $node (i32.const 0)))
+       (ref.eq (call $f (get_local $n)) (get_local $n))))`).exports.test(),
+         1);
+
+// struct.narrow: anyref -> struct when the anyref is some random gunk.
+
+assertEq(wasmEvalText(
+    `(module
+      (gc_feature_opt_in 1)
+      (type $node (struct (field i32)))
+      (func (export "test") (param $p anyref) (result anyref)
+       (struct.narrow anyref (ref $node) (get_local $p))))`).exports.test({hi:37}),
+         null);
+
+// Types are private to an instance and struct.narrow can't break this
+
+{
+    let txt =
+        `(module
+          (gc_feature_opt_in 1)
+          (type $node (struct (field i32)))
+          (func (export "make") (param $n i32) (result anyref)
+           (struct.new $node (get_local $n)))
+          (func (export "coerce") (param $p anyref) (result i32)
+           (ref.is_null (struct.narrow anyref (ref $node) (get_local $p)))))`;
+    let mod = new WebAssembly.Module(wasmTextToBinary(txt));
+    let ins1 = new WebAssembly.Instance(mod).exports;
+    let ins2 = new WebAssembly.Instance(mod).exports;
+    let obj = ins1.make(37);
+    assertEq(obj._0, 37);
+    assertEq(ins2.coerce(obj), 1);
+}
+
+// Negative tests
+
+// Attempting to mutate immutable field with struct.set
+
+assertErrorMessage(() => wasmEvalText(
+    `(module
+      (gc_feature_opt_in 1)
+      (type $node (struct (field i32)))
+      (func $f (param $p (ref $node))
+       (struct.set $node 0 (get_local $p) (i32.const 37))))`),
+                   WebAssembly.CompileError,
+                   /field is not mutable/);
+
+// Attempting to store incompatible value in mutable field with struct.set
+
+assertErrorMessage(() => wasmEvalText(
+    `(module
+      (gc_feature_opt_in 1)
+      (type $node (struct (field (mut i32))))
+      (func $f (param $p (ref $node))
+       (struct.set $node 0 (get_local $p) (f32.const 37))))`),
+                   WebAssembly.CompileError,
+                   /expression has type f32 but expected i32/);
+
+// Out-of-bounds reference for struct.get
+
+assertErrorMessage(() => wasmEvalText(
+    `(module
+      (gc_feature_opt_in 1)
+      (type $node (struct (field i32)))
+      (func $f (param $p (ref $node)) (result i32)
+       (struct.get $node 1 (get_local $p))))`),
+                   WebAssembly.CompileError,
+                   /field index out of range/);
+
+// Out-of-bounds reference for struct.set
+
+assertErrorMessage(() => wasmEvalText(
+    `(module
+      (gc_feature_opt_in 1)
+      (type $node (struct (field (mut i32))))
+      (func $f (param $p (ref $node))
+       (struct.set $node 1 (get_local $p) (i32.const 37))))`),
+                   WebAssembly.CompileError,
+                   /field index out of range/);
+
+// Base pointer is of unrelated type to stated type in struct.get
+
+assertErrorMessage(() => wasmEvalText(
+    `(module
+      (gc_feature_opt_in 1)
+      (type $node (struct (field i32)))
+      (type $snort (struct (field f64)))
+      (func $f (param $p (ref $snort)) (result i32)
+       (struct.get $node 0 (get_local $p))))`),
+                   WebAssembly.CompileError,
+                   /expression has type.*but expected.*/);
+
+// Base pointer is of unrelated type to stated type in struct.set
+
+assertErrorMessage(() => wasmEvalText(
+    `(module
+      (gc_feature_opt_in 1)
+      (type $node (struct (field (mut i32))))
+      (type $snort (struct (field f64)))
+      (func $f (param $p (ref $snort)) (result i32)
+       (struct.set $node 0 (get_local $p) (i32.const 0))))`),
+                   WebAssembly.CompileError,
+                   /expression has type.*but expected.*/);
+
+// Base pointer is of unrelated type to stated type in struct.narrow
+
+assertErrorMessage(() => wasmEvalText(
+    `(module
+      (gc_feature_opt_in 1)
+      (type $node (struct (field i32)))
+      (type $node2 (struct (field i32) (field f32)))
+      (type $snort (struct (field f64)))
+      (func $f (param $p (ref $snort)) (result (ref $node2))
+       (struct.narrow (ref $node) (ref $node2) (get_local 0))))`),
+                   WebAssembly.CompileError,
+                   /expression has type.*but expected.*/);
+
+// source and target types are compatible except for mutability
+
+assertErrorMessage(() => wasmEvalText(
+    `(module
+      (gc_feature_opt_in 1)
+      (type $node (struct (field i32)))
+      (type $node2 (struct (field (mut i32)) (field f32)))
+      (func $f (param $p (ref $node)) (result (ref $node2))
+       (struct.narrow (ref $node) (ref $node2) (get_local 0))))`),
+                   WebAssembly.CompileError,
+                   /invalid narrowing operation/);
+
+assertErrorMessage(() => wasmEvalText(
+    `(module
+      (gc_feature_opt_in 1)
+      (type $node (struct (field (mut i32))))
+      (type $node2 (struct (field i32) (field f32)))
+      (func $f (param $p (ref $node)) (result (ref $node2))
+       (struct.narrow (ref $node) (ref $node2) (get_local 0))))`),
+                   WebAssembly.CompileError,
+                   /invalid narrowing operation/);
+
+// source and target types must be ref types: source syntax
+
+assertErrorMessage(() => wasmEvalText(
+    `(module
+      (gc_feature_opt_in 1)
+      (type $node (struct (field i32)))
+      (func $f (param $p (ref $node)) (result anyref)
+       (struct.narrow i32 anyref (get_local 0))))`),
+                   SyntaxError,
+                   /struct.narrow requires ref type/);
+
+assertErrorMessage(() => wasmEvalText(
+    `(module
+      (gc_feature_opt_in 1)
+      (type $node (struct (field i32)))
+      (func $f (param $p (ref $node)) (result anyref)
+       (struct.narrow anyref i32 (get_local 0))))`),
+                   SyntaxError,
+                   /struct.narrow requires ref type/);
+
+// source and target types must be ref types: binary format
+
+checkInvalid(funcBody({locals:[],
+                       body:[
+                           RefNull,
+                           AnyrefCode,
+                           MiscPrefix, StructNarrow, I32Code, AnyrefCode,
+                           DropCode
+                       ]}),
+             /invalid reference type for struct.narrow/);
+
+checkInvalid(funcBody({locals:[],
+                       body:[
+                           RefNull,
+                           AnyrefCode,
+                           MiscPrefix, StructNarrow, AnyrefCode, I32Code,
+                           DropCode
+                       ]}),
+             /invalid reference type for struct.narrow/);
+
+// target type is anyref so source type must be anyref as well (no upcasts)
+
+assertErrorMessage(() => wasmEvalText(
+    `(module
+      (gc_feature_opt_in 1)
+      (type $node (struct (field i32)))
+      (func $f (param $p (ref $node)) (result anyref)
+       (struct.narrow (ref $node) anyref (get_local 0))))`),
+                   WebAssembly.CompileError,
+                   /invalid type combination in struct.narrow/);
+
+// target type must be subtype of source type (no upcasts)
+
+assertErrorMessage(() => wasmEvalText(
+    `(module
+      (gc_feature_opt_in 1)
+      (type $node (struct (field i32)))
+      (type $node2 (struct (field i32) (field f32)))
+      (func $f (param $p (ref $node2)) (result anyref)
+       (struct.narrow (ref $node2) (ref $node) (get_local 0))))`),
+                   WebAssembly.CompileError,
+                   /invalid narrowing operation/);
+
+// Null pointer dereference in struct.get
+
+assertErrorMessage(function() {
+    let ins = wasmEvalText(
+        `(module
+          (gc_feature_opt_in 1)
+          (type $node (struct (field i32)))
+          (func (export "test")
+           (drop (call $f (ref.null (ref $node)))))
+          (func $f (param $p (ref $node)) (result i32)
+           (struct.get $node 0 (get_local $p))))`);
+    ins.exports.test();
+},
+                   WebAssembly.RuntimeError,
+                   /dereferencing null pointer/);
+
+// Null pointer dereference in struct.set
+
+assertErrorMessage(function() {
+    let ins = wasmEvalText(
+        `(module
+          (gc_feature_opt_in 1)
+          (type $node (struct (field (mut i32))))
+          (func (export "test")
+           (call $f (ref.null (ref $node))))
+          (func $f (param $p (ref $node))
+           (struct.set $node 0 (get_local $p) (i32.const 0))))`);
+    ins.exports.test();
+},
+                   WebAssembly.RuntimeError,
+                   /dereferencing null pointer/);
--- a/js/src/jit-test/tests/wasm/gc/structs.js
+++ b/js/src/jit-test/tests/wasm/gc/structs.js
@@ -1,11 +1,13 @@
 if (!wasmGcEnabled())
     quit();
 
+var conf = getBuildConfiguration();
+
 var bin = wasmTextToBinary(
     `(module
       (gc_feature_opt_in 1)
 
       (table 2 anyfunc)
       (elem (i32.const 0) $doit $doitagain)
 
       ;; Type array has a mix of types
@@ -51,27 +53,463 @@ var bin = wasmTextToBinary(
       (func $doitagain (param f64) (result f64)
        (f64.mul (get_local 0) (get_local 0)))
 
       (func (export "x1") (param i32) (result i32)
        (call $x1 (get_local 0)))
 
       (func (export "x2") (param f64) (result f64)
        (call $x2 (get_local 0)))
+
+      ;; Useful for testing to ensure that the type is not type #0 here.
+
+      (func (export "mk_point") (result anyref)
+       (struct.new $point (i32.const 37) (i32.const 42)))
+
+      (func (export "mk_int_node") (param i32) (param anyref) (result anyref)
+       (struct.new $int_node (get_local 0) (get_local 1)))
+
+      ;; Too big to fit in an InlineTypedObject.
+
+      (type $bigger (struct
+                     (field $a i32)
+                     (field $b i32)
+                     (field $c i32)
+                     (field $d i32)
+                     (field $e i32)
+                     (field $f i32)
+                     (field $g i32)
+                     (field $h i32)
+                     (field $i i32)
+                     (field $j i32)
+                     (field $k i32)
+                     (field $l i32)
+                     (field $m i32)
+                     (field $n i32)
+                     (field $o i32)
+                     (field $p i32)
+                     (field $q i32)
+                     (field $r i32)
+                     (field $s i32)
+                     (field $t i32)
+                     (field $u i32)
+                     (field $v i32)
+                     (field $w i32)
+                     (field $x i32)
+                     (field $y i32)
+                     (field $z i32)
+                     (field $aa i32)
+                     (field $ab i32)
+                     (field $ac i32)
+                     (field $ad i32)
+                     (field $ae i32)
+                     (field $af i32)
+                     (field $ag i32)
+                     (field $ah i32)
+                     (field $ai i32)
+                     (field $aj i32)
+                     (field $ak i32)
+                     (field $al i32)
+                     (field $am i32)
+                     (field $an i32)
+                     (field $ao i32)
+                     (field $ap i32)
+                     (field $aq i32)
+                     (field $ar i32)
+                     (field $as i32)
+                     (field $at i32)
+                     (field $au i32)
+                     (field $av i32)
+                     (field $aw i32)
+                     (field $ax i32)
+                     (field $ay i32)
+                     (field $az i32)))
+
+      (func (export "mk_bigger") (result anyref)
+            (struct.new $bigger
+                       (i32.const 0)
+                       (i32.const 1)
+                       (i32.const 2)
+                       (i32.const 3)
+                       (i32.const 4)
+                       (i32.const 5)
+                       (i32.const 6)
+                       (i32.const 7)
+                       (i32.const 8)
+                       (i32.const 9)
+                       (i32.const 10)
+                       (i32.const 11)
+                       (i32.const 12)
+                       (i32.const 13)
+                       (i32.const 14)
+                       (i32.const 15)
+                       (i32.const 16)
+                       (i32.const 17)
+                       (i32.const 18)
+                       (i32.const 19)
+                       (i32.const 20)
+                       (i32.const 21)
+                       (i32.const 22)
+                       (i32.const 23)
+                       (i32.const 24)
+                       (i32.const 25)
+                       (i32.const 26)
+                       (i32.const 27)
+                       (i32.const 28)
+                       (i32.const 29)
+                       (i32.const 30)
+                       (i32.const 31)
+                       (i32.const 32)
+                       (i32.const 33)
+                       (i32.const 34)
+                       (i32.const 35)
+                       (i32.const 36)
+                       (i32.const 37)
+                       (i32.const 38)
+                       (i32.const 39)
+                       (i32.const 40)
+                       (i32.const 41)
+                       (i32.const 42)
+                       (i32.const 43)
+                       (i32.const 44)
+                       (i32.const 45)
+                       (i32.const 46)
+                       (i32.const 47)
+                       (i32.const 48)
+                       (i32.const 49)
+                       (i32.const 50)
+                       (i32.const 51)))
+
+      (type $withfloats (struct
+                         (field $f1 f32)
+                         (field $f2 f64)
+                         (field $f3 anyref)
+                         (field $f4 f32)
+                         (field $f5 i32)))
+
+      (func (export "mk_withfloats")
+            (param f32) (param f64) (param anyref) (param f32) (param i32)
+            (result anyref)
+            (struct.new $withfloats (get_local 0) (get_local 1) (get_local 2) (get_local 3) (get_local 4)))
+
      )`)
 
 var mod = new WebAssembly.Module(bin);
 var ins = new WebAssembly.Instance(mod, {m:{x1(x){ return x*3 }, x2(x){ return Math.PI }}}).exports;
 
 assertEq(ins.hello(4.0, 0), 2.0)
 assertEq(ins.hello(4.0, 1), 16.0)
 
 assertEq(ins.x1(12), 36)
 assertEq(ins.x2(8), Math.PI)
 
+var point = ins.mk_point();
+assertEq("_0" in point, true);
+assertEq("_1" in point, true);
+assertEq("_2" in point, false);
+assertEq(point._0, 37);
+assertEq(point._1, 42);
+
+var int_node = ins.mk_int_node(78, point);
+assertEq(int_node._0, 78);
+assertEq(int_node._1, point);
+
+var bigger = ins.mk_bigger();
+for ( let i=0; i < 52; i++ )
+    assertEq(bigger["_" + i], i);
+
+var withfloats = ins.mk_withfloats(1/3, Math.PI, bigger, 5/6, 0x1337);
+assertEq(withfloats._0, Math.fround(1/3));
+assertEq(withfloats._1, Math.PI);
+assertEq(withfloats._2, bigger);
+assertEq(withfloats._3, Math.fround(5/6));
+assertEq(withfloats._4, 0x1337);
+
+// A simple stress test
+
+var stress = wasmTextToBinary(
+    `(module
+      (gc_feature_opt_in 1)
+      (type $node (struct (field i32) (field (ref $node))))
+      (func (export "iota1") (param $n i32) (result anyref)
+       (local $list (ref $node))
+       (block $exit
+        (loop $loop
+         (br_if $exit (i32.eqz (get_local $n)))
+         (set_local $list (struct.new $node (get_local $n) (get_local $list)))
+         (set_local $n (i32.sub (get_local $n) (i32.const 1)))
+         (br $loop)))
+       (get_local $list)))`);
+var stressIns = new WebAssembly.Instance(new WebAssembly.Module(stress)).exports;
+var stressLevel = conf.x64 && !conf.tsan && !conf.asan && !conf.valgrind ? 100000 : 1000;
+var the_list = stressIns.iota1(stressLevel);
+for (let i=1; i <= stressLevel; i++) {
+    assertEq(the_list._0, i);
+    the_list = the_list._1;
+}
+assertEq(the_list, null);
+
+// Fields and their exposure in JS.  We can't export types yet so hide them
+// inside the module with globals.
+
+// i64 fields.
+
+{
+    let txt =
+        `(module
+          (gc_feature_opt_in 1)
+
+          (type $big (struct
+                      (field (mut i32))
+                      (field (mut i64))
+                      (field (mut i32))))
+
+          (func (export "set") (param anyref)
+           (local (ref $big))
+           (set_local 1 (struct.narrow anyref (ref $big) (get_local 0)))
+           (struct.set $big 1 (get_local 1) (i64.const 0x3333333376544567)))
+
+          (func (export "set2") (param $p anyref)
+           (struct.set $big 1
+            (struct.narrow anyref (ref $big) (get_local $p))
+            (i64.const 0x3141592653589793)))
+
+          (func (export "low") (param $p anyref) (result i32)
+           (i32.wrap/i64 (struct.get $big 1 (struct.narrow anyref (ref $big) (get_local $p)))))
+
+          (func (export "high") (param $p anyref) (result i32)
+           (i32.wrap/i64 (i64.shr_u
+                          (struct.get $big 1 (struct.narrow anyref (ref $big) (get_local $p)))
+                          (i64.const 32))))
+
+          (func (export "mk") (result anyref)
+           (struct.new $big (i32.const 0x7aaaaaaa) (i64.const 0x4201020337) (i32.const 0x6bbbbbbb)))
+
+         )`;
+
+    let ins = wasmEvalText(txt).exports;
+
+    let v = ins.mk();
+    assertEq(typeof v, "object");
+    assertEq(v._0, 0x7aaaaaaa);
+    assertEq(v._1_low, 0x01020337);
+    assertEq(v._1_high, 0x42);
+    assertEq(ins.low(v), 0x01020337);
+    assertEq(ins.high(v), 0x42);
+    assertEq(v._2, 0x6bbbbbbb);
+
+    v._0 = 0x5ccccccc;
+    v._2 = 0x4ddddddd;
+    assertEq(v._1_low, 0x01020337);
+
+    ins.set(v);
+    assertEq(v._0, 0x5ccccccc);
+    assertEq(v._1_low, 0x76544567);
+    assertEq(v._2, 0x4ddddddd);
+
+    ins.set2(v);
+    assertEq(v._1_low, 0x53589793);
+    assertEq(v._1_high, 0x31415926)
+    assertEq(ins.low(v), 0x53589793);
+    assertEq(ins.high(v), 0x31415926)
+}
+
+{
+    let txt =
+        `(module
+          (gc_feature_opt_in 1)
+
+          (type $big (struct
+                      (field (mut i32))
+                      (field (mut i64))
+                      (field (mut i32))))
+
+          (global $g (mut (ref $big)) (ref.null (ref $big)))
+
+          (func (export "make") (result anyref)
+           (set_global $g
+            (struct.new $big (i32.const 0x7aaaaaaa) (i64.const 0x4201020337) (i32.const 0x6bbbbbbb)))
+           (get_global $g))
+
+          (func (export "update0") (param $x i32)
+           (struct.set $big 0 (get_global $g) (get_local $x)))
+
+          (func (export "get0") (result i32)
+           (struct.get $big 0 (get_global $g)))
+
+          (func (export "update1") (param $hi i32) (param $lo i32)
+           (struct.set $big 1 (get_global $g)
+            (i64.or
+             (i64.shl (i64.extend_u/i32 (get_local $hi)) (i64.const 32))
+             (i64.extend_u/i32 (get_local $lo)))))
+
+          (func (export "get1_low") (result i32)
+           (i32.wrap/i64 (struct.get $big 1 (get_global $g))))
+
+          (func (export "get1_high") (result i32)
+           (i32.wrap/i64
+            (i64.shr_u (struct.get $big 1 (get_global $g)) (i64.const 32))))
+
+          (func (export "update2") (param $x i32)
+           (struct.set $big 2 (get_global $g) (get_local $x)))
+
+          (func (export "get2") (result i32)
+           (struct.get $big 2 (get_global $g)))
+
+         )`;
+
+    let ins = wasmEvalText(txt).exports;
+
+    let v = ins.make();
+    assertEq(v._0, 0x7aaaaaaa);
+    assertEq(v._1_low, 0x01020337);
+    assertEq(v._1_high, 0x42);
+    assertEq(v._2, 0x6bbbbbbb);
+
+    ins.update0(0x45367101);
+    assertEq(v._0, 0x45367101);
+    assertEq(ins.get0(), 0x45367101);
+    assertEq(v._1_low, 0x01020337);
+    assertEq(v._1_high, 0x42);
+    assertEq(v._2, 0x6bbbbbbb);
+
+    ins.update2(0x62345123);
+    assertEq(v._0, 0x45367101);
+    assertEq(v._1_low, 0x01020337);
+    assertEq(v._1_high, 0x42);
+    assertEq(ins.get2(), 0x62345123);
+    assertEq(v._2, 0x62345123);
+
+    ins.update1(0x77777777, 0x22222222);
+    assertEq(v._0, 0x45367101);
+    assertEq(ins.get1_low(), 0x22222222);
+    assertEq(v._1_low, 0x22222222);
+    assertEq(ins.get1_high(), 0x77777777);
+    assertEq(v._1_high, 0x77777777);
+    assertEq(v._2, 0x62345123);
+}
+
+
+var bin = wasmTextToBinary(
+    `(module
+      (gc_feature_opt_in 1)
+
+      (type $cons (struct (field i32) (field (ref $cons))))
+
+      (global $g (mut (ref $cons)) (ref.null (ref $cons)))
+
+      (func (export "push") (param i32)
+       (set_global $g (struct.new $cons (get_local 0) (get_global $g))))
+
+      (func (export "top") (result i32)
+       (struct.get $cons 0 (get_global $g)))
+
+      (func (export "pop")
+       (set_global $g (struct.get $cons 1 (get_global $g))))
+
+      (func (export "is_empty") (result i32)
+       (ref.is_null (get_global $g)))
+
+      )`);
+
+var mod = new WebAssembly.Module(bin);
+var ins = new WebAssembly.Instance(mod).exports;
+ins.push(37);
+ins.push(42);
+ins.push(86);
+assertEq(ins.top(), 86);
+ins.pop();
+assertEq(ins.top(), 42);
+ins.pop();
+assertEq(ins.top(), 37);
+ins.pop();
+assertEq(ins.is_empty(), 1);
+assertErrorMessage(() => ins.pop(),
+                   WebAssembly.RuntimeError,
+                   /dereferencing null pointer/);
+
+// Check that a wrapped object cannot be unboxed from anyref even if the wrapper
+// points to the right type.  This is a temporary restriction, until we're able
+// to avoid dealing with wrappers inside the engine.
+
+{
+    var ins = wasmEvalText(
+        `(module
+          (gc_feature_opt_in 1)
+          (type $Node (struct (field i32)))
+          (func (export "mk") (result anyref)
+           (struct.new $Node (i32.const 37)))
+          (func (export "f") (param $n anyref) (result anyref)
+           (struct.narrow anyref (ref $Node) (get_local $n))))`).exports;
+    var n = ins.mk();
+    assertEq(ins.f(n), n);
+    assertEq(ins.f(wrapWithProto(n, {})), null);
+}
+
+// negative tests
+
+// Wrong type passed as initializer
+
+assertErrorMessage(() => new WebAssembly.Module(wasmTextToBinary(`
+(module
+  (gc_feature_opt_in 1)
+  (type $r (struct (field i32)))
+  (func $f (param f64) (result anyref)
+    (struct.new $r (get_local 0)))
+)`)),
+WebAssembly.CompileError, /type mismatch/);
+
+// Too few values passed for initializer
+
+assertErrorMessage(() => new WebAssembly.Module(wasmTextToBinary(`
+(module
+  (gc_feature_opt_in 1)
+  (type $r (struct (field i32) (field i32)))
+  (func $f (result anyref)
+    (struct.new $r (i32.const 0)))
+)`)),
+WebAssembly.CompileError, /popping value from empty stack/);
+
+// Too many values passed for initializer, sort of
+
+assertErrorMessage(() => new WebAssembly.Module(wasmTextToBinary(`
+(module
+  (gc_feature_opt_in 1)
+  (type $r (struct (field i32) (field i32)))
+  (func $f (result anyref)
+    (i32.const 0)
+    (i32.const 1)
+    (i32.const 2)
+    struct.new $r)
+)`)),
+WebAssembly.CompileError, /unused values/);
+
+// Not referencing a structure type
+
+assertErrorMessage(() => new WebAssembly.Module(wasmTextToBinary(`
+(module
+  (gc_feature_opt_in 1)
+  (type (func (param i32) (result i32)))
+  (func $f (result anyref)
+    (struct.new 0))
+)`)),
+WebAssembly.CompileError, /not a struct type/);
+
+// Nominal type equivalence for structs, but the prefix rule allows this
+// conversion to succeed.
+
+wasmEvalText(`
+ (module
+   (gc_feature_opt_in 1)
+   (type $p (struct (field i32)))
+   (type $q (struct (field i32)))
+   (func $f (result (ref $p))
+    (struct.new $q (i32.const 0))))
+`);
+
 // The field name is optional, so this should work.
 
 wasmEvalText(`
 (module
  (gc_feature_opt_in 1)
  (type $s (struct (field i32))))
 `)
 
@@ -136,16 +574,39 @@ assertErrorMessage(() => wasmEvalText(`
 (module
  (gc_feature_opt_in 1)
  (type $s (struct))
  (type $f (func (param i32) (result i32)))
  (func (type 0) (param i32) (result i32) (unreachable)))
 `),
 WebAssembly.CompileError, /signature index references non-signature/);
 
+// Can't set immutable fields from JS
+
+{
+    let ins = wasmEvalText(
+        `(module
+          (gc_feature_opt_in 1)
+          (type $s (struct
+                    (field i32)
+                    (field (mut i64))))
+          (func (export "make") (result anyref)
+           (struct.new $s (i32.const 37) (i64.const 42))))`).exports;
+    let v = ins.make();
+    assertErrorMessage(() => v._0 = 12,
+                       Error,
+                       /setting immutable field/);
+    assertErrorMessage(() => v._1_low = 12,
+                       Error,
+                       /setting immutable field/);
+    assertErrorMessage(() => v._1_high = 12,
+                       Error,
+                       /setting immutable field/);
+}
+
 // Function should not reference struct type: binary test
 
 var bad = new Uint8Array([0x00, 0x61, 0x73, 0x6d,
                           0x01, 0x00, 0x00, 0x00,
 
                           0x2a,                   // GcFeatureOptIn section
                           0x01,                   // Section size
                           0x01,                   // Version
--- a/js/src/js.msg
+++ b/js/src/js.msg
@@ -388,16 +388,17 @@ MSG_DEF(JSMSG_WASM_UNREACHABLE,        0
 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_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\"")
--- a/js/src/vm/GlobalObject.h
+++ b/js/src/vm/GlobalObject.h
@@ -21,16 +21,18 @@
 
 namespace js {
 
 class Debugger;
 class TypedObjectModuleObject;
 class LexicalEnvironmentObject;
 class RegExpStatics;
 
+enum class ReferenceType;
+
 /*
  * Global object slots are reserved as follows:
  *
  * [0, APPLICATION_SLOTS)
  *   Pre-reserved slots in all global objects set aside for the embedding's
  *   use. As with all reserved slots these start out as UndefinedValue() and
  *   are traced for GC purposes. Apart from that the engine never touches
  *   these slots, so the embedding can do whatever it wants with them.
@@ -467,16 +469,24 @@ class GlobalObject : public NativeObject
     }
 
     static JSObject*
     getOrCreateTypedObjectModule(JSContext* cx, Handle<GlobalObject*> global) {
         return getOrCreateObject(cx, global, APPLICATION_SLOTS + JSProto_TypedObject,
                                  initTypedObjectModule);
     }
 
+    static TypeDescr*
+    getOrCreateScalarTypeDescr(JSContext* cx, Handle<GlobalObject*> global,
+                               Scalar::Type scalarType);
+
+    static TypeDescr*
+    getOrCreateReferenceTypeDescr(JSContext* cx, Handle<GlobalObject*> global,
+                                  ReferenceType type);
+
     TypedObjectModuleObject& getTypedObjectModule() const;
 
     static JSObject*
     getOrCreateCollatorPrototype(JSContext* cx, Handle<GlobalObject*> global) {
         return getOrCreateObject(cx, global, COLLATOR_PROTO, initIntlObject);
     }
 
     static JSFunction*
--- a/js/src/wasm/WasmAST.h
+++ b/js/src/wasm/WasmAST.h
@@ -493,16 +493,22 @@ enum class AstExprKind
     If,
     Load,
 #ifdef ENABLE_WASM_BULKMEM_OPS
     MemOrTableCopy,
     MemOrTableDrop,
     MemFill,
     MemOrTableInit,
 #endif
+#ifdef ENABLE_WASM_GC
+    StructNew,
+    StructGet,
+    StructSet,
+    StructNarrow,
+#endif
     Nop,
     Pop,
     RefNull,
     Return,
     SetGlobal,
     SetLocal,
     TeeLocal,
     Store,
@@ -1048,16 +1054,92 @@ class AstMemOrTableInit : public AstExpr
     bool     isMem()    const { return isMem_; }
     uint32_t segIndex() const { return segIndex_; }
     AstExpr& dst()      const { return *dst_; }
     AstExpr& src()      const { return *src_; }
     AstExpr& len()      const { return *len_; }
 };
 #endif
 
+#ifdef ENABLE_WASM_GC
+class AstStructNew : public AstExpr
+{
+    AstRef structType_;
+    AstExprVector fieldValues_;
+
+  public:
+    static const AstExprKind Kind = AstExprKind::StructNew;
+    AstStructNew(AstRef structType, AstExprType type, AstExprVector&& fieldVals)
+      : AstExpr(Kind, type), structType_(structType), fieldValues_(std::move(fieldVals))
+    {}
+    AstRef& structType() { return structType_; }
+    const AstExprVector& fieldValues() const { return fieldValues_; }
+};
+
+class AstStructGet : public AstExpr
+{
+    AstRef   structType_;
+    uint32_t index_;
+    AstExpr* ptr_;
+
+  public:
+    static const AstExprKind Kind = AstExprKind::StructGet;
+    AstStructGet(AstRef structType, uint32_t index, AstExprType type, AstExpr* ptr)
+      : AstExpr(Kind, type),
+        structType_(structType),
+        index_(index),
+        ptr_(ptr)
+    {}
+    AstRef& structType() { return structType_; }
+    uint32_t index() { return index_; }
+    AstExpr& ptr() const { return *ptr_; }
+};
+
+class AstStructSet : public AstExpr
+{
+    AstRef   structType_;
+    uint32_t index_;
+    AstExpr* ptr_;
+    AstExpr* value_;
+
+  public:
+    static const AstExprKind Kind = AstExprKind::StructSet;
+    AstStructSet(AstRef structType, uint32_t index, AstExpr* ptr, AstExpr* value)
+      : AstExpr(Kind, ExprType::Void),
+        structType_(structType),
+        index_(index),
+        ptr_(ptr),
+        value_(value)
+    {}
+    AstRef& structType() { return structType_; }
+    uint32_t index() { return index_; }
+    AstExpr& ptr() const { return *ptr_; }
+    AstExpr& value() const { return *value_; }
+};
+
+class AstStructNarrow : public AstExpr
+{
+    AstValType inputStruct_;
+    AstValType outputStruct_;
+    AstExpr*   ptr_;
+
+  public:
+    static const AstExprKind Kind = AstExprKind::StructNarrow;
+    AstStructNarrow(AstValType inputStruct, AstValType outputStruct, AstExpr* ptr)
+      : AstExpr(Kind, AstExprType(outputStruct)),
+        inputStruct_(inputStruct),
+        outputStruct_(outputStruct),
+        ptr_(ptr)
+    {}
+    AstValType& inputStruct() { return inputStruct_; }
+    AstValType& outputStruct() { return outputStruct_; }
+    AstExpr& ptr() const { return *ptr_; }
+};
+#endif
+
 class AstCurrentMemory final : public AstExpr
 {
   public:
     static const AstExprKind Kind = AstExprKind::CurrentMemory;
     explicit AstCurrentMemory()
       : AstExpr(Kind, ExprType::I32)
     {}
 };
--- a/js/src/wasm/WasmBaselineCompile.cpp
+++ b/js/src/wasm/WasmBaselineCompile.cpp
@@ -1922,16 +1922,17 @@ class BaseCompiler final : public BaseCo
     ValTypeVector               SigD_;
     ValTypeVector               SigF_;
     MIRTypeVector               SigP_;
     MIRTypeVector               SigPI_;
     MIRTypeVector               SigPL_;
     MIRTypeVector               SigPII_;
     MIRTypeVector               SigPIII_;
     MIRTypeVector               SigPIIL_;
+    MIRTypeVector               SigPIIP_;
     MIRTypeVector               SigPILL_;
     MIRTypeVector               SigPIIII_;
     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
@@ -5777,64 +5778,81 @@ class BaseCompiler final : public BaseCo
         MOZ_ASSERT(!GeneralRegisterSet::All().hasRegisterIndex(x28.asUnsized()));
         masm.Mov(x28, sp);
 # endif
         masm.call(scratch);
 
         masm.bind(&skipBarrier);
     }
 
-    // This emits a GC post-write barrier. This is needed to ensure that the GC
-    // is aware of slots of tenured things containing references to nursery
-    // values. Pass None for object when the field's owner object is known to
-    // be tenured or heap-allocated.
+    // Emit a GC post-write barrier.  The barrier is needed to ensure that the
+    // GC is aware of slots of tenured things containing references to nursery
+    // values.
+    //
+    // The barrier has five easy steps:
+    //
+    //   Label skipBarrier;
+    //   sync();
+    //   emitPostBarrierGuard(..., &skipBarrier);
+    //   emitPostBarrier(...);
+    //   bind(&skipBarrier);
     //
-    // This frees the register `valueAddr`.
-
-    void emitPostBarrier(const Maybe<RegPtr>& object, RegPtr otherScratch, RegPtr valueAddr, RegPtr setValue) {
-        Label skipBarrier;
-
-        // One of the branches (in case we need the C++ call) will cause a sync,
-        // so ensure the stack is sync'd before, so that the join is sync'd too.
-        sync();
-
+    // These are divided up to allow other actions to be placed between them,
+    // such as saving and restoring live registers.  postBarrier() will make a
+    // call to C++ and will kill all live registers.  The initial sync() is
+    // required to make sure all paths sync the same amount.
+
+    // Pass None for `object` when the field's owner object is known to be
+    // tenured or heap-allocated.
+
+    void emitPostBarrierGuard(const Maybe<RegPtr>& object, RegPtr otherScratch, RegPtr setValue,
+                              Label* skipBarrier)
+    {
         // If the pointer being stored is null, no barrier.
-        masm.branchTestPtr(Assembler::Zero, setValue, setValue, &skipBarrier);
+        masm.branchTestPtr(Assembler::Zero, setValue, setValue, skipBarrier);
 
         // If there is a containing object and it is in the nursery, no barrier.
         if (object) {
-            masm.branchPtrInNurseryChunk(Assembler::Equal, *object, otherScratch, &skipBarrier);
+            masm.branchPtrInNurseryChunk(Assembler::Equal, *object, otherScratch, skipBarrier);
         }
 
         // If the pointer being stored is to a tenured object, no barrier.
-        masm.branchPtrInNurseryChunk(Assembler::NotEqual, setValue, otherScratch, &skipBarrier);
-
-        // Need a barrier.
+        masm.branchPtrInNurseryChunk(Assembler::NotEqual, setValue, otherScratch, skipBarrier);
+    }
+
+    // This frees the register `valueAddr`.
+
+    void emitPostBarrier(RegPtr valueAddr) {
         uint32_t bytecodeOffset = iter_.lastOpcodeOffset();
 
         // The `valueAddr` is a raw pointer to the cell within some GC object or
         // TLS area, and we guarantee that the GC will not run while the
         // postbarrier call is active, so push a uintptr_t value.
 # ifdef JS_64BIT
         pushI64(RegI64(Register64(valueAddr)));
         emitInstanceCall(bytecodeOffset, SigPL_, ExprType::Void, SymbolicAddress::PostBarrier);
 # else
         pushI32(RegI32(valueAddr));
         emitInstanceCall(bytecodeOffset, SigPI_, ExprType::Void, SymbolicAddress::PostBarrier);
 # endif
-
-        masm.bind(&skipBarrier);
     }
 
     void emitBarrieredStore(const Maybe<RegPtr>& object, RegPtr valueAddr, RegPtr value) {
         emitPreBarrier(valueAddr); // Preserves valueAddr
         masm.storePtr(value, Address(valueAddr, 0));
+
+        Label skipBarrier;
+        sync();
+
         RegPtr otherScratch = needRef();
-        emitPostBarrier(object, otherScratch, valueAddr, value); // Consumes valueAddr
+        emitPostBarrierGuard(object, otherScratch, value, &skipBarrier);
         freeRef(otherScratch);
+
+        emitPostBarrier(valueAddr);
+        masm.bind(&skipBarrier);
     }
 #endif // ENABLE_WASM_GC
 
     ////////////////////////////////////////////////////////////
     //
     // Machinery for optimized conditional branches.
     //
     // To disable this optimization it is enough always to return false from
@@ -6141,16 +6159,22 @@ 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
+#ifdef ENABLE_WASM_GC
+    MOZ_MUST_USE bool emitStructNew();
+    MOZ_MUST_USE bool emitStructGet();
+    MOZ_MUST_USE bool emitStructSet();
+    MOZ_MUST_USE bool emitStructNarrow();
+#endif
 };
 
 void
 BaseCompiler::emitAddI32()
 {
     int32_t c;
     if (popConstI32(&c)) {
         RegI32 r = popI32();
@@ -9204,18 +9228,19 @@ BaseCompiler::emitInstanceCall(uint32_t 
     beginCall(baselineCall, UseABI::System, InterModule::True);
 
     ABIArg instanceArg = reservePointerArgument(&baselineCall);
 
     startCallArgs(stackArgAreaSize(sig), &baselineCall);
     for (uint32_t i = 1; i < sig.length(); i++) {
         ValType t;
         switch (sig[i]) {
-          case MIRType::Int32: t = ValType::I32; break;
-          case MIRType::Int64: t = ValType::I64; break;
+          case MIRType::Int32:   t = ValType::I32; break;
+          case MIRType::Int64:   t = ValType::I64; break;
+          case MIRType::Pointer: t = ValType::AnyRef; break;
           default:             MOZ_CRASH("Unexpected type");
         }
         passArg(t, peek(numArgs - i), &baselineCall);
     }
     builtinInstanceMethodCall(builtin, instanceArg, baselineCall);
     endCall(baselineCall, stackSpace);
 
     popValueStackBy(numArgs);
@@ -9729,16 +9754,378 @@ BaseCompiler::emitMemOrTableInit(bool is
     masm.branchTest32(Assembler::NotSigned, ReturnReg, ReturnReg, &ok);
     trap(Trap::ThrowReported);
     masm.bind(&ok);
 
     return true;
 }
 #endif
 
+#ifdef ENABLE_WASM_GC
+bool
+BaseCompiler::emitStructNew()
+{
+    uint32_t lineOrBytecode = readCallSiteLineOrBytecode();
+
+    uint32_t typeIndex;
+    BaseOpIter::ValueVector args;
+    if (!iter_.readStructNew(&typeIndex, &args)) {
+        return false;
+    }
+
+    if (deadCode_) {
+        return true;
+    }
+
+    // Allocate zeroed storage.  The parameter to StructNew is an index into a
+    // descriptor table that the instance has.
+
+    const StructType& structType = env_.types[typeIndex].structType();
+
+    pushI32(structType.moduleIndex_);
+    emitInstanceCall(lineOrBytecode, SigPI_, ExprType::AnyRef, SymbolicAddress::StructNew);
+
+    // Null pointer check.
+
+    Label ok;
+    masm.branchTestPtr(Assembler::NotEqual, ReturnReg, ReturnReg, &ok);
+    trap(Trap::ThrowReported);
+    masm.bind(&ok);
+
+    // As many arguments as there are fields.
+
+    MOZ_ASSERT(args.length() == structType.fields_.length());
+
+    // Optimization opportunity: Iterate backward to pop arguments off the
+    // stack.  This will generate more instructions than we want, since we
+    // really only need to pop the stack once at the end, not for every element,
+    // but to do better we need a bit more machinery to load elements off the
+    // stack into registers.
+
+    RegPtr rp = popRef();
+    RegPtr rdata = rp;
+
+    if (!structType.isInline_) {
+        rdata = needRef();
+        masm.loadPtr(Address(rp, OutlineTypedObject::offsetOfData()), rdata);
+    }
+
+    // Optimization opportunity: when the value being stored is a known
+    // zero/null we need store nothing.  This case may be somewhat common
+    // because struct.new forces a value to be specified for every field.
+
+    uint32_t fieldNo = structType.fields_.length();
+    while (fieldNo-- > 0) {
+        uint32_t offs = structType.fields_[fieldNo].offset;
+        switch (structType.fields_[fieldNo].type.code()) {
+          case ValType::I32: {
+            RegI32 r = popI32();
+            masm.store32(r, Address(rdata, offs));
+            freeI32(r);
+            break;
+          }
+          case ValType::I64: {
+            RegI64 r = popI64();
+            masm.store64(r, Address(rdata, offs));
+            freeI64(r);
+            break;
+          }
+          case ValType::F32: {
+            RegF32 r = popF32();
+            masm.storeFloat32(r, Address(rdata, offs));
+            freeF32(r);
+            break;
+          }
+          case ValType::F64: {
+            RegF64 r = popF64();
+            masm.storeDouble(r, Address(rdata, offs));
+            freeF64(r);
+            break;
+          }
+          case ValType::Ref:
+          case ValType::AnyRef: {
+            RegPtr value = popRef();
+            masm.storePtr(value, Address(rdata, offs));
+
+            // A write barrier is needed here for the extremely unlikely case
+            // that the object is allocated in the tenured area - a result of
+            // a GC artifact.
+
+            Label skipBarrier;
+
+            sync();
+
+            RegPtr rowner = rp;
+            if (!structType.isInline_) {
+                rowner = needRef();
+                masm.loadPtr(Address(rp, OutlineTypedObject::offsetOfOwner()), rowner);
+            }
+
+            RegPtr otherScratch = needRef();
+            emitPostBarrierGuard(Some(rowner), otherScratch, value, &skipBarrier);
+            freeRef(otherScratch);
+
+            if (!structType.isInline_) {
+                freeRef(rowner);
+            }
+
+            freeRef(value);
+
+            pushRef(rp);                // Save rp across the call
+            RegPtr valueAddr = needRef();
+            masm.computeEffectiveAddress(Address(rdata, offs), valueAddr);
+            emitPostBarrier(valueAddr); // Consumes valueAddr
+            popRef(rp);                 // Restore rp
+            if (!structType.isInline_) {
+                masm.loadPtr(Address(rp, OutlineTypedObject::offsetOfData()), rdata);
+            }
+
+            masm.bind(&skipBarrier);
+            break;
+          }
+          default: {
+            MOZ_CRASH("Unexpected field type");
+          }
+        }
+    }
+
+    if (!structType.isInline_) {
+        freeRef(rdata);
+    }
+
+    pushRef(rp);
+
+    return true;
+}
+
+bool
+BaseCompiler::emitStructGet()
+{
+    uint32_t typeIndex;
+    uint32_t fieldIndex;
+    Nothing nothing;
+    if (!iter_.readStructGet(&typeIndex, &fieldIndex, &nothing)) {
+        return false;
+    }
+
+    if (deadCode_) {
+        return true;
+    }
+
+    const StructType& structType = env_.types[typeIndex].structType();
+
+    RegPtr rp = popRef();
+
+    Label ok;
+    masm.branchTestPtr(Assembler::NotEqual, rp, rp, &ok);
+    trap(Trap::NullPointerDereference);
+    masm.bind(&ok);
+
+    if (!structType.isInline_) {
+        masm.loadPtr(Address(rp, OutlineTypedObject::offsetOfData()), rp);
+    }
+
+    uint32_t offs = structType.fields_[fieldIndex].offset;
+    switch (structType.fields_[fieldIndex].type.code()) {
+      case ValType::I32: {
+          RegI32 r = needI32();
+          masm.load32(Address(rp, offs), r);
+          pushI32(r);
+          break;
+      }
+      case ValType::I64: {
+          RegI64 r = needI64();
+          masm.load64(Address(rp, offs), r);
+          pushI64(r);
+          break;
+      }
+      case ValType::F32: {
+          RegF32 r = needF32();
+          masm.loadFloat32(Address(rp, offs), r);
+          pushF32(r);
+          break;
+      }
+      case ValType::F64: {
+          RegF64 r = needF64();
+          masm.loadDouble(Address(rp, offs), r);
+          pushF64(r);
+          break;
+      }
+      case ValType::Ref:
+      case ValType::AnyRef: {
+          RegPtr r = needRef();
+          masm.loadPtr(Address(rp, offs), r);
+          pushRef(r);
+          break;
+      }
+      default: {
+          MOZ_CRASH("Unexpected field type");
+      }
+    }
+
+    freeRef(rp);
+
+    return true;
+}
+
+bool
+BaseCompiler::emitStructSet()
+{
+    uint32_t typeIndex;
+    uint32_t fieldIndex;
+    Nothing nothing;
+    if (!iter_.readStructSet(&typeIndex, &fieldIndex, &nothing, &nothing)) {
+        return false;
+    }
+
+    if (deadCode_) {
+        return true;
+    }
+
+    const StructType& structType = env_.types[typeIndex].structType();
+
+    RegI32 ri;
+    RegI64 rl;
+    RegF32 rf;
+    RegF64 rd;
+    RegPtr rr;
+
+#ifdef ENABLE_WASM_GC
+    // Reserve this register early if we will need it so that it is not taken by
+    // rr or rp.
+    RegPtr valueAddr;
+    if (structType.fields_[fieldIndex].type.isRefOrAnyRef()) {
+        valueAddr = RegPtr(PreBarrierReg);
+        needRef(valueAddr);
+    }
+#endif
+
+    switch (structType.fields_[fieldIndex].type.code()) {
+      case ValType::I32:
+        ri = popI32();
+        break;
+      case ValType::I64:
+        rl = popI64();
+        break;
+      case ValType::F32:
+        rf = popF32();
+        break;
+      case ValType::F64:
+        rd = popF64();
+        break;
+      case ValType::Ref:
+      case ValType::AnyRef:
+        rr = popRef();
+        break;
+      default:
+        MOZ_CRASH("Unexpected field type");
+    }
+
+    RegPtr rp = popRef();
+
+    Label ok;
+    masm.branchTestPtr(Assembler::NotEqual, rp, rp, &ok);
+    trap(Trap::NullPointerDereference);
+    masm.bind(&ok);
+
+    if (!structType.isInline_) {
+        masm.loadPtr(Address(rp, OutlineTypedObject::offsetOfData()), rp);
+    }
+
+    uint32_t offs = structType.fields_[fieldIndex].offset;
+    switch (structType.fields_[fieldIndex].type.code()) {
+      case ValType::I32: {
+        masm.store32(ri, Address(rp, offs));
+        freeI32(ri);
+        break;
+      }
+      case ValType::I64: {
+        masm.store64(rl, Address(rp, offs));
+        freeI64(rl);
+        break;
+      }
+      case ValType::F32: {
+        masm.storeFloat32(rf, Address(rp, offs));
+        freeF32(rf);
+        break;
+      }
+      case ValType::F64: {
+        masm.storeDouble(rd, Address(rp, offs));
+        freeF64(rd);
+        break;
+      }
+#ifdef ENABLE_WASM_GC
+      case ValType::Ref:
+      case ValType::AnyRef:
+        masm.computeEffectiveAddress(Address(rp, offs), valueAddr);
+        emitBarrieredStore(Some(rp), valueAddr, rr);// Consumes valueAddr
+        freeRef(rr);
+        break;
+#endif
+      default: {
+        MOZ_CRASH("Unexpected field type");
+      }
+    }
+
+    freeRef(rp);
+
+    return true;
+}
+
+bool
+BaseCompiler::emitStructNarrow()
+{
+    uint32_t lineOrBytecode = readCallSiteLineOrBytecode();
+
+    ValType inputType, outputType;
+    Nothing nothing;
+    if (!iter_.readStructNarrow(&inputType, &outputType, &nothing)) {
+        return false;
+    }
+
+    if (deadCode_) {
+        return true;
+    }
+
+    // AnyRef -> AnyRef is a no-op, just leave the value on the stack.
+
+    if (inputType == ValType::AnyRef && outputType == ValType::AnyRef) {
+        return true;
+    }
+
+    // Null pointers are just passed through.
+
+    Label done;
+    Label doTest;
+    RegPtr rp = popRef();
+    masm.branchTestPtr(Assembler::NotEqual, rp, rp, &doTest);
+    pushRef(NULLREF_VALUE);
+    masm.jump(&done);
+
+    // 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
+
+    const StructType& outputStruct = env_.types[outputType.refTypeIndex()].structType();
+
+    masm.bind(&doTest);
+
+    pushI32(mustUnboxAnyref);
+    pushI32(outputStruct.moduleIndex_);
+    pushRef(rp);
+    emitInstanceCall(lineOrBytecode, SigPIIP_, ExprType::AnyRef, SymbolicAddress::StructNarrow);
+
+    masm.bind(&done);
+
+    return true;
+}
+#endif
+
 bool
 BaseCompiler::emitBody()
 {
     if (!iter_.readFunctionStart(funcType().ret())) {
         return false;
     }
 
     initControl(controlItem());
@@ -10382,16 +10769,38 @@ 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_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) {
+                  return iter_.unrecognizedOpcode(&op);
+                }
+                CHECK_NEXT(emitStructGet());
+              case uint16_t(MiscOp::StructSet):
+                if (env_.gcTypesEnabled() == HasGcTypes::False) {
+                  return iter_.unrecognizedOpcode(&op);
+                }
+                CHECK_NEXT(emitStructSet());
+              case uint16_t(MiscOp::StructNarrow):
+                if (env_.gcTypesEnabled() == HasGcTypes::False) {
+                  return iter_.unrecognizedOpcode(&op);
+                }
+                CHECK_NEXT(emitStructNarrow());
+#endif
               default:
                 break;
             } // switch (op.b1)
             return iter_.unrecognizedOpcode(&op);
           }
 
           // Thread operations
           case uint16_t(Op::ThreadPrefix): {
@@ -10544,17 +10953,17 @@ BaseCompiler::emitBody()
                 return iter_.unrecognizedOpcode(&op);
             }
 #else
             return iter_.unrecognizedOpcode(&op);
 #endif  // ENABLE_WASM_THREAD_OPS
             break;
           }
 
-          // asm.js operations
+          // asm.js and other private operations
           case uint16_t(Op::MozPrefix):
             return iter_.unrecognizedOpcode(&op);
 
           default:
             return iter_.unrecognizedOpcode(&op);
         }
 
 #undef CHECK
@@ -10647,16 +11056,21 @@ BaseCompiler::init()
     {
         return false;
     }
     if (!SigPIIL_.append(MIRType::Pointer) || !SigPIIL_.append(MIRType::Int32) ||
         !SigPIIL_.append(MIRType::Int32) || !SigPIIL_.append(MIRType::Int64))
     {
         return false;
     }
+    if (!SigPIIP_.append(MIRType::Pointer) || !SigPIIP_.append(MIRType::Int32) ||
+        !SigPIIP_.append(MIRType::Int32) || !SigPIIP_.append(MIRType::Pointer))
+    {
+        return false;
+    }
     if (!SigPILL_.append(MIRType::Pointer) || !SigPILL_.append(MIRType::Int32) ||
         !SigPILL_.append(MIRType::Int64) || !SigPILL_.append(MIRType::Int64))
     {
         return false;
     }
     if (!SigPIIII_.append(MIRType::Pointer) || !SigPIIII_.append(MIRType::Int32) ||
         !SigPIIII_.append(MIRType::Int32) || !SigPIIII_.append(MIRType::Int32) ||
         !SigPIIII_.append(MIRType::Int32))
--- a/js/src/wasm/WasmBuiltins.cpp
+++ b/js/src/wasm/WasmBuiltins.cpp
@@ -261,16 +261,18 @@ WasmHandleTrap()
       case Trap::InvalidConversionToInteger:
         return ReportError(cx, JSMSG_WASM_INVALID_CONVERSION);
       case Trap::IntegerDivideByZero:
         return ReportError(cx, JSMSG_WASM_INT_DIVIDE_BY_ZERO);
       case Trap::IndirectCallToNull:
         return ReportError(cx, JSMSG_WASM_IND_CALL_TO_NULL);
       case Trap::IndirectCallBadSig:
         return ReportError(cx, JSMSG_WASM_IND_CALL_BAD_SIG);
+      case Trap::NullPointerDereference:
+        return ReportError(cx, JSMSG_WASM_DEREF_NULL);
       case Trap::OutOfBounds:
         return ReportError(cx, JSMSG_WASM_OUT_OF_BOUNDS);
       case Trap::UnalignedAccess:
         return ReportError(cx, JSMSG_WASM_UNALIGNED_ACCESS);
       case Trap::CheckInterrupt:
         return CheckInterrupt(cx, activation);
       case Trap::StackOverflow:
         // TlsData::setInterrupt() causes a fake stack overflow. Since
@@ -686,16 +688,22 @@ AddressOf(SymbolicAddress imm, ABIFuncti
       case SymbolicAddress::TableInit:
         *abiType = Args_General5;
         return FuncCast(Instance::tableInit, *abiType);
 #ifdef ENABLE_WASM_GC
       case SymbolicAddress::PostBarrier:
         *abiType = Args_General2;
         return FuncCast(Instance::postBarrier, *abiType);
 #endif
+      case SymbolicAddress::StructNew:
+        *abiType = Args_General2;
+        return FuncCast(Instance::structNew, *abiType);
+      case SymbolicAddress::StructNarrow:
+        *abiType = Args_General4;
+        return FuncCast(Instance::structNarrow, *abiType);
 #if defined(JS_CODEGEN_MIPS32)
       case SymbolicAddress::js_jit_gAtomic64Lock:
         return &js::jit::gAtomic64Lock;
 #endif
       case SymbolicAddress::Limit:
         break;
     }
 
@@ -770,16 +778,18 @@ wasm::NeedsBuiltinThunk(SymbolicAddress 
       case SymbolicAddress::MemFill:
       case SymbolicAddress::MemInit:
       case SymbolicAddress::TableCopy:
       case SymbolicAddress::TableDrop:
       case SymbolicAddress::TableInit:
 #ifdef ENABLE_WASM_GC
       case SymbolicAddress::PostBarrier:
 #endif
+      case SymbolicAddress::StructNew:
+      case SymbolicAddress::StructNarrow:
         return true;
       case SymbolicAddress::Limit:
         break;
     }
 
     MOZ_CRASH("unexpected symbolic address");
 }
 
--- a/js/src/wasm/WasmCode.cpp
+++ b/js/src/wasm/WasmCode.cpp
@@ -1225,21 +1225,23 @@ JumpTables::init(CompileMode mode, const
             setTieringEntry(cr.funcIndex(), codeBase + cr.funcTierEntry());
         } else if (cr.isJitEntry()) {
             setJitEntry(cr.funcIndex(), codeBase + cr.begin());
         }
     }
     return true;
 }
 
-Code::Code(UniqueCodeTier tier1, const Metadata& metadata, JumpTables&& maybeJumpTables)
+Code::Code(UniqueCodeTier tier1, const Metadata& metadata, JumpTables&& maybeJumpTables,
+           StructTypeVector&& structTypes)
   : tier1_(std::move(tier1)),
     metadata_(&metadata),
     profilingLabels_(mutexid::WasmCodeProfilingLabels, CacheableCharsVector()),
-    jumpTables_(std::move(maybeJumpTables))
+    jumpTables_(std::move(maybeJumpTables)),
+    structTypes_(std::move(structTypes))
 {}
 
 bool
 Code::initialize(const LinkData& linkData)
 {
     MOZ_ASSERT(!initialized());
 
     if (!tier1_->initialize(*this, linkData, *metadata_)) {
@@ -1523,32 +1525,35 @@ Code::addSizeOfMiscIfNotSeen(MallocSizeO
     *data += mallocSizeOf(this) +
              metadata().sizeOfIncludingThisIfNotSeen(mallocSizeOf, seenMetadata) +
              profilingLabels_.lock()->sizeOfExcludingThis(mallocSizeOf) +
              jumpTables_.sizeOfMiscExcludingThis();
 
     for (auto t : tiers()) {
         codeTier(t).addSizeOfMisc(mallocSizeOf, code, data);
     }
+    *data += SizeOfVectorExcludingThis(structTypes_, mallocSizeOf);
 }
 
 size_t
 Code::serializedSize() const
 {
     return metadata().serializedSize() +
-           codeTier(Tier::Serialized).serializedSize();
+           codeTier(Tier::Serialized).serializedSize() +
+           SerializedVectorSize(structTypes_);
 }
 
 uint8_t*
 Code::serialize(uint8_t* cursor, const LinkData& linkData) const
 {
     MOZ_RELEASE_ASSERT(!metadata().debugEnabled);
 
     cursor = metadata().serialize(cursor);
     cursor = codeTier(Tier::Serialized).serialize(cursor, linkData);
+    cursor = SerializeVector(cursor, structTypes_);
     return cursor;
 }
 
 /* static */ const uint8_t*
 Code::deserialize(const uint8_t* cursor,
                   const LinkData& linkData,
                   Metadata& metadata,
                   SharedCode* out)
@@ -1564,16 +1569,23 @@ Code::deserialize(const uint8_t* cursor,
         return nullptr;
     }
 
     JumpTables jumpTables;
     if (!jumpTables.init(CompileMode::Once, codeTier->segment(), codeTier->metadata().codeRanges)) {
         return nullptr;
     }
 
-    MutableCode code = js_new<Code>(std::move(codeTier), metadata, std::move(jumpTables));
+    StructTypeVector structTypes;
+    cursor = DeserializeVector(cursor, &structTypes);
+    if (!cursor) {
+        return nullptr;
+    }
+
+    MutableCode code = js_new<Code>(std::move(codeTier), metadata, std::move(jumpTables),
+                                    std::move(structTypes));
     if (!code || !code->initialize(linkData)) {
         return nullptr;
     }
 
     *out = code;
     return cursor;
 }
--- a/js/src/wasm/WasmCode.h
+++ b/js/src/wasm/WasmCode.h
@@ -681,21 +681,23 @@ typedef RefPtr<Code> MutableCode;
 class Code : public ShareableBase<Code>
 {
     UniqueCodeTier                      tier1_;
     mutable UniqueConstCodeTier         tier2_; // Access only when hasTier2() is true
     mutable Atomic<bool>                hasTier2_;
     SharedMetadata                      metadata_;
     ExclusiveData<CacheableCharsVector> profilingLabels_;
     JumpTables                          jumpTables_;
+    StructTypeVector                    structTypes_;
 
   public:
     Code(UniqueCodeTier tier1,
          const Metadata& metadata,
-         JumpTables&& maybeJumpTables);
+         JumpTables&& maybeJumpTables,
+         StructTypeVector&& structTypes);
     bool initialized() const { return tier1_->initialized(); }
 
     bool initialize(const LinkData& linkData);
 
     void setTieringEntry(size_t i, void* target) const { jumpTables_.setTieringEntry(i, target); }
     void** tieringJumpTable() const { return jumpTables_.tiering(); }
 
     void setJitEntry(size_t i, void* target) const { jumpTables_.setJitEntry(i, target); }
@@ -709,16 +711,17 @@ class Code : public ShareableBase<Code>
     Tiers tiers() const;
     bool hasTier(Tier t) const;
 
     Tier stableTier() const;    // This is stable during a run
     Tier bestTier() const;      // This may transition from Baseline -> Ion at any time
 
     const CodeTier& codeTier(Tier tier) const;
     const Metadata& metadata() const { return *metadata_; }
+    const StructTypeVector& structTypes() const { return structTypes_; }
 
     const ModuleSegment& segment(Tier iter) const {
         return codeTier(iter).segment();
     }
     const MetadataTier& metadata(Tier iter) const {
         return codeTier(iter).metadata();
     }
 
--- a/js/src/wasm/WasmConstants.h
+++ b/js/src/wasm/WasmConstants.h
@@ -99,16 +99,18 @@ enum class Trap
     OutOfBounds,
     // Unaligned on wasm atomic accesses; also used for non-standard ARM
     // unaligned access faults.
     UnalignedAccess,
     // call_indirect to null.
     IndirectCallToNull,
     // call_indirect signature mismatch.
     IndirectCallBadSig,
+    // Dereference null pointer in operation on (Ref T)
+    NullPointerDereference,
 
     // The internal stack space was exhausted. For compatibility, this throws
     // the same over-recursed error as JS.
     StackOverflow,
 
     // The wasm execution has potentially run too long and the engine must call
     // CheckForInterrupt(). This trap is resumable.
     CheckInterrupt,
@@ -394,16 +396,22 @@ enum class MiscOp
     MemInit                              = 0x08,
     MemDrop                              = 0x09,
     MemCopy                              = 0x0a,
     MemFill                              = 0x0b,
     TableInit                            = 0x0c,
     TableDrop                            = 0x0d,
     TableCopy                            = 0x0e,
 
+    // Structure operations.  Note, these are unofficial.
+    StructNew                            = 0x50,
+    StructGet                            = 0x51,
+    StructSet                            = 0x52,
+    StructNarrow                         = 0x53,
+
     Limit
 };
 
 // Opcodes from threads proposal as of June 30, 2017
 enum class ThreadOp
 {
     // Wait and wake
     Wake                                 = 0x00,
--- a/js/src/wasm/WasmFrameIter.cpp
+++ b/js/src/wasm/WasmFrameIter.cpp
@@ -1400,16 +1400,20 @@ ThunkedNativeToDescription(SymbolicAddre
       case SymbolicAddress::TableDrop:
         return "call to native table.drop function";
       case SymbolicAddress::TableInit:
         return "call to native table.init function";
 #ifdef ENABLE_WASM_GC
       case SymbolicAddress::PostBarrier:
         return "call to native GC postbarrier (in wasm)";
 #endif
+      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:
         MOZ_CRASH();
 #endif
       case SymbolicAddress::Limit:
         break;
     }
     return "?";
--- a/js/src/wasm/WasmGenerator.cpp
+++ b/js/src/wasm/WasmGenerator.cpp
@@ -1032,28 +1032,29 @@ ModuleGenerator::finishModule(const Shar
     if (!jumpTables.init(mode(), codeTier->segment(), codeTier->metadata().codeRanges)) {
         return nullptr;
     }
 
     if (!finishMetadata(bytecode.bytes)) {
         return nullptr;
     }
 
-    MutableCode code = js_new<Code>(std::move(codeTier), *metadata_, std::move(jumpTables));
-    if (!code || !code->initialize(*linkData_)) {
-        return nullptr;
-    }
-
     StructTypeVector structTypes;
     for (TypeDef& td : env_->types) {
         if (td.isStructType() && !structTypes.append(std::move(td.structType()))) {
             return nullptr;
         }
     }
 
+    MutableCode code = js_new<Code>(std::move(codeTier), *metadata_, std::move(jumpTables),
+                                    std::move(structTypes));
+    if (!code || !code->initialize(*linkData_)) {
+        return nullptr;
+    }
+
     // Copy over data from the Bytecode, which is going away at the end of
     // compilation.
 
     DataSegmentVector dataSegments;
     if (!dataSegments.reserve(env_->dataSegments.length())) {
         return nullptr;
     }
     for (const DataSegmentEnv& srcSeg : env_->dataSegments) {
@@ -1113,17 +1114,16 @@ ModuleGenerator::finishModule(const Shar
     }
 
     // All the components are finished, so create the complete Module and start
     // tier-2 compilation if requested.
 
     MutableModule module = js_new<Module>(*code,
                                           std::move(env_->imports),
                                           std::move(env_->exports),
-                                          std::move(structTypes),
                                           std::move(dataSegments),
                                           std::move(env_->elemSegments),
                                           std::move(customSections),
                                           std::move(debugUnlinkedCode),
                                           std::move(debugLinkData),
                                           debugBytecode);
     if (!module) {
         return nullptr;
--- a/js/src/wasm/WasmInstance.cpp
+++ b/js/src/wasm/WasmInstance.cpp
@@ -691,35 +691,110 @@ Instance::tableInit(Instance* instance, 
 /* static */ void
 Instance::postBarrier(Instance* instance, gc::Cell** location)
 {
     MOZ_ASSERT(location);
     TlsContext.get()->runtime()->gc.storeBuffer().putCell(location);
 }
 #endif // ENABLE_WASM_GC
 
+// 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*
+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*
+Instance::structNarrow(Instance* instance, uint32_t mustUnboxAnyref, uint32_t outputTypeIndex,
+                       void* nonnullPtr)
+{
+    JSContext* cx = TlsContext.get();
+
+    Rooted<TypedObject*> obj(cx);
+    Rooted<StructTypeDescr*> typeDescr(cx);
+
+    if (mustUnboxAnyref) {
+        Rooted<NativeObject*> no(cx, static_cast<NativeObject*>(nonnullPtr));
+        if (!no->is<TypedObject>()) {
+            return nullptr;
+        }
+        obj = &no->as<TypedObject>();
+        Rooted<TypeDescr*> td(cx, &obj->typeDescr());
+        if (td->kind() != type::Struct) {
+            return nullptr;
+        }
+        typeDescr = &td->as<StructTypeDescr>();
+    } else {
+        obj = static_cast<TypedObject*>(nonnullPtr);
+        typeDescr = &obj->typeDescr().as<StructTypeDescr>();
+    }
+
+    // Optimization opportunity: instead of this loop we could perhaps load an
+    // index from `typeDescr` and use that to index into the structTypes table
+    // of the instance.  If the index is in bounds and the desc at that index is
+    // the desc we have then we know the index is good, and we can use that for
+    // the prefix check.
+
+    uint32_t found = UINT32_MAX;
+    for (uint32_t i = 0; i < instance->structTypeDescrs_.length(); i++) {
+        if (instance->structTypeDescrs_[i] == typeDescr) {
+            found = i;
+            break;
+        }
+    }
+
+    if (found == UINT32_MAX) {
+        return nullptr;
+    }
+
+    // Also asserted in constructor; let's just be double sure.
+
+    MOZ_ASSERT(instance->structTypeDescrs_.length() == instance->structTypes().length());
+
+    // Now we know that the object was created by the instance, and we know its
+    // concrete type.  We need to check that its type is an extension of the
+    // type of outputTypeIndex.
+
+    if (!instance->structTypes()[found].hasPrefix(instance->structTypes()[outputTypeIndex])) {
+        return nullptr;
+    }
+
+    return nonnullPtr;
+}
+
 Instance::Instance(JSContext* cx,
                    Handle<WasmInstanceObject*> object,
                    SharedCode code,
                    UniqueTlsData tlsDataIn,
                    HandleWasmMemoryObject memory,
                    SharedTableVector&& tables,
+                   StructTypeDescrVector&& structTypeDescrs,
                    Handle<FunctionVector> funcImports,
                    HandleValVector globalImportValues,
                    const WasmGlobalObjectVector& globalObjs,
                    UniqueDebugState maybeDebug)
   : realm_(cx->realm()),
     object_(object),
     code_(code),
     tlsData_(std::move(tlsDataIn)),
     memory_(memory),
     tables_(std::move(tables)),
-    maybeDebug_(std::move(maybeDebug))
+    maybeDebug_(std::move(maybeDebug)),
+    structTypeDescrs_(std::move(structTypeDescrs))
 {
     MOZ_ASSERT(!!maybeDebug_ == metadata().debugEnabled);
+    MOZ_ASSERT(structTypeDescrs_.length() == structTypes().length());
 
 #ifdef DEBUG
     for (auto t : code_->tiers()) {
         MOZ_ASSERT(funcImports.length() == metadata(t).funcImports.length());
     }
 #endif
     MOZ_ASSERT(tables_.length() == metadata().tables.length());
 
@@ -965,16 +1040,17 @@ Instance::tracePrivate(JSTracer* trc)
             continue;
         }
         GCPtrObject* obj = (GCPtrObject*)(globalData() + global.offset());
         TraceNullableEdge(trc, obj, "wasm ref/anyref global");
     }
 #endif
 
     TraceNullableEdge(trc, &memory_, "wasm buffer");
+    structTypeDescrs_.trace(trc);
 }
 
 void
 Instance::trace(JSTracer* trc)
 {
     // Technically, instead of having this method, the caller could use
     // Instance::object() to get the owning WasmInstanceObject to mark,
     // but this method is simpler and more efficient. The trace hook of
--- a/js/src/wasm/WasmInstance.h
+++ b/js/src/wasm/WasmInstance.h
@@ -14,16 +14,17 @@
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
 
 #ifndef wasm_instance_h
 #define wasm_instance_h
 
+#include "builtin/TypedObject.h"
 #include "gc/Barrier.h"
 #include "jit/shared/Assembler-shared.h"
 #include "vm/SharedMem.h"
 #include "wasm/WasmCode.h"
 #include "wasm/WasmDebug.h"
 #include "wasm/WasmProcess.h"
 #include "wasm/WasmTable.h"
 
@@ -52,16 +53,17 @@ class Instance
 #endif
     const SharedCode                code_;
     const UniqueTlsData             tlsData_;
     GCPtrWasmMemoryObject           memory_;
     const SharedTableVector         tables_;
     DataSegmentVector               passiveDataSegments_;
     ElemSegmentVector               passiveElemSegments_;
     const UniqueDebugState          maybeDebug_;
+    StructTypeDescrVector           structTypeDescrs_;
 
     // Internal helpers:
     const void** addressOfFuncTypeId(const FuncTypeIdDesc& funcTypeId) const;
     FuncImportTls& funcImportTls(const FuncImport& fi);
     TableTls& tableTls(const TableDesc& td) const;
 
     // Only WasmInstanceObject can call the private trace function.
     friend class js::WasmInstanceObject;
@@ -72,16 +74,17 @@ class Instance
 
   public:
     Instance(JSContext* cx,
              HandleWasmInstanceObject object,
              SharedCode code,
              UniqueTlsData tlsData,
              HandleWasmMemoryObject memory,
              SharedTableVector&& tables,
+             StructTypeDescrVector&& structTypeDescrs,
              Handle<FunctionVector> funcImports,
              HandleValVector globalImportValues,
              const WasmGlobalObjectVector& globalObjs,
              UniqueDebugState maybeDebug);
     ~Instance();
     bool init(JSContext* cx,
               const DataSegmentVector& dataSegments,
               const ElemSegmentVector& elemSegments);
@@ -102,16 +105,17 @@ class Instance
     const SharedTableVector& tables() const { return tables_; }
     SharedMem<uint8_t*> memoryBase() const;
     WasmMemoryObject* memory() const;
     size_t memoryMappedSize() const;
     SharedArrayRawBuffer* sharedMemoryBuffer() const; // never null
 #ifdef JS_SIMULATOR
     bool memoryAccessInGuardRegion(uint8_t* addr, unsigned numBytes) const;
 #endif
+    const StructTypeVector& structTypes() const { return code_->structTypes(); }
 
     static constexpr size_t offsetOfJSJitArgsRectifier() {
         return offsetof(Instance, jsJitArgsRectifier_);
     }
     static constexpr size_t offsetOfJSJitExceptionHandler() {
         return offsetof(Instance, jsJitExceptionHandler_);
     }
 #ifdef ENABLE_WASM_GC
@@ -190,16 +194,18 @@ class Instance
                            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 tableDrop(Instance* instance, uint32_t segIndex);
     static int32_t tableInit(Instance* instance, uint32_t dstOffset,
                              uint32_t srcOffset, uint32_t len, uint32_t segIndex);
 #ifdef ENABLE_WASM_GC
     static void postBarrier(Instance* instance, gc::Cell** location);
 #endif
+    static void* structNew(Instance* instance, uint32_t typeIndex);
+    static void* structNarrow(Instance* instance, uint32_t mustUnboxAnyref, uint32_t outputTypeIndex, void* ptr);
 };
 
 typedef UniquePtr<Instance> UniqueInstance;
 
 } // namespace wasm
 } // namespace js
 
 #endif // wasm_instance_h
--- a/js/src/wasm/WasmIonCompile.cpp
+++ b/js/src/wasm/WasmIonCompile.cpp
@@ -3608,16 +3608,24 @@ 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_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
               default:
                 return f.iter().unrecognizedOpcode(&op);
             }
             break;
           }
 
           // Thread operations
           case uint16_t(Op::ThreadPrefix): {
--- a/js/src/wasm/WasmJS.cpp
+++ b/js/src/wasm/WasmJS.cpp
@@ -19,16 +19,17 @@
 #include "wasm/WasmJS.h"
 
 #include "mozilla/CheckedInt.h"
 #include "mozilla/EndianUtils.h"
 #include "mozilla/Maybe.h"
 #include "mozilla/RangedPtr.h"
 
 #include "builtin/Promise.h"
+#include "builtin/TypedObject.h"
 #include "gc/FreeOp.h"
 #include "jit/AtomicOperations.h"
 #include "jit/JitOptions.h"
 #include "js/Printf.h"
 #include "util/StringBuffer.h"
 #include "util/Text.h"
 #include "vm/Interpreter.h"
 #include "vm/StringType.h"
@@ -1171,16 +1172,17 @@ WasmInstanceObject::trace(JSTracer* trc,
 /* static */ WasmInstanceObject*
 WasmInstanceObject::create(JSContext* cx,
                            SharedCode code,
                            const DataSegmentVector& dataSegments,
                            const ElemSegmentVector& elemSegments,
                            UniqueTlsData tlsData,
                            HandleWasmMemoryObject memory,
                            SharedTableVector&& tables,
+                           StructTypeDescrVector&& structTypeDescrs,
                            Handle<FunctionVector> funcImports,
                            const GlobalDescVector& globals,
                            HandleValVector globalImportValues,
                            const WasmGlobalObjectVector& globalObjs,
                            HandleObject proto,
                            UniqueDebugState maybeDebug)
 {
     UniquePtr<ExportMap> exports = js::MakeUnique<ExportMap>();
@@ -1239,16 +1241,17 @@ WasmInstanceObject::create(JSContext* cx
 
     // Root the Instance via WasmInstanceObject before any possible GC.
     auto* instance = cx->new_<Instance>(cx,
                                         obj,
                                         code,
                                         std::move(tlsData),
                                         memory,
                                         std::move(tables),
+                                        std::move(structTypeDescrs),
                                         funcImports,
                                         globalImportValues,
                                         globalObjs,
                                         std::move(maybeDebug));
     if (!instance) {
         return nullptr;
     }
 
--- a/js/src/wasm/WasmJS.h
+++ b/js/src/wasm/WasmJS.h
@@ -21,16 +21,17 @@
 
 #include "gc/Policy.h"
 #include "vm/NativeObject.h"
 #include "wasm/WasmTypes.h"
 
 namespace js {
 
 class GlobalObject;
+class StructTypeDescr;
 class TypedArrayObject;
 class WasmFunctionScope;
 class WasmInstanceScope;
 class SharedArrayRawBuffer;
 
 namespace wasm {
 
 // Return whether WebAssembly can be compiled on this platform.
@@ -225,16 +226,17 @@ class WasmInstanceObject : public Native
 
     static WasmInstanceObject* create(JSContext* cx,
                                       RefPtr<const wasm::Code> code,
                                       const wasm::DataSegmentVector& dataSegments,
                                       const wasm::ElemSegmentVector& elemSegments,
                                       wasm::UniqueTlsData tlsData,
                                       HandleWasmMemoryObject memory,
                                       Vector<RefPtr<wasm::Table>, 0, SystemAllocPolicy>&& tables,
+                                      GCVector<HeapPtr<StructTypeDescr*>, 0, SystemAllocPolicy>&& structTypeDescrs,
                                       Handle<FunctionVector> funcImports,
                                       const wasm::GlobalDescVector& globals,
                                       wasm::HandleValVector globalImportValues,
                                       const WasmGlobalObjectVector& globalObjs,
                                       HandleObject proto,
                                       UniquePtr<wasm::DebugState> maybeDebug);
     void initExportsObj(JSObject& exportsObj);
 
--- a/js/src/wasm/WasmModule.cpp
+++ b/js/src/wasm/WasmModule.cpp
@@ -16,16 +16,17 @@
  * limitations under the License.
  */
 
 #include "wasm/WasmModule.h"
 
 #include <chrono>
 #include <thread>
 
+#include "builtin/TypedObject.h"
 #include "jit/JitOptions.h"
 #include "threading/LockGuard.h"
 #include "util/NSPR.h"
 #include "wasm/WasmCompile.h"
 #include "wasm/WasmInstance.h"
 #include "wasm/WasmJS.h"
 #include "wasm/WasmSerialize.h"
 
@@ -191,17 +192,16 @@ Module::serializedSize(const LinkData& l
 {
     JS::BuildIdCharVector buildId;
     JS::GetOptimizedEncodingBuildId(&buildId);
 
     return SerializedPodVectorSize(buildId) +
            linkData.serializedSize() +
            SerializedVectorSize(imports_) +
            SerializedVectorSize(exports_) +
-           SerializedVectorSize(structTypes_) +
            SerializedVectorSize(dataSegments_) +
            SerializedVectorSize(elemSegments_) +
            SerializedVectorSize(customSections_) +
            code_->serializedSize();
 }
 
 /* virtual */ void
 Module::serialize(const LinkData& linkData, uint8_t* begin, size_t size) const
@@ -213,17 +213,16 @@ Module::serialize(const LinkData& linkDa
     JS::BuildIdCharVector buildId;
     JS::GetOptimizedEncodingBuildId(&buildId);
 
     uint8_t* cursor = begin;
     cursor = SerializePodVector(cursor, buildId);
     cursor = linkData.serialize(cursor);
     cursor = SerializeVector(cursor, imports_);
     cursor = SerializeVector(cursor, exports_);
-    cursor = SerializeVector(cursor, structTypes_);
     cursor = SerializeVector(cursor, dataSegments_);
     cursor = SerializeVector(cursor, elemSegments_);
     cursor = SerializeVector(cursor, customSections_);
     cursor = code_->serialize(cursor, linkData);
     MOZ_RELEASE_ASSERT(cursor == begin + size);
 }
 
 /* static */ MutableModule
@@ -263,22 +262,16 @@ Module::deserialize(const uint8_t* begin
     }
 
     ExportVector exports;
     cursor = DeserializeVector(cursor, &exports);
     if (!cursor) {
         return nullptr;
     }
 
-    StructTypeVector structTypes;
-    cursor = DeserializeVector(cursor, &structTypes);
-    if (!cursor) {
-        return nullptr;
-    }
-
     DataSegmentVector dataSegments;
     cursor = DeserializeVector(cursor, &dataSegments);
     if (!cursor) {
         return nullptr;
     }
 
     ElemSegmentVector elemSegments;
     cursor = DeserializeVector(cursor, &elemSegments);
@@ -306,17 +299,16 @@ Module::deserialize(const uint8_t* begin
     }
 
     MOZ_RELEASE_ASSERT(cursor == begin + size);
     MOZ_RELEASE_ASSERT(!!maybeMetadata == code->metadata().isAsmJS());
 
     return js_new<Module>(*code,
                           std::move(imports),
                           std::move(exports),
-                          std::move(structTypes),
                           std::move(dataSegments),
                           std::move(elemSegments),
                           std::move(customSections));
 }
 
 void
 Module::serialize(const LinkData& linkData, JS::OptimizedEncodingListener& listener) const
 {
@@ -454,17 +446,16 @@ Module::addSizeOfMisc(MallocSizeOf mallo
                       Code::SeenSet* seenCode,
                       size_t* code,
                       size_t* data) const
 {
     code_->addSizeOfMiscIfNotSeen(mallocSizeOf, seenMetadata, seenCode, code, data);
     *data += mallocSizeOf(this) +
              SizeOfVectorExcludingThis(imports_, mallocSizeOf) +
              SizeOfVectorExcludingThis(exports_, mallocSizeOf) +
-             SizeOfVectorExcludingThis(structTypes_, mallocSizeOf) +
              SizeOfVectorExcludingThis(dataSegments_, mallocSizeOf) +
              SizeOfVectorExcludingThis(elemSegments_, mallocSizeOf) +
              SizeOfVectorExcludingThis(customSections_, mallocSizeOf);
 
     if (debugUnlinkedCode_) {
         *data += debugUnlinkedCode_->sizeOfExcludingThis(mallocSizeOf);
     }
 }
@@ -974,17 +965,27 @@ Module::getDebugEnabledCode() const
         return nullptr;
     }
 
     JumpTables jumpTables;
     if (!jumpTables.init(CompileMode::Once, codeTier->segment(), metadata(tier).codeRanges)) {
         return nullptr;
     }
 
-    MutableCode debugCode = js_new<Code>(std::move(codeTier), metadata(), std::move(jumpTables));
+    StructTypeVector structTypes;
+    if (!structTypes.resize(code_->structTypes().length())) {
+        return nullptr;
+    }
+    for (uint32_t i = 0; i < code_->structTypes().length(); i++) {
+        if (!structTypes[i].copyFrom(code_->structTypes()[i])) {
+            return nullptr;
+        }
+    }
+    MutableCode debugCode = js_new<Code>(std::move(codeTier), metadata(), std::move(jumpTables),
+                                         std::move(structTypes));
     if (!debugCode || !debugCode->initialize(*debugLinkData_)) {
         return nullptr;
     }
 
     return debugCode;
 }
 
 static bool
@@ -1076,16 +1077,160 @@ CreateExportObject(JSContext* cx,
             return false;
         }
     }
 
     instanceObj->initExportsObj(*exportObj);
     return true;
 }
 
+static bool
+MakeStructField(JSContext* cx, const ValType& v, bool isMutable, const char* format,
+                uint32_t fieldNo, AutoIdVector* ids, AutoValueVector* fieldTypeObjs,
+                Vector<StructFieldProps>* fieldProps)
+{
+    char buf[20];
+    sprintf(buf, format, fieldNo);
+    RootedString str(cx, JS_AtomizeAndPinString(cx, buf));
+    if (!str) {
+        return false;
+    }
+
+    StructFieldProps props;
+    props.isMutable = isMutable;
+
+    Rooted<TypeDescr*> t(cx);
+    switch (v.code()) {
+      case ValType::I32:
+        t = GlobalObject::getOrCreateScalarTypeDescr(cx, cx->global(), Scalar::Int32);
+        break;
+      case ValType::I64:
+        // Align for int64 but allocate only an int32, another int32 allocation
+        // will follow immediately.  JS will see two immutable int32 values but
+        // wasm knows it's a single int64.  See makeStructTypeDescrs(), below.
+        props.alignAsInt64 = true;
+        t = GlobalObject::getOrCreateScalarTypeDescr(cx, cx->global(), Scalar::Int32);
+        break;
+      case ValType::F32:
+        t = GlobalObject::getOrCreateScalarTypeDescr(cx, cx->global(), Scalar::Float32);
+        break;
+      case ValType::F64:
+        t = GlobalObject::getOrCreateScalarTypeDescr(cx, cx->global(), Scalar::Float64);
+        break;
+      case ValType::Ref:
+      case ValType::AnyRef:
+        t = GlobalObject::getOrCreateReferenceTypeDescr(cx, cx->global(),
+                                                        ReferenceType::TYPE_OBJECT);
+        break;
+      default:
+        MOZ_CRASH("Bad field type");
+    }
+    MOZ_ASSERT(t != nullptr);
+
+    if (!ids->append(INTERNED_STRING_TO_JSID(cx, str))) {
+        return false;
+    }
+
+    if (!fieldTypeObjs->append(ObjectValue(*t))) {
+        return false;
+    }
+
+    if (!fieldProps->append(props)) {
+        return false;
+    }
+
+    return true;
+}
+
+
+bool
+Module::makeStructTypeDescrs(JSContext* cx,
+                             MutableHandle<StructTypeDescrVector> structTypeDescrs) const
+{
+    // Not just any prototype object will do, we must have the actual StructTypePrototype.
+    RootedObject typedObjectModule(cx, GlobalObject::getOrCreateTypedObjectModule(cx,
+                                                                                  cx->global()));
+    if (!typedObjectModule) {
+       return false;
+    }
+
+    RootedNativeObject toModule(cx, &typedObjectModule->as<NativeObject>());
+    RootedObject prototype(cx, &toModule->getReservedSlot(
+                                   TypedObjectModuleObject::StructTypePrototype).toObject());
+
+    for (const StructType& structType : structTypes()) {
+        AutoIdVector ids(cx);
+        AutoValueVector fieldTypeObjs(cx);
+        Vector<StructFieldProps> fieldProps(cx);
+        bool allowConstruct = true;
+
+        uint32_t k = 0;
+        for (StructField sf : structType.fields_) {
+            const ValType& v = sf.type;
+            if (v.code() == ValType::I64) {
+                // TypedObjects don't yet have a notion of int64 fields.  Thus
+                // we handle int64 by allocating two adjacent int32 fields, the
+                // first of them aligned as for int64.  We mark these fields as
+                // immutable for JS and render the object non-constructible
+                // from JS.  Wasm however sees one i64 field with appropriate
+                // mutability.
+                sf.isMutable = false;
+                allowConstruct = false;
+
+                if (!MakeStructField(cx, ValType::I64, sf.isMutable, "_%d_low", k, &ids,
+                                     &fieldTypeObjs, &fieldProps))
+                {
+                    return false;
+                }
+                if (!MakeStructField(cx, ValType::I32, sf.isMutable, "_%d_high", k++, &ids,
+                                     &fieldTypeObjs, &fieldProps))
+                {
+                    return false;
+                }
+            } else {
+                // TypedObjects don't yet have a sufficient notion of type
+                // constraints on TypedObject properties.  Thus we handle fields
+                // of type (ref T) by marking them as immutable for JS and by
+                // rendering the objects non-constructible from JS.  Wasm
+                // however sees properly-typed (ref T) fields with appropriate
+                // mutability.
+                if (v.isRef()) {
+                    sf.isMutable = false;
+                    allowConstruct = false;
+                }
+
+                if (!MakeStructField(cx, v, sf.isMutable, "_%d", k++, &ids, &fieldTypeObjs,
+                                     &fieldProps))
+                {
+                    return false;
+                }
+            }
+        }
+
+        // Types must be opaque, which we ensure here, and sealed, which is true
+        // for every TypedObject.  If they contain fields of type Ref T then we
+        // prevent JS from constructing instances of them.
+
+        Rooted<StructTypeDescr*>
+            structTypeDescr(cx, StructMetaTypeDescr::createFromArrays(cx,
+                                                                      prototype,
+                                                                      /* opaque= */ true,
+                                                                      allowConstruct,
+                                                                      ids,
+                                                                      fieldTypeObjs,
+                                                                      fieldProps));
+
+        if (!structTypeDescr || !structTypeDescrs.append(structTypeDescr)) {
+            return false;
+        }
+    }
+
+    return true;
+}
+
 bool
 Module::instantiate(JSContext* cx,
                     Handle<FunctionVector> funcImports,
                     HandleWasmTableObject tableImport,
                     HandleWasmMemoryObject memoryImport,
                     HandleValVector globalImportValues,
                     WasmGlobalObjectVector& globalObjs,
                     HandleObject instanceProto,
@@ -1129,23 +1274,31 @@ Module::instantiate(JSContext* cx,
         maybeDebug = cx->make_unique<DebugState>(*code, *this, binarySource);
         if (!maybeDebug) {
             return false;
         }
     } else {
         code = code_;
     }
 
+    // Create type descriptors for any struct types that the module has.
+
+    Rooted<StructTypeDescrVector> structTypeDescrs(cx);
+    if (!makeStructTypeDescrs(cx, &structTypeDescrs)) {
+        return false;
+    }
+
     instance.set(WasmInstanceObject::create(cx,
                                             code,
                                             dataSegments_,
                                             elemSegments_,
                                             std::move(tlsData),
                                             memory,
                                             std::move(tables),
+                                            std::move(structTypeDescrs.get()),
                                             funcImports,
                                             metadata().globals,
                                             globalImportValues,
                                             globalObjs,
                                             instanceProto,
                                             std::move(maybeDebug)));
     if (!instance) {
         return false;
--- a/js/src/wasm/WasmModule.h
+++ b/js/src/wasm/WasmModule.h
@@ -53,17 +53,16 @@ typedef RefPtr<JS::OptimizedEncodingList
 // first instance; it then instantiates new Code objects from a copy of the
 // unlinked code that it keeps around for that purpose.
 
 class Module : public JS::WasmModule
 {
     const SharedCode          code_;
     const ImportVector        imports_;
     const ExportVector        exports_;
-    const StructTypeVector    structTypes_;
     const DataSegmentVector   dataSegments_;
     const ElemSegmentVector   elemSegments_;
     const CustomSectionVector customSections_;
 
     // These fields are only meaningful when code_->metadata().debugEnabled.
     // `debugCodeClaimed_` is set to false initially and then to true when
     // `code_` is already being used for an instance and can't be shared because
     // it may be patched by the debugger. Subsequent instances must then create
@@ -95,34 +94,34 @@ class Module : public JS::WasmModule
     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;
+    bool makeStructTypeDescrs(JSContext* cx,
+                              MutableHandle<StructTypeDescrVector> structTypeDescrs) const;
 
     class Tier2GeneratorTaskImpl;
 
   public:
     Module(const Code& code,
            ImportVector&& imports,
            ExportVector&& exports,
-           StructTypeVector&& structTypes,
            DataSegmentVector&& dataSegments,
            ElemSegmentVector&& elemSegments,
            CustomSectionVector&& customSections,
            UniqueConstBytes debugUnlinkedCode = nullptr,
            UniqueLinkData debugLinkData = nullptr,
            const ShareableBytes* debugBytecode = nullptr)
       : code_(&code),
         imports_(std::move(imports)),
         exports_(std::move(exports)),
-        structTypes_(std::move(structTypes)),
         dataSegments_(std::move(dataSegments)),
         elemSegments_(std::move(elemSegments)),
         customSections_(std::move(customSections)),
         debugCodeClaimed_(false),
         debugUnlinkedCode_(std::move(debugUnlinkedCode)),
         debugLinkData_(std::move(debugLinkData)),
         debugBytecode_(debugBytecode),
         testingTier2Active_(false)
@@ -135,16 +134,17 @@ class Module : public JS::WasmModule
     const ModuleSegment& moduleSegment(Tier t) const { return code_->segment(t); }
     const Metadata& metadata() const { return code_->metadata(); }
     const MetadataTier& metadata(Tier t) const { return code_->metadata(t); }
     const ImportVector& imports() const { return imports_; }
     const ExportVector& exports() const { return exports_; }
     const CustomSectionVector& customSections() const { return customSections_; }
     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,
                      HandleWasmMemoryObject memoryImport,
                      HandleValVector globalImportValues,
--- a/js/src/wasm/WasmOpIter.cpp
+++ b/js/src/wasm/WasmOpIter.cpp
@@ -263,16 +263,26 @@ wasm::Classify(OpBytes op)
             case MiscOp::TableDrop:
               return OpKind::MemOrTableDrop;
             case MiscOp::MemFill:
               return OpKind::MemFill;
             case MiscOp::MemInit:
             case MiscOp::TableInit:
               return OpKind::MemOrTableInit;
 #endif
+#ifdef ENABLE_WASM_GC
+            case MiscOp::StructNew:
+              return OpKind::StructNew;
+            case MiscOp::StructGet:
+              return OpKind::StructGet;
+            case MiscOp::StructSet:
+              return OpKind::StructSet;
+            case MiscOp::StructNarrow:
+              return OpKind::StructNarrow;
+#endif
             default:
               break;
           }
           break;
       }
       case Op::ThreadPrefix: {
 #ifdef ENABLE_WASM_THREAD_OPS
           switch (ThreadOp(op.b1)) {
--- a/js/src/wasm/WasmOpIter.h
+++ b/js/src/wasm/WasmOpIter.h
@@ -187,16 +187,20 @@ enum class OpKind {
     Swizzle,
     Shuffle,
     Splat,
     MemOrTableCopy,
     MemOrTableDrop,
     MemFill,
     MemOrTableInit,
     RefNull,
+    StructNew,
+    StructGet,
+    StructSet,
+    StructNarrow,
 };
 
 // Return the OpKind for a given Op. This is used for sanity-checking that
 // API users use the correct read function for a given Op.
 OpKind
 Classify(OpBytes op);
 #endif
 
@@ -380,16 +384,19 @@ class MOZ_STACK_CLASS OpIter : private P
     }
     MOZ_MUST_USE bool readFixedF64(double* out) {
         return d_.readFixedF64(out);
     }
 
     MOZ_MUST_USE bool readLinearMemoryAddress(uint32_t byteSize, LinearMemoryAddress<Value>* addr);
     MOZ_MUST_USE bool readLinearMemoryAddressAligned(uint32_t byteSize, LinearMemoryAddress<Value>* addr);
     MOZ_MUST_USE bool readBlockType(ExprType* expr);
+    MOZ_MUST_USE bool readStructTypeIndex(uint32_t* typeIndex);
+    MOZ_MUST_USE bool readFieldIndex(uint32_t* fieldIndex, const StructType& structType);
+
     MOZ_MUST_USE bool popCallArgs(const ValTypeVector& expectedTypes, Vector<Value, 8, SystemAllocPolicy>* values);
 
     MOZ_MUST_USE bool popAnyType(StackType* type, Value* value);
     MOZ_MUST_USE bool typeMismatch(StackType actual, StackType expected);
     MOZ_MUST_USE bool popWithType(StackType expectedType, Value* value);
     MOZ_MUST_USE bool popWithType(ValType valType, Value* value) { return popWithType(StackType(valType), value); }
     MOZ_MUST_USE bool popWithType(ExprType expectedType, Value* value);
     MOZ_MUST_USE bool topWithType(ExprType expectedType, Value* value);
@@ -471,16 +478,19 @@ class MOZ_STACK_CLASS OpIter : private P
     // Return a pointer to the end of the buffer being decoded by this iterator.
     const uint8_t* end() const {
         return d_.end();
     }
 
     // Report a general failure.
     MOZ_MUST_USE bool fail(const char* msg) MOZ_COLD;
 
+    // Report a general failure with a context
+    MOZ_MUST_USE bool fail_ctx(const char* fmt, const char* context) MOZ_COLD;
+
     // Report an unrecognized opcode.
     MOZ_MUST_USE bool unrecognizedOpcode(const OpBytes* expr) MOZ_COLD;
 
     // Return whether the innermost block has a polymorphic base of its stack.
     // Ideally this accessor would be removed; consider using something else.
     bool currentBlockHasPolymorphicBase() const {
         return !controlStack_.empty() && controlStack_.back().polymorphicBase();
     }
@@ -559,16 +569,21 @@ class MOZ_STACK_CLASS OpIter : private P
                                         Value* oldValue,
                                         Value* newValue);
     MOZ_MUST_USE bool readMemOrTableCopy(bool isMem,
                                          Value* dst, 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);
+    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.
     // Never fails: returns uint16_t(Op::Limit) in op->b0 if it can't read.
     void peekOp(OpBytes* op);
 
     // ------------------------------------------------------------------------
     // Stack management.
@@ -704,16 +719,27 @@ OpIter<Policy>::unrecognizedOpcode(const
 
 template <typename Policy>
 inline bool
 OpIter<Policy>::fail(const char* msg)
 {
     return d_.fail(lastOpcodeOffset(), msg);
 }
 
+template <typename Policy>
+inline bool
+OpIter<Policy>::fail_ctx(const char* fmt, const char* context)
+{
+    UniqueChars error(JS_smprintf(fmt, context));
+    if (!error) {
+        return false;
+    }
+    return fail(error.get());
+}
+
 // This function pops exactly one value from the stack, yielding Any types in
 // various cases and therefore making it the caller's responsibility to do the
 // right thing for StackType::Any. Prefer (pop|top)WithType.
 template <typename Policy>
 inline bool
 OpIter<Policy>::popAnyType(StackType* type, Value* value)
 {
     ControlStackEntry<ControlItem>& block = controlStack_.back();
@@ -1670,30 +1696,48 @@ OpIter<Policy>::readF64Const(double* f64
            push(ValType::F64);
 }
 
 template <typename Policy>
 inline bool
 OpIter<Policy>::readRefNull(ValType* type)
 {
     MOZ_ASSERT(Classify(op_) == OpKind::RefNull);
+    if (!readReferenceType(type, "ref.null")) {
+        return false;
+    }
+
+    return push(StackType(*type));
+}
+
+template <typename Policy>
+inline bool
+OpIter<Policy>::readReferenceType(ValType* type, const char* context)
+{
     uint8_t code;
     uint32_t refTypeIndex;
+
     if (!d_.readValType(&code, &refTypeIndex)) {
-        return fail("unknown nullref type");
+        return fail_ctx("invalid reference type for %s", context);
     }
+
     if (code == uint8_t(TypeCode::Ref)) {
-        if (refTypeIndex >= MaxTypes || refTypeIndex >= env_.types.length()) {
-            return fail("invalid nullref type");
+        if (refTypeIndex >= env_.types.length()) {
+            return fail_ctx("invalid reference type for %s", context);
+        }
+        if (!env_.types[refTypeIndex].isStructType()) {
+            return fail_ctx("reference to struct required for %s", context);
         }
     } else if (code != uint8_t(TypeCode::AnyRef)) {
-        return fail("unknown nullref type");
+        return fail_ctx("invalid reference type for %s", context);
     }
+
     *type = ValType(ValType::Code(code), refTypeIndex);
-    return push(StackType(*type));
+
+    return true;
 }
 
 template <typename Policy>
 inline bool
 OpIter<Policy>::popCallArgs(const ValTypeVector& expectedTypes, ValueVector* values)
 {
     // Iterate through the argument types backward so that pops occur in the
     // right order.
@@ -2114,16 +2158,175 @@ OpIter<Policy>::readMemOrTableInit(bool 
     } else {
         if (*segIndex >= env_.elemSegments.length())
             return fail("table.init index out of range");
     }
 
     return true;
 }
 
+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");
+    }
+
+    if (!env_.types[*typeIndex].isStructType()) {
+        return fail("not a struct type");
+    }
+
+    return true;
+}
+
+template <typename Policy>
+inline bool
+OpIter<Policy>::readFieldIndex(uint32_t* fieldIndex, const StructType& structType)
+{
+    if (!readVarU32(fieldIndex)) {
+        return fail("unable to read field index");
+    }
+
+    if (structType.fields_.length() <= *fieldIndex) {
+        return fail("field index out of range");
+    }
+
+    return true;
+}
+
+// Semantics of struct.new, struct.get, struct.set, and struct.narrow documented
+// (for now) on https://github.com/lars-t-hansen/moz-gc-experiments.
+
+template <typename Policy>
+inline bool
+OpIter<Policy>::readStructNew(uint32_t* typeIndex, ValueVector* argValues)
+{
+    MOZ_ASSERT(Classify(op_) == OpKind::StructNew);
+
+    if (!readStructTypeIndex(typeIndex)) {
+        return false;
+    }
+
+    const StructType& str = env_.types[*typeIndex].structType();
+
+    if (!argValues->resize(str.fields_.length())) {
+        return false;
+    }
+
+    static_assert(MaxStructFields <= INT32_MAX, "Or we iloop below");
+
+    for (int32_t i = str.fields_.length() - 1; i >= 0; i--) {
+        if (!popWithType(str.fields_[i].type, &(*argValues)[i])) {
+            return false;
+        }
+    }
+
+    return push(ValType(ValType::Ref, *typeIndex));
+}
+
+template <typename Policy>
+inline bool
+OpIter<Policy>::readStructGet(uint32_t* typeIndex, uint32_t* fieldIndex, Value* ptr)
+{
+    MOZ_ASSERT(typeIndex != fieldIndex);
+    MOZ_ASSERT(Classify(op_) == OpKind::StructGet);
+
+    if (!readStructTypeIndex(typeIndex)) {
+        return false;
+    }
+
+    const StructType& structType = env_.types[*typeIndex].structType();
+
+    if (!readFieldIndex(fieldIndex, structType)) {
+        return false;
+    }
+
+    if (!popWithType(ValType(ValType::Ref, *typeIndex), ptr)) {
+        return false;
+    }
+
+    return push(structType.fields_[*fieldIndex].type);
+}
+
+template <typename Policy>
+inline bool
+OpIter<Policy>::readStructSet(uint32_t* typeIndex, uint32_t* fieldIndex, Value* ptr, Value* val)
+{
+    MOZ_ASSERT(typeIndex != fieldIndex);
+    MOZ_ASSERT(Classify(op_) == OpKind::StructSet);
+
+    if (!readStructTypeIndex(typeIndex)) {
+        return false;
+    }
+
+    const StructType& structType = env_.types[*typeIndex].structType();
+
+    if (!readFieldIndex(fieldIndex, structType)) {
+        return false;
+    }
+
+    if (!popWithType(structType.fields_[*fieldIndex].type, val)) {
+        return false;
+    }
+
+    if (!structType.fields_[*fieldIndex].isMutable) {
+        return fail("field is not mutable");
+    }
+
+    if (!popWithType(ValType(ValType::Ref, *typeIndex), ptr)) {
+        return false;
+    }
+
+    return true;
+}
+
+template <typename Policy>
+inline bool
+OpIter<Policy>::readStructNarrow(ValType* inputType, ValType* outputType, Value* ptr)
+{
+    MOZ_ASSERT(inputType != outputType);
+    MOZ_ASSERT(Classify(op_) == OpKind::StructNarrow);
+
+    if (!readReferenceType(inputType, "struct.narrow")) {
+        return false;
+    }
+
+    if (!readReferenceType(outputType, "struct.narrow")) {
+        return false;
+    }
+
+    if (inputType->isRef()) {
+        if (!outputType->isRef()) {
+            return fail("invalid type combination in struct.narrow");
+        }
+
+        const StructType& inputStruct = env_.types[inputType->refTypeIndex()].structType();
+        const StructType& outputStruct = env_.types[outputType->refTypeIndex()].structType();
+
+        if (!outputStruct.hasPrefix(inputStruct)) {
+            return fail("invalid narrowing operation");
+        }
+    } else if (*outputType == ValType::AnyRef) {
+        if (*inputType != ValType::AnyRef) {
+            return fail("invalid type combination in struct.narrow");
+        }
+    }
+
+    if (!popWithType(*inputType, ptr)) {
+        return false;
+    }
+
+    return push(*outputType);
+}
+
 } // namespace wasm
 } // namespace js
 
 namespace mozilla {
 
 // Specialize IsPod for the Nothing specializations.
 template<> struct IsPod<js::wasm::TypeAndValue<Nothing>> : TrueType {};
 template<> struct IsPod<js::wasm::ControlStackEntry<Nothing>> : TrueType {};
--- a/js/src/wasm/WasmTextToBinary.cpp
+++ b/js/src/wasm/WasmTextToBinary.cpp
@@ -116,16 +116,22 @@ class WasmToken
         MemCopy,
         MemDrop,
         MemFill,
         MemInit,
 #endif
         Module,
         Mutable,
         Name,
+#ifdef ENABLE_WASM_GC
+        StructNew,
+        StructGet,
+        StructSet,
+        StructNarrow,
+#endif
         Nop,
         Offset,
         OpenParen,
         Param,
 #ifdef ENABLE_WASM_BULKMEM_OPS
         Passive,
 #endif
         Ref,
@@ -355,16 +361,22 @@ class WasmToken
           case Load:
           case Loop:
 #ifdef ENABLE_WASM_BULKMEM_OPS
           case MemCopy:
           case MemDrop:
           case MemFill:
           case MemInit:
 #endif
+#ifdef ENABLE_WASM_GC
+          case StructNew:
+          case StructGet:
+          case StructSet:
+          case StructNarrow:
+#endif
           case Nop:
           case RefNull:
           case Return:
           case SetGlobal:
           case SetLocal:
           case Store:
 #ifdef ENABLE_WASM_BULKMEM_OPS
           case TableCopy:
@@ -2081,16 +2093,30 @@ WasmTokenStream::next()
         if (consume(u"shared")) {
             return WasmToken(WasmToken::Shared, begin, cur_);
         }
 #endif
         if (consume(u"start")) {
             return WasmToken(WasmToken::Start, begin, cur_);
         }
         if (consume(u"struct")) {
+#ifdef ENABLE_WASM_GC
+            if (consume(u".new")) {
+                return WasmToken(WasmToken::StructNew, begin, cur_);
+            }
+            if (consume(u".get")) {
+                return WasmToken(WasmToken::StructGet, begin, cur_);
+            }
+            if (consume(u".set")) {
+                return WasmToken(WasmToken::StructSet, begin, cur_);
+            }
+            if (consume(u".narrow")) {
+                return WasmToken(WasmToken::StructNarrow, begin, cur_);
+            }
+#endif
             return WasmToken(WasmToken::Struct, begin, cur_);
         }
         break;
 
       case 't':
 #ifdef ENABLE_WASM_BULKMEM_OPS
         if (consume(u"table.")) {
             if (consume(u"copy")) {
@@ -3581,16 +3607,132 @@ ParseMemOrTableInit(WasmParseContext& c,
     AstExpr* len = ParseExpr(c, inParens);
     if (!len)
         return nullptr;
 
     return new(c.lifo) AstMemOrTableInit(isMem, segIndexTok.index(), dst, src, len);
 }
 #endif
 
+#ifdef ENABLE_WASM_GC
+static AstExpr*
+ParseStructNew(WasmParseContext& c, bool inParens)
+{
+    AstRef typeDef;
+    if (!c.ts.matchRef(&typeDef, c.error)) {
+        return nullptr;
+    }
+
+    AstExprVector args(c.lifo);
+    if (inParens) {
+        if (!ParseArgs(c, &args)) {
+            return nullptr;
+        }
+    }
+
+    // An AstRef cast to AstValType turns into a Ref type, which is exactly what
+    // we need here.
+
+    return new(c.lifo) AstStructNew(typeDef,
+                                    AstExprType(AstValType(typeDef)),
+                                    std::move(args));
+}
+
+static AstExpr*
+ParseStructGet(WasmParseContext& c, bool inParens)
+{
+    AstRef typeDef;
+    if (!c.ts.matchRef(&typeDef, c.error)) {
+        return nullptr;
+    }
+
+    AstRef fieldDef;
+    if (!c.ts.matchRef(&fieldDef, c.error)) {
+        return nullptr;
+    }
+
+    if (!fieldDef.name().empty()) {
+        c.ts.generateError(c.ts.peek(), "constant field index required at this time", c.error);
+        return nullptr;
+    }
+
+    AstExpr* ptr = ParseExpr(c, inParens);
+    if (!ptr) {
+        return nullptr;
+    }
+
+    // The field type is not available here, we must first resolve the type.
+    // Fortunately, we don't need to inspect the result type of this operation.
+
+    return new(c.lifo) AstStructGet(typeDef, fieldDef.index(), ExprType(), ptr);
+}
+
+static AstExpr*
+ParseStructSet(WasmParseContext& c, bool inParens)
+{
+    AstRef typeDef;
+    if (!c.ts.matchRef(&typeDef, c.error)) {
+        return nullptr;
+    }
+
+    AstRef fieldDef;
+    if (!c.ts.matchRef(&fieldDef, c.error)) {
+        return nullptr;
+    }
+
+    if (!fieldDef.name().empty()) {
+        c.ts.generateError(c.ts.peek(), "constant field index required at this time", c.error);
+        return nullptr;
+    }
+
+    AstExpr* ptr = ParseExpr(c, inParens);
+    if (!ptr) {
+        return nullptr;
+    }
+
+    AstExpr* value = ParseExpr(c, inParens);
+    if (!value) {
+        return nullptr;
+    }
+
+    return new(c.lifo) AstStructSet(typeDef, fieldDef.index(), ptr, value);
+}
+
+static AstExpr*
+ParseStructNarrow(WasmParseContext& c, bool inParens)
+{
+    AstValType inputType;
+    if (!ParseValType(c, &inputType)) {
+        return nullptr;
+    }
+
+    if (!inputType.isRefType()) {
+        c.ts.generateError(c.ts.peek(), "struct.narrow requires ref type", c.error);
+        return nullptr;
+    }
+
+    AstValType outputType;
+    if (!ParseValType(c, &outputType)) {
+        return nullptr;
+    }
+
+    if (!outputType.isRefType()) {
+        c.ts.generateError(c.ts.peek(), "struct.narrow requires ref type", c.error);
+        return nullptr;
+    }
+
+    AstExpr* ptr = ParseExpr(c, inParens);
+    if (!ptr) {
+        return nullptr;
+    }
+
+    return new(c.lifo) AstStructNarrow(inputType, outputType, ptr);
+}
+#endif
+
 static AstExpr*
 ParseRefNull(WasmParseContext& c)
 {
     WasmToken token;
     AstValType vt;
 
     if (!ParseValType(c, &vt)) {
         return nullptr;
@@ -3692,16 +3834,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_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:
+        return ParseStructNarrow(c, inParens);
+#endif
       case WasmToken::RefNull:
         return ParseRefNull(c);
       default:
         c.ts.generateError(token, c.error);
         return nullptr;
     }
 }
 
@@ -5329,16 +5481,66 @@ static bool
 ResolveMemOrTableInit(Resolver& r, AstMemOrTableInit& s)
 {
     return ResolveExpr(r, s.dst()) &&
            ResolveExpr(r, s.src()) &&
            ResolveExpr(r, s.len());
 }
 #endif
 
+#ifdef ENABLE_WASM_GC
+static bool
+ResolveStructNew(Resolver& r, AstStructNew& s)
+{
+    if (!ResolveArgs(r, s.fieldValues())) {
+        return false;
+    }
+
+    if (!r.resolveType(s.structType())) {
+        return false;
+    }
+
+    return true;
+}
+
+static bool
+ResolveStructGet(Resolver& r, AstStructGet& s)
+{
+    if (!r.resolveType(s.structType())) {
+        return false;
+    }
+
+    return ResolveExpr(r, s.ptr());
+}
+
+static bool
+ResolveStructSet(Resolver& r, AstStructSet& s)
+{
+    if (!r.resolveType(s.structType())) {
+        return false;
+    }
+
+    return ResolveExpr(r, s.ptr()) && ResolveExpr(r, s.value());
+}
+
+static bool
+ResolveStructNarrow(Resolver& r, AstStructNarrow& s)
+{
+    if (!ResolveType(r, s.inputStruct())) {
+        return false;
+    }
+
+    if (!ResolveType(r, s.outputStruct())) {
+        return false;
+    }
+
+    return ResolveExpr(r, s.ptr());
+}
+#endif
+
 static bool
 ResolveRefNull(Resolver& r, AstRefNull& s)
 {
     return ResolveType(r, s.baseType());
 }
 
 static bool
 ResolveExpr(Resolver& r, AstExpr& expr)
@@ -5418,16 +5620,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_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:
+        return ResolveStructNarrow(r, expr.as<AstStructNarrow>());
+#endif
     }
     MOZ_CRASH("Bad expr kind");
 }
 
 static bool
 ResolveFunc(Resolver& r, AstFunc& func)
 {
     r.beginFunc();
@@ -6115,16 +6327,93 @@ EncodeMemOrTableInit(Encoder& e, AstMemO
     return EncodeExpr(e, s.dst()) &&
            EncodeExpr(e, s.src()) &&
            EncodeExpr(e, s.len()) &&
            e.writeOp(s.isMem() ? MiscOp::MemInit : MiscOp::TableInit) &&
            e.writeVarU32(s.segIndex());
 }
 #endif
 
+#ifdef ENABLE_WASM_GC
+static bool
+EncodeStructNew(Encoder& e, AstStructNew& s)
+{
+    if (!EncodeArgs(e, s.fieldValues())) {
+        return false;
+    }
+
+    if (!e.writeOp(MiscOp::StructNew)) {
+        return false;
+    }
+
+    if (!e.writeVarU32(s.structType().index())) {
+        return false;
+    }
+
+    return true;
+}
+
+static bool
+EncodeStructGet(Encoder& e, AstStructGet& s)
+{
+    if (!EncodeExpr(e, s.ptr())) {
+        return false;
+    }
+    if (!e.writeOp(MiscOp::StructGet)) {
+        return false;
+    }
+    if (!e.writeVarU32(s.structType().index())) {
+        return false;
+    }
+    if (!e.writeVarU32(s.index())) {
+        return false;
+    }
+    return true;
+}
+
+static bool
+EncodeStructSet(Encoder& e, AstStructSet& s)
+{
+    if (!EncodeExpr(e, s.ptr())) {
+        return false;
+    }
+    if (!EncodeExpr(e, s.value())) {
+        return false;
+    }
+    if (!e.writeOp(MiscOp::StructSet)) {
+        return false;
+    }
+    if (!e.writeVarU32(s.structType().index())) {
+        return false;
+    }
+    if (!e.writeVarU32(s.index())) {
+        return false;
+    }
+    return true;
+}
+
+static bool
+EncodeStructNarrow(Encoder& e, AstStructNarrow& s)
+{
+    if (!EncodeExpr(e, s.ptr())) {
+        return false;
+    }
+    if (!e.writeOp(MiscOp::StructNarrow)) {
+        return false;
+    }
+    if (!e.writeValType(s.inputStruct().type())) {
+        return false;
+    }
+    if (!e.writeValType(s.outputStruct().type())) {
+        return false;
+    }
+    return true;
+}
+#endif
+
 static bool
 EncodeRefNull(Encoder& e, AstRefNull& s)
 {
     return e.writeOp(Op::RefNull) &&
            e.writeValType(s.baseType().type());
 }
 
 static bool
@@ -6208,16 +6497,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_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:
+        return EncodeStructNarrow(e, expr.as<AstStructNarrow>());
+#endif
     }
     MOZ_CRASH("Bad expr kind");
 }
 
 /*****************************************************************************/
 // wasm AST binary serialization
 
 #ifdef ENABLE_WASM_GC
--- a/js/src/wasm/WasmTypes.h
+++ b/js/src/wasm/WasmTypes.h
@@ -69,16 +69,19 @@ class WasmTableObject;
 typedef Rooted<WasmTableObject*> RootedWasmTableObject;
 typedef Handle<WasmTableObject*> HandleWasmTableObject;
 typedef MutableHandle<WasmTableObject*> MutableHandleWasmTableObject;
 
 class WasmGlobalObject;
 typedef GCVector<WasmGlobalObject*, 0, SystemAllocPolicy> WasmGlobalObjectVector;
 typedef Rooted<WasmGlobalObject*> RootedWasmGlobalObject;
 
+class StructTypeDescr;
+typedef GCVector<HeapPtr<StructTypeDescr*>, 0, SystemAllocPolicy> StructTypeDescrVector;
+
 namespace wasm {
 
 using mozilla::ArrayEqual;
 using mozilla::Atomic;
 using mozilla::DebugOnly;
 using mozilla::EnumeratedArray;
 using mozilla::Maybe;
 using mozilla::MallocSizeOf;
@@ -825,41 +828,57 @@ struct FuncTypeHashPolicy
 {
     typedef const FuncType& Lookup;
     static HashNumber hash(Lookup ft) { return ft.hash(); }
     static bool match(const FuncType* lhs, Lookup rhs) { return *lhs == rhs; }
 };
 
 // Structure type.
 //
-// The Module owns a dense array of Struct values that represent the structure
-// types that the module knows about.  It is created from the sparse array of
-// types in the ModuleEnvironment when the Module is created.
+// The Module owns a dense array of StructType values that represent the
+// structure types that the module knows about.  It is created from the sparse
+// array of types in the ModuleEnvironment when the Module is created.
 
 struct StructField
 {
     ValType  type;
     uint32_t offset;
     bool     isMutable;
 };
 
 typedef Vector<StructField, 0, SystemAllocPolicy> StructFieldVector;
 
 class StructType
 {
   public:
-    StructFieldVector fields_;
-
+    StructFieldVector fields_; // Field type, offset, and mutability
+    uint32_t moduleIndex_;     // Index in a dense array of structs in the module
+    bool isInline_;            // True if this is an InlineTypedObject and we
+                               //   interpret the offsets from the object pointer;
+                               //   if false this is an OutlineTypedObject and we
+                               //   interpret everything relative to the pointer to
+                               //   the attached storage.
   public:
-    StructType() : fields_() {}
-
-    explicit StructType(StructFieldVector&& fields)
-      : fields_(std::move(fields))
+    StructType() : fields_(), moduleIndex_(0), isInline_(true) {}
+
+    StructType(StructFieldVector&& fields, uint32_t index, bool isInline)
+      : fields_(std::move(fields)),
+        moduleIndex_(index),
+        isInline_(isInline)
     {}
 
+    bool copyFrom(const StructType& src) {
+        if (!fields_.appendAll(src.fields_)) {
+            return false;
+        }
+        moduleIndex_ = src.moduleIndex_;
+        isInline_ = src.isInline_;
+        return true;
+    }
+
     bool hasPrefix(const StructType& other) const;
 
     WASM_DECLARE_SERIALIZABLE(StructType)
 };
 
 typedef Vector<StructType, 0, SystemAllocPolicy> StructTypeVector;
 
 // An InitExpr describes a deferred initializer expression, used to initialize
@@ -1871,16 +1890,18 @@ enum class SymbolicAddress
     MemFill,
     MemInit,
     TableCopy,
     TableDrop,
     TableInit,
 #ifdef ENABLE_WASM_GC
     PostBarrier,
 #endif
+    StructNew,
+    StructNarrow,
 #if defined(JS_CODEGEN_MIPS32)
     js_jit_gAtomic64Lock,
 #endif
     Limit
 };
 
 bool
 IsRoundingFunction(SymbolicAddress callee, jit::RoundingMode* mode);
--- a/js/src/wasm/WasmValidate.cpp
+++ b/js/src/wasm/WasmValidate.cpp
@@ -17,28 +17,30 @@
  */
 
 #include "wasm/WasmValidate.h"
 
 #include "mozilla/CheckedInt.h"
 #include "mozilla/Unused.h"
 #include "mozilla/Utf8.h"
 
+#include "builtin/TypedObject.h"
 #include "jit/JitOptions.h"
 #include "js/Printf.h"
 #include "vm/JSContext.h"
 #include "vm/Realm.h"
 #include "wasm/WasmOpIter.h"
 
 using namespace js;
 using namespace js::jit;
 using namespace js::wasm;
 
 using mozilla::CheckedInt;
 using mozilla::IsValidUtf8;
+using mozilla::CheckedInt32;
 using mozilla::Unused;
 
 // Decoder implementation.
 
 bool
 Decoder::failf(const char* msg, ...)
 {
     va_list ap;
@@ -926,16 +928,47 @@ DecodeFunctionBodyExprs(const ModuleEnvi
                 CHECK(iter.readMemOrTableDrop(/*isMem=*/false, &unusedSegIndex));
               }
               case uint16_t(MiscOp::TableInit): {
                 uint32_t unusedSegIndex;
                 CHECK(iter.readMemOrTableInit(/*isMem=*/false,
                                               &unusedSegIndex, &nothing, &nothing, &nothing));
               }
 #endif
+#ifdef ENABLE_WASM_GC
+              case uint16_t(MiscOp::StructNew): {
+                if (env.gcTypesEnabled() == HasGcTypes::False) {
+                    return iter.unrecognizedOpcode(&op);
+                }
+                uint32_t unusedUint;
+                ValidatingOpIter::ValueVector unusedArgs;
+                CHECK(iter.readStructNew(&unusedUint, &unusedArgs));
+              }
+              case uint16_t(MiscOp::StructGet): {
+                if (env.gcTypesEnabled() == HasGcTypes::False) {
+                    return iter.unrecognizedOpcode(&op);
+                }
+                uint32_t unusedUint1, unusedUint2;
+                CHECK(iter.readStructGet(&unusedUint1, &unusedUint2, &nothing));
+              }
+              case uint16_t(MiscOp::StructSet): {
+                if (env.gcTypesEnabled() == HasGcTypes::False) {
+                    return iter.unrecognizedOpcode(&op);
+                }
+                uint32_t unusedUint1, unusedUint2;
+                CHECK(iter.readStructSet(&unusedUint1, &unusedUint2, &nothing, &nothing));
+              }
+              case uint16_t(MiscOp::StructNarrow): {
+                if (env.gcTypesEnabled() == HasGcTypes::False) {
+                    return iter.unrecognizedOpcode(&op);
+                }
+                ValType unusedTy, unusedTy2;
+                CHECK(iter.readStructNarrow(&unusedTy, &unusedTy2, &nothing));
+              }
+#endif
               default:
                 return iter.unrecognizedOpcode(&op);
             }
             break;
           }
 #ifdef ENABLE_WASM_GC
           case uint16_t(Op::RefEq): {
             if (env.gcTypesEnabled() == HasGcTypes::False) {
@@ -1305,41 +1338,80 @@ DecodeStructType(Decoder& d, ModuleEnvir
         return d.fail("too many fields in structure");
     }
 
     StructFieldVector fields;
     if (!fields.resize(numFields)) {
         return false;
     }
 
-    // TODO (subsequent patch): lay out the fields.
-
+    StructMetaTypeDescr::Layout layout;
     for (uint32_t i = 0; i < numFields; i++) {
         uint8_t flags;
         if (!d.readFixedU8(&flags)) {
             return d.fail("expected flag");
         }
         if ((flags & ~uint8_t(FieldFlags::AllowedMask)) != 0) {
             return d.fail("garbage flag bits");
         }
         fields[i].isMutable = flags & uint8_t(FieldFlags::Mutable);
         if (!DecodeValType(d, ModuleKind::Wasm, env->types.length(), env->gcTypesEnabled(), &fields[i].type)) {
             return false;
         }
         if (!ValidateRefType(d, typeState, fields[i].type)) {
             return false;
         }
+
+        CheckedInt32 offset;
+        switch (fields[i].type.code()) {
+          case ValType::I32:
+            offset = layout.addScalar(Scalar::Int32);
+            break;
+          case ValType::I64:
+            offset = layout.addScalar(Scalar::Int64);
+            break;
+          case ValType::F32:
+            offset = layout.addScalar(Scalar::Float32);
+            break;
+          case ValType::F64:
+            offset = layout.addScalar(Scalar::Float64);
+            break;
+          case ValType::Ref:
+          case ValType::AnyRef:
+            offset = layout.addReference(ReferenceType::TYPE_OBJECT);
+            break;
+          default:
+            MOZ_CRASH("Unknown type");
+        }
+        if (!offset.isValid()) {
+            return d.fail("Object too large");
+        }
+
+        fields[i].offset = offset.value();
+    }
+
+    CheckedInt32 totalSize = layout.close();
+    if (!totalSize.isValid()) {
+        return d.fail("Object too large");
+    }
+
+    bool isInline = InlineTypedObject::canAccommodateSize(totalSize.value());
+    uint32_t offsetBy = isInline ? InlineTypedObject::offsetOfDataStart() : 0;
+
+    for (StructField& f : fields) {
+        f.offset += offsetBy;
     }
 
     if ((*typeState)[typeIndex] != TypeState::None && (*typeState)[typeIndex] != TypeState::ForwardStruct) {
         return d.fail("struct type entry referenced as function");
     }
 
-    env->types[typeIndex] = TypeDef(StructType(std::move(fields)));
+    env->types[typeIndex] = TypeDef(StructType(std::move(fields), env->numStructTypes, isInline));
     (*typeState)[typeIndex] = TypeState::Struct;
+    env->numStructTypes++;
 
     return true;
 }
 
 #ifdef ENABLE_WASM_GC
 static bool
 DecodeGCFeatureOptInSection(Decoder& d, ModuleEnvironment* env)
 {
--- a/js/src/wasm/WasmValidate.h
+++ b/js/src/wasm/WasmValidate.h
@@ -160,16 +160,17 @@ struct ModuleEnvironment
     // The flag is used in the value of gcTypesEnabled(), which controls whether
     // ref types and struct types and associated instructions are accepted
     // during validation.
     HasGcTypes                gcFeatureOptIn;
 #endif
     MemoryUsage               memoryUsage;
     uint32_t                  minMemoryLength;
     Maybe<uint32_t>           maxMemoryLength;
+    uint32_t                  numStructTypes;
     TypeDefVector             types;
     FuncTypeWithIdPtrVector   funcTypes;
     Uint32Vector              funcImportGlobalDataOffsets;
     GlobalDescVector          globals;
     TableDescVector           tables;
     Uint32Vector              asmJSSigToTableIndex;
     ImportVector              imports;
     ExportVector              exports;
@@ -191,17 +192,18 @@ struct ModuleEnvironment
       : kind(kind),
         sharedMemoryEnabled(sharedMemoryEnabled),
         gcTypesConfigured(gcTypesConfigured),
         compilerEnv(compilerEnv),
 #ifdef ENABLE_WASM_GC
         gcFeatureOptIn(HasGcTypes::False),
 #endif
         memoryUsage(MemoryUsage::None),
-        minMemoryLength(0)
+        minMemoryLength(0),
+        numStructTypes(0)
     {}
 
     Tier tier() const {
         return compilerEnv->tier();
     }
     CompileMode mode() const {
         return compilerEnv->mode();
     }
@@ -696,16 +698,19 @@ class Decoder
         static_assert(uint8_t(TypeCode::Limit) <= UINT8_MAX, "fits");
         if (!readFixedU8(code)) {
             return false;
         }
         if (*code == uint8_t(TypeCode::Ref)) {
             if (!readVarU32(refTypeIndex)) {
                 return false;
             }
+            if (*refTypeIndex > MaxTypes) {
+                return false;
+            }
         } else {
             *refTypeIndex = NoRefTypeIndex;
         }
         return true;
     }
     MOZ_MUST_USE bool readBlockType(uint8_t* code, uint32_t* refTypeIndex) {
         static_assert(size_t(TypeCode::Limit) <= UINT8_MAX, "fits");
         if (!readFixedU8(code)) {
--- a/netwerk/protocol/res/ExtensionProtocolHandler.cpp
+++ b/netwerk/protocol/res/ExtensionProtocolHandler.cpp
@@ -179,16 +179,17 @@ public:
     NS_WARNING_ASSERTION(NS_SUCCEEDED(rv), "NS_DispatchToMainThread");
     return NS_OK;
   }
 
   NS_IMETHOD SendBackFD()
   {
     MOZ_ASSERT(NS_IsMainThread());
     mResolve(mFD);
+    mResolve = nullptr;
     return NS_OK;
   }
 
   NS_DECL_THREADSAFE_ISUPPORTS
 
 private:
   virtual ~ExtensionJARFileOpener() = default;
 
--- a/testing/mozharness/scripts/android_emulator_unittest.py
+++ b/testing/mozharness/scripts/android_emulator_unittest.py
@@ -646,22 +646,23 @@ class AndroidEmulatorTest(TestingMixin, 
     def preflight_install(self):
         # in the base class, this checks for mozinstall, but we don't use it
         pass
 
     @PreScriptAction('create-virtualenv')
     def pre_create_virtualenv(self, action):
         dirs = self.query_abs_dirs()
         requirements = None
-        if self.test_suite == 'mochitest-media':
+        suites = self._query_suites()
+        if ('mochitest-media', 'mochitest-media') in suites:
             # mochitest-media is the only thing that needs this
             requirements = os.path.join(dirs['abs_mochitest_dir'],
                                         'websocketprocessbridge',
                                         'websocketprocessbridge_requirements.txt')
-        elif self.test_suite == 'marionette':
+        elif ('marionette', 'marionette') in suites:
             requirements = os.path.join(dirs['abs_test_install_dir'],
                                         'config', 'marionette_requirements.txt')
         if requirements:
             self.register_virtualenv_module(requirements=[requirements],
                                             two_pass=True)
 
     def setup_avds(self):
         '''
--- a/testing/mozharness/scripts/android_hardware_unittest.py
+++ b/testing/mozharness/scripts/android_hardware_unittest.py
@@ -164,22 +164,23 @@ class AndroidHardwareTest(TestingMixin, 
                 abs_dirs[key] = dirs[key]
         self.abs_dirs = abs_dirs
         return self.abs_dirs
 
     @PreScriptAction('create-virtualenv')
     def _pre_create_virtualenv(self, action):
         dirs = self.query_abs_dirs()
         requirements = None
-        if self.test_suite == 'mochitest-media':
+        suites = self._query_suites()
+        if ('mochitest-media', 'mochitest-media') in suites:
             # mochitest-media is the only thing that needs this
             requirements = os.path.join(dirs['abs_mochitest_dir'],
                                         'websocketprocessbridge',
                                         'websocketprocessbridge_requirements.txt')
-        elif self.test_suite == 'marionette':
+        elif ('marionette', 'marionette') in suites:
             requirements = os.path.join(dirs['abs_test_install_dir'],
                                         'config', 'marionette_requirements.txt')
         if requirements:
             self.register_virtualenv_module(requirements=[requirements],
                                             two_pass=True)
 
     def _retry(self, max_attempts, interval, func, description, max_time=0):
         '''
--- a/testing/mozharness/scripts/mobile_l10n.py
+++ b/testing/mozharness/scripts/mobile_l10n.py
@@ -202,19 +202,16 @@ class MobileSingleLocale(LocalesMixin, T
              'abs_tools_dir': os.path.join(abs_dirs['base_work_dir'], 'tools'),
              'build_dir': os.path.join(abs_dirs['base_work_dir'], 'build'),
         }
 
         abs_dirs.update(dirs)
         self.abs_dirs = abs_dirs
         return self.abs_dirs
 
-    def add_failure(self, locale, message, **kwargs):
-        AutomationMixin.add_failure(self, locale, message=message, **kwargs)
-
     # Actions {{{2
     def clone_locales(self):
         self.pull_locale_source()
 
     # list_locales() is defined in LocalesMixin.
 
     def _setup_configure(self):
         dirs = self.query_abs_dirs()
--- a/toolkit/content/tests/chrome/test_bug562554.xul
+++ b/toolkit/content/tests/chrome/test_bug562554.xul
@@ -1,10 +1,9 @@
 <?xml version="1.0"?>
-<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
 <?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?>
 <!--
   XUL Widget Test for bug 562554
   -->
 <window title="Bug 562554" width="400" height="400"
         xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
         xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
   <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>  
@@ -42,22 +41,25 @@ SimpleTest.waitForFocus(test);
 function test() {
   disableNonTestMouseEvents(true);
   nextTest();
 }
 
 let tests = [
   // Click on the toolbarbutton itself - should call popupshowing
   () => synthesizeMouse($("toolbarmenu"), 10, 50, {}, window),
+  () => is(eventCount.popupshowing, 1, "Got first popupshowing event"),
 
   // Click on button1 which has allowevents="true" - should call clickbutton1
   () => synthesizeMouse($("toolbarmenu"), 10, 15, {}, window),
+  () => is(eventCount.clickbutton1, 1, "Button 1 clicked"),
 
   // Click on button2 where it intersects with button1 - should call popupshowing
   () => synthesizeMouse($("toolbarmenu"), 85, 15, {}, window),
+  () => is(eventCount.popupshowing, 2, "Got second popupshowing event"),
 
   // Click on button2 outside of intersection - should call popupshowing
   () => synthesizeMouse($("toolbarmenu"), 150, 15, {}, window)
 ];
 
 function nextTest() {
   if (tests.length) {
     let func = tests.shift();
--- a/toolkit/content/widgets.css
+++ b/toolkit/content/widgets.css
@@ -15,12 +15,14 @@
 @import url("chrome://global/skin/groupbox.css");
 @import url("chrome://global/skin/menu.css");
 @import url("chrome://global/skin/menulist.css");
 @import url("chrome://global/skin/notification.css");
 @import url("chrome://global/skin/popup.css");
 @import url("chrome://global/skin/progressmeter.css");
 @import url("chrome://global/skin/radio.css");
 @import url("chrome://global/skin/richlistbox.css");
+@import url("chrome://global/skin/scrollbox.css");
 @import url("chrome://global/skin/splitter.css");
 @import url("chrome://global/skin/tabbox.css");
 @import url("chrome://global/skin/toolbar.css");
+@import url("chrome://global/skin/toolbarbutton.css");
 @import url("chrome://global/skin/wizard.css");
--- a/toolkit/content/widgets/scrollbox.xml
+++ b/toolkit/content/widgets/scrollbox.xml
@@ -13,20 +13,16 @@
     <content>
       <xul:box class="box-inherit scrollbox-innerbox" xbl:inherits="orient,align,pack,dir" flex="1">
         <children/>
       </xul:box>
     </content>
   </binding>
 
   <binding id="arrowscrollbox" extends="chrome://global/content/bindings/general.xml#basecontrol">
-    <resources>
-      <stylesheet src="chrome://global/skin/scrollbox.css"/>
-    </resources>
-
     <content>
       <xul:toolbarbutton class="scrollbutton-up"
                          anonid="scrollbutton-up"
                          xbl:inherits="orient,collapsed=notoverflowing,disabled=scrolledtostart"
                          onmouseover="_startScroll(-1);"
                          onmouseout="_stopScroll();"/>
       <xul:spacer class="arrowscrollbox-overflow-start-indicator"
                   xbl:inherits="collapsed=scrolledtostart"/>
--- a/toolkit/content/widgets/toolbarbutton.xml
+++ b/toolkit/content/widgets/toolbarbutton.xml
@@ -6,20 +6,16 @@
 
 <bindings id="toolbarbuttonBindings"
    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="toolbarbutton" display="xul:button"
            extends="chrome://global/content/bindings/button.xml#button-base">
-    <resources>
-      <stylesheet src="chrome://global/skin/toolbarbutton.css"/>
-    </resources>
-
     <content>
       <children includes="observes|template|menupopup|panel|tooltip"/>
       <xul:image class="toolbarbutton-icon" xbl:inherits="validate,src=image,label,type,consumeanchor,triggeringprincipal=iconloadingprincipal"/>
       <xul:label class="toolbarbutton-text" crop="right" flex="1"
                  xbl:inherits="value=label,accesskey,crop,dragover-top,wrap"/>
       <xul:label class="toolbarbutton-multiline-text" flex="1"
                  xbl:inherits="xbl:text=label,accesskey,wrap"/>
       <children includes="box"/>
--- a/toolkit/themes/linux/global/toolbarbutton.css
+++ b/toolkit/themes/linux/global/toolbarbutton.css
@@ -23,61 +23,53 @@ toolbarbutton {
 }
 
 .toolbarbutton-icon[label]:not([label=""]),
 .toolbarbutton-icon[type="menu"] {
   margin-inline-end: 2px;
 }
 
 .toolbarbutton-text {
-  margin: 0 !important; /* !important for overriding global.css */
+  margin: 0;
   text-align: center;
 }
 
 toolbarbutton.tabbable {
   -moz-user-focus: normal !important;
 }
 
 toolbarbutton:hover {
   color: -moz-buttonhovertext;
 }
 
-toolbarbutton:hover:active,
+toolbarbutton:hover:active:not([disabled="true"]),
 toolbarbutton[open="true"] {
   padding-top: 4px;
   padding-bottom: 2px;
   padding-inline-start: 4px;
   padding-inline-end: 2px;
   color: ButtonText;
 }
 
-toolbarbutton[disabled="true"],
-toolbarbutton[disabled="true"]:hover,
-toolbarbutton[disabled="true"]:hover:active,
-toolbarbutton[disabled="true"][open="true"] {
-  padding: 3px;
+toolbarbutton[disabled="true"] {
   color: GrayText;
 }
 
 toolbarbutton[checked="true"]:not(:hover) {
   color: ButtonText;
 }
 
 toolbarbutton:-moz-lwtheme:not(:hover):not([checked="true"]):not([open="true"]):not([disabled="true"]) {
   text-shadow: inherit;
 }
 
 /* ::::: toolbarbutton menu ::::: */
 
 .toolbarbutton-menu-dropmarker {
-  -moz-appearance: toolbarbutton-dropdown !important;
-}
-
-.toolbarbutton-menu-dropmarker[disabled="true"] {
-  padding: 0 !important;
+  -moz-appearance: toolbarbutton-dropdown;
 }
 
 /* ::::: toolbarbutton badged ::::: */
 .toolbarbutton-badge-stack > .toolbarbutton-icon[label]:not([label=""]) {
   margin-inline-end: 0;
 }
 
 .toolbarbutton-badge {
--- a/toolkit/themes/osx/global/toolbarbutton.css
+++ b/toolkit/themes/osx/global/toolbarbutton.css
@@ -8,42 +8,40 @@ toolbarbutton {
   -moz-box-align: center;
   -moz-box-pack: center;
   margin: 0 2px;
   padding: 3px 2px;
   background-color: transparent;
 }
 
 .toolbarbutton-text {
-  margin: 0 !important; /* !important for overriding global.css */
-  padding: 0px;
+  margin: 0;
+  padding: 0;
   text-align: center;
   vertical-align: middle;
 }
 
 toolbarbutton[disabled="true"],
 toolbarbutton[disabled="true"]:hover,
 toolbarbutton[disabled="true"]:hover:active,
 toolbarbutton[disabled="true"][open="true"] {
   color: -moz-mac-disabledtoolbartext;
 }
 
 /* ::::: toolbarbutton menu ::::: */
 
 .toolbarbutton-menu-dropmarker {
-  -moz-appearance: none !important;
+  -moz-appearance: none;
   list-style-image: url("chrome://global/skin/arrow/arrow-dn.png");
   padding-inline-start: 2px;
   width: auto;
 }
 
 .toolbarbutton-menu-dropmarker[disabled="true"] {
   list-style-image: url("chrome://global/skin/arrow/arrow-dn-dis.png");
-  padding: 0;
-  padding-inline-start: 2px;
 }
 
 /* ::::: toolbarbutton badged ::::: */
 
 .toolbarbutton-badge {
   background-color: #d90000;
   font-size: 9px;
   padding: 1px 2px;
--- a/toolkit/themes/windows/global/toolbarbutton.css
+++ b/toolkit/themes/windows/global/toolbarbutton.css
@@ -19,17 +19,17 @@ toolbarbutton {
 }
 
 .toolbarbutton-icon[label]:not([label=""]),
 .toolbarbutton-icon[type="menu"] {
   margin-inline-end: 5px;
 }
 
 .toolbarbutton-text {
-  margin: 0 !important; /* !important for overriding global.css */
+  margin: 0;
   text-align: center;
 }
 
 toolbarbutton.tabbable {
   -moz-user-focus: normal !important;
 }
 
 toolbarbutton:-moz-focusring {
@@ -86,17 +86,17 @@ toolbarbutton[checked="true"]:not([disab
   :root[lwtheme-image] toolbarbutton:not([disabled="true"]) {
     text-shadow: inherit;
   }
 }
 
 /* ::::: toolbarbutton menu ::::: */
 
 .toolbarbutton-menu-dropmarker {
-  -moz-appearance: none !important;
+  -moz-appearance: none;
   list-style-image: url("chrome://global/skin/icons/arrow-dropdown-12.svg");
   -moz-context-properties: fill;
   fill: currentColor;
   width: 12px;
   height: 12px;
 }
 
 /* ::::: toolbarbutton badged ::::: */
--- a/view/nsView.cpp
+++ b/view/nsView.cpp
@@ -229,27 +229,16 @@ bool nsView::IsEffectivelyVisible()
 {
   for (nsView* v = this; v; v = v->mParent) {
     if (v->GetVisibility() == nsViewVisibility_kHide)
       return false;
   }
   return true;
 }
 
-uint32_t nsView::GetParentWindowScaleFactor()
-{
-  uint32_t scaleFactor = 1;
-  nsIWidget* parentWidget =
-    GetParent() ? GetParent()->GetNearestWidget(nullptr) : nullptr;
-  if (parentWidget) {
-    scaleFactor = parentWidget->RoundsWidgetCoordinatesTo();
-  }
-  return scaleFactor;
-}
-
 LayoutDeviceIntRect nsView::CalcWidgetBounds(nsWindowType aType)
 {
   int32_t p2a = mViewManager->AppUnitsPerDevPixel();
 
   nsRect viewBounds(mDimBounds);
 
   nsView* parent = GetParent();
   nsIWidget* parentWidget = nullptr;
@@ -326,19 +315,17 @@ void nsView::DoResetWidgetBounds(bool aM
   MOZ_ASSERT(mWindow, "Why was this called??");
 
   // Hold this ref to make sure it stays alive.
   nsCOMPtr<nsIWidget> widget = mWindow;
 
   // Stash a copy of these and use them so we can handle this being deleted (say
   // from sync painting/flushing from Show/Move/Resize on the widget).
   LayoutDeviceIntRect newBounds;
-#if !defined(MOZ_WIDGET_GTK)
   RefPtr<nsDeviceContext> dx = mViewManager->GetDeviceContext();
-#endif
 
   nsWindowType type = widget->WindowType();
 
   LayoutDeviceIntRect curBounds = widget->GetClientBounds();
   bool invisiblePopup = type == eWindowType_popup &&
                         ((curBounds.IsEmpty() && mDimBounds.IsEmpty()) ||
                          mVis == nsViewVisibility_kHide);
 
@@ -368,25 +355,17 @@ void nsView::DoResetWidgetBounds(bool aM
   bool changedSize = curBounds.Size() != newBounds.Size();
 
   // Child views are never attached to top level widgets, this is safe.
 
   // Coordinates are converted to desktop pixels for window Move/Resize APIs,
   // because of the potential for device-pixel coordinate spaces for mixed
   // hidpi/lodpi screens to overlap each other and result in bad placement
   // (bug 814434).
-#if defined(MOZ_WIDGET_GTK)
-  // The GetDesktopToDeviceScale does not work under Wayland because we
-  // don't know the absolute position of the window in Wayland. We can
-  // use the same for X11, because it always returns 1 for both calls.
-  DesktopToLayoutDeviceScale scale = mozilla::DesktopToLayoutDeviceScale(
-    GetParentWindowScaleFactor());
-#else
   DesktopToLayoutDeviceScale scale = dx->GetDesktopToDeviceScale();
-#endif
 
   DesktopRect deskRect = newBounds / scale;
   if (changedPos) {
     if (changedSize && !aMoveOnly) {
       widget->ResizeClient(deskRect.X(), deskRect.Y(),
                            deskRect.Width(), deskRect.Height(),
                            aInvalidateChangedSize);
     } else {
--- a/view/nsView.h
+++ b/view/nsView.h
@@ -457,19 +457,16 @@ private:
 
   void ResetWidgetBounds(bool aRecurse, bool aForceSync);
   void AssertNoWindow();
 
   void NotifyEffectiveVisibilityChanged(bool aEffectivelyVisible);
 
   // Update the cached RootViewManager for all view manager descendents.
   void InvalidateHierarchy();
-  // Return current scale factor of the monitor where the parent window of the popup
-  // is mostly located.
-  uint32_t GetParentWindowScaleFactor();
 
   nsViewManager    *mViewManager;
   nsView           *mParent;
   nsCOMPtr<nsIWidget> mWindow;
   nsCOMPtr<nsIWidget> mPreviousWindow;
   nsView           *mNextSibling;
   nsView           *mFirstChild;
   nsIFrame         *mFrame;
--- a/xpcom/base/CycleCollectedJSContext.cpp
+++ b/xpcom/base/CycleCollectedJSContext.cpp
@@ -222,22 +222,16 @@ protected:
   virtual void Run(AutoSlowOperation& aAso) override
   {
     JSObject* callback = mCallback->CallbackPreserveColor();
     nsIGlobalObject* global = callback ? xpc::NativeGlobal(callback) : nullptr;
     if (global && !global->IsDying()) {
       mCallback->Call("promise callback");
       aAso.CheckForInterrupt();
     }
-    // Now that mCallback is no longer needed, clear any pointers it contains to
-    // JS GC things. This removes any storebuffer entries associated with those
-    // pointers, which can cause problems by taking up memory and by triggering
-    // minor GCs. This otherwise would not happen until the next minor GC or
-    // cycle collection.
-    mCallback->Reset();
   }
 
   virtual bool Suppressed() override
   {
     nsIGlobalObject* global =
       xpc::NativeGlobal(mCallback->CallbackPreserveColor());
     return global && global->IsInSyncOperation();
   }