Merge inbound to m-c. a=merge
authorRyan VanderMeulen <ryanvm@gmail.com>
Wed, 10 Oct 2018 12:30:22 -0400
changeset 496253 1a9cbc785296806682eb165e0307b05b8f45b7e7
parent 496188 0f1d5395f8013b4dafcd4c849a8cccdcf3c26587 (current diff)
parent 496252 641b4a3789232b184c76a64b6e698c79e65f0b3d (diff)
child 496254 20773596530b6b6a6fb405a7bd7110a990f8db8f
child 496288 6eb2ec7487c13cbfc024ed3ee2ba857ac87e1e89
child 496301 3c2fc3b5c03adc291583cb49e346e7580f283c0f
push id9984
push userffxbld-merge
push dateMon, 15 Oct 2018 21:07:35 +0000
treeherdermozilla-beta@183d27ea8570 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone64.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge inbound to m-c. a=merge
build/virtualenv_packages.txt
devtools/client/aboutdebugging-new/src/modules/usb-runtimes.js
devtools/client/webide/content/addons.js
devtools/client/webide/modules/runtimes.js
devtools/shared/adb/adb-scanner.js
devtools/shared/adb/moz.build
devtools/shared/adb/test/test_adb.js
devtools/shared/adb/test/xpcshell-head.js
devtools/shared/adb/test/xpcshell.ini
dom/canvas/WebGLExtensionCompressedTextureATC.cpp
dom/canvas/test/webgl-mochitest/ensure-exts/test_WEBGL_compressed_texture_es3.html
python/mozbuild/mozbuild/vendor_python.py
testing/talos/talos/startup_test/tresize/addon/bootstrap.js
testing/talos/talos/startup_test/tresize/addon/chrome.manifest
testing/talos/talos/startup_test/tresize/addon/content/Profiler.js
testing/talos/talos/startup_test/tresize/addon/content/framescript.js
testing/talos/talos/startup_test/tresize/addon/content/initialize_browser.js
testing/talos/talos/startup_test/tresize/addon/content/tresize-test.html
testing/talos/talos/startup_test/tresize/addon/content/tresize.js
testing/talos/talos/startup_test/tresize/addon/install.rdf
toolkit/themes/shared/in-content/check.svg
--- a/devtools/client/aboutdebugging-new/src/modules/usb-runtimes.js
+++ b/devtools/client/aboutdebugging-new/src/modules/usb-runtimes.js
@@ -1,43 +1,39 @@
 /* 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 { ADBScanner } = require("devtools/shared/adb/adb-scanner");
-loader.lazyRequireGetter(this, "adbAddon", "devtools/shared/adb/adb-addon", true);
-loader.lazyRequireGetter(this, "ADB_ADDON_STATES", "devtools/shared/adb/adb-addon", true);
+loader.lazyGetter(this, "adbScanner", () => {
+  const { AddonAwareADBScanner } = require("devtools/shared/adb/addon-aware-adb-scanner");
+  return new AddonAwareADBScanner();
+});
 
 /**
  * This module provides a collection of helper methods to detect USB runtimes whom Firefox
  * is running on.
  */
 function addUSBRuntimesObserver(listener) {
-  ADBScanner.on("runtime-list-updated", listener);
+  adbScanner.on("runtime-list-updated", listener);
 }
 exports.addUSBRuntimesObserver = addUSBRuntimesObserver;
 
 function disableUSBRuntimes() {
-  ADBScanner.disable();
+  adbScanner.disable();
 }
 exports.disableUSBRuntimes = disableUSBRuntimes;
 
 async function enableUSBRuntimes() {
-  if (adbAddon.status !== ADB_ADDON_STATES.INSTALLED) {
-    console.error("ADB extension is not installed");
-    return;
-  }
-
-  ADBScanner.enable();
+  adbScanner.enable();
 }
 exports.enableUSBRuntimes = enableUSBRuntimes;
 
 function getUSBRuntimes() {
-  return ADBScanner.listRuntimes();
+  return adbScanner.listRuntimes();
 }
 exports.getUSBRuntimes = getUSBRuntimes;
 
 function removeUSBRuntimesObserver(listener) {
-  ADBScanner.off("runtime-list-updated", listener);
+  adbScanner.off("runtime-list-updated", listener);
 }
 exports.removeUSBRuntimesObserver = removeUSBRuntimesObserver;
--- a/devtools/client/webide/content/addons.js
+++ b/devtools/client/webide/content/addons.js
@@ -3,21 +3,18 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 const {loader, require} = ChromeUtils.import("resource://devtools/shared/Loader.jsm", {});
 
 const Services = require("Services");
 const Strings = Services.strings.createBundle("chrome://devtools/locale/webide.properties");
 
 const {gDevTools} = require("devtools/client/framework/devtools");
-const {ADBScanner} = require("devtools/shared/adb/adb-scanner");
-const {RuntimeScanners} = require("devtools/client/webide/modules/runtimes");
 
 loader.lazyRequireGetter(this, "adbAddon", "devtools/shared/adb/adb-addon", true);
-loader.lazyRequireGetter(this, "ADB_ADDON_STATES", "devtools/shared/adb/adb-addon", true);
 
 window.addEventListener("load", function() {
   document.querySelector("#aboutaddons").onclick = function() {
     const browserWin = Services.wm.getMostRecentWindow(gDevTools.chromeWindowType);
     if (browserWin && browserWin.BrowserOpenAddonsMgr) {
       browserWin.BrowserOpenAddonsMgr("addons://list/extension");
     }
   };
@@ -29,21 +26,16 @@ function CloseUI() {
   window.parent.UI.openProject();
 }
 
 function BuildUI() {
   function onAddonUpdate(arg) {
     progress.removeAttribute("value");
     li.setAttribute("status", adbAddon.status);
     status.textContent = Strings.GetStringFromName("addons_status_" + adbAddon.status);
-    if (adbAddon.status == ADB_ADDON_STATES.INSTALLED) {
-      RuntimeScanners.add(ADBScanner);
-    } else if (adbAddon.status == ADB_ADDON_STATES.UNINSTALLED) {
-      RuntimeScanners.remove(ADBScanner);
-    }
   }
 
   function onAddonFailure(arg) {
     window.parent.UI.reportError("error_operationFail", arg);
   }
 
   function onAddonProgress(arg) {
     if (arg == -1) {
--- a/devtools/client/webide/modules/runtimes.js
+++ b/devtools/client/webide/modules/runtimes.js
@@ -5,16 +5,22 @@
 "use strict";
 
 const Services = require("Services");
 const {DebuggerServer} = require("devtools/server/main");
 const discovery = require("devtools/shared/discovery/discovery");
 const EventEmitter = require("devtools/shared/event-emitter");
 const {RuntimeTypes} = require("devtools/client/webide/modules/runtime-types");
 const promise = require("promise");
+
+loader.lazyGetter(this, "adbScanner", () => {
+  const { AddonAwareADBScanner } = require("devtools/shared/adb/addon-aware-adb-scanner");
+  return new AddonAwareADBScanner();
+});
+
 loader.lazyRequireGetter(this, "AuthenticationResult",
   "devtools/shared/security/auth", true);
 loader.lazyRequireGetter(this, "DevToolsUtils",
   "devtools/shared/DevToolsUtils");
 
 const Strings = Services.strings.createBundle("chrome://devtools/locale/webide.properties");
 
 /**
@@ -187,16 +193,20 @@ var RuntimeScanners = {
 };
 
 EventEmitter.decorate(RuntimeScanners);
 
 exports.RuntimeScanners = RuntimeScanners;
 
 /* SCANNERS */
 
+// The adb-scanner will automatically start and stop when the ADB extension is installed
+// and uninstalled, so the scanner itself can always be used.
+RuntimeScanners.add(adbScanner);
+
 var WiFiScanner = {
 
   _runtimes: [],
 
   init() {
     this.updateRegistration();
     Services.prefs.addObserver(this.ALLOWED_PREF, this);
   },
--- a/devtools/shared/adb/adb-scanner.js
+++ b/devtools/shared/adb/adb-scanner.js
@@ -7,57 +7,60 @@
 const EventEmitter = require("devtools/shared/event-emitter");
 const { Devices } = require("devtools/shared/apps/Devices.jsm");
 const { dumpn } = require("devtools/shared/DevToolsUtils");
 const { RuntimeTypes } =
   require("devtools/client/webide/modules/runtime-types");
 const { ADB } = require("devtools/shared/adb/adb");
 loader.lazyRequireGetter(this, "Device", "devtools/shared/adb/adb-device");
 
-const ADBScanner = {
+class ADBScanner extends EventEmitter {
+  constructor() {
+    super();
+    this._runtimes = [];
 
-  _runtimes: [],
+    this._onDeviceConnected = this._onDeviceConnected.bind(this);
+    this._onDeviceDisconnected = this._onDeviceDisconnected.bind(this);
+    this._updateRuntimes = this._updateRuntimes.bind(this);
+  }
 
   enable() {
-    this._onDeviceConnected = this._onDeviceConnected.bind(this);
-    this._onDeviceDisconnected = this._onDeviceDisconnected.bind(this);
     EventEmitter.on(ADB, "device-connected", this._onDeviceConnected);
     EventEmitter.on(ADB, "device-disconnected", this._onDeviceDisconnected);
 
-    this._updateRuntimes = this._updateRuntimes.bind(this);
     Devices.on("register", this._updateRuntimes);
     Devices.on("unregister", this._updateRuntimes);
     Devices.on("addon-status-updated", this._updateRuntimes);
 
     ADB.start().then(() => {
       ADB.trackDevices();
     });
     this._updateRuntimes();
-  },
+  }
 
   disable() {
     EventEmitter.off(ADB, "device-connected", this._onDeviceConnected);
     EventEmitter.off(ADB, "device-disconnected", this._onDeviceDisconnected);
     Devices.off("register", this._updateRuntimes);
     Devices.off("unregister", this._updateRuntimes);
     Devices.off("addon-status-updated", this._updateRuntimes);
-  },
+  }
 
   _emitUpdated() {
     this.emit("runtime-list-updated");
-  },
+  }
 
   _onDeviceConnected(deviceId) {
     const device = new Device(deviceId);
     Devices.register(deviceId, device);
-  },
+  }
 
   _onDeviceDisconnected(deviceId) {
     Devices.unregister(deviceId);
-  },
+  }
 
   _updateRuntimes() {
     if (this._updatingPromise) {
       return this._updatingPromise;
     }
     this._runtimes = [];
     const promises = [];
     for (const id of Devices.available()) {
@@ -67,36 +70,33 @@ const ADBScanner = {
     this._updatingPromise = Promise.all(promises);
     this._updatingPromise.then(() => {
       this._emitUpdated();
       this._updatingPromise = null;
     }, () => {
       this._updatingPromise = null;
     });
     return this._updatingPromise;
-  },
+  }
 
-  _detectRuntimes: async function(device) {
+  async _detectRuntimes(device) {
     const model = await device.getModel();
     const detectedRuntimes =
       await FirefoxOnAndroidRuntime.detect(device, model);
     this._runtimes.push(...detectedRuntimes);
-  },
+  }
 
   scan() {
     return this._updateRuntimes();
-  },
+  }
 
   listRuntimes() {
     return this._runtimes;
   }
-
-};
-
-EventEmitter.decorate(ADBScanner);
+}
 
 function Runtime(device, model, socketPath) {
   this.device = device;
   this._model = model;
   this._socketPath = socketPath;
 }
 
 Runtime.prototype = {
new file mode 100644
--- /dev/null
+++ b/devtools/shared/adb/addon-aware-adb-scanner.js
@@ -0,0 +1,98 @@
+/* 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 EventEmitter = require("devtools/shared/event-emitter");
+
+loader.lazyRequireGetter(this, "adbAddon", "devtools/shared/adb/adb-addon", true);
+loader.lazyRequireGetter(this, "ADBScanner", "devtools/shared/adb/adb-scanner", true);
+
+/**
+ * The AddonAwareADBScanner decorates an instance of ADBScanner. It will wait until the
+ * ADB addon is installed to enable() the real scanner, and will automatically disable
+ * it if the addon is uninstalled.
+ *
+ * It implements the following public API of ADBScanner:
+ * - enable
+ * - disable
+ * - scan
+ * - listRuntimes
+ * - event "runtime-list-updated"
+ */
+class AddonAwareADBScanner extends EventEmitter {
+  /**
+   * Parameters are provided only to allow tests to replace actual implementations with
+   * mocks.
+   *
+   * @param {ADBScanner} scanner
+   *        Only provided in tests for mocks
+   * @param {ADBAddon} addon
+   *        Only provided in tests for mocks
+   */
+  constructor(scanner = new ADBScanner(), addon = adbAddon) {
+    super();
+
+    this._onScannerListUpdated = this._onScannerListUpdated.bind(this);
+    this._onAddonUpdate = this._onAddonUpdate.bind(this);
+
+    this._scanner = scanner;
+    this._scanner.on("runtime-list-updated", this._onScannerListUpdated);
+
+    this._addon = addon;
+  }
+
+  /**
+   * Only forward the enable() call if the addon is installed, because ADBScanner::enable
+   * only works if the addon is installed.
+   */
+  enable() {
+    if (this._addon.status === "installed") {
+      this._scanner.enable();
+    }
+
+    // Remove any previous listener, to make sure we only add one listener if enable() is
+    // called several times.
+    this._addon.off("update", this._onAddonUpdate);
+
+    this._addon.on("update", this._onAddonUpdate);
+  }
+
+  disable() {
+    this._scanner.disable();
+
+    this._addon.off("update", this._onAddonUpdate);
+  }
+
+  /**
+   * Scan for USB devices.
+   *
+   * @return {Promise} Promise that will resolve when the scan is completed.
+   */
+  scan() {
+    return this._scanner.scan();
+  }
+
+  /**
+   * Get the list of currently detected runtimes.
+   *
+   * @return {Array} Array of currently detected runtimes.
+   */
+  listRuntimes() {
+    return this._scanner.listRuntimes();
+  }
+
+  _onAddonUpdate() {
+    if (this._addon.status === "installed") {
+      this._scanner.enable();
+    } else {
+      this._scanner.disable();
+    }
+  }
+
+  _onScannerListUpdated() {
+    this.emit("runtime-list-updated");
+  }
+}
+exports.AddonAwareADBScanner = AddonAwareADBScanner;
--- a/devtools/shared/adb/moz.build
+++ b/devtools/shared/adb/moz.build
@@ -6,14 +6,15 @@ DevToolsModules(
     'adb-addon.js',
     'adb-binary.js',
     'adb-client.js',
     'adb-device.js',
     'adb-running-checker.js',
     'adb-scanner.js',
     'adb-socket.js',
     'adb.js',
+    'addon-aware-adb-scanner.js',
 )
 
 with Files('**'):
     BUG_COMPONENT = ('DevTools', 'about:debugging')
 
 XPCSHELL_TESTS_MANIFESTS += ['test/xpcshell.ini']
--- a/devtools/shared/adb/test/test_adb.js
+++ b/devtools/shared/adb/test/test_adb.js
@@ -1,8 +1,11 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
 "use strict";
 
 const EventEmitter = require("devtools/shared/event-emitter");
 const { ExtensionTestUtils } = ChromeUtils.import("resource://testing-common/ExtensionXPCShellUtils.jsm", {});
 const { NetUtil } = require("resource://gre/modules/NetUtil.jsm");
 const { getFileForBinary } = require("devtools/shared/adb/adb-binary");
 const { check } = require("devtools/shared/adb/adb-running-checker");
 const { ADB } = require("devtools/shared/adb/adb");
new file mode 100644
--- /dev/null
+++ b/devtools/shared/adb/test/test_addon-aware-adb-scanner.js
@@ -0,0 +1,221 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const EventEmitter = require("devtools/shared/event-emitter");
+const { AddonAwareADBScanner } = require("devtools/shared/adb/addon-aware-adb-scanner");
+
+/**
+ * For the scanner mock, we create an object with spies for each of the public methods
+ * used by the AddonAwareADBScanner, and the ability to emit events.
+ */
+function prepareMockScanner() {
+  const mockScanner = {
+    enable: sinon.spy(),
+    disable: sinon.spy(),
+    scan: sinon.spy(),
+    listRuntimes: sinon.spy()
+  };
+  EventEmitter.decorate(mockScanner);
+  return mockScanner;
+}
+
+/**
+ * For the addon mock, we simply need an object that is able to emit events and has a
+ * status.
+ */
+function prepareMockAddon() {
+  const mockAddon = {
+    status: "unknown",
+  };
+  EventEmitter.decorate(mockAddon);
+  return mockAddon;
+}
+
+/**
+ * Prepare all mocks needed for the scanner tests.
+ */
+function prepareMocks() {
+  const mockScanner = prepareMockScanner();
+  const mockAddon = prepareMockAddon();
+  const addonAwareAdbScanner = new AddonAwareADBScanner(mockScanner, mockAddon);
+  return { addonAwareAdbScanner, mockAddon, mockScanner };
+}
+
+/**
+ * This test covers basic usage of enable() on the AddonAwareADBScanner, and checks the
+ * different behaviors based on the addon status.
+ */
+add_task(async function testCallingEnable() {
+  const { mockScanner, mockAddon, addonAwareAdbScanner } = prepareMocks();
+
+  // Check that enable() is not called if the addon is uninstalled
+  mockAddon.status = "uninstalled";
+  addonAwareAdbScanner.enable();
+  ok(mockScanner.enable.notCalled, "enable() was not called");
+  mockScanner.enable.reset();
+
+  // Check that enable() is called if the addon is installed
+  mockAddon.status = "installed";
+  addonAwareAdbScanner.enable();
+  ok(mockScanner.enable.called, "enable() was called");
+  mockScanner.enable.reset();
+});
+
+/**
+ * This test checks that enable()/disable() methods from the internal ADBScanner are
+ * called when the addon is installed or uninstalled.
+ */
+add_task(async function testUpdatingAddonEnablesDisablesScanner() {
+  const { mockScanner, mockAddon, addonAwareAdbScanner } = prepareMocks();
+
+  // Enable the addon aware scanner
+  addonAwareAdbScanner.enable();
+  ok(mockScanner.enable.notCalled, "enable() was not called initially");
+
+  // Check that enable() is called automatically when the addon is installed
+  mockAddon.status = "installed";
+  mockAddon.emit("update");
+  ok(mockScanner.enable.called, "enable() was called when installing the addon");
+  ok(mockScanner.disable.notCalled, "disable() was not called when installing the addon");
+  mockScanner.enable.reset();
+  mockScanner.disable.reset();
+
+  // Check that disabled() is called automatically when the addon is uninstalled
+  mockAddon.status = "uninstalled";
+  mockAddon.emit("update");
+  ok(mockScanner.enable.notCalled, "enable() was not called when uninstalling the addon");
+  ok(mockScanner.disable.called, "disable() was called when uninstalling the addon");
+  mockScanner.enable.reset();
+  mockScanner.disable.reset();
+
+  // Check that enable() is called again when the addon is reinstalled
+  mockAddon.status = "installed";
+  mockAddon.emit("update");
+  ok(mockScanner.enable.called, "enable() was called when installing the addon");
+  ok(mockScanner.disable.notCalled, "disable() was not called when installing the addon");
+  mockScanner.enable.reset();
+  mockScanner.disable.reset();
+});
+
+/**
+ * This test checks that disable() is forwarded from the AddonAwareADBScanner to the real
+ * scanner even if the addon is uninstalled. We might miss the addon uninstall
+ * notification, so it is safer to always proceed with disabling.
+ */
+add_task(async function testScannerIsDisabledWhenMissingAddonUpdate() {
+  const { mockScanner, mockAddon, addonAwareAdbScanner } = prepareMocks();
+
+  // Enable the addon aware scanner
+  mockAddon.status = "installed";
+  addonAwareAdbScanner.enable();
+  ok(mockScanner.enable.called, "enable() was called initially");
+  mockScanner.enable.reset();
+
+  // Uninstall the addon without firing any event
+  mockAddon.status = "uninstalled";
+
+  // Programmatically call disable, check that the scanner's disable is called even though
+  // the addon was uninstalled.
+  addonAwareAdbScanner.disable();
+  ok(mockScanner.disable.called, "disable() was called when uninstalling the addon");
+  mockScanner.disable.reset();
+});
+
+/**
+ * This test checks that when the AddonAwareADBScanner is disabled, then enable/disable
+ * are not called on the inner scanner when the addon status changes.
+ */
+add_task(async function testInnerEnableIsNotCalledIfNotStarted() {
+  const { mockScanner, mockAddon, addonAwareAdbScanner } = prepareMocks();
+
+  // Check that enable() is not called on the inner scanner when the addon is installed
+  // if the AddonAwareADBScanner was not enabled
+  mockAddon.status = "installed";
+  mockAddon.emit("update");
+  ok(mockScanner.enable.notCalled, "enable() was not called");
+
+  // Same for disable() and "uninstall"
+  mockAddon.status = "uninstalled";
+  mockAddon.emit("update");
+  ok(mockScanner.disable.notCalled, "disable() was not called");
+
+  // Enable the addon aware scanner
+  addonAwareAdbScanner.enable();
+  ok(mockScanner.enable.notCalled, "enable() was not called");
+  ok(mockScanner.disable.notCalled, "disable() was not called");
+});
+
+/**
+ * This test checks that when the AddonAwareADBScanner is disabled, installing the addon
+ * no longer enables the internal ADBScanner.
+ */
+add_task(async function testEnableIsNoLongerCalledAfterDisabling() {
+  const { mockScanner, mockAddon, addonAwareAdbScanner } = prepareMocks();
+
+  // Start with the addon installed
+  mockAddon.status = "installed";
+  addonAwareAdbScanner.enable();
+  ok(mockScanner.enable.called, "enable() was called since addon was already installed");
+  mockScanner.enable.reset();
+
+  // Here we call enable again to check that we will not add too many events.
+  // A single call to disable() should stop all listeners, even if we called enable()
+  // several times.
+  addonAwareAdbScanner.enable();
+  ok(mockScanner.enable.called, "enable() was called again");
+  mockScanner.enable.reset();
+
+  // Disable the scanner
+  addonAwareAdbScanner.disable();
+  ok(mockScanner.disable.called, "disable() was called");
+  mockScanner.disable.reset();
+
+  // Emit an addon update event
+  mockAddon.emit("update");
+  ok(mockScanner.enable.notCalled,
+    "enable() is not called since the main scanner is disabled");
+});
+
+/**
+ * Basic check that the "runtime-list-updated" event is forwarded.
+ */
+add_task(async function testListUpdatedEventForwarding() {
+  const { mockScanner, addonAwareAdbScanner } = prepareMocks();
+
+  const spy = sinon.spy();
+  addonAwareAdbScanner.on("runtime-list-updated", spy);
+  mockScanner.emit("runtime-list-updated");
+  ok(spy.called, "The runtime-list-updated event was forwarded from ADBScanner");
+  addonAwareAdbScanner.off("runtime-list-updated", spy);
+});
+
+/**
+ * Basic check that calls to scan() are forwarded.
+ */
+add_task(async function testScanCallForwarding() {
+  const { mockScanner, addonAwareAdbScanner } = prepareMocks();
+
+  ok(mockScanner.scan.notCalled, "ADBScanner scan() is not called initially");
+
+  addonAwareAdbScanner.scan();
+  mockScanner.emit("runtime-list-updated");
+  ok(mockScanner.scan.called, "ADBScanner scan() was called");
+  mockScanner.scan.reset();
+});
+
+/**
+ * Basic check that calls to scan() are forwarded.
+ */
+add_task(async function testListRuntimesCallForwarding() {
+  const { mockScanner, addonAwareAdbScanner } = prepareMocks();
+
+  ok(mockScanner.listRuntimes.notCalled,
+    "ADBScanner listRuntimes() is not called initially");
+
+  addonAwareAdbScanner.listRuntimes();
+  mockScanner.emit("runtime-list-updated");
+  ok(mockScanner.listRuntimes.called, "ADBScanner listRuntimes() was called");
+  mockScanner.scan.reset();
+});
--- a/devtools/shared/adb/test/xpcshell-head.js
+++ b/devtools/shared/adb/test/xpcshell-head.js
@@ -1,8 +1,17 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 "use strict";
 
 /* eslint no-unused-vars: [2, {"vars": "local"}] */
 
 const { require } = ChromeUtils.import("resource://devtools/shared/Loader.jsm", {});
+const Services = require("Services");
+
+// ================================================
+// Load mocking/stubbing library, sinon
+// docs: http://sinonjs.org/releases/v2.3.2/
+ChromeUtils.import("resource://gre/modules/Timer.jsm");
+Services.scriptloader.loadSubScript("resource://testing-common/sinon-2.3.2.js", this);
+/* globals sinon */
+// ================================================
--- a/devtools/shared/adb/test/xpcshell.ini
+++ b/devtools/shared/adb/test/xpcshell.ini
@@ -3,8 +3,9 @@ tags = devtools
 head = xpcshell-head.js
 firefox-appdir = browser
 skip-if = toolkit == 'android'
 support-files =
   adb.py
 
 [test_adb.js]
 run-sequentially = An extension having the same id is installed/uninstalled in different tests
+[test_addon-aware-adb-scanner.js]
--- a/dom/canvas/test/webgl-conf/generated-mochitest.ini
+++ b/dom/canvas/test/webgl-conf/generated-mochitest.ini
@@ -5335,17 +5335,16 @@ subsuite = webgl2-core
 fail-if = (os == 'mac') || (os == 'win')
 [generated/test_2_conformance2__rendering__instanced-arrays.html]
 subsuite = webgl2-core
 fail-if = 1
 [generated/test_2_conformance2__rendering__instanced-rendering-bug.html]
 subsuite = webgl2-core
 [generated/test_2_conformance2__rendering__instanced-rendering-large-divisor.html]
 subsuite = webgl2-core
-skip-if = (os == 'win')
 [generated/test_2_conformance2__rendering__line-rendering-quality.html]
 subsuite = webgl2-core
 [generated/test_2_conformance2__rendering__multisampling-fragment-evaluation.html]
 subsuite = webgl2-core
 [generated/test_2_conformance2__rendering__out-of-bounds-index-buffers-after-copying.html]
 subsuite = webgl2-core
 [generated/test_2_conformance2__rendering__read-draw-when-missing-image.html]
 subsuite = webgl2-core
--- a/dom/canvas/test/webgl-conf/mochitest-errata.ini
+++ b/dom/canvas/test/webgl-conf/mochitest-errata.ini
@@ -1167,11 +1167,8 @@ skip-if = (os == 'win')
 [generated/test_conformance__misc__webgl-specific-stencil-settings.html]
 skip-if = (os == 'win')
 [generated/test_conformance__textures__misc__tex-video-using-tex-unit-non-zero.html]
 # Fails on QuantumRender configs, but passes on standard configs?
 # Might be intermittant.
 skip-if = (os == 'win')
 [generated/test_2_conformance__textures__misc__tex-video-using-tex-unit-non-zero.html]
 skip-if = (os == 'win')
-[generated/test_2_conformance2__rendering__instanced-rendering-large-divisor.html]
-# Assertion failed: static_cast<unsigned int>(packedAttrib.divisor) == divisor, file z:/build/build/src/gfx/angle/checkout/src/libANGLE/renderer/d3d/d3d11/InputLayoutCache.cpp, line 89
-skip-if = (os == 'win')
--- a/dom/ipc/ContentParent.cpp
+++ b/dom/ipc/ContentParent.cpp
@@ -2097,31 +2097,16 @@ ContentParent::RecvCreateReplayingProces
   mReplayingChildren[aChannelId] = new GeckoChildProcessHost(GeckoProcessType_Content);
   if (!mReplayingChildren[aChannelId]->LaunchAndWaitForProcessHandle(extraArgs)) {
     return IPC_FAIL_NO_REASON(this);
   }
 
   return IPC_OK();
 }
 
-mozilla::ipc::IPCResult
-ContentParent::RecvTerminateReplayingProcess(const uint32_t& aChannelId)
-{
-  // We should only get this message from the child if it is recording or replaying.
-  if (!this->IsRecordingOrReplaying()) {
-    return IPC_FAIL_NO_REASON(this);
-  }
-
-  if (aChannelId < mReplayingChildren.length() && mReplayingChildren[aChannelId]) {
-    DelayedDeleteSubprocess(mReplayingChildren[aChannelId]);
-    mReplayingChildren[aChannelId] = nullptr;
-  }
-  return IPC_OK();
-}
-
 jsipc::CPOWManager*
 ContentParent::GetCPOWManager()
 {
   if (PJavaScriptParent* p = LoneManagedOrNullAsserts(ManagedPJavaScriptParent())) {
     return CPOWManagerFor(p);
   }
   return nullptr;
 }
--- a/dom/ipc/ContentParent.h
+++ b/dom/ipc/ContentParent.h
@@ -302,17 +302,16 @@ public:
                                                          bool* aIsForBrowser) override;
 
   virtual mozilla::ipc::IPCResult RecvBridgeToChildProcess(const ContentParentId& aCpId,
                                                            Endpoint<PContentBridgeParent>* aEndpoint) override;
 
   virtual mozilla::ipc::IPCResult RecvOpenRecordReplayChannel(const uint32_t& channelId,
                                                               FileDescriptor* connection) override;
   virtual mozilla::ipc::IPCResult RecvCreateReplayingProcess(const uint32_t& aChannelId) override;
-  virtual mozilla::ipc::IPCResult RecvTerminateReplayingProcess(const uint32_t& aChannelId) override;
 
   virtual mozilla::ipc::IPCResult RecvCreateGMPService() override;
 
   virtual mozilla::ipc::IPCResult RecvLoadPlugin(const uint32_t& aPluginId, nsresult* aRv,
                                                  uint32_t* aRunID,
                                                  Endpoint<PPluginModuleParent>* aEndpoint) override;
 
   virtual mozilla::ipc::IPCResult RecvMaybeReloadPlugins() override;
--- a/dom/ipc/PContent.ipdl
+++ b/dom/ipc/PContent.ipdl
@@ -737,17 +737,16 @@ parent:
                             TabId tabId)
         returns (ContentParentId cpId, bool isForBrowser);
     sync BridgeToChildProcess(ContentParentId cpId)
         returns (Endpoint<PContentBridgeParent> endpoint);
 
     sync OpenRecordReplayChannel(uint32_t channelId)
         returns (FileDescriptor connection);
     async CreateReplayingProcess(uint32_t channelId);
-    async TerminateReplayingProcess(uint32_t channelId);
 
     async CreateGMPService();
 
     async InitStreamFilter(uint64_t channelId, nsString addonId)
         returns (Endpoint<PStreamFilterChild> aEndpoint);
 
     /**
      * This call connects the content process to a plugin process. This call
--- a/gfx/angle/checkout/out/gen/angle/id/commit.h
+++ b/gfx/angle/checkout/out/gen/angle/id/commit.h
@@ -1,3 +1,3 @@
-#define ANGLE_COMMIT_HASH "598f2502e3a4"
+#define ANGLE_COMMIT_HASH "790e8e6b4179"
 #define ANGLE_COMMIT_HASH_SIZE 12
-#define ANGLE_COMMIT_DATE "2018-10-09 13:54:05 -0700"
+#define ANGLE_COMMIT_DATE "2018-10-09 17:41:46 -0700"
--- a/gfx/angle/checkout/src/libANGLE/renderer/d3d/d3d11/InputLayoutCache.cpp
+++ b/gfx/angle/checkout/src/libANGLE/renderer/d3d/d3d11/InputLayoutCache.cpp
@@ -54,17 +54,18 @@ GLenum GetGLSLAttributeType(const std::v
     return GL_NONE;
 }
 
 struct PackedAttribute
 {
     uint8_t attribType;
     uint8_t semanticIndex;
     uint8_t vertexFormatType;
-    uint8_t divisor;
+    uint8_t dummyPadding;
+    uint32_t divisor;
 };
 
 }  // anonymous namespace
 
 PackedAttributeLayout::PackedAttributeLayout() : numAttributes(0), flags(0), attributeData({})
 {
 }
 
@@ -76,27 +77,28 @@ void PackedAttributeLayout::addAttribute
                                              unsigned int divisor)
 {
     gl::AttributeType attribType = gl::GetAttributeType(glType);
 
     PackedAttribute packedAttrib;
     packedAttrib.attribType       = static_cast<uint8_t>(attribType);
     packedAttrib.semanticIndex    = static_cast<uint8_t>(semanticIndex);
     packedAttrib.vertexFormatType = static_cast<uint8_t>(vertexFormatType);
-    packedAttrib.divisor          = static_cast<uint8_t>(divisor);
+    packedAttrib.dummyPadding     = 0u;
+    packedAttrib.divisor          = static_cast<uint32_t>(divisor);
 
     ASSERT(static_cast<gl::AttributeType>(packedAttrib.attribType) == attribType);
     ASSERT(static_cast<UINT>(packedAttrib.semanticIndex) == semanticIndex);
     ASSERT(static_cast<gl::VertexFormatType>(packedAttrib.vertexFormatType) == vertexFormatType);
     ASSERT(static_cast<unsigned int>(packedAttrib.divisor) == divisor);
 
-    static_assert(sizeof(uint32_t) == sizeof(PackedAttribute),
-                  "PackedAttributes must be 32-bits exactly.");
+    static_assert(sizeof(uint64_t) == sizeof(PackedAttribute),
+                  "PackedAttributes must be 64-bits exactly.");
 
-    attributeData[numAttributes++] = gl::bitCast<uint32_t>(packedAttrib);
+    attributeData[numAttributes++] = gl::bitCast<uint64_t>(packedAttrib);
 }
 
 bool PackedAttributeLayout::operator==(const PackedAttributeLayout &other) const
 {
     return (numAttributes == other.numAttributes) && (flags == other.flags) &&
            (attributeData == other.attributeData);
 }
 
--- a/gfx/angle/checkout/src/libANGLE/renderer/d3d/d3d11/InputLayoutCache.h
+++ b/gfx/angle/checkout/src/libANGLE/renderer/d3d/d3d11/InputLayoutCache.h
@@ -43,17 +43,17 @@ struct PackedAttributeLayout
     {
         FLAG_USES_INSTANCED_SPRITES     = 0x1,
         FLAG_INSTANCED_SPRITES_ACTIVE   = 0x2,
         FLAG_INSTANCED_RENDERING_ACTIVE = 0x4,
     };
 
     uint32_t numAttributes;
     uint32_t flags;
-    gl::AttribArray<uint32_t> attributeData;
+    gl::AttribArray<uint64_t> attributeData;
 };
 }  // namespace rx
 
 namespace std
 {
 template <>
 struct hash<rx::PackedAttributeLayout>
 {
--- a/gfx/angle/cherry_picks.txt
+++ b/gfx/angle/cherry_picks.txt
@@ -1,8 +1,28 @@
+commit 790e8e6b417905eca335d06c16ec54c977188110
+Author: Olli Etuaho <oetuaho@nvidia.com>
+Date:   Thu Sep 20 13:20:50 2018 +0300
+
+    Fix using a large vertex attrib divisor on D3D11
+    
+    A divisor >= 256 used to trigger an assert on the D3D11 backend since
+    it couldn't fit into the input layout cache. Increase the space
+    reserved for the divisor in the input layout cache to make sure that
+    the correct input layout will get used and to fix the assert.
+    
+    BUG=angleproject:2832
+    TEST=angle_end2end_tests
+    
+    Change-Id: I34eead6c4e8c4fea379bbafc8670b4e32a5b633b
+    Reviewed-on: https://chromium-review.googlesource.com/1236293
+    Reviewed-by: Jamie Madill <jmadill@chromium.org>
+    Reviewed-by: Geoff Lang <geofflang@chromium.org>
+    Commit-Queue: Olli Etuaho <oetuaho@nvidia.com>
+
 commit 598f2502e3a41b76d90037e7858c43c18e66399d
 Merge: 8212058a6 e15a25c6f
 Author: Jeff Gilbert <jdashg@gmail.com>
 Date:   Tue Oct 9 13:54:05 2018 -0700
 
     Merge pull request #13 from mattwoodrow/async-load-program
     
     Support async glProgramBInary
--- a/gfx/gl/GLContextProviderEGL.cpp
+++ b/gfx/gl/GLContextProviderEGL.cpp
@@ -529,16 +529,18 @@ GLContextEGL::GetWSIInfo(nsCString* cons
 
 // hold a reference to the given surface
 // for the lifetime of this context.
 void
 GLContextEGL::HoldSurface(gfxASurface* aSurf) {
     mThebesSurface = aSurf;
 }
 
+#define LOCAL_EGL_CONTEXT_PROVOKING_VERTEX_DONT_CARE_MOZ 0x6000
+
 already_AddRefed<GLContextEGL>
 GLContextEGL::CreateGLContext(CreateContextFlags flags,
                 const SurfaceCaps& caps,
                 bool isOffscreen,
                 EGLConfig config,
                 EGLSurface surface,
                 nsACString* const out_failureId)
 {
@@ -562,16 +564,23 @@ GLContextEGL::CreateGLContext(CreateCont
     if (!debugFlags &&
         flags & CreateContextFlags::NO_VALIDATION &&
         egl->IsExtensionSupported(GLLibraryEGL::KHR_create_context_no_error))
     {
         required_attribs.push_back(LOCAL_EGL_CONTEXT_OPENGL_NO_ERROR_KHR);
         required_attribs.push_back(LOCAL_EGL_TRUE);
     }
 
+    if (flags & CreateContextFlags::PROVOKING_VERTEX_DONT_CARE &&
+        egl->IsExtensionSupported(GLLibraryEGL::MOZ_create_context_provoking_vertex_dont_care))
+    {
+        required_attribs.push_back(LOCAL_EGL_CONTEXT_PROVOKING_VERTEX_DONT_CARE_MOZ);
+        required_attribs.push_back(LOCAL_EGL_TRUE);
+    }
+
     std::vector<EGLint> robustness_attribs;
     std::vector<EGLint> rbab_attribs; // RBAB: Robust Buffer Access Behavior
     if (flags & CreateContextFlags::PREFER_ROBUSTNESS) {
         if (egl->IsExtensionSupported(GLLibraryEGL::EXT_create_context_robustness)) {
             robustness_attribs = required_attribs;
             robustness_attribs.push_back(LOCAL_EGL_CONTEXT_OPENGL_RESET_NOTIFICATION_STRATEGY_EXT);
             robustness_attribs.push_back(LOCAL_EGL_LOSE_CONTEXT_ON_RESET_EXT);
 
--- a/gfx/gl/GLContextTypes.h
+++ b/gfx/gl/GLContextTypes.h
@@ -54,15 +54,17 @@ enum class CreateContextFlags : uint8_t 
     ALLOW_OFFLINE_RENDERER =  1 << 2,
     // Ask for ES3 if possible
     PREFER_ES3 = 1 << 3,
 
     NO_VALIDATION = 1 << 4,
     PREFER_ROBUSTNESS = 1 << 5,
 
     HIGH_POWER = 1 << 6,
+
+    PROVOKING_VERTEX_DONT_CARE = 1 << 7,
 };
 MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(CreateContextFlags)
 
 } /* namespace gl */
 } /* namespace mozilla */
 
 #endif /* GLCONTEXT_TYPES_H_ */
--- a/gfx/gl/GLLibraryEGL.cpp
+++ b/gfx/gl/GLLibraryEGL.cpp
@@ -64,17 +64,18 @@ static const char* sEGLExtensionNames[] 
     "EGL_KHR_stream",
     "EGL_KHR_stream_consumer_gltexture",
     "EGL_EXT_device_query",
     "EGL_NV_stream_consumer_gltexture_yuv",
     "EGL_ANGLE_stream_producer_d3d_texture",
     "EGL_ANGLE_device_creation",
     "EGL_ANGLE_device_creation_d3d11",
     "EGL_KHR_surfaceless_context",
-    "EGL_KHR_create_context_no_error"
+    "EGL_KHR_create_context_no_error",
+    "EGL_MOZ_create_context_provoking_vertex_dont_care"
 };
 
 #if defined(ANDROID)
 
 static PRLibrary* LoadApitraceLibrary()
 {
     // Initialization of gfx prefs here is only needed during the unit tests...
     gfxPrefs::GetSingleton();
--- a/gfx/gl/GLLibraryEGL.h
+++ b/gfx/gl/GLLibraryEGL.h
@@ -92,16 +92,17 @@ public:
         KHR_stream_consumer_gltexture,
         EXT_device_query,
         NV_stream_consumer_gltexture_yuv,
         ANGLE_stream_producer_d3d_texture,
         ANGLE_device_creation,
         ANGLE_device_creation_d3d11,
         KHR_surfaceless_context,
         KHR_create_context_no_error,
+        MOZ_create_context_provoking_vertex_dont_care,
         Extensions_Max
     };
 
     bool IsExtensionSupported(EGLExtensions aKnownExtension) const {
         return mAvailableExtensions[aKnownExtension];
     }
 
     void MarkExtensionUnsupported(EGLExtensions aKnownExtension) {
--- a/gfx/layers/wr/AsyncImagePipelineManager.cpp
+++ b/gfx/layers/wr/AsyncImagePipelineManager.cpp
@@ -275,16 +275,18 @@ AsyncImagePipelineManager::UpdateImageKe
     }
     Range<wr::ImageKey> keys(&aKeys[0], aKeys.Length());
     auto externalImageKey =
       aPipeline->mWrTextureWrapper ? aPipeline->mWrTextureWrapper->GetExternalImageKey() : wrTexture->GetExternalImageKey();
     wrTexture->PushResourceUpdates(aMaybeFastTxn, op, keys, externalImageKey);
   }
 
   if (aPipeline->mWrTextureWrapper) {
+    // Force frame rendering, since WebRenderTextureHost update its data outside of WebRender.
+    aMaybeFastTxn.InvalidateRenderedFrame();
     HoldExternalImage(aPipelineId, aEpoch, aPipeline->mWrTextureWrapper);
   }
 
   return Some(op);
 }
 
 Maybe<TextureHost::ResourceUpdateOp>
 AsyncImagePipelineManager::UpdateWithoutExternalImage(TextureHost* aTexture,
--- a/gfx/layers/wr/WebRenderBridgeParent.cpp
+++ b/gfx/layers/wr/WebRenderBridgeParent.cpp
@@ -1388,16 +1388,22 @@ WebRenderBridgeParent::UpdateWebRender(C
 }
 
 mozilla::ipc::IPCResult
 WebRenderBridgeParent::RecvScheduleComposite()
 {
   if (mDestroyed) {
     return IPC_OK();
   }
+
+  // Force frame rendering during next frame generation.
+  wr::TransactionBuilder fastTxn(/* aUseSceneBuilderThread */ false);
+  fastTxn.InvalidateRenderedFrame();
+  mApi->SendTransaction(fastTxn);
+
   ScheduleGenerateFrame();
   return IPC_OK();
 }
 
 mozilla::ipc::IPCResult
 WebRenderBridgeParent::RecvCapture()
 {
   if (!mDestroyed) {
--- a/gfx/thebes/gfxPlatform.cpp
+++ b/gfx/thebes/gfxPlatform.cpp
@@ -2675,16 +2675,22 @@ gfxPlatform::InitWebRenderConfig()
   //   WR? WR+   => means WR was enabled via gfx.webrender.all.qualified
   //   WR! WR+   => means WR was enabled via gfx.webrender.{all,enabled} or envvar
   // On Beta/Release:
   //   WR? WR+   => means WR was enabled via gfx.webrender.all.qualified on qualified hardware
   //   WR! WR+   => means WR was enabled via envvar, possibly on unqualified hardware.
   // In all cases WR- means WR was not enabled, for one of many possible reasons.
   ScopedGfxFeatureReporter reporter("WR", prefEnabled || envvarEnabled);
   if (!XRE_IsParentProcess()) {
+    // Force-disable WebRender in recording/replaying child processes, which
+    // have their own compositor.
+    if (recordreplay::IsRecordingOrReplaying()) {
+      gfxVars::SetUseWebRender(false);
+    }
+
     // The parent process runs through all the real decision-making code
     // later in this function. For other processes we still want to report
     // the state of the feature for crash reports.
     if (gfxVars::UseWebRender()) {
       reporter.SetSuccessful();
     }
     return;
   }
--- a/gfx/webrender/src/batch.rs
+++ b/gfx/webrender/src/batch.rs
@@ -14,17 +14,17 @@ use gpu_types::{BrushFlags, BrushInstanc
 use gpu_types::{ClipMaskInstance, SplitCompositeInstance};
 use gpu_types::{PrimitiveInstanceData, RasterizationSpace, GlyphInstance};
 use gpu_types::{PrimitiveHeader, PrimitiveHeaderIndex, TransformPaletteId, TransformPalette};
 use internal_types::{FastHashMap, SavedTargetIndex, TextureSource};
 use picture::{PictureCompositeMode, PicturePrimitive, PictureSurface};
 use plane_split::{BspSplitter, Clipper, Polygon, Splitter};
 use prim_store::{BrushKind, BrushPrimitive, BrushSegmentTaskId, DeferredResolve};
 use prim_store::{EdgeAaSegmentMask, ImageSource};
-use prim_store::{PrimitiveMetadata, VisibleGradientTile, PrimitiveInstance};
+use prim_store::{VisibleGradientTile, PrimitiveInstance};
 use prim_store::{BorderSource, Primitive, PrimitiveDetails};
 use render_task::{RenderTaskAddress, RenderTaskId, RenderTaskTree};
 use renderer::{BlendMode, ImageBufferKind, ShaderColorMode};
 use renderer::BLOCKS_PER_UV_RECT;
 use resource_cache::{CacheItem, GlyphFetchResult, ImageRequest, ResourceCache, ImageProperties};
 use scene::FilterOpHelpers;
 use std::{f32, i32};
 use tiling::{RenderTargetContext};
@@ -471,24 +471,24 @@ impl AlphaBatchBuilder {
         // Z axis is directed at the screen, `sort` is ascending, and we need back-to-front order.
         for poly in splitter.sort(vec3(0.0, 0.0, 1.0)) {
             let prim_instance = &pic.prim_instances[poly.anchor];
             let prim_index = prim_instance.prim_index;
             let pic_metadata = &ctx.prim_store.primitives[prim_index.0].metadata;
             if cfg!(debug_assertions) && ctx.prim_store.chase_id == Some(prim_index) {
                 println!("\t\tsplit polygon {:?}", poly.points);
             }
-            let transform = transforms.get_world_transform(pic_metadata.spatial_node_index).inverse().unwrap();
+            let transform = transforms.get_world_transform(prim_instance.spatial_node_index).inverse().unwrap();
             let transform_id = transforms.get_id(
-                pic_metadata.spatial_node_index,
+                prim_instance.spatial_node_index,
                 ROOT_SPATIAL_NODE_INDEX,
                 ctx.clip_scroll_tree,
             );
 
-            let clip_task_address = pic_metadata
+            let clip_task_address = prim_instance
                 .clip_task_id
                 .map_or(OPAQUE_TASK_ADDRESS, |id| render_tasks.get_task_address(id));
 
             let prim_header = PrimitiveHeader {
                 local_rect: pic_metadata.local_rect,
                 local_clip_rect: prim_instance.combined_local_clip_rect,
                 task_address,
                 specific_prim_address: GpuCacheAddress::invalid(),
@@ -533,17 +533,17 @@ impl AlphaBatchBuilder {
             let key = BatchKey::new(
                 BatchKind::SplitComposite,
                 BlendMode::PremultipliedAlpha,
                 BatchTextures::no_texture(),
             );
             let batch = self.batch_list
                             .get_suitable_batch(
                                 key,
-                                &pic_metadata.clipped_world_rect.as_ref().expect("bug"),
+                                &prim_instance.clipped_world_rect.as_ref().expect("bug"),
                             );
 
             let gpu_address = gpu_cache.get_address(&gpu_handle);
 
             let instance = SplitCompositeInstance::new(
                 prim_header_index,
                 gpu_address,
                 prim_headers.z_generator.next(),
@@ -570,35 +570,35 @@ impl AlphaBatchBuilder {
         prim_headers: &mut PrimitiveHeaders,
         transforms: &mut TransformPalette,
         root_spatial_node_index: SpatialNodeIndex,
         plane_split_anchor: usize,
     ) {
         let prim = &ctx.prim_store.primitives[prim_instance.prim_index.0];
         let prim_metadata = &prim.metadata;
 
-        if prim_metadata.clipped_world_rect.is_none() {
+        if prim_instance.clipped_world_rect.is_none() {
             return;
         }
 
         #[cfg(debug_assertions)] //TODO: why is this needed?
-        debug_assert_eq!(prim_metadata.prepared_frame_id, render_tasks.frame_id());
+        debug_assert_eq!(prim_instance.prepared_frame_id, render_tasks.frame_id());
 
         let transform_id = transforms
             .get_id(
-                prim_metadata.spatial_node_index,
+                prim_instance.spatial_node_index,
                 root_spatial_node_index,
                 ctx.clip_scroll_tree,
             );
 
         // TODO(gw): Calculating this for every primitive is a bit
         //           wasteful. We should probably cache this in
         //           the scroll node...
         let transform_kind = transform_id.transform_kind();
-        let bounding_rect = prim_metadata.clipped_world_rect
+        let bounding_rect = prim_instance.clipped_world_rect
                                          .as_ref()
                                          .expect("bug");
 
         // If the primitive is internally decomposed into multiple sub-primitives we may not
         // use some of the per-primitive data typically stored in PrimitiveMetadata and get
         // it from each sub-primitive instead.
         let is_multiple_primitives = match prim.details {
             PrimitiveDetails::Brush(ref brush) => {
@@ -610,27 +610,27 @@ impl AlphaBatchBuilder {
                 }
             }
             _ => false,
         };
 
         let prim_cache_address = if is_multiple_primitives {
             GpuCacheAddress::invalid()
         } else {
-            gpu_cache.get_address(&prim_metadata.gpu_location)
+            gpu_cache.get_address(&prim_instance.gpu_location)
         };
 
-        let clip_task_address = prim_metadata
+        let clip_task_address = prim_instance
             .clip_task_id
             .map_or(OPAQUE_TASK_ADDRESS, |id| render_tasks.get_task_address(id));
 
         let specified_blend_mode = prim.get_blend_mode();
 
-        let non_segmented_blend_mode = if !prim_metadata.opacity.is_opaque ||
-            prim_metadata.clip_task_id.is_some() ||
+        let non_segmented_blend_mode = if !prim_instance.opacity.is_opaque ||
+            prim_instance.clip_task_id.is_some() ||
             transform_kind == TransformedRectKind::Complex {
             specified_blend_mode
         } else {
             BlendMode::None
         };
 
         let prim_header = PrimitiveHeader {
             local_rect: prim_metadata.local_rect,
@@ -651,31 +651,31 @@ impl AlphaBatchBuilder {
                 match brush.kind {
                     BrushKind::Picture(ref picture) => {
                         // If this picture is participating in a 3D rendering context,
                         // then don't add it to any batches here. Instead, create a polygon
                         // for it and add it to the current plane splitter.
                         if picture.is_in_3d_context {
                             // Push into parent plane splitter.
                             debug_assert!(picture.raster_config.is_some());
-                            let transform = transforms.get_world_transform(prim_metadata.spatial_node_index);
+                            let transform = transforms.get_world_transform(prim_instance.spatial_node_index);
 
                             // Apply the local clip rect here, before splitting. This is
                             // because the local clip rect can't be applied in the vertex
                             // shader for split composites, since we are drawing polygons
                             // rather that rectangles. The interpolation still works correctly
                             // since we determine the UVs by doing a bilerp with a factor
                             // from the original local rect.
                             let local_rect = prim_metadata.local_rect
                                                           .intersection(&prim_instance.combined_local_clip_rect);
 
                             if let Some(local_rect) = local_rect {
                                 match transform.transform_kind() {
                                     TransformedRectKind::AxisAligned => {
-                                        let inv_transform = transforms.get_world_inv_transform(prim_metadata.spatial_node_index);
+                                        let inv_transform = transforms.get_world_inv_transform(prim_instance.spatial_node_index);
                                         let polygon = Polygon::from_transformed_rect_with_inverse(
                                             local_rect.cast(),
                                             &transform.cast(),
                                             &inv_transform.cast(),
                                             plane_split_anchor,
                                         ).unwrap();
                                         splitter.add(polygon);
                                     }
@@ -1057,17 +1057,17 @@ impl AlphaBatchBuilder {
                             let prim_header_index = prim_headers.push(&prim_header, user_data);
                             if cfg!(debug_assertions) && ctx.prim_store.chase_id == Some(prim_instance.prim_index) {
                                 println!("\t{:?} {:?}, task relative bounds {:?}",
                                     batch_kind, prim_header_index, bounding_rect);
                             }
 
                             self.add_brush_to_batch(
                                 brush,
-                                prim_metadata,
+                                prim_instance,
                                 batch_kind,
                                 specified_blend_mode,
                                 non_segmented_blend_mode,
                                 textures,
                                 prim_header_index,
                                 clip_task_address,
                                 bounding_rect,
                                 transform_kind,
@@ -1195,17 +1195,17 @@ impl AlphaBatchBuilder {
         };
         let batch = self.batch_list.get_suitable_batch(batch_key, bounding_rect);
         batch.push(PrimitiveInstanceData::from(base_instance));
     }
 
     fn add_brush_to_batch(
         &mut self,
         brush: &BrushPrimitive,
-        prim_metadata: &PrimitiveMetadata,
+        prim_instance: &PrimitiveInstance,
         batch_kind: BrushBatchKind,
         alpha_blend_mode: BlendMode,
         non_segmented_blend_mode: BlendMode,
         textures: BatchTextures,
         prim_header_index: PrimitiveHeaderIndex,
         clip_task_address: RenderTaskAddress,
         bounding_rect: &WorldRect,
         transform_kind: TransformedRectKind,
@@ -1240,17 +1240,17 @@ impl AlphaBatchBuilder {
 
                 let opaque_batch = self.batch_list.opaque_batch_list.get_suitable_batch(
                     opaque_batch_key,
                     bounding_rect,
                 );
 
                 for (i, segment) in segment_desc.segments.iter().enumerate() {
                     let is_inner = segment.edge_flags.is_empty();
-                    let needs_blending = !prim_metadata.opacity.is_opaque ||
+                    let needs_blending = !prim_instance.opacity.is_opaque ||
                                          segment.clip_task_id.needs_blending() ||
                                          (!is_inner && transform_kind == TransformedRectKind::Complex);
 
                     let clip_task_address = match segment.clip_task_id {
                         BrushSegmentTaskId::RenderTaskId(id) =>
                             render_tasks.get_task_address(id),
                         BrushSegmentTaskId::Opaque => OPAQUE_TASK_ADDRESS,
                         BrushSegmentTaskId::Empty => continue,
--- a/gfx/webrender/src/clip.rs
+++ b/gfx/webrender/src/clip.rs
@@ -1,14 +1,14 @@
 /* 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, ClipMode, ComplexClipRegion, DeviceIntRect, DevicePixelScale, ImageMask};
-use api::{ImageRendering, LayoutRect, LayoutSize, LayoutPoint, LayoutVector2D, LocalClip};
+use api::{ImageRendering, LayoutRect, LayoutSize, LayoutPoint, LayoutVector2D};
 use api::{BoxShadowClipMode, LayoutToWorldScale, LineOrientation, LineStyle, PicturePixel, WorldPixel};
 use api::{PictureRect, LayoutPixel, WorldPoint, WorldSize, WorldRect, LayoutToWorldTransform};
 use api::{VoidPtrToSizeFn, LayoutRectAu, ImageKey, AuHelpers};
 use app_units::Au;
 use border::{ensure_no_corner_overlap, BorderRadiusAu};
 use box_shadow::{BLUR_SAMPLE_SCALE, BoxShadowClipSource, BoxShadowCacheKey};
 use clip_scroll_tree::{ClipScrollTree, CoordinateSystemId, ROOT_SPATIAL_NODE_INDEX, SpatialNodeIndex};
 use ellipse::Ellipse;
@@ -708,34 +708,23 @@ impl<J> ClipRegion<ComplexTranslateIter<
                 offset: *reference_frame_relative_offset,
             },
         }
     }
 }
 
 impl ClipRegion<Option<ComplexClipRegion>> {
     pub fn create_for_clip_node_with_local_clip(
-        local_clip: &LocalClip,
+        local_clip: &LayoutRect,
         reference_frame_relative_offset: &LayoutVector2D
     ) -> Self {
         ClipRegion {
-            main: local_clip
-                .clip_rect()
-                .translate(reference_frame_relative_offset),
+            main: local_clip.translate(reference_frame_relative_offset),
             image_mask: None,
-            complex_clips: match *local_clip {
-                LocalClip::Rect(_) => None,
-                LocalClip::RoundedRect(_, ref region) => {
-                    Some(ComplexClipRegion {
-                        rect: region.rect.translate(reference_frame_relative_offset),
-                        radii: region.radii,
-                        mode: region.mode,
-                    })
-                },
-            }
+            complex_clips: None,
         }
     }
 }
 
 // The ClipItemKey is a hashable representation of the contents
 // of a clip item. It is used during interning to de-duplicate
 // clip nodes between frames and display lists. This allows quick
 // comparison of clip node equality by handle, and also allows
--- a/gfx/webrender/src/device/gl.rs
+++ b/gfx/webrender/src/device/gl.rs
@@ -757,17 +757,16 @@ pub struct Device {
     extensions: Vec<String>,
 }
 
 impl Device {
     pub fn new(
         gl: Rc<gl::Gl>,
         resource_override_path: Option<PathBuf>,
         upload_method: UploadMethod,
-        _file_changed_handler: Box<FileWatcherHandler>,
         cached_programs: Option<Rc<ProgramCache>>,
     ) -> Device {
         let mut max_texture_size = [0];
         unsafe {
             gl.get_integer_v(gl::MAX_TEXTURE_SIZE, &mut max_texture_size);
         }
         let max_texture_size = max_texture_size[0] as u32;
         let renderer_name = gl.get_string(gl::RENDERER);
--- a/gfx/webrender/src/display_list_flattener.rs
+++ b/gfx/webrender/src/display_list_flattener.rs
@@ -4,41 +4,42 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 use api::{AlphaType, BorderDetails, BorderDisplayItem, BuiltDisplayListIter, ClipAndScrollInfo};
 use api::{ClipId, ColorF, ComplexClipRegion, DeviceIntPoint, DeviceIntRect, DeviceIntSize};
 use api::{DevicePixelScale, DeviceUintRect, DisplayItemRef, ExtendMode, ExternalScrollId};
 use api::{FilterOp, FontInstanceKey, GlyphInstance, GlyphOptions, RasterSpace, GradientStop};
 use api::{IframeDisplayItem, ImageKey, ImageRendering, ItemRange, LayoutPoint, ColorDepth};
 use api::{LayoutPrimitiveInfo, LayoutRect, LayoutSize, LayoutTransform, LayoutVector2D};
-use api::{LineOrientation, LineStyle, LocalClip, NinePatchBorderSource, PipelineId};
+use api::{LineOrientation, LineStyle, NinePatchBorderSource, PipelineId};
 use api::{PropertyBinding, ReferenceFrame, RepeatMode, ScrollFrameDisplayItem, ScrollSensitivity};
 use api::{Shadow, SpecificDisplayItem, StackingContext, StickyFrameDisplayItem, TexelRect};
 use api::{ClipMode, TransformStyle, YuvColorSpace, YuvData};
 use clip::{ClipChainId, ClipRegion, ClipItemKey, ClipStore};
 use clip_scroll_tree::{ClipScrollTree, SpatialNodeIndex};
 use euclid::vec2;
 use frame_builder::{ChasePrimitive, FrameBuilder, FrameBuilderConfig};
 use glyph_rasterizer::FontInstance;
 use gpu_cache::GpuCacheHandle;
 use gpu_types::BrushFlags;
 use hit_test::{HitTestingItem, HitTestingRun};
 use image::simplify_repeated_primitive;
 use internal_types::{FastHashMap, FastHashSet};
 use picture::{PictureCompositeMode, PictureIdGenerator, PicturePrimitive};
-use prim_store::{BrushKind, BrushPrimitive, BrushSegmentDescriptor};
-use prim_store::{EdgeAaSegmentMask, ImageSource, PrimitiveOpacity};
+use prim_store::{BrushKind, BrushPrimitive, BrushSegmentDescriptor, PrimitiveInstance};
+use prim_store::{EdgeAaSegmentMask, ImageSource, PrimitiveOpacity, PrimitiveKey};
 use prim_store::{BorderSource, BrushSegment, BrushSegmentVec, PrimitiveContainer, PrimitiveIndex, PrimitiveStore};
 use prim_store::{OpacityBinding, ScrollNodeAndClipChain, TextRunPrimitive};
 use render_backend::{DocumentView};
 use resource_cache::{FontInstanceMap, ImageRequest};
 use scene::{Scene, ScenePipeline, StackingContextHelpers};
 use scene_builder::DocumentResources;
 use spatial_node::{SpatialNodeType, StickyFrameInfo};
 use std::{f32, mem};
+use std::collections::vec_deque::VecDeque;
 use tiling::{CompositeOps};
 use util::{MaxRect, RectHelpers};
 
 #[derive(Debug, Copy, Clone)]
 struct ClipNode {
     id: ClipChainId,
     count: usize,
 }
@@ -127,18 +128,18 @@ pub struct DisplayListFlattener<'a> {
 
     /// The data structure that converting between ClipId and the various index
     /// types that the ClipScrollTree uses.
     id_to_index_mapper: ClipIdToIndexMapper,
 
     /// A stack of stacking context properties.
     sc_stack: Vec<FlattenedStackingContext>,
 
-    /// A stack of the currently active shadows
-    shadow_stack: Vec<(Shadow, PrimitiveIndex)>,
+    /// Maintains state for any currently active shadows
+    pending_shadow_items: VecDeque<ShadowItem>,
 
     /// The stack keeping track of the root clip chains associated with pipelines.
     pipeline_clip_chain_stack: Vec<ClipChainId>,
 
     /// The store of primitives.
     pub prim_store: PrimitiveStore,
 
     /// Information about all primitives involved in hit testing.
@@ -152,16 +153,20 @@ pub struct DisplayListFlattener<'a> {
     pub config: FrameBuilderConfig,
 
     /// Reference to the document resources, which contains
     /// shared (interned) data between display lists.
     resources: &'a mut DocumentResources,
 
     /// The estimated count of primtives we expect to encounter during flattening.
     prim_count_estimate: usize,
+
+    /// The root primitive index for this flattener. This is the primitive
+    /// to start the culling phase from.
+    pub root_prim_index: PrimitiveIndex,
 }
 
 impl<'a> DisplayListFlattener<'a> {
     pub fn create_frame_builder(
         scene: &Scene,
         clip_scroll_tree: &mut ClipScrollTree,
         font_instances: FontInstanceMap,
         view: &DocumentView,
@@ -183,24 +188,25 @@ impl<'a> DisplayListFlattener<'a> {
         let mut flattener = DisplayListFlattener {
             scene,
             clip_scroll_tree,
             font_instances,
             config: *frame_builder_config,
             output_pipelines,
             id_to_index_mapper: ClipIdToIndexMapper::default(),
             hit_testing_runs: Vec::new(),
-            shadow_stack: Vec::new(),
+            pending_shadow_items: VecDeque::new(),
             sc_stack: Vec::new(),
             pipeline_clip_chain_stack: vec![ClipChainId::NONE],
             prim_store: PrimitiveStore::new(),
             clip_store: ClipStore::new(),
             picture_id_generator,
             resources,
             prim_count_estimate: 0,
+            root_prim_index: PrimitiveIndex(0),
         };
 
         flattener.push_root(
             root_pipeline_id,
             &root_pipeline.viewport_size,
             &root_pipeline.content_size,
         );
         flattener.setup_viewport_offset(view.inner_rect, view.accumulated_scale_factor());
@@ -469,17 +475,17 @@ impl<'a> DisplayListFlattener<'a> {
             },
         };
 
         //TODO: use or assert on `clip_and_scroll_ids.clip_node_id` ?
         let clip_chain_index = self.add_clip_node(
             info.clip_id,
             clip_and_scroll_ids.scroll_node_id,
             ClipRegion::create_for_clip_node_with_local_clip(
-                &LocalClip::from(*item.clip_rect()),
+                item.clip_rect(),
                 reference_frame_relative_offset
             ),
         );
         self.pipeline_clip_chain_stack.push(clip_chain_index);
 
         let bounds = item.rect();
         let origin = *reference_frame_relative_offset + bounds.origin.to_vector();
         self.push_reference_frame(
@@ -816,27 +822,36 @@ impl<'a> DisplayListFlattener<'a> {
     /// add the primitive to the draw list, so can be used for creating
     /// sub-primitives.
     pub fn create_primitive(
         &mut self,
         info: &LayoutPrimitiveInfo,
         clip_chain_id: ClipChainId,
         spatial_node_index: SpatialNodeIndex,
         container: PrimitiveContainer,
-    ) -> PrimitiveIndex {
+    ) -> PrimitiveInstance {
         let stacking_context = self.sc_stack.last().expect("bug: no stacking context!");
 
-        self.prim_store.add_primitive(
+        let prim_key = PrimitiveKey::new(
+            info.is_backface_visible && stacking_context.is_backface_visible,
+        );
+
+        let prim_data_handle = self.resources.prim_interner.intern(&prim_key);
+
+        let prim_index = self.prim_store.add_primitive(
             &info.rect,
             &info.clip_rect,
-            info.is_backface_visible && stacking_context.is_backface_visible,
+            container,
+        );
+
+        PrimitiveInstance::new(
+            prim_index,
+            prim_data_handle,
             clip_chain_id,
             spatial_node_index,
-            info.tag,
-            container,
         )
     }
 
     pub fn add_primitive_to_hit_testing_list(
         &mut self,
         info: &LayoutPrimitiveInfo,
         clip_and_scroll: ScrollNodeAndClipChain
     ) {
@@ -856,92 +871,66 @@ impl<'a> DisplayListFlattener<'a> {
         }
 
         self.hit_testing_runs.push(HitTestingRun(vec![new_item], clip_and_scroll));
     }
 
     /// Add an already created primitive to the draw lists.
     pub fn add_primitive_to_draw_list(
         &mut self,
-        prim_index: PrimitiveIndex,
+        prim_instance: PrimitiveInstance,
     ) {
-        // Add primitive to the top-most Picture on the stack.
-        let pic_prim_index = self.sc_stack.last().unwrap().leaf_prim_index;
-        if cfg!(debug_assertions) && self.prim_store.chase_id == Some(prim_index) {
-            println!("\tadded to picture primitive {:?}", pic_prim_index);
+        // Add primitive to the top-most stacking context on the stack.
+        if cfg!(debug_assertions) && self.prim_store.chase_id == Some(prim_instance.prim_index) {
+            println!("\tadded to stacking context at {}", self.sc_stack.len());
         }
-        let pic = self.prim_store.get_pic_mut(pic_prim_index);
-        pic.add_primitive(prim_index);
+        let stacking_context = self.sc_stack.last_mut().unwrap();
+        stacking_context.normal_primitives.push(prim_instance);
     }
 
     /// Convenience interface that creates a primitive entry and adds it
     /// to the draw list.
     pub fn add_primitive(
         &mut self,
         clip_and_scroll: ScrollNodeAndClipChain,
         info: &LayoutPrimitiveInfo,
         clip_items: Vec<ClipItemKey>,
         container: PrimitiveContainer,
     ) {
-        if !self.shadow_stack.is_empty() {
-            // TODO(gw): Restructure this so we don't need to move the shadow
-            //           stack out (borrowck due to create_primitive below).
-            let shadow_stack = mem::replace(&mut self.shadow_stack, Vec::new());
-            for &(ref shadow, shadow_pic_prim_index) in &shadow_stack {
-                // Offset the local rect and clip rect by the shadow offset.
-                let mut info = info.clone();
-                info.rect = info.rect.translate(&shadow.offset);
-                info.clip_rect = info.clip_rect.translate(&shadow.offset);
-
-                // Offset any local clip sources by the shadow offset.
-                let clip_items: Vec<ClipItemKey> = clip_items
-                    .iter()
-                    .map(|cs| cs.offset(&shadow.offset))
-                    .collect();
+        // If a shadow context is not active, then add the primitive
+        // directly to the parent picture.
+        if self.pending_shadow_items.is_empty() {
+            if container.is_visible() {
                 let clip_chain_id = self.build_clip_chain(
                     clip_items,
                     clip_and_scroll.spatial_node_index,
                     clip_and_scroll.clip_chain_id,
                 );
-
-                // Construct and add a primitive for the given shadow.
-                let shadow_prim_index = self.create_primitive(
-                    &info,
+                let prim_instance = self.create_primitive(
+                    info,
                     clip_chain_id,
                     clip_and_scroll.spatial_node_index,
-                    container.create_shadow(shadow),
+                    container,
                 );
-
-                // Add the new primitive to the shadow picture.
-                let shadow_pic = self.prim_store.get_pic_mut(shadow_pic_prim_index);
-                shadow_pic.add_primitive(shadow_prim_index);
+                if cfg!(debug_assertions) && ChasePrimitive::LocalRect(info.rect) == self.config.chase_primitive {
+                    println!("Chasing {:?} by local rect", prim_instance.prim_index);
+                    self.prim_store.chase_id = Some(prim_instance.prim_index);
+                }
+                self.add_primitive_to_hit_testing_list(info, clip_and_scroll);
+                self.add_primitive_to_draw_list(prim_instance);
             }
-            self.shadow_stack = shadow_stack;
-        }
-
-        if container.is_visible() {
-            let clip_chain_id = self.build_clip_chain(
+        } else {
+            // There is an active shadow context. Store as a pending primitive
+            // for processing during pop_all_shadows.
+            self.pending_shadow_items.push_back(ShadowItem::Primitive(PendingPrimitive {
+                clip_and_scroll,
+                info: *info,
                 clip_items,
-                clip_and_scroll.spatial_node_index,
-                clip_and_scroll.clip_chain_id,
-            );
-            let prim_index = self.create_primitive(
-                info,
-                clip_chain_id,
-                clip_and_scroll.spatial_node_index,
                 container,
-            );
-            if cfg!(debug_assertions) && ChasePrimitive::LocalRect(info.rect) == self.config.chase_primitive {
-                println!("Chasing {:?} by local rect", prim_index);
-                self.prim_store.chase_id = Some(prim_index);
-            }
-            self.add_primitive_to_hit_testing_list(info, clip_and_scroll);
-            self.add_primitive_to_draw_list(
-                prim_index,
-            );
+            }));
         }
     }
 
     pub fn push_stacking_context(
         &mut self,
         pipeline_id: PipelineId,
         composite_ops: CompositeOps,
         transform_style: TransformStyle,
@@ -952,23 +941,23 @@ impl<'a> DisplayListFlattener<'a> {
         requested_raster_space: RasterSpace,
     ) {
         let spatial_node_index = self.id_to_index_mapper.get_spatial_node_index(spatial_node);
         let clip_chain_id = match clipping_node {
             Some(ref clipping_node) => self.id_to_index_mapper.get_clip_chain_id(clipping_node),
             None => ClipChainId::NONE,
         };
 
-        // An arbitrary large clip rect. For now, we don't
-        // specify a clip specific to the stacking context.
-        // However, now that they are represented as Picture
-        // primitives, we can apply any kind of clip mask
-        // to them, as for a normal primitive. This is needed
-        // to correctly handle some CSS cases (see #1957).
-        let max_clip = LayoutRect::max_rect();
+        // Check if this stacking context is the root of a pipeline, and the caller
+        // has requested it as an output frame.
+        let frame_output_pipeline_id = if is_pipeline_root && self.output_pipelines.contains(&pipeline_id) {
+            Some(pipeline_id)
+        } else {
+            None
+        };
 
         // Get the transform-style of the parent stacking context,
         // which determines if we *might* need to draw this on
         // an intermediate surface for plane splitting purposes.
         let parent_transform_style = match self.sc_stack.last() {
             Some(sc) => sc.transform_style,
             None => TransformStyle::Flat,
         };
@@ -985,153 +974,21 @@ impl<'a> DisplayListFlattener<'a> {
 
         // If this is participating in a 3d context *and* the
         // parent was not a 3d context, then this must be the
         // element that establishes a new 3d context.
         let establishes_3d_context =
             participating_in_3d_context &&
             parent_transform_style == TransformStyle::Flat;
 
-        // By default, this picture will be collapsed into
-        // the owning target.
-        let mut composite_mode = None;
-
-        // If this stacking context is the root of a pipeline, and the caller
-        // has requested it as an output frame, create a render task to isolate it.
-        let mut frame_output_pipeline_id = None;
-        if is_pipeline_root && self.output_pipelines.contains(&pipeline_id) {
-            composite_mode = Some(PictureCompositeMode::Blit);
-            frame_output_pipeline_id = Some(pipeline_id);
-        }
-
         // Force an intermediate surface if the stacking context
         // has a clip node. In the future, we may decide during
         // prepare step to skip the intermediate surface if the
         // clip node doesn't affect the stacking context rect.
-        if participating_in_3d_context || clipping_node.is_some() {
-            // TODO(gw): For now, as soon as this picture is in
-            //           a 3D context, we draw it to an intermediate
-            //           surface and apply plane splitting. However,
-            //           there is a large optimization opportunity here.
-            //           During culling, we can check if there is actually
-            //           perspective present, and skip the plane splitting
-            //           completely when that is not the case.
-            composite_mode = Some(PictureCompositeMode::Blit);
-        }
-
-        // Add picture for this actual stacking context contents to render into.
-        let leaf_picture = PicturePrimitive::new_image(
-            self.picture_id_generator.next(),
-            composite_mode,
-            participating_in_3d_context,
-            pipeline_id,
-            frame_output_pipeline_id,
-            true,
-            requested_raster_space,
-        );
-
-        // Create a brush primitive that draws this picture.
-        let leaf_prim = BrushPrimitive::new_picture(leaf_picture);
-
-        // Add the brush to the parent picture.
-        let leaf_prim_index = self.prim_store.add_primitive(
-            &LayoutRect::zero(),
-            &max_clip,
-            true,
-            clip_chain_id,
-            spatial_node_index,
-            None,
-            PrimitiveContainer::Brush(leaf_prim),
-        );
-
-        // Create a chain of pictures based on presence of filters,
-        // mix-blend-mode and/or 3d rendering context containers.
-        let mut current_prim_index = leaf_prim_index;
-
-        // For each filter, create a new image with that composite mode.
-        for filter in &composite_ops.filters {
-            let filter = filter.sanitize();
-
-            let mut filter_picture = PicturePrimitive::new_image(
-                self.picture_id_generator.next(),
-                Some(PictureCompositeMode::Filter(filter)),
-                false,
-                pipeline_id,
-                None,
-                true,
-                requested_raster_space,
-            );
-
-            filter_picture.add_primitive(current_prim_index);
-            let filter_prim = BrushPrimitive::new_picture(filter_picture);
-
-            current_prim_index = self.prim_store.add_primitive(
-                &LayoutRect::zero(),
-                &max_clip,
-                true,
-                clip_chain_id,
-                spatial_node_index,
-                None,
-                PrimitiveContainer::Brush(filter_prim),
-            );
-        }
-
-        // Same for mix-blend-mode.
-        if let Some(mix_blend_mode) = composite_ops.mix_blend_mode {
-            let mut blend_picture = PicturePrimitive::new_image(
-                self.picture_id_generator.next(),
-                Some(PictureCompositeMode::MixBlend(mix_blend_mode)),
-                false,
-                pipeline_id,
-                None,
-                true,
-                requested_raster_space,
-            );
-
-            blend_picture.add_primitive(current_prim_index);
-            let blend_prim = BrushPrimitive::new_picture(blend_picture);
-
-            current_prim_index = self.prim_store.add_primitive(
-                &LayoutRect::zero(),
-                &max_clip,
-                true,
-                clip_chain_id,
-                spatial_node_index,
-                None,
-                PrimitiveContainer::Brush(blend_prim),
-            );
-        }
-
-        if establishes_3d_context {
-            // If establishing a 3d context, we need to add a picture
-            // that will be the container for all the planes and any
-            // un-transformed content.
-            let mut container_picture = PicturePrimitive::new_image(
-                self.picture_id_generator.next(),
-                None,
-                false,
-                pipeline_id,
-                None,
-                true,
-                requested_raster_space,
-            );
-
-            container_picture.add_primitive(current_prim_index);
-            let container_prim = BrushPrimitive::new_picture(container_picture);
-
-            current_prim_index = self.prim_store.add_primitive(
-                &LayoutRect::zero(),
-                &max_clip,
-                true,
-                clip_chain_id,
-                spatial_node_index,
-                None,
-                PrimitiveContainer::Brush(container_prim),
-            );
-        }
+        let should_isolate = clipping_node.is_some();
 
         // 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!
@@ -1141,79 +998,243 @@ impl<'a> DisplayListFlattener<'a> {
                 .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,
+        self.sc_stack.push(FlattenedStackingContext {
+            preserve3d_primitives: Vec::new(),
+            normal_primitives: Vec::new(),
             pipeline_id,
+            is_backface_visible,
+            requested_raster_space,
+            spatial_node_index,
+            clip_chain_id,
+            frame_output_pipeline_id,
+            composite_ops,
+            should_isolate,
             transform_style,
-            establishes_3d_context,
             participating_in_3d_context,
-            leaf_prim_index,
-            root_prim_index: current_prim_index,
-            has_mix_blend_mode: composite_ops.mix_blend_mode.is_some(),
-        };
-
-        self.sc_stack.push(sc);
+            establishes_3d_context,
+        });
     }
 
     pub fn pop_stacking_context(&mut self) {
-        let sc = self.sc_stack.pop().unwrap();
+        let stacking_context = self.sc_stack.pop().unwrap();
+
+        // An arbitrary large clip rect. For now, we don't
+        // specify a clip specific to the stacking context.
+        // However, now that they are represented as Picture
+        // primitives, we can apply any kind of clip mask
+        // to them, as for a normal primitive. This is needed
+        // to correctly handle some CSS cases (see #1957).
+        let max_clip = LayoutRect::max_rect();
+
+        // By default, this picture will be collapsed into
+        // the owning target.
+        let mut composite_mode = if stacking_context.should_isolate {
+            Some(PictureCompositeMode::Blit)
+        } else {
+            None
+        };
+
+        // Force an intermediate surface if the stacking context
+        // has a clip node. In the future, we may decide during
+        // prepare step to skip the intermediate surface if the
+        // clip node doesn't affect the stacking context rect.
+        if stacking_context.participating_in_3d_context {
+            // TODO(gw): For now, as soon as this picture is in
+            //           a 3D context, we draw it to an intermediate
+            //           surface and apply plane splitting. However,
+            //           there is a large optimization opportunity here.
+            //           During culling, we can check if there is actually
+            //           perspective present, and skip the plane splitting
+            //           completely when that is not the case.
+            composite_mode = Some(PictureCompositeMode::Blit);
+        }
+
+        let prim_key = PrimitiveKey::new(true);
+        let prim_data_handle = self.resources.prim_interner.intern(&prim_key);
+
+        // Add picture for this actual stacking context contents to render into.
+        let leaf_picture = PicturePrimitive::new_image(
+            self.picture_id_generator.next(),
+            composite_mode,
+            stacking_context.participating_in_3d_context,
+            stacking_context.pipeline_id,
+            stacking_context.frame_output_pipeline_id,
+            true,
+            stacking_context.requested_raster_space,
+            stacking_context.normal_primitives,
+        );
+
+        // Create a brush primitive that draws this picture.
+        let leaf_prim = BrushPrimitive::new_picture(leaf_picture);
+
+        // Add the brush to the parent picture.
+        let leaf_prim_index = self.prim_store.add_primitive(
+            &LayoutRect::zero(),
+            &max_clip,
+            PrimitiveContainer::Brush(leaf_prim),
+        );
+
+        // Create a chain of pictures based on presence of filters,
+        // mix-blend-mode and/or 3d rendering context containers.
+        let mut current_prim_index = leaf_prim_index;
+
+        // For each filter, create a new image with that composite mode.
+        for filter in &stacking_context.composite_ops.filters {
+            let filter = filter.sanitize();
 
-        // Run the optimize pass on each picture in the chain,
-        // to see if we can collapse opacity and avoid drawing
-        // to an off-screen surface.
-        for i in sc.leaf_prim_index.0 .. sc.root_prim_index.0 + 1 {
-            let prim_index = PrimitiveIndex(i);
-            self.prim_store.optimize_picture_if_possible(prim_index);
+            let filter_picture = PicturePrimitive::new_image(
+                self.picture_id_generator.next(),
+                Some(PictureCompositeMode::Filter(filter)),
+                false,
+                stacking_context.pipeline_id,
+                None,
+                true,
+                stacking_context.requested_raster_space,
+                vec![
+                    PrimitiveInstance::new(
+                        current_prim_index,
+                        prim_data_handle,
+                        stacking_context.clip_chain_id,
+                        stacking_context.spatial_node_index,
+                    ),
+                ],
+            );
+
+            let filter_prim = BrushPrimitive::new_picture(filter_picture);
+
+            current_prim_index = self.prim_store.add_primitive(
+                &LayoutRect::zero(),
+                &max_clip,
+                PrimitiveContainer::Brush(filter_prim),
+            );
+
+            // Run the optimize pass on this picture, to see if we can
+            // collapse opacity and avoid drawing to an off-screen surface.
+            self.prim_store.optimize_picture_if_possible(current_prim_index);
+        }
+
+        // Same for mix-blend-mode.
+        if let Some(mix_blend_mode) = stacking_context.composite_ops.mix_blend_mode {
+            let blend_picture = PicturePrimitive::new_image(
+                self.picture_id_generator.next(),
+                Some(PictureCompositeMode::MixBlend(mix_blend_mode)),
+                false,
+                stacking_context.pipeline_id,
+                None,
+                true,
+                stacking_context.requested_raster_space,
+                vec![
+                    PrimitiveInstance::new(
+                        current_prim_index,
+                        prim_data_handle,
+                        stacking_context.clip_chain_id,
+                        stacking_context.spatial_node_index,
+                    ),
+                ],
+            );
+
+            let blend_prim = BrushPrimitive::new_picture(blend_picture);
+
+            current_prim_index = self.prim_store.add_primitive(
+                &LayoutRect::zero(),
+                &max_clip,
+                PrimitiveContainer::Brush(blend_prim),
+            );
+        }
+
+        if stacking_context.establishes_3d_context {
+            // If establishing a 3d context, we need to add a picture
+            // that will be the container for all the planes and any
+            // un-transformed content.
+            let mut prims = vec![
+                PrimitiveInstance::new(
+                    current_prim_index,
+                    prim_data_handle,
+                    stacking_context.clip_chain_id,
+                    stacking_context.spatial_node_index,
+                ),
+            ];
+            prims.extend(stacking_context.preserve3d_primitives);
+
+            let container_picture = PicturePrimitive::new_image(
+                self.picture_id_generator.next(),
+                None,
+                false,
+                stacking_context.pipeline_id,
+                None,
+                true,
+                stacking_context.requested_raster_space,
+                prims,
+            );
+
+            let container_prim = BrushPrimitive::new_picture(container_picture);
+
+            current_prim_index = self.prim_store.add_primitive(
+                &LayoutRect::zero(),
+                &max_clip,
+                PrimitiveContainer::Brush(container_prim),
+            );
+        } else {
+            debug_assert!(stacking_context.preserve3d_primitives.is_empty());
         }
 
         if self.sc_stack.is_empty() {
             // This must be the root stacking context
+            self.root_prim_index = current_prim_index;
             return;
         }
 
-        let parent_prim_index = if !sc.establishes_3d_context && sc.participating_in_3d_context {
+        let sc_count = self.sc_stack.len();
+        let prim_instance = PrimitiveInstance::new(
+            current_prim_index,
+            prim_data_handle,
+            stacking_context.clip_chain_id,
+            stacking_context.spatial_node_index,
+        );
+
+        if !stacking_context.establishes_3d_context && stacking_context.participating_in_3d_context {
             // If we're in a 3D context, we will parent the picture
             // to the first stacking context we find that is a
             // 3D rendering context container. This follows the spec
             // by hoisting these items out into the same 3D context
             // for plane splitting.
-            self.sc_stack
+            let parent_index = self.sc_stack
                 .iter()
-                .rev()
-                .find(|sc| sc.establishes_3d_context)
-                .map(|sc| sc.root_prim_index)
-                .unwrap()
+                .rposition(|sc| sc.establishes_3d_context)
+                .unwrap();
+
+            let parent_stacking_context = &mut self.sc_stack[parent_index];
+            parent_stacking_context.preserve3d_primitives.push(prim_instance);
+
         } else {
-            self.sc_stack.last().unwrap().leaf_prim_index
+            let parent_stacking_context = self.sc_stack.last_mut().unwrap();
+
+            // If we have a mix-blend-mode, and we aren't the primary framebuffer,
+            // the stacking context needs to be isolated to blend correctly as per
+            // the CSS spec.
+            // If not already isolated for some other reason,
+            // make this picture as isolated.
+            if stacking_context.composite_ops.mix_blend_mode.is_some() &&
+               sc_count > 2 {
+                parent_stacking_context.should_isolate = true;
+            }
+
+            parent_stacking_context.normal_primitives.push(prim_instance);
         };
 
-        let parent_pic = self.prim_store.get_pic_mut(parent_prim_index);
-        parent_pic.add_primitive(sc.root_prim_index);
-
-        // If we have a mix-blend-mode, and we aren't the primary framebuffer,
-        // the stacking context needs to be isolated to blend correctly as per
-        // the CSS spec.
-        // If not already isolated for some other reason,
-        // make this picture as isolated.
-        if sc.has_mix_blend_mode &&
-           self.sc_stack.len() > 2 &&
-           parent_pic.requested_composite_mode.is_none() {
-            parent_pic.requested_composite_mode = Some(PictureCompositeMode::Blit);
-        }
-
         assert!(
-            self.shadow_stack.is_empty(),
-            "Found unpopped text shadows when popping stacking context!"
+            self.pending_shadow_items.is_empty(),
+            "Found unpopped shadows when popping stacking context!"
         );
     }
 
     pub fn push_reference_frame(
         &mut self,
         reference_frame_id: ClipId,
         parent_id: Option<ClipId>,
         pipeline_id: PipelineId,
@@ -1395,79 +1416,172 @@ impl<'a> DisplayListFlattener<'a> {
         node_index
     }
 
     pub fn push_shadow(
         &mut self,
         shadow: Shadow,
         clip_and_scroll: ScrollNodeAndClipChain,
     ) {
-        let pipeline_id = self.sc_stack.last().unwrap().pipeline_id;
-        let max_clip = LayoutRect::max_rect();
-
-        // Quote from https://drafts.csswg.org/css-backgrounds-3/#shadow-blur
-        // "the image that would be generated by applying to the shadow a
-        // Gaussian blur with a standard deviation equal to half the blur radius."
-        let std_deviation = shadow.blur_radius * 0.5;
-
-        // If the shadow has no blur, any elements will get directly rendered
-        // into the parent picture surface, instead of allocating and drawing
-        // into an intermediate surface. In this case, we will need to apply
-        // the local clip rect to primitives.
-        let is_passthrough = shadow.blur_radius == 0.0;
-
-        // shadows always rasterize in local space.
-        // TODO(gw): expose API for clients to specify a raster scale
-        let raster_space = if is_passthrough {
-            let parent_pic_prim_index = self.sc_stack.last().unwrap().leaf_prim_index;
-            self.prim_store
-                .get_pic(parent_pic_prim_index)
-                .requested_raster_space
-        } else {
-            RasterSpace::Local(1.0)
-        };
-
-        // Create a picture that the shadow primitives will be added to. If the
-        // blur radius is 0, the code in Picture::prepare_for_render will
-        // detect this and mark the picture to be drawn directly into the
-        // parent picture, which avoids an intermediate surface and blur.
-        let blur_filter = FilterOp::Blur(std_deviation).sanitize();
-        let shadow_pic = PicturePrimitive::new_image(
-            self.picture_id_generator.next(),
-            Some(PictureCompositeMode::Filter(blur_filter)),
-            false,
-            pipeline_id,
-            None,
-            is_passthrough,
-            raster_space,
-        );
-
-        // Create the primitive to draw the shadow picture into the scene.
-        let shadow_prim = BrushPrimitive::new_picture(shadow_pic);
-        let shadow_prim_index = self.prim_store.add_primitive(
-            &LayoutRect::zero(),
-            &max_clip,
-            true,
-            clip_and_scroll.clip_chain_id,
-            clip_and_scroll.spatial_node_index,
-            None,
-            PrimitiveContainer::Brush(shadow_prim),
-        );
-
-        // Add the shadow primitive. This must be done before pushing this
-        // picture on to the shadow stack, to avoid infinite recursion!
-        self.add_primitive_to_draw_list(
-            shadow_prim_index,
-        );
-        self.shadow_stack.push((shadow, shadow_prim_index));
+        // Store this shadow in the pending list, for processing
+        // during pop_all_shadows.
+        self.pending_shadow_items.push_back(ShadowItem::Shadow(PendingShadow {
+            shadow,
+            clip_and_scroll,
+        }));
     }
 
     pub fn pop_all_shadows(&mut self) {
-        assert!(self.shadow_stack.len() > 0, "popped shadows, but none were present");
-        self.shadow_stack.clear();
+        assert!(!self.pending_shadow_items.is_empty(), "popped shadows, but none were present");
+
+        let pipeline_id = self.sc_stack.last().unwrap().pipeline_id;
+        let max_clip = LayoutRect::max_rect();
+        let mut items = mem::replace(&mut self.pending_shadow_items, VecDeque::new());
+
+        //
+        // The pending_shadow_items queue contains a list of shadows and primitives
+        // that were pushed during the active shadow context. To process these, we:
+        //
+        // Iterate the list, popping an item from the front each iteration.
+        //
+        // If the item is a shadow:
+        //      - Create a shadow picture primitive.
+        //      - Add *any* primitives that remain in the item list to this shadow.
+        // If the item is a primitive:
+        //      - Add that primitive as a normal item (if alpha > 0)
+        //
+
+        while let Some(item) = items.pop_front() {
+            match item {
+                ShadowItem::Shadow(pending_shadow) => {
+                    // Quote from https://drafts.csswg.org/css-backgrounds-3/#shadow-blur
+                    // "the image that would be generated by applying to the shadow a
+                    // Gaussian blur with a standard deviation equal to half the blur radius."
+                    let std_deviation = pending_shadow.shadow.blur_radius * 0.5;
+
+                    // If the shadow has no blur, any elements will get directly rendered
+                    // into the parent picture surface, instead of allocating and drawing
+                    // into an intermediate surface. In this case, we will need to apply
+                    // the local clip rect to primitives.
+                    let is_passthrough = pending_shadow.shadow.blur_radius == 0.0;
+
+                    // shadows always rasterize in local space.
+                    // TODO(gw): expose API for clients to specify a raster scale
+                    let raster_space = if is_passthrough {
+                        self.sc_stack.last().unwrap().requested_raster_space
+                    } else {
+                        RasterSpace::Local(1.0)
+                    };
+
+                    // Add any primitives that come after this shadow in the item
+                    // list to this shadow.
+                    let mut prims = Vec::new();
+
+                    for item in &items {
+                        if let ShadowItem::Primitive(ref pending_primitive) = item {
+                            // Offset the local rect and clip rect by the shadow offset.
+                            let mut info = pending_primitive.info.clone();
+                            info.rect = info.rect.translate(&pending_shadow.shadow.offset);
+                            info.clip_rect = info.clip_rect.translate(&pending_shadow.shadow.offset);
+
+                            // Offset any local clip sources by the shadow offset.
+                            let clip_items: Vec<ClipItemKey> = pending_primitive
+                                .clip_items
+                                .iter()
+                                .map(|cs| cs.offset(&pending_shadow.shadow.offset))
+                                .collect();
+                            let clip_chain_id = self.build_clip_chain(
+                                clip_items,
+                                pending_primitive.clip_and_scroll.spatial_node_index,
+                                pending_primitive.clip_and_scroll.clip_chain_id,
+                            );
+
+                            // Construct and add a primitive for the given shadow.
+                            let shadow_prim_instance = self.create_primitive(
+                                &info,
+                                clip_chain_id,
+                                pending_primitive.clip_and_scroll.spatial_node_index,
+                                pending_primitive.container.create_shadow(&pending_shadow.shadow),
+                            );
+
+                            // Add the new primitive to the shadow picture.
+                            prims.push(shadow_prim_instance);
+                        }
+                    }
+
+                    // No point in adding a shadow here if there were no primitives
+                    // added to the shadow.
+                    if !prims.is_empty() {
+                        // Create a picture that the shadow primitives will be added to. If the
+                        // blur radius is 0, the code in Picture::prepare_for_render will
+                        // detect this and mark the picture to be drawn directly into the
+                        // parent picture, which avoids an intermediate surface and blur.
+                        let blur_filter = FilterOp::Blur(std_deviation).sanitize();
+                        let mut shadow_pic = PicturePrimitive::new_image(
+                            self.picture_id_generator.next(),
+                            Some(PictureCompositeMode::Filter(blur_filter)),
+                            false,
+                            pipeline_id,
+                            None,
+                            is_passthrough,
+                            raster_space,
+                            prims,
+                        );
+
+                        // Create the primitive to draw the shadow picture into the scene.
+                        let shadow_prim = BrushPrimitive::new_picture(shadow_pic);
+                        let shadow_prim_index = self.prim_store.add_primitive(
+                            &LayoutRect::zero(),
+                            &max_clip,
+                            PrimitiveContainer::Brush(shadow_prim),
+                        );
+
+                        let shadow_prim_key = PrimitiveKey::new(true);
+                        let shadow_prim_data_handle = self.resources.prim_interner.intern(&shadow_prim_key);
+
+                        let shadow_prim_instance = PrimitiveInstance::new(
+                            shadow_prim_index,
+                            shadow_prim_data_handle,
+                            pending_shadow.clip_and_scroll.clip_chain_id,
+                            pending_shadow.clip_and_scroll.spatial_node_index,
+                        );
+
+                        // Add the shadow primitive. This must be done before pushing this
+                        // picture on to the shadow stack, to avoid infinite recursion!
+                        self.add_primitive_to_draw_list(shadow_prim_instance);
+                    }
+                }
+                ShadowItem::Primitive(pending_primitive) => {
+                    // For a normal primitive, if it has alpha > 0, then we add this
+                    // as a normal primitive to the parent picture.
+                    if pending_primitive.container.is_visible() {
+                        let clip_chain_id = self.build_clip_chain(
+                            pending_primitive.clip_items,
+                            pending_primitive.clip_and_scroll.spatial_node_index,
+                            pending_primitive.clip_and_scroll.clip_chain_id,
+                        );
+                        let prim_instance = self.create_primitive(
+                            &pending_primitive.info,
+                            clip_chain_id,
+                            pending_primitive.clip_and_scroll.spatial_node_index,
+                            pending_primitive.container,
+                        );
+                        if cfg!(debug_assertions) && ChasePrimitive::LocalRect(pending_primitive.info.rect) == self.config.chase_primitive {
+                            println!("Chasing {:?} by local rect", prim_instance.prim_index);
+                            self.prim_store.chase_id = Some(prim_instance.prim_index);
+                        }
+                        self.add_primitive_to_hit_testing_list(&pending_primitive.info, pending_primitive.clip_and_scroll);
+                        self.add_primitive_to_draw_list(prim_instance);
+                    }
+                }
+            }
+        }
+
+        debug_assert!(items.is_empty());
+        self.pending_shadow_items = items;
     }
 
     pub fn add_solid_rectangle(
         &mut self,
         clip_and_scroll: ScrollNodeAndClipChain,
         info: &LayoutPrimitiveInfo,
         color: ColorF,
         segments: Option<BrushSegmentDescriptor>,
@@ -2043,26 +2157,78 @@ impl<'a> DisplayListFlattener<'a> {
         self.map_clip_and_scroll(&ClipAndScrollInfo::simple(*id))
     }
 }
 
 /// Properties of a stacking context that are maintained
 /// during creation of the scene. These structures are
 /// not persisted after the initial scene build.
 struct FlattenedStackingContext {
+    /// The list of un-transformed content being
+    /// added to this stacking context.
+    normal_primitives: Vec<PrimitiveInstance>,
+
+    /// The list of preserve-3d primitives that
+    /// are being hoisted to this stacking context
+    /// (implies establishes_3d_context).
+    preserve3d_primitives: Vec<PrimitiveInstance>,
+
+    /// Whether or not the caller wants this drawn in
+    /// screen space (quality) or local space (performance)
+    requested_raster_space: RasterSpace,
+
+    /// The positioning node for this stacking context
+    spatial_node_index: SpatialNodeIndex,
+
+    /// The clip chain for this stacking context
+    clip_chain_id: ClipChainId,
+
+    /// If set, this should be provided to caller
+    /// as an output texture.
+    frame_output_pipeline_id: Option<PipelineId>,
+
+    /// The list of filters / mix-blend-mode for this
+    /// stacking context.
+    composite_ops: CompositeOps,
+
+    /// If true, this stacking context should be
+    /// isolated by forcing an off-screen surface.
+    should_isolate: bool,
+
     /// Pipeline this stacking context belongs to.
     pipeline_id: PipelineId,
 
     /// If true, visible when backface is visible.
     is_backface_visible: bool,
 
     /// CSS transform-style property.
     transform_style: TransformStyle,
 
-    root_prim_index: PrimitiveIndex,
-    leaf_prim_index: PrimitiveIndex,
-
     /// If true, this stacking context establishes a new
     /// 3d rendering context.
     establishes_3d_context: bool,
+
+    /// If true, this stacking context is part of a
+    /// surrounding 3d rendering context.
     participating_in_3d_context: bool,
-    has_mix_blend_mode: bool,
+}
+
+/// A primitive that is added while a shadow context is
+/// active is stored as a pending primitive and only
+/// added to pictures during pop_all_shadows.
+struct PendingPrimitive {
+    clip_and_scroll: ScrollNodeAndClipChain,
+    info: LayoutPrimitiveInfo,
+    clip_items: Vec<ClipItemKey>,
+    container: PrimitiveContainer,
 }
+
+/// As shadows are pushed, they are stored as pending
+/// shadows, and handled at once during pop_all_shadows.
+struct PendingShadow {
+    shadow: Shadow,
+    clip_and_scroll: ScrollNodeAndClipChain,
+}
+
+enum ShadowItem {
+    Shadow(PendingShadow),
+    Primitive(PendingPrimitive),
+}
--- a/gfx/webrender/src/frame_builder.rs
+++ b/gfx/webrender/src/frame_builder.rs
@@ -53,16 +53,17 @@ pub struct FrameBuilderConfig {
 }
 
 /// A builder structure for `tiling::Frame`
 pub struct FrameBuilder {
     screen_rect: DeviceUintRect,
     background_color: Option<ColorF>,
     window_size: DeviceUintSize,
     scene_id: u64,
+    root_prim_index: PrimitiveIndex,
     pub prim_store: PrimitiveStore,
     pub clip_store: ClipStore,
     pub hit_testing_runs: Vec<HitTestingRun>,
     pub config: FrameBuilderConfig,
 }
 
 pub struct FrameBuildingContext<'a> {
     pub scene_id: u64,
@@ -132,16 +133,17 @@ impl FrameBuilder {
         FrameBuilder {
             hit_testing_runs: Vec::new(),
             prim_store: PrimitiveStore::new(),
             clip_store: ClipStore::new(),
             screen_rect: DeviceUintRect::zero(),
             window_size: DeviceUintSize::zero(),
             background_color: None,
             scene_id: 0,
+            root_prim_index: PrimitiveIndex(0),
             config: FrameBuilderConfig {
                 default_font_render_mode: FontRenderMode::Mono,
                 dual_source_blending_is_enabled: true,
                 dual_source_blending_is_supported: false,
                 chase_primitive: ChasePrimitive::Nothing,
             },
         }
     }
@@ -152,16 +154,17 @@ impl FrameBuilder {
         window_size: DeviceUintSize,
         scene_id: u64,
         flattener: DisplayListFlattener,
     ) -> Self {
         FrameBuilder {
             hit_testing_runs: flattener.hit_testing_runs,
             prim_store: flattener.prim_store,
             clip_store: flattener.clip_store,
+            root_prim_index: flattener.root_prim_index,
             screen_rect,
             background_color,
             window_size,
             scene_id,
             config: flattener.config,
         }
     }
 
@@ -181,20 +184,17 @@ impl FrameBuilder {
         transform_palette: &mut TransformPalette,
         resources: &mut FrameResources,
     ) -> Option<RenderTaskId> {
         profile_scope!("cull");
 
         if self.prim_store.primitives.is_empty() {
             return None
         }
-        self.prim_store.reset_prim_visibility();
 
-        // The root picture is always the first one added.
-        let root_prim_index = PrimitiveIndex(0);
         let root_spatial_node_index = clip_scroll_tree.root_reference_frame_index();
 
         const MAX_CLIP_COORD: f32 = 1.0e9;
 
         let world_rect = (self.screen_rect.to_f32() / device_pixel_scale).round_out();
 
         let frame_context = FrameBuildingContext {
             scene_id: self.scene_id,
@@ -223,17 +223,17 @@ impl FrameBuilder {
 
         let prim_context = PrimitiveContext::new(
             &clip_scroll_tree.spatial_nodes[root_spatial_node_index.0],
             root_spatial_node_index,
         );
 
         let (pic_context, mut pic_state, mut instances) = self
             .prim_store
-            .get_pic_mut(root_prim_index)
+            .get_pic_mut(self.root_prim_index)
             .take_context(
                 &prim_context,
                 root_spatial_node_index,
                 root_spatial_node_index,
                 true,
                 &mut frame_state,
                 &frame_context,
                 false,
@@ -248,31 +248,31 @@ impl FrameBuilder {
             &mut pic_state,
             &frame_context,
             &mut frame_state,
             &mut pic_rect,
         );
 
         let pic = self
             .prim_store
-            .get_pic_mut(root_prim_index);
+            .get_pic_mut(self.root_prim_index);
         pic.restore_context(
             instances,
             pic_context,
             pic_state,
             Some(pic_rect),
             &mut frame_state,
         );
 
         let pic_state = pic.take_state();
 
         let root_render_task = RenderTask::new_picture(
             RenderTaskLocation::Fixed(self.screen_rect.to_i32()),
             self.screen_rect.size.to_f32(),
-            root_prim_index,
+            self.root_prim_index,
             DeviceIntPoint::zero(),
             pic_state.tasks,
             UvRectKind::Rect,
             root_spatial_node_index,
         );
 
         let render_task_id = frame_state.render_tasks.add(root_render_task);
         pic.raster_config = Some(RasterConfig {
--- a/gfx/webrender/src/glyph_cache.rs
+++ b/gfx/webrender/src/glyph_cache.rs
@@ -1,15 +1,15 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #[cfg(feature = "pathfinder")]
 use api::DeviceIntPoint;
-use glyph_rasterizer::{FontInstance, GlyphFormat, GlyphKey};
+use glyph_rasterizer::{FontInstance, GlyphFormat, GlyphKey, GlyphRasterizer};
 use internal_types::FastHashMap;
 use render_task::RenderTaskCache;
 #[cfg(feature = "pathfinder")]
 use render_task::RenderTaskCacheKey;
 use resource_cache::ResourceClassCache;
 use std::sync::Arc;
 use texture_cache::{EvictionNotice, TextureCache};
 #[cfg(not(feature = "pathfinder"))]
@@ -138,36 +138,43 @@ impl GlyphCache {
 
             cache.clear();
             false
         })
     }
 
     // Clear out evicted entries from glyph key caches and, if possible,
     // also remove entirely any subsequently empty glyph key caches.
-    fn clear_evicted(&mut self,
-                     texture_cache: &TextureCache,
-                     render_task_cache: &RenderTaskCache) {
-        self.glyph_key_caches.retain(|_, cache| {
+    fn clear_evicted(
+        &mut self,
+        texture_cache: &TextureCache,
+        render_task_cache: &RenderTaskCache,
+        glyph_rasterizer: &mut GlyphRasterizer,
+    ) {
+        self.glyph_key_caches.retain(|key, cache| {
             // Scan for any glyph key caches that have evictions.
             if cache.eviction_notice().check() {
                 // If there are evictions, filter out any glyphs evicted from the
                 // texture cache from the glyph key cache.
                 let mut keep_cache = false;
                 cache.retain(|_, entry| {
                     let keep_glyph = entry.is_allocated(texture_cache, render_task_cache);
                     keep_cache |= keep_glyph;
                     keep_glyph
                 });
+                if !keep_cache {
+                    glyph_rasterizer.delete_font_instance(key);
+                }
                 // Only keep the glyph key cache if it still has valid glyphs.
                 keep_cache
             } else {
                 true
             }
         });
     }
 
     pub fn begin_frame(&mut self,
                        texture_cache: &TextureCache,
-                       render_task_cache: &RenderTaskCache) {
-        self.clear_evicted(texture_cache, render_task_cache);
+                       render_task_cache: &RenderTaskCache,
+                       glyph_rasterizer: &mut GlyphRasterizer) {
+        self.clear_evicted(texture_cache, render_task_cache, glyph_rasterizer);
     }
 }
--- a/gfx/webrender/src/glyph_rasterizer/mod.rs
+++ b/gfx/webrender/src/glyph_rasterizer/mod.rs
@@ -540,16 +540,18 @@ pub struct GlyphRasterizer {
     #[allow(dead_code)]
     glyph_tx: Sender<GlyphRasterJobs>,
 
     // We defer removing fonts to the end of the frame so that:
     // - this work is done outside of the critical path,
     // - we don't have to worry about the ordering of events if a font is used on
     //   a frame where it is used (although it seems unlikely).
     fonts_to_remove: Vec<FontKey>,
+    // Defer removal of font instances, as for fonts.
+    font_instances_to_remove: Vec<FontInstance>,
 
     #[allow(dead_code)]
     next_gpu_glyph_cache_key: GpuGlyphCacheKey,
 }
 
 impl GlyphRasterizer {
     pub fn new(workers: Arc<ThreadPool>) -> Result<Self, ResourceCacheError> {
         let (glyph_tx, glyph_rx) = channel();
@@ -575,16 +577,17 @@ impl GlyphRasterizer {
 
         Ok(GlyphRasterizer {
             font_contexts: Arc::new(font_context),
             pending_glyphs: 0,
             glyph_rx,
             glyph_tx,
             workers,
             fonts_to_remove: Vec::new(),
+            font_instances_to_remove: Vec::new(),
             next_gpu_glyph_cache_key: GpuGlyphCacheKey(0),
         })
     }
 
     pub fn add_font(&mut self, font_key: FontKey, template: FontTemplate) {
         #[cfg(feature = "pathfinder")]
         self.add_font_to_pathfinder(&font_key, &template);
 
@@ -592,16 +595,20 @@ impl GlyphRasterizer {
             context.add_font(&font_key, &template);
         });
     }
 
     pub fn delete_font(&mut self, font_key: FontKey) {
         self.fonts_to_remove.push(font_key);
     }
 
+    pub fn delete_font_instance(&mut self, instance: &FontInstance) {
+        self.font_instances_to_remove.push(instance.clone());
+    }
+
     pub fn prepare_font(&self, font: &mut FontInstance) {
         FontContext::prepare_font(font);
     }
 
     pub fn get_glyph_dimensions(
         &mut self,
         font: &FontInstance,
         glyph_index: GlyphIndex,
@@ -619,33 +626,38 @@ impl GlyphRasterizer {
 
     pub fn get_glyph_index(&mut self, font_key: FontKey, ch: char) -> Option<u32> {
         self.font_contexts
             .lock_shared_context()
             .get_glyph_index(font_key, ch)
     }
 
     fn remove_dead_fonts(&mut self) {
-        if self.fonts_to_remove.is_empty() {
+        if self.fonts_to_remove.is_empty() && self.font_instances_to_remove.is_empty() {
             return
         }
 
         let fonts_to_remove = mem::replace(&mut self.fonts_to_remove, Vec::new());
+        let font_instances_to_remove = mem::replace(& mut self.font_instances_to_remove, Vec::new());
         self.font_contexts.for_each(move |mut context| {
             for font_key in &fonts_to_remove {
                 context.delete_font(font_key);
             }
+            for instance in &font_instances_to_remove {
+                context.delete_font_instance(instance);
+            }
         });
     }
 
     #[cfg(feature = "replay")]
     pub fn reset(&mut self) {
         //TODO: any signals need to be sent to the workers?
         self.pending_glyphs = 0;
         self.fonts_to_remove.clear();
+        self.font_instances_to_remove.clear();
     }
 }
 
 trait AddFont {
     fn add_font(&mut self, font_key: &FontKey, template: &FontTemplate);
 }
 
 impl AddFont for FontContext {
--- a/gfx/webrender/src/internal_types.rs
+++ b/gfx/webrender/src/internal_types.rs
@@ -145,16 +145,17 @@ pub enum DebugOutput {
     FetchDocuments(String),
     FetchClipScrollTree(String),
     #[cfg(feature = "capture")]
     SaveCapture(CaptureConfig, Vec<ExternalCaptureImage>),
     #[cfg(feature = "replay")]
     LoadCapture(PathBuf, Vec<PlainExternalImage>),
 }
 
+#[allow(dead_code)]
 pub enum ResultMsg {
     DebugCommand(DebugCommand),
     DebugOutput(DebugOutput),
     RefreshShader(PathBuf),
     UpdateGpuCache(GpuCacheUpdateList),
     UpdateResources {
         updates: TextureUpdateList,
         memory_pressure: bool,
--- a/gfx/webrender/src/lib.rs
+++ b/gfx/webrender/src/lib.rs
@@ -177,16 +177,18 @@ extern crate base64;
 #[cfg(all(feature = "capture", feature = "png"))]
 extern crate png;
 
 pub extern crate webrender_api;
 
 #[doc(hidden)]
 pub use device::{build_shader_strings, ReadPixelsFormat, UploadMethod, VertexUsageHint};
 pub use device::{ProgramBinary, ProgramCache, ProgramCacheObserver, ProgramSources};
+pub use device::{Device};
 pub use frame_builder::ChasePrimitive;
 pub use renderer::{AsyncPropertySampler, CpuProfile, DebugFlags, OutputImageHandler, RendererKind};
 pub use renderer::{ExternalImage, ExternalImageHandler, ExternalImageSource, GpuProfile};
 pub use renderer::{GraphicsApi, GraphicsApiInfo, PipelineInfo, Renderer, RendererOptions};
 pub use renderer::{RendererStats, SceneBuilderHooks, ThreadListener, ShaderPrecacheFlags};
 pub use renderer::MAX_VERTEX_TEXTURE_WIDTH;
+pub use shade::{Shaders, WrShaders};
 pub use webrender_api as api;
 pub use resource_cache::intersect_for_tile;
--- a/gfx/webrender/src/picture.rs
+++ b/gfx/webrender/src/picture.rs
@@ -9,17 +9,17 @@ use api::{PicturePixel, RasterPixel, Wor
 use box_shadow::{BLUR_SAMPLE_SCALE};
 use clip::ClipNodeCollector;
 use clip_scroll_tree::{ROOT_SPATIAL_NODE_INDEX, SpatialNodeIndex};
 use euclid::TypedScale;
 use frame_builder::{FrameBuildingContext, FrameBuildingState, PictureState};
 use frame_builder::{PictureContext, PrimitiveContext};
 use gpu_cache::{GpuCacheHandle};
 use gpu_types::UvRectKind;
-use prim_store::{PrimitiveIndex, PrimitiveInstance, SpaceMapper};
+use prim_store::{PrimitiveInstance, SpaceMapper};
 use prim_store::{PrimitiveMetadata, get_raster_rects};
 use render_task::{ClearMode, RenderTask, RenderTaskCacheEntryHandle};
 use render_task::{RenderTaskCacheKey, RenderTaskCacheKeyKind, RenderTaskId, RenderTaskLocation};
 use scene::{FilterOpHelpers, SceneProperties};
 use std::mem;
 use tiling::RenderTargetKind;
 use util::{TransformedRectKind, MatrixHelpers, MaxRect};
 
@@ -217,19 +217,20 @@ impl PicturePrimitive {
     pub fn new_image(
         id: PictureId,
         requested_composite_mode: Option<PictureCompositeMode>,
         is_in_3d_context: bool,
         pipeline_id: PipelineId,
         frame_output_pipeline_id: Option<PipelineId>,
         apply_local_clip_rect: bool,
         requested_raster_space: RasterSpace,
+        prim_instances: Vec<PrimitiveInstance>,
     ) -> Self {
         PicturePrimitive {
-            prim_instances: Vec::new(),
+            prim_instances,
             state: None,
             secondary_render_task_id: None,
             requested_composite_mode,
             raster_config: None,
             is_in_3d_context,
             frame_output_pipeline_id,
             extra_gpu_data_handle: GpuCacheHandle::new(),
             apply_local_clip_rect,
@@ -373,26 +374,16 @@ impl PicturePrimitive {
             raster_space,
         };
 
         let instances = mem::replace(&mut self.prim_instances, Vec::new());
 
         Some((context, state, instances))
     }
 
-    pub fn add_primitive(
-        &mut self,
-        prim_index: PrimitiveIndex,
-    ) {
-        self.prim_instances.push(PrimitiveInstance {
-            prim_index,
-            combined_local_clip_rect: LayoutRect::zero(),
-        });
-    }
-
     pub fn restore_context(
         &mut self,
         prim_instances: Vec<PrimitiveInstance>,
         context: PictureContext,
         state: PictureState,
         local_rect: Option<PictureRect>,
         frame_state: &mut FrameBuildingState,
     ) -> (LayoutRect, Option<ClipNodeCollector>) {
@@ -443,39 +434,39 @@ impl PicturePrimitive {
     }
 
     pub fn take_state(&mut self) -> PictureState {
         self.state.take().expect("bug: no state present!")
     }
 
     pub fn prepare_for_render(
         &mut self,
-        prim_index: PrimitiveIndex,
-        prim_metadata: &mut PrimitiveMetadata,
+        prim_instance: &PrimitiveInstance,
+        prim_metadata: &PrimitiveMetadata,
         pic_state: &mut PictureState,
         frame_context: &FrameBuildingContext,
         frame_state: &mut FrameBuildingState,
     ) -> bool {
         let mut pic_state_for_children = self.take_state();
 
         match self.raster_config {
             Some(ref mut raster_config) => {
                 let (map_raster_to_world, map_pic_to_raster) = create_raster_mappers(
-                    prim_metadata.spatial_node_index,
+                    prim_instance.spatial_node_index,
                     raster_config.raster_spatial_node_index,
                     frame_context,
                 );
 
                 let pic_rect = PictureRect::from_untyped(&prim_metadata.local_rect.to_untyped());
 
                 let (clipped, unclipped, transform) = match get_raster_rects(
                     pic_rect,
                     &map_pic_to_raster,
                     &map_raster_to_world,
-                    prim_metadata.clipped_world_rect.expect("bug1"),
+                    prim_instance.clipped_world_rect.expect("bug1"),
                     frame_context.device_pixel_scale,
                 ) {
                     Some(info) => info,
                     None => return false,
                 };
 
                 // TODO(gw): Almost all of the Picture types below use extra_gpu_cache_data
                 //           to store the same type of data. The exception is the filter
@@ -515,17 +506,17 @@ impl PicturePrimitive {
                         // anyway. In the future we should relax this a bit, so that we can
                         // cache tasks with complex coordinate systems if we detect the
                         // relevant transforms haven't changed from frame to frame.
                         let surface = if pic_state_for_children.has_non_root_coord_system ||
                                          !pic_state_for_children.is_cacheable {
                             let picture_task = RenderTask::new_picture(
                                 RenderTaskLocation::Dynamic(None, device_rect.size),
                                 unclipped.size,
-                                prim_index,
+                                prim_instance.prim_index,
                                 device_rect.origin,
                                 pic_state_for_children.tasks,
                                 uv_rect_kind,
                                 pic_state_for_children.raster_spatial_node_index,
                             );
 
                             let picture_task_id = frame_state.render_tasks.add(picture_task);
 
@@ -573,17 +564,17 @@ impl PicturePrimitive {
                                 None,
                                 false,
                                 |render_tasks| {
                                     let child_tasks = mem::replace(&mut pic_state_for_children.tasks, Vec::new());
 
                                     let picture_task = RenderTask::new_picture(
                                         RenderTaskLocation::Dynamic(None, device_rect.size),
                                         unclipped.size,
-                                        prim_index,
+                                        prim_instance.prim_index,
                                         device_rect.origin,
                                         child_tasks,
                                         uv_rect_kind,
                                         pic_state_for_children.raster_spatial_node_index,
                                     );
 
                                     let picture_task_id = render_tasks.add(picture_task);
 
@@ -630,17 +621,17 @@ impl PicturePrimitive {
                             &transform,
                             &device_rect,
                             frame_context.device_pixel_scale,
                         );
 
                         let mut picture_task = RenderTask::new_picture(
                             RenderTaskLocation::Dynamic(None, device_rect.size),
                             unclipped.size,
-                            prim_index,
+                            prim_instance.prim_index,
                             device_rect.origin,
                             pic_state_for_children.tasks,
                             uv_rect_kind,
                             pic_state_for_children.raster_spatial_node_index,
                         );
                         picture_task.mark_for_saving();
 
                         let picture_task_id = frame_state.render_tasks.add(picture_task);
@@ -701,17 +692,17 @@ impl PicturePrimitive {
                             &transform,
                             &clipped,
                             frame_context.device_pixel_scale,
                         );
 
                         let picture_task = RenderTask::new_picture(
                             RenderTaskLocation::Dynamic(None, clipped.size),
                             unclipped.size,
-                            prim_index,
+                            prim_instance.prim_index,
                             clipped.origin,
                             pic_state_for_children.tasks,
                             uv_rect_kind,
                             pic_state_for_children.raster_spatial_node_index,
                         );
 
                         let readback_task_id = frame_state.render_tasks.add(
                             RenderTask::new_readback(clipped)
@@ -738,17 +729,17 @@ impl PicturePrimitive {
                             &transform,
                             &clipped,
                             frame_context.device_pixel_scale,
                         );
 
                         let picture_task = RenderTask::new_picture(
                             RenderTaskLocation::Dynamic(None, clipped.size),
                             unclipped.size,
-                            prim_index,
+                            prim_instance.prim_index,
                             clipped.origin,
                             pic_state_for_children.tasks,
                             uv_rect_kind,
                             pic_state_for_children.raster_spatial_node_index,
                         );
 
                         let render_task_id = frame_state.render_tasks.add(picture_task);
                         pic_state.tasks.push(render_task_id);
@@ -760,17 +751,17 @@ impl PicturePrimitive {
                             &transform,
                             &clipped,
                             frame_context.device_pixel_scale,
                         );
 
                         let picture_task = RenderTask::new_picture(
                             RenderTaskLocation::Dynamic(None, clipped.size),
                             unclipped.size,
-                            prim_index,
+                            prim_instance.prim_index,
                             clipped.origin,
                             pic_state_for_children.tasks,
                             uv_rect_kind,
                             pic_state_for_children.raster_spatial_node_index,
                         );
 
                         let render_task_id = frame_state.render_tasks.add(picture_task);
                         pic_state.tasks.push(render_task_id);
--- a/gfx/webrender/src/platform/macos/font.rs
+++ b/gfx/webrender/src/platform/macos/font.rs
@@ -320,16 +320,21 @@ impl FontContext {
     }
 
     pub fn delete_font(&mut self, font_key: &FontKey) {
         if let Some(_) = self.cg_fonts.remove(font_key) {
             self.ct_fonts.retain(|k, _| k.0 != *font_key);
         }
     }
 
+    pub fn delete_font_instance(&mut self, instance: &FontInstance) {
+        // Remove the CoreText font corresponding to this instance.
+        self.ct_fonts.remove(&(instance.font_key, instance.size, instance.variations.clone()));
+    }
+
     fn get_ct_font(
         &mut self,
         font_key: FontKey,
         size: Au,
         variations: &[FontVariation],
     ) -> Option<CTFont> {
         match self.ct_fonts.entry((font_key, size, variations.to_vec())) {
             Entry::Occupied(entry) => Some((*entry.get()).clone()),
--- a/gfx/webrender/src/platform/unix/font.rs
+++ b/gfx/webrender/src/platform/unix/font.rs
@@ -235,16 +235,20 @@ impl FontContext {
 
     pub fn delete_font(&mut self, font_key: &FontKey) {
         if let Some(face) = self.faces.remove(font_key) {
             let result = unsafe { FT_Done_Face(face.face) };
             assert!(succeeded(result));
         }
     }
 
+    pub fn delete_font_instance(&mut self, _instance: &FontInstance) {
+        // This backend does not yet support variations, so there is nothing to do here.
+    }
+
     fn load_glyph(&self, font: &FontInstance, glyph: &GlyphKey) -> Option<(FT_GlyphSlot, f32)> {
         debug_assert!(self.faces.contains_key(&font.font_key));
         let face = self.faces.get(&font.font_key).unwrap();
 
         let mut load_flags = FT_LOAD_DEFAULT;
         let FontInstancePlatformOptions { mut hinting, .. } = font.platform_options.unwrap_or_default();
         // Disable hinting if there is a non-axis-aligned transform.
         if font.synthetic_italics.is_enabled() ||
--- a/gfx/webrender/src/platform/windows/font.rs
+++ b/gfx/webrender/src/platform/windows/font.rs
@@ -140,16 +140,28 @@ impl FontContext {
     }
 
     pub fn delete_font(&mut self, font_key: &FontKey) {
         if let Some(_) = self.fonts.remove(font_key) {
             self.variations.retain(|k, _| k.0 != *font_key);
         }
     }
 
+    pub fn delete_font_instance(&mut self, instance: &FontInstance) {
+        // Ensure we don't keep around excessive amounts of stale variations.
+        if !instance.variations.is_empty() {
+            let sims = if instance.flags.contains(FontInstanceFlags::SYNTHETIC_BOLD) {
+                dwrote::DWRITE_FONT_SIMULATIONS_BOLD
+            } else {
+                dwrote::DWRITE_FONT_SIMULATIONS_NONE
+            };
+            self.variations.remove(&(instance.font_key, sims, instance.variations.clone()));
+        }
+    }
+
     // Assumes RGB format from dwrite, which is 3 bytes per pixel as dwrite
     // doesn't output an alpha value via GlyphRunAnalysis::CreateAlphaTexture
     #[allow(dead_code)]
     fn print_glyph_data(&self, data: &[u8], width: usize, height: usize) {
         // Rust doesn't have step_by support on stable :(
         for i in 0 .. height {
             let current_height = i * width * 3;
 
@@ -180,17 +192,18 @@ impl FontContext {
             Entry::Occupied(entry) => entry.into_mut(),
             Entry::Vacant(entry) => {
                 let normal_face = self.fonts.get(&font.font_key).unwrap();
                 if !font.variations.is_empty() {
                     if let Some(var_face) = normal_face.create_font_face_with_variations(
                         sims,
                         &font.variations.iter().map(|var| {
                             dwrote::DWRITE_FONT_AXIS_VALUE {
-                                axisTag: var.tag,
+                                // OpenType tags are big-endian, but DWrite wants little-endian.
+                                axisTag: var.tag.swap_bytes(),
                                 value: var.value,
                             }
                         }).collect::<Vec<_>>(),
                     ) {
                         return entry.insert(var_face);
                     }
                 }
                 entry.insert(normal_face.create_font_face_with_simulations(sims))
--- a/gfx/webrender/src/prim_store.rs
+++ b/gfx/webrender/src/prim_store.rs
@@ -1,31 +1,32 @@
 /* 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::{AlphaType, BorderRadius, BuiltDisplayList, ClipMode, ColorF, PictureRect};
 use api::{DeviceIntRect, DeviceIntSize, DevicePixelScale, ExtendMode, DeviceRect, PictureToRasterTransform};
-use api::{FilterOp, GlyphInstance, GradientStop, ImageKey, ImageRendering, ItemRange, ItemTag, TileOffset};
+use api::{FilterOp, GlyphInstance, GradientStop, ImageKey, ImageRendering, ItemRange, TileOffset};
 use api::{RasterSpace, LayoutPoint, LayoutRect, LayoutSideOffsets, LayoutSize, LayoutToWorldTransform};
 use api::{LayoutVector2D, PremultipliedColorF, PropertyBinding, Shadow, YuvColorSpace, YuvFormat};
 use api::{DeviceIntSideOffsets, WorldPixel, BoxShadowClipMode, LayoutToWorldScale, NormalBorder, WorldRect};
 use api::{PicturePixel, RasterPixel, ColorDepth};
 use app_units::Au;
 use border::{BorderCacheKey, BorderRenderTaskInfo};
 use clip_scroll_tree::{ClipScrollTree, CoordinateSystemId, SpatialNodeIndex};
 use clip::{ClipNodeFlags, ClipChainId, ClipChainInstance, ClipItem, ClipNodeCollector};
 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 intern;
 use picture::{PictureCompositeMode, PicturePrimitive};
 #[cfg(debug_assertions)]
 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;
@@ -233,42 +234,63 @@ impl GpuCacheAddress {
     pub fn as_int(&self) -> i32 {
         // TODO(gw): Temporarily encode GPU Cache addresses as a single int.
         //           In the future, we can change the PrimitiveInstanceData struct
         //           to use 2x u16 for the vertex attribute instead of an i32.
         self.v as i32 * MAX_VERTEX_TEXTURE_WIDTH as i32 + self.u as i32
     }
 }
 
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
+#[derive(Debug, Clone, Eq, PartialEq, Hash)]
+pub struct PrimitiveKey {
+    pub is_backface_visible: bool,
+}
+
+impl PrimitiveKey {
+    pub fn new(
+        is_backface_visible: bool,
+    ) -> Self {
+        PrimitiveKey {
+            is_backface_visible,
+        }
+    }
+}
+
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
+pub struct PrimitiveTemplate {
+    pub is_backface_visible: bool,
+}
+
+impl From<PrimitiveKey> for PrimitiveTemplate {
+    fn from(item: PrimitiveKey) -> Self {
+        PrimitiveTemplate {
+            is_backface_visible: item.is_backface_visible,
+        }
+    }
+}
+
+// Type definitions for interning primitives.
+#[cfg_attr(feature = "capture", derive(Serialize))]
+#[cfg_attr(feature = "replay", derive(Deserialize))]
+#[derive(Clone, Copy, Debug)]
+pub struct PrimitiveDataMarker;
+
+pub type PrimitiveDataStore = intern::DataStore<PrimitiveKey, PrimitiveTemplate, PrimitiveDataMarker>;
+pub type PrimitiveDataHandle = intern::Handle<PrimitiveDataMarker>;
+pub type PrimitiveDataUpdateList = intern::UpdateList<PrimitiveKey>;
+pub type PrimitiveDataInterner = intern::Interner<PrimitiveKey, PrimitiveDataMarker>;
+
 // TODO(gw): Pack the fields here better!
 #[derive(Debug)]
 pub struct PrimitiveMetadata {
-    pub opacity: PrimitiveOpacity,
-    pub clip_chain_id: ClipChainId,
-    pub spatial_node_index: SpatialNodeIndex,
-    pub gpu_location: GpuCacheHandle,
-    pub clip_task_id: Option<RenderTaskId>,
-
-    // TODO(gw): In the future, we should just pull these
-    //           directly from the DL item, instead of
-    //           storing them here.
     pub local_rect: LayoutRect,
     pub local_clip_rect: LayoutRect,
-
-    pub is_backface_visible: bool,
-    pub clipped_world_rect: Option<WorldRect>,
-
-    /// A tag used to identify this primitive outside of WebRender. This is
-    /// used for returning useful data during hit testing.
-    pub tag: Option<ItemTag>,
-
-    /// The last frame ID (of the `RenderTaskTree`) this primitive
-    /// was prepared for rendering in.
-    #[cfg(debug_assertions)]
-    pub prepared_frame_id: FrameId,
 }
 
 // Maintains a list of opacity bindings that have been collapsed into
 // the color of a single primitive. This is an important optimization
 // that avoids allocating an intermediate surface for most common
 // uses of opacity filters.
 #[derive(Debug)]
 pub struct OpacityBinding {
@@ -1411,21 +1433,80 @@ impl Primitive {
                 panic!("bug: not a picture!");
             }
         }
     }
 }
 
 #[derive(Debug)]
 pub struct PrimitiveInstance {
+    /// Index into the prim store containing information about
+    /// the specific primitive. This will be removed once all
+    /// primitive data is interned.
     pub prim_index: PrimitiveIndex,
 
-    // The current combined local clip for this primitive, from
-    // the primitive local clip above and the current clip chain.
+    /// Handle to the common interned data for this primitive.
+    pub prim_data_handle: PrimitiveDataHandle,
+
+    /// The current combined local clip for this primitive, from
+    /// the primitive local clip above and the current clip chain.
     pub combined_local_clip_rect: LayoutRect,
+
+    /// The last frame ID (of the `RenderTaskTree`) this primitive
+    /// was prepared for rendering in.
+    #[cfg(debug_assertions)]
+    pub prepared_frame_id: FrameId,
+
+    /// The current world rect of this primitive, clipped to the
+    /// world rect of the screen. None means the primitive is
+    /// completely off-screen.
+    pub clipped_world_rect: Option<WorldRect>,
+
+    /// If this primitive has a global clip mask, this identifies
+    /// the render task for it.
+    pub clip_task_id: Option<RenderTaskId>,
+
+    /// The main GPU cache handle that this primitive uses to
+    /// store data accessible to shaders. This should be moved
+    /// into the interned data in order to retain this between
+    /// display list changes, but needs to be split into shared
+    /// and per-instance data.
+    pub gpu_location: GpuCacheHandle,
+
+    /// The current opacity of the primitive contents.
+    pub opacity: PrimitiveOpacity,
+
+    /// ID of the clip chain that this primitive is clipped by.
+    pub clip_chain_id: ClipChainId,
+
+    /// ID of the spatial node that this primitive is positioned by.
+    pub spatial_node_index: SpatialNodeIndex,
+}
+
+impl PrimitiveInstance {
+    pub fn new(
+        prim_index: PrimitiveIndex,
+        prim_data_handle: PrimitiveDataHandle,
+        clip_chain_id: ClipChainId,
+        spatial_node_index: SpatialNodeIndex,
+    ) -> Self {
+        PrimitiveInstance {
+            prim_index,
+            prim_data_handle,
+            combined_local_clip_rect: LayoutRect::zero(),
+            clipped_world_rect: None,
+            #[cfg(debug_assertions)]
+            prepared_frame_id: FrameId(0),
+            clip_task_id: None,
+            gpu_location: GpuCacheHandle::new(),
+            opacity: PrimitiveOpacity::translucent(),
+            clip_chain_id,
+            spatial_node_index,
+        }
+    }
 }
 
 pub struct PrimitiveStore {
     pub primitives: Vec<Primitive>,
 
     /// A primitive index to chase through debugging.
     pub chase_id: Option<PrimitiveIndex>,
 }
@@ -1445,78 +1526,38 @@ impl PrimitiveStore {
     pub fn get_pic_mut(&mut self, index: PrimitiveIndex) -> &mut PicturePrimitive {
         self.primitives[index.0].as_pic_mut()
     }
 
     pub fn add_primitive(
         &mut self,
         local_rect: &LayoutRect,
         local_clip_rect: &LayoutRect,
-        is_backface_visible: bool,
-        clip_chain_id: ClipChainId,
-        spatial_node_index: SpatialNodeIndex,
-        tag: Option<ItemTag>,
         container: PrimitiveContainer,
     ) -> PrimitiveIndex {
         let prim_index = self.primitives.len();
 
         let base_metadata = PrimitiveMetadata {
-            clip_chain_id,
-            gpu_location: GpuCacheHandle::new(),
-            clip_task_id: None,
-            spatial_node_index,
             local_rect: *local_rect,
             local_clip_rect: *local_clip_rect,
-            is_backface_visible,
-            clipped_world_rect: None,
-            tag,
-            opacity: PrimitiveOpacity::translucent(),
-            #[cfg(debug_assertions)]
-            prepared_frame_id: FrameId(0),
         };
 
         let prim = match container {
             PrimitiveContainer::Brush(brush) => {
-                let opacity = match brush.kind {
-                    BrushKind::Clear => PrimitiveOpacity::translucent(),
-                    BrushKind::Solid { ref color, .. } => PrimitiveOpacity::from_alpha(color.a),
-                    BrushKind::Image { .. } => PrimitiveOpacity::translucent(),
-                    BrushKind::YuvImage { .. } => PrimitiveOpacity::opaque(),
-                    BrushKind::RadialGradient { .. } => PrimitiveOpacity::translucent(),
-                    BrushKind::LinearGradient { stretch_size, tile_spacing, stops_opacity, .. } => {
-                        // If the coverage of the gradient extends to or beyond
-                        // the primitive rect, then the opacity can be determined
-                        // by the colors of the stops. If we have tiling / spacing
-                        // then we just assume the gradient is translucent for now.
-                        // (In the future we could consider segmenting in some cases).
-                        let stride = stretch_size + tile_spacing;
-                        if stride.width >= local_rect.size.width &&
-                           stride.height >= local_rect.size.height {
-                            stops_opacity
-                        } else {
-                            PrimitiveOpacity::translucent()
-                        }
-                    }
-                    BrushKind::Picture { .. } => PrimitiveOpacity::translucent(),
-                    BrushKind::Border { .. } => PrimitiveOpacity::translucent(),
-                };
-
                 let metadata = PrimitiveMetadata {
-                    opacity,
                     ..base_metadata
                 };
 
                 Primitive {
                     metadata,
                     details: PrimitiveDetails::Brush(brush),
                 }
             }
             PrimitiveContainer::TextRun(text_cpu) => {
                 let metadata = PrimitiveMetadata {
-                    opacity: PrimitiveOpacity::translucent(),
                     ..base_metadata
                 };
 
                 Primitive {
                     metadata,
                     details: PrimitiveDetails::TextRun(text_cpu),
                 }
             }
@@ -1718,17 +1759,17 @@ impl PrimitiveStore {
                         pic_context_for_children,
                         pic_state_for_children,
                         pic_rect,
                         frame_state,
                     );
 
                 if new_local_rect != prim.metadata.local_rect {
                     prim.metadata.local_rect = new_local_rect;
-                    frame_state.gpu_cache.invalidate(&mut prim.metadata.gpu_location);
+                    frame_state.gpu_cache.invalidate(&mut prim_instance.gpu_location);
                     pic_state.local_rect_changed = true;
                 }
 
                 (is_passthrough, clip_node_collector)
             }
             None => {
                 (false, None)
             }
@@ -1736,17 +1777,17 @@ impl PrimitiveStore {
 
         let prim = &mut self.primitives[prim_instance.prim_index.0];
 
         if !prim.is_cacheable(frame_state.resource_cache) {
             pic_state.is_cacheable = false;
         }
 
         if is_passthrough {
-            prim.metadata.clipped_world_rect = Some(pic_state.map_pic_to_world.bounds);
+            prim_instance.clipped_world_rect = Some(pic_state.map_pic_to_world.bounds);
         } else {
             if prim.metadata.local_rect.size.width <= 0.0 ||
                prim.metadata.local_rect.size.height <= 0.0 {
                 if cfg!(debug_assertions) && is_chased {
                     println!("\tculled for zero local rectangle");
                 }
                 return false;
             }
@@ -1769,17 +1810,17 @@ impl PrimitiveStore {
                     }
                     return false;
                 }
             };
 
             let clip_chain = frame_state
                 .clip_store
                 .build_clip_chain_instance(
-                    prim.metadata.clip_chain_id,
+                    prim_instance.clip_chain_id,
                     local_rect,
                     prim.metadata.local_clip_rect,
                     prim_context.spatial_node_index,
                     &pic_state.map_local_to_pic,
                     &pic_state.map_pic_to_world,
                     &frame_context.clip_scroll_tree,
                     frame_state.gpu_cache,
                     frame_state.resource_cache,
@@ -1787,17 +1828,17 @@ impl PrimitiveStore {
                     &frame_context.world_rect,
                     &clip_node_collector,
                     &mut frame_state.resources.clip_data_store,
                 );
 
             let clip_chain = match clip_chain {
                 Some(clip_chain) => clip_chain,
                 None => {
-                    prim.metadata.clipped_world_rect = None;
+                    prim_instance.clipped_world_rect = None;
                     return false;
                 }
             };
 
             if cfg!(debug_assertions) && is_chased {
                 println!("\teffective clip chain from {:?} {}",
                     clip_chain.clips_range,
                     if pic_context.apply_local_clip_rect { "(applied)" } else { "" },
@@ -1830,25 +1871,27 @@ impl PrimitiveStore {
 
             let clipped_world_rect = match world_rect.intersection(&frame_context.world_rect) {
                 Some(rect) => rect,
                 None => {
                     return false;
                 }
             };
 
-            prim.metadata.clipped_world_rect = Some(clipped_world_rect);
+            prim_instance.clipped_world_rect = Some(clipped_world_rect);
 
             prim.build_prim_segments_if_needed(
+                prim_instance,
                 pic_state,
                 frame_state,
                 frame_context,
             );
 
             prim.update_clip_task(
+                prim_instance,
                 prim_context,
                 clipped_world_rect,
                 pic_state.raster_spatial_node_index,
                 &clip_chain,
                 pic_state,
                 frame_context,
                 frame_state,
                 is_chased,
@@ -1871,66 +1914,57 @@ impl PrimitiveStore {
             frame_state,
             display_list,
             is_chased,
         );
 
         true
     }
 
-    // TODO(gw): Make this simpler / more efficient by tidying
-    //           up the logic that early outs from prepare_prim_for_render.
-    pub fn reset_prim_visibility(&mut self) {
-        for prim in &mut self.primitives {
-            prim.metadata.clipped_world_rect = None;
-        }
-    }
-
     pub fn prepare_primitives(
         &mut self,
         prim_instances: &mut Vec<PrimitiveInstance>,
         pic_context: &PictureContext,
         pic_state: &mut PictureState,
         frame_context: &FrameBuildingContext,
         frame_state: &mut FrameBuildingState,
         current_pic_rect: &mut PictureRect,
     ) {
         let display_list = &frame_context
             .pipelines
             .get(&pic_context.pipeline_id)
             .expect("No display list?")
             .display_list;
 
         for prim_instance in prim_instances {
+            prim_instance.clipped_world_rect = None;
+
             let prim_index = prim_instance.prim_index;
             let is_chased = Some(prim_index) == self.chase_id;
 
             if is_chased {
                 println!("\tpreparing prim {:?} in pipeline {:?}",
                     prim_instance.prim_index, pic_context.pipeline_id);
             }
 
-            // TODO(gw): These workarounds for borrowck are unfortunate. We
-            //           should see if we can re-structure these to avoid so
-            //           many special borrow blocks.
-            let (spatial_node_index, is_backface_visible) = {
-                let prim = &self.primitives[prim_instance.prim_index.0];
-                (prim.metadata.spatial_node_index, prim.metadata.is_backface_visible)
-            };
+            let is_backface_visible = frame_state
+                .resources
+                .prim_data_store[prim_instance.prim_data_handle]
+                .is_backface_visible;
 
             let spatial_node = &frame_context
                 .clip_scroll_tree
-                .spatial_nodes[spatial_node_index.0];
+                .spatial_nodes[prim_instance.spatial_node_index.0];
 
             // TODO(gw): Although constructing these is cheap, they are often
             //           the same for many consecutive primitives, so it may
             //           be worth caching the most recent context.
             let prim_context = PrimitiveContext::new(
                 spatial_node,
-                spatial_node_index,
+                prim_instance.spatial_node_index,
             );
 
             // Do some basic checks first, that can early out
             // without even knowing the local rect.
             if !is_backface_visible && spatial_node.world_content_transform.is_backface_visible() {
                 if cfg!(debug_assertions) && is_chased {
                     println!("\tculled for not having visible back faces");
                 }
@@ -1945,17 +1979,17 @@ impl PrimitiveStore {
             }
 
             // Mark whether this picture contains any complex coordinate
             // systems, due to either the scroll node or the clip-chain.
             pic_state.has_non_root_coord_system |=
                 spatial_node.coordinate_system_id != CoordinateSystemId::root();
 
             pic_state.map_local_to_pic.set_target_spatial_node(
-                spatial_node_index,
+                prim_instance.spatial_node_index,
                 frame_context.clip_scroll_tree,
             );
 
             if self.prepare_prim_for_render(
                 prim_instance,
                 &prim_context,
                 pic_context,
                 pic_state,
@@ -1987,34 +2021,34 @@ fn build_gradient_stops_request(
             reverse_stops,
             &mut request,
         );
     }
 }
 
 fn decompose_repeated_primitive(
     visible_tiles: &mut Vec<VisibleGradientTile>,
-    instance: &PrimitiveInstance,
+    instance: &mut PrimitiveInstance,
     metadata: &mut PrimitiveMetadata,
     stretch_size: &LayoutSize,
     tile_spacing: &LayoutSize,
     prim_context: &PrimitiveContext,
     frame_state: &mut FrameBuildingState,
     callback: &mut FnMut(&LayoutRect, GpuDataRequest),
 ) {
     visible_tiles.clear();
 
     // Tighten the clip rect because decomposing the repeated image can
     // produce primitives that are partially covering the original image
     // rect and we want to clip these extra parts out.
     let tight_clip_rect = instance
         .combined_local_clip_rect
         .intersection(&metadata.local_rect).unwrap();
 
-    let clipped_world_rect = &metadata
+    let clipped_world_rect = &instance
         .clipped_world_rect
         .unwrap();
 
     let visible_rect = compute_conservative_visible_rect(
         prim_context,
         clipped_world_rect,
         &tight_clip_rect
     );
@@ -2044,17 +2078,17 @@ fn decompose_repeated_primitive(
     );
 
     if visible_tiles.is_empty() {
         // At this point if we don't have tiles to show it means we could probably
         // have done a better a job at culling during an earlier stage.
         // Clearing the screen rect has the effect of "culling out" the primitive
         // from the point of view of the batch builder, and ensures we don't hit
         // assertions later on because we didn't request any image.
-        metadata.clipped_world_rect = None;
+        instance.clipped_world_rect = None;
     }
 }
 
 fn compute_conservative_visible_rect(
     prim_context: &PrimitiveContext,
     clipped_world_rect: &WorldRect,
     local_clip_rect: &LayoutRect,
 ) -> LayoutRect {
@@ -2257,16 +2291,17 @@ fn write_brush_segment_description(
             }
         }
     }
 }
 
 impl Primitive {
     fn update_clip_task_for_brush(
         &mut self,
+        prim_instance: &PrimitiveInstance,
         root_spatial_node_index: SpatialNodeIndex,
         prim_bounding_rect: WorldRect,
         prim_context: &PrimitiveContext,
         prim_clip_chain: &ClipChainInstance,
         pic_state: &mut PictureState,
         frame_context: &FrameBuildingContext,
         frame_state: &mut FrameBuildingState,
         clip_node_collector: &Option<ClipNodeCollector>,
@@ -2304,17 +2339,17 @@ impl Primitive {
         } else {
             for segment in &mut segment_desc.segments {
                 // Build a clip chain for the smaller segment rect. This will
                 // often manage to eliminate most/all clips, and sometimes
                 // clip the segment completely.
                 let segment_clip_chain = frame_state
                     .clip_store
                     .build_clip_chain_instance(
-                        self.metadata.clip_chain_id,
+                        prim_instance.clip_chain_id,
                         segment.local_rect,
                         self.metadata.local_clip_rect,
                         prim_context.spatial_node_index,
                         &pic_state.map_local_to_pic,
                         &pic_state.map_pic_to_world,
                         &frame_context.clip_scroll_tree,
                         frame_state.gpu_cache,
                         frame_state.resource_cache,
@@ -2336,18 +2371,21 @@ impl Primitive {
         }
 
         true
     }
 
     // Returns true if the primitive *might* need a clip mask. If
     // false, there is no need to even check for clip masks for
     // this primitive.
-    fn reset_clip_task(&mut self) -> bool {
-        self.metadata.clip_task_id = None;
+    fn reset_clip_task(
+        &mut self,
+        prim_instance: &mut PrimitiveInstance,
+    ) -> bool {
+        prim_instance.clip_task_id = None;
         match self.details {
             PrimitiveDetails::Brush(ref mut brush) => {
                 if let Some(ref mut desc) = brush.segment_desc {
                     for segment in &mut desc.segments {
                         segment.clip_task_id = BrushSegmentTaskId::Opaque;
                     }
                 }
                 brush.may_need_clip_mask()
@@ -2355,30 +2393,30 @@ impl Primitive {
             PrimitiveDetails::TextRun(..) => {
                 true
             }
         }
     }
 
     fn prepare_prim_for_render_inner(
         &mut self,
-        prim_instance: &PrimitiveInstance,
+        prim_instance: &mut PrimitiveInstance,
         prim_context: &PrimitiveContext,
         pic_context: &PictureContext,
         pic_state: &mut PictureState,
         frame_context: &FrameBuildingContext,
         frame_state: &mut FrameBuildingState,
         display_list: &BuiltDisplayList,
         is_chased: bool,
     ) {
         let mut is_tiled = false;
         let metadata = &mut self.metadata;
         #[cfg(debug_assertions)]
         {
-            metadata.prepared_frame_id = frame_state.render_tasks.frame_id();
+            prim_instance.prepared_frame_id = frame_state.render_tasks.frame_id();
         }
 
         match self.details {
             PrimitiveDetails::TextRun(ref mut text) => {
                 // The transform only makes sense for screen space rasterization
                 let transform = prim_context.spatial_node.world_content_transform.to_transform();
                 text.prepare_for_render(
                     frame_context.device_pixel_scale,
@@ -2409,22 +2447,22 @@ impl Primitive {
 
                         // Set if we need to request the source image from the cache this frame.
                         if let Some(image_properties) = image_properties {
                             is_tiled = image_properties.tiling.is_some();
 
                             // If the opacity changed, invalidate the GPU cache so that
                             // the new color for this primitive gets uploaded.
                             if opacity_binding.update(frame_context.scene_properties) {
-                                frame_state.gpu_cache.invalidate(&mut metadata.gpu_location);
+                                frame_state.gpu_cache.invalidate(&mut prim_instance.gpu_location);
                             }
 
                             // Update opacity for this primitive to ensure the correct
                             // batching parameters are used.
-                            metadata.opacity.is_opaque =
+                            prim_instance.opacity.is_opaque =
                                 image_properties.descriptor.is_opaque &&
                                 opacity_binding.current == 1.0 &&
                                 color.a == 1.0;
 
                             if *tile_spacing != LayoutSize::zero() && !is_tiled {
                                 *source = ImageSource::Cache {
                                     // Size in device-pixels we need to allocate in render task cache.
                                     size: image_properties.descriptor.size.to_i32(),
@@ -2459,17 +2497,17 @@ impl Primitive {
                                         0,
                                     );
 
                                     let inner_size = *size;
                                     size.width += padding.horizontal();
                                     size.height += padding.vertical();
 
                                     if padding != DeviceIntSideOffsets::zero() {
-                                        metadata.opacity.is_opaque = false;
+                                        prim_instance.opacity.is_opaque = false;
                                     }
 
                                     let image_cache_key = ImageCacheKey {
                                         request,
                                         texel_rect: sub_rect,
                                     };
 
                                     // Request a pre-rendered image task.
@@ -2530,17 +2568,17 @@ impl Primitive {
                                 // produce primitives that are partially covering the original image
                                 // rect and we want to clip these extra parts out.
                                 let tight_clip_rect = prim_instance
                                     .combined_local_clip_rect
                                     .intersection(&metadata.local_rect).unwrap();
 
                                 let visible_rect = compute_conservative_visible_rect(
                                     prim_context,
-                                    &metadata.clipped_world_rect.unwrap(),
+                                    &prim_instance.clipped_world_rect.unwrap(),
                                     &tight_clip_rect
                                 );
 
                                 let base_edge_flags = edge_flags_for_tile_spacing(tile_spacing);
 
                                 let stride = stretch_size + *tile_spacing;
 
                                 visible_tiles.clear();
@@ -2590,27 +2628,29 @@ impl Primitive {
                                 );
 
                                 if visible_tiles.is_empty() {
                                     // At this point if we don't have tiles to show it means we could probably
                                     // have done a better a job at culling during an earlier stage.
                                     // Clearing the screen rect has the effect of "culling out" the primitive
                                     // from the point of view of the batch builder, and ensures we don't hit
                                     // assertions later on because we didn't request any image.
-                                    metadata.clipped_world_rect = None;
+                                    prim_instance.clipped_world_rect = None;
                                 }
                             } else if request_source_image {
                                 frame_state.resource_cache.request_image(
                                     request,
                                     frame_state.gpu_cache,
                                 );
                             }
                         }
                     }
                     BrushKind::YuvImage { format, yuv_key, image_rendering, .. } => {
+                        prim_instance.opacity = PrimitiveOpacity::opaque();
+
                         let channel_num = format.get_plane_num();
                         debug_assert!(channel_num <= 3);
                         for channel in 0 .. channel_num {
                             frame_state.resource_cache.request_image(
                                 ImageRequest {
                                     key: yuv_key[channel],
                                     rendering: image_rendering,
                                     tile: None,
@@ -2624,17 +2664,17 @@ impl Primitive {
                             BorderSource::Image(request) => {
                                 let image_properties = frame_state
                                     .resource_cache
                                     .get_image_properties(request.key);
 
                                 if let Some(image_properties) = image_properties {
                                     // Update opacity for this primitive to ensure the correct
                                     // batching parameters are used.
-                                    metadata.opacity.is_opaque =
+                                    prim_instance.opacity.is_opaque =
                                         image_properties.descriptor.is_opaque;
 
                                     frame_state.resource_cache.request_image(
                                         request,
                                         frame_state.gpu_cache,
                                     );
                                 }
                             }
@@ -2697,20 +2737,33 @@ impl Primitive {
                     BrushKind::LinearGradient {
                         stops_range,
                         reverse_stops,
                         start_point,
                         end_point,
                         extend_mode,
                         stretch_size,
                         tile_spacing,
+                        stops_opacity,
                         ref mut stops_handle,
                         ref mut visible_tiles,
                         ..
                     } => {
+                        // If the coverage of the gradient extends to or beyond
+                        // the primitive rect, then the opacity can be determined
+                        // by the colors of the stops. If we have tiling / spacing
+                        // then we just assume the gradient is translucent for now.
+                        // (In the future we could consider segmenting in some cases).
+                        let stride = stretch_size + tile_spacing;
+                        prim_instance.opacity = if stride.width >= metadata.local_rect.size.width &&
+                           stride.height >= metadata.local_rect.size.height {
+                            stops_opacity
+                        } else {
+                            PrimitiveOpacity::translucent()
+                        };
 
                         build_gradient_stops_request(
                             stops_handle,
                             stops_range,
                             reverse_stops,
                             frame_state,
                             display_list,
                         );
@@ -2741,47 +2794,47 @@ impl Primitive {
                                     ]);
                                     request.write_segment(*rect, [0.0; 4]);
                                 }
                             );
                         }
                     }
                     BrushKind::Picture(ref mut pic) => {
                         if !pic.prepare_for_render(
-                            prim_instance.prim_index,
+                            prim_instance,
                             metadata,
                             pic_state,
                             frame_context,
                             frame_state,
                         ) {
-                            metadata.clipped_world_rect = None;
+                            prim_instance.clipped_world_rect = None;
                         }
                     }
                     BrushKind::Solid { ref color, ref mut opacity_binding, .. } => {
                         // If the opacity changed, invalidate the GPU cache so that
                         // the new color for this primitive gets uploaded. Also update
                         // the opacity field that controls which batches this primitive
                         // will be added to.
                         if opacity_binding.update(frame_context.scene_properties) {
-                            metadata.opacity = PrimitiveOpacity::from_alpha(opacity_binding.current * color.a);
-                            frame_state.gpu_cache.invalidate(&mut metadata.gpu_location);
+                            frame_state.gpu_cache.invalidate(&mut prim_instance.gpu_location);
                         }
+                        prim_instance.opacity = PrimitiveOpacity::from_alpha(opacity_binding.current * color.a);
                     }
                     BrushKind::Clear => {}
                 }
             }
         }
 
         if is_tiled {
             // we already requested each tile's gpu data.
             return;
         }
 
         // Mark this GPU resource as required for this frame.
-        if let Some(mut request) = frame_state.gpu_cache.request(&mut metadata.gpu_location) {
+        if let Some(mut request) = frame_state.gpu_cache.request(&mut prim_instance.gpu_location) {
             match self.details {
                 PrimitiveDetails::TextRun(ref mut text) => {
                     text.write_gpu_blocks(&mut request);
                 }
                 PrimitiveDetails::Brush(ref mut brush) => {
                     brush.write_gpu_blocks(&mut request, metadata.local_rect);
 
                     match brush.segment_desc {
@@ -2806,37 +2859,39 @@ impl Primitive {
                     }
                 }
             }
         }
     }
 
     fn update_clip_task(
         &mut self,
+        prim_instance: &mut PrimitiveInstance,
         prim_context: &PrimitiveContext,
         prim_bounding_rect: WorldRect,
         root_spatial_node_index: SpatialNodeIndex,
         clip_chain: &ClipChainInstance,
         pic_state: &mut PictureState,
         frame_context: &FrameBuildingContext,
         frame_state: &mut FrameBuildingState,
         is_chased: bool,
         clip_node_collector: &Option<ClipNodeCollector>,
     ) {
         if cfg!(debug_assertions) && is_chased {
             println!("\tupdating clip task with pic rect {:?}", clip_chain.pic_clip_rect);
         }
         // Reset clips from previous frames since we may clip differently each frame.
         // If this primitive never needs clip masks, just return straight away.
-        if !self.reset_clip_task() {
+        if !self.reset_clip_task(prim_instance) {
             return;
         }
 
         // First try to  render this primitive's mask using optimized brush rendering.
         if self.update_clip_task_for_brush(
+            prim_instance,
             root_spatial_node_index,
             prim_bounding_rect,
             prim_context,
             &clip_chain,
             pic_state,
             frame_context,
             frame_state,
             clip_node_collector,
@@ -2863,27 +2918,28 @@ impl Primitive {
                     frame_state.gpu_cache,
                     frame_state.resource_cache,
                     frame_state.render_tasks,
                     &mut frame_state.resources.clip_data_store,
                 );
 
                 let clip_task_id = frame_state.render_tasks.add(clip_task);
                 if cfg!(debug_assertions) && is_chased {
-                    println!("\tcreated task {:?} with world rect {:?}",
-                        clip_task_id, self.metadata.clipped_world_rect);
+                    println!("\tcreated task {:?} with device rect {:?}",
+                        clip_task_id, device_rect);
                 }
-                self.metadata.clip_task_id = Some(clip_task_id);
+                prim_instance.clip_task_id = Some(clip_task_id);
                 pic_state.tasks.push(clip_task_id);
             }
         }
     }
 
     fn build_prim_segments_if_needed(
         &mut self,
+        prim_instance: &mut PrimitiveInstance,
         pic_state: &mut PictureState,
         frame_state: &mut FrameBuildingState,
         frame_context: &FrameBuildingContext,
     ) {
         let brush = match self.details {
             PrimitiveDetails::Brush(ref mut brush) => brush,
             PrimitiveDetails::TextRun(..) => return,
         };
@@ -2955,17 +3011,17 @@ impl Primitive {
 
             if needs_update {
                 brush.segment_desc = Some(BrushSegmentDescriptor {
                     segments: new_segments,
                 });
 
                 // The segments have changed, so force the GPU cache to
                 // re-upload the primitive information.
-                frame_state.gpu_cache.invalidate(&mut self.metadata.gpu_location);
+                frame_state.gpu_cache.invalidate(&mut prim_instance.gpu_location);
             }
         }
     }
 }
 
 pub fn get_raster_rects(
     pic_rect: PictureRect,
     map_to_raster: &SpaceMapper<PicturePixel, RasterPixel>,
--- a/gfx/webrender/src/render_backend.rs
+++ b/gfx/webrender/src/render_backend.rs
@@ -25,16 +25,17 @@ use api::CapturedDocument;
 use clip::ClipDataStore;
 use clip_scroll_tree::{SpatialNodeIndex, ClipScrollTree};
 #[cfg(feature = "debugger")]
 use debug_server;
 use frame_builder::{FrameBuilder, FrameBuilderConfig};
 use gpu_cache::GpuCache;
 use hit_test::{HitTest, HitTester};
 use internal_types::{DebugOutput, FastHashMap, FastHashSet, RenderedDocument, ResultMsg};
+use prim_store::PrimitiveDataStore;
 use profiler::{BackendProfileCounters, IpcProfileCounters, ResourceProfileCounters};
 use record::ApiRecordingReceiver;
 use renderer::{AsyncPropertySampler, PipelineInfo};
 use resource_cache::ResourceCache;
 #[cfg(feature = "replay")]
 use resource_cache::PlainCacheOwn;
 #[cfg(any(feature = "capture", feature = "replay"))]
 use resource_cache::PlainResources;
@@ -84,25 +85,30 @@ impl DocumentView {
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 pub struct FrameId(pub u32);
 
 // A collection of resources that are shared by clips, primitives
 // between display lists.
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 pub struct FrameResources {
-    // The store of currently active / available clip nodes. This is kept
-    // in sync with the clip interner in the scene builder for each document.
+    /// The store of currently active / available clip nodes. This is kept
+    /// in sync with the clip interner in the scene builder for each document.
     pub clip_data_store: ClipDataStore,
+
+    /// Currently active / available primitives. Kept in sync with the
+    /// primitive interner in the scene builder, per document.
+    pub prim_data_store: PrimitiveDataStore,
 }
 
 impl FrameResources {
     fn new() -> Self {
         FrameResources {
             clip_data_store: ClipDataStore::new(),
+            prim_data_store: PrimitiveDataStore::new(),
         }
     }
 }
 
 struct Document {
     // The latest built scene, usable to build frames.
     // received from the scene builder thread.
     scene: Scene,
@@ -134,16 +140,17 @@ struct Document {
     /// 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,
+    rendered_frame_is_valid: bool,
 
     resources: FrameResources,
 }
 
 impl Document {
     pub fn new(
         window_size: DeviceUintSize,
         layer: DocumentLayer,
@@ -164,16 +171,17 @@ impl Document {
             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,
+            rendered_frame_is_valid: false,
             resources: FrameResources::new(),
         }
     }
 
     fn can_render(&self) -> bool {
         self.frame_builder.is_some() && self.scene.has_root_pipeline()
     }
 
@@ -651,16 +659,17 @@ impl RenderBackend {
 
                         self.update_document(
                             txn.document_id,
                             replace(&mut txn.resource_updates, Vec::new()),
                             txn.doc_resource_updates.take(),
                             replace(&mut txn.frame_ops, Vec::new()),
                             replace(&mut txn.notifications, Vec::new()),
                             txn.render_frame,
+                            txn.invalidate_rendered_frame,
                             &mut frame_counter,
                             &mut profile_counters,
                             has_built_scene,
                         );
                     },
                     SceneBuilderResult::FlushComplete(tx) => {
                         tx.send(()).ok();
                     }
@@ -931,16 +940,17 @@ impl RenderBackend {
             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,
             render_frame: transaction_msg.generate_frame,
+            invalidate_rendered_frame: transaction_msg.invalidate_rendered_frame,
         });
 
         self.resource_cache.pre_scene_building_update(
             &mut txn.resource_updates,
             &mut profile_counters.resources,
         );
 
         for scene_msg in transaction_msg.scene_ops.drain(..) {
@@ -966,16 +976,17 @@ impl RenderBackend {
         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()),
                 None,
                 replace(&mut txn.frame_ops, Vec::new()),
                 replace(&mut txn.notifications, Vec::new()),
                 txn.render_frame,
+                txn.invalidate_rendered_frame,
                 frame_counter,
                 profile_counters,
                 false
             );
 
             return;
         }
 
@@ -1003,16 +1014,17 @@ impl RenderBackend {
     fn update_document(
         &mut self,
         document_id: DocumentId,
         resource_updates: Vec<ResourceUpdate>,
         doc_resource_updates: Option<DocumentResourceUpdates>,
         mut frame_ops: Vec<FrameMsg>,
         mut notifications: Vec<NotificationRequest>,
         mut render_frame: bool,
+        invalidate_rendered_frame: bool,
         frame_counter: &mut u32,
         profile_counters: &mut BackendProfileCounters,
         has_built_scene: bool,
     ) {
         let requested_frame = render_frame;
 
         // 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
@@ -1026,16 +1038,17 @@ impl RenderBackend {
         }
 
         let doc = self.documents.get_mut(&document_id).unwrap();
 
         // If there are any additions or removals of clip modes
         // during the scene build, apply them to the data store now.
         if let Some(updates) = doc_resource_updates {
             doc.resources.clip_data_store.apply_updates(updates.clip_updates);
+            doc.resources.prim_data_store.apply_updates(updates.prim_updates);
         }
 
         // 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);
@@ -1063,21 +1076,29 @@ impl RenderBackend {
             // 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.
             render_frame = false;
         }
 
         // Avoid re-building the frame if the current built frame is still valid.
         let build_frame = render_frame && !doc.frame_is_valid;
 
+        // Request composite is true when we want to composite frame even when
+        // there is no frame update. This happens when video frame is updated under
+        // external image with NativeTexture or when platform requested to composite frame.
+        if invalidate_rendered_frame {
+            doc.rendered_frame_is_valid = false;
+        }
+
         let mut frame_build_time = None;
         if build_frame && doc.has_pixels() {
             profile_scope!("generate frame");
 
             *frame_counter += 1;
+            doc.rendered_frame_is_valid = false;
 
             // borrow ck hack for profile_counters
             let (pending_update, rendered_document) = {
                 let _timer = profile_counters.total_time.timer();
                 let frame_build_start_time = precise_time_ns();
 
                 let rendered_document = doc.build_frame(
                     &mut self.resource_cache,
@@ -1128,16 +1149,22 @@ impl RenderBackend {
         if !notifications.is_empty() {
             self.result_tx.send(ResultMsg::AppendNotificationRequests(notifications)).unwrap();
         }
 
         // 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 {
+            // If rendered frame is already valid, there is no need to render frame.
+            if doc.rendered_frame_is_valid {
+                render_frame = false;
+            } else if render_frame {
+                doc.rendered_frame_is_valid = true;
+            }
             self.notifier.new_frame_ready(document_id, scroll, render_frame, frame_build_time);
         }
 
         if !doc.hit_tester_is_valid {
             doc.rebuild_hit_tester();
         }
     }
 
@@ -1449,16 +1476,17 @@ impl RenderBackend {
                 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,
+                rendered_frame_is_valid: false,
                 resources: frame_resources,
             };
 
             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/renderer.rs
+++ b/gfx/webrender/src/renderer.rs
@@ -32,17 +32,17 @@ use api::{channel};
 use api::DebugCommand;
 use api::channel::PayloadReceiverHelperMethods;
 use batch::{BatchKind, BatchTextures, BrushBatchKind};
 #[cfg(any(feature = "capture", feature = "replay"))]
 use capture::{CaptureConfig, ExternalCaptureImage, PlainExternalImage};
 use debug_colors;
 use device::{DepthFunction, Device, FrameId, Program, UploadMethod, Texture, PBO};
 use device::{ExternalTexture, FBOId, TextureSlot};
-use device::{FileWatcherHandler, ShaderError, TextureFilter,
+use device::{ShaderError, TextureFilter,
              VertexUsageHint, VAO, VBO, CustomVAO};
 use device::{ProgramCache, ReadPixelsFormat};
 #[cfg(feature = "debug_renderer")]
 use euclid::rect;
 use euclid::Transform3D;
 use frame_builder::{ChasePrimitive, FrameBuilderConfig};
 use gleam::gl;
 use glyph_rasterizer::{GlyphFormat, GlyphRasterizer};
@@ -59,34 +59,35 @@ use internal_types::{RenderTargetInfo, S
 use prim_store::DeferredResolve;
 use profiler::{BackendProfileCounters, FrameProfileCounters,
                GpuProfileTag, RendererProfileCounters, RendererProfileTimers};
 use device::query::GpuProfiler;
 use rayon::{ThreadPool, ThreadPoolBuilder};
 use record::ApiRecordingReceiver;
 use render_backend::RenderBackend;
 use scene_builder::{SceneBuilder, LowPrioritySceneBuilder};
-use shade::Shaders;
+use shade::{Shaders, WrShaders};
 use smallvec::SmallVec;
 use render_task::{RenderTask, RenderTaskKind, RenderTaskTree};
 use resource_cache::ResourceCache;
 use util::drain_filter;
 
 use std;
 use std::cmp;
 use std::collections::VecDeque;
 use std::collections::hash_map::Entry;
 use std::f32;
 use std::mem;
 use std::os::raw::c_void;
 use std::path::PathBuf;
 use std::rc::Rc;
 use std::sync::Arc;
-use std::sync::mpsc::{channel, Receiver, Sender};
+use std::sync::mpsc::{channel, Receiver};
 use std::thread;
+use std::cell::RefCell;
 use texture_cache::TextureCache;
 use thread_profiler::{register_thread_with_profiler, write_profile};
 use tiling::{AlphaRenderTarget, ColorRenderTarget};
 use tiling::{BlitJob, BlitJobSource, RenderPass, RenderPassKind, RenderTargetList};
 use tiling::{Frame, RenderTarget, RenderTargetKind, TextureCacheRenderTarget};
 #[cfg(not(feature = "pathfinder"))]
 use tiling::GlyphJob;
 use time::precise_time_ns;
@@ -1336,28 +1337,16 @@ impl VertexDataTexture {
     }
 
     fn deinit(self, device: &mut Device) {
         device.delete_pbo(self.pbo);
         device.delete_texture(self.texture);
     }
 }
 
-struct FileWatcher {
-    notifier: Box<RenderNotifier>,
-    result_tx: Sender<ResultMsg>,
-}
-
-impl FileWatcherHandler for FileWatcher {
-    fn file_changed(&self, path: PathBuf) {
-        self.result_tx.send(ResultMsg::RefreshShader(path)).ok();
-        self.notifier.wake_up();
-    }
-}
-
 struct FrameOutput {
     last_access: FrameId,
     fbo_id: FBOId,
 }
 
 #[derive(PartialEq)]
 struct TargetSelector {
     size: DeviceUintSize,
@@ -1428,17 +1417,17 @@ pub struct Renderer {
     result_rx: Receiver<ResultMsg>,
     debug_server: DebugServer,
     pub device: Device,
     pending_texture_updates: Vec<TextureUpdateList>,
     pending_gpu_cache_updates: Vec<GpuCacheUpdateList>,
     pending_shader_updates: Vec<PathBuf>,
     active_documents: Vec<(DocumentId, RenderedDocument)>,
 
-    shaders: Shaders,
+    shaders: Rc<RefCell<Shaders>>,
 
     pub gpu_glyph_renderer: GpuGlyphRenderer,
 
     max_texture_size: u32,
     max_recorded_profiles: usize,
 
     clear_color: Option<ColorF>,
     enable_clear_scissor: bool,
@@ -1554,34 +1543,29 @@ impl Renderer {
     /// };
     /// let (renderer, sender) = Renderer::new(opts);
     /// ```
     /// [rendereroptions]: struct.RendererOptions.html
     pub fn new(
         gl: Rc<gl::Gl>,
         notifier: Box<RenderNotifier>,
         mut options: RendererOptions,
+        shaders: Option<&mut WrShaders>
     ) -> Result<(Self, RenderApiSender), RendererError> {
         let (api_tx, api_rx) = channel::msg_channel()?;
         let (payload_tx, payload_rx) = channel::payload_channel()?;
         let (result_tx, result_rx) = channel();
         let gl_type = gl.get_type();
 
         let debug_server = DebugServer::new(api_tx.clone());
 
-        let file_watch_handler = FileWatcher {
-            result_tx: result_tx.clone(),
-            notifier: notifier.clone(),
-        };
-
         let mut device = Device::new(
             gl,
             options.resource_override_path.clone(),
             options.upload_method.clone(),
-            Box::new(file_watch_handler),
             options.cached_programs.take(),
         );
 
         let ext_dual_source_blending = !options.disable_dual_source_blending &&
             device.supports_extension("GL_ARB_blend_func_extended") &&
             device.supports_extension("GL_ARB_explicit_attrib_location");
 
         let device_max_size = device.max_texture_size();
@@ -1603,17 +1587,20 @@ impl Renderer {
             ),
             min_texture_size,
         );
 
         register_thread_with_profiler("Compositor".to_owned());
 
         device.begin_frame();
 
-        let shaders = Shaders::new(&mut device, gl_type, &options)?;
+        let shaders = match shaders {
+            Some(shaders) => Rc::clone(&shaders.shaders),
+            None => Rc::new(RefCell::new(Shaders::new(&mut device, gl_type, &options)?)),
+        };
 
         let backend_profile_counters = BackendProfileCounters::new();
 
         let dither_matrix_texture = if options.enable_dithering {
             let dither_matrix: [u8; 64] = [
                 00,
                 48,
                 12,
@@ -2960,24 +2947,24 @@ impl Renderer {
         stats: &mut RendererStats,
     ) {
         if scalings.is_empty() {
             return
         }
 
         match source {
             TextureSource::PrevPassColor => {
-                self.shaders.cs_scale_rgba8.bind(&mut self.device,
-                                                 &projection,
-                                                 &mut self.renderer_errors);
+                self.shaders.borrow_mut().cs_scale_rgba8.bind(&mut self.device,
+                                                              &projection,
+                                                              &mut self.renderer_errors);
             }
             TextureSource::PrevPassAlpha => {
-                self.shaders.cs_scale_a8.bind(&mut self.device,
-                                              &projection,
-                                              &mut self.renderer_errors);
+                self.shaders.borrow_mut().cs_scale_a8.bind(&mut self.device,
+                                                           &projection,
+                                                           &mut self.renderer_errors);
             }
             _ => unreachable!(),
         }
 
         self.draw_instanced_batch(
             &scalings,
             VertexArrayKind::Scale,
             &BatchTextures::no_texture(),
@@ -3064,17 +3051,17 @@ impl Renderer {
         // separable implementation.
         // TODO(gw): In the future, consider having
         //           fast path blur shaders for common
         //           blur radii with fixed weights.
         if !target.vertical_blurs.is_empty() || !target.horizontal_blurs.is_empty() {
             let _timer = self.gpu_profile.start_timer(GPU_TAG_BLUR);
 
             self.set_blend(false, framebuffer_kind);
-            self.shaders.cs_blur_rgba8
+            self.shaders.borrow_mut().cs_blur_rgba8
                 .bind(&mut self.device, projection, &mut self.renderer_errors);
 
             if !target.vertical_blurs.is_empty() {
                 self.draw_instanced_batch(
                     &target.vertical_blurs,
                     VertexArrayKind::Blur,
                     &BatchTextures::no_texture(),
                     stats,
@@ -3122,17 +3109,17 @@ impl Renderer {
 
                 // Draw opaque batches front-to-back for maximum
                 // z-buffer efficiency!
                 for batch in alpha_batch_container
                     .opaque_batches
                     .iter()
                     .rev()
                 {
-                    self.shaders
+                    self.shaders.borrow_mut()
                         .get(&batch.key, self.debug_flags)
                         .bind(
                             &mut self.device, projection,
                             &mut self.renderer_errors,
                         );
 
                     let _timer = self.gpu_profile.start_timer(batch.key.kind.sampler_tag());
                     self.draw_instanced_batch(
@@ -3169,17 +3156,17 @@ impl Renderer {
                 } else {
                     target_rect
                 };
                 self.device.enable_scissor();
                 self.device.set_scissor_rect(rect);
             }
 
             for batch in &alpha_batch_container.alpha_batches {
-                self.shaders
+                self.shaders.borrow_mut()
                     .get(&batch.key, self.debug_flags)
                     .bind(
                         &mut self.device, projection,
                         &mut self.renderer_errors,
                     );
 
                 if batch.key.blend_mode != prev_blend_mode {
                     match batch.key.blend_mode {
@@ -3356,17 +3343,17 @@ impl Renderer {
         // separable implementation.
         // TODO(gw): In the future, consider having
         //           fast path blur shaders for common
         //           blur radii with fixed weights.
         if !target.vertical_blurs.is_empty() || !target.horizontal_blurs.is_empty() {
             let _timer = self.gpu_profile.start_timer(GPU_TAG_BLUR);
 
             self.set_blend(false, FramebufferKind::Other);
-            self.shaders.cs_blur_a8
+            self.shaders.borrow_mut().cs_blur_a8
                 .bind(&mut self.device, projection, &mut self.renderer_errors);
 
             if !target.vertical_blurs.is_empty() {
                 self.draw_instanced_batch(
                     &target.vertical_blurs,
                     VertexArrayKind::Blur,
                     &BatchTextures::no_texture(),
                     stats,
@@ -3391,17 +3378,17 @@ impl Renderer {
 
             // switch to multiplicative blending
             self.set_blend(true, FramebufferKind::Other);
             self.set_blend_mode_multiply(FramebufferKind::Other);
 
             // draw rounded cornered rectangles
             if !target.clip_batcher.rectangles.is_empty() {
                 let _gm2 = self.gpu_profile.start_marker("clip rectangles");
-                self.shaders.cs_clip_rectangle.bind(
+                self.shaders.borrow_mut().cs_clip_rectangle.bind(
                     &mut self.device,
                     projection,
                     &mut self.renderer_errors,
                 );
                 self.draw_instanced_batch(
                     &target.clip_batcher.rectangles,
                     VertexArrayKind::Clip,
                     &BatchTextures::no_texture(),
@@ -3413,30 +3400,30 @@ impl Renderer {
                 let _gm2 = self.gpu_profile.start_marker("box-shadows");
                 let textures = BatchTextures {
                     colors: [
                         mask_texture_id.clone(),
                         TextureSource::Invalid,
                         TextureSource::Invalid,
                     ],
                 };
-                self.shaders.cs_clip_box_shadow
+                self.shaders.borrow_mut().cs_clip_box_shadow
                     .bind(&mut self.device, projection, &mut self.renderer_errors);
                 self.draw_instanced_batch(
                     items,
                     VertexArrayKind::Clip,
                     &textures,
                     stats,
                 );
             }
 
             // draw line decoration clips
             if !target.clip_batcher.line_decorations.is_empty() {
                 let _gm2 = self.gpu_profile.start_marker("clip lines");
-                self.shaders.cs_clip_line.bind(
+                self.shaders.borrow_mut().cs_clip_line.bind(
                     &mut self.device,
                     projection,
                     &mut self.renderer_errors,
                 );
                 self.draw_instanced_batch(
                     &target.clip_batcher.line_decorations,
                     VertexArrayKind::Clip,
                     &BatchTextures::no_texture(),
@@ -3449,17 +3436,17 @@ impl Renderer {
                 let _gm2 = self.gpu_profile.start_marker("clip images");
                 let textures = BatchTextures {
                     colors: [
                         mask_texture_id.clone(),
                         TextureSource::Invalid,
                         TextureSource::Invalid,
                     ],
                 };
-                self.shaders.cs_clip_image
+                self.shaders.borrow_mut().cs_clip_image
                     .bind(&mut self.device, projection, &mut self.renderer_errors);
                 self.draw_instanced_batch(
                     items,
                     VertexArrayKind::Clip,
                     &textures,
                     stats,
                 );
             }
@@ -3525,32 +3512,32 @@ impl Renderer {
            !target.border_segments_complex.is_empty()
         {
             let _timer = self.gpu_profile.start_timer(GPU_TAG_CACHE_BORDER);
 
             self.set_blend(true, FramebufferKind::Other);
             self.set_blend_mode_premultiplied_alpha(FramebufferKind::Other);
 
             if !target.border_segments_solid.is_empty() {
-                self.shaders.cs_border_solid.bind(
+                self.shaders.borrow_mut().cs_border_solid.bind(
                     &mut self.device,
                     &projection,
                     &mut self.renderer_errors,
                 );
 
                 self.draw_instanced_batch(
                     &target.border_segments_solid,
                     VertexArrayKind::Border,
                     &BatchTextures::no_texture(),
                     stats,
                 );
             }
 
             if !target.border_segments_complex.is_empty() {
-                self.shaders.cs_border_segment.bind(
+                self.shaders.borrow_mut().cs_border_segment.bind(
                     &mut self.device,
                     &projection,
                     &mut self.renderer_errors,
                 );
 
                 self.draw_instanced_batch(
                     &target.border_segments_complex,
                     VertexArrayKind::Border,
@@ -3561,20 +3548,23 @@ impl Renderer {
 
             self.set_blend(false, FramebufferKind::Other);
         }
 
         // Draw any blurs for this target.
         if !target.horizontal_blurs.is_empty() {
             let _timer = self.gpu_profile.start_timer(GPU_TAG_BLUR);
 
-            match target.target_kind {
-                RenderTargetKind::Alpha => &mut self.shaders.cs_blur_a8,
-                RenderTargetKind::Color => &mut self.shaders.cs_blur_rgba8,
-            }.bind(&mut self.device, &projection, &mut self.renderer_errors);
+            {
+                let mut shaders = self.shaders.borrow_mut();
+                match target.target_kind {
+                    RenderTargetKind::Alpha => &mut shaders.cs_blur_a8,
+                    RenderTargetKind::Color => &mut shaders.cs_blur_rgba8,
+                }.bind(&mut self.device, &projection, &mut self.renderer_errors);
+            }
 
             self.draw_instanced_batch(
                 &target.horizontal_blurs,
                 VertexArrayKind::Blur,
                 &BatchTextures::no_texture(),
                 stats,
             );
         }
@@ -4238,17 +4228,19 @@ impl Renderer {
         #[cfg(feature = "debug_renderer")]
         {
             self.debug.deinit(&mut self.device);
         }
 
         for (_, target) in self.output_targets {
             self.device.delete_fbo(target.fbo_id);
         }
-        self.shaders.deinit(&mut self.device);
+        if let Ok(shaders) = Rc::try_unwrap(self.shaders) {
+            shaders.into_inner().deinit(&mut self.device);
+        }
         #[cfg(feature = "capture")]
         self.device.delete_fbo(self.read_fbo);
         #[cfg(feature = "replay")]
         for (_, ext) in self.owned_external_images {
             self.device.delete_external_texture(ext);
         }
         self.device.end_frame();
     }
--- a/gfx/webrender/src/resource_cache.rs
+++ b/gfx/webrender/src/resource_cache.rs
@@ -1390,17 +1390,17 @@ impl ResourceCache {
             }
         })
     }
 
     pub fn begin_frame(&mut self, frame_id: FrameId) {
         debug_assert_eq!(self.state, State::Idle);
         self.state = State::AddResources;
         self.texture_cache.begin_frame(frame_id);
-        self.cached_glyphs.begin_frame(&self.texture_cache, &self.cached_render_tasks);
+        self.cached_glyphs.begin_frame(&self.texture_cache, &self.cached_render_tasks, &mut self.glyph_rasterizer);
         self.cached_render_tasks.begin_frame(&mut self.texture_cache);
         self.current_frame_id = frame_id;
     }
 
     pub fn block_until_all_resources_added(
         &mut self,
         gpu_cache: &mut GpuCache,
         render_tasks: &mut RenderTaskTree,
--- a/gfx/webrender/src/scene_builder.rs
+++ b/gfx/webrender/src/scene_builder.rs
@@ -9,29 +9,31 @@ use api::channel::MsgSender;
 #[cfg(feature = "capture")]
 use capture::CaptureConfig;
 use frame_builder::{FrameBuilderConfig, FrameBuilder};
 use clip::{ClipDataInterner, ClipDataUpdateList};
 use clip_scroll_tree::ClipScrollTree;
 use display_list_flattener::DisplayListFlattener;
 use internal_types::{FastHashMap, FastHashSet};
 use picture::PictureIdGenerator;
+use prim_store::{PrimitiveDataInterner, PrimitiveDataUpdateList};
 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;
 use std::thread;
 use std::time::Duration;
 
 pub struct DocumentResourceUpdates {
     pub clip_updates: ClipDataUpdateList,
+    pub prim_updates: PrimitiveDataUpdateList,
 }
 
 /// 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)>,
@@ -39,16 +41,17 @@ pub struct Transaction {
     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 render_frame: bool,
+    pub invalidate_rendered_frame: bool,
 }
 
 impl Transaction {
     pub fn can_skip_scene_builder(&self) -> bool {
         self.request_scene_build.is_none() &&
             self.display_list_updates.is_empty() &&
             self.epoch_updates.is_empty() &&
             self.removed_pipelines.is_empty() &&
@@ -72,16 +75,17 @@ pub struct BuiltTransaction {
     pub blob_rasterizer: Option<Box<AsyncBlobImageRasterizer>>,
     pub frame_ops: Vec<FrameMsg>,
     pub removed_pipelines: Vec<PipelineId>,
     pub notifications: Vec<NotificationRequest>,
     pub doc_resource_updates: Option<DocumentResourceUpdates>,
     pub scene_build_start_time: u64,
     pub scene_build_end_time: u64,
     pub render_frame: bool,
+    pub invalidate_rendered_frame: bool,
 }
 
 pub struct DisplayListUpdate {
     pub pipeline_id: PipelineId,
     pub epoch: Epoch,
     pub built_display_list: BuiltDisplayList,
     pub background: Option<ColorF>,
     pub viewport_size: LayoutSize,
@@ -155,22 +159,24 @@ pub enum SceneSwapResult {
 // primitives and other things between display lists so that:
 // - GPU cache handles remain valid, reducing GPU cache updates.
 // - Comparison of primitives and pictures between two
 //   display lists is (a) fast (b) done during scene building.
 #[cfg_attr(feature = "capture", derive(Serialize))]
 #[cfg_attr(feature = "replay", derive(Deserialize))]
 pub struct DocumentResources {
     pub clip_interner: ClipDataInterner,
+    pub prim_interner: PrimitiveDataInterner,
 }
 
 impl DocumentResources {
     fn new() -> Self {
         DocumentResources {
             clip_interner: ClipDataInterner::new(),
+            prim_interner: PrimitiveDataInterner::new(),
         }
     }
 }
 
 // A document in the scene builder contains the current scene,
 // as well as a persistent clip interner. This allows clips
 // to be de-duplicated, and persisted in the GPU cache between
 // display lists.
@@ -329,19 +335,25 @@ impl SceneBuilder {
                     &mut item.doc_resources,
                 );
 
                 let clip_updates = item
                     .doc_resources
                     .clip_interner
                     .end_frame_and_get_pending_updates();
 
+                let prim_updates = item
+                    .doc_resources
+                    .prim_interner
+                    .end_frame_and_get_pending_updates();
+
                 doc_resource_updates = Some(
                     DocumentResourceUpdates {
                         clip_updates,
+                        prim_updates,
                     }
                 );
 
                 built_scene = Some(BuiltScene {
                     scene: new_scene,
                     frame_builder,
                     clip_scroll_tree,
                 });
@@ -353,16 +365,17 @@ impl SceneBuilder {
                     scene: item.scene,
                     resources: item.doc_resources,
                 },
             );
 
             let txn = Box::new(BuiltTransaction {
                 document_id: item.document_id,
                 render_frame: item.build_frame,
+                invalidate_rendered_frame: false,
                 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,
@@ -428,19 +441,25 @@ impl SceneBuilder {
                 );
 
                 // Retrieve the list of updates from the clip interner.
                 let clip_updates = doc
                     .resources
                     .clip_interner
                     .end_frame_and_get_pending_updates();
 
+                let prim_updates = doc
+                    .resources
+                    .prim_interner
+                    .end_frame_and_get_pending_updates();
+
                 doc_resource_updates = Some(
                     DocumentResourceUpdates {
                         clip_updates,
+                        prim_updates,
                     }
                 );
 
                 built_scene = Some(BuiltScene {
                     scene: new_scene,
                     frame_builder,
                     clip_scroll_tree,
                 });
@@ -463,16 +482,17 @@ impl SceneBuilder {
 
         if self.simulate_slow_ms > 0 {
             thread::sleep(Duration::from_millis(self.simulate_slow_ms as u64));
         }
 
         Box::new(BuiltTransaction {
             document_id: txn.document_id,
             render_frame: txn.render_frame,
+            invalidate_rendered_frame: txn.invalidate_rendered_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()),
             doc_resource_updates,
--- a/gfx/webrender/src/shade.rs
+++ b/gfx/webrender/src/shade.rs
@@ -15,16 +15,18 @@ use renderer::{
     MAX_VERTEX_TEXTURE_WIDTH,
     BlendMode, DebugFlags, ImageBufferKind, RendererError, RendererOptions,
     TextureSampler, VertexArrayKind, ShaderPrecacheFlags,
 };
 
 use gleam::gl::GlType;
 use time::precise_time_ns;
 
+use std::cell::RefCell;
+use std::rc::Rc;
 
 impl ImageBufferKind {
     pub(crate) fn get_feature_string(&self) -> &'static str {
         match *self {
             ImageBufferKind::Texture2D => "TEXTURE_2D",
             ImageBufferKind::Texture2DArray => "",
             ImageBufferKind::TextureRect => "TEXTURE_RECT",
             ImageBufferKind::TextureExternal => "TEXTURE_EXTERNAL",
@@ -812,8 +814,16 @@ impl Shaders {
                 shader.deinit(device);
             }
         }
         self.cs_border_solid.deinit(device);
         self.cs_border_segment.deinit(device);
         self.ps_split_composite.deinit(device);
     }
 }
+
+// A wrapper around a strong reference to a Shaders
+// object. We have this so that external (ffi)
+// consumers can own a reference to a shared Shaders
+// instance without understanding rust's refcounting.
+pub struct WrShaders {
+    pub shaders: Rc<RefCell<Shaders>>,
+}
--- a/gfx/webrender/src/spatial_node.rs
+++ b/gfx/webrender/src/spatial_node.rs
@@ -246,20 +246,25 @@ impl SpatialNode {
         state: &mut TransformUpdateState,
         coord_systems: &mut Vec<CoordinateSystem>,
         scene_properties: &SceneProperties,
     ) {
         match self.node_type {
             SpatialNodeType::ReferenceFrame(ref mut info) => {
                 // Resolve the transform against any property bindings.
                 let source_transform = scene_properties.resolve_layout_transform(&info.source_transform);
+                // Do a change-basis operation on the perspective matrix using
+                // the scroll offset.
+                let scrolled_perspective = info.source_perspective
+                    .pre_translate(&state.parent_accumulated_scroll_offset)
+                    .post_translate(-state.parent_accumulated_scroll_offset);
                 info.resolved_transform =
                     LayoutFastTransform::with_vector(info.origin_in_parent_reference_frame)
                     .pre_mul(&source_transform.into())
-                    .pre_mul(&info.source_perspective);
+                    .pre_mul(&scrolled_perspective);
 
                 // The transformation for this viewport in world coordinates is the transformation for
                 // our parent reference frame, plus any accumulated scrolling offsets from nodes
                 // between our reference frame and this node. Finally, we also include
                 // whatever local transformation this reference frame provides.
                 let relative_transform = info.resolved_transform
                     .post_translate(state.parent_accumulated_scroll_offset)
                     .to_transform()
--- a/gfx/webrender_api/src/api.rs
+++ b/gfx/webrender_api/src/api.rs
@@ -55,29 +55,32 @@ pub struct Transaction {
     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,
 
+    invalidate_rendered_frame: bool,
+
     low_priority: bool,
 }
 
 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,
+            invalidate_rendered_frame: false,
             low_priority: false,
         }
     }
 
     // TODO: better name?
     pub fn skip_scene_builder(&mut self) {
         self.use_scene_builder_thread = false;
     }
@@ -85,16 +88,17 @@ impl Transaction {
     // TODO: this is temporary, using the scene builder thread is the default for
     // most transactions, and opt-in for specific cases like scrolling and async video.
     pub fn use_scene_builder_thread(&mut self) {
         self.use_scene_builder_thread = true;
     }
 
     pub fn is_empty(&self) -> bool {
         !self.generate_frame &&
+            !self.invalidate_rendered_frame &&
             self.scene_ops.is_empty() &&
             self.frame_ops.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.
@@ -241,16 +245,26 @@ impl Transaction {
     /// no-op; the arguments passed to `new_frame_ready` will provide information
     /// as to when happened.
     ///
     /// [notifier]: trait.RenderNotifier.html#tymethod.new_frame_ready
     pub fn generate_frame(&mut self) {
         self.generate_frame = true;
     }
 
+    /// Invalidate rendered frame. It ensure that frame will be rendered during
+    /// next frame generation. WebRender could skip frame rendering if there
+    /// is no update.
+    /// But there are cases that needs to force rendering.
+    ///  - Content of image is updated by reusing same ExternalImageId.
+    ///  - Platform requests it if pixels become stale (like wakeup from standby).
+    pub fn invalidate_rendered_frame(&mut self) {
+        self.invalidate_rendered_frame = true;
+    }
+
     /// Supply a list of animated property bindings that should be used to resolve
     /// bindings in the current display list.
     pub fn update_dynamic_properties(&mut self, properties: DynamicProperties) {
         self.frame_ops.push(FrameMsg::UpdateDynamicProperties(properties));
     }
 
     /// Add to the list of animated property bindings that should be used to
     /// resolve bindings in the current display list. This is a convenience method
@@ -275,16 +289,17 @@ impl Transaction {
         (
             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,
+                invalidate_rendered_frame: self.invalidate_rendered_frame,
                 low_priority: self.low_priority,
             },
             self.payloads,
         )
     }
 
     pub fn add_image(
         &mut self,
@@ -385,52 +400,56 @@ impl Transaction {
 
 /// Represents a transaction in the format sent through the channel.
 #[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 invalidate_rendered_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.invalidate_rendered_frame &&
             self.scene_ops.is_empty() &&
             self.frame_ops.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,
+            invalidate_rendered_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,
+            invalidate_rendered_frame: false,
             use_scene_builder_thread: false,
             low_priority: false,
         }
     }
 }
 
 #[derive(Clone, Deserialize, Serialize)]
 pub struct AddImage {
--- a/gfx/webrender_api/src/display_item.rs
+++ b/gfx/webrender_api/src/display_item.rs
@@ -738,67 +738,16 @@ impl YuvFormat {
 #[repr(C)]
 #[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
 pub struct ImageMask {
     pub image: ImageKey,
     pub rect: LayoutRect,
     pub repeat: bool,
 }
 
-#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
-pub enum LocalClip {
-    Rect(LayoutRect),
-    RoundedRect(LayoutRect, ComplexClipRegion),
-}
-
-impl From<LayoutRect> for LocalClip {
-    fn from(rect: LayoutRect) -> Self {
-        LocalClip::Rect(rect)
-    }
-}
-
-impl LocalClip {
-    pub fn clip_rect(&self) -> &LayoutRect {
-        match *self {
-            LocalClip::Rect(ref rect) => rect,
-            LocalClip::RoundedRect(ref rect, _) => rect,
-        }
-    }
-
-    pub fn create_with_offset(&self, offset: &LayoutVector2D) -> LocalClip {
-        match *self {
-            LocalClip::Rect(rect) => LocalClip::from(rect.translate(offset)),
-            LocalClip::RoundedRect(rect, complex) => LocalClip::RoundedRect(
-                rect.translate(offset),
-                ComplexClipRegion {
-                    rect: complex.rect.translate(offset),
-                    radii: complex.radii,
-                    mode: complex.mode,
-                },
-            ),
-        }
-    }
-
-    pub fn clip_by(&self, rect: &LayoutRect) -> LocalClip {
-        match *self {
-            LocalClip::Rect(clip_rect) => {
-                LocalClip::Rect(
-                    clip_rect.intersection(rect).unwrap_or_else(LayoutRect::zero)
-                )
-            }
-            LocalClip::RoundedRect(clip_rect, complex) => {
-                LocalClip::RoundedRect(
-                    clip_rect.intersection(rect).unwrap_or_else(LayoutRect::zero),
-                    complex,
-                )
-            }
-        }
-    }
-}
-
 #[repr(C)]
 #[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize, Eq, Hash)]
 pub enum ClipMode {
     Clip,    // Pixels inside the region are visible.
     ClipOut, // Pixels outside the region are visible.
 }
 
 impl Not for ClipMode {
--- a/gfx/webrender_bindings/RenderThread.cpp
+++ b/gfx/webrender_bindings/RenderThread.cpp
@@ -78,28 +78,18 @@ RenderThread::Start()
   sRenderThread = new RenderThread(thread);
 #ifdef XP_WIN
   widget::WinCompositorWindowThread::Start();
 #endif
   layers::SharedSurfacesParent::Initialize();
 
   RefPtr<Runnable> runnable = WrapRunnable(
     RefPtr<RenderThread>(sRenderThread.get()),
-    &RenderThread::InitSharedGLContext);
+    &RenderThread::InitDeviceTask);
   sRenderThread->Loop()->PostTask(runnable.forget());
-
-  if (XRE_IsGPUProcess() &&
-      gfx::gfxVars::UseWebRenderProgramBinary()) {
-    MOZ_ASSERT(gfx::gfxVars::UseWebRender());
-    // Initialize program cache if necessary
-    RefPtr<Runnable> runnable = WrapRunnable(
-      RefPtr<RenderThread>(sRenderThread.get()),
-      &RenderThread::ProgramCacheTask);
-    sRenderThread->Loop()->PostTask(runnable.forget());
-  }
 }
 
 // static
 void
 RenderThread::ShutDown()
 {
   MOZ_ASSERT(NS_IsMainThread());
   MOZ_ASSERT(sRenderThread);
@@ -661,19 +651,29 @@ RenderThread::GetRenderTexture(wr::WrExt
   MOZ_ASSERT(it != mRenderTextures.end());
   if (it == mRenderTextures.end()) {
     return nullptr;
   }
   return it->second;
 }
 
 void
-RenderThread::ProgramCacheTask()
+RenderThread::InitDeviceTask()
 {
-  ProgramCache();
+  MOZ_ASSERT(IsInRenderThread());
+  MOZ_ASSERT(!mSharedGL);
+
+  mSharedGL = CreateGLContext();
+  if (XRE_IsGPUProcess() &&
+      gfx::gfxVars::UseWebRenderProgramBinary()) {
+    ProgramCache();
+  }
+  // Query the shared GL context to force the
+  // lazy initialization to happen now.
+  SharedGL();
 }
 
 void
 RenderThread::HandleDeviceReset(const char* aWhere, bool aNotify)
 {
   MOZ_ASSERT(IsInRenderThread());
 
   if (mHandlingDeviceReset) {
@@ -727,42 +727,49 @@ RenderThread::ProgramCache()
   MOZ_ASSERT(IsInRenderThread());
 
   if (!mProgramCache) {
     mProgramCache = MakeUnique<WebRenderProgramCache>(ThreadPool().Raw());
   }
   return mProgramCache.get();
 }
 
-void
-RenderThread::InitSharedGLContext()
-{
-  MOZ_ASSERT(IsInRenderThread());
-
-  if (!mSharedGL) {
-    mSharedGL = CreateGLContext();
-  }
-}
-
 gl::GLContext*
 RenderThread::SharedGL()
 {
   MOZ_ASSERT(IsInRenderThread());
-  InitSharedGLContext();
+  if (!mSharedGL) {
+    mSharedGL = CreateGLContext();
+    mShaders = nullptr;
+  }
+  if (mSharedGL && !mShaders) {
+    mShaders = MakeUnique<WebRenderShaders>(mSharedGL, mProgramCache.get());
+  }
 
   return mSharedGL.get();
 }
 
 void
 RenderThread::ClearSharedGL()
 {
   MOZ_ASSERT(IsInRenderThread());
+  mShaders = nullptr;
   mSharedGL = nullptr;
 }
 
+WebRenderShaders::WebRenderShaders(gl::GLContext* gl,
+                                   WebRenderProgramCache* programCache)
+{
+  mGL = gl;
+  mShaders = wr_shaders_new(gl, programCache ? programCache->Raw() : nullptr);
+}
+
+WebRenderShaders::~WebRenderShaders() {
+  wr_shaders_delete(mShaders, mGL.get());
+}
 
 WebRenderThreadPool::WebRenderThreadPool()
 {
   mThreadPool = wr_thread_pool_new();
 }
 
 WebRenderThreadPool::~WebRenderThreadPool()
 {
@@ -797,16 +804,22 @@ CreateGLContextANGLE()
   if (!gl::GLLibraryEGL::EnsureInitialized(/* forceAccel */ true, &discardFailureId)) {
     gfxCriticalNote << "Failed to load EGL library: " << discardFailureId.get();
     return nullptr;
   }
 
   auto* egl = gl::GLLibraryEGL::Get();
   auto flags = gl::CreateContextFlags::PREFER_ES3;
 
+  if (egl->IsExtensionSupported(
+     gl::GLLibraryEGL::MOZ_create_context_provoking_vertex_dont_care))
+  {
+     flags |= gl::CreateContextFlags::PROVOKING_VERTEX_DONT_CARE;
+  }
+
   // Create GLContext with dummy EGLSurface, the EGLSurface is not used.
   // Instread we override it with EGLSurface of SwapChain's back buffer.
   RefPtr<gl::GLContext> gl = gl::GLContextProviderEGL::CreateHeadless(flags, &discardFailureId);
   if (!gl || !gl->IsANGLE()) {
     gfxCriticalNote << "Failed ANGLE GL context creation for WebRender: " << gfx::hexa(gl.get());
     return nullptr;
   }
 
--- a/gfx/webrender_bindings/RenderThread.h
+++ b/gfx/webrender_bindings/RenderThread.h
@@ -54,16 +54,28 @@ public:
   ~WebRenderProgramCache();
 
   wr::WrProgramCache* Raw() { return mProgramCache; }
 
 protected:
   wr::WrProgramCache* mProgramCache;
 };
 
+class WebRenderShaders {
+public:
+  WebRenderShaders(gl::GLContext* gl, WebRenderProgramCache* programCache);
+  ~WebRenderShaders();
+
+  wr::WrShaders* RawShaders() { return mShaders; }
+
+protected:
+  RefPtr<gl::GLContext> mGL;
+  wr::WrShaders* mShaders;
+};
+
 /// Base class for an event that can be scheduled to run on the render thread.
 ///
 /// The event can be passed through the same channels as regular WebRender messages
 /// to preserve ordering.
 class RendererEvent
 {
 public:
   virtual ~RendererEvent() {}
@@ -176,17 +188,18 @@ public:
 
   /// Can be called from any thread.
   WebRenderThreadPool& ThreadPool() { return mThreadPool; }
 
   /// Can only be called from the render thread.
   WebRenderProgramCache* ProgramCache();
 
   /// Can only be called from the render thread.
-  void InitSharedGLContext();
+  WebRenderShaders* Shaders() { return mShaders.get(); }
+
   /// Can only be called from the render thread.
   gl::GLContext* SharedGL();
 
   void ClearSharedGL();
 
   /// Can only be called from the render thread.
   void HandleDeviceReset(const char* aWhere, bool aNotify);
   /// Can only be called from the render thread.
@@ -196,26 +209,28 @@ public:
 
   size_t RendererCount();
 
 private:
   explicit RenderThread(base::Thread* aThread);
 
   void DeferredRenderTextureHostDestroy();
   void ShutDownTask(layers::SynchronousTask* aTask);
-  void ProgramCacheTask();
+  void InitDeviceTask();
 
   void DoAccumulateMemoryReport(MemoryReport, const RefPtr<MemoryReportPromise::Private>&);
 
   ~RenderThread();
 
   base::Thread* const mThread;
 
   WebRenderThreadPool mThreadPool;
+
   UniquePtr<WebRenderProgramCache> mProgramCache;
+  UniquePtr<WebRenderShaders> mShaders;
 
   // An optional shared GLContext to be used for all
   // windows.
   RefPtr<gl::GLContext> mSharedGL;
 
   std::map<wr::WindowId, UniquePtr<RendererOGL>> mRenderers;
 
   struct WindowInfo {
--- a/gfx/webrender_bindings/WebRenderAPI.cpp
+++ b/gfx/webrender_bindings/WebRenderAPI.cpp
@@ -68,16 +68,18 @@ public:
 
     *mUseANGLE = compositor->UseANGLE();
     *mUseDComp = compositor->UseDComp();
 
     bool supportLowPriorityTransactions = true; // TODO only for main windows.
     wr::Renderer* wrRenderer = nullptr;
     if (!wr_window_new(aWindowId, mSize.width, mSize.height, supportLowPriorityTransactions,
                        compositor->gl(),
+                       aRenderThread.ProgramCache() ? aRenderThread.ProgramCache()->Raw() : nullptr,
+                       aRenderThread.Shaders() ? aRenderThread.Shaders()->RawShaders() : nullptr,
                        aRenderThread.ThreadPool().Raw(),
                        &WebRenderMallocSizeOf,
                        mDocHandle, &wrRenderer,
                        mMaxTextureSize)) {
       // wr_window_new puts a message into gfxCriticalNote if it returns false
       return;
     }
     MOZ_ASSERT(wrRenderer);
@@ -86,19 +88,16 @@ public:
     auto renderer = MakeUnique<RendererOGL>(std::move(thread),
                                             std::move(compositor),
                                             aWindowId,
                                             wrRenderer,
                                             mBridge);
     if (wrRenderer && renderer) {
       wr::WrExternalImageHandler handler = renderer->GetExternalImageHandler();
       wr_renderer_set_external_image_handler(wrRenderer, &handler);
-      if (gfx::gfxVars::UseWebRenderProgramBinary()) {
-        wr_renderer_update_program_cache(wrRenderer, aRenderThread.ProgramCache()->Raw());
-      }
     }
 
     if (renderer) {
       layers::SyncObjectHost* syncObj = renderer->GetSyncObject();
       if (syncObj) {
         *mSyncHandle = syncObj->GetSyncHandle();
       }
     }
@@ -205,16 +204,22 @@ TransactionBuilder::ClearDisplayList(Epo
 
 void
 TransactionBuilder::GenerateFrame()
 {
   wr_transaction_generate_frame(mTxn);
 }
 
 void
+TransactionBuilder::InvalidateRenderedFrame()
+{
+  wr_transaction_invalidate_rendered_frame(mTxn);
+}
+
+void
 TransactionBuilder::UpdateDynamicProperties(const nsTArray<wr::WrOpacityProperty>& aOpacityArray,
                                      const nsTArray<wr::WrTransformProperty>& aTransformArray)
 {
   wr_transaction_update_dynamic_properties(
       mTxn,
       aOpacityArray.IsEmpty() ?  nullptr : aOpacityArray.Elements(),
       aOpacityArray.Length(),
       aTransformArray.IsEmpty() ?  nullptr : aTransformArray.Elements(),
--- a/gfx/webrender_bindings/WebRenderAPI.h
+++ b/gfx/webrender_bindings/WebRenderAPI.h
@@ -84,16 +84,18 @@ public:
                       const wr::LayoutSize& content_size,
                       wr::BuiltDisplayListDescriptor dl_descriptor,
                       wr::Vec<uint8_t>& dl_data);
 
   void ClearDisplayList(Epoch aEpoch, wr::WrPipelineId aPipeline);
 
   void GenerateFrame();
 
+  void InvalidateRenderedFrame();
+
   void UpdateDynamicProperties(const nsTArray<wr::WrOpacityProperty>& aOpacityArray,
                                const nsTArray<wr::WrTransformProperty>& aTransformArray);
 
   void SetWindowParameters(const LayoutDeviceIntSize& aWindowSize,
                            const LayoutDeviceIntRect& aDocRect);
 
   void UpdateScrollPosition(const wr::WrPipelineId& aPipelineId,
                             const layers::FrameMetrics::ViewID& aScrollId,
--- a/gfx/webrender_bindings/revision.txt
+++ b/gfx/webrender_bindings/revision.txt
@@ -1,1 +1,1 @@
-3c3f9a4e919b81639f078d7bd101012de61b9396
+69fddc3faf1a379a560106f12687d08cbbe304dd
--- a/gfx/webrender_bindings/src/bindings.rs
+++ b/gfx/webrender_bindings/src/bindings.rs
@@ -1,25 +1,26 @@
 use std::ffi::{CStr, CString};
 use std::{mem, slice, ptr, env};
 use std::path::PathBuf;
 use std::rc::Rc;
+use std::cell::RefCell;
 use std::sync::Arc;
 use std::sync::atomic::{AtomicUsize, Ordering};
 use std::os::raw::{c_void, c_char, c_float};
 use gleam::gl;
 
 use webrender::api::*;
 use webrender::{ReadPixelsFormat, Renderer, RendererOptions, ThreadListener};
 use webrender::{ExternalImage, ExternalImageHandler, ExternalImageSource};
 use webrender::DebugFlags;
 use webrender::{ApiRecordingReceiver, BinaryRecorder};
 use webrender::{AsyncPropertySampler, PipelineInfo, SceneBuilderHooks};
 use webrender::{UploadMethod, VertexUsageHint};
-use webrender::ShaderPrecacheFlags;
+use webrender::{Device, Shaders, WrShaders, ShaderPrecacheFlags};
 use thread_profiler::register_thread_with_profiler;
 use moz2d_renderer::Moz2dBlobImageHandler;
 use program_cache::{WrProgramCache, remove_disk_cache};
 use app_units::Au;
 use rayon;
 use euclid::SideOffsets2D;
 use nsstring::nsAString;
 
@@ -920,22 +921,67 @@ pub extern "C" fn wr_renderer_update_pro
 }
 
 // This matches IsEnvSet in gfxEnv.h
 fn env_var_to_bool(key: &'static str) -> bool {
     env::var(key).ok().map_or(false, |v| !v.is_empty())
 }
 
 // Call MakeCurrent before this.
+fn wr_device_new(gl_context: *mut c_void, pc: Option<&mut WrProgramCache>)
+    -> Device
+{
+    assert!(unsafe { is_in_render_thread() });
+
+    let gl;
+    if unsafe { is_glcontext_egl(gl_context) } {
+        gl = unsafe { gl::GlesFns::load_with(|symbol| get_proc_address(gl_context, symbol)) };
+    } else {
+        gl = unsafe { gl::GlFns::load_with(|symbol| get_proc_address(gl_context, symbol)) };
+    }
+
+    let version = gl.get_string(gl::VERSION);
+
+    info!("WebRender - OpenGL version new {}", version);
+
+    let upload_method = if unsafe { is_glcontext_angle(gl_context) } {
+        UploadMethod::Immediate
+    } else {
+        UploadMethod::PixelBuffer(VertexUsageHint::Dynamic)
+    };
+
+    let resource_override_path = unsafe {
+        let override_charptr = gfx_wr_resource_path_override();
+        if override_charptr.is_null() {
+            None
+        } else {
+            match CStr::from_ptr(override_charptr).to_str() {
+                Ok(override_str) => Some(PathBuf::from(override_str)),
+                _ => None
+            }
+        }
+    };
+
+    let cached_programs = match pc {
+      Some(cached_programs) => Some(Rc::clone(cached_programs.rc_get())),
+      None => None,
+    };
+
+    Device::new(gl, resource_override_path, upload_method, cached_programs)
+}
+
+// Call MakeCurrent before this.
 #[no_mangle]
 pub extern "C" fn wr_window_new(window_id: WrWindowId,
                                 window_width: u32,
                                 window_height: u32,
                                 support_low_priority_transactions: bool,
                                 gl_context: *mut c_void,
+                                program_cache: Option<&mut WrProgramCache>,
+                                shaders: Option<&mut WrShaders>,
                                 thread_pool: *mut WrThreadPool,
                                 size_of_op: VoidPtrToSizeFn,
                                 out_handle: &mut *mut DocumentHandle,
                                 out_renderer: &mut *mut Renderer,
                                 out_max_texture_size: *mut u32)
                                 -> bool {
     assert!(unsafe { is_in_render_thread() });
 
@@ -969,25 +1015,31 @@ pub extern "C" fn wr_window_new(window_i
     };
 
     let precache_flags = if env_var_to_bool("MOZ_WR_PRECACHE_SHADERS") {
         ShaderPrecacheFlags::FULL_COMPILE
     } else {
         ShaderPrecacheFlags::empty()
     };
 
+    let cached_programs = match program_cache {
+        Some(program_cache) => Some(Rc::clone(&program_cache.rc_get())),
+        None => None,
+    };
+
     let opts = RendererOptions {
         enable_aa: true,
         enable_subpixel_aa: true,
         support_low_priority_transactions,
         recorder: recorder,
         blob_image_handler: Some(Box::new(Moz2dBlobImageHandler::new(workers.clone()))),
         workers: Some(workers.clone()),
         thread_listener: Some(Box::new(GeckoProfilerThreadListener::new())),
         size_of_op: Some(size_of_op),
+        cached_programs,
         resource_override_path: unsafe {
             let override_charptr = gfx_wr_resource_path_override();
             if override_charptr.is_null() {
                 None
             } else {
                 match CStr::from_ptr(override_charptr).to_str() {
                     Ok(override_str) => Some(PathBuf::from(override_str)),
                     _ => None
@@ -1003,17 +1055,17 @@ pub extern "C" fn wr_window_new(window_i
         precache_flags,
         namespace_alloc_by_client: true,
         ..Default::default()
     };
 
     let notifier = Box::new(CppNotifier {
         window_id: window_id,
     });
-    let (renderer, sender) = match Renderer::new(gl, notifier, opts) {
+    let (renderer, sender) = match Renderer::new(gl, notifier, opts, shaders) {
         Ok((renderer, sender)) => (renderer, sender),
         Err(e) => {
             warn!(" Failed to create a Renderer: {:?}", e);
             let msg = CString::new(format!("wr_window_new: {:?}", e)).unwrap();
             unsafe {
                 gfx_critical_note(msg.as_ptr());
             }
             return false;
@@ -1218,16 +1270,21 @@ pub extern "C" fn wr_transaction_set_win
 }
 
 #[no_mangle]
 pub extern "C" fn wr_transaction_generate_frame(txn: &mut Transaction) {
     txn.generate_frame();
 }
 
 #[no_mangle]
+pub extern "C" fn wr_transaction_invalidate_rendered_frame(txn: &mut Transaction) {
+    txn.invalidate_rendered_frame();
+}
+
+#[no_mangle]
 pub extern "C" fn wr_transaction_update_dynamic_properties(
     txn: &mut Transaction,
     opacity_array: *const WrOpacityProperty,
     opacity_count: usize,
     transform_array: *const WrTransformProperty,
     transform_count: usize,
 ) {
     let mut properties = DynamicProperties {
@@ -2590,8 +2647,63 @@ fn unpack_clip_id(id: usize, pipeline_id
     let id = id >> 1;
 
     match type_value {
         0 => ClipId::Spatial(id, pipeline_id),
         1 => ClipId::Clip(id, pipeline_id),
         _ => unreachable!("Got a bizarre value for the clip type"),
     }
 }
+
+/// cbindgen:postfix=WR_DESTRUCTOR_SAFE_FUNC
+#[no_mangle]
+pub unsafe extern "C" fn wr_device_delete(device: *mut Device) {
+    Box::from_raw(device);
+}
+
+// Call MakeCurrent before this.
+#[no_mangle]
+pub extern "C" fn wr_shaders_new(gl_context: *mut c_void,
+                                 program_cache: Option<&mut WrProgramCache>) -> *mut WrShaders {
+    let mut device = wr_device_new(gl_context, program_cache);
+
+    let precache_flags = if env_var_to_bool("MOZ_WR_PRECACHE_SHADERS") {
+        ShaderPrecacheFlags::FULL_COMPILE
+    } else {
+        ShaderPrecacheFlags::ASYNC_COMPILE
+    };
+
+    let opts = RendererOptions {
+        precache_flags,
+        ..Default::default()
+    };
+
+    let gl_type = device.gl().get_type();
+    device.begin_frame();
+
+    let shaders = Rc::new(RefCell::new(match Shaders::new(&mut device, gl_type, &opts) {
+        Ok(shaders) => shaders,
+        Err(e) => {
+            warn!(" Failed to create a Shaders: {:?}", e);
+            let msg = CString::new(format!("wr_shaders_new: {:?}", e)).unwrap();
+            unsafe {
+                gfx_critical_note(msg.as_ptr());
+            }
+            return ptr::null_mut();
+        }
+    }));
+
+    let shaders = WrShaders { shaders };
+
+    device.end_frame();
+    Box::into_raw(Box::new(shaders))
+}
+
+/// cbindgen:postfix=WR_DESTRUCTOR_SAFE_FUNC
+#[no_mangle]
+pub unsafe extern "C" fn wr_shaders_delete(shaders: *mut WrShaders, gl_context: *mut c_void) {
+    let mut device = wr_device_new(gl_context, None);
+    let shaders = Box::from_raw(shaders);
+    if let Ok(shaders) = Rc::try_unwrap(shaders.shaders) {
+      shaders.into_inner().deinit(&mut device);
+    }
+    // let shaders go out of scope and get dropped
+}
--- a/gfx/webrender_bindings/webrender_ffi_generated.h
+++ b/gfx/webrender_bindings/webrender_ffi_generated.h
@@ -265,16 +265,18 @@ enum class YuvColorSpace : uint32_t {
   Rec709 = 1,
 
   Sentinel /* this must be last for serialization purposes. */
 };
 
 template<typename T>
 struct Arc;
 
+struct Device;
+
 // Geometry in the coordinate system of the render target (screen or intermediate
 // surface) in physical pixels.
 struct DevicePixel;
 
 struct DocumentHandle;
 
 // Geometry in a stacking context's local coordinate space (logical pixels).
 struct LayoutPixel;
@@ -298,16 +300,18 @@ struct UnknownUnit;
 template<typename T>
 struct Vec;
 
 // Geometry in the document's coordinate space (logical pixels).
 struct WorldPixel;
 
 struct WrProgramCache;
 
+struct WrShaders;
+
 struct WrState;
 
 struct WrThreadPool;
 
 struct IdNamespace {
   uint32_t mHandle;
 
   bool operator==(const IdNamespace& aOther) const {
@@ -1237,16 +1241,20 @@ WR_INLINE
 void wr_clear_item_tag(WrState *aState)
 WR_FUNC;
 
 WR_INLINE
 void wr_dec_ref_arc(const VecU8 *aArc)
 WR_DESTRUCTOR_SAFE_FUNC;
 
 WR_INLINE
+void wr_device_delete(Device *aDevice)
+WR_DESTRUCTOR_SAFE_FUNC;
+
+WR_INLINE
 void wr_dp_clear_save(WrState *aState)
 WR_FUNC;
 
 WR_INLINE
 uintptr_t wr_dp_define_clip(WrState *aState,
                             const uintptr_t *aParentId,
                             LayoutRect aClipRect,
                             const ComplexClipRegion *aComplex,
@@ -1760,16 +1768,26 @@ extern void wr_schedule_render(WrWindowI
 
 WR_INLINE
 void wr_set_item_tag(WrState *aState,
                      uint64_t aScrollId,
                      uint16_t aHitInfo)
 WR_FUNC;
 
 WR_INLINE
+void wr_shaders_delete(WrShaders *aShaders,
+                       void *aGlContext)
+WR_DESTRUCTOR_SAFE_FUNC;
+
+WR_INLINE
+WrShaders *wr_shaders_new(void *aGlContext,
+                          WrProgramCache *aProgramCache)
+WR_FUNC;
+
+WR_INLINE
 void wr_state_delete(WrState *aState)
 WR_DESTRUCTOR_SAFE_FUNC;
 
 WR_INLINE
 WrState *wr_state_new(WrPipelineId aPipelineId,
                       LayoutSize aContentSize,
                       uintptr_t aCapacity)
 WR_FUNC;
@@ -1798,16 +1816,20 @@ WR_INLINE
 void wr_transaction_delete(Transaction *aTxn)
 WR_DESTRUCTOR_SAFE_FUNC;
 
 WR_INLINE
 void wr_transaction_generate_frame(Transaction *aTxn)
 WR_FUNC;
 
 WR_INLINE
+void wr_transaction_invalidate_rendered_frame(Transaction *aTxn)
+WR_FUNC;
+
+WR_INLINE
 bool wr_transaction_is_empty(const Transaction *aTxn)
 WR_FUNC;
 
 WR_INLINE
 Transaction *wr_transaction_new(bool aDoAsync)
 WR_FUNC;
 
 extern void wr_transaction_notification_notified(uintptr_t aHandler,
@@ -1887,16 +1909,18 @@ void wr_vec_u8_push_bytes(WrVecU8 *aV,
 WR_FUNC;
 
 WR_INLINE
 bool wr_window_new(WrWindowId aWindowId,
                    uint32_t aWindowWidth,
                    uint32_t aWindowHeight,
                    bool aSupportLowPriorityTransactions,
                    void *aGlContext,
+                   WrProgramCache *aProgramCache,
+                   WrShaders *aShaders,
                    WrThreadPool *aThreadPool,
                    VoidPtrToSizeFn aSizeOfOp,
                    DocumentHandle **aOutHandle,
                    Renderer **aOutRenderer,
                    uint32_t *aOutMaxTextureSize)
 WR_FUNC;
 
 } // extern "C"
--- a/gfx/wrench/src/wrench.rs
+++ b/gfx/wrench/src/wrench.rs
@@ -233,17 +233,17 @@ impl Wrench {
         }
 
         let (timing_sender, timing_receiver) = chase_lev::deque();
         let notifier = notifier.unwrap_or_else(|| {
             let data = Arc::new(Mutex::new(NotifierData::new(proxy, timing_receiver, verbose)));
             Box::new(Notifier(data))
         });
 
-        let (renderer, sender) = webrender::Renderer::new(window.clone_gl(), notifier, opts).unwrap();
+        let (renderer, sender) = webrender::Renderer::new(window.clone_gl(), notifier, opts, None).unwrap();
         let api = sender.create_api();
         let document_id = api.add_document(size, 0);
 
         let graphics_api = renderer.get_graphics_api_info();
         let zoom_factor = ZoomFactor::new(zoom_factor);
 
         let mut wrench = Wrench {
             window_size: size,
--- a/js/src/frontend/BytecodeEmitter.cpp
+++ b/js/src/frontend/BytecodeEmitter.cpp
@@ -20,24 +20,29 @@
 #include <string.h>
 
 #include "jsnum.h"
 #include "jstypes.h"
 #include "jsutil.h"
 
 #include "ds/Nestable.h"
 #include "frontend/BytecodeControlStructures.h"
+#include "frontend/CallOrNewEmitter.h"
 #include "frontend/CForEmitter.h"
 #include "frontend/DoWhileEmitter.h"
+#include "frontend/ElemOpEmitter.h"
 #include "frontend/EmitterScope.h"
+#include "frontend/ExpressionStatementEmitter.h"
 #include "frontend/ForInEmitter.h"
 #include "frontend/ForOfEmitter.h"
 #include "frontend/ForOfLoopControl.h"
 #include "frontend/IfEmitter.h"
+#include "frontend/NameOpEmitter.h"
 #include "frontend/Parser.h"
+#include "frontend/PropOpEmitter.h"
 #include "frontend/SwitchEmitter.h"
 #include "frontend/TDZCheckCache.h"
 #include "frontend/TryEmitter.h"
 #include "frontend/WhileEmitter.h"
 #include "js/CompileOptions.h"
 #include "vm/BytecodeUtil.h"
 #include "vm/Debugger.h"
 #include "vm/GeneratorObject.h"
@@ -874,17 +879,16 @@ BytecodeEmitter::emitIndexOp(JSOp op, ui
     updateDepth(offset);
     return true;
 }
 
 bool
 BytecodeEmitter::emitAtomOp(JSAtom* atom, JSOp op)
 {
     MOZ_ASSERT(atom);
-    MOZ_ASSERT(JOF_OPTYPE(op) == JOF_ATOM);
 
     // .generator lookups should be emitted as JSOP_GETALIASEDVAR instead of
     // JSOP_GETNAME etc, to bypass |with| objects on the scope chain.
     // It's safe to emit .this lookups though because |with| objects skip
     // those.
     MOZ_ASSERT_IF(op == JSOP_GETNAME || op == JSOP_GETGNAME,
                   atom != cx->names().dotGenerator);
 
@@ -893,24 +897,25 @@ BytecodeEmitter::emitAtomOp(JSAtom* atom
         op = JSOP_LENGTH;
     }
 
     uint32_t index;
     if (!makeAtomIndex(atom, &index)) {
         return false;
     }
 
-    return emitIndexOp(op, index);
-}
-
-bool
-BytecodeEmitter::emitAtomOp(NameNode* nameNode, JSOp op)
-{
-    MOZ_ASSERT(nameNode->atom() != nullptr);
-    return emitAtomOp(nameNode->atom(), op);
+    return emitAtomOp(index, op);
+}
+
+bool
+BytecodeEmitter::emitAtomOp(uint32_t atomIndex, JSOp op)
+{
+    MOZ_ASSERT(JOF_OPTYPE(op) == JOF_ATOM);
+
+    return emitIndexOp(op, atomIndex);
 }
 
 bool
 BytecodeEmitter::emitInternedScopeOp(uint32_t index, JSOp op)
 {
     MOZ_ASSERT(JOF_OPTYPE(op) == JOF_SCOPE);
     MOZ_ASSERT(index < scopeList.length());
     return emitIndex32(op, index);
@@ -989,29 +994,16 @@ BytecodeEmitter::emitEnvCoordOp(JSOp op,
     SET_ENVCOORD_HOPS(pc, ec.hops());
     pc += ENVCOORD_HOPS_LEN;
     SET_ENVCOORD_SLOT(pc, ec.slot());
     pc += ENVCOORD_SLOT_LEN;
     checkTypeSet(op);
     return true;
 }
 
-static JSOp
-GetIncDecInfo(ParseNodeKind kind, bool* post)
-{
-    MOZ_ASSERT(kind == ParseNodeKind::PostIncrement ||
-               kind == ParseNodeKind::PreIncrement ||
-               kind == ParseNodeKind::PostDecrement ||
-               kind == ParseNodeKind::PreDecrement);
-    *post = kind == ParseNodeKind::PostIncrement || kind == ParseNodeKind::PostDecrement;
-    return (kind == ParseNodeKind::PostIncrement || kind == ParseNodeKind::PreIncrement)
-           ? JSOP_ADD
-           : JSOP_SUB;
-}
-
 JSOp
 BytecodeEmitter::strictifySetNameOp(JSOp op)
 {
     switch (op) {
       case JSOP_SETNAME:
         if (sc->strict()) {
             op = JSOP_STRICTSETNAME;
         }
@@ -1708,298 +1700,30 @@ BytecodeEmitter::emitFinishIteratorResul
     }
     if (!emitIndex32(JSOP_INITPROP, done_id)) {
         return false;
     }
     return true;
 }
 
 bool
-BytecodeEmitter::emitGetNameAtLocation(JSAtom* name, const NameLocation& loc, bool callContext)
-{
-    switch (loc.kind()) {
-      case NameLocation::Kind::Dynamic:
-        if (!emitAtomOp(name, JSOP_GETNAME)) {
-            return false;
-        }
-        break;
-
-      case NameLocation::Kind::Global:
-        if (!emitAtomOp(name, JSOP_GETGNAME)) {
-            return false;
-        }
-        break;
-
-      case NameLocation::Kind::Intrinsic:
-        if (!emitAtomOp(name, JSOP_GETINTRINSIC)) {
-            return false;
-        }
-        break;
-
-      case NameLocation::Kind::NamedLambdaCallee:
-        if (!emit1(JSOP_CALLEE)) {
-            return false;
-        }
-        break;
-
-      case NameLocation::Kind::Import:
-        if (!emitAtomOp(name, JSOP_GETIMPORT)) {
-            return false;
-        }
-        break;
-
-      case NameLocation::Kind::ArgumentSlot:
-        if (!emitArgOp(JSOP_GETARG, loc.argumentSlot())) {
-            return false;
-        }
-        break;
-
-      case NameLocation::Kind::FrameSlot:
-        if (loc.isLexical()) {
-            if (!emitTDZCheckIfNeeded(name, loc)) {
-                return false;
-            }
-        }
-        if (!emitLocalOp(JSOP_GETLOCAL, loc.frameSlot())) {
-            return false;
-        }
-        break;
-
-      case NameLocation::Kind::EnvironmentCoordinate:
-        if (loc.isLexical()) {
-            if (!emitTDZCheckIfNeeded(name, loc)) {
-                return false;
-            }
-        }
-        if (!emitEnvCoordOp(JSOP_GETALIASEDVAR, loc.environmentCoordinate())) {
-            return false;
-        }
-        break;
-
-      case NameLocation::Kind::DynamicAnnexBVar:
-        MOZ_CRASH("Synthesized vars for Annex B.3.3 should only be used in initialization");
-    }
-
-    // Need to provide |this| value for call.
-    if (callContext) {
-        switch (loc.kind()) {
-          case NameLocation::Kind::Dynamic: {
-            JSOp thisOp = needsImplicitThis() ? JSOP_IMPLICITTHIS : JSOP_GIMPLICITTHIS;
-            if (!emitAtomOp(name, thisOp)) {
-                return false;
-            }
-            break;
-          }
-
-          case NameLocation::Kind::Global:
-            if (!emitAtomOp(name, JSOP_GIMPLICITTHIS)) {
-                return false;
-            }
-            break;
-
-          case NameLocation::Kind::Intrinsic:
-          case NameLocation::Kind::NamedLambdaCallee:
-          case NameLocation::Kind::Import:
-          case NameLocation::Kind::ArgumentSlot:
-          case NameLocation::Kind::FrameSlot:
-          case NameLocation::Kind::EnvironmentCoordinate:
-            if (!emit1(JSOP_UNDEFINED)) {
-                return false;
-            }
-            break;
-
-          case NameLocation::Kind::DynamicAnnexBVar:
-            MOZ_CRASH("Synthesized vars for Annex B.3.3 should only be used in initialization");
-        }
-    }
-
-    return true;
-}
-
-bool
-BytecodeEmitter::emitGetName(ParseNode* pn, bool callContext)
-{
-    return emitGetName(pn->name(), callContext);
-}
-
-template <typename RHSEmitter>
-bool
-BytecodeEmitter::emitSetOrInitializeNameAtLocation(HandleAtom name, const NameLocation& loc,
-                                                   RHSEmitter emitRhs, bool initialize)
-{
-    bool emittedBindOp = false;
-
-    switch (loc.kind()) {
-      case NameLocation::Kind::Dynamic:
-      case NameLocation::Kind::Import:
-      case NameLocation::Kind::DynamicAnnexBVar: {
-        uint32_t atomIndex;
-        if (!makeAtomIndex(name, &atomIndex)) {
-            return false;
-        }
-        if (loc.kind() == NameLocation::Kind::DynamicAnnexBVar) {
-            // Annex B vars always go on the nearest variable environment,
-            // even if lexical environments in between contain same-named
-            // bindings.
-            if (!emit1(JSOP_BINDVAR)) {
-                return false;
-            }
-        } else {
-            if (!emitIndexOp(JSOP_BINDNAME, atomIndex)) {
-                return false;
-            }
-        }
-        emittedBindOp = true;
-        if (!emitRhs(this, loc, emittedBindOp)) {
-            return false;
-        }
-        if (!emitIndexOp(strictifySetNameOp(JSOP_SETNAME), atomIndex)) {
-            return false;
-        }
-        break;
-      }
-
-      case NameLocation::Kind::Global: {
-        JSOp op;
-        uint32_t atomIndex;
-        if (!makeAtomIndex(name, &atomIndex)) {
-            return false;
-        }
-        if (loc.isLexical() && initialize) {
-            // INITGLEXICAL always gets the global lexical scope. It doesn't
-            // need a BINDGNAME.
-            MOZ_ASSERT(innermostScope()->is<GlobalScope>());
-            op = JSOP_INITGLEXICAL;
-        } else {
-            if (!emitIndexOp(JSOP_BINDGNAME, atomIndex)) {
-                return false;
-            }
-            emittedBindOp = true;
-            op = strictifySetNameOp(JSOP_SETGNAME);
-        }
-        if (!emitRhs(this, loc, emittedBindOp)) {
-            return false;
-        }
-        if (!emitIndexOp(op, atomIndex)) {
-            return false;
-        }
-        break;
-      }
-
-      case NameLocation::Kind::Intrinsic:
-        if (!emitRhs(this, loc, emittedBindOp)) {
-            return false;
-        }
-        if (!emitAtomOp(name, JSOP_SETINTRINSIC)) {
-            return false;
-        }
-        break;
-
-      case NameLocation::Kind::NamedLambdaCallee:
-        if (!emitRhs(this, loc, emittedBindOp)) {
-            return false;
-        }
-        // Assigning to the named lambda is a no-op in sloppy mode but
-        // throws in strict mode.
-        if (sc->strict() && !emit1(JSOP_THROWSETCALLEE)) {
-            return false;
-        }
-        break;
-
-      case NameLocation::Kind::ArgumentSlot: {
-        // If we assign to a positional formal parameter and the arguments
-        // object is unmapped (strict mode or function with
-        // default/rest/destructing args), parameters do not alias
-        // arguments[i], and to make the arguments object reflect initial
-        // parameter values prior to any mutation we create it eagerly
-        // whenever parameters are (or might, in the case of calls to eval)
-        // assigned.
-        FunctionBox* funbox = sc->asFunctionBox();
-        if (funbox->argumentsHasLocalBinding() && !funbox->hasMappedArgsObj()) {
-            funbox->setDefinitelyNeedsArgsObj();
-        }
-
-        if (!emitRhs(this, loc, emittedBindOp)) {
-            return false;
-        }
-        if (!emitArgOp(JSOP_SETARG, loc.argumentSlot())) {
-            return false;
-        }
-        break;
-      }
-
-      case NameLocation::Kind::FrameSlot: {
-        JSOp op = JSOP_SETLOCAL;
-        if (!emitRhs(this, loc, emittedBindOp)) {
-            return false;
-        }
-        if (loc.isLexical()) {
-            if (initialize) {
-                op = JSOP_INITLEXICAL;
-            } else {
-                if (loc.isConst()) {
-                    op = JSOP_THROWSETCONST;
-                }
-
-                if (!emitTDZCheckIfNeeded(name, loc)) {
-                    return false;
-                }
-            }
-        }
-        if (!emitLocalOp(op, loc.frameSlot())) {
-            return false;
-        }
-        if (op == JSOP_INITLEXICAL) {
-            if (!innermostTDZCheckCache->noteTDZCheck(this, name, DontCheckTDZ)) {
-                return false;
-            }
-        }
-        break;
-      }
-
-      case NameLocation::Kind::EnvironmentCoordinate: {
-        JSOp op = JSOP_SETALIASEDVAR;
-        if (!emitRhs(this, loc, emittedBindOp)) {
-            return false;
-        }
-        if (loc.isLexical()) {
-            if (initialize) {
-                op = JSOP_INITALIASEDLEXICAL;
-            } else {
-                if (loc.isConst()) {
-                    op = JSOP_THROWSETALIASEDCONST;
-                }
-
-                if (!emitTDZCheckIfNeeded(name, loc)) {
-                    return false;
-                }
-            }
-        }
-        if (loc.bindingKind() == BindingKind::NamedLambdaCallee) {
-            // Assigning to the named lambda is a no-op in sloppy mode and throws
-            // in strict mode.
-            op = JSOP_THROWSETALIASEDCONST;
-            if (sc->strict() && !emitEnvCoordOp(op, loc.environmentCoordinate())) {
-                return false;
-            }
-        } else {
-            if (!emitEnvCoordOp(op, loc.environmentCoordinate())) {
-                return false;
-            }
-        }
-        if (op == JSOP_INITALIASEDLEXICAL) {
-            if (!innermostTDZCheckCache->noteTDZCheck(this, name, DontCheckTDZ)) {
-                return false;
-            }
-        }
-        break;
-      }
-    }
-
-    return true;
+BytecodeEmitter::emitGetNameAtLocation(JSAtom* name, const NameLocation& loc)
+{
+    NameOpEmitter noe(this, name, loc, NameOpEmitter::Kind::Get);
+    if (!noe.emitGet()) {
+        return false;
+    }
+
+    return true;
+}
+
+bool
+BytecodeEmitter::emitGetName(ParseNode* pn)
+{
+    return emitGetName(pn->name());
 }
 
 bool
 BytecodeEmitter::emitTDZCheckIfNeeded(JSAtom* name, const NameLocation& loc)
 {
     // Dynamic accesses have TDZ checks built into their VM code and should
     // never emit explicit TDZ checks.
     MOZ_ASSERT(loc.hasKnownSlot());
@@ -2059,294 +1783,80 @@ BytecodeEmitter::emitPropLHS(PropertyAcc
 
     // 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)) {
+        if (!emitAtomOp(pndot->key().atom(), 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(PropertyAccess* prop, JSOp op)
-{
-    if (!emitPropLHS(prop)) {
-        return false;
-    }
-
-    if (op == JSOP_CALLPROP && !emit1(JSOP_DUP)) {
-        return false;
-    }
-
-    if (!emitAtomOp(&prop->key(), op)) {
-        return false;
-    }
-
-    if (op == JSOP_CALLPROP && !emit1(JSOP_SWAP)) {
-        return false;
-    }
-
-    return true;
-}
-
-bool
-BytecodeEmitter::emitSuperGetProp(PropertyAccess* prop, bool isCall)
-{
-    UnaryNode* base = &prop->expression().as<UnaryNode>();
-    if (!emitSuperPropLHS(base, isCall)) {
-        return false;
-    }
-
-    if (!emitAtomOp(&prop->key(), JSOP_GETPROP_SUPER)) {
-        return false;
-    }
-
-    if (isCall && !emit1(JSOP_SWAP)) {
-        return false;
-    }
-
-    return true;
-}
-
-bool
 BytecodeEmitter::emitPropIncDec(UnaryNode* incDec)
 {
     PropertyAccess* prop = &incDec->kid()->as<PropertyAccess>();
-
-    bool post;
     bool isSuper = prop->isSuper();
-    JSOp binop = GetIncDecInfo(incDec->getKind(), &post);
-
+    ParseNodeKind kind = incDec->getKind();
+    PropOpEmitter poe(this,
+                      kind == ParseNodeKind::PostIncrement ? PropOpEmitter::Kind::PostIncrement
+                      : kind == ParseNodeKind::PreIncrement ? PropOpEmitter::Kind::PreIncrement
+                      : kind == ParseNodeKind::PostDecrement ? PropOpEmitter::Kind::PostDecrement
+                      : PropOpEmitter::Kind::PreDecrement,
+                      isSuper
+                      ? PropOpEmitter::ObjKind::Super
+                      : PropOpEmitter::ObjKind::Other);
+    if (!poe.prepareForObj()) {
+        return false;
+    }
     if (isSuper) {
         UnaryNode* base = &prop->expression().as<UnaryNode>();
-        if (!emitSuperPropLHS(base)) {              // THIS OBJ
-            return false;
-        }
-        if (!emit1(JSOP_DUP2)) {                    // THIS OBJ THIS OBJ
+        if (!emitGetThisForSuperBase(base)) {       // THIS
             return false;
         }
     } else {
         if (!emitPropLHS(prop)) {
             return false;                           // OBJ
         }
-        if (!emit1(JSOP_DUP)) {                     // OBJ OBJ
-            return false;
-        }
-    }
-    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;
-    }
-    if (!emit1(JSOP_ONE)) {                         // OBJ N? N 1
-        return false;
-    }
-    if (!emit1(binop)) {                            // OBJ N? N+1
-        return false;
-    }
-
-    if (post) {
-        if (!emit2(JSOP_PICK, 2 + isSuper)) {      // N? N+1 OBJ
-            return false;
-        }
-        if (!emit1(JSOP_SWAP)) {                   // N? OBJ N+1
-            return false;
-        }
-        if (isSuper) {
-            if (!emit2(JSOP_PICK, 3)) {            // N THIS N+1 OBJ
-                return false;
-            }
-            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(&prop->key(), setOp)) {         // N? N+1
-        return false;
-    }
-    if (post && !emit1(JSOP_POP)) {                 // RESULT
-        return false;
-    }
-
-    return true;
-}
-
-bool
-BytecodeEmitter::emitGetNameAtLocationForCompoundAssignment(JSAtom* name, const NameLocation& loc)
-{
-    if (loc.kind() == NameLocation::Kind::Dynamic) {
-        // For dynamic accesses we need to emit GETBOUNDNAME instead of
-        // GETNAME for correctness: looking up @@unscopables on the
-        // environment chain (due to 'with' environments) must only happen
-        // once.
-        //
-        // GETBOUNDNAME uses the environment already pushed on the stack from
-        // the earlier BINDNAME.
-        if (!emit1(JSOP_DUP)) {                            // ENV ENV
-            return false;
-        }
-        if (!emitAtomOp(name, JSOP_GETBOUNDNAME)) {        // ENV V
-            return false;
-        }
-    } else {
-        if (!emitGetNameAtLocation(name, loc)) {           // ENV? V
-            return false;
-        }
-    }
-
-    return true;
-}
+    }
+    if (!poe.emitIncDec(prop->key().atom())) {      // RESULT
+        return false;
+    }
+
+    return true;
+}
+
 
 bool
 BytecodeEmitter::emitNameIncDec(UnaryNode* incDec)
 {
     MOZ_ASSERT(incDec->kid()->isKind(ParseNodeKind::Name));
 
-    bool post;
-    JSOp binop = GetIncDecInfo(incDec->getKind(), &post);
-
-    auto emitRhs = [incDec, post, binop](BytecodeEmitter* bce, const NameLocation& loc,
-                                         bool emittedBindOp)
-    {
-        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;
-        }
-        if (!bce->emit1(JSOP_ONE)) {                       // ENV? N? N 1
-            return false;
-        }
-        if (!bce->emit1(binop)) {                          // ENV? N? N+1
-            return false;
-        }
-
-        if (post && emittedBindOp) {
-            if (!bce->emit2(JSOP_PICK, 2)) {               // N? N+1 ENV?
-                return false;
-            }
-            if (!bce->emit1(JSOP_SWAP)) {                  // N? ENV? N+1
-                return false;
-            }
-        }
-
-        return true;
-    };
-
-    if (!emitSetName(incDec->kid(), emitRhs)) {
-        return false;
-    }
-
-    if (post && !emit1(JSOP_POP)) {
-        return false;
-    }
-
-    return true;
-}
-
-bool
-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(&elem->key())) {
-        return false;
-    }
-
-    if (opts == EmitElemOption::IncDec || opts == EmitElemOption::CompoundAssign) {
-        if (!emit1(JSOP_TOID)) {
-            return false;
-        }
-    }
-    return true;
-}
-
-bool
-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(&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;
-        }
-    }
-
-    if (!emit1(JSOP_SUPERBASE)) {                   // THIS? THIS KEY SUPERBASE
+    ParseNodeKind kind = incDec->getKind();
+    NameNode* name = &incDec->kid()->as<NameNode>();
+    NameOpEmitter noe(this, name->atom(),
+                      kind == ParseNodeKind::PostIncrement ? NameOpEmitter::Kind::PostIncrement
+                      : kind == ParseNodeKind::PreIncrement ? NameOpEmitter::Kind::PreIncrement
+                      : kind == ParseNodeKind::PostDecrement ? NameOpEmitter::Kind::PostDecrement
+                      : NameOpEmitter::Kind::PreDecrement);
+    if (!noe.emitIncDec()) {
         return false;
     }
 
     return true;
 }
 
 bool
 BytecodeEmitter::emitElemOpBase(JSOp op)
@@ -2355,118 +1865,74 @@ BytecodeEmitter::emitElemOpBase(JSOp op)
         return false;
     }
 
     checkTypeSet(op);
     return true;
 }
 
 bool
-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(elem, opts) && emitElemOpBase(op);
-}
-
-bool
-BytecodeEmitter::emitSuperGetElem(PropertyByValue* elem, bool isCall)
-{
-    EmitElemOption opts = isCall ? EmitElemOption::Call : EmitElemOption::Get;
-
-    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
+BytecodeEmitter::emitElemObjAndKey(PropertyByValue* elem, bool isSuper, ElemOpEmitter& eoe)
+{
+    if (isSuper) {
+        if (!eoe.prepareForObj()) {                       //
+            return false;
+        }
+        UnaryNode* base = &elem->expression().as<UnaryNode>();
+        if (!emitGetThisForSuperBase(base)) {             // THIS
+            return false;
+        }
+        if (!eoe.prepareForKey()) {                       // THIS
+            return false;
+        }
+        if (!emitTree(&elem->key())) {                    // THIS KEY
+            return false;
+        }
+
+        return true;
+    }
+
+    if (!eoe.prepareForObj()) {                           //
+        return false;
+    }
+    if (!emitTree(&elem->expression())) {                 // OBJ
+        return false;
+    }
+    if (!eoe.prepareForKey()) {                           // OBJ? OBJ
+        return false;
+    }
+    if (!emitTree(&elem->key())) {                        // OBJ? OBJ KEY
         return false;
     }
 
     return true;
 }
 
 bool
 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(elem, EmitElemOption::IncDec)) {
-            return false;
-        }
-    } else {
-        if (!emitElemOperands(elem, EmitElemOption::IncDec)) {
-            return false;
-        }
-    }
-
-    bool 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;
-        }
-        if (!emitDupAt(2)) {                            // THIS KEY OBJ THIS KEY
-            return false;
-        }
-        if (!emitDupAt(2)) {                            // THIS KEY OBJ THIS KEY OBJ
-            return false;
-        }
-        getOp = JSOP_GETELEM_SUPER;
-    } else {
-                                                        // OBJ KEY
-        if (!emit1(JSOP_DUP2)) {                        // OBJ KEY OBJ KEY
-            return false;
-        }
-        getOp = JSOP_GETELEM;
-    }
-    if (!emitElemOpBase(getOp)) {                       // OBJ KEY V
-        return false;
-    }
-    if (!emit1(JSOP_POS)) {                             // OBJ KEY N
-        return false;
-    }
-    if (post) {
-        if (!emit1(JSOP_DUP)) {                         // OBJ KEY N N
-            return false;
-        }
-        if (!emit2(JSOP_UNPICK, 3 + isSuper)) {         // N OBJ KEY N
-            return false;
-        }
-    }
-    if (!emit1(JSOP_ONE)) {                             // N? OBJ KEY N 1
-        return false;
-    }
-    if (!emit1(binop)) {                                // N? OBJ KEY N+1
-        return false;
-    }
-
-    JSOp setOp = isSuper ? (sc->strict() ? JSOP_STRICTSETELEM_SUPER : JSOP_SETELEM_SUPER)
-                         : (sc->strict() ? JSOP_STRICTSETELEM : JSOP_SETELEM);
-    if (!emitElemOpBase(setOp)) {                       // N? N+1
-        return false;
-    }
-    if (post && !emit1(JSOP_POP)) {                     // RESULT
-        return false;
+    PropertyByValue* elemExpr = &incDec->kid()->as<PropertyByValue>();
+    bool isSuper = elemExpr->isSuper();
+    ParseNodeKind kind = incDec->getKind();
+    ElemOpEmitter eoe(this,
+                      kind == ParseNodeKind::PostIncrement ? ElemOpEmitter::Kind::PostIncrement
+                      : kind == ParseNodeKind::PreIncrement ? ElemOpEmitter::Kind::PreIncrement
+                      : kind == ParseNodeKind::PostDecrement ? ElemOpEmitter::Kind::PostDecrement
+                      : ElemOpEmitter::Kind::PreDecrement,
+                      isSuper
+                      ? ElemOpEmitter::ObjKind::Super
+                      : ElemOpEmitter::ObjKind::Other);
+    if (!emitElemObjAndKey(elemExpr, isSuper, eoe)) {     // [Super]
+        //                                                // THIS KEY
+        //                                                // [Other]
+        //                                                // OBJ KEY
+        return false;
+    }
+    if (!eoe.emitIncDec()) {                              // RESULT
+         return false;
     }
 
     return true;
 }
 
 bool
 BytecodeEmitter::emitCallIncDec(UnaryNode* incDec)
 {
@@ -2751,52 +2217,58 @@ BytecodeEmitter::emitSetThis(BinaryNode*
 {
     // ParseNodeKind::SetThis is used to update |this| after a super() call
     // in a derived class constructor.
 
     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(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;
-        }
-        if (!bce->emit1(JSOP_CHECKTHISREINIT)) {
-            return false;
-        }
-        if (!bce->emit1(JSOP_POP)) {
-            return false;
-        }
-        return true;
-    };
 
     // The 'this' binding is not lexical, but due to super() semantics this
     // initialization needs to be treated as a lexical one.
     NameLocation loc = lookupName(name);
     NameLocation lexicalLoc;
     if (loc.kind() == NameLocation::Kind::FrameSlot) {
         lexicalLoc = NameLocation::FrameSlot(BindingKind::Let, loc.frameSlot());
     } else if (loc.kind() == NameLocation::Kind::EnvironmentCoordinate) {
         EnvironmentCoordinate coord = loc.environmentCoordinate();
         uint8_t hops = AssertedCast<uint8_t>(coord.hops());
         lexicalLoc = NameLocation::EnvironmentCoordinate(BindingKind::Let, hops, coord.slot());
     } else {
         MOZ_ASSERT(loc.kind() == NameLocation::Kind::Dynamic);
         lexicalLoc = loc;
     }
 
-    return emitSetOrInitializeNameAtLocation(name, lexicalLoc, emitRhs, true);
+    NameOpEmitter noe(this, name, lexicalLoc, NameOpEmitter::Kind::Initialize);
+    if (!noe.prepareForRhs()) {                           //
+        return false;
+    }
+
+    // Emit the new |this| value.
+    if (!emitTree(setThisNode->right()))                  // NEWTHIS
+        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 (!emitGetName(name)) {                             // NEWTHIS THIS
+        return false;
+    }
+    if (!emit1(JSOP_CHECKTHISREINIT)) {                   // NEWTHIS THIS
+        return false;
+    }
+    if (!emit1(JSOP_POP)) {                               // NEWTHIS
+        return false;
+    }
+    if (!noe.emitAssignment()) {                          // NEWTHIS
+        return false;
+    }
+
+    return true;
 }
 
 bool
 BytecodeEmitter::emitScript(ParseNode* body)
 {
     AutoFrontendTraceLog traceLog(cx, TraceLogger_BytecodeEmission, parser->errorReporter(), body);
 
     setScriptStartOffsetIfUnset(body->pn_pos);
@@ -2982,43 +2454,73 @@ BytecodeEmitter::emitDestructuringLHSRef
 
 #ifdef DEBUG
     int depth = stackDepth;
 #endif
 
     switch (target->getKind()) {
       case ParseNodeKind::Dot: {
         PropertyAccess* prop = &target->as<PropertyAccess>();
-        if (prop->isSuper()) {
-            if (!emitSuperPropLHS(&prop->expression().as<UnaryNode>())) {
-                return false;
-            }
+        bool isSuper = prop->isSuper();
+        PropOpEmitter poe(this,
+                          PropOpEmitter::Kind::SimpleAssignment,
+                          isSuper
+                          ? PropOpEmitter::ObjKind::Super
+                          : PropOpEmitter::ObjKind::Other);
+        if (!poe.prepareForObj()) {
+            return false;
+        }
+        if (isSuper) {
+            UnaryNode* base = &prop->expression().as<UnaryNode>();
+            if (!emitGetThisForSuperBase(base)) {         // THIS SUPERBASE
+                return false;
+            }
+            // SUPERBASE is pushed onto THIS in poe.prepareForRhs below.
             *emitted = 2;
         } else {
-            if (!emitTree(&prop->expression())) {
+            if (!emitTree(&prop->expression())) {         // OBJ
                 return false;
             }
             *emitted = 1;
         }
+        if (!poe.prepareForRhs()) {                       // [Super]
+            //                                            // THIS SUPERBASE
+            //                                            // [Other]
+            //                                            // OBJ
+            return false;
+        }
         break;
       }
 
       case ParseNodeKind::Elem: {
         PropertyByValue* elem = &target->as<PropertyByValue>();
-        if (elem->isSuper()) {
-            if (!emitSuperElemOperands(elem, EmitElemOption::Ref)) {
-                return false;
-            }
+        bool isSuper = elem->isSuper();
+        ElemOpEmitter eoe(this,
+                          ElemOpEmitter::Kind::SimpleAssignment,
+                          isSuper
+                          ? ElemOpEmitter::ObjKind::Super
+                          : ElemOpEmitter::ObjKind::Other);
+        if (!emitElemObjAndKey(elem, isSuper, eoe)) {     // [Super]
+            //                                            // THIS KEY
+            //                                            // [Other]
+            //                                            // OBJ KEY
+            return false;
+        }
+        if (isSuper) {
+            // SUPERBASE is pushed onto KEY in eoe.prepareForRhs below.
             *emitted = 3;
         } else {
-            if (!emitElemOperands(elem, EmitElemOption::Ref)) {
-                return false;
-            }
             *emitted = 2;
         }
+        if (!eoe.prepareForRhs()) {                       // [Super]
+            //                                            // THIS KEY SUPERBASE
+            //                                            // [Other]
+            //                                            // OBJ KEY
+            return false;
+        }
         break;
       }
 
       case ParseNodeKind::Call:
         MOZ_ASSERT_UNREACHABLE("Parser::reportIfNotValidSimpleAssignmentTarget "
                                "rejects function calls as assignment "
                                "targets in destructuring assignments");
         break;
@@ -3051,120 +2553,129 @@ BytecodeEmitter::emitSetOrInitializeDest
         // Per its post-condition, emitDestructuringOps has left the
         // to-be-destructured value on top of the stack.
         if (!emit1(JSOP_POP)) {
             return false;
         }
     } else {
         switch (target->getKind()) {
           case ParseNodeKind::Name: {
-            auto emitSwapScopeAndRhs = [](BytecodeEmitter* bce, const NameLocation&,
-                                          bool emittedBindOp)
-            {
-                if (emittedBindOp) {
-                    // This is like ordinary assignment, but with one
-                    // difference.
-                    //
-                    // In `a = b`, we first determine a binding for `a` (using
-                    // JSOP_BINDNAME or JSOP_BINDGNAME), then we evaluate `b`,
-                    // then a JSOP_SETNAME instruction.
-                    //
-                    // In `[a] = [b]`, per spec, `b` is evaluated first, then
-                    // we determine a binding for `a`. Then we need to do
-                    // assignment-- but the operands are on the stack in the
-                    // wrong order for JSOP_SETPROP, so we have to add a
-                    // JSOP_SWAP.
-                    //
-                    // In the cases where we are emitting a name op, emit a
-                    // swap because of this.
-                    return bce->emit1(JSOP_SWAP);
-                }
-
-                // In cases of emitting a frame slot or environment slot,
-                // nothing needs be done.
-                return true;
-            };
-
             RootedAtom name(cx, target->name());
+            NameLocation loc;
+            NameOpEmitter::Kind kind;
             switch (flav) {
               case DestructuringDeclaration:
-                if (!emitInitializeName(name, emitSwapScopeAndRhs)) {
-                    return false;
-                }
+                loc = lookupName(name);
+                kind = NameOpEmitter::Kind::Initialize;
                 break;
-
               case DestructuringFormalParameterInVarScope: {
                 // If there's an parameter expression var scope, the
                 // destructuring declaration needs to initialize the name in
                 // the function scope. The innermost scope is the var scope,
                 // and its enclosing scope is the function scope.
                 EmitterScope* funScope = innermostEmitterScope()->enclosingInFrame();
-                NameLocation paramLoc = *locationOfNameBoundInScope(name, funScope);
-                if (!emitSetOrInitializeNameAtLocation(name, paramLoc, emitSwapScopeAndRhs, true)) {
-                    return false;
-                }
+                loc = *locationOfNameBoundInScope(name, funScope);
+                kind = NameOpEmitter::Kind::Initialize;
                 break;
               }
 
               case DestructuringAssignment:
-                if (!emitSetName(name, emitSwapScopeAndRhs)) {
+                loc = lookupName(name);
+                kind = NameOpEmitter::Kind::SimpleAssignment;
+                break;
+            }
+
+            NameOpEmitter noe(this, name, loc, kind);
+            if (!noe.prepareForRhs()) {                   // V ENV?
+                return false;
+            }
+            if (noe.emittedBindOp()) {
+                // This is like ordinary assignment, but with one difference.
+                //
+                // In `a = b`, we first determine a binding for `a` (using
+                // JSOP_BINDNAME or JSOP_BINDGNAME), then we evaluate `b`, then
+                // a JSOP_SETNAME instruction.
+                //
+                // In `[a] = [b]`, per spec, `b` is evaluated first, then we
+                // determine a binding for `a`. Then we need to do assignment--
+                // but the operands are on the stack in the wrong order for
+                // JSOP_SETPROP, so we have to add a JSOP_SWAP.
+                //
+                // In the cases where we are emitting a name op, emit a swap
+                // because of this.
+                if (!emit1(JSOP_SWAP)) {                  // ENV V
                     return false;
                 }
-                break;
+            } else {
+                // In cases of emitting a frame slot or environment slot,
+                // nothing needs be done.
+            }
+            if (!noe.emitAssignment()) {                  // V
+                return false;
             }
 
             break;
           }
 
           case ParseNodeKind::Dot: {
             // The reference is already pushed by emitDestructuringLHSRef.
+            //                                            // [Super]
+            //                                            // THIS SUPERBASE VAL
+            //                                            // [Other]
+            //                                            // OBJ VAL
             PropertyAccess* prop = &target->as<PropertyAccess>();
-            JSOp setOp;
-            if (prop->isSuper()) {
-                setOp = sc->strict() ? JSOP_STRICTSETPROP_SUPER : JSOP_SETPROP_SUPER;
-            } else {
-                setOp = sc->strict() ? JSOP_STRICTSETPROP : JSOP_SETPROP;
-            }
-            if (!emitAtomOp(&prop->key(), setOp)) {
-                return false;
+            bool isSuper = prop->isSuper();
+            PropOpEmitter poe(this,
+                              PropOpEmitter::Kind::SimpleAssignment,
+                              isSuper
+                              ? PropOpEmitter::ObjKind::Super
+                              : PropOpEmitter::ObjKind::Other);
+            if (!poe.skipObjAndRhs()) {
+                return false;
+            }
+            if (!poe.emitAssignment(prop->key().atom())) {
+                return false;                             // VAL
             }
             break;
           }
 
           case ParseNodeKind::Elem: {
             // The reference is already pushed by emitDestructuringLHSRef.
+            //                                            // [Super]
+            //                                            // THIS KEY SUPERBASE VAL
+            //                                            // [Other]
+            //                                            // OBJ KEY VAL
             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;
-                if (!emitElemOpBase(setOp)) {
-                    return false;
-                }
+            bool isSuper = elem->isSuper();
+            ElemOpEmitter eoe(this,
+                              ElemOpEmitter::Kind::SimpleAssignment,
+                              isSuper
+                              ? ElemOpEmitter::ObjKind::Super
+                              : ElemOpEmitter::ObjKind::Other);
+            if (!eoe.skipObjAndKeyAndRhs()) {
+                return false;
+            }
+            if (!eoe.emitAssignment()) {                  // VAL
+                return false;
             }
             break;
           }
 
           case ParseNodeKind::Call:
             MOZ_ASSERT_UNREACHABLE("Parser::reportIfNotValidSimpleAssignmentTarget "
                                    "rejects function calls as assignment "
                                    "targets in destructuring assignments");
             break;
 
           default:
             MOZ_CRASH("emitSetOrInitializeDestructuring: bad lhs kind");
         }
 
         // Pop the assigned value.
-        if (!emit1(JSOP_POP)) {
+        if (!emit1(JSOP_POP)) {                           // !STACK EMPTY!
             return false;
         }
     }
 
     return true;
 }
 
 bool
@@ -4272,37 +3783,43 @@ BytecodeEmitter::emitSingleDeclaration(P
 {
     MOZ_ASSERT(decl->isKind(ParseNodeKind::Name));
 
     // Nothing to do for initializer-less 'var' declarations, as there's no TDZ.
     if (!initializer && declList->isKind(ParseNodeKind::Var)) {
         return true;
     }
 
-    auto emitRhs = [initializer, declList, decl](BytecodeEmitter* bce, const NameLocation&, bool) {
-        if (!initializer) {
-            // Lexical declarations are initialized to undefined without an
-            // initializer.
-            MOZ_ASSERT(declList->isKind(ParseNodeKind::Let),
-                       "var declarations without initializers handled above, "
-                       "and const declarations must have initializers");
-            Unused << declList; // silence clang -Wunused-lambda-capture in opt builds
-            return bce->emit1(JSOP_UNDEFINED);
-        }
-
+    NameOpEmitter noe(this, decl->name(), NameOpEmitter::Kind::Initialize);
+    if (!noe.prepareForRhs()) {                           // ENV?
+        return false;
+    }
+    if (!initializer) {
+        // Lexical declarations are initialized to undefined without an
+        // initializer.
+        MOZ_ASSERT(declList->isKind(ParseNodeKind::Let),
+                   "var declarations without initializers handled above, "
+                   "and const declarations must have initializers");
+        if (!emit1(JSOP_UNDEFINED)) {                     // ENV? UNDEF
+            return false;
+        }
+    } else {
         MOZ_ASSERT(initializer);
-        return bce->emitInitializer(initializer, decl);
-    };
-
-    if (!emitInitializeName(decl, emitRhs)) {
-        return false;
-    }
-
-    // Pop the RHS.
-    return emit1(JSOP_POP);
+        if (!emitInitializer(initializer, decl)) {        // ENV? V
+            return false;
+        }
+    }
+    if (!noe.emitAssignment()) {                          // V
+         return false;
+    }
+    if (!emit1(JSOP_POP)) {                               //
+        return false;
+    }
+
+    return true;
 }
 
 static bool
 EmitAssignmentRhs(BytecodeEmitter* bce, ParseNode* rhs, uint8_t offset)
 {
     // If there is a RHS tree, emit the tree.
     if (rhs) {
         return bce->emitTree(rhs);
@@ -4336,97 +3853,115 @@ CompoundAssignmentParseNodeKindToJSOp(Pa
       case ParseNodeKind::DivAssign:    return JSOP_DIV;
       case ParseNodeKind::ModAssign:    return JSOP_MOD;
       case ParseNodeKind::PowAssign:    return JSOP_POW;
       default: MOZ_CRASH("unexpected compound assignment op");
     }
 }
 
 bool
-BytecodeEmitter::emitAssignment(ParseNode* lhs, ParseNodeKind pnk, ParseNode* rhs)
-{
-    JSOp op = CompoundAssignmentParseNodeKindToJSOp(pnk);
+BytecodeEmitter::emitAssignment(ParseNode* lhs, JSOp compoundOp, ParseNode* rhs)
+{
+    bool isCompound = compoundOp != JSOP_NOP;
 
     // Name assignments are handled separately because choosing ops and when
     // to emit BINDNAME is involved and should avoid duplication.
     if (lhs->isKind(ParseNodeKind::Name)) {
-        auto emitRhs = [op, lhs, rhs](BytecodeEmitter* bce, const NameLocation& lhsLoc,
-                                      bool emittedBindOp)
-        {
-            // For compound assignments, first get the LHS value, then emit
-            // the RHS and the op.
-            if (op != JSOP_NOP) {
-                if (!bce->emitGetNameAtLocationForCompoundAssignment(lhs->name(), lhsLoc)) {
-                    return false;
-                }
-            }
-
-            // Emit the RHS. If we emitted a BIND[G]NAME, then the scope is on
-            // the top of the stack and we need to pick the right RHS value.
-            if (!EmitAssignmentRhs(bce, rhs, emittedBindOp ? 2 : 1)) {
-                return false;
-            }
-
-            if (rhs && rhs->isDirectRHSAnonFunction()) {
-                MOZ_ASSERT(!lhs->isInParens());
-                MOZ_ASSERT(op == JSOP_NOP);
-                RootedAtom name(bce->cx, lhs->name());
-                if (!bce->setOrEmitSetFunName(rhs, name)) {
-                    return false;
-                }
-            }
-
-            // Emit the compound assignment op if there is one.
-            if (op != JSOP_NOP) {
-                if (!bce->emit1(op)) {
-                    return false;
-                }
-            }
-
-            return true;
-        };
-
-        return emitSetName(lhs, emitRhs);
-    }
+        NameOpEmitter noe(this,
+                          lhs->name(),
+                          isCompound
+                          ? NameOpEmitter::Kind::CompoundAssignment
+                          : NameOpEmitter::Kind::SimpleAssignment);
+        if (!noe.prepareForRhs()) {                       // ENV? VAL?
+            return false;
+        }
+
+        // Emit the RHS. If we emitted a BIND[G]NAME, then the scope is on
+        // the top of the stack and we need to pick the right RHS value.
+        uint8_t offset = noe.emittedBindOp() ? 2 : 1;
+        if (!EmitAssignmentRhs(this, rhs, offset)) {      // ENV? VAL? RHS
+            return false;
+        }
+        if (rhs && rhs->isDirectRHSAnonFunction()) {
+            MOZ_ASSERT(!lhs->isInParens());
+            MOZ_ASSERT(!isCompound);
+            RootedAtom name(cx, lhs->name());
+            if (!setOrEmitSetFunName(rhs, name)) {         // ENV? VAL? RHS
+                return false;
+            }
+        }
+
+        // Emit the compound assignment op if there is one.
+        if (isCompound) {
+            if (!emit1(compoundOp)) {                     // ENV? VAL
+                return false;
+            }
+        }
+        if (!noe.emitAssignment()) {                      // VAL
+            return false;
+        }
+
+        return true;
+    }
+
+    Maybe<PropOpEmitter> poe;
+    Maybe<ElemOpEmitter> eoe;
 
     // Deal with non-name assignments.
-    uint32_t atomIndex = (uint32_t) -1;
     uint8_t offset = 1;
 
     switch (lhs->getKind()) {
       case ParseNodeKind::Dot: {
         PropertyAccess* prop = &lhs->as<PropertyAccess>();
-        if (prop->isSuper()) {
-            if (!emitSuperPropLHS(&prop->expression().as<UnaryNode>())) {
-                return false;
-            }
+        bool isSuper = prop->isSuper();
+        poe.emplace(this,
+                    isCompound
+                    ? PropOpEmitter::Kind::CompoundAssignment
+                    : PropOpEmitter::Kind::SimpleAssignment,
+                    isSuper
+                    ? PropOpEmitter::ObjKind::Super
+                    : PropOpEmitter::ObjKind::Other);
+        if (!poe->prepareForObj()) {
+            return false;
+        }
+        if (isSuper) {
+            UnaryNode* base = &prop->expression().as<UnaryNode>();
+            if (!emitGetThisForSuperBase(base)) {         // THIS SUPERBASE
+                return false;
+            }
+            // SUPERBASE is pushed onto THIS later in poe->emitGet below.
             offset += 2;
         } else {
-            if (!emitTree(&prop->expression())) {
+            if (!emitTree(&prop->expression())) {         // OBJ
                 return false;
             }
             offset += 1;
         }
-        if (!makeAtomIndex(prop->key().atom(), &atomIndex)) {
-            return false;
-        }
         break;
       }
       case ParseNodeKind::Elem: {
         PropertyByValue* elem = &lhs->as<PropertyByValue>();
-        EmitElemOption opt = op == JSOP_NOP ? EmitElemOption::Get : EmitElemOption::CompoundAssign;
-        if (elem->isSuper()) {
-            if (!emitSuperElemOperands(elem, opt)) {
-                return false;
-            }
+        bool isSuper = elem->isSuper();
+        eoe.emplace(this,
+                    isCompound
+                    ? ElemOpEmitter::Kind::CompoundAssignment
+                    : ElemOpEmitter::Kind::SimpleAssignment,
+                    isSuper
+                    ? ElemOpEmitter::ObjKind::Super
+                    : ElemOpEmitter::ObjKind::Other);
+        if (!emitElemObjAndKey(elem, isSuper, *eoe)) {    // [Super]
+            //                                            // THIS KEY
+            //                                            // [Other]
+            //                                            // OBJ KEY
+            return false;
+        }
+        if (isSuper) {
+            // SUPERBASE is pushed onto KEY in eoe->emitGet below.
             offset += 3;
         } else {
-            if (!emitElemOperands(elem, opt)) {
-                return false;
-            }
             offset += 2;
         }
         break;
       }
       case ParseNodeKind::Array:
       case ParseNodeKind::Object:
         break;
       case ParseNodeKind::Call:
@@ -4444,111 +3979,110 @@ BytecodeEmitter::emitAssignment(ParseNod
         if (!emit1(JSOP_POP)) {
             return false;
         }
         break;
       default:
         MOZ_ASSERT(0);
     }
 
-    if (op != JSOP_NOP) {
+    if (isCompound) {
         MOZ_ASSERT(rhs);
         switch (lhs->getKind()) {
           case ParseNodeKind::Dot: {
-            JSOp getOp;
             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 = prop->key().atom() == cx->names().length;
-                getOp = isLength ? JSOP_LENGTH : JSOP_GETPROP;
-            }
-            if (!emitIndex32(getOp, atomIndex)) {
+            if (!poe->emitGet(prop->key().atom())) {      // [Super]
+                //                                        // THIS SUPERBASE PROP
+                //                                        // [Other]
+                //                                        // OBJ PROP
                 return false;
             }
             break;
           }
           case ParseNodeKind::Elem: {
-            JSOp elemOp;
-            PropertyByValue* elem = &lhs->as<PropertyByValue>();
-            if (elem->isSuper()) {
-                if (!emitDupAt(2)) {
-                    return false;
-                }
-                if (!emitDupAt(2)) {
-                    return false;
-                }
-                if (!emitDupAt(2)) {
-                    return false;
-                }
-                elemOp = JSOP_GETELEM_SUPER;
-            } else {
-                if (!emit1(JSOP_DUP2)) {
-                    return false;
-                }
-                elemOp = JSOP_GETELEM;
-            }
-            if (!emitElemOpBase(elemOp)) {
+            if (!eoe->emitGet()) {                        // KEY THIS OBJ ELEM
                 return false;
             }
             break;
           }
           case ParseNodeKind::Call:
             // We just emitted a JSOP_THROWMSG and popped the call's return
             // value.  Push a random value to make sure the stack depth is
             // correct.
             if (!emit1(JSOP_NULL)) {
                 return false;
             }
             break;
           default:;
         }
     }
 
-    if (!EmitAssignmentRhs(this, rhs, offset)) {
+    switch (lhs->getKind()) {
+      case ParseNodeKind::Dot:
+        if (!poe->prepareForRhs()) {                      // [Simple,Super]
+            //                                            // THIS SUPERBASE
+            //                                            // [Simple,Other]
+            //                                            // OBJ
+            //                                            // [Compound,Super]
+            //                                            // THIS SUPERBASE PROP
+            //                                            // [Compound,Other]
+            //                                            // OBJ PROP
+            return false;
+        }
+        break;
+      case ParseNodeKind::Elem:
+        if (!eoe->prepareForRhs()) {                      // [Simple,Super]
+            //                                            // THIS KEY SUPERBASE
+            //                                            // [Simple,Other]
+            //                                            // OBJ KEY
+            //                                            // [Compound,Super]
+            //                                            // THIS KEY SUPERBASE ELEM
+            //                                            // [Compound,Other]
+            //                                            // OBJ KEY ELEM
+            return false;
+        }
+        break;
+      default:
+        break;
+    }
+
+    if (!EmitAssignmentRhs(this, rhs, offset)) {          // ... VAL? RHS
         return false;
     }
 
     /* If += etc., emit the binary operator with a source note. */
-    if (op != JSOP_NOP) {
+    if (isCompound) {
         if (!newSrcNote(SRC_ASSIGNOP)) {
             return false;
         }
-        if (!emit1(op)) {
+        if (!emit1(compoundOp)) {                         // ... VAL
             return false;
         }
     }
 
     /* Finally, emit the specialized assignment bytecode. */
     switch (lhs->getKind()) {
       case ParseNodeKind::Dot: {
-        JSOp setOp = lhs->as<PropertyAccess>().isSuper() ?
-                       (sc->strict() ? JSOP_STRICTSETPROP_SUPER : JSOP_SETPROP_SUPER) :
-                       (sc->strict() ? JSOP_STRICTSETPROP : JSOP_SETPROP);
-        if (!emitIndexOp(setOp, atomIndex)) {
-            return false;
-        }
+        PropertyAccess* prop = &lhs->as<PropertyAccess>();
+        if (!poe->emitAssignment(prop->key().atom())) {   // VAL
+            return false;
+        }
+
+        poe.reset();
         break;
       }
       case ParseNodeKind::Call:
         // We threw above, so nothing to do here.
         break;
       case ParseNodeKind::Elem: {
-        JSOp setOp = lhs->as<PropertyByValue>().isSuper() ?
-                       sc->strict() ? JSOP_STRICTSETELEM_SUPER : JSOP_SETELEM_SUPER :
-                       sc->strict() ? JSOP_STRICTSETELEM : JSOP_SETELEM;
-        if (!emit1(setOp)) {
-            return false;
-        }
+        if (!eoe->emitAssignment()) {                     // VAL
+            return false;
+        }
+
+        eoe.reset();
         break;
       }
       case ParseNodeKind::Array:
       case ParseNodeKind::Object:
         if (!emitDestructuringOps(&lhs->as<ListNode>(), DestructuringAssignment)) {
             return false;
         }
         break;
@@ -5353,53 +4887,57 @@ BytecodeEmitter::emitInitializeForInOrOf
 
     ParseNode* target = forHead->kid1();
     MOZ_ASSERT(!forHead->kid2());
 
     // If the for-in/of loop didn't have a variable declaration, per-loop
     // initialization is just assigning the iteration value to a target
     // expression.
     if (!parser->astGenerator().isDeclarationList(target)) {
-        return emitAssignment(target, ParseNodeKind::Assign, nullptr); // ... ITERVAL
+        return emitAssignment(target, JSOP_NOP, nullptr); // ... ITERVAL
     }
 
     // Otherwise, per-loop initialization is (possibly) declaration
     // initialization.  If the declaration is a lexical declaration, it must be
     // initialized.  If the declaration is a variable declaration, an
     // assignment to that name (which does *not* necessarily assign to the
     // variable!) must be generated.
 
     if (!updateSourceCoordNotes(target->pn_pos.begin)) {
         return false;
     }
 
     MOZ_ASSERT(target->isForLoopDeclaration());
     target = parser->astGenerator().singleBindingFromDeclaration(&target->as<ListNode>());
 
     if (target->isKind(ParseNodeKind::Name)) {
-        auto emitSwapScopeAndRhs = [](BytecodeEmitter* bce, const NameLocation&,
-                                      bool emittedBindOp)
-        {
-            if (emittedBindOp) {
-                // Per-iteration initialization in for-in/of loops computes the
-                // iteration value *before* initializing.  Thus the
-                // initializing value may be buried under a bind-specific value
-                // on the stack.  Swap it to the top of the stack.
-                MOZ_ASSERT(bce->stackDepth >= 2);
-                return bce->emit1(JSOP_SWAP);
-            }
-
-            // In cases of emitting a frame slot or environment slot,
-            // nothing needs be done.
-            MOZ_ASSERT(bce->stackDepth >= 1);
-            return true;
-        };
+        NameOpEmitter noe(this, target->name(), NameOpEmitter::Kind::Initialize);
+        if (!noe.prepareForRhs()) {
+            return false;
+        }
+        if (noe.emittedBindOp()) {
+            // Per-iteration initialization in for-in/of loops computes the
+            // iteration value *before* initializing.  Thus the initializing
+            // value may be buried under a bind-specific value on the stack.
+            // Swap it to the top of the stack.
+            MOZ_ASSERT(stackDepth >= 2);
+            if (!emit1(JSOP_SWAP)) {
+                return false;
+            }
+        } else {
+             // In cases of emitting a frame slot or environment slot,
+             // nothing needs be done.
+            MOZ_ASSERT(stackDepth >= 1);
+        }
+        if (!noe.emitAssignment()) {
+            return false;
+        }
 
         // The caller handles removing the iteration value from the stack.
-        return emitInitializeName(target, emitSwapScopeAndRhs);
+        return true;
     }
 
     MOZ_ASSERT(!target->isKind(ParseNodeKind::Assign),
                "for-in/of loop destructuring declarations can't have initializers");
 
     MOZ_ASSERT(target->isKind(ParseNodeKind::Array) ||
                target->isKind(ParseNodeKind::Object));
     return emitDestructuringOps(&target->as<ListNode>(), DestructuringDeclaration);
@@ -5493,21 +5031,24 @@ BytecodeEmitter::emitForIn(ForNode* forI
             if (ParseNode* initializer = decl->as<NameNode>().initializer()) {
                 MOZ_ASSERT(forInTarget->isKind(ParseNodeKind::Var),
                            "for-in initializers are only permitted for |var| declarations");
 
                 if (!updateSourceCoordNotes(decl->pn_pos.begin)) {
                     return false;
                 }
 
-                auto emitRhs = [decl, initializer](BytecodeEmitter* bce, const NameLocation&, bool) {
-                    return bce->emitInitializer(initializer, decl);
-                };
-
-                if (!emitInitializeName(decl, emitRhs)) {
+                NameOpEmitter noe(this, decl->name(), NameOpEmitter::Kind::Initialize);
+                if (!noe.prepareForRhs()) {
+                    return false;
+                }
+                if (!emitInitializer(initializer, decl)) {
+                    return false;
+                }
+                if (!noe.emitAssignment()) {
                     return false;
                 }
 
                 // Pop the initializer.
                 if (!emit1(JSOP_POP)) {
                     return false;
                 }
             }
@@ -5670,22 +5211,16 @@ BytecodeEmitter::emitFunction(CodeNode* 
     // emitted. Function definitions that need hoisting to the top of the
     // function will be seen by emitFunction in two places.
     if (funbox->wasEmitted) {
         // Annex B block-scoped functions are hoisted like any other
         // block-scoped function to the top of their scope. When their
         // definitions are seen for the second time, we need to emit the
         // assignment that assigns the function to the outer 'var' binding.
         if (funbox->isAnnexB) {
-            auto emitRhs = [&name](BytecodeEmitter* bce, const NameLocation&, bool) {
-                // The RHS is the value of the lexically bound name in the
-                // innermost scope.
-                return bce->emitGetName(name);
-            };
-
             // Get the location of the 'var' binding in the body scope. The
             // name must be found, else there is a bug in the Annex B handling
             // in Parser.
             //
             // In sloppy eval contexts, this location is dynamic.
             Maybe<NameLocation> lhsLoc = locationOfNameBoundInScope(name, varEmitterScope);
 
             // If there are parameter expressions, the var name could be a
@@ -5698,17 +5233,24 @@ BytecodeEmitter::emitFunction(CodeNode* 
                 lhsLoc = Some(NameLocation::DynamicAnnexBVar());
             } else {
                 MOZ_ASSERT(lhsLoc->bindingKind() == BindingKind::Var ||
                            lhsLoc->bindingKind() == BindingKind::FormalParameter ||
                            (lhsLoc->bindingKind() == BindingKind::Let &&
                             sc->asFunctionBox()->hasParameterExprs));
             }
 
-            if (!emitSetOrInitializeNameAtLocation(name, *lhsLoc, emitRhs, false)) {
+            NameOpEmitter noe(this, name, *lhsLoc, NameOpEmitter::Kind::SimpleAssignment);
+            if (!noe.prepareForRhs()) {
+                return false;
+            }
+            if (!emitGetName(name)) {
+                return false;
+            }
+            if (!noe.emitAssignment()) {
                 return false;
             }
             if (!emit1(JSOP_POP)) {
                 return false;
             }
         }
 
         MOZ_ASSERT_IF(fun->hasScript(), fun->nonLazyScript());
@@ -5867,28 +5409,32 @@ BytecodeEmitter::emitFunction(CodeNode* 
                 return false;
             }
             switchToMain();
         }
     } else {
         // For functions nested within functions and blocks, make a lambda and
         // initialize the binding name of the function in the current scope.
 
-        bool isAsync = funbox->isAsync();
-        bool isGenerator = funbox->isGenerator();
-        auto emitLambda = [index, isAsync, isGenerator](BytecodeEmitter* bce,
-                                                        const NameLocation&, bool) {
-            if (isAsync) {
-                return bce->emitAsyncWrapper(index, /* needsHomeObject = */ false,
-                                             /* isArrow = */ false, isGenerator);
-            }
-            return bce->emitIndexOp(JSOP_LAMBDA, index);
-        };
-
-        if (!emitInitializeName(name, emitLambda)) {
+        NameOpEmitter noe(this, name, NameOpEmitter::Kind::Initialize);
+        if (!noe.prepareForRhs()) {
+            return false;
+        }
+        if (funbox->isAsync()) {
+            if (!emitAsyncWrapper(index, /* needsHomeObject = */ false,
+                                  /* isArrow = */ false, funbox->isGenerator()))
+            {
+                return false;
+            }
+        } else {
+            if (!emitIndexOp(JSOP_LAMBDA, index)) {
+                return false;
+            }
+        }
+        if (!noe.emitAssignment()) {
             return false;
         }
         if (!emit1(JSOP_POP)) {
             return false;
         }
     }
 
     return true;
@@ -6072,46 +5618,60 @@ BytecodeEmitter::emitContinue(PropertyNa
 
 bool
 BytecodeEmitter::emitGetFunctionThis(ParseNode* pn)
 {
     MOZ_ASSERT(sc->thisBinding() == ThisBinding::Function);
     MOZ_ASSERT(pn->isKind(ParseNodeKind::Name));
     MOZ_ASSERT(pn->name() == cx->names().dotThis);
 
-    if (!emitTree(pn)) {
-        return false;
-    }
-    if (sc->needsThisTDZChecks() && !emit1(JSOP_CHECKTHIS)) {
-        return false;
+    return emitGetFunctionThis(Some(pn->pn_pos.begin));
+}
+
+bool
+BytecodeEmitter::emitGetFunctionThis(const mozilla::Maybe<uint32_t>& offset)
+{
+    if (offset) {
+        if (!updateLineNumberNotes(*offset)) {
+            return false;
+        }
+    }
+
+    if (!emitGetName(cx->names().dotThis)) {              // THIS
+        return false;
+    }
+    if (sc->needsThisTDZChecks()) {
+        if (!emit1(JSOP_CHECKTHIS)) {                     // THIS
+            return false;
+        }
     }
 
     return true;
 }
 
 bool
 BytecodeEmitter::emitGetThisForSuperBase(UnaryNode* superBase)
 {
     MOZ_ASSERT(superBase->isKind(ParseNodeKind::SuperBase));
-    return emitGetFunctionThis(superBase->kid());
+    return emitGetFunctionThis(superBase->kid());         // THIS
 }
 
 bool
 BytecodeEmitter::emitThisLiteral(ThisLiteral* pn)
 {
     if (ParseNode* thisName = pn->kid()) {
-        return emitGetFunctionThis(thisName);
+        return emitGetFunctionThis(thisName);             // THIS
     }
 
     if (sc->thisBinding() == ThisBinding::Module) {
-        return emit1(JSOP_UNDEFINED);
+        return emit1(JSOP_UNDEFINED);                     // UNDEF
     }
 
     MOZ_ASSERT(sc->thisBinding() == ThisBinding::Global);
-    return emit1(JSOP_GLOBALTHIS);
+    return emit1(JSOP_GLOBALTHIS);                        // THIS
 }
 
 bool
 BytecodeEmitter::emitCheckDerivedClassConstructorReturn()
 {
     MOZ_ASSERT(lookupName(cx->names().dotThis).hasKnownSlot());
     if (!emitGetName(cx->names().dotThis)) {
         return false;
@@ -6754,26 +6314,26 @@ BytecodeEmitter::emitExpressionStatement
             innermostNestableControl->is<LabelControl>() &&
             innermostNestableControl->as<LabelControl>().startOffset() >= offset())
         {
             useful = true;
         }
     }
 
     if (useful) {
-        JSOp op = wantval ? JSOP_SETRVAL : JSOP_POP;
+        MOZ_ASSERT_IF(expr->isKind(ParseNodeKind::Assign), expr->isOp(JSOP_NOP));
         ValueUsage valueUsage = wantval ? ValueUsage::WantValue : ValueUsage::IgnoreValue;
-        MOZ_ASSERT_IF(expr->isKind(ParseNodeKind::Assign), expr->isOp(JSOP_NOP));
-        if (!updateSourceCoordNotes(exprStmt->pn_pos.begin)) {
+        ExpressionStatementEmitter ese(this, valueUsage);
+        if (!ese.prepareForExpr(Some(exprStmt->pn_pos.begin))) {
             return false;
         }
         if (!emitTree(expr, valueUsage)) {
             return false;
         }
-        if (!emit1(op)) {
+        if (!ese.emitEnd()) {
             return false;
         }
     } else if (exprStmt->isDirectivePrologueMember()) {
         // Don't complain about directive prologue members; just don't emit
         // their code.
     } else {
         if (JSAtom* atom = exprStmt->isStringExprStatement()) {
             // Warn if encountering a non-directive prologue member string
@@ -6812,93 +6372,103 @@ BytecodeEmitter::emitExpressionStatement
 bool
 BytecodeEmitter::emitDeleteName(UnaryNode* deleteNode)
 {
     MOZ_ASSERT(deleteNode->isKind(ParseNodeKind::DeleteName));
 
     NameNode* nameExpr = &deleteNode->kid()->as<NameNode>();
     MOZ_ASSERT(nameExpr->isKind(ParseNodeKind::Name));
 
-    return emitAtomOp(nameExpr, JSOP_DELNAME);
+    return emitAtomOp(nameExpr->atom(), JSOP_DELNAME);
 }
 
 bool
 BytecodeEmitter::emitDeleteProperty(UnaryNode* deleteNode)
 {
     MOZ_ASSERT(deleteNode->isKind(ParseNodeKind::DeleteProp));
 
     PropertyAccess* propExpr = &deleteNode->kid()->as<PropertyAccess>();
-
+    PropOpEmitter poe(this,
+                      PropOpEmitter::Kind::Delete,
+                      propExpr->as<PropertyAccess>().isSuper()
+                      ? PropOpEmitter::ObjKind::Super
+                      : PropOpEmitter::ObjKind::Other);
     if (propExpr->isSuper()) {
         // The expression |delete super.foo;| has to evaluate |super.foo|,
         // which could throw if |this| hasn't yet been set by a |super(...)|
         // call or the super-base is not an object, before throwing a
         // ReferenceError for attempting to delete a super-reference.
-        if (!emitGetThisForSuperBase(&propExpr->expression().as<UnaryNode>())) {
-            return false;
-        }
-
-        if (!emit1(JSOP_SUPERBASE)) {
-            return false;
-        }
-
-        // Unconditionally throw when attempting to delete a super-reference.
-        if (!emitUint16Operand(JSOP_THROWMSG, JSMSG_CANT_DELETE_SUPER)) {
-            return false;
-        }
-
-        // Another wrinkle: Balance the stack from the emitter's point of view.
-        // Execution will not reach here, as the last bytecode threw.
-        return emit1(JSOP_POP);
-    }
-
-    JSOp delOp = sc->strict() ? JSOP_STRICTDELPROP : JSOP_DELPROP;
-    return emitPropOp(propExpr, delOp);
+        UnaryNode* base = &propExpr->expression().as<UnaryNode>();
+        if (!emitGetThisForSuperBase(base)) {             // THIS
+            return false;
+        }
+    } else {
+        if (!poe.prepareForObj()) {
+            return false;
+        }
+        if (!emitPropLHS(propExpr)) {                         // OBJ
+            return false;
+        }
+    }
+
+    if (!poe.emitDelete(propExpr->key().atom())) {        // [Super]
+        //                                                // THIS
+        //                                                // [Other]
+        //                                                // SUCCEEDED
+        return false;
+    }
+
+    return true;
 }
 
 bool
 BytecodeEmitter::emitDeleteElement(UnaryNode* deleteNode)
 {
     MOZ_ASSERT(deleteNode->isKind(ParseNodeKind::DeleteElem));
 
     PropertyByValue* elemExpr = &deleteNode->kid()->as<PropertyByValue>();
-
-    if (elemExpr->isSuper()) {
+    bool isSuper = elemExpr->isSuper();
+    ElemOpEmitter eoe(this,
+                      ElemOpEmitter::Kind::Delete,
+                      isSuper
+                      ? ElemOpEmitter::ObjKind::Super
+                      : ElemOpEmitter::ObjKind::Other);
+    if (isSuper) {
         // The expression |delete super[foo];| has to evaluate |super[foo]|,
         // which could throw if |this| hasn't yet been set by a |super(...)|
         // call, or trigger side-effects when evaluating ToPropertyKey(foo),
         // or also throw when the super-base is not an object, before throwing
         // a ReferenceError for attempting to delete a super-reference.
-        if (!emitGetThisForSuperBase(&elemExpr->expression().as<UnaryNode>())) {
-            return false;
-        }
-
-        if (!emitTree(&elemExpr->key())) {
-            return false;
-        }
-        if (!emit1(JSOP_TOID)) {
-            return false;
-        }
-
-        if (!emit1(JSOP_SUPERBASE)) {
-            return false;
-        }
-
-        // Unconditionally throw when attempting to delete a super-reference.
-        if (!emitUint16Operand(JSOP_THROWMSG, JSMSG_CANT_DELETE_SUPER)) {
-            return false;
-        }
-
-        // Another wrinkle: Balance the stack from the emitter's point of view.
-        // Execution will not reach here, as the last bytecode threw.
-        return emitPopN(2);
-    }
-
-    JSOp delOp = sc->strict() ? JSOP_STRICTDELELEM : JSOP_DELELEM;
-    return emitElemOp(elemExpr, delOp);
+        if (!eoe.prepareForObj()) {                       //
+            return false;
+        }
+
+        UnaryNode* base = &elemExpr->expression().as<UnaryNode>();
+        if (!emitGetThisForSuperBase(base)) {             // THIS
+            return false;
+        }
+        if (!eoe.prepareForKey()) {                       // THIS
+            return false;
+        }
+        if (!emitTree(&elemExpr->key())) {                // THIS KEY
+            return false;
+        }
+    } else {
+        if (!emitElemObjAndKey(elemExpr, false, eoe)) {   // OBJ KEY
+            return false;
+        }
+    }
+    if (!eoe.emitDelete()) {                              // [Super]
+        //                                                // THIS
+        //                                                // [Other]
+        //                                                // SUCCEEDED
+        return false;
+    }
+
+    return true;
 }
 
 bool
 BytecodeEmitter::emitDeleteExpression(UnaryNode* deleteNode)
 {
     MOZ_ASSERT(deleteNode->isKind(ParseNodeKind::DeleteExpr));
 
     ParseNode* expression = deleteNode->kid();
@@ -7202,238 +6772,194 @@ BytecodeEmitter::isRestParameter(ParseNo
             return paramName && name == paramName;
         }
     }
 
     return false;
 }
 
 bool
-BytecodeEmitter::emitCallee(ParseNode* callee, ParseNode* call, bool* callop)
+BytecodeEmitter::emitCalleeAndThis(ParseNode* callee, ParseNode* call, CallOrNewEmitter& cone)
 {
     switch (callee->getKind()) {
       case ParseNodeKind::Name:
-        if (!emitGetName(callee, *callop)) {
+        if (!cone.emitNameCallee(callee->name())) {       // CALLEE THIS
             return false;
         }
         break;
       case ParseNodeKind::Dot: {
+        MOZ_ASSERT(emitterMode != BytecodeEmitter::SelfHosting);
         PropertyAccess* prop = &callee->as<PropertyAccess>();
-        MOZ_ASSERT(emitterMode != BytecodeEmitter::SelfHosting);
-        if (prop->isSuper()) {
-            if (!emitSuperGetProp(prop, /* isCall = */ *callop)) {
+        bool isSuper = prop->isSuper();
+
+        PropOpEmitter& poe = cone.prepareForPropCallee(isSuper);
+        if (!poe.prepareForObj()) {
+            return false;
+        }
+        if (isSuper) {
+            UnaryNode* base = &prop->expression().as<UnaryNode>();
+            if (!emitGetThisForSuperBase(base)) {        // THIS
                 return false;
             }
         } else {
-            if (!emitPropOp(prop, *callop ? JSOP_CALLPROP : JSOP_GETPROP)) {
-                return false;
-            }
+            if (!emitPropLHS(prop)) {                    // OBJ
+                return false;
+            }
+        }
+        if (!poe.emitGet(prop->key().atom())) {           // CALLEE THIS?
+            return false;
         }
 
         break;
       }
       case ParseNodeKind::Elem: {
+        MOZ_ASSERT(emitterMode != BytecodeEmitter::SelfHosting);
         PropertyByValue* elem = &callee->as<PropertyByValue>();
-        MOZ_ASSERT(emitterMode != BytecodeEmitter::SelfHosting);
-        if (elem->isSuper()) {
-            if (!emitSuperGetElem(elem, /* isCall = */ *callop)) {
-                return false;
-            }
-        } else {
-            if (!emitElemOp(elem, *callop ? JSOP_CALLELEM : JSOP_GETELEM)) {
-                return false;
-            }
-            if (*callop) {
-                if (!emit1(JSOP_SWAP)) {
-                    return false;
-                }
-            }
+        bool isSuper = elem->isSuper();
+
+        ElemOpEmitter& eoe = cone.prepareForElemCallee(isSuper);
+        if (!emitElemObjAndKey(elem, isSuper, eoe)) {     // [Super]
+            //                                            // THIS? THIS KEY
+            //                                            // [needsThis,Other]
+            //                                            // OBJ? OBJ KEY
+            return false;
+        }
+        if (!eoe.emitGet()) {                             // CALLEE? THIS
+            return false;
         }
 
         break;
       }
       case ParseNodeKind::Function:
-        /*
-         * Top level lambdas which are immediately invoked should be
-         * treated as only running once. Every time they execute we will
-         * create new types and scripts for their contents, to increase
-         * the quality of type information within them and enable more
-         * backend optimizations. Note that this does not depend on the
-         * lambda being invoked at most once (it may be named or be
-         * accessed via foo.caller indirection), as multiple executions
-         * will just cause the inner scripts to be repeatedly cloned.
-         */
-        MOZ_ASSERT(!emittingRunOnceLambda);
-        if (checkRunOnceContext()) {
-            emittingRunOnceLambda = true;
-            if (!emitTree(callee)) {
-                return false;
-            }
-            emittingRunOnceLambda = false;
-        } else {
-            if (!emitTree(callee)) {
-                return false;
-            }
-        }
-        *callop = false;
+        if (!cone.prepareForFunctionCallee()) {
+            return false;
+        }
+        if (!emitTree(callee)) {                          // CALLEE
+            return false;
+        }
         break;
       case ParseNodeKind::SuperBase:
         MOZ_ASSERT(call->isKind(ParseNodeKind::SuperCall));
         MOZ_ASSERT(parser->astGenerator().isSuperBase(callee));
-        if (!emit1(JSOP_SUPERFUN)) {
+        if (!cone.emitSuperCallee()) {                    // CALLEE THIS
             return false;
         }
         break;
       default:
+        if (!cone.prepareForOtherCallee()) {
+            return false;
+        }
         if (!emitTree(callee)) {
             return false;
         }
-        *callop = false;             /* trigger JSOP_UNDEFINED after */
         break;
     }
 
+    if (!cone.emitThis()) {                               // CALLEE THIS
+        return false;
+    }
+
     return true;
 }
 
 bool
 BytecodeEmitter::emitPipeline(ListNode* node)
 {
     MOZ_ASSERT(node->count() >= 2);
 
-    if (!emitTree(node->head())) {
+    if (!emitTree(node->head())) {                        // ARG
         return false;
     }
 
     ParseNode* callee = node->head()->pn_next;
-
+    CallOrNewEmitter cone(this, JSOP_CALL,
+                          CallOrNewEmitter::ArgumentsKind::Other,
+                          ValueUsage::WantValue);
     do {
-        bool callop = true;
-        if (!emitCallee(callee, node, &callop)) {
-            return false;
-        }
-
-        // Emit room for |this|
-        if (!callop) {
-            if (!emit1(JSOP_UNDEFINED)) {
-                return false;
-            }
-        }
-
-        if (!emit2(JSOP_PICK, 2)) {
-            return false;
-        }
-
-        if (!emitCall(JSOP_CALL, 1, node)) {
-            return false;
-        }
+        if (!emitCalleeAndThis(callee, node, cone)) {     // ARG CALLEE THIS
+            return false;
+        }
+        if (!emit2(JSOP_PICK, 2)) {                       // CALLEE THIS ARG
+            return false;
+        }
+        if (!cone.emitEnd(1, Some(node->pn_pos.begin))) { // RVAL
+            return false;
+        }
+
+        cone.reset();
 
         checkTypeSet(JSOP_CALL);
     } while ((callee = callee->pn_next));
 
     return true;
 }
 
 bool
-BytecodeEmitter::emitArguments(ListNode* argsList, bool callop, bool spread)
+BytecodeEmitter::emitArguments(ListNode* argsList, bool isCall, bool isSpread,
+                               CallOrNewEmitter& cone)
 {
     uint32_t argc = argsList->count();
-
     if (argc >= ARGC_LIMIT) {
-        reportError(argsList, callop ? JSMSG_TOO_MANY_FUN_ARGS : JSMSG_TOO_MANY_CON_ARGS);
-        return false;
-    }
-
-    if (!spread) {
+        reportError(argsList, isCall ? JSMSG_TOO_MANY_FUN_ARGS : JSMSG_TOO_MANY_CON_ARGS);
+        return false;
+    }
+    if (!isSpread) {
+        if (!cone.prepareForNonSpreadArguments()) {       // CALLEE THIS
+            return false;
+        }
         for (ParseNode* arg : argsList->contents()) {
             if (!emitTree(arg)) {
                 return false;
             }
         }
     } else {
-        ParseNode* args = argsList->head();
-        MOZ_ASSERT_IF(argc == 1, args->isKind(ParseNodeKind::Spread));
-        bool emitOptCode = (argc == 1) && isRestParameter(args->as<UnaryNode>().kid());
-        InternalIfEmitter ifNotOptimizable(this);
-
-        if (emitOptCode) {
-            // Emit a preparation code to optimize the spread call with a rest
-            // parameter:
-            //
-            //   function f(...args) {
-            //     g(...args);
-            //   }
-            //
-            // If the spread operand is a rest parameter and it's optimizable
-            // array, skip spread operation and pass it directly to spread call
-            // operation.  See the comment in OptimizeSpreadCall in
-            // Interpreter.cpp for the optimizable conditons.
-
-            if (!emitTree(args->as<UnaryNode>().kid())) {
-                return false;
-            }
-
-            if (!emit1(JSOP_OPTIMIZE_SPREADCALL)) {
-                return false;
-            }
-
-            if (!emit1(JSOP_NOT)) {
-                return false;
-            }
-
-            if (!ifNotOptimizable.emitThen()) {
-                return false;
-            }
-
-            if (!emit1(JSOP_POP)) {
-                return false;
-            }
-        }
-
-        if (!emitArray(args, argc)) {
-            return false;
-        }
-
-        if (emitOptCode) {
-            if (!ifNotOptimizable.emitEnd()) {
-                return false;
-            }
+        if (cone.wantSpreadOperand()) {
+            UnaryNode* spreadNode = &argsList->head()->as<UnaryNode>();
+            if (!emitTree(spreadNode->kid())) {           // CALLEE THIS ARG0
+                return false;
+            }
+        }
+        if (!cone.emitSpreadArgumentsTest()) {            // CALLEE THIS
+            return false;
+        }
+        if (!emitArray(argsList->head(), argc)) {         // CALLEE THIS ARR
+            return false;
         }
     }
 
     return true;
 }
 
 bool
 BytecodeEmitter::emitCallOrNew(BinaryNode* callNode,
                                ValueUsage valueUsage /* = ValueUsage::WantValue */)
 {
-    bool callop =
-        callNode->isKind(ParseNodeKind::Call) || callNode->isKind(ParseNodeKind::TaggedTemplate);
-
     /*
      * Emit callable invocation or operator new (constructor call) code.
      * First, emit code for the left operand to evaluate the callable or
      * constructable object expression.
      *
      * For operator new, we emit JSOP_GETPROP instead of JSOP_CALLPROP, etc.
      * This is necessary to interpose the lambda-initialized method read
      * barrier -- see the code in jsinterp.cpp for JSOP_LAMBDA followed by
      * JSOP_{SET,INIT}PROP.
      *
      * Then (or in a call case that has no explicit reference-base
      * object) we emit JSOP_UNDEFINED to produce the undefined |this|
      * value required for calls (which non-strict mode functions
      * will box into the global object).
      */
+    bool isCall =
+        callNode->isKind(ParseNodeKind::Call) || callNode->isKind(ParseNodeKind::TaggedTemplate);
     ParseNode* calleeNode = callNode->left();
     ListNode* argsList = &callNode->right()->as<ListNode>();
-
-    bool spread = JOF_OPTYPE(callNode->getOp()) == JOF_BYTE;
-
-    if (calleeNode->isKind(ParseNodeKind::Name) && emitterMode == BytecodeEmitter::SelfHosting && !spread) {
+    bool isSpread = JOF_OPTYPE(callNode->getOp()) == JOF_BYTE;
+    if (calleeNode->isKind(ParseNodeKind::Name) && emitterMode == BytecodeEmitter::SelfHosting &&
+        !isSpread)
+    {
         // Calls to "forceInterpreter", "callFunction",
         // "callContentFunction", or "resumeGenerator" in self-hosted
         // code generate inline bytecode.
         PropertyName* calleeName = calleeNode->name();
         if (calleeName == cx->names().callFunction ||
             calleeName == cx->names().callContentFunction ||
             calleeName == cx->names().constructContentFunction)
         {
@@ -7455,64 +6981,29 @@ BytecodeEmitter::emitCallOrNew(BinaryNod
             return emitSelfHostedHasOwn(callNode);
         }
         if (calleeName == cx->names().getPropertySuper) {
             return emitSelfHostedGetPropertySuper(callNode);
         }
         // Fall through
     }
 
-    if (!emitCallee(calleeNode, callNode, &callop)) {
-        return false;
-    }
-
     JSOp op = callNode->getOp();
-    bool isNewOp = op == JSOP_NEW || op == JSOP_SPREADNEW ||
-                   op == JSOP_SUPERCALL ||
-                   op == JSOP_SPREADSUPERCALL;
-
-    // Emit room for |this|.
-    if (!callop) {
-        if (isNewOp) {
-            if (!emit1(JSOP_IS_CONSTRUCTING)) {
-                return false;
-            }
-        } else {
-            if (!emit1(JSOP_UNDEFINED)) {
-                return false;
-            }
-        }
-    }
-
-    if (!emitArguments(argsList, callop, spread)) {
-        return false;
-    }
-
     uint32_t argc = argsList->count();
-
-    /*
-     * Emit code for each argument in order, then emit the JSOP_*CALL or
-     * JSOP_NEW bytecode with a two-byte immediate telling how many args
-     * were pushed on the operand stack.
-     */
-    if (isNewOp) {
-        if (callNode->isKind(ParseNodeKind::SuperCall)) {
-            if (!emit1(JSOP_NEWTARGET)) {
-                return false;
-            }
-        } else if (!spread) {
-            // Repush the callee as new.target
-            if (!emitDupAt(argc + 1)) {
-                return false;
-            }
-        } else {
-            if (!emitDupAt(2)) {
-                return false;
-            }
-        }
+    CallOrNewEmitter cone(this, op,
+                          isSpread && (argc == 1) &&
+                          isRestParameter(argsList->head()->as<UnaryNode>().kid())
+                          ? CallOrNewEmitter::ArgumentsKind::SingleSpreadRest
+                          : CallOrNewEmitter::ArgumentsKind::Other,
+                          valueUsage);
+    if (!emitCalleeAndThis(calleeNode, callNode, cone)) { // CALLEE THIS
+        return false;
+    }
+    if (!emitArguments(argsList, isCall, isSpread, cone)) {
+        return false;                                     // CALLEE THIS ARGS...
     }
 
     ParseNode* coordNode = callNode;
     if (op == JSOP_CALL || op == JSOP_SPREADCALL) {
         switch (calleeNode->getKind()) {
           case ParseNodeKind::Dot: {
 
             // Check if this member is a simple chain of simple chain of
@@ -7547,48 +7038,18 @@ BytecodeEmitter::emitCallOrNew(BinaryNod
             // obj[expr]() // expression
             //          ^  // column coord
             coordNode = argsList;
             break;
           default:
             break;
         }
     }
-
-    if (!spread) {
-        if (op == JSOP_CALL && valueUsage == ValueUsage::IgnoreValue) {
-            if (!emitCall(JSOP_CALL_IGNORES_RV, argc, coordNode)) {
-                return false;
-            }
-            checkTypeSet(JSOP_CALL_IGNORES_RV);
-        } else {
-            if (!emitCall(op, argc, coordNode)) {
-                return false;
-            }
-            checkTypeSet(op);
-        }
-    } else {
-        if (coordNode) {
-            if (!updateSourceCoordNotes(coordNode->pn_pos.begin)) {
-                return false;
-            }
-        }
-
-        if (!emit1(op)) {
-            return false;
-        }
-        checkTypeSet(op);
-    }
-    if (op == JSOP_EVAL || op == JSOP_STRICTEVAL ||
-        op == JSOP_SPREADEVAL || op == JSOP_STRICTSPREADEVAL)
-    {
-        uint32_t lineNum = parser->errorReporter().lineAt(callNode->pn_pos.begin);
-        if (!emitUint32Operand(JSOP_LINENO, lineNum)) {
-            return false;
-        }
+    if (!cone.emitEnd(argc, Some(coordNode->pn_pos.begin))) {
+        return false;                                     // RVAL
     }
 
     return true;
 }
 
 static const JSOp ParseNodeKindToJSOp[] = {
     // JSOP_NOP is for pipeline operator which does not emit its own JSOp
     // but has highest precedence in binary operators
@@ -8339,24 +7800,26 @@ BytecodeEmitter::emitFunctionFormalParam
                         }
 
                         // The '.this' and '.generator' function special
                         // bindings should never appear in the extra var
                         // scope. 'arguments', however, may.
                         MOZ_ASSERT(name != cx->names().dotThis &&
                                    name != cx->names().dotGenerator);
 
+                        NameOpEmitter noe(this, name, NameOpEmitter::Kind::Initialize);
+                        if (!noe.prepareForRhs()) {
+                            return false;
+                        }
+
                         NameLocation paramLoc = *locationOfNameBoundInScope(name, &funEmitterScope);
-                        auto emitRhs = [&name, &paramLoc](BytecodeEmitter* bce,
-                                                          const NameLocation&, bool)
-                        {
-                            return bce->emitGetNameAtLocation(name, paramLoc);
-                        };
-
-                        if (!emitInitializeName(name, emitRhs)) {
+                        if (!emitGetNameAtLocation(name, paramLoc)) {
+                            return false;
+                        }
+                        if (!noe.emitAssignment()) {
                             return false;
                         }
                         if (!emit1(JSOP_POP)) {
                             return false;
                         }
                     }
                 }
             }
@@ -8486,50 +7949,37 @@ BytecodeEmitter::emitFunctionFormalParam
                                       : DestructuringDeclaration))
             {
                 return false;
             }
 
             if (!emit1(JSOP_POP)) {
                 return false;
             }
-        } else {
+        } else if (hasParameterExprs || isRest) {
             RootedAtom paramName(cx, bindingElement->name());
             NameLocation paramLoc = *locationOfNameBoundInScope(paramName, funScope);
-
+            NameOpEmitter noe(this, paramName, paramLoc, NameOpEmitter::Kind::Initialize);
+            if (!noe.prepareForRhs()) {
+                return false;
+            }
             if (hasParameterExprs) {
-                auto emitRhs = [argSlot, initializer, isRest](BytecodeEmitter* bce,
-                                                              const NameLocation&, bool)
-                {
-                    // If we had an initializer or a rest parameter, the value is
-                    // already on the stack.
-                    if (!initializer && !isRest) {
-                        return bce->emitArgOp(JSOP_GETARG, argSlot);
+                // If we had an initializer or a rest parameter, the value is
+                // already on the stack.
+                if (!initializer && !isRest) {
+                    if (!emitArgOp(JSOP_GETARG, argSlot)) {
+                        return false;
                     }
-                    return true;
-                };
-
-                if (!emitSetOrInitializeNameAtLocation(paramName, paramLoc, emitRhs, true)) {
-                    return false;
                 }
-                if (!emit1(JSOP_POP)) {
-                    return false;
-                }
-            } else if (isRest) {
-                // The rest value is already on top of the stack.
-                auto nop = [](BytecodeEmitter*, const NameLocation&, bool) {
-                    return true;
-                };
-
-                if (!emitSetOrInitializeNameAtLocation(paramName, paramLoc, nop, true)) {
-                    return false;
-                }
-                if (!emit1(JSOP_POP)) {
-                    return false;
-                }
+            }
+            if (!noe.emitAssignment()) {
+                return false;
+            }
+            if (!emit1(JSOP_POP)) {
+                return false;
             }
         }
 
         if (paramExprVarScope) {
             if (!paramExprVarScope->leave(this)) {
                 return false;
             }
         }
@@ -8545,21 +7995,24 @@ BytecodeEmitter::emitInitializeFunctionS
 
     auto emitInitializeFunctionSpecialName = [](BytecodeEmitter* bce, HandlePropertyName name,
                                                 JSOp op)
     {
         // A special name must be slotful, either on the frame or on the
         // call environment.
         MOZ_ASSERT(bce->lookupName(name).hasKnownSlot());
 
-        auto emitInitial = [op](BytecodeEmitter* bce, const NameLocation&, bool) {
-            return bce->emit1(op);
-        };
-
-        if (!bce->emitInitializeName(name, emitInitial)) {
+        NameOpEmitter noe(bce, name, NameOpEmitter::Kind::Initialize);
+        if (!noe.prepareForRhs()) {
+            return false;
+        }
+        if (!bce->emit1(op)) {
+            return false;
+        }
+        if (!noe.emitAssignment()) {
             return false;
         }
         if (!bce->emit1(JSOP_POP)) {
             return false;
         }
 
         return true;
     };
@@ -8646,24 +8099,31 @@ BytecodeEmitter::emitFunctionBody(ParseN
     }
 
     return true;
 }
 
 bool
 BytecodeEmitter::emitLexicalInitialization(ParseNode* pn)
 {
+    NameOpEmitter noe(this, pn->name(), NameOpEmitter::Kind::Initialize);
+    if (!noe.prepareForRhs()) {
+        return false;
+    }
+
     // The caller has pushed the RHS to the top of the stack. Assert that the
     // name is lexical and no BIND[G]NAME ops were emitted.
-    auto assertLexical = [](BytecodeEmitter*, const NameLocation& loc, bool emittedBindOp) {
-        MOZ_ASSERT(loc.isLexical());
-        MOZ_ASSERT(!emittedBindOp);
-        return true;
-    };
-    return emitInitializeName(pn, assertLexical);
+    MOZ_ASSERT(noe.loc().isLexical());
+    MOZ_ASSERT(!noe.emittedBindOp());
+
+    if (!noe.emitAssignment()) {
+        return false;
+    }
+
+    return true;
 }
 
 // This follows ES6 14.5.14 (ClassDefinitionEvaluation) and ES6 14.5.15
 // (BindingClassDeclarationEvaluation).
 bool
 BytecodeEmitter::emitClass(ClassNode* classNode)
 {
     ClassNames* names = classNode->names();
@@ -9107,17 +8567,20 @@ BytecodeEmitter::emitTree(ParseNode* pn,
       case ParseNodeKind::LshAssign:
       case ParseNodeKind::RshAssign:
       case ParseNodeKind::UrshAssign:
       case ParseNodeKind::MulAssign:
       case ParseNodeKind::DivAssign:
       case ParseNodeKind::ModAssign:
       case ParseNodeKind::PowAssign: {
         AssignmentNode* assignNode = &pn->as<AssignmentNode>();
-        if (!emitAssignment(assignNode->left(), assignNode->getKind(), assignNode->right())) {
+        if (!emitAssignment(assignNode->left(),
+                            CompoundAssignmentParseNodeKindToJSOp(assignNode->getKind()),
+                            assignNode->right()))
+        {
             return false;
         }
         break;
       }
 
       case ParseNodeKind::Conditional:
         if (!emitConditionalExpression(pn->as<ConditionalExpression>(), valueUsage)) {
             return false;
@@ -9222,39 +8685,59 @@ BytecodeEmitter::emitTree(ParseNode* pn,
       case ParseNodeKind::DeleteExpr:
         if (!emitDeleteExpression(&pn->as<UnaryNode>())) {
             return false;
         }
         break;
 
       case ParseNodeKind::Dot: {
         PropertyAccess* prop = &pn->as<PropertyAccess>();
-        if (prop->isSuper()) {
-            if (!emitSuperGetProp(prop)) {
+        bool isSuper = prop->isSuper();
+        PropOpEmitter poe(this,
+                          PropOpEmitter::Kind::Get,
+                          isSuper
+                          ? PropOpEmitter::ObjKind::Super
+                          : PropOpEmitter::ObjKind::Other);
+        if (!poe.prepareForObj()) {
+            return false;
+        }
+        if (isSuper) {
+            UnaryNode* base = &prop->expression().as<UnaryNode>();
+            if (!emitGetThisForSuperBase(base)) {         // THIS
                 return false;
             }
         } else {
-            if (!emitPropOp(prop, JSOP_GETPROP)) {
-                return false;
-            }
+            if (!emitPropLHS(prop)) {                     // OBJ
+                return false;
+            }
+        }
+        if (!poe.emitGet(prop->key().atom())) {           // PROP
+            return false;
         }
         break;
       }
 
       case ParseNodeKind::Elem: {
         PropertyByValue* elem = &pn->as<PropertyByValue>();
-        if (elem->isSuper()) {
-            if (!emitSuperGetElem(elem)) {
-                return false;
-            }
-        } else {
-            if (!emitElemOp(elem, JSOP_GETELEM)) {
-                return false;
-            }
-        }
+        bool isSuper = elem->isSuper();
+        ElemOpEmitter eoe(this,
+                          ElemOpEmitter::Kind::Get,
+                          isSuper
+                          ? ElemOpEmitter::ObjKind::Super
+                          : ElemOpEmitter::ObjKind::Other);
+        if (!emitElemObjAndKey(elem, isSuper, eoe)) {     // [Super]
+            //                                            // THIS KEY
+            //                                            // [Other]
+            //                                            // OBJ KEY
+            return false;
+        }
+        if (!eoe.emitGet()) {                             // ELEM
+            return false;
+        }
+
         break;
       }
 
       case ParseNodeKind::New:
       case ParseNodeKind::TaggedTemplate:
       case ParseNodeKind::Call:
       case ParseNodeKind::SuperCall:
         if (!emitCallOrNew(&pn->as<BinaryNode>(), valueUsage)) {
@@ -9329,17 +8812,17 @@ BytecodeEmitter::emitTree(ParseNode* pn,
       case ParseNodeKind::TemplateStringList:
         if (!emitTemplateString(&pn->as<ListNode>())) {
             return false;
         }
         break;
 
       case ParseNodeKind::TemplateString:
       case ParseNodeKind::String:
-        if (!emitAtomOp(&pn->as<NameNode>(), JSOP_STRING)) {
+        if (!emitAtomOp(pn->as<NameNode>().atom(), JSOP_STRING)) {
             return false;
         }
         break;
 
       case ParseNodeKind::Number:
         if (!emitNumberOp(pn->as<NumericLiteral>().value())) {
             return false;
         }
--- a/js/src/frontend/BytecodeEmitter.h
+++ b/js/src/frontend/BytecodeEmitter.h
@@ -14,16 +14,17 @@
 
 #include "ds/InlineTable.h"
 #include "frontend/BCEParserHandle.h"
 #include "frontend/EitherParser.h"
 #include "frontend/JumpList.h"
 #include "frontend/NameFunctions.h"
 #include "frontend/SharedContext.h"
 #include "frontend/SourceNotes.h"
+#include "frontend/ValueUsage.h"
 #include "vm/BytecodeUtil.h"
 #include "vm/Interpreter.h"
 #include "vm/Iteration.h"
 
 namespace js {
 namespace frontend {
 
 class CGNumberList {
@@ -104,28 +105,18 @@ struct CGYieldAndAwaitOffsetList {
     void finish(mozilla::Span<uint32_t> array, uint32_t prologueLength);
 };
 
 // Have a few inline elements, so as to avoid heap allocation for tiny
 // sequences.  See bug 1390526.
 typedef Vector<jsbytecode, 64> BytecodeVector;
 typedef Vector<jssrcnote, 64> SrcNotesVector;
 
-// Used to control whether JSOP_CALL_IGNORES_RV is emitted for function calls.
-enum class ValueUsage {
-    // Assume the value of the current expression may be used. This is always
-    // correct but prohibits JSOP_CALL_IGNORES_RV.
-    WantValue,
-
-    // Pass this when emitting an expression if the expression's value is
-    // definitely unused by later instructions. You must make sure the next
-    // instruction is JSOP_POP, a jump to a JSOP_POP, or something similar.
-    IgnoreValue
-};
-
+class CallOrNewEmitter;
+class ElemOpEmitter;
 class EmitterScope;
 class NestableControl;
 class TDZCheckCache;
 
 struct MOZ_STACK_CLASS BytecodeEmitter
 {
     SharedContext* const sc;      /* context shared between parsing and bytecode generation */
 
@@ -526,16 +517,17 @@ struct MOZ_STACK_CLASS BytecodeEmitter
 
     // Emit (1 + extra) bytecodes, for N bytes of op and its immediate operand.
     MOZ_MUST_USE bool emitN(JSOp op, size_t extra, ptrdiff_t* offset = nullptr);
 
     MOZ_MUST_USE bool emitNumberOp(double dval);
 
     MOZ_MUST_USE bool emitThisLiteral(ThisLiteral* pn);
     MOZ_MUST_USE bool emitGetFunctionThis(ParseNode* pn);
+    MOZ_MUST_USE bool emitGetFunctionThis(const mozilla::Maybe<uint32_t>& offset);
     MOZ_MUST_USE bool emitGetThisForSuperBase(UnaryNode* superBase);
     MOZ_MUST_USE bool emitSetThis(BinaryNode* setThisNode);
     MOZ_MUST_USE bool emitCheckDerivedClassConstructorReturn();
 
     // Handle jump opcodes and jump targets.
     MOZ_MUST_USE bool emitJumpTarget(JumpTarget* target);
     MOZ_MUST_USE bool emitJumpNoFallthrough(JSOp op, JumpList* jump);
     MOZ_MUST_USE bool emitJump(JSOp op, JumpList* jump);
@@ -553,17 +545,17 @@ struct MOZ_STACK_CLASS BytecodeEmitter
 
     MOZ_MUST_USE bool emitGoto(NestableControl* target, JumpList* jumplist,
                                SrcNoteType noteType = SRC_NULL);
 
     MOZ_MUST_USE bool emitIndex32(JSOp op, uint32_t index);
     MOZ_MUST_USE bool emitIndexOp(JSOp op, uint32_t index);
 
     MOZ_MUST_USE bool emitAtomOp(JSAtom* atom, JSOp op);
-    MOZ_MUST_USE bool emitAtomOp(NameNode* nameNode, JSOp op);
+    MOZ_MUST_USE bool emitAtomOp(uint32_t atomIndex, JSOp op);
 
     MOZ_MUST_USE bool emitArrayLiteral(ListNode* array);
     MOZ_MUST_USE bool emitArray(ParseNode* arrayHead, uint32_t count);
 
     MOZ_MUST_USE bool emitInternedScopeOp(uint32_t index, JSOp op);
     MOZ_MUST_USE bool emitInternedObjectOp(uint32_t index, JSOp op);
     MOZ_MUST_USE bool emitObjectOp(ObjectBox* objbox, JSOp op);
     MOZ_MUST_USE bool emitObjectPairOp(ObjectBox* objbox1, ObjectBox* objbox2, JSOp op);
@@ -584,52 +576,21 @@ struct MOZ_STACK_CLASS BytecodeEmitter
     // instead be emitted using EmitVarOp. In special cases, when the caller
     // definitely knows that a given local slot is unaliased, this function may be
     // used as a non-asserting version of emitUint16Operand.
     MOZ_MUST_USE bool emitLocalOp(JSOp op, uint32_t slot);
 
     MOZ_MUST_USE bool emitArgOp(JSOp op, uint16_t slot);
     MOZ_MUST_USE bool emitEnvCoordOp(JSOp op, EnvironmentCoordinate ec);
 
-    MOZ_MUST_USE bool emitGetNameAtLocation(JSAtom* name, const NameLocation& loc,
-                                            bool callContext = false);
-    MOZ_MUST_USE bool emitGetNameAtLocationForCompoundAssignment(JSAtom* name,
-                                                                 const NameLocation& loc);
-    MOZ_MUST_USE bool emitGetName(JSAtom* name, bool callContext = false) {
-        return emitGetNameAtLocation(name, lookupName(name), callContext);
-    }
-    MOZ_MUST_USE bool emitGetName(ParseNode* pn, bool callContext = false);
-
-    template <typename RHSEmitter>
-    MOZ_MUST_USE bool emitSetOrInitializeNameAtLocation(HandleAtom name, const NameLocation& loc,
-                                                        RHSEmitter emitRhs, bool initialize);
-    template <typename RHSEmitter>
-    MOZ_MUST_USE bool emitSetOrInitializeName(HandleAtom name, RHSEmitter emitRhs,
-                                              bool initialize)
-    {
-        return emitSetOrInitializeNameAtLocation(name, lookupName(name), emitRhs, initialize);
+    MOZ_MUST_USE bool emitGetNameAtLocation(JSAtom* name, const NameLocation& loc);
+    MOZ_MUST_USE bool emitGetName(JSAtom* name) {
+        return emitGetNameAtLocation(name, lookupName(name));
     }
-    template <typename RHSEmitter>
-    MOZ_MUST_USE bool emitSetName(ParseNode* pn, RHSEmitter emitRhs) {
-        RootedAtom name(cx, pn->name());
-        return emitSetName(name, emitRhs);
-    }
-    template <typename RHSEmitter>
-    MOZ_MUST_USE bool emitSetName(HandleAtom name, RHSEmitter emitRhs) {
-        return emitSetOrInitializeName(name, emitRhs, false);
-    }
-    template <typename RHSEmitter>
-    MOZ_MUST_USE bool emitInitializeName(ParseNode* pn, RHSEmitter emitRhs) {
-        RootedAtom name(cx, pn->name());
-        return emitInitializeName(name, emitRhs);
-    }
-    template <typename RHSEmitter>
-    MOZ_MUST_USE bool emitInitializeName(HandleAtom name, RHSEmitter emitRhs) {
-        return emitSetOrInitializeName(name, emitRhs, true);
-    }
+    MOZ_MUST_USE bool emitGetName(ParseNode* pn);
 
     MOZ_MUST_USE bool emitTDZCheckIfNeeded(JSAtom* name, const NameLocation& loc);
 
     MOZ_MUST_USE bool emitNameIncDec(UnaryNode* incDec);
 
     MOZ_MUST_USE bool emitDeclarationList(ListNode* declList);
     MOZ_MUST_USE bool emitSingleDeclaration(ParseNode* declList, ParseNode* decl,
                                             ParseNode* initializer);
@@ -652,31 +613,31 @@ struct MOZ_STACK_CLASS BytecodeEmitter
     MOZ_MUST_USE bool emitYieldStar(ParseNode* iter);
     MOZ_MUST_USE bool emitAwaitInInnermostScope() {
         return emitAwaitInScope(*innermostEmitterScope());
     }
     MOZ_MUST_USE bool emitAwaitInInnermostScope(UnaryNode* awaitNode);
     MOZ_MUST_USE bool emitAwaitInScope(EmitterScope& currentScope);
 
     MOZ_MUST_USE bool emitPropLHS(PropertyAccess* prop);
-    MOZ_MUST_USE bool emitPropOp(PropertyAccess* prop, JSOp op);
     MOZ_MUST_USE bool emitPropIncDec(UnaryNode* incDec);
 
     MOZ_MUST_USE bool emitAsyncWrapperLambda(unsigned index, bool isArrow);
     MOZ_MUST_USE bool emitAsyncWrapper(unsigned index, bool needsHomeObject, bool isArrow,
                                        bool isGenerator);
 
     MOZ_MUST_USE bool emitComputedPropertyName(UnaryNode* computedPropName);
 
     // Emit bytecode to put operands for a JSOP_GETELEM/CALLELEM/SETELEM/DELELEM
     // opcode onto the stack in the right order. In the case of SETELEM, the
     // value to be assigned must already be pushed.
     enum class EmitElemOption { Get, Call, IncDec, CompoundAssign, Ref };
     MOZ_MUST_USE bool emitElemOperands(PropertyByValue* elem, EmitElemOption opts);
 
+    MOZ_MUST_USE bool emitElemObjAndKey(PropertyByValue* elem, bool isSuper, ElemOpEmitter& eoe);
     MOZ_MUST_USE bool emitElemOpBase(JSOp op);
     MOZ_MUST_USE bool emitElemOp(PropertyByValue* elem, JSOp op);
     MOZ_MUST_USE bool emitElemIncDec(UnaryNode* incDec);
 
     MOZ_MUST_USE bool emitCatch(BinaryNode* catchClause);
     MOZ_MUST_USE bool emitIf(TernaryNode* ifNode);
     MOZ_MUST_USE bool emitWith(BinaryNode* withNode);
 
@@ -769,17 +730,17 @@ struct MOZ_STACK_CLASS BytecodeEmitter
     MOZ_MUST_USE bool emitDefault(ParseNode* defaultExpr, ParseNode* pattern);
 
     MOZ_MUST_USE bool setOrEmitSetFunName(ParseNode* maybeFun, HandleAtom name);
 
     MOZ_MUST_USE bool emitInitializer(ParseNode* initializer, ParseNode* pattern);
 
     MOZ_MUST_USE bool emitCallSiteObject(CallSiteNode* callSiteObj);
     MOZ_MUST_USE bool emitTemplateString(ListNode* templateString);
-    MOZ_MUST_USE bool emitAssignment(ParseNode* lhs, ParseNodeKind pnk, ParseNode* rhs);
+    MOZ_MUST_USE bool emitAssignment(ParseNode* lhs, JSOp compoundOp, ParseNode* rhs);
 
     MOZ_MUST_USE bool emitReturn(UnaryNode* returnNode);
     MOZ_MUST_USE bool emitExpressionStatement(UnaryNode* exprStmt);
     MOZ_MUST_USE bool emitStatementList(ListNode* stmtList);
 
     MOZ_MUST_USE bool emitDeleteName(UnaryNode* deleteNode);
     MOZ_MUST_USE bool emitDeleteProperty(UnaryNode* deleteNode);
     MOZ_MUST_USE bool emitDeleteElement(UnaryNode* deleteNode);
@@ -797,17 +758,18 @@ struct MOZ_STACK_CLASS BytecodeEmitter
 
     MOZ_NEVER_INLINE MOZ_MUST_USE bool emitIncOrDec(UnaryNode* incDec);
 
     MOZ_MUST_USE bool emitConditionalExpression(ConditionalExpression& conditional,
                                                 ValueUsage valueUsage = ValueUsage::WantValue);
 
     bool isRestParameter(ParseNode* pn);
 
-    MOZ_MUST_USE bool emitArguments(ListNode* argsList, bool callop, bool spread);
+    MOZ_MUST_USE bool emitArguments(ListNode* argsList, bool isCall, bool isSpread,
+                                    CallOrNewEmitter& cone);
     MOZ_MUST_USE bool emitCallOrNew(BinaryNode* callNode,
                                     ValueUsage valueUsage = ValueUsage::WantValue);
     MOZ_MUST_USE bool emitSelfHostedCallFunction(BinaryNode* callNode);
     MOZ_MUST_USE bool emitSelfHostedResumeGenerator(BinaryNode* callNode);
     MOZ_MUST_USE bool emitSelfHostedForceInterpreter();
     MOZ_MUST_USE bool emitSelfHostedAllowContentIter(BinaryNode* callNode);
     MOZ_MUST_USE bool emitSelfHostedDefineDataProperty(BinaryNode* callNode);
     MOZ_MUST_USE bool emitSelfHostedGetPropertySuper(BinaryNode* callNode);
@@ -839,23 +801,22 @@ struct MOZ_STACK_CLASS BytecodeEmitter
     // and the iterator to be on the stack in that order (iterator on the bottom).
     // It will pop the iterator and I, then iterate over the iterator by calling
     // |.next()| and put the results into the I-th element of array with
     // incrementing I, then push the result I (it will be original I +
     // iteration count). The stack after iteration will look like |ARRAY INDEX|.
     MOZ_MUST_USE bool emitSpread(bool allowSelfHosted = false);
 
     MOZ_MUST_USE bool emitClass(ClassNode* classNode);
-    MOZ_MUST_USE bool emitSuperPropLHS(UnaryNode* superBase, bool isCall = false);
-    MOZ_MUST_USE bool emitSuperGetProp(PropertyAccess* prop, bool isCall = false);
     MOZ_MUST_USE bool emitSuperElemOperands(PropertyByValue* elem,
                                             EmitElemOption opts = EmitElemOption::Get);
     MOZ_MUST_USE bool emitSuperGetElem(PropertyByValue* elem, bool isCall = false);
 
-    MOZ_MUST_USE bool emitCallee(ParseNode* callee, ParseNode* call, bool* callop);
+    MOZ_MUST_USE bool emitCalleeAndThis(ParseNode* callee, ParseNode* call,
+                                        CallOrNewEmitter& cone);
 
     MOZ_MUST_USE bool emitPipeline(ListNode* node);
 
     MOZ_MUST_USE bool emitExportDefault(BinaryNode* exportNode);
 };
 
 class MOZ_RAII AutoCheckUnstableEmitterScope {
 #ifdef DEBUG
new file mode 100644
--- /dev/null
+++ b/js/src/frontend/CallOrNewEmitter.cpp
@@ -0,0 +1,319 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * vim: set ts=8 sts=4 et sw=4 tw=99:
+ * 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 "frontend/CallOrNewEmitter.h"
+
+#include "frontend/BytecodeEmitter.h"
+#include "frontend/NameOpEmitter.h"
+#include "frontend/SharedContext.h"
+#include "vm/Opcodes.h"
+#include "vm/StringType.h"
+
+using namespace js;
+using namespace js::frontend;
+
+using mozilla::Maybe;
+
+AutoEmittingRunOnceLambda::AutoEmittingRunOnceLambda(BytecodeEmitter* bce)
+  : bce_(bce)
+{
+    MOZ_ASSERT(!bce_->emittingRunOnceLambda);
+    bce_->emittingRunOnceLambda = true;
+}
+
+AutoEmittingRunOnceLambda::~AutoEmittingRunOnceLambda()
+{
+    bce_->emittingRunOnceLambda = false;
+}
+
+CallOrNewEmitter::CallOrNewEmitter(BytecodeEmitter* bce, JSOp op,
+                                   ArgumentsKind argumentsKind,
+                                   ValueUsage valueUsage)
+  : bce_(bce),
+    op_(op),
+    argumentsKind_(argumentsKind)
+{
+    if (op_ == JSOP_CALL && valueUsage == ValueUsage::IgnoreValue) {
+        op_ = JSOP_CALL_IGNORES_RV;
+    }
+
+    MOZ_ASSERT(isCall() || isNew() || isSuperCall());
+}
+
+bool
+CallOrNewEmitter::emitNameCallee(JSAtom* name)
+{
+    MOZ_ASSERT(state_ == State::Start);
+
+    NameOpEmitter noe(bce_, name,
+                      isCall()
+                      ? NameOpEmitter::Kind::Call
+                      : NameOpEmitter::Kind::Get);
+    if (!noe.emitGet()) {                             // CALLEE THIS
+        return false;
+    }
+
+    state_ = State::NameCallee;
+    return true;
+}
+
+MOZ_MUST_USE PropOpEmitter&
+CallOrNewEmitter::prepareForPropCallee(bool isSuperProp)
+{
+    MOZ_ASSERT(state_ == State::Start);
+
+    poe_.emplace(bce_,
+                 isCall()
+                 ? PropOpEmitter::Kind::Call
+                 : PropOpEmitter::Kind::Get,
+                 isSuperProp
+                 ? PropOpEmitter::ObjKind::Super
+                 : PropOpEmitter::ObjKind::Other);
+
+    state_ = State::PropCallee;
+    return *poe_;
+}
+
+MOZ_MUST_USE ElemOpEmitter&
+CallOrNewEmitter::prepareForElemCallee(bool isSuperElem)
+{
+    MOZ_ASSERT(state_ == State::Start);
+
+    eoe_.emplace(bce_,
+                 isCall()
+                 ? ElemOpEmitter::Kind::Call
+                 : ElemOpEmitter::Kind::Get,
+                 isSuperElem
+                 ? ElemOpEmitter::ObjKind::Super
+                 : ElemOpEmitter::ObjKind::Other);
+
+    state_ = State::ElemCallee;
+    return *eoe_;
+}
+
+bool
+CallOrNewEmitter::prepareForFunctionCallee()
+{
+    MOZ_ASSERT(state_ == State::Start);
+
+    // Top level lambdas which are immediately invoked should be treated as
+    // only running once. Every time they execute we will create new types and
+    // scripts for their contents, to increase the quality of type information
+    // within them and enable more backend optimizations. Note that this does
+    // not depend on the lambda being invoked at most once (it may be named or
+    // be accessed via foo.caller indirection), as multiple executions will
+    // just cause the inner scripts to be repeatedly cloned.
+    MOZ_ASSERT(!bce_->emittingRunOnceLambda);
+    if (bce_->checkRunOnceContext()) {
+        autoEmittingRunOnceLambda_.emplace(bce_);
+    }
+
+    state_ = State::FunctionCallee;
+    return true;
+}
+
+bool
+CallOrNewEmitter::emitSuperCallee()
+{
+    MOZ_ASSERT(state_ == State::Start);
+
+    if (!bce_->emit1(JSOP_SUPERFUN)) {                // CALLEE
+        return false;
+    }
+    if (!bce_->emit1(JSOP_IS_CONSTRUCTING)) {         // CALLEE THIS
+        return false;
+    }
+
+    state_ = State::SuperCallee;
+    return true;
+}
+
+bool
+CallOrNewEmitter::prepareForOtherCallee()
+{
+    MOZ_ASSERT(state_ == State::Start);
+
+    state_ = State::OtherCallee;
+    return true;
+}
+
+bool
+CallOrNewEmitter::emitThis()
+{
+    MOZ_ASSERT(state_ == State::NameCallee ||
+               state_ == State::PropCallee ||
+               state_ == State::ElemCallee ||
+               state_ == State::FunctionCallee ||
+               state_ == State::SuperCallee ||
+               state_ == State::OtherCallee);
+
+    bool needsThis = false;
+    switch (state_) {
+      case State::NameCallee:
+        if (!isCall()) {
+            needsThis = true;
+        }
+        break;
+      case State::PropCallee:
+        poe_.reset();
+        if (!isCall()) {
+            needsThis = true;
+        }
+        break;
+      case State::ElemCallee:
+        eoe_.reset();
+        if (!isCall()) {
+            needsThis = true;
+        }
+        break;
+      case State::FunctionCallee:
+        autoEmittingRunOnceLambda_.reset();
+        needsThis = true;
+        break;
+      case State::SuperCallee:
+        break;
+      case State::OtherCallee:
+        needsThis = true;
+        break;
+      default:;
+    }
+    if (needsThis) {
+        if (isNew() || isSuperCall()) {
+            if (!bce_->emit1(JSOP_IS_CONSTRUCTING)) { // CALLEE THIS
+                return false;
+            }
+        } else {
+            if (!bce_->emit1(JSOP_UNDEFINED)) {       // CALLEE THIS
+                return false;
+            }
+        }
+    }
+
+    state_ = State::This;
+    return true;
+}
+
+// Used by BytecodeEmitter::emitPipeline to reuse CallOrNewEmitter instance
+// across multiple chained calls.
+void
+CallOrNewEmitter::reset()
+{
+    MOZ_ASSERT(state_ == State::End);
+    state_ = State::Start;
+}
+
+bool
+CallOrNewEmitter::prepareForNonSpreadArguments()
+{
+    MOZ_ASSERT(state_ == State::This);
+    MOZ_ASSERT(!isSpread());
+
+    state_ = State::Arguments;
+    return true;
+}
+
+// See the usage in the comment at the top of the class.
+bool
+CallOrNewEmitter::wantSpreadOperand()
+{
+    MOZ_ASSERT(state_ == State::This);
+    MOZ_ASSERT(isSpread());
+
+    state_ = State::WantSpreadOperand;
+    return isSingleSpreadRest();
+}
+
+bool
+CallOrNewEmitter::emitSpreadArgumentsTest()
+{
+    // Caller should check wantSpreadOperand before this.
+    MOZ_ASSERT(state_ == State::WantSpreadOperand);
+    MOZ_ASSERT(isSpread());
+
+    if (isSingleSpreadRest()) {
+        // Emit a preparation code to optimize the spread call with a rest
+        // parameter:
+        //
+        //   function f(...args) {
+        //     g(...args);
+        //   }
+        //
+        // If the spread operand is a rest parameter and it's optimizable
+        // array, skip spread operation and pass it directly to spread call
+        // operation.  See the comment in OptimizeSpreadCall in
+        // Interpreter.cpp for the optimizable conditons.
+
+        ifNotOptimizable_.emplace(bce_);
+        //                                            // CALLEE THIS ARG0
+        if (!bce_->emit1(JSOP_OPTIMIZE_SPREADCALL)) { // CALLEE THIS ARG0 OPTIMIZED
+            return false;
+        }
+        if (!bce_->emit1(JSOP_NOT)) {                 // CALLEE THIS ARG0 !OPTIMIZED
+            return false;
+        }
+        if (!ifNotOptimizable_->emitThen()) {         // CALLEE THIS ARG0
+            return false;
+        }
+        if (!bce_->emit1(JSOP_POP)) {                 // CALLEE THIS
+            return false;
+        }
+    }
+
+    state_ = State::Arguments;
+    return true;
+}
+
+bool
+CallOrNewEmitter::emitEnd(uint32_t argc, const Maybe<uint32_t>& beginPos)
+{
+    MOZ_ASSERT(state_ == State::Arguments);
+
+    if (isSingleSpreadRest()) {
+        if (!ifNotOptimizable_->emitEnd()) {          // CALLEE THIS ARR
+            return false;
+        }
+
+        ifNotOptimizable_.reset();
+    }
+    if (isNew() || isSuperCall()) {
+        if (isSuperCall()) {
+            if (!bce_->emit1(JSOP_NEWTARGET)) {       // CALLEE THIS ARG.. NEW.TARGET
+                return false;
+            }
+        } else {
+            // Repush the callee as new.target
+            uint32_t effectiveArgc = isSpread() ? 1 : argc;
+            if (!bce_->emitDupAt(effectiveArgc + 1)) {
+                return false;                         // CALLEE THIS ARR CALLEE
+            }
+        }
+    }
+    if (!isSpread()) {
+        if (!bce_->emitCall(op_, argc, beginPos)) {   // RVAL
+            return false;
+        }
+    } else {
+        if (beginPos) {
+            if (!bce_->updateSourceCoordNotes(*beginPos)) {
+                return false;
+            }
+        }
+        if (!bce_->emit1(op_)) {                      // RVAL
+            return false;
+        }
+    }
+    bce_->checkTypeSet(op_);
+
+    if (isEval() && beginPos) {
+        uint32_t lineNum = bce_->parser->errorReporter().lineAt(*beginPos);
+        if (!bce_->emitUint32Operand(JSOP_LINENO, lineNum)) {
+            return false;
+        }
+    }
+
+    state_ = State::End;
+    return true;
+}
new file mode 100644
--- /dev/null
+++ b/js/src/frontend/CallOrNewEmitter.h
@@ -0,0 +1,338 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * vim: set ts=8 sts=4 et sw=4 tw=99:
+ * 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/. */
+
+#ifndef frontend_CallOrNewEmitter_h
+#define frontend_CallOrNewEmitter_h
+
+#include "mozilla/Attributes.h"
+#include "mozilla/Maybe.h"
+
+#include <stdint.h>
+
+#include "frontend/ElemOpEmitter.h"
+#include "frontend/IfEmitter.h"
+#include "frontend/PropOpEmitter.h"
+#include "frontend/ValueUsage.h"
+#include "js/TypeDecls.h"
+#include "vm/BytecodeUtil.h"
+#include "vm/Opcodes.h"
+
+namespace js {
+namespace frontend {
+
+struct BytecodeEmitter;
+
+class MOZ_RAII AutoEmittingRunOnceLambda
+{
+    BytecodeEmitter* bce_;
+
+  public:
+    explicit AutoEmittingRunOnceLambda(BytecodeEmitter* bce);
+    ~AutoEmittingRunOnceLambda();
+};
+
+// Class for emitting bytecode for call or new expression.
+//
+// Usage: (check for the return value is omitted for simplicity)
+//
+//   `print(arg);`
+//     CallOrNewEmitter cone(this, JSOP_CALL,
+//                           CallOrNewEmitter::ArgumentsKind::Other,
+//                           ValueUsage::WantValue);
+//     cone.emitNameCallee();
+//     emit(print);
+//     cone.emitThis();
+//     cone.prepareForNonSpreadArguments();
+//     emit(arg);
+//     cone.emitEnd(1, Some(offset_of_callee));
+//
+//   `callee.prop(arg1, arg2);`
+//     CallOrNewEmitter cone(this, JSOP_CALL,
+//                           CallOrNewEmitter::ArgumentsKind::Other,
+//                           ValueUsage::WantValue);
+//     PropOpEmitter& poe = cone.prepareForPropCallee(false);
+//     ... emit `callee.prop` with `poe` here...
+//     cone.emitThis();
+//     cone.prepareForNonSpreadArguments();
+//     emit(arg1);
+//     emit(arg2);
+//     cone.emitEnd(2, Some(offset_of_callee));
+//
+//   `callee[key](arg);`
+//     CallOrNewEmitter cone(this, JSOP_CALL,
+//                           CallOrNewEmitter::ArgumentsKind::Other,
+//                           ValueUsage::WantValue);
+//     ElemOpEmitter& eoe = cone.prepareForElemCallee(false);
+//     ... emit `callee[key]` with `eoe` here...
+//     cone.emitThis();
+//     cone.prepareForNonSpreadArguments();
+//     emit(arg);
+//     cone.emitEnd(1, Some(offset_of_callee));
+//
+//   `(function() { ... })(arg);`
+//     CallOrNewEmitter cone(this, JSOP_CALL,
+//                           CallOrNewEmitter::ArgumentsKind::Other,
+//                           ValueUsage::WantValue);
+//     cone.prepareForFunctionCallee();
+//     emit(function);
+//     cone.emitThis();
+//     cone.prepareForNonSpreadArguments();
+//     emit(arg);
+//     cone.emitEnd(1, Some(offset_of_callee));
+//
+//   `super(arg);`
+//     CallOrNewEmitter cone(this, JSOP_CALL,
+//                           CallOrNewEmitter::ArgumentsKind::Other,
+//                           ValueUsage::WantValue);
+//     cone.emitSuperCallee();
+//     cone.emitThis();
+//     cone.prepareForNonSpreadArguments();
+//     emit(arg);
+//     cone.emitEnd(1, Some(offset_of_callee));
+//
+//   `(some_other_expression)(arg);`
+//     CallOrNewEmitter cone(this, JSOP_CALL,
+//                           CallOrNewEmitter::ArgumentsKind::Other,
+//                           ValueUsage::WantValue);
+//     cone.prepareForOtherCallee();
+//     emit(some_other_expression);
+//     cone.emitThis();
+//     cone.prepareForNonSpreadArguments();
+//     emit(arg);
+//     cone.emitEnd(1, Some(offset_of_callee));
+//
+//   `print(...arg);`
+//     CallOrNewEmitter cone(this, JSOP_SPREADCALL,
+//                           CallOrNewEmitter::ArgumentsKind::Other,
+//                           ValueUsage::WantValue);
+//     cone.emitNameCallee();
+//     emit(print);
+//     cone.emitThis();
+//     if (cone.wantSpreadOperand())
+//       emit(arg)
+//     cone.emitSpreadArgumentsTest();
+//     emit([...arg]);
+//     cone.emitEnd(1, Some(offset_of_callee));
+//
+//   `print(...rest);`
+//   where `rest` is rest parameter
+//     CallOrNewEmitter cone(this, JSOP_SPREADCALL,
+//                           CallOrNewEmitter::ArgumentsKind::SingleSpreadRest,
+//                           ValueUsage::WantValue);
+//     cone.emitNameCallee();
+//     emit(print);
+//     cone.emitThis();
+//     if (cone.wantSpreadOperand())
+//       emit(arg)
+//     cone.emitSpreadArgumentsTest();
+//     emit([...arg]);
+//     cone.emitEnd(1, Some(offset_of_callee));
+//
+//   `new f(arg);`
+//     CallOrNewEmitter cone(this, JSOP_NEW,
+//                           CallOrNewEmitter::ArgumentsKind::Other,
+//                           ValueUsage::WantValue);
+//     cone.emitNameCallee();
+//     emit(f);
+//     cone.emitThis();
+//     cone.prepareForNonSpreadArguments();
+//     emit(arg);
+//     cone.emitEnd(1, Some(offset_of_callee));
+//
+class MOZ_STACK_CLASS CallOrNewEmitter
+{
+  public:
+    enum class ArgumentsKind {
+        Other,
+
+        // Specify this for the following case:
+        //
+        //   function f(...rest) {
+        //     g(...rest);
+        //   }
+        //
+        // This enables optimization to avoid allocating an intermediate array
+        // for spread operation.
+        //
+        // wantSpreadOperand() returns true when this is specified.
+        SingleSpreadRest
+    };
+
+  private:
+    BytecodeEmitter* bce_;
+
+    // The opcode for the call or new.
+    JSOp op_;
+
+    // Whether the call is a spread call with single rest parameter or not.
+    // See the comment in emitSpreadArgumentsTest for more details.
+    ArgumentsKind argumentsKind_;
+
+    // The branch for spread call optimization.
+    mozilla::Maybe<InternalIfEmitter> ifNotOptimizable_;
+
+    mozilla::Maybe<AutoEmittingRunOnceLambda> autoEmittingRunOnceLambda_;
+
+    mozilla::Maybe<PropOpEmitter> poe_;
+    mozilla::Maybe<ElemOpEmitter> eoe_;
+
+    // The state of this emitter.
+    //
+    // +-------+   emitNameCallee           +------------+
+    // | Start |-+------------------------->| NameCallee |------+
+    // +-------+ |                          +------------+      |
+    //           |                                              |
+    //           | prepareForPropCallee     +------------+      v
+    //           +------------------------->| PropCallee |----->+
+    //           |                          +------------+      |
+    //           |                                              |
+    //           | prepareForElemCallee     +------------+      v
+    //           +------------------------->| ElemCallee |----->+
+    //           |                          +------------+      |
+    //           |                                              |
+    //           | prepareForFunctionCallee +----------------+  v
+    //           +------------------------->| FunctionCallee |->+
+    //           |                          +----------------+  |
+    //           |                                              |
+    //           | emitSuperCallee          +-------------+     v
+    //           +------------------------->| SuperCallee |---->+
+    //           |                          +-------------+     |
+    //           |                                              |
+    //           | prepareForOtherCallee    +-------------+     v
+    //           +------------------------->| OtherCallee |---->+
+    //                                      +-------------+     |
+    //                                                          |
+    // +--------------------------------------------------------+
+    // |
+    // | emitThis +------+
+    // +--------->| This |-+
+    //            +------+ |
+    //                     |
+    // +-------------------+
+    // |
+    // | [!isSpread]
+    // |   prepareForNonSpreadArguments    +-----------+ emitEnd +-----+
+    // +------------------------------->+->| Arguments |-------->| End |
+    // |                                ^  +-----------+         +-----+
+    // |                                |
+    // |                                +----------------------------------+
+    // |                                                                   |
+    // | [isSpread]                                                        |
+    // |   wantSpreadOperand +-------------------+ emitSpreadArgumentsTest |
+    // +-------------------->| WantSpreadOperand |-------------------------+
+    //                       +-------------------+
+    enum class State {
+        // The initial state.
+        Start,
+
+        // After calling emitNameCallee.
+        NameCallee,
+
+        // After calling prepareForPropCallee.
+        PropCallee,
+
+        // After calling prepareForElemCallee.
+        ElemCallee,
+
+        // After calling prepareForFunctionCallee.
+        FunctionCallee,
+
+        // After calling emitSuperCallee.
+        SuperCallee,
+
+        // After calling prepareForOtherCallee.
+        OtherCallee,
+
+        // After calling emitThis.
+        This,
+
+        // After calling wantSpreadOperand.
+        WantSpreadOperand,
+
+        // After calling prepareForNonSpreadArguments.
+        Arguments,
+
+        // After calling emitEnd.
+        End
+    };
+    State state_ = State::Start;
+
+  public:
+    CallOrNewEmitter(BytecodeEmitter* bce, JSOp op,
+                     ArgumentsKind argumentsKind,
+                     ValueUsage valueUsage);
+
+  private:
+    MOZ_MUST_USE bool isCall() const {
+        return op_ == JSOP_CALL || op_ == JSOP_CALL_IGNORES_RV ||
+               op_ == JSOP_SPREADCALL ||
+               isEval() || isFunApply() || isFunCall();
+    }
+
+    MOZ_MUST_USE bool isNew() const {
+        return op_ == JSOP_NEW || op_ == JSOP_SPREADNEW;
+    }
+
+    MOZ_MUST_USE bool isSuperCall() const {
+        return op_ == JSOP_SUPERCALL || op_ == JSOP_SPREADSUPERCALL;
+    }
+
+    MOZ_MUST_USE bool isEval() const {
+        return op_ == JSOP_EVAL || op_ == JSOP_STRICTEVAL ||
+               op_ == JSOP_SPREADEVAL || op_ == JSOP_STRICTSPREADEVAL;
+    }
+
+    MOZ_MUST_USE bool isFunApply() const {
+        return op_ == JSOP_FUNAPPLY;
+    }
+
+    MOZ_MUST_USE bool isFunCall() const {
+        return op_ == JSOP_FUNCALL;
+    }
+
+    MOZ_MUST_USE bool isSpread() const {
+        return JOF_OPTYPE(op_) == JOF_BYTE;
+    }
+
+    MOZ_MUST_USE bool isSingleSpreadRest() const {
+        return argumentsKind_ == ArgumentsKind::SingleSpreadRest;
+    }
+
+  public:
+    MOZ_MUST_USE bool emitNameCallee(JSAtom* name);
+    MOZ_MUST_USE PropOpEmitter& prepareForPropCallee(bool isSuperProp);
+    MOZ_MUST_USE ElemOpEmitter& prepareForElemCallee(bool isSuperElem);
+    MOZ_MUST_USE bool prepareForFunctionCallee();
+    MOZ_MUST_USE bool emitSuperCallee();
+    MOZ_MUST_USE bool prepareForOtherCallee();
+
+    MOZ_MUST_USE bool emitThis();
+
+    // Used by BytecodeEmitter::emitPipeline to reuse CallOrNewEmitter instance
+    // across multiple chained calls.
+    void reset();
+
+    MOZ_MUST_USE bool prepareForNonSpreadArguments();
+
+    // See the usage in the comment at the top of the class.
+    MOZ_MUST_USE bool wantSpreadOperand();
+    MOZ_MUST_USE bool emitSpreadArgumentsTest();
+
+    // Parameters are the offset in the source code for each character below:
+    //
+    //   callee(arg);
+    //   ^
+    //   |
+    //   beginPos
+    //
+    // Can be Nothing() if not available.
+    MOZ_MUST_USE bool emitEnd(uint32_t argc, const mozilla::Maybe<uint32_t>& beginPos);
+};
+
+} /* namespace frontend */
+} /* namespace js */
+
+#endif /* frontend_CallOrNewEmitter_h */
new file mode 100644
--- /dev/null
+++ b/js/src/frontend/ElemOpEmitter.cpp
@@ -0,0 +1,288 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * vim: set ts=8 sts=4 et sw=4 tw=99:
+ * 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 "frontend/ElemOpEmitter.h"
+
+#include "frontend/BytecodeEmitter.h"
+#include "frontend/SharedContext.h"
+#include "vm/Opcodes.h"
+
+using namespace js;
+using namespace js::frontend;
+
+ElemOpEmitter::ElemOpEmitter(BytecodeEmitter* bce, Kind kind, ObjKind objKind)
+  : bce_(bce),
+    kind_(kind),
+    objKind_(objKind)
+{}
+
+bool
+ElemOpEmitter::prepareForObj()
+{
+    MOZ_ASSERT(state_ == State::Start);
+
+#ifdef DEBUG
+    state_ = State::Obj;
+#endif
+    return true;
+}
+
+bool
+ElemOpEmitter::prepareForKey()
+{
+    MOZ_ASSERT(state_ == State::Obj);
+
+    if (!isSuper() && isIncDec()) {
+        if (!bce_->emit1(JSOP_CHECKOBJCOERCIBLE)) {   // OBJ
+            return false;
+        }
+    }
+    if (isCall()) {
+        if (!bce_->emit1(JSOP_DUP)) {                 // [Super]
+            //                                        // THIS THIS
+            //                                        // [Other]
+            //                                        // OBJ OBJ
+            return false;
+        }
+    }
+
+#ifdef DEBUG
+    state_ = State::Key;
+#endif
+    return true;
+}
+
+bool
+ElemOpEmitter::emitGet()
+{
+    MOZ_ASSERT(state_ == State::Key);
+
+    if (isIncDec() || isCompoundAssignment()) {
+        if (!bce_->emit1(JSOP_TOID)) {                // [Super]
+            //                                        // THIS KEY
+            //                                        // [Other]
+            //                                        // OBJ KEY
+            return false;
+        }
+    }
+    if (isSuper()) {
+        if (!bce_->emit1(JSOP_SUPERBASE)) {           // THIS? THIS KEY SUPERBASE
+            return false;
+        }
+    }
+    if (isIncDec() || isCompoundAssignment()) {
+        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 (!bce_->emitDupAt(2)) {                // THIS KEY SUPERBASE THIS
+                return false;
+            }
+            if (!bce_->emitDupAt(2)) {                // THIS KEY SUPERBASE THIS KEY
+                return false;
+            }
+            if (!bce_->emitDupAt(2)) {                // THIS KEY SUPERBASE THIS KEY SUPERBASE
+                return false;
+            }
+        } else {
+            if (!bce_->emit1(JSOP_DUP2)) {            // OBJ KEY OBJ KEY
+                return false;
+            }
+        }
+    }
+
+    JSOp op;
+    if (isSuper()) {
+        op = JSOP_GETELEM_SUPER;
+    } else if (isCall()) {
+        op = JSOP_CALLELEM;
+    } else {
+        op = JSOP_GETELEM;
+    }
+    if (!bce_->emitElemOpBase(op)) {                  // [Get]
+        //                                            // ELEM
+        //                                            // [Call]
+        //                                            // THIS ELEM
+        //                                            // [Inc/Dec/Assignment,
+        //                                            //  Super]
+        //                                            // THIS KEY SUPERBASE ELEM
+        //                                            // [Inc/Dec/Assignment,
+        //                                            //  Other]
+        //                                            // OBJ KEY ELEM
+        return false;
+    }
+    if (isCall()) {
+        if (!bce_->emit1(JSOP_SWAP)) {                // ELEM THIS
+            return false;
+        }
+    }
+
+#ifdef DEBUG
+    state_ = State::Get;
+#endif
+    return true;
+}
+
+bool
+ElemOpEmitter::prepareForRhs()
+{
+    MOZ_ASSERT(isSimpleAssignment() || isCompoundAssignment());
+    MOZ_ASSERT_IF(isSimpleAssignment(), state_ == State::Key);
+    MOZ_ASSERT_IF(isCompoundAssignment(), state_ == State::Get);
+
+    if (isSimpleAssignment()) {
+        // For CompoundAssignment, SUPERBASE is already emitted by emitGet.
+        if (isSuper()) {
+            if (!bce_->emit1(JSOP_SUPERBASE)) {           // THIS KEY SUPERBASE
+                return false;
+            }
+        }
+    }
+
+#ifdef DEBUG
+    state_ = State::Rhs;
+#endif
+    return true;
+}
+
+bool
+ElemOpEmitter::skipObjAndKeyAndRhs()
+{
+    MOZ_ASSERT(state_ == State::Start);
+    MOZ_ASSERT(isSimpleAssignment());
+
+#ifdef DEBUG
+    state_ = State::Rhs;
+#endif
+    return true;
+}
+
+bool
+ElemOpEmitter::emitDelete()
+{
+    MOZ_ASSERT(state_ == State::Key);
+    MOZ_ASSERT(isDelete());
+
+    if (isSuper()) {
+        if (!bce_->emit1(JSOP_TOID)) {                // THIS KEY
+            return false;
+        }
+        if (!bce_->emit1(JSOP_SUPERBASE)) {           // THIS KEY SUPERBASE
+            return false;
+        }
+
+        // Unconditionally throw when attempting to delete a super-reference.
+        if (!bce_->emitUint16Operand(JSOP_THROWMSG, JSMSG_CANT_DELETE_SUPER)) {
+            return false;                             // THIS KEY SUPERBASE
+        }
+
+        // Another wrinkle: Balance the stack from the emitter's point of view.
+        // Execution will not reach here, as the last bytecode threw.
+        if (!bce_->emitPopN(2)) {                     // THIS
+            return false;
+        }
+    } else {
+        JSOp op = bce_->sc->strict() ? JSOP_STRICTDELELEM : JSOP_DELELEM;
+        if (!bce_->emitElemOpBase(op)){              // SUCCEEDED
+            return false;
+        }
+    }
+
+#ifdef DEBUG
+    state_ = State::Delete;
+#endif
+    return true;
+}
+
+bool
+ElemOpEmitter::emitAssignment()
+{
+    MOZ_ASSERT(isSimpleAssignment() || isCompoundAssignment());
+    MOZ_ASSERT(state_ == State::Rhs);
+
+    JSOp setOp = isSuper()
+                 ? bce_->sc->strict() ? JSOP_STRICTSETELEM_SUPER : JSOP_SETELEM_SUPER
+                 : bce_->sc->strict() ? JSOP_STRICTSETELEM : JSOP_SETELEM;
+    if (!bce_->emitElemOpBase(setOp)) {               // ELEM
+        return false;
+    }
+
+#ifdef DEBUG
+    state_ = State::Assignment;
+#endif
+    return true;
+}
+
+bool
+ElemOpEmitter::emitIncDec()
+{
+    MOZ_ASSERT(state_ == State::Key);
+    MOZ_ASSERT(isIncDec());
+
+    if (!emitGet()) {                                 // ... ELEM
+        return false;
+    }
+
+    MOZ_ASSERT(state_ == State::Get);
+
+    JSOp binOp = isInc() ? JSOP_ADD : JSOP_SUB;
+    if (!bce_->emit1(JSOP_POS)) {                     // ... N
+        return false;
+    }
+    if (isPostIncDec()) {
+        if (!bce_->emit1(JSOP_DUP)) {                 // ... N? N
+            return false;
+        }
+    }
+    if (!bce_->emit1(JSOP_ONE)) {                     // ... N? N 1
+        return false;
+    }
+    if (!bce_->emit1(binOp)) {                        // ... N? N+1
+        return false;
+    }
+    if (isPostIncDec()) {
+        if (isSuper()) {                              // THIS KEY OBJ N N+1
+            if (!bce_->emit2(JSOP_PICK, 4)) {         // KEY SUPERBASE N N+1 THIS
+                return false;
+            }
+            if (!bce_->emit2(JSOP_PICK, 4)) {         // SUPERBASE N N+1 THIS KEY
+                return false;
+            }
+            if (!bce_->emit2(JSOP_PICK, 4)) {         // N N+1 THIS KEY SUPERBASE
+                return false;
+            }
+            if (!bce_->emit2(JSOP_PICK, 3)) {         // N THIS KEY SUPERBASE N+1
+                return false;
+            }
+        } else {                                      // OBJ KEY N N+1
+            if (!bce_->emit2(JSOP_PICK, 3)) {         // KEY N N+1 OBJ
+                return false;
+            }
+            if (!bce_->emit2(JSOP_PICK, 3)) {         // N N+1 OBJ KEY
+                return false;
+            }
+            if (!bce_->emit2(JSOP_PICK, 2)) {         // N OBJ KEY N+1
+                return false;
+            }
+        }
+    }
+
+    JSOp setOp = isSuper()
+                 ? (bce_->sc->strict() ? JSOP_STRICTSETELEM_SUPER : JSOP_SETELEM_SUPER)
+                 : (bce_->sc->strict() ? JSOP_STRICTSETELEM : JSOP_SETELEM);
+    if (!bce_->emitElemOpBase(setOp)) {               // N? N+1
+        return false;
+    }
+    if (isPostIncDec()) {
+        if (!bce_->emit1(JSOP_POP)) {                 // N
+            return false;
+        }
+    }
+
+#ifdef DEBUG
+    state_ = State::IncDec;
+#endif
+    return true;
+}
new file mode 100644
--- /dev/null
+++ b/js/src/frontend/ElemOpEmitter.h
@@ -0,0 +1,278 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * vim: set ts=8 sts=4 et sw=4 tw=99:
+ * 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/. */
+
+#ifndef frontend_ElemOpEmitter_h
+#define frontend_ElemOpEmitter_h
+
+#include "mozilla/Attributes.h"
+
+namespace js {
+namespace frontend {
+
+struct BytecodeEmitter;
+
+// Class for emitting bytecode for element operation.
+//
+// Usage: (check for the return value is omitted for simplicity)
+//
+//   `obj[key];`
+//     ElemOpEmitter eoe(this,
+//                       ElemOpEmitter::Kind::Get,
+//                       ElemOpEmitter::ObjKind::Other);
+//     eoe.prepareForObj();
+//     emit(obj);
+//     eoe.prepareForKey();
+//     emit(key);
+//     eoe.emitGet();
+//
+//   `super[key];`
+//     ElemOpEmitter eoe(this,
+//                       ElemOpEmitter::Kind::Get,
+//                       ElemOpEmitter::ObjKind::Super);
+//     eoe.prepareForObj();
+//     emit(this_for_super);
+//     eoe.prepareForKey();
+//     emit(key);
+//     eoe.emitGet();
+//
+//   `obj[key]();`
+//     ElemOpEmitter eoe(this,
+//                       ElemOpEmitter::Kind::Call,
+//                       ElemOpEmitter::ObjKind::Other);
+//     eoe.prepareForObj();
+//     emit(obj);
+//     eoe.prepareForKey();
+//     emit(key);
+//     eoe.emitGet();
+//     emit_call_here();
+//
+//   `new obj[key]();`
+//     ElemOpEmitter eoe(this,
+//                       ElemOpEmitter::Kind::Call,
+//                       ElemOpEmitter::ObjKind::Other);
+//     eoe.prepareForObj();
+//     emit(obj);
+//     eoe.prepareForKey();
+//     emit(key);
+//     eoe.emitGet();
+//     emit_call_here();
+//
+//   `delete obj[key];`
+//     ElemOpEmitter eoe(this,
+//                       ElemOpEmitter::Kind::Delete,
+//                       ElemOpEmitter::ObjKind::Other);
+//     eoe.prepareForObj();
+//     emit(obj);
+//     eoe.prepareForKey();
+//     emit(key);
+//     eoe.emitDelete();
+//
+//   `delete super[key];`
+//     ElemOpEmitter eoe(this,
+//                       ElemOpEmitter::Kind::Delete,
+//                       ElemOpEmitter::ObjKind::Super);
+//     eoe.prepareForObj();
+//     emit(this_for_super);
+//     eoe.prepareForKey();
+//     emit(key);
+//     eoe.emitDelete();
+//
+//   `obj[key]++;`
+//     ElemOpEmitter eoe(this,
+//                       ElemOpEmitter::Kind::PostIncrement,
+//                       ElemOpEmitter::ObjKind::Other);
+//     eoe.prepareForObj();
+//     emit(obj);
+//     eoe.prepareForKey();
+//     emit(key);
+//     eoe.emitIncDec();
+//
+//   `obj[key] = value;`
+//     ElemOpEmitter eoe(this,
+//                       ElemOpEmitter::Kind::SimpleAssignment,
+//                       ElemOpEmitter::ObjKind::Other);
+//     eoe.prepareForObj();
+//     emit(obj);
+//     eoe.prepareForKey();
+//     emit(key);
+//     eoe.prepareForRhs();
+//     emit(value);
+//     eoe.emitAssignment();
+//
+//   `obj[key] += value;`
+//     ElemOpEmitter eoe(this,
+//                       ElemOpEmitter::Kind::CompoundAssignment,
+//                       ElemOpEmitter::ObjKind::Other);
+//     eoe.prepareForObj();
+//     emit(obj);
+//     eoe.prepareForKey();
+//     emit(key);
+//     eoe.emitGet();
+//     eoe.prepareForRhs();
+//     emit(value);
+//     emit_add_op_here();
+//     eoe.emitAssignment();
+//
+class MOZ_STACK_CLASS ElemOpEmitter
+{
+  public:
+    enum class Kind {
+        Get,
+        Call,
+        Set,
+        Delete,
+        PostIncrement,
+        PreIncrement,
+        PostDecrement,
+        PreDecrement,
+        SimpleAssignment,
+        CompoundAssignment
+    };
+    enum class ObjKind {
+        Super,
+        Other
+    };
+
+  private:
+    BytecodeEmitter* bce_;
+
+    Kind kind_;
+    ObjKind objKind_;
+
+#ifdef DEBUG
+    // The state of this emitter.
+    //
+    //             skipObjAndKeyAndRhs
+    //           +------------------------------------------------+
+    //           |                                                |
+    // +-------+ | prepareForObj +-----+ prepareForKey +-----+    |
+    // | Start |-+-------------->| Obj |-------------->| Key |-+  |
+    // +-------+                 +-----+               +-----+ |  |
+    //                                                         |  |
+    // +-------------------------------------------------------+  |
+    // |                                                          |
+    // |                                                          |
+    // |                                                          |
+    // | [Get]                                                    |
+    // | [Call]                                                   |
+    // |   emitGet +-----+                                        |
+    // +---------->| Get |                                        |
+    // |           +-----+                                        |
+    // |                                                          |
+    // | [Delete]                                                 |
+    // |   emitDelete +--------+                                  |
+    // +------------->| Delete |                                  |
+    // |              +--------+                                  |
+    // |                                                          |
+    // | [PostIncrement]                                          |
+    // | [PreIncrement]                                           |
+    // | [PostDecrement]                                          |
+    // | [PreDecrement]                                           |
+    // |   emitIncDec +--------+                                  |
+    // +------------->| IncDec |                                  |
+    // |              +--------+                                  |
+    // |                                      +-------------------+
+    // | [SimpleAssignment]                   |
+    // |                        prepareForRhs v  +-----+
+    // +--------------------->+-------------->+->| Rhs |-+
+    // |                      ^                  +-----+ |
+    // |                      |                          |
+    // |                      |            +-------------+
+    // | [CompoundAssignment] |            |
+    // |   emitGet +-----+    |            | emitAssignment +------------+
+    // +---------->| Get |----+            +--------------->| Assignment |
+    //             +-----+                                  +------------+
+    enum class State {
+        // The initial state.
+        Start,
+
+        // After calling prepareForObj.
+        Obj,
+
+        // After calling emitKey.
+        Key,
+
+        // After calling emitGet.
+        Get,
+
+        // After calling emitDelete.
+        Delete,
+
+        // After calling emitIncDec.
+        IncDec,
+
+        // After calling prepareForRhs or skipObjAndKeyAndRhs.
+        Rhs,
+
+        // After calling emitAssignment.
+        Assignment,
+    };
+    State state_ = State::Start;
+#endif
+
+  public:
+    ElemOpEmitter(BytecodeEmitter* bce, Kind kind, ObjKind objKind);
+
+  private:
+    MOZ_MUST_USE bool isCall() const {
+        return kind_ == Kind::Call;
+    }
+
+    MOZ_MUST_USE bool isSimpleAssignment() const {
+        return kind_ == Kind::SimpleAssignment;
+    }
+
+    MOZ_MUST_USE bool isDelete() const {
+        return kind_ == Kind::Delete;
+    }
+
+    MOZ_MUST_USE bool isCompoundAssignment() const {
+        return kind_ == Kind::CompoundAssignment;
+    }
+
+    MOZ_MUST_USE bool isIncDec() const {
+        return isPostIncDec() || isPreIncDec();
+    }
+
+    MOZ_MUST_USE bool isPostIncDec() const {
+        return kind_ == Kind::PostIncrement ||
+               kind_ == Kind::PostDecrement;
+    }
+
+    MOZ_MUST_USE bool isPreIncDec() const {
+        return kind_ == Kind::PreIncrement ||
+               kind_ == Kind::PreDecrement;
+    }
+
+    MOZ_MUST_USE bool isInc() const {
+        return kind_ == Kind::PostIncrement ||
+               kind_ == Kind::PreIncrement;
+    }
+
+    MOZ_MUST_USE bool isSuper() const {
+        return objKind_ == ObjKind::Super;
+    }
+
+  public:
+    MOZ_MUST_USE bool prepareForObj();
+    MOZ_MUST_USE bool prepareForKey();
+
+    MOZ_MUST_USE bool emitGet();
+
+    MOZ_MUST_USE bool prepareForRhs();
+    MOZ_MUST_USE bool skipObjAndKeyAndRhs();
+
+    MOZ_MUST_USE bool emitDelete();
+
+    MOZ_MUST_USE bool emitAssignment();
+
+    MOZ_MUST_USE bool emitIncDec();
+};
+
+} /*