Merge inbound to mozilla-central. a=merge
authorMargareta Eliza Balazs <ebalazs@mozilla.com>
Thu, 13 Sep 2018 12:37:56 +0300
changeset 491855 efccb758c78c
parent 491824 59e80dad4626 (current diff)
parent 491854 1bf2fd049433 (diff)
child 491856 2603d8004c78
child 491866 a3934bdc3c68
child 491922 8fc78d730c06
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
efccb758c78c / 64.0a1 / 20180913100107 / files
nightly linux64
efccb758c78c / 64.0a1 / 20180913100107 / files
nightly mac
efccb758c78c / 64.0a1 / 20180913100107 / files
nightly win32
efccb758c78c / 64.0a1 / 20180913100107 / files
nightly win64
efccb758c78c / 64.0a1 / 20180913100107 / 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
browser/config/mozconfigs/linux64/debug-lto
browser/config/mozconfigs/linux64/nightly-lto
build/unix/mozconfig.lto
editor/composer/nsComposerRegistration.cpp
testing/mozharness/configs/builds/releng_sub_linux_configs/64_lto_tc.py
testing/mozharness/configs/builds/releng_sub_linux_configs/64_lto_tc_and_debug.py
--- a/browser/base/content/test/performance/browser_preferences_usage.js
+++ b/browser/base/content/test/performance/browser_preferences_usage.js
@@ -94,16 +94,25 @@ add_task(async function startup() {
     },
     "network.loadinfo.skip_type_assertion": {
       // This is accessed in debug only.
     },
     "extensions.getAddons.cache.enabled": {
       min: 7,
       max: 55,
     },
+
+    // Disabling screenshots in the default test profile triggers some
+    // work in the chrome registry that reads this pref.  This can be removed
+    // when bootstrapped extensions are gone, or even when screenshots
+    // moves away from bootstrap (bug 1422437)
+    "chrome.override_package.global": {
+      min: 0,
+      max: 50,
+    },
   };
 
   let startupRecorder = Cc["@mozilla.org/test/startuprecorder;1"].getService().wrappedJSObject;
   await startupRecorder.done;
 
   ok(startupRecorder.data.prefStats, "startupRecorder has prefStats");
 
   checkPrefGetters(startupRecorder.data.prefStats, max, whitelist);
--- a/browser/components/nsBrowserGlue.js
+++ b/browser/components/nsBrowserGlue.js
@@ -1396,16 +1396,32 @@ BrowserGlue.prototype = {
     // them is collected on all channels.
     if (AppConstants.MOZ_DATA_REPORTING) {
       this.browserErrorReporter.uninit();
     }
 
     Normandy.uninit();
   },
 
+  // Set up a listener to enable/disable the screenshots extension
+  // based on its preference.
+  _monitorScreenshotsPref() {
+    const PREF = "extensions.screenshots.disabled";
+    const ID = "screenshots@mozilla.org";
+    Services.prefs.addObserver(PREF, async () => {
+      let addon = await AddonManager.getAddonByID(ID);
+      let disabled = Services.prefs.getBoolPref(PREF, false);
+      if (disabled) {
+        await addon.disable({allowSystemAddons: true});
+      } else {
+        await addon.enable({allowSystemAddons: true});
+      }
+    });
+  },
+
   // All initial windows have opened.
   _onWindowsRestored: function BG__onWindowsRestored() {
     if (this._windowsWereRestored) {
       return;
     }
     this._windowsWereRestored = true;
 
     // Browser errors are only collected on Nightly, but telemetry for
@@ -1452,16 +1468,18 @@ BrowserGlue.prototype = {
         idleService.removeIdleObserver(this._lateTasksIdleObserver,
                                        LATE_TASKS_IDLE_TIME_SEC);
         delete this._lateTasksIdleObserver;
         this._scheduleArbitrarilyLateIdleTasks();
       }
     };
     this._idleService.addIdleObserver(
       this._lateTasksIdleObserver, LATE_TASKS_IDLE_TIME_SEC);
+
+    this._monitorScreenshotsPref();
   },
 
   /**
    * Use this function as an entry point to schedule tasks that
    * need to run only once after startup, and can be scheduled
    * by using an idle callback.
    *
    * The functions scheduled here will fire from idle callbacks
deleted file mode 100644
--- a/browser/config/mozconfigs/linux64/debug-lto
+++ /dev/null
@@ -1,17 +0,0 @@
-ac_add_options --enable-debug
-ac_add_options --enable-optimize="-O1"
-
-. $topsrcdir/build/mozconfig.stylo
-
-. $topsrcdir/build/unix/mozconfig.lto
-
-# Enable Telemetry
-export MOZ_TELEMETRY_REPORTING=1
-
-# Package js shell.
-export MOZ_PACKAGE_JSSHELL=1
-
-# Need this to prevent name conflicts with the normal nightly build packages
-export MOZ_PKG_SPECIAL=lto
-
-. "$topsrcdir/build/mozconfig.common.override"
deleted file mode 100644
--- a/browser/config/mozconfigs/linux64/nightly-lto
+++ /dev/null
@@ -1,14 +0,0 @@
-ac_add_options --disable-debug
-ac_add_options --enable-optimize="-O2"
-
-. $topsrcdir/build/mozconfig.stylo
-
-. $topsrcdir/build/unix/mozconfig.lto
-
-# Package js shell.
-export MOZ_PACKAGE_JSSHELL=1
-
-# Need this to prevent name conflicts with the normal nightly build packages
-export MOZ_PKG_SPECIAL=lto
-
-. "$topsrcdir/build/mozconfig.common.override"
--- a/browser/extensions/screenshots/bootstrap.js
+++ b/browser/extensions/screenshots/bootstrap.js
@@ -20,35 +20,16 @@ ChromeUtils.defineModuleGetter(this, "Se
 
 let addonResourceURI;
 let appStartupDone;
 let appStartupPromise = new Promise((resolve, reject) => {
   appStartupDone = resolve;
 });
 
 const prefs = Services.prefs;
-const prefObserver = {
-  register() {
-    prefs.addObserver(PREF_BRANCH, this, false); // eslint-disable-line mozilla/no-useless-parameters
-  },
-
-  unregister() {
-    prefs.removeObserver(PREF_BRANCH, this, false); // eslint-disable-line mozilla/no-useless-parameters
-  },
-
-  observe(aSubject, aTopic, aData) {
-    // aSubject is the nsIPrefBranch we're observing (after appropriate QI)
-    // aData is the name of the pref that's been changed (relative to aSubject)
-    if (aData === USER_DISABLE_PREF) {
-      // eslint-disable-next-line promise/catch-or-return
-      appStartupPromise = appStartupPromise.then(handleStartup);
-    }
-  }
-};
-
 
 const appStartupObserver = {
   register() {
     Services.obs.addObserver(this, "sessionstore-windows-restored", false); // eslint-disable-line mozilla/no-useless-parameters
   },
 
   unregister() {
     Services.obs.removeObserver(this, "sessionstore-windows-restored", false); // eslint-disable-line mozilla/no-useless-parameters
@@ -114,31 +95,40 @@ const LibraryButton = {
   },
 };
 
 const APP_STARTUP = 1;
 const APP_SHUTDOWN = 2;
 let addonData, startupReason;
 
 function startup(data, reason) { // eslint-disable-line no-unused-vars
+  addonResourceURI = data.resourceURI;
+
+  if (Services.prefs.getBoolPref(USER_DISABLE_PREF, false)) {
+    AddonManager.getActiveAddons().then(result => {
+      let addon = result.addons.find(a => a.id == ADDON_ID);
+      if (addon) {
+        addon.disable({allowSystemAddons: true});
+      }
+    });
+    return;
+  }
+
   addonData = data;
   startupReason = reason;
   if (reason === APP_STARTUP) {
     appStartupObserver.register();
   } else {
     appStartupDone();
   }
-  prefObserver.register();
-  addonResourceURI = data.resourceURI;
   // eslint-disable-next-line promise/catch-or-return
   appStartupPromise = appStartupPromise.then(handleStartup);
 }
 
 function shutdown(data, reason) { // eslint-disable-line no-unused-vars
-  prefObserver.unregister();
   const webExtension = LegacyExtensionsUtils.getEmbeddedExtensionFor({
     id: ADDON_ID,
     resourceURI: addonResourceURI
   });
   // Immediately exit if Firefox is exiting, #3323
   if (reason === APP_SHUTDOWN) {
     stop(webExtension, reason);
     return;
@@ -150,30 +140,24 @@ function shutdown(data, reason) { // esl
 function install(data, reason) {} // eslint-disable-line no-unused-vars
 
 function uninstall(data, reason) {} // eslint-disable-line no-unused-vars
 
 function getBoolPref(pref) {
   return prefs.getPrefType(pref) && prefs.getBoolPref(pref);
 }
 
-function shouldDisable() {
-  return getBoolPref(USER_DISABLE_PREF);
-}
-
 function handleStartup() {
   const webExtension = LegacyExtensionsUtils.getEmbeddedExtensionFor({
     id: ADDON_ID,
     resourceURI: addonResourceURI
   });
 
-  if (!shouldDisable() && !webExtension.started) {
+  if (!webExtension.started) {
     start(webExtension);
-  } else if (shouldDisable()) {
-    stop(webExtension, ADDON_DISABLE);
   }
 }
 
 function start(webExtension) {
   return webExtension.startup(startupReason, addonData).then((api) => {
     api.browser.runtime.onMessage.addListener(handleMessage);
     LibraryButton.init(webExtension);
   }).catch((err) => {
deleted file mode 100644
--- a/build/unix/mozconfig.lto
+++ /dev/null
@@ -1,10 +0,0 @@
-MOZ_AUTOMATION_L10N_CHECK=0
-
-. "$topsrcdir/build/unix/mozconfig.linux"
-
-# Use Clang as specified in manifest
-export AR="$topsrcdir/clang/bin/llvm-ar"
-export NM="$topsrcdir/clang/bin/llvm-nm"
-export RANLIB="$topsrcdir/clang/bin/llvm-ranlib"
-
-ac_add_options --enable-lto
--- a/devtools/client/locales/en-US/responsive.properties
+++ b/devtools/client/locales/en-US/responsive.properties
@@ -125,14 +125,20 @@ responsive.reloadConditions.touchSimulat
 responsive.reloadConditions.userAgent=Reload when user agent is changed
 
 # LOCALIZATION NOTE (responsive.reloadNotification.description2): Text in notification bar
 # shown on first open to clarify that some features need a reload to apply.
 responsive.reloadNotification.description2=Device simulation changes require a reload to fully apply. Automatic reloads are disabled by default to avoid losing any changes in DevTools. You can enable reloading via the Settings menu.
 
 # LOCALIZATION NOTE (responsive.leftAlignViewport): Label on checkbox used in the settings
 # menu.
-responsive.leftAlignViewport = Left-align Viewport
+responsive.leftAlignViewport=Left-align Viewport
 
 # LOCALIZATION NOTE (responsive.settingOnboarding.content): This is the content shown in
 # the setting onboarding tooltip that is displayed below the settings menu button in
 # Responsive Design Mode.
 responsive.settingOnboarding.content=New: Change to left-alignment or edit reload behavior here.
+
+# LOCALIZATION NOTE (responsive.customUserAgent): This is the placeholder for the user
+# agent input in the responsive design mode toolbar.
+responsive.customUserAgent=Custom User Agent
+
+responsive.showUserAgentInput=Show user agent
--- a/devtools/client/preferences/devtools-client.js
+++ b/devtools/client/preferences/devtools-client.js
@@ -330,16 +330,22 @@ pref("devtools.responsive.reloadConditio
 // Whether to show the notification about reloading to apply emulation
 pref("devtools.responsive.reloadNotification.enabled", true);
 // Whether to show the settings onboarding tooltip only in release or beta builds.
 #if defined(RELEASE_OR_BETA)
 pref("devtools.responsive.show-setting-tooltip", true);
 #else
 pref("devtools.responsive.show-setting-tooltip", false);
 #endif
+// Show the custom user agent input in Nightly builds.
+#if defined(NIGHTLY_BUILD)
+pref("devtools.responsive.showUserAgentInput", true);
+#else
+pref("devtools.responsive.showUserAgentInput", false);
+#endif
 
 // Enable new about:debugging.
 pref("devtools.aboutdebugging.new-enabled", false);
 pref("devtools.aboutdebugging.network-locations", "[]");
 // Debug target pane collapse/expand settings.
 pref("devtools.aboutdebugging.collapsibilities.installedExtension", false);
 pref("devtools.aboutdebugging.collapsibilities.otherWorker", false);
 pref("devtools.aboutdebugging.collapsibilities.serviceWorker", false);
--- a/devtools/client/responsive.html/actions/devices.js
+++ b/devtools/client/responsive.html/actions/devices.js
@@ -11,17 +11,16 @@ const {
   ADD_DEVICE_TYPE,
   LOAD_DEVICE_LIST_START,
   LOAD_DEVICE_LIST_ERROR,
   LOAD_DEVICE_LIST_END,
   REMOVE_DEVICE,
   UPDATE_DEVICE_DISPLAYED,
   UPDATE_DEVICE_MODAL,
 } = require("./index");
-const { removeDeviceAssociation } = require("./viewports");
 
 const { addDevice, getDevices, removeDevice } = require("devtools/client/shared/devices");
 
 const DISPLAYED_DEVICES_PREF = "devtools.responsive.html.displayedDeviceList";
 
 /**
  * Returns an object containing the user preference of displayed devices.
  *
@@ -97,25 +96,17 @@ module.exports = {
   addDeviceType(deviceType) {
     return {
       type: ADD_DEVICE_TYPE,
       deviceType,
     };
   },
 
   removeCustomDevice(device) {
-    return async function(dispatch, getState) {
-      // Check if the custom device is currently associated with any viewports
-      const { viewports } = getState();
-      for (const viewport of viewports) {
-        if (viewport.device == device.name) {
-          dispatch(removeDeviceAssociation(viewport.id));
-        }
-      }
-
+    return async function(dispatch) {
       // Remove custom device from device storage
       await removeDevice(device, "custom");
       dispatch({
         type: REMOVE_DEVICE,
         device,
         deviceType: "custom",
       });
     };
--- a/devtools/client/responsive.html/actions/index.js
+++ b/devtools/client/responsive.html/actions/index.js
@@ -35,16 +35,19 @@ createEnum([
   // The pixel ratio of the display has changed. This may be triggered by the user
   // when changing the monitor resolution, or when the window is dragged to a different
   // display with a different pixel ratio.
   "CHANGE_DISPLAY_PIXEL_RATIO",
 
   // Change the network throttling profile.
   CHANGE_NETWORK_THROTTLING,
 
+  // Change the user agent of the viewport.
+  "CHANGE_USER_AGENT",
+
   // The pixel ratio of the viewport has changed. This may be triggered by the user
   // when changing the device displayed in the viewport, or when a pixel ratio is
   // selected from the device pixel ratio dropdown.
   "CHANGE_PIXEL_RATIO",
 
   // Change one of the reload conditions.
   "CHANGE_RELOAD_CONDITION",
 
@@ -79,15 +82,18 @@ createEnum([
   "TAKE_SCREENSHOT_END",
 
   // Toggles the left alignment of the viewports.
   "TOGGLE_LEFT_ALIGNMENT",
 
   // Toggles the touch simulation state of the viewports.
   "TOGGLE_TOUCH_SIMULATION",
 
+  // Toggles the user agent input displayed in the toolbar.
+  "TOGGLE_USER_AGENT_INPUT",
+
   // Update the device display state in the device selector.
   "UPDATE_DEVICE_DISPLAYED",
 
   // Update the device modal state.
   "UPDATE_DEVICE_MODAL",
 
 ], module.exports);
--- a/devtools/client/responsive.html/actions/ui.js
+++ b/devtools/client/responsive.html/actions/ui.js
@@ -1,41 +1,57 @@
 /* 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/. */
 
 "use strict";
 
 const {
+  CHANGE_USER_AGENT,
   CHANGE_DISPLAY_PIXEL_RATIO,
   TOGGLE_LEFT_ALIGNMENT,
   TOGGLE_TOUCH_SIMULATION,
+  TOGGLE_USER_AGENT_INPUT,
 } = require("./index");
 
 module.exports = {
 
   /**
    * The pixel ratio of the display has changed. This may be triggered by the user
    * when changing the monitor resolution, or when the window is dragged to a different
    * display with a different pixel ratio.
    */
   changeDisplayPixelRatio(displayPixelRatio) {
     return {
       type: CHANGE_DISPLAY_PIXEL_RATIO,
       displayPixelRatio,
     };
   },
 
+  changeUserAgent(userAgent) {
+    return {
+      type: CHANGE_USER_AGENT,
+      userAgent,
+    };
+  },
+
   toggleLeftAlignment(enabled) {
     return {
       type: TOGGLE_LEFT_ALIGNMENT,
       enabled,
     };
   },
 
   toggleTouchSimulation(enabled) {
     return {
       type: TOGGLE_TOUCH_SIMULATION,
       enabled,
     };
   },
 
+  toggleUserAgentInput(enabled) {
+    return {
+      type: TOGGLE_USER_AGENT_INPUT,
+      enabled,
+    };
+  },
+
 };
--- a/devtools/client/responsive.html/components/App.js
+++ b/devtools/client/responsive.html/components/App.js
@@ -23,18 +23,20 @@ const {
   removeCustomDevice,
   updateDeviceDisplayed,
   updateDeviceModal,
   updatePreferredDevices,
 } = require("../actions/devices");
 const { changeReloadCondition } = require("../actions/reload-conditions");
 const { takeScreenshot } = require("../actions/screenshot");
 const {
+  changeUserAgent,
   toggleTouchSimulation,
   toggleLeftAlignment,
+  toggleUserAgentInput,
 } = require("../actions/ui");
 const {
   changeDevice,
   changePixelRatio,
   removeDeviceAssociation,
   resizeViewport,
   rotateViewport,
 } = require("../actions/viewports");
@@ -58,25 +60,27 @@ class App extends PureComponent {
 
     this.onAddCustomDevice = this.onAddCustomDevice.bind(this);
     this.onBrowserMounted = this.onBrowserMounted.bind(this);
     this.onChangeDevice = this.onChangeDevice.bind(this);
     this.onChangeNetworkThrottling = this.onChangeNetworkThrottling.bind(this);
     this.onChangePixelRatio = this.onChangePixelRatio.bind(this);
     this.onChangeReloadCondition = this.onChangeReloadCondition.bind(this);
     this.onChangeTouchSimulation = this.onChangeTouchSimulation.bind(this);
+    this.onChangeUserAgent = this.onChangeUserAgent.bind(this);
     this.onContentResize = this.onContentResize.bind(this);
     this.onDeviceListUpdate = this.onDeviceListUpdate.bind(this);
     this.onExit = this.onExit.bind(this);
     this.onRemoveCustomDevice = this.onRemoveCustomDevice.bind(this);
     this.onRemoveDeviceAssociation = this.onRemoveDeviceAssociation.bind(this);
     this.onResizeViewport = this.onResizeViewport.bind(this);
     this.onRotateViewport = this.onRotateViewport.bind(this);
     this.onScreenshot = this.onScreenshot.bind(this);
     this.onToggleLeftAlignment = this.onToggleLeftAlignment.bind(this);
+    this.onToggleUserAgentInput = this.onToggleUserAgentInput.bind(this);
     this.onUpdateDeviceDisplayed = this.onUpdateDeviceDisplayed.bind(this);
     this.onUpdateDeviceModal = this.onUpdateDeviceModal.bind(this);
   }
 
   onAddCustomDevice(device) {
     this.props.dispatch(addCustomDevice(device));
   }
 
@@ -88,18 +92,19 @@ class App extends PureComponent {
     // TODO: Bug 1332754: Move messaging and logic into the action creator so that the
     // message is sent from the action creator and device property changes are sent from
     // there instead of this function.
     window.postMessage({
       type: "change-device",
       device,
     }, "*");
     this.props.dispatch(changeDevice(id, device.name, deviceType));
+    this.props.dispatch(changePixelRatio(id, device.pixelRatio));
+    this.props.dispatch(changeUserAgent(device.userAgent));
     this.props.dispatch(toggleTouchSimulation(device.touch));
-    this.props.dispatch(changePixelRatio(id, device.pixelRatio));
   }
 
   onChangeNetworkThrottling(enabled, profile) {
     window.postMessage({
       type: "change-network-throttling",
       enabled,
       profile,
     }, "*");
@@ -121,16 +126,24 @@ class App extends PureComponent {
   onChangeTouchSimulation(enabled) {
     window.postMessage({
       type: "change-touch-simulation",
       enabled,
     }, "*");
     this.props.dispatch(toggleTouchSimulation(enabled));
   }
 
+  onChangeUserAgent(userAgent) {
+    window.postMessage({
+      type: "change-user-agent",
+      userAgent,
+    }, "*");
+    this.props.dispatch(changeUserAgent(userAgent));
+  }
+
   onContentResize({ width, height }) {
     window.postMessage({
       type: "content-resize",
       width,
       height,
     }, "*");
   }
 
@@ -138,25 +151,34 @@ class App extends PureComponent {
     updatePreferredDevices(devices);
   }
 
   onExit() {
     window.postMessage({ type: "exit" }, "*");
   }
 
   onRemoveCustomDevice(device) {
+    // If the custom device is currently selected on any of the viewports,
+    // remove the device association and reset all the ui state.
+    for (const viewport of this.props.viewports) {
+      if (viewport.device === device.name) {
+        this.onRemoveDeviceAssociation(viewport.id);
+      }
+    }
+
     this.props.dispatch(removeCustomDevice(device));
   }
 
   onRemoveDeviceAssociation(id) {
     // TODO: Bug 1332754: Move messaging and logic into the action creator so that device
     // property changes are sent from there instead of this function.
     this.props.dispatch(removeDeviceAssociation(id));
     this.props.dispatch(toggleTouchSimulation(false));
     this.props.dispatch(changePixelRatio(id, 0));
+    this.props.dispatch(changeUserAgent(""));
   }
 
   onResizeViewport(id, width, height) {
     this.props.dispatch(resizeViewport(id, width, height));
   }
 
   onRotateViewport(id) {
     this.props.dispatch(rotateViewport(id));
@@ -165,16 +187,20 @@ class App extends PureComponent {
   onScreenshot() {
     this.props.dispatch(takeScreenshot());
   }
 
   onToggleLeftAlignment() {
     this.props.dispatch(toggleLeftAlignment());
   }
 
+  onToggleUserAgentInput() {
+    this.props.dispatch(toggleUserAgentInput());
+  }
+
   onUpdateDeviceDisplayed(device, deviceType, displayed) {
     this.props.dispatch(updateDeviceDisplayed(device, deviceType, displayed));
   }
 
   onUpdateDeviceModal(isOpen, modalOpenedFromViewport) {
     this.props.dispatch(updateDeviceModal(isOpen, modalOpenedFromViewport));
   }
 
@@ -190,25 +216,27 @@ class App extends PureComponent {
     const {
       onAddCustomDevice,
       onBrowserMounted,
       onChangeDevice,
       onChangeNetworkThrottling,
       onChangePixelRatio,
       onChangeReloadCondition,
       onChangeTouchSimulation,
+      onChangeUserAgent,
       onContentResize,
       onDeviceListUpdate,
       onExit,
       onRemoveCustomDevice,
       onRemoveDeviceAssociation,
       onResizeViewport,
       onRotateViewport,
       onScreenshot,
       onToggleLeftAlignment,
+      onToggleUserAgentInput,
       onUpdateDeviceDisplayed,
       onUpdateDeviceModal,
     } = this;
 
     if (!viewports.length) {
       return null;
     }
 
@@ -230,22 +258,24 @@ class App extends PureComponent {
           selectedDevice,
           selectedPixelRatio,
           viewport: viewports[0],
           onChangeDevice,
           onChangeNetworkThrottling,
           onChangePixelRatio,
           onChangeReloadCondition,
           onChangeTouchSimulation,
+          onChangeUserAgent,
           onExit,
           onRemoveDeviceAssociation,
           onResizeViewport,
           onRotateViewport,
           onScreenshot,
           onToggleLeftAlignment,
+          onToggleUserAgentInput,
           onUpdateDeviceModal,
         }),
         Viewports({
           screenshot,
           viewports,
           onBrowserMounted,
           onContentResize,
           onRemoveDeviceAssociation,
--- a/devtools/client/responsive.html/components/SettingsMenu.js
+++ b/devtools/client/responsive.html/components/SettingsMenu.js
@@ -15,45 +15,59 @@ const Types = require("../types");
 loader.lazyRequireGetter(this, "showMenu", "devtools/client/shared/components/menu/utils", true);
 
 class SettingsMenu extends PureComponent {
   static get propTypes() {
     return {
       leftAlignmentEnabled: PropTypes.bool.isRequired,
       onChangeReloadCondition: PropTypes.func.isRequired,
       onToggleLeftAlignment: PropTypes.func.isRequired,
+      onToggleUserAgentInput: PropTypes.func.isRequired,
       reloadConditions: PropTypes.shape(Types.reloadConditions).isRequired,
+      showUserAgentInput: PropTypes.bool.isRequired,
     };
   }
 
   constructor(props) {
     super(props);
     this.onToggleSettingMenu = this.onToggleSettingMenu.bind(this);
   }
 
   onToggleSettingMenu(event) {
     const {
       leftAlignmentEnabled,
       onChangeReloadCondition,
       onToggleLeftAlignment,
+      onToggleUserAgentInput,
       reloadConditions,
+      showUserAgentInput,
     } = this.props;
 
     const menuItems = [
       {
         id: "toggleLeftAlignment",
         checked: leftAlignmentEnabled,
         label: getStr("responsive.leftAlignViewport"),
         type: "checkbox",
         click: () => {
           onToggleLeftAlignment();
         },
       },
       "-",
       {
+        id: "toggleUserAgentInput",
+        checked: showUserAgentInput,
+        label: getStr("responsive.showUserAgentInput"),
+        type: "checkbox",
+        click: () => {
+          onToggleUserAgentInput();
+        },
+      },
+      "-",
+      {
         id: "touchSimulation",
         checked: reloadConditions.touchSimulation,
         label: getStr("responsive.reloadConditions.touchSimulation"),
         type: "checkbox",
         click: () => {
           onChangeReloadCondition("touchSimulation", !reloadConditions.touchSimulation);
         },
       },
@@ -83,12 +97,13 @@ class SettingsMenu extends PureComponent
       })
     );
   }
 }
 
 const mapStateToProps = state => {
   return {
     leftAlignmentEnabled: state.ui.leftAlignmentEnabled,
+    showUserAgentInput: state.ui.showUserAgentInput,
   };
 };
 
 module.exports = connect(mapStateToProps)(SettingsMenu);
--- a/devtools/client/responsive.html/components/Toolbar.js
+++ b/devtools/client/responsive.html/components/Toolbar.js
@@ -1,56 +1,89 @@
 /* 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/. */
 
 "use strict";
 
-const { PureComponent, createFactory } = require("devtools/client/shared/vendor/react");
+const {
+  createElement,
+  createFactory,
+  Fragment,
+  PureComponent,
+} = require("devtools/client/shared/vendor/react");
 const dom = require("devtools/client/shared/vendor/react-dom-factories");
 const PropTypes = require("devtools/client/shared/vendor/react-prop-types");
 const { connect } = require("devtools/client/shared/vendor/react-redux");
 
 const DevicePixelRatioMenu = createFactory(require("./DevicePixelRatioMenu"));
 const DeviceSelector = createFactory(require("./DeviceSelector"));
 const NetworkThrottlingMenu = createFactory(require("devtools/client/shared/components/throttling/NetworkThrottlingMenu"));
 const SettingsMenu = createFactory(require("./SettingsMenu"));
 const ViewportDimension = createFactory(require("./ViewportDimension"));
 
+loader.lazyGetter(this, "UserAgentInput", function() {
+  return createFactory(require("./UserAgentInput"));
+});
+
 const { getStr } = require("../utils/l10n");
 const Types = require("../types");
 
 class Toolbar extends PureComponent {
   static get propTypes() {
     return {
       devices: PropTypes.shape(Types.devices).isRequired,
       displayPixelRatio: PropTypes.number.isRequired,
       leftAlignmentEnabled: PropTypes.bool.isRequired,
       networkThrottling: PropTypes.shape(Types.networkThrottling).isRequired,
       onChangeDevice: PropTypes.func.isRequired,
       onChangeNetworkThrottling: PropTypes.func.isRequired,
       onChangePixelRatio: PropTypes.func.isRequired,
       onChangeReloadCondition: PropTypes.func.isRequired,
       onChangeTouchSimulation: PropTypes.func.isRequired,
+      onChangeUserAgent: PropTypes.func.isRequired,
       onExit: PropTypes.func.isRequired,
       onRemoveDeviceAssociation: PropTypes.func.isRequired,
       onResizeViewport: PropTypes.func.isRequired,
       onRotateViewport: PropTypes.func.isRequired,
       onScreenshot: PropTypes.func.isRequired,
       onToggleLeftAlignment: PropTypes.func.isRequired,
+      onToggleUserAgentInput: PropTypes.func.isRequired,
       onUpdateDeviceModal: PropTypes.func.isRequired,
       reloadConditions: PropTypes.shape(Types.reloadConditions).isRequired,
       screenshot: PropTypes.shape(Types.screenshot).isRequired,
       selectedDevice: PropTypes.string.isRequired,
       selectedPixelRatio: PropTypes.number.isRequired,
+      showUserAgentInput: PropTypes.bool.isRequired,
       touchSimulationEnabled: PropTypes.bool.isRequired,
+      userAgent: PropTypes.string.isRequired,
       viewport: PropTypes.shape(Types.viewport).isRequired,
     };
   }
 
+  renderUserAgent() {
+    const {
+      onChangeUserAgent,
+      showUserAgentInput,
+      userAgent,
+    } = this.props;
+
+    if (!showUserAgentInput) {
+      return null;
+    }
+
+    return createElement(Fragment, null,
+      UserAgentInput({
+        onChangeUserAgent,
+        userAgent,
+      }),
+      dom.div({ className: "devtools-separator" }),
+    );
+  }
+
   render() {
     const {
       devices,
       displayPixelRatio,
       leftAlignmentEnabled,
       networkThrottling,
       onChangeDevice,
       onChangeNetworkThrottling,
@@ -58,45 +91,46 @@ class Toolbar extends PureComponent {
       onChangeReloadCondition,
       onChangeTouchSimulation,
       onExit,
       onRemoveDeviceAssociation,
       onResizeViewport,
       onRotateViewport,
       onScreenshot,
       onToggleLeftAlignment,
+      onToggleUserAgentInput,
       onUpdateDeviceModal,
       reloadConditions,
       screenshot,
       selectedDevice,
       selectedPixelRatio,
       touchSimulationEnabled,
       viewport,
     } = this.props;
 
     return (
       dom.header(
-        {
-          id: "toolbar",
-          className: leftAlignmentEnabled ? "left-aligned" : "",
-        },
+        { id: "toolbar" },
         DeviceSelector({
           devices,
           onChangeDevice,
           onResizeViewport,
           onUpdateDeviceModal,
           selectedDevice,
           viewportId: viewport.id,
         }),
         leftAlignmentEnabled ?
           dom.div({ className: "devtools-separator" })
           :
           null,
         dom.div(
-          { id: "toolbar-center-controls" },
+          {
+            id: "toolbar-center-controls",
+            className: leftAlignmentEnabled ? "left-aligned" : "",
+          },
           ViewportDimension({
             onRemoveDeviceAssociation,
             onResizeViewport,
             viewport,
           }),
           dom.button({
             id: "rotate-button",
             className: "devtools-button",
@@ -113,16 +147,17 @@ class Toolbar extends PureComponent {
           }),
           dom.div({ className: "devtools-separator" }),
           NetworkThrottlingMenu({
             networkThrottling,
             onChangeNetworkThrottling,
             useTopLevelWindow: true,
           }),
           dom.div({ className: "devtools-separator" }),
+          this.renderUserAgent(),
           dom.button({
             id: "touch-simulation-button",
             className: "devtools-button" +
                        (touchSimulationEnabled ? " checked" : ""),
             title: (touchSimulationEnabled ?
               getStr("responsive.disableTouch") : getStr("responsive.enableTouch")),
             onClick: () => onChangeTouchSimulation(!touchSimulationEnabled),
           })
@@ -135,16 +170,17 @@ class Toolbar extends PureComponent {
             title: getStr("responsive.screenshot"),
             onClick: onScreenshot,
             disabled: screenshot.isCapturing,
           }),
           SettingsMenu({
             reloadConditions,
             onChangeReloadCondition,
             onToggleLeftAlignment,
+            onToggleUserAgentInput,
           }),
           dom.div({ className: "devtools-separator" }),
           dom.button({
             id: "exit-button",
             className: "devtools-button",
             title: getStr("responsive.exit"),
             onClick: onExit,
           })
@@ -153,13 +189,15 @@ class Toolbar extends PureComponent {
     );
   }
 }
 
 const mapStateToProps = state => {
   return {
     displayPixelRatio: state.ui.displayPixelRatio,
     leftAlignmentEnabled: state.ui.leftAlignmentEnabled,
+    showUserAgentInput: state.ui.showUserAgentInput,
     touchSimulationEnabled: state.ui.touchSimulationEnabled,
+    userAgent: state.ui.userAgent,
   };
 };
 
 module.exports = connect(mapStateToProps)(Toolbar);
new file mode 100644
--- /dev/null
+++ b/devtools/client/responsive.html/components/UserAgentInput.js
@@ -0,0 +1,89 @@
+/* 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/. */
+
+"use strict";
+
+const { PureComponent } = require("devtools/client/shared/vendor/react");
+const dom = require("devtools/client/shared/vendor/react-dom-factories");
+const PropTypes = require("devtools/client/shared/vendor/react-prop-types");
+const { KeyCodes } = require("devtools/client/shared/keycodes");
+
+const { getStr } = require("../utils/l10n");
+
+class UserAgentInput extends PureComponent {
+  static get propTypes() {
+    return {
+      onChangeUserAgent: PropTypes.func.isRequired,
+      userAgent: PropTypes.string.isRequired,
+    };
+  }
+
+  constructor(props) {
+    super(props);
+
+    this.state = {
+      // The user agent input value.
+      value: this.props.userAgent,
+    };
+
+    this.onChange = this.onChange.bind(this);
+    this.onKeyUp = this.onKeyUp.bind(this);
+  }
+
+  componentWillReceiveProps(nextProps) {
+    if (this.props.userAgent !== nextProps.userAgent) {
+      this.setState({ value: nextProps.userAgent });
+    }
+  }
+
+  /**
+   * Input change handler.
+   *
+   * @param  {Event} event
+   */
+  onChange({ target }) {
+    const value = target.value;
+
+    this.setState((prevState) => {
+      return {
+        ...prevState,
+        value,
+      };
+    });
+  }
+
+  /**
+   * Input key up handler.
+   *
+   * @param  {Event} event
+   */
+  onKeyUp({ target, keyCode }) {
+    if (keyCode == KeyCodes.DOM_VK_RETURN) {
+      this.props.onChangeUserAgent(target.value);
+      target.blur();
+    }
+
+    if (keyCode == KeyCodes.DOM_VK_ESCAPE) {
+      target.blur();
+    }
+  }
+
+  render() {
+    return (
+      dom.label({ id: "user-agent-label" },
+        "UA:",
+        dom.input({
+          id: "user-agent-input",
+          onChange: this.onChange,
+          onKeyUp: this.onKeyUp,
+          placeholder: getStr("responsive.customUserAgent"),
+          type: "text",
+          value: this.state.value,
+        })
+      )
+    );
+  }
+}
+
+module.exports = UserAgentInput;
--- a/devtools/client/responsive.html/components/moz.build
+++ b/devtools/client/responsive.html/components/moz.build
@@ -9,11 +9,12 @@ DevToolsModules(
     'Browser.js',
     'DeviceAdder.js',
     'DeviceModal.js',
     'DevicePixelRatioMenu.js',
     'DeviceSelector.js',
     'ResizableViewport.js',
     'SettingsMenu.js',
     'Toolbar.js',
+    'UserAgentInput.js',
     'ViewportDimension.js',
     'Viewports.js',
 )
--- a/devtools/client/responsive.html/index.css
+++ b/devtools/client/responsive.html/index.css
@@ -60,46 +60,63 @@ body,
 
 /**
  * Toolbar
  */
 
 #toolbar {
   background-color: var(--theme-tab-toolbar-background);
   border-bottom: 1px solid var(--theme-splitter-color);
-  display: grid;
-  grid-template-columns: min-content auto min-content;
+  display: flex;
   width: 100%;
   min-height: 29px;
   -moz-user-select: none;
 }
 
-#toolbar.left-aligned {
-  grid-template-columns: min-content min-content min-content auto;
-}
-
 #toolbar-center-controls,
 #toolbar-end-controls {
   display: flex;
   align-items: center;
 }
 
 #toolbar-center-controls {
-  justify-self: center;
+  flex: 1;
+  justify-content: center;
   margin: 1px;
 }
 
+#toolbar-center-controls.left-aligned {
+  justify-content: start;
+}
+
 #toolbar.left-aligned #toolbar-end-controls {
   justify-self: end;
 }
 
 #rotate-button::before {
   background-image: url("./images/rotate-viewport.svg");
 }
 
+#user-agent-label {
+  display: flex;
+  align-items: center;
+  margin-inline-start: 3px;
+  margin-inline-end: 3px;
+  width: 300px;
+}
+
+#user-agent-label:focus-within {
+  flex: 1;
+}
+
+#user-agent-input {
+  margin-inline-start: 3px;;
+  width: 100%;
+}
+
 #touch-simulation-button::before {
   background-image: url("./images/touch-events.svg");
 }
 
 #screenshot-button::before {
   background-image: url("./images/screenshot.svg");
 }
 
--- a/devtools/client/responsive.html/manager.js
+++ b/devtools/client/responsive.html/manager.js
@@ -498,16 +498,19 @@ ResponsiveUI.prototype = {
         this.onChangeNetworkThrottling(event);
         break;
       case "change-pixel-ratio":
         this.onChangePixelRatio(event);
         break;
       case "change-touch-simulation":
         this.onChangeTouchSimulation(event);
         break;
+      case "change-user-agent":
+        this.onChangeUserAgent(event);
+        break;
       case "content-resize":
         this.onContentResize(event);
         break;
       case "exit":
         this.onExit();
         break;
       case "remove-device-association":
         this.onRemoveDeviceAssociation(event);
@@ -548,16 +551,26 @@ ResponsiveUI.prototype = {
                          this.reloadOnChange("touchSimulation");
     if (reloadNeeded) {
       this.getViewportBrowser().reload();
     }
     // Used by tests
     this.emit("touch-simulation-changed");
   },
 
+  async onChangeUserAgent(event) {
+    const { userAgent } = event.data;
+    const reloadNeeded = await this.updateUserAgent(userAgent) &&
+                         this.reloadOnChange("userAgent");
+    if (reloadNeeded) {
+      this.getViewportBrowser().reload();
+    }
+    this.emit("user-agent-changed");
+  },
+
   onContentResize(event) {
     const { width, height } = event.data;
     this.emit("content-resize", {
       width,
       height,
     });
   },
 
--- a/devtools/client/responsive.html/reducers/ui.js
+++ b/devtools/client/responsive.html/reducers/ui.js
@@ -3,39 +3,52 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
 const Services = require("Services");
 
 const {
   CHANGE_DISPLAY_PIXEL_RATIO,
+  CHANGE_USER_AGENT,
   TOGGLE_LEFT_ALIGNMENT,
   TOGGLE_TOUCH_SIMULATION,
+  TOGGLE_USER_AGENT_INPUT,
 } = require("../actions/index");
 
 const LEFT_ALIGNMENT_ENABLED = "devtools.responsive.leftAlignViewport.enabled";
+const SHOW_USER_AGENT_INPUT = "devtools.responsive.showUserAgentInput";
 
 const INITIAL_UI = {
   // The pixel ratio of the display.
   displayPixelRatio: 0,
   // Whether or not the viewports are left aligned.
   leftAlignmentEnabled: Services.prefs.getBoolPref(LEFT_ALIGNMENT_ENABLED),
+  // Whether or not to show the user agent input in the toolbar.
+  showUserAgentInput: Services.prefs.getBoolPref(SHOW_USER_AGENT_INPUT),
   // Whether or not touch simulation is enabled.
   touchSimulationEnabled: false,
+  // The user agent of the viewport.
+  userAgent: "",
 };
 
 const reducers = {
 
   [CHANGE_DISPLAY_PIXEL_RATIO](ui, { displayPixelRatio }) {
     return Object.assign({}, ui, {
       displayPixelRatio,
     });
   },
 
+  [CHANGE_USER_AGENT](ui, { userAgent }) {
+    return Object.assign({}, ui, {
+      userAgent,
+    });
+  },
+
   [TOGGLE_LEFT_ALIGNMENT](ui, { enabled }) {
     const leftAlignmentEnabled = enabled !== undefined ?
       enabled : !ui.leftAlignmentEnabled;
 
     Services.prefs.setBoolPref(LEFT_ALIGNMENT_ENABLED, leftAlignmentEnabled);
 
     return Object.assign({}, ui, {
       leftAlignmentEnabled,
@@ -43,16 +56,27 @@ const reducers = {
   },
 
   [TOGGLE_TOUCH_SIMULATION](ui, { enabled }) {
     return Object.assign({}, ui, {
       touchSimulationEnabled: enabled,
     });
   },
 
+  [TOGGLE_USER_AGENT_INPUT](ui, { enabled }) {
+    const showUserAgentInput = enabled !== undefined ?
+      enabled : !ui.showUserAgentInput;
+
+    Services.prefs.setBoolPref(SHOW_USER_AGENT_INPUT, showUserAgentInput);
+
+    return Object.assign({}, ui, {
+      showUserAgentInput,
+    });
+  },
+
 };
 
 module.exports = function(ui = INITIAL_UI, action) {
   const reducer = reducers[action.type];
   if (!reducer) {
     return ui;
   }
   return reducer(ui, action);
--- a/devtools/client/responsive.html/test/browser/browser.ini
+++ b/devtools/client/responsive.html/test/browser/browser.ini
@@ -57,10 +57,11 @@ skip-if = true # Bug 1413765
 [browser_telemetry_activate_rdm.js]
 [browser_toolbox_computed_view.js]
 [browser_toolbox_rule_view.js]
 [browser_toolbox_rule_view_reload.js]
 [browser_toolbox_swap_browsers.js]
 [browser_toolbox_swap_inspector.js]
 [browser_touch_device.js]
 [browser_touch_simulation.js]
+[browser_user_agent_input.js]
 [browser_viewport_basics.js]
 [browser_window_close.js]
--- a/devtools/client/responsive.html/test/browser/browser_device_change.js
+++ b/devtools/client/responsive.html/test/browser/browser_device_change.js
@@ -1,20 +1,17 @@
 /* Any copyright is dedicated to the Public Domain.
 http://creativecommons.org/publicdomain/zero/1.0/ */
 
 "use strict";
 
 // Tests changing viewport device (need HTTP load for proper UA testing)
+
 const TEST_URL = `${URL_ROOT}doc_page_state.html`;
-
 const DEFAULT_DPPX = window.devicePixelRatio;
-const DEFAULT_UA = Cc["@mozilla.org/network/protocol;1?name=http"]
-  .getService(Ci.nsIHttpProtocolHandler)
-  .userAgent;
 
 const Types = require("devtools/client/responsive.html/types");
 
 const testDevice = {
   "name": "Fake Phone RDM Test",
   "width": 320,
   "height": 570,
   "pixelRatio": 5.5,
new file mode 100644
--- /dev/null
+++ b/devtools/client/responsive.html/test/browser/browser_user_agent_input.js
@@ -0,0 +1,28 @@
+/* Any copyright is dedicated to the Public Domain.
+http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const TEST_URL = "data:text/html;charset=utf-8,";
+const NEW_USER_AGENT = "Mozilla/5.0 (Mobile; rv:39.0) Gecko/39.0 Firefox/39.0";
+
+addRDMTask(TEST_URL, async function({ ui }) {
+  const { store } = ui.toolWindow;
+
+  reloadOnUAChange(true);
+
+  await waitUntilState(store, state => state.viewports.length == 1);
+
+  info("Check the default state of the user agent input");
+  await testUserAgent(ui, DEFAULT_UA);
+
+  info(`Change the user agent input to ${NEW_USER_AGENT}`);
+  await changeUserAgentInput(ui, NEW_USER_AGENT);
+  await testUserAgent(ui, NEW_USER_AGENT);
+
+  info("Reset the user agent input back to the default UA");
+  await changeUserAgentInput(ui, "");
+  await testUserAgent(ui, DEFAULT_UA);
+
+  reloadOnUAChange(false);
+});
--- a/devtools/client/responsive.html/test/browser/head.js
+++ b/devtools/client/responsive.html/test/browser/head.js
@@ -24,46 +24,52 @@ Services.scriptloader.loadSubScript(
 Services.scriptloader.loadSubScript(
   "chrome://mochitests/content/browser/devtools/client/inspector/test/shared-head.js",
   this);
 
 const { _loadPreferredDevices } = require("devtools/client/responsive.html/actions/devices");
 const { getStr } = require("devtools/client/responsive.html/utils/l10n");
 const { getTopLevelWindow } = require("devtools/client/responsive.html/utils/window");
 const { addDevice, removeDevice, removeLocalDevices } = require("devtools/client/shared/devices");
+const { KeyCodes } = require("devtools/client/shared/keycodes");
 const asyncStorage = require("devtools/shared/async-storage");
 
 loader.lazyRequireGetter(this, "ResponsiveUIManager", "devtools/client/responsive.html/manager", true);
 
 const E10S_MULTI_ENABLED = Services.prefs.getIntPref("dom.ipc.processCount") > 1;
 const TEST_URI_ROOT = "http://example.com/browser/devtools/client/responsive.html/test/browser/";
 const RELOAD_CONDITION_PREF_PREFIX = "devtools.responsive.reloadConditions.";
+const DEFAULT_UA = Cc["@mozilla.org/network/protocol;1?name=http"]
+  .getService(Ci.nsIHttpProtocolHandler)
+  .userAgent;
 
 SimpleTest.requestCompleteLog();
 SimpleTest.waitForExplicitFinish();
 
 // Toggling the RDM UI involves several docShell swap operations, which are somewhat slow
 // on debug builds. Usually we are just barely over the limit, so a blanket factor of 2
 // should be enough.
 requestLongerTimeout(2);
 
 Services.prefs.setCharPref("devtools.devices.url", TEST_URI_ROOT + "devices.json");
 // The appearance of this notification causes intermittent behavior in some tests that
 // send mouse events, since it causes the content to shift when it appears.
 Services.prefs.setBoolPref("devtools.responsive.reloadNotification.enabled", false);
 // Don't show the setting onboarding tooltip in the test suites.
 Services.prefs.setBoolPref("devtools.responsive.show-setting-tooltip", false);
+Services.prefs.setBoolPref("devtools.responsive.showUserAgentInput", true);
 
 registerCleanupFunction(async () => {
   Services.prefs.clearUserPref("devtools.devices.url");
   Services.prefs.clearUserPref("devtools.responsive.reloadNotification.enabled");
   Services.prefs.clearUserPref("devtools.responsive.html.displayedDeviceList");
   Services.prefs.clearUserPref("devtools.responsive.reloadConditions.touchSimulation");
   Services.prefs.clearUserPref("devtools.responsive.reloadConditions.userAgent");
   Services.prefs.clearUserPref("devtools.responsive.show-setting-tooltip");
+  Services.prefs.clearUserPref("devtools.responsive.showUserAgentInput");
   await asyncStorage.removeItem("devtools.devices.url_cache");
   await removeLocalDevices();
 });
 
 /**
  * Open responsive design mode for the given tab.
  */
 var openRDM = async function(tab) {
@@ -389,27 +395,52 @@ async function toggleTouchSimulation(ui)
   const { document } = ui.toolWindow;
   const touchButton = document.getElementById("touch-simulation-button");
   const changed = once(ui, "touch-simulation-changed");
   const loaded = waitForViewportLoad(ui);
   touchButton.click();
   await Promise.all([ changed, loaded ]);
 }
 
-function testUserAgent(ui, expected) {
-  testUserAgentFromBrowser(ui.getViewportBrowser(), expected);
+async function testUserAgent(ui, expected) {
+  const { document } = ui.toolWindow;
+  const userAgentInput = document.getElementById("user-agent-input");
+
+  if (expected === DEFAULT_UA) {
+    is(userAgentInput.value, "", "UA input should be empty");
+  } else {
+    is(userAgentInput.value, expected, `UA input should be set to ${expected}`);
+  }
+
+  await testUserAgentFromBrowser(ui.getViewportBrowser(), expected);
 }
 
 async function testUserAgentFromBrowser(browser, expected) {
   const ua = await ContentTask.spawn(browser, {}, async function() {
     return content.navigator.userAgent;
   });
   is(ua, expected, `UA should be set to ${expected}`);
 }
 
+async function changeUserAgentInput(ui, value) {
+  const { Simulate } =
+    ui.toolWindow.require("devtools/client/shared/vendor/react-dom-test-utils");
+  const { document, store } = ui.toolWindow;
+
+  const userAgentInput = document.getElementById("user-agent-input");
+  userAgentInput.value = value;
+  Simulate.change(userAgentInput);
+
+  const userAgentChanged = waitUntilState(store, state => state.ui.userAgent === value);
+  const changed = once(ui, "user-agent-changed");
+  const loaded = waitForViewportLoad(ui);
+  Simulate.keyUp(userAgentInput, { keyCode: KeyCodes.DOM_VK_RETURN });
+  await Promise.all([ changed, loaded, userAgentChanged ]);
+}
+
 /**
  * Assuming the device modal is open and the device adder form is shown, this helper
  * function adds `device` via the form, saves it, and waits for it to appear in the store.
  */
 function addDeviceInModal(ui, device) {
   const { Simulate } =
     ui.toolWindow.require("devtools/client/shared/vendor/react-dom-test-utils");
   const { document, store } = ui.toolWindow;
new file mode 100644
--- /dev/null
+++ b/devtools/client/responsive.html/test/unit/test_change_user_agent.js
@@ -0,0 +1,22 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test changing the user agent.
+
+const { changeUserAgent } = require("devtools/client/responsive.html/actions/ui");
+
+const NEW_USER_AGENT = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) " +
+  "AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.106 Safari/537.36";
+
+add_task(async function() {
+  const store = Store();
+  const { getState, dispatch } = store;
+
+  equal(getState().ui.userAgent, "", "User agent is empty by default.");
+
+  dispatch(changeUserAgent(NEW_USER_AGENT));
+  equal(getState().ui.userAgent, NEW_USER_AGENT,
+    `User Agent changed to ${NEW_USER_AGENT}`);
+});
--- a/devtools/client/responsive.html/test/unit/xpcshell.ini
+++ b/devtools/client/responsive.html/test/unit/xpcshell.ini
@@ -5,12 +5,13 @@ firefox-appdir = browser
 
 [test_add_device.js]
 [test_add_device_type.js]
 [test_add_viewport.js]
 [test_change_device.js]
 [test_change_display_pixel_ratio.js]
 [test_change_network_throttling.js]
 [test_change_pixel_ratio.js]
+[test_change_user_agent.js]
 [test_resize_viewport.js]
 [test_rotate_viewport.js]
 [test_update_device_displayed.js]
 [test_update_touch_simulation_enabled.js]
--- a/docshell/base/nsDocShell.cpp
+++ b/docshell/base/nsDocShell.cpp
@@ -4141,17 +4141,17 @@ nsDocShell::GoForward()
 NS_IMETHODIMP
 nsDocShell::GotoIndex(int32_t aIndex)
 {
   if (!IsNavigationAllowed()) {
     return NS_OK; // JS may not handle returning of an error code
   }
   RefPtr<ChildSHistory> rootSH = GetRootSessionHistory();
   NS_ENSURE_TRUE(rootSH, NS_ERROR_FAILURE);
-  return rootSH->LegacySHistoryImpl()->GotoIndex(aIndex);
+  return rootSH->LegacySHistory()->GotoIndex(aIndex);
 }
 
 NS_IMETHODIMP
 nsDocShell::LoadURI(const nsAString& aURI,
                     uint32_t aLoadFlags,
                     nsIURI* aReferringURI,
                     nsIInputStream* aPostStream,
                     nsIInputStream* aHeaderStream,
@@ -8642,18 +8642,17 @@ nsDocShell::CreateContentViewer(const ns
       errorOnLocationChangeNeeded = OnNewURI(
         failedURI, failedChannel, triggeringPrincipal,
         nullptr, mLoadType, false, false, false);
     }
 
     // Be sure to have a correct mLSHE, it may have been cleared by
     // EndPageLoad. See bug 302115.
     if (mSessionHistory && !mLSHE) {
-      int32_t idx;
-      mSessionHistory->LegacySHistory()->GetRequestedIndex(&idx);
+      int32_t idx = mSessionHistory->LegacySHistory()->GetRequestedIndex();
       if (idx == -1) {
         idx = mSessionHistory->Index();
       }
       mSessionHistory->LegacySHistory()->
         GetEntryAtIndex(idx, getter_AddRefs(mLSHE));
     }
 
     mLoadType = LOAD_ERROR_PAGE;
@@ -11562,18 +11561,17 @@ nsDocShell::OnNewURI(nsIURI* aURI, nsICh
        */
       (void)AddToSessionHistory(aURI, aChannel, aTriggeringPrincipal,
                                 aPrincipalToInherit, aCloneSHChildren,
                                 getter_AddRefs(mLSHE));
     }
   } else if (mSessionHistory && mLSHE && mURIResultedInDocument) {
     // Even if we don't add anything to SHistory, ensure the current index
     // points to the same SHEntry as our mLSHE.
-    int32_t index = 0;
-    mSessionHistory->LegacySHistory()->GetRequestedIndex(&index);
+    int32_t index = mSessionHistory->LegacySHistory()->GetRequestedIndex();
     if (index == -1) {
       index = mSessionHistory->Index();
     }
     nsCOMPtr<nsISHEntry> currentSH;
     mSessionHistory->LegacySHistory()->GetEntryAtIndex(
       index, getter_AddRefs(currentSH));
     if (currentSH != mLSHE) {
       mSessionHistory->LegacySHistory()->ReplaceEntry(index, mLSHE);
@@ -11921,19 +11919,18 @@ nsDocShell::AddState(JS::Handle<JS::Valu
   if (!aReplace) {
     int32_t curIndex = rootSH->Index();
     if (curIndex > -1) {
       rootSH->LegacySHistory()->EvictOutOfRangeContentViewers(curIndex);
     }
   } else {
     nsCOMPtr<nsISHEntry> rootSHEntry = nsSHistory::GetRootSHEntry(newSHEntry);
 
-    int32_t index = -1;
-    rv = rootSH->LegacySHistory()->GetIndexOfEntry(rootSHEntry, &index);
-    if (NS_SUCCEEDED(rv) && index > -1) {
+    int32_t index = rootSH->LegacySHistory()->GetIndexOfEntry(rootSHEntry);
+    if (index > -1) {
       rootSH->LegacySHistory()->ReplaceEntry(index, rootSHEntry);
     }
   }
 
   // Step 6: If the document's URI changed, update document's URI and update
   // global history.
   //
   // We need to call FireOnLocationChange so that the browser's address bar
@@ -12233,18 +12230,17 @@ nsDocShell::AddToSessionHistory(nsIURI* 
     }
 
     // This is the root docshell
     bool addToSHistory = !LOAD_TYPE_HAS_FLAGS(mLoadType, LOAD_FLAGS_REPLACE_HISTORY);
     if (!addToSHistory) {
       // Replace current entry in session history; If the requested index is
       // valid, it indicates the loading was triggered by a history load, and
       // we should replace the entry at requested index instead.
-      int32_t index = 0;
-      mSessionHistory->LegacySHistory()->GetRequestedIndex(&index);
+      int32_t index = mSessionHistory->LegacySHistory()->GetRequestedIndex();
       if (index == -1) {
         index = mSessionHistory->Index();
       }
 
       // Replace the current entry with the new entry
       if (index >= 0) {
         rv = mSessionHistory->LegacySHistory()->ReplaceEntry(index, entry);
       } else {
--- a/docshell/shistory/ChildSHistory.cpp
+++ b/docshell/shistory/ChildSHistory.cpp
@@ -27,19 +27,17 @@ ChildSHistory::ChildSHistory(nsDocShell*
 
 ChildSHistory::~ChildSHistory()
 {
 }
 
 int32_t
 ChildSHistory::Count()
 {
-  int32_t count;
-  mHistory->GetCount(&count);
-  return count;
+  return mHistory->GetCount();
 }
 
 int32_t
 ChildSHistory::Index()
 {
   int32_t index;
   mHistory->GetIndex(&index);
   return index;
@@ -81,22 +79,16 @@ ChildSHistory::EvictLocalContentViewers(
 }
 
 nsISHistory*
 ChildSHistory::LegacySHistory()
 {
   return mHistory;
 }
 
-nsSHistory*
-ChildSHistory::LegacySHistoryImpl()
-{
-  return mHistory;
-}
-
 ParentSHistory*
 ChildSHistory::GetParentIfSameProcess()
 {
   if (XRE_IsContentProcess()) {
     return nullptr;
   }
 
   MOZ_CRASH("Unimplemented!");
--- a/docshell/shistory/ChildSHistory.h
+++ b/docshell/shistory/ChildSHistory.h
@@ -66,17 +66,16 @@ public:
   void Go(int32_t aOffset, ErrorResult& aRv);
 
   /**
    * Evicts all content viewers within the current process.
    */
   void EvictLocalContentViewers();
 
   nsISHistory* LegacySHistory();
-  nsSHistory* LegacySHistoryImpl();
 
   ParentSHistory* GetParentIfSameProcess();
 
 private:
   virtual ~ChildSHistory();
 
   RefPtr<nsDocShell> mDocShell;
   RefPtr<nsSHistory> mHistory;
--- a/docshell/shistory/nsISHistory.idl
+++ b/docshell/shistory/nsISHistory.idl
@@ -23,54 +23,39 @@ interface nsIURI;
  * object creates an instance of session history for each open window.
  * A handle to the session history object can be obtained from
  * nsIWebNavigation. In a non-embedded situation, the  owner of the
  * session history component must create a instance of it and set
  * it in the nsIWebNavigation object.
  * This interface is accessible from javascript.
  */
 
-[scriptable, uuid(7b807041-e60a-4384-935f-af3061d8b815)]
+[builtinclass, scriptable, uuid(7b807041-e60a-4384-935f-af3061d8b815)]
 interface nsISHistory: nsISupports
 {
   /**
-   * The size of the window of SHEntries which can have alive viewers in the
-   * bfcache around the currently active SHEntry.
-   *
-   * We try to keep viewers for SHEntries between index - VIEWER_WINDOW and
-   * index + VIEWER_WINDOW alive.
-   */
-  const long VIEWER_WINDOW = 3;
-
-  /**
    * A readonly property of the interface that returns
    * the number of toplevel documents currently available
    * in session history.
    */
-  readonly attribute long count;
+  [infallible] readonly attribute long count;
 
   /**
-   * The index of the current document in session history.
+   * The index of the current document in session history. Not infallible
+   * because setting can fail if the assigned value is out of range.
    */
   attribute long index;
 
   /**
    * A readonly property of the interface that returns
    * the index of the last document that started to load and
    * didn't finished yet. When document finishes the loading
    * value -1 is returned.
    */
-  readonly attribute long requestedIndex;
-
-  /**
-   * A read/write property of the interface, used to Get/Set
-   * the maximum number of toplevel documents, session history
-   * can hold for each instance.
-   */
-  attribute long maxLength;
+  [infallible] readonly attribute long requestedIndex;
 
   /**
    * Get the history entry at a given index. Returns non-null on success.
    *
    * @param index             The index value whose entry is requested.
    *                          The oldest entry is located at index == 0.
    * @return                  The found entry; never null.
    */
@@ -121,41 +106,49 @@ interface nsISHistory: nsISupports
    * @see nsISHistoryListener
    * @see nsSupportsWeakReference
    */
   void removeSHistoryListener(in nsISHistoryListener aListener);
 
   void reloadCurrentEntry();
 
   /**
+   * Load the entry at the particular index.
+   */
+  [noscript]
+  void gotoIndex(in long aIndex);
+
+  /**
    * Called to obtain the index to a given history entry.
    *
    * @param aEntry            The entry to obtain the index of.
    *
    * @return                  <code>NS_OK</code> index for the history entry
    *                          is obtained successfully.
    *                          <code>NS_ERROR_FAILURE</code> Error in obtaining
    *                          index for the given history entry.
    */
+  [noscript, notxpcom]
   long getIndexOfEntry(in nsISHEntry aEntry);
 
   /**
    * Add a new Entry to the History List.
    *
    * @param aEntry            The entry to add.
    * @param aPersist          If true this specifies that the entry should
    *                          persist in the list. If false, this means that
    *                          when new entries are added this element will not
    *                          appear in the session history list.
    */
   void addEntry(in nsISHEntry aEntry, in boolean aPersist);
 
   /**
    * Sets the toplevel docshell object to which this SHistory object belongs to.
    */
+  [noscript, notxpcom]
   void setRootDocShell(in nsIDocShell rootDocShell);
 
   /**
    * Update the index maintained by sessionHistory
    */
   void updateIndex();
 
   /**
@@ -205,21 +198,23 @@ interface nsISHistory: nsISupports
    * Evict all the content viewers in this session history
    */
   void evictAllContentViewers();
 
   /**
    * Add a BFCache entry to expiration tracker so it gets evicted on
    * expiration.
    */
+  [noscript, notxpcom]
   void addToExpirationTracker(in nsIBFCacheEntry aEntry);
 
   /**
    * Remove a BFCache entry from expiration tracker.
    */
+  [noscript, notxpcom]
   void removeFromExpirationTracker(in nsIBFCacheEntry aEntry);
 
   /**
    * Remove dynamic entries found at given index.
    *
    * @param aIndex           Index to remove dynamic entries from. It will be
    *                         passed to RemoveEntries as aStartIndex.
    * @param aEntry (optional)  The entry to start looking in for dynamic
--- a/docshell/shistory/nsSHistory.cpp
+++ b/docshell/shistory/nsSHistory.cpp
@@ -213,18 +213,17 @@ nsSHistory::EvictContentViewerForEntry(n
     // Drop the presentation state before destroying the viewer, so that
     // document teardown is able to correctly persist the state.
     ownerEntry->SetContentViewer(nullptr);
     ownerEntry->SyncPresentationState();
     viewer->Destroy();
   }
 
   // When dropping bfcache, we have to remove associated dynamic entries as well.
-  int32_t index = -1;
-  GetIndexOfEntry(aEntry, &index);
+  int32_t index = GetIndexOfEntry(aEntry);
   if (index != -1) {
     RemoveDynEntries(index, aEntry);
   }
 }
 
 nsSHistory::nsSHistory()
   : mIndex(-1)
   , mRequestedIndex(-1)
@@ -648,17 +647,17 @@ nsSHistory::AddEntry(nsISHEntry* aSHEntr
 
   return NS_OK;
 }
 
 /* Get size of the history list */
 NS_IMETHODIMP
 nsSHistory::GetCount(int32_t* aResult)
 {
-  NS_ENSURE_ARG_POINTER(aResult);
+  MOZ_ASSERT(aResult, "null out param?");
   *aResult = Length();
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsSHistory::GetIndex(int32_t* aResult)
 {
   MOZ_ASSERT(aResult, "null out param?");
@@ -695,32 +694,26 @@ nsSHistory::GetEntryAtIndex(int32_t aInd
     return NS_ERROR_FAILURE;
   }
 
   *aResult = mEntries[aIndex];
   NS_ADDREF(*aResult);
   return NS_OK;
 }
 
-/* Get the index of a given entry */
-NS_IMETHODIMP
-nsSHistory::GetIndexOfEntry(nsISHEntry* aSHEntry, int32_t* aResult)
+NS_IMETHODIMP_(int32_t)
+nsSHistory::GetIndexOfEntry(nsISHEntry* aSHEntry)
 {
-  NS_ENSURE_ARG(aSHEntry);
-  NS_ENSURE_ARG_POINTER(aResult);
-  *aResult = -1;
-
   for (int32_t i = 0; i < Length(); i++) {
     if (aSHEntry == mEntries[i]) {
-      *aResult = i;
-      return NS_OK;
+      return i;
     }
   }
 
-  return NS_ERROR_FAILURE;
+  return -1;
 }
 
 #ifdef DEBUG
 nsresult
 nsSHistory::PrintHistory()
 {
   for (int32_t i = 0; i < Length(); i++) {
     nsCOMPtr<nsISHEntry> entry = mEntries[i];
@@ -745,46 +738,22 @@ nsSHistory::PrintHistory()
     printf("\t\t layout History Data = %x\n", layoutHistoryState.get());
 #endif
   }
 
   return NS_OK;
 }
 #endif
 
-/* Get the max size of the history list */
-NS_IMETHODIMP
-nsSHistory::GetMaxLength(int32_t* aResult)
-{
-  NS_ENSURE_ARG_POINTER(aResult);
-  *aResult = gHistoryMaxSize;
-  return NS_OK;
-}
-
-/* Set the max size of the history list */
-NS_IMETHODIMP
-nsSHistory::SetMaxLength(int32_t aMaxSize)
-{
-  if (aMaxSize < 0) {
-    return NS_ERROR_ILLEGAL_VALUE;
-  }
-
-  gHistoryMaxSize = aMaxSize;
-  if (Length() > aMaxSize) {
-    PurgeHistory(Length() - aMaxSize);
-  }
-  return NS_OK;
-}
-
 void
 nsSHistory::WindowIndices(int32_t aIndex, int32_t* aOutStartIndex,
                           int32_t* aOutEndIndex)
 {
-  *aOutStartIndex = std::max(0, aIndex - nsISHistory::VIEWER_WINDOW);
-  *aOutEndIndex = std::min(Length() - 1, aIndex + nsISHistory::VIEWER_WINDOW);
+  *aOutStartIndex = std::max(0, aIndex - nsSHistory::VIEWER_WINDOW);
+  *aOutEndIndex = std::min(Length() - 1, aIndex + nsSHistory::VIEWER_WINDOW);
 }
 
 NS_IMETHODIMP
 nsSHistory::PurgeHistory(int32_t aNumEntries)
 {
   if (Length() <= 0 || aNumEntries <= 0) {
     return NS_ERROR_FAILURE;
   }
@@ -1185,39 +1154,39 @@ nsSHistory::EvictExpiredContentViewerFor
 
   if (shEntry) {
     EvictContentViewerForEntry(shEntry);
   }
 
   return NS_OK;
 }
 
-NS_IMETHODIMP
+NS_IMETHODIMP_(void)
 nsSHistory::AddToExpirationTracker(nsIBFCacheEntry* aBFEntry)
 {
   RefPtr<nsSHEntryShared> entry = static_cast<nsSHEntryShared*>(aBFEntry);
   if (!mHistoryTracker || !entry) {
-    return NS_ERROR_FAILURE;
+    return;
   }
 
   mHistoryTracker->AddObject(entry);
-  return NS_OK;
+  return;
 }
 
-NS_IMETHODIMP
+NS_IMETHODIMP_(void)
 nsSHistory::RemoveFromExpirationTracker(nsIBFCacheEntry* aBFEntry)
 {
   RefPtr<nsSHEntryShared> entry = static_cast<nsSHEntryShared*>(aBFEntry);
   MOZ_ASSERT(mHistoryTracker && !mHistoryTracker->IsEmpty());
   if (!mHistoryTracker || !entry) {
-    return NS_ERROR_FAILURE;
+    return;
   }
 
   mHistoryTracker->RemoveObject(entry);
-  return NS_OK;
+  return;
 }
 
 // Evicts all content viewers in all history objects.  This is very
 // inefficient, because it requires a linear search through all SHistory
 // objects for each viewer to be evicted.  However, this method is called
 // infrequently -- only when the disk or memory cache is cleared.
 
 // static
@@ -1445,17 +1414,17 @@ nsSHistory::GetCurrentURI(nsIURI** aResu
   rv = GetEntryAtIndex(mIndex, getter_AddRefs(currentEntry));
   if (NS_FAILED(rv) && !currentEntry) {
     return rv;
   }
   rv = currentEntry->GetURI(aResultURI);
   return rv;
 }
 
-nsresult
+NS_IMETHODIMP
 nsSHistory::GotoIndex(int32_t aIndex)
 {
   return LoadEntry(aIndex, LOAD_HISTORY, HIST_CMD_GOTOINDEX);
 }
 
 nsresult
 nsSHistory::LoadNextPossibleEntry(int32_t aNewIndex, long aLoadType,
                                   uint32_t aHistCmd)
@@ -1648,27 +1617,27 @@ nsSHistory::InitiateLoad(nsISHEntry* aFr
   nsCOMPtr<nsIURI> nextURI;
   aFrameEntry->GetURI(getter_AddRefs(nextURI));
   // Time   to initiate a document load
   return aFrameDS->LoadURI(nextURI, loadInfo,
                            nsIWebNavigation::LOAD_FLAGS_NONE, false);
 
 }
 
-NS_IMETHODIMP
+NS_IMETHODIMP_(void)
 nsSHistory::SetRootDocShell(nsIDocShell* aDocShell)
 {
   mRootDocShell = aDocShell;
 
   // Init mHistoryTracker on setting mRootDocShell so we can bind its event
   // target to the tabGroup.
   if (mRootDocShell) {
     nsCOMPtr<nsPIDOMWindowOuter> win = mRootDocShell->GetWindow();
     if (!win) {
-      return NS_ERROR_UNEXPECTED;
+      return;
     }
 
     // Seamonkey moves shistory between <xul:browser>s when restoring a tab.
     // Let's try not to break our friend too badly...
     if (mHistoryTracker) {
       NS_WARNING("Change the root docshell of a shistory is unsafe and "
                  "potentially problematic.");
       mHistoryTracker->AgeAllGenerations();
@@ -1677,11 +1646,9 @@ nsSHistory::SetRootDocShell(nsIDocShell*
     nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(win);
 
     mHistoryTracker = mozilla::MakeUnique<HistoryTracker>(
       this,
       mozilla::Preferences::GetUint(CONTENT_VIEWER_TIMEOUT_SECONDS,
                                     CONTENT_VIEWER_TIMEOUT_SECONDS_DEFAULT),
       global->EventTargetFor(mozilla::TaskCategory::Other));
   }
-
-  return NS_OK;
 }
--- a/docshell/shistory/nsSHistory.h
+++ b/docshell/shistory/nsSHistory.h
@@ -63,17 +63,16 @@ public:
     nsISHEntry* destTreeParent;  // constant; the node under destTreeRoot
                                  // whose children will correspond to aEntry
   };
 
   nsSHistory();
   NS_DECL_ISUPPORTS
   NS_DECL_NSISHISTORY
 
-  nsresult GotoIndex(int32_t aIndex);
   nsresult Reload(uint32_t aReloadFlags);
   nsresult GetCurrentURI(nsIURI** aResultURI);
 
   // One time initialization method called upon docshell module construction
   static nsresult Startup();
   static void Shutdown();
   static void UpdatePrefs();
 
@@ -126,30 +125,37 @@ public:
                                      nsDocShell* aRootShell,
                                      WalkHistoryEntriesFunc aCallback,
                                      void* aData);
 
 private:
   virtual ~nsSHistory();
   friend class nsSHistoryObserver;
 
+  // The size of the window of SHEntries which can have alive viewers in the
+  // bfcache around the currently active SHEntry.
+  //
+  // We try to keep viewers for SHEntries between index - VIEWER_WINDOW and
+  // index + VIEWER_WINDOW alive.
+  static const int32_t VIEWER_WINDOW = 3;
+
   nsresult LoadDifferingEntries(nsISHEntry* aPrevEntry, nsISHEntry* aNextEntry,
                                 nsIDocShell* aRootDocShell, long aLoadType,
                                 bool& aDifferenceFound);
   nsresult InitiateLoad(nsISHEntry* aFrameEntry, nsIDocShell* aFrameDS,
                         long aLoadType);
 
   nsresult LoadEntry(int32_t aIndex, long aLoadType, uint32_t aHistCmd);
 
 #ifdef DEBUG
   nsresult PrintHistory();
 #endif
 
   // Find the history entry for a given bfcache entry. It only looks up between
-  // the range where alive viewers may exist (i.e nsISHistory::VIEWER_WINDOW).
+  // the range where alive viewers may exist (i.e nsSHistory::VIEWER_WINDOW).
   nsresult FindEntryForBFCache(nsIBFCacheEntry* aBFEntry,
                                nsISHEntry** aResult,
                                int32_t* aResultIndex);
 
   // Evict content viewers in this window which don't lie in the "safe" range
   // around aIndex.
   void EvictOutOfRangeWindowContentViewers(int32_t aIndex);
   void EvictContentViewerForEntry(nsISHEntry* aEntry);
--- a/dom/xul/nsXULElement.cpp
+++ b/dom/xul/nsXULElement.cpp
@@ -615,37 +615,16 @@ nsXULElement::UpdateEditableState(bool a
     // Don't call through to Element here because the things
     // it does don't work for cases when we're an editable control.
     nsIContent *parent = GetParent();
 
     SetEditableFlag(parent && parent->HasFlag(NODE_IS_EDITABLE));
     UpdateState(aNotify);
 }
 
-#ifdef DEBUG
-/**
- * Returns true if the user-agent style sheet rules for this XUL element are
- * in minimal-xul.css instead of xul.css.
- */
-static inline bool XULElementsRulesInMinimalXULSheet(nsAtom* aTag)
-{
-  return // scrollbar parts:
-         aTag == nsGkAtoms::scrollbar ||
-         aTag == nsGkAtoms::scrollbarbutton ||
-         aTag == nsGkAtoms::scrollcorner ||
-         aTag == nsGkAtoms::slider ||
-         aTag == nsGkAtoms::thumb ||
-         // other
-         aTag == nsGkAtoms::datetimebox ||
-         aTag == nsGkAtoms::resizer ||
-         aTag == nsGkAtoms::label ||
-         aTag == nsGkAtoms::videocontrols;
-}
-#endif
-
 class XULInContentErrorReporter : public Runnable
 {
 public:
   explicit XULInContentErrorReporter(nsIDocument* aDocument)
     : mozilla::Runnable("XULInContentErrorReporter")
     , mDocument(aDocument)
   {
   }
@@ -692,21 +671,33 @@ nsXULElement::BindToTree(nsIDocument* aD
   nsIDocument* doc = GetComposedDoc();
 #ifdef DEBUG
   if (doc && !doc->AllowXULXBL() && !doc->IsUnstyledDocument()) {
     // To save CPU cycles and memory, non-XUL documents only load the user
     // agent style sheet rules for a minimal set of XUL elements such as
     // 'scrollbar' that may be created implicitly for their content (those
     // rules being in minimal-xul.css).
     //
-    // This assertion makes sure no other XUL element than the ones in the
-    // minimal XUL sheet is used in the bindings.
-    if (!XULElementsRulesInMinimalXULSheet(NodeInfo()->NameAtom())) {
-      NS_ERROR("Unexpected XUL element in non-XUL doc");
-    }
+    // This assertion makes sure no other XUL element is used in a non-XUL
+    // document.
+    nsAtom* tag = NodeInfo()->NameAtom();
+    MOZ_ASSERT(
+      // scrollbar parts
+      tag == nsGkAtoms::scrollbar ||
+      tag == nsGkAtoms::scrollbarbutton ||
+      tag == nsGkAtoms::scrollcorner ||
+      tag == nsGkAtoms::slider ||
+      tag == nsGkAtoms::thumb ||
+      // other
+      tag == nsGkAtoms::datetimebox ||
+      tag == nsGkAtoms::resizer ||
+      tag == nsGkAtoms::label ||
+      tag == nsGkAtoms::videocontrols,
+      "Unexpected XUL element in non-XUL doc"
+    );
   }
 #endif
 
   if (doc && NeedTooltipSupport(*this)) {
       AddTooltipSupport();
   }
 
   return rv;
--- a/editor/composer/moz.build
+++ b/editor/composer/moz.build
@@ -11,17 +11,16 @@ MOCHITEST_CHROME_MANIFESTS += ['test/chr
 XPIDL_SOURCES += [
     'nsIEditingSession.idl',
 ]
 
 XPIDL_MODULE = 'composer'
 
 UNIFIED_SOURCES += [
     'ComposerCommandsUpdater.cpp',
-    'nsComposerRegistration.cpp',
     'nsEditingSession.cpp',
 ]
 
 EXPORTS += [
     'nsEditingSession.h',
 ]
 
 EXPORTS.mozilla += [
deleted file mode 100644
--- a/editor/composer/nsComposerRegistration.cpp
+++ /dev/null
@@ -1,55 +0,0 @@
-/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-#include "mozilla/EditorSpellCheck.h"   // for NS_EDITORSPELLCHECK_CID, etc
-#include "mozilla/HTMLEditorController.h" // for HTMLEditorController, etc
-#include "mozilla/Module.h"             // for Module, Module::CIDEntry, etc
-#include "mozilla/ModuleUtils.h"
-#include "mozilla/mozalloc.h"           // for operator new
-#include "nsCOMPtr.h"                   // for nsCOMPtr, getter_AddRefs, etc
-#include "nsBaseCommandController.h"    // for nsBaseCommandController
-#include "nsComponentManagerUtils.h"    // for do_CreateInstance
-#include "nsDebug.h"                    // for NS_ENSURE_SUCCESS
-#include "nsError.h"                    // for NS_ERROR_NO_AGGREGATION, etc
-#include "nsIController.h"              // for nsIController
-#include "nsIControllerContext.h"       // for nsIControllerContext
-#include "nsID.h"                       // for NS_DEFINE_NAMED_CID, etc
-#include "nsISupportsImpl.h"
-#include "nsISupportsUtils.h"           // for NS_ADDREF, NS_RELEASE
-#include "nsControllerCommandTable.h"   // for nsControllerCommandTable, etc
-#include "nsServiceManagerUtils.h"      // for do_GetService
-#include "nscore.h"                     // for nsresult
-
-using mozilla::EditorSpellCheck;
-
-class nsISupports;
-
-////////////////////////////////////////////////////////////////////////
-// Define the contructor function for the objects
-//
-// NOTE: This creates an instance of objects by using the default constructor
-//
-
-NS_GENERIC_FACTORY_CONSTRUCTOR(EditorSpellCheck)
-
-NS_DEFINE_NAMED_CID(NS_EDITORSPELLCHECK_CID);
-
-static const mozilla::Module::CIDEntry kComposerCIDs[] = {
-  { &kNS_EDITORSPELLCHECK_CID, false, nullptr, EditorSpellCheckConstructor },
-  { nullptr }
-};
-
-static const mozilla::Module::ContractIDEntry kComposerContracts[] = {
-  { "@mozilla.org/editor/editorspellchecker;1", &kNS_EDITORSPELLCHECK_CID },
-  { nullptr }
-};
-
-static const mozilla::Module kComposerModule = {
-  mozilla::Module::kVersion,
-  kComposerCIDs,
-  kComposerContracts
-};
-
-NSMODULE_DEFN(nsComposerModule) = &kComposerModule;
--- a/editor/libeditor/tests/test_bug1368544.html
+++ b/editor/libeditor/tests/test_bug1368544.html
@@ -16,19 +16,17 @@ https://bugzilla.mozilla.org/show_bug.cg
 </pre>
 
 <script class="testbody" type="application/javascript">
 SimpleTest.waitForExplicitFinish();
 SimpleTest.waitForFocus(() => {
   let textarea = document.getElementById("textarea");
   let editor = SpecialPowers.wrap(textarea).editor;
 
-  let spellChecker =
-    SpecialPowers.Cc['@mozilla.org/editor/editorspellchecker;1']
-    .createInstance(SpecialPowers.Ci.nsIEditorSpellCheck);
+  let spellChecker = SpecialPowers.Cu.createSpellChecker();
   spellChecker.InitSpellChecker(editor, false);
 
   textarea.focus();
 
   SpecialPowers.Cu.import(
     "resource://testing-common/AsyncSpellCheckTestHelper.jsm")
   .onSpellCheck(textarea, () => {
 
--- a/editor/spellchecker/EditorSpellCheck.h
+++ b/editor/spellchecker/EditorSpellCheck.h
@@ -14,22 +14,16 @@
 #include "nsString.h"                   // for nsString
 #include "nsTArray.h"                   // for nsTArray
 #include "nscore.h"                     // for nsresult
 
 class mozSpellChecker;
 class nsIEditor;
 class nsISpellChecker;
 
-#define NS_EDITORSPELLCHECK_CID                     \
-{ /* {75656ad9-bd13-4c5d-939a-ec6351eea0cc} */        \
-    0x75656ad9, 0xbd13, 0x4c5d,                       \
-    { 0x93, 0x9a, 0xec, 0x63, 0x51, 0xee, 0xa0, 0xcc }\
-}
-
 namespace mozilla {
 
 class DictionaryFetcher;
 class EditorBase;
 
 enum dictCompare
 {
   DICT_NORMAL_COMPARE,
--- a/editor/spellchecker/tests/test_bug1219928.html
+++ b/editor/spellchecker/tests/test_bug1219928.html
@@ -39,18 +39,17 @@ SimpleTest.waitForFocus(function() {
     var Ci = SpecialPowers.Ci;
     var editingSession = SpecialPowers.wrap(window).docShell.editingSession;
     var editor = editingSession.getEditorForWindow(window);
     var selcon = editor.selectionController;
     var sel = selcon.getSelection(selcon.SELECTION_SPELLCHECK);
 
     is(sel.toString(), "missspelled", "one misspelled word expected: missspelled");
 
-    spellchecker = SpecialPowers.Cc['@mozilla.org/editor/editorspellchecker;1']
-                                .createInstance(Ci.nsIEditorSpellCheck);
+    spellchecker = SpecialPowers.Cu.createSpellChecker();
     spellchecker.setFilterType(spellchecker.FILTERTYPE_NORMAL);
     spellchecker.InitSpellChecker(editor, false, spellCheckStarted);
   });
 });
 
 function spellCheckStarted() {
   var misspelledWord = spellchecker.GetNextMisspelledWord();
   is(misspelledWord, "missspelled", "first misspelled word expected: missspelled");
--- a/editor/spellchecker/tests/test_bug1365383.html
+++ b/editor/spellchecker/tests/test_bug1365383.html
@@ -16,19 +16,17 @@ https://bugzilla.mozilla.org/show_bug.cg
 <textarea id="editor" spellcheck="true"></textarea>
 </div>
 <pre id="test">
 <script class="testbody" type="text/javascript">
 SimpleTest.waitForExplicitFinish();
 SimpleTest.waitForFocus(() => {
   let textarea = document.getElementById("editor");
   let editor = SpecialPowers.wrap(textarea).editor;
-  let spellChecker =
-    SpecialPowers.Cc['@mozilla.org/editor/editorspellchecker;1']
-    .createInstance(SpecialPowers.Ci.nsIEditorSpellCheck);
+  let spellChecker = SpecialPowers.Cu.createSpellChecker();
 
   // Callback parameter isn't set
   spellChecker.InitSpellChecker(editor, false);
 
   textarea.focus();
 
   SpecialPowers.Cu.import(
     "resource://testing-common/AsyncSpellCheckTestHelper.jsm")
--- a/extensions/spellcheck/locales/en-US/hunspell/en-US.dic
+++ b/extensions/spellcheck/locales/en-US/hunspell/en-US.dic
@@ -1,9 +1,9 @@
-52891
+52914
 0/nm
 0th/pt
 1/n1
 1st/p
 1th/tc
 2/nm
 2nd/p
 2th/tc
@@ -7043,16 +7043,17 @@ Lassen/M
 Lassie/M
 Lat/M
 Latasha/M
 Lateran/M
 Latham/M
 Latin/MRS
 Latina
 Latino/SM
+Latinx
 Latisha/M
 Latonya/M
 Latoya/M
 Latrobe/M
 Latvia/M
 Latvian/MS
 Laud/MR
 Lauder/M
@@ -13880,16 +13881,17 @@ adolescent/SM
 adopt/AGVDS
 adoptable
 adoptee/MS
 adopter/MS
 adoption/SM
 adorableness/M
 adorably
 adoration/M
+adorbs
 adore/BZGDRS
 adorer/M
 adoring/Y
 adorn/LGDS
 adorned/U
 adornment/MS
 adrenal/MS
 adrenalin's
@@ -15732,16 +15734,17 @@ aviation/M
 aviator/MS
 aviatrices
 aviatrix/MS
 avid/Y
 avidity/M
 avionic/S
 avionics/M
 avitaminosis/M
+avo/S
 avocado/SM
 avocation/MS
 avocational
 avoid/SDGB
 avoidable/U
 avoidably/U
 avoidance/M
 avoidant
@@ -16783,16 +16786,17 @@ binary/SM
 binaural
 bind's
 bind/AUGS
 binder/MS
 bindery/SM
 binding/MS
 bindweed/M
 binge/MGDS
+bingeable
 bingeing
 bingo/M
 binman
 binmen
 binnacle/SM
 binned
 binning
 binocular/MS
@@ -16808,16 +16812,18 @@ biodiesel/M
 biodiversity/M
 bioethics/M
 biofeedback/M
 biog
 biographer/SM
 biographic
 biographical/Y
 biography/SM
+biohacker/MS
+biohacking
 bioinformatic/MS
 biol
 biologic
 biological/Y
 biologist/MS
 biology/M
 biomass/M
 biomedical
@@ -17452,16 +17458,17 @@ bottomless
 botulinum
 botulism/M
 boudoir/SM
 bouffant/SM
 bougainvillea/MS
 bough/M
 boughs
 bought
+bougie/S
 bouillabaisse/SM
 bouillon/MS
 boulder/SM
 boules
 boulevard/SM
 bounce/DRSMZG
 bouncer/M
 bouncily
@@ -26033,16 +26040,17 @@ faultiness/M
 faultless/PY
 faultlessness/M
 faulty/PRT
 faun/MS
 fauna/SM
 fauvism/M
 fauvist/SM
 faux
+fav/S
 fave/S
 favor/ESMDG
 favorable/U
 favorably/U
 favorite/SM
 favoritism/M
 fawn/MDRZGS
 fawner/M
@@ -26384,16 +26392,18 @@ finis/MS
 finish's
 finish/ADSG
 finished/U
 finisher/MS
 finite/IY
 fink/MDGS
 finned
 finny
+fintech
+fintechs
 fir/ZGSJMDRH
 fire/MS
 firearm/SM
 fireball/MS
 firebomb/MDSJG
 firebox/MS
 firebrand/SM
 firebreak/SM
@@ -28256,16 +28266,17 @@ gobbet/SM
 gobbing
 gobble/DRSMZG
 gobbledygook/M
 gobbler/M
 goblet/SM
 goblin/SM
 gobsmacked
 gobstopper/S
+gochujang
 god/SM
 godawful
 godchild/M
 godchildren/M
 goddam/D
 goddammit
 goddamn/D
 goddaughter/MS
@@ -28741,16 +28752,17 @@ grump/SM
 grumpily
 grumpiness/M
 grumpy/PRT
 grunge/MS
 grungy/RT
 grunion/SM
 grunt/SGMD
 gt
+guac
 guacamole/M
 guanine/M
 guano/M
 guarani/MS
 guaranies
 guarantee/MDS
 guaranteeing
 guarantor/MS
@@ -29115,16 +29127,17 @@ hangar/MS
 hangdog
 hanger/M
 hanging/M
 hangman/M
 hangmen
 hangnail/MS
 hangout/SM
 hangover/MS
+hangry/TR
 hangup/MS
 hank/MRZS
 hanker/GJD
 hankering/M
 hankie/M
 hanky/SM
 hansom/MS
 hap/MY
@@ -30013,16 +30026,17 @@ hooter/M
 hoover/DSG
 hooves
 hop/SGMD
 hope/MS
 hopeful/PSMY
 hopefulness/M
 hopeless/YP
 hopelessness/M
+hophead/S
 hopped
 hopper/MS
 hopping
 hopscotch/MDSG
 hora/MS
 horde/DSMG
 horehound/SM
 horizon/SM
@@ -30562,16 +30576,17 @@ idolatry/M
 idolization/M
 idolize/GDS
 idyll/SM
 idyllic
 idyllically
 if/SM
 iffiness/M
 iffy/RTP
+iftar/S
 igloo/SM
 igneous
 ignite/ZGNBXDRS
 igniter/M
 ignition/M
 ignoble
 ignobly
 ignominious/Y
@@ -34772,16 +34787,17 @@ mercantile
 mercantilism/M
 mercenary/SM
 mercer/MS
 mercerize/GDS
 merchandise/MZGDRS
 merchandiser/M
 merchandising/M
 merchant/MBSG
+merchantability
 merchantman/M
 merchantmen
 merciful/UY
 merciless/PY
 mercilessness/M
 mercurial/Y
 mercuric
 mercury/M
@@ -35260,16 +35276,17 @@ misdiagnose/GDS
 misdiagnosis/M
 misdid
 misdirect/SDG
 misdirection/M
 misdo/JG
 misdoes
 misdoing/M
 misdone
+mise/S
 miser/SBMY
 miserableness/M
 miserably
 miserliness/M
 misery/SM
 misfeasance/M
 misfeature/S
 misfile/GDS
@@ -35424,16 +35441,17 @@ mobilizer/SM
 mobster/SM
 moccasin/SM
 mocha/SM
 mock/DRSZG
 mocker/M
 mockery/SM
 mocking/Y
 mockingbird/SM
+mocktail/S
 mod/STM
 modal/SM
 modality/S
 modded
 modding
 mode/MS
 model/ZGSJMDR
 modeler/M
@@ -41884,16 +41902,17 @@ rancher/M
 ranching/M
 rancid/P
 rancidity/M
 rancidness/M
 rancor/M
 rancorous/Y
 rand/M
 randiness/M
+rando/S
 random/PSY
 randomization/M
 randomize/DSG
 randomness/MS
 randy/RTP
 ranee/MS
 rang/ZR
 range's
@@ -42925,16 +42944,17 @@ rhythm/SM
 rhythmic
 rhythmical/Y
 rial/MS
 rib/SM
 ribald
 ribaldry/M
 ribbed
 ribber/SM
+ribbie/S
 ribbing
 ribbon/SM
 riboflavin/M
 rice/MZGDRS
 ricer/M
 rich/TMRSYP
 richness/M
 rick/GMDS
@@ -43494,16 +43514,17 @@ saint/MDYS
 sainthood/M
 saintlike
 saintliness/M
 saintly/PRT
 saith
 sake/M
 saki/M
 salaam/SMDG
+salability
 salable/U
 salacious/PY
 salaciousness/M
 salacity/M
 salad/MS
 salamander/SM
 salami/SM
 salary/DSM
@@ -47456,16 +47477,17 @@ succulency/M
 succulent/SM
 succumb/GDS
 such
 suchlike
 suck/MDRZGS
 sucker/GMD
 suckle/DSJG
 suckling/M
+sucky
 sucrose/M
 suction/SMDG
 sudden/PY
 suddenness/M
 suds/M
 sudsy/TR
 sue/DSG
 suede/M
@@ -52873,16 +52895,17 @@ zoology/M
 zoom/MDSG
 zoophyte/SM
 zoophytic
 zooplankton
 zorch
 zoster
 zounds
 zucchini/MS
+zuke/S
 zwieback/M
 zydeco/M
 zygote/SM
 zygotic
 zymurgy/M
 Ångström/M
 éclair/SM
 éclat/M
--- a/extensions/spellcheck/src/mozInlineSpellChecker.cpp
+++ b/extensions/spellcheck/src/mozInlineSpellChecker.cpp
@@ -662,26 +662,23 @@ nsresult mozInlineSpellChecker::Cleanup(
 //
 //    Whenever dictionaries are added or removed at runtime, this value must be
 //    updated before an observer notification is sent out about the change, to
 //    avoid editors getting a wrong cached result.
 
 bool // static
 mozInlineSpellChecker::CanEnableInlineSpellChecking()
 {
-  nsresult rv;
   if (gCanEnableSpellChecking == SpellCheck_Uninitialized) {
     gCanEnableSpellChecking = SpellCheck_NotAvailable;
 
-    nsCOMPtr<nsIEditorSpellCheck> spellchecker =
-      do_CreateInstance("@mozilla.org/editor/editorspellchecker;1", &rv);
-    NS_ENSURE_SUCCESS(rv, false);
+    nsCOMPtr<nsIEditorSpellCheck> spellchecker = new EditorSpellCheck();
 
     bool canSpellCheck = false;
-    rv = spellchecker->CanSpellCheck(&canSpellCheck);
+    nsresult rv = spellchecker->CanSpellCheck(&canSpellCheck);
     NS_ENSURE_SUCCESS(rv, false);
 
     if (canSpellCheck)
       gCanEnableSpellChecking = SpellCheck_Available;
   }
   return (gCanEnableSpellChecking == SpellCheck_Available);
 }
 
--- a/extensions/universalchardet/src/xpcom/nsUdetXPCOMWrapper.cpp
+++ b/extensions/universalchardet/src/xpcom/nsUdetXPCOMWrapper.cpp
@@ -84,47 +84,8 @@ NS_IMETHODIMP nsXPCOMDetector::Done()
 void nsXPCOMDetector::Report(const char* aCharset)
 {
   NS_ASSERTION(mObserver != nullptr , "have not init yet");
 #ifdef DEBUG_chardet
   printf("Universal Charset Detector report charset %s . \r\n", aCharset);
 #endif
   mObserver->Notify(aCharset, eBestAnswer);
 }
-
-
-//---------------------------------------------------------------------
-nsXPCOMStringDetector:: nsXPCOMStringDetector()
-  : nsUniversalDetector()
-{
-}
-//---------------------------------------------------------------------
-nsXPCOMStringDetector::~nsXPCOMStringDetector()
-{
-}
-//---------------------------------------------------------------------
-NS_IMPL_ISUPPORTS(nsXPCOMStringDetector, nsIStringCharsetDetector)
-//---------------------------------------------------------------------
-void nsXPCOMStringDetector::Report(const char *aCharset)
-{
-  mResult = aCharset;
-#ifdef DEBUG_chardet
-  printf("New Charset Prober report charset %s . \r\n", aCharset);
-#endif
-}
-//---------------------------------------------------------------------
-NS_IMETHODIMP nsXPCOMStringDetector::DoIt(const char* aBuf,
-                     uint32_t aLen, const char** oCharset,
-                     nsDetectionConfident &oConf)
-{
-  mResult = nullptr;
-  this->Reset();
-  nsresult rv = this->HandleData(aBuf, aLen);
-  if (NS_FAILED(rv))
-    return rv;
-  this->DataEnd();
-  if (mResult)
-  {
-    *oCharset=mResult;
-    oConf = eBestAnswer;
-  }
-  return NS_OK;
-}
--- a/extensions/universalchardet/src/xpcom/nsUdetXPCOMWrapper.h
+++ b/extensions/universalchardet/src/xpcom/nsUdetXPCOMWrapper.h
@@ -34,35 +34,16 @@ class nsXPCOMDetector :
     NS_IMETHOD Done() override;
   protected:
     virtual ~nsXPCOMDetector();
     virtual void Report(const char* aCharset) override;
   private:
     nsCOMPtr<nsICharsetDetectionObserver> mObserver;
 };
 
-
-//=====================================================================
-class nsXPCOMStringDetector :
-      public nsUniversalDetector,
-      public nsIStringCharsetDetector
-{
-  NS_DECL_ISUPPORTS
-  public:
-    nsXPCOMStringDetector();
-    NS_IMETHOD DoIt(const char* aBuf, uint32_t aLen,
-                    const char** oCharset, nsDetectionConfident &oConf) override;
-  protected:
-    virtual ~nsXPCOMStringDetector();
-    virtual void Report(const char* aCharset) override;
-  private:
-    nsCOMPtr<nsICharsetDetectionObserver> mObserver;
-    const char* mResult;
-};
-
 //=====================================================================
 
 class nsJAPSMDetector final : public nsXPCOMDetector
 {
 public:
   nsJAPSMDetector()
     : nsXPCOMDetector() {}
 };
--- a/gfx/thebes/DeviceManagerDx.cpp
+++ b/gfx/thebes/DeviceManagerDx.cpp
@@ -196,17 +196,25 @@ DeviceManagerDx::CreateCompositorDevices
   // as well for D2D1 and device resets.
   mD3D11Module.disown();
 
   MOZ_ASSERT(mCompositorDevice);
   if (!d3d11.IsEnabled()) {
     return false;
   }
 
-  PreloadAttachmentsOnCompositorThread();
+  // When WR is used, do not preload attachments for D3D11 Non-WR compositor.
+  //
+  // Fallback from WR to D3D11 Non-WR compositor without re-creating gpu process
+  // could happen when WR causes error. In this case, the attachments are loaded
+  // synchronously.
+  if (!gfx::gfxVars::UseWebRender()) {
+    PreloadAttachmentsOnCompositorThread();
+  }
+
   return true;
 }
 
 bool
 DeviceManagerDx::CreateVRDevice()
 {
   MOZ_ASSERT(ProcessOwnsCompositor());
 
--- a/gfx/webrender/src/clip.rs
+++ b/gfx/webrender/src/clip.rs
@@ -12,17 +12,17 @@ use clip_scroll_tree::{ClipScrollTree, C
 use ellipse::Ellipse;
 use gpu_cache::{GpuCache, GpuCacheHandle, ToGpuBlocks};
 use gpu_types::{BoxShadowStretchMode};
 use internal_types::FastHashSet;
 use prim_store::{ClipData, ImageMaskData, SpaceMapper};
 use render_task::to_cache_size;
 use resource_cache::{ImageRequest, ResourceCache};
 use std::{cmp, u32};
-use util::{extract_inner_rect_safe, pack_as_float, project_rect, recycle_vec};
+use util::{extract_inner_rect_safe, pack_as_float, project_rect, recycle_vec, ScaleOffset};
 
 /*
 
  Module Overview
 
  There are a number of data structures involved in the clip module:
 
  ClipStore - Main interface used by other modules.
@@ -195,17 +195,17 @@ pub struct ClipNodeRange {
 // A helper struct for converting between coordinate systems
 // of clip sources and primitives.
 // todo(gw): optimize:
 //  separate arrays for matrices
 //  cache and only build as needed.
 #[derive(Debug)]
 enum ClipSpaceConversion {
     Local,
-    Offset(LayoutVector2D),
+    ScaleOffset(ScaleOffset),
     Transform(LayoutToWorldTransform),
 }
 
 // Temporary information that is cached and reused
 // during building of a clip chain instance.
 struct ClipNodeInfo {
     conversion: ClipSpaceConversion,
     node_index: ClipNodeIndex,
@@ -533,19 +533,19 @@ impl ClipStore {
         for node_info in self.clip_node_info.drain(..) {
             let node = &mut self.clip_nodes[node_info.node_index.0 as usize];
 
             // See how this clip affects the prim region.
             let clip_result = match node_info.conversion {
                 ClipSpaceConversion::Local => {
                     node.item.get_clip_result(&local_bounding_rect)
                 }
-                ClipSpaceConversion::Offset(offset) => {
+                ClipSpaceConversion::ScaleOffset(ref scale_offset) => {
                     has_non_local_clips = true;
-                    node.item.get_clip_result(&local_bounding_rect.translate(&-offset))
+                    node.item.get_clip_result(&scale_offset.unmap_rect(&local_bounding_rect))
                 }
                 ClipSpaceConversion::Transform(ref transform) => {
                     has_non_local_clips = true;
                     node.item.get_clip_result_complex(
                         transform,
                         &world_clip_rect,
                         world_rect,
                     )
@@ -571,17 +571,17 @@ impl ClipStore {
                     );
 
                     // Calculate some flags that are required for the segment
                     // building logic.
                     let flags = match node_info.conversion {
                         ClipSpaceConversion::Local => {
                             ClipNodeFlags::SAME_SPATIAL_NODE | ClipNodeFlags::SAME_COORD_SYSTEM
                         }
-                        ClipSpaceConversion::Offset(..) => {
+                        ClipSpaceConversion::ScaleOffset(..) => {
                             ClipNodeFlags::SAME_COORD_SYSTEM
                         }
                         ClipSpaceConversion::Transform(..) => {
                             ClipNodeFlags::empty()
                         }
                     };
 
                     // As a special case, a partial accept of a clip rect that is
@@ -1151,19 +1151,22 @@ fn add_clip_node_to_current_chain(
     let clip_spatial_node = &clip_scroll_tree.spatial_nodes[clip_node.spatial_node_index.0 as usize];
     let ref_spatial_node = &clip_scroll_tree.spatial_nodes[spatial_node_index.0];
 
     // Determine the most efficient way to convert between coordinate
     // systems of the primitive and clip node.
     let conversion = if spatial_node_index == clip_node.spatial_node_index {
         Some(ClipSpaceConversion::Local)
     } else if ref_spatial_node.coordinate_system_id == clip_spatial_node.coordinate_system_id {
-        let offset = clip_spatial_node.coordinate_system_relative_offset -
-                     ref_spatial_node.coordinate_system_relative_offset;
-        Some(ClipSpaceConversion::Offset(offset))
+        let scale_offset = ref_spatial_node
+            .coordinate_system_relative_scale_offset
+            .difference(
+                &clip_spatial_node.coordinate_system_relative_scale_offset
+            );
+        Some(ClipSpaceConversion::ScaleOffset(scale_offset))
     } else {
         let xf = clip_scroll_tree.get_relative_transform(
             clip_node.spatial_node_index,
             ROOT_SPATIAL_NODE_INDEX,
         );
 
         xf.map(|xf| {
             ClipSpaceConversion::Transform(xf.with_destination::<WorldPixel>())
@@ -1176,18 +1179,18 @@ fn add_clip_node_to_current_chain(
         if let Some(clip_rect) = clip_node.item.get_local_clip_rect() {
             match conversion {
                 ClipSpaceConversion::Local => {
                     *local_clip_rect = match local_clip_rect.intersection(&clip_rect) {
                         Some(rect) => rect,
                         None => return false,
                     };
                 }
-                ClipSpaceConversion::Offset(ref offset) => {
-                    let clip_rect = clip_rect.translate(offset);
+                ClipSpaceConversion::ScaleOffset(ref scale_offset) => {
+                    let clip_rect = scale_offset.map_rect(&clip_rect);
                     *local_clip_rect = match local_clip_rect.intersection(&clip_rect) {
                         Some(rect) => rect,
                         None => return false,
                     };
                 }
                 ClipSpaceConversion::Transform(..) => {
                     // TODO(gw): In the future, we can reduce the size
                     //           of the pic_clip_rect here. To do this,
--- a/gfx/webrender/src/clip_scroll_tree.rs
+++ b/gfx/webrender/src/clip_scroll_tree.rs
@@ -1,47 +1,45 @@
 /* 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/. */
 
-use api::{ExternalScrollId, LayoutPoint, LayoutRect, LayoutVector2D, LayoutVector3D};
+use api::{ExternalScrollId, LayoutPoint, LayoutRect, LayoutVector2D};
 use api::{PipelineId, ScrollClamping, ScrollNodeState, ScrollLocation};
 use api::{LayoutSize, LayoutTransform, PropertyBinding, ScrollSensitivity, WorldPoint};
 use gpu_types::TransformPalette;
 use internal_types::{FastHashMap, FastHashSet};
 use print_tree::{PrintTree, PrintTreePrinter};
 use scene::SceneProperties;
 use smallvec::SmallVec;
 use spatial_node::{ScrollFrameInfo, SpatialNode, SpatialNodeType, StickyFrameInfo};
-use util::LayoutToWorldFastTransform;
+use util::{LayoutToWorldFastTransform, ScaleOffset};
 
 pub type ScrollStates = FastHashMap<ExternalScrollId, ScrollFrameInfo>;
 
 /// An id that identifies coordinate systems in the ClipScrollTree. Each
 /// coordinate system has an id and those ids will be shared when the coordinates
 /// system are the same or are in the same axis-aligned space. This allows
 /// for optimizing mask generation.
 #[derive(Debug, Copy, Clone, PartialEq)]
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 pub struct CoordinateSystemId(pub u32);
 
 /// A node in the hierarchy of coordinate system
 /// transforms.
 #[derive(Debug)]
 pub struct CoordinateSystem {
-    pub offset: LayoutVector3D,
     pub transform: LayoutTransform,
     pub parent: Option<CoordinateSystemId>,
 }
 
 impl CoordinateSystem {
     fn root() -> Self {
         CoordinateSystem {
-            offset: LayoutVector3D::zero(),
             transform: LayoutTransform::identity(),
             parent: None,
         }
     }
 }
 
 #[derive(Debug, Copy, Clone, Eq, Hash, PartialEq, PartialOrd)]
 #[cfg_attr(feature = "capture", derive(Serialize))]
@@ -82,18 +80,18 @@ pub struct TransformUpdateState {
     pub nearest_scrolling_ancestor_viewport: LayoutRect,
 
     /// An id for keeping track of the axis-aligned space of this node. This is used in
     /// order to to track what kinds of clip optimizations can be done for a particular
     /// display list item, since optimizations can usually only be done among
     /// coordinate systems which are relatively axis aligned.
     pub current_coordinate_system_id: CoordinateSystemId,
 
-    /// Offset from the coordinate system that started this compatible coordinate system.
-    pub coordinate_system_relative_offset: LayoutVector2D,
+    /// Scale and offset from the coordinate system that started this compatible coordinate system.
+    pub coordinate_system_relative_scale_offset: ScaleOffset,
 
     /// True if this node is transformed by an invertible transform.  If not, display items
     /// transformed by this node will not be displayed and display items not transformed by this
     /// node will not be clipped by clips that are transformed by this node.
     pub invertible: bool,
 }
 
 impl ClipScrollTree {
@@ -130,34 +128,27 @@ impl ClipScrollTree {
         while coordinate_system_id != parent.coordinate_system_id {
             nodes.push(coordinate_system_id);
             let coord_system = &self.coord_systems[coordinate_system_id.0 as usize];
             coordinate_system_id = coord_system.parent.expect("invalid parent!");
         }
 
         nodes.reverse();
 
-        let mut transform = LayoutTransform::create_translation(
-            -parent.coordinate_system_relative_offset.x,
-            -parent.coordinate_system_relative_offset.y,
-            0.0,
-        );
+        let mut transform = parent.coordinate_system_relative_scale_offset
+                                  .inverse()
+                                  .to_transform();
 
         for node in nodes {
             let coord_system = &self.coord_systems[node.0 as usize];
-            transform = transform.pre_translate(coord_system.offset)
-                                 .pre_mul(&coord_system.transform);
+            transform = transform.pre_mul(&coord_system.transform);
         }
 
-        let transform = transform.pre_translate(
-            LayoutVector3D::new(
-                child.coordinate_system_relative_offset.x,
-                child.coordinate_system_relative_offset.y,
-                0.0,
-            )
+        let transform = transform.pre_mul(
+            &child.coordinate_system_relative_scale_offset.to_transform(),
         );
 
         if inverse {
             transform.inverse()
         } else {
             Some(transform)
         }
     }
@@ -269,17 +260,17 @@ impl ClipScrollTree {
 
         let root_reference_frame_index = self.root_reference_frame_index();
         let mut state = TransformUpdateState {
             parent_reference_frame_transform: LayoutVector2D::new(pan.x, pan.y).into(),
             parent_accumulated_scroll_offset: LayoutVector2D::zero(),
             nearest_scrolling_ancestor_offset: LayoutVector2D::zero(),
             nearest_scrolling_ancestor_viewport: LayoutRect::zero(),
             current_coordinate_system_id: CoordinateSystemId::root(),
-            coordinate_system_relative_offset: LayoutVector2D::zero(),
+            coordinate_system_relative_scale_offset: ScaleOffset::identity(),
             invertible: true,
         };
 
         self.update_node(
             root_reference_frame_index,
             &mut state,
             &mut transform_palette,
             scene_properties,
--- a/gfx/webrender/src/display_list_flattener.rs
+++ b/gfx/webrender/src/display_list_flattener.rs
@@ -1070,16 +1070,32 @@ impl<'a> DisplayListFlattener<'a> {
                 true,
                 clip_chain_id,
                 spatial_node_index,
                 None,
                 PrimitiveContainer::Brush(container_prim),
             );
         }
 
+        // preserve-3d's semantics are to hoist all your children to be your siblings
+        // when doing backface-visibility checking, so we need to grab the backface-visibility
+        // of the lowest ancestor which *doesn't* preserve-3d, and AND it in with ours.
+        //
+        // No this isn't obvious or clear, it's just what we worked out over a day of testing.
+        // There's probably a bug in here, but I couldn't find it with the examples and tests
+        // at my disposal!
+        let ancestor_is_backface_visible =
+            self.sc_stack
+                .iter()
+                .rfind(|sc| sc.transform_style == TransformStyle::Flat)
+                .map(|sc| sc.is_backface_visible)
+                .unwrap_or(is_backface_visible);
+
+        let is_backface_visible = is_backface_visible && ancestor_is_backface_visible;
+
         // Push the SC onto the stack, so we know how to handle things in
         // pop_stacking_context.
         let sc = FlattenedStackingContext {
             is_backface_visible,
             pipeline_id,
             transform_style,
             establishes_3d_context,
             participating_in_3d_context,
--- a/gfx/webrender/src/prim_store.rs
+++ b/gfx/webrender/src/prim_store.rs
@@ -7,17 +7,17 @@ use api::{DeviceIntRect, DeviceIntSize, 
 use api::{FilterOp, GlyphInstance, GradientStop, ImageKey, ImageRendering, ItemRange, ItemTag, TileOffset};
 use api::{GlyphRasterSpace, LayoutPoint, LayoutRect, LayoutSize, LayoutToWorldTransform, LayoutVector2D};
 use api::{PremultipliedColorF, PropertyBinding, Shadow, YuvColorSpace, YuvFormat, DeviceIntSideOffsets, WorldPixel};
 use api::{BorderWidths, BoxShadowClipMode, LayoutToWorldScale, NormalBorder, WorldRect, PicturePixel, RasterPixel};
 use app_units::Au;
 use border::{BorderCacheKey, BorderRenderTaskInfo};
 use clip_scroll_tree::{ClipScrollTree, CoordinateSystemId, SpatialNodeIndex};
 use clip::{ClipNodeFlags, ClipChainId, ClipChainInstance, ClipItem, ClipNodeCollector};
-use euclid::{TypedVector2D, TypedTransform3D, TypedRect};
+use euclid::{TypedTransform3D, TypedRect};
 use frame_builder::{FrameBuildingContext, FrameBuildingState, PictureContext, PictureState};
 use frame_builder::PrimitiveContext;
 use glyph_rasterizer::{FontInstance, FontTransform, GlyphKey, FONT_SIZE_LIMIT};
 use gpu_cache::{GpuBlockData, GpuCache, GpuCacheAddress, GpuCacheHandle, GpuDataRequest,
                 ToGpuBlocks};
 use gpu_types::BrushFlags;
 use image::{for_each_tile, for_each_repetition};
 use picture::{PictureCompositeMode, PicturePrimitive};
@@ -25,17 +25,17 @@ use picture::{PictureCompositeMode, Pict
 use render_backend::FrameId;
 use render_task::{BlitSource, RenderTask, RenderTaskCacheKey};
 use render_task::{RenderTaskCacheKeyKind, RenderTaskId, RenderTaskCacheEntryHandle};
 use renderer::{MAX_VERTEX_TEXTURE_WIDTH};
 use resource_cache::{ImageProperties, ImageRequest, ResourceCache};
 use scene::SceneProperties;
 use segment::SegmentBuilder;
 use std::{cmp, fmt, mem, usize};
-use util::{MatrixHelpers, pack_as_float, recycle_vec, project_rect, raster_rect_to_device_pixels};
+use util::{ScaleOffset, MatrixHelpers, pack_as_float, recycle_vec, project_rect, raster_rect_to_device_pixels};
 
 
 const MIN_BRUSH_SPLIT_AREA: f32 = 256.0 * 256.0;
 pub const VECS_PER_SEGMENT: usize = 2;
 
 #[derive(Clone, Copy, Debug, Eq, PartialEq)]
 pub struct ScrollNodeAndClipChain {
     pub spatial_node_index: SpatialNodeIndex,
@@ -90,17 +90,17 @@ impl PrimitiveOpacity {
             is_opaque: alpha == 1.0,
         }
     }
 }
 
 #[derive(Debug)]
 pub enum CoordinateSpaceMapping<F, T> {
     Local,
-    Offset(TypedVector2D<f32, F>),
+    ScaleOffset(ScaleOffset),
     Transform(TypedTransform3D<f32, F, T>),
 }
 
 #[derive(Debug)]
 pub struct SpaceMapper<F, T> {
     kind: CoordinateSpaceMapping<F, T>,
     pub ref_spatial_node_index: SpatialNodeIndex,
     pub current_target_spatial_node_index: SpatialNodeIndex,
@@ -140,23 +140,22 @@ impl<F, T> SpaceMapper<F, T> where F: fm
             let spatial_nodes = &clip_scroll_tree.spatial_nodes;
             let ref_spatial_node = &spatial_nodes[self.ref_spatial_node_index.0];
             let target_spatial_node = &spatial_nodes[target_node_index.0];
             self.current_target_spatial_node_index = target_node_index;
 
             self.kind = if self.ref_spatial_node_index == target_node_index {
                 CoordinateSpaceMapping::Local
             } else if ref_spatial_node.coordinate_system_id == target_spatial_node.coordinate_system_id {
-                let offset = TypedVector2D::new(
-                    target_spatial_node.coordinate_system_relative_offset.x -
-                        ref_spatial_node.coordinate_system_relative_offset.x,
-                    target_spatial_node.coordinate_system_relative_offset.y -
-                        ref_spatial_node.coordinate_system_relative_offset.y,
-                );
-                CoordinateSpaceMapping::Offset(offset)
+                CoordinateSpaceMapping::ScaleOffset(
+                    ref_spatial_node.coordinate_system_relative_scale_offset
+                        .difference(
+                            &target_spatial_node.coordinate_system_relative_scale_offset
+                        )
+                )
             } else {
                 let transform = clip_scroll_tree.get_relative_transform(
                     target_node_index,
                     self.ref_spatial_node_index,
                 ).expect("bug: should have already been culled");
 
                 CoordinateSpaceMapping::Transform(
                     transform.with_source::<F>().with_destination::<T>()
@@ -165,47 +164,46 @@ impl<F, T> SpaceMapper<F, T> where F: fm
         }
     }
 
     pub fn get_transform(&self) -> TypedTransform3D<f32, F, T> {
         match self.kind {
             CoordinateSpaceMapping::Local => {
                 TypedTransform3D::identity()
             }
-            CoordinateSpaceMapping::Offset(offset) => {
-                TypedTransform3D::create_translation(offset.x, offset.y, 0.0)
+            CoordinateSpaceMapping::ScaleOffset(ref scale_offset) => {
+                scale_offset.to_transform()
             }
             CoordinateSpaceMapping::Transform(transform) => {
                 transform
             }
         }
     }
 
     pub fn unmap(&self, rect: &TypedRect<f32, T>) -> Option<TypedRect<f32, F>> {
         match self.kind {
             CoordinateSpaceMapping::Local => {
                 Some(TypedRect::from_untyped(&rect.to_untyped()))
             }
-            CoordinateSpaceMapping::Offset(ref offset) => {
-                let offset = TypedVector2D::new(-offset.x, -offset.y);
-                Some(TypedRect::from_untyped(&rect.translate(&offset).to_untyped()))
+            CoordinateSpaceMapping::ScaleOffset(ref scale_offset) => {
+                Some(scale_offset.unmap_rect(rect))
             }
             CoordinateSpaceMapping::Transform(ref transform) => {
                 transform.inverse_rect_footprint(rect)
             }
         }
     }
 
     pub fn map(&self, rect: &TypedRect<f32, F>) -> Option<TypedRect<f32, T>> {
         match self.kind {
             CoordinateSpaceMapping::Local => {
                 Some(TypedRect::from_untyped(&rect.to_untyped()))
             }
-            CoordinateSpaceMapping::Offset(ref offset) => {
-                Some(TypedRect::from_untyped(&rect.translate(offset).to_untyped()))
+            CoordinateSpaceMapping::ScaleOffset(ref scale_offset) => {
+                Some(scale_offset.map_rect(rect))
             }
             CoordinateSpaceMapping::Transform(ref transform) => {
                 match project_rect(transform, rect, &self.bounds) {
                     Some(bounds) => {
                         Some(bounds)
                     }
                     None => {
                         warn!("parent relative transform can't transform the primitive rect for {:?}", rect);
--- a/gfx/webrender/src/render_backend.rs
+++ b/gfx/webrender/src/render_backend.rs
@@ -4,16 +4,17 @@
 
 use api::{ApiMsg, BuiltDisplayList, ClearCache, DebugCommand};
 #[cfg(feature = "debugger")]
 use api::{BuiltDisplayListIter, SpecificDisplayItem};
 use api::{DeviceIntPoint, DevicePixelScale, DeviceUintPoint, DeviceUintRect, DeviceUintSize};
 use api::{DocumentId, DocumentLayer, ExternalScrollId, FrameMsg, HitTestFlags, HitTestResult};
 use api::{IdNamespace, LayoutPoint, PipelineId, RenderNotifier, SceneMsg, ScrollClamping};
 use api::{ScrollLocation, ScrollNodeState, TransactionMsg, ResourceUpdate, ImageKey};
+use api::{NotificationRequest, Checkpoint};
 use api::channel::{MsgReceiver, Payload};
 #[cfg(feature = "capture")]
 use api::CaptureBits;
 #[cfg(feature = "replay")]
 use api::CapturedDocument;
 use clip_scroll_tree::{SpatialNodeIndex, ClipScrollTree};
 #[cfg(feature = "debugger")]
 use debug_server;
@@ -39,16 +40,17 @@ use serde_json;
 use std::path::PathBuf;
 use std::sync::atomic::{ATOMIC_USIZE_INIT, AtomicUsize, Ordering};
 use std::mem::replace;
 use std::sync::mpsc::{channel, Sender, Receiver};
 use std::u32;
 #[cfg(feature = "replay")]
 use tiling::Frame;
 use time::precise_time_ns;
+use util::drain_filter;
 
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 #[derive(Clone)]
 pub struct DocumentView {
     pub window_size: DeviceUintSize,
     pub inner_rect: DeviceUintRect,
     pub layer: DocumentLayer,
@@ -100,16 +102,21 @@ struct Document {
 
     /// A data structure to allow hit testing against rendered frames. This is updated
     /// every time we produce a fully rendered frame.
     hit_tester: Option<HitTester>,
 
     /// Properties that are resolved during frame building and can be changed at any time
     /// without requiring the scene to be re-built.
     dynamic_properties: SceneProperties,
+
+    /// Track whether the last built frame is up to date or if it will need to be re-built
+    /// before rendering again.
+    frame_is_valid: bool,
+    hit_tester_is_valid: bool,
 }
 
 impl Document {
     pub fn new(
         window_size: DeviceUintSize,
         layer: DocumentLayer,
         default_device_pixel_ratio: f32,
     ) -> Self {
@@ -126,16 +133,18 @@ impl Document {
                 device_pixel_ratio: default_device_pixel_ratio,
             },
             clip_scroll_tree: ClipScrollTree::new(),
             frame_id: FrameId(0),
             frame_builder: None,
             output_pipelines: FastHashSet::default(),
             hit_tester: None,
             dynamic_properties: SceneProperties::new(),
+            frame_is_valid: false,
+            hit_tester_is_valid: false,
         }
     }
 
     fn can_render(&self) -> bool {
         self.frame_builder.is_some() && self.scene.has_root_pipeline()
     }
 
     fn has_pixels(&self) -> bool {
@@ -168,17 +177,20 @@ impl Document {
                         hit_tester.find_node_under_point(test)
                     }
                     None => {
                         None
                     }
                 };
 
                 if self.hit_tester.is_some() {
-                    let _scrolled = self.scroll_nearest_scrolling_ancestor(delta, node_index);
+                    if self.scroll_nearest_scrolling_ancestor(delta, node_index) {
+                        self.hit_tester_is_valid = false;
+                        self.frame_is_valid = false;
+                    }
                 }
 
                 return DocumentOps {
                     // TODO: Does it make sense to track this as a scrolling even if we
                     // ended up not scrolling anything?
                     scroll: true,
                     ..DocumentOps::nop()
                 };
@@ -190,22 +202,29 @@ impl Document {
                         hit_tester.hit_test(HitTest::new(pipeline_id, point, flags))
                     }
                     None => HitTestResult { items: Vec::new() },
                 };
 
                 tx.send(result).unwrap();
             }
             FrameMsg::SetPan(pan) => {
-                self.view.pan = pan;
+                if self.view.pan != pan {
+                    self.view.pan = pan;
+                    self.hit_tester_is_valid = false;
+                    self.frame_is_valid = false;
+                }
             }
             FrameMsg::ScrollNodeWithId(origin, id, clamp) => {
                 profile_scope!("ScrollNodeWithScrollId");
 
-                let _scrolled = self.scroll_node(origin, id, clamp);
+                if self.scroll_node(origin, id, clamp) {
+                    self.hit_tester_is_valid = false;
+                    self.frame_is_valid = false;
+                }
 
                 return DocumentOps {
                     scroll: true,
                     ..DocumentOps::nop()
                 };
             }
             FrameMsg::GetScrollNodeState(tx) => {
                 profile_scope!("GetScrollNodeState");
@@ -246,16 +265,19 @@ impl Document {
                 &mut resource_profile.texture_cache,
                 &mut resource_profile.gpu_cache,
                 &self.dynamic_properties,
             );
             self.hit_tester = Some(frame_builder.create_hit_tester(&self.clip_scroll_tree));
             frame
         };
 
+        self.frame_is_valid = true;
+        self.hit_tester_is_valid = true;
+
         RenderedDocument {
             frame,
             is_new_scene,
         }
     }
 
     pub fn updated_pipeline_info(&mut self) -> PipelineInfo {
         let removed_pipelines = replace(&mut self.removed_pipelines, Vec::new());
@@ -290,40 +312,40 @@ impl Document {
     }
 
     pub fn get_scroll_node_state(&self) -> Vec<ScrollNodeState> {
         self.clip_scroll_tree.get_scroll_node_state()
     }
 
     pub fn new_async_scene_ready(&mut self, built_scene: BuiltScene) {
         self.scene = built_scene.scene;
+        self.frame_is_valid = false;
+        self.hit_tester_is_valid = false;
 
         self.frame_builder = Some(built_scene.frame_builder);
 
         let old_scrolling_states = self.clip_scroll_tree.drain();
         self.clip_scroll_tree = built_scene.clip_scroll_tree;
         self.clip_scroll_tree.finalize_and_apply_pending_scroll_offsets(old_scrolling_states);
 
         // Advance to the next frame.
         self.frame_id.0 += 1;
     }
 }
 
 struct DocumentOps {
     scroll: bool,
     build_frame: bool,
-    render_frame: bool,
 }
 
 impl DocumentOps {
     fn nop() -> Self {
         DocumentOps {
             scroll: false,
             build_frame: false,
-            render_frame: false,
         }
     }
 }
 
 /// The unique id for WR resource identification.
 static NEXT_NAMESPACE_ID: AtomicUsize = ATOMIC_USIZE_INIT;
 
 #[cfg(any(feature = "capture", feature = "replay"))]
@@ -569,16 +591,17 @@ impl RenderBackend {
                         if let Some(rasterizer) = txn.blob_rasterizer.take() {
                             self.resource_cache.set_blob_rasterizer(rasterizer);
                         }
 
                         self.update_document(
                             txn.document_id,
                             replace(&mut txn.resource_updates, Vec::new()),
                             replace(&mut txn.frame_ops, Vec::new()),
+                            replace(&mut txn.notifications, Vec::new()),
                             txn.build_frame,
                             txn.render_frame,
                             &mut frame_counter,
                             &mut profile_counters,
                             has_built_scene,
                         );
                     },
                     SceneBuilderResult::FlushComplete(tx) => {
@@ -820,16 +843,17 @@ impl RenderBackend {
             removed_pipelines: Vec::new(),
             epoch_updates: Vec::new(),
             request_scene_build: None,
             blob_rasterizer: None,
             blob_requests: Vec::new(),
             resource_updates: transaction_msg.resource_updates,
             frame_ops: transaction_msg.frame_ops,
             rasterized_blobs: Vec::new(),
+            notifications: transaction_msg.notifications,
             set_root_pipeline: None,
             build_frame: transaction_msg.generate_frame,
             render_frame: transaction_msg.generate_frame,
         });
 
         self.resource_cache.pre_scene_building_update(
             &mut txn.resource_updates,
             &mut profile_counters.resources,
@@ -855,16 +879,17 @@ impl RenderBackend {
             txn.blob_rasterizer = blob_rasterizer;
         }
 
         if !transaction_msg.use_scene_builder_thread && txn.can_skip_scene_builder() {
             self.update_document(
                 txn.document_id,
                 replace(&mut txn.resource_updates, Vec::new()),
                 replace(&mut txn.frame_ops, Vec::new()),
+                replace(&mut txn.notifications, Vec::new()),
                 txn.build_frame,
                 txn.render_frame,
                 frame_counter,
                 profile_counters,
                 false
             );
 
             return;
@@ -891,73 +916,84 @@ impl RenderBackend {
         tx.send(SceneBuilderRequest::Transaction(txn)).unwrap();
     }
 
     fn update_document(
         &mut self,
         document_id: DocumentId,
         resource_updates: Vec<ResourceUpdate>,
         mut frame_ops: Vec<FrameMsg>,
+        mut notifications: Vec<NotificationRequest>,
         mut build_frame: bool,
         mut render_frame: bool,
         frame_counter: &mut u32,
         profile_counters: &mut BackendProfileCounters,
         has_built_scene: bool,
     ) {
         let requested_frame = render_frame;
-        self.resource_cache.post_scene_building_update(
-            resource_updates,
-            &mut profile_counters.resources,
-        );
 
         // If we have a sampler, get more frame ops from it and add them
         // to the transaction. This is a hook to allow the WR user code to
         // fiddle with things after a potentially long scene build, but just
         // before rendering. This is useful for rendering with the latest
         // async transforms.
         if build_frame {
             if let Some(ref sampler) = self.sampler {
                 frame_ops.append(&mut sampler.sample());
             }
         }
 
-
         let doc = self.documents.get_mut(&document_id).unwrap();
 
+        // TODO: this scroll variable doesn't necessarily mean we scrolled. It is only used
+        // for something wrench specific and we should remove it.
         let mut scroll = false;
         for frame_msg in frame_ops {
             let _timer = profile_counters.total_time.timer();
             let op = doc.process_frame_msg(frame_msg);
             build_frame |= op.build_frame;
-            render_frame |= op.render_frame;
             scroll |= op.scroll;
         }
 
+        for update in &resource_updates {
+            if let ResourceUpdate::UpdateImage(..) = update {
+                doc.frame_is_valid = false;
+            }
+        }
+
+        self.resource_cache.post_scene_building_update(
+            resource_updates,
+            &mut profile_counters.resources,
+        );
+
         // After applying the new scene we need to
         // rebuild the hit-tester, so we trigger a frame generation
         // step.
         //
         // TODO: We could avoid some the cost of building the frame by only
         // building the information required for hit-testing (See #2807).
         build_frame |= has_built_scene;
 
         if doc.dynamic_properties.flush_pending_updates() {
+            doc.frame_is_valid = false;
+            doc.hit_tester_is_valid = false;
             build_frame = true;
         }
 
         if !doc.can_render() {
             // TODO: this happens if we are building the first scene asynchronously and
             // scroll at the same time. we should keep track of the fact that we skipped
             // composition here and do it as soon as we receive the scene.
             build_frame = false;
             render_frame = false;
         }
 
-        // If we don't generate a frame it makes no sense to render.
-        debug_assert!(build_frame || !render_frame);
+        if doc.frame_is_valid {
+            build_frame = false;
+        }
 
         let mut frame_build_time = None;
         if build_frame && doc.has_pixels() {
             profile_scope!("generate frame");
 
             *frame_counter += 1;
 
             // borrow ck hack for profile_counters
@@ -991,25 +1027,31 @@ impl RenderBackend {
             let msg = ResultMsg::PublishDocument(
                 document_id,
                 rendered_document,
                 pending_update,
                 profile_counters.clone()
             );
             self.result_tx.send(msg).unwrap();
             profile_counters.reset();
-        } else if build_frame {
+        } else if requested_frame {
             // WR-internal optimization to avoid doing a bunch of render work if
             // there's no pixels. We still want to pretend to render and request
             // a render to make sure that the callbacks (particularly the
             // new_frame_ready callback below) has the right flags.
             let msg = ResultMsg::PublishPipelineInfo(doc.updated_pipeline_info());
             self.result_tx.send(msg).unwrap();
         }
 
+        drain_filter(
+            &mut notifications,
+            |n| { n.when() == Checkpoint::FrameBuilt },
+            |n| { n.notify(); },
+        );
+
         // Always forward the transaction to the renderer if a frame was requested,
         // otherwise gecko can get into a state where it waits (forever) for the
         // transaction to complete before sending new work.
         if requested_frame {
             self.notifier.new_frame_ready(document_id, scroll, render_frame, frame_build_time);
         }
     }
 
@@ -1282,16 +1324,18 @@ impl RenderBackend {
                 removed_pipelines: Vec::new(),
                 view: view.clone(),
                 clip_scroll_tree: ClipScrollTree::new(),
                 frame_id: FrameId(0),
                 frame_builder: Some(FrameBuilder::empty()),
                 output_pipelines: FastHashSet::default(),
                 dynamic_properties: SceneProperties::new(),
                 hit_tester: None,
+                frame_is_valid: false,
+                hit_tester_is_valid: false,
             };
 
             let frame_name = format!("frame-{}-{}", (id.0).0, id.1);
             let frame = CaptureConfig::deserialize::<Frame, _>(root, frame_name);
             let build_frame = match frame {
                 Some(frame) => {
                     info!("\tloaded a built frame with {} passes", frame.passes.len());
 
--- a/gfx/webrender/src/resource_cache.rs
+++ b/gfx/webrender/src/resource_cache.rs
@@ -37,16 +37,17 @@ use std::collections::hash_map::ValuesMu
 use std::{cmp, mem};
 use std::fmt::Debug;
 use std::hash::Hash;
 #[cfg(any(feature = "capture", feature = "replay"))]
 use std::path::PathBuf;
 use std::sync::{Arc, RwLock};
 use texture_cache::{TextureCache, TextureCacheHandle, Eviction};
 use tiling::SpecialRenderPasses;
+use util::drain_filter;
 
 const DEFAULT_TILE_SIZE: TileSize = 512;
 
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 pub struct GlyphFetchResult {
     pub index_in_text_run: i32,
     pub uv_rect_address: GpuCacheAddress,
@@ -513,19 +514,18 @@ impl ResourceCache {
         }
     }
 
     pub fn pre_scene_building_update(
         &mut self,
         updates: &mut Vec<ResourceUpdate>,
         profile_counters: &mut ResourceProfileCounters,
     ) {
-        let mut new_updates = Vec::with_capacity(updates.len());
-        for update in mem::replace(updates, Vec::new()) {
-            match update {
+        for update in updates.iter() {
+            match *update {
                 ResourceUpdate::AddImage(ref img) => {
                     if let ImageData::Blob(ref blob_data) = img.data {
                         self.add_blob_image(
                             img.key,
                             &img.descriptor,
                             img.tiling,
                             Arc::clone(blob_data),
                         );
@@ -536,59 +536,64 @@ impl ResourceCache {
                         self.update_blob_image(
                             img.key,
                             &img.descriptor,
                             &img.dirty_rect,
                             Arc::clone(blob_data)
                         );
                     }
                 }
-                ResourceUpdate::SetImageVisibleArea(key, area) => {
+                ResourceUpdate::SetImageVisibleArea(ref key, ref area) => {
                     if let Some(template) = self.blob_image_templates.get_mut(&key) {
                         if let Some(tile_size) = template.tiling {
                             template.viewport_tiles = Some(compute_tile_range(
                                 &area,
                                 &template.descriptor.size,
                                 tile_size,
                             ));
                         }
                     }
                 }
                 _ => {}
             }
+        }
 
-            match update {
+        drain_filter(
+            updates,
+            |update| match *update {
+                ResourceUpdate::AddFont(_) |
+                ResourceUpdate::AddFontInstance(_) => true,
+                _ => false,
+            },
+            // Updates that were moved out of the array:
+            |update: ResourceUpdate| match update {
                 ResourceUpdate::AddFont(font) => {
                     match font {
                         AddFont::Raw(id, bytes, index) => {
                             profile_counters.font_templates.inc(bytes.len());
                             self.add_font_template(id, FontTemplate::Raw(Arc::new(bytes), index));
                         }
                         AddFont::Native(id, native_font_handle) => {
                             self.add_font_template(id, FontTemplate::Native(native_font_handle));
                         }
                     }
                 }
-                ResourceUpdate::AddFontInstance(mut instance) => {
+                ResourceUpdate::AddFontInstance(instance) => {
                     self.add_font_instance(
                         instance.key,
                         instance.font_key,
                         instance.glyph_size,
                         instance.options,
                         instance.platform_options,
                         instance.variations,
                     );
                 }
-                other => {
-                    new_updates.push(other);
-                }
+                _ => { unreachable!(); }
             }
-        }
-
-        *updates = new_updates;
+        );
     }
 
     pub fn set_blob_rasterizer(&mut self, rasterizer: Box<AsyncBlobImageRasterizer>) {
         self.blob_image_rasterizer = Some(rasterizer);
     }
 
     pub fn add_rasterized_blob_images(&mut self, images: Vec<(BlobImageRequest, BlobImageResult)>) {
         for (request, result) in images {
--- a/gfx/webrender/src/scene_builder.rs
+++ b/gfx/webrender/src/scene_builder.rs
@@ -1,40 +1,42 @@
 /* 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/. */
 
 use api::{AsyncBlobImageRasterizer, BlobImageRequest, BlobImageParams, BlobImageResult};
 use api::{DocumentId, PipelineId, ApiMsg, FrameMsg, ResourceUpdate, Epoch};
-use api::{BuiltDisplayList, ColorF, LayoutSize};
+use api::{BuiltDisplayList, ColorF, LayoutSize, NotificationRequest, Checkpoint};
 use api::channel::MsgSender;
 use frame_builder::{FrameBuilderConfig, FrameBuilder};
 use clip_scroll_tree::ClipScrollTree;
 use display_list_flattener::DisplayListFlattener;
 use internal_types::{FastHashMap, FastHashSet};
 use resource_cache::FontInstanceMap;
 use render_backend::DocumentView;
 use renderer::{PipelineInfo, SceneBuilderHooks};
 use scene::Scene;
 use std::sync::mpsc::{channel, Receiver, Sender};
 use std::mem::replace;
 use time::precise_time_ns;
+use util::drain_filter;
 
 /// Represents the work associated to a transaction before scene building.
 pub struct Transaction {
     pub document_id: DocumentId,
     pub display_list_updates: Vec<DisplayListUpdate>,
     pub removed_pipelines: Vec<PipelineId>,
     pub epoch_updates: Vec<(PipelineId, Epoch)>,
     pub request_scene_build: Option<SceneRequest>,
     pub blob_requests: Vec<BlobImageParams>,
     pub blob_rasterizer: Option<Box<AsyncBlobImageRasterizer>>,
     pub rasterized_blobs: Vec<(BlobImageRequest, BlobImageResult)>,
     pub resource_updates: Vec<ResourceUpdate>,
     pub frame_ops: Vec<FrameMsg>,
+    pub notifications: Vec<NotificationRequest>,
     pub set_root_pipeline: Option<PipelineId>,
     pub build_frame: bool,
     pub render_frame: bool,
 }
 
 impl Transaction {
     pub fn can_skip_scene_builder(&self) -> bool {
         self.request_scene_build.is_none() &&
@@ -56,16 +58,17 @@ impl Transaction {
 pub struct BuiltTransaction {
     pub document_id: DocumentId,
     pub built_scene: Option<BuiltScene>,
     pub resource_updates: Vec<ResourceUpdate>,
     pub rasterized_blobs: Vec<(BlobImageRequest, BlobImageResult)>,
     pub blob_rasterizer: Option<Box<AsyncBlobImageRasterizer>>,
     pub frame_ops: Vec<FrameMsg>,
     pub removed_pipelines: Vec<PipelineId>,
+    pub notifications: Vec<NotificationRequest>,
     pub scene_build_start_time: u64,
     pub scene_build_end_time: u64,
     pub build_frame: bool,
     pub render_frame: bool,
 }
 
 pub struct DisplayListUpdate {
     pub pipeline_id: PipelineId,
@@ -246,16 +249,17 @@ impl SceneBuilder {
                 build_frame: true,
                 render_frame: item.build_frame,
                 built_scene,
                 resource_updates: Vec::new(),
                 rasterized_blobs: Vec::new(),
                 blob_rasterizer: None,
                 frame_ops: Vec::new(),
                 removed_pipelines: Vec::new(),
+                notifications: Vec::new(),
                 scene_build_start_time,
                 scene_build_end_time: precise_time_ns(),
             });
 
             self.forward_built_transaction(txn);
         }
     }
 
@@ -317,26 +321,33 @@ impl SceneBuilder {
 
         let blob_requests = replace(&mut txn.blob_requests, Vec::new());
         let mut rasterized_blobs = txn.blob_rasterizer.as_mut().map_or(
             Vec::new(),
             |rasterizer| rasterizer.rasterize(&blob_requests),
         );
         rasterized_blobs.append(&mut txn.rasterized_blobs);
 
+        drain_filter(
+            &mut txn.notifications,
+            |n| { n.when() == Checkpoint::SceneBuilt },
+            |n| { n.notify(); },
+        );
+
         Box::new(BuiltTransaction {
             document_id: txn.document_id,
             build_frame: txn.build_frame || built_scene.is_some(),
             render_frame: txn.render_frame,
             built_scene,
             rasterized_blobs,
             resource_updates: replace(&mut txn.resource_updates, Vec::new()),
             blob_rasterizer: replace(&mut txn.blob_rasterizer, None),
             frame_ops: replace(&mut txn.frame_ops, Vec::new()),
             removed_pipelines: replace(&mut txn.removed_pipelines, Vec::new()),
+            notifications: replace(&mut txn.notifications, Vec::new()),
             scene_build_start_time,
             scene_build_end_time: precise_time_ns(),
         })
     }
 
     /// Send the result of process_transaction back to the render backend.
     fn forward_built_transaction(&mut self, txn: Box<BuiltTransaction>) {
         // We only need the pipeline info and the result channel if we
--- a/gfx/webrender/src/spatial_node.rs
+++ b/gfx/webrender/src/spatial_node.rs
@@ -1,21 +1,21 @@
 
 /* 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/. */
 
 use api::{ExternalScrollId, LayoutPixel, LayoutPoint, LayoutRect, LayoutSize, LayoutTransform};
 use api::{LayoutVector2D, PipelineId, PropertyBinding, ScrollClamping, ScrollLocation};
-use api::{ScrollSensitivity, StickyOffsetBounds, LayoutVector3D};
+use api::{ScrollSensitivity, StickyOffsetBounds};
 use clip_scroll_tree::{CoordinateSystem, CoordinateSystemId, SpatialNodeIndex, TransformUpdateState};
 use euclid::SideOffsets2D;
 use gpu_types::TransformPalette;
 use scene::SceneProperties;
-use util::{LayoutFastTransform, LayoutToWorldFastTransform, MatrixHelpers, TransformedRectKind};
+use util::{LayoutFastTransform, LayoutToWorldFastTransform, ScaleOffset, TransformedRectKind};
 
 #[derive(Clone, Debug)]
 pub enum SpatialNodeType {
     /// A special kind of node that adjusts its position based on the position
     /// of its parent node and a given set of sticky positioning offset bounds.
     /// Sticky positioned is described in the CSS Positioned Layout Module Level 3 here:
     /// https://www.w3.org/TR/css-position-3/#sticky-pos
     StickyFrame(StickyFrameInfo),
@@ -61,17 +61,17 @@ pub struct SpatialNode {
     pub invertible: bool,
 
     /// The axis-aligned coordinate system id of this node.
     pub coordinate_system_id: CoordinateSystemId,
 
     /// The transformation from the coordinate system which established our compatible coordinate
     /// system (same coordinate system id) and us. This can change via scroll offsets and via new
     /// reference frame transforms.
-    pub coordinate_system_relative_offset: LayoutVector2D,
+    pub coordinate_system_relative_scale_offset: ScaleOffset,
 }
 
 impl SpatialNode {
     pub fn new(
         pipeline_id: PipelineId,
         parent_index: Option<SpatialNodeIndex>,
         node_type: SpatialNodeType,
     ) -> Self {
@@ -80,17 +80,17 @@ impl SpatialNode {
             world_content_transform: LayoutToWorldFastTransform::identity(),
             transform_kind: TransformedRectKind::AxisAligned,
             parent: parent_index,
             children: Vec::new(),
             pipeline_id,
             node_type,
             invertible: true,
             coordinate_system_id: CoordinateSystemId(0),
-            coordinate_system_relative_offset: LayoutVector2D::zero(),
+            coordinate_system_relative_scale_offset: ScaleOffset::identity(),
         }
     }
 
     pub fn new_scroll_frame(
         pipeline_id: PipelineId,
         parent_index: SpatialNodeIndex,
         external_id: Option<ExternalScrollId>,
         frame_rect: &LayoutRect,
@@ -270,37 +270,38 @@ impl SpatialNode {
 
                 info.invertible = self.world_viewport_transform.is_invertible();
                 if !info.invertible {
                     return;
                 }
 
                 // Try to update our compatible coordinate system transform. If we cannot, start a new
                 // incompatible coordinate system.
-                if relative_transform.is_simple_2d_translation() {
-                    self.coordinate_system_relative_offset =
-                        state.coordinate_system_relative_offset +
-                        LayoutVector2D::new(relative_transform.m41, relative_transform.m42);
-                } else {
-                    // If we break 2D axis alignment or have a perspective component, we need to start a
-                    // new incompatible coordinate system with which we cannot share clips without masking.
-                    self.coordinate_system_relative_offset = LayoutVector2D::zero();
+                match ScaleOffset::from_transform(&relative_transform) {
+                    Some(ref scale_offset) => {
+                        self.coordinate_system_relative_scale_offset =
+                            state.coordinate_system_relative_scale_offset.accumulate(scale_offset);
+                    }
+                    None => {
+                        // If we break 2D axis alignment or have a perspective component, we need to start a
+                        // new incompatible coordinate system with which we cannot share clips without masking.
+                        self.coordinate_system_relative_scale_offset = ScaleOffset::identity();
 
-                    // Push that new coordinate system and record the new id.
-                    let coord_system = CoordinateSystem {
-                        offset: LayoutVector3D::new(
-                            state.coordinate_system_relative_offset.x,
-                            state.coordinate_system_relative_offset.y,
-                            0.0,
-                        ),
-                        transform: relative_transform,
-                        parent: Some(state.current_coordinate_system_id),
-                    };
-                    state.current_coordinate_system_id = CoordinateSystemId(coord_systems.len() as u32);
-                    coord_systems.push(coord_system);
+                        let transform = state.coordinate_system_relative_scale_offset
+                                             .to_transform()
+                                             .pre_mul(&relative_transform);
+
+                        // Push that new coordinate system and record the new id.
+                        let coord_system = CoordinateSystem {
+                            transform,
+                            parent: Some(state.current_coordinate_system_id),
+                        };
+                        state.current_coordinate_system_id = CoordinateSystemId(coord_systems.len() as u32);
+                        coord_systems.push(coord_system);
+                    }
                 }
 
                 self.coordinate_system_id = state.current_coordinate_system_id;
             }
             _ => {
                 // We calculate this here to avoid a double-borrow later.
                 let sticky_offset = self.calculate_sticky_offset(
                     &state.nearest_scrolling_ancestor_offset,
@@ -322,18 +323,18 @@ impl SpatialNode {
                 let scroll_offset = self.scroll_offset();
                 self.world_content_transform = if scroll_offset != LayoutVector2D::zero() {
                     self.world_viewport_transform.pre_translate(&scroll_offset)
                 } else {
                     self.world_viewport_transform
                 };
 
                 let added_offset = state.parent_accumulated_scroll_offset + sticky_offset + scroll_offset;
-                self.coordinate_system_relative_offset =
-                    state.coordinate_system_relative_offset + added_offset;
+                self.coordinate_system_relative_scale_offset =
+                    state.coordinate_system_relative_scale_offset.offset(added_offset.to_untyped());
 
                 if let SpatialNodeType::StickyFrame(ref mut info) = self.node_type {
                     info.current_offset = sticky_offset;
                 }
 
                 self.coordinate_system_id = state.current_coordinate_system_id;
             }
         }
@@ -470,17 +471,17 @@ impl SpatialNode {
                 state.parent_accumulated_scroll_offset =
                     scrolling.offset + state.parent_accumulated_scroll_offset;
                 state.nearest_scrolling_ancestor_offset = scrolling.offset;
                 state.nearest_scrolling_ancestor_viewport = scrolling.viewport_rect;
             }
             SpatialNodeType::ReferenceFrame(ref info) => {
                 state.parent_reference_frame_transform = self.world_viewport_transform;
                 state.parent_accumulated_scroll_offset = LayoutVector2D::zero();
-                state.coordinate_system_relative_offset = self.coordinate_system_relative_offset;
+                state.coordinate_system_relative_scale_offset = self.coordinate_system_relative_scale_offset;
                 let translation = -info.origin_in_parent_reference_frame;
                 state.nearest_scrolling_ancestor_viewport =
                     state.nearest_scrolling_ancestor_viewport
                        .translate(&translation);
             }
         }
     }
 
--- a/gfx/webrender/src/util.rs
+++ b/gfx/webrender/src/util.rs
@@ -1,25 +1,180 @@
 /* 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/. */
 
 use api::{BorderRadius, DeviceIntPoint, DeviceIntRect, DeviceIntSize, DevicePixelScale};
 use api::{LayoutPixel, DeviceRect, WorldPixel, RasterRect};
-use euclid::{Point2D, Rect, Size2D, TypedPoint2D, TypedRect, TypedSize2D};
+use euclid::{Point2D, Rect, Size2D, TypedPoint2D, TypedRect, TypedSize2D, Vector2D};
 use euclid::{TypedTransform2D, TypedTransform3D, TypedVector2D, TypedScale};
 use num_traits::Zero;
 use plane_split::{Clipper, Polygon};
 use std::{i32, f32, fmt};
 use std::borrow::Cow;
 
-
 // Matches the definition of SK_ScalarNearlyZero in Skia.
 const NEARLY_ZERO: f32 = 1.0 / 4096.0;
 
+// Represents an optimized transform where there is only
+// a scale and translation (which are guaranteed to maintain
+// an axis align rectangle under transformation). The
+// scaling is applied first, followed by the translation.
+// TODO(gw): We should try and incorporate F <-> T units here,
+//           but it's a bit tricky to do that now with the
+//           way the current clip-scroll tree works.
+#[derive(Debug, Clone, Copy)]
+pub struct ScaleOffset {
+    pub scale: Vector2D<f32>,
+    pub offset: Vector2D<f32>,
+}
+
+impl ScaleOffset {
+    pub fn identity() -> Self {
+        ScaleOffset {
+            scale: Vector2D::new(1.0, 1.0),
+            offset: Vector2D::zero(),
+        }
+    }
+
+    // Construct a ScaleOffset from a transform. Returns
+    // None if the matrix is not a pure scale / translation.
+    pub fn from_transform<F, T>(
+        m: &TypedTransform3D<f32, F, T>,
+    ) -> Option<ScaleOffset> {
+
+        // To check that we have a pure scale / translation:
+        // Every field must match an identity matrix, except:
+        //  - Any value present in tx,ty
+        //  - Any non-neg value present in sx,sy (avoid negative for reflection/rotation)
+
+        if m.m11 < 0.0 ||
+           m.m12.abs() > NEARLY_ZERO ||
+           m.m13.abs() > NEARLY_ZERO ||
+           m.m14.abs() > NEARLY_ZERO ||
+           m.m21.abs() > NEARLY_ZERO ||
+           m.m22 < 0.0 ||
+           m.m23.abs() > NEARLY_ZERO ||
+           m.m24.abs() > NEARLY_ZERO ||
+           m.m31.abs() > NEARLY_ZERO ||
+           m.m32.abs() > NEARLY_ZERO ||
+           (m.m33 - 1.0).abs() > NEARLY_ZERO ||
+           m.m34.abs() > NEARLY_ZERO ||
+           m.m43.abs() > NEARLY_ZERO ||
+           (m.m44 - 1.0).abs() > NEARLY_ZERO {
+            return None;
+        }
+
+        Some(ScaleOffset {
+            scale: Vector2D::new(m.m11, m.m22),
+            offset: Vector2D::new(m.m41, m.m42),
+        })
+    }
+
+    pub fn inverse(&self) -> Self {
+        ScaleOffset {
+            scale: Vector2D::new(
+                1.0 / self.scale.x,
+                1.0 / self.scale.y,
+            ),
+            offset: Vector2D::new(
+                -self.offset.x / self.scale.x,
+                -self.offset.y / self.scale.y,
+            ),
+        }
+    }
+
+    pub fn offset(&self, offset: Vector2D<f32>) -> Self {
+        ScaleOffset {
+            scale: self.scale,
+            offset: self.offset + offset,
+        }
+    }
+
+    // Produce a ScaleOffset that includes both self
+    // and other. The 'self' ScaleOffset is applied
+    // after other.
+    pub fn accumulate(&self, other: &ScaleOffset) -> Self {
+        ScaleOffset {
+            scale: Vector2D::new(
+                self.scale.x * other.scale.x,
+                self.scale.y * other.scale.y,
+            ),
+            offset: Vector2D::new(
+                self.offset.x + self.scale.x * other.offset.x,
+                self.offset.y + self.scale.y * other.offset.y,
+            ),
+        }
+    }
+
+    // Find the difference between two ScaleOffset types.
+    pub fn difference(&self, other: &ScaleOffset) -> Self {
+        ScaleOffset {
+            scale: Vector2D::new(
+                other.scale.x / self.scale.x,
+                other.scale.y / self.scale.y,
+            ),
+            offset: Vector2D::new(
+                (other.offset.x - self.offset.x) / self.scale.x,
+                (other.offset.y - self.offset.y) / self.scale.y,
+            ),
+        }
+    }
+
+    pub fn map_rect<F, T>(&self, rect: &TypedRect<f32, F>) -> TypedRect<f32, T> {
+        TypedRect::new(
+            TypedPoint2D::new(
+                rect.origin.x * self.scale.x + self.offset.x,
+                rect.origin.y * self.scale.y + self.offset.y,
+            ),
+            TypedSize2D::new(
+                rect.size.width * self.scale.x,
+                rect.size.height * self.scale.y,
+            )
+        )
+    }
+
+    pub fn unmap_rect<F, T>(&self, rect: &TypedRect<f32, F>) -> TypedRect<f32, T> {
+        TypedRect::new(
+            TypedPoint2D::new(
+                (rect.origin.x - self.offset.x) / self.scale.x,
+                (rect.origin.y - self.offset.y) / self.scale.y,
+            ),
+            TypedSize2D::new(
+                rect.size.width / self.scale.x,
+                rect.size.height / self.scale.y,
+            )
+        )
+    }
+
+    pub fn to_transform<F, T>(&self) -> TypedTransform3D<f32, F, T> {
+        TypedTransform3D::row_major(
+            self.scale.x,
+            0.0,
+            0.0,
+            0.0,
+
+            0.0,
+            self.scale.y,
+            0.0,
+            0.0,
+
+            0.0,
+            0.0,
+            1.0,
+            0.0,
+
+            self.offset.x,
+            self.offset.y,
+            0.0,
+            1.0,
+        )
+    }
+}
+
 // TODO: Implement these in euclid!
 pub trait MatrixHelpers<Src, Dst> {
     fn preserves_2d_axis_alignment(&self) -> bool;
     fn has_perspective_component(&self) -> bool;
     fn has_2d_inverse(&self) -> bool;
     fn exceeds_2d_scale(&self, limit: f64) -> bool;
     fn inverse_project(&self, target: &TypedPoint2D<f32, Dst>) -> Option<TypedPoint2D<f32, Src>>;
     fn inverse_rect_footprint(&self, rect: &TypedRect<f32, Dst>) -> Option<TypedRect<f32, Src>>;
@@ -507,8 +662,68 @@ pub fn project_rect<F, T>(
 pub fn raster_rect_to_device_pixels(
     rect: RasterRect,
     device_pixel_scale: DevicePixelScale,
 ) -> DeviceRect {
     let world_rect = rect * TypedScale::new(1.0);
     let device_rect = world_rect * device_pixel_scale;
     device_rect.round_out()
 }
+
+/// Run the first callback over all elements in the array. If the callback returns true,
+/// the element is removed from the array and moved to a second callback.
+///
+/// This is a simple implementation waiting for Vec::drain_filter to be stable.
+/// When that happens, code like:
+///
+/// let filter = |op| {
+///     match *op {
+///         Enum::Foo | Enum::Bar => true,
+///         Enum::Baz => false,
+///     }
+/// };
+/// drain_filter(
+///     &mut ops,
+///     filter,
+///     |op| {
+///         match op {
+///             Enum::Foo => { foo(); }
+///             Enum::Bar => { bar(); }
+///             Enum::Baz => { unreachable!(); }
+///         }
+///     },
+/// );
+///
+/// Can be rewritten as:
+///
+/// let filter = |op| {
+///     match *op {
+///         Enum::Foo | Enum::Bar => true,
+///         Enum::Baz => false,
+///     }
+/// };
+/// for op in ops.drain_filter(filter) {
+///     match op {
+///         Enum::Foo => { foo(); }
+///         Enum::Bar => { bar(); }
+///         Enum::Baz => { unreachable!(); }
+///     }
+/// }
+///
+/// See https://doc.rust-lang.org/std/vec/struct.Vec.html#method.drain_filter
+pub fn drain_filter<T, Filter, Action>(
+    vec: &mut Vec<T>,
+    mut filter: Filter,
+    mut action: Action,
+)
+where
+    Filter: FnMut(&mut T) -> bool,
+    Action: FnMut(T)
+{
+    let mut i = 0;
+    while i != vec.len() {
+        if filter(&mut vec[i]) {
+            action(vec.remove(i));
+        } else {
+            i += 1;
+        }
+    }
+}
--- a/gfx/webrender_api/src/api.rs
+++ b/gfx/webrender_api/src/api.rs
@@ -5,16 +5,17 @@
 extern crate serde_bytes;
 
 use app_units::Au;
 use channel::{self, MsgSender, Payload, PayloadSender, PayloadSenderHelperMethods};
 use std::cell::Cell;
 use std::fmt;
 use std::marker::PhantomData;
 use std::path::PathBuf;
+use std::sync::Arc;
 use std::u32;
 use {BuiltDisplayList, BuiltDisplayListDescriptor, ColorF, DeviceIntPoint, DeviceUintRect};
 use {DeviceUintSize, ExternalScrollId, FontInstanceKey, FontInstanceOptions};
 use {FontInstancePlatformOptions, FontKey, FontVariation, GlyphDimensions, GlyphIndex, ImageData};
 use {ImageDescriptor, ImageKey, ItemTag, LayoutPoint, LayoutSize, LayoutTransform, LayoutVector2D};
 use {NativeFontHandle, WorldPoint, NormalizedRect};
 
 pub type TileSize = u16;
@@ -43,16 +44,18 @@ pub struct Transaction {
     // Operations affecting the scene (applied before scene building).
     scene_ops: Vec<SceneMsg>,
     // Operations affecting the generation of frames (applied after scene building).
     frame_ops: Vec<FrameMsg>,
 
     // Additional display list data.
     payloads: Vec<Payload>,
 
+    notifications: Vec<NotificationRequest>,
+
     // Resource updates are applied after scene building.
     pub resource_updates: Vec<ResourceUpdate>,
 
     // If true the transaction is piped through the scene building thread, if false
     // it will be applied directly on the render backend.
     use_scene_builder_thread: bool,
 
     generate_frame: bool,
@@ -62,16 +65,17 @@ pub struct Transaction {
 
 impl Transaction {
     pub fn new() -> Self {
         Transaction {
             scene_ops: Vec::new(),
             frame_ops: Vec::new(),
             resource_updates: Vec::new(),
             payloads: Vec::new(),
+            notifications: Vec::new(),
             use_scene_builder_thread: true,
             generate_frame: false,
             low_priority: false,
         }
     }
 
     // TODO: better name?
     pub fn skip_scene_builder(&mut self) {
@@ -83,17 +87,18 @@ impl Transaction {
     pub fn use_scene_builder_thread(&mut self) {
         self.use_scene_builder_thread = true;
     }
 
     pub fn is_empty(&self) -> bool {
         !self.generate_frame &&
             self.scene_ops.is_empty() &&
             self.frame_ops.is_empty() &&
-            self.resource_updates.is_empty()
+            self.resource_updates.is_empty() &&
+            self.notifications.is_empty()
     }
 
     pub fn update_epoch(&mut self, pipeline_id: PipelineId, epoch: Epoch) {
         // We track epochs before and after scene building.
         // This one will be applied to the pending scene right away:
         self.scene_ops.push(SceneMsg::UpdateEpoch(pipeline_id, epoch));
         // And this one will be applied to the currently built scene at the end
         // of the transaction (potentially long after the scene_ops one).
@@ -166,16 +171,31 @@ impl Transaction {
         );
         self.payloads.push(Payload { epoch, pipeline_id, display_list_data });
     }
 
     pub fn update_resources(&mut self, resources: Vec<ResourceUpdate>) {
         self.merge(resources);
     }
 
+    // Note: Gecko uses this to get notified when a transaction that contains
+    // potentially long blob rasterization or scene build is ready to be rendered.
+    // so that the tab-switching integration can react adequately when tab
+    // switching takes too long. For this use case when matters is that the
+    // notification doesn't fire before scene building and blob rasterization.
+
+    /// Trigger a notification at a certain stage of the rendering pipeline.
+    ///
+    /// Not that notification requests are skipped during serialization, so is is
+    /// best to use them for synchronization purposes and not for things that could
+    /// affect the WebRender's state.
+    pub fn notify(&mut self, event: NotificationRequest) {
+        self.notifications.push(event);
+    }
+
     pub fn set_window_parameters(
         &mut self,
         window_size: DeviceUintSize,
         inner_rect: DeviceUintRect,
         device_pixel_ratio: f32,
     ) {
         self.scene_ops.push(
             SceneMsg::SetWindowParameters {
@@ -214,17 +234,17 @@ impl Transaction {
     pub fn set_pan(&mut self, pan: DeviceIntPoint) {
         self.frame_ops.push(FrameMsg::SetPan(pan));
     }
 
     /// Generate a new frame. When it's done and a RenderNotifier has been set
     /// in `webrender::Renderer`, [new_frame_ready()][notifier] gets called.
     /// Note that the notifier is called even if the frame generation was a
     /// no-op; the arguments passed to `new_frame_ready` will provide information
-    /// as to what happened.
+    /// as to when happened.
     ///
     /// [notifier]: trait.RenderNotifier.html#tymethod.new_frame_ready
     pub fn generate_frame(&mut self) {
         self.generate_frame = true;
     }
 
     /// Supply a list of animated property bindings that should be used to resolve
     /// bindings in the current display list.
@@ -252,16 +272,17 @@ impl Transaction {
     }
 
     fn finalize(self) -> (TransactionMsg, Vec<Payload>) {
         (
             TransactionMsg {
                 scene_ops: self.scene_ops,
                 frame_ops: self.frame_ops,
                 resource_updates: self.resource_updates,
+                notifications: self.notifications,
                 use_scene_builder_thread: self.use_scene_builder_thread,
                 generate_frame: self.generate_frame,
                 low_priority: self.low_priority,
             },
             self.payloads,
         )
     }
 
@@ -366,43 +387,49 @@ impl Transaction {
 #[derive(Clone, Deserialize, Serialize)]
 pub struct TransactionMsg {
     pub scene_ops: Vec<SceneMsg>,
     pub frame_ops: Vec<FrameMsg>,
     pub resource_updates: Vec<ResourceUpdate>,
     pub generate_frame: bool,
     pub use_scene_builder_thread: bool,
     pub low_priority: bool,
+
+    #[serde(skip)]
+    pub notifications: Vec<NotificationRequest>,
 }
 
 impl TransactionMsg {
     pub fn is_empty(&self) -> bool {
         !self.generate_frame &&
             self.scene_ops.is_empty() &&
             self.frame_ops.is_empty() &&
-            self.resource_updates.is_empty()
+            self.resource_updates.is_empty() &&
+            self.notifications.is_empty()
     }
 
     // TODO: We only need this for a few RenderApi methods which we should remove.
     pub fn frame_message(msg: FrameMsg) -> Self {
         TransactionMsg {
             scene_ops: Vec::new(),
             frame_ops: vec![msg],
             resource_updates: Vec::new(),
+            notifications: Vec::new(),
             generate_frame: false,
             use_scene_builder_thread: false,
             low_priority: false,
         }
     }
 
     pub fn scene_message(msg: SceneMsg) -> Self {
         TransactionMsg {
             scene_ops: vec![msg],
             frame_ops: Vec::new(),
             resource_updates: Vec::new(),
+            notifications: Vec::new(),
             generate_frame: false,
             use_scene_builder_thread: false,
             low_priority: false,
         }
     }
 }
 
 #[derive(Clone, Deserialize, Serialize)]
@@ -1135,8 +1162,53 @@ pub trait RenderNotifier: Send {
     fn clone(&self) -> Box<RenderNotifier>;
     fn wake_up(&self);
     fn new_frame_ready(&self, DocumentId, scrolled: bool, composite_needed: bool, render_time_ns: Option<u64>);
     fn external_event(&self, _evt: ExternalEvent) {
         unimplemented!()
     }
     fn shut_down(&self) {}
 }
+
+#[derive(Copy, Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
+pub enum Checkpoint {
+    SceneBuilt,
+    FrameBuilt,
+    /// NotificationRequests get notified with this if they get dropped without having been
+    /// notified. This provides the guarantee that if a request is created it will get notified.
+    TransactionDropped,
+}
+
+pub trait NotificationHandler : Send + Sync {
+    fn notify(&self, when: Checkpoint);
+}
+
+#[derive(Clone)]
+pub struct NotificationRequest {
+    handler: Arc<NotificationHandler>,
+    when: Checkpoint,
+    done: bool,
+}
+
+impl NotificationRequest {
+    pub fn new(when: Checkpoint, handler: Arc<NotificationHandler>) -> Self {
+        NotificationRequest {
+            handler,
+            when,
+            done: false,
+        }
+    }
+
+    pub fn when(&self) -> Checkpoint { self.when }
+
+    pub fn notify(mut self) {
+        self.handler.notify(self.when);
+        self.done = true;
+    }
+}
+
+impl Drop for NotificationRequest {
+    fn drop(&mut self) {
+        if !self.done {
+            self.handler.notify(Checkpoint::TransactionDropped);
+        }
+    }
+}
--- a/gfx/webrender_bindings/revision.txt
+++ b/gfx/webrender_bindings/revision.txt
@@ -1,1 +1,1 @@
-02f14d0f333ef125d1abff7b1146039a0ba75f43
+70edb5f8a75ea1e1440ba7984cc42df9eb05ae69
--- a/intl/chardet/nsCyrillicDetector.cpp
+++ b/intl/chardet/nsCyrillicDetector.cpp
@@ -11,17 +11,16 @@
 #include "nsICharsetDetector.h"
 #include "nsICharsetDetectionObserver.h"
 #include "nsIStringCharsetDetector.h"
 #include "nsCyrillicDetector.h"
 
 //----------------------------------------------------------------------
 // Interface nsISupports [implementation]
 NS_IMPL_ISUPPORTS(nsCyrXPCOMDetector, nsICharsetDetector)
-NS_IMPL_ISUPPORTS(nsCyrXPCOMStringDetector, nsIStringCharsetDetector)
 
 void nsCyrillicDetector::HandleData(const char* aBuf, uint32_t aLen)
 {
    uint8_t cls;
    const char* b;
    uint32_t i;
    if(mDone)
       return;
@@ -119,43 +118,8 @@ NS_IMETHODIMP nsCyrXPCOMDetector::Done()
 }
 
 //----------------------------------------------------------
 void nsCyrXPCOMDetector::Report(const char* aCharset)
 {
   NS_ASSERTION(mObserver != nullptr , "have not init yet");
   mObserver->Notify(aCharset, eBestAnswer);
 }
-
-//---------------------------------------------------------------------
-nsCyrXPCOMStringDetector:: nsCyrXPCOMStringDetector(uint8_t aItems,
-                      const uint8_t ** aCyrillicClass,
-                      const char **aCharsets)
-	     : nsCyrillicDetector(aItems, aCyrillicClass, aCharsets)
-	     , mResult(nullptr)
-{
-}
-
-//---------------------------------------------------------------------
-nsCyrXPCOMStringDetector::~nsCyrXPCOMStringDetector()
-{
-}
-
-//---------------------------------------------------------------------
-void nsCyrXPCOMStringDetector::Report(const char *aCharset)
-{
-   mResult = aCharset;
-}
-
-//---------------------------------------------------------------------
-NS_IMETHODIMP nsCyrXPCOMStringDetector::DoIt(const char* aBuf, uint32_t aLen,
-                     const char** oCharset, nsDetectionConfident &oConf)
-{
-   mResult = nullptr;
-   mDone = false;
-   this->HandleData(aBuf, aLen);
-   this->DataEnd();
-   *oCharset=mResult;
-   oConf = eBestAnswer;
-   return NS_OK;
-}
-
-
--- a/intl/chardet/nsCyrillicDetector.h
+++ b/intl/chardet/nsCyrillicDetector.h
@@ -3,24 +3,20 @@
  * 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/. */
 #ifndef nsCyrillicDetector_h__
 #define nsCyrillicDetector_h__
 
 #include "nsCyrillicClass.h"
 #include "nsIStringCharsetDetector.h"
 
-
-
-
 // {2002F781-3960-11d3-B3C3-00805F8A6670}
 #define NS_RU_PROBDETECTOR_CID \
 { 0x2002f781, 0x3960, 0x11d3, { 0xb3, 0xc3, 0x0, 0x80, 0x5f, 0x8a, 0x66, 0x70 } }
 
-
 // {2002F782-3960-11d3-B3C3-00805F8A6670}
 #define NS_UK_PROBDETECTOR_CID \
 { 0x2002f782, 0x3960, 0x11d3, { 0xb3, 0xc3, 0x0, 0x80, 0x5f, 0x8a, 0x66, 0x70 } }
 
 // {2002F783-3960-11d3-B3C3-00805F8A6670}
 #define NS_RU_STRING_PROBDETECTOR_CID \
 { 0x2002f783, 0x3960, 0x11d3, { 0xb3, 0xc3, 0x0, 0x80, 0x5f, 0x8a, 0x66, 0x70 } }
 
@@ -98,36 +94,16 @@ class nsCyrXPCOMDetector :
     NS_IMETHOD Done() override;
   protected:
     virtual ~nsCyrXPCOMDetector();
     virtual void Report(const char* aCharset) override;
   private:
     nsCOMPtr<nsICharsetDetectionObserver> mObserver;
 };
 
-class nsCyrXPCOMStringDetector :
-      public nsCyrillicDetector,
-      public nsIStringCharsetDetector
-{
-  public:
-    // nsISupports interface
-    NS_DECL_ISUPPORTS
-    nsCyrXPCOMStringDetector(uint8_t aItems,
-                      const uint8_t ** aCyrillicClass,
-                      const char **aCharsets);
-    NS_IMETHOD DoIt(const char* aBuf, uint32_t aLen,
-                     const char** oCharset, nsDetectionConfident &oConf) override;
-  protected:
-    virtual ~nsCyrXPCOMStringDetector();
-    virtual void Report(const char* aCharset) override;
-  private:
-    nsCOMPtr<nsICharsetDetectionObserver> mObserver;
-    const char* mResult;
-};
-
 class nsRUProbDetector final : public nsCyrXPCOMDetector
 {
   public:
     nsRUProbDetector()
       : nsCyrXPCOMDetector(5, gCyrillicCls, gRussian) {}
 };
 
 class nsUKProbDetector final : public nsCyrXPCOMDetector
--- a/js/src/builtin/ModuleObject.cpp
+++ b/js/src/builtin/ModuleObject.cpp
@@ -1361,41 +1361,49 @@ ModuleBuilder::initModule()
                                  localExportEntries,
                                  indirectExportEntries,
                                  starExportEntries);
 
     return true;
 }
 
 bool
-ModuleBuilder::processImport(frontend::ParseNode* pn)
+ModuleBuilder::processImport(frontend::BinaryNode* importNode)
 {
     using namespace js::frontend;
 
-    MOZ_ASSERT(pn->isKind(ParseNodeKind::Import));
-    MOZ_ASSERT(pn->isArity(PN_BINARY));
-    MOZ_ASSERT(pn->pn_left->isKind(ParseNodeKind::ImportSpecList));
-    MOZ_ASSERT(pn->pn_right->isKind(ParseNodeKind::String));
+    MOZ_ASSERT(importNode->isKind(ParseNodeKind::Import));
+
+    ListNode* specList = &importNode->left()->as<ListNode>();
+    MOZ_ASSERT(specList->isKind(ParseNodeKind::ImportSpecList));
 
-    RootedAtom module(cx_, pn->pn_right->pn_atom);
-    if (!maybeAppendRequestedModule(module, pn->pn_right)) {
+    NameNode* moduleSpec = &importNode->right()->as<NameNode>();
+    MOZ_ASSERT(moduleSpec->isKind(ParseNodeKind::String));
+
+    RootedAtom module(cx_, moduleSpec->atom());
+    if (!maybeAppendRequestedModule(module, moduleSpec)) {
         return false;
     }
 
-    for (ParseNode* spec = pn->pn_left->pn_head; spec; spec = spec->pn_next) {
+    RootedAtom importName(cx_);
+    RootedAtom localName(cx_);
+    for (ParseNode* item : specList->contents()) {
+        BinaryNode* spec = &item->as<BinaryNode>();
         MOZ_ASSERT(spec->isKind(ParseNodeKind::ImportSpec));
-        MOZ_ASSERT(spec->pn_left->isArity(PN_NAME));
-        MOZ_ASSERT(spec->pn_right->isArity(PN_NAME));
+
+        NameNode* importNameNode = &spec->left()->as<NameNode>();
 
-        RootedAtom importName(cx_, spec->pn_left->pn_atom);
-        RootedAtom localName(cx_, spec->pn_right->pn_atom);
+        NameNode* localNameNode = &spec->right()->as<NameNode>();
+
+        importName = importNameNode->atom();
+        localName = localNameNode->atom();
 
         uint32_t line;
         uint32_t column;
-        tokenStream_.lineAndColumnAt(spec->pn_left->pn_pos.begin, &line, &column);
+        tokenStream_.lineAndColumnAt(importNameNode->pn_pos.begin, &line, &column);
 
         RootedImportEntryObject importEntry(cx_);
         importEntry = ImportEntryObject::create(cx_, module, importName, localName, line, column);
         if (!importEntry || !appendImportEntryObject(importEntry)) {
             return false;
         }
     }
 
@@ -1405,90 +1413,100 @@ ModuleBuilder::processImport(frontend::P
 bool
 ModuleBuilder::appendImportEntryObject(HandleImportEntryObject importEntry)
 {
     MOZ_ASSERT(importEntry->localName());
     return importEntries_.put(importEntry->localName(), importEntry);
 }
 
 bool
-ModuleBuilder::processExport(frontend::ParseNode* pn)
+ModuleBuilder::processExport(frontend::ParseNode* exportNode)
 {
     using namespace js::frontend;
 
-    MOZ_ASSERT(pn->isKind(ParseNodeKind::Export) || pn->isKind(ParseNodeKind::ExportDefault));
-    MOZ_ASSERT(pn->getArity() == (pn->isKind(ParseNodeKind::Export) ? PN_UNARY : PN_BINARY));
+    MOZ_ASSERT(exportNode->isKind(ParseNodeKind::Export) ||
+               exportNode->isKind(ParseNodeKind::ExportDefault));
 
-    bool isDefault = pn->getKind() == ParseNodeKind::ExportDefault;
-    ParseNode* kid = isDefault ? pn->pn_left : pn->pn_kid;
+    bool isDefault = exportNode->isKind(ParseNodeKind::ExportDefault);
+    ParseNode* kid = isDefault
+                     ? exportNode->as<BinaryNode>().left()
+                     : exportNode->as<UnaryNode>().kid();
 
-    if (isDefault && pn->pn_right) {
+    if (isDefault && exportNode->as<BinaryNode>().right()) {
         // This is an export default containing an expression.
         HandlePropertyName localName = cx_->names().default_;
         HandlePropertyName exportName = cx_->names().default_;
         return appendExportEntry(exportName, localName);
     }
 
     switch (kid->getKind()) {
-      case ParseNodeKind::ExportSpecList:
+      case ParseNodeKind::ExportSpecList: {
         MOZ_ASSERT(!isDefault);
-        for (ParseNode* spec = kid->pn_head; spec; spec = spec->pn_next) {
+        RootedAtom localName(cx_);
+        RootedAtom exportName(cx_);
+        for (ParseNode* item : kid->as<ListNode>().contents()) {
+            BinaryNode* spec = &item->as<BinaryNode>();
             MOZ_ASSERT(spec->isKind(ParseNodeKind::ExportSpec));
-            RootedAtom localName(cx_, spec->pn_left->pn_atom);
-            RootedAtom exportName(cx_, spec->pn_right->pn_atom);
+
+            NameNode* localNameNode = &spec->left()->as<NameNode>();
+            NameNode* exportNameNode = &spec->right()->as<NameNode>();
+            localName = localNameNode->atom();
+            exportName = exportNameNode->atom();
             if (!appendExportEntry(exportName, localName, spec)) {
                 return false;
             }
         }
         break;
+      }
 
       case ParseNodeKind::Class: {
         const ClassNode& cls = kid->as<ClassNode>();
         MOZ_ASSERT(cls.names());
-        RootedAtom localName(cx_, cls.names()->innerBinding()->pn_atom);
+        RootedAtom localName(cx_, cls.names()->innerBinding()->atom());
         RootedAtom exportName(cx_, isDefault ? cx_->names().default_ : localName.get());
         if (!appendExportEntry(exportName, localName)) {
             return false;
         }
         break;
       }
 
       case ParseNodeKind::Var:
       case ParseNodeKind::Const:
       case ParseNodeKind::Let: {
-        MOZ_ASSERT(kid->isArity(PN_LIST));
-        for (ParseNode* binding = kid->pn_head; binding; binding = binding->pn_next) {
+        RootedAtom localName(cx_);
+        RootedAtom exportName(cx_);
+        for (ParseNode* binding : kid->as<ListNode>().contents()) {
             if (binding->isKind(ParseNodeKind::Assign)) {
-                binding = binding->pn_left;
+                binding = binding->as<AssignmentNode>().left();
             } else {
                 MOZ_ASSERT(binding->isKind(ParseNodeKind::Name));
             }
 
             if (binding->isKind(ParseNodeKind::Name)) {
-                RootedAtom localName(cx_, binding->pn_atom);
-                RootedAtom exportName(cx_, isDefault ? cx_->names().default_ : localName.get());
+                localName = binding->as<NameNode>().atom();
+                exportName = isDefault ? cx_->names().default_ : localName.get();
                 if (!appendExportEntry(exportName, localName)) {
                     return false;
                 }
             } else if (binding->isKind(ParseNodeKind::Array)) {
-                if (!processExportArrayBinding(binding)) {
+                if (!processExportArrayBinding(&binding->as<ListNode>())) {
                     return false;
                 }
             } else {
                 MOZ_ASSERT(binding->isKind(ParseNodeKind::Object));
-                if (!processExportObjectBinding(binding)) {
+                if (!processExportObjectBinding(&binding->as<ListNode>())) {
                     return false;
                 }
             }
         }
         break;
       }
 
       case ParseNodeKind::Function: {
-        RootedFunction func(cx_, kid->pn_funbox->function());
+        RootedFunction func(cx_, kid->as<CodeNode>().funbox()->function());
         MOZ_ASSERT(!func->isArrow());
         RootedAtom localName(cx_, func->explicitName());
         RootedAtom exportName(cx_, isDefault ? cx_->names().default_ : localName.get());
         MOZ_ASSERT_IF(isDefault, localName);
         if (!appendExportEntry(exportName, localName)) {
             return false;
         }
         break;
@@ -1502,118 +1520,123 @@ ModuleBuilder::processExport(frontend::P
 }
 
 bool
 ModuleBuilder::processExportBinding(frontend::ParseNode* binding)
 {
     using namespace js::frontend;
 
     if (binding->isKind(ParseNodeKind::Name)) {
-        RootedAtom name(cx_, binding->pn_atom);
+        RootedAtom name(cx_, binding->as<NameNode>().atom());
         return appendExportEntry(name, name);
     }
 
     if (binding->isKind(ParseNodeKind::Array)) {
-        return processExportArrayBinding(binding);
+        return processExportArrayBinding(&binding->as<ListNode>());
     }
 
     MOZ_ASSERT(binding->isKind(ParseNodeKind::Object));
-    return processExportObjectBinding(binding);
+    return processExportObjectBinding(&binding->as<ListNode>());
 }
 
 bool
-ModuleBuilder::processExportArrayBinding(frontend::ParseNode* pn)
+ModuleBuilder::processExportArrayBinding(frontend::ListNode* array)
 {
     using namespace js::frontend;
 
-    MOZ_ASSERT(pn->isKind(ParseNodeKind::Array));
-    MOZ_ASSERT(pn->isArity(PN_LIST));
+    MOZ_ASSERT(array->isKind(ParseNodeKind::Array));
 
-    for (ParseNode* node = pn->pn_head; node; node = node->pn_next) {
+    for (ParseNode* node : array->contents()) {
         if (node->isKind(ParseNodeKind::Elision)) {
             continue;
         }
 
         if (node->isKind(ParseNodeKind::Spread)) {
-            node = node->pn_kid;
+            node = node->as<UnaryNode>().kid();
         } else if (node->isKind(ParseNodeKind::Assign)) {
-            node = node->pn_left;
+            node = node->as<AssignmentNode>().left();
         }
 
         if (!processExportBinding(node)) {
             return false;
         }
     }
 
     return true;
 }
 
 bool
-ModuleBuilder::processExportObjectBinding(frontend::ParseNode* pn)
+ModuleBuilder::processExportObjectBinding(frontend::ListNode* obj)
 {
     using namespace js::frontend;
 
-    MOZ_ASSERT(pn->isKind(ParseNodeKind::Object));
-    MOZ_ASSERT(pn->isArity(PN_LIST));
+    MOZ_ASSERT(obj->isKind(ParseNodeKind::Object));
 
-    for (ParseNode* node = pn->pn_head; node; node = node->pn_next) {
+    for (ParseNode* node : obj->contents()) {
         MOZ_ASSERT(node->isKind(ParseNodeKind::MutateProto) ||
                    node->isKind(ParseNodeKind::Colon) ||
                    node->isKind(ParseNodeKind::Shorthand) ||
                    node->isKind(ParseNodeKind::Spread));
 
         ParseNode* target;
         if (node->isKind(ParseNodeKind::Spread)) {
-            target = node->pn_kid;
+            target = node->as<UnaryNode>().kid();
         } else {
             if (node->isKind(ParseNodeKind::MutateProto)) {
-                target = node->pn_kid;
+                target = node->as<UnaryNode>().kid();
             } else {
-                target = node->pn_right;
+                target = node->as<BinaryNode>().right();
             }
 
             if (target->isKind(ParseNodeKind::Assign)) {
-                target = target->pn_left;
+                target = target->as<AssignmentNode>().left();
             }
         }
 
         if (!processExportBinding(target)) {
             return false;
         }
     }
 
     return true;
 }
 
 bool
-ModuleBuilder::processExportFrom(frontend::ParseNode* pn)
+ModuleBuilder::processExportFrom(frontend::BinaryNode* exportNode)
 {
     using namespace js::frontend;
 
-    MOZ_ASSERT(pn->isKind(ParseNodeKind::ExportFrom));
-    MOZ_ASSERT(pn->isArity(PN_BINARY));
-    MOZ_ASSERT(pn->pn_left->isKind(ParseNodeKind::ExportSpecList));
-    MOZ_ASSERT(pn->pn_right->isKind(ParseNodeKind::String));
+    MOZ_ASSERT(exportNode->isKind(ParseNodeKind::ExportFrom));
+
+    ListNode* specList = &exportNode->left()->as<ListNode>();
+    MOZ_ASSERT(specList->isKind(ParseNodeKind::ExportSpecList));
 
-    RootedAtom module(cx_, pn->pn_right->pn_atom);
-    if (!maybeAppendRequestedModule(module, pn->pn_right)) {
+    NameNode* moduleSpec = &exportNode->right()->as<NameNode>();
+    MOZ_ASSERT(moduleSpec->isKind(ParseNodeKind::String));
+
+    RootedAtom module(cx_, moduleSpec->atom());
+    if (!maybeAppendRequestedModule(module, moduleSpec)) {
         return false;
     }
 
-    for (ParseNode* spec = pn->pn_left->pn_head; spec; spec = spec->pn_next) {
+    RootedAtom bindingName(cx_);
+    RootedAtom exportName(cx_);
+    for (ParseNode* spec : specList->contents()) {
         if (spec->isKind(ParseNodeKind::ExportSpec)) {
-            RootedAtom bindingName(cx_, spec->pn_left->pn_atom);
-            RootedAtom exportName(cx_, spec->pn_right->pn_atom);
-            if (!appendExportFromEntry(exportName, module, bindingName, spec->pn_left)) {
+            NameNode* localNameNode = &spec->as<BinaryNode>().left()->as<NameNode>();
+            NameNode* exportNameNode = &spec->as<BinaryNode>().right()->as<NameNode>();
+            bindingName = localNameNode->atom();
+            exportName = exportNameNode->atom();
+            if (!appendExportFromEntry(exportName, module, bindingName, localNameNode)) {
                 return false;
             }
         } else {
             MOZ_ASSERT(spec->isKind(ParseNodeKind::ExportBatchSpec));
-            RootedAtom importName(cx_, cx_->names().star);
-            if (!appendExportFromEntry(nullptr, module, importName, spec)) {
+            exportName = cx_->names().star;
+            if (!appendExportFromEntry(nullptr, module, exportName, spec)) {
                 return false;
             }
         }
     }
 
     return true;
 }
 
--- a/js/src/builtin/ModuleObject.h
+++ b/js/src/builtin/ModuleObject.h
@@ -21,16 +21,18 @@
 #include "vm/ProxyObject.h"
 
 namespace js {
 
 class ModuleEnvironmentObject;
 class ModuleObject;
 
 namespace frontend {
+class BinaryNode;
+class ListNode;
 class ParseNode;
 class TokenStreamAnyChars;
 } /* namespace frontend */
 
 typedef Rooted<ModuleObject*> RootedModuleObject;
 typedef Handle<ModuleObject*> HandleModuleObject;
 typedef Rooted<ModuleEnvironmentObject*> RootedModuleEnvironmentObject;
 typedef Handle<ModuleEnvironmentObject*> HandleModuleEnvironmentObject;
@@ -350,19 +352,19 @@ class ModuleObject : public NativeObject
 // Process a module's parse tree to collate the import and export data used when
 // creating a ModuleObject.
 class MOZ_STACK_CLASS ModuleBuilder
 {
   public:
     explicit ModuleBuilder(JSContext* cx, HandleModuleObject module,
                            const frontend::TokenStreamAnyChars& tokenStream);
 
-    bool processImport(frontend::ParseNode* pn);
-    bool processExport(frontend::ParseNode* pn);
-    bool processExportFrom(frontend::ParseNode* pn);
+    bool processImport(frontend::BinaryNode* importNode);
+    bool processExport(frontend::ParseNode* exportNode);
+    bool processExportFrom(frontend::BinaryNode* exportNode);
 
     bool hasExportedName(JSAtom* name) const;
 
     using ExportEntryVector = GCVector<ExportEntryObject*>;
     const ExportEntryVector& localExportEntries() const {
         return localExportEntries_;
     }
 
@@ -388,18 +390,18 @@ class MOZ_STACK_CLASS ModuleBuilder
     RootedAtomSet exportNames_;
     RootedExportEntryVector localExportEntries_;
     RootedExportEntryVector indirectExportEntries_;
     RootedExportEntryVector starExportEntries_;
 
     ImportEntryObject* importEntryFor(JSAtom* localName) const;
 
     bool processExportBinding(frontend::ParseNode* pn);
-    bool processExportArrayBinding(frontend::ParseNode* pn);
-    bool processExportObjectBinding(frontend::ParseNode* pn);
+    bool processExportArrayBinding(frontend::ListNode* array);
+    bool processExportObjectBinding(frontend::ListNode* obj);
 
     bool appendImportEntryObject(HandleImportEntryObject importEntry);
 
     bool appendExportEntry(HandleAtom exportName, HandleAtom localName,
                            frontend::ParseNode* node = nullptr);
     bool appendExportFromEntry(HandleAtom exportName, HandleAtom moduleRequest,
                                HandleAtom importName, frontend::ParseNode* node);
     bool appendExportEntryObject(HandleExportEntryObject exportEntry);
--- a/js/src/builtin/ReflectParse.cpp
+++ b/js/src/builtin/ReflectParse.cpp
@@ -1730,94 +1730,94 @@ class ASTSerializer
     Value unrootedAtomContents(JSAtom* atom) {
         return StringValue(atom ? atom : cx->names().empty);
     }
 
     BinaryOperator binop(ParseNodeKind kind);
     UnaryOperator unop(ParseNodeKind kind);
     AssignmentOperator aop(ParseNodeKind kind);
 
-    bool statements(ParseNode* pn, NodeVector& elts);
-    bool expressions(ParseNode* pn, NodeVector& elts);
-    bool leftAssociate(ParseNode* pn, MutableHandleValue dst);
-    bool rightAssociate(ParseNode* pn, MutableHandleValue dst);
-    bool functionArgs(ParseNode* pn, ParseNode* pnargs,
+    bool statements(ListNode* stmtList, NodeVector& elts);
+    bool expressions(ListNode* exprList, NodeVector& elts);
+    bool leftAssociate(ListNode* node, MutableHandleValue dst);
+    bool rightAssociate(ListNode* node, MutableHandleValue dst);
+    bool functionArgs(ParseNode* pn, ListNode* argsList,
                       NodeVector& args, NodeVector& defaults, MutableHandleValue rest);
 
     bool sourceElement(ParseNode* pn, MutableHandleValue dst);
 
     bool declaration(ParseNode* pn, MutableHandleValue dst);
-    bool variableDeclaration(ParseNode* pn, bool lexical, MutableHandleValue dst);
+    bool variableDeclaration(ListNode* declList, bool lexical, MutableHandleValue dst);
     bool variableDeclarator(ParseNode* pn, MutableHandleValue dst);
-    bool importDeclaration(ParseNode* pn, MutableHandleValue dst);
-    bool importSpecifier(ParseNode* pn, MutableHandleValue dst);
-    bool exportDeclaration(ParseNode* pn, MutableHandleValue dst);
-    bool exportSpecifier(ParseNode* pn, MutableHandleValue dst);
-    bool classDefinition(ParseNode* pn, bool expr, MutableHandleValue dst);
+    bool importDeclaration(BinaryNode* importNode, MutableHandleValue dst);
+    bool importSpecifier(BinaryNode* importSpec, MutableHandleValue dst);
+    bool exportDeclaration(ParseNode* exportNode, MutableHandleValue dst);
+    bool exportSpecifier(BinaryNode* exportSpec, MutableHandleValue dst);
+    bool classDefinition(ClassNode* pn, bool expr, MutableHandleValue dst);
 
     bool optStatement(ParseNode* pn, MutableHandleValue dst) {
         if (!pn) {
             dst.setMagic(JS_SERIALIZE_NO_NODE);
             return true;
         }
         return statement(pn, dst);
     }
 
     bool forInit(ParseNode* pn, MutableHandleValue dst);
-    bool forIn(ParseNode* loop, ParseNode* head, HandleValue var, HandleValue stmt,
+    bool forIn(ForNode* loop, ParseNode* iterExpr, HandleValue var, HandleValue stmt,
                MutableHandleValue dst);
-    bool forOf(ParseNode* loop, ParseNode* head, HandleValue var, HandleValue stmt,
+    bool forOf(ForNode* loop, ParseNode* iterExpr, HandleValue var, HandleValue stmt,
                MutableHandleValue dst);
     bool statement(ParseNode* pn, MutableHandleValue dst);
-    bool blockStatement(ParseNode* pn, MutableHandleValue dst);
-    bool switchStatement(ParseNode* pn, MutableHandleValue dst);
-    bool switchCase(ParseNode* pn, MutableHandleValue dst);
-    bool tryStatement(ParseNode* pn, MutableHandleValue dst);
-    bool catchClause(ParseNode* pn, MutableHandleValue dst);
+    bool blockStatement(ListNode* node, MutableHandleValue dst);
+    bool switchStatement(SwitchStatement* switchStmt, MutableHandleValue dst);
+    bool switchCase(CaseClause* caseClause, MutableHandleValue dst);
+    bool tryStatement(TryNode* tryNode, MutableHandleValue dst);
+    bool catchClause(BinaryNode* catchClause, MutableHandleValue dst);
 
     bool optExpression(ParseNode* pn, MutableHandleValue dst) {
         if (!pn) {
             dst.setMagic(JS_SERIALIZE_NO_NODE);
             return true;
         }
         return expression(pn, dst);
     }
 
     bool expression(ParseNode* pn, MutableHandleValue dst);
 
-    bool propertyName(ParseNode* pn, MutableHandleValue dst);
+    bool propertyName(ParseNode* key, MutableHandleValue dst);
     bool property(ParseNode* pn, MutableHandleValue dst);
 
-    bool classMethod(ParseNode* pn, MutableHandleValue dst);
+    bool classMethod(ClassMethod* classMethod, MutableHandleValue dst);
 
     bool optIdentifier(HandleAtom atom, TokenPos* pos, MutableHandleValue dst) {
         if (!atom) {
             dst.setMagic(JS_SERIALIZE_NO_NODE);
             return true;
         }
         return identifier(atom, pos, dst);
     }
 
     bool identifier(HandleAtom atom, TokenPos* pos, MutableHandleValue dst);
-    bool identifier(ParseNode* pn, MutableHandleValue dst);
+    bool identifier(NameNode* id, MutableHandleValue dst);
     bool literal(ParseNode* pn, MutableHandleValue dst);
 
     bool optPattern(ParseNode* pn, MutableHandleValue dst) {
         if (!pn) {
             dst.setMagic(JS_SERIALIZE_NO_NODE);
             return true;
         }
         return pattern(pn, dst);
     }
 
     bool pattern(ParseNode* pn, MutableHandleValue dst);
-    bool arrayPattern(ParseNode* pn, MutableHandleValue dst);
-    bool objectPattern(ParseNode* pn, MutableHandleValue dst);
-
-    bool function(ParseNode* pn, ASTType type, MutableHandleValue dst);
+    bool arrayPattern(ListNode* array, MutableHandleValue dst);
+    bool objectPattern(ListNode* obj, MutableHandleValue dst);
+
+    bool function(CodeNode* funNode, ASTType type, MutableHandleValue dst);
     bool functionArgsAndBody(ParseNode* pn, NodeVector& args, NodeVector& defaults,
                              bool isAsync, bool isExpression,
                              MutableHandleValue body, MutableHandleValue rest);
     bool functionBody(ParseNode* pn, TokenPos* pos, MutableHandleValue dst);
 
   public:
     ASTSerializer(JSContext* c, bool l, char const* src, uint32_t ln)
         : cx(c)
@@ -1832,17 +1832,17 @@ class ASTSerializer
         return builder.init(userobj);
     }
 
     void setParser(Parser<FullParseHandler, char16_t>* p) {
         parser = p;
         builder.setTokenStream(&p->anyChars);
     }
 
-    bool program(ParseNode* pn, MutableHandleValue dst);
+    bool program(ListNode* node, MutableHandleValue dst);
 };
 
 } /* anonymous namespace */
 
 AssignmentOperator
 ASTSerializer::aop(ParseNodeKind kind)
 {
     switch (kind) {
@@ -1957,76 +1957,75 @@ ASTSerializer::binop(ParseNodeKind kind)
       case ParseNodeKind::Pipeline:
         return BINOP_PIPELINE;
       default:
         return BINOP_ERR;
     }
 }
 
 bool
-ASTSerializer::statements(ParseNode* pn, NodeVector& elts)
+ASTSerializer::statements(ListNode* stmtList, NodeVector& elts)
 {
-    MOZ_ASSERT(pn->isKind(ParseNodeKind::StatementList));
-    MOZ_ASSERT(pn->isArity(PN_LIST));
-
-    if (!elts.reserve(pn->pn_count)) {
+    MOZ_ASSERT(stmtList->isKind(ParseNodeKind::StatementList));
+
+    if (!elts.reserve(stmtList->count())) {
         return false;
     }
 
-    for (ParseNode* next = pn->pn_head; next; next = next->pn_next) {
-        MOZ_ASSERT(pn->pn_pos.encloses(next->pn_pos));
+    for (ParseNode* stmt : stmtList->contents()) {
+        MOZ_ASSERT(stmtList->pn_pos.encloses(stmt->pn_pos));
 
         RootedValue elt(cx);
-        if (!sourceElement(next, &elt)) {
+        if (!sourceElement(stmt, &elt)) {
             return false;
         }
         elts.infallibleAppend(elt);
     }
 
     return true;
 }
 
 bool
-ASTSerializer::expressions(ParseNode* pn, NodeVector& elts)
+ASTSerializer::expressions(ListNode* exprList, NodeVector& elts)
 {
-    if (!elts.reserve(pn->pn_count)) {
+    if (!elts.reserve(exprList->count())) {
         return false;
     }
 
-    for (ParseNode* next = pn->pn_head; next; next = next->pn_next) {
-        MOZ_ASSERT(pn->pn_pos.encloses(next->pn_pos));
+    for (ParseNode* expr : exprList->contents()) {
+        MOZ_ASSERT(exprList->pn_pos.encloses(expr->pn_pos));
 
         RootedValue elt(cx);
-        if (!expression(next, &elt)) {
+        if (!expression(expr, &elt)) {
             return false;
         }
         elts.infallibleAppend(elt);
     }
 
     return true;
 }
 
 bool
-ASTSerializer::blockStatement(ParseNode* pn, MutableHandleValue dst)
+ASTSerializer::blockStatement(ListNode* node, MutableHandleValue dst)
 {
-    MOZ_ASSERT(pn->isKind(ParseNodeKind::StatementList));
+    MOZ_ASSERT(node->isKind(ParseNodeKind::StatementList));
 
     NodeVector stmts(cx);
-    return statements(pn, stmts) &&
-           builder.blockStatement(stmts, &pn->pn_pos, dst);
+    return statements(node, stmts) &&
+           builder.blockStatement(stmts, &node->pn_pos, dst);
 }
 
 bool
-ASTSerializer::program(ParseNode* pn, MutableHandleValue dst)
+ASTSerializer::program(ListNode* node, MutableHandleValue dst)
 {
-    MOZ_ASSERT(parser->anyChars.srcCoords.lineNum(pn->pn_pos.begin) == lineno);
+    MOZ_ASSERT(parser->anyChars.srcCoords.lineNum(node->pn_pos.begin) == lineno);
 
     NodeVector stmts(cx);
-    return statements(pn, stmts) &&
-           builder.program(stmts, &pn->pn_pos, dst);
+    return statements(node, stmts) &&
+           builder.program(stmts, &node->pn_pos, dst);
 }
 
 bool
 ASTSerializer::sourceElement(ParseNode* pn, MutableHandleValue dst)
 {
     /* SpiderMonkey allows declarations even in pure statement contexts. */
     return statement(pn, dst);
 }
@@ -2036,352 +2035,368 @@ ASTSerializer::declaration(ParseNode* pn
 {
     MOZ_ASSERT(pn->isKind(ParseNodeKind::Function) ||
                pn->isKind(ParseNodeKind::Var) ||
                pn->isKind(ParseNodeKind::Let) ||
                pn->isKind(ParseNodeKind::Const));
 
     switch (pn->getKind()) {
       case ParseNodeKind::Function:
-        return function(pn, AST_FUNC_DECL, dst);
+        return function(&pn->as<CodeNode>(), AST_FUNC_DECL, dst);
 
       case ParseNodeKind::Var:
-        return variableDeclaration(pn, false, dst);
+        return variableDeclaration(&pn->as<ListNode>(), false, dst);
 
       default:
         MOZ_ASSERT(pn->isKind(ParseNodeKind::Let) || pn->isKind(ParseNodeKind::Const));
-        return variableDeclaration(pn, true, dst);
+        return variableDeclaration(&pn->as<ListNode>(), true, dst);
     }
 }
 
 bool
-ASTSerializer::variableDeclaration(ParseNode* pn, bool lexical, MutableHandleValue dst)
+ASTSerializer::variableDeclaration(ListNode* declList, bool lexical, MutableHandleValue dst)
 {
-    MOZ_ASSERT_IF(lexical, pn->isKind(ParseNodeKind::Let) || pn->isKind(ParseNodeKind::Const));
-    MOZ_ASSERT_IF(!lexical, pn->isKind(ParseNodeKind::Var));
+    MOZ_ASSERT_IF(lexical,
+                  declList->isKind(ParseNodeKind::Let) || declList->isKind(ParseNodeKind::Const));
+    MOZ_ASSERT_IF(!lexical, declList->isKind(ParseNodeKind::Var));
 
     VarDeclKind kind = VARDECL_ERR;
     // Treat both the toplevel const binding (secretly var-like) and the lexical const
     // the same way
     if (lexical) {
-        kind = pn->isKind(ParseNodeKind::Let) ? VARDECL_LET : VARDECL_CONST;
+        kind = declList->isKind(ParseNodeKind::Let) ? VARDECL_LET : VARDECL_CONST;
     } else {
-        kind = pn->isKind(ParseNodeKind::Var) ? VARDECL_VAR : VARDECL_CONST;
+        kind = declList->isKind(ParseNodeKind::Var) ? VARDECL_VAR : VARDECL_CONST;
     }
 
     NodeVector dtors(cx);
-    if (!dtors.reserve(pn->pn_count)) {
+    if (!dtors.reserve(declList->count())) {
         return false;
     }
-    for (ParseNode* next = pn->pn_head; next; next = next->pn_next) {
+    for (ParseNode* decl : declList->contents()) {
         RootedValue child(cx);
-        if (!variableDeclarator(next, &child)) {
+        if (!variableDeclarator(decl, &child)) {
             return false;
         }
         dtors.infallibleAppend(child);
     }
-    return builder.variableDeclaration(dtors, kind, &pn->pn_pos, dst);
+    return builder.variableDeclaration(dtors, kind, &declList->pn_pos, dst);
 }
 
 bool
 ASTSerializer::variableDeclarator(ParseNode* pn, MutableHandleValue dst)
 {
-    ParseNode* pnleft;
-    ParseNode* pnright;
+    ParseNode* patternNode;
+    ParseNode* initNode;
 
     if (pn->isKind(ParseNodeKind::Name)) {
-        pnleft = pn;
-        pnright = pn->pn_expr;
-        MOZ_ASSERT_IF(pnright, pn->pn_pos.encloses(pnright->pn_pos));
+        patternNode = pn;
+        initNode = pn->as<NameNode>().initializer();
+        MOZ_ASSERT_IF(initNode, pn->pn_pos.encloses(initNode->pn_pos));
     } else if (pn->isKind(ParseNodeKind::Assign)) {
-        pnleft = pn->pn_left;
-        pnright = pn->pn_right;
-        MOZ_ASSERT(pn->pn_pos.encloses(pnleft->pn_pos));
-        MOZ_ASSERT(pn->pn_pos.encloses(pnright->pn_pos));
+        AssignmentNode* assignNode = &pn->as<AssignmentNode>();
+        patternNode = assignNode->left();
+        initNode = assignNode->right();
+        MOZ_ASSERT(pn->pn_pos.encloses(patternNode->pn_pos));
+        MOZ_ASSERT(pn->pn_pos.encloses(initNode->pn_pos));
     } else {
         /* This happens for a destructuring declarator in a for-in/of loop. */
-        pnleft = pn;
-        pnright = nullptr;
+        patternNode = pn;
+        initNode = nullptr;
     }
 
-    RootedValue left(cx), right(cx);
-    return pattern(pnleft, &left) &&
-           optExpression(pnright, &right) &&
-           builder.variableDeclarator(left, right, &pn->pn_pos, dst);
+    RootedValue patternVal(cx), init(cx);
+    return pattern(patternNode, &patternVal) &&
+           optExpression(initNode, &init) &&
+           builder.variableDeclarator(patternVal, init, &pn->pn_pos, dst);
 }
 
 bool
-ASTSerializer::importDeclaration(ParseNode* pn, MutableHandleValue dst)
+ASTSerializer::importDeclaration(BinaryNode* importNode, MutableHandleValue dst)
 {
-    MOZ_ASSERT(pn->isKind(ParseNodeKind::Import));
-    MOZ_ASSERT(pn->isArity(PN_BINARY));
-    MOZ_ASSERT(pn->pn_left->isKind(ParseNodeKind::ImportSpecList));
-    MOZ_ASSERT(pn->pn_right->isKind(ParseNodeKind::String));
+    MOZ_ASSERT(importNode->isKind(ParseNodeKind::Import));
+
+    ListNode* specList = &importNode->left()->as<ListNode>();
+    MOZ_ASSERT(specList->isKind(ParseNodeKind::ImportSpecList));
+
+    ParseNode* moduleSpecNode = importNode->right();
+    MOZ_ASSERT(moduleSpecNode->isKind(ParseNodeKind::String));
 
     NodeVector elts(cx);
-    if (!elts.reserve(pn->pn_left->pn_count)) {
+    if (!elts.reserve(specList->count())) {
         return false;
     }
 
-    for (ParseNode* next = pn->pn_left->pn_head; next; next = next->pn_next) {
+    for (ParseNode* item : specList->contents()) {
+        BinaryNode* spec = &item->as<BinaryNode>();
         RootedValue elt(cx);
-        if (!importSpecifier(next, &elt)) {
+        if (!importSpecifier(spec, &elt)) {
             return false;
         }
         elts.infallibleAppend(elt);
     }
 
     RootedValue moduleSpec(cx);
-    return literal(pn->pn_right, &moduleSpec) &&
-           builder.importDeclaration(elts, moduleSpec, &pn->pn_pos, dst);
+    return literal(moduleSpecNode, &moduleSpec) &&
+           builder.importDeclaration(elts, moduleSpec, &importNode->pn_pos, dst);
 }
 
 bool
-ASTSerializer::importSpecifier(ParseNode* pn, MutableHandleValue dst)
+ASTSerializer::importSpecifier(BinaryNode* importSpec, MutableHandleValue dst)
 {
-    MOZ_ASSERT(pn->isKind(ParseNodeKind::ImportSpec));
+    MOZ_ASSERT(importSpec->isKind(ParseNodeKind::ImportSpec));
+    NameNode* importNameNode = &importSpec->left()->as<NameNode>();
+    NameNode* bindingNameNode = &importSpec->right()->as<NameNode>();
 
     RootedValue importName(cx);
     RootedValue bindingName(cx);
-    return identifier(pn->pn_left, &importName) &&
-           identifier(pn->pn_right, &bindingName) &&
-           builder.importSpecifier(importName, bindingName, &pn->pn_pos, dst);
+    return identifier(importNameNode, &importName) &&
+           identifier(bindingNameNode, &bindingName) &&
+           builder.importSpecifier(importName, bindingName, &importSpec->pn_pos, dst);
 }
 
 bool
-ASTSerializer::exportDeclaration(ParseNode* pn, MutableHandleValue dst)
+ASTSerializer::exportDeclaration(ParseNode* exportNode, MutableHandleValue dst)
 {
-    MOZ_ASSERT(pn->isKind(ParseNodeKind::Export) ||
-               pn->isKind(ParseNodeKind::ExportFrom) ||
-               pn->isKind(ParseNodeKind::ExportDefault));
-    MOZ_ASSERT(pn->getArity() == (pn->isKind(ParseNodeKind::Export) ? PN_UNARY : PN_BINARY));
-    MOZ_ASSERT_IF(pn->isKind(ParseNodeKind::ExportFrom), pn->pn_right->isKind(ParseNodeKind::String));
+    MOZ_ASSERT(exportNode->isKind(ParseNodeKind::Export) ||
+               exportNode->isKind(ParseNodeKind::ExportFrom) ||
+               exportNode->isKind(ParseNodeKind::ExportDefault));
+    MOZ_ASSERT_IF(exportNode->isKind(ParseNodeKind::Export), exportNode->is<UnaryNode>());
+    MOZ_ASSERT_IF(exportNode->isKind(ParseNodeKind::ExportFrom),
+                  exportNode->as<BinaryNode>().right()->isKind(ParseNodeKind::String));
 
     RootedValue decl(cx, NullValue());
     NodeVector elts(cx);
 
-    ParseNode* kid = pn->isKind(ParseNodeKind::Export) ? pn->pn_kid : pn->pn_left;
+    ParseNode* kid = exportNode->isKind(ParseNodeKind::Export)
+                     ? exportNode->as<UnaryNode>().kid()
+                     : exportNode->as<BinaryNode>().left();
     switch (ParseNodeKind kind = kid->getKind()) {
-      case ParseNodeKind::ExportSpecList:
-        if (!elts.reserve(pn->pn_left->pn_count)) {
+      case ParseNodeKind::ExportSpecList: {
+        ListNode* specList = &kid->as<ListNode>();
+        if (!elts.reserve(specList->count())) {
             return false;
         }
 
-        for (ParseNode* next = pn->pn_left->pn_head; next; next = next->pn_next) {
+        for (ParseNode* spec : specList->contents()) {
             RootedValue elt(cx);
-            if (next->isKind(ParseNodeKind::ExportSpec)) {
-                if (!exportSpecifier(next, &elt)) {
+            if (spec->isKind(ParseNodeKind::ExportSpec)) {
+                if (!exportSpecifier(&spec->as<BinaryNode>(), &elt)) {
                     return false;
                 }
             } else {
-                if (!builder.exportBatchSpecifier(&pn->pn_pos, &elt)) {
+                if (!builder.exportBatchSpecifier(&exportNode->pn_pos, &elt)) {
                     return false;
                 }
             }
             elts.infallibleAppend(elt);
         }
         break;
+      }
 
       case ParseNodeKind::Function:
-        if (!function(kid, AST_FUNC_DECL, &decl)) {
+        if (!function(&kid->as<CodeNode>(), AST_FUNC_DECL, &decl)) {
             return false;
         }
         break;
 
       case ParseNodeKind::Class:
-        if (!classDefinition(kid, false, &decl)) {
+        if (!classDefinition(&kid->as<ClassNode>(), false, &decl)) {
             return false;
         }
         break;
 
       case ParseNodeKind::Var:
       case ParseNodeKind::Const:
       case ParseNodeKind::Let:
-        if (!variableDeclaration(kid, kind != ParseNodeKind::Var, &decl)) {
+        if (!variableDeclaration(&kid->as<ListNode>(), kind != ParseNodeKind::Var, &decl)) {
             return false;
         }
         break;
 
       default:
           if (!expression(kid, &decl)) {
               return false;
           }
           break;
     }
 
     RootedValue moduleSpec(cx, NullValue());
-    if (pn->isKind(ParseNodeKind::ExportFrom) && !literal(pn->pn_right, &moduleSpec)) {
-        return false;
+    if (exportNode->isKind(ParseNodeKind::ExportFrom)) {
+        if (!literal(exportNode->as<BinaryNode>().right(), &moduleSpec)) {
+            return false;
+        }
     }
 
     RootedValue isDefault(cx, BooleanValue(false));
-    if (pn->isKind(ParseNodeKind::ExportDefault)) {
+    if (exportNode->isKind(ParseNodeKind::ExportDefault)) {
         isDefault.setBoolean(true);
     }
 
-    return builder.exportDeclaration(decl, elts, moduleSpec, isDefault, &pn->pn_pos, dst);
+    return builder.exportDeclaration(decl, elts, moduleSpec, isDefault, &exportNode->pn_pos, dst);
 }
 
 bool
-ASTSerializer::exportSpecifier(ParseNode* pn, MutableHandleValue dst)
+ASTSerializer::exportSpecifier(BinaryNode* exportSpec, MutableHandleValue dst)
 {
-    MOZ_ASSERT(pn->isKind(ParseNodeKind::ExportSpec));
+    MOZ_ASSERT(exportSpec->isKind(ParseNodeKind::ExportSpec));
+    NameNode* bindingNameNode = &exportSpec->left()->as<NameNode>();
+    NameNode* exportNameNode = &exportSpec->right()->as<NameNode>();
 
     RootedValue bindingName(cx);
     RootedValue exportName(cx);
-    return identifier(pn->pn_left, &bindingName) &&
-           identifier(pn->pn_right, &exportName) &&
-           builder.exportSpecifier(bindingName, exportName, &pn->pn_pos, dst);
+    return identifier(bindingNameNode, &bindingName) &&
+           identifier(exportNameNode, &exportName) &&
+           builder.exportSpecifier(bindingName, exportName, &exportSpec->pn_pos, dst);
 }
 
 bool
-ASTSerializer::switchCase(ParseNode* pn, MutableHandleValue dst)
+ASTSerializer::switchCase(CaseClause* caseClause, MutableHandleValue dst)
 {
-    MOZ_ASSERT_IF(pn->pn_left, pn->pn_pos.encloses(pn->pn_left->pn_pos));
-    MOZ_ASSERT(pn->pn_pos.encloses(pn->pn_right->pn_pos));
+    MOZ_ASSERT_IF(caseClause->caseExpression(),
+                  caseClause->pn_pos.encloses(caseClause->caseExpression()->pn_pos));
+    MOZ_ASSERT(caseClause->pn_pos.encloses(caseClause->statementList()->pn_pos));
 
     NodeVector stmts(cx);
-
     RootedValue expr(cx);
-
-    return optExpression(pn->as<CaseClause>().caseExpression(), &expr) &&
-           statements(pn->as<CaseClause>().statementList(), stmts) &&
-           builder.switchCase(expr, stmts, &pn->pn_pos, dst);
+    return optExpression(caseClause->caseExpression(), &expr) &&
+           statements(caseClause->statementList(), stmts) &&
+           builder.switchCase(expr, stmts, &caseClause->pn_pos, dst);
 }
 
 bool
-ASTSerializer::switchStatement(ParseNode* pn, MutableHandleValue dst)
+ASTSerializer::switchStatement(SwitchStatement* switchStmt, MutableHandleValue dst)
 {
-    MOZ_ASSERT(pn->pn_pos.encloses(pn->pn_left->pn_pos));
-    MOZ_ASSERT(pn->pn_pos.encloses(pn->pn_right->pn_pos));
+    MOZ_ASSERT(switchStmt->pn_pos.encloses(switchStmt->discriminant().pn_pos));
+    MOZ_ASSERT(switchStmt->pn_pos.encloses(switchStmt->lexicalForCaseList().pn_pos));
 
     RootedValue disc(cx);
-
-    if (!expression(pn->pn_left, &disc)) {
+    if (!expression(&switchStmt->discriminant(), &disc)) {
         return false;
     }
 
-    ParseNode* listNode;
-    bool lexical;
-
-    if (pn->pn_right->isKind(ParseNodeKind::LexicalScope)) {
-        listNode = pn->pn_right->pn_expr;
-        lexical = true;
-    } else {
-        listNode = pn->pn_right;
-        lexical = false;
-    }
+    ListNode* caseList = &switchStmt->lexicalForCaseList().scopeBody()->as<ListNode>();
 
     NodeVector cases(cx);
-    if (!cases.reserve(listNode->pn_count)) {
+    if (!cases.reserve(caseList->count())) {
         return false;
     }
 
-    for (ParseNode* next = listNode->pn_head; next; next = next->pn_next) {
+    for (ParseNode* item : caseList->contents()) {
+        CaseClause* caseClause = &item->as<CaseClause>();
         RootedValue child(cx);
-        if (!switchCase(next, &child)) {
+        if (!switchCase(caseClause, &child)) {
             return false;
         }
         cases.infallibleAppend(child);
     }
 
-    return builder.switchStatement(disc, cases, lexical, &pn->pn_pos, dst);
+    // `lexical` field is always true.
+    return builder.switchStatement(disc, cases, true, &switchStmt->pn_pos, dst);
 }
 
 bool
-ASTSerializer::catchClause(ParseNode* pn, MutableHandleValue dst)
+ASTSerializer::catchClause(BinaryNode* catchClause, MutableHandleValue dst)
 {
-    MOZ_ASSERT(pn->isKind(ParseNodeKind::Catch));
-    MOZ_ASSERT_IF(pn->pn_left, pn->pn_pos.encloses(pn->pn_left->pn_pos));
-    MOZ_ASSERT(pn->pn_pos.encloses(pn->pn_right->pn_pos));
+    MOZ_ASSERT(catchClause->isKind(ParseNodeKind::Catch));
+
+    ParseNode* varNode = catchClause->left();
+    MOZ_ASSERT_IF(varNode, catchClause->pn_pos.encloses(varNode->pn_pos));
+
+    ParseNode* bodyNode = catchClause->right();
+    MOZ_ASSERT(catchClause->pn_pos.encloses(bodyNode->pn_pos));
 
     RootedValue var(cx), body(cx);
-
-    if (!optPattern(pn->pn_left, &var)) {
+    if (!optPattern(varNode, &var)) {
         return false;
     }
 
-    return statement(pn->pn_right, &body) &&
-           builder.catchClause(var, body, &pn->pn_pos, dst);
+    return statement(bodyNode, &body) &&
+           builder.catchClause(var, body, &catchClause->pn_pos, dst);
 }
 
 bool
-ASTSerializer::tryStatement(ParseNode* pn, MutableHandleValue dst)
+ASTSerializer::tryStatement(TryNode* tryNode, MutableHandleValue dst)
 {
-    MOZ_ASSERT(pn->pn_pos.encloses(pn->pn_kid1->pn_pos));
-    MOZ_ASSERT_IF(pn->pn_kid2, pn->pn_pos.encloses(pn->pn_kid2->pn_pos));
-    MOZ_ASSERT_IF(pn->pn_kid3, pn->pn_pos.encloses(pn->pn_kid3->pn_pos));
+    ParseNode* bodyNode = tryNode->body();
+    MOZ_ASSERT(tryNode->pn_pos.encloses(bodyNode->pn_pos));
+
+    LexicalScopeNode* catchNode = tryNode->catchScope();
+    MOZ_ASSERT_IF(catchNode, tryNode->pn_pos.encloses(catchNode->pn_pos));
+
+    ParseNode* finallyNode = tryNode->finallyBlock();
+    MOZ_ASSERT_IF(finallyNode, tryNode->pn_pos.encloses(finallyNode->pn_pos));
 
     RootedValue body(cx);
-    if (!statement(pn->pn_kid1, &body)) {
+    if (!statement(bodyNode, &body)) {
         return false;
     }
 
     RootedValue handler(cx, NullValue());
-    if (ParseNode* catchScope = pn->pn_kid2) {
-        MOZ_ASSERT(catchScope->isKind(ParseNodeKind::LexicalScope));
-        if (!catchClause(catchScope->scopeBody(), &handler)) {
+    if (catchNode) {
+        LexicalScopeNode* catchScope = &catchNode->as<LexicalScopeNode>();
+        if (!catchClause(&catchScope->scopeBody()->as<BinaryNode>(), &handler)) {
             return false;
         }
     }
 
     RootedValue finally(cx);
-    return optStatement(pn->pn_kid3, &finally) &&
-           builder.tryStatement(body, handler, finally, &pn->pn_pos, dst);
+    return optStatement(finallyNode, &finally) &&
+           builder.tryStatement(body, handler, finally, &tryNode->pn_pos, dst);
 }
 
 bool
 ASTSerializer::forInit(ParseNode* pn, MutableHandleValue dst)
 {
     if (!pn) {
         dst.setMagic(JS_SERIALIZE_NO_NODE);
         return true;
     }
 
     bool lexical = pn->isKind(ParseNodeKind::Let) || pn->isKind(ParseNodeKind::Const);
     return (lexical || pn->isKind(ParseNodeKind::Var))
-           ? variableDeclaration(pn, lexical, dst)
+           ? variableDeclaration(&pn->as<ListNode>(), lexical, dst)
            : expression(pn, dst);
 }
 
 bool
-ASTSerializer::forOf(ParseNode* loop, ParseNode* head, HandleValue var, HandleValue stmt,
-                         MutableHandleValue dst)
+ASTSerializer::forOf(ForNode* loop, ParseNode* iterExpr, HandleValue var, HandleValue stmt,
+                     MutableHandleValue dst)
 {
     RootedValue expr(cx);
 
-    return expression(head->pn_kid3, &expr) &&
+    return expression(iterExpr, &expr) &&
         builder.forOfStatement(var, expr, stmt, &loop->pn_pos, dst);
 }
 
 bool
-ASTSerializer::forIn(ParseNode* loop, ParseNode* head, HandleValue var, HandleValue stmt,
-                         MutableHandleValue dst)
+ASTSerializer::forIn(ForNode* loop, ParseNode* iterExpr, HandleValue var, HandleValue stmt,
+                     MutableHandleValue dst)
 {
     RootedValue expr(cx);
 
-    return expression(head->pn_kid3, &expr) &&
+    return expression(iterExpr, &expr) &&
         builder.forInStatement(var, expr, stmt, &loop->pn_pos, dst);
 }
 
 bool
-ASTSerializer::classDefinition(ParseNode* pn, bool expr, MutableHandleValue dst)
+ASTSerializer::classDefinition(ClassNode* pn, bool expr, MutableHandleValue dst)
 {
     RootedValue className(cx, MagicValue(JS_SERIALIZE_NO_NODE));
     RootedValue heritage(cx);
     RootedValue classBody(cx);
 
-    if (pn->pn_kid1) {
-        if (!identifier(pn->pn_kid1->as<ClassNames>().innerBinding(), &className)) {
+    if (ClassNames* names = pn->names()) {
+        if (!identifier(names->innerBinding(), &className)) {
             return false;
         }
     }
 
-    return optExpression(pn->pn_kid2, &heritage) &&
-           statement(pn->pn_kid3, &classBody) &&
+    return optExpression(pn->heritage(), &heritage) &&
+           statement(pn->methodList(), &classBody) &&
            builder.classDefinition(expr, className, heritage, classBody, &pn->pn_pos, dst);
 }
 
 bool
 ASTSerializer::statement(ParseNode* pn, MutableHandleValue dst)
 {
     if (!CheckRecursionLimit(cx)) {
         return false;
@@ -2392,220 +2407,255 @@ ASTSerializer::statement(ParseNode* pn, 
       case ParseNodeKind::Var:
         return declaration(pn, dst);
 
       case ParseNodeKind::Let:
       case ParseNodeKind::Const:
         return declaration(pn, dst);
 
       case ParseNodeKind::Import:
-        return importDeclaration(pn, dst);
+        return importDeclaration(&pn->as<BinaryNode>(), dst);
 
       case ParseNodeKind::Export:
       case ParseNodeKind::ExportDefault:
       case ParseNodeKind::ExportFrom:
         return exportDeclaration(pn, dst);
 
       case ParseNodeKind::EmptyStatement:
         return builder.emptyStatement(&pn->pn_pos, dst);
 
       case ParseNodeKind::ExpressionStatement:
       {
         RootedValue expr(cx);
-        return expression(pn->pn_kid, &expr) &&
+        return expression(pn->as<UnaryNode>().kid(), &expr) &&
             builder.expressionStatement(expr, &pn->pn_pos, dst);
       }
 
       case ParseNodeKind::LexicalScope:
-        pn = pn->pn_expr;
+        pn = pn->as<LexicalScopeNode>().scopeBody();
         if (!pn->isKind(ParseNodeKind::StatementList)) {
             return statement(pn, dst);
         }
         MOZ_FALLTHROUGH;
 
       case ParseNodeKind::StatementList:
-        return blockStatement(pn, dst);
+        return blockStatement(&pn->as<ListNode>(), dst);
 
       case ParseNodeKind::If:
       {
-        MOZ_ASSERT(pn->pn_pos.encloses(pn->pn_kid1->pn_pos));
-        MOZ_ASSERT(pn->pn_pos.encloses(pn->pn_kid2->pn_pos));
-        MOZ_ASSERT_IF(pn->pn_kid3, pn->pn_pos.encloses(pn->pn_kid3->pn_pos));
+        TernaryNode* ifNode = &pn->as<TernaryNode>();
+
+        ParseNode* testNode = ifNode->kid1();
+        MOZ_ASSERT(ifNode->pn_pos.encloses(testNode->pn_pos));
+
+        ParseNode* consNode = ifNode->kid2();
+        MOZ_ASSERT(ifNode->pn_pos.encloses(consNode->pn_pos));
+
+        ParseNode* altNode = ifNode->kid3();
+        MOZ_ASSERT_IF(altNode, ifNode->pn_pos.encloses(altNode->pn_pos));
 
         RootedValue test(cx), cons(cx), alt(cx);
 
-        return expression(pn->pn_kid1, &test) &&
-               statement(pn->pn_kid2, &cons) &&
-               optStatement(pn->pn_kid3, &alt) &&
-               builder.ifStatement(test, cons, alt, &pn->pn_pos, dst);
+        return expression(testNode, &test) &&
+               statement(consNode, &cons) &&
+               optStatement(altNode, &alt) &&
+               builder.ifStatement(test, cons, alt, &ifNode->pn_pos, dst);
       }
 
       case ParseNodeKind::Switch:
-        return switchStatement(pn, dst);
+        return switchStatement(&pn->as<SwitchStatement>(), dst);
 
       case ParseNodeKind::Try:
-        return tryStatement(pn, dst);
+        return tryStatement(&pn->as<TryNode>(), dst);
 
       case ParseNodeKind::With:
       case ParseNodeKind::While:
       {
-        MOZ_ASSERT(pn->pn_pos.encloses(pn->pn_left->pn_pos));
-        MOZ_ASSERT(pn->pn_pos.encloses(pn->pn_right->pn_pos));
+        BinaryNode* node = &pn->as<BinaryNode>();
+
+        ParseNode* exprNode = node->left();
+        MOZ_ASSERT(node->pn_pos.encloses(exprNode->pn_pos));
+
+        ParseNode* stmtNode = node->right();
+        MOZ_ASSERT(node->pn_pos.encloses(stmtNode->pn_pos));
 
         RootedValue expr(cx), stmt(cx);
 
-        return expression(pn->pn_left, &expr) &&
-               statement(pn->pn_right, &stmt) &&
-               (pn->isKind(ParseNodeKind::With)
-                ? builder.withStatement(expr, stmt, &pn->pn_pos, dst)
-                : builder.whileStatement(expr, stmt, &pn->pn_pos, dst));
+        return expression(exprNode, &expr) &&
+               statement(stmtNode, &stmt) &&
+               (node->isKind(ParseNodeKind::With)
+                ? builder.withStatement(expr, stmt, &node->pn_pos, dst)
+                : builder.whileStatement(expr, stmt, &node->pn_pos, dst));
       }
 
       case ParseNodeKind::DoWhile:
       {
-        MOZ_ASSERT(pn->pn_pos.encloses(pn->pn_left->pn_pos));
-        MOZ_ASSERT(pn->pn_pos.encloses(pn->pn_right->pn_pos));
+        BinaryNode* node = &pn->as<BinaryNode>();
+
+        ParseNode* stmtNode = node->left();
+        MOZ_ASSERT(node->pn_pos.encloses(stmtNode->pn_pos));
+
+        ParseNode* testNode = node->right();
+        MOZ_ASSERT(node->pn_pos.encloses(testNode->pn_pos));
 
         RootedValue stmt(cx), test(cx);
 
-        return statement(pn->pn_left, &stmt) &&
-               expression(pn->pn_right, &test) &&
-               builder.doWhileStatement(stmt, test, &pn->pn_pos, dst);
+        return statement(stmtNode, &stmt) &&
+               expression(testNode, &test) &&
+               builder.doWhileStatement(stmt, test, &node->pn_pos, dst);
       }
 
       case ParseNodeKind::For:
       {
-        MOZ_ASSERT(pn->pn_pos.encloses(pn->pn_left->pn_pos));
-        MOZ_ASSERT(pn->pn_pos.encloses(pn->pn_right->pn_pos));
-
-        ParseNode* head = pn->pn_left;
-
-        MOZ_ASSERT_IF(head->pn_kid1, head->pn_pos.encloses(head->pn_kid1->pn_pos));
-        MOZ_ASSERT_IF(head->pn_kid2, head->pn_pos.encloses(head->pn_kid2->pn_pos));
-        MOZ_ASSERT_IF(head->pn_kid3, head->pn_pos.encloses(head->pn_kid3->pn_pos));
+        ForNode* forNode = &pn->as<ForNode>();
+
+        TernaryNode* head = forNode->head();
+        MOZ_ASSERT(forNode->pn_pos.encloses(head->pn_pos));
+
+        ParseNode* stmtNode = forNode->right();
+        MOZ_ASSERT(forNode->pn_pos.encloses(stmtNode->pn_pos));
+
+        ParseNode* initNode = head->kid1();
+        MOZ_ASSERT_IF(initNode, head->pn_pos.encloses(initNode->pn_pos));
+
+        ParseNode* maybeTest = head->kid2();
+        MOZ_ASSERT_IF(maybeTest, head->pn_pos.encloses(maybeTest->pn_pos));
+
+        ParseNode* updateOrIter = head->kid3();
+        MOZ_ASSERT_IF(updateOrIter, head->pn_pos.encloses(updateOrIter->pn_pos));
 
         RootedValue stmt(cx);
-        if (!statement(pn->pn_right, &stmt)) {
+        if (!statement(stmtNode, &stmt)) {
             return false;
         }
 
         if (head->isKind(ParseNodeKind::ForIn) || head->isKind(ParseNodeKind::ForOf)) {
             RootedValue var(cx);
-            if (head->pn_kid1->isKind(ParseNodeKind::LexicalScope)) {
-                if (!variableDeclaration(head->pn_kid1->pn_expr, true, &var)) {
+            if (initNode->is<LexicalScopeNode>()) {
+                LexicalScopeNode* scopeNode = &initNode->as<LexicalScopeNode>();
+                if (!variableDeclaration(&scopeNode->scopeBody()->as<ListNode>(), true, &var)) {
                     return false;
                 }
-            } else if (!head->pn_kid1->isKind(ParseNodeKind::Var) &&
-                       !head->pn_kid1->isKind(ParseNodeKind::Let) &&
-                       !head->pn_kid1->isKind(ParseNodeKind::Const))
+            } else if (!initNode->isKind(ParseNodeKind::Var) &&
+                       !initNode->isKind(ParseNodeKind::Let) &&
+                       !initNode->isKind(ParseNodeKind::Const))
             {
-                if (!pattern(head->pn_kid1, &var)) {
+                if (!pattern(initNode, &var)) {
                     return false;
                 }
             } else {
-                if (!variableDeclaration(head->pn_kid1,
-                                         head->pn_kid1->isKind(ParseNodeKind::Let) ||
-                                         head->pn_kid1->isKind(ParseNodeKind::Const),
+                if (!variableDeclaration(&initNode->as<ListNode>(),
+                                         initNode->isKind(ParseNodeKind::Let) ||
+                                         initNode->isKind(ParseNodeKind::Const),
                                          &var))
                 {
                     return false;
                 }
             }
             if (head->isKind(ParseNodeKind::ForIn)) {
-                return forIn(pn, head, var, stmt, dst);
+                return forIn(forNode, updateOrIter, var, stmt, dst);
             }
-            return forOf(pn, head, var, stmt, dst);
+            return forOf(forNode, updateOrIter, var, stmt, dst);
         }
 
         RootedValue init(cx), test(cx), update(cx);
 
-        return forInit(head->pn_kid1, &init) &&
-               optExpression(head->pn_kid2, &test) &&
-               optExpression(head->pn_kid3, &update) &&
-               builder.forStatement(init, test, update, stmt, &pn->pn_pos, dst);
+        return forInit(initNode, &init) &&
+               optExpression(maybeTest, &test) &&
+               optExpression(updateOrIter, &update) &&
+               builder.forStatement(init, test, update, stmt, &forNode->pn_pos, dst);
       }
 
       case ParseNodeKind::Break:
       case ParseNodeKind::Continue:
       {
+        LoopControlStatement* node = &pn->as<LoopControlStatement>();
         RootedValue label(cx);
-        RootedAtom pnAtom(cx, pn->pn_atom);
+        RootedAtom pnAtom(cx, node->label());
         return optIdentifier(pnAtom, nullptr, &label) &&
-               (pn->isKind(ParseNodeKind::Break)
-                ? builder.breakStatement(label, &pn->pn_pos, dst)
-                : builder.continueStatement(label, &pn->pn_pos, dst));
+               (node->isKind(ParseNodeKind::Break)
+                ? builder.breakStatement(label, &node->pn_pos, dst)
+                : builder.continueStatement(label, &node->pn_pos, dst));
       }
 
       case ParseNodeKind::Label:
       {
-        MOZ_ASSERT(pn->pn_pos.encloses(pn->pn_expr->pn_pos));
+        LabeledStatement* labelNode = &pn->as<LabeledStatement>();
+        ParseNode* stmtNode = labelNode->statement();
+        MOZ_ASSERT(labelNode->pn_pos.encloses(stmtNode->pn_pos));
 
         RootedValue label(cx), stmt(cx);
-        RootedAtom pnAtom(cx, pn->as<LabeledStatement>().label());
+        RootedAtom pnAtom(cx, labelNode->label());
         return identifier(pnAtom, nullptr, &label) &&
-               statement(pn->pn_expr, &stmt) &&
-               builder.labeledStatement(label, stmt, &pn->pn_pos, dst);
+               statement(stmtNode, &stmt) &&
+               builder.labeledStatement(label, stmt, &labelNode->pn_pos, dst);
       }
 
       case ParseNodeKind::Throw:
       {
-        MOZ_ASSERT_IF(pn->pn_kid, pn->pn_pos.encloses(pn->pn_kid->pn_pos));
+        UnaryNode* throwNode = &pn->as<UnaryNode>();
+        ParseNode* operand = throwNode->kid();
+        MOZ_ASSERT(throwNode->pn_pos.encloses(operand->pn_pos));
 
         RootedValue arg(cx);
 
-        return optExpression(pn->pn_kid, &arg) &&
-               builder.throwStatement(arg, &pn->pn_pos, dst);
+        return expression(operand, &arg) &&
+               builder.throwStatement(arg, &throwNode->pn_pos, dst);
       }
 
       case ParseNodeKind::Return:
       {
-        MOZ_ASSERT_IF(pn->pn_kid, pn->pn_pos.encloses(pn->pn_kid->pn_pos));
+        UnaryNode* returnNode = &pn->as<UnaryNode>();
+        ParseNode* operand = returnNode->kid();
+        MOZ_ASSERT_IF(operand, returnNode->pn_pos.encloses(operand->pn_pos));
 
         RootedValue arg(cx);
 
-        return optExpression(pn->pn_kid, &arg) &&
-               builder.returnStatement(arg, &pn->pn_pos, dst);
+        return optExpression(operand, &arg) &&
+               builder.returnStatement(arg, &returnNode->pn_pos, dst);
       }
 
       case ParseNodeKind::Debugger:
         return builder.debuggerStatement(&pn->pn_pos, dst);
 
       case ParseNodeKind::Class:
-        return classDefinition(pn, false, dst);
+        return classDefinition(&pn->as<ClassNode>(), false, dst);
 
       case ParseNodeKind::ClassMethodList:
       {
+        ListNode* methodList = &pn->as<ListNode>();
         NodeVector methods(cx);
-        if (!methods.reserve(pn->pn_count)) {
+        if (!methods.reserve(methodList->count())) {
             return false;
         }
 
-        for (ParseNode* next = pn->pn_head; next; next = next->pn_next) {
-            MOZ_ASSERT(pn->pn_pos.encloses(next->pn_pos));
+        for (ParseNode* item : methodList->contents()) {
+            ClassMethod* method = &item->as<ClassMethod>();
+            MOZ_ASSERT(methodList->pn_pos.encloses(method->pn_pos));
 
             RootedValue prop(cx);
-            if (!classMethod(next, &prop)) {
+            if (!classMethod(method, &prop)) {
                 return false;
             }
             methods.infallibleAppend(prop);
         }
 
         return builder.classMethods(methods, dst);
       }
 
       default:
         LOCAL_NOT_REACHED("unexpected statement type");
     }
 }
 
 bool
-ASTSerializer::classMethod(ParseNode* pn, MutableHandleValue dst)
+ASTSerializer::classMethod(ClassMethod* classMethod, MutableHandleValue dst)
 {
     PropKind kind;
-    switch (pn->getOp()) {
+    switch (classMethod->getOp()) {
       case JSOP_INITPROP:
         kind = PROP_INIT;
         break;
 
       case JSOP_INITPROP_GETTER:
         kind = PROP_GETTER;
         break;
 
@@ -2613,73 +2663,71 @@ ASTSerializer::classMethod(ParseNode* pn
         kind = PROP_SETTER;
         break;
 
       default:
         LOCAL_NOT_REACHED("unexpected object-literal property");
     }
 
     RootedValue key(cx), val(cx);
-    bool isStatic = pn->as<ClassMethod>().isStatic();
-    return propertyName(pn->pn_left, &key) &&
-           expression(pn->pn_right, &val) &&
-           builder.classMethod(key, val, kind, isStatic, &pn->pn_pos, dst);
+    bool isStatic = classMethod->isStatic();
+    return propertyName(&classMethod->name(), &key) &&
+           expression(&classMethod->method(), &val) &&
+           builder.classMethod(key, val, kind, isStatic, &classMethod->pn_pos, dst);
 }
 
 bool
-ASTSerializer::leftAssociate(ParseNode* pn, MutableHandleValue dst)
+ASTSerializer::leftAssociate(ListNode* node, MutableHandleValue dst)
 {
-    MOZ_ASSERT(pn->isArity(PN_LIST));
-    MOZ_ASSERT(pn->pn_count >= 1);
-
-    ParseNodeKind kind = pn->getKind();
+    MOZ_ASSERT(!node->empty());
+
+    ParseNodeKind kind = node->getKind();
     bool lor = kind == ParseNodeKind::Or;
     bool logop = lor || (kind == ParseNodeKind::And);
 
-    ParseNode* head = pn->pn_head;
+    ParseNode* head = node->head();
     RootedValue left(cx);
     if (!expression(head, &left)) {
         return false;
     }
-    for (ParseNode* next = head->pn_next; next; next = next->pn_next) {
+    for (ParseNode* next : node->contentsFrom(head->pn_next)) {
         RootedValue right(cx);
         if (!expression(next, &right)) {
             return false;
         }
 
-        TokenPos subpos(pn->pn_pos.begin, next->pn_pos.end);
+        TokenPos subpos(node->pn_pos.begin, next->pn_pos.end);
 
         if (logop) {
             if (!builder.logicalExpression(lor, left, right, &subpos, &left)) {
                 return false;
             }
         } else {
-            BinaryOperator op = binop(pn->getKind());
+            BinaryOperator op = binop(node->getKind());
             LOCAL_ASSERT(op > BINOP_ERR && op < BINOP_LIMIT);
 
             if (!builder.binaryExpression(op, left, right, &subpos, &left)) {
                 return false;
             }
         }
     }
 
     dst.set(left);
     return true;
 }
 
 bool
-ASTSerializer::rightAssociate(ParseNode* pn, MutableHandleValue dst)
+ASTSerializer::rightAssociate(ListNode* node, MutableHandleValue dst)
 {
-    MOZ_ASSERT(pn->isArity(PN_LIST));
-    MOZ_ASSERT(pn->pn_count >= 1);
+    MOZ_ASSERT(!node->empty());
 
     // First, we need to reverse the list, so that we can traverse it in the right order.
     // It's OK to destructively reverse the list, because there are no other consumers.
 
-    ParseNode* head = pn->pn_head;
+    ParseNode* head = node->head();
     ParseNode* prev = nullptr;
     ParseNode* current = head;
     ParseNode* next;
     while (current != nullptr) {
         next = current->pn_next;
         current->pn_next = prev;
         prev = current;
         current = next;
@@ -2692,19 +2740,19 @@ ASTSerializer::rightAssociate(ParseNode*
         return false;
     }
     for (ParseNode* next = head->pn_next; next; next = next->pn_next) {
         RootedValue left(cx);
         if (!expression(next, &left)) {
             return false;
         }
 
-        TokenPos subpos(pn->pn_pos.begin, next->pn_pos.end);
-
-        BinaryOperator op = binop(pn->getKind());
+        TokenPos subpos(node->pn_pos.begin, next->pn_pos.end);
+
+        BinaryOperator op = binop(node->getKind());
         LOCAL_ASSERT(op > BINOP_ERR && op < BINOP_LIMIT);
 
         if (!builder.binaryExpression(op, left, right, &subpos, &right)) {
             return false;
         }
     }
 
     dst.set(right);
@@ -2716,91 +2764,103 @@ ASTSerializer::expression(ParseNode* pn,
 {
     if (!CheckRecursionLimit(cx)) {
         return false;
     }
 
     switch (pn->getKind()) {
       case ParseNodeKind::Function:
       {
-        ASTType type = pn->pn_funbox->function()->isArrow() ? AST_ARROW_EXPR : AST_FUNC_EXPR;
-        return function(pn, type, dst);
+        CodeNode* funNode = &pn->as<CodeNode>();
+        ASTType type = funNode->funbox()->function()->isArrow() ? AST_ARROW_EXPR : AST_FUNC_EXPR;
+        return function(funNode, type, dst);
       }
 
       case ParseNodeKind::Comma:
       {
         NodeVector exprs(cx);
-        return expressions(pn, exprs) &&
+        return expressions(&pn->as<ListNode>(), exprs) &&
                builder.sequenceExpression(exprs, &pn->pn_pos, dst);
       }
 
       case ParseNodeKind::Conditional:
       {
-        MOZ_ASSERT(pn->pn_pos.encloses(pn->pn_kid1->pn_pos));
-        MOZ_ASSERT(pn->pn_pos.encloses(pn->pn_kid2->pn_pos));
-        MOZ_ASSERT(pn->pn_pos.encloses(pn->pn_kid3->pn_pos));
+        ConditionalExpression* condNode = &pn->as<ConditionalExpression>();
+        ParseNode* testNode = condNode->kid1();
+        ParseNode* consNode = condNode->kid2();
+        ParseNode* altNode = condNode->kid3();
+        MOZ_ASSERT(condNode->pn_pos.encloses(testNode->pn_pos));
+        MOZ_ASSERT(condNode->pn_pos.encloses(consNode->pn_pos));
+        MOZ_ASSERT(condNode->pn_pos.encloses(altNode->pn_pos));
 
         RootedValue test(cx), cons(cx), alt(cx);
 
-        return expression(pn->pn_kid1, &test) &&
-               expression(pn->pn_kid2, &cons) &&
-               expression(pn->pn_kid3, &alt) &&
-               builder.conditionalExpression(test, cons, alt, &pn->pn_pos, dst);
+        return expression(testNode, &test) &&
+               expression(consNode, &cons) &&
+               expression(altNode, &alt) &&
+               builder.conditionalExpression(test, cons, alt, &condNode->pn_pos, dst);
       }
 
       case ParseNodeKind::Or:
       case ParseNodeKind::And:
-        return leftAssociate(pn, dst);
+        return leftAssociate(&pn->as<ListNode>(), dst);
 
       case ParseNodeKind::PreIncrement:
       case ParseNodeKind::PreDecrement:
       {
-        MOZ_ASSERT(pn->pn_pos.encloses(pn->pn_kid->pn_pos));
-
-        bool inc = pn->isKind(ParseNodeKind::PreIncrement);
+        UnaryNode* incDec = &pn->as<UnaryNode>();
+        ParseNode* operand = incDec->kid();
+        MOZ_ASSERT(incDec->pn_pos.encloses(operand->pn_pos));
+
+        bool inc = incDec->isKind(ParseNodeKind::PreIncrement);
         RootedValue expr(cx);
-        return expression(pn->pn_kid, &expr) &&
-               builder.updateExpression(expr, inc, true, &pn->pn_pos, dst);
+        return expression(operand, &expr) &&
+               builder.updateExpression(expr, inc, true, &incDec->pn_pos, dst);
       }
 
       case ParseNodeKind::PostIncrement:
       case ParseNodeKind::PostDecrement:
       {
-        MOZ_ASSERT(pn->pn_pos.encloses(pn->pn_kid->pn_pos));
-
-        bool inc = pn->isKind(ParseNodeKind::PostIncrement);
+        UnaryNode* incDec = &pn->as<UnaryNode>();
+        ParseNode* operand = incDec->kid();
+        MOZ_ASSERT(incDec->pn_pos.encloses(operand->pn_pos));
+
+        bool inc = incDec->isKind(ParseNodeKind::PostIncrement);
         RootedValue expr(cx);
-        return expression(pn->pn_kid, &expr) &&
-               builder.updateExpression(expr, inc, false, &pn->pn_pos, dst);
+        return expression(operand, &expr) &&
+               builder.updateExpression(expr, inc, false, &incDec->pn_pos, dst);
       }
 
       case ParseNodeKind::Assign:
       case ParseNodeKind::AddAssign:
       case ParseNodeKind::SubAssign:
       case ParseNodeKind::BitOrAssign:
       case ParseNodeKind::BitXorAssign:
       case ParseNodeKind::BitAndAssign:
       case ParseNodeKind::LshAssign:
       case ParseNodeKind::RshAssign:
       case ParseNodeKind::UrshAssign:
       case ParseNodeKind::MulAssign:
       case ParseNodeKind::DivAssign:
       case ParseNodeKind::ModAssign:
       case ParseNodeKind::PowAssign:
       {
-        MOZ_ASSERT(pn->pn_pos.encloses(pn->pn_left->pn_pos));
-        MOZ_ASSERT(pn->pn_pos.encloses(pn->pn_right->pn_pos));
-
-        AssignmentOperator op = aop(pn->getKind());
+        AssignmentNode* assignNode = &pn->as<AssignmentNode>();
+        ParseNode* lhsNode = assignNode->left();
+        ParseNode* rhsNode = assignNode->right();
+        MOZ_ASSERT(assignNode->pn_pos.encloses(lhsNode->pn_pos));
+        MOZ_ASSERT(assignNode->pn_pos.encloses(rhsNode->pn_pos));
+
+        AssignmentOperator op = aop(assignNode->getKind());
         LOCAL_ASSERT(op > AOP_ERR && op < AOP_LIMIT);
 
         RootedValue lhs(cx), rhs(cx);
-        return pattern(pn->pn_left, &lhs) &&
-               expression(pn->pn_right, &rhs) &&
-               builder.assignmentExpression(op, lhs, rhs, &pn->pn_pos, dst);
+        return pattern(lhsNode, &lhs) &&
+               expression(rhsNode, &rhs) &&
+               builder.assignmentExpression(op, lhs, rhs, &assignNode->pn_pos, dst);
       }
 
       case ParseNodeKind::Pipeline:
       case ParseNodeKind::Add:
       case ParseNodeKind::Sub:
       case ParseNodeKind::StrictEq:
       case ParseNodeKind::Eq:
       case ParseNodeKind::StrictNe:
@@ -2815,357 +2875,381 @@ ASTSerializer::expression(ParseNode* pn,
       case ParseNodeKind::Star:
       case ParseNodeKind::Div:
       case ParseNodeKind::Mod:
       case ParseNodeKind::BitOr:
       case ParseNodeKind::BitXor:
       case ParseNodeKind::BitAnd:
       case ParseNodeKind::In:
       case ParseNodeKind::InstanceOf:
-        return leftAssociate(pn, dst);
+        return leftAssociate(&pn->as<ListNode>(), dst);
 
       case ParseNodeKind::Pow:
-        return rightAssociate(pn, dst);
+        return rightAssociate(&pn->as<ListNode>(), dst);
 
       case ParseNodeKind::DeleteName:
       case ParseNodeKind::DeleteProp:
       case ParseNodeKind::DeleteElem:
       case ParseNodeKind::DeleteExpr:
       case ParseNodeKind::TypeOfName:
       case ParseNodeKind::TypeOfExpr:
       case ParseNodeKind::Void:
       case ParseNodeKind::Not:
       case ParseNodeKind::BitNot:
       case ParseNodeKind::Pos:
       case ParseNodeKind::Await:
       case ParseNodeKind::Neg: {
-        MOZ_ASSERT(pn->pn_pos.encloses(pn->pn_kid->pn_pos));
-
-        UnaryOperator op = unop(pn->getKind());
+        UnaryNode* unaryNode = &pn->as<UnaryNode>();
+        ParseNode* operand = unaryNode->kid();
+        MOZ_ASSERT(unaryNode->pn_pos.encloses(operand->pn_pos));
+
+        UnaryOperator op = unop(unaryNode->getKind());
         LOCAL_ASSERT(op > UNOP_ERR && op < UNOP_LIMIT);
 
         RootedValue expr(cx);
-        return expression(pn->pn_kid, &expr) &&
-               builder.unaryExpression(op, expr, &pn->pn_pos, dst);
+        return expression(operand, &expr) &&
+               builder.unaryExpression(op, expr, &unaryNode->pn_pos, dst);
       }
 
       case ParseNodeKind::New:
       case ParseNodeKind::TaggedTemplate:
       case ParseNodeKind::Call:
       case ParseNodeKind::SuperCall:
       {
-        ParseNode* pn_callee = pn->pn_left;
-        ParseNode* pn_args = pn->pn_right;
-        MOZ_ASSERT(pn->pn_pos.encloses(pn_callee->pn_pos));
+        BinaryNode* node = &pn->as<BinaryNode>();
+        ParseNode* calleeNode = node->left();
+        ListNode* argsList = &node->right()->as<ListNode>();
+        MOZ_ASSERT(node->pn_pos.encloses(calleeNode->pn_pos));
 
         RootedValue callee(cx);
-        if (pn->isKind(ParseNodeKind::SuperCall)) {
-            MOZ_ASSERT(pn_callee->isKind(ParseNodeKind::SuperBase));
-            if (!builder.super(&pn_callee->pn_pos, &callee)) {
+        if (node->isKind(ParseNodeKind::SuperCall)) {
+            MOZ_ASSERT(calleeNode->isKind(ParseNodeKind::SuperBase));
+            if (!builder.super(&calleeNode->pn_pos, &callee)) {
                 return false;
             }
         } else {
-            if (!expression(pn_callee, &callee)) {
+            if (!expression(calleeNode, &callee)) {
                 return false;
             }
         }
 
         NodeVector args(cx);
-        if (!args.reserve(pn_args->pn_count)) {
+        if (!args.reserve(argsList->count())) {
             return false;
         }
 
-        for (ParseNode* next = pn_args->pn_head; next; next = next->pn_next) {
-            MOZ_ASSERT(pn->pn_pos.encloses(next->pn_pos));
+        for (ParseNode* argNode : argsList->contents()) {
+            MOZ_ASSERT(node->pn_pos.encloses(argNode->pn_pos));
 
             RootedValue arg(cx);
-            if (!expression(next, &arg)) {
+            if (!expression(argNode, &arg)) {
                 return false;
             }
             args.infallibleAppend(arg);
         }
 
-        if (pn->getKind() == ParseNodeKind::TaggedTemplate) {
-            return builder.taggedTemplate(callee, args, &pn->pn_pos, dst);
+        if (node->getKind() == ParseNodeKind::TaggedTemplate) {
+            return builder.taggedTemplate(callee, args, &node->pn_pos, dst);
         }
 
         // SUPERCALL is Call(super, args)
-        return pn->isKind(ParseNodeKind::New)
-               ? builder.newExpression(callee, args, &pn->pn_pos, dst)
-
-            : builder.callExpression(callee, args, &pn->pn_pos, dst);
+        return node->isKind(ParseNodeKind::New)
+               ? builder.newExpression(callee, args, &node->pn_pos, dst)
+               : builder.callExpression(callee, args, &node->pn_pos, dst);
       }
 
       case ParseNodeKind::Dot:
       {
-        MOZ_ASSERT(pn->pn_pos.encloses(pn->pn_left->pn_pos));
+        PropertyAccess* prop = &pn->as<PropertyAccess>();
+        MOZ_ASSERT(prop->pn_pos.encloses(prop->expression().pn_pos));
 
         RootedValue expr(cx);
         RootedValue propname(cx);
-        RootedAtom pnAtom(cx, pn->pn_right->pn_atom);
-
-        if (pn->as<PropertyAccess>().isSuper()) {
-            if (!builder.super(&pn->pn_left->pn_pos, &expr)) {
+        RootedAtom pnAtom(cx, prop->key().atom());
+
+        if (prop->isSuper()) {
+            if (!builder.super(&prop->expression().pn_pos, &expr)) {
                 return false;
             }
         } else {
-            if (!expression(pn->pn_left, &expr)) {
+            if (!expression(&prop->expression(), &expr)) {
                 return false;
             }
         }
 
         return identifier(pnAtom, nullptr, &propname) &&
-               builder.memberExpression(false, expr, propname, &pn->pn_pos, dst);
+               builder.memberExpression(false, expr, propname, &prop->pn_pos, dst);
       }
 
       case ParseNodeKind::Elem:
       {
-        MOZ_ASSERT(pn->pn_pos.encloses(pn->pn_left->pn_pos));
-        MOZ_ASSERT(pn->pn_pos.encloses(pn->pn_right->pn_pos));
-
-        RootedValue left(cx), right(cx);
-
-        if (pn->as<PropertyByValue>().isSuper()) {
-            if (!builder.super(&pn->pn_left->pn_pos, &left)) {
+        PropertyByValue* elem = &pn->as<PropertyByValue>();
+        MOZ_ASSERT(elem->pn_pos.encloses(elem->expression().pn_pos));
+        MOZ_ASSERT(elem->pn_pos.encloses(elem->key().pn_pos));
+
+        RootedValue expr(cx), key(cx);
+
+        if (elem->isSuper()) {
+            if (!builder.super(&elem->expression().pn_pos, &expr)) {
                 return false;
             }
         } else {
-            if (!expression(pn->pn_left, &left)) {
+            if (!expression(&elem->expression(), &expr)) {
                 return false;
             }
         }
 
-        return expression(pn->pn_right, &right) &&
-               builder.memberExpression(true, left, right, &pn->pn_pos, dst);
+        return expression(&elem->key(), &key) &&
+               builder.memberExpression(true, expr, key, &elem->pn_pos, dst);
       }
 
       case ParseNodeKind::CallSiteObj:
       {
+        CallSiteNode* callSiteObj = &pn->as<CallSiteNode>();
+        ListNode* rawNodes = callSiteObj->rawNodes();
         NodeVector raw(cx);
-        if (!raw.reserve(pn->pn_head->pn_count)) {
+        if (!raw.reserve(rawNodes->count())) {
             return false;
         }
-        for (ParseNode* next = pn->pn_head->pn_head; next; next = next->pn_next) {
-            MOZ_ASSERT(pn->pn_pos.encloses(next->pn_pos));
+        for (ParseNode* item : rawNodes->contents()) {
+            NameNode* rawItem = &item->as<NameNode>();
+            MOZ_ASSERT(callSiteObj->pn_pos.encloses(rawItem->pn_pos));
 
             RootedValue expr(cx);
-            expr.setString(next->pn_atom);
+            expr.setString(rawItem->atom());
             raw.infallibleAppend(expr);
         }
 
         NodeVector cooked(cx);
-        if (!cooked.reserve(pn->pn_count - 1)) {
+        if (!cooked.reserve(callSiteObj->count() - 1)) {
             return false;
         }
 
-        for (ParseNode* next = pn->pn_head->pn_next; next; next = next->pn_next) {
-            MOZ_ASSERT(pn->pn_pos.encloses(next->pn_pos));
+        for (ParseNode* cookedItem : callSiteObj->contentsFrom(rawNodes->pn_next)) {
+            MOZ_ASSERT(callSiteObj->pn_pos.encloses(cookedItem->pn_pos));
 
             RootedValue expr(cx);
-            if (next->isKind(ParseNodeKind::RawUndefined)) {
+            if (cookedItem->isKind(ParseNodeKind::RawUndefined)) {
                 expr.setUndefined();
             } else {
-                MOZ_ASSERT(next->isKind(ParseNodeKind::TemplateString));
-                expr.setString(next->pn_atom);
+                MOZ_ASSERT(cookedItem->isKind(ParseNodeKind::TemplateString));
+                expr.setString(cookedItem->as<NameNode>().atom());
             }
             cooked.infallibleAppend(expr);
         }
 
-        return builder.callSiteObj(raw, cooked, &pn->pn_pos, dst);
+        return builder.callSiteObj(raw, cooked, &callSiteObj->pn_pos, dst);
       }
 
       case ParseNodeKind::Array:
       {
+        ListNode* array = &pn->as<ListNode>();
         NodeVector elts(cx);
-        if (!elts.reserve(pn->pn_count)) {
+        if (!elts.reserve(array->count())) {
             return false;
         }
 
-        for (ParseNode* next = pn->pn_head; next; next = next->pn_next) {
-            MOZ_ASSERT(pn->pn_pos.encloses(next->pn_pos));
-
-            if (next->isKind(ParseNodeKind::Elision)) {
+        for (ParseNode* item : array->contents()) {
+            MOZ_ASSERT(array->pn_pos.encloses(item->pn_pos));
+
+            if (item->isKind(ParseNodeKind::Elision)) {
                 elts.infallibleAppend(NullValue());
             } else {
                 RootedValue expr(cx);
-                if (!expression(next, &expr)) {
+                if (!expression(item, &expr)) {
                     return false;
                 }
                 elts.infallibleAppend(expr);
             }
         }
 
-        return builder.arrayExpression(elts, &pn->pn_pos, dst);
+        return builder.arrayExpression(elts, &array->pn_pos, dst);
       }
 
       case ParseNodeKind::Spread:
       {
           RootedValue expr(cx);
-          return expression(pn->pn_kid, &expr) &&
+          return expression(pn->as<UnaryNode>().kid(), &expr) &&
                  builder.spreadExpression(expr, &pn->pn_pos, dst);
       }
 
       case ParseNodeKind::ComputedName:
       {
          RootedValue name(cx);
-         return expression(pn->pn_kid, &name) &&
+         return expression(pn->as<UnaryNode>().kid(), &name) &&
                 builder.computedName(name, &pn->pn_pos, dst);
       }
 
       case ParseNodeKind::Object:
       {
+        ListNode* obj = &pn->as<ListNode>();
         NodeVector elts(cx);
-        if (!elts.reserve(pn->pn_count)) {
+        if (!elts.reserve(obj->count())) {
             return false;
         }
 
-        for (ParseNode* next = pn->pn_head; next; next = next->pn_next) {
-            MOZ_ASSERT(pn->pn_pos.encloses(next->pn_pos));
+        for (ParseNode* item : obj->contents()) {
+            MOZ_ASSERT(obj->pn_pos.encloses(item->pn_pos));
 
             RootedValue prop(cx);
-            if (!property(next, &prop)) {
+            if (!property(item, &prop)) {
                 return false;
             }
             elts.infallibleAppend(prop);
         }
 
-        return builder.objectExpression(elts, &pn->pn_pos, dst);
+        return builder.objectExpression(elts, &obj->pn_pos, dst);
       }
 
       case ParseNodeKind::Name:
-        return identifier(pn, dst);
+        return identifier(&pn->as<NameNode>(), dst);
 
       case ParseNodeKind::This:
         return builder.thisExpression(&pn->pn_pos, dst);
 
       case ParseNodeKind::TemplateStringList:
       {
+        ListNode* list = &pn->as<ListNode>();
         NodeVector elts(cx);
-        if (!elts.reserve(pn->pn_count)) {
+        if (!elts.reserve(list->count())) {
             return false;
         }
 
-        for (ParseNode* next = pn->pn_head; next; next = next->pn_next) {
-            MOZ_ASSERT(pn->pn_pos.encloses(next->pn_pos));
+        for (ParseNode* item : list->contents()) {
+            MOZ_ASSERT(list->pn_pos.encloses(item->pn_pos));
 
             RootedValue expr(cx);
-            if (!expression(next, &expr)) {
+            if (!expression(item, &expr)) {
                 return false;
             }
             elts.infallibleAppend(expr);
         }
 
-        return builder.templateLiteral(elts, &pn->pn_pos, dst);
+        return builder.templateLiteral(elts, &list->pn_pos, dst);
       }
 
       case ParseNodeKind::TemplateString:
       case ParseNodeKind::String:
       case ParseNodeKind::RegExp:
       case ParseNodeKind::Number:
       case ParseNodeKind::True:
       case ParseNodeKind::False:
       case ParseNodeKind::Null:
       case ParseNodeKind::RawUndefined:
         return literal(pn, dst);
 
       case ParseNodeKind::YieldStar:
       {
-        MOZ_ASSERT(pn->pn_pos.encloses(pn->pn_kid->pn_pos));
+        UnaryNode* yieldNode = &pn->as<UnaryNode>();
+        ParseNode* operand = yieldNode->kid();
+        MOZ_ASSERT(yieldNode->pn_pos.encloses(operand->pn_pos));
 
         RootedValue arg(cx);
-        return expression(pn->pn_kid, &arg) &&
-               builder.yieldExpression(arg, Delegating, &pn->pn_pos, dst);
+        return expression(operand, &arg) &&
+               builder.yieldExpression(arg, Delegating, &yieldNode->pn_pos, dst);
       }
 
       case ParseNodeKind::Yield:
       {
-        MOZ_ASSERT_IF(pn->pn_kid, pn->pn_pos.encloses(pn->pn_kid->pn_pos));
+        UnaryNode* yieldNode = &pn->as<UnaryNode>();
+        ParseNode* operand = yieldNode->kid();
+        MOZ_ASSERT_IF(operand, yieldNode->pn_pos.encloses(operand->pn_pos));
 
         RootedValue arg(cx);
-        return optExpression(pn->pn_kid, &arg) &&
-               builder.yieldExpression(arg, NotDelegating, &pn->pn_pos, dst);
+        return optExpression(operand, &arg) &&
+               builder.yieldExpression(arg, NotDelegating, &yieldNode->pn_pos, dst);
       }
 
       case ParseNodeKind::Class:
-        return classDefinition(pn, true, dst);
+        return classDefinition(&pn->as<ClassNode>(), true, dst);
 
       case ParseNodeKind::NewTarget:
       case ParseNodeKind::ImportMeta:
       {
-        MOZ_ASSERT(pn->pn_left->isKind(ParseNodeKind::PosHolder));
-        MOZ_ASSERT(pn->pn_pos.encloses(pn->pn_left->pn_pos));
-        MOZ_ASSERT(pn->pn_right->isKind(ParseNodeKind::PosHolder));
-        MOZ_ASSERT(pn->pn_pos.encloses(pn->pn_right->pn_pos));
+        BinaryNode* node = &pn->as<BinaryNode>();
+        ParseNode* firstNode = node->left();
+        MOZ_ASSERT(firstNode->isKind(ParseNodeKind::PosHolder));
+        MOZ_ASSERT(node->pn_pos.encloses(firstNode->pn_pos));
+
+        ParseNode* secondNode = node->right();
+        MOZ_ASSERT(secondNode->isKind(ParseNodeKind::PosHolder));
+        MOZ_ASSERT(node->pn_pos.encloses(secondNode->pn_pos));
 
         RootedValue firstIdent(cx);
         RootedValue secondIdent(cx);
 
         RootedAtom firstStr(cx);
         RootedAtom secondStr(cx);
 
-        if (pn->getKind() == ParseNodeKind::NewTarget) {
+        if (node->getKind() == ParseNodeKind::NewTarget) {
             firstStr = cx->names().new_;
             secondStr = cx->names().target;
         } else {
             firstStr = cx->names().import;
             secondStr = cx->names().meta;
         }
 
-        return identifier(firstStr, &pn->pn_left->pn_pos, &firstIdent) &&
-               identifier(secondStr, &pn->pn_right->pn_pos, &secondIdent) &&
-               builder.metaProperty(firstIdent, secondIdent, &pn->pn_pos, dst);
+        return identifier(firstStr, &firstNode->pn_pos, &firstIdent) &&
+               identifier(secondStr, &secondNode->pn_pos, &secondIdent) &&
+               builder.metaProperty(firstIdent, secondIdent, &node->pn_pos, dst);
       }
 
       case ParseNodeKind::CallImport:
       {
-        MOZ_ASSERT(pn->pn_left->isKind(ParseNodeKind::PosHolder));
-        MOZ_ASSERT(pn->pn_pos.encloses(pn->pn_left->pn_pos));
-        MOZ_ASSERT(pn->pn_pos.encloses(pn->pn_right->pn_pos));
+        BinaryNode* node = &pn->as<BinaryNode>();
+        ParseNode* identNode = node->left();
+        MOZ_ASSERT(identNode->isKind(ParseNodeKind::PosHolder));
+        MOZ_ASSERT(identNode->pn_pos.encloses(identNode->pn_pos));
+
+        ParseNode* argNode = node->right();
+        MOZ_ASSERT(node->pn_pos.encloses(argNode->pn_pos));
 
         RootedValue ident(cx);
         RootedValue arg(cx);
 
         HandlePropertyName name = cx->names().import;
-        return identifier(name, &pn->pn_left->pn_pos, &ident) &&
-               expression(pn->pn_right, &arg) &&
+        return identifier(name, &identNode->pn_pos, &ident) &&
+               expression(argNode, &arg) &&
                builder.callImportExpression(ident, arg, &pn->pn_pos, dst);
       }
 
-      case ParseNodeKind::SetThis:
+      case ParseNodeKind::SetThis: {
         // SETTHIS is used to assign the result of a super() call to |this|.
         // It's not part of the original AST, so just forward to the call.
-        MOZ_ASSERT(pn->pn_left->isKind(ParseNodeKind::Name));
-        return expression(pn->pn_right, dst);
+        BinaryNode* node = &pn->as<BinaryNode>();
+        MOZ_ASSERT(node->left()->isKind(ParseNodeKind::Name));
+        return expression(node->right(), dst);
+      }
 
       default:
         LOCAL_NOT_REACHED("unexpected expression type");
     }
 }
 
 bool
-ASTSerializer::propertyName(ParseNode* pn, MutableHandleValue dst)
+ASTSerializer::propertyName(ParseNode* key, MutableHandleValue dst)
 {
-    if (pn->isKind(ParseNodeKind::ComputedName)) {
-        return expression(pn, dst);
+    if (key->isKind(ParseNodeKind::ComputedName)) {
+        return expression(key, dst);
     }
-    if (pn->isKind(ParseNodeKind::ObjectPropertyName)) {
-        return identifier(pn, dst);
+    if (key->isKind(ParseNodeKind::ObjectPropertyName)) {
+        return identifier(&key->as<NameNode>(), dst);
     }
 
-    LOCAL_ASSERT(pn->isKind(ParseNodeKind::String) || pn->isKind(ParseNodeKind::Number));
-
-    return literal(pn, dst);
+    LOCAL_ASSERT(key->isKind(ParseNodeKind::String) || key->isKind(ParseNodeKind::Number));
+
+    return literal(key, dst);
 }
 
 bool
 ASTSerializer::property(ParseNode* pn, MutableHandleValue dst)
 {
     if (pn->isKind(ParseNodeKind::MutateProto)) {
         RootedValue val(cx);
-        return expression(pn->pn_kid, &val) &&
+        return expression(pn->as<UnaryNode>().kid(), &val) &&
                builder.prototypeMutation(val, &pn->pn_pos, dst);
     }
     if (pn->isKind(ParseNodeKind::Spread)) {
         return expression(pn, dst);
     }
 
     PropKind kind;
     switch (pn->getOp()) {
@@ -3180,34 +3264,38 @@ ASTSerializer::property(ParseNode* pn, M
       case JSOP_INITPROP_SETTER:
         kind = PROP_SETTER;
         break;
 
       default:
         LOCAL_NOT_REACHED("unexpected object-literal property");
     }
 
-    bool isShorthand = pn->isKind(ParseNodeKind::Shorthand);
+    BinaryNode* node = &pn->as<BinaryNode>();
+    ParseNode* keyNode = node->left();
+    ParseNode* valNode = node->right();
+
+    bool isShorthand = node->isKind(ParseNodeKind::Shorthand);
     bool isMethod =
-        pn->pn_right->isKind(ParseNodeKind::Function) &&
-        pn->pn_right->pn_funbox->function()->kind() == JSFunction::Method;
+        valNode->isKind(ParseNodeKind::Function) &&
+        valNode->as<CodeNode>().funbox()->function()->kind() == JSFunction::Method;
     RootedValue key(cx), val(cx);
-    return propertyName(pn->pn_left, &key) &&
-           expression(pn->pn_right, &val) &&
-           builder.propertyInitializer(key, val, kind, isShorthand, isMethod, &pn->pn_pos, dst);
+    return propertyName(keyNode, &key) &&
+           expression(valNode, &val) &&
+           builder.propertyInitializer(key, val, kind, isShorthand, isMethod, &node->pn_pos, dst);
 }
 
 bool
 ASTSerializer::literal(ParseNode* pn, MutableHandleValue dst)
 {
     RootedValue val(cx);
     switch (pn->getKind()) {
       case ParseNodeKind::TemplateString:
       case ParseNodeKind::String:
-        val.setString(pn->pn_atom);
+        val.setString(pn->as<NameNode>().atom());
         break;
 
       case ParseNodeKind::RegExp:
       {
         RootedObject re1(cx, pn->as<RegExpLiteral>().objbox()->object);
         LOCAL_ASSERT(re1 && re1->is<RegExpObject>());
 
         RootedObject re2(cx, CloneRegExpObject(cx, re1.as<RegExpObject>()));
@@ -3215,17 +3303,17 @@ ASTSerializer::literal(ParseNode* pn, Mu
             return false;
         }
 
         val.setObject(*re2);
         break;
       }
 
       case ParseNodeKind::Number:
-        val.setNumber(pn->pn_dval);
+        val.setNumber(pn->as<NumericLiteral>().value());
         break;
 
       case ParseNodeKind::Null:
         val.setNull();
         break;
 
       case ParseNodeKind::RawUndefined:
         val.setUndefined();
@@ -3242,263 +3330,266 @@ ASTSerializer::literal(ParseNode* pn, Mu
       default:
         LOCAL_NOT_REACHED("unexpected literal type");
     }
 
     return builder.literal(val, &pn->pn_pos, dst);
 }
 
 bool
-ASTSerializer::arrayPattern(ParseNode* pn, MutableHandleValue dst)
+ASTSerializer::arrayPattern(ListNode* array, MutableHandleValue dst)
 {
-    MOZ_ASSERT(pn->isKind(ParseNodeKind::Array));
+    MOZ_ASSERT(array->isKind(ParseNodeKind::Array));
 
     NodeVector elts(cx);
-    if (!elts.reserve(pn->pn_count)) {
+    if (!elts.reserve(array->count())) {
         return false;
     }
 
-    for (ParseNode* next = pn->pn_head; next; next = next->pn_next) {
-        if (next->isKind(ParseNodeKind::Elision)) {
+    for (ParseNode* item : array->contents()) {
+        if (item->isKind(ParseNodeKind::Elision)) {
             elts.infallibleAppend(NullValue());
-        } else if (next->isKind(ParseNodeKind::Spread)) {
+        } else if (item->isKind(ParseNodeKind::Spread)) {
             RootedValue target(cx);
             RootedValue spread(cx);
-            if (!pattern(next->pn_kid, &target)) {
+            if (!pattern(item->as<UnaryNode>().kid(), &target)) {
                 return false;
             }
-            if(!builder.spreadExpression(target, &next->pn_pos, &spread))
+            if(!builder.spreadExpression(target, &item->pn_pos, &spread))
                 return false;
             elts.infallibleAppend(spread);
         } else {
             RootedValue patt(cx);
-            if (!pattern(next, &patt)) {
+            if (!pattern(item, &patt)) {
                 return false;
             }
             elts.infallibleAppend(patt);
         }
     }
 
-    return builder.arrayPattern(elts, &pn->pn_pos, dst);
+    return builder.arrayPattern(elts, &array->pn_pos, dst);
 }
 
 bool
-ASTSerializer::objectPattern(ParseNode* pn, MutableHandleValue dst)
+ASTSerializer::objectPattern(ListNode* obj, MutableHandleValue dst)
 {
-    MOZ_ASSERT(pn->isKind(ParseNodeKind::Object));
+    MOZ_ASSERT(obj->isKind(ParseNodeKind::Object));
 
     NodeVector elts(cx);
-    if (!elts.reserve(pn->pn_count)) {
+    if (!elts.reserve(obj->count())) {
         return false;
     }
 
-    for (ParseNode* propdef = pn->pn_head; propdef; propdef = propdef->pn_next) {
+    for (ParseNode* propdef : obj->contents()) {
         if (propdef->isKind(ParseNodeKind::Spread)) {
             RootedValue target(cx);
             RootedValue spread(cx);
-            if (!pattern(propdef->pn_kid, &target)) {
+            if (!pattern(propdef->as<UnaryNode>().kid(), &target)) {
                 return false;
             }
             if(!builder.spreadExpression(target, &propdef->pn_pos, &spread))
                 return false;
             elts.infallibleAppend(spread);
             continue;
         }
         LOCAL_ASSERT(propdef->isKind(ParseNodeKind::MutateProto) != propdef->isOp(JSOP_INITPROP));
 
         RootedValue key(cx);
         ParseNode* target;
         if (propdef->isKind(ParseNodeKind::MutateProto)) {
             RootedValue pname(cx, StringValue(cx->names().proto));
             if (!builder.literal(pname, &propdef->pn_pos, &key)) {
                 return false;
             }
-            target = propdef->pn_kid;
+            target = propdef->as<UnaryNode>().kid();
         } else {
-            if (!propertyName(propdef->pn_left, &key)) {
+            BinaryNode* prop = &propdef->as<BinaryNode>();
+            if (!propertyName(prop->left(), &key)) {
                 return false;
             }
-            target = propdef->pn_right;
+            target = prop->right();
         }
 
         RootedValue patt(cx), prop(cx);
         if (!pattern(target, &patt) ||
             !builder.propertyPattern(key, patt, propdef->isKind(ParseNodeKind::Shorthand),
                                      &propdef->pn_pos,
                                      &prop))
         {
             return false;
         }
 
         elts.infallibleAppend(prop);
     }
 
-    return builder.objectPattern(elts, &pn->pn_pos, dst);
+    return builder.objectPattern(elts, &obj->pn_pos, dst);
 }
 
 bool
 ASTSerializer::pattern(ParseNode* pn, MutableHandleValue dst)
 {
     if (!CheckRecursionLimit(cx)) {
         return false;
     }
 
     switch (pn->getKind()) {
       case ParseNodeKind::Object:
-        return objectPattern(pn, dst);
+        return objectPattern(&pn->as<ListNode>(), dst);
 
       case ParseNodeKind::Array:
-        return arrayPattern(pn, dst);
+        return arrayPattern(&pn->as<ListNode>(), dst);
 
       default:
         return expression(pn, dst);
     }
 }
 
 bool
 ASTSerializer::identifier(HandleAtom atom, TokenPos* pos, MutableHandleValue dst)
 {
     RootedValue atomContentsVal(cx, unrootedAtomContents(atom));
     return builder.identifier(atomContentsVal, pos, dst);
 }
 
 bool
-ASTSerializer::identifier(ParseNode* pn, MutableHandleValue dst)
+ASTSerializer::identifier(NameNode* id, MutableHandleValue dst)
 {
-    LOCAL_ASSERT(pn->isArity(PN_NAME) || pn->isArity(PN_NULLARY));
-    LOCAL_ASSERT(pn->pn_atom);
-
-    RootedAtom pnAtom(cx, pn->pn_atom);
-    return identifier(pnAtom, &pn->pn_pos, dst);
+    LOCAL_ASSERT(id->atom());
+
+    RootedAtom pnAtom(cx, id->atom());
+    return identifier(pnAtom, &id->pn_pos, dst);
 }
 
 bool
-ASTSerializer::function(ParseNode* pn, ASTType type, MutableHandleValue dst)
+ASTSerializer::function(CodeNode* funNode, ASTType type, MutableHandleValue dst)
 {
-    RootedFunction func(cx, pn->pn_funbox->function());
+    FunctionBox* funbox = funNode->funbox();
+    RootedFunction func(cx, funbox->function());
 
     GeneratorStyle generatorStyle =
-        pn->pn_funbox->isGenerator()
+        funbox->isGenerator()
         ? GeneratorStyle::ES6
         : GeneratorStyle::None;
 
-    bool isAsync = pn->pn_funbox->isAsync();
-    bool isExpression = pn->pn_funbox->hasExprBody();
+    bool isAsync = funbox->isAsync();
+    bool isExpression = funbox->hasExprBody();
 
     RootedValue id(cx);
     RootedAtom funcAtom(cx, func->explicitName());
     if (!optIdentifier(funcAtom, nullptr, &id)) {
         return false;
     }
 
     NodeVector args(cx);
     NodeVector defaults(cx);
 
     RootedValue body(cx), rest(cx);
-    if (pn->pn_funbox->hasRest()) {
+    if (funbox->hasRest()) {
         rest.setUndefined();
     } else {
         rest.setNull();
     }
-    return functionArgsAndBody(pn->pn_body, args, defaults, isAsync, isExpression, &body, &rest) &&
-           builder.function(type, &pn->pn_pos, id, args, defaults, body,
+    return functionArgsAndBody(funNode->body(), args, defaults, isAsync, isExpression, &body,
+                               &rest) &&
+           builder.function(type, &funNode->pn_pos, id, args, defaults, body,
                             rest, generatorStyle, isAsync, isExpression, dst);
 }
 
 bool
 ASTSerializer::functionArgsAndBody(ParseNode* pn, NodeVector& args, NodeVector& defaults,
                                    bool isAsync, bool isExpression,
                                    MutableHandleValue body, MutableHandleValue rest)
 {
-    ParseNode* pnargs;
-    ParseNode* pnbody;
+    ListNode* argsList;
+    ParseNode* bodyNode;
 
     /* Extract the args and body separately. */
     if (pn->isKind(ParseNodeKind::ParamsBody)) {
-        pnargs = pn;
-        pnbody = pn->last();
+        argsList = &pn->as<ListNode>();
+        bodyNode = argsList->last();
     } else {
-        pnargs = nullptr;
-        pnbody = pn;
+        argsList = nullptr;
+        bodyNode = pn;
     }
 
-    if (pnbody->isKind(ParseNodeKind::LexicalScope)) {
-        pnbody = pnbody->scopeBody();
+    if (bodyNode->is<LexicalScopeNode>()) {
+        bodyNode = bodyNode->as<LexicalScopeNode>().scopeBody();
     }
 
     /* Serialize the arguments and body. */
-    switch (pnbody->getKind()) {
+    switch (bodyNode->getKind()) {
       case ParseNodeKind::Return: /* expression closure, no destructured args */
-        return functionArgs(pn, pnargs, args, defaults, rest) &&
-               expression(pnbody->pn_kid, body);
+        return functionArgs(pn, argsList, args, defaults, rest) &&
+               expression(bodyNode->as<UnaryNode>().kid(), body);
 
       case ParseNodeKind::StatementList:     /* statement closure */
       {
-        ParseNode* pnstart = pnbody->pn_head;
+        ParseNode* firstNode = bodyNode->as<ListNode>().head();
 
         // Skip over initial yield in generator.
-        if (pnstart && pnstart->isKind(ParseNodeKind::InitialYield)) {
-            pnstart = pnstart->pn_next;
+        if (firstNode && firstNode->isKind(ParseNodeKind::InitialYield)) {
+            firstNode = firstNode->pn_next;
         }
 
         // Async arrow with expression body is converted into STATEMENTLIST
         // to insert initial yield.
         if (isAsync && isExpression) {
-            MOZ_ASSERT(pnstart->getKind() == ParseNodeKind::Return);
-            return functionArgs(pn, pnargs, args, defaults, rest) &&
-                   expression(pnstart->pn_kid, body);
+            MOZ_ASSERT(firstNode->getKind() == ParseNodeKind::Return);
+            return functionArgs(pn, argsList, args, defaults, rest) &&
+                   expression(firstNode->as<UnaryNode>().kid(), body);
         }
 
-        return functionArgs(pn, pnargs, args, defaults, rest) &&
-               functionBody(pnstart, &pnbody->pn_pos, body);
+        return functionArgs(pn, argsList, args, defaults, rest) &&
+               functionBody(firstNode, &bodyNode->pn_pos, body);
       }
 
       default:
         LOCAL_NOT_REACHED("unexpected function contents");
     }
 }
 
 bool
-ASTSerializer::functionArgs(ParseNode* pn, ParseNode* pnargs,
+ASTSerializer::functionArgs(ParseNode* pn, ListNode* argsList,
                             NodeVector& args, NodeVector& defaults,
                             MutableHandleValue rest)
 {
-    if (!pnargs) {
+    if (!argsList) {
         return true;
     }
 
     RootedValue node(cx);
     bool defaultsNull = true;
     MOZ_ASSERT(defaults.empty(),
                "must be initially empty for it to be proper to clear this "
                "when there are no defaults");
 
-    for (ParseNode* arg = pnargs->pn_head; arg && arg != pnargs->last(); arg = arg->pn_next) {
+    for (ParseNode* arg : argsList->contentsTo(argsList->last())) {
         ParseNode* pat;
         ParseNode* defNode;
         if (arg->isKind(ParseNodeKind::Name) ||
             arg->isKind(ParseNodeKind::Array) ||
             arg->isKind(ParseNodeKind::Object))
         {
             pat = arg;
             defNode = nullptr;
         } else {
             MOZ_ASSERT(arg->isKind(ParseNodeKind::Assign));
-            pat = arg->pn_left;
-            defNode = arg->pn_right;
+            AssignmentNode* assignNode = &arg->as<AssignmentNode>();
+            pat = assignNode->left();
+            defNode = assignNode->right();
         }
 
         // Process the name or pattern.
         MOZ_ASSERT(pat->isKind(ParseNodeKind::Name) ||
                    pat->isKind(ParseNodeKind::Array) ||
                    pat->isKind(ParseNodeKind::Object));
         if (!pattern(pat, &node)) {
             return false;
         }
-        if (rest.isUndefined() && arg->pn_next == pnargs->last()) {
+        if (rest.isUndefined() && arg->pn_next == argsList->last()) {
             rest.setObject(node.toObject());
         } else {
             if (!args.append(node)) {
                 return false;
             }
         }
 
         // Process its default (or lack thereof).
@@ -3724,21 +3815,21 @@ reflect_parse(JSContext* cx, uint32_t ar
 
         ModuleSharedContext modulesc(cx, module, &cx->global()->emptyGlobalScope(), builder);
         pn = parser.moduleBody(&modulesc);
         if (!pn) {
             return false;
         }
 
         MOZ_ASSERT(pn->getKind() == ParseNodeKind::Module);
-        pn = pn->pn_body;
+        pn = pn->as<CodeNode>().body();
     }
 
     RootedValue val(cx);
-    if (!serialize.program(pn, &val)) {
+    if (!serialize.program(&pn->as<ListNode>(), &val)) {
         args.rval().setNull();
         return false;
     }
 
     args.rval().set(val);
     return true;
 }
 
--- a/js/src/frontend/BinSource-auto.cpp
+++ b/js/src/frontend/BinSource-auto.cpp
@@ -1907,17 +1907,17 @@ BinASTParser<Tok>::parseSumParameter(con
 {
     ParseNode* result;
     switch (kind) {
       case BinKind::ArrayBinding:
         MOZ_TRY_VAR(result, parseInterfaceArrayBinding(start, kind, fields));
         break;
       case BinKind::BindingIdentifier:
         MOZ_TRY_VAR(result, parseInterfaceBindingIdentifier(start, kind, fields));
-        if (!parseContext_->positionalFormalParameterNames().append(result->pn_atom))
+        if (!parseContext_->positionalFormalParameterNames().append(result->template as<NameNode>().atom()))
             return raiseOOM();
         if (parseContext_->isFunctionBox())
             parseContext_->functionBox()->length++;
         break;
       case BinKind::BindingWithInitializer:
         MOZ_TRY_VAR(result, parseInterfaceBindingWithInitializer(start, kind, fields));
         break;
       case BinKind::ObjectBinding:
@@ -3151,17 +3151,17 @@ BinASTParser<Tok>::parseInterfaceBinaryE
         break;
     }
 
     ParseNode* result;
     if (left->isKind(pnk) &&
         pnk != ParseNodeKind::Pow /* ParseNodeKind::Pow is not left-associative */)
     {
         // Regroup left-associative operations into lists.
-        left->appendWithoutOrderAssumption(right);
+        left->template as<ListNode>().appendWithoutOrderAssumption(right);
         result = left;
     } else {
         BINJS_TRY_DECL(list, factory_.newList(pnk, tokenizer_->pos(start)));
 
         list->appendWithoutOrderAssumption(left);
         list->appendWithoutOrderAssumption(right);
         result = list;
     }
@@ -3471,17 +3471,17 @@ BinASTParser<Tok>::parseInterfaceCallExp
 
 /*
  interface CatchClause : Node {
     AssertedParameterScope? bindingScope;
     Binding binding;
     Block body;
  }
 */
-template<typename Tok> JS::Result<ParseNode*>
+template<typename Tok> JS::Result<LexicalScopeNode*>
 BinASTParser<Tok>::parseCatchClause()
 {
     BinKind kind;
     BinFields fields(cx_);
     AutoTaggedTuple guard(*tokenizer_);
 
     MOZ_TRY(tokenizer_->enterTaggedTuple(kind, fields, guard));
     if (kind != BinKind::CatchClause) {
@@ -3489,17 +3489,17 @@ BinASTParser<Tok>::parseCatchClause()
     }
     const auto start = tokenizer_->offset();
     BINJS_MOZ_TRY_DECL(result, parseInterfaceCatchClause(start, kind, fields));
     MOZ_TRY(guard.done());
 
     return result;
 }
 
-template<typename Tok> JS::Result<ParseNode*>
+template<typename Tok> JS::Result<LexicalScopeNode*>
 BinASTParser<Tok>::parseInterfaceCatchClause(const size_t start, const BinKind kind, const BinFields& fields)
 {
     MOZ_ASSERT(kind == BinKind::CatchClause);
     BINJS_TRY(CheckRecursionLimit(cx_));
 
 #if defined(DEBUG)
     const BinField expected_fields[3] = { BinField::BindingScope, BinField::Binding, BinField::Body };
     MOZ_TRY(tokenizer_->checkFields(kind, fields, expected_fields));
@@ -4805,17 +4805,17 @@ BinASTParser<Tok>::parseInterfaceForInOf
     AutoVariableDeclarationKind kindGuard(this);
 
     BINJS_MOZ_TRY_DECL(kind_, parseVariableDeclarationKind());
 
     BINJS_MOZ_TRY_DECL(binding, parseBinding());
 
     // Restored by `kindGuard`.
     variableDeclarationKind_ = kind_;
-    MOZ_TRY(checkBinding(binding->pn_atom->asPropertyName()));
+    MOZ_TRY(checkBinding(binding->template as<NameNode>().atom()->asPropertyName()));
     auto pnk =
         kind_ == VariableDeclarationKind::Let
             ? ParseNodeKind::Let
             : ParseNodeKind::Var;
     BINJS_TRY_DECL(result, factory_.newDeclarationList(pnk, tokenizer_->pos(start)));
     factory_.addList(result, binding);
     return result;
 }
@@ -4866,17 +4866,18 @@ BinASTParser<Tok>::parseInterfaceForInSt
 
     BINJS_MOZ_TRY_DECL(left, parseForInOfBindingOrAssignmentTarget());
 
     BINJS_MOZ_TRY_DECL(right, parseExpression());
 
     BINJS_MOZ_TRY_DECL(body, parseStatement());
 
     BINJS_TRY_DECL(forHead, factory_.newForInOrOfHead(ParseNodeKind::ForIn, left, right, tokenizer_->pos(start)));
-    BINJS_TRY_DECL(result, factory_.newForStatement(start, forHead, body, /*flags*/ 0));
+    ParseNode* result;
+    BINJS_TRY_VAR(result, factory_.newForStatement(start, forHead, body, /*flags*/ 0));
 
     if (!scope.isEmpty()) {
         BINJS_TRY_DECL(bindings, NewLexicalScopeData(cx_, scope, alloc_, parseContext_));
         BINJS_TRY_VAR(result, factory_.newLexicalScope(*bindings, result));
     }
     return result;
 }
 
@@ -4961,33 +4962,34 @@ BinASTParser<Tok>::parseInterfaceForStat
 
     BINJS_MOZ_TRY_DECL(test, parseOptionalExpression());
 
     BINJS_MOZ_TRY_DECL(update, parseOptionalExpression());
 
     BINJS_MOZ_TRY_DECL(body, parseStatement());
 
     BINJS_TRY_DECL(forHead, factory_.newForHead(init, test, update, tokenizer_->pos(start)));
-    BINJS_TRY_DECL(result, factory_.newForStatement(start, forHead, body, /* iflags = */ 0));
+    ParseNode* result;
+    BINJS_TRY_VAR(result, factory_.newForStatement(start, forHead, body, /* iflags = */ 0));
 
     if (!scope.isEmpty()) {
         BINJS_TRY_DECL(bindings, NewLexicalScopeData(cx_, scope, alloc_, parseContext_));
         BINJS_TRY_VAR(result, factory_.newLexicalScope(*bindings, result));
     }
     return result;
 }
 
 
 /*
  interface FormalParameters : Node {
     FrozenArray<Parameter> items;
     Binding? rest;
  }
 */
-template<typename Tok> JS::Result<ParseNode*>
+template<typename Tok> JS::Result<ListNode*>
 BinASTParser<Tok>::parseFormalParameters()
 {
     BinKind kind;
     BinFields fields(cx_);
     AutoTaggedTuple guard(*tokenizer_);
 
     MOZ_TRY(tokenizer_->enterTaggedTuple(kind, fields, guard));
     if (kind != BinKind::FormalParameters) {
@@ -4995,17 +4997,17 @@ BinASTParser<Tok>::parseFormalParameters
     }
     const auto start = tokenizer_->offset();
     BINJS_MOZ_TRY_DECL(result, parseInterfaceFormalParameters(start, kind, fields));
     MOZ_TRY(guard.done());
 
     return result;
 }
 
-template<typename Tok> JS::Result<ParseNode*>
+template<typename Tok> JS::Result<ListNode*>
 BinASTParser<Tok>::parseInterfaceFormalParameters(const size_t start, const BinKind kind, const BinFields& fields)
 {
     MOZ_ASSERT(kind == BinKind::FormalParameters);
     BINJS_TRY(CheckRecursionLimit(cx_));
 
 #if defined(DEBUG)
     const BinField expected_fields[2] = { BinField::Items, BinField::Rest };
     MOZ_TRY(tokenizer_->checkFields(kind, fields, expected_fields));
@@ -6289,17 +6291,17 @@ BinASTParser<Tok>::parseInterfaceSuper(c
 
 
 /*
  interface SwitchCase : Node {
     Expression test;
     FrozenArray<Statement> consequent;
  }
 */
-template<typename Tok> JS::Result<ParseNode*>
+template<typename Tok> JS::Result<CaseClause*>
 BinASTParser<Tok>::parseSwitchCase()
 {
     BinKind kind;
     BinFields fields(cx_);
     AutoTaggedTuple guard(*tokenizer_);
 
     MOZ_TRY(tokenizer_->enterTaggedTuple(kind, fields, guard));
     if (kind != BinKind::SwitchCase) {
@@ -6307,17 +6309,17 @@ BinASTParser<Tok>::parseSwitchCase()
     }
     const auto start = tokenizer_->offset();
     BINJS_MOZ_TRY_DECL(result, parseInterfaceSwitchCase(start, kind, fields));
     MOZ_TRY(guard.done());
 
     return result;
 }
 
-template<typename Tok> JS::Result<ParseNode*>
+template<typename Tok> JS::Result<CaseClause*>
 BinASTParser<Tok>::parseInterfaceSwitchCase(const size_t start, const BinKind kind, const BinFields& fields)
 {
     MOZ_ASSERT(kind == BinKind::SwitchCase);
     BINJS_TRY(CheckRecursionLimit(cx_));
 
 #if defined(DEBUG)
     const BinField expected_fields[2] = { BinField::Test, BinField::Consequent };
     MOZ_TRY(tokenizer_->checkFields(kind, fields, expected_fields));
@@ -6461,17 +6463,17 @@ BinASTParser<Tok>::parseInterfaceSwitchS
 
     BINJS_MOZ_TRY_DECL(defaultCase, parseSwitchDefault());
 
     BINJS_MOZ_TRY_DECL(postDefaultCases, parseListOfSwitchCase());
 
     // Concatenate `preDefaultCase`, `defaultCase`, `postDefaultCase`
     auto cases = preDefaultCases;
     factory_.addList(cases, defaultCase);
-    ParseNode* iter = postDefaultCases->pn_head;
+    ParseNode* iter = postDefaultCases->head();
     while (iter) {
         ParseNode* next = iter->pn_next;
         factory_.addList(cases, iter);
         iter = next;
     }
     BINJS_TRY_DECL(scope, factory_.newLexicalScope(nullptr, cases));
     BINJS_TRY_DECL(result, factory_.newSwitchStatement(start, discriminant, scope, true));
     return result;
@@ -6915,17 +6917,17 @@ BinASTParser<Tok>::parseInterfaceVariabl
     AutoVariableDeclarationKind kindGuard(this);
 
     BINJS_MOZ_TRY_DECL(kind_, parseVariableDeclarationKind());
     // Restored by `kindGuard`.
     variableDeclarationKind_ = kind_;
     BINJS_MOZ_TRY_DECL(declarators, parseListOfVariableDeclarator());
 
     // By specification, the list may not be empty.
-    if (declarators->pn_count == 0)
+    if (declarators->empty())
         return raiseEmpty("VariableDeclaration");
 
     ParseNodeKind pnk;
     switch (kind_) {
       case VariableDeclarationKind::Var:
         pnk = ParseNodeKind::Var;
         break;
       case VariableDeclarationKind::Let:
@@ -6978,21 +6980,21 @@ BinASTParser<Tok>::parseInterfaceVariabl
 
     BINJS_MOZ_TRY_DECL(binding, parseBinding());
 
     BINJS_MOZ_TRY_DECL(init, parseOptionalExpression());
 
     ParseNode* result;
     if (binding->isKind(ParseNodeKind::Name)) {
         // `var foo [= bar]``
-        MOZ_TRY(checkBinding(binding->pn_atom->asPropertyName()));
-
-        BINJS_TRY_VAR(result, factory_.newName(binding->pn_atom->asPropertyName(), tokenizer_->pos(start), cx_));
+        MOZ_TRY(checkBinding(binding->template as<NameNode>().atom()->asPropertyName()));
+
+        BINJS_TRY_VAR(result, factory_.newName(binding->template as<NameNode>().atom()->asPropertyName(), tokenizer_->pos(start), cx_));
         if (init)
-            result->pn_expr = init;
+            result->as<NameNode>().setInitializer(init);
     } else {
         // `var pattern = bar`
         if (!init) {
             // Here, `init` is required.
             return raiseMissingField("VariableDeclarator (with non-trivial pattern)", BinField::Init);
         }
 
         MOZ_CRASH("Unimplemented: AssertedScope check for BindingPattern variable declaration");
@@ -7417,17 +7419,17 @@ BinASTParser<Tok>::parseListOfBindingPro
 }
 
 template<typename Tok> JS::Result<ParseNode*>
 BinASTParser<Tok>::parseListOfClassElement()
 {
     return raiseError("FIXME: Not implemented yet (ListOfClassElement)");
 }
 
-template<typename Tok> JS::Result<ParseNode*>
+template<typename Tok> JS::Result<ListNode*>
 BinASTParser<Tok>::parseListOfDirective()
 {
     uint32_t length;
     AutoList guard(*tokenizer_);
 
     const auto start = tokenizer_->offset();
     MOZ_TRY(tokenizer_->enterList(length, guard));
     BINJS_TRY_DECL(result, factory_.newStatementList(tokenizer_->pos(start)));
@@ -7472,17 +7474,17 @@ BinASTParser<Tok>::parseListOfImportDecl
 }
 
 template<typename Tok> JS::Result<ParseNode*>
 BinASTParser<Tok>::parseListOfImportSpecifier()
 {
     return raiseError("FIXME: Not implemented yet (ListOfImportSpecifier)");
 }
 
-template<typename Tok> JS::Result<ParseNode*>
+template<typename Tok> JS::Result<ListNode*>
 BinASTParser<Tok>::parseListOfObjectProperty()
 {
     uint32_t length;
     AutoList guard(*tokenizer_);
 
     const auto start = tokenizer_->offset();
     MOZ_TRY(tokenizer_->enterList(length, guard));
     BINJS_TRY_DECL(result, factory_.newObjectLiteral(start));
@@ -7497,17 +7499,17 @@ BinASTParser<Tok>::parseListOfObjectProp
 }
 
 template<typename Tok> JS::Result<ParseNode*>
 BinASTParser<Tok>::parseListOfOptionalBindingOrBindingWithInitializer()
 {
     return raiseError("FIXME: Not implemented yet (ListOfOptionalBindingOrBindingWithInitializer)");
 }
 
-template<typename Tok> JS::Result<ParseNode*>
+template<typename Tok> JS::Result<ListNode*>
 BinASTParser<Tok>::parseListOfOptionalSpreadElementOrExpression()
 {
     uint32_t length;
     AutoList guard(*tokenizer_);
 
     const auto start = tokenizer_->offset();
     MOZ_TRY(tokenizer_->enterList(length, guard));
     BINJS_TRY_DECL(result, factory_.newArrayLiteral(start));
@@ -7519,17 +7521,17 @@ BinASTParser<Tok>::parseListOfOptionalSp
         else
             BINJS_TRY(factory_.addElision(result, tokenizer_->pos(start)));
     }
 
     MOZ_TRY(guard.done());
     return result;
 }
 
-template<typename Tok> JS::Result<ParseNode*>
+template<typename Tok> JS::Result<ListNode*>
 BinASTParser<Tok>::parseListOfParameter()
 {
     uint32_t length;
     AutoList guard(*tokenizer_);
 
     const auto start = tokenizer_->offset();
     MOZ_TRY(tokenizer_->enterList(length, guard));
     BINJS_TRY_DECL(result, new_<ListNode>(ParseNodeKind::ParamsBody, tokenizer_->pos(start)));
@@ -7538,17 +7540,17 @@ BinASTParser<Tok>::parseListOfParameter(
         BINJS_MOZ_TRY_DECL(item, parseParameter());
         factory_.addList(/* list = */ result, /* kid = */ item);
     }
 
     MOZ_TRY(guard.done());
     return result;
 }
 
-template<typename Tok> JS::Result<ParseNode*>
+template<typename Tok> JS::Result<ListNode*>
 BinASTParser<Tok>::parseListOfStatement()
 {
     uint32_t length;
     AutoList guard(*tokenizer_);
 
     const auto start = tokenizer_->offset();
     MOZ_TRY(tokenizer_->enterList(length, guard));
     BINJS_TRY_DECL(result, factory_.newStatementList(tokenizer_->pos(start)));
@@ -7557,17 +7559,17 @@ BinASTParser<Tok>::parseListOfStatement(
         BINJS_MOZ_TRY_DECL(item, parseStatement());
         factory_.addStatementToList(result, item);
     }
 
     MOZ_TRY(guard.done());
     return result;
 }
 
-template<typename Tok> JS::Result<ParseNode*>
+template<typename Tok> JS::Result<ListNode*>
 BinASTParser<Tok>::parseListOfSwitchCase()
 {
     uint32_t length;
     AutoList guard(*tokenizer_);
 
     const auto start = tokenizer_->offset();
     MOZ_TRY(tokenizer_->enterList(length, guard));
     BINJS_TRY_DECL(result, factory_.newStatementList(tokenizer_->pos(start)));
@@ -7576,17 +7578,17 @@ BinASTParser<Tok>::parseListOfSwitchCase
         BINJS_MOZ_TRY_DECL(item, parseSwitchCase());
         factory_.addCaseStatementToList(result, item);
     }
 
     MOZ_TRY(guard.done());
     return result;
 }
 
-template<typename Tok> JS::Result<ParseNode*>
+template<typename Tok> JS::Result<ListNode*>
 BinASTParser<Tok>::parseListOfVariableDeclarator()
 {
     uint32_t length;
     AutoList guard(*tokenizer_);
 
     const auto start = tokenizer_->offset();
     MOZ_TRY(tokenizer_->enterList(length, guard));
     BINJS_TRY_DECL(result, factory_.newDeclarationList(ParseNodeKind::Const /*Placeholder*/,
@@ -7746,25 +7748,25 @@ BinASTParser<Tok>::parseOptionalBindingO
         const auto start = tokenizer_->offset();
         MOZ_TRY_VAR(result, parseSumBindingOrBindingWithInitializer(start, kind, fields));
     }
     MOZ_TRY(guard.done());
 
     return result;
 }
 
-template<typename Tok> JS::Result<ParseNode*>
+template<typename Tok> JS::Result<LexicalScopeNode*>
 BinASTParser<Tok>::parseOptionalCatchClause()
 {
     BinKind kind;
     BinFields fields(cx_);
     AutoTaggedTuple guard(*tokenizer_);
 
     MOZ_TRY(tokenizer_->enterTaggedTuple(kind, fields, guard));
-    ParseNode* result;
+    LexicalScopeNode* result;
     if (kind == BinKind::_Null) {
         result = nullptr;
     } else if (kind == BinKind::CatchClause) {
         const auto start = tokenizer_->offset();
         MOZ_TRY_VAR(result, parseInterfaceCatchClause(start, kind, fields));
     } else {
         return raiseInvalidKind("CatchClause", kind);
     }
--- a/js/src/frontend/BinSource-auto.h
+++ b/js/src/frontend/BinSource-auto.h
@@ -174,17 +174,17 @@ JS::Result<ParseNode*> parseAwaitExpress
 JS::Result<ParseNode*> parseBinaryExpression();
 JS::Result<ParseNode*> parseBindingIdentifier();
 JS::Result<ParseNode*> parseBindingPropertyIdentifier();
 JS::Result<ParseNode*> parseBindingPropertyProperty();
 JS::Result<ParseNode*> parseBindingWithInitializer();
 JS::Result<ParseNode*> parseBlock();
 JS::Result<ParseNode*> parseBreakStatement();
 JS::Result<ParseNode*> parseCallExpression();
-JS::Result<ParseNode*> parseCatchClause();
+JS::Result<LexicalScopeNode*> parseCatchClause();
 JS::Result<ParseNode*> parseClassDeclaration();
 JS::Result<ParseNode*> parseClassElement();
 JS::Result<ParseNode*> parseClassExpression();
 JS::Result<ParseNode*> parseCompoundAssignmentExpression();
 JS::Result<ParseNode*> parseComputedMemberAssignmentTarget();
 JS::Result<ParseNode*> parseComputedMemberExpression();
 JS::Result<ParseNode*> parseComputedPropertyName();
 JS::Result<ParseNode*> parseConditionalExpression();
@@ -207,17 +207,17 @@ JS::Result<ParseNode*> parseExportFrom()
 JS::Result<ParseNode*> parseExportFromSpecifier();
 JS::Result<ParseNode*> parseExportLocalSpecifier();
 JS::Result<ParseNode*> parseExportLocals();
 JS::Result<ParseNode*> parseExpressionStatement();
 JS::Result<ParseNode*> parseForInOfBinding();
 JS::Result<ParseNode*> parseForInStatement();
 JS::Result<ParseNode*> parseForOfStatement();
 JS::Result<ParseNode*> parseForStatement();
-JS::Result<ParseNode*> parseFormalParameters();
+JS::Result<ListNode*> parseFormalParameters();
 JS::Result<ParseNode*> parseFunctionBody();
 JS::Result<ParseNode*> parseIdentifierExpression();
 JS::Result<ParseNode*> parseIfStatement();
 JS::Result<ParseNode*> parseImport();
 JS::Result<ParseNode*> parseImportNamespace();
 JS::Result<ParseNode*> parseImportSpecifier();
 JS::Result<ParseNode*> parseLabelledStatement();
 JS::Result<ParseNode*> parseLiteralBooleanExpression();
@@ -241,17 +241,17 @@ JS::Result<ParseNode*> parseSkippableFun
 JS::Result<ParseNode*> parseSkippableFunctionExpression();
 JS::Result<ParseNode*> parseSkippableGetter();
 JS::Result<ParseNode*> parseSkippableMethod();
 JS::Result<ParseNode*> parseSkippableSetter();
 JS::Result<ParseNode*> parseSpreadElement();
 JS::Result<ParseNode*> parseStaticMemberAssignmentTarget();
 JS::Result<ParseNode*> parseStaticMemberExpression();
 JS::Result<ParseNode*> parseSuper();
-JS::Result<ParseNode*> parseSwitchCase();
+JS::Result<CaseClause*> parseSwitchCase();
 JS::Result<ParseNode*> parseSwitchDefault();
 JS::Result<ParseNode*> parseSwitchStatement();
 JS::Result<ParseNode*> parseSwitchStatementWithDefault();
 JS::Result<ParseNode*> parseTemplateElement();
 JS::Result<ParseNode*> parseTemplateExpression();
 JS::Result<ParseNode*> parseThisExpression();
 JS::Result<ParseNode*> parseThrowStatement();
 JS::Result<ParseNode*> parseTryCatchStatement();
@@ -280,17 +280,17 @@ JS::Result<ParseNode*> parseInterfaceAwa
 JS::Result<ParseNode*> parseInterfaceBinaryExpression(const size_t start, const BinKind kind, const BinFields& fields);
 JS::Result<ParseNode*> parseInterfaceBindingIdentifier(const size_t start, const BinKind kind, const BinFields& fields);
 JS::Result<ParseNode*> parseInterfaceBindingPropertyIdentifier(const size_t start, const BinKind kind, const BinFields& fields);
 JS::Result<ParseNode*> parseInterfaceBindingPropertyProperty(const size_t start, const BinKind kind, const BinFields& fields);
 JS::Result<ParseNode*> parseInterfaceBindingWithInitializer(const size_t start, const BinKind kind, const BinFields& fields);
 JS::Result<ParseNode*> parseInterfaceBlock(const size_t start, const BinKind kind, const BinFields& fields);
 JS::Result<ParseNode*> parseInterfaceBreakStatement(const size_t start, const BinKind kind, const BinFields& fields);
 JS::Result<ParseNode*> parseInterfaceCallExpression(const size_t start, const BinKind kind, const BinFields& fields);
-JS::Result<ParseNode*> parseInterfaceCatchClause(const size_t start, const BinKind kind, const BinFields& fields);
+JS::Result<LexicalScopeNode*> parseInterfaceCatchClause(const size_t start, const BinKind kind, const BinFields& fields);
 JS::Result<ParseNode*> parseInterfaceClassDeclaration(const size_t start, const BinKind kind, const BinFields& fields);
 JS::Result<ParseNode*> parseInterfaceClassElement(const size_t start, const BinKind kind, const BinFields& fields);
 JS::Result<ParseNode*> parseInterfaceClassExpression(const size_t start, const BinKind kind, const BinFields& fields);
 JS::Result<ParseNode*> parseInterfaceCompoundAssignmentExpression(const size_t start, const BinKind kind, const BinFields& fields);
 JS::Result<ParseNode*> parseInterfaceComputedMemberAssignmentTarget(const size_t start, const BinKind kind, const BinFields& fields);
 JS::Result<ParseNode*> parseInterfaceComputedMemberExpression(const size_t start, const BinKind kind, const BinFields& fields);
 JS::Result<ParseNode*> parseInterfaceComputedPropertyName(const size_t start, const BinKind kind, const BinFields& fields);
 JS::Result<ParseNode*> parseInterfaceConditionalExpression(const size_t start, const BinKind kind, const BinFields& fields);
@@ -313,17 +313,17 @@ JS::Result<ParseNode*> parseInterfaceExp
 JS::Result<ParseNode*> parseInterfaceExportFromSpecifier(const size_t start, const BinKind kind, const BinFields& fields);
 JS::Result<ParseNode*> parseInterfaceExportLocalSpecifier(const size_t start, const BinKind kind, const BinFields& fields);
 JS::Result<ParseNode*> parseInterfaceExportLocals(const size_t start, const BinKind kind, const BinFields& fields);
 JS::Result<ParseNode*> parseInterfaceExpressionStatement(const size_t start, const BinKind kind, const BinFields& fields);
 JS::Result<ParseNode*> parseInterfaceForInOfBinding(const size_t start, const BinKind kind, const BinFields& fields);
 JS::Result<ParseNode*> parseInterfaceForInStatement(const size_t start, const BinKind kind, const BinFields& fields);
 JS::Result<ParseNode*> parseInterfaceForOfStatement(const size_t start, const BinKind kind, const BinFields& fields);
 JS::Result<ParseNode*> parseInterfaceForStatement(const size_t start, const BinKind kind, const BinFields& fields);
-JS::Result<ParseNode*> parseInterfaceFormalParameters(const size_t start, const BinKind kind, const BinFields& fields);
+JS::Result<ListNode*> parseInterfaceFormalParameters(const size_t start, const BinKind kind, const BinFields& fields);
 JS::Result<ParseNode*> parseInterfaceFunctionBody(const size_t start, const BinKind kind, const BinFields& fields);
 JS::Result<ParseNode*> parseInterfaceIdentifierExpression(const size_t start, const BinKind kind, const BinFields& fields);
 JS::Result<ParseNode*> parseInterfaceIfStatement(const size_t start, const BinKind kind, const BinFields& fields);
 JS::Result<ParseNode*> parseInterfaceImport(const size_t start, const BinKind kind, const BinFields& fields);
 JS::Result<ParseNode*> parseInterfaceImportNamespace(const size_t start, const BinKind kind, const BinFields& fields);
 JS::Result<ParseNode*> parseInterfaceImportSpecifier(const size_t start, const BinKind kind, const BinFields& fields);
 JS::Result<ParseNode*> parseInterfaceLabelledStatement(const size_t start, const BinKind kind, const BinFields& fields);
 JS::Result<ParseNode*> parseInterfaceLiteralBooleanExpression(const size_t start, const BinKind kind, const BinFields& fields);
@@ -347,17 +347,17 @@ JS::Result<ParseNode*> parseInterfaceSki
 JS::Result<ParseNode*> parseInterfaceSkippableFunctionExpression(const size_t start, const BinKind kind, const BinFields& fields);
 JS::Result<ParseNode*> parseInterfaceSkippableGetter(const size_t start, const BinKind kind, const BinFields& fields);
 JS::Result<ParseNode*> parseInterfaceSkippableMethod(const size_t start, const BinKind kind, const BinFields& fields);
 JS::Result<ParseNode*> parseInterfaceSkippableSetter(const size_t start, const BinKind kind, const BinFields& fields);
 JS::Result<ParseNode*> parseInterfaceSpreadElement(const size_t start, const BinKind kind, const BinFields& fields);
 JS::Result<ParseNode*> parseInterfaceStaticMemberAssignmentTarget(const size_t start, const BinKind kind, const BinFields& fields);
 JS::Result<ParseNode*> parseInterfaceStaticMemberExpression(const size_t start, const BinKind kind, const BinFields& fields);
 JS::Result<ParseNode*> parseInterfaceSuper(const size_t start, const BinKind kind, const BinFields& fields);
-JS::Result<ParseNode*> parseInterfaceSwitchCase(const size_t start, const BinKind kind, const BinFields& fields);
+JS::Result<CaseClause*> parseInterfaceSwitchCase(const size_t start, const BinKind kind, const BinFields& fields);
 JS::Result<ParseNode*> parseInterfaceSwitchDefault(const size_t start, const BinKind kind, const BinFields& fields);
 JS::Result<ParseNode*> parseInterfaceSwitchStatement(const size_t start, const BinKind kind, const BinFields& fields);
 JS::Result<ParseNode*> parseInterfaceSwitchStatementWithDefault(const size_t start, const BinKind kind, const BinFields& fields);
 JS::Result<ParseNode*> parseInterfaceTemplateElement(const size_t start, const BinKind kind, const BinFields& fields);
 JS::Result<ParseNode*> parseInterfaceTemplateExpression(const size_t start, const BinKind kind, const BinFields& fields);
 JS::Result<ParseNode*> parseInterfaceThisExpression(const size_t start, const BinKind kind, const BinFields& fields);
 JS::Result<ParseNode*> parseInterfaceThrowStatement(const size_t start, const BinKind kind, const BinFields& fields);
 JS::Result<ParseNode*> parseInterfaceTryCatchStatement(const size_t start, const BinKind kind, const BinFields& fields);
@@ -383,41 +383,41 @@ JS::Result<typename BinASTParser<Tok>::V
 
 // ----- Lists (by lexicographical order)
 // Implementations are autogenerated
 JS::Result<ParseNode*> parseArguments();
 JS::Result<ParseNode*> parseListOfAssignmentTargetOrAssignmentTargetWithInitializer();
 JS::Result<ParseNode*> parseListOfAssignmentTargetProperty();
 JS::Result<ParseNode*> parseListOfBindingProperty();
 JS::Result<ParseNode*> parseListOfClassElement();
-JS::Result<ParseNode*> parseListOfDirective();
+JS::Result<ListNode*> parseListOfDirective();
 JS::Result<ParseNode*> parseListOfExportFromSpecifier();
 JS::Result<ParseNode*> parseListOfExportLocalSpecifier();
 JS::Result<ParseNode*> parseListOfExpressionOrTemplateElement();
 JS::Result<ParseNode*> parseListOfIdentifierName();
 JS::Result<ParseNode*> parseListOfImportDeclarationOrExportDeclarationOrStatement();
 JS::Result<ParseNode*> parseListOfImportSpecifier();
-JS::Result<ParseNode*> parseListOfObjectProperty();
+JS::Result<ListNode*> parseListOfObjectProperty();
 JS::Result<ParseNode*> parseListOfOptionalBindingOrBindingWithInitializer();
-JS::Result<ParseNode*> parseListOfOptionalSpreadElementOrExpression();
-JS::Result<ParseNode*> parseListOfParameter();
-JS::Result<ParseNode*> parseListOfStatement();
-JS::Result<ParseNode*> parseListOfSwitchCase();
-JS::Result<ParseNode*> parseListOfVariableDeclarator();
+JS::Result<ListNode*> parseListOfOptionalSpreadElementOrExpression();
+JS::Result<ListNode*> parseListOfParameter();
+JS::Result<ListNode*> parseListOfStatement();
+JS::Result<ListNode*> parseListOfSwitchCase();
+JS::Result<ListNode*> parseListOfVariableDeclarator();
 
 
 // ----- Default values (by lexicographical order)
 // Implementations are autogenerated
 JS::Result<Ok> parseOptionalAssertedBlockScope();
 JS::Result<Ok> parseOptionalAssertedParameterScope();
 JS::Result<Ok> parseOptionalAssertedVarScope();
 JS::Result<ParseNode*> parseOptionalAssignmentTarget();
 JS::Result<ParseNode*> parseOptionalBinding();
 JS::Result<ParseNode*> parseOptionalBindingIdentifier();
 JS::Result<ParseNode*> parseOptionalBindingOrBindingWithInitializer();
-JS::Result<ParseNode*> parseOptionalCatchClause();
+JS::Result<LexicalScopeNode*> parseOptionalCatchClause();
 JS::Result<ParseNode*> parseOptionalExpression();
 JS::Result<ParseNode*> parseOptionalIdentifierName();
 JS::Result<ParseNode*> parseOptionalLabel();
 JS::Result<ParseNode*> parseOptionalSpreadElementOrExpression();
 JS::Result<ParseNode*> parseOptionalStatement();
 JS::Result<ParseNode*> parseOptionalVariableDeclarationOrExpression();
 
--- a/js/src/frontend/BinSource.cpp
+++ b/js/src/frontend/BinSource.cpp
@@ -179,23 +179,23 @@ BinASTParser<Tok>::buildFunctionBox(Gene
 
     traceListHead_ = funbox;
     funbox->initWithEnclosingParseContext(parseContext_, syntax);
     return funbox;
 }
 
 template<typename Tok> JS::Result<ParseNode*>
 BinASTParser<Tok>::buildFunction(const size_t start, const BinKind kind, ParseNode* name,
-                                 ParseNode* params, ParseNode* body, FunctionBox* funbox)
+                                 ListNode* params, ParseNode* body, FunctionBox* funbox)
 {
     TokenPos pos = tokenizer_->pos(start);
 
     // Set the argument count for building argument packets. Function.length is handled
     // by setting the appropriate funbox field during argument parsing.
-    funbox->function()->setArgCount(params ? uint16_t(params->pn_count) : 0);
+    funbox->function()->setArgCount(params ? uint16_t(params->count()) : 0);
 
     // ParseNode represents the body as concatenated after the params.
     params->appendWithoutOrderAssumption(body);
 
     bool isStatement = kind == BinKind::EagerFunctionDeclaration ||
                        kind == BinKind::SkippableFunctionDeclaration;
 
     BINJS_TRY_DECL(result, isStatement
@@ -360,42 +360,42 @@ BinASTParser<Tok>::checkFunctionClosedVa
     if (parseContext_->functionBox()->function()->isNamedLambda()) {
         MOZ_TRY(checkClosedVars(parseContext_->namedLambdaScope()));
     }
 
     return Ok();
 }
 
 template<typename Tok> JS::Result<ParseNode*>
-BinASTParser<Tok>::appendDirectivesToBody(ParseNode* body, ParseNode* directives)
+BinASTParser<Tok>::appendDirectivesToBody(ListNode* body, ListNode* directives)
 {
+    if (!directives) {
+        return body;
+    }
+
     ParseNode* result = body;
-    if (directives && directives->pn_count >= 1) {
-        MOZ_ASSERT(directives->isArity(PN_LIST));
-
+    if (!directives->empty()) {
         // Convert directive list to a list of strings.
-        BINJS_TRY_DECL(prefix, factory_.newStatementList(directives->pn_head->pn_pos));
-        for (ParseNode* iter = directives->pn_head; iter != nullptr; iter = iter->pn_next) {
+        auto pos = directives->head()->pn_pos;
+        BINJS_TRY_DECL(prefix, factory_.newStatementList(pos));
+        for (ParseNode* iter : directives->contents()) {
             BINJS_TRY_DECL(statement, factory_.newExprStatement(iter, iter->pn_pos.end));
             prefix->appendWithoutOrderAssumption(statement);
         }
 
         // Prepend to the body.
-        ParseNode* iter = body->pn_head;
+        ParseNode* iter = body->head();
         while (iter) {
             ParseNode* next = iter->pn_next;
             prefix->appendWithoutOrderAssumption(iter);
             iter = next;
         }
         prefix->setKind(body->getKind());
         prefix->setOp(body->getOp());
         result = prefix;
-#if defined(DEBUG)
-        result->checkListConsistency();
-#endif // defined(DEBUG)
     }
 
     return result;
 }
 
 template<typename Tok> mozilla::GenericErrorResult<JS::Error&>
 BinASTParser<Tok>::raiseInvalidClosedVar(JSAtom* name)
 {
--- a/js/src/frontend/BinSource.h
+++ b/js/src/frontend/BinSource.h
@@ -158,17 +158,17 @@ class BinASTParser : public BinASTParser
 
     // Auto-generated methods
 #include "frontend/BinSource-auto.h"
 
     // --- Auxiliary parsing functions
 
     // Build a function object for a function-producing production. Called AFTER creating the scope.
     JS::Result<ParseNode*>
-    buildFunction(const size_t start, const BinKind kind, ParseNode* name, ParseNode* params,
+    buildFunction(const size_t start, const BinKind kind, ParseNode* name, ListNode* params,
         ParseNode* body, FunctionBox* funbox);
     JS::Result<FunctionBox*>
     buildFunctionBox(GeneratorKind generatorKind, FunctionAsyncKind functionAsyncKind, FunctionSyntaxKind syntax, ParseNode* name);
 
     // Parse full scope information to a specific var scope / let scope combination.
     MOZ_MUST_USE JS::Result<Ok> parseAndUpdateScope(ParseContext::Scope& varScope,
         ParseContext::Scope& letScope);
     // Parse a list of names and add it to a given scope.
@@ -180,18 +180,18 @@ class BinASTParser : public BinASTParser
     // When leaving a scope, check that none of its bindings are known closed over and un-marked.
     MOZ_MUST_USE JS::Result<Ok> checkClosedVars(ParseContext::Scope& scope);
 
     // As a convenience, a helper that checks the body, parameter, and recursive binding scopes.
     MOZ_MUST_USE JS::Result<Ok> checkFunctionClosedVars();
 
     // --- Utilities.
 
-    MOZ_MUST_USE JS::Result<ParseNode*> appendDirectivesToBody(ParseNode* body,
-        ParseNode* directives);
+    MOZ_MUST_USE JS::Result<ParseNode*> appendDirectivesToBody(ListNode* body,
+        ListNode* directives);
 
   private: // Implement ErrorReporter
     const JS::ReadOnlyCompileOptions& options_;
 
     const JS::ReadOnlyCompileOptions& options() const override {
         return this->options_;
     }
 
--- a/js/src/frontend/BinSource.yaml
+++ b/js/src/frontend/BinSource.yaml
@@ -363,17 +363,17 @@ BinaryExpression:
             break;
         }
 
         ParseNode* result;
         if (left->isKind(pnk) &&
             pnk != ParseNodeKind::Pow /* ParseNodeKind::Pow is not left-associative */)
         {
             // Regroup left-associative operations into lists.
-            left->appendWithoutOrderAssumption(right);
+            left->template as<ListNode>().appendWithoutOrderAssumption(right);
             result = left;
         } else {
             BINJS_TRY_DECL(list, factory_.newList(pnk, tokenizer_->pos(start)));
 
             list->appendWithoutOrderAssumption(left);
             list->appendWithoutOrderAssumption(right);
             result = list;
         }
@@ -429,16 +429,18 @@ CallExpression:
             }
         }
 
         BINJS_TRY_DECL(result, factory_.newCall(callee, arguments));
         result->setOp(op);
 
 
 CatchClause:
+    type-ok:
+        LexicalScopeNode*
     init: |
         ParseContext::Statement stmt(parseContext_, StatementKind::Catch);
         ParseContext::Scope currentScope(cx_, parseContext_, usedNames_);
         BINJS_TRY(currentScope.init(parseContext_));
     build: |
         BINJS_TRY_DECL(bindings, NewLexicalScopeData(cx_, currentScope, alloc_, parseContext_));
         BINJS_TRY_DECL(result, factory_.newLexicalScope(*bindings, body));
         BINJS_TRY(factory_.setupCatchScope(result, binding, body));
@@ -635,17 +637,17 @@ ExpressionStatement:
         BINJS_TRY_DECL(result, factory_.newExprStatement(expression, tokenizer_->offset()));
 
 ForInOfBinding:
     init:
         AutoVariableDeclarationKind kindGuard(this);
     build: |
         // Restored by `kindGuard`.
         variableDeclarationKind_ = kind_;
-        MOZ_TRY(checkBinding(binding->pn_atom->asPropertyName()));
+        MOZ_TRY(checkBinding(binding->template as<NameNode>().atom()->asPropertyName()));
         auto pnk =
             kind_ == VariableDeclarationKind::Let
                 ? ParseNodeKind::Let
                 : ParseNodeKind::Var;
         BINJS_TRY_DECL(result, factory_.newDeclarationList(pnk, tokenizer_->pos(start)));
         factory_.addList(result, binding);
 
 
@@ -656,24 +658,27 @@ ForInStatement:
 
         // Implicit scope around the `for`, used to store `for (let x in  ...)`
         // or `for (const x in ...)`-style declarations. Detail on the
         // declaration is stored as part of `scope`.
         ParseContext::Scope scope(cx_, parseContext_, usedNames_);
         BINJS_TRY(scope.init(parseContext_));
     build: |
         BINJS_TRY_DECL(forHead, factory_.newForInOrOfHead(ParseNodeKind::ForIn, left, right, tokenizer_->pos(start)));
-        BINJS_TRY_DECL(result, factory_.newForStatement(start, forHead, body, /*flags*/ 0));
+        ParseNode* result;
+        BINJS_TRY_VAR(result, factory_.newForStatement(start, forHead, body, /*flags*/ 0));
 
         if (!scope.isEmpty()) {
             BINJS_TRY_DECL(bindings, NewLexicalScopeData(cx_, scope, alloc_, parseContext_));
             BINJS_TRY_VAR(result, factory_.newLexicalScope(*bindings, result));
         }
 
 FormalParameters:
+    type-ok:
+        ListNode*
     build: |
         auto result = items;
         if (rest) {
             BINJS_TRY_DECL(spread, factory_.newSpread(start, rest));
             factory_.addList(result, spread);
         }
 
 ForStatement:
@@ -682,17 +687,18 @@ ForStatement:
 
         // Implicit scope around the `for`, used to store `for (let x; ...; ...)`
         // or `for (const x; ...; ...)`-style declarations. Detail on the
         // declaration is stored as part of `BINJS_Scope`.
         ParseContext::Scope scope(cx_, parseContext_, usedNames_);
         BINJS_TRY(scope.init(parseContext_));
     build: |
         BINJS_TRY_DECL(forHead, factory_.newForHead(init, test, update, tokenizer_->pos(start)));
-        BINJS_TRY_DECL(result, factory_.newForStatement(start, forHead, body, /* iflags = */ 0));
+        ParseNode* result;
+        BINJS_TRY_VAR(result, factory_.newForStatement(start, forHead, body, /* iflags = */ 0));
 
         if (!scope.isEmpty()) {
             BINJS_TRY_DECL(bindings, NewLexicalScopeData(cx_, scope, alloc_, parseContext_));
             BINJS_TRY_VAR(result, factory_.newLexicalScope(*bindings, result));
         }
 
 FunctionBody:
     build: |
@@ -716,60 +722,76 @@ LabelledStatement:
             after: |
                 if (!IsIdentifier(label))
                     return raiseError("Invalid identifier");
                 ParseContext::LabelStatement stmt(parseContext_, label);
     build:
         BINJS_TRY_DECL(result, factory_.newLabeledStatement(label->asPropertyName(), body, start));
 
 ListOfDirective:
+    type-ok:
+        ListNode*
     init:
         BINJS_TRY_DECL(result, factory_.newStatementList(tokenizer_->pos(start)));
     append:
         factory_.addStatementToList(result, item);
 
 ListOfObjectProperty:
+    type-ok:
+        ListNode*
     init:
         BINJS_TRY_DECL(result, factory_.newObjectLiteral(start));
 
 ListOfOptionalSpreadElementOrExpression:
+    type-ok:
+        ListNode*
     init:
         BINJS_TRY_DECL(result, factory_.newArrayLiteral(start));
     append: |
         if (item)
             factory_.addArrayElement(result, item); // Infallible.
         else
             BINJS_TRY(factory_.addElision(result, tokenizer_->pos(start)));
 
 ListOfParameter:
+    type-ok:
+        ListNode*
     init: |
         BINJS_TRY_DECL(result, new_<ListNode>(ParseNodeKind::ParamsBody, tokenizer_->pos(start)));
     append:
         factory_.addList(/* list = */ result, /* kid = */ item);
 
 ListOfStatement:
+    type-ok:
+        ListNode*
     init:
         BINJS_TRY_DECL(result, factory_.newStatementList(tokenizer_->pos(start)));
     append:
         factory_.addStatementToList(result, item);
 
 
 #ListOfSpreadElementOrExpression:
+#    type-ok:
+#        ListNode*
 #    init:
 #        BINJS_TRY_DECL(result, new_<ListNode>(ParseNodeKind::ParamsBody, tokenizer_->pos()));
 #    append:
 #        result->appendWithoutOrderAssumption(item);
 
 ListOfSwitchCase:
+    type-ok:
+        ListNode*
     init:
         BINJS_TRY_DECL(result, factory_.newStatementList(tokenizer_->pos(start)));
     append:
         factory_.addCaseStatementToList(result, item);
 
 ListOfVariableDeclarator:
+    type-ok:
+        ListNode*
     init: |
         BINJS_TRY_DECL(result, factory_.newDeclarationList(ParseNodeKind::Const /*Placeholder*/,
             tokenizer_->pos(start)));
 
 LiteralBooleanExpression:
     build:
         BINJS_TRY_DECL(result, factory_.newBooleanLiteral(value, tokenizer_->pos(start)));
 
@@ -843,21 +865,25 @@ OptionalAssertedBlockScope:
 OptionalAssertedVarScope:
     type-ok:
         Ok
 
 OptionalAssertedParameterScope:
     type-ok:
         Ok
 
+OptionalCatchClause:
+    type-ok:
+        LexicalScopeNode*
+
 Parameter:
     sum-arms:
         BindingIdentifier:
             after: |
-                if (!parseContext_->positionalFormalParameterNames().append(result->pn_atom))
+                if (!parseContext_->positionalFormalParameterNames().append(result->template as<NameNode>().atom()))
                     return raiseOOM();
                 if (parseContext_->isFunctionBox())
                     parseContext_->functionBox()->length++;
 
 ReturnStatement:
     init: |
         if (!parseContext_->isFunctionBox()) {
             // Return statements are permitted only inside functions.
@@ -886,16 +912,18 @@ Setter:
 ShorthandProperty:
     build: |
         if (!factory_.isUsableAsObjectPropertyName(name))
             BINJS_TRY_VAR(name, factory_.newObjectLiteralPropertyName(name->name(), tokenizer_->pos(start)));
 
         BINJS_TRY_DECL(result, factory_.newObjectMethodOrPropertyDefinition(name, name, AccessorType::None));
 
 SwitchCase:
+    type-ok:
+        CaseClause*
     build: |
         BINJS_TRY_DECL(result, factory_.newCaseOrDefault(start, test, consequent));
 
 SwitchDefault:
     build: |
         BINJS_TRY_DECL(result, factory_.newCaseOrDefault(start, nullptr, consequent));
 
 SwitchStatement:
@@ -903,17 +931,17 @@ SwitchStatement:
         BINJS_TRY_DECL(scope, factory_.newLexicalScope(nullptr, cases));
         BINJS_TRY_DECL(result, factory_.newSwitchStatement(start, discriminant, scope, false));
 
 SwitchStatementWithDefault:
     build: |
         // Concatenate `preDefaultCase`, `defaultCase`, `postDefaultCase`
         auto cases = preDefaultCases;
         factory_.addList(cases, defaultCase);
-        ParseNode* iter = postDefaultCases->pn_head;
+        ParseNode* iter = postDefaultCases->head();
         while (iter) {
             ParseNode* next = iter->pn_next;
             factory_.addList(cases, iter);
             iter = next;
         }
         BINJS_TRY_DECL(scope, factory_.newLexicalScope(nullptr, cases));
         BINJS_TRY_DECL(result, factory_.newSwitchStatement(start, discriminant, scope, true));
 
@@ -1062,17 +1090,17 @@ VariableDeclaration:
     fields:
         kind:
             after: |
                 // Restored by `kindGuard`.
                 variableDeclarationKind_ = kind_;
 
     build: |
         // By specification, the list may not be empty.
-        if (declarators->pn_count == 0)
+        if (declarators->empty())
             return raiseEmpty("VariableDeclaration");
 
         ParseNodeKind pnk;
         switch (kind_) {
           case VariableDeclarationKind::Var:
             pnk = ParseNodeKind::Var;
             break;
           case VariableDeclarationKind::Let:
@@ -1085,21 +1113,21 @@ VariableDeclaration:
         declarators->setKind(pnk);
         auto result = declarators;
 
 VariableDeclarator:
     build: |
         ParseNode* result;
         if (binding->isKind(ParseNodeKind::Name)) {
             // `var foo [= bar]``
-            MOZ_TRY(checkBinding(binding->pn_atom->asPropertyName()));
+            MOZ_TRY(checkBinding(binding->template as<NameNode>().atom()->asPropertyName()));
 
-            BINJS_TRY_VAR(result, factory_.newName(binding->pn_atom->asPropertyName(), tokenizer_->pos(start), cx_));
+            BINJS_TRY_VAR(result, factory_.newName(binding->template as<NameNode>().atom()->asPropertyName(), tokenizer_->pos(start), cx_));
             if (init)
-                result->pn_expr = init;
+                result->as<NameNode>().setInitializer(init);
         } else {
             // `var pattern = bar`
             if (!init) {
                 // Here, `init` is required.
                 return raiseMissingField("VariableDeclarator (with non-trivial pattern)", BinField::Init);
             }
 
             MOZ_CRASH("Unimplemented: AssertedScope check for BindingPattern variable declaration");
--- a/js/src/frontend/BytecodeCompiler.cpp
+++ b/js/src/frontend/BytecodeCompiler.cpp
@@ -436,17 +436,17 @@ BytecodeCompiler::compileModule()
     if (!pn) {
         return nullptr;
     }
 
     Maybe<BytecodeEmitter> emitter;
     if (!emplaceEmitter(emitter, &modulesc)) {
         return nullptr;
     }
-    if (!emitter->emitScript(pn->pn_body)) {
+    if (!emitter->emitScript(pn->as<CodeNode>().body())) {
         return nullptr;
     }
 
     if (!builder.initModule()) {
         return nullptr;
     }
 
     RootedModuleEnvironmentObject env(cx, ModuleEnvironmentObject::create(cx, module));
@@ -493,32 +493,35 @@ BytecodeCompiler::compileStandaloneFunct
         Directives newDirectives = directives;
         fn = parser->standaloneFunction(fun, enclosingScope, parameterListEnd, generatorKind,
                                         asyncKind, directives, &newDirectives);
         if (!fn && !handleParseFailure(newDirectives, startPosition)) {
             return false;
         }
     } while (!fn);
 
-    if (fn->pn_funbox->function()->isInterpreted()) {
-        MOZ_ASSERT(fun == fn->pn_funbox->function());
+    FunctionBox* funbox = fn->as<CodeNode>().funbox();
+    if (funbox->function()->isInterpreted()) {
+        MOZ_ASSERT(fun == funbox->function());
 
-        if (!createScript(fn->pn_funbox->toStringStart, fn->pn_funbox->toStringEnd)) {
+        if (!createScript(funbox->toStringStart, funbox->toStringEnd)) {
             return false;
         }
 
         Maybe<BytecodeEmitter> emitter;
-        if (!emplaceEmitter(emitter, fn->pn_funbox)) {
+        if (!emplaceEmitter(emitter, funbox)) {
             return false;
         }
-        if (!emitter->emitFunctionScript(fn, BytecodeEmitter::TopLevelFunction::Yes)) {
+        if (!emitter->emitFunctionScript(&fn->as<CodeNode>(),
+                                         BytecodeEmitter::TopLevelFunction::Yes))
+        {
             return false;
         }
     } else {
-        fun.set(fn->pn_funbox->function());
+        fun.set(funbox->function());
         MOZ_ASSERT(IsAsmJSModule(fun));
     }
 
     // Enqueue an off-thread source compression task after finishing parsing.
     if (!scriptSource->tryCompressOffThread(cx)) {
         return false;
     }
 
@@ -892,23 +895,23 @@ frontend::CompileLazyFunction(JSContext*
 
     if (lazy->isLikelyConstructorWrapper()) {
         script->setLikelyConstructorWrapper();
     }
     if (lazy->hasBeenCloned()) {
         script->setHasBeenCloned();
     }
 
-    BytecodeEmitter bce(/* parent = */ nullptr, &parser, pn->pn_funbox, script, lazy,
+    BytecodeEmitter bce(/* parent = */ nullptr, &parser, pn->as<CodeNode>().funbox(), script, lazy,
                         pn->pn_pos, BytecodeEmitter::LazyFunction);
     if (!bce.init()) {
         return false;
     }
 
-    if (!bce.emitFunctionScript(pn, BytecodeEmitter::TopLevelFunction::Yes)) {
+    if (!bce.emitFunctionScript(&pn->as<CodeNode>(), BytecodeEmitter::TopLevelFunction::Yes)) {
         return false;
     }
 
     delazificationCompletion.complete();
     assertException.reset();
     return true;
 }
 
--- a/js/src/frontend/BytecodeEmitter.cpp
+++ b/js/src/frontend/BytecodeEmitter.cpp
@@ -557,22 +557,23 @@ BytecodeEmitter::getOffsetForLoop(ParseN
 {
     if (!nextpn) {
         return Nothing();
     }
 
     // Try to give the JSOP_LOOPHEAD and JSOP_LOOPENTRY the same line number as
     // the next instruction. nextpn is often a block, in which case the next
     // instruction typically comes from the first statement inside.
-    if (nextpn->isKind(ParseNodeKind::LexicalScope)) {
-        nextpn = nextpn->scopeBody();
-    }
-    MOZ_ASSERT_IF(nextpn->isKind(ParseNodeKind::StatementList), nextpn->isArity(PN_LIST));
-    if (nextpn->isKind(ParseNodeKind::StatementList) && nextpn->pn_head) {
-        nextpn = nextpn->pn_head;
+    if (nextpn->is<LexicalScopeNode>()) {
+        nextpn = nextpn->as<LexicalScopeNode>().scopeBody();
+    }
+    if (nextpn->isKind(ParseNodeKind::StatementList)) {
+        if (ParseNode* firstStatement = nextpn->as<ListNode>().head()) {
+            nextpn = firstStatement;
+        }
     }
 
     return Some(nextpn->pn_pos.begin);
 }
 
 void
 BytecodeEmitter::checkTypeSet(JSOp op)
 {
@@ -895,20 +896,20 @@ BytecodeEmitter::emitAtomOp(JSAtom* atom
     if (!makeAtomIndex(atom, &index)) {
         return false;
     }
 
     return emitIndexOp(op, index);
 }
 
 bool
-BytecodeEmitter::emitAtomOp(ParseNode* pn, JSOp op)
-{
-    MOZ_ASSERT(pn->pn_atom != nullptr);
-    return emitAtomOp(pn->pn_atom, op);
+BytecodeEmitter::emitAtomOp(NameNode* nameNode, JSOp op)
+{
+    MOZ_ASSERT(nameNode->atom() != nullptr);
+    return emitAtomOp(nameNode->atom(), op);
 }
 
 bool
 BytecodeEmitter::emitInternedScopeOp(uint32_t index, JSOp op)
 {
     MOZ_ASSERT(JOF_OPTYPE(op) == JOF_SCOPE);
     MOZ_ASSERT(index < scopeList.length());
     return emitIndex32(op, index);
@@ -1031,187 +1032,206 @@ BytecodeEmitter::checkSideEffects(ParseN
         return false;
     }
 
  restart:
 
     switch (pn->getKind()) {
       // Trivial cases with no side effects.
       case ParseNodeKind::EmptyStatement:
-      case ParseNodeKind::String:
-      case ParseNodeKind::TemplateString:
-      case ParseNodeKind::RegExp:
       case ParseNodeKind::True:
       case ParseNodeKind::False:
       case ParseNodeKind::Null:
       case ParseNodeKind::RawUndefined:
       case ParseNodeKind::Elision:
       case ParseNodeKind::Generator:
-      case ParseNodeKind::Number:
+        MOZ_ASSERT(pn->is<NullaryNode>());
+        *answer = false;
+        return true;
+
       case ParseNodeKind::ObjectPropertyName:
-        MOZ_ASSERT(pn->isArity(PN_NULLARY));
+      case ParseNodeKind::String:
+      case ParseNodeKind::TemplateString:
+        MOZ_ASSERT(pn->is<NameNode>());
+        *answer = false;
+        return true;
+
+      case ParseNodeKind::RegExp:
+        MOZ_ASSERT(pn->is<RegExpLiteral>());
+        *answer = false;
+        return true;
+
+      case ParseNodeKind::Number:
+        MOZ_ASSERT(pn->is<NumericLiteral>());
         *answer = false;
         return true;
 
       // |this| can throw in derived class constructors, including nested arrow
       // functions or eval.
       case ParseNodeKind::This:
-        MOZ_ASSERT(pn->isArity(PN_UNARY));
+        MOZ_ASSERT(pn->is<UnaryNode>());
         *answer = sc->needsThisTDZChecks();
         return true;
 
       // Trivial binary nodes with more token pos holders.
       case ParseNodeKind::NewTarget:
-      case ParseNodeKind::ImportMeta:
-        MOZ_ASSERT(pn->isArity(PN_BINARY));
-        MOZ_ASSERT(pn->pn_left->isKind(ParseNodeKind::PosHolder));
-        MOZ_ASSERT(pn->pn_right->isKind(ParseNodeKind::PosHolder));
+      case ParseNodeKind::ImportMeta: {
+        MOZ_ASSERT(pn->as<BinaryNode>().left()->isKind(ParseNodeKind::PosHolder));
+        MOZ_ASSERT(pn->as<BinaryNode>().right()->isKind(ParseNodeKind::PosHolder));
         *answer = false;
         return true;
+      }
 
       case ParseNodeKind::Break:
+        MOZ_ASSERT(pn->is<BreakStatement>());
+        *answer = true;
+        return true;
+
       case ParseNodeKind::Continue:
+        MOZ_ASSERT(pn->is<ContinueStatement>());
+        *answer = true;
+        return true;
+
       case ParseNodeKind::Debugger:
-        MOZ_ASSERT(pn->isArity(PN_NULLARY));
+        MOZ_ASSERT(pn->is<DebuggerStatement>());
         *answer = true;
         return true;
 
       // Watch out for getters!
       case ParseNodeKind::Dot:
-        MOZ_ASSERT(pn->isArity(PN_BINARY));
+        MOZ_ASSERT(pn->is<BinaryNode>());
         *answer = true;
         return true;
 
       // Unary cases with side effects only if the child has them.
       case ParseNodeKind::TypeOfExpr:
       case ParseNodeKind::Void:
       case ParseNodeKind::Not:
-        MOZ_ASSERT(pn->isArity(PN_UNARY));
-        return checkSideEffects(pn->pn_kid, answer);
+        return checkSideEffects(pn->as<UnaryNode>().kid(), answer);
 
       // Even if the name expression is effect-free, performing ToPropertyKey on
       // it might not be effect-free:
       //
       //   RegExp.prototype.toString = () => { throw 42; };
       //   ({ [/regex/]: 0 }); // ToPropertyKey(/regex/) throws 42
       //
       //   function Q() {
       //     ({ [new.target]: 0 });
       //   }
       //   Q.toString = () => { throw 17; };
       //   new Q; // new.target will be Q, ToPropertyKey(Q) throws 17
       case ParseNodeKind::ComputedName:
-        MOZ_ASSERT(pn->isArity(PN_UNARY));
+        MOZ_ASSERT(pn->is<UnaryNode>());
         *answer = true;
         return true;
 
       // Looking up or evaluating the associated name could throw.
       case ParseNodeKind::TypeOfName:
-        MOZ_ASSERT(pn->isArity(PN_UNARY));
+        MOZ_ASSERT(pn->is<UnaryNode>());
         *answer = true;
         return true;
 
       // This unary case has side effects on the enclosing object, sure.  But
       // that's not the question this function answers: it's whether the
       // operation may have a side effect on something *other* than the result
       // of the overall operation in which it's embedded.  The answer to that
       // is no, because an object literal having a mutated prototype only
       // produces a value, without affecting anything else.
       case ParseNodeKind::MutateProto:
-        MOZ_ASSERT(pn->isArity(PN_UNARY));
-        return checkSideEffects(pn->pn_kid, answer);
+        return checkSideEffects(pn->as<UnaryNode>().kid(), answer);
 
       // Unary cases with obvious side effects.
       case ParseNodeKind::PreIncrement:
       case ParseNodeKind::PostIncrement:
       case ParseNodeKind::PreDecrement:
       case ParseNodeKind::PostDecrement:
       case ParseNodeKind::Throw:
-        MOZ_ASSERT(pn->isArity(PN_UNARY));
+        MOZ_ASSERT(pn->is<UnaryNode>());
         *answer = true;
         return true;
 
       // These might invoke valueOf/toString, even with a subexpression without
       // side effects!  Consider |+{ valueOf: null, toString: null }|.
       case ParseNodeKind::BitNot:
       case ParseNodeKind::Pos:
       case ParseNodeKind::Neg:
-        MOZ_ASSERT(pn->isArity(PN_UNARY));
+        MOZ_ASSERT(pn->is<UnaryNode>());
         *answer = true;
         return true;
 
       // This invokes the (user-controllable) iterator protocol.
       case ParseNodeKind::Spread:
-        MOZ_ASSERT(pn->isArity(PN_UNARY));
+        MOZ_ASSERT(pn->is<UnaryNode>());
         *answer = true;
         return true;
 
       case ParseNodeKind::InitialYield:
       case ParseNodeKind::YieldStar:
       case ParseNodeKind::Yield:
       case ParseNodeKind::Await:
-        MOZ_ASSERT(pn->isArity(PN_UNARY));
+        MOZ_ASSERT(pn->is<UnaryNode>());
         *answer = true;
         return true;
 
       // Deletion generally has side effects, even if isolated cases have none.
       case ParseNodeKind::DeleteName:
       case ParseNodeKind::DeleteProp:
       case ParseNodeKind::DeleteElem:
-        MOZ_ASSERT(pn->isArity(PN_UNARY));
+        MOZ_ASSERT(pn->is<UnaryNode>());
         *answer = true;
         return true;
 
       // Deletion of a non-Reference expression has side effects only through
       // evaluating the expression.
       case ParseNodeKind::DeleteExpr: {
-        MOZ_ASSERT(pn->isArity(PN_UNARY));
-        ParseNode* expr = pn->pn_kid;
+        ParseNode* expr = pn->as<UnaryNode>().kid();
         return checkSideEffects(expr, answer);
       }
 
       case ParseNodeKind::ExpressionStatement:
-        MOZ_ASSERT(pn->isArity(PN_UNARY));
-        return checkSideEffects(pn->pn_kid, answer);
+        return checkSideEffects(pn->as<UnaryNode>().kid(), answer);
 
       // Binary cases with obvious side effects.
       case ParseNodeKind::Assign:
       case ParseNodeKind::AddAssign:
       case ParseNodeKind::SubAssign:
       case ParseNodeKind::BitOrAssign:
       case ParseNodeKind::BitXorAssign:
       case ParseNodeKind::BitAndAssign:
       case ParseNodeKind::LshAssign:
       case ParseNodeKind::RshAssign:
       case ParseNodeKind::UrshAssign:
       case ParseNodeKind::MulAssign:
       case ParseNodeKind::DivAssign:
       case ParseNodeKind::ModAssign:
       case ParseNodeKind::PowAssign:
+        MOZ_ASSERT(pn->is<AssignmentNode>());
+        *answer = true;
+        return true;
+
       case ParseNodeKind::SetThis:
-        MOZ_ASSERT(pn->isArity(PN_BINARY));
+        MOZ_ASSERT(pn->is<BinaryNode>());
         *answer = true;
         return true;
 
       case ParseNodeKind::StatementList:
       // Strict equality operations and logical operators are well-behaved and
       // perform no conversions.
       case ParseNodeKind::Or:
       case ParseNodeKind::And:
       case ParseNodeKind::StrictEq:
       case ParseNodeKind::StrictNe:
       // Any subexpression of a comma expression could be effectful.
       case ParseNodeKind::Comma:
-        MOZ_ASSERT(pn->pn_count > 0);
+        MOZ_ASSERT(!pn->as<ListNode>().empty());
         MOZ_FALLTHROUGH;
       // Subcomponents of a literal may be effectful.
       case ParseNodeKind::Array:
       case ParseNodeKind::Object:
-        MOZ_ASSERT(pn->isArity(PN_LIST));
-        for (ParseNode* item = pn->pn_head; item; item = item->pn_next) {
+        for (ParseNode* item : pn->as<ListNode>().contents()) {
             if (!checkSideEffects(item, answer)) {
                 return false;
             }
             if (*answer) {
                 return true;
             }
         }
         return true;
@@ -1237,232 +1257,235 @@ BytecodeEmitter::checkSideEffects(ParseN
       case ParseNodeKind::Rsh:
       case ParseNodeKind::Ursh:
       case ParseNodeKind::Add:
       case ParseNodeKind::Sub:
       case ParseNodeKind::Star:
       case ParseNodeKind::Div:
       case ParseNodeKind::Mod:
       case ParseNodeKind::Pow:
-        MOZ_ASSERT(pn->isArity(PN_LIST));
-        MOZ_ASSERT(pn->pn_count >= 2);
+        MOZ_ASSERT(pn->as<ListNode>().count() >= 2);
         *answer = true;
         return true;
 
       case ParseNodeKind::Colon:
-      case ParseNodeKind::Case:
-        MOZ_ASSERT(pn->isArity(PN_BINARY));
-        if (!checkSideEffects(pn->pn_left, answer)) {
+      case ParseNodeKind::Case: {
+        BinaryNode* node = &pn->as<BinaryNode>();
+        if (!checkSideEffects(node->left(), answer)) {
             return false;
         }
         if (*answer) {
             return true;
         }
-        return checkSideEffects(pn->pn_right, answer);
+        return checkSideEffects(node->right(), answer);
+      }
 
       // More getters.
       case ParseNodeKind::Elem:
-        MOZ_ASSERT(pn->isArity(PN_BINARY));
+        MOZ_ASSERT(pn->is<BinaryNode>());
         *answer = true;
         return true;
 
       // These affect visible names in this code, or in other code.
       case ParseNodeKind::Import:
       case ParseNodeKind::ExportFrom:
       case ParseNodeKind::ExportDefault:
-        MOZ_ASSERT(pn->isArity(PN_BINARY));
+        MOZ_ASSERT(pn->is<BinaryNode>());
         *answer = true;
         return true;
 
       // Likewise.
       case ParseNodeKind::Export:
-        MOZ_ASSERT(pn->isArity(PN_UNARY));
+        MOZ_ASSERT(pn->is<UnaryNode>());
         *answer = true;
         return true;
 
       case ParseNodeKind::CallImport:
-        MOZ_ASSERT(pn->isArity(PN_BINARY));
+        MOZ_ASSERT(pn->is<BinaryNode>());
         *answer = true;
         return true;
 
       // Every part of a loop might be effect-free, but looping infinitely *is*
       // an effect.  (Language lawyer trivia: C++ says threads can be assumed
       // to exit or have side effects, C++14 [intro.multithread]p27, so a C++
       // implementation's equivalent of the below could set |*answer = false;|
       // if all loop sub-nodes set |*answer = false|!)
       case ParseNodeKind::DoWhile:
       case ParseNodeKind::While:
       case ParseNodeKind::For:
-        MOZ_ASSERT(pn->isArity(PN_BINARY));
+        MOZ_ASSERT(pn->is<BinaryNode>());
         *answer = true;
         return true;
 
       // Declarations affect the name set of the relevant scope.
       case ParseNodeKind::Var:
       case ParseNodeKind::Const:
       case ParseNodeKind::Let:
-        MOZ_ASSERT(pn->isArity(PN_LIST));
+        MOZ_ASSERT(pn->is<ListNode>());
         *answer = true;
         return true;
 
       case ParseNodeKind::If:
       case ParseNodeKind::Conditional:
-        MOZ_ASSERT(pn->isArity(PN_TERNARY));
-        if (!checkSideEffects(pn->pn_kid1, answer)) {
+      {
+        TernaryNode* node = &pn->as<TernaryNode>();
+        if (!checkSideEffects(node->kid1(), answer)) {
             return false;
         }
         if (*answer) {
             return true;
         }
-        if (!checkSideEffects(pn->pn_kid2, answer)) {
+        if (!checkSideEffects(node->kid2(), answer)) {
             return false;
         }
         if (*answer) {
             return true;
         }
-        if ((pn = pn->pn_kid3)) {
+        if ((pn = node->kid3())) {
             goto restart;
         }
         return true;
+      }
 
       // Function calls can invoke non-local code.
       case ParseNodeKind::New:
       case ParseNodeKind::Call:
       case ParseNodeKind::TaggedTemplate:
       case ParseNodeKind::SuperCall:
-        MOZ_ASSERT(pn->isArity(PN_BINARY));
+        MOZ_ASSERT(pn->is<BinaryNode>());
         *answer = true;
         return true;
 
       // Function arg lists can contain arbitrary expressions. Technically
       // this only causes side-effects if one of the arguments does, but since
       // the call being made will always trigger side-effects, it isn't needed.
       case ParseNodeKind::Arguments:
-        MOZ_ASSERT(pn->isArity(PN_LIST));
+        MOZ_ASSERT(pn->is<ListNode>());
         *answer = true;
         return true;
 
       case ParseNodeKind::Pipeline:
-        MOZ_ASSERT(pn->isArity(PN_LIST));
-        MOZ_ASSERT(pn->pn_count >= 2);
+        MOZ_ASSERT(pn->as<ListNode>().count() >= 2);
         *answer = true;
         return true;
 
       // Classes typically introduce names.  Even if no name is introduced,
       // the heritage and/or class body (through computed property names)
       // usually have effects.
       case ParseNodeKind::Class:
-        MOZ_ASSERT(pn->isArity(PN_TERNARY));
+        MOZ_ASSERT(pn->is<ClassNode>());
         *answer = true;
         return true;
 
       // |with| calls |ToObject| on its expression and so throws if that value
       // is null/undefined.
       case ParseNodeKind::With:
-        MOZ_ASSERT(pn->isArity(PN_BINARY));
+        MOZ_ASSERT(pn->is<BinaryNode>());
         *answer = true;
         return true;
 
       case ParseNodeKind::Return:
-        MOZ_ASSERT(pn->isArity(PN_BINARY));
+        MOZ_ASSERT(pn->is<BinaryNode>());
         *answer = true;
         return true;
 
       case ParseNodeKind::Name:
-        MOZ_ASSERT(pn->isArity(PN_NAME));
+        MOZ_ASSERT(pn->is<NameNode>());
         *answer = true;
         return true;
 
       // Shorthands could trigger getters: the |x| in the object literal in
       // |with ({ get x() { throw 42; } }) ({ x });|, for example, triggers
       // one.  (Of course, it isn't necessary to use |with| for a shorthand to
       // trigger a getter.)
       case ParseNodeKind::Shorthand:
-        MOZ_ASSERT(pn->isArity(PN_BINARY));
+        MOZ_ASSERT(pn->is<BinaryNode>());
         *answer = true;
         return true;
 
       case ParseNodeKind::Function:
-        MOZ_ASSERT(pn->isArity(PN_CODE));
+        MOZ_ASSERT(pn->is<CodeNode>());
         /*
          * A named function, contrary to ES3, is no longer effectful, because
          * we bind its name lexically (using JSOP_CALLEE) instead of creating
          * an Object instance and binding a readonly, permanent property in it
          * (the object and binding can be detected and hijacked or captured).
          * This is a bug fix to ES3; it is fixed in ES3.1 drafts.
          */
         *answer = false;
         return true;
 
       case ParseNodeKind::Module:
         *answer = false;
         return true;
 
       case ParseNodeKind::Try:
-        MOZ_ASSERT(pn->isArity(PN_TERNARY));
-        if (!checkSideEffects(pn->pn_kid1, answer)) {
+      {
+        TryNode* tryNode = &pn->as<TryNode>();
+        if (!checkSideEffects(tryNode->body(), answer)) {
             return false;
         }
         if (*answer) {
             return true;
         }
-        if (ParseNode* catchScope = pn->pn_kid2) {
-            MOZ_ASSERT(catchScope->isKind(ParseNodeKind::LexicalScope));
+        if (LexicalScopeNode* catchScope = tryNode->catchScope()) {
             if (!checkSideEffects(catchScope, answer)) {
                 return false;
             }
             if (*answer) {
                 return true;
             }
         }
-        if (ParseNode* finallyBlock = pn->pn_kid3) {
+        if (ParseNode* finallyBlock = tryNode->finallyBlock()) {
             if (!checkSideEffects(finallyBlock, answer)) {
                 return false;
             }
         }
         return true;
-
-      case ParseNodeKind::Catch:
-        MOZ_ASSERT(pn->isArity(PN_BINARY));
-        if (ParseNode* name = pn->pn_left) {
+      }
+
+      case ParseNodeKind::Catch: {
+        BinaryNode* catchClause = &pn->as<BinaryNode>();
+        if (ParseNode* name = catchClause->left()) {
             if (!checkSideEffects(name, answer)) {
                 return false;
             }
             if (*answer) {
                 return true;
             }
         }
-        return checkSideEffects(pn->pn_right, answer);
-
-      case ParseNodeKind::Switch:
-        MOZ_ASSERT(pn->isArity(PN_BINARY));
-        if (!checkSideEffects(pn->pn_left, answer)) {
-            return false;
-        }
-        return *answer || checkSideEffects(pn->pn_right, answer);
+        return checkSideEffects(catchClause->right(), answer);
+      }
+
+      case ParseNodeKind::Switch: {
+        SwitchStatement* switchStmt = &pn->as<SwitchStatement>();
+        if (!checkSideEffects(&switchStmt->discriminant(), answer)) {
+            return false;
+        }
+        return *answer || checkSideEffects(&switchStmt->lexicalForCaseList(), answer);
+      }
 
       case ParseNodeKind::Label:
-        MOZ_ASSERT(pn->isArity(PN_NAME));
-        return checkSideEffects(pn->expr(), answer);
+        return checkSideEffects(pn->as<LabeledStatement>().statement(), answer);
 
       case ParseNodeKind::LexicalScope:
-        MOZ_ASSERT(pn->isArity(PN_SCOPE));
-        return checkSideEffects(pn->scopeBody(), answer);
+        return checkSideEffects(pn->as<LexicalScopeNode>().scopeBody(), answer);
 
       // We could methodically check every interpolated expression, but it's
       // probably not worth the trouble.  Treat template strings as effect-free
       // only if they don't contain any substitutions.
-      case ParseNodeKind::TemplateStringList:
-        MOZ_ASSERT(pn->isArity(PN_LIST));
-        MOZ_ASSERT(pn->pn_count > 0);
-        MOZ_ASSERT((pn->pn_count % 2) == 1,
+      case ParseNodeKind::TemplateStringList: {
+        ListNode* list = &pn->as<ListNode>();
+        MOZ_ASSERT(!list->empty());
+        MOZ_ASSERT((list->count() % 2) == 1,
                    "template strings must alternate template and substitution "
                    "parts");
-        *answer = pn->pn_count > 1;
+        *answer = list->count() > 1;
         return true;
+      }
 
       // This should be unreachable but is left as-is for now.
       case ParseNodeKind::ParamsBody:
         *answer = true;
         return true;
 
       case ParseNodeKind::ForIn:           // by ParseNodeKind::For
       case ParseNodeKind::ForOf:           // by ParseNodeKind::For
@@ -2000,150 +2023,149 @@ BytecodeEmitter::emitTDZCheckIfNeeded(JS
             return false;
         }
     }
 
     return innermostTDZCheckCache->noteTDZCheck(this, name, DontCheckTDZ);
 }
 
 bool
-BytecodeEmitter::emitPropLHS(ParseNode* pn)
-{
-    MOZ_ASSERT(pn->isKind(ParseNodeKind::Dot));
-    MOZ_ASSERT(!pn->as<PropertyAccess>().isSuper());
-
-    ParseNode* pn2 = pn->pn_left;
-
-    /*
-     * If the object operand is also a dotted property reference, reverse the
-     * list linked via pn_left temporarily so we can iterate over it from the
-     * bottom up (reversing again as we go), to avoid excessive recursion.
-     */
-    if (pn2->isKind(ParseNodeKind::Dot) && !pn2->as<PropertyAccess>().isSuper()) {
-        ParseNode* pndot = pn2;
-        ParseNode* pnup = nullptr;
-        ParseNode* pndown;
-        for (;;) {
-            /* Reverse pndot->pn_left to point up, not down. */
-            pndown = pndot->pn_left;
-            pndot->pn_left = pnup;
-            if (!pndown->isKind(ParseNodeKind::Dot) || pndown->as<PropertyAccess>().isSuper()) {
-                break;
-            }
-            pnup = pndot;
-            pndot = pndown;
-        }
-
-        /* pndown is a primary expression, not a dotted property reference. */
-        if (!emitTree(pndown)) {
-            return false;
-        }
-
-        do {
-            /* Walk back up the list, emitting annotated name ops. */
-            if (!emitAtomOp(pndot->pn_right, JSOP_GETPROP)) {
-                return false;
-            }
-
-            /* Reverse the pn_left link again. */
-            pnup = pndot->pn_left;
-            pndot->pn_left = pndown;
-            pndown = pndot;
-        } while ((pndot = pnup) != nullptr);
-        return true;
-    }
-
-    // The non-optimized case.
-    return emitTree(pn2);
-}
-
-bool
-BytecodeEmitter::emitSuperPropLHS(ParseNode* superBase, bool isCall)
+BytecodeEmitter::emitPropLHS(PropertyAccess* prop)
+{
+    MOZ_ASSERT(!prop->isSuper());
+
+    ParseNode* expr = &prop->expression();
+
+    if (!expr->is<PropertyAccess>() || expr->as<PropertyAccess>().isSuper()) {
+        // The non-optimized case.
+        return emitTree(expr);
+    }
+
+    // If the object operand is also a dotted property reference, reverse the
+    // list linked via expression() temporarily so we can iterate over it from
+    // the bottom up (reversing again as we go), to avoid excessive recursion.
+    PropertyAccess* pndot = &expr->as<PropertyAccess>();
+    ParseNode* pnup = nullptr;
+    ParseNode* pndown;
+    for (;;) {
+        // Reverse pndot->expression() to point up, not down.
+        pndown = &pndot->expression();
+        pndot->setExpression(pnup);
+        if (!pndown->is<PropertyAccess>() || pndown->as<PropertyAccess>().isSuper()) {
+            break;
+        }
+        pnup = pndot;
+        pndot = &pndown->as<PropertyAccess>();
+    }
+
+    // pndown is a primary expression, not a dotted property reference.
+    if (!emitTree(pndown)) {
+        return false;
+    }
+
+    while (true) {
+        // Walk back up the list, emitting annotated name ops.
+        if (!emitAtomOp(&pndot->key(), JSOP_GETPROP)) {
+            return false;
+        }
+
+        // Reverse the pndot->expression() link again.
+        pnup = pndot->maybeExpression();
+        pndot->setExpression(pndown);
+        pndown = pndot;
+        if (!pnup) {
+            break;
+        }
+        pndot = &pnup->as<PropertyAccess>();
+    }
+    return true;
+}
+
+bool
+BytecodeEmitter::emitSuperPropLHS(UnaryNode* superBase, bool isCall)
 {
     if (!emitGetThisForSuperBase(superBase)) {
         return false;
     }
     if (isCall && !emit1(JSOP_DUP)) {
         return false;
     }
     if (!emit1(JSOP_SUPERBASE)) {
         return false;
     }
     return true;
 }
 
 bool
-BytecodeEmitter::emitPropOp(ParseNode* pn, JSOp op)
-{
-    MOZ_ASSERT(pn->isArity(PN_BINARY));
-
-    if (!emitPropLHS(pn)) {
+BytecodeEmitter::emitPropOp(PropertyAccess* prop, JSOp op)
+{
+    if (!emitPropLHS(prop)) {
         return false;
     }
 
     if (op == JSOP_CALLPROP && !emit1(JSOP_DUP)) {
         return false;
     }
 
-    if (!emitAtomOp(pn->pn_right, op)) {
+    if (!emitAtomOp(&prop->key(), op)) {
         return false;
     }
 
     if (op == JSOP_CALLPROP && !emit1(JSOP_SWAP)) {
         return false;
     }
 
     return true;
 }
 
 bool
-BytecodeEmitter::emitSuperGetProp(ParseNode* pn, bool isCall)
-{
-    ParseNode* base = &pn->as<PropertyAccess>().expression();
+BytecodeEmitter::emitSuperGetProp(PropertyAccess* prop, bool isCall)
+{
+    UnaryNode* base = &prop->expression().as<UnaryNode>();
     if (!emitSuperPropLHS(base, isCall)) {
         return false;
     }
 
-    if (!emitAtomOp(pn->pn_right, JSOP_GETPROP_SUPER)) {
+    if (!emitAtomOp(&prop->key(), JSOP_GETPROP_SUPER)) {
         return false;
     }
 
     if (isCall && !emit1(JSOP_SWAP)) {
         return false;
     }
 
     return true;
 }
 
 bool
-BytecodeEmitter::emitPropIncDec(ParseNode* pn)
-{
-    MOZ_ASSERT(pn->pn_kid->isKind(ParseNodeKind::Dot));
+BytecodeEmitter::emitPropIncDec(UnaryNode* incDec)
+{
+    PropertyAccess* prop = &incDec->kid()->as<PropertyAccess>();
 
     bool post;
-    bool isSuper = pn->pn_kid->as<PropertyAccess>().isSuper();
-    JSOp binop = GetIncDecInfo(pn->getKind(), &post);
+    bool isSuper = prop->isSuper();
+    JSOp binop = GetIncDecInfo(incDec->getKind(), &post);
 
     if (isSuper) {
-        ParseNode* base = &pn->pn_kid->as<PropertyAccess>().expression();
+        UnaryNode* base = &prop->expression().as<UnaryNode>();
         if (!emitSuperPropLHS(base)) {              // THIS OBJ
             return false;
         }
         if (!emit1(JSOP_DUP2)) {                    // THIS OBJ THIS OBJ
             return false;
         }
     } else {
-        if (!emitPropLHS(pn->pn_kid)) {             // OBJ
-            return false;
+        if (!emitPropLHS(prop)) {
+            return false;                           // OBJ
         }
         if (!emit1(JSOP_DUP)) {                     // OBJ OBJ
             return false;
         }
     }
-    if (!emitAtomOp(pn->pn_kid->pn_right, isSuper ? JSOP_GETPROP_SUPER : JSOP_GETPROP)) { // OBJ V
+    if (!emitAtomOp(&prop->key(), isSuper ? JSOP_GETPROP_SUPER : JSOP_GETPROP)) { // OBJ V
         return false;
     }
     if (!emit1(JSOP_POS)) {                         // OBJ N
         return false;
     }
     if (post && !emit1(JSOP_DUP)) {                 // OBJ N? N
         return false;
     }
@@ -2168,17 +2190,17 @@ BytecodeEmitter::emitPropIncDec(ParseNod
             if (!emit1(JSOP_SWAP)) {               // N THIS OBJ N+1
                 return false;
             }
         }
     }
 
     JSOp setOp = isSuper ? sc->strict() ? JSOP_STRICTSETPROP_SUPER : JSOP_SETPROP_SUPER
                          : sc->strict() ? JSOP_STRICTSETPROP : JSOP_SETPROP;
-    if (!emitAtomOp(pn->pn_kid->pn_right, setOp)) { // N? N+1
+    if (!emitAtomOp(&prop->key(), setOp)) {         // N? N+1
         return false;
     }
     if (post && !emit1(JSOP_POP)) {                 // RESULT
         return false;
     }
 
     return true;
 }
@@ -2205,27 +2227,27 @@ BytecodeEmitter::emitGetNameAtLocationFo
             return false;
         }
     }
 
     return true;
 }
 
 bool
-BytecodeEmitter::emitNameIncDec(ParseNode* pn)
-{
-    MOZ_ASSERT(pn->pn_kid->isKind(ParseNodeKind::Name));
+BytecodeEmitter::emitNameIncDec(UnaryNode* incDec)
+{
+    MOZ_ASSERT(incDec->kid()->isKind(ParseNodeKind::Name));
 
     bool post;
-    JSOp binop = GetIncDecInfo(pn->getKind(), &post);
-
-    auto emitRhs = [pn, post, binop](BytecodeEmitter* bce, const NameLocation& loc,
-                                     bool emittedBindOp)
+    JSOp binop = GetIncDecInfo(incDec->getKind(), &post);
+
+    auto emitRhs = [incDec, post, binop](BytecodeEmitter* bce, const NameLocation& loc,
+                                         bool emittedBindOp)
     {
-        JSAtom* name = pn->pn_kid->name();
+        JSAtom* name = incDec->kid()->name();
         if (!bce->emitGetNameAtLocationForCompoundAssignment(name, loc)) { // ENV? V
             return false;
         }
         if (!bce->emit1(JSOP_POS)) {                       // ENV? N
             return false;
         }
         if (post && !bce->emit1(JSOP_DUP)) {               // ENV? N? N
             return false;
@@ -2244,76 +2266,74 @@ BytecodeEmitter::emitNameIncDec(ParseNod
             if (!bce->emit1(JSOP_SWAP)) {                  // N? ENV? N+1
                 return false;
             }
         }
 
         return true;
     };
 
-    if (!emitSetName(pn->pn_kid, emitRhs)) {
+    if (!emitSetName(incDec->kid(), emitRhs)) {
         return false;
     }
 
     if (post && !emit1(JSOP_POP)) {
         return false;
     }
 
     return true;
 }
 
 bool
-BytecodeEmitter::emitElemOperands(ParseNode* pn, EmitElemOption opts)
-{
-    MOZ_ASSERT(pn->isArity(PN_BINARY));
-
-    if (!emitTree(pn->pn_left)) {
+BytecodeEmitter::emitElemOperands(PropertyByValue* elem, EmitElemOption opts)
+{
+    if (!emitTree(&elem->expression())) {
         return false;
     }
 
     if (opts == EmitElemOption::IncDec) {
         if (!emit1(JSOP_CHECKOBJCOERCIBLE)) {
             return false;
         }
     } else if (opts == EmitElemOption::Call) {
         if (!emit1(JSOP_DUP)) {
             return false;
         }
     }
 
-    if (!emitTree(pn->pn_right)) {
+    if (!emitTree(&elem->key())) {
         return false;
     }
 
     if (opts == EmitElemOption::IncDec || opts == EmitElemOption::CompoundAssign) {
         if (!emit1(JSOP_TOID)) {
             return false;
         }
     }
     return true;
 }
 
 bool
-BytecodeEmitter::emitSuperElemOperands(ParseNode* pn, EmitElemOption opts)
-{
-    MOZ_ASSERT(pn->isKind(ParseNodeKind::Elem) && pn->as<PropertyByValue>().isSuper());
-
-    if (!emitGetThisForSuperBase(pn->pn_left)) {    // THIS
-        return false;
+BytecodeEmitter::emitSuperElemOperands(PropertyByValue* elem, EmitElemOption opts)
+{
+    MOZ_ASSERT(elem->isSuper());
+
+    if (!emitGetThisForSuperBase(&elem->expression().as<UnaryNode>())) {
+        return false;                               // THIS
     }
 
     if (opts == EmitElemOption::Call) {
         // We need a second |this| that will be consumed during computation of
         // the property value. (The original |this| is passed to the call.)
         if (!emit1(JSOP_DUP)) {                     // THIS THIS
             return false;
         }
     }
 
-    if (!emitTree(pn->pn_right)) {                  // THIS? THIS KEY
+    if (!emitTree(&elem->key())) {                  // THIS? THIS KEY
         return false;
     }
 
     // We need to convert the key to an object id first, so that we do not do
     // it inside both the GETELEM and the SETELEM.
     if (opts == EmitElemOption::IncDec || opts == EmitElemOption::CompoundAssign) {
         if (!emit1(JSOP_TOID)) {                    // THIS? THIS KEY
             return false;
@@ -2334,69 +2354,68 @@ BytecodeEmitter::emitElemOpBase(JSOp op)
         return false;
     }
 
     checkTypeSet(op);
     return true;
 }
 
 bool
-BytecodeEmitter::emitElemOp(ParseNode* pn, JSOp op)
+BytecodeEmitter::emitElemOp(PropertyByValue* elem, JSOp op)
 {
     MOZ_ASSERT(op == JSOP_GETELEM ||
                op == JSOP_CALLELEM ||
                op == JSOP_DELELEM ||
                op == JSOP_STRICTDELELEM);
 
     EmitElemOption opts = op == JSOP_CALLELEM ? EmitElemOption::Call : EmitElemOption::Get;
 
-    return emitElemOperands(pn, opts) && emitElemOpBase(op);
-}
-
-bool
-BytecodeEmitter::emitSuperGetElem(ParseNode* pn, bool isCall)
+    return emitElemOperands(elem, opts) && emitElemOpBase(op);
+}
+
+bool
+BytecodeEmitter::emitSuperGetElem(PropertyByValue* elem, bool isCall)
 {
     EmitElemOption opts = isCall ? EmitElemOption::Call : EmitElemOption::Get;
 
-    if (!emitSuperElemOperands(pn, opts)) {         // THIS? THIS KEY SUPERBASE
+    if (!emitSuperElemOperands(elem, opts)) {       // THIS? THIS KEY SUPERBASE
         return false;
     }
     if (!emitElemOpBase(JSOP_GETELEM_SUPER)) {      // THIS? VALUE
         return false;
     }
 
     if (isCall && !emit1(JSOP_SWAP)) {              // VALUE THIS
         return false;
     }
 
     return true;
 }
 
 bool
-BytecodeEmitter::emitElemIncDec(ParseNode* pn)
-{
-    MOZ_ASSERT(pn->pn_kid->isKind(ParseNodeKind::Elem));
-
-    bool isSuper = pn->pn_kid->as<PropertyByValue>().isSuper();
+BytecodeEmitter::emitElemIncDec(UnaryNode* incDec)
+{
+    PropertyByValue* elem = &incDec->kid()->as<PropertyByValue>();
+    bool isSuper = elem->isSuper();
 
     // We need to convert the key to an object id first, so that we do not do
     // it inside both the GETELEM and the SETELEM. This is done by
     // emit(Super)ElemOperands.
     if (isSuper) {
-        if (!emitSuperElemOperands(pn->pn_kid, EmitElemOption::IncDec)) {
+        if (!emitSuperElemOperands(elem, EmitElemOption::IncDec)) {
             return false;
         }
     } else {
-        if (!emitElemOperands(pn->pn_kid, EmitElemOption::IncDec)) {
+        if (!emitElemOperands(elem, EmitElemOption::IncDec)) {
             return false;
         }
     }
 
     bool post;
-    JSOp binop = GetIncDecInfo(pn->getKind(), &post);
+    JSOp binop = GetIncDecInfo(incDec->getKind(), &post);
 
     JSOp getOp;
     if (isSuper) {
         // There's no such thing as JSOP_DUP3, so we have to be creative.
         // Note that pushing things again is no fewer JSOps.
         if (!emitDupAt(2)) {                            // THIS KEY OBJ THIS
             return false;
         }
@@ -2443,26 +2462,25 @@ BytecodeEmitter::emitElemIncDec(ParseNod
     if (post && !emit1(JSOP_POP)) {                     // RESULT
         return false;
     }
 
     return true;
 }
 
 bool
-BytecodeEmitter::emitCallIncDec(ParseNode* incDec)
+BytecodeEmitter::emitCallIncDec(UnaryNode* incDec)
 {
     MOZ_ASSERT(incDec->isKind(ParseNodeKind::PreIncrement) ||
                incDec->isKind(ParseNodeKind::PostIncrement) ||
                incDec->isKind(ParseNodeKind::PreDecrement) ||
                incDec->isKind(ParseNodeKind::PostDecrement));
 
-    MOZ_ASSERT(incDec->pn_kid->isKind(ParseNodeKind::Call));
-
-    ParseNode* call = incDec->pn_kid;
+    ParseNode* call = incDec->kid();
+    MOZ_ASSERT(call->isKind(ParseNodeKind::Call));
     if (!emitTree(call)) {                              // CALLRESULT
         return false;
     }
     if (!emit1(JSOP_POS)) {                             // N
         return false;
     }
 
     // The increment/decrement has no side effects, so proceed to throw for
@@ -2514,74 +2532,77 @@ BytecodeEmitter::emitNumberOp(double dva
 }
 
 /*
  * Using MOZ_NEVER_INLINE in here is a workaround for llvm.org/pr14047.
  * LLVM is deciding to inline this function which uses a lot of stack space
  * into emitTree which is recursive and uses relatively little stack space.
  */
 MOZ_NEVER_INLINE bool
-BytecodeEmitter::emitSwitch(SwitchStatement* pn)
-{
-    ParseNode& lexical = pn->lexicalForCaseList();
+BytecodeEmitter::emitSwitch(SwitchStatement* switchStmt)
+{
+    LexicalScopeNode& lexical = switchStmt->lexicalForCaseList();
     MOZ_ASSERT(lexical.isKind(ParseNodeKind::LexicalScope));
-    ParseNode* cases = lexical.scopeBody();
+    ListNode* cases = &lexical.scopeBody()->as<ListNode>();
     MOZ_ASSERT(cases->isKind(ParseNodeKind::StatementList));
 
     SwitchEmitter se(this);
-    if (!se.emitDiscriminant(Some(pn->pn_pos.begin))) {
-        return false;
-    }
-    if (!emitTree(&pn->discriminant())) {
+    if (!se.emitDiscriminant(Some(switchStmt->pn_pos.begin))) {
+        return false;
+    }
+    if (!emitTree(&switchStmt->discriminant())) {
         return false;
     }
 
     // Enter the scope before pushing the switch BreakableControl since all
     // breaks are under this scope.
+
     if (!lexical.isEmptyScope()) {
         if (!se.emitLexical(lexical.scopeBindings())) {
             return false;
         }
 
         // A switch statement may contain hoisted functions inside its
         // cases. The PNX_FUNCDEFS flag is propagated from the STATEMENTLIST
         // bodies of the cases to the case list.
-        if (cases->pn_xflags & PNX_FUNCDEFS) {
-            for (ParseNode* caseNode = cases->pn_head; caseNode; caseNode = caseNode->pn_next) {
-                if (caseNode->pn_right->pn_xflags & PNX_FUNCDEFS) {
-                    if (!emitHoistedFunctionsInList(caseNode->pn_right)) {
+        if (cases->hasTopLevelFunctionDeclarations()) {
+            for (ParseNode* item : cases->contents()) {
+                CaseClause* caseClause = &item->as<CaseClause>();
+                ListNode* statements = caseClause->statementList();
+                if (statements->hasTopLevelFunctionDeclarations()) {
+                    if (!emitHoistedFunctionsInList(statements)) {
                         return false;
                     }
                 }
             }
         }
     } else {
-        MOZ_ASSERT(!(cases->pn_xflags & PNX_FUNCDEFS));
+        MOZ_ASSERT(!cases->hasTopLevelFunctionDeclarations());
     }
 
     SwitchEmitter::TableGenerator tableGen(this);
-    uint32_t caseCount = cases->pn_count - (pn->hasDefault() ? 1 : 0);
-    CaseClause* firstCase = cases->pn_head ? &cases->pn_head->as<CaseClause>() : nullptr;
+    uint32_t caseCount = cases->count() - (switchStmt->hasDefault() ? 1 : 0);
     if (caseCount == 0) {
         tableGen.finish(0);
     } else {
-        for (CaseClause* caseNode = firstCase; caseNode; caseNode = caseNode->next()) {
-            if (caseNode->isDefault()) {
+        for (ParseNode* item : cases->contents()) {
+            CaseClause* caseClause = &item->as<CaseClause>();
+            if (caseClause->isDefault()) {
                 continue;
             }
 
-            ParseNode* caseValue = caseNode->caseExpression();
+            ParseNode* caseValue = caseClause->caseExpression();
 
             if (caseValue->getKind() != ParseNodeKind::Number) {
                 tableGen.setInvalid();
                 break;
             }
 
             int32_t i;
-            if (!NumberEqualsInt32(caseValue->pn_dval, &i)) {
+            if (!NumberEqualsInt32(caseValue->as<NumericLiteral>().value(), &i)) {
                 tableGen.setInvalid();
                 break;
             }
 
             if (!tableGen.addNumber(i)) {
                 return false;
             }
         }
@@ -2599,61 +2620,67 @@ BytecodeEmitter::emitSwitch(SwitchStatem
             return false;
         }
     } else {
         if (!se.emitCond()) {
             return false;
         }
 
         // Emit code for evaluating cases and jumping to case statements.
-        for (CaseClause* caseNode = firstCase; caseNode; caseNode = caseNode->next()) {
-            if (caseNode->isDefault()) {
+        for (ParseNode* item : cases->contents()) {
+            CaseClause* caseClause = &item->as<CaseClause>();
+            if (caseClause->isDefault()) {
                 continue;
             }
 
-            ParseNode* caseValue = caseNode->caseExpression();
+            ParseNode* caseValue = caseClause->caseExpression();
             // If the expression is a literal, suppress line number emission so
             // that debugging works more naturally.
             if (!emitTree(caseValue, ValueUsage::WantValue,
                           caseValue->isLiteral() ? SUPPRESS_LINENOTE : EMIT_LINENOTE))
             {
                 return false;
             }
 
             if (!se.emitCaseJump()) {
                 return false;
             }
         }
     }
 
     // Emit code for each case's statements.
-    for (CaseClause* caseNode = firstCase; caseNode; caseNode = caseNode->next()) {
-        if (caseNode->isDefault()) {
+    for (ParseNode* item : cases->contents()) {
+        CaseClause* caseClause = &item->as<CaseClause>();
+        if (caseClause->isDefault()) {
             if (!se.emitDefaultBody()) {
                 return false;
             }
         } else {
             if (isTableSwitch) {
-                ParseNode* caseValue = caseNode->caseExpression();
+                ParseNode* caseValue = caseClause->caseExpression();
                 MOZ_ASSERT(caseValue->isKind(ParseNodeKind::Number));
 
-                int32_t i = int32_t(caseValue->pn_dval);
-                MOZ_ASSERT(double(i) == caseValue->pn_dval);
+                NumericLiteral* literal = &caseValue->as<NumericLiteral>();
+#ifdef DEBUG
+                int32_t v;
+                MOZ_ASSERT(mozilla::NumberIsInt32(literal->value(), &v));
+#endif
+                int32_t i = int32_t(literal->value());
 
                 if (!se.emitCaseBody(i, tableGen)) {
                     return false;
                 }
             } else {
                 if (!se.emitCaseBody()) {
                     return false;
                 }
             }
         }
 
-        if (!emitTree(caseNode->statementList())) {
+        if (!emitTree(caseClause->statementList())) {
             return false;
         }
     }
 
     if (!se.emitEnd()) {
         return false;
     }
 
@@ -2711,28 +2738,28 @@ BytecodeEmitter::emitYieldOp(JSOp op)
     if (!yieldAndAwaitOffsetList.append(offset())) {
         return false;
     }
 
     return emit1(JSOP_DEBUGAFTERYIELD);
 }
 
 bool
-BytecodeEmitter::emitSetThis(ParseNode* pn)
+BytecodeEmitter::emitSetThis(BinaryNode* setThisNode)
 {
     // ParseNodeKind::SetThis is used to update |this| after a super() call
     // in a derived class constructor.
 
-    MOZ_ASSERT(pn->isKind(ParseNodeKind::SetThis));
-    MOZ_ASSERT(pn->pn_left->isKind(ParseNodeKind::Name));
-
-    RootedAtom name(cx, pn->pn_left->name());
-    auto emitRhs = [&name, pn](BytecodeEmitter* bce, const NameLocation&, bool) {
+    MOZ_ASSERT(setThisNode->isKind(ParseNodeKind::SetThis));
+    MOZ_ASSERT(setThisNode->left()->isKind(ParseNodeKind::Name));
+
+    RootedAtom name(cx, setThisNode->left()->name());
+    auto emitRhs = [&name, setThisNode](BytecodeEmitter* bce, const NameLocation&, bool) {
         // Emit the new |this| value.
-        if (!bce->emitTree(pn->pn_right)) {
+        if (!bce->emitTree(setThisNode->right())) {
             return false;
         }
         // Get the original |this| and throw if we already initialized
         // it. Do *not* use the NameLocation argument, as that's the special
         // lexical location below to deal with super() semantics.
         if (!bce->emitGetName(name)) {
             return false;
         }
@@ -2789,31 +2816,32 @@ BytecodeEmitter::emitScript(ParseNode* b
         if (!emitterScope.enterModule(this, sc->asModuleContext())) {
             return false;
         }
     }
 
     setFunctionBodyEndPos(body->pn_pos);
 
     if (sc->isEvalContext() && !sc->strict() &&
-        body->isKind(ParseNodeKind::LexicalScope) && !body->isEmptyScope())
+        body->is<LexicalScopeNode>() && !body->as<LexicalScopeNode>().isEmptyScope())
     {
         // Sloppy eval scripts may need to emit DEFFUNs in the prologue. If there is
         // an immediately enclosed lexical scope, we need to enter the lexical
         // scope in the prologue for the DEFFUNs to pick up the right
         // environment chain.
         EmitterScope lexicalEmitterScope(this);
+        LexicalScopeNode* scope = &body->as<LexicalScopeNode>();
 
         switchToPrologue();
-        if (!lexicalEmitterScope.enterLexical(this, ScopeKind::Lexical, body->scopeBindings())) {
+        if (!lexicalEmitterScope.enterLexical(this, ScopeKind::Lexical, scope->scopeBindings())) {
             return false;
         }
         switchToMain();
 
-        if (!emitLexicalScopeBody(body->scopeBody())) {
+        if (!emitLexicalScopeBody(scope->scopeBody())) {
             return false;
         }
 
         if (!lexicalEmitterScope.leave(this)) {
             return false;
         }
     } else {
         if (!emitTree(body)) {
@@ -2842,20 +2870,20 @@ BytecodeEmitter::emitScript(ParseNode* b
     }
 
     tellDebuggerAboutCompiledScript(cx);
 
     return true;
 }
 
 bool
-BytecodeEmitter::emitFunctionScript(ParseNode* fn, TopLevelFunction isTopLevel)
-{
-    MOZ_ASSERT(fn->isKind(ParseNodeKind::Function));
-    ParseNode* body = fn->pn_body;
+BytecodeEmitter::emitFunctionScript(CodeNode* funNode, TopLevelFunction isTopLevel)
+{
+    MOZ_ASSERT(funNode->isKind(ParseNodeKind::Function));
+    ParseNode* body = funNode->body();
     MOZ_ASSERT(body->isKind(ParseNodeKind::ParamsBody));
     FunctionBox* funbox = sc->asFunctionBox();
     AutoFrontendTraceLog traceLog(cx, TraceLogger_BytecodeEmission, parser->errorReporter(), funbox);
 
     setScriptStartOffsetIfUnset(body->pn_pos);
 
     // The ordering of these EmitterScopes is important. The named lambda
     // scope needs to enclose the function scope needs to enclose the extra
@@ -2907,17 +2935,17 @@ BytecodeEmitter::emitFunctionScript(Pars
     if (namedLambdaEmitterScope) {
         if (!namedLambdaEmitterScope->leave(this)) {
             return false;
         }
         namedLambdaEmitterScope.reset();
     }
 
     if (isTopLevel == TopLevelFunction::Yes) {
-        if (!NameFunctions(cx, fn)) {
+        if (!NameFunctions(cx, funNode)) {
             return false;
         }
     }
 
     if (!JSScript::fullyInitFromEmitter(cx, script, this)) {
         return false;
     }
 
@@ -2927,19 +2955,19 @@ BytecodeEmitter::emitFunctionScript(Pars
 }
 
 bool
 BytecodeEmitter::emitDestructuringLHSRef(ParseNode* target, size_t* emitted)
 {
     *emitted = 0;
 
     if (target->isKind(ParseNodeKind::Spread)) {
-        target = target->pn_kid;
+        target = target->as<UnaryNode>().kid();
     } else if (target->isKind(ParseNodeKind::Assign)) {
-        target = target->pn_left;
+        target = target->as<AssignmentNode>().left();
     }
 
     // No need to recur into ParseNodeKind::Array and
     // ParseNodeKind::Object subpatterns here, since
     // emitSetOrInitializeDestructuring does the recursion when
     // setting or initializing value.  Getting reference doesn't recur.
     if (target->isKind(ParseNodeKind::Name) ||
         target->isKind(ParseNodeKind::Array) ||
@@ -2949,38 +2977,40 @@ BytecodeEmitter::emitDestructuringLHSRef
     }
 
 #ifdef DEBUG
     int depth = stackDepth;
 #endif
 
     switch (target->getKind()) {
       case ParseNodeKind::Dot: {
-        if (target->as<PropertyAccess>().isSuper()) {
-            if (!emitSuperPropLHS(&target->as<PropertyAccess>().expression())) {
+        PropertyAccess* prop = &target->as<PropertyAccess>();
+        if (prop->isSuper()) {
+            if (!emitSuperPropLHS(&prop->expression().as<UnaryNode>())) {
                 return false;
             }
             *emitted = 2;
         } else {
-            if (!emitTree(target->pn_left)) {
+            if (!emitTree(&prop->expression())) {
                 return false;
             }
             *emitted = 1;
         }
         break;
       }
 
       case ParseNodeKind::Elem: {
-        if (target->as<PropertyByValue>().isSuper()) {
-            if (!emitSuperElemOperands(target, EmitElemOption::Ref)) {
+        PropertyByValue* elem = &target->as<PropertyByValue>();
+        if (elem->isSuper()) {
+            if (!emitSuperElemOperands(elem, EmitElemOption::Ref)) {
                 return false;
             }
             *emitted = 3;
         } else {
-            if (!emitElemOperands(target, EmitElemOption::Ref)) {
+            if (!emitElemOperands(elem, EmitElemOption::Ref)) {
                 return false;
             }
             *emitted = 2;
         }
         break;
       }
 
       case ParseNodeKind::Call:
@@ -3001,22 +3031,22 @@ BytecodeEmitter::emitDestructuringLHSRef
 bool
 BytecodeEmitter::emitSetOrInitializeDestructuring(ParseNode* target, DestructuringFlavor flav)
 {
     // Now emit the lvalue opcode sequence. If the lvalue is a nested
     // destructuring initialiser-form, call ourselves to handle it, then pop
     // the matched value. Otherwise emit an lvalue bytecode sequence followed
     // by an assignment op.
     if (target->isKind(ParseNodeKind::Spread)) {
-        target = target->pn_kid;
+        target = target->as<UnaryNode>().kid();
     } else if (target->isKind(ParseNodeKind::Assign)) {
-        target = target->pn_left;
+        target = target->as<AssignmentNode>().left();
     }
     if (target->isKind(ParseNodeKind::Array) || target->isKind(ParseNodeKind::Object)) {
-        if (!emitDestructuringOps(target, flav)) {
+        if (!emitDestructuringOps(&target->as<ListNode>(), flav)) {
             return false;
         }
         // Per its post-condition, emitDestructuringOps has left the
         // to-be-destructured value on top of the stack.
         if (!emit1(JSOP_POP)) {
             return false;
         }
     } else {
@@ -3077,31 +3107,33 @@ BytecodeEmitter::emitSetOrInitializeDest
                 break;
             }
 
             break;
           }
 
           case ParseNodeKind::Dot: {
             // The reference is already pushed by emitDestructuringLHSRef.
+            PropertyAccess* prop = &target->as<PropertyAccess>();
             JSOp setOp;
-            if (target->as<PropertyAccess>().isSuper()) {
+            if (prop->isSuper()) {
                 setOp = sc->strict() ? JSOP_STRICTSETPROP_SUPER : JSOP_SETPROP_SUPER;
             } else {
                 setOp = sc->strict() ? JSOP_STRICTSETPROP : JSOP_SETPROP;
             }
-            if (!emitAtomOp(target->pn_right, setOp)) {
+            if (!emitAtomOp(&prop->key(), setOp)) {
                 return false;
             }
             break;
           }
 
           case ParseNodeKind::Elem: {
             // The reference is already pushed by emitDestructuringLHSRef.
-            if (target->as<PropertyByValue>().isSuper()) {
+            PropertyByValue* elem = &target->as<PropertyByValue>();
+            if (elem->isSuper()) {
                 JSOp setOp = sc->strict() ? JSOP_STRICTSETELEM_SUPER : JSOP_SETELEM_SUPER;
                 // emitDestructuringLHSRef already did emitSuperElemOperands
                 // part of emitSuperElemOp.  Perform remaining part here.
                 if (!emitElemOpBase(setOp)) {
                     return false;
                 }
             } else {
                 JSOp setOp = sc->strict() ? JSOP_STRICTSETELEM : JSOP_SETELEM;
@@ -3431,17 +3463,17 @@ BytecodeEmitter::emitDefault(ParseNode* 
 bool
 BytecodeEmitter::setOrEmitSetFunName(ParseNode* maybeFun, HandleAtom name)
 {
     MOZ_ASSERT(maybeFun->isDirectRHSAnonFunction());
 
     if (maybeFun->isKind(ParseNodeKind::Function)) {
         // Function doesn't have 'name' property at this point.
         // Set function's name at compile time.
-        JSFunction* fun = maybeFun->pn_funbox->function();
+        JSFunction* fun = maybeFun->as<CodeNode>().funbox()->function();
 
         // The inferred name may already be set if this function is an
         // interpreted lazy function and we OOM'ed after we set the inferred
         // name the first time.
         if (fun->hasInferredName()) {
             MOZ_ASSERT(fun->isInterpretedLazy());
             MOZ_ASSERT(fun->inferredName() == name);
 
@@ -3489,20 +3521,19 @@ BytecodeEmitter::emitInitializer(ParseNo
 bool
 BytecodeEmitter::emitInitializerInBranch(ParseNode* initializer, ParseNode* pattern)
 {
     TDZCheckCache tdzCache(this);
     return emitInitializer(initializer, pattern);
 }
 
 bool
-BytecodeEmitter::emitDestructuringOpsArray(ParseNode* pattern, DestructuringFlavor flav)
+BytecodeEmitter::emitDestructuringOpsArray(ListNode* pattern, DestructuringFlavor flav)
 {
     MOZ_ASSERT(pattern->isKind(ParseNodeKind::Array));
-    MOZ_ASSERT(pattern->isArity(PN_LIST));
     MOZ_ASSERT(this->stackDepth != 0);
 
     // Here's pseudo code for |let [a, b, , c=y, ...d] = x;|
     //
     // Lines that are annotated "covered by trynote" mean that upon throwing
     // an exception, IteratorClose is called on iter only if done is false.
     //
     //   let x, y;
@@ -3592,17 +3623,17 @@ BytecodeEmitter::emitDestructuringOpsArr
         return false;
     }
     if (!emitIterator()) {                                        // ... OBJ NEXT ITER
         return false;
     }
 
     // For an empty pattern [], call IteratorClose unconditionally. Nothing
     // else needs to be done.
-    if (!pattern->pn_head) {
+    if (!pattern->head()) {
         if (!emit1(JSOP_SWAP)) {                                  // ... OBJ ITER NEXT
             return false;
         }
         if (!emit1(JSOP_POP)) {                                   // ... OBJ ITER
             return false;
         }
 
         return emitIteratorCloseInInnermostScope();               // ... OBJ
@@ -3613,26 +3644,26 @@ BytecodeEmitter::emitDestructuringOpsArr
         return false;
     }
 
     // JSTRY_DESTRUCTURING_ITERCLOSE expects the iterator and the done value
     // to be the second to top and the top of the stack, respectively.
     // IteratorClose is called upon exception only if done is false.
     int32_t tryNoteDepth = stackDepth;
 
-    for (ParseNode* member = pattern->pn_head; member; member = member->pn_next) {
-        bool isFirst = member == pattern->pn_head;
+    for (ParseNode* member : pattern->contents()) {
+        bool isFirst = member == pattern->head();
         DebugOnly<bool> hasNext = !!member->pn_next;
 
         size_t emitted = 0;
 
         // Spec requires LHS reference to be evaluated first.
         ParseNode* lhsPattern = member;
         if (lhsPattern->isKind(ParseNodeKind::Assign)) {
-            lhsPattern = lhsPattern->pn_left;
+            lhsPattern = lhsPattern->as<AssignmentNode>().left();
         }
 
         bool isElision = lhsPattern->isKind(ParseNodeKind::Elision);
         if (!isElision) {
             auto emitLHSRef = [lhsPattern, &emitted](BytecodeEmitter* bce) {
                 return bce->emitDestructuringLHSRef(lhsPattern, &emitted); // ... OBJ NEXT ITER DONE *LREF
             };
             if (!wrapWithDestructuringIteratorCloseTryNote(tryNoteDepth, emitLHSRef)) {
@@ -3719,17 +3750,17 @@ BytecodeEmitter::emitDestructuringOpsArr
             }
 
             MOZ_ASSERT(!hasNext);
             break;
         }
 
         ParseNode* pndefault = nullptr;
         if (member->isKind(ParseNodeKind::Assign)) {
-            pndefault = member->pn_right;
+            pndefault = member->as<AssignmentNode>().right();
         }
 
         MOZ_ASSERT(!member->isKind(ParseNodeKind::Spread));
 
         InternalIfEmitter ifAlreadyDone(this);
         if (!isFirst) {
                                                                   // ... OBJ NEXT ITER *LREF DONE
             if (!ifAlreadyDone.emitThenElse()) {                  // ... OBJ NEXT ITER *LREF
@@ -3864,61 +3895,62 @@ BytecodeEmitter::emitDestructuringOpsArr
     if (!ifDone.emitEnd()) {
         return false;
     }
 
     return true;
 }
 
 bool
-BytecodeEmitter::emitComputedPropertyName(ParseNode* computedPropName)
+BytecodeEmitter::emitComputedPropertyName(UnaryNode* computedPropName)
 {
     MOZ_ASSERT(computedPropName->isKind(ParseNodeKind::ComputedName));
-    return emitTree(computedPropName->pn_kid) && emit1(JSOP_TOID);
-}
-
-bool
-BytecodeEmitter::emitDestructuringOpsObject(ParseNode* pattern, DestructuringFlavor flav)
+    return emitTree(computedPropName->kid()) && emit1(JSOP_TOID);
+}
+
+bool
+BytecodeEmitter::emitDestructuringOpsObject(ListNode* pattern, DestructuringFlavor flav)
 {
     MOZ_ASSERT(pattern->isKind(ParseNodeKind::Object));
-    MOZ_ASSERT(pattern->isArity(PN_LIST));
 
     MOZ_ASSERT(this->stackDepth > 0);                             // ... RHS
 
     if (!emit1(JSOP_CHECKOBJCOERCIBLE)) {                         // ... RHS
         return false;
     }
 
-    bool needsRestPropertyExcludedSet = pattern->pn_count > 1 &&
+    bool needsRestPropertyExcludedSet = pattern->count() > 1 &&
                                         pattern->last()->isKind(ParseNodeKind::Spread);
     if (needsRestPropertyExcludedSet) {
         if (!emitDestructuringObjRestExclusionSet(pattern)) {     // ... RHS SET
             return false;
         }
 
         if (!emit1(JSOP_SWAP)) {                                  // ... SET RHS
             return false;
         }
     }
 
-    for (ParseNode* member = pattern->pn_head; member; member = member->pn_next) {
+    for (ParseNode* member : pattern->contents()) {
         ParseNode* subpattern;
         if (member->isKind(ParseNodeKind::MutateProto) ||
             member->isKind(ParseNodeKind::Spread))
         {
-            subpattern = member->pn_kid;
+            subpattern = member->as<UnaryNode>().kid();
         } else {
-            subpattern = member->pn_right;
+            MOZ_ASSERT(member->isKind(ParseNodeKind::Colon) ||
+                       member->isKind(ParseNodeKind::Shorthand));
+            subpattern = member->as<BinaryNode>().right();
         }
 
         ParseNode* lhs = subpattern;
         MOZ_ASSERT_IF(member->isKind(ParseNodeKind::Spread),
                       !lhs->isKind(ParseNodeKind::Assign));
         if (lhs->isKind(ParseNodeKind::Assign)) {
-            lhs = lhs->pn_left;
+            lhs = lhs->as<AssignmentNode>().left();
         }
 
         size_t emitted;
         if (!emitDestructuringLHSRef(lhs, &emitted)) {            // ... *SET RHS *LREF
             return false;
         }
 
         // Duplicate the value being destructured to use as a reference base.
@@ -3972,31 +4004,31 @@ BytecodeEmitter::emitDestructuringOpsObj
             if (!emitAtomOp(cx->names().proto, JSOP_GETPROP)) {   // ... *SET RHS *LREF PROP
                 return false;
             }
             needsGetElem = false;
         } else {
             MOZ_ASSERT(member->isKind(ParseNodeKind::Colon) ||
                        member->isKind(ParseNodeKind::Shorthand));
 
-            ParseNode* key = member->pn_left;
+            ParseNode* key = member->as<BinaryNode>().left();
             if (key->isKind(ParseNodeKind::Number)) {
-                if (!emitNumberOp(key->pn_dval)) {                // ... *SET RHS *LREF RHS KEY
-                    return false;
+                if (!emitNumberOp(key->as<NumericLiteral>().value())) {
+                    return false;                                 // ... *SET RHS *LREF RHS KEY
                 }
             } else if (key->isKind(ParseNodeKind::ObjectPropertyName) ||
                        key->isKind(ParseNodeKind::String))
             {
-                if (!emitAtomOp(key->pn_atom, JSOP_GETPROP)) {    // ... *SET RHS *LREF PROP
-                    return false;
+                if (!emitAtomOp(key->as<NameNode>().atom(), JSOP_GETPROP)) {
+                    return false;                                 // ... *SET RHS *LREF PROP
                 }
                 needsGetElem = false;
             } else {
-                if (!emitComputedPropertyName(key)) {             // ... *SET RHS *LREF RHS KEY
-                    return false;
+                if (!emitComputedPropertyName(&key->as<UnaryNode>())) {
+                    return false;                                 // ... *SET RHS *LREF RHS KEY
                 }
 
                 // Add the computed property key to the exclusion set.
                 if (needsRestPropertyExcludedSet) {
                     if (!emitDupAt(emitted + 3)) {                // ... SET RHS *LREF RHS KEY SET
                         return false;
                     }
                     if (!emitDupAt(1)) {                          // ... SET RHS *LREF RHS KEY SET KEY
@@ -4016,76 +4048,75 @@ BytecodeEmitter::emitDestructuringOpsObj
         }
 
         // Get the property value if not done already.
         if (needsGetElem && !emitElemOpBase(JSOP_GETELEM)) {      // ... *SET RHS *LREF PROP
             return false;
         }
 
         if (subpattern->isKind(ParseNodeKind::Assign)) {
-            if (!emitDefault(subpattern->pn_right, lhs)) {        // ... *SET RHS *LREF VALUE
-                return false;
+            if (!emitDefault(subpattern->as<AssignmentNode>().right(), lhs)) {
+                return false;                                     // ... *SET RHS *LREF VALUE
             }
         }
 
         // Destructure PROP per this member's lhs.
         if (!emitSetOrInitializeDestructuring(subpattern, flav)) {  // ... *SET RHS
             return false;
         }
     }
 
     return true;
 }
 
 bool
-BytecodeEmitter::emitDestructuringObjRestExclusionSet(ParseNode* pattern)
+BytecodeEmitter::emitDestructuringObjRestExclusionSet(ListNode* pattern)
 {
     MOZ_ASSERT(pattern->isKind(ParseNodeKind::Object));
-    MOZ_ASSERT(pattern->isArity(PN_LIST));
     MOZ_ASSERT(pattern->last()->isKind(ParseNodeKind::Spread));
 
     ptrdiff_t offset = this->offset();
     if (!emitNewInit()) {
         return false;
     }
 
     // Try to construct the shape of the object as we go, so we can emit a
     // JSOP_NEWOBJECT with the final shape instead.
     // In the case of computed property names and indices, we cannot fix the
     // shape at bytecode compile time. When the shape cannot be determined,
     // |obj| is nulled out.
 
     // No need to do any guessing for the object kind, since we know the upper
     // bound of how many properties we plan to have.
-    gc::AllocKind kind = gc::GetGCObjectKind(pattern->pn_count - 1);
+    gc::AllocKind kind = gc::GetGCObjectKind(pattern->count() - 1);
     RootedPlainObject obj(cx, NewBuiltinClassInstance<PlainObject>(cx, kind, TenuredObject));
     if (!obj) {
         return false;
     }
 
     RootedAtom pnatom(cx);
-    for (ParseNode* member = pattern->pn_head; member; member = member->pn_next) {
+    for (ParseNode* member : pattern->contents()) {
         if (member->isKind(ParseNodeKind::Spread)) {
             break;
         }
 
         bool isIndex = false;
         if (member->isKind(ParseNodeKind::MutateProto)) {
             pnatom.set(cx->names().proto);
         } else {
-            ParseNode* key = member->pn_left;
+            ParseNode* key = member->as<BinaryNode>().left();
             if (key->isKind(ParseNodeKind::Number)) {
-                if (!emitNumberOp(key->pn_dval)) {
+                if (!emitNumberOp(key->as<NumericLiteral>().value())) {
                     return false;
                 }
                 isIndex = true;
             } else if (key->isKind(ParseNodeKind::ObjectPropertyName) ||
                        key->isKind(ParseNodeKind::String))
             {
-                pnatom.set(key->pn_atom);
+                pnatom.set(key->as<NameNode>().atom());
             } else {
                 // Otherwise this is a computed property name which needs to
                 // be added dynamically.
                 obj.set(nullptr);
                 continue;
             }
         }
 
@@ -4129,50 +4160,48 @@ BytecodeEmitter::emitDestructuringObjRes
             return false;
         }
     }
 
     return true;
 }
 
 bool
-BytecodeEmitter::emitDestructuringOps(ParseNode* pattern, DestructuringFlavor flav)
+BytecodeEmitter::emitDestructuringOps(ListNode* pattern, DestructuringFlavor flav)
 {
     if (pattern->isKind(ParseNodeKind::Array)) {
         return emitDestructuringOpsArray(pattern, flav);
     }
     return emitDestructuringOpsObject(pattern, flav);
 }
 
 bool
-BytecodeEmitter::emitTemplateString(ParseNode* pn)
-{
-    MOZ_ASSERT(pn->isArity(PN_LIST));
-
+BytecodeEmitter::emitTemplateString(ListNode* templateString)
+{
     bool pushedString = false;
 
-    for (ParseNode* pn2 = pn->pn_head; pn2 != NULL; pn2 = pn2->pn_next) {
-        bool isString = (pn2->getKind() == ParseNodeKind::String ||
-                         pn2->getKind() == ParseNodeKind::TemplateString);
+    for (ParseNode* item : templateString->contents()) {
+        bool isString = (item->getKind() == ParseNodeKind::String ||
+                         item->getKind() == ParseNodeKind::TemplateString);
 
         // Skip empty strings. These are very common: a template string like
         // `${a}${b}` has three empty strings and without this optimization
         // we'd emit four JSOP_ADD operations instead of just one.
-        if (isString && pn2->pn_atom->empty()) {
+        if (isString && item->as<NameNode>().atom()->empty()) {
             continue;
         }
 
         if (!isString) {
             // We update source notes before emitting the expression
-            if (!updateSourceCoordNotes(pn2->pn_pos.begin)) {
-                return false;
-            }
-        }
-
-        if (!emitTree(pn2)) {
+            if (!updateSourceCoordNotes(item->pn_pos.begin)) {
+                return false;
+            }
+        }
+
+        if (!emitTree(item)) {
             return false;
         }
 
         if (!isString) {
             // We need to convert the expression to a string
             if (!emit1(JSOP_TOSTRING)) {
                 return false;
             }
@@ -4195,48 +4224,46 @@ BytecodeEmitter::emitTemplateString(Pars
             return false;
         }
     }
 
     return true;
 }
 
 bool
-BytecodeEmitter::emitDeclarationList(ParseNode* declList)
-{
-    MOZ_ASSERT(declList->isArity(PN_LIST));
+BytecodeEmitter::emitDeclarationList(ListNode* declList)
+{
     MOZ_ASSERT(declList->isOp(JSOP_NOP));
 
-    ParseNode* next;
-    for (ParseNode* decl = declList->pn_head; decl; decl = next) {
+    for (ParseNode* decl : declList->contents()) {
         if (!updateSourceCoordNotes(decl->pn_pos.begin)) {
             return false;
         }
-        next = decl->pn_next;
 
         if (decl->isKind(ParseNodeKind::Assign)) {
             MOZ_ASSERT(decl->isOp(JSOP_NOP));
 
-            ParseNode* pattern = decl->pn_left;
+            AssignmentNode* assignNode = &decl->as<AssignmentNode>();
+            ListNode* pattern = &assignNode->left()->as<ListNode>();
             MOZ_ASSERT(pattern->isKind(ParseNodeKind::Array) ||
                        pattern->isKind(ParseNodeKind::Object));
 
-            if (!emitTree(decl->pn_right)) {
+            if (!emitTree(assignNode->right())) {
                 return false;
             }
 
             if (!emitDestructuringOps(pattern, DestructuringDeclaration)) {
                 return false;
             }
 
             if (!emit1(JSOP_POP)) {
                 return false;
             }
         } else {
-            if (!emitSingleDeclaration(declList, decl, decl->expr())) {
+            if (!emitSingleDeclaration(declList, decl, decl->as<NameNode>().initializer())) {
                 return false;
             }
         }
     }
     return true;
 }
 
 bool
@@ -4360,42 +4387,44 @@ BytecodeEmitter::emitAssignment(ParseNod
         return emitSetName(lhs, emitRhs);
     }
 
     // Deal with non-name assignments.
     uint32_t atomIndex = (uint32_t) -1;
     uint8_t offset = 1;
 
     switch (lhs->getKind()) {
-      case ParseNodeKind::Dot:
-        if (lhs->as<PropertyAccess>().isSuper()) {
-            if (!emitSuperPropLHS(&lhs->as<PropertyAccess>().expression())) {
+      case ParseNodeKind::Dot: {
+        PropertyAccess* prop = &lhs->as<PropertyAccess>();
+        if (prop->isSuper()) {
+            if (!emitSuperPropLHS(&prop->expression().as<UnaryNode>())) {
                 return false;
             }
             offset += 2;
         } else {
-            if (!emitTree(lhs->pn_left)) {
+            if (!emitTree(&prop->expression())) {
                 return false;
             }
             offset += 1;
         }
-        if (!makeAtomIndex(lhs->pn_right->pn_atom, &atomIndex)) {
+        if (!makeAtomIndex(prop->key().atom(), &atomIndex)) {
             return false;
         }
         break;
+      }
       case ParseNodeKind::Elem: {
-        MOZ_ASSERT(lhs->isArity(PN_BINARY));
+        PropertyByValue* elem = &lhs->as<PropertyByValue>();
         EmitElemOption opt = op == JSOP_NOP ? EmitElemOption::Get : EmitElemOption::CompoundAssign;
-        if (lhs->as<PropertyByValue>().isSuper()) {
-            if (!emitSuperElemOperands(lhs, opt)) {
+        if (elem->isSuper()) {
+            if (!emitSuperElemOperands(elem, opt)) {
                 return false;
             }
             offset += 3;
         } else {
-            if (!emitElemOperands(lhs, opt)) {
+            if (!emitElemOperands(elem, opt)) {
                 return false;
             }
             offset += 2;
         }
         break;
       }
       case ParseNodeKind::Array:
       case ParseNodeKind::Object:
@@ -4420,36 +4449,38 @@ BytecodeEmitter::emitAssignment(ParseNod
         MOZ_ASSERT(0);
     }
 
     if (op != JSOP_NOP) {
         MOZ_ASSERT(rhs);
         switch (lhs->getKind()) {
           case ParseNodeKind::Dot: {
             JSOp getOp;
-            if (lhs->as<PropertyAccess>().isSuper()) {
+            PropertyAccess* prop = &lhs->as<PropertyAccess>();
+            if (prop->isSuper()) {
                 if (!emit1(JSOP_DUP2)) {
                     return false;
                 }
                 getOp = JSOP_GETPROP_SUPER;
             } else {
                 if (!emit1(JSOP_DUP)) {
                     return false;
                 }
-                bool isLength = (lhs->pn_right->pn_atom == cx->names().length);
+                bool isLength = prop->key().atom() == cx->names().length;
                 getOp = isLength ? JSOP_LENGTH : JSOP_GETPROP;
             }
             if (!emitIndex32(getOp, atomIndex)) {
                 return false;
             }
             break;
           }
           case ParseNodeKind::Elem: {
             JSOp elemOp;
-            if (lhs->as<PropertyByValue>().isSuper()) {
+            PropertyByValue* elem = &lhs->as<PropertyByValue>();
+            if (elem->isSuper()) {
                 if (!emitDupAt(2)) {
                     return false;
                 }
                 if (!emitDupAt(2)) {
                     return false;
                 }
                 if (!emitDupAt(2)) {
                     return false;
@@ -4512,17 +4543,17 @@ BytecodeEmitter::emitAssignment(ParseNod
                        sc->strict() ? JSOP_STRICTSETELEM : JSOP_SETELEM;
         if (!emit1(setOp)) {
             return false;
         }
         break;
       }
       case ParseNodeKind::Array:
       case ParseNodeKind::Object:
-        if (!emitDestructuringOps(lhs, DestructuringAssignment)) {
+        if (!emitDestructuringOps(&lhs->as<ListNode>(), DestructuringAssignment)) {
             return false;
         }
         break;
       default:
         MOZ_ASSERT(0);
     }
     return true;
 }
@@ -4531,21 +4562,21 @@ bool
 ParseNode::getConstantValue(JSContext* cx, AllowConstantObjects allowObjects,
                             MutableHandleValue vp, Value* compare, size_t ncompare,
                             NewObjectKind newKind)
 {
     MOZ_ASSERT(newKind == TenuredObject || newKind == SingletonObject);
 
     switch (getKind()) {
       case ParseNodeKind::Number:
-        vp.setNumber(pn_dval);
+        vp.setNumber(as<NumericLiteral>().value());
         return true;
       case ParseNodeKind::TemplateString:
       case ParseNodeKind::String:
-        vp.setString(pn_atom);
+        vp.setString(as<NameNode>().atom());
         return true;
       case ParseNodeKind::True:
         vp.setBoolean(true);
         return true;
       case ParseNodeKind::False:
         vp.setBoolean(false);
         return true;
       case ParseNodeKind::Null:
@@ -4566,22 +4597,22 @@ ParseNode::getConstantValue(JSContext* c
 
         ObjectGroup::NewArrayKind arrayKind = ObjectGroup::NewArrayKind::Normal;
         if (allowObjects == ForCopyOnWriteArray) {
             arrayKind = ObjectGroup::NewArrayKind::CopyOnWrite;
             allowObjects = DontAllowObjects;
         }
 
         if (getKind() == ParseNodeKind::CallSiteObj) {
-            count = pn_count - 1;
-            pn = pn_head->pn_next;
+            count = as<CallSiteNode>().count() - 1;
+            pn = as<CallSiteNode>().head()->pn_next;
         } else {
-            MOZ_ASSERT(!(pn_xflags & PNX_NONCONST));
-            count = pn_count;
-            pn = pn_head;
+            MOZ_ASSERT(!as<ListNode>().hasNonConstInitializer());
+            count = as<ListNode>().count();
+            pn = as<ListNode>().head();
         }
 
         AutoValueVector values(cx);
         if (!values.appendN(MagicValue(JS_ELEMENTS_HOLE), count)) {
             return false;
         }
         size_t idx;
         for (idx = 0; pn; idx++, pn = pn->pn_next) {
@@ -4604,44 +4635,46 @@ ParseNode::getConstantValue(JSContext* c
         if (!CombineArrayElementTypes(cx, obj, compare, ncompare)) {
             return false;
         }
 
         vp.setObject(*obj);
         return true;
       }
       case ParseNodeKind::Object: {
-        MOZ_ASSERT(!(pn_xflags & PNX_NONCONST));
+        MOZ_ASSERT(!as<ListNode>().hasNonConstInitializer());
 
         if (allowObjects == DontAllowObjects) {
             vp.setMagic(JS_GENERIC_MAGIC);
             return true;
         }
         MOZ_ASSERT(allowObjects == AllowObjects);
 
         Rooted<IdValueVector> properties(cx, IdValueVector(cx));
 
         RootedValue value(cx), idvalue(cx);
-        for (ParseNode* pn = pn_head; pn; pn = pn->pn_next) {
-            if (!pn->pn_right->getConstantValue(cx, allowObjects, &value)) {
+        for (ParseNode* item : as<ListNode>().contents()) {
+            // MutateProto and Spread, both are unary, cannot appear here.
+            BinaryNode* prop = &item->as<BinaryNode>();
+            if (!prop->right()->getConstantValue(cx, allowObjects, &value)) {
                 return false;
             }
             if (value.isMagic(JS_GENERIC_MAGIC)) {
                 vp.setMagic(JS_GENERIC_MAGIC);
                 return true;
             }
 
-            ParseNode* pnid = pn->pn_left;
-            if (pnid->isKind(ParseNodeKind::Number)) {
-                idvalue = NumberValue(pnid->pn_dval);
+            ParseNode* key = prop->left();
+            if (key->isKind(ParseNodeKind::Number)) {
+                idvalue = NumberValue(key->as<NumericLiteral>().value());
             } else {
-                MOZ_ASSERT(pnid->isKind(ParseNodeKind::ObjectPropertyName) ||
-                           pnid->isKind(ParseNodeKind::String));
-                MOZ_ASSERT(pnid->pn_atom != cx->names().proto);
-                idvalue = StringValue(pnid->pn_atom);
+                MOZ_ASSERT(key->isKind(ParseNodeKind::ObjectPropertyName) ||
+                           key->isKind(ParseNodeKind::String));
+                MOZ_ASSERT(key->as<NameNode>().atom() != cx->names().proto);
+                idvalue = StringValue(key->as<NameNode>().atom());
             }
 
             RootedId id(cx);
             if (!ValueToId<CanGC>(cx, idvalue, &id)) {
                 return false;
             }
 
             if (!properties.append(IdValuePair(id, value))) {
@@ -4685,31 +4718,31 @@ BytecodeEmitter::emitSingletonInitialise
     if (!objbox) {
         return false;
     }
 
     return emitObjectOp(objbox, JSOP_OBJECT);
 }
 
 bool
-BytecodeEmitter::emitCallSiteObject(ParseNode* pn)
+BytecodeEmitter::emitCallSiteObject(CallSiteNode* callSiteObj)
 {
     RootedValue value(cx);
-    if (!pn->getConstantValue(cx, ParseNode::AllowObjects, &value)) {
+    if (!callSiteObj->getConstantValue(cx, ParseNode::AllowObjects, &value)) {
         return false;
     }
 
     MOZ_ASSERT(value.isObject());
 
     ObjectBox* objbox1 = parser->newObjectBox(&value.toObject());
     if (!objbox1) {
         return false;
     }
 
-    if (!pn->as<CallSiteNode>().getRawArrayValue(cx, &value)) {
+    if (!callSiteObj->getRawArrayValue(cx, &value)) {
         return false;
     }
 
     MOZ_ASSERT(value.isObject());
 
     ObjectBox* objbox2 = parser->newObjectBox(&value.toObject());
     if (!objbox2) {
         return false;
@@ -4726,69 +4759,69 @@ class EmitLevelManager
   public:
     explicit EmitLevelManager(BytecodeEmitter* bce) : bce(bce) { bce->emitLevel++; }
     ~EmitLevelManager() { bce->emitLevel--; }
 };
 
 } /* anonymous namespace */
 
 bool
-BytecodeEmitter::emitCatch(ParseNode* pn)
+BytecodeEmitter::emitCatch(BinaryNode* catchClause)
 {
     // We must be nested under a try-finally statement.
     MOZ_ASSERT(innermostNestableControl->is<TryFinallyControl>());
 
     /* Pick up the pending exception and bind it to the catch variable. */
     if (!emit1(JSOP_EXCEPTION)) {
         return false;
     }
 
-    ParseNode* pn2 = pn->pn_left;
-    if (!pn2) {
+    ParseNode* param = catchClause->left();
+    if (!param) {
         // Catch parameter was omitted; just discard the exception.
         if (!emit1(JSOP_POP)) {
             return false;
         }
     } else {
-        switch (pn2->getKind()) {
+        switch (param->getKind()) {
           case ParseNodeKind::Array:
           case ParseNodeKind::Object:
-            if (!emitDestructuringOps(pn2, DestructuringDeclaration)) {
+            if (!emitDestructuringOps(&param->as<ListNode>(), DestructuringDeclaration)) {
                 return false;
             }
             if (!emit1(JSOP_POP)) {
                 return false;
             }
             break;
 
           case ParseNodeKind::Name:
-            if (!emitLexicalInitialization(pn2)) {
+            if (!emitLexicalInitialization(param)) {
                 return false;
             }
             if (!emit1(JSOP_POP)) {
                 return false;
             }
             break;
 
           default:
             MOZ_ASSERT(0);
         }
     }
 
     /* Emit the catch body. */
-    return emitTree(pn->pn_right);
+    return emitTree(catchClause->right());
 }
 
 // Using MOZ_NEVER_INLINE in here is a workaround for llvm.org/pr14047. See the
 // comment on EmitSwitch.
 MOZ_NEVER_INLINE bool
-BytecodeEmitter::emitTry(ParseNode* pn)
-{
-    ParseNode* catchScope = pn->pn_kid2;
-    ParseNode* finallyNode = pn->pn_kid3;
+BytecodeEmitter::emitTry(TryNode* tryNode)
+{
+    LexicalScopeNode* catchScope = tryNode->catchScope();
+    ParseNode* finallyNode = tryNode->finallyBlock();
 
     TryEmitter::Kind kind;
     if (catchScope) {
         if (finallyNode) {
             kind = TryEmitter::Kind::TryCatchFinally;
         } else {
             kind = TryEmitter::Kind::TryCatch;
         }
@@ -4797,17 +4830,17 @@ BytecodeEmitter::emitTry(ParseNode* pn)
         kind = TryEmitter::Kind::TryFinally;
     }
     TryEmitter tryCatch(this, kind, TryEmitter::ControlKind::Syntactic);
 
     if (!tryCatch.emitTry()) {
         return false;
     }
 
-    if (!emitTree(pn->pn_kid1)) {
+    if (!emitTree(tryNode->body())) {
         return false;
     }
 
     // If this try has a catch block, emit it.
     if (catchScope) {
         // The emitted code for a catch block looks like:
         //
         // [pushlexicalenv]             only if any local aliased
@@ -4819,17 +4852,16 @@ BytecodeEmitter::emitTry(ParseNode* pn)
         // if there is a finally block:
         //   gosub <finally>
         //   goto <after finally>
         if (!tryCatch.emitCatch()) {
             return false;
         }
 
         // Emit the lexical scope and catch body.
-        MOZ_ASSERT(catchScope->isKind(ParseNodeKind::LexicalScope));
         if (!emitTree(catchScope)) {
             return false;
         }
     }
 
     // Emit the finally handler, if there is one.
     if (finallyNode) {
         if (!tryCatch.emitFinally(Some(finallyNode->pn_pos.begin))) {
@@ -4844,51 +4876,51 @@ BytecodeEmitter::emitTry(ParseNode* pn)
     if (!tryCatch.emitEnd()) {
         return false;
     }
 
     return true;
 }
 
 bool
-BytecodeEmitter::emitIf(ParseNode* pn)
+BytecodeEmitter::emitIf(TernaryNode* ifNode)
 {
     IfEmitter ifThenElse(this);
 
-    if (!ifThenElse.emitIf(Some(pn->pn_pos.begin))) {
+    if (!ifThenElse.emitIf(Some(ifNode->pn_pos.begin))) {
         return false;
     }
 
   if_again:
     /* Emit code for the condition before pushing stmtInfo. */
-    if (!emitTree(pn->pn_kid1)) {
-        return false;
-    }
-
-    ParseNode* elseNode = pn->pn_kid3;
+    if (!emitTree(ifNode->kid1())) {
+        return false;
+    }
+
+    ParseNode* elseNode = ifNode->kid3();
     if (elseNode) {
         if (!ifThenElse.emitThenElse()) {
             return false;
         }
     } else {
         if (!ifThenElse.emitThen()) {
             return false;
         }
     }
 
     /* Emit code for the then part. */
-    if (!emitTree(pn->pn_kid2)) {
+    if (!emitTree(ifNode->kid2())) {
         return false;
     }
 
     if (elseNode) {
         if (elseNode->isKind(ParseNodeKind::If)) {
-            pn = elseNode;
-
-            if (!ifThenElse.emitElseIf(Some(pn->pn_pos.begin))) {
+            ifNode = &elseNode->as<TernaryNode>();
+
+            if (!ifThenElse.emitElseIf(Some(ifNode->pn_pos.begin))) {
                 return false;
             }
 
             goto if_again;
         }
 
         if (!ifThenElse.emitElse()) {
             return false;
@@ -4903,127 +4935,130 @@ BytecodeEmitter::emitIf(ParseNode* pn)
     if (!ifThenElse.emitEnd()) {
         return false;
     }
 
     return true;
 }
 
 bool
-BytecodeEmitter::emitHoistedFunctionsInList(ParseNode* list)
-{
-    MOZ_ASSERT(list->pn_xflags & PNX_FUNCDEFS);
-
-    for (ParseNode* pn = list->pn_head; pn; pn = pn->pn_next) {
-        ParseNode* maybeFun = pn;
+BytecodeEmitter::emitHoistedFunctionsInList(ListNode* stmtList)
+{
+    MOZ_ASSERT(stmtList->hasTopLevelFunctionDeclarations());
+
+    for (ParseNode* stmt : stmtList->contents()) {
+        ParseNode* maybeFun = stmt;
 
         if (!sc->strict()) {
             while (maybeFun->isKind(ParseNodeKind::Label)) {
                 maybeFun = maybeFun->as<LabeledStatement>().statement();
             }
         }
 
-        if (maybeFun->isKind(ParseNodeKind::Function) && maybeFun->functionIsHoisted()) {
+        if (maybeFun->isKind(ParseNodeKind::Function) &&
+            maybeFun->as<CodeNode>().functionIsHoisted())
+        {
             if (!emitTree(maybeFun)) {
                 return false;
             }
         }
     }
 
     return true;
 }
 
 bool
 BytecodeEmitter::emitLexicalScopeBody(ParseNode* body, EmitLineNumberNote emitLineNote)
 {
-    if (body->isKind(ParseNodeKind::StatementList) && body->pn_xflags & PNX_FUNCDEFS) {
+    if (body->isKind(ParseNodeKind::StatementList) &&
+        body->as<ListNode>().hasTopLevelFunctionDeclarations())
+    {
         // This block contains function statements whose definitions are
         // hoisted to the top of the block. Emit these as a separate pass
         // before the rest of the block.
-        if (!emitHoistedFunctionsInList(body)) {
+        if (!emitHoistedFunctionsInList(&body->as<ListNode>())) {
             return false;
         }
     }
 
     // Line notes were updated by emitLexicalScope.
     return emitTree(body, ValueUsage::WantValue, emitLineNote);
 }
 
 // Using MOZ_NEVER_INLINE in here is a workaround for llvm.org/pr14047. See
 // the comment on emitSwitch.
 MOZ_NEVER_INLINE bool
-BytecodeEmitter::emitLexicalScope(ParseNode* pn)
-{
-    MOZ_ASSERT(pn->isKind(ParseNodeKind::LexicalScope));
-
+BytecodeEmitter::emitLexicalScope(LexicalScopeNode* lexicalScope)
+{
     TDZCheckCache tdzCache(this);
 
-    ParseNode* body = pn->scopeBody();
-    if (pn->isEmptyScope()) {
+    ParseNode* body = lexicalScope->scopeBody();
+    if (lexicalScope->isEmptyScope()) {
         return emitLexicalScopeBody(body);
     }
 
     // We are about to emit some bytecode for what the spec calls "declaration
     // instantiation". Assign these instructions to the opening `{` of the
     // block. (Using the location of each declaration we're instantiating is
     // too weird when stepping in the debugger.)
     if (!ParseNodeRequiresSpecialLineNumberNotes(body)) {
-        if (!updateSourceCoordNotes(pn->pn_pos.begin)) {
+        if (!updateSourceCoordNotes(lexicalScope->pn_pos.begin)) {
             return false;
         }
     }
 
     EmitterScope emitterScope(this);
     ScopeKind kind;
     if (body->isKind(ParseNodeKind::Catch)) {
-        kind = (!body->pn_left || body->pn_left->isKind(ParseNodeKind::Name))
+        BinaryNode* catchNode = &body->as<BinaryNode>();
+        kind = (!catchNode->left() || catchNode->left()->isKind(ParseNodeKind::Name))
                ? ScopeKind::SimpleCatch
                : ScopeKind::Catch;
     } else {
         kind = ScopeKind::Lexical;
     }
 
-    if (!emitterScope.enterLexical(this, kind, pn->scopeBindings())) {
+    if (!emitterScope.enterLexical(this, kind, lexicalScope->scopeBindings())) {
         return false;
     }
 
     if (body->isKind(ParseNodeKind::For)) {
         // for loops need to emit {FRESHEN,RECREATE}LEXICALENV if there are
         // lexical declarations in the head. Signal this by passing a
         // non-nullptr lexical scope.
-        if (!emitFor(body, &emitterScope)) {
+        if (!emitFor(&body->as<ForNode>(), &emitterScope)) {
             return false;
         }
     } else {
         if (!emitLexicalScopeBody(body, SUPPRESS_LINENOTE)) {
             return false;
         }
     }
 
     return emitterScope.leave(this);
 }
 
 bool
-BytecodeEmitter::emitWith(ParseNode* pn)
+BytecodeEmitter::emitWith(BinaryNode* withNode)
 {
     // Ensure that the column of the 'with' is set properly.
-    if (!updateSourceCoordNotes(pn->pn_pos.begin)) {
-        return false;
-    }
-
-    if (!emitTree(pn->pn_left)) {
+    if (!updateSourceCoordNotes(withNode->pn_pos.begin)) {
+        return false;
+    }
+
+    if (!emitTree(withNode->left())) {
         return false;