Merge m-c to fx-team, a=merge
authorWes Kocher <wkocher@mozilla.com>
Mon, 28 Mar 2016 13:08:46 -0700
changeset 290715 f40c52c4b8005e98065133bf4baaa33b5ca62084
parent 290714 a66e1fb0320678f5d2acb2b8712a4133104d4b45 (current diff)
parent 290704 a66bf0a800f3d859b4bd2ceebc57b2e3fa6544d8 (diff)
child 290716 2572bf0929df26a694f58cc8765da73abea0f655
push id19656
push usergwagner@mozilla.com
push dateMon, 04 Apr 2016 13:43:23 +0000
treeherderb2g-inbound@e99061fde28a [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone48.0a1
Merge m-c to fx-team, a=merge MozReview-Commit-ID: 5KL6t5QkqNQ
dom/media/mediasource/SourceBufferContentManager.cpp
dom/media/mediasource/SourceBufferContentManager.h
testing/taskcluster/tasks/builds/legacy/b2g_aries_spark_dogfood.yml
testing/taskcluster/tasks/builds/legacy/b2g_aries_spark_noril_opt.yml
testing/taskcluster/tasks/builds/legacy/b2g_aries_spark_opt.yml
testing/taskcluster/tasks/builds/legacy/b2g_aries_spark_ota_balrog_debug.yml
testing/taskcluster/tasks/builds/legacy/b2g_aries_spark_ota_balrog_opt.yml
testing/taskcluster/tasks/builds/legacy/b2g_aries_spark_ota_base.yml
testing/taskcluster/tasks/builds/legacy/b2g_aries_spark_ota_debug.yml
testing/taskcluster/tasks/builds/legacy/b2g_aries_spark_ota_opt.yml
testing/taskcluster/tasks/builds/legacy/b2g_dolphin_512_eng.yml
testing/taskcluster/tasks/builds/legacy/b2g_dolphin_512_opt.yml
testing/taskcluster/tasks/builds/legacy/b2g_dolphin_base.yml
testing/taskcluster/tasks/builds/legacy/b2g_dolphin_eng.yml
testing/taskcluster/tasks/builds/legacy/b2g_dolphin_opt.yml
testing/taskcluster/tasks/builds/legacy/b2g_flame_kk_debug.yml
testing/taskcluster/tasks/builds/legacy/b2g_flame_kk_eng.yml
testing/taskcluster/tasks/builds/legacy/b2g_flame_kk_opt.yml
testing/taskcluster/tasks/builds/legacy/b2g_flame_kk_ota_base.yml
testing/taskcluster/tasks/builds/legacy/b2g_flame_kk_ota_debug.yml
testing/taskcluster/tasks/builds/legacy/b2g_flame_kk_ota_opt.yml
testing/taskcluster/tasks/builds/legacy/b2g_flame_kk_spark_eng.yml
testing/taskcluster/tasks/builds/legacy/b2g_hamachi_eng.yml
testing/taskcluster/tasks/builds/legacy/b2g_hamachi_user.yml
testing/taskcluster/tasks/builds/legacy/b2g_helix_user.yml
testing/taskcluster/tasks/builds/legacy/b2g_nexus_4_kk_eng.yml
testing/taskcluster/tasks/builds/legacy/b2g_nexus_4_kk_ota_debug.yml
testing/taskcluster/tasks/builds/legacy/b2g_nexus_4_kk_user.yml
testing/taskcluster/tasks/builds/legacy/b2g_nexus_5l_ota_debug.yml
testing/taskcluster/tasks/builds/legacy/b2g_nexus_5l_user.yml
--- a/accessible/base/AccEvent.cpp
+++ b/accessible/base/AccEvent.cpp
@@ -116,16 +116,19 @@ AccHideEvent::
 ////////////////////////////////////////////////////////////////////////////////
 // AccShowEvent
 ////////////////////////////////////////////////////////////////////////////////
 
 AccShowEvent::
   AccShowEvent(Accessible* aTarget) :
   AccMutationEvent(::nsIAccessibleEvent::EVENT_SHOW, aTarget)
 {
+  int32_t idx = aTarget->IndexInParent();
+  MOZ_ASSERT(idx >= 0);
+  mInsertionIndex = idx;
 }
 
 
 ////////////////////////////////////////////////////////////////////////////////
 // AccTextSelChangeEvent
 ////////////////////////////////////////////////////////////////////////////////
 
 AccTextSelChangeEvent::AccTextSelChangeEvent(HyperTextAccessible* aTarget,
--- a/accessible/base/AccEvent.h
+++ b/accessible/base/AccEvent.h
@@ -282,16 +282,21 @@ public:
   explicit AccShowEvent(Accessible* aTarget);
 
   // Event
   static const EventGroup kEventGroup = eShowEvent;
   virtual unsigned int GetEventGroups() const override
   {
     return AccMutationEvent::GetEventGroups() | (1U << eShowEvent);
   }
+
+  uint32_t InsertionIndex() const { return mInsertionIndex; }
+
+private:
+  uint32_t mInsertionIndex;
 };
 
 
 /**
  * Class for reorder accessible event. Takes care about
  */
 class AccReorderEvent : public AccEvent
 {
--- a/accessible/base/nsCoreUtils.cpp
+++ b/accessible/base/nsCoreUtils.cpp
@@ -137,34 +137,34 @@ nsCoreUtils::DispatchMouseEvent(EventMes
 {
   WidgetMouseEvent event(true, aMessage, aRootWidget,
                          WidgetMouseEvent::eReal, WidgetMouseEvent::eNormal);
 
   event.refPoint = LayoutDeviceIntPoint(aX, aY);
 
   event.clickCount = 1;
   event.button = WidgetMouseEvent::eLeftButton;
-  event.time = PR_IntervalNow();
+  event.mTime = PR_IntervalNow();
   event.inputSource = nsIDOMMouseEvent::MOZ_SOURCE_UNKNOWN;
 
   nsEventStatus status = nsEventStatus_eIgnore;
   aPresShell->HandleEventWithTarget(&event, aFrame, aContent, &status);
 }
 
 void
 nsCoreUtils::DispatchTouchEvent(EventMessage aMessage, int32_t aX, int32_t aY,
                                 nsIContent* aContent, nsIFrame* aFrame,
                                 nsIPresShell* aPresShell, nsIWidget* aRootWidget)
 {
   if (!dom::TouchEvent::PrefEnabled())
     return;
 
   WidgetTouchEvent event(true, aMessage, aRootWidget);
 
-  event.time = PR_IntervalNow();
+  event.mTime = PR_IntervalNow();
 
   // XXX: Touch has an identifier of -1 to hint that it is synthesized.
   RefPtr<dom::Touch> t = new dom::Touch(-1, LayoutDeviceIntPoint(aX, aY),
                                         LayoutDeviceIntPoint(1, 1), 0.0f, 1.0f);
   t->SetTarget(aContent);
   event.touches.AppendElement(t);
   nsEventStatus status = nsEventStatus_eIgnore;
   aPresShell->HandleEventWithTarget(&event, aFrame, aContent, &status);
--- a/accessible/ipc/DocAccessibleChild.cpp
+++ b/accessible/ipc/DocAccessibleChild.cpp
@@ -138,17 +138,17 @@ DocAccessibleChild::IdToTableAccessible(
   return (acc && acc->IsTable()) ? acc->AsTable() : nullptr;
 }
 
 void
 DocAccessibleChild::ShowEvent(AccShowEvent* aShowEvent)
 {
   Accessible* parent = aShowEvent->Parent();
   uint64_t parentID = parent->IsDoc() ? 0 : reinterpret_cast<uint64_t>(parent->UniqueID());
-  uint32_t idxInParent = aShowEvent->GetAccessible()->IndexInParent();
+  uint32_t idxInParent = aShowEvent->InsertionIndex();
   nsTArray<AccessibleData> shownTree;
   ShowEventData data(parentID, idxInParent, shownTree);
   SerializeTree(aShowEvent->GetAccessible(), data.NewTree());
   SendShowEvent(data);
 }
 
 bool
 DocAccessibleChild::RecvState(const uint64_t& aID, uint64_t* aState)
--- a/browser/app/profile/firefox.js
+++ b/browser/app/profile/firefox.js
@@ -1380,16 +1380,19 @@ pref("browser.newtabpage.columns", 5);
 pref("browser.newtabpage.directory.source", "https://tiles.services.mozilla.com/v3/links/fetch/%LOCALE%/%CHANNEL%");
 
 // endpoint to send newtab click and view pings
 pref("browser.newtabpage.directory.ping", "https://tiles.services.mozilla.com/v3/links/");
 
 // activates the remote-hosted newtab page
 pref("browser.newtabpage.remote", false);
 
+// remote newtab version targeted
+pref("browser.newtabpage.remote.version", "1");
+
 // Toggles endpoints allowed for remote newtab communications
 pref("browser.newtabpage.remote.mode", "production");
 
 // content-signature tests for remote newtab
 pref("browser.newtabpage.remote.content-signing-test", false);
 
 // verification keys for remote-hosted newtab page
 pref("browser.newtabpage.remote.keys", "");
--- a/browser/components/newtab/NewTabPrefsProvider.jsm
+++ b/browser/components/newtab/NewTabPrefsProvider.jsm
@@ -14,16 +14,17 @@ XPCOMUtils.defineLazyGetter(this, "Event
   const {EventEmitter} = Cu.import("resource://devtools/shared/event-emitter.js", {});
   return EventEmitter;
 });
 
 // Supported prefs and data type
 const gPrefsMap = new Map([
   ["browser.newtabpage.remote", "bool"],
   ["browser.newtabpage.remote.mode", "str"],
+  ["browser.newtabpage.remote.version", "str"],
   ["browser.newtabpage.enabled", "bool"],
   ["browser.newtabpage.enhanced", "bool"],
   ["browser.newtabpage.introShown", "bool"],
   ["browser.newtabpage.updateIntroShown", "bool"],
   ["browser.newtabpage.pinned", "str"],
   ["browser.newtabpage.blocked", "str"],
   ["intl.locale.matchOS", "bool"],
   ["general.useragent.locale", "localized"],
--- a/browser/components/newtab/NewTabRemoteResources.jsm
+++ b/browser/components/newtab/NewTabRemoteResources.jsm
@@ -2,14 +2,14 @@
 
 "use strict";
 
 this.EXPORTED_SYMBOLS = ["NewTabRemoteResources"];
 
 const NewTabRemoteResources = {
   MODE_CHANNEL_MAP: {
     production: {origin: "https://content.cdn.mozilla.net"},
-    staging: {origin: "https://content-cdn.stage.mozaws.net"},
+    staging: {origin: "https://s3_proxy_tiles.stage.mozaws.net"},
     test: {origin: "https://example.com"},
     test2: {origin: "http://mochi.test:8888"},
     dev: {origin: "http://localhost:8888"}
   }
 };
--- a/browser/components/newtab/aboutNewTabService.js
+++ b/browser/components/newtab/aboutNewTabService.js
@@ -20,17 +20,17 @@ XPCOMUtils.defineLazyModuleGetter(this, 
                                   "resource:///modules/NewTabPrefsProvider.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "Locale",
                                   "resource://gre/modules/Locale.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "NewTabRemoteResources",
                                   "resource:///modules/NewTabRemoteResources.jsm");
 
 const LOCAL_NEWTAB_URL = "chrome://browser/content/newtab/newTab.xhtml";
 
-const REMOTE_NEWTAB_PATH = "/v%VERSION%/%CHANNEL%/%LOCALE%/index.html";
+const REMOTE_NEWTAB_PATH = "/newtab/v%VERSION%/%CHANNEL%/%LOCALE%/index.html";
 
 const ABOUT_URL = "about:newtab";
 
 // Pref that tells if remote newtab is enabled
 const PREF_REMOTE_ENABLED = "browser.newtabpage.remote";
 
 // Pref branch necesssary for testing
 const PREF_REMOTE_CS_TEST = "browser.newtabpage.remote.content-signing-test";
@@ -39,19 +39,20 @@ const PREF_REMOTE_CS_TEST = "browser.new
 const PREF_MATCH_OS_LOCALE = "intl.locale.matchOS";
 
 // The preference that tells what locale the user selected
 const PREF_SELECTED_LOCALE = "general.useragent.locale";
 
 // The preference that tells what remote mode is enabled.
 const PREF_REMOTE_MODE = "browser.newtabpage.remote.mode";
 
-const VALID_CHANNELS = new Set(["esr", "release", "beta", "aurora", "nightly"]);
+// The preference that tells which remote version is expected.
+const PREF_REMOTE_VERSION = "browser.newtabpage.remote.version";
 
-const REMOTE_NEWTAB_VERSION = "0";
+const VALID_CHANNELS = new Set(["esr", "release", "beta", "aurora", "nightly"]);
 
 function AboutNewTabService() {
   NewTabPrefsProvider.prefs.on(PREF_REMOTE_ENABLED, this._handleToggleEvent.bind(this));
 
   this._updateRemoteMaybe = this._updateRemoteMaybe.bind(this);
 
   // trigger remote change if needed, according to pref
   this.toggleRemote(Services.prefs.getBoolPref(PREF_REMOTE_ENABLED));
@@ -140,36 +141,40 @@ AboutNewTabService.prototype = {
         PREF_SELECTED_LOCALE,
         this._updateRemoteMaybe);
       NewTabPrefsProvider.prefs.on(
         PREF_MATCH_OS_LOCALE,
         this._updateRemoteMaybe);
       NewTabPrefsProvider.prefs.on(
         PREF_REMOTE_MODE,
         this._updateRemoteMaybe);
+      NewTabPrefsProvider.prefs.on(
+        PREF_REMOTE_VERSION,
+        this._updateRemoteMaybe);
       this._remoteEnabled = true;
     } else {
       NewTabPrefsProvider.prefs.off(PREF_SELECTED_LOCALE, this._updateRemoteMaybe);
       NewTabPrefsProvider.prefs.off(PREF_MATCH_OS_LOCALE, this._updateRemoteMaybe);
       NewTabPrefsProvider.prefs.off(PREF_REMOTE_MODE, this._updateRemoteMaybe);
+      NewTabPrefsProvider.prefs.off(PREF_REMOTE_VERSION, this._updateRemoteMaybe);
       this._remoteEnabled = false;
     }
     if (!csTest) {
       this._newTabURL = ABOUT_URL;
     }
     return true;
   },
 
   /*
    * Generate a default url based on remote mode, version, locale and update channel
    */
   generateRemoteURL() {
     let releaseName = this.releaseFromUpdateChannel(UpdateUtils.UpdateChannel);
     let path = REMOTE_NEWTAB_PATH
-      .replace("%VERSION%", REMOTE_NEWTAB_VERSION)
+      .replace("%VERSION%", this.remoteVersion)
       .replace("%LOCALE%", Locale.getLocale())
       .replace("%CHANNEL%", releaseName);
     let mode = Services.prefs.getCharPref(PREF_REMOTE_MODE, "production");
     if (!(mode in NewTabRemoteResources.MODE_CHANNEL_MAP)) {
       mode = "production";
     }
     return NewTabRemoteResources.MODE_CHANNEL_MAP[mode].origin + path;
   },
@@ -219,17 +224,17 @@ AboutNewTabService.prototype = {
     return VALID_CHANNELS.has(channelName) ? channelName : "nightly";
   },
 
   get newTabURL() {
     return this._newTabURL;
   },
 
   get remoteVersion() {
-    return REMOTE_NEWTAB_VERSION;
+    return Services.prefs.getCharPref(PREF_REMOTE_VERSION, "1");
   },
 
   get remoteReleaseName() {
     return this.releaseFromUpdateChannel(UpdateUtils.UpdateChannel);
   },
 
   set newTabURL(aNewTabURL) {
     let csTest = Services.prefs.getBoolPref(PREF_REMOTE_CS_TEST);
--- a/browser/components/newtab/tests/xpcshell/test_AboutNewTabService.js
+++ b/browser/components/newtab/tests/xpcshell/test_AboutNewTabService.js
@@ -13,22 +13,27 @@ Cu.import("resource://gre/modules/Prefer
 
 XPCOMUtils.defineLazyModuleGetter(this, "NewTabPrefsProvider",
                                   "resource:///modules/NewTabPrefsProvider.jsm");
 
 XPCOMUtils.defineLazyServiceGetter(this, "aboutNewTabService",
                                    "@mozilla.org/browser/aboutnewtab-service;1",
                                    "nsIAboutNewTabService");
 
+XPCOMUtils.defineLazyModuleGetter(this, "Locale",
+                                  "resource://gre/modules/Locale.jsm");
+
 const DEFAULT_HREF = aboutNewTabService.generateRemoteURL();
 const DEFAULT_CHROME_URL = "chrome://browser/content/newtab/newTab.xhtml";
 const DOWNLOADS_URL = "chrome://browser/content/downloads/contentAreaDownloadsView.xul";
+const DEFAULT_VERSION = aboutNewTabService.remoteVersion;
 
 function cleanup() {
   Services.prefs.setBoolPref("browser.newtabpage.remote", false);
+  Services.prefs.setCharPref("browser.newtabpage.remote.version", DEFAULT_VERSION);
   aboutNewTabService.resetNewTabURL();
   NewTabPrefsProvider.prefs.uninit();
 }
 
 do_register_cleanup(cleanup);
 
 /**
  * Test the overriding of the default URL
@@ -102,17 +107,18 @@ add_task(function* test_updates() {
    * Simulates a "cold-boot" situation, with some pref already set before testing a series
    * of changes.
    */
   Preferences.set("browser.newtabpage.remote", true);
   aboutNewTabService.resetNewTabURL(); // need to set manually because pref notifs are off
   let notificationPromise;
   let productionModeBaseUrl = "https://content.cdn.mozilla.net";
   let testModeBaseUrl = "https://example.com";
-  let expectedPath = `/v${aboutNewTabService.remoteVersion}` +
+  let expectedPath = `/newtab` +
+                     `/v${aboutNewTabService.remoteVersion}` +
                      `/${aboutNewTabService.remoteReleaseName}` +
                      "/en-GB" +
                      "/index.html";
   let expectedHref = productionModeBaseUrl + expectedPath;
   Preferences.set("intl.locale.matchOS", true);
   Preferences.set("general.useragent.locale", "en-GB");
   Preferences.set("browser.newtabpage.remote.mode", "production");
   NewTabPrefsProvider.prefs.init();
@@ -188,16 +194,42 @@ add_task(function* test_release_names() 
   }
 
   for (let channel of invalid_channels) {
     Assert.equal("nightly", aboutNewTabService.releaseFromUpdateChannel(channel),
           "release == nightly when invalid");
   }
 });
 
+/**
+ * Verifies that remote version updates changes the remote newtab url
+ */
+add_task(function* test_version_update() {
+  NewTabPrefsProvider.prefs.init();
+
+  Services.prefs.setBoolPref("browser.newtabpage.remote", true);
+  Assert.ok(aboutNewTabService.remoteEnabled, "remote mode enabled");
+
+  let productionModeBaseUrl = "https://content.cdn.mozilla.net";
+  let version_incr = String(parseInt(DEFAULT_VERSION) + 1);
+  let expectedPath = `/newtab` +
+                     `/v${version_incr}` +
+                     `/${aboutNewTabService.remoteReleaseName}` +
+                     `/${Locale.getLocale()}` +
+                     `/index.html`;
+  let expectedHref = productionModeBaseUrl + expectedPath;
+
+  let notificationPromise;
+  notificationPromise = nextChangeNotificationPromise(expectedHref);
+  Preferences.set("browser.newtabpage.remote.version", version_incr);
+  yield notificationPromise;
+
+  cleanup();
+});
+
 function nextChangeNotificationPromise(aNewURL, testMessage) {
   return new Promise(resolve => {
     Services.obs.addObserver(function observer(aSubject, aTopic, aData) {  // jshint unused:false
       Services.obs.removeObserver(observer, aTopic);
       Assert.equal(aData, aNewURL, testMessage);
       resolve();
     }, "newtab-url-changed", false);
   });
--- a/browser/modules/ContentLinkHandler.jsm
+++ b/browser/modules/ContentLinkHandler.jsm
@@ -102,19 +102,44 @@ this.ContentLinkHandler = {
                 }
               }
             }
           } else {
             sizesType = SIZES_TELEMETRY_ENUM.NO_SIZES;
           }
           sizeHistogramTypes.add(sizesType);
 
-          chromeGlobal.sendAsyncMessage(
-            "Link:SetIcon",
-            {url: uri.spec, loadingPrincipal: link.ownerDocument.nodePrincipal});
+	  if (uri.scheme == 'blob') {
+            // Blob URLs don't work cross process, work around this by sending as a data uri
+            let channel = Cc["@mozilla.org/network/io-service;1"].
+                getService(Ci.nsIIOService).newChannelFromURI2(uri, null, Services.scriptSecurityManager.getSystemPrincipal(), null, Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL, Ci.nsIContentPolicy.TYPE_OTHER);
+            let listener = {
+              encoded: "",
+              bis: null,
+              onStartRequest: function(aRequest, aContext) {
+                this.bis = Components.classes["@mozilla.org/binaryinputstream;1"]
+                    .createInstance(Components.interfaces.nsIBinaryInputStream);
+              },
+              onStopRequest: function(aRequest, aContext, aStatusCode) {
+                let spec = "data:" + channel.contentType + ";base64," + this.encoded;
+                chromeGlobal.sendAsyncMessage(
+                  "Link:SetIcon",
+                  {url: spec, loadingPrincipal: link.ownerDocument.nodePrincipal});
+              },
+              onDataAvailable: function(request, context, inputStream, offset, count) {
+                this.bis.setInputStream(inputStream);
+                this.encoded += btoa(this.bis.readBytes(this.bis.available()));
+              }
+            }
+            channel.asyncOpen(listener, null);
+          } else {
+            chromeGlobal.sendAsyncMessage(
+              "Link:SetIcon",
+              {url: uri.spec, loadingPrincipal: link.ownerDocument.nodePrincipal});
+          }
           iconAdded = true;
           break;
         case "search":
           if (!searchAdded && event.type == "DOMLinkAdded") {
             var type = link.type && link.type.toLowerCase();
             type = type.replace(/^\s+|\s*(?:;.*)?$/g, "");
 
             let re = /^(?:https?|ftp):/i;
--- a/browser/themes/osx/browser.css
+++ b/browser/themes/osx/browser.css
@@ -3277,23 +3277,16 @@ menulist.translate-infobar-element > .me
 }
 
 
 
 .gclitoolbar-input-node > .textbox-input-box > html|*.textbox-input::-moz-selection {
   color: hsl(210,11%,16%);
 }
 
-/* Developer Toolbar */
-
-#developer-toolbar-closebutton {
-  margin-left: 8px;
-  margin-right: 8px;
-}
-
 /* Error counter */
 
 #developer-toolbar-toolbox-button[error-count]:before {
   color: #FDF3DE;
   min-width: 16px;
   text-shadow: none;
   background-image: linear-gradient(#B4211B, #8A1915);
   border-radius: 1px;
--- a/chrome/nsChromeRegistry.cpp
+++ b/chrome/nsChromeRegistry.cpp
@@ -439,21 +439,23 @@ nsresult nsChromeRegistry::RefreshWindow
   // Iterate over the style sheets.
   for (int32_t i = 0; i < count; i++) {
     // Get the style sheet
     StyleSheetHandle styleSheet = document->GetStyleSheetAt(i);
     oldSheets.AppendElement(styleSheet);
   }
 
   // Iterate over our old sheets and kick off a sync load of the new
-  // sheet if and only if it's a chrome URL.
+  // sheet if and only if it's a non-inline sheet with a chrome URL.
   for (StyleSheetHandle sheet : oldSheets) {
-    nsIURI* uri = sheet ? sheet->GetOriginalURI() : nullptr;
+    MOZ_ASSERT(sheet, "GetStyleSheetAt shouldn't return nullptr for "
+                      "in-range sheet indexes");
+    nsIURI* uri = sheet->GetSheetURI();
 
-    if (uri && IsChromeURI(uri)) {
+    if (!sheet->IsInline() && IsChromeURI(uri)) {
       // Reload the sheet.
       StyleSheetHandle::RefPtr newSheet;
       // XXX what about chrome sheets that have a title or are disabled?  This
       // only works by sheer dumb luck.
       document->LoadChromeSheetSync(uri, false, &newSheet);
       // Even if it's null, we put in in there.
       newSheets.AppendElement(newSheet);
     } else {
--- a/devtools/client/animationinspector/components/animation-time-block.js
+++ b/devtools/client/animationinspector/components/animation-time-block.js
@@ -49,17 +49,17 @@ AnimationTimeBlock.prototype = {
     this.unrender();
 
     this.animation = animation;
     let {state} = this.animation;
 
     // Create a container element to hold the delay and iterations.
     // It is positioned according to its delay (divided by the playbackrate),
     // and its width is according to its duration (divided by the playbackrate).
-    let {x, iterationW, delayX, delayW, negativeDelayW} =
+    let {x, iterationW, delayX, delayW, negativeDelayW, endDelayX, endDelayW} =
       TimeScale.getAnimationDimensions(animation);
 
     // background properties for .iterations element
     let backgroundIterations = TimeScale.getIterationsBackgroundData(animation);
 
     createNode({
       parent: this.containerEl,
       attributes: {
@@ -101,38 +101,59 @@ AnimationTimeBlock.prototype = {
         parent: this.containerEl,
         attributes: {
           "class": "delay" + (state.delay < 0 ? " negative" : ""),
           "style": `left:${delayX}%;
                     width:${delayW}%;`
         }
       });
     }
+
+    // endDelay
+    if (state.endDelay) {
+      createNode({
+        parent: this.containerEl,
+        attributes: {
+          "class": "end-delay" + (state.endDelay < 0 ? " negative" : ""),
+          "style": `left:${endDelayX}%;
+                    width:${endDelayW}%;`
+        }
+      });
+    }
   },
 
   getTooltipText: function(state) {
     let getTime = time => L10N.getFormatStr("player.timeLabel",
                             L10N.numberWithDecimals(time / 1000, 2));
 
     let text = "";
 
     // Adding the name.
     text += getFormattedAnimationTitle({state});
     text += "\n";
 
     // Adding the delay.
-    text += L10N.getStr("player.animationDelayLabel") + " ";
-    text += getTime(state.delay);
-    text += "\n";
+    if (state.delay) {
+      text += L10N.getStr("player.animationDelayLabel") + " ";
+      text += getTime(state.delay);
+      text += "\n";
+    }
 
     // Adding the duration.
     text += L10N.getStr("player.animationDurationLabel") + " ";
     text += getTime(state.duration);
     text += "\n";
 
+    // Adding the endDelay.
+    if (state.endDelay) {
+      text += L10N.getStr("player.animationEndDelayLabel") + " ";
+      text += getTime(state.endDelay);
+      text += "\n";
+    }
+
     // Adding the iteration count (the infinite symbol, or an integer).
     if (state.iterationCount !== 1) {
       text += L10N.getStr("player.animationIterationCountLabel") + " ";
       text += state.iterationCount ||
               L10N.getStr("player.infiniteIterationCountText");
       text += "\n";
     }
 
--- a/devtools/client/animationinspector/test/browser.ini
+++ b/devtools/client/animationinspector/test/browser.ini
@@ -1,14 +1,15 @@
 [DEFAULT]
 tags = devtools
 subsuite = devtools
 skip-if = e10s && debug # bug 1252283
 support-files =
   doc_body_animation.html
+  doc_end_delay.html
   doc_frame_script.js
   doc_keyframes.html
   doc_modify_playbackRate.html
   doc_negative_animation.html
   doc_script_animation.html
   doc_simple_animation.html
   doc_multiple_animation_types.html
   head.js
@@ -43,16 +44,17 @@ skip-if = os == "linux" && !debug # Bug 
 [browser_animation_timeline_pause_button.js]
 skip-if = os == "linux" && bits == 32 # Bug 1220974
 [browser_animation_timeline_rate_selector.js]
 [browser_animation_timeline_rewind_button.js]
 [browser_animation_timeline_scrubber_exists.js]
 [browser_animation_timeline_scrubber_movable.js]
 [browser_animation_timeline_scrubber_moves.js]
 [browser_animation_timeline_shows_delay.js]
+[browser_animation_timeline_shows_endDelay.js]
 [browser_animation_timeline_shows_iterations.js]
 [browser_animation_timeline_shows_time_info.js]
 [browser_animation_timeline_takes_rate_into_account.js]
 [browser_animation_timeline_ui.js]
 [browser_animation_toggle_button_resets_on_navigate.js]
 [browser_animation_toggle_button_toggles_animations.js]
 [browser_animation_toolbar_exists.js]
 [browser_animation_ui_updates_when_animation_data_changes.js]
new file mode 100644
--- /dev/null
+++ b/devtools/client/animationinspector/test/browser_animation_timeline_shows_endDelay.js
@@ -0,0 +1,44 @@
+/* vim: set ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+requestLongerTimeout(2);
+
+// Check that animation endDelay is visualized in the timeline when the
+// animation is delayed.
+// Also check that negative endDelays do not overflow the UI, and are shown
+// like positive endDelays.
+
+add_task(function*() {
+  yield addTab(URL_ROOT + "doc_end_delay.html");
+  let {inspector, panel} = yield openAnimationInspector();
+
+  let selectors = ["#target1", "#target2", "#target3", "#target4"];
+  for (let i = 0; i < selectors.length; i++) {
+    let selector = selectors[i];
+    yield selectNode(selector, inspector);
+    let timelineEl = panel.animationsTimelineComponent.rootWrapperEl;
+    let animationEl = timelineEl.querySelectorAll(".animation")[0];
+    checkEndDelayAndName(animationEl);
+  }
+});
+
+function checkEndDelayAndName(animationEl) {
+  let endDelay = animationEl.querySelector(".end-delay");
+  let name = animationEl.querySelector(".name");
+  let targetNode = animationEl.querySelector(".target");
+
+  // Check that the endDelay element does not cause the timeline to overflow.
+  let endDelayLeft = Math.round(endDelay.getBoundingClientRect().x);
+  let sidebarWidth = Math.round(targetNode.getBoundingClientRect().width);
+  ok(endDelayLeft >= sidebarWidth,
+     "The endDelay element isn't displayed over the sidebar");
+
+  // Check that the endDelay is not displayed on top of the name.
+  let endDelayRight = Math.round(endDelay.getBoundingClientRect().right);
+  let nameLeft = Math.round(name.getBoundingClientRect().left);
+  ok(endDelayRight >= nameLeft,
+     "The endDelay element does not span over the name element");
+}
--- a/devtools/client/animationinspector/test/browser_animation_timeline_shows_time_info.js
+++ b/devtools/client/animationinspector/test/browser_animation_timeline_shows_time_info.js
@@ -18,18 +18,23 @@ add_task(function*() {
   let timeBlockNameEls = timelineEl.querySelectorAll(".time-block .name");
 
   // Verify that each time-block's name element has a tooltip that looks sort of
   // ok. We don't need to test the actual content.
   [...timeBlockNameEls].forEach((el, i) => {
     ok(el.hasAttribute("title"), "The tooltip is defined for animation " + i);
 
     let title = el.getAttribute("title");
-    ok(title.match(/Delay: [\d.-]+s/), "The tooltip shows the delay");
+    if (controller.animationPlayers[i].state.delay) {
+      ok(title.match(/Delay: [\d.-]+s/), "The tooltip shows the delay");
+    }
     ok(title.match(/Duration: [\d.]+s/), "The tooltip shows the duration");
+    if (controller.animationPlayers[i].state.endDelay) {
+      ok(title.match(/End delay: [\d.-]+s/), "The tooltip shows the endDelay");
+    }
     if (controller.animationPlayers[i].state.iterationCount !== 1) {
       ok(title.match(/Repeats: /), "The tooltip shows the iterations");
     } else {
       ok(!title.match(/Repeats: /), "The tooltip doesn't show the iterations");
     }
     ok(!title.match(/Iteration start:/),
       "The tooltip doesn't show the iteration start");
   });
new file mode 100644
--- /dev/null
+++ b/devtools/client/animationinspector/test/doc_end_delay.html
@@ -0,0 +1,60 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+  <meta charset="UTF-8">
+  <style>
+  .target {
+    width: 50px;
+    height: 50px;
+    background: blue;
+  }
+  </style>
+</head>
+<body>
+  <div id="target1" class="target"></div>
+  <div id="target2" class="target"></div>
+  <div id="target3" class="target"></div>
+  <div id="target4" class="target"></div>
+  <script>
+    "use strict";
+
+    var el = document.getElementById("target1");
+    el.animate(
+      { opacity: [ 0, 1 ] },
+      { id: "endDelay_animation1",
+        duration: 1000000,
+        endDelay: 500000,
+        fill: "none" }
+    );
+
+    el = document.getElementById("target2");
+    el.animate(
+      { opacity: [ 0, 1 ] },
+      { id: "endDelay_animation2",
+        duration: 1000000,
+        endDelay: -500000,
+        fill: "none" }
+    );
+
+    el = document.getElementById("target3");
+    el.animate(
+      { opacity: [ 0, 1 ] },
+      { id: "endDelay_animation3",
+        duration: 1000000,
+        endDelay: -1500000,
+        fill: "forwards" }
+    );
+
+    el = document.getElementById("target4");
+    el.animate(
+      { opacity: [ 0, 1 ] },
+      { id: "endDelay_animation4",
+        duration: 100000,
+        delay: 100000,
+        endDelay: -1500000,
+        fill: "forwards" }
+    );
+
+  </script>
+</body>
+</html>
--- a/devtools/client/animationinspector/test/doc_simple_animation.html
+++ b/devtools/client/animationinspector/test/doc_simple_animation.html
@@ -123,10 +123,22 @@
   <div class="ball multi"></div>
   <div class="ball delayed"></div>
   <div class="ball multi-finite"></div>
   <div class="ball short"></div>
   <div class="ball long"></div>
   <div class="ball negative-delay"></div>
   <div class="ball no-compositor"></div>
   <div class="ball pseudo"></div>
+  <div class="ball" id="endDelayed"></div>
+  <script>
+    "use strict";
+
+    var el = document.getElementById("endDelayed");
+    el.animate(
+      { opacity: [ 0, 1 ] },
+      { duration: 1000000,
+        endDelay: 500000,
+        fill: "none" }
+    );
+  </script>
 </body>
 </html>
--- a/devtools/client/animationinspector/utils.js
+++ b/devtools/client/animationinspector/utils.js
@@ -194,30 +194,42 @@ var TimeScale = {
   minStartTime: Infinity,
   maxEndTime: 0,
 
   /**
    * Add a new animation to time scale.
    * @param {Object} state A PlayerFront.state object.
    */
   addAnimation: function(state) {
-    let {previousStartTime, delay, duration,
+    let {previousStartTime, delay, duration, endDelay,
          iterationCount, playbackRate} = state;
 
+    endDelay = typeof endDelay === "undefined" ? 0 : endDelay;
+    let toRate = v => v / playbackRate;
+    let minZero = v => Math.max(v, 0);
+    let rateRelativeDuration =
+      toRate(duration * (!iterationCount ? 1 : iterationCount));
     // Negative-delayed animations have their startTimes set such that we would
     // be displaying the delay outside the time window if we didn't take it into
     // account here.
-    let relevantDelay = delay < 0 ? delay / playbackRate : 0;
+    let relevantDelay = delay < 0 ? toRate(delay) : 0;
     previousStartTime = previousStartTime || 0;
 
-    this.minStartTime = Math.min(this.minStartTime,
-                                 previousStartTime + relevantDelay);
-    let length = (delay / playbackRate) +
-                 ((duration / playbackRate) *
-                  (!iterationCount ? 1 : iterationCount));
+    let startTime = toRate(minZero(delay)) +
+                    rateRelativeDuration +
+                    endDelay;
+    this.minStartTime = Math.min(
+      this.minStartTime,
+      previousStartTime +
+      relevantDelay +
+      Math.min(startTime, 0)
+    );
+    let length = toRate(delay) +
+                 rateRelativeDuration +
+                 toRate(minZero(endDelay));
     let endTime = previousStartTime + length;
     this.maxEndTime = Math.max(this.maxEndTime, endTime);
   },
 
   /**
    * Reset the current time scale.
    */
   reset: function() {
@@ -289,31 +301,37 @@ var TimeScale = {
    * animation in the timeline.
    */
   getAnimationDimensions: function({state}) {
     let start = state.previousStartTime || 0;
     let duration = state.duration;
     let rate = state.playbackRate;
     let count = state.iterationCount;
     let delay = state.delay || 0;
+    let endDelay = state.endDelay || 0;
 
     // The start position.
     let x = this.startTimeToDistance(start + (delay / rate));
     // The width for a single iteration.
     let w = this.durationToDistance(duration / rate);
     // The width for all iterations.
     let iterationW = w * (count || 1);
     // The start position of the delay.
     let delayX = delay < 0 ? x : this.startTimeToDistance(start);
     // The width of the delay.
     let delayW = this.durationToDistance(Math.abs(delay) / rate);
     // The width of the delay if it is negative, 0 otherwise.
     let negativeDelayW = delay < 0 ? delayW : 0;
+    // The width of the endDelay.
+    let endDelayW = this.durationToDistance(Math.abs(endDelay) / rate);
+    // The start position of the endDelay.
+    let endDelayX = endDelay < 0 ? x + w - endDelayW : x + w;
 
-    return {x, w, iterationW, delayX, delayW, negativeDelayW};
+    return {x, w, iterationW, delayX, delayW, negativeDelayW,
+            endDelayX, endDelayW};
   },
 
   /**
    * Given an animation, get the background data for .iterations element.
    * This background represents iterationCount and iterationStart.
    * Returns three properties.
    * 1. size: x of background-size (%)
    * 2. position: x of background-position (%)
--- a/devtools/client/locales/en-US/animationinspector.properties
+++ b/devtools/client/locales/en-US/animationinspector.properties
@@ -38,16 +38,21 @@ player.transitionNameLabel=Transition
 # displayed before the animation duration.
 player.animationDurationLabel=Duration:
 
 # LOCALIZATION NOTE (player.animationDelayLabel):
 # This string is displayed in each animation player widget. It is the label
 # displayed before the animation delay.
 player.animationDelayLabel=Delay:
 
+# LOCALIZATION NOTE (player.animationEndDelayLabel):
+# This string is displayed in each animation player widget. It is the label
+# displayed before the animation endDelay.
+player.animationEndDelayLabel=End delay:
+
 # LOCALIZATION NOTE (player.animationRateLabel):
 # This string is displayed in each animation player widget. It is the label
 # displayed before the animation playback rate.
 player.animationRateLabel=Playback rate:
 
 # LOCALIZATION NOTE (player.animationIterationCountLabel):
 # This string is displayed in each animation player widget. It is the label
 # displayed before the number of times the animation is set to repeat.
--- a/devtools/client/themes/animationinspector.css
+++ b/devtools/client/themes/animationinspector.css
@@ -222,17 +222,17 @@ body {
   left: var(--timeline-sidebar-width);
   /* Leave the width of a marker right of a track so the 100% markers can be
      selected easily */
   right: var(--keyframes-marker-size);
   height: var(--timeline-animation-height);
 }
 
 .animation-timeline .scrubber-wrapper {
-  z-index: 1;
+  z-index: 2;
   pointer-events: none;
   height: 100%;
 }
 
 .animation-timeline .scrubber {
   position: absolute;
   height: 100%;
   width: 0;
@@ -369,16 +369,17 @@ body {
   position: absolute;
   color: var(--theme-selection-color);
   height: 100%;
   display: flex;
   align-items: center;
   padding: 0 2px;
   box-sizing: border-box;
   --fast-track-icon-width: 12px;
+  z-index: 1;
 }
 
 .animation-timeline .animation .name div {
   /* Flex items don't support text-overflow, so a child div is used */
   white-space: nowrap;
   overflow: hidden;
   text-overflow: ellipsis;
 }
@@ -391,39 +392,53 @@ body {
   /* Animations running on the compositor have the fast-track background image*/
   content: "";
   display: block;
   position: absolute;
   top: 0;
   right: 0;
   height: 100%;
   width: var(--fast-track-icon-width);
+  z-index: 1;
 
   background-image: url("images/animation-fast-track.svg");
   background-repeat: no-repeat;
   background-position: center;
 }
 
-.animation-timeline .animation .delay {
+.animation-timeline .animation .delay,
+.animation-timeline .animation .end-delay {
   position: absolute;
   height: 100%;
-
   border: 1px solid var(--timeline-border-color);
   box-sizing: border-box;
+}
+
+.animation-timeline .animation .delay {
   border-width: 1px 0 1px 1px;
-
   background-image: repeating-linear-gradient(45deg,
                                               transparent,
                                               transparent 1px,
                                               var(--theme-selection-color) 1px,
                                               var(--theme-selection-color) 4px);
   background-color: var(--timeline-border-color);
 }
 
-.animation-timeline .animation .delay.negative {
+.animation-timeline .animation .end-delay {
+  border-width: 1px 1px 1px 0;
+  background-image: repeating-linear-gradient(
+                      -45deg,
+                      transparent,
+                      transparent 3px,
+                      var(--timeline-border-color) 3px,
+                      var(--timeline-border-color) 4px);
+}
+
+.animation-timeline .animation .delay.negative,
+.animation-timeline .animation .end-delay.negative {
   /* Negative delays are displayed on top of the animation, so they need a
      right border. Whereas normal delays are displayed just before the
      animation, so there's already the animation's left border that serves as
      a separation. */
   border-width: 1px;
 }
 
 /* Animation target node gutter, contains a preview of the dom node */
--- a/devtools/client/themes/commandline.inc.css
+++ b/devtools/client/themes/commandline.inc.css
@@ -45,27 +45,31 @@
   padding: 0 10px;
   width: 32px;
 }
 
 .developer-toolbar-button > image {
   margin: auto 10px;
 }
 
-:root[devtoolstheme="light"] #developer-toolbar > toolbarbutton:not([checked=true]) > image,
+:root[devtoolstheme="light"] #developer-toolbar > .developer-toolbar-button:not([checked=true]) > image,
 :root[devtoolstheme="light"] .gclitoolbar-input-node:not([focused=true])::before  {
   filter: invert(1);
 }
 
-.developer-toolbar-button > .toolbarbutton-icon,
-#developer-toolbar-closebutton > .toolbarbutton-icon {
+.developer-toolbar-button > .toolbarbutton-icon {
   width: 16px;
   height: 16px;
 }
 
+/* The toolkit close button is low contrast in the dark theme so invert it. */
+:root[devtoolstheme="dark"] #developer-toolbar > .close-icon:not(:hover) > image {
+  filter: invert(1);
+}
+
 #developer-toolbar-toolbox-button {
   list-style-image: url("chrome://devtools/skin/images/toggle-tools.png");
   -moz-image-region: rect(0px, 16px, 16px, 0px);
 }
 
 #developer-toolbar-toolbox-button > label {
   display: none;
 }
@@ -96,50 +100,16 @@
     -moz-image-region: rect(0px, 96px, 32px, 64px);
   }
 
   #developer-toolbar-toolbox-button[checked=true] {
     -moz-image-region: rect(0px, 128px, 32px, 96px);
   }
 }
 
-#developer-toolbar-closebutton {
-  list-style-image: url("chrome://devtools/skin/images/close.png");
-  -moz-appearance: none;
-  border: none;
-  margin: 0 4px;
-  min-width: 16px;
-  width: 16px;
-  opacity: 0.6;
-}
-
-@media (min-resolution: 1.1dppx) {
-  #developer-toolbar-closebutton {
-    list-style-image: url("chrome://devtools/skin/images/close@2x.png");
-  }
-}
-
-#developer-toolbar-closebutton > .toolbarbutton-icon {
-  /* XXX Buttons have padding in widget/ that we don't want here but can't override with good CSS, so we must
-     use evil CSS to give the impression of smaller content */
-  margin: -4px;
-}
-
-#developer-toolbar-closebutton > .toolbarbutton-text {
-  display: none;
-}
-
-#developer-toolbar-closebutton:hover {
-  opacity: 0.8;
-}
-
-#developer-toolbar-closebutton:hover:active {
-  opacity: 1;
-}
-
 /* GCLI */
 
 html|*#gcli-tooltip-frame,
 html|*#gcli-output-frame {
   padding: 0;
   border-width: 0;
   background-color: transparent;
 }
--- a/devtools/server/actors/animation.js
+++ b/devtools/server/actors/animation.js
@@ -177,16 +177,24 @@ var AnimationPlayerActor = ActorClass({
    * Get the animation delay from this player, in milliseconds.
    * @return {Number}
    */
   getDelay: function() {
     return this.player.effect.getComputedTiming().delay;
   },
 
   /**
+   * Get the animation endDelay from this player, in milliseconds.
+   * @return {Number}
+   */
+  getEndDelay: function() {
+    return this.player.effect.getComputedTiming().endDelay;
+  },
+
+  /**
    * Get the animation iteration count for this player. That is, how many times
    * is the animation scheduled to run.
    * @return {Number} The number of iterations, or null if the animation repeats
    * infinitely.
    */
   getIterationCount: function() {
     let iterations = this.player.effect.getComputedTiming().iterations;
     return iterations === "Infinity" ? null : iterations;
@@ -224,16 +232,17 @@ var AnimationPlayerActor = ActorClass({
       startTime: this.player.startTime,
       previousStartTime: this.previousStartTime,
       currentTime: this.player.currentTime,
       playState: this.player.playState,
       playbackRate: this.player.playbackRate,
       name: this.getName(),
       duration: this.getDuration(),
       delay: this.getDelay(),
+      endDelay: this.getEndDelay(),
       iterationCount: this.getIterationCount(),
       iterationStart: this.getIterationStart(),
       // animation is hitting the fast path or not. Returns false whenever the
       // animation is paused as it is taken off the compositor then.
       isRunningOnCompositor: this.player.isRunningOnCompositor,
       // The document timeline's currentTime is being sent along too. This is
       // not strictly related to the node's animationPlayer, but is useful to
       // know the current time of the animation with respect to the document's.
@@ -298,17 +307,18 @@ var AnimationPlayerActor = ActorClass({
       if (hasCurrentAnimation(changedAnimations)) {
         // Only consider the state has having changed if any of delay, duration,
         // iterationcount or iterationStart has changed (for now at least).
         let newState = this.getState();
         let oldState = this.currentState;
         hasChanged = newState.delay !== oldState.delay ||
                      newState.iterationCount !== oldState.iterationCount ||
                      newState.iterationStart !== oldState.iterationStart ||
-                     newState.duration !== oldState.duration;
+                     newState.duration !== oldState.duration ||
+                     newState.endDelay !== oldState.endDelay;
         break;
       }
     }
 
     if (hasChanged) {
       events.emit(this, "changed", this.getCurrentState());
     }
   },
@@ -451,16 +461,17 @@ var AnimationPlayerFront = FrontClass(An
       startTime: this._form.startTime,
       previousStartTime: this._form.previousStartTime,
       currentTime: this._form.currentTime,
       playState: this._form.playState,
       playbackRate: this._form.playbackRate,
       name: this._form.name,
       duration: this._form.duration,
       delay: this._form.delay,
+      endDelay: this._form.endDelay,
       iterationCount: this._form.iterationCount,
       iterationStart: this._form.iterationStart,
       isRunningOnCompositor: this._form.isRunningOnCompositor,
       documentCurrentTime: this._form.documentCurrentTime
     };
   },
 
   /**
--- a/docshell/resources/content/netError.xhtml
+++ b/docshell/resources/content/netError.xhtml
@@ -288,19 +288,16 @@
         <h1 id="et_netInterrupt">&netInterrupt.title;</h1>
         <h1 id="et_deniedPortAccess">&deniedPortAccess.title;</h1>
         <h1 id="et_proxyResolveFailure">&proxyResolveFailure.title;</h1>
         <h1 id="et_proxyConnectFailure">&proxyConnectFailure.title;</h1>
         <h1 id="et_contentEncodingError">&contentEncodingError.title;</h1>
         <h1 id="et_unsafeContentType">&unsafeContentType.title;</h1>
         <h1 id="et_nssFailure2">&nssFailure2.title;</h1>
         <h1 id="et_nssBadCert">&nssBadCert.title;</h1>
-        <h1 id="et_malwareBlocked">&malwareBlocked.title;</h1>
-        <h1 id="et_unwantedBlocked">&unwantedBlocked.title;</h1>
-        <h1 id="et_forbiddenBlocked">&forbiddenBlocked.title;</h1>
         <h1 id="et_cspBlocked">&cspBlocked.title;</h1>
         <h1 id="et_remoteXUL">&remoteXUL.title;</h1>
         <h1 id="et_corruptedContentError">&corruptedContentError.title;</h1>
       </div>
       <div id="errorDescriptionsContainer">
         <div id="ed_generic">&generic.longDesc;</div>
         <div id="ed_dnsNotFound">&dnsNotFound.longDesc;</div>
         <div id="ed_fileNotFound">&fileNotFound.longDesc;</div>
@@ -316,19 +313,16 @@
         <div id="ed_netInterrupt">&netInterrupt.longDesc;</div>
         <div id="ed_deniedPortAccess">&deniedPortAccess.longDesc;</div>
         <div id="ed_proxyResolveFailure">&proxyResolveFailure.longDesc;</div>
         <div id="ed_proxyConnectFailure">&proxyConnectFailure.longDesc;</div>
         <div id="ed_contentEncodingError">&contentEncodingError.longDesc;</div>
         <div id="ed_unsafeContentType">&unsafeContentType.longDesc;</div>
         <div id="ed_nssFailure2">&nssFailure2.longDesc2;</div>
         <div id="ed_nssBadCert">&nssBadCert.longDesc2;</div>
-        <div id="ed_malwareBlocked">&malwareBlocked.longDesc;</div>
-        <div id="ed_unwantedBlocked">&unwantedBlocked.longDesc;</div>
-        <div id="ed_forbiddenBlocked">&forbiddenBlocked.longDesc;</div>
         <div id="ed_cspBlocked">&cspBlocked.longDesc;</div>
         <div id="ed_remoteXUL">&remoteXUL.longDesc;</div>
         <div id="ed_corruptedContentError">&corruptedContentError.longDesc;</div>
       </div>
     </div>
 
     <!-- PAGE CONTAINER (for styling purposes only) -->
     <div id="errorPageContainer">
--- a/dom/animation/AnimValuesStyleRule.h
+++ b/dom/animation/AnimValuesStyleRule.h
@@ -56,18 +56,17 @@ public:
 
   struct PropertyValuePair {
     nsCSSProperty mProperty;
     StyleAnimationValue mValue;
   };
 
   void AddPropertiesToSet(nsCSSPropertySet& aSet) const
   {
-    for (size_t i = 0, i_end = mPropertyValuePairs.Length(); i < i_end; ++i) {
-      const PropertyValuePair &cv = mPropertyValuePairs[i];
+    for (const PropertyValuePair& cv : mPropertyValuePairs) {
       aSet.AddProperty(cv.mProperty);
     }
   }
 
 private:
   ~AnimValuesStyleRule() {}
 
   InfallibleTArray<PropertyValuePair> mPropertyValuePairs;
--- a/dom/animation/AnimationEffectTiming.cpp
+++ b/dom/animation/AnimationEffectTiming.cpp
@@ -68,17 +68,28 @@ AnimationEffectTiming::SetIterationStart
   mTiming.mIterationStart = aIterationStart;
 
   PostSpecifiedTimingUpdated(mEffect);
 }
 
 void
 AnimationEffectTiming::SetIterations(double aIterations, ErrorResult& aRv)
 {
-  // TODO: Bug 1244640 - implement AnimationEffectTiming iterations
+  if (mTiming.mIterations == aIterations) {
+    return;
+  }
+
+  TimingParams::ValidateIterations(aIterations, aRv);
+  if (aRv.Failed()) {
+    return;
+  }
+
+  mTiming.mIterations = aIterations;
+
+  PostSpecifiedTimingUpdated(mEffect);
 }
 
 void
 AnimationEffectTiming::SetDuration(const UnrestrictedDoubleOrString& aDuration,
                                    ErrorResult& aRv)
 {
   Maybe<StickyTimeDuration> newDuration =
     TimingParams::ParseDuration(aDuration, aRv);
@@ -93,17 +104,23 @@ AnimationEffectTiming::SetDuration(const
   mTiming.mDuration = newDuration;
 
   PostSpecifiedTimingUpdated(mEffect);
 }
 
 void
 AnimationEffectTiming::SetDirection(const PlaybackDirection& aDirection)
 {
-  // TODO: Bug 1244642 - implement AnimationEffectTiming direction
+  if (mTiming.mDirection == aDirection) {
+    return;
+  }
+
+  mTiming.mDirection = aDirection;
+
+  PostSpecifiedTimingUpdated(mEffect);
 }
 
 void
 AnimationEffectTiming::SetEasing(const nsAString& aEasing, ErrorResult& aRv)
 {
   // TODO: Bug 1244643 - implement AnimationEffectTiming easing
 }
 
--- a/dom/animation/AnimationEffectTimingReadOnly.cpp
+++ b/dom/animation/AnimationEffectTimingReadOnly.cpp
@@ -35,17 +35,17 @@ AnimationEffectTimingReadOnly::GetDurati
   } else {
     aRetVal.SetAsString().AssignLiteral("auto");
   }
 }
 
 void
 AnimationEffectTimingReadOnly::GetEasing(nsString& aRetVal) const
 {
-  if (mTiming.mFunction.isSome()) {
+  if (mTiming.mFunction) {
     mTiming.mFunction->AppendToString(aRetVal);
   } else {
     aRetVal.AssignLiteral("linear");
   }
 }
 
 } // namespace dom
 } // namespace mozilla
--- a/dom/animation/ComputedTimingFunction.h
+++ b/dom/animation/ComputedTimingFunction.h
@@ -39,17 +39,17 @@ public:
     return !(*this == aOther);
   }
   int32_t Compare(const ComputedTimingFunction& aRhs) const;
   void AppendToString(nsAString& aResult) const;
 
   static double GetPortion(const Maybe<ComputedTimingFunction>& aFunction,
                            double aPortion)
   {
-    return aFunction.isSome() ? aFunction->GetValue(aPortion) : aPortion;
+    return aFunction ? aFunction->GetValue(aPortion) : aPortion;
   }
   static int32_t Compare(const Maybe<ComputedTimingFunction>& aLhs,
                          const Maybe<ComputedTimingFunction>& aRhs);
 
 private:
   nsTimingFunction::Type mType;
   nsSMILKeySpline mTimingFunction;
   uint32_t mSteps;
--- a/dom/animation/test/chrome/test_animation_observers.html
+++ b/dom/animation/test/chrome/test_animation_observers.html
@@ -1596,16 +1596,49 @@ addAsyncAnimTest("change_enddelay_and_cu
 addAsyncAnimTest("change_enddelay_and_currenttime",
                  { observe: div, subtree: true }, function*() {
   var anim = div.animate({ opacity: [ 0, 1 ] },
                          { duration: 100, endDelay: -100 });
   yield await_frame();
   assert_records([], "records after animation is added");
 });
 
+addAsyncAnimTest("change_iterations",
+                 { observe: div, subtree: true }, function*() {
+  var anim = div.animate({ opacity: [ 0, 1 ] }, 100000);
+
+  yield await_frame();
+  assert_records([{ added: [anim], changed: [], removed: [] }],
+                 "records after animation is added");
+
+  anim.effect.timing.iterations = 2;
+  yield await_frame();
+  assert_records([{ added: [], changed: [anim], removed: [] }],
+                 "records after iterations is changed");
+
+  anim.effect.timing.iterations = 2;
+  yield await_frame();
+  assert_records([], "records after assigning same value");
+
+  anim.effect.timing.iterations = 0;
+  yield await_frame();
+  assert_records([{ added: [], changed: [], removed: [anim] }],
+                 "records after animation end");
+
+  anim.effect.timing.iterations = Infinity;
+  yield await_frame();
+  assert_records([{ added: [anim], changed: [], removed: [] }],
+                 "records after animation restarted");
+
+  anim.cancel();
+  yield await_frame();
+  assert_records([{ added: [], changed: [], removed: [anim] }],
+                 "records after animation end");
+});
+
 addAsyncAnimTest("exclude_animations_targeting_pseudo_elements",
                  { observe: div, subtree: false }, function*() {
   var anim = div.animate({ opacity: [ 0, 1 ] }, { duration: 100000 });
   var pAnim = pseudoTarget.animate({ opacity: [ 0, 1 ] }, { duration: 100000 });
 
   yield await_frame();
   assert_records([{ added: [anim], changed: [], removed: [] }],
                  "records after animation is added");
--- a/dom/animation/test/css-animations/file_animation-starttime.html
+++ b/dom/animation/test/css-animations/file_animation-starttime.html
@@ -212,34 +212,38 @@ async_test(function(t)
     div.style.animationPlayState = 'running';
     getComputedStyle(div).animationPlayState;
     assert_not_equals(animation.startTime, null,
                       'startTime is preserved when a pause is aborted');
     t.done();
   }));
 }, 'startTime while pause-pending and play-pending');
 
-async_test(function(t)
-{
+async_test(function(t) {
   var div = addDiv(t, { 'style': 'animation: anim 100s' });
   var animation = div.getAnimations()[0];
   // Seek to end to put us in the finished state
-  // FIXME: Once we implement finish(), use that here.
   animation.currentTime = 100 * 1000;
   animation.ready.then(t.step_func(function() {
     // Call play() which puts us back in the running state
     animation.play();
-    // FIXME: Enable this once we implement finishing behavior (bug 1074630)
-    /*
     assert_equals(animation.startTime, null, 'startTime is unresolved');
-    */
     t.done();
   }));
 }, 'startTime while play-pending from finished state');
 
+test(function(t) {
+  var div = addDiv(t, { 'style': 'animation: anim 100s' });
+  var animation = div.getAnimations()[0];
+  animation.finish();
+  // Call play() which puts us back in the running state
+  animation.play();
+  assert_equals(animation.startTime, null, 'startTime is unresolved');
+}, 'startTime while play-pending from finished state using finish()');
+
 async_test(function(t) {
   var div = addDiv(t, { style: 'animation: anim 1000s' });
   var animation = div.getAnimations()[0];
 
   assert_equals(animation.startTime, null, 'The initial startTime is null');
   var initialTimelineTime = document.timeline.currentTime;
 
   animation.ready.then(t.step_func(function() {
--- a/dom/base/Element.cpp
+++ b/dom/base/Element.cpp
@@ -2918,17 +2918,17 @@ Element::PreHandleEventForLinks(EventCha
   // updated even if the event is consumed before we have a chance to set it.
   switch (aVisitor.mEvent->mMessage) {
   // Set the status bar similarly for mouseover and focus
   case eMouseOver:
     aVisitor.mEventStatus = nsEventStatus_eConsumeNoDefault;
     MOZ_FALLTHROUGH;
   case eFocus: {
     InternalFocusEvent* focusEvent = aVisitor.mEvent->AsFocusEvent();
-    if (!focusEvent || !focusEvent->isRefocus) {
+    if (!focusEvent || !focusEvent->mIsRefocus) {
       nsAutoString target;
       GetLinkTarget(target);
       nsContentUtils::TriggerLink(this, aVisitor.mPresContext, absURI, target,
                                   false, true, true);
       // Make sure any ancestor links don't also TriggerLink
       aVisitor.mEvent->mFlags.mMultipleActionsPrevented = true;
     }
     break;
@@ -3010,17 +3010,17 @@ Element::PostHandleEventForLinks(EventCh
       // The default action is simply to dispatch DOMActivate
       nsCOMPtr<nsIPresShell> shell = aVisitor.mPresContext->GetPresShell();
       if (shell) {
         // single-click
         nsEventStatus status = nsEventStatus_eIgnore;
         // DOMActive event should be trusted since the activation is actually
         // occurred even if the cause is an untrusted click event.
         InternalUIEvent actEvent(true, eLegacyDOMActivate, mouseEvent);
-        actEvent.detail = 1;
+        actEvent.mDetail = 1;
 
         rv = shell->HandleDOMEventWithTarget(this, &actEvent, &status);
         if (NS_SUCCEEDED(rv)) {
           aVisitor.mEventStatus = nsEventStatus_eConsumeNoDefault;
         }
       }
     }
     break;
--- a/dom/base/nsContentUtils.cpp
+++ b/dom/base/nsContentUtils.cpp
@@ -7694,17 +7694,17 @@ nsContentUtils::SendKeyEvent(nsIWidget* 
         default:
           event.location = nsIDOMKeyEvent::DOM_KEY_LOCATION_STANDARD;
           break;
       }
       break;
   }
 
   event.refPoint.x = event.refPoint.y = 0;
-  event.time = PR_IntervalNow();
+  event.mTime = PR_IntervalNow();
   if (!(aAdditionalFlags & nsIDOMWindowUtils::KEY_FLAG_NOT_SYNTHESIZED_FOR_TESTS)) {
     event.mFlags.mIsSynthesizedForTests = true;
   }
 
   if (aAdditionalFlags & nsIDOMWindowUtils::KEY_FLAG_PREVENT_DEFAULT) {
     event.PreventDefaultBeforeDispatch();
   }
 
@@ -7769,17 +7769,17 @@ nsContentUtils::SendMouseEvent(nsCOMPtr<
                                           WidgetMouseEvent::eNormal);
   event.modifiers = GetWidgetModifiers(aModifiers);
   event.button = aButton;
   event.buttons = GetButtonsFlagForButton(aButton);
   event.widget = widget;
   event.pressure = aPressure;
   event.inputSource = aInputSourceArg;
   event.clickCount = aClickCount;
-  event.time = PR_IntervalNow();
+  event.mTime = PR_IntervalNow();
   event.mFlags.mIsSynthesizedForTests = aIsSynthesized;
 
   nsPresContext* presContext = aPresShell->GetPresContext();
   if (!presContext)
     return NS_ERROR_FAILURE;
 
   event.refPoint = ToWidgetPoint(CSSPoint(aX, aY), offset, presContext);
   event.ignoreRootScrollFrame = aIgnoreRootScrollFrame;
--- a/dom/base/nsCopySupport.cpp
+++ b/dom/base/nsCopySupport.cpp
@@ -688,17 +688,17 @@ nsCopySupport::FireClipboardEvent(EventM
   RefPtr<DataTransfer> clipboardData;
   if (chromeShell || Preferences::GetBool("dom.event.clipboardevents.enabled", true)) {
     clipboardData =
       new DataTransfer(piWindow, aEventMessage, aEventMessage == ePaste,
                        aClipboardType);
 
     nsEventStatus status = nsEventStatus_eIgnore;
     InternalClipboardEvent evt(true, aEventMessage);
-    evt.clipboardData = clipboardData;
+    evt.mClipboardData = clipboardData;
     EventDispatcher::Dispatch(content, presShell->GetPresContext(), &evt,
                               nullptr, &status);
     // If the event was cancelled, don't do the clipboard operation
     doDefault = (status != nsEventStatus_eConsumeNoDefault);
   }
 
   // No need to do anything special during a paste. Either an event listener
   // took care of it and cancelled the event, or the caller will handle it.
--- a/dom/base/nsDOMWindowUtils.cpp
+++ b/dom/base/nsDOMWindowUtils.cpp
@@ -677,17 +677,17 @@ nsDOMWindowUtils::SendPointerEventCommon
   event.inputSource = aInputSourceArg;
   event.pointerId = aPointerId;
   event.width = aWidth;
   event.height = aHeight;
   event.tiltX = aTiltX;
   event.tiltY = aTiltY;
   event.isPrimary = (nsIDOMMouseEvent::MOZ_SOURCE_MOUSE == aInputSourceArg) ? true : aIsPrimary;
   event.clickCount = aClickCount;
-  event.time = PR_IntervalNow();
+  event.mTime = PR_IntervalNow();
   event.mFlags.mIsSynthesizedForTests = aOptionalArgCount >= 10 ? aIsSynthesized : true;
 
   nsPresContext* presContext = GetPresContext();
   if (!presContext) {
     return NS_ERROR_FAILURE;
   }
 
   event.refPoint = nsContentUtils::ToWidgetPoint(CSSPoint(aX, aY), offset, presContext);
@@ -802,17 +802,17 @@ nsDOMWindowUtils::SendWheelEvent(float a
   wheelEvent.mIsNoLineOrPageDelta =
     (aOptions & WHEEL_EVENT_CAUSED_BY_NO_LINE_OR_PAGE_DELTA_DEVICE) != 0;
   wheelEvent.customizedByUserPrefs =
     (aOptions & WHEEL_EVENT_CUSTOMIZED_BY_USER_PREFS) != 0;
   wheelEvent.lineOrPageDeltaX = aLineOrPageDeltaX;
   wheelEvent.lineOrPageDeltaY = aLineOrPageDeltaY;
   wheelEvent.widget = widget;
 
-  wheelEvent.time = PR_Now() / 1000;
+  wheelEvent.mTime = PR_Now() / 1000;
 
   nsPresContext* presContext = GetPresContext();
   NS_ENSURE_TRUE(presContext, NS_ERROR_FAILURE);
 
   wheelEvent.refPoint = nsContentUtils::ToWidgetPoint(CSSPoint(aX, aY), offset, presContext);
 
   widget->DispatchInputEvent(&wheelEvent);
 
@@ -934,17 +934,17 @@ nsDOMWindowUtils::SendTouchEventCommon(c
   } else if (aType.EqualsLiteral("touchcancel")) {
     msg = eTouchCancel;
   } else {
     return NS_ERROR_UNEXPECTED;
   }
   WidgetTouchEvent event(true, msg, widget);
   event.modifiers = nsContentUtils::GetWidgetModifiers(aModifiers);
   event.widget = widget;
-  event.time = PR_Now();
+  event.mTime = PR_Now();
 
   nsPresContext* presContext = GetPresContext();
   if (!presContext) {
     return NS_ERROR_FAILURE;
   }
   event.touches.SetCapacity(aCount);
   for (uint32_t i = 0; i < aCount; ++i) {
     LayoutDeviceIntPoint pt =
@@ -1291,17 +1291,17 @@ nsDOMWindowUtils::SendSimpleGestureEvent
     return NS_ERROR_FAILURE;
   }
 
   WidgetSimpleGestureEvent event(true, msg, widget);
   event.modifiers = nsContentUtils::GetWidgetModifiers(aModifiers);
   event.direction = aDirection;
   event.delta = aDelta;
   event.clickCount = aClickCount;
-  event.time = PR_IntervalNow();
+  event.mTime = PR_IntervalNow();
 
   nsPresContext* presContext = GetPresContext();
   if (!presContext)
     return NS_ERROR_FAILURE;
 
   event.refPoint = nsContentUtils::ToWidgetPoint(CSSPoint(aX, aY), offset, presContext);
 
   nsEventStatus status;
@@ -1797,17 +1797,17 @@ nsDOMWindowUtils::DispatchDOMEventViaPre
 }
 
 static void
 InitEvent(WidgetGUIEvent& aEvent, LayoutDeviceIntPoint* aPt = nullptr)
 {
   if (aPt) {
     aEvent.refPoint = *aPt;
   }
-  aEvent.time = PR_IntervalNow();
+  aEvent.mTime = PR_IntervalNow();
 }
 
 NS_IMETHODIMP
 nsDOMWindowUtils::SendQueryContentEvent(uint32_t aType,
                                         uint32_t aOffset, uint32_t aLength,
                                         int32_t aX, int32_t aY,
                                         uint32_t aAdditionalFlags,
                                         nsIQueryContentEventResult **aResult)
--- a/dom/base/nsFocusManager.cpp
+++ b/dom/base/nsFocusManager.cpp
@@ -1960,18 +1960,18 @@ public:
   {
   }
 
   NS_IMETHOD Run()
   {
     InternalFocusEvent event(true, mEventMessage);
     event.mFlags.mBubbles = false;
     event.mFlags.mCancelable = false;
-    event.fromRaise = mWindowRaised;
-    event.isRefocus = mIsRefocus;
+    event.mFromRaise = mWindowRaised;
+    event.mIsRefocus = mIsRefocus;
     return EventDispatcher::Dispatch(mTarget, mContext, &event);
   }
 
   nsCOMPtr<nsISupports>   mTarget;
   RefPtr<nsPresContext> mContext;
   EventMessage            mEventMessage;
   bool                    mWindowRaised;
   bool                    mIsRefocus;
--- a/dom/base/nsGlobalWindow.cpp
+++ b/dom/base/nsGlobalWindow.cpp
@@ -3111,17 +3111,17 @@ nsGlobalWindow::PreHandleEvent(EventChai
     if (count++ % 100 == 0) {
       //Since the high bits seem to be zero's most of the time,
       //let's only take the lowest half of the point structure.
       int16_t myCoord[2];
 
       myCoord[0] = aVisitor.mEvent->refPoint.x;
       myCoord[1] = aVisitor.mEvent->refPoint.y;
       gEntropyCollector->RandomUpdate((void*)myCoord, sizeof(myCoord));
-      gEntropyCollector->RandomUpdate((void*)&(aVisitor.mEvent->time),
+      gEntropyCollector->RandomUpdate((void*)&(aVisitor.mEvent->mTime),
                                       sizeof(uint32_t));
     }
   } else if (msg == eResize && aVisitor.mEvent->IsTrusted()) {
     // QIing to window so that we can keep the old behavior also in case
     // a child window is handling resize.
     nsCOMPtr<nsPIDOMWindowInner> window =
       do_QueryInterface(aVisitor.mEvent->originalTarget);
     if (window) {
--- a/dom/base/nsStyleLinkElement.cpp
+++ b/dom/base/nsStyleLinkElement.cpp
@@ -214,17 +214,17 @@ nsStyleLinkElement::UpdateStyleSheet(nsI
 {
   if (aForceReload) {
     // We remove this stylesheet from the cache to load a new version.
     nsCOMPtr<nsIContent> thisContent;
     CallQueryInterface(this, getter_AddRefs(thisContent));
     nsCOMPtr<nsIDocument> doc = thisContent->IsInShadowTree() ?
       thisContent->OwnerDoc() : thisContent->GetUncomposedDoc();
     if (doc && doc->CSSLoader()->GetEnabled() &&
-        mStyleSheet && mStyleSheet->GetOriginalURI()) {
+        mStyleSheet && !mStyleSheet->IsInline()) {
       doc->CSSLoader()->ObsoleteSheet(mStyleSheet->GetOriginalURI());
     }
   }
   return DoUpdateStyleSheet(nullptr, nullptr, aObserver, aWillNotify,
                             aIsAlternate, aForceReload);
 }
 
 nsresult
--- a/dom/events/AnimationEvent.cpp
+++ b/dom/events/AnimationEvent.cpp
@@ -17,17 +17,17 @@ AnimationEvent::AnimationEvent(EventTarg
   : Event(aOwner, aPresContext,
           aEvent ? aEvent : new InternalAnimationEvent(false, eVoidEvent))
 {
   if (aEvent) {
     mEventIsInternal = false;
   }
   else {
     mEventIsInternal = true;
-    mEvent->time = PR_Now();
+    mEvent->mTime = PR_Now();
   }
 }
 
 NS_INTERFACE_MAP_BEGIN(AnimationEvent)
   NS_INTERFACE_MAP_ENTRY(nsIDOMAnimationEvent)
 NS_INTERFACE_MAP_END_INHERITING(Event)
 
 NS_IMPL_ADDREF_INHERITED(AnimationEvent, Event)
@@ -42,48 +42,48 @@ AnimationEvent::Constructor(const Global
 {
   nsCOMPtr<EventTarget> t = do_QueryInterface(aGlobal.GetAsSupports());
   RefPtr<AnimationEvent> e = new AnimationEvent(t, nullptr, nullptr);
   bool trusted = e->Init(t);
 
   e->InitEvent(aType, aParam.mBubbles, aParam.mCancelable);
 
   InternalAnimationEvent* internalEvent = e->mEvent->AsAnimationEvent();
-  internalEvent->animationName = aParam.mAnimationName;
-  internalEvent->elapsedTime = aParam.mElapsedTime;
-  internalEvent->pseudoElement = aParam.mPseudoElement;
+  internalEvent->mAnimationName = aParam.mAnimationName;
+  internalEvent->mElapsedTime = aParam.mElapsedTime;
+  internalEvent->mPseudoElement = aParam.mPseudoElement;
 
   e->SetTrusted(trusted);
   return e.forget();
 }
 
 NS_IMETHODIMP
 AnimationEvent::GetAnimationName(nsAString& aAnimationName)
 {
-  aAnimationName = mEvent->AsAnimationEvent()->animationName;
+  aAnimationName = mEvent->AsAnimationEvent()->mAnimationName;
   return NS_OK;
 }
 
 NS_IMETHODIMP
 AnimationEvent::GetElapsedTime(float* aElapsedTime)
 {
   *aElapsedTime = ElapsedTime();
   return NS_OK;
 }
 
 float
 AnimationEvent::ElapsedTime()
 {
-  return mEvent->AsAnimationEvent()->elapsedTime;
+  return mEvent->AsAnimationEvent()->mElapsedTime;
 }
 
 NS_IMETHODIMP
 AnimationEvent::GetPseudoElement(nsAString& aPseudoElement)
 {
-  aPseudoElement = mEvent->AsAnimationEvent()->pseudoElement;
+  aPseudoElement = mEvent->AsAnimationEvent()->mPseudoElement;
   return NS_OK;
 }
 
 } // namespace dom
 } // namespace mozilla
 
 using namespace mozilla;
 using namespace mozilla::dom;
--- a/dom/events/BeforeAfterKeyboardEvent.cpp
+++ b/dom/events/BeforeAfterKeyboardEvent.cpp
@@ -21,17 +21,17 @@ BeforeAfterKeyboardEvent::BeforeAfterKey
                                                                 eVoidEvent,
                                                                 nullptr))
 {
   MOZ_ASSERT(mEvent->mClass == eBeforeAfterKeyboardEventClass,
              "event type mismatch eBeforeAfterKeyboardEventClass");
 
   if (!aEvent) {
     mEventIsInternal = true;
-    mEvent->time = PR_Now();
+    mEvent->mTime = PR_Now();
   }
 }
 
 // static
 already_AddRefed<BeforeAfterKeyboardEvent>
 BeforeAfterKeyboardEvent::Constructor(
                             EventTarget* aOwner,
                             const nsAString& aType,
--- a/dom/events/ClipboardEvent.cpp
+++ b/dom/events/ClipboardEvent.cpp
@@ -17,17 +17,17 @@ ClipboardEvent::ClipboardEvent(EventTarg
                                InternalClipboardEvent* aEvent)
   : Event(aOwner, aPresContext,
           aEvent ? aEvent : new InternalClipboardEvent(false, eVoidEvent))
 {
   if (aEvent) {
     mEventIsInternal = false;
   } else {
     mEventIsInternal = true;
-    mEvent->time = PR_Now();
+    mEvent->mTime = PR_Now();
   }
 }
 
 NS_INTERFACE_MAP_BEGIN(ClipboardEvent)
   NS_INTERFACE_MAP_ENTRY(nsIDOMClipboardEvent)
 NS_INTERFACE_MAP_END_INHERITING(Event)
 
 NS_IMPL_ADDREF_INHERITED(ClipboardEvent, Event)
@@ -49,17 +49,17 @@ ClipboardEvent::InitClipboardEvent(const
 }
 
 void
 ClipboardEvent::InitClipboardEvent(const nsAString& aType, bool aCanBubble,
                                    bool aCancelable,
                                    DataTransfer* aClipboardData)
 {
   Event::InitEvent(aType, aCanBubble, aCancelable);
-  mEvent->AsClipboardEvent()->clipboardData = aClipboardData;
+  mEvent->AsClipboardEvent()->mClipboardData = aClipboardData;
 }
 
 already_AddRefed<ClipboardEvent>
 ClipboardEvent::Constructor(const GlobalObject& aGlobal,
                             const nsAString& aType,
                             const ClipboardEventInit& aParam,
                             ErrorResult& aRv)
 {
@@ -93,29 +93,29 @@ ClipboardEvent::GetClipboardData(nsIDOMD
   return NS_OK;
 }
 
 DataTransfer*
 ClipboardEvent::GetClipboardData()
 {
   InternalClipboardEvent* event = mEvent->AsClipboardEvent();
 
-  if (!event->clipboardData) {
+  if (!event->mClipboardData) {
     if (mEventIsInternal) {
-      event->clipboardData =
+      event->mClipboardData =
         new DataTransfer(ToSupports(this), eCopy, false, -1);
     } else {
-      event->clipboardData =
+      event->mClipboardData =
         new DataTransfer(ToSupports(this), event->mMessage,
                          event->mMessage == ePaste,
                          nsIClipboard::kGlobalClipboard);
     }
   }
 
-  return event->clipboardData;
+  return event->mClipboardData;
 }
 
 } // namespace dom
 } // namespace mozilla
 
 using namespace mozilla;
 using namespace mozilla::dom;
 
--- a/dom/events/CommandEvent.cpp
+++ b/dom/events/CommandEvent.cpp
@@ -13,17 +13,17 @@ namespace dom {
 
 CommandEvent::CommandEvent(EventTarget* aOwner,
                            nsPresContext* aPresContext,
                            WidgetCommandEvent* aEvent)
   : Event(aOwner, aPresContext,
           aEvent ? aEvent :
                    new WidgetCommandEvent(false, nullptr, nullptr, nullptr))
 {
-  mEvent->time = PR_Now();
+  mEvent->mTime = PR_Now();
   if (aEvent) {
     mEventIsInternal = false;
   } else {
     mEventIsInternal = true;
   }
 }
 
 NS_INTERFACE_MAP_BEGIN(CommandEvent)
--- a/dom/events/CompositionEvent.cpp
+++ b/dom/events/CompositionEvent.cpp
@@ -20,17 +20,17 @@ CompositionEvent::CompositionEvent(Event
 {
   NS_ASSERTION(mEvent->mClass == eCompositionEventClass,
                "event type mismatch");
 
   if (aEvent) {
     mEventIsInternal = false;
   } else {
     mEventIsInternal = true;
-    mEvent->time = PR_Now();
+    mEvent->mTime = PR_Now();
 
     // XXX compositionstart is cancelable in draft of DOM3 Events.
     //     However, it doesn't make sence for us, we cannot cancel composition
     //     when we sends compositionstart event.
     mEvent->mFlags.mCancelable = false;
   }
 
   // XXX Do we really need to duplicate the data value?
--- a/dom/events/DragEvent.cpp
+++ b/dom/events/DragEvent.cpp
@@ -19,17 +19,17 @@ DragEvent::DragEvent(EventTarget* aOwner
                aEvent ? aEvent :
                         new WidgetDragEvent(false, eVoidEvent, nullptr))
 {
   if (aEvent) {
     mEventIsInternal = false;
   }
   else {
     mEventIsInternal = true;
-    mEvent->time = PR_Now();
+    mEvent->mTime = PR_Now();
     mEvent->refPoint.x = mEvent->refPoint.y = 0;
     mEvent->AsMouseEvent()->inputSource = nsIDOMMouseEvent::MOZ_SOURCE_UNKNOWN;
   }
 }
 
 NS_IMPL_ADDREF_INHERITED(DragEvent, MouseEvent)
 NS_IMPL_RELEASE_INHERITED(DragEvent, MouseEvent)
 
--- a/dom/events/Event.cpp
+++ b/dom/events/Event.cpp
@@ -102,17 +102,17 @@ Event::ConstructorInit(EventTarget* aOwn
           }
           else {
             mEventIsInternal = true;
           }
           ...
         }
      */
     mEvent = new WidgetEvent(false, eVoidEvent);
-    mEvent->time = PR_Now();
+    mEvent->mTime = PR_Now();
   }
 
   InitPresContextData(aPresContext);
 }
 
 void
 Event::InitPresContextData(nsPresContext* aPresContext)
 {
@@ -166,23 +166,23 @@ NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(Ev
         break;
       case eDragEventClass: {
         WidgetDragEvent* dragEvent = tmp->mEvent->AsDragEvent();
         dragEvent->dataTransfer = nullptr;
         dragEvent->relatedTarget = nullptr;
         break;
       }
       case eClipboardEventClass:
-        tmp->mEvent->AsClipboardEvent()->clipboardData = nullptr;
+        tmp->mEvent->AsClipboardEvent()->mClipboardData = nullptr;
         break;
       case eMutationEventClass:
         tmp->mEvent->AsMutationEvent()->mRelatedNode = nullptr;
         break;
       case eFocusEventClass:
-        tmp->mEvent->AsFocusEvent()->relatedTarget = nullptr;
+        tmp->mEvent->AsFocusEvent()->mRelatedTarget = nullptr;
         break;
       default:
         break;
     }
   }
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mPresContext);
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mExplicitOriginalTarget);
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mOwner);
@@ -207,26 +207,26 @@ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(
         WidgetDragEvent* dragEvent = tmp->mEvent->AsDragEvent();
         NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mEvent->dataTransfer");
         cb.NoteXPCOMChild(dragEvent->dataTransfer);
         NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mEvent->relatedTarget");
         cb.NoteXPCOMChild(dragEvent->relatedTarget);
         break;
       }
       case eClipboardEventClass:
-        NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mEvent->clipboardData");
-        cb.NoteXPCOMChild(tmp->mEvent->AsClipboardEvent()->clipboardData);
+        NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mEvent->mClipboardData");
+        cb.NoteXPCOMChild(tmp->mEvent->AsClipboardEvent()->mClipboardData);
         break;
       case eMutationEventClass:
         NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mEvent->mRelatedNode");
         cb.NoteXPCOMChild(tmp->mEvent->AsMutationEvent()->mRelatedNode);
         break;
       case eFocusEventClass:
-        NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mEvent->relatedTarget");
-        cb.NoteXPCOMChild(tmp->mEvent->AsFocusEvent()->relatedTarget);
+        NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mEvent->mRelatedTarget");
+        cb.NoteXPCOMChild(tmp->mEvent->AsFocusEvent()->mRelatedTarget);
         break;
       default:
         break;
     }
   }
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPresContext)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mExplicitOriginalTarget)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mOwner)
@@ -454,17 +454,17 @@ Event::GetCancelable(bool* aCancelable)
 {
   *aCancelable = Cancelable();
   return NS_OK;
 }
 
 NS_IMETHODIMP
 Event::GetTimeStamp(uint64_t* aTimeStamp)
 {
-  *aTimeStamp = mEvent->time;
+  *aTimeStamp = mEvent->mTime;
   return NS_OK;
 }
 
 NS_IMETHODIMP
 Event::StopPropagation()
 {
   mEvent->StopPropagation();
   return NS_OK;
@@ -1059,20 +1059,20 @@ Event::DefaultPrevented(JSContext* aCx) 
   // this is called by chrome.
   return mEvent->DefaultPreventedByContent() || IsChrome(aCx);
 }
 
 double
 Event::TimeStamp() const
 {
   if (!sReturnHighResTimeStamp) {
-    return static_cast<double>(mEvent->time);
+    return static_cast<double>(mEvent->mTime);
   }
 
-  if (mEvent->timeStamp.IsNull()) {
+  if (mEvent->mTimeStamp.IsNull()) {
     return 0.0;
   }
 
   if (mIsMainThreadEvent) {
     if (NS_WARN_IF(!mOwner)) {
       return 0.0;
     }
 
@@ -1080,28 +1080,28 @@ Event::TimeStamp() const
     if (NS_WARN_IF(!win)) {
       return 0.0;
     }
     nsPerformance* perf = win->GetPerformance();
     if (NS_WARN_IF(!perf)) {
       return 0.0;
     }
 
-    return perf->GetDOMTiming()->TimeStampToDOMHighRes(mEvent->timeStamp);
+    return perf->GetDOMTiming()->TimeStampToDOMHighRes(mEvent->mTimeStamp);
   }
 
   // For dedicated workers, we should make times relative to the navigation
   // start of the document that created the worker, which is the same as the
   // timebase for performance.now().
   workers::WorkerPrivate* workerPrivate =
     workers::GetCurrentThreadWorkerPrivate();
   MOZ_ASSERT(workerPrivate);
 
   TimeDuration duration =
-    mEvent->timeStamp - workerPrivate->NowBaseTimeStamp();
+    mEvent->mTimeStamp - workerPrivate->NowBaseTimeStamp();
   return duration.ToMilliseconds();
 }
 
 bool
 Event::GetPreventDefault() const
 {
   nsCOMPtr<nsPIDOMWindowInner> win(do_QueryInterface(mOwner));
   if (win) {
--- a/dom/events/EventStateManager.cpp
+++ b/dom/events/EventStateManager.cpp
@@ -46,16 +46,17 @@
 #include "nsFrameSelection.h"
 #include "nsPIDOMWindow.h"
 #include "nsPIWindowRoot.h"
 #include "nsIWebNavigation.h"
 #include "nsIContentViewer.h"
 #include "nsFrameManager.h"
 #include "nsITabChild.h"
 #include "nsPluginFrame.h"
+#include "nsMenuPopupFrame.h"
 
 #include "nsIDOMXULElement.h"
 #include "nsIDOMKeyEvent.h"
 #include "nsIObserverService.h"
 #include "nsIDocShell.h"
 #include "nsIDOMWheelEvent.h"
 #include "nsIDOMUIEvent.h"
 #include "nsIMozBrowserFrame.h"
@@ -2293,18 +2294,18 @@ EventStateManager::SendLineScrollEvent(n
   }
 
   WidgetMouseScrollEvent event(aEvent->IsTrusted(),
                                eLegacyMouseLineOrPageScroll, aEvent->widget);
   event.mFlags.mDefaultPrevented = aState.mDefaultPrevented;
   event.mFlags.mDefaultPreventedByContent = aState.mDefaultPreventedByContent;
   event.refPoint = aEvent->refPoint;
   event.widget = aEvent->widget;
-  event.time = aEvent->time;
-  event.timeStamp = aEvent->timeStamp;
+  event.mTime = aEvent->mTime;
+  event.mTimeStamp = aEvent->mTimeStamp;
   event.modifiers = aEvent->modifiers;
   event.buttons = aEvent->buttons;
   event.isHorizontal = (aDeltaDirection == DELTA_DIRECTION_X);
   event.delta = aDelta;
   event.inputSource = aEvent->inputSource;
 
   nsEventStatus status = nsEventStatus_eIgnore;
   EventDispatcher::Dispatch(targetContent, aTargetFrame->PresContext(),
@@ -2333,18 +2334,18 @@ EventStateManager::SendPixelScrollEvent(
   }
 
   WidgetMouseScrollEvent event(aEvent->IsTrusted(),
                                eLegacyMousePixelScroll, aEvent->widget);
   event.mFlags.mDefaultPrevented = aState.mDefaultPrevented;
   event.mFlags.mDefaultPreventedByContent = aState.mDefaultPreventedByContent;
   event.refPoint = aEvent->refPoint;
   event.widget = aEvent->widget;
-  event.time = aEvent->time;
-  event.timeStamp = aEvent->timeStamp;
+  event.mTime = aEvent->mTime;
+  event.mTimeStamp = aEvent->mTimeStamp;
   event.modifiers = aEvent->modifiers;
   event.buttons = aEvent->buttons;
   event.isHorizontal = (aDeltaDirection == DELTA_DIRECTION_X);
   event.delta = aPixelDelta;
   event.inputSource = aEvent->inputSource;
 
   nsEventStatus status = nsEventStatus_eIgnore;
   EventDispatcher::Dispatch(targetContent, aTargetFrame->PresContext(),
@@ -2433,16 +2434,20 @@ EventStateManager::ComputeScrollTarget(n
       // action.
       if (aOptions & INCLUDE_PLUGIN_AS_TARGET) {
         nsPluginFrame* pluginFrame = do_QueryFrame(scrollFrame);
         if (pluginFrame &&
             pluginFrame->WantsToHandleWheelEventAsDefaultAction()) {
           return scrollFrame;
         }
       }
+      nsMenuPopupFrame* menuPopupFrame = do_QueryFrame(scrollFrame);
+      if (menuPopupFrame) {
+        return nullptr;
+      }
       continue;
     }
 
     nsIFrame* frameToScroll = do_QueryFrame(scrollableFrame);
     MOZ_ASSERT(frameToScroll);
 
     // Don't scroll vertically by mouse-wheel on a single-line text control.
     if (checkIfScrollableY) {
@@ -4633,18 +4638,18 @@ EventStateManager::CheckForAndDispatchCl
       aEvent->button == WidgetMouseEvent::eRightButton);
 
     WidgetMouseEvent event(aEvent->IsTrusted(), eMouseClick,
                            aEvent->widget, WidgetMouseEvent::eReal);
     event.refPoint = aEvent->refPoint;
     event.clickCount = aEvent->clickCount;
     event.modifiers = aEvent->modifiers;
     event.buttons = aEvent->buttons;
-    event.time = aEvent->time;
-    event.timeStamp = aEvent->timeStamp;
+    event.mTime = aEvent->mTime;
+    event.mTimeStamp = aEvent->mTimeStamp;
     event.mFlags.mNoContentDispatch = notDispatchToContents;
     event.button = aEvent->button;
     event.inputSource = aEvent->inputSource;
 
     nsCOMPtr<nsIPresShell> presShell = mPresContext->GetPresShell();
     if (presShell) {
       nsCOMPtr<nsIContent> mouseContent = GetEventTargetContent(aEvent);
       // Click events apply to *elements* not nodes. At this point the target
--- a/dom/events/FocusEvent.cpp
+++ b/dom/events/FocusEvent.cpp
@@ -18,44 +18,44 @@ FocusEvent::FocusEvent(EventTarget* aOwn
                        InternalFocusEvent* aEvent)
   : UIEvent(aOwner, aPresContext,
             aEvent ? aEvent : new InternalFocusEvent(false, eFocus))
 {
   if (aEvent) {
     mEventIsInternal = false;
   } else {
     mEventIsInternal = true;
-    mEvent->time = PR_Now();
+    mEvent->mTime = PR_Now();
   }
 }
 
 NS_IMETHODIMP
 FocusEvent::GetRelatedTarget(nsIDOMEventTarget** aRelatedTarget)
 {
   NS_ENSURE_ARG_POINTER(aRelatedTarget);
   NS_IF_ADDREF(*aRelatedTarget = GetRelatedTarget());
   return NS_OK;
 }
 
 EventTarget*
 FocusEvent::GetRelatedTarget()
 {
-  return mEvent->AsFocusEvent()->relatedTarget;
+  return mEvent->AsFocusEvent()->mRelatedTarget;
 }
 
 void
 FocusEvent::InitFocusEvent(const nsAString& aType,
                            bool aCanBubble,
                            bool aCancelable,
                            nsGlobalWindow* aView,
                            int32_t aDetail,
                            EventTarget* aRelatedTarget)
 {
   UIEvent::InitUIEvent(aType, aCanBubble, aCancelable, aView, aDetail);
-  mEvent->AsFocusEvent()->relatedTarget = aRelatedTarget;
+  mEvent->AsFocusEvent()->mRelatedTarget = aRelatedTarget;
 }
 
 already_AddRefed<FocusEvent>
 FocusEvent::Constructor(const GlobalObject& aGlobal,
                         const nsAString& aType,
                         const FocusEventInit& aParam,
                         ErrorResult& aRv)
 {
--- a/dom/events/InputEvent.cpp
+++ b/dom/events/InputEvent.cpp
@@ -20,17 +20,17 @@ InputEvent::InputEvent(EventTarget* aOwn
 {
   NS_ASSERTION(mEvent->mClass == eEditorInputEventClass,
                "event type mismatch");
 
   if (aEvent) {
     mEventIsInternal = false;
   } else {
     mEventIsInternal = true;
-    mEvent->time = PR_Now();
+    mEvent->mTime = PR_Now();
   }
 }
 
 NS_IMPL_ADDREF_INHERITED(InputEvent, UIEvent)
 NS_IMPL_RELEASE_INHERITED(InputEvent, UIEvent)
 
 NS_INTERFACE_MAP_BEGIN(InputEvent)
 NS_INTERFACE_MAP_END_INHERITING(UIEvent)
--- a/dom/events/KeyboardEvent.cpp
+++ b/dom/events/KeyboardEvent.cpp
@@ -20,17 +20,17 @@ KeyboardEvent::KeyboardEvent(EventTarget
   , mInitializedByCtor(false)
   , mInitializedWhichValue(0)
 {
   if (aEvent) {
     mEventIsInternal = false;
   }
   else {
     mEventIsInternal = true;
-    mEvent->time = PR_Now();
+    mEvent->mTime = PR_Now();
     mEvent->AsKeyboardEvent()->mKeyNameIndex = KEY_NAME_INDEX_USE_STRING;
   }
 }
 
 NS_IMPL_ADDREF_INHERITED(KeyboardEvent, UIEvent)
 NS_IMPL_RELEASE_INHERITED(KeyboardEvent, UIEvent)
 
 NS_INTERFACE_MAP_BEGIN(KeyboardEvent)
--- a/dom/events/MouseEvent.cpp
+++ b/dom/events/MouseEvent.cpp
@@ -26,17 +26,17 @@ MouseEvent::MouseEvent(EventTarget* aOwn
   // DOM event.
 
   WidgetMouseEvent* mouseEvent = mEvent->AsMouseEvent();
   if (aEvent) {
     mEventIsInternal = false;
   }
   else {
     mEventIsInternal = true;
-    mEvent->time = PR_Now();
+    mEvent->mTime = PR_Now();
     mEvent->refPoint.x = mEvent->refPoint.y = 0;
     mouseEvent->inputSource = nsIDOMMouseEvent::MOZ_SOURCE_UNKNOWN;
   }
 
   if (mouseEvent) {
     MOZ_ASSERT(mouseEvent->reason != WidgetMouseEvent::eSynthesized,
                "Don't dispatch DOM events from synthesized mouse events");
     mDetail = mouseEvent->clickCount;
--- a/dom/events/MouseScrollEvent.cpp
+++ b/dom/events/MouseScrollEvent.cpp
@@ -18,17 +18,17 @@ MouseScrollEvent::MouseScrollEvent(Event
   : MouseEvent(aOwner, aPresContext,
                aEvent ? aEvent :
                         new WidgetMouseScrollEvent(false, eVoidEvent, nullptr))
 {
   if (aEvent) {
     mEventIsInternal = false;
   } else {
     mEventIsInternal = true;
-    mEvent->time = PR_Now();
+    mEvent->mTime = PR_Now();
     mEvent->refPoint.x = mEvent->refPoint.y = 0;
     static_cast<WidgetMouseEventBase*>(mEvent)->inputSource =
       nsIDOMMouseEvent::MOZ_SOURCE_UNKNOWN;
   }
 
   mDetail = mEvent->AsMouseScrollEvent()->delta;
 }
 
--- a/dom/events/PointerEvent.cpp
+++ b/dom/events/PointerEvent.cpp
@@ -23,17 +23,17 @@ PointerEvent::PointerEvent(EventTarget* 
   NS_ASSERTION(mEvent->mClass == ePointerEventClass,
                "event type mismatch ePointerEventClass");
 
   WidgetMouseEvent* mouseEvent = mEvent->AsMouseEvent();
   if (aEvent) {
     mEventIsInternal = false;
   } else {
     mEventIsInternal = true;
-    mEvent->time = PR_Now();
+    mEvent->mTime = PR_Now();
     mEvent->refPoint.x = mEvent->refPoint.y = 0;
     mouseEvent->inputSource = nsIDOMMouseEvent::MOZ_SOURCE_UNKNOWN;
   }
 }
 
 static uint16_t
 ConvertStringToPointerType(const nsAString& aPointerTypeArg)
 {
--- a/dom/events/SimpleGestureEvent.cpp
+++ b/dom/events/SimpleGestureEvent.cpp
@@ -21,17 +21,17 @@ SimpleGestureEvent::SimpleGestureEvent(E
 {
   NS_ASSERTION(mEvent->mClass == eSimpleGestureEventClass,
                "event type mismatch");
 
   if (aEvent) {
     mEventIsInternal = false;
   } else {
     mEventIsInternal = true;
-    mEvent->time = PR_Now();
+    mEvent->mTime = PR_Now();
     mEvent->refPoint.x = mEvent->refPoint.y = 0;
     static_cast<WidgetMouseEventBase*>(mEvent)->inputSource =
       nsIDOMMouseEvent::MOZ_SOURCE_UNKNOWN;
   }
 }
 
 NS_IMPL_ADDREF_INHERITED(SimpleGestureEvent, MouseEvent)
 NS_IMPL_RELEASE_INHERITED(SimpleGestureEvent, MouseEvent)
--- a/dom/events/TextComposition.cpp
+++ b/dom/events/TextComposition.cpp
@@ -115,18 +115,18 @@ TextComposition::CloneAndDispatchAs(
 {
   MOZ_RELEASE_ASSERT(!mTabParent);
 
   MOZ_ASSERT(IsValidStateForComposition(aCompositionEvent->widget),
              "Should be called only when it's safe to dispatch an event");
 
   WidgetCompositionEvent compositionEvent(aCompositionEvent->IsTrusted(),
                                           aMessage, aCompositionEvent->widget);
-  compositionEvent.time = aCompositionEvent->time;
-  compositionEvent.timeStamp = aCompositionEvent->timeStamp;
+  compositionEvent.mTime = aCompositionEvent->mTime;
+  compositionEvent.mTimeStamp = aCompositionEvent->mTimeStamp;
   compositionEvent.mData = aCompositionEvent->mData;
   compositionEvent.mNativeIMEContext = aCompositionEvent->mNativeIMEContext;
   compositionEvent.mOriginalMessage = aCompositionEvent->mMessage;
   compositionEvent.mFlags.mIsSynthesizedForTests =
     aCompositionEvent->mFlags.mIsSynthesizedForTests;
 
   nsEventStatus dummyStatus = nsEventStatus_eConsumeNoDefault;
   nsEventStatus* status = aStatus ? aStatus : &dummyStatus;
--- a/dom/events/TouchEvent.cpp
+++ b/dom/events/TouchEvent.cpp
@@ -70,17 +70,17 @@ TouchEvent::TouchEvent(EventTarget* aOwn
     mEventIsInternal = false;
 
     for (uint32_t i = 0; i < aEvent->touches.Length(); ++i) {
       Touch* touch = aEvent->touches[i];
       touch->InitializePoints(mPresContext, aEvent);
     }
   } else {
     mEventIsInternal = true;
-    mEvent->time = PR_Now();
+    mEvent->mTime = PR_Now();
   }
 }
 
 NS_IMPL_CYCLE_COLLECTION_INHERITED(TouchEvent, UIEvent,
                                    mTouches,
                                    mTargetTouches,
                                    mChangedTouches)
 
--- a/dom/events/TransitionEvent.cpp
+++ b/dom/events/TransitionEvent.cpp
@@ -17,17 +17,17 @@ TransitionEvent::TransitionEvent(EventTa
   : Event(aOwner, aPresContext,
           aEvent ? aEvent : new InternalTransitionEvent(false, eVoidEvent))
 {
   if (aEvent) {
     mEventIsInternal = false;
   }
   else {
     mEventIsInternal = true;
-    mEvent->time = PR_Now();
+    mEvent->mTime = PR_Now();
   }
 }
 
 NS_INTERFACE_MAP_BEGIN(TransitionEvent)
   NS_INTERFACE_MAP_ENTRY(nsIDOMTransitionEvent)
 NS_INTERFACE_MAP_END_INHERITING(Event)
 
 NS_IMPL_ADDREF_INHERITED(TransitionEvent, Event)
@@ -42,48 +42,48 @@ TransitionEvent::Constructor(const Globa
 {
   nsCOMPtr<EventTarget> t = do_QueryInterface(aGlobal.GetAsSupports());
   RefPtr<TransitionEvent> e = new TransitionEvent(t, nullptr, nullptr);
   bool trusted = e->Init(t);
 
   e->InitEvent(aType, aParam.mBubbles, aParam.mCancelable);
 
   InternalTransitionEvent* internalEvent = e->mEvent->AsTransitionEvent();
-  internalEvent->propertyName = aParam.mPropertyName;
-  internalEvent->elapsedTime = aParam.mElapsedTime;
-  internalEvent->pseudoElement = aParam.mPseudoElement;
+  internalEvent->mPropertyName = aParam.mPropertyName;
+  internalEvent->mElapsedTime = aParam.mElapsedTime;
+  internalEvent->mPseudoElement = aParam.mPseudoElement;
 
   e->SetTrusted(trusted);
   return e.forget();
 }
 
 NS_IMETHODIMP
 TransitionEvent::GetPropertyName(nsAString& aPropertyName)
 {
-  aPropertyName = mEvent->AsTransitionEvent()->propertyName;
+  aPropertyName = mEvent->AsTransitionEvent()->mPropertyName;
   return NS_OK;
 }
 
 NS_IMETHODIMP
 TransitionEvent::GetElapsedTime(float* aElapsedTime)
 {
   *aElapsedTime = ElapsedTime();
   return NS_OK;
 }
 
 float
 TransitionEvent::ElapsedTime()
 {
-  return mEvent->AsTransitionEvent()->elapsedTime;
+  return mEvent->AsTransitionEvent()->mElapsedTime;
 }
 
 NS_IMETHODIMP
 TransitionEvent::GetPseudoElement(nsAString& aPseudoElement)
 {
-  aPseudoElement = mEvent->AsTransitionEvent()->pseudoElement;
+  aPseudoElement = mEvent->AsTransitionEvent()->mPseudoElement;
   return NS_OK;
 }
 
 } // namespace dom
 } // namespace mozilla
 
 using namespace mozilla;
 using namespace mozilla::dom;
--- a/dom/events/UIEvent.cpp
+++ b/dom/events/UIEvent.cpp
@@ -37,25 +37,25 @@ UIEvent::UIEvent(EventTarget* aOwner,
   , mIsPointerLocked(EventStateManager::sIsPointerLocked)
   , mLastClientPoint(EventStateManager::sLastClientPoint)
 {
   if (aEvent) {
     mEventIsInternal = false;
   }
   else {
     mEventIsInternal = true;
-    mEvent->time = PR_Now();
+    mEvent->mTime = PR_Now();
   }
   
   // Fill mDetail and mView according to the mEvent (widget-generated
   // event) we've got
   switch(mEvent->mClass) {
     case eUIEventClass:
     {
-      mDetail = mEvent->AsUIEvent()->detail;
+      mDetail = mEvent->AsUIEvent()->mDetail;
       break;
     }
 
     case eScrollPortEventClass:
     {
       InternalScrollPortEvent* scrollEvent = mEvent->AsScrollPortEvent();
       mDetail = (int32_t)scrollEvent->orient;
       break;
--- a/dom/events/WheelEvent.cpp
+++ b/dom/events/WheelEvent.cpp
@@ -25,17 +25,17 @@ WheelEvent::WheelEvent(EventTarget* aOwn
     // device pixels.  However, JS contents need the delta values in CSS pixels.
     // We should store the value of mAppUnitsPerDevPixel here because
     // it might be changed by changing zoom or something.
     if (aWheelEvent->deltaMode == nsIDOMWheelEvent::DOM_DELTA_PIXEL) {
       mAppUnitsPerDevPixel = aPresContext->AppUnitsPerDevPixel();
     }
   } else {
     mEventIsInternal = true;
-    mEvent->time = PR_Now();
+    mEvent->mTime = PR_Now();
     mEvent->refPoint.x = mEvent->refPoint.y = 0;
     mEvent->AsWheelEvent()->inputSource = nsIDOMMouseEvent::MOZ_SOURCE_UNKNOWN;
   }
 }
 
 NS_IMPL_ADDREF_INHERITED(WheelEvent, MouseEvent)
 NS_IMPL_RELEASE_INHERITED(WheelEvent, MouseEvent)
 
--- a/dom/events/XULCommandEvent.cpp
+++ b/dom/events/XULCommandEvent.cpp
@@ -17,17 +17,17 @@ XULCommandEvent::XULCommandEvent(EventTa
             aEvent ? aEvent :
                      new WidgetInputEvent(false, eVoidEvent, nullptr))
 {
   if (aEvent) {
     mEventIsInternal = false;
   }
   else {
     mEventIsInternal = true;
-    mEvent->time = PR_Now();
+    mEvent->mTime = PR_Now();
   }
 }
 
 NS_IMPL_ADDREF_INHERITED(XULCommandEvent, UIEvent)
 NS_IMPL_RELEASE_INHERITED(XULCommandEvent, UIEvent)
 
 NS_IMPL_CYCLE_COLLECTION_INHERITED(XULCommandEvent, UIEvent,
                                    mSourceEvent)
--- a/dom/html/HTMLButtonElement.cpp
+++ b/dom/html/HTMLButtonElement.cpp
@@ -248,17 +248,17 @@ HTMLButtonElement::PostHandleEvent(Event
   }
 
   if (aVisitor.mEventStatus != nsEventStatus_eConsumeNoDefault) {
     WidgetMouseEvent* mouseEvent = aVisitor.mEvent->AsMouseEvent();
     if (mouseEvent && mouseEvent->IsLeftClickEvent()) {
       // DOMActive event should be trusted since the activation is actually
       // occurred even if the cause is an untrusted click event.
       InternalUIEvent actEvent(true, eLegacyDOMActivate, mouseEvent);
-      actEvent.detail = 1;
+      actEvent.mDetail = 1;
 
       nsCOMPtr<nsIPresShell> shell = aVisitor.mPresContext->GetPresShell();
       if (shell) {
         nsEventStatus status = nsEventStatus_eIgnore;
         mInInternalActivate = true;
         shell->HandleDOMEventWithTarget(this, &actEvent, &status);
         mInInternalActivate = false;
 
--- a/dom/html/HTMLInputElement.cpp
+++ b/dom/html/HTMLInputElement.cpp
@@ -3776,17 +3776,17 @@ HTMLInputElement::PostHandleEvent(EventC
       !IsSingleLineTextControl(true) &&
       mType != NS_FORM_INPUT_NUMBER) {
     WidgetMouseEvent* mouseEvent = aVisitor.mEvent->AsMouseEvent();
     if (mouseEvent && mouseEvent->IsLeftClickEvent() &&
         !ShouldPreventDOMActivateDispatch(aVisitor.mEvent->originalTarget)) {
       // DOMActive event should be trusted since the activation is actually
       // occurred even if the cause is an untrusted click event.
       InternalUIEvent actEvent(true, eLegacyDOMActivate, mouseEvent);
-      actEvent.detail = 1;
+      actEvent.mDetail = 1;
 
       nsCOMPtr<nsIPresShell> shell = aVisitor.mPresContext->GetPresShell();
       if (shell) {
         nsEventStatus status = nsEventStatus_eIgnore;
         mInInternalActivate = true;
         rv = shell->HandleDOMEventWithTarget(this, &actEvent, &status);
         mInInternalActivate = false;
 
@@ -3897,17 +3897,17 @@ HTMLInputElement::PostHandleEvent(EventC
       switch (aVisitor.mEvent->mMessage) {
         case eFocus: {
           // see if we should select the contents of the textbox. This happens
           // for text and password fields when the field was focused by the
           // keyboard or a navigation, the platform allows it, and it wasn't
           // just because we raised a window.
           nsIFocusManager* fm = nsFocusManager::GetFocusManager();
           if (fm && IsSingleLineTextControl(false) &&
-              !aVisitor.mEvent->AsFocusEvent()->fromRaise &&
+              !aVisitor.mEvent->AsFocusEvent()->mFromRaise &&
               SelectTextFieldOnFocus()) {
             nsIDocument* document = GetComposedDoc();
             if (document) {
               uint32_t lastFocusMethod;
               fm->GetLastFocusMethod(document->GetWindow(), &lastFocusMethod);
               if (lastFocusMethod &
                   (nsIFocusManager::FLAG_BYKEY | nsIFocusManager::FLAG_BYMOVEFOCUS)) {
                 RefPtr<nsPresContext> presContext =
--- a/dom/ipc/TabParent.cpp
+++ b/dom/ipc/TabParent.cpp
@@ -2754,17 +2754,17 @@ TabParent::InjectTouchEvent(const nsAStr
 
   nsCOMPtr<nsIWidget> widget = GetWidget();
   if (!widget) {
     return NS_ERROR_FAILURE;
   }
 
   WidgetTouchEvent event(true, msg, widget);
   event.modifiers = aModifiers;
-  event.time = PR_IntervalNow();
+  event.mTime = PR_IntervalNow();
 
   nsCOMPtr<nsIContent> content = do_QueryInterface(mFrameElement);
   if (!content || !content->OwnerDoc()) {
     return NS_ERROR_FAILURE;
   }
 
   nsIDocument* doc = content->OwnerDoc();
   if (!doc || !doc->GetShell()) {
new file mode 100644
--- /dev/null
+++ b/dom/media/mediasource/AutoTaskQueue.h
@@ -0,0 +1,58 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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 MOZILLA_AUTOTASKQUEUE_H_
+#define MOZILLA_AUTOTASKQUEUE_H_
+
+#include "mozilla/RefPtr.h"
+#include "mozilla/TaskQueue.h"
+
+namespace mozilla {
+
+// A convenience TaskQueue not requiring explicit shutdown.
+class AutoTaskQueue : public AbstractThread
+{
+public:
+  explicit AutoTaskQueue(already_AddRefed<SharedThreadPool> aPool, bool aSupportsTailDispatch = false)
+  : AbstractThread(aSupportsTailDispatch)
+  , mTaskQueue(new TaskQueue(Move(aPool), aSupportsTailDispatch))
+  {}
+
+  TaskDispatcher& TailDispatcher() override
+  {
+    return mTaskQueue->TailDispatcher();
+  }
+
+  void Dispatch(already_AddRefed<nsIRunnable> aRunnable,
+                DispatchFailureHandling aFailureHandling = AssertDispatchSuccess,
+                DispatchReason aReason = NormalDispatch) override
+  {
+    mTaskQueue->Dispatch(Move(aRunnable), aFailureHandling, aReason);
+  }
+
+  // Blocks until all tasks finish executing.
+  void AwaitIdle() { mTaskQueue->AwaitIdle(); }
+
+  bool IsEmpty() { return mTaskQueue->IsEmpty(); }
+
+  // Returns true if the current thread is currently running a Runnable in
+  // the task queue.
+  bool IsCurrentThreadIn() override { return mTaskQueue->IsCurrentThreadIn(); }
+
+private:
+  ~AutoTaskQueue()
+  {
+    RefPtr<TaskQueue> taskqueue = mTaskQueue;
+    nsCOMPtr<nsIRunnable> task =
+      NS_NewRunnableFunction([taskqueue]() { taskqueue->BeginShutdown(); });
+    AbstractThread::MainThread()->Dispatch(task.forget());
+  }
+  RefPtr<TaskQueue> mTaskQueue;
+};
+
+} // namespace mozilla
+
+#endif
--- a/dom/media/mediasource/MediaSource.cpp
+++ b/dom/media/mediasource/MediaSource.cpp
@@ -476,26 +476,16 @@ MediaSource::DurationChange(double aOldD
   if (aNewDuration < aOldDuration) {
     // Remove all buffered data from aNewDuration.
     mSourceBuffers->RangeRemoval(aNewDuration, PositiveInfinity<double>());
   }
   // TODO: If partial audio frames/text cues exist, clamp duration based on mSourceBuffers.
 }
 
 void
-MediaSource::NotifyEvicted(double aStart, double aEnd)
-{
-  MOZ_ASSERT(NS_IsMainThread());
-  MSE_DEBUG("NotifyEvicted(aStart=%f, aEnd=%f)", aStart, aEnd);
-  // Cycle through all SourceBuffers and tell them to evict data in
-  // the given range.
-  mSourceBuffers->Evict(aStart, aEnd);
-}
-
-void
 MediaSource::GetMozDebugReaderData(nsAString& aString)
 {
   mDecoder->GetMozDebugReaderData(aString);
 }
 
 nsPIDOMWindowInner*
 MediaSource::GetParentObject() const
 {
--- a/dom/media/mediasource/MediaSource.h
+++ b/dom/media/mediasource/MediaSource.h
@@ -90,21 +90,16 @@ public:
     return mDecoder;
   }
 
   nsIPrincipal* GetPrincipal()
   {
     return mPrincipal;
   }
 
-  // Called by SourceBuffers to notify this MediaSource that data has
-  // been evicted from the buffered data. The start and end times
-  // that were evicted are provided.
-  void NotifyEvicted(double aStart, double aEnd);
-
   // Returns a string describing the state of the MediaSource internal
   // buffered data. Used for debugging purposes.
   void GetMozDebugReaderData(nsAString& aString);
 
 private:
   // MediaSourceDecoder uses DurationChange to set the duration
   // without hitting the checks in SetDuration.
   friend class mozilla::MediaSourceDecoder;
--- a/dom/media/mediasource/MediaSourceDemuxer.cpp
+++ b/dom/media/mediasource/MediaSourceDemuxer.cpp
@@ -16,18 +16,18 @@
 
 namespace mozilla {
 
 typedef TrackInfo::TrackType TrackType;
 using media::TimeUnit;
 using media::TimeIntervals;
 
 MediaSourceDemuxer::MediaSourceDemuxer()
-  : mTaskQueue(new TaskQueue(GetMediaThreadPool(MediaThreadType::PLAYBACK),
-                             /* aSupportsTailDispatch = */ false))
+  : mTaskQueue(new AutoTaskQueue(GetMediaThreadPool(MediaThreadType::PLAYBACK),
+                                 /* aSupportsTailDispatch = */ false))
   , mMonitor("MediaSourceDemuxer")
 {
   MOZ_ASSERT(NS_IsMainThread());
 }
 
 // Due to inaccuracies in determining buffer end
 // frames (Bug 1065207). This value is based on the end of frame
 // default value used in Blink, kDefaultBufferDurationInMs.
@@ -245,18 +245,16 @@ MediaSourceDemuxer::GetManager(TrackType
     default:
       return nullptr;
   }
 }
 
 MediaSourceDemuxer::~MediaSourceDemuxer()
 {
   mInitPromise.RejectIfExists(DemuxerFailureReason::SHUTDOWN, __func__);
-  mTaskQueue->BeginShutdown();
-  mTaskQueue = nullptr;
 }
 
 void
 MediaSourceDemuxer::GetMozDebugReaderData(nsAString& aString)
 {
   MonitorAutoLock mon(mMonitor);
   nsAutoCString result;
   result += nsPrintfCString("Dumping data for demuxer %p:\n", this);
--- a/dom/media/mediasource/MediaSourceDemuxer.h
+++ b/dom/media/mediasource/MediaSourceDemuxer.h
@@ -5,17 +5,17 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #if !defined(MediaSourceDemuxer_h_)
 #define MediaSourceDemuxer_h_
 
 #include "mozilla/Atomics.h"
 #include "mozilla/Maybe.h"
 #include "mozilla/Monitor.h"
-#include "mozilla/TaskQueue.h"
+#include "AutoTaskQueue.h"
 
 #include "MediaDataDemuxer.h"
 #include "MediaDecoderReader.h"
 #include "MediaResource.h"
 #include "MediaSource.h"
 #include "TrackBuffersManager.h"
 
 namespace mozilla {
@@ -42,17 +42,17 @@ public:
 
   bool ShouldComputeStartTime() const override { return false; }
 
   void NotifyDataArrived() override;
 
   /* interface for TrackBuffersManager */
   void AttachSourceBuffer(TrackBuffersManager* aSourceBuffer);
   void DetachSourceBuffer(TrackBuffersManager* aSourceBuffer);
-  TaskQueue* GetTaskQueue() { return mTaskQueue; }
+  AutoTaskQueue* GetTaskQueue() { return mTaskQueue; }
 
   // Returns a string describing the state of the MediaSource internal
   // buffered data. Used for debugging purposes.
   void GetMozDebugReaderData(nsAString& aString);
 
   void AddSizeOfResources(MediaSourceDecoder::ResourceSizes* aSizes);
 
   // Gap allowed between frames.
@@ -68,17 +68,17 @@ private:
   TrackInfo* GetTrackInfo(TrackInfo::TrackType);
   void DoAttachSourceBuffer(TrackBuffersManager* aSourceBuffer);
   void DoDetachSourceBuffer(TrackBuffersManager* aSourceBuffer);
   bool OnTaskQueue()
   {
     return !GetTaskQueue() || GetTaskQueue()->IsCurrentThreadIn();
   }
 
-  RefPtr<TaskQueue> mTaskQueue;
+  RefPtr<AutoTaskQueue> mTaskQueue;
   nsTArray<RefPtr<MediaSourceTrackDemuxer>> mDemuxers;
 
   nsTArray<RefPtr<TrackBuffersManager>> mSourceBuffers;
 
   MozPromiseHolder<InitPromise> mInitPromise;
 
   // Monitor to protect members below across multiple threads.
   mutable Monitor mMonitor;
--- a/dom/media/mediasource/SourceBuffer.cpp
+++ b/dom/media/mediasource/SourceBuffer.cpp
@@ -28,92 +28,95 @@ class JSObject;
 
 extern mozilla::LogModule* GetMediaSourceLog();
 extern mozilla::LogModule* GetMediaSourceAPILog();
 
 #define MSE_DEBUG(arg, ...) MOZ_LOG(GetMediaSourceLog(), mozilla::LogLevel::Debug, ("SourceBuffer(%p:%s)::%s: " arg, this, mType.get(), __func__, ##__VA_ARGS__))
 #define MSE_DEBUGV(arg, ...) MOZ_LOG(GetMediaSourceLog(), mozilla::LogLevel::Verbose, ("SourceBuffer(%p:%s)::%s: " arg, this, mType.get(), __func__, ##__VA_ARGS__))
 #define MSE_API(arg, ...) MOZ_LOG(GetMediaSourceAPILog(), mozilla::LogLevel::Debug, ("SourceBuffer(%p:%s)::%s: " arg, this, mType.get(), __func__, ##__VA_ARGS__))
 
+#if defined(MOZ_GONK_MEDIACODEC) || defined(XP_WIN) || defined(MOZ_APPLEMEDIA) || defined(MOZ_FFMPEG)
+#define MP4_READER_DORMANT_HEURISTIC
+#else
+#undef MP4_READER_DORMANT_HEURISTIC
+#endif
+
 namespace mozilla {
 
 using media::TimeUnit;
+typedef SourceBufferAttributes::AppendState AppendState;
 
 namespace dom {
 
 void
 SourceBuffer::SetMode(SourceBufferAppendMode aMode, ErrorResult& aRv)
 {
-  typedef mozilla::SourceBufferContentManager::AppendState AppendState;
-
   MOZ_ASSERT(NS_IsMainThread());
   MSE_API("SetMode(aMode=%d)", aMode);
   if (!IsAttached() || mUpdating) {
     aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
     return;
   }
-  if (mAttributes->mGenerateTimestamps &&
+  if (mCurrentAttributes.mGenerateTimestamps &&
       aMode == SourceBufferAppendMode::Segments) {
     aRv.Throw(NS_ERROR_DOM_INVALID_ACCESS_ERR);
     return;
   }
   MOZ_ASSERT(mMediaSource->ReadyState() != MediaSourceReadyState::Closed);
   if (mMediaSource->ReadyState() == MediaSourceReadyState::Ended) {
     mMediaSource->SetReadyState(MediaSourceReadyState::Open);
   }
-  if (mContentManager->GetAppendState() == AppendState::PARSING_MEDIA_SEGMENT){
+  if (mCurrentAttributes.GetAppendState() == AppendState::PARSING_MEDIA_SEGMENT){
     aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
     return;
   }
 
   if (aMode == SourceBufferAppendMode::Sequence) {
     // Will set GroupStartTimestamp to GroupEndTimestamp.
-    mContentManager->RestartGroupStartTimestamp();
+    mCurrentAttributes.RestartGroupStartTimestamp();
   }
 
-  mAttributes->SetAppendMode(aMode);
+  mCurrentAttributes.SetAppendMode(aMode);
 }
 
 void
 SourceBuffer::SetTimestampOffset(double aTimestampOffset, ErrorResult& aRv)
 {
-  typedef mozilla::SourceBufferContentManager::AppendState AppendState;
-
   MOZ_ASSERT(NS_IsMainThread());
   MSE_API("SetTimestampOffset(aTimestampOffset=%f)", aTimestampOffset);
   if (!IsAttached() || mUpdating) {
     aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
     return;
   }
   MOZ_ASSERT(mMediaSource->ReadyState() != MediaSourceReadyState::Closed);
   if (mMediaSource->ReadyState() == MediaSourceReadyState::Ended) {
     mMediaSource->SetReadyState(MediaSourceReadyState::Open);
   }
-  if (mContentManager->GetAppendState() == AppendState::PARSING_MEDIA_SEGMENT){
+  if (mCurrentAttributes.GetAppendState() == AppendState::PARSING_MEDIA_SEGMENT){
     aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
     return;
   }
-  mAttributes->SetApparentTimestampOffset(aTimestampOffset);
-  if (mAttributes->GetAppendMode() == SourceBufferAppendMode::Sequence) {
-    mContentManager->SetGroupStartTimestamp(mAttributes->GetTimestampOffset());
+  mCurrentAttributes.SetApparentTimestampOffset(aTimestampOffset);
+  if (mCurrentAttributes.GetAppendMode() == SourceBufferAppendMode::Sequence) {
+    mCurrentAttributes.SetGroupStartTimestamp(mCurrentAttributes.GetTimestampOffset());
   }
 }
 
 TimeRanges*
 SourceBuffer::GetBuffered(ErrorResult& aRv)
 {
   MOZ_ASSERT(NS_IsMainThread());
   // http://w3c.github.io/media-source/index.html#widl-SourceBuffer-buffered
   // 1. If this object has been removed from the sourceBuffers attribute of the parent media source then throw an InvalidStateError exception and abort these steps.
   if (!IsAttached()) {
     aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
     return nullptr;
   }
   bool rangeChanged = true;
-  media::TimeIntervals intersection = mContentManager->Buffered();
+  media::TimeIntervals intersection = mTrackBuffersManager->Buffered();
   MSE_DEBUGV("intersection=%s", DumpTimeRanges(intersection).get());
   if (mBuffered) {
     media::TimeIntervals currentValue(mBuffered);
     rangeChanged = (intersection != currentValue);
     MSE_DEBUGV("currentValue=%s", DumpTimeRanges(currentValue).get());
   }
   // 5. If intersection ranges does not contain the exact same range information as the current value of this attribute, then update the current value of this attribute to intersection ranges.
   if (rangeChanged) {
@@ -122,51 +125,51 @@ SourceBuffer::GetBuffered(ErrorResult& a
   }
   // 6. Return the current value of this attribute.
   return mBuffered;
 }
 
 media::TimeIntervals
 SourceBuffer::GetTimeIntervals()
 {
-  return mContentManager->Buffered();
+  return mTrackBuffersManager->Buffered();
 }
 
 void
 SourceBuffer::SetAppendWindowStart(double aAppendWindowStart, ErrorResult& aRv)
 {
   MOZ_ASSERT(NS_IsMainThread());
   MSE_API("SetAppendWindowStart(aAppendWindowStart=%f)", aAppendWindowStart);
   if (!IsAttached() || mUpdating) {
     aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
     return;
   }
   if (aAppendWindowStart < 0 ||
-      aAppendWindowStart >= mAttributes->GetAppendWindowEnd()) {
+      aAppendWindowStart >= mCurrentAttributes.GetAppendWindowEnd()) {
     aRv.Throw(NS_ERROR_DOM_INVALID_ACCESS_ERR);
     return;
   }
-  mAttributes->SetAppendWindowStart(aAppendWindowStart);
+  mCurrentAttributes.SetAppendWindowStart(aAppendWindowStart);
 }
 
 void
 SourceBuffer::SetAppendWindowEnd(double aAppendWindowEnd, ErrorResult& aRv)
 {
   MOZ_ASSERT(NS_IsMainThread());
   MSE_API("SetAppendWindowEnd(aAppendWindowEnd=%f)", aAppendWindowEnd);
   if (!IsAttached() || mUpdating) {
     aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
     return;
   }
   if (IsNaN(aAppendWindowEnd) ||
-      aAppendWindowEnd <= mAttributes->GetAppendWindowStart()) {
+      aAppendWindowEnd <= mCurrentAttributes.GetAppendWindowStart()) {
     aRv.Throw(NS_ERROR_DOM_INVALID_ACCESS_ERR);
     return;
   }
-  mAttributes->SetAppendWindowEnd(aAppendWindowEnd);
+  mCurrentAttributes.SetAppendWindowEnd(aAppendWindowEnd);
 }
 
 void
 SourceBuffer::AppendBuffer(const ArrayBuffer& aData, ErrorResult& aRv)
 {
   MOZ_ASSERT(NS_IsMainThread());
   MSE_API("AppendBuffer(ArrayBuffer)");
   aData.ComputeLengthAndData();
@@ -191,37 +194,40 @@ SourceBuffer::Abort(ErrorResult& aRv)
     aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
     return;
   }
   if (mMediaSource->ReadyState() != MediaSourceReadyState::Open) {
     aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
     return;
   }
   AbortBufferAppend();
-  mContentManager->ResetParserState();
-  mAttributes->SetAppendWindowStart(0);
-  mAttributes->SetAppendWindowEnd(PositiveInfinity<double>());
+  ResetParserState();
+  mCurrentAttributes.SetAppendWindowStart(0);
+  mCurrentAttributes.SetAppendWindowEnd(PositiveInfinity<double>());
 }
 
 void
 SourceBuffer::AbortBufferAppend()
 {
   if (mUpdating) {
     if (mPendingAppend.Exists()) {
       mPendingAppend.Disconnect();
-      mContentManager->AbortAppendData();
-      // Some data may have been added by the Segment Parser Loop.
-      // Check if we need to update the duration.
-      CheckEndTime();
+      mTrackBuffersManager->AbortAppendData();
     }
     AbortUpdating();
   }
 }
 
 void
+SourceBuffer::ResetParserState()
+{
+  mTrackBuffersManager->ResetParserState(mCurrentAttributes);
+}
+
+void
 SourceBuffer::Remove(double aStart, double aEnd, ErrorResult& aRv)
 {
   MOZ_ASSERT(NS_IsMainThread());
   MSE_API("Remove(aStart=%f, aEnd=%f)", aStart, aEnd);
   if (!IsAttached()) {
     aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
     return;
   }
@@ -243,85 +249,85 @@ SourceBuffer::Remove(double aStart, doub
 }
 
 void
 SourceBuffer::RangeRemoval(double aStart, double aEnd)
 {
   StartUpdating();
 
   RefPtr<SourceBuffer> self = this;
-  mContentManager->RangeRemoval(TimeUnit::FromSeconds(aStart),
-                                TimeUnit::FromSeconds(aEnd))
+  mTrackBuffersManager->RangeRemoval(TimeUnit::FromSeconds(aStart),
+                                     TimeUnit::FromSeconds(aEnd))
     ->Then(AbstractThread::MainThread(), __func__,
            [self] (bool) { self->StopUpdating(); },
            []() { MOZ_ASSERT(false); });
 }
 
 void
 SourceBuffer::Detach()
 {
   MOZ_ASSERT(NS_IsMainThread());
   MSE_DEBUG("Detach");
   if (!mMediaSource) {
     MSE_DEBUG("Already detached");
     return;
   }
   AbortBufferAppend();
-  if (mContentManager) {
-    mContentManager->Detach();
+  if (mTrackBuffersManager) {
+    mTrackBuffersManager->Detach();
     mMediaSource->GetDecoder()->GetDemuxer()->DetachSourceBuffer(
-      static_cast<mozilla::TrackBuffersManager*>(mContentManager.get()));
+      mTrackBuffersManager.get());
   }
-  mContentManager = nullptr;
+  mTrackBuffersManager = nullptr;
   mMediaSource = nullptr;
 }
 
 void
 SourceBuffer::Ended()
 {
   MOZ_ASSERT(NS_IsMainThread());
   MOZ_ASSERT(IsAttached());
   MSE_DEBUG("Ended");
-  mContentManager->Ended();
+  mTrackBuffersManager->Ended();
   // We want the MediaSourceReader to refresh its buffered range as it may
   // have been modified (end lined up).
   mMediaSource->GetDecoder()->NotifyDataArrived();
 }
 
 SourceBuffer::SourceBuffer(MediaSource* aMediaSource, const nsACString& aType)
   : DOMEventTargetHelper(aMediaSource->GetParentObject())
   , mMediaSource(aMediaSource)
+  , mCurrentAttributes(aType.LowerCaseEqualsLiteral("audio/mpeg") ||
+                       aType.LowerCaseEqualsLiteral("audio/aac"))
   , mUpdating(false)
   , mActive(false)
   , mType(aType)
 {
   MOZ_ASSERT(NS_IsMainThread());
   MOZ_ASSERT(aMediaSource);
-  bool generateTimestamps = false;
-  if (aType.LowerCaseEqualsLiteral("audio/mpeg") ||
-      aType.LowerCaseEqualsLiteral("audio/aac")) {
-    generateTimestamps = true;
-  }
-  mAttributes = new SourceBufferAttributes(generateTimestamps);
+
+  mTrackBuffersManager =
+    new TrackBuffersManager(aMediaSource->GetDecoder(), aType);
 
-  mContentManager =
-    SourceBufferContentManager::CreateManager(mAttributes,
-                                              aMediaSource->GetDecoder(),
-                                              aType);
-  MSE_DEBUG("Create mContentManager=%p",
-            mContentManager.get());
+  // Now that we know what type we're dealing with, enable dormant as needed.
+#if defined(MP4_READER_DORMANT_HEURISTIC)
+  aMediaSource->GetDecoder()->NotifyDormantSupported(Preferences::GetBool("media.decoder.heuristic.dormant.enabled", false));
+#endif
+
+  MSE_DEBUG("Create mTrackBuffersManager=%p",
+            mTrackBuffersManager.get());
 
   ErrorResult dummy;
-  if (mAttributes->mGenerateTimestamps) {
+  if (mCurrentAttributes.mGenerateTimestamps) {
     SetMode(SourceBufferAppendMode::Sequence, dummy);
   } else {
     SetMode(SourceBufferAppendMode::Segments, dummy);
   }
   mMediaSource->GetDecoder()->GetDemuxer()->AttachSourceBuffer(
-    static_cast<mozilla::TrackBuffersManager*>(mContentManager.get()));
+    mTrackBuffersManager.get());
 }
 
 SourceBuffer::~SourceBuffer()
 {
   MOZ_ASSERT(NS_IsMainThread());
   MOZ_ASSERT(!mMediaSource);
   MSE_DEBUG("");
 }
@@ -386,71 +392,61 @@ SourceBuffer::AbortUpdating()
   QueueAsyncSimpleEvent("updateend");
 }
 
 void
 SourceBuffer::CheckEndTime()
 {
   MOZ_ASSERT(NS_IsMainThread());
   // Check if we need to update mMediaSource duration
-  double endTime = mContentManager->GroupEndTimestamp().ToSeconds();
+  double endTime = mCurrentAttributes.GetGroupEndTimestamp().ToSeconds();
   double duration = mMediaSource->Duration();
   if (endTime > duration) {
     mMediaSource->SetDuration(endTime, MSRangeRemovalAction::SKIP);
   }
 }
 
 void
 SourceBuffer::AppendData(const uint8_t* aData, uint32_t aLength, ErrorResult& aRv)
 {
   MSE_DEBUG("AppendData(aLength=%u)", aLength);
 
   RefPtr<MediaByteBuffer> data = PrepareAppend(aData, aLength, aRv);
   if (!data) {
     return;
   }
-  mContentManager->AppendData(data, mAttributes->GetTimestampOffset());
-
   StartUpdating();
 
-  BufferAppend();
-}
-
-void
-SourceBuffer::BufferAppend()
-{
-  MOZ_ASSERT(mUpdating);
-  MOZ_ASSERT(mMediaSource);
-  MOZ_ASSERT(!mPendingAppend.Exists());
-
-  mPendingAppend.Begin(mContentManager->BufferAppend()
+  mPendingAppend.Begin(mTrackBuffersManager->AppendData(data, mCurrentAttributes)
                        ->Then(AbstractThread::MainThread(), __func__, this,
                               &SourceBuffer::AppendDataCompletedWithSuccess,
                               &SourceBuffer::AppendDataErrored));
 }
 
 void
-SourceBuffer::AppendDataCompletedWithSuccess(bool aHasActiveTracks)
+SourceBuffer::AppendDataCompletedWithSuccess(SourceBufferTask::AppendBufferResult aResult)
 {
   MOZ_ASSERT(mUpdating);
   mPendingAppend.Complete();
 
-  if (aHasActiveTracks) {
+  if (aResult.first()) {
     if (!mActive) {
       mActive = true;
       mMediaSource->SourceBufferIsActive(this);
     }
   }
   if (mActive) {
     // Tell our parent decoder that we have received new data.
     mMediaSource->GetDecoder()->NotifyDataArrived();
     // Send progress event.
     mMediaSource->GetDecoder()->NotifyBytesDownloaded();
   }
 
+  mCurrentAttributes = aResult.second();
+
   CheckEndTime();
 
   StopUpdating();
 }
 
 void
 SourceBuffer::AppendDataErrored(nsresult aError)
 {
@@ -468,17 +464,17 @@ SourceBuffer::AppendDataErrored(nsresult
   }
 }
 
 void
 SourceBuffer::AppendError(bool aDecoderError)
 {
   MOZ_ASSERT(NS_IsMainThread());
 
-  mContentManager->ResetParserState();
+  ResetParserState();
 
   mUpdating = false;
 
   QueueAsyncSimpleEvent("error");
   QueueAsyncSimpleEvent("updateend");
 
   if (aDecoderError) {
     Optional<MediaSourceEndOfStreamError> decodeError(
@@ -486,17 +482,17 @@ SourceBuffer::AppendError(bool aDecoderE
     ErrorResult dummy;
     mMediaSource->EndOfStream(decodeError, dummy);
   }
 }
 
 already_AddRefed<MediaByteBuffer>
 SourceBuffer::PrepareAppend(const uint8_t* aData, uint32_t aLength, ErrorResult& aRv)
 {
-  typedef SourceBufferContentManager::EvictDataResult Result;
+  typedef TrackBuffersManager::EvictDataResult Result;
 
   if (!IsAttached() || mUpdating) {
     aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
     return nullptr;
   }
 
   // If the HTMLMediaElement.error attribute is not null, then throw an
   // InvalidStateError exception and abort these steps.
@@ -507,43 +503,26 @@ SourceBuffer::PrepareAppend(const uint8_
     return nullptr;
   }
 
   if (mMediaSource->ReadyState() == MediaSourceReadyState::Ended) {
     mMediaSource->SetReadyState(MediaSourceReadyState::Open);
   }
 
   // Eviction uses a byte threshold. If the buffer is greater than the
-  // number of bytes then data is evicted. The time range for this
-  // eviction is reported back to the media source. It will then
-  // evict data before that range across all SourceBuffers it knows
-  // about.
-  // TODO: Make the eviction threshold smaller for audio-only streams.
+  // number of bytes then data is evicted.
   // TODO: Drive evictions off memory pressure notifications.
   // TODO: Consider a global eviction threshold  rather than per TrackBuffer.
-  TimeUnit newBufferStartTime;
-  // Attempt to evict the amount of data we are about to add by lowering the
-  // threshold.
+  // Give a chance to the TrackBuffersManager to evict some data if needed.
   Result evicted =
-    mContentManager->EvictData(TimeUnit::FromSeconds(mMediaSource->GetDecoder()->GetCurrentTime()),
-                               aLength, &newBufferStartTime);
-  if (evicted == Result::DATA_EVICTED) {
-    MSE_DEBUG("AppendData Evict; current buffered start=%f",
-              GetBufferedStart());
-
-    // We notify that we've evicted from the time range 0 through to
-    // the current start point.
-    mMediaSource->NotifyEvicted(0.0, newBufferStartTime.ToSeconds());
-  }
+    mTrackBuffersManager->EvictData(TimeUnit::FromSeconds(mMediaSource->GetDecoder()->GetCurrentTime()),
+                                    aLength);
 
   // See if we have enough free space to append our new data.
-  // As we can only evict once we have playable data, we must give a chance
-  // to the DASH player to provide a complete media segment.
-  if (aLength > mContentManager->EvictionThreshold() ||
-      evicted == Result::BUFFER_FULL) {
+  if (evicted == Result::BUFFER_FULL) {
     aRv.Throw(NS_ERROR_DOM_QUOTA_EXCEEDED_ERR);
     return nullptr;
   }
 
   RefPtr<MediaByteBuffer> data = new MediaByteBuffer();
   if (!data->AppendElements(aData, aLength, fallible)) {
     aRv.Throw(NS_ERROR_DOM_QUOTA_EXCEEDED_ERR);
     return nullptr;
@@ -564,35 +543,21 @@ double
 SourceBuffer::GetBufferedEnd()
 {
   MOZ_ASSERT(NS_IsMainThread());
   ErrorResult dummy;
   RefPtr<TimeRanges> ranges = GetBuffered(dummy);
   return ranges->Length() > 0 ? ranges->GetEndTime() : 0;
 }
 
-void
-SourceBuffer::Evict(double aStart, double aEnd)
-{
-  MOZ_ASSERT(NS_IsMainThread());
-  MSE_DEBUG("Evict(aStart=%f, aEnd=%f)", aStart, aEnd);
-  double currentTime = mMediaSource->GetDecoder()->GetCurrentTime();
-  double evictTime = aEnd;
-  const double safety_threshold = 5;
-  if (currentTime + safety_threshold >= evictTime) {
-    evictTime -= safety_threshold;
-  }
-  mContentManager->EvictBefore(TimeUnit::FromSeconds(evictTime));
-}
-
 NS_IMPL_CYCLE_COLLECTION_CLASS(SourceBuffer)
 
 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(SourceBuffer)
   // Tell the TrackBuffer to end its current SourceBufferResource.
-  SourceBufferContentManager* manager = tmp->mContentManager;
+  TrackBuffersManager* manager = tmp->mTrackBuffersManager;
   if (manager) {
     manager->Detach();
   }
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mMediaSource)
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mBuffered)
 NS_IMPL_CYCLE_COLLECTION_UNLINK_END_INHERITED(DOMEventTargetHelper)
 
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(SourceBuffer,
--- a/dom/media/mediasource/SourceBuffer.h
+++ b/dom/media/mediasource/SourceBuffer.h
@@ -9,169 +9,79 @@
 
 #include "mozilla/MozPromise.h"
 #include "MediaSource.h"
 #include "js/RootingAPI.h"
 #include "mozilla/Assertions.h"
 #include "mozilla/Atomics.h"
 #include "mozilla/Attributes.h"
 #include "mozilla/DOMEventTargetHelper.h"
+#include "mozilla/UniquePtr.h"
 #include "mozilla/dom/SourceBufferBinding.h"
 #include "mozilla/dom/TypedArray.h"
 #include "mozilla/mozalloc.h"
 #include "nsAutoPtr.h"
 #include "nsCOMPtr.h"
 #include "nsCycleCollectionNoteChild.h"
 #include "nsCycleCollectionParticipant.h"
 #include "nsISupports.h"
 #include "nsString.h"
 #include "nscore.h"
-#include "SourceBufferContentManager.h"
-#include "mozilla/Monitor.h"
+#include "TrackBuffersManager.h"
+#include "SourceBufferTask.h"
 
 class JSObject;
 struct JSContext;
 
 namespace mozilla {
 
 class ErrorResult;
 class MediaByteBuffer;
 template <typename T> class AsyncEventRunner;
-class TrackBuffersManager;
 
 namespace dom {
 
 class TimeRanges;
 
-class SourceBufferAttributes {
-public:
-  NS_INLINE_DECL_THREADSAFE_REFCOUNTING(SourceBufferAttributes);
-  explicit SourceBufferAttributes(bool aGenerateTimestamp)
-    : mGenerateTimestamps(aGenerateTimestamp)
-    , mMonitor("SourceBufferAttributes")
-    , mAppendWindowStart(0)
-    , mAppendWindowEnd(PositiveInfinity<double>())
-    , mAppendMode(SourceBufferAppendMode::Segments)
-    , mApparentTimestampOffset(0)
-  {}
-
-  double GetAppendWindowStart()
-  {
-    MonitorAutoLock mon(mMonitor);
-    return mAppendWindowStart;
-  }
-
-  double GetAppendWindowEnd()
-  {
-    MonitorAutoLock mon(mMonitor);
-    return mAppendWindowEnd;
-  }
-
-  void SetAppendWindowStart(double aWindowStart)
-  {
-    MonitorAutoLock mon(mMonitor);
-    mAppendWindowStart = aWindowStart;
-  }
-
-  void SetAppendWindowEnd(double aWindowEnd)
-  {
-    MonitorAutoLock mon(mMonitor);
-    mAppendWindowEnd = aWindowEnd;
-  }
-
-  double GetApparentTimestampOffset()
-  {
-    MonitorAutoLock mon(mMonitor);
-    return mApparentTimestampOffset;
-  }
-
-  void SetApparentTimestampOffset(double aTimestampOffset)
-  {
-    MonitorAutoLock mon(mMonitor);
-    mApparentTimestampOffset = aTimestampOffset;
-    mTimestampOffset = media::TimeUnit::FromSeconds(aTimestampOffset);
-  }
-
-  media::TimeUnit GetTimestampOffset()
-  {
-    MonitorAutoLock mon(mMonitor);
-    return mTimestampOffset;
-  }
-
-  void SetTimestampOffset(media::TimeUnit& aTimestampOffset)
-  {
-    MonitorAutoLock mon(mMonitor);
-    mTimestampOffset = aTimestampOffset;
-    mApparentTimestampOffset = aTimestampOffset.ToSeconds();
-  }
-
-  SourceBufferAppendMode GetAppendMode()
-  {
-    MonitorAutoLock mon(mMonitor);
-    return mAppendMode;
-  }
-
-  void SetAppendMode(SourceBufferAppendMode aAppendMode)
-  {
-    MonitorAutoLock mon(mMonitor);
-    mAppendMode = aAppendMode;
-  }
-
-  // mGenerateTimestamp isn't mutable once the source buffer has been constructed
-  // We don't need a monitor to protect it across threads.
-  const bool mGenerateTimestamps;
-
-private:
-  ~SourceBufferAttributes() {};
-
-  // Monitor protecting all members below.
-  Monitor mMonitor;
-  double mAppendWindowStart;
-  double mAppendWindowEnd;
-  SourceBufferAppendMode mAppendMode;
-  double mApparentTimestampOffset;
-  media::TimeUnit mTimestampOffset;
-};
-
 class SourceBuffer final : public DOMEventTargetHelper
 {
 public:
   /** WebIDL Methods. */
   SourceBufferAppendMode Mode() const
   {
-    return mAttributes->GetAppendMode();
+    return mCurrentAttributes.GetAppendMode();
   }
 
   void SetMode(SourceBufferAppendMode aMode, ErrorResult& aRv);
 
   bool Updating() const
   {
     return mUpdating;
   }
 
   TimeRanges* GetBuffered(ErrorResult& aRv);
   media::TimeIntervals GetTimeIntervals();
 
   double TimestampOffset() const
   {
-    return mAttributes->GetApparentTimestampOffset();
+    return mCurrentAttributes.GetApparentTimestampOffset();
   }
 
   void SetTimestampOffset(double aTimestampOffset, ErrorResult& aRv);
 
   double AppendWindowStart() const
   {
-    return mAttributes->GetAppendWindowStart();
+    return mCurrentAttributes.GetAppendWindowStart();
   }
 
   void SetAppendWindowStart(double aAppendWindowStart, ErrorResult& aRv);
 
   double AppendWindowEnd() const
   {
-    return mAttributes->GetAppendWindowEnd();
+    return mCurrentAttributes.GetAppendWindowEnd();
   }
 
   void SetAppendWindowEnd(double aAppendWindowEnd, ErrorResult& aRv);
 
   void AppendBuffer(const ArrayBuffer& aData, ErrorResult& aRv);
   void AppendBuffer(const ArrayBufferView& aData, ErrorResult& aRv);
 
   void Abort(ErrorResult& aRv);
@@ -194,19 +104,16 @@ public:
   void Detach();
   bool IsAttached() const
   {
     return mMediaSource != nullptr;
   }
 
   void Ended();
 
-  // Evict data in the source buffer in the given time range.
-  void Evict(double aStart, double aEnd);
-
   double GetBufferedStart();
   double GetBufferedEnd();
 
   // Runs the range removal algorithm as defined by the MSE spec.
   void RangeRemoval(double aStart, double aEnd);
 
   bool IsActive() const
   {
@@ -221,51 +128,51 @@ private:
   friend class mozilla::TrackBuffersManager;
   void DispatchSimpleEvent(const char* aName);
   void QueueAsyncSimpleEvent(const char* aName);
 
   // Update mUpdating and fire the appropriate events.
   void StartUpdating();
   void StopUpdating();
   void AbortUpdating();
+  void ResetParserState();
 
   // If the media segment contains data beyond the current duration,
   // then run the duration change algorithm with new duration set to the
   // maximum of the current duration and the group end timestamp.
   void CheckEndTime();
 
   // Shared implementation of AppendBuffer overloads.
   void AppendData(const uint8_t* aData, uint32_t aLength, ErrorResult& aRv);
-  void BufferAppend();
 
   // Implement the "Append Error Algorithm".
   // Will call endOfStream() with "decode" error if aDecodeError is true.
   // 3.5.3 Append Error Algorithm
   // http://w3c.github.io/media-source/#sourcebuffer-append-error
   void AppendError(bool aDecoderError);
 
   // Implements the "Prepare Append Algorithm". Returns MediaByteBuffer object
   // on success or nullptr (with aRv set) on error.
   already_AddRefed<MediaByteBuffer> PrepareAppend(const uint8_t* aData,
                                                   uint32_t aLength,
                                                   ErrorResult& aRv);
 
-  void AppendDataCompletedWithSuccess(bool aHasActiveTracks);
+  void AppendDataCompletedWithSuccess(SourceBufferTask::AppendBufferResult aResult);
   void AppendDataErrored(nsresult aError);
 
   RefPtr<MediaSource> mMediaSource;
 
-  RefPtr<SourceBufferContentManager> mContentManager;
-  RefPtr<SourceBufferAttributes> mAttributes;
+  RefPtr<TrackBuffersManager> mTrackBuffersManager;
+  SourceBufferAttributes mCurrentAttributes;
 
   bool mUpdating;
 
   mozilla::Atomic<bool> mActive;
 
-  MozPromiseRequestHolder<SourceBufferContentManager::AppendPromise> mPendingAppend;
+  MozPromiseRequestHolder<SourceBufferTask::AppendPromise> mPendingAppend;
   const nsCString mType;
 
   RefPtr<TimeRanges> mBuffered;
 };
 
 } // namespace dom
 
 } // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/dom/media/mediasource/SourceBufferAttributes.h
@@ -0,0 +1,157 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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 mozilla_SourceBufferAttributes_h_
+#define mozilla_SourceBufferAttributes_h_
+
+#include "TimeUnits.h"
+#include "mozilla/dom/SourceBufferBinding.h"
+#include "mozilla/Maybe.h"
+
+namespace mozilla {
+
+class SourceBufferAttributes {
+public:
+
+  // Current state as per Segment Parser Loop Algorithm
+  // http://w3c.github.io/media-source/index.html#sourcebuffer-segment-parser-loop
+  enum class AppendState
+  {
+    WAITING_FOR_SEGMENT,
+    PARSING_INIT_SEGMENT,
+    PARSING_MEDIA_SEGMENT,
+  };
+
+  explicit SourceBufferAttributes(bool aGenerateTimestamp)
+  : mGenerateTimestamps(aGenerateTimestamp)
+  , mAppendWindowStart(0)
+  , mAppendWindowEnd(PositiveInfinity<double>())
+  , mAppendMode(dom::SourceBufferAppendMode::Segments)
+  , mApparentTimestampOffset(0)
+  , mAppendState(AppendState::WAITING_FOR_SEGMENT)
+  {}
+
+  SourceBufferAttributes(const SourceBufferAttributes& aOther) = default;
+
+  double GetAppendWindowStart() const
+  {
+    return mAppendWindowStart;
+  }
+
+  double GetAppendWindowEnd() const
+  {
+    return mAppendWindowEnd;
+  }
+
+  void SetAppendWindowStart(double aWindowStart)
+  {
+    mAppendWindowStart = aWindowStart;
+  }
+
+  void SetAppendWindowEnd(double aWindowEnd)
+  {
+    mAppendWindowEnd = aWindowEnd;
+  }
+
+  double GetApparentTimestampOffset() const
+  {
+    return mApparentTimestampOffset;
+  }
+
+  void SetApparentTimestampOffset(double aTimestampOffset)
+  {
+    mApparentTimestampOffset = aTimestampOffset;
+    mTimestampOffset = media::TimeUnit::FromSeconds(aTimestampOffset);
+  }
+
+  media::TimeUnit GetTimestampOffset() const
+  {
+    return mTimestampOffset;
+  }
+
+  void SetTimestampOffset(const media::TimeUnit& aTimestampOffset)
+  {
+    mTimestampOffset = aTimestampOffset;
+    mApparentTimestampOffset = aTimestampOffset.ToSeconds();
+  }
+
+  dom::SourceBufferAppendMode GetAppendMode() const
+  {
+    return mAppendMode;
+  }
+
+  void SetAppendMode(dom::SourceBufferAppendMode aAppendMode)
+  {
+    mAppendMode = aAppendMode;
+  }
+
+  void SetGroupStartTimestamp(const media::TimeUnit& aGroupStartTimestamp)
+  {
+    mGroupStartTimestamp = Some(aGroupStartTimestamp);
+  }
+
+  media::TimeUnit GetGroupStartTimestamp() const
+  {
+    return mGroupStartTimestamp.ref();
+  }
+
+  bool HaveGroupStartTimestamp() const
+  {
+    return mGroupStartTimestamp.isSome();
+  }
+
+  void ResetGroupStartTimestamp()
+  {
+    mGroupStartTimestamp.reset();
+  }
+
+  void RestartGroupStartTimestamp()
+  {
+    mGroupStartTimestamp = Some(mGroupEndTimestamp);
+  }
+
+  media::TimeUnit GetGroupEndTimestamp() const
+  {
+    return mGroupEndTimestamp;
+  }
+
+  void SetGroupEndTimestamp(const media::TimeUnit& aGroupEndTimestamp)
+  {
+    mGroupEndTimestamp = aGroupEndTimestamp;
+  }
+
+  AppendState GetAppendState() const
+  {
+    return mAppendState;
+  }
+
+  void SetAppendState(AppendState aState)
+  {
+    mAppendState = aState;
+  }
+
+  // mGenerateTimestamp isn't mutable once the source buffer has been constructed
+  bool mGenerateTimestamps;
+
+  SourceBufferAttributes& operator=(const SourceBufferAttributes& aOther) = default;
+
+private:
+  SourceBufferAttributes() = delete;
+
+  double mAppendWindowStart;
+  double mAppendWindowEnd;
+  dom::SourceBufferAppendMode mAppendMode;
+  double mApparentTimestampOffset;
+  media::TimeUnit mTimestampOffset;
+  Maybe<media::TimeUnit> mGroupStartTimestamp;
+  media::TimeUnit mGroupEndTimestamp;
+  // The current append state as per https://w3c.github.io/media-source/#sourcebuffer-append-state
+  AppendState mAppendState;
+};
+
+} // end namespace mozilla
+
+#endif /* mozilla_SourceBufferAttributes_h_ */
deleted file mode 100644
--- a/dom/media/mediasource/SourceBufferContentManager.cpp
+++ /dev/null
@@ -1,35 +0,0 @@
-/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
-/* vim: set ts=8 sts=2 et sw=2 tw=80: */
-/* 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 "SourceBufferContentManager.h"
-#include "mozilla/Preferences.h"
-#include "TrackBuffersManager.h"
-
-namespace mozilla {
-
-#if defined(MOZ_GONK_MEDIACODEC) || defined(XP_WIN) || defined(MOZ_APPLEMEDIA) || defined(MOZ_FFMPEG)
-#define MP4_READER_DORMANT_HEURISTIC
-#else
-#undef MP4_READER_DORMANT_HEURISTIC
-#endif
-
-already_AddRefed<SourceBufferContentManager>
-SourceBufferContentManager::CreateManager(dom::SourceBufferAttributes* aAttributes,
-                                          MediaSourceDecoder* aParentDecoder,
-                                          const nsACString &aType)
-{
-  RefPtr<SourceBufferContentManager> manager;
-  manager = new TrackBuffersManager(aAttributes, aParentDecoder, aType);
-
-  // Now that we know what type we're dealing with, enable dormant as needed.
-#if defined(MP4_READER_DORMANT_HEURISTIC)
-  aParentDecoder->NotifyDormantSupported(Preferences::GetBool("media.decoder.heuristic.dormant.enabled", false));
-#endif
-
-  return  manager.forget();
-}
-
-} // namespace mozilla
deleted file mode 100644
--- a/dom/media/mediasource/SourceBufferContentManager.h
+++ /dev/null
@@ -1,118 +0,0 @@
-/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
-/* vim: set ts=8 sts=2 et sw=2 tw=80: */
-/* 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 MOZILLA_SOURCEBUFFERCONTENTMANAGER_H_
-#define MOZILLA_SOURCEBUFFERCONTENTMANAGER_H_
-
-#include "mozilla/MozPromise.h"
-
-#include "MediaData.h"
-#include "MediaSourceDecoder.h"
-#include "TimeUnits.h"
-#include "nsString.h"
-
-namespace mozilla {
-
-namespace dom {
-class SourceBuffer;
-class SourceBufferAttributes;
-}
-
-class SourceBufferContentManager {
-public:
-  NS_INLINE_DECL_THREADSAFE_REFCOUNTING(SourceBufferContentManager);
-
-  typedef MozPromise<bool, nsresult, /* IsExclusive = */ true> AppendPromise;
-  typedef AppendPromise RangeRemovalPromise;
-
-  static already_AddRefed<SourceBufferContentManager>
-  CreateManager(dom::SourceBufferAttributes* aAttributes,
-                MediaSourceDecoder* aParentDecoder,
-                const nsACString& aType);
-
-  // Add data to the end of the input buffer.
-  // Returns false if the append failed.
-  virtual bool
-  AppendData(MediaByteBuffer* aData, media::TimeUnit aTimestampOffset) = 0;
-
-  // Run MSE Buffer Append Algorithm
-  // 3.5.5 Buffer Append Algorithm.
-  // http://w3c.github.io/media-source/index.html#sourcebuffer-buffer-append
-  virtual RefPtr<AppendPromise> BufferAppend() = 0;
-
-  // Abort any pending AppendData.
-  virtual void AbortAppendData() = 0;
-
-  // Run MSE Reset Parser State Algorithm.
-  // 3.5.2 Reset Parser State
-  // http://w3c.github.io/media-source/#sourcebuffer-reset-parser-state
-  virtual void ResetParserState() = 0;
-
-  // Runs MSE range removal algorithm.
-  // http://w3c.github.io/media-source/#sourcebuffer-coded-frame-removal
-  virtual RefPtr<RangeRemovalPromise> RangeRemoval(media::TimeUnit aStart,
-                                                   media::TimeUnit aEnd) = 0;
-
-  enum class EvictDataResult : int8_t
-  {
-    NO_DATA_EVICTED,
-    DATA_EVICTED,
-    CANT_EVICT,
-    BUFFER_FULL,
-  };
-
-  // Evicts data up to aPlaybackTime. aThreshold is used to
-  // bound the data being evicted. It will not evict more than aThreshold
-  // bytes. aBufferStartTime contains the new start time of the data after the
-  // eviction.
-  virtual EvictDataResult
-  EvictData(media::TimeUnit aPlaybackTime,
-            int64_t aThreshold,
-            media::TimeUnit* aBufferStartTime) = 0;
-
-  // Evicts data up to aTime.
-  virtual void EvictBefore(media::TimeUnit aTime) = 0;
-
-  // Returns the buffered range currently managed.
-  // This may be called on any thread.
-  // Buffered must conform to http://w3c.github.io/media-source/index.html#widl-SourceBuffer-buffered
-  virtual media::TimeIntervals Buffered() = 0;
-
-  // Return the size of the data managed by this SourceBufferContentManager.
-  virtual int64_t GetSize() = 0;
-
-  // Indicate that the MediaSource parent object got into "ended" state.
-  virtual void Ended() = 0;
-
-  // The parent SourceBuffer is about to be destroyed.
-  virtual void Detach() = 0;
-
-  // Current state as per Segment Parser Loop Algorithm
-  // http://w3c.github.io/media-source/index.html#sourcebuffer-segment-parser-loop
-  enum class AppendState : int32_t
-  {
-    WAITING_FOR_SEGMENT,
-    PARSING_INIT_SEGMENT,
-    PARSING_MEDIA_SEGMENT,
-  };
-
-  virtual AppendState GetAppendState()
-  {
-    return AppendState::WAITING_FOR_SEGMENT;
-  }
-
-  virtual void SetGroupStartTimestamp(const media::TimeUnit& aGroupStartTimestamp) {}
-  virtual void RestartGroupStartTimestamp() {}
-  virtual media::TimeUnit GroupEndTimestamp() = 0;
-  virtual int64_t EvictionThreshold() const = 0;
-
-protected:
-  virtual ~SourceBufferContentManager() { }
-};
-
-} // namespace mozilla
-
-#endif /* MOZILLA_SOURCEBUFFERCONTENTMANAGER_H_ */
--- a/dom/media/mediasource/SourceBufferList.cpp
+++ b/dom/media/mediasource/SourceBufferList.cpp
@@ -126,26 +126,16 @@ SourceBufferList::RangeRemoval(double aS
   MOZ_ASSERT(NS_IsMainThread());
   MSE_DEBUG("RangeRemoval(aStart=%f, aEnd=%f)", aStart, aEnd);
   for (uint32_t i = 0; i < mSourceBuffers.Length(); ++i) {
     mSourceBuffers[i]->RangeRemoval(aStart, aEnd);
   }
 }
 
 void
-SourceBufferList::Evict(double aStart, double aEnd)
-{
-  MOZ_ASSERT(NS_IsMainThread());
-  MSE_DEBUG("Evict(aStart=%f, aEnd=%f)", aStart, aEnd);
-  for (uint32_t i = 0; i < mSourceBuffers.Length(); ++i) {
-    mSourceBuffers[i]->Evict(aStart, aEnd);
-  }
-}
-
-void
 SourceBufferList::Ended()
 {
   MOZ_ASSERT(NS_IsMainThread());
   for (uint32_t i = 0; i < mSourceBuffers.Length(); ++i) {
     mSourceBuffers[i]->Ended();
   }
 }
 
--- a/dom/media/mediasource/SourceBufferList.h
+++ b/dom/media/mediasource/SourceBufferList.h
@@ -66,19 +66,16 @@ public:
   bool AnyUpdating();
 
   // Runs the range removal steps from the MSE specification on each SourceBuffer.
   void RangeRemoval(double aStart, double aEnd);
 
   // Mark all SourceBuffers input buffers as ended.
   void Ended();
 
-  // Evicts data for the given time range from each SourceBuffer in the list.
-  void Evict(double aStart, double aEnd);
-
   // Returns the highest end time of any of the Sourcebuffers.
   double GetHighestBufferedEndTime();
 
   // Append a SourceBuffer to the list. No event is fired.
   void AppendSimple(SourceBuffer* aSourceBuffer);
 
   // Remove all SourceBuffers from mSourceBuffers.
   //  No event is fired and no action is performed on the sourcebuffers.
new file mode 100644
--- /dev/null
+++ b/dom/media/mediasource/SourceBufferTask.h
@@ -0,0 +1,103 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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 MOZILLA_SOURCEBUFFERTASK_H_
+#define MOZILLA_SOURCEBUFFERTASK_H_
+
+#include "mozilla/MozPromise.h"
+#include "mozilla/Pair.h"
+#include "mozilla/RefPtr.h"
+#include "SourceBufferAttributes.h"
+#include "TimeUnits.h"
+
+namespace mozilla {
+
+class SourceBufferTask {
+public:
+  NS_INLINE_DECL_THREADSAFE_REFCOUNTING(SourceBufferTask);
+  enum class Type  {
+    AppendBuffer,
+    Abort,
+    Reset,
+    RangeRemoval,
+    EvictData
+  };
+
+  typedef Pair<bool, SourceBufferAttributes> AppendBufferResult;
+  typedef MozPromise<AppendBufferResult, nsresult, /* IsExclusive = */ true> AppendPromise;
+  typedef MozPromise<bool, nsresult, /* IsExclusive = */ true> RangeRemovalPromise;
+
+  virtual Type GetType() const = 0;
+
+  template<typename ReturnType>
+  ReturnType* As()
+  {
+    MOZ_ASSERT(this->GetType() == ReturnType::sType);
+    return static_cast<ReturnType*>(this);
+  }
+
+protected:
+  virtual ~SourceBufferTask() {}
+};
+
+class AppendBufferTask : public SourceBufferTask {
+public:
+  AppendBufferTask(MediaByteBuffer* aData,
+                   SourceBufferAttributes aAttributes)
+  : mBuffer(aData)
+  , mAttributes(aAttributes)
+  {}
+
+  static const Type sType = Type::AppendBuffer;
+  Type GetType() const override { return Type::AppendBuffer; }
+
+  RefPtr<MediaByteBuffer> mBuffer;
+  SourceBufferAttributes mAttributes;
+  MozPromiseHolder<AppendPromise> mPromise;
+};
+
+class AbortTask : public SourceBufferTask {
+public:
+  static const Type sType = Type::Abort;
+  Type GetType() const override { return Type::Abort; }
+};
+
+class ResetTask : public SourceBufferTask {
+public:
+  static const Type sType = Type::Reset;
+  Type GetType() const override { return Type::Reset; }
+};
+
+class RangeRemovalTask : public SourceBufferTask {
+public:
+  explicit RangeRemovalTask(const media::TimeInterval& aRange)
+  : mRange(aRange)
+  {}
+
+  static const Type sType = Type::RangeRemoval;
+  Type GetType() const override { return Type::RangeRemoval; }
+
+  media::TimeInterval mRange;
+  MozPromiseHolder<RangeRemovalPromise> mPromise;
+};
+
+class EvictDataTask : public SourceBufferTask {
+public:
+  EvictDataTask(const media::TimeUnit& aPlaybackTime, int64_t aSizetoEvict)
+  : mPlaybackTime(aPlaybackTime)
+  , mSizeToEvict(aSizetoEvict)
+  {}
+
+  static const Type sType = Type::EvictData;
+  Type GetType() const override { return Type::EvictData; }
+
+  media::TimeUnit mPlaybackTime;
+  int64_t mSizeToEvict;
+};
+
+} // end mozilla namespace
+
+#endif
\ No newline at end of file
--- a/dom/media/mediasource/TrackBuffersManager.cpp
+++ b/dom/media/mediasource/TrackBuffersManager.cpp
@@ -8,16 +8,17 @@
 #include "ContainerParser.h"
 #include "MediaSourceDemuxer.h"
 #include "MediaSourceUtils.h"
 #include "mozilla/Preferences.h"
 #include "mozilla/StateMirroring.h"
 #include "SourceBufferResource.h"
 #include "SourceBuffer.h"
 #include "WebMDemuxer.h"
+#include "SourceBufferTask.h"
 
 #ifdef MOZ_FMP4
 #include "MP4Demuxer.h"
 #endif
 
 #include <limits>
 
 extern mozilla::LogModule* GetMediaSourceLog();
@@ -33,26 +34,27 @@ mozilla::LogModule* GetMediaSourceSample
 #define SAMPLE_DEBUG(arg, ...) MOZ_LOG(GetMediaSourceSamplesLog(), mozilla::LogLevel::Debug, ("TrackBuffersManager(%p:%s)::%s: " arg, this, mType.get(), __func__, ##__VA_ARGS__))
 
 namespace mozilla {
 
 using dom::SourceBufferAppendMode;
 using media::TimeUnit;
 using media::TimeInterval;
 using media::TimeIntervals;
+typedef SourceBufferTask::AppendBufferResult AppendBufferResult;
 
 static const char*
-AppendStateToStr(TrackBuffersManager::AppendState aState)
+AppendStateToStr(SourceBufferAttributes::AppendState aState)
 {
   switch (aState) {
-    case TrackBuffersManager::AppendState::WAITING_FOR_SEGMENT:
+    case SourceBufferAttributes::AppendState::WAITING_FOR_SEGMENT:
       return "WAITING_FOR_SEGMENT";
-    case TrackBuffersManager::AppendState::PARSING_INIT_SEGMENT:
+    case SourceBufferAttributes::AppendState::PARSING_INIT_SEGMENT:
       return "PARSING_INIT_SEGMENT";
-    case TrackBuffersManager::AppendState::PARSING_MEDIA_SEGMENT:
+    case SourceBufferAttributes::AppendState::PARSING_MEDIA_SEGMENT:
       return "PARSING_MEDIA_SEGMENT";
     default:
       return "IMPOSSIBLE";
   }
 }
 
 static Atomic<uint32_t> sStreamSourceID(0u);
 
@@ -79,139 +81,238 @@ public:
   }
 private:
   RefPtr<AbstractMediaDecoder> mDecoder;
   nsTArray<uint8_t> mInitData;
   nsString mInitDataType;
 };
 #endif // MOZ_EME
 
-TrackBuffersManager::TrackBuffersManager(dom::SourceBufferAttributes* aAttributes,
-                                         MediaSourceDecoder* aParentDecoder,
+TrackBuffersManager::TrackBuffersManager(MediaSourceDecoder* aParentDecoder,
                                          const nsACString& aType)
   : mInputBuffer(new MediaByteBuffer)
-  , mAppendState(AppendState::WAITING_FOR_SEGMENT)
   , mBufferFull(false)
   , mFirstInitializationSegmentReceived(false)
   , mNewMediaSegmentStarted(false)
   , mActiveTrack(false)
   , mType(aType)
   , mParser(ContainerParser::CreateForMIMEType(aType))
   , mProcessedInput(0)
   , mTaskQueue(aParentDecoder->GetDemuxer()->GetTaskQueue())
-  , mSourceBufferAttributes(aAttributes)
   , mParentDecoder(new nsMainThreadPtrHolder<MediaSourceDecoder>(aParentDecoder, false /* strict */))
+  , mEnded(false)
+  , mDetached(false)
   , mVideoEvictionThreshold(Preferences::GetUint("media.mediasource.eviction_threshold.video",
                                                  100 * 1024 * 1024))
   , mAudioEvictionThreshold(Preferences::GetUint("media.mediasource.eviction_threshold.audio",
-                                                 15 * 1024 * 1024))
+                                                 30 * 1024 * 1024))
   , mEvictionOccurred(false)
   , mMonitor("TrackBuffersManager")
-  , mAppendRunning(false)
 {
   MOZ_ASSERT(NS_IsMainThread(), "Must be instanciated on the main thread");
 }
 
 TrackBuffersManager::~TrackBuffersManager()
 {
+  CancelAllTasks();
   ShutdownDemuxers();
 }
 
-bool
+RefPtr<TrackBuffersManager::AppendPromise>
 TrackBuffersManager::AppendData(MediaByteBuffer* aData,
-                                TimeUnit aTimestampOffset)
+                                const SourceBufferAttributes& aAttributes)
 {
   MOZ_ASSERT(NS_IsMainThread());
   MSE_DEBUG("Appending %lld bytes", aData->Length());
 
   mEnded = false;
-  nsCOMPtr<nsIRunnable> task =
-    NS_NewRunnableMethodWithArg<IncomingBuffer>(
-      this, &TrackBuffersManager::AppendIncomingBuffer,
-      IncomingBuffer(aData, aTimestampOffset));
-  GetTaskQueue()->Dispatch(task.forget());
-  return true;
+
+  RefPtr<MediaByteBuffer> buffer = aData;
+
+  return InvokeAsync(GetTaskQueue(), this,
+                     __func__, &TrackBuffersManager::DoAppendData,
+                     buffer, aAttributes);
+}
+
+RefPtr<TrackBuffersManager::AppendPromise>
+TrackBuffersManager::DoAppendData(RefPtr<MediaByteBuffer> aData,
+                                  SourceBufferAttributes aAttributes)
+{
+  RefPtr<AppendBufferTask> task = new AppendBufferTask(aData, aAttributes);
+  RefPtr<AppendPromise> p = task->mPromise.Ensure(__func__);
+  mQueue.Push(task);
+
+  ProcessTasks();
+
+  return p;
 }
 
 void
-TrackBuffersManager::AppendIncomingBuffer(IncomingBuffer aData)
+TrackBuffersManager::ProcessTasks()
 {
-  MOZ_ASSERT(OnTaskQueue());
-  mIncomingBuffers.AppendElement(aData);
+  typedef SourceBufferTask::Type Type;
+
+  if (mDetached) {
+    return;
+  }
+
+  if (OnTaskQueue()) {
+    if (mCurrentTask) {
+      // Already have a task pending. ProcessTask will be scheduled once the
+      // current task complete.
+      return;
+    }
+    RefPtr<SourceBufferTask> task = mQueue.Pop();
+    if (!task) {
+      // nothing to do.
+      return;
+    }
+    switch (task->GetType()) {
+      case Type::AppendBuffer:
+        mCurrentTask = task;
+        if (!mInputBuffer) {
+          mInputBuffer = task->As<AppendBufferTask>()->mBuffer;
+        } else if (!mInputBuffer->AppendElements(*task->As<AppendBufferTask>()->mBuffer, fallible)) {
+          RejectAppend(NS_ERROR_OUT_OF_MEMORY, __func__);
+          return;
+        }
+        mSourceBufferAttributes =
+          MakeUnique<SourceBufferAttributes>(task->As<AppendBufferTask>()->mAttributes);
+        mAppendWindow =
+          TimeInterval(TimeUnit::FromSeconds(mSourceBufferAttributes->GetAppendWindowStart()),
+                       TimeUnit::FromSeconds(mSourceBufferAttributes->GetAppendWindowEnd()));
+        ScheduleSegmentParserLoop();
+        break;
+      case Type::RangeRemoval:
+      {
+        bool rv = CodedFrameRemoval(task->As<RangeRemovalTask>()->mRange);
+        task->As<RangeRemovalTask>()->mPromise.Resolve(rv, __func__);
+        break;
+      }
+      case Type::EvictData:
+        DoEvictData(task->As<EvictDataTask>()->mPlaybackTime,
+                    task->As<EvictDataTask>()->mSizeToEvict);
+        break;
+      case Type::Abort:
+        // not handled yet, and probably never.
+        break;
+      case Type::Reset:
+        CompleteResetParserState();
+        break;
+      default:
+        NS_WARNING("Invalid Task");
+    }
+  }
+  nsCOMPtr<nsIRunnable> task =
+    NS_NewRunnableMethod(this, &TrackBuffersManager::ProcessTasks);
+  GetTaskQueue()->Dispatch(task.forget());
 }
 
-RefPtr<TrackBuffersManager::AppendPromise>
-TrackBuffersManager::BufferAppend()
+// A PromiseHolder will assert upon destruction if it has a pending promise
+// that hasn't been completed. It is possible that a task didn't get processed
+// due to the owning SourceBuffer having shutdown.
+// We resolve/reject all pending promises and remove all pending tasks from the
+// queue.
+void
+TrackBuffersManager::CancelAllTasks()
 {
-  MOZ_ASSERT(NS_IsMainThread());
-  MSE_DEBUG("");
+  typedef SourceBufferTask::Type Type;
+  MOZ_DIAGNOSTIC_ASSERT(mDetached);
+
+  if (mCurrentTask) {
+    mQueue.Push(mCurrentTask);
+    mCurrentTask = nullptr;
+  }
 
-  mAppendRunning = true;
-  return InvokeAsync(GetTaskQueue(), this,
-                     __func__, &TrackBuffersManager::InitSegmentParserLoop);
+  RefPtr<SourceBufferTask> task;
+  while ((task = mQueue.Pop())) {
+    switch (task->GetType()) {
+      case Type::AppendBuffer:
+        task->As<AppendBufferTask>()->mPromise.RejectIfExists(NS_ERROR_ABORT, __func__);
+        break;
+      case Type::RangeRemoval:
+        task->As<RangeRemovalTask>()->mPromise.ResolveIfExists(false, __func__);
+        break;
+      case Type::EvictData:
+        break;
+      case Type::Abort:
+        // not handled yet, and probably never.
+        break;
+      case Type::Reset:
+        break;
+      default:
+        NS_WARNING("Invalid Task");
+    }
+  }
 }
 
 // The MSE spec requires that we abort the current SegmentParserLoop
 // which is then followed by a call to ResetParserState.
-// However due to our asynchronous design this causes inherent difficulities.
-// As the spec behaviour is non deterministic anyway, we instead wait until the
-// current AppendData has completed its run.
+// However due to our asynchronous design this causes inherent difficulties.
+// As the spec behaviour is non deterministic anyway, we instead process all
+// pending frames found in the input buffer.
 void
 TrackBuffersManager::AbortAppendData()
 {
   MOZ_ASSERT(NS_IsMainThread());
   MSE_DEBUG("");
 
-  MonitorAutoLock mon(mMonitor);
-  while (mAppendRunning) {
-    mon.Wait();
-  }
+  RefPtr<AbortTask> task = new AbortTask();
+  mQueue.Push(task);
+  ProcessTasks();
 }
 
 void
-TrackBuffersManager::ResetParserState()
+TrackBuffersManager::ResetParserState(SourceBufferAttributes& aAttributes)
 {
   MOZ_ASSERT(NS_IsMainThread());
-  MOZ_DIAGNOSTIC_ASSERT(!mAppendRunning, "Append is running, abort must have been called");
   MSE_DEBUG("");
 
+  // Spec states:
   // 1. If the append state equals PARSING_MEDIA_SEGMENT and the input buffer contains some complete coded frames, then run the coded frame processing algorithm until all of these complete coded frames have been processed.
-  // SourceBuffer.abort() has ensured that all complete coded frames have been
-  // processed. As such, we don't need to check for the value of mAppendState.
-  nsCOMPtr<nsIRunnable> task =
-    NS_NewRunnableMethod(this, &TrackBuffersManager::CompleteResetParserState);
-  GetTaskQueue()->Dispatch(task.forget());
+  // However, we will wait until all coded frames have been processed regardless
+  // of the value of append state.
+  RefPtr<ResetTask> task = new ResetTask();
+  mQueue.Push(task);
+  ProcessTasks();
 
-  // 7. Set append state to WAITING_FOR_SEGMENT.
-  SetAppendState(AppendState::WAITING_FOR_SEGMENT);
+  // ResetParserState has some synchronous steps that much be performed now.
+  // The remaining steps will be performed once the ResetTask gets executed.
+
+  // 6. If the mode attribute equals "sequence", then set the group start timestamp to the group end timestamp
+  if (aAttributes.GetAppendMode() == SourceBufferAppendMode::Sequence) {
+    aAttributes.SetGroupStartTimestamp(aAttributes.GetGroupEndTimestamp());
+  }
+  // 8. Set append state to WAITING_FOR_SEGMENT.
+  aAttributes.SetAppendState(AppendState::WAITING_FOR_SEGMENT);
 }
 
 RefPtr<TrackBuffersManager::RangeRemovalPromise>
 TrackBuffersManager::RangeRemoval(TimeUnit aStart, TimeUnit aEnd)
 {
   MOZ_ASSERT(NS_IsMainThread());
-  MOZ_DIAGNOSTIC_ASSERT(!mAppendRunning, "Append is running");
   MSE_DEBUG("From %.2f to %.2f", aStart.ToSeconds(), aEnd.ToSeconds());
 
   mEnded = false;
 
   return InvokeAsync(GetTaskQueue(), this, __func__,
                      &TrackBuffersManager::CodedFrameRemovalWithPromise,
                      TimeInterval(aStart, aEnd));
 }
 
 TrackBuffersManager::EvictDataResult
-TrackBuffersManager::EvictData(TimeUnit aPlaybackTime,
-                               int64_t aThresholdReduct,
-                               TimeUnit* aBufferStartTime)
+TrackBuffersManager::EvictData(const TimeUnit& aPlaybackTime, int64_t aSize)
 {
   MOZ_ASSERT(NS_IsMainThread());
 
-  const int64_t toEvict = GetSize() -
-    std::max(EvictionThreshold() - aThresholdReduct, aThresholdReduct);
+  if (aSize > EvictionThreshold()) {
+    // We're adding more data than we can hold.
+    return EvictDataResult::BUFFER_FULL;
+  }
+  const int64_t toEvict = GetSize() + aSize - EvictionThreshold();
 
   MSE_DEBUG("buffered=%lldkb, eviction threshold=%ukb, evict=%lldkb",
             GetSize() / 1024, EvictionThreshold() / 1024, toEvict / 1024);
 
   if (toEvict <= 0) {
     return EvictDataResult::NO_DATA_EVICTED;
   }
   if (toEvict <= 512*1024) {
@@ -220,38 +321,23 @@ TrackBuffersManager::EvictData(TimeUnit 
   }
 
   if (mBufferFull && mEvictionOccurred) {
     return EvictDataResult::BUFFER_FULL;
   }
 
   MSE_DEBUG("Reaching our size limit, schedule eviction of %lld bytes", toEvict);
 
-  nsCOMPtr<nsIRunnable> task =
-    NS_NewRunnableMethodWithArgs<TimeUnit, uint32_t>(
-      this, &TrackBuffersManager::DoEvictData,
-      aPlaybackTime, toEvict);
-  GetTaskQueue()->Dispatch(task.forget());
+  RefPtr<EvictDataTask> task = new EvictDataTask(aPlaybackTime, toEvict);
+  mQueue.Push(task);
+  ProcessTasks();
 
   return EvictDataResult::NO_DATA_EVICTED;
 }
 
-void
-TrackBuffersManager::EvictBefore(TimeUnit aTime)
-{
-  MOZ_ASSERT(NS_IsMainThread());
-  MSE_DEBUG("");
-
-  nsCOMPtr<nsIRunnable> task =
-    NS_NewRunnableMethodWithArg<TimeInterval>(
-      this, &TrackBuffersManager::CodedFrameRemoval,
-      TimeInterval(TimeUnit::FromSeconds(0), aTime));
-  GetTaskQueue()->Dispatch(task.forget());
-}
-
 TimeIntervals
 TrackBuffersManager::Buffered()
 {
   MSE_DEBUG("");
   MonitorAutoLock mon(mMonitor);
   // http://w3c.github.io/media-source/index.html#widl-SourceBuffer-buffered
   // 2. Let highest end time be the largest track buffer ranges end time across all the track buffers managed by this SourceBuffer object.
   TimeUnit highestEndTime;
@@ -279,66 +365,58 @@ TrackBuffersManager::Buffered()
     }
     // 3. Let new intersection ranges equal the intersection between the intersection ranges and the track ranges.
     intersection.Intersection(*trackRanges);
   }
   return intersection;
 }
 
 int64_t
-TrackBuffersManager::GetSize()
+TrackBuffersManager::GetSize() const
 {
   return mSizeSourceBuffer;
 }
 
 void
 TrackBuffersManager::Ended()
 {
   mEnded = true;
 }
 
 void
 TrackBuffersManager::Detach()
 {
   MOZ_ASSERT(NS_IsMainThread());
   MSE_DEBUG("");
+  mDetached = true;
 }
 
 void
 TrackBuffersManager::CompleteResetParserState()
 {
   MOZ_ASSERT(OnTaskQueue());
   MSE_DEBUG("");
 
   // We shouldn't change mInputDemuxer while a demuxer init/reset request is
   // being processed. See bug 1239983.
-  NS_ASSERTION(!mDemuxerInitRequest.Exists(), "Previous AppendBuffer didn't complete");
-  if (mDemuxerInitRequest.Exists()) {
-    mDemuxerInitRequest.Disconnect();
-  }
+  MOZ_DIAGNOSTIC_ASSERT(!mDemuxerInitRequest.Exists(), "Previous AppendBuffer didn't complete");
 
   for (auto& track : GetTracksList()) {
     // 2. Unset the last decode timestamp on all track buffers.
     // 3. Unset the last frame duration on all track buffers.
     // 4. Unset the highest end timestamp on all track buffers.
     // 5. Set the need random access point flag on all track buffers to true.
     track->ResetAppendState();
 
     // if we have been aborted, we may have pending frames that we are going
     // to discard now.
     track->mQueuedSamples.Clear();
   }
 
-  // 6. If the mode attribute equals "sequence", then set the group start timestamp to the group end timestamp
-  if (mSourceBufferAttributes->GetAppendMode() == SourceBufferAppendMode::Sequence) {
-    mGroupStartTimestamp = Some(mGroupEndTimestamp);
-  }
-
   // 7. Remove all bytes from the input buffer.
-  mIncomingBuffers.Clear();
   mInputBuffer = nullptr;
   if (mCurrentInputBuffer) {
     mCurrentInputBuffer->EvictAll();
     // The demuxer will be recreated during the next run of SegmentParserLoop.
     // As such we don't need to notify it that data has been removed.
     mCurrentInputBuffer = new SourceBufferResource(mType);
   }
 
@@ -351,22 +429,16 @@ TrackBuffersManager::CompleteResetParser
     // The aim here is really to destroy our current demuxer.
     CreateDemuxerforMIMEType();
     // Recreate our input buffer. We can't directly assign the initData buffer
     // to mInputBuffer as it will get modified in the Segment Parser Loop.
     mInputBuffer = new MediaByteBuffer;
     mInputBuffer->AppendElements(*mInitData);
   }
   RecreateParser(true);
-
-  // 8. Set append state to WAITING_FOR_SEGMENT.
-  SetAppendState(AppendState::WAITING_FOR_SEGMENT);
-
-  // Reject our promise immediately.
-  mAppendPromise.RejectIfExists(NS_ERROR_ABORT, __func__);
 }
 
 int64_t
 TrackBuffersManager::EvictionThreshold() const
 {
   if (HasVideo()) {
     return mVideoEvictionThreshold;
   }
@@ -399,26 +471,27 @@ TrackBuffersManager::DoEvictData(const T
       partialEvict = 0;
     }
     if (frame->mTime >= lowerLimit.ToMicroseconds()) {
       break;
     }
     partialEvict += frame->ComputedSizeOfIncludingThis();
   }
 
+  const int64_t finalSize = mSizeSourceBuffer - aSizeToEvict;
+
   if (lastKeyFrameIndex > 0) {
     MSE_DEBUG("Step1. Evicting %lld bytes prior currentTime",
               aSizeToEvict - toEvict);
     CodedFrameRemoval(
       TimeInterval(TimeUnit::FromMicroseconds(0),
                    TimeUnit::FromMicroseconds(buffer[lastKeyFrameIndex]->mTime - 1)));
   }
 
-  const int64_t finalSize = mSizeSourceBuffer - aSizeToEvict;
-  if (mSizeSourceBuffer <= finalSize || !buffer.Length()) {
+  if (mSizeSourceBuffer <= finalSize) {
     return;
   }
 
   toEvict = mSizeSourceBuffer - finalSize;
 
   // Still some to remove. Remove data starting from the end, up to 30s ahead
   // of the later of the playback time or the next sample to be demuxed.
   // 30s is a value chosen as it appears to work with YouTube.
@@ -443,18 +516,22 @@ TrackBuffersManager::DoEvictData(const T
                    TimeUnit::FromInfinity()));
   }
 }
 
 RefPtr<TrackBuffersManager::RangeRemovalPromise>
 TrackBuffersManager::CodedFrameRemovalWithPromise(TimeInterval aInterval)
 {
   MOZ_ASSERT(OnTaskQueue());
-  bool rv = CodedFrameRemoval(aInterval);
-  return RangeRemovalPromise::CreateAndResolve(rv, __func__);
+
+  RefPtr<RangeRemovalTask> task = new RangeRemovalTask(aInterval);
+  RefPtr<RangeRemovalPromise> p = task->mPromise.Ensure(__func__);
+  mQueue.Push(task);
+  ProcessTasks();
+  return p;
 }
 
 bool
 TrackBuffersManager::CodedFrameRemoval(TimeInterval aInterval)
 {
   MOZ_ASSERT(OnTaskQueue());
   MSE_DEBUG("From %.2fs to %.2f",
             aInterval.mStart.ToSeconds(), aInterval.mEnd.ToSeconds());
@@ -544,54 +621,16 @@ TrackBuffersManager::UpdateBufferedRange
     MSE_DEBUG("after video ranges=%s",
               DumpTimeRanges(mVideoTracks.mBufferedRanges).get());
   }
   if (HasAudio()) {
     MSE_DEBUG("after audio ranges=%s",
               DumpTimeRanges(mAudioTracks.mBufferedRanges).get());
   }
 #endif
-
-  mOfficialGroupEndTimestamp = mGroupEndTimestamp;
-}
-
-RefPtr<TrackBuffersManager::AppendPromise>
-TrackBuffersManager::InitSegmentParserLoop()
-{
-  MOZ_ASSERT(OnTaskQueue());
-  MOZ_DIAGNOSTIC_ASSERT(mAppendPromise.IsEmpty());
-  MSE_DEBUG("");
-
-  RefPtr<AppendPromise> p = mAppendPromise.Ensure(__func__);
-
-  AppendIncomingBuffers();
-  SegmentParserLoop();
-
-  return p;
-}
-
-void
-TrackBuffersManager::AppendIncomingBuffers()
-{
-  MOZ_ASSERT(OnTaskQueue());
-  MonitorAutoLock mon(mMonitor);
-  for (auto& incomingBuffer : mIncomingBuffers) {
-    if (!mInputBuffer) {
-      mInputBuffer = incomingBuffer.first();
-    } else if (!mInputBuffer->AppendElements(*incomingBuffer.first(), fallible)) {
-      RejectAppend(NS_ERROR_OUT_OF_MEMORY, __func__);
-    }
-    mTimestampOffset = incomingBuffer.second();
-    mLastTimestampOffset = mTimestampOffset;
-  }
-  mIncomingBuffers.Clear();
-
-  mAppendWindow =
-    TimeInterval(TimeUnit::FromSeconds(mSourceBufferAttributes->GetAppendWindowStart()),
-                 TimeUnit::FromSeconds(mSourceBufferAttributes->GetAppendWindowEnd()));
 }
 
 void
 TrackBuffersManager::SegmentParserLoop()
 {
   MOZ_ASSERT(OnTaskQueue());
 
   while (true) {
@@ -608,17 +647,17 @@ TrackBuffersManager::SegmentParserLoop()
     // 3. Remove any bytes that the byte stream format specifications say must be
     // ignored from the start of the input buffer.
     // We do not remove bytes from our input buffer. Instead we enforce that
     // our ContainerParser is able to skip over all data that is supposed to be
     // ignored.
 
     // 4. If the append state equals WAITING_FOR_SEGMENT, then run the following
     // steps:
-    if (mAppendState == AppendState::WAITING_FOR_SEGMENT) {
+    if (mSourceBufferAttributes->GetAppendState() == AppendState::WAITING_FOR_SEGMENT) {
       if (mParser->IsInitSegmentPresent(mInputBuffer)) {
         SetAppendState(AppendState::PARSING_INIT_SEGMENT);
         if (mFirstInitializationSegmentReceived) {
           // This is a new initialization segment. Obsolete the old one.
           RecreateParser(false);
         }
         continue;
       }
@@ -635,26 +674,26 @@ TrackBuffersManager::SegmentParserLoop()
     }
 
     int64_t start, end;
     bool newData = mParser->ParseStartAndEndTimestamps(mInputBuffer, start, end);
     mProcessedInput += mInputBuffer->Length();
 
     // 5. If the append state equals PARSING_INIT_SEGMENT, then run the
     // following steps:
-    if (mAppendState == AppendState::PARSING_INIT_SEGMENT) {
+    if (mSourceBufferAttributes->GetAppendState() == AppendState::PARSING_INIT_SEGMENT) {
       if (mParser->InitSegmentRange().IsEmpty()) {
         mInputBuffer = nullptr;
         NeedMoreData();
         return;
       }
       InitializationSegmentReceived();
       return;
     }
-    if (mAppendState == AppendState::PARSING_MEDIA_SEGMENT) {
+    if (mSourceBufferAttributes->GetAppendState() == AppendState::PARSING_MEDIA_SEGMENT) {
       // 1. If the first initialization segment received flag is false, then run the append error algorithm with the decode error parameter set to true and abort this algorithm.
       if (!mFirstInitializationSegmentReceived) {
         RejectAppend(NS_ERROR_FAILURE, __func__);
         return;
       }
 
       // We can't feed some demuxers (WebMDemuxer) with data that do not have
       // monotonizally increasing timestamps. So we check if we have a
@@ -711,42 +750,55 @@ TrackBuffersManager::SegmentParserLoop()
     }
   }
 }
 
 void
 TrackBuffersManager::NeedMoreData()
 {
   MSE_DEBUG("");
-  RestoreCachedVariables();
-  mAppendRunning = false;
-  {
-    // Wake-up any pending Abort()
-    MonitorAutoLock mon(mMonitor);
-    mon.NotifyAll();
+  if (mDetached) {
+    // We've been detached.
+    return;
   }
-  mAppendPromise.ResolveIfExists(mActiveTrack, __func__);
+  MOZ_DIAGNOSTIC_ASSERT(mCurrentTask && mCurrentTask->GetType() == SourceBufferTask::Type::AppendBuffer);
+  MOZ_DIAGNOSTIC_ASSERT(mSourceBufferAttributes);
+
+  mCurrentTask->As<AppendBufferTask>()->mPromise.Resolve(
+    SourceBufferTask::AppendBufferResult(mActiveTrack,
+                                         *mSourceBufferAttributes),
+                                         __func__);
+  mSourceBufferAttributes = nullptr;
+  mCurrentTask = nullptr;
+  ProcessTasks();
 }
 
 void
 TrackBuffersManager::RejectAppend(nsresult aRejectValue, const char* aName)
 {
   MSE_DEBUG("rv=%d", aRejectValue);
-  mAppendRunning = false;
-  {
-    // Wake-up any pending Abort()
-    MonitorAutoLock mon(mMonitor);
-    mon.NotifyAll();
+  if (mDetached) {
+    // We've been detached.
+    return;
   }
-  mAppendPromise.RejectIfExists(aRejectValue, aName);
+  MOZ_DIAGNOSTIC_ASSERT(mCurrentTask && mCurrentTask->GetType() == SourceBufferTask::Type::AppendBuffer);
+  MOZ_DIAGNOSTIC_ASSERT(mSourceBufferAttributes);
+
+  mCurrentTask->As<AppendBufferTask>()->mPromise.Reject(aRejectValue, __func__);
+  mSourceBufferAttributes = nullptr;
+  mCurrentTask = nullptr;
+  ProcessTasks();
 }
 
 void
 TrackBuffersManager::ScheduleSegmentParserLoop()
 {
+  if (mDetached) {
+    return;
+  }
   nsCOMPtr<nsIRunnable> task =
     NS_NewRunnableMethod(this, &TrackBuffersManager::SegmentParserLoop);
   GetTaskQueue()->Dispatch(task.forget());
 }
 
 void
 TrackBuffersManager::ShutdownDemuxers()
 {
@@ -765,23 +817,25 @@ TrackBuffersManager::ShutdownDemuxers()
   mLastParsedEndTime.reset();
 }
 
 void
 TrackBuffersManager::CreateDemuxerforMIMEType()
 {
   ShutdownDemuxers();
 
-  if (mType.LowerCaseEqualsLiteral("video/webm") || mType.LowerCaseEqualsLiteral("audio/webm")) {
+  if (mType.LowerCaseEqualsLiteral("video/webm") ||
+      mType.LowerCaseEqualsLiteral("audio/webm")) {
     mInputDemuxer = new WebMDemuxer(mCurrentInputBuffer, true /* IsMediaSource*/ );
     return;
   }
 
 #ifdef MOZ_FMP4
-  if (mType.LowerCaseEqualsLiteral("video/mp4") || mType.LowerCaseEqualsLiteral("audio/mp4")) {
+  if (mType.LowerCaseEqualsLiteral("video/mp4") ||
+      mType.LowerCaseEqualsLiteral("audio/mp4")) {
     mInputDemuxer = new MP4Demuxer(mCurrentInputBuffer);
     return;
   }
 #endif
   NS_WARNING("Not supported (yet)");
   return;
 }
 
@@ -816,24 +870,26 @@ TrackBuffersManager::OnDemuxerResetDone(
   // mInputDemuxer shouldn't have been destroyed while a demuxer init/reset
   // request was being processed. See bug 1239983.
   MOZ_DIAGNOSTIC_ASSERT(mInputDemuxer);
 
   // Recreate track demuxers.
   uint32_t numVideos = mInputDemuxer->GetNumberTracks(TrackInfo::kVideoTrack);
   if (numVideos) {
     // We currently only handle the first video track.
-    mVideoTracks.mDemuxer = mInputDemuxer->GetTrackDemuxer(TrackInfo::kVideoTrack, 0);
+    mVideoTracks.mDemuxer =
+      mInputDemuxer->GetTrackDemuxer(TrackInfo::kVideoTrack, 0);
     MOZ_ASSERT(mVideoTracks.mDemuxer);
   }
 
   uint32_t numAudios = mInputDemuxer->GetNumberTracks(TrackInfo::kAudioTrack);
   if (numAudios) {
     // We currently only handle the first audio track.
-    mAudioTracks.mDemuxer = mInputDemuxer->GetTrackDemuxer(TrackInfo::kAudioTrack, 0);
+    mAudioTracks.mDemuxer =
+      mInputDemuxer->GetTrackDemuxer(TrackInfo::kAudioTrack, 0);
     MOZ_ASSERT(mAudioTracks.mDemuxer);
   }
 
   if (mPendingInputBuffer) {
     // We had a partial media segment header stashed aside.
     // Reparse its content so we can continue parsing the current input buffer.
     int64_t start, end;
     mParser->ParseStartAndEndTimestamps(mPendingInputBuffer, start, end);
@@ -879,40 +935,37 @@ TrackBuffersManager::InitializationSegme
                              &TrackBuffersManager::OnDemuxerInitDone,
                              &TrackBuffersManager::OnDemuxerInitFailed));
 }
 
 void
 TrackBuffersManager::OnDemuxerInitDone(nsresult)
 {
   MOZ_ASSERT(OnTaskQueue());
-  mDemuxerInitRequest.Complete();
+  MOZ_DIAGNOSTIC_ASSERT(mInputDemuxer, "mInputDemuxer has been destroyed");
 
-  if (!mInputDemuxer) {
-    // mInputDemuxer shouldn't have been destroyed while a demuxer init/reset
-    // request was being processed. See bug 1239983.
-    NS_ASSERTION(false, "mInputDemuxer has been destroyed");
-    RejectAppend(NS_ERROR_ABORT, __func__);
-  }
+  mDemuxerInitRequest.Complete();
 
   MediaInfo info;
 
   uint32_t numVideos = mInputDemuxer->GetNumberTracks(TrackInfo::kVideoTrack);
   if (numVideos) {
     // We currently only handle the first video track.
-    mVideoTracks.mDemuxer = mInputDemuxer->GetTrackDemuxer(TrackInfo::kVideoTrack, 0);
+    mVideoTracks.mDemuxer =
+      mInputDemuxer->GetTrackDemuxer(TrackInfo::kVideoTrack, 0);
     MOZ_ASSERT(mVideoTracks.mDemuxer);
     info.mVideo = *mVideoTracks.mDemuxer->GetInfo()->GetAsVideoInfo();
     info.mVideo.mTrackId = 2;
   }
 
   uint32_t numAudios = mInputDemuxer->GetNumberTracks(TrackInfo::kAudioTrack);
   if (numAudios) {
     // We currently only handle the first audio track.
-    mAudioTracks.mDemuxer = mInputDemuxer->GetTrackDemuxer(TrackInfo::kAudioTrack, 0);
+    mAudioTracks.mDemuxer =
+      mInputDemuxer->GetTrackDemuxer(TrackInfo::kAudioTrack, 0);
     MOZ_ASSERT(mAudioTracks.mDemuxer);
     info.mAudio = *mAudioTracks.mDemuxer->GetInfo()->GetAsAudioInfo();
     info.mAudio.mTrackId = 1;
   }
 
   int64_t videoDuration = numVideos ? info.mVideo.mDuration : 0;
   int64_t audioDuration = numAudios ? info.mAudio.mDuration : 0;
 
@@ -1043,16 +1096,17 @@ TrackBuffersManager::OnDemuxerInitDone(n
     mFirstInitializationSegmentReceived = true;
   } else {
     // Check that audio configuration hasn't changed as this is something
     // we do not support yet (bug 1185827).
     if (mAudioTracks.mNumTracks &&
         (info.mAudio.mChannels != mAudioTracks.mInfo->GetAsAudioInfo()->mChannels ||
          info.mAudio.mRate != mAudioTracks.mInfo->GetAsAudioInfo()->mRate)) {
       RejectAppend(NS_ERROR_FAILURE, __func__);
+      return;
     }
     mAudioTracks.mLastInfo = new SharedTrackInfo(info.mAudio, streamID);
     mVideoTracks.mLastInfo = new SharedTrackInfo(info.mVideo, streamID);
   }
 
   UniquePtr<EncryptionInfo> crypto = mInputDemuxer->GetCrypto();
   if (crypto && crypto->IsEncrypted()) {
 #ifdef MOZ_EME
@@ -1302,22 +1356,24 @@ TrackBuffersManager::ResolveProcessing(b
 {
   mProcessingPromise.ResolveIfExists(aResolveValue, __func__);
 }
 
 void
 TrackBuffersManager::CheckSequenceDiscontinuity(const TimeUnit& aPresentationTime)
 {
   if (mSourceBufferAttributes->GetAppendMode() == SourceBufferAppendMode::Sequence &&
-      mGroupStartTimestamp.isSome()) {
-    mTimestampOffset = mGroupStartTimestamp.ref() - aPresentationTime;
-    mGroupEndTimestamp = mGroupStartTimestamp.ref();
+      mSourceBufferAttributes->HaveGroupStartTimestamp()) {
+    mSourceBufferAttributes->SetTimestampOffset(
+      mSourceBufferAttributes->GetGroupStartTimestamp() - aPresentationTime);
+    mSourceBufferAttributes->SetGroupEndTimestamp(
+      mSourceBufferAttributes->GetGroupStartTimestamp());
     mVideoTracks.mNeedRandomAccessPoint = true;
     mAudioTracks.mNeedRandomAccessPoint = true;
-    mGroupStartTimestamp.reset();
+    mSourceBufferAttributes->ResetGroupStartTimestamp();
   }
 }
 
 void
 TrackBuffersManager::ProcessFrames(TrackBuffer& aSamples, TrackData& aTrackData)
 {
   if (!aSamples.Length()) {
     return;
@@ -1398,44 +1454,45 @@ TrackBuffersManager::ProcessFrames(Track
     //   Let decode timestamp be a double precision floating point representation of the coded frame's decode timestamp in seconds.
 
     // 2. Let frame duration be a double precision floating point representation of the coded frame's duration in seconds.
     // Step 3 is performed earlier or when a discontinuity has been detected.
     // 4. If timestampOffset is not 0, then run the following steps:
 
     TimeInterval sampleInterval =
       mSourceBufferAttributes->mGenerateTimestamps
-        ? TimeInterval(mTimestampOffset,
-                       mTimestampOffset + TimeUnit::FromMicroseconds(sample->mDuration))
-        : TimeInterval(TimeUnit::FromMicroseconds(sample->mTime) + mTimestampOffset,
-                       TimeUnit::FromMicroseconds(sample->GetEndTime()) + mTimestampOffset);
+        ? TimeInterval(mSourceBufferAttributes->GetTimestampOffset(),
+                       mSourceBufferAttributes->GetTimestampOffset() + TimeUnit::FromMicroseconds(sample->mDuration))
+        : TimeInterval(TimeUnit::FromMicroseconds(sample->mTime) + mSourceBufferAttributes->GetTimestampOffset(),
+                       TimeUnit::FromMicroseconds(sample->GetEndTime()) + mSourceBufferAttributes->GetTimestampOffset());
     TimeUnit decodeTimestamp =
       mSourceBufferAttributes->mGenerateTimestamps
-        ? mTimestampOffset
-        : TimeUnit::FromMicroseconds(sample->mTimecode) + mTimestampOffset;
+        ? mSourceBufferAttributes->GetTimestampOffset()
+        : TimeUnit::FromMicroseconds(sample->mTimecode) + mSourceBufferAttributes->GetTimestampOffset();
 
     // 6. If last decode timestamp for track buffer is set and decode timestamp is less than last decode timestamp:
     // OR
     // If last decode timestamp for track buffer is set and the difference between decode timestamp and last decode timestamp is greater than 2 times last frame duration:
 
     if (needDiscontinuityCheck && trackBuffer.mLastDecodeTimestamp.isSome() &&
         (decodeTimestamp < trackBuffer.mLastDecodeTimestamp.ref() ||
          decodeTimestamp - trackBuffer.mLastDecodeTimestamp.ref() > 2*trackBuffer.mLongestFrameDuration.ref())) {
       MSE_DEBUG("Discontinuity detected.");
       SourceBufferAppendMode appendMode = mSourceBufferAttributes->GetAppendMode();
 
       // 1a. If mode equals "segments":
       if (appendMode == SourceBufferAppendMode::Segments) {
         // Set group end timestamp to presentation timestamp.
-        mGroupEndTimestamp = sampleInterval.mStart;
+        mSourceBufferAttributes->SetGroupEndTimestamp(sampleInterval.mStart);
       }
       // 1b. If mode equals "sequence":
       if (appendMode == SourceBufferAppendMode::Sequence) {
         // Set group start timestamp equal to the group end timestamp.
-        mGroupStartTimestamp = Some(mGroupEndTimestamp);
+        mSourceBufferAttributes->SetGroupStartTimestamp(
+          mSourceBufferAttributes->GetGroupEndTimestamp());
       }
       for (auto& track : GetTracksList()) {
         // 2. Unset the last decode timestamp on all track buffers.
         // 3. Unset the last frame duration on all track buffers.
         // 4. Unset the highest end timestamp on all track buffers.
         // 5. Set the need random access point flag on all track buffers to true.
         track->ResetAppendState();
       }
@@ -1446,28 +1503,28 @@ TrackBuffersManager::ProcessFrames(Track
       TimeUnit presentationTimestamp = mSourceBufferAttributes->mGenerateTimestamps
         ? TimeUnit() : TimeUnit::FromMicroseconds(sample->mTime);
       CheckSequenceDiscontinuity(presentationTimestamp);
 
       if (!sample->mKeyframe) {
         continue;
       }
       if (appendMode == SourceBufferAppendMode::Sequence) {
-        // mTimestampOffset was modified during CheckSequenceDiscontinuity.
+        // mSourceBufferAttributes->GetTimestampOffset() was modified during CheckSequenceDiscontinuity.
         // We need to update our variables.
         sampleInterval =
           mSourceBufferAttributes->mGenerateTimestamps
-            ? TimeInterval(mTimestampOffset,
-                           mTimestampOffset + TimeUnit::FromMicroseconds(sample->mDuration))
-            : TimeInterval(TimeUnit::FromMicroseconds(sample->mTime) + mTimestampOffset,
-                           TimeUnit::FromMicroseconds(sample->GetEndTime()) + mTimestampOffset);
+            ? TimeInterval(mSourceBufferAttributes->GetTimestampOffset(),
+                           mSourceBufferAttributes->GetTimestampOffset() + TimeUnit::FromMicroseconds(sample->mDuration))
+            : TimeInterval(TimeUnit::FromMicroseconds(sample->mTime) + mSourceBufferAttributes->GetTimestampOffset(),
+                           TimeUnit::FromMicroseconds(sample->GetEndTime()) + mSourceBufferAttributes->GetTimestampOffset());
         decodeTimestamp =
           mSourceBufferAttributes->mGenerateTimestamps
-            ? mTimestampOffset
-            : TimeUnit::FromMicroseconds(sample->mTimecode) + mTimestampOffset;
+            ? mSourceBufferAttributes->GetTimestampOffset()
+            : TimeUnit::FromMicroseconds(sample->mTimecode) + mSourceBufferAttributes->GetTimestampOffset();
       }
       trackBuffer.mNeedRandomAccessPoint = false;
       needDiscontinuityCheck = false;
     }
 
     // 7. Let frame end timestamp equal the sum of presentation timestamp and frame duration.
     // This is sampleInterval.mEnd
 
@@ -1511,22 +1568,22 @@ TrackBuffersManager::ProcessFrames(Track
                       trackBuffer.mLongestFrameDuration.ref()));
 
     // 19. If highest end timestamp for track buffer is unset or frame end timestamp is greater than highest end timestamp, then set highest end timestamp for track buffer to frame end timestamp.
     if (trackBuffer.mHighestEndTimestamp.isNothing() ||
         sampleInterval.mEnd > trackBuffer.mHighestEndTimestamp.ref()) {
       trackBuffer.mHighestEndTimestamp = Some(sampleInterval.mEnd);
     }
     // 20. If frame end timestamp is greater than group end timestamp, then set group end timestamp equal to frame end timestamp.
-    if (sampleInterval.mEnd > mGroupEndTimestamp) {
-      mGroupEndTimestamp = sampleInterval.mEnd;
+    if (sampleInterval.mEnd > mSourceBufferAttributes->GetGroupEndTimestamp()) {
+      mSourceBufferAttributes->SetGroupEndTimestamp(sampleInterval.mEnd);
     }
     // 21. If generate timestamps flag equals true, then set timestampOffset equal to frame end timestamp.
     if (mSourceBufferAttributes->mGenerateTimestamps) {
-      mTimestampOffset = sampleInterval.mEnd;
+      mSourceBufferAttributes->SetTimestampOffset(sampleInterval.mEnd);
     }
   }
 
   if (samples.Length()) {
     InsertFrames(samples, samplesRange, trackBuffer);
     trackBuffer.mSizeBuffer += sizeNewSamples;
   }
 }
@@ -1773,66 +1830,21 @@ TrackBuffersManager::GetTracksList()
   }
   if (HasAudio()) {
     tracks.AppendElement(&mAudioTracks);
   }
   return tracks;
 }
 
 void
-TrackBuffersManager::RestoreCachedVariables()
-{
-  MOZ_ASSERT(OnTaskQueue());
-  if (mTimestampOffset != mLastTimestampOffset) {
-    mSourceBufferAttributes->SetTimestampOffset(mTimestampOffset);
-  }
-}
-
-void
-TrackBuffersManager::SetAppendState(TrackBuffersManager::AppendState aAppendState)
+TrackBuffersManager::SetAppendState(SourceBufferAttributes::AppendState aAppendState)
 {
   MSE_DEBUG("AppendState changed from %s to %s",
-            AppendStateToStr(mAppendState), AppendStateToStr(aAppendState));
-  mAppendState = aAppendState;
-}
-
-void
-TrackBuffersManager::SetGroupStartTimestamp(const TimeUnit& aGroupStartTimestamp)
-{
-  if (NS_IsMainThread()) {
-    nsCOMPtr<nsIRunnable> task =
-      NS_NewRunnableMethodWithArg<TimeUnit>(
-        this,
-        &TrackBuffersManager::SetGroupStartTimestamp,
-        aGroupStartTimestamp);
-    GetTaskQueue()->Dispatch(task.forget());
-    return;
-  }
-  MOZ_ASSERT(OnTaskQueue());
-  mGroupStartTimestamp = Some(aGroupStartTimestamp);
-}
-
-void
-TrackBuffersManager::RestartGroupStartTimestamp()
-{
-  if (NS_IsMainThread()) {
-    nsCOMPtr<nsIRunnable> task =
-      NS_NewRunnableMethod(this, &TrackBuffersManager::RestartGroupStartTimestamp);
-    GetTaskQueue()->Dispatch(task.forget());
-    return;
-  }
-  MOZ_ASSERT(OnTaskQueue());
-  mGroupStartTimestamp = Some(mGroupEndTimestamp);
-}
-
-TimeUnit
-TrackBuffersManager::GroupEndTimestamp()
-{
-  MonitorAutoLock mon(mMonitor);
-  return mOfficialGroupEndTimestamp;
+            AppendStateToStr(mSourceBufferAttributes->GetAppendState()), AppendStateToStr(aAppendState));
+  mSourceBufferAttributes->SetAppendState(aAppendState);
 }
 
 MediaInfo
 TrackBuffersManager::GetMetadata()
 {
   MonitorAutoLock mon(mMonitor);
   return mInfo;
 }
--- a/dom/media/mediasource/TrackBuffersManager.h
+++ b/dom/media/mediasource/TrackBuffersManager.h
@@ -5,84 +5,132 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef MOZILLA_TRACKBUFFERSMANAGER_H_
 #define MOZILLA_TRACKBUFFERSMANAGER_H_
 
 #include "mozilla/Atomics.h"
 #include "mozilla/Maybe.h"
 #include "mozilla/Monitor.h"
-#include "mozilla/Pair.h"
+#include "AutoTaskQueue.h"
 #include "mozilla/dom/SourceBufferBinding.h"
 
-#include "SourceBufferContentManager.h"
+#include "MediaData.h"
 #include "MediaDataDemuxer.h"
 #include "MediaSourceDecoder.h"
+#include "SourceBufferTask.h"
+#include "TimeUnits.h"
 #include "nsProxyRelease.h"
+#include "nsString.h"
 #include "nsTArray.h"
 
 namespace mozilla {
 
 class ContainerParser;
 class MediaByteBuffer;
 class MediaRawData;
 class MediaSourceDemuxer;
 class SourceBufferResource;
 
-namespace dom {
-  class SourceBufferAttributes;
-}
+class SourceBufferTaskQueue {
+public:
+  SourceBufferTaskQueue()
+  : mMonitor("SourceBufferTaskQueue")
+  {}
+
+  void Push(SourceBufferTask* aTask)
+  {
+    MonitorAutoLock mon(mMonitor);
+    mQueue.AppendElement(aTask);
+  }
+
+  already_AddRefed<SourceBufferTask> Pop()
+  {
+    MonitorAutoLock mon(mMonitor);
+    if (!mQueue.Length()) {
+      return nullptr;
+    }
+    RefPtr<SourceBufferTask> task = Move(mQueue[0]);
+    mQueue.RemoveElementAt(0);
+    return task.forget();
+  }
 
-class TrackBuffersManager : public SourceBufferContentManager {
+  nsTArray<SourceBufferTask>::size_type Length() const
+  {
+    MonitorAutoLock mon(mMonitor);
+    return mQueue.Length();
+  }
+
+private:
+  mutable Monitor mMonitor;
+  nsTArray<RefPtr<SourceBufferTask>> mQueue;
+};
+
+class TrackBuffersManager {
 public:
-  typedef MozPromise<bool, nsresult, /* IsExclusive = */ true> CodedFrameProcessingPromise;
+  NS_INLINE_DECL_THREADSAFE_REFCOUNTING(TrackBuffersManager);
+
+  enum class EvictDataResult : int8_t
+  {
+    NO_DATA_EVICTED,
+    CANT_EVICT,
+    BUFFER_FULL,
+  };
+
   typedef TrackInfo::TrackType TrackType;
   typedef MediaData::Type MediaType;
   typedef nsTArray<RefPtr<MediaRawData>> TrackBuffer;
+  typedef SourceBufferTask::AppendPromise AppendPromise;
+  typedef SourceBufferTask::RangeRemovalPromise RangeRemovalPromise;
 
-  TrackBuffersManager(dom::SourceBufferAttributes* aAttributes,
-                      MediaSourceDecoder* aParentDecoder,
+  // Interface for SourceBuffer
+  TrackBuffersManager(MediaSourceDecoder* aParentDecoder,
                       const nsACString& aType);
 
-  bool AppendData(MediaByteBuffer* aData,
-                  media::TimeUnit aTimestampOffset) override;
-
-  RefPtr<AppendPromise> BufferAppend() override;
-
-  void AbortAppendData() override;
+  // Queue a task to add data to the end of the input buffer and run the MSE
+  // Buffer Append Algorithm
+  // 3.5.5 Buffer Append Algorithm.
+  // http://w3c.github.io/media-source/index.html#sourcebuffer-buffer-append
+  RefPtr<AppendPromise> AppendData(MediaByteBuffer* aData,
+                                   const SourceBufferAttributes& aAttributes);
 
-  void ResetParserState() override;
-
-  RefPtr<RangeRemovalPromise> RangeRemoval(media::TimeUnit aStart,
-                                             media::TimeUnit aEnd) override;
+  // Queue a task to abort any pending AppendData.
+  // Does nothing at this stage.
+  void AbortAppendData();
 
-  EvictDataResult
-  EvictData(media::TimeUnit aPlaybackTime,
-            int64_t aThresholdReduct,
-            media::TimeUnit* aBufferStartTime) override;
+  // Queue a task to run MSE Reset Parser State Algorithm.
+  // 3.5.2 Reset Parser State
+  void ResetParserState(SourceBufferAttributes& aAttributes);
+
+  // Queue a task to run the MSE range removal algorithm.
+  // http://w3c.github.io/media-source/#sourcebuffer-coded-frame-removal
+  RefPtr<RangeRemovalPromise> RangeRemoval(media::TimeUnit aStart,
+                                             media::TimeUnit aEnd);
 
-  void EvictBefore(media::TimeUnit aTime) override;
-
-  media::TimeIntervals Buffered() override;
+  // Schedule data eviction if necessary as the next call to AppendData will
+  // add aSize bytes.
+  // Eviction is done in two steps, first remove data up to aPlaybackTime
+  // and if still more space is needed remove from the end.
+  EvictDataResult EvictData(const media::TimeUnit& aPlaybackTime, int64_t aSize);
 
-  int64_t GetSize() override;
-
-  void Ended() override;
-
-  void Detach() override;
+  // Returns the buffered range currently managed.
+  // This may be called on any thread.
+  // Buffered must conform to http://w3c.github.io/media-source/index.html#widl-SourceBuffer-buffered
+  media::TimeIntervals Buffered();
 
-  AppendState GetAppendState() override
-  {
-    return mAppendState;
-  }
+  // Return the size of the data managed by this SourceBufferContentManager.
+  int64_t GetSize() const;
 
-  void SetGroupStartTimestamp(const media::TimeUnit& aGroupStartTimestamp) override;
-  void RestartGroupStartTimestamp() override;
-  media::TimeUnit GroupEndTimestamp() override;
-  int64_t EvictionThreshold() const override;
+  // Indicate that the MediaSource parent object got into "ended" state.
+  void Ended();
+
+  // The parent SourceBuffer is about to be destroyed.
+  void Detach();
+
+  int64_t EvictionThreshold() const;
 
   // Interface for MediaSourceDemuxer
   MediaInfo GetMetadata();
   const TrackBuffer& GetTrackBuffer(TrackInfo::TrackType aTrack);
   const media::TimeIntervals& Buffered(TrackInfo::TrackType);
   media::TimeIntervals SafeBuffered(TrackInfo::TrackType) const;
   bool IsEnded() const
   {
@@ -97,69 +145,61 @@ public:
   already_AddRefed<MediaRawData> GetSample(TrackInfo::TrackType aTrack,
                                            const media::TimeUnit& aFuzz,
                                            bool& aError);
   media::TimeUnit GetNextRandomAccessPoint(TrackInfo::TrackType aTrack);
 
   void AddSizeOfResources(MediaSourceDecoder::ResourceSizes* aSizes);
 
 private:
+  typedef MozPromise<bool, nsresult, /* IsExclusive = */ true> CodedFrameProcessingPromise;
+
   // for MediaSourceDemuxer::GetMozDebugReaderData
   friend class MediaSourceDemuxer;
   virtual ~TrackBuffersManager();
   // All following functions run on the taskqueue.
-  RefPtr<AppendPromise> InitSegmentParserLoop();
+  RefPtr<AppendPromise> DoAppendData(RefPtr<MediaByteBuffer> aData,
+                                     SourceBufferAttributes aAttributes);
   void ScheduleSegmentParserLoop();
   void SegmentParserLoop();
-  void AppendIncomingBuffers();
   void InitializationSegmentReceived();
   void ShutdownDemuxers();
   void CreateDemuxerforMIMEType();
   void ResetDemuxingState();
   void NeedMoreData();
   void RejectAppend(nsresult aRejectValue, const char* aName);
   // Will return a promise that will be resolved once all frames of the current
   // media segment have been processed.
   RefPtr<CodedFrameProcessingPromise> CodedFrameProcessing();
   void CompleteCodedFrameProcessing();
   // Called by ResetParserState.
   void CompleteResetParserState();
   RefPtr<RangeRemovalPromise>
     CodedFrameRemovalWithPromise(media::TimeInterval aInterval);
   bool CodedFrameRemoval(media::TimeInterval aInterval);
-  void SetAppendState(AppendState aAppendState);
+  void SetAppendState(SourceBufferAttributes::AppendState aAppendState);
 
   bool HasVideo() const
   {
     return mVideoTracks.mNumTracks > 0;
   }
   bool HasAudio() const
   {
     return mAudioTracks.mNumTracks > 0;
   }
 
-  typedef Pair<RefPtr<MediaByteBuffer>, media::TimeUnit> IncomingBuffer;
-  void AppendIncomingBuffer(IncomingBuffer aData);
-  nsTArray<IncomingBuffer> mIncomingBuffers;
-
   // The input buffer as per http://w3c.github.io/media-source/index.html#sourcebuffer-input-buffer
   RefPtr<MediaByteBuffer> mInputBuffer;
-  // The current append state as per https://w3c.github.io/media-source/#sourcebuffer-append-state
-  // Accessed on both the main thread and the task queue.
-  Atomic<AppendState> mAppendState;
   // Buffer full flag as per https://w3c.github.io/media-source/#sourcebuffer-buffer-full-flag.
   // Accessed on both the main thread and the task queue.
-  // TODO: Unused for now.
   Atomic<bool> mBufferFull;
   bool mFirstInitializationSegmentReceived;
   // Set to true once a new segment is started.
   bool mNewMediaSegmentStarted;
   bool mActiveTrack;
-  Maybe<media::TimeUnit> mGroupStartTimestamp;
-  media::TimeUnit mGroupEndTimestamp;
   nsCString mType;
 
   // ContainerParser objects and methods.
   // Those are used to parse the incoming input buffer.
 
   // Recreate the ContainerParser and if aReuseInitData is true then
   // feed it with the previous init segment found.
   void RecreateParser(bool aReuseInitData);
@@ -196,17 +236,17 @@ private:
   void DoDemuxAudio();
   void OnAudioDemuxCompleted(RefPtr<MediaTrackDemuxer::SamplesHolder> aSamples);
   void OnAudioDemuxFailed(DemuxerFailureReason aFailure)
   {
     mAudioTracks.mDemuxRequest.Complete();
     OnDemuxFailed(TrackType::kAudioTrack, aFailure);
   }
 
-  void DoEvictData(const media::TimeUnit& aPlaybackTime, int64_t aThreshold);
+  void DoEvictData(const media::TimeUnit& aPlaybackTime, int64_t aSizeToEvict);
 
   struct TrackData {
     TrackData()
       : mNumTracks(0)
       , mNeedRandomAccessPoint(true)
       , mSizeBuffer(0)
     {}
     uint32_t mNumTracks;
@@ -299,18 +339,16 @@ private:
   uint32_t FindSampleIndex(const TrackBuffer& aTrackBuffer,
                            const media::TimeInterval& aInterval);
   void UpdateBufferedRanges();
   void RejectProcessing(nsresult aRejectValue, const char* aName);
   void ResolveProcessing(bool aResolveValue, const char* aName);
   MozPromiseRequestHolder<CodedFrameProcessingPromise> mProcessingRequest;
   MozPromiseHolder<CodedFrameProcessingPromise> mProcessingPromise;
 
-  MozPromiseHolder<AppendPromise> mAppendPromise;
-
   // Trackbuffers definition.
   nsTArray<TrackData*> GetTracksList();
   TrackData& GetTracksData(TrackType aTrack)
   {
     switch(aTrack) {
       case TrackType::kVideoTrack:
         return mVideoTracks;
       case TrackType::kAudioTrack:
@@ -324,44 +362,52 @@ private:
   // TaskQueue methods and objects.
   AbstractThread* GetTaskQueue() {
     return mTaskQueue;
   }
   bool OnTaskQueue()
   {
     return !GetTaskQueue() || GetTaskQueue()->IsCurrentThreadIn();
   }
-  RefPtr<TaskQueue> mTaskQueue;
+  RefPtr<AutoTaskQueue> mTaskQueue;
 
+  // SourceBuffer Queues and running context.
+  SourceBufferTaskQueue mQueue;
+  void ProcessTasks();
+  void CancelAllTasks();
+  // Set if the TrackBuffersManager is currently processing a task.
+  // At this stage, this task is always a AppendBufferTask.
+  RefPtr<SourceBufferTask> mCurrentTask;
+  // Current SourceBuffer state for ongoing task.
+  // Its content is returned to the SourceBuffer once the AppendBufferTask has
+  // completed.
+  UniquePtr<SourceBufferAttributes> mSourceBufferAttributes;
+  // The current sourcebuffer append window. It's content is equivalent to
+  // mSourceBufferAttributes.mAppendWindowStart/End
   media::TimeInterval mAppendWindow;
-  media::TimeUnit mTimestampOffset;
-  media::TimeUnit mLastTimestampOffset;
-  void RestoreCachedVariables();
 
   // Strong references to external objects.
-  RefPtr<dom::SourceBufferAttributes> mSourceBufferAttributes;
   nsMainThreadPtrHandle<MediaSourceDecoder> mParentDecoder;
 
   // Set to true if mediasource state changed to ended.
   Atomic<bool> mEnded;
+  // Set to true if the parent SourceBuffer has shutdown.
+  // We will not reschedule or process new task once mDetached is set.
+  Atomic<bool> mDetached;
 
   // Global size of this source buffer content.
   Atomic<int64_t> mSizeSourceBuffer;
   const int64_t mVideoEvictionThreshold;
   const int64_t mAudioEvictionThreshold;
   Atomic<bool> mEvictionOccurred;
 
   // Monitor to protect following objects accessed across multipple threads.
-  // mMonitor is also notified if the value of mAppendRunning becomes false.
   mutable Monitor mMonitor;
-  // Set to true while a BufferAppend is running or is pending.
-  Atomic<bool> mAppendRunning;
   // Stable audio and video track time ranges.
   media::TimeIntervals mVideoBufferedRanges;
   media::TimeIntervals mAudioBufferedRanges;
-  media::TimeUnit mOfficialGroupEndTimestamp;
   // MediaInfo of the first init segment read.
   MediaInfo mInfo;
 };
 
 } // namespace mozilla
 
 #endif /* MOZILLA_TRACKBUFFERSMANAGER_H_ */
--- a/dom/media/mediasource/moz.build
+++ b/dom/media/mediasource/moz.build
@@ -2,36 +2,38 @@
 # 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/.
 
 MOCHITEST_MANIFESTS += ['test/mochitest.ini']
 
 EXPORTS += [
     'AsyncEventRunner.h',
+    'AutoTaskQueue.h',
     'MediaSourceDecoder.h',
     'MediaSourceDemuxer.h',
-    'SourceBufferContentManager.h',
+    'SourceBufferAttributes.h',
+    'SourceBufferTask.h',
+    'TrackBuffersManager.h',
 ]
 
 EXPORTS.mozilla.dom += [
     'MediaSource.h',
     'SourceBuffer.h',
     'SourceBufferList.h',
 ]
 
 UNIFIED_SOURCES += [
     'ContainerParser.cpp',
     'MediaSource.cpp',
     'MediaSourceDecoder.cpp',
     'MediaSourceDemuxer.cpp',
     'MediaSourceUtils.cpp',
     'ResourceQueue.cpp',
     'SourceBuffer.cpp',
-    'SourceBufferContentManager.cpp',
     'SourceBufferList.cpp',
     'SourceBufferResource.cpp',
     'TrackBuffersManager.cpp',
 ]
 
 TEST_DIRS += [
     'gtest',
 ]
--- a/dom/media/test/manifest.js
+++ b/dom/media/test/manifest.js
@@ -215,16 +215,19 @@ var gPlayTests = [
     type:"video/ogg", duration:0.266 },
 
   // Test playback of a webm file
   { name:"seek.webm", type:"video/webm", duration:3.966 },
 
   // Test playback of a WebM file with non-zero start time.
   { name:"split.webm", type:"video/webm", duration:1.967 },
 
+  // Test playback of a WebM file with resolution changes.
+  { name:"resolution-change.webm", type:"video/webm", duration:6.533 },
+
   // Test playback of a raw file
   { name:"seek.yuv", type:"video/x-raw-yuv", duration:1.833 },
 
   // A really short, low sample rate, single channel file. This tests whether
   // we can handle playing files when only push very little audio data to the
   // hardware.
   { name:"spacestorm-1000Hz-100ms.ogg", type:"audio/ogg", duration:0.099 },
 
@@ -279,16 +282,21 @@ var gSnifferTests = [
   { name:"320x240.ogv", type:"video/ogg", width:320, height:240, duration:0.233, size:28942 },
   { name:"seek.webm", type:"video/webm", duration:3.966, size:215529 },
   { name:"gizmo.mp4", type:"video/mp4", duration:5.56, size:383631 },
   // A mp3 file with id3 tags.
   { name:"id3tags.mp3", type:"audio/mpeg", duration:0.28, size:3530},
   { name:"bogus.duh", type:"bogus/duh" }
 ];
 
+// Files that contain resolution changes
+var gResolutionChangeTests = [
+  { name:"resolution-change.webm", type:"video/webm", duration:6.533 },
+];
+
 // Files we must reject as invalid.
 var gInvalidTests = [
   { name:"invalid-m0c0.opus", type:"audio/ogg; codecs=opus"},
   { name:"invalid-m0c3.opus", type:"audio/ogg; codecs=opus"},
   { name:"invalid-m1c0.opus", type:"audio/ogg; codecs=opus"},
   { name:"invalid-m1c9.opus", type:"audio/ogg; codecs=opus"},
   { name:"invalid-m2c0.opus", type:"audio/ogg; codecs=opus"},
   { name:"invalid-m2c1.opus", type:"audio/ogg; codecs=opus"},
--- a/dom/media/test/mochitest.ini
+++ b/dom/media/test/mochitest.ini
@@ -470,16 +470,18 @@ support-files =
   r11025_u8_c1_trunc.wav
   r11025_u8_c1_trunc.wav^headers^
   r16000_u8_c1_list.wav
   r16000_u8_c1_list.wav^headers^
   reactivate_helper.html
   redirect.sjs
   referer.sjs
   region.vtt
+  resolution-change.webm
+  resolution-change.webm^headers^
   sample.3gp
   sample.3g2
   sample-fisbone-skeleton4.ogv
   sample-fisbone-skeleton4.ogv^headers^
   sample-fisbone-wrong-header.ogv
   sample-fisbone-wrong-header.ogv^headers^
   seek.ogv
   seek.ogv^headers^
@@ -767,16 +769,17 @@ skip-if = toolkit == 'gonk' # bug 112884
 [test_reactivate.html]
 skip-if = toolkit == 'gonk' # bug 1128845 on gonk
 [test_readyState.html]
 [test_referer.html]
 [test_replay_metadata.html]
 [test_reset_events_async.html]
 [test_reset_src.html]
 [test_video_dimensions.html]
+[test_resolution_change.html]
 tags=capturestream
 [test_resume.html]
 skip-if = true # bug 1021673
 [test_seek_negative.html]
 [test_seek_nosrc.html]
 [test_seek_out_of_range.html]
 [test_seek-1.html]
 [test_seek-2.html]
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..29aad93b96c54878e7c8bb9c42a1940fb5a26283
GIT binary patch
literal 7166
zc%0RldrVVT0LIU~w58?I@*X0#6PJad10*QMuu<zAI6;{_G%gC_#Ni8_3PTs|EpJ6Y
zCxnM{%rfU27%D<FFr8?OqT=Ec6-5ONn)1kSTIhPEi(PWR5eWa#1j_yWE{C4W`7V5?
zE;AmeDf0FqQ=~DJqQ~&j$?z0ij8LIUNn9GUN|Dacid2Sn4pT1cKXvqZ+mPb*@(CfD
zZDEH}9A_Sg4Li&{2+?F2j-0}et_VA<qP+VLUBfi&q060i<4-9iBjCYK$@qF_c-*W=
zmMG*Q%I(366JjU%dHPKDoapU6Z`yy4*OlgmC%?JA;;XUuk1VYWj=K_c<+IETd3EaB
zQP`_u9B}$k;}=tJGSs?r47HmfZpU{gFI~MXEj2PKGIUBZLyc=mPN8%q5zWSP#pR5D
z4dWhecnUW>%ch^$&~I#btqf7`X9!W2)8t3W!=Jbl5-3bf%_vOCplKq=o^$DZamRep
zr#}>`ROd5fEV)Jz%EpE`6X*F}-9(s0h+1C85Cy5=_lp>1E3aMBCiW_A$n=$HWp%P!
z5@nIOR#qNRE-RTTiw(X%dnL(e?(4Z1rO*5!QktOF2Uu>7j~kU$w7sSJ;>5CBPXk=e
zEPu4;`|1}h2@y~CrqXvGZ(3=W_UeYcpibAgc9zfnZ`LemvC7?e>7a|N)5)_R$O@Sz
zfkHWIQ_!U-&*Cu+<35i6^4YhdaB+L!=;L#q#YUKEZ0lb{c<r=L)bO-x*qMCIH4_6*
z1$-ai7r1yHL!Oa}x7;C`ERL?dO5D8J(<uw?VW<g!hXB4B@La$v0B;1moAVcailH5q
zp#pZTNQr~#TA?@mNi{k)2PgwdU87Gkz2lEjNe8=?yRsfAeH%tiv2efZ)+6J~`+8S*
z>HMe#(c@kRHD9jWl`yU3z*?t<;%uM5_nsWk>!e%v)bR6HNn<CJ7PK$<JTN~;=XNYA
zzCLDdpX~y#s@9ft=hJcXev;qRUAX*Lo-R(`*7f{KuYH}$J*gwHr|D(F7N5eYdm0Yu
zosY|Ug3GFEJG~Z>dUNy4+BrmB!qAgRhjCigSs|gen%s20yuPcWN4qmunpUSSWa#<t
zCN7m5CVncA)OXbpRwZFogw-jm?g3T6(0%WgPZFuUN_V&#zsc-<IG))wtYWZA!Ri;R
z>al8riYEf<9K-WahMIBQgy`NPvsbkFjx&BCP3HUS`t%`odcDL;=U=Ps)EhqQ)slzl
z+Ou!2cZaeImlw>vpn0nR&j!34@N0l~a^DB|4xIM^{v_^U_>q8r2Y4aiHGnq(-V5?B
zkdEZM3*-akU9cD~R)GquQmoEl^%UmaT!`{+{t+C{{0ppBVznKsqgY+Vssn~vXg4gO
z7W#6pT44m>34j*>UJdvi?obPLoS_zt;T}fxDd5q7Gl1^}{13pNfKXcqMslbv+y@G^
z#Z0(ZEtX@Ih1C(P8epg`v?$b;ws1U4AFRT$+JIF)RwuE#4MT1DdRRhj<;uNktv&&K
z5#UO|e+K*vcc`r%bB5ZQ&pnLwhkyqG9tZeVzz+j{8Ss}N>|&dd9Cq=<fx<4H3m36?
zJyv;GoxtiA47>Pm6n2RV98cnp)k3T`V^xCH@7(I$et;;TRV8_3NH`(Gl5pY%I`eeE
zQ72CW{MSKt^5@*0+;Whed^X_mgY4wL0e&4Mnd%<NNv53!?B0I3D*sO{--uQ5;A^>|
zd*4S%ruDe={!cAeV6|jOwR|5|=i#4iv$o+W*kA{XHy&_Q$iWU4FJ~}?Jdfcua)z4M
z1&Xc%;FAHLH^@R>1h{4}h1?K!e&0wAyFfNz@s{JBj5n(U$wTVoXtSz-5wybMEyRu2
z1FKm>>g3s2l?|zrcS0q!$I2I8Ct)PIPQpYyUIA7$Slz`+2bD;QRRC7eSmEs-k(yhv
zZ&(@Mba|-Bsbomi)*goHP*Gd7jG1OfrHXc}+V(F|(Vi^gd6*zx28#qz6C{wbNN8t*
zgbP_@AuvIrE*4q7HbGWCEE3z8AnRsLZl&R0Jmbr<&CxPE_9WXI#*++0MHR8g{-Oyo
NyUilUW)s9u{|9kbM8p69
new file mode 100644
--- /dev/null
+++ b/dom/media/test/resolution-change.webm^headers^
@@ -0,0 +1,1 @@
+Cache-Control: no-store
new file mode 100644
--- /dev/null
+++ b/dom/media/test/test_resolution_change.html
@@ -0,0 +1,52 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <title>Test playback of files with resolution changes</title>
+  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+  <script type="text/javascript" src="manifest.js"></script>
+</head>
+<body>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+var manager = new MediaTestManager;
+
+function loadedData(e) {
+  var v = e.target;
+  v.addEventListener("resize", resize, false);
+  v.play();
+}
+
+function resize(e) {
+  var v = e.target;
+  v.seenResolutionChange = true;
+}
+
+function ended(e)  {
+  var v = e.target;
+  ok(v.seenResolutionChange, v.token + ": A resolution change should have ocurred by the end of playback");
+  removeNodeAndSource(v);
+  manager.finished(v.token);
+}
+
+function startTest(test, token) {
+  var v = document.createElement('video');
+  v.preload = "metadata";
+  v.token = token;
+  v.src = test.name;
+  v.seenResolutionChange = false;
+
+  v.addEventListener("loadeddata", loadedData, false)
+  v.addEventListener("ended", ended, false);
+
+  manager.started(token);
+  document.body.appendChild(v);
+}
+
+manager.runTests(gResolutionChangeTests, startTest);
+
+</script>
+</pre>
+</body>
+</html>
--- a/dom/media/webm/WebMDemuxer.cpp
+++ b/dom/media/webm/WebMDemuxer.cpp
@@ -6,16 +6,17 @@
 
 #include "nsError.h"
 #include "MediaDecoderStateMachine.h"
 #include "AbstractMediaDecoder.h"
 #include "MediaResource.h"
 #include "WebMDemuxer.h"
 #include "WebMBufferedParser.h"
 #include "gfx2DGlue.h"
+#include "mozilla/Atomics.h"
 #include "mozilla/Endian.h"
 #include "mozilla/Preferences.h"
 #include "mozilla/SharedThreadPool.h"
 #include "MediaDataDemuxer.h"
 #include "nsAutoRef.h"
 #include "NesteggPacketHolder.h"
 #include "XiphExtradata.h"
 #include "prprf.h"           // leaving it for PR_vsnprintf()
@@ -37,16 +38,18 @@ using namespace gfx;
 LazyLogModule gWebMDemuxerLog("WebMDemuxer");
 LazyLogModule gNesteggLog("Nestegg");
 
 // How far ahead will we look when searching future keyframe. In microseconds.
 // This value is based on what appears to be a reasonable value as most webm
 // files encountered appear to have keyframes located < 4s.
 #define MAX_LOOK_AHEAD 10000000
 
+static Atomic<uint32_t> sStreamSourceID(0u);
+
 // Functions for reading and seeking using WebMDemuxer required for
 // nestegg_io. The 'user data' passed to these functions is the
 // demuxer.
 static int webmdemux_read(void* aBuffer, size_t aLength, void* aUserData)
 {
   MOZ_ASSERT(aUserData);
   MOZ_ASSERT(aLength < UINT32_MAX);
   WebMDemuxer* demuxer = reinterpret_cast<WebMDemuxer*>(aUserData);
@@ -567,32 +570,51 @@ WebMDemuxer::GetNextPacket(TrackInfo::Tr
         case NESTEGG_CODEC_VP8:
           vpx_codec_peek_stream_info(vpx_codec_vp8_dx(), data, length, &si);
           break;
         case NESTEGG_CODEC_VP9:
           vpx_codec_peek_stream_info(vpx_codec_vp9_dx(), data, length, &si);
           break;
       }
       isKeyframe = si.is_kf;
+      if (isKeyframe) {
+        // We only look for resolution changes on keyframes for both VP8 and
+        // VP9. Other resolution changes are invalid.
+        if (mLastSeenFrameWidth.isSome() && mLastSeenFrameHeight.isSome() &&
+            (si.w != mLastSeenFrameWidth.value() ||
+             si.h != mLastSeenFrameHeight.value())) {
+          // We ignore cropping information on resizes during streams.
+          // Cropping alone is rare, and we do not consider cropping to
+          // still be valid after a resolution change
+          mInfo.mVideo.mDisplay = nsIntSize(si.w, si.h);
+          mInfo.mVideo.mImage = nsIntRect(0, 0, si.w, si.h);
+          mSharedVideoTrackInfo = new SharedTrackInfo(mInfo.mVideo, ++sStreamSourceID);
+        }
+        mLastSeenFrameWidth = Some(si.w);
+        mLastSeenFrameHeight = Some(si.h);
+      }
     }
 
     WEBM_DEBUG("push sample tstamp: %ld next_tstamp: %ld length: %ld kf: %d",
                tstamp, next_tstamp, length, isKeyframe);
     RefPtr<MediaRawData> sample = new MediaRawData(data, length);
     sample->mTimecode = tstamp;
     sample->mTime = tstamp;
     sample->mDuration = next_tstamp - tstamp;
     sample->mOffset = holder->Offset();
     sample->mKeyframe = isKeyframe;
     if (discardPadding && i == count - 1) {
       uint8_t c[8];
       BigEndian::writeInt64(&c[0], discardPadding);
       sample->mExtraData = new MediaByteBuffer;
       sample->mExtraData->AppendElements(&c[0], 8);
     }
+    if (aType == TrackInfo::kVideoTrack) {
+      sample->mTrackInfo = mSharedVideoTrackInfo;
+    }
     aSamples->Push(sample);
   }
   return true;
 }
 
 RefPtr<NesteggPacketHolder>
 WebMDemuxer::NextPacket(TrackInfo::TrackType aType)
 {
--- a/dom/media/webm/WebMDemuxer.h
+++ b/dom/media/webm/WebMDemuxer.h
@@ -196,16 +196,22 @@ private:
   bool mHasAudio;
   bool mNeedReIndex;
 
   // The last complete block parsed by the WebMBufferedState. -1 if not set.
   // We cache those values rather than retrieving them for performance reasons
   // as nestegg only performs 1-byte read at a time.
   int64_t mLastWebMBlockOffset;
   const bool mIsMediaSource;
+
+  Maybe<uint32_t> mLastSeenFrameWidth;
+  Maybe<uint32_t> mLastSeenFrameHeight;
+  // This will be populated only if a resolution change occurs, otherwise it
+  // will be left as null so the original metadata is used
+  RefPtr<SharedTrackInfo> mSharedVideoTrackInfo;
 };
 
 class WebMTrackDemuxer : public MediaTrackDemuxer
 {
 public:
   WebMTrackDemuxer(WebMDemuxer* aParent,
                   TrackInfo::TrackType aType,
                   uint32_t aTrackNumber);
--- a/dom/media/webrtc/MediaEngineWebRTC.h
+++ b/dom/media/webrtc/MediaEngineWebRTC.h
@@ -194,17 +194,17 @@ public:
     // Note: if the device is gone, this will be -1
     return (*mDeviceIndexes)[aIndex]; // translate to mDevices index
   }
 
   static bool GetDeviceID(int aDeviceIndex, CubebUtils::AudioDeviceID &aID)
   {
     int dev_index = DeviceIndex(aDeviceIndex);
     if (dev_index != -1) {
-      aID = mDevices->device[dev_index]->device_id;
+      aID = mDevices->device[dev_index]->devid;
       return true;
     }
     return false;
   }
 
   int GetRecordingDeviceName(int aIndex, char aStrNameUTF8[128],
                              char aStrGuidUTF8[128])
   {
--- a/dom/network/TCPSocket.cpp
+++ b/dom/network/TCPSocket.cpp
@@ -157,16 +157,17 @@ TCPSocket::TCPSocket(nsIGlobalObject* aG
   , mSsl(aSsl)
   , mAsyncCopierActive(false)
   , mWaitingForDrain(false)
   , mInnerWindowID(0)
   , mBufferedAmount(0)
   , mSuspendCount(0)
   , mTrackingNumber(0)
   , mWaitingForStartTLS(false)
+  , mObserversActive(false)
 #ifdef MOZ_WIDGET_GONK
   , mTxBytes(0)
   , mRxBytes(0)
   , mAppId(nsIScriptSecurityManager::UNKNOWN_APP_ID)
   , mInIsolatedMozBrowser(false)
 #endif
 {
   if (aGlobal) {
@@ -174,16 +175,23 @@ TCPSocket::TCPSocket(nsIGlobalObject* aG
     if (window) {
       mInnerWindowID = window->WindowID();
     }
   }
 }
 
 TCPSocket::~TCPSocket()
 {
+  if (mObserversActive) {
+    nsCOMPtr<nsIObserverService> obs = do_GetService("@mozilla.org/observer-service;1");
+    if (obs) {
+      obs->RemoveObserver(this, "inner-window-destroyed");
+      obs->RemoveObserver(this, "profile-change-net-teardown");
+    }
+  }
 }
 
 nsresult
 TCPSocket::CreateStream()
 {
   nsresult rv = mTransport->OpenInputStream(0, 0, 0, getter_AddRefs(mSocketInputStream));
   NS_ENSURE_SUCCESS(rv, rv);
   rv = mTransport->OpenOutputStream(nsITransport::OPEN_UNBUFFERED, 0, 0, getter_AddRefs(mSocketOutputStream));
@@ -253,17 +261,19 @@ TCPSocket::InitWithUnconnectedTransport(
   return NS_OK;
 }
 
 nsresult
 TCPSocket::Init()
 {
   nsCOMPtr<nsIObserverService> obs = do_GetService("@mozilla.org/observer-service;1");
   if (obs) {
-    obs->AddObserver(this, "inner-window-destroyed", true);
+    mObserversActive = true;
+    obs->AddObserver(this, "inner-window-destroyed", true); // weak reference
+    obs->AddObserver(this, "profile-change-net-teardown", true); // weak ref
   }
 
   if (XRE_GetProcessType() == GeckoProcessType_Content) {
     mReadyState = TCPReadyState::Connecting;
     mSocketBridgeChild = new TCPSocketChild(mHost, mPort);
     mSocketBridgeChild->SendOpen(this, mSsl, mUseArrayBuffers);
     return NS_OK;
   }
@@ -371,16 +381,17 @@ CopierCallbacks::OnStartRequest(nsIReque
 {
   return NS_OK;
 }
 
 NS_IMETHODIMP
 CopierCallbacks::OnStopRequest(nsIRequest* aRequest, nsISupports* aContext, nsresult aStatus)
 {
   mOwner->NotifyCopyComplete(aStatus);
+  mOwner = nullptr;
   return NS_OK;
 }
 } // unnamed namespace
 
 nsresult
 TCPSocket::EnsureCopying()
 {
   if (mAsyncCopierActive) {
@@ -437,17 +448,20 @@ TCPSocket::NotifyCopyComplete(nsresult a
   // If we have a connected child, we let the child decide whether
   // ondrain should be dispatched.
   if (mWaitingForDrain && !mSocketBridgeParent) {
     mWaitingForDrain = false;
     FireEvent(NS_LITERAL_STRING("drain"));
   }
 
   if (mReadyState == TCPReadyState::Closing) {
-    mSocketOutputStream->Close();
+    if (mSocketOutputStream) {
+      mSocketOutputStream->Close();
+      mSocketOutputStream = nullptr;
+    }
     mReadyState = TCPReadyState::Closed;
     FireEvent(NS_LITERAL_STRING("close"));
   }
 }
 
 void
 TCPSocket::ActivateTLS()
 {
@@ -629,16 +643,19 @@ TCPSocket::MaybeReportErrorAndCloseIfOpe
   SaveNetworkStats(true);
 #endif
 
   // If we're closed, we've already reported the error or just don't need to
   // report the error.
   if (mReadyState == TCPReadyState::Closed) {
     return NS_OK;
   }
+
+  // go through ::Closing state and then mark ::Closed
+  Close();
   mReadyState = TCPReadyState::Closed;
 
   if (NS_FAILED(status)) {
     // Convert the status code to an appropriate error message.
 
     nsString errorType, errName;
 
     // security module? (and this is an error)
@@ -753,21 +770,29 @@ TCPSocket::Close()
   mReadyState = TCPReadyState::Closing;
 
   if (mSocketBridgeChild) {
     mSocketBridgeChild->SendClose();
     return;
   }
 
   uint32_t count = 0;
-  mMultiplexStream->GetCount(&count);
+  if (mMultiplexStream) {
+    mMultiplexStream->GetCount(&count);
+  }
   if (!count) {
-    mSocketOutputStream->Close();
+    if (mSocketOutputStream) {
+      mSocketOutputStream->Close();
+      mSocketOutputStream = nullptr;
+    }
   }
-  mSocketInputStream->Close();
+  if (mSocketInputStream) {
+    mSocketInputStream->Close();
+    mSocketInputStream = nullptr;
+  }
 }
 
 void
 TCPSocket::SendWithTrackingNumber(const nsACString& aData,
                                   const uint32_t& aTrackingNumber,
                                   mozilla::ErrorResult& aRv)
 {
   MOZ_ASSERT(mSocketBridgeParent);
@@ -950,16 +975,19 @@ TCPSocket::Constructor(const GlobalObjec
   }
 
   return socket.forget();
 }
 
 nsresult
 TCPSocket::CreateInputStreamPump()
 {
+  if (!mSocketInputStream) {
+    return NS_ERROR_NOT_AVAILABLE;
+  }
   nsresult rv;
   mInputStreamPump = do_CreateInstance("@mozilla.org/network/input-stream-pump;1", &rv);
   NS_ENSURE_SUCCESS(rv, rv);
 
   rv = mInputStreamPump->Init(mSocketInputStream, -1, -1, 0, 0, false);
   NS_ENSURE_SUCCESS(rv, rv);
 
   uint64_t suspendCount = mSuspendCount;
@@ -1171,23 +1199,20 @@ TCPSocket::Observe(nsISupports* aSubject
     NS_ENSURE_TRUE(wrapper, NS_ERROR_FAILURE);
     uint64_t innerID;
     nsresult rv = wrapper->GetData(&innerID);
     if (NS_WARN_IF(NS_FAILED(rv))) {
       return rv;
     }
 
     if (innerID == mInnerWindowID) {
-      nsCOMPtr<nsIObserverService> obs = do_GetService("@mozilla.org/observer-service;1");
-      if (obs) {
-        obs->RemoveObserver(this, "inner-window-destroyed");
-      }
-
       Close();
     }
+  } else if (!strcmp(aTopic, "profile-change-net-teardown")) {
+    Close();
   }
 
   return NS_OK;
 }
 
 /* static */
 bool
 TCPSocket::ShouldTCPSocketExist(JSContext* aCx, JSObject* aGlobal)
--- a/dom/network/TCPSocket.h
+++ b/dom/network/TCPSocket.h
@@ -234,16 +234,18 @@ private:
   uint32_t mTrackingNumber;
 
   // True if this socket has been upgraded to secure after the initial connection,
   // but the actual upgrade is waiting for an in-progress copy operation to complete.
   bool mWaitingForStartTLS;
   // The buffered data awaiting the TLS upgrade to finish.
   nsTArray<nsCOMPtr<nsIInputStream>> mPendingDataAfterStartTLS;
 
+  bool mObserversActive;
+
 #ifdef MOZ_WIDGET_GONK
   // Number of bytes sent.
   uint32_t mTxBytes;
   // Number of bytes received.
   uint32_t mRxBytes;
   // The app that owns this socket.
   uint32_t mAppId;
   // Was this socket created inside of an isolated browser frame?
--- a/dom/network/tests/test_tcpsocket_enabled_with_perm.html
+++ b/dom/network/tests/test_tcpsocket_enabled_with_perm.html
@@ -21,14 +21,17 @@ SpecialPowers.pushPrefEnv({"set": [['dom
   SpecialPowers.pushPermissions([{type: "tcp-socket", allow: true, context: document}], runTest);
 });
 
 function runTest() {
   ok('TCPSocket' in this, "TCPSocket should be accessible if dom.mozTCPSocket.enabled is true");
 
   ok(new TCPSocket('localhost', 80), "TCPSocket constructor should work for content that has the tcp-socket permission");
   ok(navigator.mozTCPSocket.open('localhost', 80), "navigator.mozTCPSocket.open should work for content that has the tcp-socket permission");
+  // This just helps the test harness clean up quickly
+  SpecialPowers.forceCC();
+  SpecialPowers.forceGC();
   SimpleTest.finish();
 }
 </script>
 </pre>
 </body>
 </html>
--- a/dom/network/tests/test_tcpsocket_legacy.html
+++ b/dom/network/tests/test_tcpsocket_legacy.html
@@ -35,22 +35,31 @@ Test of legacy navigator interface for o
     // test was using.
     var serverPort = 8085;
 
     var listeningServer = navigator.mozTCPSocket.listen(serverPort,
                                                         { binaryType: 'arraybuffer' },
                                                         -1);
     listeningServer.onconnect = function(ev) {
       ok(true, "got server connect");
+      listeningServer.close();
+      listeningServer = null;
       ev.socket.close()
     }
 
     var clientSocket = navigator.mozTCPSocket.open('127.0.0.1', serverPort,
                                                    { binaryType: 'arraybuffer' });
     clientSocket.onopen = function() { ok(true, "got client open"); }
     clientSocket.onclose = function() {
       ok(true, "got client close");
-      SimpleTest.finish();
+      clientSocket.close();
+      clientSocket = null;
+      setTimeout(function() {
+        // This just helps the test harness clean up quickly
+        SpecialPowers.forceCC();
+        SpecialPowers.forceGC();
+        SimpleTest.finish();
+      }, 0);
     }
   }
 </script>
 </body>
 </html>
--- a/dom/plugins/base/nsPluginInstanceOwner.cpp
+++ b/dom/plugins/base/nsPluginInstanceOwner.cpp
@@ -2578,17 +2578,17 @@ nsEventStatus nsPluginInstanceOwner::Pro
         switch (anEvent.mMessage) {
           case eMouseOver:
           case eMouseOut:
             {
               XCrossingEvent& event = pluginEvent.xcrossing;
               event.type = anEvent.mMessage == eMouseOver ?
                 EnterNotify : LeaveNotify;
               event.root = root;
-              event.time = anEvent.time;
+              event.time = anEvent.mTime;
               event.x = pluginPoint.x;
               event.y = pluginPoint.y;
               event.x_root = rootPoint.x;
               event.y_root = rootPoint.y;
               event.state = XInputEventState(mouseEvent);
               // information lost
               event.subwindow = None;
               event.mode = -1;
@@ -2597,17 +2597,17 @@ nsEventStatus nsPluginInstanceOwner::Pro
               event.focus = mContentFocused;
             }
             break;
           case eMouseMove:
             {
               XMotionEvent& event = pluginEvent.xmotion;
               event.type = MotionNotify;
               event.root = root;
-              event.time = anEvent.time;
+              event.time = anEvent.mTime;
               event.x = pluginPoint.x;
               event.y = pluginPoint.y;
               event.x_root = rootPoint.x;
               event.y_root = rootPoint.y;
               event.state = XInputEventState(mouseEvent);
               // information lost
               event.subwindow = None;
               event.is_hint = NotifyNormal;
@@ -2616,17 +2616,17 @@ nsEventStatus nsPluginInstanceOwner::Pro
             break;
           case eMouseDown:
           case eMouseUp:
             {
               XButtonEvent& event = pluginEvent.xbutton;
               event.type = anEvent.mMessage == eMouseDown ?
                 ButtonPress : ButtonRelease;
               event.root = root;
-              event.time = anEvent.time;
+              event.time = anEvent.mTime;
               event.x = pluginPoint.x;
               event.y = pluginPoint.y;
               event.x_root = rootPoint.x;
               event.y_root = rootPoint.y;
               event.state = XInputEventState(mouseEvent);
               switch (mouseEvent.button)
                 {
                 case WidgetMouseEvent::eMiddleButton:
@@ -2653,17 +2653,17 @@ nsEventStatus nsPluginInstanceOwner::Pro
    //XXX case eMouseScrollEventClass: not received.
 
    case eKeyboardEventClass:
       if (anEvent.mPluginEvent)
         {
           XKeyEvent &event = pluginEvent.xkey;
 #ifdef MOZ_WIDGET_GTK
           event.root = GDK_ROOT_WINDOW();
-          event.time = anEvent.time;
+          event.time = anEvent.mTime;
           const GdkEventKey* gdkEvent =
             static_cast<const GdkEventKey*>(anEvent.mPluginEvent);
           event.keycode = gdkEvent->hardware_keycode;
           event.state = gdkEvent->state;
           switch (anEvent.mMessage)
             {
             case eKeyDown:
               // Handle eKeyDown for modifier key presses
--- a/dom/security/nsCSPService.cpp
+++ b/dom/security/nsCSPService.cpp
@@ -36,19 +36,29 @@ CSPService::CSPService()
 
 CSPService::~CSPService()
 {
   mAppStatusCache.Clear();
 }
 
 NS_IMPL_ISUPPORTS(CSPService, nsIContentPolicy, nsIChannelEventSink)
 
-// Helper function to identify protocols not subject to CSP.
+// Helper function to identify protocols and content types not subject to CSP.
 bool
-subjectToCSP(nsIURI* aURI) {
+subjectToCSP(nsIURI* aURI, nsContentPolicyType aContentType) {
+  // These content types are not subject to CSP content policy checks:
+  // TYPE_CSP_REPORT -- csp can't block csp reports
+  // TYPE_REFRESH    -- never passed to ShouldLoad (see nsIContentPolicy.idl)
+  // TYPE_DOCUMENT   -- used for frame-ancestors
+  if (aContentType == nsIContentPolicy::TYPE_CSP_REPORT ||
+      aContentType == nsIContentPolicy::TYPE_REFRESH ||
+      aContentType == nsIContentPolicy::TYPE_DOCUMENT) {
+    return false;
+  }
+
   // The three protocols: data:, blob: and filesystem: share the same
   // protocol flag (URI_IS_LOCAL_RESOURCE) with other protocols, like
   // chrome:, resource:, moz-icon:, but those three protocols get
   // special attention in CSP and are subject to CSP, hence we have
   // to make sure those protocols are subject to CSP, see:
   // http://www.w3.org/TR/CSP2/#source-list-guid-matching
   bool match = false;
   nsresult rv = aURI->SchemeIs("data", &match);
@@ -112,31 +122,21 @@ CSPService::ShouldLoad(uint32_t aContent
     MOZ_LOG(gCspPRLog, LogLevel::Debug,
            ("CSPService::ShouldLoad called for %s", location.get()));
   }
 
   // default decision, CSP can revise it if there's a policy to enforce
   *aDecision = nsIContentPolicy::ACCEPT;
 
   // No need to continue processing if CSP is disabled or if the protocol
-  // is *not* subject to CSP.
+  // or type is *not* subject to CSP.
   // Please note, the correct way to opt-out of CSP using a custom
   // protocolHandler is to set one of the nsIProtocolHandler flags
   // that are whitelistet in subjectToCSP()
-  if (!sCSPEnabled || !subjectToCSP(aContentLocation)) {
-    return NS_OK;
-  }
-
-  // These content types are not subject to CSP content policy checks:
-  // TYPE_CSP_REPORT -- csp can't block csp reports
-  // TYPE_REFRESH    -- never passed to ShouldLoad (see nsIContentPolicy.idl)
-  // TYPE_DOCUMENT   -- used for frame-ancestors
-  if (aContentType == nsIContentPolicy::TYPE_CSP_REPORT ||
-    aContentType == nsIContentPolicy::TYPE_REFRESH ||
-    aContentType == nsIContentPolicy::TYPE_DOCUMENT) {
+  if (!sCSPEnabled || !subjectToCSP(aContentLocation, aContentType)) {
     return NS_OK;
   }
 
   // query the principal of the document; if no document is passed, then
   // fall back to using the requestPrincipal (e.g. service workers do not
   // pass a document).
   nsCOMPtr<nsINode> node(do_QueryInterface(aRequestContext));
   nsCOMPtr<nsIPrincipal> principal = node ? node->NodePrincipal()
@@ -247,44 +247,43 @@ CSPService::AsyncOnChannelRedirect(nsICh
                                    nsIAsyncVerifyRedirectCallback *callback)
 {
   nsAsyncRedirectAutoCallback autoCallback(callback);
 
   nsCOMPtr<nsIURI> newUri;
   nsresult rv = newChannel->GetURI(getter_AddRefs(newUri));
   NS_ENSURE_SUCCESS(rv, rv);
 
+  nsCOMPtr<nsILoadInfo> loadInfo = oldChannel->GetLoadInfo();
+
+  // if no loadInfo on the channel, nothing for us to do
+  if (!loadInfo) {
+    return NS_OK;
+  }
+
   // No need to continue processing if CSP is disabled or if the protocol
   // is *not* subject to CSP.
   // Please note, the correct way to opt-out of CSP using a custom
   // protocolHandler is to set one of the nsIProtocolHandler flags
   // that are whitelistet in subjectToCSP()
-  if (!sCSPEnabled || !subjectToCSP(newUri)) {
-    return NS_OK;
-  }
-
-  nsCOMPtr<nsILoadInfo> loadInfo;
-  rv = oldChannel->GetLoadInfo(getter_AddRefs(loadInfo));
-
-  // if no loadInfo on the channel, nothing for us to do
-  if (!loadInfo) {
+  nsContentPolicyType policyType = loadInfo->InternalContentPolicyType();
+  if (!sCSPEnabled || !subjectToCSP(newUri, policyType)) {
     return NS_OK;
   }
 
   /* Since redirecting channels don't call into nsIContentPolicy, we call our
    * Content Policy implementation directly when redirects occur using the
    * information set in the LoadInfo when channels are created.
    *
    * We check if the CSP permits this host for this type of load, if not,
    * we cancel the load now.
    */
   nsCOMPtr<nsIURI> originalUri;
   rv = oldChannel->GetOriginalURI(getter_AddRefs(originalUri));
   NS_ENSURE_SUCCESS(rv, rv);
-  nsContentPolicyType policyType = loadInfo->InternalContentPolicyType();
 
   bool isPreload = nsContentUtils::IsPreloadType(policyType);
 
   /* On redirect, if the content policy is a preload type, rejecting the preload
    * results in the load silently failing, so we convert preloads to the actual
    * type. See Bug 1219453.
    */
   policyType =
--- a/dom/smil/TimeEvent.cpp
+++ b/dom/smil/TimeEvent.cpp
@@ -14,17 +14,17 @@
 namespace mozilla {
 namespace dom {
 
 TimeEvent::TimeEvent(EventTarget* aOwner,
                      nsPresContext* aPresContext,
                      InternalSMILTimeEvent* aEvent)
   : Event(aOwner, aPresContext,
           aEvent ? aEvent : new InternalSMILTimeEvent(false, eVoidEvent))
-  , mDetail(mEvent->AsSMILTimeEvent()->detail)
+  , mDetail(mEvent->AsSMILTimeEvent()->mDetail)
 {
   if (aEvent) {
     mEventIsInternal = false;
   } else {
     mEventIsInternal = true;
   }
 
   if (mPresContext) {
--- a/dom/smil/nsSMILTimedElement.cpp
+++ b/dom/smil/nsSMILTimedElement.cpp
@@ -92,17 +92,17 @@ namespace
       , mMsg(aMsg)
       , mDetail(aDetail)
     {
     }
 
     NS_IMETHOD Run()
     {
       InternalSMILTimeEvent event(true, mMsg);
-      event.detail = mDetail;
+      event.mDetail = mDetail;
 
       nsPresContext* context = nullptr;
       nsIDocument* doc = mTarget->GetCurrentDoc();
       if (doc) {
         nsCOMPtr<nsIPresShell> shell = doc->GetShell();
         if (shell) {
           context = shell->GetPresContext();
         }
--- a/dom/svg/SVGZoomEvent.cpp
+++ b/dom/svg/SVGZoomEvent.cpp
@@ -35,17 +35,17 @@ SVGZoomEvent::SVGZoomEvent(EventTarget* 
   , mPreviousScale(0)
   , mNewScale(0)
 {
   if (aEvent) {
     mEventIsInternal = false;
   }
   else {
     mEventIsInternal = true;
-    mEvent->time = PR_Now();
+    mEvent->mTime = PR_Now();
   }
 
   // We must store the "Previous" and "New" values before this event is
   // dispatched. Reading the values from the root 'svg' element after we've
   // been dispatched is not an option since event handler code may change
   // currentScale and currentTranslate in response to this event.
   nsIPresShell *presShell;
   if (mPresContext && (presShell = mPresContext->GetPresShell())) {
--- a/dom/xbl/nsXBLPrototypeResources.cpp
+++ b/dom/xbl/nsXBLPrototypeResources.cpp
@@ -75,31 +75,33 @@ nsXBLPrototypeResources::FlushSkinSheets
   // If doc is null, we're in the process of tearing things down, so just
   // return without rebuilding anything.
   if (!doc) {
     return NS_OK;
   }
 
   // We have scoped stylesheets.  Reload any chrome stylesheets we
   // encounter.  (If they aren't skin sheets, it doesn't matter, since
-  // they'll still be in the chrome cache.
+  // they'll still be in the chrome cache.  Skip inline sheets, which
+  // skin sheets can't be, and which in any case don't have a usable
+  // URL to reload.)
 
   nsTArray<StyleSheetHandle::RefPtr> oldSheets;
 
   oldSheets.SwapElements(mStyleSheetList);
 
   mozilla::css::Loader* cssLoader = doc->CSSLoader();
 
   for (size_t i = 0, count = oldSheets.Length(); i < count; ++i) {
     StyleSheetHandle oldSheet = oldSheets[i];
 
     nsIURI* uri = oldSheet->GetSheetURI();
 
     StyleSheetHandle::RefPtr newSheet;
-    if (IsChromeURI(uri)) {
+    if (!oldSheet->IsInline() && IsChromeURI(uri)) {
       if (NS_FAILED(cssLoader->LoadSheetSync(uri, &newSheet)))
         continue;
     }
     else {
       newSheet = oldSheet;
     }
 
     mStyleSheetList.AppendElement(newSheet);
--- a/editor/libeditor/nsEditor.cpp
+++ b/editor/libeditor/nsEditor.cpp
@@ -1779,17 +1779,17 @@ public:
     nsCOMPtr<nsIWidget> widget = mEditor->GetWidget();
     if (!widget) {
       return NS_OK;
     }
 
     // Even if the change is caused by untrusted event, we need to dispatch
     // trusted input event since it's a fact.
     InternalEditorInputEvent inputEvent(true, eEditorInput, widget);
-    inputEvent.time = static_cast<uint64_t>(PR_Now() / 1000);
+    inputEvent.mTime = static_cast<uint64_t>(PR_Now() / 1000);
     inputEvent.mIsComposing = mIsComposing;
     nsEventStatus status = nsEventStatus_eIgnore;
     nsresult rv =
       ps->HandleEventWithTarget(&inputEvent, nullptr, mTarget, &status);
     NS_ENSURE_SUCCESS(rv, NS_OK); // print the warning if error
     return NS_OK;
   }
 
--- a/gfx/2d/DrawTargetSkia.cpp
+++ b/gfx/2d/DrawTargetSkia.cpp
@@ -12,16 +12,17 @@
 #include "HelpersSkia.h"
 
 #include "skia/include/core/SkSurface.h"
 #include "skia/include/core/SkTypeface.h"
 #include "skia/include/effects/SkGradientShader.h"
 #include "skia/include/core/SkColorFilter.h"
 #include "skia/include/effects/SkBlurImageFilter.h"
 #include "skia/include/effects/SkLayerRasterizer.h"
+#include "Blur.h"
 #include "Logging.h"
 #include "Tools.h"
 #include "DataSurfaceHelpers.h"
 #include <algorithm>
 
 #ifdef USE_SKIA_GPU
 #include "GLDefs.h"
 #include "skia/include/gpu/SkGr.h"
@@ -427,26 +428,47 @@ DrawTargetSkia::DrawSurfaceWithShadow(So
   // We can't use the SkDropShadowImageFilter here because it applies the xfer
   // mode first to render the bitmap to a temporary layer, and then implicitly
   // uses src-over to composite the resulting shadow.
   // The canvas spec, however, states that the composite op must be used to
   // composite the resulting shadow, so we must instead use a SkBlurImageFilter
   // to blur the image ourselves.
 
   SkPaint shadowPaint;
-  SkAutoTUnref<SkImageFilter> blurFilter(SkBlurImageFilter::Create(aSigma, aSigma));
-  SkAutoTUnref<SkColorFilter> colorFilter(
-    SkColorFilter::CreateModeFilter(ColorToSkColor(aColor, 1.0), SkXfermode::kSrcIn_Mode));
-
   shadowPaint.setXfermode(paint.getXfermode());
-  shadowPaint.setImageFilter(blurFilter.get());
-  shadowPaint.setColorFilter(colorFilter.get());
 
   IntPoint shadowDest = RoundedToInt(aDest + aOffset);
-  mCanvas->drawBitmap(bitmap, shadowDest.x, shadowDest.y, &shadowPaint);
+
+  SkBitmap blurMask;
+  if (!UsingSkiaGPU() &&
+      bitmap.extractAlpha(&blurMask)) {
+    // Prefer using our own box blur instead of Skia's when we're
+    // not using the GPU. It currently performs much better than
+    // SkBlurImageFilter or SkBlurMaskFilter on the CPU.
+    AlphaBoxBlur blur(Rect(0, 0, blurMask.width(), blurMask.height()),
+                      int32_t(blurMask.rowBytes()),
+                      aSigma, aSigma);
+    blurMask.lockPixels();
+    blur.Blur(reinterpret_cast<uint8_t*>(blurMask.getPixels()));
+    blurMask.unlockPixels();
+    blurMask.notifyPixelsChanged();
+
+    shadowPaint.setColor(ColorToSkColor(aColor, 1.0f));
+
+    mCanvas->drawBitmap(blurMask, shadowDest.x, shadowDest.y, &shadowPaint);
+  } else {
+    SkAutoTUnref<SkImageFilter> blurFilter(SkBlurImageFilter::Create(aSigma, aSigma));
+    SkAutoTUnref<SkColorFilter> colorFilter(
+      SkColorFilter::CreateModeFilter(ColorToSkColor(aColor, 1.0f), SkXfermode::kSrcIn_Mode));
+
+    shadowPaint.setImageFilter(blurFilter.get());
+    shadowPaint.setColorFilter(colorFilter.get());
+
+    mCanvas->drawBitmap(bitmap, shadowDest.x, shadowDest.y, &shadowPaint);
+  }
 
   // Composite the original image after the shadow
   IntPoint dest = RoundedToInt(aDest);
   mCanvas->drawBitmap(bitmap, dest.x, dest.y, &paint);
 
   mCanvas->restore();
 }
 
--- a/gfx/2d/Logging.h
+++ b/gfx/2d/Logging.h
@@ -127,16 +127,17 @@ enum class LogReason : int {
   FilterNodeD2D1Backend,
   SourceSurfaceIncompatible,
   GlyphAllocFailedCairo,
   GlyphAllocFailedCG,
   InvalidRect,
   CannotDraw3D, // 20
   IncompatibleBasicTexturedEffect,
   InvalidFont,
+  PAllocTextureBackendMismatch,
   // End
   MustBeLessThanThis = 101,
 };
 
 struct BasicLogger
 {
   // For efficiency, this method exists and copies the logic of the
   // OutputMessage below.  If making any changes here, also make it
--- a/gfx/layers/Compositor.cpp
+++ b/gfx/layers/Compositor.cpp
@@ -393,16 +393,28 @@ Compositor::ComputeBackdropCopyRect(cons
   gfx::Matrix4x4 transform;
   transform.PostScale(rtSize.width, rtSize.height, 1.0);
   transform.PostTranslate(-result.x, -result.y, 0.0);
   transform.PostScale(1 / float(result.width), 1 / float(result.height), 1.0);
   *aOutTransform = transform;
   return result;
 }
 
+void
+Compositor::SetInvalid()
+{
+  mParent = nullptr;
+}
+
+bool
+Compositor::IsValid() const
+{
+  return !mParent;
+}
+
 #if defined(MOZ_WIDGET_GONK) && ANDROID_VERSION >= 17
 void
 Compositor::SetDispAcquireFence(Layer* aLayer, nsIWidget* aWidget)
 {
   // OpenGL does not provide ReleaseFence for rendering.
   // Instead use DispAcquireFence as layer buffer's ReleaseFence
   // to prevent flickering and tearing.
   // DispAcquireFence is DisplaySurface's AcquireFence.
--- a/gfx/layers/Compositor.h
+++ b/gfx/layers/Compositor.h
@@ -512,16 +512,21 @@ public:
         mCompositeUntilTime < aTimeStamp) {
       mCompositeUntilTime = aTimeStamp;
     }
   }
   TimeStamp GetCompositeUntilTime() const {
     return mCompositeUntilTime;
   }
 
+  // A stale Compositor has no CompositorBridgeParent; it will not process
+  // frames and should not be used.
+  void SetInvalid();
+  bool IsValid() const;
+
 protected:
   void DrawDiagnosticsInternal(DiagnosticFlags aFlags,
                                const gfx::Rect& aVisibleRect,
                                const gfx::Rect& aClipRect,
                                const gfx::Matrix4x4& transform,
                                uint32_t aFlashCounter);
 
   bool ShouldDrawDiagnostics(DiagnosticFlags);
--- a/gfx/layers/CompositorTypes.h
+++ b/gfx/layers/CompositorTypes.h
@@ -58,19 +58,22 @@ enum class TextureFlags : uint32_t {
   // textures.
   IMMUTABLE          = 1 << 9,
   // The contents of the texture must be uploaded or copied immediately
   // during the transaction, because the producer may want to write
   // to it again.
   IMMEDIATE_UPLOAD   = 1 << 10,
   // The texture is part of a component-alpha pair
   COMPONENT_ALPHA    = 1 << 11,
+  // The texture is being allocated for a compositor that no longer exists.
+  // This flag is only used in the parent process.
+  INVALID_COMPOSITOR = 1 << 12,
 
   // OR union of all valid bits
-  ALL_BITS           = (1 << 12) - 1,
+  ALL_BITS           = (1 << 13) - 1,
   // the default flags
   DEFAULT = NO_FLAGS
 };
 MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(TextureFlags)
 
 static inline bool
 TextureRequiresLocking(TextureFlags aFlags)
 {
--- a/gfx/layers/apz/src/APZCTreeManager.cpp
+++ b/gfx/layers/apz/src/APZCTreeManager.cpp
@@ -1088,17 +1088,17 @@ APZCTreeManager::ProcessWheelEvent(Widge
         gfxPrefs::WheelSmoothScrollEnabled()) ||
        (aEvent.deltaMode == nsIDOMWheelEvent::DOM_DELTA_PAGE &&
         gfxPrefs::PageSmoothScrollEnabled())))
   {
     scrollMode = ScrollWheelInput::SCROLLMODE_SMOOTH;
   }
 
   ScreenPoint origin(aEvent.refPoint.x, aEvent.refPoint.y);
-  ScrollWheelInput input(aEvent.time, aEvent.timeStamp, 0,
+  ScrollWheelInput input(aEvent.mTime, aEvent.mTimeStamp, 0,
                          scrollMode,
                          ScrollWheelInput::DeltaTypeForDeltaMode(aEvent.deltaMode),
                          origin,
                          aEvent.deltaX, aEvent.deltaY,
                          aEvent.mAllowToOverrideSystemScrollSpeed);
 
   // We add the user multiplier as a separate field, rather than premultiplying
   // it, because if the input is converted back to a WidgetWheelEvent, then
--- a/gfx/layers/apz/util/APZCCallbackHelper.cpp
+++ b/gfx/layers/apz/util/APZCCallbackHelper.cpp
@@ -569,17 +569,17 @@ APZCCallbackHelper::DispatchSynthesizedM
                                                   nsIWidget* aWidget)
 {
   MOZ_ASSERT(aMsg == eMouseMove || aMsg == eMouseDown ||
              aMsg == eMouseUp || aMsg == eMouseLongTap);
 
   WidgetMouseEvent event(true, aMsg, nullptr,
                          WidgetMouseEvent::eReal, WidgetMouseEvent::eNormal);
   event.refPoint = LayoutDeviceIntPoint(aRefPoint.x, aRefPoint.y);
-  event.time = aTime;
+  event.mTime = aTime;
   event.button = WidgetMouseEvent::eLeftButton;
   event.inputSource = nsIDOMMouseEvent::MOZ_SOURCE_TOUCH;
   event.ignoreRootScrollFrame = true;
   if (aMsg != eMouseMove) {
     event.clickCount = 1;
   }
   event.modifiers = aModifiers;
   event.widget = aWidget;
--- a/gfx/layers/basic/BasicCompositor.cpp
+++ b/gfx/layers/basic/BasicCompositor.cpp
@@ -73,18 +73,19 @@ public:
     SetUpdateSerial(0);
   }
 
 public:
   RefPtr<gfx::DataSourceSurface> mSurface;
   bool mWrappingExistingData;
 };
 
-BasicCompositor::BasicCompositor(nsIWidget *aWidget)
-  : mWidget(aWidget)
+BasicCompositor::BasicCompositor(CompositorBridgeParent* aParent, nsIWidget *aWidget)
+  : Compositor(aParent)
+  , mWidget(aWidget)
   , mDidExternalComposition(false)
 {
   MOZ_COUNT_CTOR(BasicCompositor);
 
   mMaxTextureSize =
     Factory::GetMaxSurfaceSize(gfxPlatform::GetPlatform()->GetContentBackendFor(LayersBackend::LAYERS_BASIC));
 }
 
--- a/gfx/layers/basic/BasicCompositor.h
+++ b/gfx/layers/basic/BasicCompositor.h
@@ -37,17 +37,17 @@ public:
 
   RefPtr<gfx::DrawTarget> mDrawTarget;
   gfx::IntSize mSize;
 };
 
 class BasicCompositor : public Compositor
 {
 public:
-  explicit BasicCompositor(nsIWidget *aWidget);
+  explicit BasicCompositor(CompositorBridgeParent* aParent, nsIWidget *aWidget);
 
 protected:
   virtual ~BasicCompositor();
 
 public:
   virtual bool Initialize() override;
 
   virtual void Destroy() override;
--- a/gfx/layers/basic/X11BasicCompositor.h
+++ b/gfx/layers/basic/X11BasicCompositor.h
@@ -41,18 +41,19 @@ public:
 private:
   // We are going to buffer layer content on this xlib draw target
   RefPtr<mozilla::gfx::DrawTarget> mBufferDrawTarget;
 };
 
 class X11BasicCompositor : public BasicCompositor
 {
 public:
-
-  explicit X11BasicCompositor(nsIWidget *aWidget) : BasicCompositor(aWidget) {}
+  explicit X11BasicCompositor(CompositorBridgeParent* aParent, nsIWidget *aWidget)
+    : BasicCompositor(aParent, aWidget)
+  {}
 
   virtual already_AddRefed<DataTextureSource>
   CreateDataTextureSource(TextureFlags aFlags = TextureFlags::NO_FLAGS) override;
 
   virtual already_AddRefed<DataTextureSource>
   CreateDataTextureSourceAround(gfx::DataSourceSurface* aSurface) override { return nullptr; }
 
   virtual void EndFrame() override;
--- a/gfx/layers/d3d11/CompositorD3D11.cpp
+++ b/gfx/layers/d3d11/CompositorD3D11.cpp
@@ -153,18 +153,19 @@ private:
     return true;
   }
 
   // Only used during initialization.
   RefPtr<ID3D11Device> mDevice;
   bool mInitOkay;
 };
 
-CompositorD3D11::CompositorD3D11(nsIWidget* aWidget)
-  : mAttachments(nullptr)
+CompositorD3D11::CompositorD3D11(CompositorBridgeParent* aParent, nsIWidget* aWidget)
+  : Compositor(aParent)
+  , mAttachments(nullptr)
   , mWidget(aWidget)
   , mHwnd(nullptr)
   , mDisableSequenceForNextFrame(false)
   , mVerifyBuffersFailed(false)
 {
 }
 
 CompositorD3D11::~CompositorD3D11()
--- a/gfx/layers/d3d11/CompositorD3D11.h
+++ b/gfx/layers/d3d11/CompositorD3D11.h
@@ -37,17 +37,17 @@ struct PixelShaderConstants
   int blendConfig[4];
 };
 
 struct DeviceAttachmentsD3D11;
 
 class CompositorD3D11 : public Compositor
 {
 public:
-  CompositorD3D11(nsIWidget* aWidget);
+  CompositorD3D11(CompositorBridgeParent* aParent, nsIWidget* aWidget);
   ~CompositorD3D11();
 
   virtual bool Initialize() override;
   virtual void Destroy() override {}
 
   virtual TextureFactoryIdentifier
     GetTextureFactoryIdentifier() override;
 
--- a/gfx/layers/d3d11/TextureD3D11.cpp
+++ b/gfx/layers/d3d11/TextureD3D11.cpp
@@ -624,16 +624,20 @@ DXGITextureHostD3D11::OpenSharedHandle()
   mTexture->GetDesc(&desc);
   mSize = IntSize(desc.Width, desc.Height);
   return true;
 }
 
 RefPtr<ID3D11Device>
 DXGITextureHostD3D11::GetDevice()
 {
+  if (mFlags & TextureFlags::INVALID_COMPOSITOR) {
+    return nullptr;
+  }
+
   RefPtr<ID3D11Device> device;
   gfxWindowsPlatform::GetPlatform()->GetD3D11Device(&device);
   return device;
 }
 
 static bool AssertD3D11Compositor(Compositor* aCompositor)
 {
   bool ok = aCompositor && aCompositor->GetBackendType() == LayersBackend::LAYERS_D3D11;
@@ -742,16 +746,20 @@ DXGIYCbCrTextureHostD3D11::OpenSharedHan
   mTextures[2] = textures[2].forget();
 
   return true;
 }
 
 RefPtr<ID3D11Device>
 DXGIYCbCrTextureHostD3D11::GetDevice()
 {
+  if (mFlags & TextureFlags::INVALID_COMPOSITOR) {
+    return nullptr;
+  }
+
   RefPtr<ID3D11Device> device;
   gfxWindowsPlatform::GetPlatform()->GetD3D11Device(&device);
   return device;
 }
 
 void
 DXGIYCbCrTextureHostD3D11::SetCompositor(Compositor* aCompositor)
 {
--- a/gfx/layers/d3d9/TextureD3D9.cpp
+++ b/gfx/layers/d3d9/TextureD3D9.cpp
@@ -906,16 +906,19 @@ TextureHostD3D9::UpdatedInternal(const n
   if (!mTextureSource->UpdateFromTexture(mTexture, regionToUpdate)) {
     gfxCriticalError() << "[D3D9] DataTextureSourceD3D9::UpdateFromTexture failed";
   }
 }
 
 IDirect3DDevice9*
 TextureHostD3D9::GetDevice()
 {
+  if (mFlags & TextureFlags::INVALID_COMPOSITOR) {
+    return nullptr;
+  }
   return mCompositor ? mCompositor->device() : nullptr;
 }
 
 void
 TextureHostD3D9::SetCompositor(Compositor* aCompositor)
 {
   if (!AssertD3D9Compositor(aCompositor)) {
     mCompositor = nullptr;
@@ -971,16 +974,19 @@ DXGITextureHostD3D9::DXGITextureHostD3D9
 {
   MOZ_ASSERT(mHandle);
   OpenSharedHandle();
 }
 
 IDirect3DDevice9*
 DXGITextureHostD3D9::GetDevice()
 {
+  if (mFlags & TextureFlags::INVALID_COMPOSITOR) {
+    return nullptr;
+  }
   return mCompositor ? mCompositor->device() : nullptr;
 }
 
 void
 DXGITextureHostD3D9::OpenSharedHandle()
 {
   MOZ_ASSERT(!mTextureSource);
 
@@ -1065,16 +1071,19 @@ DXGIYCbCrTextureHostD3D9::DXGIYCbCrTextu
   mHandles[0] = reinterpret_cast<HANDLE>(aDescriptor.handleY());
   mHandles[1] = reinterpret_cast<HANDLE>(aDescriptor.handleCb());
   mHandles[2] = reinterpret_cast<HANDLE>(aDescriptor.handleCr());
 }
 
 IDirect3DDevice9*
 DXGIYCbCrTextureHostD3D9::GetDevice()
 {
+  if (mFlags & TextureFlags::INVALID_COMPOSITOR) {
+    return nullptr;
+  }
   return mCompositor ? mCompositor->device() : nullptr;
 }
 
 void
 DXGIYCbCrTextureHostD3D9::SetCompositor(Compositor* aCompositor)
 {
   if (!AssertD3D9Compositor(aCompositor)) {
     mCompositor = nullptr;
--- a/gfx/layers/ipc/CompositableTransactionParent.cpp
+++ b/gfx/layers/ipc/CompositableTransactionParent.cpp
@@ -30,39 +30,32 @@
 #include "nsRegion.h"                   // for nsIntRegion
 
 namespace mozilla {
 namespace layers {
 
 class ClientTiledLayerBuffer;
 class Compositor;
 
-template<typename Op>
-CompositableHost* AsCompositable(const Op& op)
-{
-  return CompositableHost::FromIPDLActor(op.compositableParent());
-}
-
 // This function can in some cases fail and return false without it being a bug.
 // This can theoretically happen if the ImageBridge sends frames before
 // we created the layer tree. Since we can't enforce that the layer
 // tree is already created before ImageBridge operates, there isn't much
 // we can do about it, but in practice it is very rare.
 // Typically when a tab with a video is dragged from a window to another,
 // there can be a short time when the video is still sending frames
 // asynchonously while the layer tree is not reconstructed. It's not a
 // big deal.
 // Note that Layers transactions do not need to call this because they always
 // schedule the composition, in LayerManagerComposite::EndTransaction.
-template<typename T>
-bool ScheduleComposition(const T& op)
+static bool
+ScheduleComposition(CompositableHost* aCompositable)
 {
-  CompositableHost* comp = AsCompositable(op);
-  uint64_t id = comp->GetCompositorID();
-  if (!comp || !id) {
+  uint64_t id = aCompositable->GetCompositorID();
+  if (!id) {
     return false;
   }
   CompositorBridgeParent* cp = CompositorBridgeParent::GetCompositor(id);
   if (!cp) {
     return false;
   }
   cp->ScheduleComposition();
   return true;
@@ -76,22 +69,28 @@ static bool ValidatePictureRect(const mo
       !aPictureRect.IsEmpty();
 }
 #endif
 
 bool
 CompositableParentManager::ReceiveCompositableUpdate(const CompositableOperation& aEdit,
                                                      EditReplyVector& replyv)
 {
-  switch (aEdit.type()) {
-    case CompositableOperation::TOpPaintTextureRegion: {
+  // Ignore all operations on compositables created on stale compositors. We
+  // return true because the child is unable to handle errors.
+  CompositableHost* compositable = CompositableHost::FromIPDLActor(aEdit.compositableParent());
+  if (compositable->GetCompositor() && compositable->GetCompositor()->IsValid()) {
+    return true;
+  }
+
+  switch (aEdit.detail().type()) {
+    case CompositableOperationDetail::TOpPaintTextureRegion: {
       MOZ_LAYERS_LOG(("[ParentSide] Paint PaintedLayer"));
 
-      const OpPaintTextureRegion& op = aEdit.get_OpPaintTextureRegion();
-      CompositableHost* compositable = AsCompositable(op);
+      const OpPaintTextureRegion& op = aEdit.detail().get_OpPaintTextureRegion();
       Layer* layer = compositable->GetLayer();
       if (!layer || layer->GetType() != Layer::TYPE_PAINTED) {
         return false;
       }
       PaintedLayerComposite* thebes = static_cast<PaintedLayerComposite*>(layer);
 
       const ThebesBufferData& bufferData = op.bufferData();
 
@@ -101,49 +100,48 @@ CompositableParentManager::ReceiveCompos
       if (!compositable->UpdateThebes(bufferData,
                                       op.updatedRegion(),
                                       thebes->GetValidRegion(),
                                       &frontUpdatedRegion))
       {
         return false;
       }
       replyv.push_back(
-        OpContentBufferSwap(op.compositableParent(), nullptr, frontUpdatedRegion));
+        OpContentBufferSwap(aEdit.compositableParent(), nullptr, frontUpdatedRegion));
 
       RenderTraceInvalidateEnd(thebes, "FF00FF");
       break;
     }
-    case CompositableOperation::TOpUseTiledLayerBuffer: {
+    case CompositableOperationDetail::TOpUseTiledLayerBuffer: {
       MOZ_LAYERS_LOG(("[ParentSide] Paint TiledLayerBuffer"));
-      const OpUseTiledLayerBuffer& op = aEdit.get_OpUseTiledLayerBuffer();
-      TiledContentHost* compositable = AsCompositable(op)->AsTiledContentHost();
+      const OpUseTiledLayerBuffer& op = aEdit.detail().get_OpUseTiledLayerBuffer();
+      TiledContentHost* tiledHost = compositable->AsTiledContentHost();
 
-      NS_ASSERTION(compositable, "The compositable is not tiled");
+      NS_ASSERTION(tiledHost, "The compositable is not tiled");
 
       const SurfaceDescriptorTiles& tileDesc = op.tileLayerDescriptor();
-      bool success = compositable->UseTiledLayerBuffer(this, tileDesc);
+      bool success = tiledHost->UseTiledLayerBuffer(this, tileDesc);
       if (!success) {
         return false;
       }
       break;
     }
-    case CompositableOperation::TOpRemoveTexture: {
-      const OpRemoveTexture& op = aEdit.get_OpRemoveTexture();
-      CompositableHost* compositable = AsCompositable(op);
+    case CompositableOperationDetail::TOpRemoveTexture: {
+      const OpRemoveTexture& op = aEdit.detail().get_OpRemoveTexture();
+
       RefPtr<TextureHost> tex = TextureHost::AsTextureHost(op.textureParent());
 
       MOZ_ASSERT(tex.get());
       compositable->RemoveTextureHost(tex);
       // send FenceHandle if present.
       SendFenceHandleIfPresent(op.textureParent());
       break;
     }
-    case CompositableOperation::TOpRemoveTextureAsync: {
-      const OpRemoveTextureAsync& op = aEdit.get_OpRemoveTextureAsync();
-      CompositableHost* compositable = AsCompositable(op);
+    case CompositableOperationDetail::TOpRemoveTextureAsync: {
+      const OpRemoveTextureAsync& op = aEdit.detail().get_OpRemoveTextureAsync();
       RefPtr<TextureHost> tex = TextureHost::AsTextureHost(op.textureParent());
 
       MOZ_ASSERT(tex.get());
       compositable->RemoveTextureHost(tex);
 
       if (!UsesImageBridge() && ImageBridgeParent::GetInstance(GetChildProcessId())) {
         // send FenceHandle if present via ImageBridge.
         ImageBridgeParent::AppendDeliverFenceMessage(
@@ -162,19 +160,18 @@ CompositableParentManager::ReceiveCompos
         // send FenceHandle if present.
         SendFenceHandleIfPresent(op.textureParent());
 
         ReplyRemoveTexture(OpReplyRemoveTexture(op.holderId(),
                                                 op.transactionId()));
       }
       break;
     }
-    case CompositableOperation::TOpUseTexture: {
-      const OpUseTexture& op = aEdit.get_OpUseTexture();
-      CompositableHost* compositable = AsCompositable(op);
+    case CompositableOperationDetail::TOpUseTexture: {
+      const OpUseTexture& op = aEdit.detail().get_OpUseTexture();
 
       AutoTArray<CompositableHost::TimedTexture,4> textures;
       for (auto& timedTexture : op.textures()) {
         CompositableHost::TimedTexture* t = textures.AppendElement();
         t->mTexture =
             TextureHost::AsTextureHost(timedTexture.textureParent());
         MOZ_ASSERT(t->mTexture);
         t->mTimeStamp = timedTexture.timeStamp();
@@ -187,41 +184,41 @@ CompositableParentManager::ReceiveCompos
         MaybeFence maybeFence = timedTexture.fence();
         if (maybeFence.type() == MaybeFence::TFenceHandle) {
           FenceHandle fence = maybeFence.get_FenceHandle();
           if (fence.IsValid()) {
             t->mTexture->SetAcquireFenceHandle(fence);
           }
         }
       }
-      compositable->UseTextureHost(textures);
+      if (textures.Length() > 0) {
+        compositable->UseTextureHost(textures);
+      }
 
       if (UsesImageBridge() && compositable->GetLayer()) {
-        ScheduleComposition(op);
+        ScheduleComposition(compositable);
       }
       break;
     }
-    case CompositableOperation::TOpUseComponentAlphaTextures: {
-      const OpUseComponentAlphaTextures& op = aEdit.get_OpUseComponentAlphaTextures();
-      CompositableHost* compositable = AsCompositable(op);
+    case CompositableOperationDetail::TOpUseComponentAlphaTextures: {
+      const OpUseComponentAlphaTextures& op = aEdit.detail().get_OpUseComponentAlphaTextures();
       RefPtr<TextureHost> texOnBlack = TextureHost::AsTextureHost(op.textureOnBlackParent());
       RefPtr<TextureHost> texOnWhite = TextureHost::AsTextureHost(op.textureOnWhiteParent());
 
       MOZ_ASSERT(texOnBlack && texOnWhite);
       compositable->UseComponentAlphaTextures(texOnBlack, texOnWhite);
 
       if (UsesImageBridge()) {
-        ScheduleComposition(op);
+        ScheduleComposition(compositable);
       }
       break;
     }
 #ifdef MOZ_WIDGET_GONK
-    case CompositableOperation::TOpUseOverlaySource: {
-      const OpUseOverlaySource& op = aEdit.get_OpUseOverlaySource();
-      CompositableHost* compositable = AsCompositable(op);
+    case CompositableOperationDetail::TOpUseOverlaySource: {
+      const OpUseOverlaySource& op = aEdit.detail().get_OpUseOverlaySource();
       if (!ValidatePictureRect(op.overlay().size(), op.picture())) {
         return false;
       }
       compositable->UseOverlaySource(op.overlay(), op.picture());
       break;
     }
 #endif
     default: {
--- a/gfx/layers/ipc/CompositorBridgeChild.cpp
+++ b/gfx/layers/ipc/CompositorBridgeChild.cpp
@@ -207,25 +207,26 @@ CompositorBridgeChild::RecvInvalidateLay
       child->InvalidateLayers();
     }
   }
   return true;
 }
 
 bool
 CompositorBridgeChild::RecvCompositorUpdated(const uint64_t& aLayersId,
-                                      const TextureFactoryIdentifier& aNewIdentifier)
+                                             const TextureFactoryIdentifier& aNewIdentifier)
 {
   if (mLayerManager) {
     // This case is handled directly by nsBaseWidget.
     MOZ_ASSERT(aLayersId == 0);
   } else if (aLayersId != 0) {
     if (dom::TabChild* child = dom::TabChild::GetFrom(aLayersId)) {
       child->CompositorUpdated(aNewIdentifier);
     }
+    SendAcknowledgeCompositorUpdate(aLayersId);
   }
   return true;
 }
 
 #if defined(XP_WIN) || defined(MOZ_WIDGET_GTK)
 static void CalculatePluginClip(const LayoutDeviceIntRect& aBounds,
                                 const nsTArray<LayoutDeviceIntRect>& aPluginClipRects,
                                 const LayoutDeviceIntPoint& aContentOffset,
--- a/gfx/layers/ipc/CompositorBridgeParent.cpp
+++ b/gfx/layers/ipc/CompositorBridgeParent.cpp
@@ -104,16 +104,17 @@ using base::ProcessId;
 using base::Thread;
 
 CompositorBridgeParent::LayerTreeState::LayerTreeState()
   : mParent(nullptr)
   , mLayerManager(nullptr)
   , mCrossProcessParent(nullptr)
   , mLayerTree(nullptr)
   , mUpdatedPluginDataAvailable(false)
+  , mPendingCompositorUpdates(0)
 {
 }
 
 CompositorBridgeParent::LayerTreeState::~LayerTreeState()
 {
   if (mController) {
     mController->Destroy();
   }
@@ -1612,32 +1613,33 @@ CompositorBridgeParent::InitializeLayerM
 }
 
 RefPtr<Compositor>
 CompositorBridgeParent::NewCompositor(const nsTArray<LayersBackend>& aBackendHints)
 {
   for (size_t i = 0; i < aBackendHints.Length(); ++i) {
     RefPtr<Compositor> compositor;
     if (aBackendHints[i] == LayersBackend::LAYERS_OPENGL) {
-      compositor = new CompositorOGL(mWidget,
+      compositor = new CompositorOGL(this,
+                                     mWidget,
                                      mEGLSurfaceSize.width,
                                      mEGLSurfaceSize.height,
                                      mUseExternalSurfaceSize);
     } else if (aBackendHints[i] == LayersBackend::LAYERS_BASIC) {
 #ifdef MOZ_WIDGET_GTK
       if (gfxPlatformGtk::GetPlatform()->UseXRender()) {
-        compositor = new X11BasicCompositor(mWidget);
+        compositor = new X11BasicCompositor(this, mWidget);
       } else
 #endif
       {
-        compositor = new BasicCompositor(mWidget);
+        compositor = new BasicCompositor(this, mWidget);
       }
 #ifdef XP_WIN
     } else if (aBackendHints[i] == LayersBackend::LAYERS_D3D11) {
-      compositor = new CompositorD3D11(mWidget);
+      compositor = new CompositorD3D11(this, mWidget);
     } else if (aBackendHints[i] == LayersBackend::LAYERS_D3D9) {
       compositor = new CompositorD3D9(this, mWidget);
 #endif
     }
 
     if (compositor && compositor->Initialize()) {
       compositor->SetCompositorID(mCompositorID);
       return compositor;
@@ -2049,16 +2051,17 @@ public:
   virtual void GetAPZTestData(const LayerTransactionParent* aLayerTree,
                               APZTestData* aOutData) override;
   virtual void SetConfirmedTargetAPZC(const LayerTransactionParent* aLayerTree,
                                       const uint64_t& aInputBlockId,
                                       const nsTArray<ScrollableLayerGuid>& aTargets) override;
 
   virtual AsyncCompositionManager* GetCompositionManager(LayerTransactionParent* aParent) override;
   virtual bool RecvRemotePluginsReady()  override { return false; }
+  virtual bool RecvAcknowledgeCompositorUpdate(const uint64_t& aLayersId) override;
 
   void DidComposite(uint64_t aId,
                     TimeStamp& aCompositeStart,
                     TimeStamp& aCompositeEnd);
 
 protected:
   void OnChannelConnected(int32_t pid) override { mCompositorThreadHolder = sCompositorThreadHolder; }
 private:
@@ -2176,16 +2179,21 @@ CompositorBridgeParent::ResetCompositorT
     // No compositor change; nothing to do.
     return;
   }
 
   MonitorAutoLock lock(*sIndirectLayerTreesLock);
   ForEachIndirectLayerTree([&] (LayerTreeState* lts, uint64_t layersId) -> void {
     if (CrossProcessCompositorBridgeParent* cpcp = lts->mCrossProcessParent) {
       Unused << cpcp->SendCompositorUpdated(layersId, newIdentifier.value());
+
+      if (LayerTransactionParent* ltp = lts->mLayerTree) {
+        ltp->AddPendingCompositorUpdate();
+      }
+      lts->mPendingCompositorUpdates++;
     }
   });
 }
 
 Maybe<TextureFactoryIdentifier>
 CompositorBridgeParent::ResetCompositorImpl(const nsTArray<LayersBackend>& aBackendHints)
 {
   if (!mLayerManager) {
@@ -2200,16 +2208,19 @@ CompositorBridgeParent::ResetCompositorI
   // Don't bother changing from basic->basic.
   if (mCompositor &&
       mCompositor->GetBackendType() == LayersBackend::LAYERS_BASIC &&
       compositor->GetBackendType() == LayersBackend::LAYERS_BASIC)
   {
     return Nothing();
   }
 
+  if (mCompositor) {
+    mCompositor->SetInvalid();
+  }
   mCompositor = compositor;
   mLayerManager->ChangeCompositor(compositor);
 
   return Some(compositor->GetTextureFactoryIdentifier());
 }
 
 static void
 OpenCompositor(CrossProcessCompositorBridgeParent* aCompositor,
@@ -2311,16 +2322,17 @@ CrossProcessCompositorBridgeParent::Allo
   if (state && state->mLayerManager) {
     state->mCrossProcessParent = this;
     LayerManagerComposite* lm = state->mLayerManager;
     *aTextureFactoryIdentifier = lm->GetCompositor()->GetTextureFactoryIdentifier();
     *aSuccess = true;
     LayerTransactionParent* p = new LayerTransactionParent(lm, this, aId);
     p->AddIPDLReference();
     sIndirectLayerTrees[aId].mLayerTree = p;
+    p->SetPendingCompositorUpdates(state->mPendingCompositorUpdates);
     return p;
   }
 
   NS_WARNING("Created child without a matching parent?");
   // XXX: should be false, but that causes us to fail some tests on Mac w/ OMTC.
   // Bug 900745. change *aSuccess to false to see test failures.
   *aSuccess = true;
   LayerTransactionParent* p = new LayerTransactionParent(nullptr, this, aId);
@@ -2715,16 +2727,30 @@ CrossProcessCompositorBridgeParent::GetC
   if (!state) {
     return nullptr;
   }
 
   MOZ_ASSERT(state->mParent);
   return state->mParent->GetCompositionManager(aLayerTree);
 }
 
+bool
+CrossProcessCompositorBridgeParent::RecvAcknowledgeCompositorUpdate(const uint64_t& aLayersId)
+{
+  MonitorAutoLock lock(*sIndirectLayerTreesLock);
+  CompositorBridgeParent::LayerTreeState& state = sIndirectLayerTrees[aLayersId];
+
+  if (LayerTransactionParent* ltp = state.mLayerTree) {
+    ltp->AcknowledgeCompositorUpdate();
+  }
+  MOZ_ASSERT(state.mPendingCompositorUpdates > 0);
+  state.mPendingCompositorUpdates--;
+  return true;
+}
+
 void
 CrossProcessCompositorBridgeParent::DeferredDestroy()
 {
   MOZ_ASSERT(mCompositorThreadHolder);
   mCompositorThreadHolder = nullptr;
   mSelfRef = nullptr;
 }
 
--- a/gfx/layers/ipc/CompositorBridgeParent.h
+++ b/gfx/layers/ipc/CompositorBridgeParent.h
@@ -236,16 +236,21 @@ public:
   virtual bool RecvNotifyChildCreated(const uint64_t& child) override;
   virtual bool RecvAdoptChild(const uint64_t& child) override;
   virtual bool RecvMakeSnapshot(const SurfaceDescriptor& aInSnapshot,
                                 const gfx::IntRect& aRect) override;
   virtual bool RecvMakeWidgetSnapshot(const SurfaceDescriptor& aInSnapshot) override;
   virtual bool RecvFlushRendering() override;
   virtual bool RecvForcePresent() override;
 
+  virtual bool RecvAcknowledgeCompositorUpdate(const uint64_t& aLayersId) override {
+    MOZ_ASSERT_UNREACHABLE("This message is only sent cross-process");
+    return true;
+  }
+
   virtual bool RecvGetTileSize(int32_t* aWidth, int32_t* aHeight) override;
 
   virtual bool RecvNotifyRegionInvalidated(const nsIntRegion& aRegion) override;
   virtual bool RecvStartFrameTimeRecording(const int32_t& aBufferSize, uint32_t* aOutStartIndex) override;
   virtual bool RecvStopFrameTimeRecording(const uint32_t& aStartIndex, InfallibleTArray<float>* intervals) override;
 
   // Unused for chrome <-> compositor communication (which this class does).
   // @see CrossProcessCompositorBridgeParent::RecvRequestNotifyAfterRemotePaint
@@ -426,16 +431,20 @@ public:
     TargetConfig mTargetConfig;
     APZTestData mApzTestData;
     LayerTransactionParent* mLayerTree;
     nsTArray<PluginWindowData> mPluginData;
     bool mUpdatedPluginDataAvailable;
     RefPtr<CompositorUpdateObserver> mLayerTreeReadyObserver;
     RefPtr<CompositorUpdateObserver> mLayerTreeClearedObserver;
 
+    // Number of times the compositor has been reset without having been
+    // acknowledged by the child.
+    uint32_t mPendingCompositorUpdates;
+
     PCompositorBridgeParent* CrossProcessPCompositorBridge() const;
   };
 
   /**
    * Lookup the indirect shadow tree for |aId| and return it if it
    * exists.  Otherwise null is returned.  This must only be called on
    * the compositor thread.
    */
--- a/gfx/layers/ipc/ImageBridgeChild.cpp
+++ b/gfx/layers/ipc/ImageBridgeChild.cpp
@@ -188,35 +188,39 @@ ImageBridgeChild::UseTextures(Compositab
     }
 
     FenceHandle fence = t.mTextureClient->GetAcquireFenceHandle();
     textures.AppendElement(TimedTexture(nullptr, t.mTextureClient->GetIPDLActor(),
                                         fence.IsValid() ? MaybeFence(fence) : MaybeFence(null_t()),
                                         t.mTimeStamp, t.mPictureRect,
                                         t.mFrameID, t.mProducerID, t.mInputFrameID));
   }
-  mTxn->AddNoSwapEdit(OpUseTexture(nullptr, aCompositable->GetIPDLActor(),
-                                   textures));
+  mTxn->AddNoSwapEdit(CompositableOperation(nullptr, aCompositable->GetIPDLActor(),
+                                            OpUseTexture(textures)));
 }
 
 void
 ImageBridgeChild::UseComponentAlphaTextures(CompositableClient* aCompositable,
                                             TextureClient* aTextureOnBlack,
                                             TextureClient* aTextureOnWhite)
 {
   MOZ_ASSERT(aCompositable);
   MOZ_ASSERT(aTextureOnWhite);
   MOZ_ASSERT(aTextureOnBlack);
   MOZ_ASSERT(aCompositable->IsConnected());
   MOZ_ASSERT(aTextureOnWhite->GetIPDLActor());
   MOZ_ASSERT(aTextureOnBlack->GetIPDLActor());
   MOZ_ASSERT(aTextureOnBlack->GetSize() == aTextureOnWhite->GetSize());
-  mTxn->AddNoSwapEdit(OpUseComponentAlphaTextures(nullptr, aCompositable->GetIPDLActor(),
-                                                  nullptr, aTextureOnBlack->GetIPDLActor(),
-                                                  nullptr, aTextureOnWhite->GetIPDLActor()));
+  mTxn->AddNoSwapEdit(
+    CompositableOperation(
+      nullptr,
+      aCompositable->GetIPDLActor(),
+      OpUseComponentAlphaTextures(
+        nullptr ,aTextureOnBlack->GetIPDLActor(),
+        nullptr, aTextureOnWhite->GetIPDLActor())));
 }
 
 #ifdef MOZ_WIDGET_GONK
 void
 ImageBridgeChild::UseOverlaySource(CompositableClient* aCompositable,
                                    const OverlaySource& aOverlay,
                                    const nsIntRect& aPictureRect)
 {
@@ -1186,41 +1190,50 @@ ImageBridgeChild::RemoveTextureFromCompo
 {
   MOZ_ASSERT(!mShuttingDown);
   MOZ_ASSERT(aTexture);
   MOZ_ASSERT(aTexture->IsSharedWithCompositor());
   MOZ_ASSERT(aCompositable->IsConnected());
   if (!aTexture || !aTexture->IsSharedWithCompositor() || !aCompositable->IsConnected()) {
     return;
   }
+
+  CompositableOperation op(
+    nullptr, aCompositable->GetIPDLActor(),
+    OpRemoveTexture(nullptr, aTexture->GetIPDLActor()));
+
   if (aTexture->GetFlags() & TextureFlags::DEALLOCATE_CLIENT) {
-    mTxn->AddEdit(OpRemoveTexture(nullptr, aCompositable->GetIPDLActor(),
-                                  nullptr, aTexture->GetIPDLActor()));
+    mTxn->AddEdit(op);
   } else {
-    mTxn->AddNoSwapEdit(OpRemoveTexture(nullptr, aCompositable->GetIPDLActor(),
-                                        nullptr, aTexture->GetIPDLActor()));
+    mTxn->AddNoSwapEdit(op);
   }
 }
 
 void
 ImageBridgeChild::RemoveTextureFromCompositableAsync(AsyncTransactionTracker* aAsyncTransactionTracker,
                                                      CompositableClient* aCompositable,
                                                      TextureClient* aTexture)
 {
   MOZ_ASSERT(!mShuttingDown);
   MOZ_ASSERT(aTexture);
   MOZ_ASSERT(aTexture->IsSharedWithCompositor());
   MOZ_ASSERT(aCompositable->IsConnected());
   if (!aTexture || !aTexture->IsSharedWithCompositor() || !aCompositable->IsConnected()) {
     return;
   }
-  mTxn->AddNoSwapEdit(OpRemoveTextureAsync(CompositableClient::GetTrackersHolderId(aCompositable->GetIPDLActor()),
-                                           aAsyncTransactionTracker->GetId(),
-                                           nullptr, aCompositable->GetIPDLActor(),
-                                           nullptr, aTexture->GetIPDLActor()));
+
+  CompositableOperation op(
+    nullptr, aCompositable->GetIPDLActor(),
+    OpRemoveTextureAsync(
+      CompositableClient::GetTrackersHolderId(aCompositable->GetIPDLActor()),
+      aAsyncTransactionTracker->GetId(),
+      nullptr, aCompositable->GetIPDLActor(),
+      nullptr, aTexture->GetIPDLActor()));
+
+  mTxn->AddNoSwapEdit(op);
   // Hold AsyncTransactionTracker until receving reply
   CompositableClient::HoldUntilComplete(aCompositable->GetIPDLActor(),
                                         aAsyncTransactionTracker);
 }
 
 bool ImageBridgeChild::IsSameProcess() const
 {
   return OtherPid() == base::GetCurrentProcId();
--- a/gfx/layers/ipc/LayerTransactionParent.cpp
+++ b/gfx/layers/ipc/LayerTransactionParent.cpp
@@ -144,16 +144,17 @@ ShadowChild(const OpRaiseToTopChild& op)
 // LayerTransactionParent
 LayerTransactionParent::LayerTransactionParent(LayerManagerComposite* aManager,
                                                ShadowLayersManager* aLayersManager,
                                                uint64_t aId)
   : mLayerManager(aManager)
   , mShadowLayersManager(aLayersManager)
   , mId(aId)
   , mPendingTransaction(0)
+  , mPendingCompositorUpdates(0)
   , mDestroyed(false)
   , mIPCOpen(false)
 {
   MOZ_COUNT_CTOR(LayerTransactionParent);
 }
 
 LayerTransactionParent::~LayerTransactionParent()
 {
@@ -581,29 +582,39 @@ LayerTransactionParent::RecvUpdate(Infal
                                 replyv)) {
         return false;
       }
       break;
     }
     case Edit::TOpAttachCompositable: {
       const OpAttachCompositable& op = edit.get_OpAttachCompositable();
       CompositableHost* host = CompositableHost::FromIPDLActor(op.compositableParent());
+      if (mPendingCompositorUpdates) {
+        // Do not attach compositables from old layer trees. Return true since
+        // content cannot handle errors.
+        return true;
+      }
       if (!Attach(cast(op.layerParent()), host, false)) {
         return false;
       }
       host->SetCompositorID(mLayerManager->GetCompositor()->GetCompositorID());
       break;
     }
     case Edit::TOpAttachAsyncCompositable: {
       const OpAttachAsyncCompositable& op = edit.get_OpAttachAsyncCompositable();
       PCompositableParent* compositableParent = CompositableMap::Get(op.containerID());
       if (!compositableParent) {
         NS_ERROR("CompositableParent not found in the map");
         return false;
       }
+      if (mPendingCompositorUpdates) {
+        // Do not attach compositables from old layer trees. Return true since
+        // content cannot handle errors.
+        return true;
+      }
       CompositableHost* host = CompositableHost::FromIPDLActor(compositableParent);
       if (!Attach(cast(op.layerParent()), host, true)) {
         return false;
       }
       host->SetCompositorID(mLayerManager->GetCompositor()->GetCompositorID());
       break;
     }
     default:
@@ -963,18 +974,30 @@ LayerTransactionParent::DeallocPComposit
   return CompositableHost::DestroyIPDLActor(aActor);
 }
 
 PTextureParent*
 LayerTransactionParent::AllocPTextureParent(const SurfaceDescriptor& aSharedData,
                                             const LayersBackend& aLayersBackend,
                                             const TextureFlags& aFlags)
 {
-  MOZ_ASSERT(aLayersBackend == mLayerManager->GetCompositor()->GetBackendType());
-  return TextureHost::CreateIPDLActor(this, aSharedData, aLayersBackend, aFlags);
+  TextureFlags flags = aFlags;
+
+  if (mPendingCompositorUpdates) {
+    // The compositor was recreated, and we're receiving layers updates for a
+    // a layer manager that will soon be discarded or invalidated. We can't
+    // return null because this will mess up deserialization later and we'll
+    // kill the content process. Instead, we signal that the underlying
+    // TextureHost should not attempt to access the compositor.
+    flags |= TextureFlags::INVALID_COMPOSITOR;
+  } else if (aLayersBackend != mLayerManager->GetCompositor()->GetBackendType()) {
+    gfxDevCrash(gfx::LogReason::PAllocTextureBackendMismatch) << "Texture backend is wrong";
+  }
+
+  return TextureHost::CreateIPDLActor(this, aSharedData, aLayersBackend, flags);
 }
 
 bool
 LayerTransactionParent::DeallocPTextureParent(PTextureParent* actor)
 {
   return TextureHost::DestroyIPDLActor(actor);
 }
 
--- a/gfx/layers/ipc/LayerTransactionParent.h
+++ b/gfx/layers/ipc/LayerTransactionParent.h
@@ -86,16 +86,29 @@ public:
 
   virtual base::ProcessId GetChildProcessId() override
   {
     return OtherPid();
   }
 
   virtual void ReplyRemoveTexture(const OpReplyRemoveTexture& aReply) override;
 
+  void AddPendingCompositorUpdate() {
+    mPendingCompositorUpdates++;
+  }
+  void SetPendingCompositorUpdates(uint32_t aCount) {
+    // Only called after construction.
+    MOZ_ASSERT(mPendingCompositorUpdates == 0);
+    mPendingCompositorUpdates = aCount;
+  }
+  void AcknowledgeCompositorUpdate() {
+    MOZ_ASSERT(mPendingCompositorUpdates > 0);
+    mPendingCompositorUpdates--;
+  }
+
 protected:
   virtual bool RecvSyncWithCompositor() override { return true; }
 
   virtual bool RecvShutdown() override;
 
   virtual bool RecvUpdate(EditArray&& cset,
                           OpDestroyArray&& aToDestroy,
                           const uint64_t& aTransactionId,
@@ -185,16 +198,21 @@ private:
   RefPtr<Layer> mRoot;
   // When this is nonzero, it refers to a layer tree owned by the
   // compositor thread.  It is always true that
   //   mId != 0 => mRoot == null
   // because the "real tree" is owned by the compositor.
   uint64_t mId;
 
   uint64_t mPendingTransaction;
+
+  // Number of compositor updates we're waiting for the child to
+  // acknowledge.
+  uint32_t mPendingCompositorUpdates;
+
   // When the widget/frame/browser stuff in this process begins its
   // destruction process, we need to Disconnect() all the currently
   // live shadow layers, because some of them might be orphaned from
   // the layer tree.  This happens in Destroy() above.  After we
   // Destroy() ourself, there's a window in which that information
   // hasn't yet propagated back to the child side and it might still
   // send us layer transactions.  We want to ignore those transactions
   // because they refer to "zombie layers" on this side.  So, we track
--- a/gfx/layers/ipc/LayersMessages.ipdlh
+++ b/gfx/layers/ipc/LayersMessages.ipdlh
@@ -345,37 +345,33 @@ struct SurfaceDescriptorTiles {
   int         retainedWidth;
   int         retainedHeight;
   float       resolution;
   float       frameXResolution;
   float       frameYResolution;
 };
 
 struct OpUseTiledLayerBuffer {
-  PCompositable compositable;
   SurfaceDescriptorTiles tileLayerDescriptor;
 };
 
 struct OpUseOverlaySource {
-  PCompositable compositable;
   OverlaySource overlay;
   IntRect picture;
 };
 
 struct OpPaintTextureRegion {
-  PCompositable compositable;
   ThebesBufferData bufferData;
   nsIntRegion updatedRegion;
 };
 
 /**
  * Tells the CompositableHost to remove the corresponding TextureHost
  */
 struct OpRemoveTexture {
-  PCompositable compositable;
   PTexture texture;
 };
 
 struct OpRemoveTextureAsync {
   uint64_t holderId;
   uint64_t transactionId;
   PCompositable compositable;
   PTexture texture;
@@ -407,22 +403,20 @@ struct TimedTexture {
  * This provides a list of textures with timestamps, ordered by timestamp.
  * The newest texture whose timestamp is <= the current time is rendered
  * (where null is considered less than every other timestamp). If there is no
  * such texture, the first texture is rendered.
  * The first timestamp value can be null, but the others must not be.
  * The list must not be empty.
  */
 struct OpUseTexture {
-  PCompositable compositable;
   TimedTexture[] textures;
 };
 
 struct OpUseComponentAlphaTextures {
-  PCompositable compositable;
   PTexture textureOnBlack;
   PTexture textureOnWhite;
 };
 
 union MaybeRegion {
   nsIntRegion;
   null_t;
 };
@@ -433,29 +427,34 @@ struct OpDeliverFence {
 };
 
 struct OpDeliverFenceToTracker {
   uint64_t destHolderId;
   uint64_t destTransactionId;
   FenceHandle fence;
 };
 
-union CompositableOperation {
+union CompositableOperationDetail {
   OpPaintTextureRegion;
 
   OpUseTiledLayerBuffer;
 
   OpRemoveTexture;
   OpRemoveTextureAsync;
 
   OpUseTexture;
   OpUseComponentAlphaTextures;
   OpUseOverlaySource;
 };
 
+struct CompositableOperation {
+  PCompositable compositable;
+  CompositableOperationDetail detail;
+};
+
 // A unit of a changeset; a set of these comprise a changeset
 union Edit {
   OpCreatePaintedLayer;
   OpCreateContainerLayer;
   OpCreateImageLayer;
   OpCreateColorLayer;
   OpCreateCanvasLayer;
   OpCreateRefLayer;
--- a/gfx/layers/ipc/PCompositorBridge.ipdl
+++ b/gfx/layers/ipc/PCompositorBridge.ipdl
@@ -95,16 +95,20 @@ child:
   async ClearCachedResources(uint64_t id);
 
 parent:
   /**
    * Confirmation callback for UpdatePluginConfigurations and HideAllPlugins.
    */
   async RemotePluginsReady();
 
+  // Confirmation that the child has invalidated all its layers, and will not
+  // request layers against an old compositor.
+  async AcknowledgeCompositorUpdate(uint64_t id);
+
   // Child sends the parent a request for fill ratio numbers.
   async RequestOverfill();
 
   // Child requests frame uniformity measurements
   sync GetFrameUniformity() returns (FrameUniformityData data);
 
   // The child is about to be destroyed, so perform any necessary cleanup.
   sync WillStop();
--- a/gfx/layers/ipc/ShadowLayers.cpp
+++ b/gfx/layers/ipc/ShadowLayers.cpp
@@ -541,32 +541,33 @@ ShadowLayerForwarder::CheckSurfaceDescri
 #endif
 
 void
 ShadowLayerForwarder::UseTiledLayerBuffer(CompositableClient* aCompositable,
                                           const SurfaceDescriptorTiles& aTileLayerDescriptor)
 {
   MOZ_ASSERT(aCompositable && aCompositable->IsConnected());
 
-  mTxn->AddNoSwapPaint(OpUseTiledLayerBuffer(nullptr, aCompositable->GetIPDLActor(),
-                                             aTileLayerDescriptor));
+  mTxn->AddNoSwapPaint(CompositableOperation(nullptr, aCompositable->GetIPDLActor(),
+                                             OpUseTiledLayerBuffer(aTileLayerDescriptor)));
 }
 
 void
 ShadowLayerForwarder::UpdateTextureRegion(CompositableClient* aCompositable,
                                           const ThebesBufferData& aThebesBufferData,
                                           const nsIntRegion& aUpdatedRegion)
 {
   MOZ_ASSERT(aCompositable);
   MOZ_ASSERT(aCompositable);
   MOZ_ASSERT(aCompositable->IsConnected());
 
-  mTxn->AddPaint(OpPaintTextureRegion(nullptr, aCompositable->GetIPDLActor(),
-                                      aThebesBufferData,
-                                      aUpdatedRegion));
+  mTxn->AddPaint(
+    CompositableOperation(
+      nullptr, aCompositable->GetIPDLActor(),
+      OpPaintTextureRegion(aThebesBufferData, aUpdatedRegion)));
 }
 
 void
 ShadowLayerForwarder::UseTextures(CompositableClient* aCompositable,
                                   const nsTArray<TimedTextureClient>& aTextures)
 {
   MOZ_ASSERT(aCompositable && aCompositable->IsConnected());
 
@@ -584,50 +585,53 @@ ShadowLayerForwarder::UseTextures(Compos
         && t.mTextureClient->HasIntermediateBuffer()) {
 
       // We use IMMEDIATE_UPLOAD when we want to be sure that the upload cannot
       // race with updates on the main thread. In this case we want the transaction
       // to be synchronous.
       mTxn->MarkSyncTransaction();
     }
   }
-  mTxn->AddEdit(OpUseTexture(nullptr, aCompositable->GetIPDLActor(),
-                             textures));
+  mTxn->AddEdit(CompositableOperation(nullptr, aCompositable->GetIPDLActor(),
+                                      OpUseTexture(textures)));
 }
 
 void
 ShadowLayerForwarder::UseComponentAlphaTextures(CompositableClient* aCompositable,
                                                 TextureClient* aTextureOnBlack,
                                                 TextureClient* aTextureOnWhite)
 {
   MOZ_ASSERT(aCompositable);
   MOZ_ASSERT(aCompositable->IsConnected());
   MOZ_ASSERT(aTextureOnWhite);
   MOZ_ASSERT(aTextureOnBlack);
   MOZ_ASSERT(aCompositable->GetIPDLActor());
   MOZ_ASSERT(aTextureOnBlack->GetIPDLActor());
   MOZ_ASSERT(aTextureOnWhite->GetIPDLActor());
   MOZ_ASSERT(aTextureOnBlack->GetSize() == aTextureOnWhite->GetSize());
 
-  mTxn->AddEdit(OpUseComponentAlphaTextures(nullptr, aCompositable->GetIPDLActor(),
-                                            nullptr, aTextureOnBlack->GetIPDLActor(),
-                                            nullptr, aTextureOnWhite->GetIPDLActor()));
+  mTxn->AddEdit(
+    CompositableOperation(
+      nullptr, aCompositable->GetIPDLActor(),
+      OpUseComponentAlphaTextures(
+        nullptr, aTextureOnBlack->GetIPDLActor(),
+        nullptr, aTextureOnWhite->GetIPDLActor())));
 }
 
 #ifdef MOZ_WIDGET_GONK
 void
 ShadowLayerForwarder::UseOverlaySource(CompositableClient* aCompositable,
                                        const OverlaySource& aOverlay,
                                        const nsIntRect& aPictureRect)
 {
   MOZ_ASSERT(aCompositable);
   MOZ_ASSERT(aCompositable->IsConnected());
 
-  mTxn->AddEdit(OpUseOverlaySource(nullptr, aCompositable->GetIPDLActor(),
-      aOverlay, aPictureRect));
+  mTxn->AddEdit(CompositableOperation(nullptr, aCompositable->GetIPDLActor(),
+                                      OpUseOverlaySource(aOverlay, aPictureRect)));
 }
 #endif
 
 static bool
 AddOpDestroy(Transaction* aTxn, const OpDestroy& op, bool synchronously)
 {
   if (!aTxn->Opened()) {
     return false;
@@ -661,43 +665,47 @@ ShadowLayerForwarder::RemoveTextureFromC
   MOZ_ASSERT(aTexture);
   MOZ_ASSERT(aCompositable->IsConnected());
   MOZ_ASSERT(aTexture->GetIPDLActor());
   if (!aCompositable->IsConnected() || !aTexture->GetIPDLActor()) {
     // We don't have an actor anymore, don't try to use it!
     return;
   }
 
-  mTxn->AddEdit(OpRemoveTexture(nullptr, aCompositable->GetIPDLActor(),
-                                nullptr, aTexture->GetIPDLActor()));
+  mTxn->AddEdit(
+    CompositableOperation(
+      nullptr, aCompositable->GetIPDLActor(),
+      OpRemoveTexture(nullptr, aTexture->GetIPDLActor())));
   if (aTexture->GetFlags() & TextureFlags::DEALLOCATE_CLIENT) {
     mTxn->MarkSyncTransaction();
   }
 }
 
 void
 ShadowLayerForwarder::RemoveTextureFromCompositableAsync(AsyncTransactionTracker* aAsyncTransactionTracker,
-                                                     CompositableClient* aCompositable,
-                                                     TextureClient* aTexture)
+                                                         CompositableClient* aCompositable,
+                                                         TextureClient* aTexture)
 {
   MOZ_ASSERT(aCompositable);
   MOZ_ASSERT(aTexture);
   MOZ_ASSERT(aCompositable->IsConnected());
   MOZ_ASSERT(aTexture->GetIPDLActor());
+
+  CompositableOperation op(
+    nullptr, aCompositable->GetIPDLActor(),
+    OpRemoveTextureAsync(CompositableClient::GetTrackersHolderId(aCompositable->GetIPDLActor()),
+      aAsyncTransactionTracker->GetId(),
+      nullptr, aCompositable->GetIPDLActor(),
+      nullptr, aTexture->GetIPDLActor()));
+
 #ifdef MOZ_WIDGET_GONK
-  mPendingAsyncMessages.push_back(OpRemoveTextureAsync(CompositableClient::GetTrackersHolderId(aCompositable->GetIPDLActor()),
-                                  aAsyncTransactionTracker->GetId(),
-                                  nullptr, aCompositable->GetIPDLActor(),
-                                  nullptr, aTexture->GetIPDLActor()));
+  mPendingAsyncMessages.push_back(op);
 #else
   if (mTxn->Opened() && aCompositable->IsConnected()) {
-    mTxn->AddEdit(OpRemoveTextureAsync(CompositableClient::GetTrackersHolderId(aCompositable->GetIPDLActor()),
-                                       aAsyncTransactionTracker->GetId(),
-                                       nullptr, aCompositable->GetIPDLActor(),
-                                       nullptr, aTexture->GetIPDLActor()));
+    mTxn->AddEdit(op);
   } else {
     NS_RUNTIMEABORT("not reached");
   }
 #endif
   CompositableClient::HoldUntilComplete(aCompositable->GetIPDLActor(),
                                         aAsyncTransactionTracker);
 }
 
--- a/gfx/layers/opengl/CompositorOGL.cpp
+++ b/gfx/layers/opengl/CompositorOGL.cpp
@@ -78,19 +78,21 @@ CompositorOGL::BindBackdrop(ShaderProgra
 
   mGLContext->fActiveTexture(aTexUnit);
   mGLContext->fBindTexture(LOCAL_GL_TEXTURE_2D, aBackdrop);
   mGLContext->fTexParameteri(LOCAL_GL_TEXTURE_2D, LOCAL_GL_TEXTURE_MIN_FILTER, LOCAL_GL_LINEAR);
   mGLContext->fTexParameteri(LOCAL_GL_TEXTURE_2D, LOCAL_GL_TEXTURE_MAG_FILTER, LOCAL_GL_LINEAR);
   aProgram->SetBackdropTextureUnit(aTexUnit - LOCAL_GL_TEXTURE0);
 }
 
-CompositorOGL::CompositorOGL(nsIWidget *aWidget, int aSurfaceWidth,
+CompositorOGL::CompositorOGL(CompositorBridgeParent* aParent,
+                             nsIWidget *aWidget, int aSurfaceWidth,
                              int aSurfaceHeight, bool aUseExternalSurfaceSize)
-  : mWidget(aWidget)
+  : Compositor(aParent)
+  , mWidget(aWidget)
   , mWidgetSize(-1, -1)
   , mSurfaceSize(aSurfaceWidth, aSurfaceHeight)
   , mHasBGRA(0)
   , mUseExternalSurfaceSize(aUseExternalSurfaceSize)
   , mFrameInProgress(false)
   , mDestroyed(false)
   , mViewportSize(0, 0)
   , mCurrentProgram(nullptr)
--- a/gfx/layers/opengl/CompositorOGL.h
+++ b/gfx/layers/opengl/CompositorOGL.h
@@ -188,17 +188,18 @@ class CompositorOGL final : public Compo
 {
   typedef mozilla::gl::GLContext GLContext;
 
   friend class GLManagerCompositor;
   friend class CompositingRenderTargetOGL;
 
   std::map<ShaderConfigOGL, ShaderProgramOGL*> mPrograms;
 public:
-  explicit CompositorOGL(nsIWidget *aWidget, int aSurfaceWidth = -1, int aSurfaceHeight = -1,
+  explicit CompositorOGL(CompositorBridgeParent* aParent,
+                         nsIWidget *aWidget, int aSurfaceWidth = -1, int aSurfaceHeight = -1,
                          bool aUseExternalSurfaceSize = false);
 
 protected:
   virtual ~CompositorOGL();
 
 public:
   virtual already_AddRefed<DataTextureSource>
   CreateDataTextureSource(TextureFlags aFlags = TextureFlags::NO_FLAGS) override;
--- a/gfx/tests/gtest/TestCompositor.cpp
+++ b/gfx/tests/gtest/TestCompositor.cpp
@@ -107,23 +107,24 @@ struct LayerManagerData {
 
 static already_AddRefed<Compositor> CreateTestCompositor(LayersBackend backend, MockWidget* widget)
 {
   gfxPrefs::GetSingleton();
 
   RefPtr<Compositor> compositor;
 
   if (backend == LayersBackend::LAYERS_OPENGL) {
-    compositor = new CompositorOGL(widget,
+    compositor = new CompositorOGL(nullptr,
+                                   widget,
                                    gCompWidth,
                                    gCompHeight,
                                    true);
     compositor->SetDestinationSurfaceSize(IntSize(gCompWidth, gCompHeight));
   } else if (backend == LayersBackend::LAYERS_BASIC) {
-    compositor = new BasicCompositor(widget);
+    compositor = new BasicCompositor(nullptr, widget);
 #ifdef XP_WIN
   } else if (backend == LayersBackend::LAYERS_D3D11) {
     //compositor = new CompositorD3D11();
     MOZ_CRASH(); // No support yet
   } else if (backend == LayersBackend::LAYERS_D3D9) {
     //compositor = new CompositorD3D9(this, mWidget);
     MOZ_CRASH(); // No support yet
 #endif
--- a/ipc/chromium/src/base/process_util_bsd.cc
+++ b/ipc/chromium/src/base/process_util_bsd.cc
@@ -7,20 +7,19 @@
 #include "base/process_util.h"
 
 #include <fcntl.h>
 #include <spawn.h>
 #include <sys/wait.h>
 
 #include <string>
 
+#include "nspr.h"
 #include "base/eintr_wrapper.h"
 
-extern "C" char **environ __attribute__((__visibility__("default")));
-
 namespace base {
 
 void FreeEnvVarsArray(char* array[], int length)
 {
   for (int i = 0; i < length; i++) {
     free(array[i]);
   }
   delete[] array;
@@ -61,25 +60,27 @@ bool LaunchApp(const std::vector<std::st
   // as close-on-exec.
   SetAllFDsToCloseOnExec();
 
   // Copy environment to a new char array and add the variables
   // in env_vars_to_set.
   // Existing variables are overwritten by env_vars_to_set.
   int pos = 0;
   environment_map combined_env_vars = env_vars_to_set;
+  char **environ = PR_DuplicateEnvironment();
   while(environ[pos] != NULL) {
     std::string varString = environ[pos];
     std::string varName = varString.substr(0, varString.find_first_of('='));
     std::string varValue = varString.substr(varString.find_first_of('=') + 1);
     if (combined_env_vars.find(varName) == combined_env_vars.end()) {
       combined_env_vars[varName] = varValue;
     }
-    pos++;
+    PR_Free(environ[pos++]);
   }
+  PR_Free(environ);
   int varsLen = combined_env_vars.size() + 1;
 
   char** vars = new char*[varsLen];
   int i = 0;
   for (environment_map::const_iterator it = combined_env_vars.begin();
        it != combined_env_vars.end(); ++it) {
     std::string entry(it->first);
     entry += "=";
--- a/js/src/jit/BaselineCacheIR.cpp
+++ b/js/src/jit/BaselineCacheIR.cpp
@@ -738,16 +738,47 @@ BaselineCacheIRCompiler::emitGuardProto(
 
     Address addr(stubAddress(reader.stubOffset()));
     masm.loadObjProto(obj, scratch);
     masm.branchPtr(Assembler::NotEqual, addr, scratch, failure->label());
     return true;
 }
 
 bool
+BaselineCacheIRCompiler::emitGuardClass()
+{
+    Register obj = allocator.useRegister(masm, reader.objOperandId());
+    AutoScratchRegister scratch(allocator, masm);
+
+    FailurePath* failure;
+    if (!addFailurePath(&failure))
+        return false;
+
+    const Class* clasp = nullptr;
+    switch (reader.guardClassKind()) {
+      case GuardClassKind::Array:
+        clasp = &ArrayObject::class_;
+        break;
+      case GuardClassKind::UnboxedArray:
+        clasp = &UnboxedArrayObject::class_;
+        break;
+      case GuardClassKind::MappedArguments:
+        clasp = &MappedArgumentsObject::class_;
+        break;
+      case GuardClassKind::UnmappedArguments:
+        clasp = &UnmappedArgumentsObject::class_;
+        break;
+    }
+
+    MOZ_ASSERT(clasp);
+    masm.branchTestObjClass(Assembler::NotEqual, obj, scratch, clasp, failure->label());
+    return true;
+}
+
+bool
 BaselineCacheIRCompiler::emitGuardNoUnboxedExpando()
 {
     Register obj = allocator.useRegister(masm, reader.objOperandId());
 
     FailurePath* failure;
     if (!addFailurePath(&failure))
         return false;
 
@@ -806,16 +837,79 @@ BaselineCacheIRCompiler::emitLoadUndefin
     // Normally for this op, the result would have to be monitored by TI.
     // However, since this stub ALWAYS returns UndefinedValue(), and we can be sure
     // that undefined is already registered with the type-set, this can be avoided.
     emitReturnFromIC();
     return true;
 }
 
 bool
+BaselineCacheIRCompiler::emitLoadInt32ArrayLengthResult()
+{
+    Register obj = allocator.useRegister(masm, reader.objOperandId());
+    AutoScratchRegister scratch(allocator, masm);
+
+    FailurePath* failure;
+    if (!addFailurePath(&failure))
+        return false;
+
+    masm.loadPtr(Address(obj, NativeObject::offsetOfElements()), scratch);
+    masm.load32(Address(scratch, ObjectElements::offsetOfLength()), scratch);
+
+    // Guard length fits in an int32.
+    masm.branchTest32(Assembler::Signed, scratch, scratch, failure->label());
+    masm.tagValue(JSVAL_TYPE_INT32, scratch, R0);
+
+    // The int32 type was monitored when attaching the stub, so we can
+    // just return.
+    emitReturnFromIC();
+    return true;
+}
+
+bool
+BaselineCacheIRCompiler::emitLoadUnboxedArrayLengthResult()
+{
+    Register obj = allocator.useRegister(masm, reader.objOperandId());
+    masm.load32(Address(obj, UnboxedArrayObject::offsetOfLength()), R0.scratchReg());
+    masm.tagValue(JSVAL_TYPE_INT32, R0.scratchReg(), R0);
+
+    // The int32 type was monitored when attaching the stub, so we can
+    // just return.
+    emitReturnFromIC();
+    return true;
+}
+
+bool
+BaselineCacheIRCompiler::emitLoadArgumentsObjectLengthResult()
+{
+    Register obj = allocator.useRegister(masm, reader.objOperandId());
+    AutoScratchRegister scratch(allocator, masm);
+
+    FailurePath* failure;
+    if (!addFailurePath(&failure))
+        return false;
+
+    // Get initial length value.
+    masm.unboxInt32(Address(obj, ArgumentsObject::getInitialLengthSlotOffset()), scratch);
+
+    // Test if length has been overridden.
+    masm.branchTest32(Assembler::NonZero,
+                      scratch,
+                      Imm32(ArgumentsObject::LENGTH_OVERRIDDEN_BIT),
+                      failure->label());
+
+    // Shift out arguments length and return it. No need to type monitor
+    // because this stub always returns int32.
+    masm.rshiftPtr(Imm32(ArgumentsObject::PACKED_BITS_COUNT), scratch);
+    masm.tagValue(JSVAL_TYPE_INT32, scratch, R0);
+    emitReturnFromIC();
+    return true;
+}
+
+bool
 BaselineCacheIRCompiler::emitLoadObject()
 {
     Register reg = allocator.defineRegister(masm, reader.objOperandId());
     masm.loadPtr(stubAddress(reader.stubOffset()), reg);
     return true;
 }
 
 bool
@@ -1003,17 +1097,18 @@ jit::AttachBaselineCacheIRStub(JSContext
         // call below will transfer ownership to the stub code HashMap, so we
         // don't have to worry about freeing it below.
         MOZ_ASSERT(!stubInfo);
         stubInfo = CacheIRStubInfo::New(kind, stubDataOffset, writer);
         if (!stubInfo)
             return nullptr;
 
         CacheIRStubKey key(stubInfo);
-        jitCompartment->putCacheIRStubCode(lookup, key, code);
+        if (!jitCompartment->putCacheIRStubCode(lookup, key, code))
+            return nullptr;
     }
 
     // We got our shared stub code and stub info. Time to allocate and attach a
     // new stub.
 
     MOZ_ASSERT(code);
     MOZ_ASSERT(stubInfo);
     MOZ_ASSERT(stub->isMonitoredFallback());
--- a/js/src/jit/BaselineIC.cpp
+++ b/js/src/jit/BaselineIC.cpp
@@ -5990,23 +5990,22 @@ CopyArray(JSContext* cx, HandleObject ob
     CopyAnyBoxedOrUnboxedDenseElements(cx, nobj, obj, 0, 0, length);
 
     result.setObject(*nobj);
     return true;
 }
 
 static bool
 TryAttachStringSplit(JSContext* cx, ICCall_Fallback* stub, HandleScript script,
-                     uint32_t argc, Value* vp, jsbytecode* pc, HandleValue res,
-                     bool* attached)
+                     uint32_t argc, HandleValue callee, Value* vp, jsbytecode* pc,
+                     HandleValue res, bool* attached)
 {
     if (stub->numOptimizedStubs() != 0)
         return true;
 
-    RootedValue callee(cx, vp[0]);
     RootedValue thisv(cx, vp[1]);
     Value* args = vp + 2;
 
     // String.prototype.split will not yield a constructable.
     if (JSOp(*pc) == JSOP_NEW)
         return true;
 
     if (!IsOptimizableCallStringSplit(callee, thisv, argc, args))
@@ -6063,87 +6062,64 @@ DoCallFallback(JSContext* cx, BaselineFr
     jsbytecode* pc = stub->icEntry()->pc(script);
     JSOp op = JSOp(*pc);
     FallbackICSpew(cx, stub, "Call(%s)", CodeName[op]);
 
     MOZ_ASSERT(argc == GET_ARGC(pc));
     bool constructing = (op == JSOP_NEW);
 
     // Ensure vp array is rooted - we may GC in here.
-    AutoArrayRooter vpRoot(cx, argc + 2 + constructing, vp);
-
+    size_t numValues = argc + 2 + constructing;
+    AutoArrayRooter vpRoot(cx, numValues, vp);
+
+    CallArgs callArgs = CallArgsFromSp(argc + constructing, vp + numValues, constructing);
     RootedValue callee(cx, vp[0]);
-    RootedValue thisv(cx, vp[1]);
-
-    Value* args = vp + 2;
 
     // Handle funapply with JSOP_ARGUMENTS
-    if (op == JSOP_FUNAPPLY && argc == 2 && args[1].isMagic(JS_OPTIMIZED_ARGUMENTS)) {
-        CallArgs callArgs = CallArgsFromVp(argc, vp);
+    if (op == JSOP_FUNAPPLY && argc == 2 && callArgs[1].isMagic(JS_OPTIMIZED_ARGUMENTS)) {
         if (!GuardFunApplyArgumentsOptimization(cx, frame, callArgs))
             return false;
     }
 
     bool createSingleton = ObjectGroup::useSingletonForNewObject(cx, script, pc);
 
     // Try attaching a call stub.
     bool handled = false;
     if (!TryAttachCallStub(cx, stub, script, pc, op, argc, vp, constructing, false,
                            createSingleton, &handled))
     {
         return false;
     }
 
     if (op == JSOP_NEW) {
-        // Callees from the stack could have any old non-constructor callee.
-        if (!IsConstructor(callee)) {
-            ReportValueError(cx, JSMSG_NOT_CONSTRUCTOR, JSDVG_IGNORE_STACK, callee, nullptr);
-            return false;
-        }
-
-        ConstructArgs cargs(cx);
-        if (!cargs.init(argc))
+        if (!ConstructFromStack(cx, callArgs))
             return false;
-
-        for (uint32_t i = 0; i < argc; i++)
-            cargs[i].set(args[i]);
-
-        RootedValue newTarget(cx, args[argc]);
-        MOZ_ASSERT(IsConstructor(newTarget),
-                   "either callee == newTarget, or the initial |new| checked "
-                   "that IsConstructor(newTarget)");
-
-        RootedObject obj(cx);
-        if (!Construct(cx, callee, cargs, newTarget, &obj))
-            return false;
-
-        res.setObject(*obj);
-
     } else if ((op == JSOP_EVAL || op == JSOP_STRICTEVAL) &&
                frame->scopeChain()->global().valueIsEval(callee))
     {
-        if (!DirectEval(cx, CallArgsFromVp(argc, vp)))
+        if (!DirectEval(cx, callArgs))
             return false;
-        res.set(vp[0]);
     } else {
         MOZ_ASSERT(op == JSOP_CALL ||
                    op == JSOP_CALLITER ||
                    op == JSOP_FUNCALL ||
                    op == JSOP_FUNAPPLY ||
                    op == JSOP_EVAL ||
                    op == JSOP_STRICTEVAL);
         if (op == JSOP_CALLITER && callee.isPrimitive()) {
             MOZ_ASSERT(argc == 0, "thisv must be on top of the stack");
-            ReportValueError(cx, JSMSG_NOT_ITERABLE, -1, thisv, nullptr);
+            ReportValueError(cx, JSMSG_NOT_ITERABLE, -1, callArgs.thisv(), nullptr);
             return false;
         }
-        if (!Invoke(cx, thisv, callee, argc, args, res))
+
+        if (!Invoke(cx, callArgs))
             return false;
     }
 
+    res.set(callArgs.rval());
     TypeScript::Monitor(cx, script, pc, res);
 
     // Check if debug mode toggling made the stub invalid.
     if (stub.invalid())
         return true;
 
     // Attach a new TypeMonitor stub for this value.
     ICTypeMonitor_Fallback* typeMonFbStub = stub->fallbackMonitorStub();
@@ -6152,18 +6128,19 @@ DoCallFallback(JSContext* cx, BaselineFr
         return false;
     }
 
     // Add a type monitor stub for the resulting value.
     if (!stub->addMonitorStubForValue(cx, &info, res))
         return false;
 
     // If 'callee' is a potential Call_StringSplit, try to attach an
-    // optimized StringSplit stub.
-    if (!TryAttachStringSplit(cx, stub, script, argc, vp, pc, res, &handled))
+    // optimized StringSplit stub. Note that vp[0] now holds the return value
+    // instead of the callee, so we pass the callee as well.
+    if (!TryAttachStringSplit(cx, stub, script, argc, callee, vp, pc, res, &handled))
         return false;
 
     if (!handled)
         stub->noteUnoptimizableCall();
     return true;
 }
 
 static bool
--- a/js/src/jit/BaselineInspector.cpp
+++ b/js/src/jit/BaselineInspector.cpp
@@ -800,18 +800,16 @@ BaselineInspector::expectedPropertyAcces
           case ICStub::GetProp_Generic:
             return MIRType_Value;
 
           case ICStub::GetProp_ArgumentsLength:
           case ICStub::GetElem_Arguments:
             // Either an object or magic arguments.
             return MIRType_Value;
 
-          case ICStub::GetProp_ArrayLength:
-          case ICStub::GetProp_UnboxedArrayLength:
           case ICStub::GetProp_Unboxed:
           case ICStub::GetProp_TypedObject:
           case ICStub::GetProp_CallScripted:
           case ICStub::GetProp_CallNative:
           case ICStub::GetProp_CallDOMProxyNative:
           case ICStub::GetProp_CallDOMProxyWithGenerationNative:
           case ICStub::GetProp_DOMProxyShadowed:
           case ICStub::GetProp_ModuleNamespace:
--- a/js/src/jit/CacheIR.cpp
+++ b/js/src/jit/CacheIR.cpp
@@ -51,16 +51,18 @@ GetPropIRGenerator::tryAttachStub(Maybe<
 
     writer.emplace();
     ValOperandId valId(writer->setInputOperandId(0));
 
     if (val_.isObject()) {
         RootedObject obj(cx_, &val_.toObject());
         ObjOperandId objId = writer->guardIsObject(valId);
 
+        if (!emitted_ && !tryAttachObjectLength(*writer, obj, objId))
+            return false;
         if (!emitted_ && !tryAttachNative(*writer, obj, objId))
             return false;
         if (!emitted_ && !tryAttachUnboxedExpando(*writer, obj, objId))
             return false;
     }
 
     return true;
 }
@@ -269,8 +271,50 @@ GetPropIRGenerator::tryAttachUnboxedExpa
     if (!shape || !shape->hasDefaultGetter() || !shape->hasSlot())
         return true;
 
     emitted_ = true;
 
     EmitReadSlotResult(writer, obj, obj, shape, objId);
     return true;
 }
+
+bool
+GetPropIRGenerator::tryAttachObjectLength(CacheIRWriter& writer, HandleObject obj, ObjOperandId objId)
+{
+    MOZ_ASSERT(!emitted_);
+
+    if (name_ != cx_->names().length)
+        return true;
+
+    if (obj->is<ArrayObject>()) {
+        // Make sure int32 is added to the TypeSet before we attach a stub, so
+        // the stub can return int32 values without monitoring the result.
+        if (obj->as<ArrayObject>().length() > INT32_MAX)
+            return true;
+
+        writer.guardClass(objId, GuardClassKind::Array);
+        writer.loadInt32ArrayLengthResult(objId);
+        emitted_ = true;
+        return true;
+    }
+
+    if (obj->is<UnboxedArrayObject>()) {
+        writer.guardClass(objId, GuardClassKind::UnboxedArray);
+        writer.loadUnboxedArrayLengthResult(objId);
+        emitted_ = true;
+        return true;
+    }
+
+    if (obj->is<ArgumentsObject>() && !obj->as<ArgumentsObject>().hasOverriddenLength()) {
+        if (obj->is<MappedArgumentsObject>()) {
+            writer.guardClass(objId, GuardClassKind::MappedArguments);
+        } else {
+            MOZ_ASSERT(obj->is<UnmappedArgumentsObject>());
+            writer.guardClass(objId, GuardClassKind::UnmappedArguments);
+        }
+        writer.loadArgumentsObjectLengthResult(objId);
+        emitted_ = true;
+        return true;
+    }
+
+    return true;
+}
--- a/js/src/jit/CacheIR.h
+++ b/js/src/jit/CacheIR.h
@@ -77,23 +77,27 @@ class ObjOperandId : public OperandId
     bool operator!=(const ObjOperandId& other) const { return id_ != other.id_; }
 };
 
 #define CACHE_IR_OPS(_)                   \
     _(GuardIsObject)                      \
     _(GuardShape)                         \
     _(GuardGroup)                         \
     _(GuardProto)                         \
+    _(GuardClass)                         \
     _(GuardNoUnboxedExpando)              \
     _(GuardAndLoadUnboxedExpando)         \
     _(LoadObject)                         \
     _(LoadProto)                          \
     _(LoadUnboxedExpando)                 \
     _(LoadFixedSlotResult)                \
     _(LoadDynamicSlotResult)              \
+    _(LoadInt32ArrayLengthResult)         \
+    _(LoadUnboxedArrayLengthResult)       \
+    _(LoadArgumentsObjectLengthResult)    \
     _(LoadUndefinedResult)
 
 enum class CacheOp {
 #define DEFINE_OP(op) op,
     CACHE_IR_OPS(DEFINE_OP)
 #undef DEFINE_OP
 };
 
@@ -109,16 +113,26 @@ struct StubField {
     uintptr_t word;
     GCType gcType;
 
     StubField(uintptr_t word, GCType gcType)
       : word(word), gcType(gcType)
     {}
 };
 
+// We use this enum as GuardClass operand, instead of storing Class* pointers
+// in the IR, to keep the IR compact and the same size on all platforms.
+enum class GuardClassKind
+{
+    Array,
+    UnboxedArray,
+    MappedArguments,
+    UnmappedArguments,
+};
+
 // Class to record CacheIR + some additional metadata for code generation.
 class MOZ_RAII CacheIRWriter
 {
     CompactBufferWriter buffer_;
 
     uint32_t nextOperandId_;
     uint32_t nextInstructionId_;
     uint32_t numInputOperands_;
@@ -235,16 +249,21 @@ class MOZ_RAII CacheIRWriter
     void guardGroup(ObjOperandId obj, ObjectGroup* group) {
         writeOpWithOperandId(CacheOp::GuardGroup, obj);
         addStubWord(uintptr_t(group), StubField::GCType::ObjectGroup);
     }
     void guardProto(ObjOperandId obj, JSObject* proto) {
         writeOpWithOperandId(CacheOp::GuardProto, obj);
         addStubWord(uintptr_t(proto), StubField::GCType::JSObject);
     }
+    void guardClass(ObjOperandId obj, GuardClassKind kind) {
+        MOZ_ASSERT(uint32_t(kind) <= UINT8_MAX);
+        writeOpWithOperandId(CacheOp::GuardClass, obj);
+        buffer_.writeByte(uint32_t(kind));
+    }
     void guardNoUnboxedExpando(ObjOperandId obj) {
         writeOpWithOperandId(CacheOp::GuardNoUnboxedExpando, obj);
     }
     ObjOperandId guardAndLoadUnboxedExpando(ObjOperandId obj) {
         ObjOperandId res(nextOperandId_++);
         writeOpWithOperandId(CacheOp::GuardAndLoadUnboxedExpando, obj);
         writeOperandId(res);
         return res;
@@ -275,16 +294,25 @@ class MOZ_RAII CacheIRWriter
     void loadFixedSlotResult(ObjOperandId obj, size_t offset) {
         writeOpWithOperandId(CacheOp::LoadFixedSlotResult, obj);
         addStubWord(offset, StubField::GCType::NoGCThing);
     }
     void loadDynamicSlotResult(ObjOperandId obj, size_t offset) {
         writeOpWithOperandId(CacheOp::LoadDynamicSlotResult, obj);
         addStubWord(offset, StubField::GCType::NoGCThing);
     }
+    void loadInt32ArrayLengthResult(ObjOperandId obj) {
+        writeOpWithOperandId(CacheOp::LoadInt32ArrayLengthResult, obj);
+    }
+    void loadUnboxedArrayLengthResult(ObjOperandId obj) {
+        writeOpWithOperandId(CacheOp::LoadUnboxedArrayLengthResult, obj);
+    }
+    void loadArgumentsObjectLengthResult(ObjOperandId obj) {
+        writeOpWithOperandId(CacheOp::LoadArgumentsObjectLengthResult, obj);
+    }
 };
 
 class CacheIRStubInfo;
 
 // Helper class for reading CacheIR bytecode.
 class MOZ_RAII CacheIRReader
 {
     CompactBufferReader buffer_;
@@ -308,19 +336,19 @@ class MOZ_RAII CacheIRReader
     }
 
     ValOperandId valOperandId() {
         return ValOperandId(buffer_.readByte());
     }
     ObjOperandId objOperandId() {
         return ObjOperandId(buffer_.readByte());
     }
-    uint32_t stubOffset() {
-        return buffer_.readByte();
-    }
+
+    uint32_t stubOffset() { return buffer_.readByte(); }
+    GuardClassKind guardClassKind() { return GuardClassKind(buffer_.readByte()); }
 
     bool matchOp(CacheOp op) {
         const uint8_t* pos = buffer_.currentPosition();
         if (readOp() == op)
             return true;
         buffer_.seek(pos, 0);
         return false;
     }
@@ -351,16 +379,17 @@ class MOZ_RAII GetPropIRGenerator
     MutableHandleValue res_;
     bool emitted_;
 
     enum class PreliminaryObjectAction { None, Unlink, NotePreliminary };
     PreliminaryObjectAction preliminaryObjectAction_;
 
     bool tryAttachNative(CacheIRWriter& writer, HandleObject obj, ObjOperandId objId);
     bool tryAttachUnboxedExpando(CacheIRWriter& writer, HandleObject obj, ObjOperandId objId);
+    bool tryAttachObjectLength(CacheIRWriter& writer, HandleObject obj, ObjOperandId objId);
 
     GetPropIRGenerator(const GetPropIRGenerator&) = delete;
     GetPropIRGenerator& operator=(const GetPropIRGenerator&) = delete;
 
   public:
     GetPropIRGenerator(JSContext* cx, jsbytecode* pc, HandleValue val, HandlePropertyName name,
                        MutableHandleValue res);
 
--- a/js/src/jit/CodeGenerator.cpp
+++ b/js/src/jit/CodeGenerator.cpp
@@ -7258,69 +7258,83 @@ CodeGenerator::visitNotV(LNotV* lir)
 
     // both branches meet here.
     masm.bind(&join);
 }
 
 void
 CodeGenerator::visitBoundsCheck(LBoundsCheck* lir)
 {
-    if (lir->index()->isConstant()) {
+    const LAllocation* index = lir->index();
+    const LAllocation* length = lir->length();
+    LSnapshot* snapshot = lir->snapshot();
+
+    if (index->isConstant()) {
         // Use uint32 so that the comparison is unsigned.
-        uint32_t index = ToInt32(lir->index());
-        if (lir->length()->isConstant()) {
-            uint32_t length = ToInt32(lir->length());
-            if (index < length)
+        uint32_t idx = ToInt32(index);
+        if (length->isConstant()) {
+            uint32_t len = ToInt32(lir->length());
+            if (idx < len)
                 return;
-            bailout(lir->snapshot());
-        } else {
-            bailoutCmp32(Assembler::BelowOrEqual, ToOperand(lir->length()), Imm32(index),
-                         lir->snapshot());
+            bailout(snapshot);
+            return;
         }
-    } else if (lir->length()->isConstant()) {
-        bailoutCmp32(Assembler::AboveOrEqual, ToRegister(lir->index()),
-                     Imm32(ToInt32(lir->length())), lir->snapshot());
-    } else {
-        bailoutCmp32(Assembler::BelowOrEqual, ToOperand(lir->length()),
-                     ToRegister(lir->index()), lir->snapshot());
-    }
+
+        if (length->isRegister())
+            bailoutCmp32(Assembler::BelowOrEqual, ToRegister(length), Imm32(idx), snapshot);
+        else
+            bailoutCmp32(Assembler::BelowOrEqual, ToAddress(length), Imm32(idx), snapshot);
+        return;
+    }
+
+    Register indexReg = ToRegister(index);
+    if (length->isConstant())
+        bailoutCmp32(Assembler::AboveOrEqual, indexReg, Imm32(ToInt32(length)), snapshot);
+    else if (length->isRegister())
+        bailoutCmp32(Assembler::BelowOrEqual, ToRegister(length), indexReg, snapshot);
+    else
+        bailoutCmp32(Assembler::BelowOrEqual, ToAddress(length), indexReg, snapshot);
 }
 
 void
 CodeGenerator::visitBoundsCheckRange(LBoundsCheckRange* lir)
 {
     int32_t min = lir->mir()->minimum();
     int32_t max = lir->mir()->maximum();
     MOZ_ASSERT(max >= min);
 
+    const LAllocation* length = lir->length();
+    LSnapshot* snapshot = lir->snapshot();
     Register temp = ToRegister(lir->getTemp(0));
     if (lir->index()->isConstant()) {
         int32_t nmin, nmax;
         int32_t index = ToInt32(lir->index());
         if (SafeAdd(index, min, &nmin) && SafeAdd(index, max, &nmax) && nmin >= 0) {
-            bailoutCmp32(Assembler::BelowOrEqual, ToOperand(lir->length()), Imm32(nmax),
-                         lir->snapshot());
+            if (length->isRegister())
+                bailoutCmp32(Assembler::BelowOrEqual, ToRegister(length), Imm32(nmax), snapshot);
+            else
+                bailoutCmp32(Assembler::BelowOrEqual, ToAddress(length), Imm32(nmax), snapshot);
             return;
         }
         masm.mov(ImmWord(index), temp);
     } else {
         masm.mov(ToRegister(lir->index()), temp);
     }
 
     // If the minimum and maximum differ then do an underflow check first.
     // If the two are the same then doing an unsigned comparison on the
     // length will also catch a negative index.
     if (min != max) {
         if (min != 0) {
             Label bail;
             masm.branchAdd32(Assembler::Overflow, Imm32(min), temp, &bail);
-            bailoutFrom(&bail, lir->snapshot());
+            bailoutFrom(&bail, snapshot);
         }
 
-        bailoutCmp32(Assembler::LessThan, temp, Imm32(0), lir->snapshot());
+        bailoutCmp32(Assembler::LessThan, temp, Imm32(0), snapshot);
 
         if (min != 0) {
             int32_t diff;
             if (SafeSub(max, min, &diff))
                 max = diff;
             else
                 masm.sub32(Imm32(min), temp);
         }
@@ -7330,23 +7344,26 @@ CodeGenerator::visitBoundsCheckRange(LBo
     // max > 0. We can only wraparound to a negative number, which will test as
     // larger than all nonnegative numbers in the unsigned comparison, and the
     // length is required to be nonnegative (else testing a negative length
     // would succeed on any nonnegative index).
     if (max != 0) {
         if (max < 0) {
             Label bail;
             masm.branchAdd32(Assembler::Overflow, Imm32(max), temp, &bail);
-            bailoutFrom(&bail, lir->snapshot());
+            bailoutFrom(&bail, snapshot);
         } else {
             masm.add32(Imm32(max), temp);
         }
     }
 
-    bailoutCmp32(Assembler::BelowOrEqual, ToOperand(lir->length()), temp, lir->snapshot());
+    if (length->isRegister())
+        bailoutCmp32(Assembler::BelowOrEqual, ToRegister(length), temp, snapshot);
+    else
+        bailoutCmp32(Assembler::BelowOrEqual, ToAddress(length), temp, snapshot);
 }
 
 void
 CodeGenerator::visitBoundsCheckLower(LBoundsCheckLower* lir)
 {
     int32_t min = lir->mir()->minimum();
     bailoutCmp32(Assembler::LessThan, ToRegister(lir->index()), Imm32(min),
                  lir->snapshot());
@@ -9940,39 +9957,48 @@ void
 CodeGenerator::visitStoreTypedArrayElementHole(LStoreTypedArrayElementHole* lir)
 {
     Register elements = ToRegister(lir->elements());
     const LAllocation* value = lir->value();
 
     Scalar::Type arrayType = lir->mir()->arrayType();
     int width = Scalar::byteSize(arrayType);
 
+    const LAllocation* index = lir->index();
+    const LAllocation* length = lir->length();
+
     bool guardLength = true;
-    if (lir->index()->isConstant() && lir->length()->isConstant()) {
-        uint32_t idx = ToInt32(lir->index());
-        uint32_t len = ToInt32(lir->length());
+    if (index->isConstant() && length->isConstant()) {
+        uint32_t idx = ToInt32(index);
+        uint32_t len = ToInt32(length);
         if (idx >= len)
             return;
         guardLength = false;
     }
     Label skip;
-    if (lir->index()->isConstant()) {
-        uint32_t idx = ToInt32(lir->index());
-        if (guardLength)
-            masm.branch32(Assembler::BelowOrEqual, ToOperand(lir->length()), Imm32(idx), &skip);
+    if (index->isConstant()) {
+        uint32_t idx = ToInt32(index);
+        if (guardLength) {
+            if (length->isRegister())
+                masm.branch32(Assembler::BelowOrEqual, ToRegister(length), Imm32(idx), &skip);
+            else
+                masm.branch32(Assembler::BelowOrEqual, ToAddress(length), Imm32(idx), &skip);
+        }
         Address dest(elements, idx * width);
         StoreToTypedArray(masm, arrayType, value, dest);
     } else {
-        Register idxReg = ToRegister(lir->index());
+        Register idxReg = ToRegister(index);
         MOZ_ASSERT(guardLength);
-        if (lir->length()->isConstant())
-            masm.branch32(Assembler::AboveOrEqual, idxReg, Imm32(ToInt32(lir->length())), &skip);
+        if (length->isConstant())
+            masm.branch32(Assembler::AboveOrEqual, idxReg, Imm32(ToInt32(length)), &skip);
+        else if (length->isRegister())
+            masm.branch32(Assembler::BelowOrEqual, ToRegister(length), idxReg, &skip);
         else
-            masm.branch32(Assembler::BelowOrEqual, ToOperand(lir->length()), idxReg, &skip);
-        BaseIndex dest(elements, ToRegister(lir->index()), ScaleFromElemWidth(width));
+            masm.branch32(Assembler::BelowOrEqual, ToAddress(length), idxReg, &skip);
+        BaseIndex dest(elements, ToRegister(index), ScaleFromElemWidth(width));
         StoreToTypedArray(masm, arrayType, value, dest);
     }
     if (guardLength)
         masm.bind(&skip);
 }
 
 void
 CodeGenerator::visitAtomicIsLockFree(LAtomicIsLockFree* lir)
--- a/js/src/jit/MacroAssembler.h
+++ b/js/src/jit/MacroAssembler.h
@@ -812,18 +812,18 @@ class MacroAssembler : public MacroAssem
         DEFINED_ON(arm, arm64, mips_shared, x86, x64);
     inline void branch32(Condition cond, const AbsoluteAddress& lhs, Imm32 rhs, Label* label)
         DEFINED_ON(arm, arm64, mips_shared, x86, x64);
 
     inline void branch32(Condition cond, const BaseIndex& lhs, Register rhs, Label* label)
         DEFINED_ON(x86_shared);
     inline void branch32(Condition cond, const BaseIndex& lhs, Imm32 rhs, Label* label) PER_SHARED_ARCH;
 
-    inline void branch32(Condition cond, const Operand& lhs, Register rhs, Label* label) PER_SHARED_ARCH;
-    inline void branch32(Condition cond, const Operand& lhs, Imm32 rhs, Label* label) PER_SHARED_ARCH;
+    inline void branch32(Condition cond, const Operand& lhs, Register rhs, Label* label) DEFINED_ON(x86_shared);
+    inline void branch32(Condition cond, const Operand& lhs, Imm32 rhs, Label* label) DEFINED_ON(x86_shared);
 
     inline void branch32(Condition cond, wasm::SymbolicAddress lhs, Imm32 rhs, Label* label)
         DEFINED_ON(arm, arm64, mips_shared, x86, x64);
 
     inline void branch64(Condition cond, const Address& lhs, Imm64 val, Label* label) PER_ARCH;
 
     // Compare the value at |lhs| with the value at |rhs|.  The scratch
     // register *must not* be the base of |lhs| or |rhs|.
--- a/js/src/jit/SharedIC.cpp
+++ b/js/src/jit/SharedIC.cpp
@@ -2112,61 +2112,16 @@ TryAttachLengthStub(JSContext* cx, Share
         if (!newStub)
             return false;
 
         *attached = true;
         stub->addNewStub(newStub);
         return true;
     }
 
-    if (!val.isObject())
-        return true;
-
-    RootedObject obj(cx, &val.toObject());
-
-    if (obj->is<ArrayObject>() && res.isInt32()) {
-        JitSpew(JitSpew_BaselineIC, "  Generating GetProp(Array.length) stub");
-        ICGetProp_ArrayLength::Compiler compiler(cx, info->engine());
-        ICStub* newStub = compiler.getStub(compiler.getStubSpace(info->outerScript(cx)));
-        if (!newStub)
-            return false;
-
-        *attached = true;
-        stub->addNewStub(newStub);
-        return true;
-    }
-
-    if (obj->is<UnboxedArrayObject>() && res.isInt32()) {
-        JitSpew(JitSpew_BaselineIC, "  Generating GetProp(UnboxedArray.length) stub");
-        ICGetProp_UnboxedArrayLength::Compiler compiler(cx, info->engine());
-        ICStub* newStub = compiler.getStub(compiler.getStubSpace(info->outerScript(cx)));
-        if (!newStub)
-            return false;
-
-        *attached = true;
-        stub->addNewStub(newStub);
-        return true;
-    }
-
-    if (obj->is<ArgumentsObject>() && res.isInt32()) {
-        JitSpew(JitSpew_BaselineIC, "  Generating GetProp(ArgsObj.length %s) stub",
-                obj->is<MappedArgumentsObject>() ? "Mapped" : "Unmapped");
-        ICGetProp_ArgumentsLength::Which which = ICGetProp_ArgumentsLength::Mapped;
-        if (obj->is<UnmappedArgumentsObject>())
-            which = ICGetProp_ArgumentsLength::Unmapped;
-        ICGetProp_ArgumentsLength::Compiler compiler(cx, info->engine(), which);
-        ICStub* newStub = compiler.getStub(compiler.getStubSpace(info->outerScript(cx)));
-        if (!newStub)
-            return false;
-
-        *attached = true;
-        stub->addNewStub(newStub);
-        return true;
-    }
-
     return true;
 }
 
 static bool
 UpdateExistingGenerationalDOMProxyStub(ICGetProp_Fallback* stub,
                                        HandleObject obj)
 {
     Value expandoSlot = GetProxyExtra(obj, GetDOMProxyExpandoSlot());
@@ -3036,68 +2991,16 @@ ICGetProp_Fallback::Compiler::postGenera
 {
     if (engine_ == Engine::Baseline) {
         void* address = code->raw() + returnOffset_;
         cx->compartment()->jitCompartment()->initBaselineGetPropReturnAddr(address);
     }
 }
 
 bool
-ICGetProp_ArrayLength::Compiler::generateStubCode(MacroAssembler& masm)
-{
-    Label failure;
-    masm.branchTestObject(Assembler::NotEqual, R0, &failure);
-
-    Register scratch = R1.scratchReg();
-
-    // Unbox R0 and guard it's an array.
-    Register obj = masm.extractObject(R0, ExtractTemp0);
-    masm.branchTestObjClass(Assembler::NotEqual, obj, scratch, &ArrayObject::class_, &failure);
-
-    // Load obj->elements->length.
-    masm.loadPtr(Address(obj, NativeObject::offsetOfElements()), scratch);
-    masm.load32(Address(scratch, ObjectElements::offsetOfLength()), scratch);
-
-    // Guard length fits in an int32.
-    masm.branchTest32(Assembler::Signed, scratch, scratch, &failure);
-
-    masm.tagValue(JSVAL_TYPE_INT32, scratch, R0);
-    EmitReturnFromIC(masm);
-
-    // Failure case - jump to next stub
-    masm.bind(&failure);
-    EmitStubGuardFailure(masm);
-    return true;
-}
-
-bool
-ICGetProp_UnboxedArrayLength::Compiler::generateStubCode(MacroAssembler& masm)
-{
-    Label failure;
-    masm.branchTestObject(Assembler::NotEqual, R0, &failure);
-
-    Register scratch = R1.scratchReg();
-
-    // Unbox R0 and guard it's an unboxed array.
-    Register obj = masm.extractObject(R0, ExtractTemp0);
-    masm.branchTestObjClass(Assembler::NotEqual, obj, scratch, &UnboxedArrayObject::class_, &failure);
-
-    // Load obj->length.
-    masm.load32(Address(obj, UnboxedArrayObject::offsetOfLength()), scratch);
-
-    masm.tagValue(JSVAL_TYPE_INT32, scratch, R0);
-    EmitReturnFromIC(masm);
-
-    // Failure case - jump to next stub
-    masm.bind(&failure);
-    EmitStubGuardFailure(masm);
-    return true;
-}
-
-bool
 ICGetProp_StringLength::Compiler::generateStubCode(MacroAssembler& masm)
 {
     Label failure;
     masm.branchTestString(Assembler::NotEqual, R0, &failure);
 
     // Unbox string and load its length.
     Register string = masm.extractString(R0, ExtractTemp0);
     masm.loadStringLength(string, string);
@@ -3794,63 +3697,32 @@ ICGetProp_DOMProxyShadowed::Compiler::ge
     masm.bind(&failure);
     EmitStubGuardFailure(masm);
     return true;
 }
 
 bool
 ICGetProp_ArgumentsLength::Compiler::generateStubCode(MacroAssembler& masm)
 {
+    MOZ_ASSERT(which_ == ICGetProp_ArgumentsLength::Magic);
+
     Label failure;
-    if (which_ == ICGetProp_ArgumentsLength::Magic) {
-        // Ensure that this is lazy arguments.
-        masm.branchTestMagicValue(Assembler::NotEqual, R0, JS_OPTIMIZED_ARGUMENTS, &failure);
-
-        // Ensure that frame has not loaded different arguments object since.
-        masm.branchTest32(Assembler::NonZero,
-                          Address(BaselineFrameReg, BaselineFrame::reverseOffsetOfFlags()),
-                          Imm32(BaselineFrame::HAS_ARGS_OBJ),
-                          &failure);
-
-        Address actualArgs(BaselineFrameReg, BaselineFrame::offsetOfNumActualArgs());
-        masm.loadPtr(actualArgs, R0.scratchReg());
-        masm.tagValue(JSVAL_TYPE_INT32, R0.scratchReg(), R0);
-        EmitReturnFromIC(masm);
-
-        masm.bind(&failure);
-        EmitStubGuardFailure(masm);
-        return true;
-    }
-    MOZ_ASSERT(which_ == ICGetProp_ArgumentsLength::Mapped ||
-               which_ == ICGetProp_ArgumentsLength::Unmapped);
-
-    const Class* clasp = (which_ == ICGetProp_ArgumentsLength::Mapped)
-                         ? &MappedArgumentsObject::class_
-                         : &UnmappedArgumentsObject::class_;
-
-    Register scratchReg = R1.scratchReg();
-
-    // Guard on input being an arguments object.
-    masm.branchTestObject(Assembler::NotEqual, R0, &failure);
-    Register objReg = masm.extractObject(R0, ExtractTemp0);
-    masm.branchTestObjClass(Assembler::NotEqual, objReg, scratchReg, clasp, &failure);
-
-    // Get initial length value.
-    masm.unboxInt32(Address(objReg, ArgumentsObject::getInitialLengthSlotOffset()), scratchReg);
-
-    // Test if length has been overridden.
+
+    // Ensure that this is lazy arguments.
+    masm.branchTestMagicValue(Assembler::NotEqual, R0, JS_OPTIMIZED_ARGUMENTS, &failure);
+
+    // Ensure that frame has not loaded different arguments object since.
     masm.branchTest32(Assembler::NonZero,
-                      scratchReg,
-                      Imm32(ArgumentsObject::LENGTH_OVERRIDDEN_BIT),
+                      Address(BaselineFrameReg, BaselineFrame::reverseOffsetOfFlags()),
+                      Imm32(BaselineFrame::HAS_ARGS_OBJ),
                       &failure);
 
-    // Nope, shift out arguments length and return it.
-    // No need to type monitor because this stub always returns Int32.
-    masm.rshiftPtr(Imm32(ArgumentsObject::PACKED_BITS_COUNT), scratchReg);
-    masm.tagValue(JSVAL_TYPE_INT32, scratchReg, R0);
+    Address actualArgs(BaselineFrameReg, BaselineFrame::offsetOfNumActualArgs());
+    masm.loadPtr(actualArgs, R0.scratchReg());
+    masm.tagValue(JSVAL_TYPE_INT32, R0.scratchReg(), R0);
     EmitReturnFromIC(masm);
 
     masm.bind(&failure);
     EmitStubGuardFailure(masm);
     return true;
 }
 
 ICGetProp_ArgumentsCallee::ICGetProp_ArgumentsCallee(JitCode* stubCode, ICStub* firstMonitorStub)
--- a/js/src/jit/SharedIC.h
+++ b/js/src/jit/SharedIC.h
@@ -2368,64 +2368,16 @@ class ICGetProp_Generic : public ICMonit
         {}
 
         ICStub* getStub(ICStubSpace* space) {
             return newStub<ICGetProp_Generic>(space, getStubCode(), firstMonitorStub_);
         }
     };
 };
 
-// Stub for accessing a dense array's length.
-class ICGetProp_ArrayLength : public ICStub
-{
-    friend class ICStubSpace;
-
-    explicit ICGetProp_ArrayLength(JitCode* stubCode)
-      : ICStub(GetProp_ArrayLength, stubCode)
-    {}
-
-  public:
-    class Compiler : public ICStubCompiler {
-        bool generateStubCode(MacroAssembler& masm);
-
-      public:
-        explicit Compiler(JSContext* cx, Engine engine)
-          : ICStubCompiler(cx, ICStub::GetProp_ArrayLength, engine)
-        {}
-
-        ICStub* getStub(ICStubSpace* space) {
-            return newStub<ICGetProp_ArrayLength>(space, getStubCode());
-        }
-    };
-};
-
-// Stub for accessing an unboxed array's length.
-class ICGetProp_UnboxedArrayLength : public ICStub
-{
-    friend class ICStubSpace;
-
-    explicit ICGetProp_UnboxedArrayLength(JitCode* stubCode)
-      : ICStub(GetProp_UnboxedArrayLength, stubCode)
-    {}
-
-  public:
-    class Compiler : public ICStubCompiler {
-        bool generateStubCode(MacroAssembler& masm);
-
-      public:
-        explicit Compiler(JSContext* cx, Engine engine)
-          : ICStubCompiler(cx, ICStub::GetProp_UnboxedArrayLength, engine)
-        {}
-
-        ICStub* getStub(ICStubSpace* space) {
-            return newStub<ICGetProp_UnboxedArrayLength>(space, getStubCode());
-        }
-    };
-};
-
 // Stub for accessing a property on a primitive's prototype.
 class ICGetProp_Primitive : public ICMonitoredStub
 {
     friend class ICStubSpace;
 
   protected: // Protected to silence Clang warning.
     // Shape of String.prototype/Number.prototype to check for.
     HeapPtrShape protoShape_;
@@ -3223,17 +3175,17 @@ class ICGetProp_DOMProxyShadowed : publi
         ICStub* getStub(ICStubSpace* space);
     };
 };
 
 class ICGetProp_ArgumentsLength : public ICStub
 {
   friend class ICStubSpace;
   public:
-    enum Which { Mapped, Unmapped, Magic };
+    enum Which { Magic };
 
   protected:
     explicit ICGetProp_ArgumentsLength(JitCode* stubCode)
       : ICStub(ICStub::GetProp_ArgumentsLength, stubCode)
     { }
 
   public:
     class Compiler : public ICStubCompiler {
--- a/js/src/jit/SharedICList.h
+++ b/js/src/jit/SharedICList.h
@@ -30,18 +30,16 @@ namespace jit {
     _(Compare_NumberWithUndefined)               \
     _(Compare_String)                            \
     _(Compare_Boolean)                           \
     _(Compare_Object)                            \
     _(Compare_ObjectWithUndefined)               \
     _(Compare_Int32WithBoolean)                  \
                                                  \
     _(GetProp_Fallback)                          \
-    _(GetProp_ArrayLength)                       \
-    _(GetProp_UnboxedArrayLength)                \
     _(GetProp_Primitive)                         \
     _(GetProp_StringLength)                      \
     _(GetProp_Unboxed)                           \
     _(GetProp_TypedObject)                       \
     _(GetProp_CallScripted)                      \
     _(GetProp_CallNative)                        \
     _(GetProp_CallNativeGlobal)                  \
     _(GetProp_CallDOMProxyNative)                \
--- a/js/src/jit/arm/CodeGenerator-arm.cpp
+++ b/js/src/jit/arm/CodeGenerator-arm.cpp
@@ -82,30 +82,37 @@ CodeGeneratorARM::visitCompare(LCompare*
 {
     Assembler::Condition cond = JSOpToCondition(comp->mir()->compareType(), comp->jsop());
     const LAllocation* left = comp->getOperand(0);
     const LAllocation* right = comp->getOperand(1);
     const LDefinition* def = comp->getDef(0);
 
     if (right->isConstant())
         masm.ma_cmp(ToRegister(left), Imm32(ToInt32(right)));
+    else if (right->isRegister())
+        masm.ma_cmp(ToRegister(left), ToRegister(right));
     else
-        masm.ma_cmp(ToRegister(left), ToOperand(right));
+        masm.ma_cmp(ToRegister(left), Operand(ToAddress(right)));
     masm.ma_mov(Imm32(0), ToRegister(def));
     masm.ma_mov(Imm32(1), ToRegister(def), cond);
 }
 
 void
 CodeGeneratorARM::visitCompareAndBranch(LCompareAndBranch* comp)
 {
     Assembler::Condition cond = JSOpToCondition(comp->cmpMir()->compareType(), comp->jsop());
-    if (comp->right()->isConstant())
-        masm.ma_cmp(ToRegister(comp->left()), Imm32(ToInt32(comp->right())));
+    const LAllocation* left = comp->left();
+    const LAllocation* right = comp->right();
+
+    if (right->isConstant())
+        masm.ma_cmp(ToRegister(left), Imm32(ToInt32(right)));
+    else if (right->isRegister())
+        masm.ma_cmp(ToRegister(left), ToRegister(right));
     else
-        masm.ma_cmp(ToRegister(comp->left()), ToOperand(comp->right()));
+        masm.ma_cmp(ToRegister(left), Operand(ToAddress(right)));
     emitBranch(cond, comp->ifTrue(), comp->ifFalse());
 }
 
 bool
 CodeGeneratorARM::generateOutOfLineCode()
 {
     if (!CodeGeneratorShared::generateOutOfLineCode())
         return false;
@@ -333,34 +340,38 @@ void
 CodeGeneratorARM::visitAddI(LAddI* ins)
 {
     const LAllocation* lhs = ins->getOperand(0);
     const LAllocation* rhs = ins->getOperand(1);
     const LDefinition* dest = ins->getDef(0);
 
     if (rhs->isConstant())
         masm.ma_add(ToRegister(lhs), Imm32(ToInt32(rhs)), ToRegister(dest), SetCC);
+    else if (rhs->isRegister())
+        masm.ma_add(ToRegister(lhs), ToRegister(rhs), ToRegister(dest), SetCC);
     else
-        masm.ma_add(ToRegister(lhs), ToOperand(rhs), ToRegister(dest), SetCC);
+        masm.ma_add(ToRegister(lhs), Operand(ToAddress(rhs)), ToRegister(dest), SetCC);
 
     if (ins->snapshot())
         bailoutIf(Assembler::Overflow, ins->snapshot());
 }
 
 void
 CodeGeneratorARM::visitSubI(LSubI* ins)
 {
     const LAllocation* lhs = ins->getOperand(0);
     const LAllocation* rhs = ins->getOperand(1);
     const LDefinition* dest = ins->getDef(0);
 
     if (rhs->isConstant())
         masm.ma_sub(ToRegister(lhs), Imm32(ToInt32(rhs)), ToRegister(dest), SetCC);
+    else if (rhs->isRegister())
+        masm.ma_sub(ToRegister(lhs), ToRegister(rhs), ToRegister(dest), SetCC);
     else
-        masm.ma_sub(ToRegister(lhs), ToOperand(rhs), ToRegister(dest), SetCC);
+        masm.ma_sub(ToRegister(lhs), Operand(ToAddress(rhs)), ToRegister(dest), SetCC);
 
     if (ins->snapshot())
         bailoutIf(Assembler::Overflow, ins->snapshot());
 }
 
 void
 CodeGeneratorARM::visitMulI(LMulI* ins)
 {
@@ -449,17 +460,16 @@ CodeGeneratorARM::visitMulI(LMulI* ins)
           }
         }
         // Bailout on overflow.
         if (mul->canOverflow())
             bailoutIf(c, ins->snapshot());
     } else {
         Assembler::Condition c = Assembler::Overflow;
 
-        // masm.imull(ToOperand(rhs), ToRegister(lhs));
         if (mul->canOverflow())
             c = masm.ma_check_mul(ToRegister(lhs), ToRegister(rhs), ToRegister(dest), c);
         else
             masm.ma_mul(ToRegister(lhs), ToRegister(rhs), ToRegister(dest));
 
         // Bailout on overflow.
         if (mul->canOverflow())
             bailoutIf(c, ins->snapshot());
--- a/js/src/jit/arm/MacroAssembler-arm-inl.h
+++ b/js/src/jit/arm/MacroAssembler-arm-inl.h
@@ -476,42 +476,16 @@ void
 MacroAssembler::branch32(Condition cond, const BaseIndex& lhs, Imm32 rhs, Label* label)
 {
     // branch32 will use ScratchRegister.
     AutoRegisterScope scratch2(*this, secondScratchReg_);
     load32(lhs, scratch2);
     branch32(cond, scratch2, rhs, label);
 }
 
-
-void
-MacroAssembler::branch32(Condition cond, const Operand& lhs, Register rhs, Label* label)
-{
-    if (lhs.getTag() == Operand::OP2) {
-        branch32(cond, lhs.toReg(), rhs, label);
-    } else {
-        ScratchRegisterScope scratch(*this);
-        ma_ldr(lhs.toAddress(), scratch);
-        branch32(cond, scratch, rhs, label);
-    }
-}
-
-void
-MacroAssembler::branch32(Condition cond, const Operand& lhs, Imm32 rhs, Label* label)
-{
-    if (lhs.getTag() == Operand::OP2) {
-        branch32(cond, lhs.toReg(), rhs, label);
-    } else {
-        // branch32 will use ScratchRegister.
-        AutoRegisterScope scratch(*this, secondScratchReg_);
-        ma_ldr(lhs.toAddress(), scratch);
-        branch32(cond, scratch, rhs, label);
-    }
-}
-
 void
 MacroAssembler::branch32(Condition cond, wasm::SymbolicAddress lhs, Imm32 rhs, Label* label)
 {
     ScratchRegisterScope scratch(*this);
     loadPtr(lhs, scratch);
     branch32(cond, scratch, rhs, label);
 }
 
--- a/js/src/jit/arm/MacroAssembler-arm.cpp
+++ b/js/src/jit/arm/MacroAssembler-arm.cpp
@@ -2360,29 +2360,16 @@ MacroAssembler::clampDoubleToUint8(Float
 void
 MacroAssemblerARMCompat::cmp32(Register lhs, Imm32 rhs)
 {
     MOZ_ASSERT(lhs != ScratchRegister);
     ma_cmp(lhs, rhs);
 }
 
 void
-MacroAssemblerARMCompat::cmp32(const Operand& lhs, Register rhs)
-{
-    ma_cmp(lhs.toReg(), rhs);
-}
-
-void
-MacroAssemblerARMCompat::cmp32(const Operand& lhs, Imm32 rhs)
-{
-    MOZ_ASSERT(lhs.toReg() != ScratchRegister);
-    ma_cmp(lhs.toReg(), rhs);
-}
-
-void
 MacroAssemblerARMCompat::cmp32(Register lhs, Register rhs)
 {
     ma_cmp(lhs, rhs);
 }
 
 void
 MacroAssemblerARMCompat::cmpPtr(Register lhs, ImmWord rhs)
 {
--- a/js/src/jit/arm/MacroAssembler-arm.h
+++ b/js/src/jit/arm/MacroAssembler-arm.h
@@ -1310,18 +1310,22 @@ class MacroAssemblerARMCompat : public M
         ma_mov(Imm32(0xff), reg, NotEqual);
         ma_mov(Imm32(0), reg, Signed);
     }
 
     inline void incrementInt32Value(const Address& addr);
 
     void cmp32(Register lhs, Imm32 rhs);
     void cmp32(Register lhs, Register rhs);
-    void cmp32(const Operand& lhs, Imm32 rhs);
-    void cmp32(const Operand& lhs, Register rhs);
+    void cmp32(const Address& lhs, Imm32 rhs) {
+        MOZ_CRASH("NYI");
+    }
+    void cmp32(const Address& lhs, Register rhs) {
+        MOZ_CRASH("NYI");
+    }
 
     void cmpPtr(Register lhs, Register rhs);
     void cmpPtr(Register lhs, ImmWord rhs);
     void cmpPtr(Register lhs, ImmPtr rhs);
     void cmpPtr(Register lhs, ImmGCPtr rhs);
     void cmpPtr(Register lhs, Imm32 rhs);
     void cmpPtr(const Address& lhs, Register rhs);
     void cmpPtr(const Address& lhs, ImmWord rhs);
--- a/js/src/jit/arm64/CodeGenerator-arm64.h
+++ b/js/src/jit/arm64/CodeGenerator-arm64.h
@@ -23,28 +23,16 @@ class CodeGeneratorARM64 : public CodeGe
     CodeGeneratorARM64* thisFromCtor() { return this; }
 
   public:
     CodeGeneratorARM64(MIRGenerator* gen, LIRGraph* graph, MacroAssembler* masm);
 
   protected:
     NonAssertingLabel deoptLabel_;
 
-    // FIXME: VIXL Operand does not match the platform-agnostic Operand,
-    // which is just a union of possible arguments.
-    inline Operand ToOperand(const LAllocation& a) {
-        MOZ_CRASH("ToOperand");
-    }
-    inline Operand ToOperand(const LAllocation* a) {
-        return ToOperand(*a);
-    }
-    inline Operand ToOperand(const LDefinition* def) {
-        return ToOperand(def->output());
-    }
-
     MoveOperand toMoveOperand(const LAllocation a) const;
 
     void bailoutIf(Assembler::Condition condition, LSnapshot* snapshot);
     void bailoutFrom(Label* label, LSnapshot* snapshot);
     void bailout(LSnapshot* snapshot);
 
     template <typename T1, typename T2>
     void bailoutCmpPtr(Assembler::Condition c, T1 lhs, T2 rhs, LSnapshot* snapshot) {
--- a/js/src/jit/arm64/MacroAssembler-arm64-inl.h
+++ b/js/src/jit/arm64/MacroAssembler-arm64-inl.h
@@ -529,33 +529,16 @@ MacroAssembler::branch32(Condition cond,
     vixl::UseScratchRegisterScope temps(this);
     const ARMRegister scratch32 = temps.AcquireW();
     MOZ_ASSERT(scratch32.asUnsized() != lhs.base);
     MOZ_ASSERT(scratch32.asUnsized() != lhs.index);
     doBaseIndex(scratch32, lhs, vixl::LDR_w);
     branch32(cond, scratch32.asUnsized(), rhs, label);
 }
 
-
-void
-MacroAssembler::branch32(Condition cond, const Operand& lhs, Register rhs, Label* label)
-{
-    // since rhs is an operand, do the compare backwards
-    Cmp(ARMRegister(rhs, 32), lhs);
-    B(label, Assembler::InvertCmpCondition(cond));
-}
-
-void
-MacroAssembler::branch32(Condition cond, const Operand& lhs, Imm32 rhs, Label* label)
-{
-    ARMRegister l = lhs.reg();
-    Cmp(l, Operand(rhs.value));
-    B(label, cond);
-}
-
 void
 MacroAssembler::branch32(Condition cond, wasm::SymbolicAddress lhs, Imm32 rhs, Label* label)
 {
     vixl::UseScratchRegisterScope temps(this);
     const Register scratch = temps.AcquireX().asUnsized();
     movePtr(lhs, scratch);
     branch32(cond, Address(scratch, 0), rhs, label);
 }
--- a/js/src/jit/arm64/MacroAssembler-arm64.h
+++ b/js/src/jit/arm64/MacroAssembler-arm64.h
@@ -1026,16 +1026,22 @@ class MacroAssemblerCompat : public vixl
         Tst(ARMRegister(lhs, 32), Operand(rhs.value));
     }
     void cmp32(Register lhs, Imm32 rhs) {
         Cmp(ARMRegister(lhs, 32), Operand(rhs.value));
     }
     void cmp32(Register a, Register b) {
         Cmp(ARMRegister(a, 32), Operand(ARMRegister(b, 32)));
     }
+    void cmp32(const Address& lhs, Imm32 rhs) {
+        cmp32(Operand(lhs.base, lhs.offset), rhs);
+    }
+    void cmp32(const Address& lhs, Register rhs) {
+        cmp32(Operand(lhs.base, lhs.offset), rhs);
+    }
     void cmp32(const Operand& lhs, Imm32 rhs) {
         vixl::UseScratchRegisterScope temps(this);
         const ARMRegister scratch32 = temps.AcquireW();
         Mov(scratch32, lhs);
         Cmp(scratch32, Operand(rhs.value));
     }
     void cmp32(const Operand& lhs, Register rhs) {
         vixl::UseScratchRegisterScope temps(this);
--- a/js/src/jit/mips-shared/CodeGenerator-mips-shared.cpp
+++ b/js/src/jit/mips-shared/CodeGenerator-mips-shared.cpp
@@ -29,34 +29,16 @@
 using namespace js;
 using namespace js::jit;
 
 using mozilla::FloorLog2;
 using mozilla::NegativeInfinity;
 using JS::GenericNaN;
 using JS::ToInt32;
 
-// inline
-Address
-CodeGeneratorMIPSShared::ToAddress(const LAllocation& a)
-{
-    MOZ_ASSERT(a.isMemory());
-    int32_t offset = ToStackOffset(&a);
-
-    return Address(StackPointer, offset);
-}
-
-// inline
-Address
-CodeGeneratorMIPSShared::ToAddress(const LAllocation* a)
-{
-    return ToAddress(*a);
-}
-
-
 // shared
 CodeGeneratorMIPSShared::CodeGeneratorMIPSShared(MIRGenerator* gen, LIRGraph* graph, MacroAssembler* masm)
   : CodeGeneratorShared(gen, graph, masm)
 {
 }
 
 void
 CodeGeneratorMIPSShared::branchToBlock(Assembler::FloatFormat fmt, FloatRegister lhs, FloatRegister rhs,
@@ -1923,44 +1905,43 @@ CodeGeneratorMIPSShared::visitAsmJSPassS
 }
 
 void
 CodeGeneratorMIPSShared::visitAsmSelect(LAsmSelect* ins)
 {
     MIRType mirType = ins->mir()->type();
 
     Register cond = ToRegister(ins->condExpr());
+    const LAllocation* falseExpr = ins->falseExpr();
 
     if (mirType == MIRType_Int32) {
-        Register falseExpr = ToRegister(ins->falseExpr());
         Register out = ToRegister(ins->output());
         MOZ_ASSERT(ToRegister(ins->trueExpr()) == out, "true expr input is reused for output");
-        masm.as_movz(out, falseExpr, cond);
+        masm.as_movz(out, ToRegister(falseExpr), cond);
         return;
     }
 
-    Operand falseExpr = ToOperand(ins->falseExpr());
     FloatRegister out = ToFloatRegister(ins->output());
     MOZ_ASSERT(ToFloatRegister(ins->trueExpr()) == out, "true expr input is reused for output");
 
-    if (falseExpr.getTag() == Operand::FREG) {
+    if (falseExpr->isFloatReg()) {
         if (mirType == MIRType_Float32)
-            masm.as_movz(Assembler::SingleFloat, out, falseExpr.toFReg(), cond);
+            masm.as_movz(Assembler::SingleFloat, out, ToFloatRegister(falseExpr), cond);
         else if (mirType == MIRType_Double)
-            masm.as_movz(Assembler::DoubleFloat, out, falseExpr.toFReg(), cond);
+            masm.as_movz(Assembler::DoubleFloat, out, ToFloatRegister(falseExpr), cond);
         else
             MOZ_CRASH("unhandled type in visitAsmSelect!");
     } else {
         Label done;
         masm.ma_b(cond, cond, &done, Assembler::NonZero, ShortJump);
 
         if (mirType == MIRType_Float32)
-            masm.loadFloat32(falseExpr.toAddress(), out);
+            masm.loadFloat32(ToAddress(falseExpr), out);
         else if (mirType == MIRType_Double)
-            masm.loadDouble(falseExpr.toAddress(), out);
+            masm.loadDouble(ToAddress(falseExpr), out);
         else
             MOZ_CRASH("unhandled type in visitAsmSelect!");
 
         masm.bind(&done);
     }
 }
 
 void
--- a/js/src/jit/mips-shared/CodeGenerator-mips-shared.h
+++ b/js/src/jit/mips-shared/CodeGenerator-mips-shared.h
@@ -21,37 +21,25 @@ class CodeGeneratorMIPSShared : public C
 
     CodeGeneratorMIPSShared* thisFromCtor() {
         return this;
     }
 
   protected:
     NonAssertingLabel deoptLabel_;
 
-    inline Address ToAddress(const LAllocation& a);
-    inline Address ToAddress(const LAllocation* a);
-
     MoveOperand toMoveOperand(LAllocation a) const;
 
     template <typename T1, typename T2>
     void bailoutCmp32(Assembler::Condition c, T1 lhs, T2 rhs, LSnapshot* snapshot) {
         Label bail;
         masm.branch32(c, lhs, rhs, &bail);
         bailoutFrom(&bail, snapshot);
     }
     template<typename T>
-    void bailoutCmp32(Assembler::Condition c, Operand lhs, T rhs, LSnapshot* snapshot) {
-        if (lhs.getTag() == Operand::REG)
-            bailoutCmp32(c, lhs.toReg(), rhs, snapshot);
-        else if (lhs.getTag() == Operand::MEM)
-            bailoutCmp32(c, lhs.toAddress(), rhs, snapshot);
-        else
-            MOZ_CRASH("Invalid operand tag.");
-    }
-    template<typename T>
     void bailoutTest32(Assembler::Condition c, Register lhs, T rhs, LSnapshot* snapshot) {
         Label bail;
         masm.branchTest32(c, lhs, rhs, &bail);
         bailoutFrom(&bail, snapshot);
     }
     template <typename T1, typename T2>
     void bailoutCmpPtr(Assembler::Condition c, T1 lhs, T2 rhs, LSnapshot* snapshot) {
         Label bail;
--- a/js/src/jit/mips-shared/MacroAssembler-mips-shared-inl.h
+++ b/js/src/jit/mips-shared/MacroAssembler-mips-shared-inl.h
@@ -238,34 +238,16 @@ MacroAssembler::branch32(Condition cond,
 void
 MacroAssembler::branch32(Condition cond, const BaseIndex& lhs, Imm32 rhs, Label* label)
 {
     load32(lhs, SecondScratchReg);
     ma_b(SecondScratchReg, rhs, label, cond);
 }
 
 void
-MacroAssembler::branch32(Condition cond, const Operand& lhs, Register rhs, Label* label)
-{
-    if (lhs.getTag() == Operand::REG)
-        ma_b(lhs.toReg(), rhs, label, cond);
-    else
-        branch32(cond, lhs.toAddress(), rhs, label);
-}
-
-void
-MacroAssembler::branch32(Condition cond, const Operand& lhs, Imm32 rhs, Label* label)
-{
-    if (lhs.getTag() == Operand::REG)
-        ma_b(lhs.toReg(), rhs, label, cond);
-    else
-        branch32(cond, lhs.toAddress(), rhs, label);
-}
-
-void
 MacroAssembler::branch32(Condition cond, wasm::SymbolicAddress addr, Imm32 imm, Label* label)
 {
     load32(addr, SecondScratchReg);
     ma_b(SecondScratchReg, imm, label, cond);
 }
 
 void
 MacroAssembler::branchPtr(Condition cond, Register lhs, Register rhs, Label* label)
--- a/js/src/jit/mips64/CodeGenerator-mips64.cpp
+++ b/js/src/jit/mips64/CodeGenerator-mips64.cpp
@@ -172,33 +172,58 @@ CodeGeneratorMIPS64::visitUnbox(LUnbox* 
 
     if (mir->fallible()) {
         const ValueOperand value = ToValue(unbox, LUnbox::Input);
         masm.splitTag(value, SecondScratchReg);
         bailoutCmp32(Assembler::NotEqual, SecondScratchReg, Imm32(MIRTypeToTag(mir->type())),
                      unbox->snapshot());
     }
 
-    Operand input = ToOperand(unbox->getOperand(LUnbox::Input));
+    LAllocation* input = unbox->getOperand(LUnbox::Input);
     Register result = ToRegister(unbox->output());
+    if (input->isRegister()) {
+        Register inputReg = ToRegister(input);
+        switch (mir->type()) {
+          case MIRType_Int32:
+            masm.unboxInt32(inputReg, result);
+            break;
+          case MIRType_Boolean:
+            masm.unboxBoolean(inputReg, result);
+            break;
+          case MIRType_Object:
+            masm.unboxObject(inputReg, result);
+            break;
+          case MIRType_String:
+            masm.unboxString(inputReg, result);
+            break;
+          case MIRType_Symbol:
+            masm.unboxSymbol(inputReg, result);
+            break;
+          default:
+            MOZ_CRASH("Given MIRType cannot be unboxed.");
+        }
+        return;
+    }
+
+    Address inputAddr = ToAddress(input);
     switch (mir->type()) {
       case MIRType_Int32:
-        masm.unboxInt32(input, result);
+        masm.unboxInt32(inputAddr, result);
         break;
       case MIRType_Boolean:
-        masm.unboxBoolean(input, result);
+        masm.unboxBoolean(inputAddr, result);
         break;
       case MIRType_Object:
-        masm.unboxObject(input, result);
+        masm.unboxObject(inputAddr, result);
         break;
       case MIRType_String:
-        masm.unboxString(input, result);
+        masm.unboxString(inputAddr, result);
         break;
       case MIRType_Symbol:
-        masm.unboxSymbol(input, result);
+        masm.unboxSymbol(inputAddr, result);
         break;
       default:
         MOZ_CRASH("Given MIRType cannot be unboxed.");
     }
 }
 
 Register
 CodeGeneratorMIPS64::splitTagForTest(const ValueOperand& value)
@@ -279,27 +304,27 @@ CodeGeneratorMIPS64::visitCompareBitwise
 }
 
 void
 CodeGeneratorMIPS64::visitAsmSelectI64(LAsmSelectI64* lir)
 {
     MOZ_ASSERT(lir->mir()->type() == MIRType_Int64);
 
     Register cond = ToRegister(lir->condExpr());
-    Operand falseExpr = ToOperand(lir->falseExpr());
+    const LAllocation* falseExpr = lir->falseExpr();
 
     Register out = ToRegister(lir->output());
     MOZ_ASSERT(ToRegister(lir->trueExpr()) == out, "true expr is reused for input");
 
-    if (falseExpr.getTag() == Operand::REG) {
-        masm.as_movz(out, falseExpr.toReg(), cond);
+    if (falseExpr->isRegister()) {
+        masm.as_movz(out, ToRegister(falseExpr), cond);
     } else {
         Label done;
         masm.ma_b(cond, cond, &done, Assembler::NonZero, ShortJump);
-        masm.loadPtr(falseExpr.toAddress(), out);
+        masm.loadPtr(ToAddress(falseExpr), out);
         masm.bind(&done);
     }
 }
 
 void
 CodeGeneratorMIPS64::setReturnDoubleRegs(LiveRegisterSet* regs)
 {
     MOZ_ASSERT(ReturnFloat32Reg.reg_ == FloatRegisters::f0);
--- a/js/src/jit/mips64/MacroAssembler-mips64.cpp
+++ b/js/src/jit/mips64/MacroAssembler-mips64.cpp
@@ -1271,31 +1271,20 @@ MacroAssemblerMIPS64Compat::unboxNonDoub
 void
 MacroAssemblerMIPS64Compat::unboxInt32(const ValueOperand& operand, Register dest)
 {
     ma_dsll(dest, operand.valueReg(), Imm32(32));
     ma_dsra(dest, dest, Imm32(32));
 }
 
 void
-MacroAssemblerMIPS64Compat::unboxInt32(const Operand& operand, Register dest)
+MacroAssemblerMIPS64Compat::unboxInt32(Register src, Register dest)
 {
-    switch(operand.getTag()) {
-    case Operand::REG:
-        ma_dsll(dest, operand.toReg(), Imm32(32));
-        ma_dsra(dest, dest, Imm32(32));
-        break;
-    case Operand::MEM:
-        unboxInt32(operand.toAddress(), dest);
-        break;
-    case Operand::FREG:
-    default:
-        MOZ_CRASH("unexpected operand kind");
-        break;
-    }
+    ma_dsll(dest, src, Imm32(32));
+    ma_dsra(dest, dest, Imm32(32));
 }
 
 void
 MacroAssemblerMIPS64Compat::unboxInt32(const Address& src, Register dest)
 {
     load32(Address(src.base, src.offset), dest);
 }
 
@@ -1308,30 +1297,19 @@ MacroAssemblerMIPS64Compat::unboxInt32(c
 
 void
 MacroAssemblerMIPS64Compat::unboxBoolean(const ValueOperand& operand, Register dest)
 {
     ma_dext(dest, operand.valueReg(), Imm32(0), Imm32(32));
 }
 
 void
-MacroAssemblerMIPS64Compat::unboxBoolean(const Operand& operand, Register dest)
+MacroAssemblerMIPS64Compat::unboxBoolean(Register src, Register dest)
 {
-    switch(operand.getTag()) {
-    case Operand::REG:
-        ma_dext(dest, operand.toReg(), Imm32(0), Imm32(32));
-        break;
-    case Operand::MEM:
-        unboxBoolean(operand.toAddress(), dest);
-        break;
-    case Operand::FREG:
-    default:
-        MOZ_CRASH("unexpected operand kind");
-        break;
-    }
+    ma_dext(dest, src, Imm32(0), Imm32(32));
 }
 
 void
 MacroAssemblerMIPS64Compat::unboxBoolean(const Address& src, Register dest)
 {
     ma_load(dest, Address(src.base, src.offset), SizeWord, ZeroExtend);
 }
 
@@ -1356,82 +1334,49 @@ MacroAssemblerMIPS64Compat::unboxDouble(
 
 void
 MacroAssemblerMIPS64Compat::unboxString(const ValueOperand& operand, Register dest)
 {
     unboxNonDouble(operand, dest);
 }
 
 void
-MacroAssemblerMIPS64Compat::unboxString(const Operand& operand, Register dest)
+MacroAssemblerMIPS64Compat::unboxString(Register src, Register dest)
 {
-    switch(operand.getTag()) {
-    case Operand::REG:
-        ma_dext(dest, operand.toReg(), Imm32(0), Imm32(JSVAL_TAG_SHIFT));
-        break;
-    case Operand::MEM:
-        unboxNonDouble(operand.toAddress(), dest);
-        break;
-    case Operand::FREG:
-    default:
-        MOZ_CRASH("unexpected operand kind");
-        break;
-    }
+    ma_dext(dest, src, Imm32(0), Imm32(JSVAL_TAG_SHIFT));
 }
 
 void
 MacroAssemblerMIPS64Compat::unboxString(const Address& src, Register dest)
 {
     unboxNonDouble(src, dest);
 }
 
 void
-MacroAssemblerMIPS64Compat::unboxSymbol(const Operand& operand, Register dest)
+MacroAssemblerMIPS64Compat::unboxSymbol(Register src, Register dest)
 {
-    switch(operand.getTag()) {
-    case Operand::REG:
-        ma_dext(dest, operand.toReg(), Imm32(0), Imm32(JSVAL_TAG_SHIFT));
-        break;
-    case Operand::MEM:
-        unboxNonDouble(operand.toAddress(), dest);
-        break;
-    case Operand::FREG:
-    default:
-        MOZ_CRASH("unexpected operand kind");
-        break;
-    }
+    ma_dext(dest, src, Imm32(0), Imm32(JSVAL_TAG_SHIFT));
 }
 
 void
 MacroAssemblerMIPS64Compat::unboxSymbol(const Address& src, Register dest)
 {
     unboxNonDouble(src, dest);
 }
 
 void
 MacroAssemblerMIPS64Compat::unboxObject(const ValueOperand& src, Register dest)
 {
     unboxNonDouble(src, dest);
 }
 
 void
-MacroAssemblerMIPS64Compat::unboxObject(const Operand& src, Register dest)
+MacroAssemblerMIPS64Compat::unboxObject(Register src, Register dest)
 {
-    switch(src.getTag()) {
-    case Operand::REG:
-        ma_dext(dest, src.toReg(), Imm32(0), Imm32(JSVAL_TAG_SHIFT));
-        break;
-    case Operand::MEM:
-        unboxNonDouble(src.toAddress(), dest);
-        break;
-    case Operand::FREG:
-    default:
-        MOZ_CRASH("unexpected operand kind");
-        break;
-    }
+    ma_dext(dest, src, Imm32(0), Imm32(JSVAL_TAG_SHIFT));
 }
 
 void
 MacroAssemblerMIPS64Compat::unboxObject(const Address& src, Register dest)
 {
     unboxNonDouble(src, dest);
 }
 
--- a/js/src/jit/mips64/MacroAssembler-mips64.h
+++ b/js/src/jit/mips64/MacroAssembler-mips64.h
@@ -336,33 +336,34 @@ class MacroAssemblerMIPS64Compat : publi
         return SecondScratchReg;
     }
 
     // unboxing code
     void unboxNonDouble(const ValueOperand& operand, Register dest);
     void unboxNonDouble(const Address& src, Register dest);
     void unboxNonDouble(const BaseIndex& src, Register dest);
     void unboxInt32(const ValueOperand& operand, Register dest);
-    void unboxInt32(const Operand& operand, Register dest);
+    void unboxInt32(Register src, Register dest);
     void unboxInt32(const Address& src, Register dest);
     void unboxInt32(const BaseIndex& src, Register dest);
     void unboxBoolean(const ValueOperand& operand, Register dest);
-    void unboxBoolean(const Operand& operand, Register dest);
+    void unboxBoolean(Register src, Register dest);
     void unboxBoolean(const Address& src, Register dest);
     void unboxBoolean(const BaseIndex& src, Register dest);
     void unboxDouble(const ValueOperand& operand, FloatRegister dest);
+    void unboxDouble(Register src, Register dest);
     void unboxDouble(const Address& src, FloatRegister dest);
     void unboxString(const ValueOperand& operand, Register dest);
-    void unboxString(const Operand& operand, Register dest);
+    void unboxString(Register src, Register dest);
     void unboxString(const Address& src, Register dest);
     void unboxSymbol(const ValueOperand& src, Register dest);
-    void unboxSymbol(const Operand& src, Register dest);
+    void unboxSymbol(Register src, Register dest);
     void unboxSymbol(const Address& src, Register dest);
     void unboxObject(const ValueOperand& src, Register dest);
-    void unboxObject(const Operand& src, Register dest);
+    void unboxObject(Register src, Register dest);
     void unboxObject(const Address& src, Register dest);
     void unboxObject(const BaseIndex& src, Register dest) { unboxNonDouble(src, dest); }
     void unboxValue(const ValueOperand& src, AnyRegister dest);
     void unboxPrivate(const ValueOperand& src, Register dest);
 
     void notBoolean(const ValueOperand& val) {
         as_xori(val.valueReg(), val.valueReg(), 1);
     }
--- a/js/src/jit/shared/CodeGenerator-shared-inl.h
+++ b/js/src/jit/shared/CodeGenerator-shared-inl.h
@@ -239,36 +239,27 @@ CodeGeneratorShared::ToStackOffset(LAllo
 }
 
 int32_t
 CodeGeneratorShared::ToStackOffset(const LAllocation* a) const
 {
     return ToStackOffset(*a);
 }
 
-Operand
-CodeGeneratorShared::ToOperand(const LAllocation& a)
+Address
+CodeGeneratorShared::ToAddress(const LAllocation& a)
 {
-    if (a.isGeneralReg())
-        return Operand(a.toGeneralReg()->reg());
-    if (a.isFloatReg())
-        return Operand(a.toFloatReg()->reg());
-    return Operand(masm.getStackPointer(), ToStackOffset(&a));
+    MOZ_ASSERT(a.isMemory());
+    return Address(masm.getStackPointer(), ToStackOffset(&a));
 }
 
-Operand
-CodeGeneratorShared::ToOperand(const LAllocation* a)
+Address
+CodeGeneratorShared::ToAddress(const LAllocation* a)
 {
-    return ToOperand(*a);
-}
-
-Operand
-CodeGeneratorShared::ToOperand(const LDefinition* def)
-{
-    return ToOperand(def->output());
+    return ToAddress(*a);
 }
 
 void
 CodeGeneratorShared::saveLive(LInstruction* ins)
 {
     MOZ_ASSERT(!ins->isCall());
     LSafepoint* safepoint = ins->safepoint();
     masm.PushRegsInMask(safepoint->liveRegs());
--- a/js/src/jit/shared/CodeGenerator-shared.h
+++ b/js/src/jit/shared/CodeGenerator-shared.h
@@ -205,24 +205,23 @@ class CodeGeneratorShared : public LElem
     inline int32_t StackOffsetToSlot(int32_t offset) const;
 
     // For argument construction for calls. Argslots are Value-sized.
     inline int32_t StackOffsetOfPassedArg(int32_t slot) const;
 
     inline int32_t ToStackOffset(LAllocation a) const;
     inline int32_t ToStackOffset(const LAllocation* a) const;
 
+    inline Address ToAddress(const LAllocation& a);
+    inline Address ToAddress(const LAllocation* a);
+
     uint32_t frameSize() const {
         return frameClass_ == FrameSizeClass::None() ? frameDepth_ : frameClass_.frameSize();
     }
 
-    inline Operand ToOperand(const LAllocation& a);
-    inline Operand ToOperand(const LAllocation* a);
-    inline Operand ToOperand(const LDefinition* def);
-
   protected:
 #ifdef CHECK_OSIPOINT_REGISTERS
     void resetOsiPointRegs(LSafepoint* safepoint);
     bool shouldVerifyOsiPointRegs(LSafepoint* safepoint);
     void verifyOsiPointRegs(LSafepoint* safepoint);
 #endif
 
     bool addNativeToBytecodeEntry(const BytecodeSite* site);
--- a/js/src/jit/x86-shared/CodeGenerator-x86-shared.cpp
+++ b/js/src/jit/x86-shared/CodeGenerator-x86-shared.cpp
@@ -1732,16 +1732,38 @@ CodeGeneratorX86Shared::visitUrshD(LUrsh
     } else {
         MOZ_ASSERT(ToRegister(rhs) == ecx);
         masm.shrl_cl(lhs);
     }
 
     masm.convertUInt32ToDouble(lhs, out);
 }
 
+Operand
+CodeGeneratorX86Shared::ToOperand(const LAllocation& a)
+{
+    if (a.isGeneralReg())
+        return Operand(a.toGeneralReg()->reg());
+    if (a.isFloatReg())
+        return Operand(a.toFloatReg()->reg());
+    return Operand(masm.getStackPointer(), ToStackOffset(&a));
+}
+
+Operand
+CodeGeneratorX86Shared::ToOperand(const LAllocation* a)
+{
+    return ToOperand(*a);
+}
+
+Operand
+CodeGeneratorX86Shared::ToOperand(const LDefinition* def)
+{
+    return ToOperand(def->output());
+}
+
 MoveOperand
 CodeGeneratorX86Shared::toMoveOperand(LAllocation a) const
 {
     if (a.isGeneralReg())
         return MoveOperand(ToRegister(a));
     if (a.isFloatReg())
         return MoveOperand(ToFloatRegister(a));
     return MoveOperand(StackPointer, ToStackOffset(a));
--- a/js/src/jit/x86-shared/CodeGenerator-x86-shared.h
+++ b/js/src/jit/x86-shared/CodeGenerator-x86-shared.h
@@ -111,16 +111,20 @@ class CodeGeneratorX86Shared : public Co
     MOZ_WARN_UNUSED_RESULT uint32_t
     maybeEmitAsmJSStoreBoundsCheck(const MAsmJSStoreHeap* mir, LAsmJSStoreHeap* ins,
                                    Label** rejoin);
 
     void cleanupAfterAsmJSBoundsCheckBranch(const MAsmJSHeapAccess* mir, Register ptr);
 
     NonAssertingLabel deoptLabel_;
 
+    Operand ToOperand(const LAllocation& a);
+    Operand ToOperand(const LAllocation* a);
+    Operand ToOperand(const LDefinition* def);
+
     MoveOperand toMoveOperand(LAllocation a) const;
 
     void bailoutIf(Assembler::Condition condition, LSnapshot* snapshot);
     void bailoutIf(Assembler::DoubleCondition condition, LSnapshot* snapshot);
     void bailoutFrom(Label* label, LSnapshot* snapshot);
     void bailout(LSnapshot* snapshot);
 
     template <typename T1, typename T2>
--- a/js/src/jit/x86-shared/MacroAssembler-x86-shared.h
+++ b/js/src/jit/x86-shared/MacroAssembler-x86-shared.h
@@ -147,16 +147,22 @@ class MacroAssemblerX86Shared : public A
         testl(rhs, lhs);
     }
     void cmp32(Register lhs, Imm32 rhs) {
         cmpl(rhs, lhs);
     }
     void cmp32(Register lhs, Register rhs) {
         cmpl(rhs, lhs);
     }
+    void cmp32(const Address& lhs, Register rhs) {
+        cmp32(Operand(lhs), rhs);
+    }
+    void cmp32(const Address& lhs, Imm32 rhs) {
+        cmp32(Operand(lhs), rhs);
+    }
     void cmp32(const Operand& lhs, Imm32 rhs) {
         cmpl(rhs, lhs);
     }
     void cmp32(const Operand& lhs, Register rhs) {
         cmpl(rhs, lhs);
     }
     void cmp32(Register lhs, const Operand& rhs) {
         cmpl(rhs, lhs);
--- a/js/src/vm/Interpreter.cpp
+++ b/js/src/vm/Interpreter.cpp
@@ -577,18 +577,18 @@ StackCheckIsConstructorCalleeNewTarget(J
 
     // The new.target has already been vetted by previous calls, or is the callee.
     // We can just assert that it's a constructor.
     MOZ_ASSERT(IsConstructor(newTarget));
 
     return true;
 }
 
-static bool
-ConstructFromStack(JSContext* cx, const CallArgs& args)
+bool
+js::ConstructFromStack(JSContext* cx, const CallArgs& args)
 {
     if (!StackCheckIsConstructorCalleeNewTarget(cx, args.calleev(), args.newTarget()))
         return false;
 
     args.setThis(MagicValue(JS_IS_CONSTRUCTING));
     return InternalConstruct(cx, static_cast<const AnyConstructArgs&>(args));
 }
 
--- a/js/src/vm/Interpreter.h
+++ b/js/src/vm/Interpreter.h
@@ -84,16 +84,28 @@ InvokeSetter(JSContext* cx, const Value&
 // callee as |newTarget| when a different value should have been passed.
 //
 // NOTE: As with the ES6 spec operation, it's the caller's responsibility to
 //       ensure |fval| and |newTarget| are both |IsConstructor|.
 extern bool
 Construct(JSContext* cx, HandleValue fval, const AnyConstructArgs& args, HandleValue newTarget,
           MutableHandleObject objp);
 
+// Check that in the given |args|, which must be |args.isConstructing()|, that
+// |IsConstructor(args.callee())|. If this is not the case, throw a TypeError.
+// Otherwise, the user must ensure that, additionally, |IsConstructor(args.newTarget())|.
+// (If |args| comes directly from the interpreter stack, as set up by JSOP_NEW,
+// this comes for free.) Then perform a Construct() operation using |args|.
+//
+// This internal operation is intended only for use with arguments known to be
+// on the JS stack, or at least in carefully-rooted memory. The vast majority of
+// potential users should instead use ConstructArgs in concert with Construct().
+extern bool
+ConstructFromStack(JSContext* cx, const CallArgs& args);
+
 // Call Construct(fval, args, newTarget), but use the given |thisv| as |this|
 // during construction of |fval|.
 //
 // This method exists only for very rare cases where a |this| was created
 // caller-side for construction of |fval|: basically only for JITs using
 // |CreateThis|.  If that's not you, use Construct()!
 extern bool
 InternalConstructWithProvidedThis(JSContext* cx, HandleValue fval, HandleValue thisv,
--- a/layout/base/TouchManager.cpp
+++ b/layout/base/TouchManager.cpp
@@ -61,17 +61,17 @@ EvictTouchPoint(RefPtr<dom::Touch>& aTou
       if (presShell) {
         nsIFrame* frame = presShell->GetRootFrame();
         if (frame) {
           nsPoint pt(aTouch->mRefPoint.x, aTouch->mRefPoint.y);
           nsCOMPtr<nsIWidget> widget = frame->GetView()->GetNearestWidget(&pt);
           if (widget) {
             WidgetTouchEvent event(true, eTouchEnd, widget);
             event.widget = widget;
-            event.time = PR_IntervalNow();
+            event.mTime = PR_IntervalNow();
             event.touches.AppendElement(aTouch);
             nsEventStatus status;
             widget->DispatchEvent(&event, status);
             return;
           }
         }
       }
     }
--- a/layout/base/nsPresContext.cpp
+++ b/layout/base/nsPresContext.cpp
@@ -328,18 +328,20 @@ nsPresContext::Destroy()
                                   "nglayout.debug.paint_flashing",
                                   this);
   Preferences::UnregisterCallback(nsPresContext::PrefChangedCallback,
                                   "nglayout.debug.paint_flashing_chrome",
                                   this);
 
   // Disconnect the refresh driver *after* the transition manager, which
   // needs it.
-  if (mRefreshDriver && mRefreshDriver->PresContext() == this) {
-    mRefreshDriver->Disconnect();
+  if (mRefreshDriver) {
+    if (mRefreshDriver->PresContext() == this) {
+      mRefreshDriver->Disconnect();
+    }
     mRefreshDriver = nullptr;
   }
 }
 
 nsPresContext::~nsPresContext()
 {
   NS_PRECONDITION(!mShell, "Presshell forgot to clear our mShell pointer");
   SetShell(nullptr);
--- a/layout/base/nsPresShell.cpp
+++ b/layout/base/nsPresShell.cpp
@@ -5505,17 +5505,17 @@ PresShell::ProcessSynthMouseMoveEvent(bo
     refpoint = mMouseLocation.ScaleToOtherAppUnits(APD, viewAPD);
     refpoint -= view->GetOffsetTo(rootView);
     refpoint += view->ViewToWidgetOffset();
   }
   NS_ASSERTION(view->GetWidget(), "view should have a widget here");
   WidgetMouseEvent event(true, eMouseMove, view->GetWidget(),
                          WidgetMouseEvent::eSynthesized);
   event.refPoint = LayoutDeviceIntPoint::FromAppUnitsToNearest(refpoint, viewAPD);
-  event.time = PR_IntervalNow();
+  event.mTime = PR_IntervalNow();
   // XXX set event.modifiers ?
   // XXX mnakano I think that we should get the latest information from widget.
 
   nsCOMPtr<nsIPresShell> shell = pointVM->GetPresShell();
   if (shell) {
     // Since this gets run in a refresh tick there isn't an InputAPZContext on
     // the stack from the nsBaseWidget. We need to simulate one with at least
     // the correct target guid, so that the correct callback transform gets
@@ -6879,18 +6879,18 @@ DispatchPointerFromMouseOrTouch(PresShel
       event.isPrimary = i == 0;
       event.pointerId = touch->Identifier();
       event.refPoint = touch->mRefPoint;
       event.modifiers = touchEvent->modifiers;
       event.width = touch->RadiusX();
       event.height = touch->RadiusY();
       event.tiltX = touch->tiltX;
       event.tiltY = touch->tiltY;
-      event.time = touchEvent->time;
-      event.timeStamp = touchEvent->timeStamp;
+      event.mTime = touchEvent->mTime;
+      event.mTimeStamp = touchEvent->mTimeStamp;
       event.mFlags = touchEvent->mFlags;
       event.button = WidgetMouseEvent::eLeftButton;
       event.buttons = WidgetMouseEvent::eLeftButtonFlag;
       event.inputSource = nsIDOMMouseEvent::MOZ_SOURCE_TOUCH;
       event.convertToPointer = touch->convertToPointer = false;
       aShell->HandleEvent(aFrame, &event, aDontRetargetEvents, aStatus, aTargetContent);
     }
   }
@@ -8139,19 +8139,19 @@ PresShell::HandleEventInternal(WidgetEve
       nsIPresShell::AllowMouseCapture(false);
       break;
     default:
       break;
     }
   }
 
   if (Telemetry::CanRecordBase() &&
-      !aEvent->timeStamp.IsNull() &&
+      !aEvent->mTimeStamp.IsNull() &&
       aEvent->AsInputEvent()) {
-    double millis = (TimeStamp::Now() - aEvent->timeStamp).ToMilliseconds();
+    double millis = (TimeStamp::Now() - aEvent->mTimeStamp).ToMilliseconds();
     Telemetry::Accumulate(Telemetry::INPUT_EVENT_RESPONSE_MS, millis);
   }
 
   return rv;
 }
 
 void
 nsIPresShell::DispatchGotOrLostPointerCaptureEvent(bool aIsGotCapture,
--- a/layout/forms/nsListControlFrame.cpp
+++ b/layout/forms/nsListControlFrame.cpp
@@ -2298,32 +2298,32 @@ nsListControlFrame::KeyPress(nsIDOMEvent
 
   // XXX Why don't we check/modify timestamp first?
 
   // Incremental Search: if time elapsed is below
   // INCREMENTAL_SEARCH_KEYPRESS_TIME, append this keystroke to the search
   // string we will use to find options and start searching at the current
   // keystroke.  Otherwise, Truncate the string if it's been a long time
   // since our last keypress.
-  if (keyEvent->time - gLastKeyTime > INCREMENTAL_SEARCH_KEYPRESS_TIME) {
+  if (keyEvent->mTime - gLastKeyTime > INCREMENTAL_SEARCH_KEYPRESS_TIME) {
     // If this is ' ' and we are at the beginning of the string, treat it as
     // "select this option" (bug 191543)
     if (keyEvent->charCode == ' ') {
       // Actually process the new index and let the selection code
       // do the scrolling for us
       PostHandleKeyEvent(mEndSelectionIndex, keyEvent->charCode,
                          keyEvent->IsShift(), isControlOrMeta);
 
       return NS_OK;
     }
 
     GetIncrementalString().Truncate();
   }
 
-  gLastKeyTime = keyEvent->time;
+  gLastKeyTime = keyEvent->mTime;
 
   // Append this keystroke to the search string. 
   char16_t uniChar = ToLowerCase(static_cast<char16_t>(keyEvent->charCode));
   GetIncrementalString().Append(uniChar);
 
   // See bug 188199, if all letters in incremental string are same, just try to
   // match the first one
   nsAutoString incrementalString(GetIncrementalString());
--- a/layout/style/CSSStyleSheet.h
+++ b/layout/style/CSSStyleSheet.h
@@ -220,16 +220,19 @@ public:
    */
   nsresult InsertRuleInternal(const nsAString& aRule,
                               uint32_t aIndex, uint32_t* aReturn);
 
   /* Get the URI this sheet was originally loaded from, if any.  Can
      return null */
   nsIURI* GetOriginalURI() const { return mInner->mOriginalSheetURI; }
 
+  // Whether the sheet is for an inline <style> element.
+  bool IsInline() const { return mInner->IsInline(); }
+
   // nsICSSLoaderObserver interface
   NS_IMETHOD StyleSheetLoaded(StyleSheetHandle aSheet, bool aWasAlternate,
                               nsresult aStatus) override;
 
   void EnsureUniqueInner();
 
   // Append all of this sheet's child sheets to aArray.
   void AppendAllChildSheets(nsTArray<CSSStyleSheet*>& aArray);
--- a/layout/style/StyleSheetHandle.h
+++ b/layout/style/StyleSheetHandle.h
@@ -99,16 +99,17 @@ public:
     inline MozExternalRefCountType AddRef();
     inline MozExternalRefCountType Release();
 
     // Style sheet interface.  These inline methods are defined in
     // StyleSheetHandleInlines.h and just forward to the underlying
     // CSSStyleSheet or ServoStyleSheet.  See corresponding comments in
     // CSSStyleSheet.h for descriptions of these methods.
 
+    inline bool IsInline() const;
     inline nsIURI* GetSheetURI() const;
     inline nsIURI* GetOriginalURI() const;
     inline nsIURI* GetBaseURI() const;
     inline void SetURIs(nsIURI* aSheetURI, nsIURI* aOriginalSheetURI, nsIURI* aBaseURI);
     inline bool IsApplicable() const;
     inline void SetParsingMode(css::SheetParsingMode aMode);
     inline bool HasRules() const;
     inline nsIDocument* GetOwningDocument() const;
--- a/layout/style/StyleSheetHandleInlines.h
+++ b/layout/style/StyleSheetHandleInlines.h
@@ -43,16 +43,22 @@ StyleSheetHandle::Ptr::AddRef()
 }
 
 MozExternalRefCountType
 StyleSheetHandle::Ptr::Release()
 {
   FORWARD(Release, ());
 }
 
+bool
+StyleSheetHandle::Ptr::IsInline() const
+{
+  FORWARD(IsInline, ());
+}
+
 nsIURI*
 StyleSheetHandle::Ptr::GetSheetURI() const
 {
   FORWARD(GetSheetURI, ());
 }
 
 nsIURI*
 StyleSheetHandle::Ptr::GetOriginalURI() const
--- a/layout/style/StyleSheetInfo.h
+++ b/layout/style/StyleSheetInfo.h
@@ -35,16 +35,19 @@ public:
                  const dom::SRIMetadata& aIntegrity);
   StyleSheetInfo(const StyleSheetInfo& aCopy);
 
   nsIURI* GetSheetURI() const { return mSheetURI; }
   nsIURI* GetOriginalURI() const { return mOriginalSheetURI; }
   nsIURI* GetBaseURI() const { return mBaseURI; }
   void SetURIs(nsIURI* aSheetURI, nsIURI* aOriginalSheetURI, nsIURI* aBaseURI);
 
+  // Whether the sheet is for an inline <style> element.
+  bool IsInline() const { return !mOriginalSheetURI; }
+
   nsIPrincipal* Principal() const { return mPrincipal; }
   void SetPrincipal(nsIPrincipal* aPrincipal);
 
   CORSMode GetCORSMode() const { return mCORSMode; }
   net::ReferrerPolicy GetReferrerPolicy() const { return mReferrerPolicy; }
   void GetIntegrity(dom::SRIMetadata& aResult) const { aResult = mIntegrity; }
 
 protected:
--- a/layout/style/nsAnimationManager.h
+++ b/layout/style/nsAnimationManager.h
@@ -41,19 +41,19 @@ struct AnimationEventInfo {
                      const TimeStamp& aTimeStamp,
                      dom::Animation* aAnimation)
     : mElement(aElement)
     , mAnimation(aAnimation)
     , mEvent(true, aMessage)
     , mTimeStamp(aTimeStamp)
   {
     // XXX Looks like nobody initialize WidgetEvent::time
-    mEvent.animationName = aAnimationName;
-    mEvent.elapsedTime = aElapsedTime.ToSeconds();
-    mEvent.pseudoElement =
+    mEvent.mAnimationName = aAnimationName;
+    mEvent.mElapsedTime = aElapsedTime.ToSeconds();
+    mEvent.mPseudoElement =
       AnimationCollection<dom::CSSAnimation>::PseudoTypeAsString(aPseudoType);
   }
 
   // InternalAnimationEvent doesn't support copy-construction, so we need
   // to ourselves in order to work with nsTArray
   AnimationEventInfo(const AnimationEventInfo& aOther)
     : mElement(aOther.mElement)
     , mAnimation(aOther.mAnimation)
--- a/layout/style/nsTransitionManager.h
+++ b/layout/style/nsTransitionManager.h
@@ -245,20 +245,20 @@ struct TransitionEventInfo {
                       const TimeStamp& aTimeStamp,
                       dom::Animation* aAnimation)
     : mElement(aElement)
     , mAnimation(aAnimation)
     , mEvent(true, eTransitionEnd)
     , mTimeStamp(aTimeStamp)
   {
     // XXX Looks like nobody initialize WidgetEvent::time
-    mEvent.propertyName =
+    mEvent.mPropertyName =
       NS_ConvertUTF8toUTF16(nsCSSProps::GetStringValue(aProperty));
-    mEvent.elapsedTime = aDuration.ToSeconds();
-    mEvent.pseudoElement =
+    mEvent.mElapsedTime = aDuration.ToSeconds();
+    mEvent.mPseudoElement =
       AnimationCollection<dom::CSSTransition>::PseudoTypeAsString(aPseudoType);
   }
 
   // InternalTransitionEvent doesn't support copy-construction, so we need
   // to ourselves in order to work with nsTArray
   TransitionEventInfo(const TransitionEventInfo& aOther)
     : mElement(aOther.mElement)
     , mAnimation(aOther.mAnimation)
new file mode 100644
--- /dev/null
+++ b/layout/style/test/file_animations_effect_timing_iterations.html
@@ -0,0 +1,73 @@
+<!DOCTYPE html>
+<html>
+<head>
+  <script type="application/javascript"
+    src="/tests/SimpleTest/paint_listener.js"></script>
+  <script type="application/javascript" src="animation_utils.js"></script>
+  <style type="text/css">
+    @keyframes anim {
+      0% { transform: translate(0px) }
+      100% { transform: translate(100px) }
+    }
+    .target {
+      /* The animation target needs geometry in order to qualify for OMTA */
+      width: 100px;
+      height: 100px;
+      background-color: white;
+    }
+  </style>
+  <script>
+    var ok = opener.ok.bind(opener);
+    var is = opener.is.bind(opener);
+    var todo = opener.todo.bind(opener);
+    function finish() {
+      var o = opener;
+      self.close();
+      o.SimpleTest.finish();
+    }
+  </script>
+</head>
+<body>
+<div id="display"></div>
+<script type="application/javascript">
+"use strict";
+
+runOMTATest(function() {
+  runAllAsyncAnimTests().then(function() {
+    finish();
+  });
+}, finish, opener.SpecialPowers);
+
+addAsyncAnimTest(function *() {
+  var [ div ] = new_div("");
+  var animation = div.animate(
+    [ { transform: 'translate(0px)' },
+      { transform: 'translate(100px)' } ],
+      { duration: 4000,
+        iterations: 2
+      });
+  yield waitForPaints();
+
+  advance_clock(6000);
+  omta_is(div, "transform", { tx: 50 }, RunningOn.Compositor,
+          "Animation is running on compositor");
+  animation.effect.timing.iterations = 1;
+  advance_clock(0);
+
+  yield waitForPaints();
+  omta_is(div, "transform", { tx: 0 }, RunningOn.MainThread,
+          "Animation is on MainThread");
+
+  animation.effect.timing.iterations = 3;
+
+  advance_clock(0);
+  yield waitForPaints();
+  omta_is(div, "transform", { tx: 50 }, RunningOn.Compositor,
+          "Animation is running again on compositor");
+
+  done_div();
+});
+
+</script>
+</body>
+</html>
--- a/layout/style/test/mochitest.ini
+++ b/layout/style/test/mochitest.ini
@@ -44,16 +44,18 @@ support-files = additional_sheets_helper
 skip-if = toolkit == 'android'
 [test_animations_async_tests.html]
 support-files = ../../reftests/fonts/Ahem.ttf file_animations_async_tests.html
 [test_animations_dynamic_changes.html]
 [test_animations_effect_timing_duration.html]
 support-files = file_animations_effect_timing_duration.html
 [test_animations_effect_timing_enddelay.html]
 support-files = file_animations_effect_timing_enddelay.html
+[test_animations_effect_timing_iterations.html]
+support-files = file_animations_effect_timing_iterations.html
 [test_animations_event_order.html]
 [test_animations_iterationstart.html]
 support-files = file_animations_iterationstart.html
 [test_animations_omta.html]
 [test_animations_omta_start.html]
 skip-if = (buildapp == 'b2g' && toolkit != 'gonk') # bug 1041017
 [test_animations_pausing.html]
 support-files = file_animations_pausing.html
new file mode 100644
--- /dev/null
+++ b/layout/style/test/test_animations_effect_timing_iterations.html
@@ -0,0 +1,24 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <title>Test for animation.effect.timing.iterations on compositor</title>
+  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<div id="display"></div>
+<pre id="test">
+<script type="application/javascript">
+"use strict";
+
+SimpleTest.waitForExplicitFinish();
+
+SpecialPowers.pushPrefEnv(
+  { "set": [[ "dom.animations-api.core.enabled", true]] },
+  function() {
+    window.open("file_animations_effect_timing_iterations.html");
+  });
+</script>
+</pre>
+</body>
+</html>
--- a/layout/svg/nsSVGImageFrame.cpp
+++ b/layout/svg/nsSVGImageFrame.cpp
@@ -152,32 +152,42 @@ nsSVGImageFrame::Init(nsIContent*       
                       nsContainerFrame* aParent,
                       nsIFrame*         aPrevInFlow)
 {
   NS_ASSERTION(aContent->IsSVGElement(nsGkAtoms::image),
                "Content is not an SVG image!");
 
   nsSVGImageFrameBase::Init(aContent, aParent, aPrevInFlow);
 
+  if (GetStateBits() & NS_FRAME_IS_NONDISPLAY) {
+    // Non-display frames are likely to be patterns, masks or the like.
+    // Treat them as always visible.
+    IncApproximateVisibleCount();
+  }
+
   mListener = new nsSVGImageListener(this);
   nsCOMPtr<nsIImageLoadingContent> imageLoader = do_QueryInterface(mContent);
   if (!imageLoader) {
     NS_RUNTIMEABORT("Why is this not an image loading content?");
   }
 
   // We should have a PresContext now, so let's notify our image loader that
   // we need to register any image animations with the refresh driver.
   imageLoader->FrameCreated(this);
 
   imageLoader->AddObserver(mListener);
 }
 
 /* virtual */ void
 nsSVGImageFrame::DestroyFrom(nsIFrame* aDestructRoot)
 {
+  if (GetStateBits() & NS_FRAME_IS_NONDISPLAY) {
+    DecApproximateVisibleCount();
+  }
+
   if (mReflowCallbackPosted) {
     PresContext()->PresShell()->CancelReflowCallback(this);
     mReflowCallbackPosted = false;
   }
 
   nsCOMPtr<nsIImageLoadingContent> imageLoader =
     do_QueryInterface(nsFrame::mContent);
 
--- a/media/libcubeb/README_MOZILLA
+++ b/media/libcubeb/README_MOZILLA
@@ -1,8 +1,8 @@
 The source from this directory was copied from the cubeb 
 git repository using the update.sh script.  The only changes
 made were those applied by update.sh and the addition of
 Makefile.in build files for the Mozilla build system.
 
 The cubeb git repository is: git://github.com/kinetiknz/cubeb.git
 
-The git commit ID used was c438f775a69cdc8ba6d7d543073c3ccf982050b8.
+The git commit ID used was f3470c48b362c9f74c1aeb55a6ed440d0509dfa1.
--- a/media/libcubeb/include/cubeb.h
+++ b/media/libcubeb/include/cubeb.h
@@ -2,17 +2,17 @@
  * Copyright © 2011 Mozilla Foundation
  *
  * This program is made available under an ISC-style license.  See the
  * accompanying file LICENSE for details.
  */
 #if !defined(CUBEB_c2f983e9_c96f_e71c_72c3_bbf62992a382)
 #define CUBEB_c2f983e9_c96f_e71c_72c3_bbf62992a382
 
-#include <cubeb/cubeb-stdint.h>
+#include <stdint.h>
 
 #if defined(__cplusplus)
 extern "C" {
 #endif
 
 /** @mainpage
 
     @section intro Introduction
--- a/media/libcubeb/include/moz.build
+++ b/media/libcubeb/include/moz.build
@@ -1,11 +1,10 @@
 # -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
 # vim: set filetype=python:
 # 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/.
 
 EXPORTS.cubeb += [
-    'cubeb-stdint.h',
     'cubeb.h',
 ]
 
--- a/media/libcubeb/src/android/audiotrack_definitions.h
+++ b/media/libcubeb/src/android/audiotrack_definitions.h
@@ -9,17 +9,17 @@
  *
  * Unless required by applicable law or agreed to in writing, software
  * distributed under the License is distributed on an "AS IS" BASIS,
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
 
-#include <cubeb/cubeb-stdint.h>
+#include <stdint.h>
 
 /*
  * The following definitions are copied from the android sources. Only the
  * relevant enum member and values needed are copied.
  */
 
 /*
  * From https://android.googlesource.com/platform/frameworks/base/+/android-2.2.3_r2.1/include/utils/Errors.h
--- a/media/libcubeb/src/cubeb.c
+++ b/media/libcubeb/src/cubeb.c
@@ -420,16 +420,20 @@ int cubeb_device_collection_destroy(cube
     cubeb_device_info_destroy(collection->device[i]);
 
   free(collection);
   return CUBEB_OK;
 }
 
 int cubeb_device_info_destroy(cubeb_device_info * info)
 {
+  if (info == NULL) {
+    return CUBEB_ERROR_INVALID_PARAMETER;
+  }
+
   free(info->device_id);
   free(info->friendly_name);
   free(info->group_id);
   free(info->vendor_name);
 
   free(info);
   return CUBEB_OK;
 }
--- a/media/libcubeb/src/cubeb_audiounit.c
+++ b/media/libcubeb/src/cubeb_audiounit.c
@@ -11,64 +11,111 @@
 #include <mach/mach_time.h>
 #include <pthread.h>
 #include <stdlib.h>
 #include <AudioUnit/AudioUnit.h>
 #if !TARGET_OS_IPHONE
 #include <CoreAudio/AudioHardware.h>
 #include <CoreAudio/HostTime.h>
 #include <CoreFoundation/CoreFoundation.h>
-#else
+#endif
 #include <CoreAudio/CoreAudioTypes.h>
 #include <AudioToolbox/AudioToolbox.h>
-#endif
 #include "cubeb/cubeb.h"
 #include "cubeb-internal.h"
 #include "cubeb_panner.h"
 #if !TARGET_OS_IPHONE
 #include "cubeb_osx_run_loop.h"
 #endif
+#include "cubeb_resampler.h"
+#include "cubeb_ring_array.h"
 
 #if !defined(kCFCoreFoundationVersionNumber10_7)
 /* From CoreFoundation CFBase.h */
 #define kCFCoreFoundationVersionNumber10_7 635.00
 #endif
 
 #if !TARGET_OS_IPHONE && MAC_OS_X_VERSION_MIN_REQUIRED < 1060
-#define MACOSX_LESS_THAN_106
+#define AudioComponent Component
+#define AudioComponentDescription ComponentDescription
+#define AudioComponentFindNext FindNextComponent
+#define AudioComponentInstanceNew OpenAComponent
+#define AudioComponentInstanceDispose CloseComponent
+#endif
+
+#if MAC_OS_X_VERSION_MIN_REQUIRED < 1080
+typedef UInt32  AudioFormatFlags;
 #endif
 
 #define CUBEB_STREAM_MAX 8
-#define NBUFS 4
+
+#define AU_OUT_BUS    0
+#define AU_IN_BUS     1
+
+#if TARGET_OS_IPHONE
+#define CUBEB_AUDIOUNIT_SUBTYPE kAudioUnitSubType_RemoteIO
+#else
+#define CUBEB_AUDIOUNIT_SUBTYPE kAudioUnitSubType_HALOutput
+#endif
+
+//#define LOGGING_ENABLED
+#ifdef LOGGING_ENABLED
+#define LOG(...) do {                           \
+    fprintf(stderr, __VA_ARGS__);               \
+  } while(0)
+#else
+#define LOG(...)
+#endif
 
 static struct cubeb_ops const audiounit_ops;
 
 struct cubeb {
   struct cubeb_ops const * ops;
   pthread_mutex_t mutex;
   int active_streams;
   int limit_streams;
+  cubeb_device_collection_changed_callback collection_changed_callback;
+  void * collection_changed_user_ptr;
+  /* Differentiate input from output devices. */
+  cubeb_device_type collection_changed_devtype;
+  uint32_t devtype_device_count;
+  AudioObjectID * devtype_device_array;
 };
 
 struct cubeb_stream {
   cubeb * context;
-  AudioUnit unit;
   cubeb_data_callback data_callback;
   cubeb_state_callback state_callback;
   cubeb_device_changed_callback device_changed_callback;
+  /* User pointer of data_callback */
   void * user_ptr;
-  AudioStreamBasicDescription sample_spec;
+  /* Format descriptions */
+  AudioStreamBasicDescription input_desc;
+  AudioStreamBasicDescription output_desc;
+  /* I/O AudioUnits */
+  AudioUnit input_unit;
+  AudioUnit output_unit;
+  /* Sample rate of input device*/
+  Float64 input_hw_rate;
   pthread_mutex_t mutex;
+  /* Hold the input samples in every
+   * input callback iteration */
+  ring_array input_buffer_array;
+  /* Frames on input buffer */
+  uint32_t input_buffer_frames;
+  /* Frame counters */
   uint64_t frames_played;
   uint64_t frames_queued;
+  uint64_t frames_read;
   int shutdown;
   int draining;
   uint64_t current_latency_frames;
   uint64_t hw_latency_frames;
   float panning;
+  cubeb_resampler * resampler;
 };
 
 #if TARGET_OS_IPHONE
 typedef UInt32 AudioDeviceID;
 typedef UInt32 AudioObjectID;
 
 #define AudioGetCurrentHostTime mach_absolute_time
 
@@ -96,80 +143,218 @@ audiotimestamp_to_latency(AudioTimeStamp
 {
   if (!(tstamp->mFlags & kAudioTimeStampHostTimeValid)) {
     return 0;
   }
 
   uint64_t pres = AudioConvertHostTimeToNanos(tstamp->mHostTime);
   uint64_t now = AudioConvertHostTimeToNanos(AudioGetCurrentHostTime());
 
-  return ((pres - now) * stream->sample_spec.mSampleRate) / 1000000000LL;
+  return ((pres - now) * stream->output_desc.mSampleRate) / 1000000000LL;
 }
 
 static OSStatus
-audiounit_output_callback(void * user_ptr, AudioUnitRenderActionFlags * flags,
-                          AudioTimeStamp const * tstamp, UInt32 bus, UInt32 nframes,
-                          AudioBufferList * bufs)
+audiounit_input_callback(void * user_ptr,
+                         AudioUnitRenderActionFlags * flags,
+                         AudioTimeStamp const * tstamp,
+                         UInt32 bus,
+                         UInt32 input_frames,
+                         AudioBufferList * bufs)
 {
-  cubeb_stream * stm;
-  unsigned char * buf;
-  long got;
-  OSStatus r;
-  float panning;
-
-  assert(bufs->mNumberBuffers == 1);
-  buf = bufs->mBuffers[0].mData;
-
-  stm = user_ptr;
+  cubeb_stream * stm = user_ptr;
+  long outframes, frames;
 
   pthread_mutex_lock(&stm->mutex);
 
-  stm->current_latency_frames = audiotimestamp_to_latency(tstamp, stm);
-  panning = stm->panning;
+  assert(stm->input_unit != NULL);
+  assert(AU_IN_BUS == bus);
 
-  if (stm->draining || stm->shutdown) {
+  if (stm->shutdown) {
     pthread_mutex_unlock(&stm->mutex);
-    if (stm->draining) {
-      r = AudioOutputUnitStop(stm->unit);
-      assert(r == 0);
-      stm->state_callback(stm, stm->user_ptr, CUBEB_STATE_DRAINED);
-    }
+    return noErr;
+  }
+
+  /* Get next store buffer from ring array */
+  AudioBuffer * input_buffer = ring_array_get_free_buffer(&stm->input_buffer_array);
+  if (input_buffer == NULL) {
+    LOG("input: Ring array is full drop one buffer\n");
+    ring_array_get_data_buffer(&stm->input_buffer_array);
+
+    input_buffer = ring_array_get_free_buffer(&stm->input_buffer_array);
+    assert(input_buffer);
+  }
+
+  AudioBufferList input_buffer_list;
+  input_buffer_list.mBuffers[0] = *input_buffer;
+  input_buffer_list.mNumberBuffers = 1;
+
+  /* Render input samples */
+  OSStatus r = AudioUnitRender(stm->input_unit,
+                               flags,
+                               tstamp,
+                               bus,
+                               input_frames,
+                               &input_buffer_list);
+  assert(r == noErr);
+  LOG("- input:  buffers %d, size %d, channels %d, frames %d\n",
+      input_buffer_list.mNumberBuffers,
+      input_buffer_list.mBuffers[0].mDataByteSize,
+      input_buffer_list.mBuffers[0].mNumberChannels,
+      input_frames);
+
+  assert(input_frames > 0);
+  stm->frames_read += input_frames;
+
+  // Full Duplex. We'll call data_callback in the AudioUnit output callback.
+  if (stm->output_unit != NULL) {
+    // User callback will be called by output callback