Merge inbound to mozilla-central. a=merge
authorBogdan Tara <btara@mozilla.com>
Sat, 22 Sep 2018 00:59:34 +0300
changeset 493472 eddbfdc38cbc
parent 493456 c348ec8b2900 (current diff)
parent 493471 6b11a8172db4 (diff)
child 493487 6c1f0d449eca
child 493515 f125ee6d1cef
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
eddbfdc38cbc / 64.0a1 / 20180921220134 / files
nightly linux64
eddbfdc38cbc / 64.0a1 / 20180921220134 / files
nightly mac
eddbfdc38cbc / 64.0a1 / 20180921220134 / files
nightly win32
eddbfdc38cbc / 64.0a1 / 20180921220134 / files
nightly win64
eddbfdc38cbc / 64.0a1 / 20180921220134 / files
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
releases
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge inbound to mozilla-central. a=merge
--- a/browser/components/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/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 ::::: */