Merge mozilla-central to b2g-inbound
authorCarsten "Tomcat" Book <cbook@mozilla.com>
Tue, 05 May 2015 12:18:26 +0200
changeset 273752 cca08f3dc531b36c296ccf3fd45fa8deae828f8d
parent 273751 2b36f0280aafa16981cd8af9b322108981c7eb99 (current diff)
parent 273703 754579ec0e68068d32be534d553d5b191d918d84 (diff)
child 273753 acef56052f433275af669b0dc7a5342f165a0bff
push id863
push userraliiev@mozilla.com
push dateMon, 03 Aug 2015 13:22:43 +0000
treeherdermozilla-release@f6321b14228d [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
milestone40.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge mozilla-central to b2g-inbound
browser/extensions/Makefile.in
mobile/android/extensions/Makefile.in
--- a/CLOBBER
+++ b/CLOBBER
@@ -17,10 +17,9 @@
 #
 # Modifying this file will now automatically clobber the buildbot machines \o/
 #
 
 # Are you updating CLOBBER because you think it's needed for your WebIDL
 # changes to stick? As of bug 928195, this shouldn't be necessary! Please
 # don't change CLOBBER for WebIDL changes any more.
 
-Bug 1159082 - Renaming *Readonly animation interfaces to *ReadOnly causes
-build bustage on case-insensitive filesystems.
+Bug 1128037 needed a clobber to properly build on OS X
--- a/browser/base/content/browser-plugins.js
+++ b/browser/base/content/browser-plugins.js
@@ -8,18 +8,17 @@ var gPluginHandler = {
   PREF_PERSISTENT_DAYS: "plugin.persistentPermissionAlways.intervalInDays",
   MESSAGES: [
     "PluginContent:ShowClickToPlayNotification",
     "PluginContent:RemoveNotification",
     "PluginContent:UpdateHiddenPluginUI",
     "PluginContent:HideNotificationBar",
     "PluginContent:ShowInstallNotification",
     "PluginContent:InstallSinglePlugin",
-    "PluginContent:ShowNPAPIPluginCrashedNotification",
-    "PluginContent:ShowGMPCrashedNotification",
+    "PluginContent:ShowPluginCrashedNotification",
     "PluginContent:SubmitReport",
     "PluginContent:LinkClickCallback",
   ],
 
   init: function () {
     const mm = window.messageManager;
     for (let msg of this.MESSAGES) {
       mm.addMessageListener(msg, this);
@@ -57,25 +56,19 @@ var gPluginHandler = {
       case "PluginContent:HideNotificationBar":
         this.hideNotificationBar(msg.target, msg.data.name);
         break;
       case "PluginContent:ShowInstallNotification":
         return this.showInstallNotification(msg.target, msg.data.pluginInfo);
       case "PluginContent:InstallSinglePlugin":
         this.installSinglePlugin(msg.data.pluginInfo);
         break;
-      case "PluginContent:ShowNPAPIPluginCrashedNotification":
-        this.showNPAPIPluginCrashedNotification(msg.target, msg.data.message,
-                                                msg.data.runID);
-        break;
-      case "PluginContent:ShowGMPCrashedNotification":
-        this.showGMPCrashedNotification(msg.target,
-                                        msg.data.messageString,
-                                        msg.data.pluginDumpID,
-                                        msg.data.browserDumpID);
+      case "PluginContent:ShowPluginCrashedNotification":
+        this.showPluginCrashedNotification(msg.target, msg.data.messageString,
+                                           msg.data.pluginID);
         break;
       case "PluginContent:SubmitReport":
         if (AppConstants.MOZ_CRASHREPORTER) {
           this.submitReport(msg.data.runID, msg.data.keyVals, msg.data.submitURLOptIn);
         }
         break;
       case "PluginContent:LinkClickCallback":
         switch (msg.data.name) {
@@ -493,103 +486,75 @@ var gPluginHandler = {
       state = "please";
     }
 
     let mm = window.getGroupMessageManager("browsers");
     mm.broadcastAsyncMessage("BrowserPlugins:NPAPIPluginProcessCrashed",
                              { pluginName, runID, state });
   },
 
-  showNPAPIPluginCrashedNotification: function (browser, messageString, runID) {
-    let crashReportCallback;
-
-    if (AppConstants.MOZ_CRASHREPORTER &&
-        PluginCrashReporter.hasCrashReport(runID)) {
-      crashReportCallback = () => {
-        PluginCrashReporter.submitCrashReport(runID);
-      };
-    }
-
-    this._showPluginCrashedNotification(browser, messageString, crashReportCallback);
-  },
-
   /**
-   * For now, GMP crashes are handled separately from NPAPI plugin crashes,
-   * since the latter are not yet working for e10s. These will be unified
-   * once e10s support is added for GMP crash reporting in bug 1146955.
-   */
-  showGMPCrashedNotification: function (browser, messageString,
-                                        pluginDumpID, browserDumpID) {
-    let crashReportCallback;
-
-    if (AppConstants.MOZ_CRASHREPORTER && pluginDumpID) {
-      crashReportCallback = () => {
-        PluginCrashReporter.submitGMPCrashReport(pluginDumpID, browserDumpID);
-      };
-    }
-
-    this._showPluginCrashedNotification(browser, messageString, crashReportCallback);
-  },
-
-  /**
-   * A helper function for showing the plugin crashed notification bar.
+   * Shows a plugin-crashed notification bar for a browser that has had an
+   * invisiable NPAPI plugin crash, or a GMP plugin crash.
    *
    * @param browser
-   *        The browser that contains the crashing plugin.
+   *        The browser to show the notification for.
    * @param messageString
-   *        The message to display in the notification.
-   * @param crashReportCallback
-   *        Optional. Pass a function to submit a crash report for this plugin
-   *        crash if a report exists. If no function is passed, the Submit Report
-   *        button will not be added.
+   *        The string to put in the notification bar
+   * @param pluginID
+   *        The unique-per-process identifier for the NPAPI plugin or GMP.
+   *        For a GMP, this is the pluginID. For NPAPI plugins (where "pluginID"
+   *        means something different), this is the runID.
    */
-  _showPluginCrashedNotification: function (browser, messageString, crashReportCallback) {
+  showPluginCrashedNotification: function (browser, messageString, pluginID) {
     // If there's already an existing notification bar, don't do anything.
     let notificationBox = gBrowser.getNotificationBox(browser);
     let notification = notificationBox.getNotificationWithValue("plugin-crashed");
-    if (notification)
+    if (notification) {
       return;
+    }
 
     // Configure the notification bar
     let priority = notificationBox.PRIORITY_WARNING_MEDIUM;
     let iconURL = "chrome://mozapps/skin/plugins/notifyPluginCrashed.png";
     let reloadLabel = gNavigatorBundle.getString("crashedpluginsMessage.reloadButton.label");
     let reloadKey   = gNavigatorBundle.getString("crashedpluginsMessage.reloadButton.accesskey");
-    let submitLabel = gNavigatorBundle.getString("crashedpluginsMessage.submitButton.label");
-    let submitKey   = gNavigatorBundle.getString("crashedpluginsMessage.submitButton.accesskey");
 
     let buttons = [{
       label: reloadLabel,
       accessKey: reloadKey,
       popup: null,
       callback: function() { browser.reload(); },
     }];
 
-    if (AppConstants.MOZ_CRASHREPORTER && crashReportCallback) {
+    if (AppConstants.MOZ_CRASHREPORTER &&
+        PluginCrashReporter.hasCrashReport(pluginID)) {
+      let submitLabel = gNavigatorBundle.getString("crashedpluginsMessage.submitButton.label");
+      let submitKey   = gNavigatorBundle.getString("crashedpluginsMessage.submitButton.accesskey");
       let submitButton = {
         label: submitLabel,
         accessKey: submitKey,
         popup: null,
-        callback: crashReportCallback,
+        callback: () => {
+          PluginCrashReporter.submitCrashReport(pluginID);
+        },
       };
 
       buttons.push(submitButton);
     }
 
     notification = notificationBox.appendNotification(messageString, "plugin-crashed",
                                                       iconURL, priority, buttons);
 
     // Add the "learn more" link.
     let XULNS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
     let link = notification.ownerDocument.createElementNS(XULNS, "label");
     link.className = "text-link";
     link.setAttribute("value", gNavigatorBundle.getString("crashedpluginsMessage.learnMore"));
     let crashurl = formatURL("app.support.baseURL", true);
     crashurl += "plugin-crashed-notificationbar";
     link.href = crashurl;
-
     let description = notification.ownerDocument.getAnonymousElementByAttribute(notification, "anonid", "messageText");
     description.appendChild(link);
   },
 };
 
 gPluginHandler.init();
-
--- a/browser/base/content/test/general/browser.ini
+++ b/browser/base/content/test/general/browser.ini
@@ -421,20 +421,18 @@ support-files =
   benignPage.html
 [browser_typeAheadFind.js]
 skip-if = buildapp == 'mulet' || e10s # Bug 921935 - focusmanager issues with e10s (test calls waitForFocus)
 [browser_unknownContentType_title.js]
 [browser_unloaddialogs.js]
 skip-if = e10s # Bug 1100700 - test relies on unload event firing on closed tabs, which it doesn't
 [browser_urlHighlight.js]
 [browser_urlbarAutoFillTrimURLs.js]
-skip-if = e10s # Bug 1093941 - Waits indefinitely for onSearchComplete
 [browser_urlbarCopying.js]
 [browser_urlbarEnter.js]
-skip-if = e10s # Bug 1093941 - used to cause obscure non-windows child process crashes on try
 [browser_urlbarEnterAfterMouseOver.js]
 skip-if = os == "linux" || e10s # Bug 1073339 - Investigate autocomplete test unreliability on Linux/e10s
 [browser_urlbarRevert.js]
 [browser_urlbarSearchSingleWordNotification.js]
 [browser_urlbarStop.js]
 [browser_urlbarTrimURLs.js]
 [browser_urlbar_search_healthreport.js]
 [browser_utilityOverlay.js]
--- a/browser/base/content/test/general/browser_urlbarEnter.js
+++ b/browser/base/content/test/general/browser_urlbarEnter.js
@@ -20,20 +20,25 @@ add_task(function* () {
 
   // Cleanup.
   gBrowser.removeCurrentTab();
 });
 
 add_task(function* () {
   info("Alt+Return keypress");
   let tab = gBrowser.selectedTab = gBrowser.addTab(START_VALUE);
+  // due to bug 691608, we must wait for the load event, else isTabEmpty() will
+  // return true on e10s for this tab, so it will be reused even with altKey.
+  yield BrowserTestUtils.browserLoaded(tab.linkedBrowser);
 
   gURLBar.focus();
   EventUtils.synthesizeKey("VK_RETURN", {altKey: true});
-  yield BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser);
+
+  // wait for the new tab to appear.
+  yield BrowserTestUtils.waitForEvent(gBrowser.tabContainer, "TabOpen");
 
   // Check url bar and selected tab.
   is(gURLBar.textValue, TEST_VALUE, "Urlbar should preserve the value on return keypress");
   isnot(gBrowser.selectedTab, tab, "New URL was loaded in a new tab");
 
   // Cleanup.
   gBrowser.removeTab(tab);
   gBrowser.removeCurrentTab();
--- a/browser/base/content/test/plugins/browser_globalplugin_crashinfobar.js
+++ b/browser/base/content/test/plugins/browser_globalplugin_crashinfobar.js
@@ -1,22 +1,20 @@
 /**
- * Test that plugin crash submissions still work properly after
- * click-to-play activation.
+ * Test that the notification bar for crashed GMPs works.
  */
 add_task(function*() {
   yield BrowserTestUtils.withNewTab({
     gBrowser,
     url: "about:blank",
   }, function* (browser) {
     yield ContentTask.spawn(browser, null, function* () {
       const GMP_CRASH_EVENT = {
+        pluginID: 1,
         pluginName: "GlobalTestPlugin",
-        pluginDumpID: "1234",
-        browserDumpID: "5678",
         submittedCrashReport: false,
         bubbles: true,
         cancelable: true,
         gmpPlugin: true,
       };
 
       let crashEvent = new content.PluginCrashedEvent("PluginCrashed",
                                                       GMP_CRASH_EVENT);
--- a/browser/devtools/animationinspector/animation-controller.js
+++ b/browser/devtools/animationinspector/animation-controller.js
@@ -107,16 +107,18 @@ let AnimationsController = {
     // Expose actor capabilities.
     this.hasToggleAll = yield target.actorHasMethod("animations", "toggleAll");
     this.hasSetCurrentTime = yield target.actorHasMethod("animationplayer",
                                                          "setCurrentTime");
     this.hasMutationEvents = yield target.actorHasMethod("animations",
                                                          "stopAnimationPlayerUpdates");
     this.hasSetPlaybackRate = yield target.actorHasMethod("animationplayer",
                                                           "setPlaybackRate");
+    this.hasTargetNode = yield target.actorHasMethod("domwalker",
+                                                     "getNodeFromActor");
 
     if (this.destroyed) {
       console.warn("Could not fully initialize the AnimationsController");
       return;
     }
 
     this.startListeners();
     yield this.onNewNodeFront();
--- a/browser/devtools/animationinspector/animation-panel.js
+++ b/browser/devtools/animationinspector/animation-panel.js
@@ -1,16 +1,23 @@
 /* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
 /* vim: set ft=javascript ts=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/. */
 
 "use strict";
 
+const {
+  PlayerMetaDataHeader,
+  PlaybackRateSelector,
+  AnimationTargetNode,
+  createNode
+} = require("devtools/animationinspector/components");
+
 /**
  * The main animations panel UI.
  */
 let AnimationsPanel = {
   UI_UPDATED_EVENT: "ui-updated",
   PANEL_INITIALIZED: "panel-initialized",
 
   initialize: Task.async(function*() {
@@ -194,16 +201,19 @@ function PlayerWidget(player, containerE
   this.onFastForwardBtnClick = this.onFastForwardBtnClick.bind(this);
   this.onCurrentTimeChanged = this.onCurrentTimeChanged.bind(this);
   this.onPlaybackRateChanged = this.onPlaybackRateChanged.bind(this);
 
   this.metaDataComponent = new PlayerMetaDataHeader();
   if (AnimationsController.hasSetPlaybackRate) {
     this.rateComponent = new PlaybackRateSelector();
   }
+  if (AnimationsController.hasTargetNode) {
+    this.targetNodeComponent = new AnimationTargetNode(gInspector);
+  }
 }
 
 PlayerWidget.prototype = {
   initialize: Task.async(function*() {
     if (this.initialized) {
       return;
     }
     this.initialized = true;
@@ -219,16 +229,19 @@ PlayerWidget.prototype = {
     this.destroyed = true;
 
     this.stopTimelineAnimation();
     this.stopListeners();
     this.metaDataComponent.destroy();
     if (this.rateComponent) {
       this.rateComponent.destroy();
     }
+    if (this.targetNodeComponent) {
+      this.targetNodeComponent.destroy();
+    }
 
     this.el.remove();
     this.playPauseBtnEl = this.rewindBtnEl = this.fastForwardBtnEl = null;
     this.currentTimeEl = this.timeDisplayEl = null;
     this.containerEl = this.el = this.player = null;
   }),
 
   startListeners: function() {
@@ -256,21 +269,27 @@ PlayerWidget.prototype = {
       this.rateComponent.off("rate-changed", this.onPlaybackRateChanged);
     }
   },
 
   createMarkup: function() {
     let state = this.player.state;
 
     this.el = createNode({
+      parent: this.containerEl,
       attributes: {
         "class": "player-widget " + state.playState
       }
     });
 
+    if (this.targetNodeComponent) {
+      this.targetNodeComponent.init(this.el);
+      this.targetNodeComponent.render(this.player);
+    }
+
     this.metaDataComponent.init(this.el);
     this.metaDataComponent.render(state);
 
     // Timeline widget.
     let timelineEl = createNode({
       parent: this.el,
       attributes: {
         "class": "timeline"
@@ -354,18 +373,16 @@ PlayerWidget.prototype = {
     // Time display
     this.timeDisplayEl = createNode({
       parent: timelineEl,
       attributes: {
         "class": "time-display"
       }
     });
 
-    this.containerEl.appendChild(this.el);
-
     // Show the initial time.
     this.displayTime(state.currentTime);
   },
 
   /**
    * Executed when the playPause button is clicked.
    * Note that tests may want to call this callback directly rather than
    * simulating a click on the button since it returns the promise returned by
@@ -566,266 +583,8 @@ PlayerWidget.prototype = {
    */
   stopTimelineAnimation: function() {
     if (this.rafID) {
       cancelAnimationFrame(this.rafID);
       this.rafID = null;
     }
   }
 };
-
-/**
- * UI component responsible for displaying and updating the player meta-data:
- * name, duration, iterations, delay.
- * The parent UI component for this should drive its updates by calling
- * render(state) whenever it wants the component to update.
- */
-function PlayerMetaDataHeader() {
-  // Store the various state pieces we need to only refresh the UI when things
-  // change.
-  this.state = {};
-}
-
-PlayerMetaDataHeader.prototype = {
-  init: function(containerEl) {
-    // The main title element.
-    this.el = createNode({
-      parent: containerEl,
-      attributes: {
-        "class": "animation-title"
-      }
-    });
-
-    // Animation name.
-    this.nameLabel = createNode({
-      parent: this.el,
-      nodeType: "span"
-    });
-
-    this.nameValue = createNode({
-      parent: this.el,
-      nodeType: "strong",
-      attributes: {
-        "style": "display:none;"
-      }
-    });
-
-    // Animation duration, delay and iteration container.
-    let metaData = createNode({
-      parent: this.el,
-      nodeType: "span",
-      attributes: {
-        "class": "meta-data"
-      }
-    });
-
-    // Animation duration.
-    this.durationLabel = createNode({
-      parent: metaData,
-      nodeType: "span"
-    });
-    this.durationLabel.textContent = L10N.getStr("player.animationDurationLabel");
-
-    this.durationValue = createNode({
-      parent: metaData,
-      nodeType: "strong"
-    });
-
-    // Animation delay (hidden by default since there may not be a delay).
-    this.delayLabel = createNode({
-      parent: metaData,
-      nodeType: "span",
-      attributes: {
-        "style": "display:none;"
-      }
-    });
-    this.delayLabel.textContent = L10N.getStr("player.animationDelayLabel");
-
-    this.delayValue = createNode({
-      parent: metaData,
-      nodeType: "strong"
-    });
-
-    // Animation iteration count (also hidden by default since we don't display
-    // single iterations).
-    this.iterationLabel = createNode({
-      parent: metaData,
-      nodeType: "span",
-      attributes: {
-        "style": "display:none;"
-      }
-    });
-    this.iterationLabel.textContent = L10N.getStr("player.animationIterationCountLabel");
-
-    this.iterationValue = createNode({
-      parent: metaData,
-      nodeType: "strong",
-      attributes: {
-        "style": "display:none;"
-      }
-    });
-  },
-
-  destroy: function() {
-    this.state = null;
-    this.el.remove();
-    this.el = null;
-    this.nameLabel = this.nameValue = null;
-    this.durationLabel = this.durationValue = null;
-    this.delayLabel = this.delayValue = null;
-    this.iterationLabel = this.iterationValue = null;
-  },
-
-  render: function(state) {
-    // Update the name if needed.
-    if (state.name !== this.state.name) {
-      if (state.name) {
-        // Animations (and transitions since bug 1122414) have names.
-        this.nameLabel.textContent = L10N.getStr("player.animationNameLabel");
-        this.nameValue.style.display = "inline";
-        this.nameValue.textContent = state.name;
-      } else {
-        // With older actors, Css transitions don't have names.
-        this.nameLabel.textContent = L10N.getStr("player.transitionNameLabel");
-        this.nameValue.style.display = "none";
-      }
-    }
-
-    // update the duration value if needed.
-    if (state.duration !== this.state.duration) {
-      this.durationValue.textContent = L10N.getFormatStr("player.timeLabel",
-        L10N.numberWithDecimals(state.duration / 1000, 2));
-    }
-
-    // Update the delay if needed.
-    if (state.delay !== this.state.delay) {
-      if (state.delay) {
-        this.delayLabel.style.display = "inline";
-        this.delayValue.style.display = "inline";
-        this.delayValue.textContent = L10N.getFormatStr("player.timeLabel",
-          L10N.numberWithDecimals(state.delay / 1000, 2));
-      } else {
-        // Hide the delay elements if there is no delay defined.
-        this.delayLabel.style.display = "none";
-        this.delayValue.style.display = "none";
-      }
-    }
-
-    // Update the iterationCount if needed.
-    if (state.iterationCount !== this.state.iterationCount) {
-      if (state.iterationCount !== 1) {
-        this.iterationLabel.style.display = "inline";
-        this.iterationValue.style.display = "inline";
-        let count = state.iterationCount ||
-                    L10N.getStr("player.infiniteIterationCount");
-        this.iterationValue.innerHTML = count;
-      } else {
-        // Hide the iteration elements if iteration is 1.
-        this.iterationLabel.style.display = "none";
-        this.iterationValue.style.display = "none";
-      }
-    }
-
-    this.state = state;
-  }
-};
-
-/**
- * UI component responsible for displaying the playback rate drop-down in each
- * player widget, updating it when the state changes, and emitting events when
- * the user selects a new value.
- * The parent UI component for this should drive its updates by calling
- * render(state) whenever it wants the component to update.
- */
-function PlaybackRateSelector() {
-  this.currentRate = null;
-  this.onSelectionChanged = this.onSelectionChanged.bind(this);
-  EventEmitter.decorate(this);
-}
-
-PlaybackRateSelector.prototype = {
-  PRESETS: [.1, .5, 1, 2, 5, 10],
-
-  init: function(containerEl) {
-    // This component is simple enough that we can re-create the markup every
-    // time it's rendered. So here we only store the parentEl.
-    this.parentEl = containerEl;
-  },
-
-  destroy: function() {
-    this.removeSelect();
-    this.parentEl = this.el = null;
-  },
-
-  removeSelect: function() {
-    if (this.el) {
-      this.el.removeEventListener("change", this.onSelectionChanged);
-      this.el.remove();
-    }
-  },
-
-  /**
-   * Get the ordered list of presets, including the current playbackRate if
-   * different from the existing presets.
-   */
-  getCurrentPresets: function({playbackRate}) {
-    return [...new Set([...this.PRESETS, playbackRate])].sort((a,b) => a > b);
-  },
-
-  render: function(state) {
-    if (state.playbackRate === this.currentRate) {
-      return;
-    }
-
-    this.removeSelect();
-
-    this.el = createNode({
-      parent: this.parentEl,
-      nodeType: "select",
-      attributes: {
-        "class": "rate devtools-button"
-      }
-    });
-
-    for (let preset of this.getCurrentPresets(state)) {
-      let option = createNode({
-        parent: this.el,
-        nodeType: "option",
-        attributes: {
-          value: preset,
-        }
-      });
-      option.textContent = L10N.getFormatStr("player.playbackRateLabel", preset);
-      if (preset === state.playbackRate) {
-        option.setAttribute("selected", "");
-      }
-    }
-
-    this.el.addEventListener("change", this.onSelectionChanged);
-
-    this.currentRate = state.playbackRate;
-  },
-
-  onSelectionChanged: function(e) {
-    this.emit("rate-changed", parseFloat(this.el.value));
-  }
-};
-
-/**
- * DOM node creation helper function.
- * @param {Object} Options to customize the node to be created.
- * @return {DOMNode} The newly created node.
- */
-function createNode(options) {
-  let type = options.nodeType || "div";
-  let node = document.createElement(type);
-
-  for (let name in options.attributes || {}) {
-    let value = options.attributes[name];
-    node.setAttribute(name, value);
-  }
-
-  if (options.parent) {
-    options.parent.appendChild(node);
-  }
-
-  return node;
-}
new file mode 100644
--- /dev/null
+++ b/browser/devtools/animationinspector/components.js
@@ -0,0 +1,502 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=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/. */
+
+"use strict";
+
+// Set of reusable UI components for the animation-inspector UI.
+// All components in this module share a common API:
+// 1. construct the component:
+//    let c = new ComponentName();
+// 2. initialize the markup of the component in a given parent node:
+//    c.init(containerElement);
+// 3. render the component, passing in some sort of state:
+//    This may be called over and over again when the state changes, to update
+//    the component output.
+//    c.render(state);
+// 4. destroy the component:
+//    c.destroy();
+
+const {Cu} = require('chrome');
+Cu.import("resource:///modules/devtools/ViewHelpers.jsm");
+
+const STRINGS_URI = "chrome://browser/locale/devtools/animationinspector.properties";
+const L10N = new ViewHelpers.L10N(STRINGS_URI);
+
+/**
+ * UI component responsible for displaying and updating the player meta-data:
+ * name, duration, iterations, delay.
+ * The parent UI component for this should drive its updates by calling
+ * render(state) whenever it wants the component to update.
+ */
+function PlayerMetaDataHeader() {
+  // Store the various state pieces we need to only refresh the UI when things
+  // change.
+  this.state = {};
+}
+
+exports.PlayerMetaDataHeader = PlayerMetaDataHeader;
+
+PlayerMetaDataHeader.prototype = {
+  init: function(containerEl) {
+    // The main title element.
+    this.el = createNode({
+      parent: containerEl,
+      attributes: {
+        "class": "animation-title"
+      }
+    });
+
+    // Animation name.
+    this.nameLabel = createNode({
+      parent: this.el,
+      nodeType: "span"
+    });
+
+    this.nameValue = createNode({
+      parent: this.el,
+      nodeType: "strong",
+      attributes: {
+        "style": "display:none;"
+      }
+    });
+
+    // Animation duration, delay and iteration container.
+    let metaData = createNode({
+      parent: this.el,
+      nodeType: "span",
+      attributes: {
+        "class": "meta-data"
+      }
+    });
+
+    // Animation duration.
+    this.durationLabel = createNode({
+      parent: metaData,
+      nodeType: "span"
+    });
+    this.durationLabel.textContent = L10N.getStr("player.animationDurationLabel");
+
+    this.durationValue = createNode({
+      parent: metaData,
+      nodeType: "strong"
+    });
+
+    // Animation delay (hidden by default since there may not be a delay).
+    this.delayLabel = createNode({
+      parent: metaData,
+      nodeType: "span",
+      attributes: {
+        "style": "display:none;"
+      }
+    });
+    this.delayLabel.textContent = L10N.getStr("player.animationDelayLabel");
+
+    this.delayValue = createNode({
+      parent: metaData,
+      nodeType: "strong"
+    });
+
+    // Animation iteration count (also hidden by default since we don't display
+    // single iterations).
+    this.iterationLabel = createNode({
+      parent: metaData,
+      nodeType: "span",
+      attributes: {
+        "style": "display:none;"
+      }
+    });
+    this.iterationLabel.textContent = L10N.getStr("player.animationIterationCountLabel");
+
+    this.iterationValue = createNode({
+      parent: metaData,
+      nodeType: "strong",
+      attributes: {
+        "style": "display:none;"
+      }
+    });
+  },
+
+  destroy: function() {
+    this.state = null;
+    this.el.remove();
+    this.el = null;
+    this.nameLabel = this.nameValue = null;
+    this.durationLabel = this.durationValue = null;
+    this.delayLabel = this.delayValue = null;
+    this.iterationLabel = this.iterationValue = null;
+  },
+
+  render: function(state) {
+    // Update the name if needed.
+    if (state.name !== this.state.name) {
+      if (state.name) {
+        // Animations (and transitions since bug 1122414) have names.
+        this.nameLabel.textContent = L10N.getStr("player.animationNameLabel");
+        this.nameValue.style.display = "inline";
+        this.nameValue.textContent = state.name;
+      } else {
+        // With older actors, Css transitions don't have names.
+        this.nameLabel.textContent = L10N.getStr("player.transitionNameLabel");
+        this.nameValue.style.display = "none";
+      }
+    }
+
+    // update the duration value if needed.
+    if (state.duration !== this.state.duration) {
+      this.durationValue.textContent = L10N.getFormatStr("player.timeLabel",
+        L10N.numberWithDecimals(state.duration / 1000, 2));
+    }
+
+    // Update the delay if needed.
+    if (state.delay !== this.state.delay) {
+      if (state.delay) {
+        this.delayLabel.style.display = "inline";
+        this.delayValue.style.display = "inline";
+        this.delayValue.textContent = L10N.getFormatStr("player.timeLabel",
+          L10N.numberWithDecimals(state.delay / 1000, 2));
+      } else {
+        // Hide the delay elements if there is no delay defined.
+        this.delayLabel.style.display = "none";
+        this.delayValue.style.display = "none";
+      }
+    }
+
+    // Update the iterationCount if needed.
+    if (state.iterationCount !== this.state.iterationCount) {
+      if (state.iterationCount !== 1) {
+        this.iterationLabel.style.display = "inline";
+        this.iterationValue.style.display = "inline";
+        let count = state.iterationCount ||
+                    L10N.getStr("player.infiniteIterationCount");
+        this.iterationValue.innerHTML = count;
+      } else {
+        // Hide the iteration elements if iteration is 1.
+        this.iterationLabel.style.display = "none";
+        this.iterationValue.style.display = "none";
+      }
+    }
+
+    this.state = state;
+  }
+};
+
+/**
+ * UI component responsible for displaying the playback rate drop-down in each
+ * player widget, updating it when the state changes, and emitting events when
+ * the user selects a new value.
+ * The parent UI component for this should drive its updates by calling
+ * render(state) whenever it wants the component to update.
+ */
+function PlaybackRateSelector() {
+  this.currentRate = null;
+  this.onSelectionChanged = this.onSelectionChanged.bind(this);
+  EventEmitter.decorate(this);
+}
+
+exports.PlaybackRateSelector = PlaybackRateSelector;
+
+PlaybackRateSelector.prototype = {
+  PRESETS: [.1, .5, 1, 2, 5, 10],
+
+  init: function(containerEl) {
+    // This component is simple enough that we can re-create the markup every
+    // time it's rendered. So here we only store the parentEl.
+    this.parentEl = containerEl;
+  },
+
+  destroy: function() {
+    this.removeSelect();
+    this.parentEl = this.el = null;
+  },
+
+  removeSelect: function() {
+    if (this.el) {
+      this.el.removeEventListener("change", this.onSelectionChanged);
+      this.el.remove();
+    }
+  },
+
+  /**
+   * Get the ordered list of presets, including the current playbackRate if
+   * different from the existing presets.
+   */
+  getCurrentPresets: function({playbackRate}) {
+    return [...new Set([...this.PRESETS, playbackRate])].sort((a,b) => a > b);
+  },
+
+  render: function(state) {
+    if (state.playbackRate === this.currentRate) {
+      return;
+    }
+
+    this.removeSelect();
+
+    this.el = createNode({
+      parent: this.parentEl,
+      nodeType: "select",
+      attributes: {
+        "class": "rate devtools-button"
+      }
+    });
+
+    for (let preset of this.getCurrentPresets(state)) {
+      let option = createNode({
+        parent: this.el,
+        nodeType: "option",
+        attributes: {
+          value: preset,
+        }
+      });
+      option.textContent = L10N.getFormatStr("player.playbackRateLabel", preset);
+      if (preset === state.playbackRate) {
+        option.setAttribute("selected", "");
+      }
+    }
+
+    this.el.addEventListener("change", this.onSelectionChanged);
+
+    this.currentRate = state.playbackRate;
+  },
+
+  onSelectionChanged: function(e) {
+    this.emit("rate-changed", parseFloat(this.el.value));
+  }
+};
+
+/**
+ * UI component responsible for displaying a preview of the target dom node of
+ * a given animation.
+ * @param {InspectorPanel} inspector Requires a reference to the inspector-panel
+ * to highlight and select the node, as well as refresh it when there are
+ * mutations.
+ */
+function AnimationTargetNode(inspector) {
+  this.inspector = inspector;
+
+  this.onPreviewMouseOver = this.onPreviewMouseOver.bind(this);
+  this.onPreviewMouseOut = this.onPreviewMouseOut.bind(this);
+  this.onSelectNodeClick = this.onSelectNodeClick.bind(this);
+  this.onMarkupMutations = this.onMarkupMutations.bind(this);
+
+  EventEmitter.decorate(this);
+}
+
+exports.AnimationTargetNode = AnimationTargetNode;
+
+AnimationTargetNode.prototype = {
+  init: function(containerEl) {
+    let document = containerEl.ownerDocument;
+
+    // Init the markup for displaying the target node.
+    this.el = createNode({
+      parent: containerEl,
+      attributes: {
+        "class": "animation-target"
+      }
+    });
+
+    // Icon to select the node in the inspector.
+    this.selectNodeEl = createNode({
+      parent: this.el,
+      nodeType: "span",
+      attributes: {
+        "class": "node-selector"
+      }
+    });
+
+    // Wrapper used for mouseover/out event handling.
+    this.previewEl = createNode({
+      parent: this.el,
+      nodeType: "span"
+    });
+
+    this.previewEl.appendChild(document.createTextNode("<"));
+
+    // Tag name.
+    this.tagNameEl = createNode({
+      parent: this.previewEl,
+      nodeType: "span",
+      attributes: {
+        "class": "tag-name theme-fg-color3"
+      }
+    });
+
+    // Id attribute container.
+    this.idEl = createNode({
+      parent: this.previewEl,
+      nodeType: "span"
+    });
+
+    createNode({
+      parent: this.idEl,
+      nodeType: "span",
+      attributes: {
+        "class": "attribute-name theme-fg-color2"
+      }
+    }).textContent = "id";
+
+    this.idEl.appendChild(document.createTextNode("=\""));
+
+    createNode({
+      parent: this.idEl,
+      nodeType: "span",
+      attributes: {
+        "class": "attribute-value theme-fg-color6"
+      }
+    });
+
+    this.idEl.appendChild(document.createTextNode("\""));
+
+    // Class attribute container.
+    this.classEl = createNode({
+      parent: this.previewEl,
+      nodeType: "span"
+    });
+
+    createNode({
+      parent: this.classEl,
+      nodeType: "span",
+      attributes: {
+        "class": "attribute-name theme-fg-color2"
+      }
+    }).textContent = "class";
+
+    this.classEl.appendChild(document.createTextNode("=\""));
+
+    createNode({
+      parent: this.classEl,
+      nodeType: "span",
+      attributes: {
+        "class": "attribute-value theme-fg-color6"
+      }
+    });
+
+    this.classEl.appendChild(document.createTextNode("\""));
+
+    this.previewEl.appendChild(document.createTextNode(">"));
+
+    // Init events for highlighting and selecting the node.
+    this.previewEl.addEventListener("mouseover", this.onPreviewMouseOver);
+    this.previewEl.addEventListener("mouseout", this.onPreviewMouseOut);
+    this.selectNodeEl.addEventListener("click", this.onSelectNodeClick);
+
+    // Start to listen for markupmutation events.
+    this.inspector.on("markupmutation", this.onMarkupMutations);
+  },
+
+  destroy: function() {
+    this.inspector.off("markupmutation", this.onMarkupMutations);
+    this.previewEl.removeEventListener("mouseover", this.onPreviewMouseOver);
+    this.previewEl.removeEventListener("mouseout", this.onPreviewMouseOut);
+    this.selectNodeEl.removeEventListener("click", this.onSelectNodeClick);
+    this.el.remove();
+    this.el = this.tagNameEl = this.idEl = this.classEl = null;
+    this.selectNodeEl = this.previewEl = null;
+    this.nodeFront = this.inspector = this.playerFront = null;
+  },
+
+  onPreviewMouseOver: function() {
+    if (!this.nodeFront) {
+      return;
+    }
+    this.inspector.toolbox.highlighterUtils.highlightNodeFront(this.nodeFront);
+  },
+
+  onPreviewMouseOut: function() {
+    this.inspector.toolbox.highlighterUtils.unhighlight();
+  },
+
+  onSelectNodeClick: function() {
+    if (!this.nodeFront) {
+      return;
+    }
+    this.inspector.selection.setNodeFront(this.nodeFront, "animationinspector");
+  },
+
+  onMarkupMutations: function(e, mutations) {
+    if (!this.nodeFront || !this.playerFront) {
+      return;
+    }
+
+    for (let {target} of mutations) {
+      if (target === this.nodeFront) {
+        // Re-render with the same nodeFront to update the output.
+        this.render(this.playerFront);
+        break;
+      }
+    }
+  },
+
+  render: function(playerFront) {
+    this.playerFront = playerFront;
+    this.inspector.walker.getNodeFromActor(playerFront.actorID, ["node"]).then(nodeFront => {
+      // We might have been destroyed in the meantime, or the node might not be found.
+      if (!this.el || !nodeFront) {
+        return;
+      }
+
+      this.nodeFront = nodeFront;
+      let {tagName, attributes} = nodeFront;
+
+      this.tagNameEl.textContent = tagName.toLowerCase();
+
+      let idIndex = attributes.findIndex(({name}) => name === "id");
+      if (idIndex > -1 && attributes[idIndex].value) {
+        this.idEl.querySelector(".attribute-value").textContent =
+          attributes[idIndex].value;
+        this.idEl.style.display = "inline";
+      } else {
+        this.idEl.style.display = "none";
+      }
+
+      let classIndex = attributes.findIndex(({name}) => name === "class");
+      if (classIndex > -1 && attributes[classIndex].value) {
+        this.classEl.querySelector(".attribute-value").textContent =
+          attributes[classIndex].value;
+        this.classEl.style.display = "inline";
+      } else {
+        this.classEl.style.display = "none";
+      }
+
+      this.emit("target-retrieved");
+    }, e => {
+      this.nodeFront = null;
+      if (!this.el) {
+        console.warn("Cound't retrieve the animation target node, widget destroyed");
+      } else {
+        console.error(e);
+      }
+    });
+  }
+};
+
+/**
+ * DOM node creation helper function.
+ * @param {Object} Options to customize the node to be created.
+ * - nodeType {String} Optional, defaults to "div",
+ * - attributes {Object} Optional attributes object like
+ *   {attrName1:value1, attrName2: value2, ...}
+ * - parent {DOMNode} Mandatory node to append the newly created node to.
+ * @return {DOMNode} The newly created node.
+ */
+function createNode(options) {
+  if (!options.parent) {
+    throw new Error("Missing parent DOMNode to create new node");
+  }
+
+  let type = options.nodeType || "div";
+  let node = options.parent.ownerDocument.createElement(type);
+
+  for (let name in options.attributes || {}) {
+    let value = options.attributes[name];
+    node.setAttribute(name, value);
+  }
+
+  options.parent.appendChild(node);
+  return node;
+}
+
+exports.createNode = createNode;
--- a/browser/devtools/animationinspector/moz.build
+++ b/browser/devtools/animationinspector/moz.build
@@ -1,7 +1,11 @@
 # -*- 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/.
 
 BROWSER_CHROME_MANIFESTS += ['test/browser.ini']
+
+EXTRA_JS_MODULES.devtools.animationinspector += [
+    'components.js',
+]
--- a/browser/devtools/animationinspector/test/browser.ini
+++ b/browser/devtools/animationinspector/test/browser.ini
@@ -15,24 +15,26 @@ support-files =
 [browser_animation_playerFronts_are_refreshed.js]
 [browser_animation_playerWidgets_appear_on_panel_init.js]
 [browser_animation_playerWidgets_destroy.js]
 [browser_animation_playerWidgets_disables_on_finished.js]
 [browser_animation_playerWidgets_dont_show_time_after_duration.js]
 [browser_animation_playerWidgets_have_control_buttons.js]
 [browser_animation_playerWidgets_meta_data.js]
 [browser_animation_playerWidgets_state_after_pause.js]
+[browser_animation_playerWidgets_target_nodes.js]
 [browser_animation_rate_select_shows_presets.js]
 [browser_animation_refresh_on_added_animation.js]
 [browser_animation_refresh_on_removed_animation.js]
 [browser_animation_refresh_when_active.js]
 [browser_animation_same_nb_of_playerWidgets_and_playerFronts.js]
 [browser_animation_setting_currentTime_works_and_pauses.js]
 [browser_animation_setting_playbackRate_works.js]
 [browser_animation_shows_player_on_valid_node.js]
+[browser_animation_target_highlight_select.js]
 [browser_animation_timeline_animates.js]
 [browser_animation_timeline_is_enabled.js]
 [browser_animation_timeline_waits_for_delay.js]
 [browser_animation_toggle_button_resets_on_navigate.js]
 [browser_animation_toggle_button_toggles_animations.js]
 [browser_animation_toggle_button_updates_playerWidgets.js]
 [browser_animation_toolbar_exists.js]
 [browser_animation_ui_updates_when_animation_changes.js]
new file mode 100644
--- /dev/null
+++ b/browser/devtools/animationinspector/test/browser_animation_playerWidgets_target_nodes.js
@@ -0,0 +1,31 @@
+/* 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";
+
+// Test that player widgets display information about target nodes
+
+add_task(function*() {
+  yield addTab(TEST_URL_ROOT + "doc_simple_animation.html");
+  let {inspector, panel} = yield openAnimationInspector();
+
+  info("Select the simple animated node");
+  yield selectNode(".animated", inspector);
+
+  let widget = panel.playerWidgets[0];
+
+  // Make sure to wait for the target-retrieved event if the nodeFront hasn't
+  // yet been retrieved by the TargetNodeComponent.
+  if (!widget.targetNodeComponent.nodeFront) {
+    yield widget.targetNodeComponent.once("target-retrieved");
+  }
+
+  let targetEl = widget.el.querySelector(".animation-target");
+  ok(targetEl, "The player widget has a target element");
+  is(targetEl.textContent, "<divid=\"\"class=\"ball animated\">",
+    "The target element's content is correct");
+
+  let selectorEl = targetEl.querySelector(".node-selector");
+  ok(selectorEl, "The icon to select the target element in the inspector exists");
+});
new file mode 100644
--- /dev/null
+++ b/browser/devtools/animationinspector/test/browser_animation_target_highlight_select.js
@@ -0,0 +1,62 @@
+/* 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";
+
+// Test that the DOM element targets displayed in animation player widgets can
+// be used to highlight elements in the DOM and select them in the inspector.
+
+add_task(function*() {
+  yield addTab(TEST_URL_ROOT + "doc_simple_animation.html");
+  let {toolbox, inspector, panel} = yield openAnimationInspector();
+
+  info("Select the simple animated node");
+  yield selectNode(".animated", inspector);
+
+  // Make sure to wait for the target-retrieved event if the nodeFront hasn't
+  // yet been retrieved by the TargetNodeComponent.
+  let targetNodeComponent = panel.playerWidgets[0].targetNodeComponent;
+  if (!targetNodeComponent.nodeFront) {
+    yield targetNodeComponent.once("target-retrieved");
+  }
+
+  info("Retrieve the part of the widget that highlights the node on hover");
+  let highlightingEl = targetNodeComponent.previewEl;
+
+  info("Listen to node-highlight event and mouse over the widget");
+  let onHighlight = toolbox.once("node-highlight");
+  EventUtils.synthesizeMouse(highlightingEl, 10, 5, {type: "mouseover"},
+                             highlightingEl.ownerDocument.defaultView);
+  let nodeFront = yield onHighlight;
+
+  ok(true, "The node-highlight event was fired");
+  is(targetNodeComponent.nodeFront, nodeFront,
+    "The highlighted node is the one stored on the animation widget");
+  is(nodeFront.tagName, "DIV", "The highlighted node has the correct tagName");
+  is(nodeFront.attributes[0].name, "class", "The highlighted node has the correct attributes");
+  is(nodeFront.attributes[0].value, "ball animated", "The highlighted node has the correct class");
+
+  info("Select the body node in order to have the list of all animations");
+  yield selectNode("body", inspector);
+
+  // Make sure to wait for the target-retrieved event if the nodeFront hasn't
+  // yet been retrieved by the TargetNodeComponent.
+  targetNodeComponent = panel.playerWidgets[0].targetNodeComponent;
+  if (!targetNodeComponent.nodeFront) {
+    yield targetNodeComponent.once("target-retrieved");
+  }
+
+  info("Click on the first animation widget's selector icon and wait for the selection to change");
+  let onSelection = inspector.selection.once("new-node-front");
+  let onPanelUpdated = panel.once(panel.UI_UPDATED_EVENT);
+  let selectIconEl = targetNodeComponent.selectNodeEl;
+  EventUtils.sendMouseEvent({type: "click"}, selectIconEl,
+                            selectIconEl.ownerDocument.defaultView);
+  yield onSelection;
+
+  is(inspector.selection.nodeFront, targetNodeComponent.nodeFront,
+    "The selected node is the one stored on the animation widget");
+
+  yield onPanelUpdated;
+});
--- a/browser/devtools/shared/moz.build
+++ b/browser/devtools/shared/moz.build
@@ -50,16 +50,17 @@ EXTRA_JS_MODULES.devtools.shared += [
     'devices.js',
     'doorhanger.js',
     'frame-script-utils.js',
     'getjson.js',
     'inplace-editor.js',
     'node-attribute-parser.js',
     'observable-object.js',
     'options-view.js',
+    'poller.js',
     'source-utils.js',
     'telemetry.js',
     'theme-switching.js',
     'theme.js',
     'undo.js',
 ]
 
 EXTRA_JS_MODULES.devtools.shared.widgets += [
new file mode 100644
--- /dev/null
+++ b/browser/devtools/shared/poller.js
@@ -0,0 +1,115 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+loader.lazyRequireGetter(this, "timers",
+  "resource://gre/modules/Timer.jsm");
+loader.lazyRequireGetter(this, "defer",
+  "sdk/core/promise", true);
+
+/**
+ * @constructor Poller
+ * Takes a function that is to be called on an interval,
+ * and can be turned on and off via methods to execute `fn` on the interval
+ * specified during `on`. If `fn` returns a promise, the polling waits for
+ * that promise to resolve before waiting the interval to call again.
+ *
+ * Specify the `wait` duration between polling here, and optionally
+ * an `immediate` boolean, indicating whether the function should be called
+ * immediately when toggling on.
+ *
+ * @param {function} fn
+ * @param {number} wait
+ * @param {boolean?} immediate
+ */
+function Poller (fn, wait, immediate) {
+  this._fn = fn;
+  this._wait = wait;
+  this._immediate = immediate;
+  this._poll = this._poll.bind(this);
+  this._preparePoll = this._preparePoll.bind(this);
+}
+exports.Poller = Poller;
+
+/**
+ * Returns a boolean indicating whether or not poller
+ * is polling.
+ *
+ * @return {boolean}
+ */
+Poller.prototype.isPolling = function pollerIsPolling () {
+  return !!this._timer;
+};
+
+/**
+ * Turns polling on.
+ *
+ * @return {Poller}
+ */
+Poller.prototype.on = function pollerOn () {
+  if (this._destroyed) {
+    throw Error("Poller cannot be turned on after destruction.");
+  }
+  if (this._timer) {
+    this.off();
+  }
+  this._immediate ? this._poll() : this._preparePoll();
+  return this;
+};
+
+/**
+ * Turns off polling. Returns a promise that resolves when
+ * the last outstanding `fn` call finishes if it's an async function.
+ *
+ * @return {Promise}
+ */
+Poller.prototype.off = function pollerOff () {
+  let { resolve, promise } = defer();
+  if (this._timer) {
+    timers.clearTimeout(this._timer);
+    this._timer = null;
+  }
+
+  // Settle an inflight poll call before resolving
+  // if using a promise-backed poll function
+  if (this._inflight) {
+    this._inflight.then(resolve);
+  } else {
+    resolve();
+  }
+  return promise;
+};
+
+/**
+ * Turns off polling and removes the reference to the poller function.
+ * Resolves when the last outstanding `fn` call finishes if it's an async function.
+ */
+Poller.prototype.destroy = function pollerDestroy () {
+  return this.off().then(() => {
+    this._destroyed = true;
+    this._fn = null
+  });
+};
+
+Poller.prototype._preparePoll = function pollerPrepare () {
+  this._timer = timers.setTimeout(this._poll, this._wait);
+};
+
+Poller.prototype._poll = function pollerPoll () {
+  let response = this._fn();
+  if (response && typeof response.then === "function") {
+    // Store the most recent in-flight polling
+    // call so we can clean it up when disabling
+    this._inflight = response;
+    response.then(() => {
+      // Only queue up the next call if poller was not turned off
+      // while this async poll call was in flight.
+      if (this._timer) {
+        this._preparePoll();
+      }
+    });
+  } else {
+    this._preparePoll();
+  }
+};
--- a/browser/devtools/shared/test/browser.ini
+++ b/browser/devtools/shared/test/browser.ini
@@ -79,16 +79,17 @@ skip-if = e10s # Layouthelpers test shou
 skip-if = e10s # Layouthelpers test should not run in a content page.
 [browser_mdn-docs-01.js]
 [browser_mdn-docs-02.js]
 [browser_num-l10n.js]
 [browser_observableobject.js]
 [browser_options-view-01.js]
 [browser_outputparser.js]
 skip-if = e10s # Test intermittently fails with e10s. Bug 1124162.
+[browser_poller.js]
 [browser_prefs-01.js]
 [browser_prefs-02.js]
 [browser_require_basic.js]
 [browser_spectrum.js]
 [browser_theme.js]
 [browser_tableWidget_basic.js]
 [browser_tableWidget_keyboard_interaction.js]
 [browser_tableWidget_mouse_interaction.js]
new file mode 100644
--- /dev/null
+++ b/browser/devtools/shared/test/browser_poller.js
@@ -0,0 +1,131 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Tests the Poller class.
+
+const { Poller } = devtools.require("devtools/shared/poller");
+
+add_task(function* () {
+  let count1 = 0, count2 = 0, count3 = 0;
+
+  let poller1 = new Poller(function () {
+    count1++;
+  }, 1000000000, true);
+  let poller2 = new Poller(function () {
+    count2++;
+  }, 10);
+  let poller3 = new Poller(function () {
+    count3++;
+  }, 1000000000);
+
+  poller2.on();
+
+  ok(!poller1.isPolling(), "isPolling() returns false for an off poller");
+  ok(poller2.isPolling(), "isPolling() returns true for an on poller");
+
+  yield waitUntil(() => count2 > 10);
+
+  ok(count2 > 10, "poller that was turned on polled several times");
+  ok(count1 === 0, "poller that was never turned on never polled");
+
+  yield poller2.off();
+  let currentCount2 = count2;
+
+  poller1.on(); // Really high poll time!
+  poller3.on(); // Really high poll time!
+
+  yield waitUntil(() => count1 === 1);
+  ok(true, "Poller calls fn immediately when `immediate` is true");
+  ok(count3 === 0, "Poller does not call fn immediately when `immediate` is not set");
+
+  ok(count2 === currentCount2, "a turned off poller does not continue to poll");
+  yield poller2.off();
+  yield poller2.off();
+  yield poller2.off();
+  ok(true, "Poller.prototype.off() is idempotent");
+
+  // This should still have not polled a second time
+  is(count1, 1, "wait time works");
+
+  ok(poller1.isPolling(), "isPolling() returns true for an on poller");
+  ok(!poller2.isPolling(), "isPolling() returns false for an off poller");
+});
+
+add_task(function *() {
+  let count = -1;
+  // Create a poller that returns a promise.
+  // The promise is resolved asynchronously after adding 9 to the count, ensuring
+  // that on every poll, we have a multiple of 10.
+  let asyncPoller = new Poller(function () {
+    count++;
+    ok(!(count%10), `Async poller called with a multiple of 10: ${count}`);
+    return new Promise(function (resolve, reject) {
+      let add9 = 9;
+      let interval = setInterval(() => {
+        if (add9--) {
+          count++;
+        } else {
+          clearInterval(interval);
+          resolve();
+        }
+      }, 10);
+    });
+  });
+
+  asyncPoller.on(1);
+  yield waitUntil(() => count > 50);
+  yield asyncPoller.off();
+});
+
+add_task(function *() {
+  // Create a poller that returns a promise. This poll call
+  // is called immediately, and then subsequently turned off.
+  // The call to `off` should not resolve until the inflight call
+  // finishes.
+  let inflightFinished = null;
+  let pollCalls = 0;
+  let asyncPoller = new Poller(function () {
+    pollCalls++;
+    return new Promise(function (resolve, reject) {
+      setTimeout(() => {
+        inflightFinished = true;
+        resolve();
+      }, 1000);
+    });
+  }, 1, true);
+  asyncPoller.on();
+
+  yield asyncPoller.off();
+  ok(inflightFinished, "off() method does not resolve until remaining inflight poll calls finish");
+  is(pollCalls, 1, "should only be one poll call to occur before turning off polling");
+});
+
+add_task(function *() {
+  // Create a poller that returns a promise. This poll call
+  // is called immediately, and then subsequently turned off.
+  // The call to `off` should not resolve until the inflight call
+  // finishes.
+  let inflightFinished = null;
+  let pollCalls = 0;
+  let asyncPoller = new Poller(function () {
+    pollCalls++;
+    return new Promise(function (resolve, reject) {
+      setTimeout(() => {
+        inflightFinished = true;
+        resolve();
+      }, 1000);
+    });
+  }, 1, true);
+  asyncPoller.on();
+
+  yield asyncPoller.destroy();
+  ok(inflightFinished, "destroy() method does not resolve until remaining inflight poll calls finish");
+  is(pollCalls, 1, "should only be one poll call to occur before destroying polling");
+  
+  try {
+    asyncPoller.on();
+    ok(false, "Calling on() after destruction should throw");
+  } catch (e) {
+    ok(true, "Calling on() after destruction should throw");
+  }
+});
--- a/browser/devtools/shared/test/head.js
+++ b/browser/devtools/shared/test/head.js
@@ -237,8 +237,29 @@ function* openAndCloseToolbox(nbOfTimes,
 
     // We use a timeout to check the toolbox's active time
     yield new Promise(resolve => setTimeout(resolve, usageTime));
 
     info("Closing toolbox " + (i + 1));
     yield gDevTools.closeToolbox(target);
   }
 }
+
+/**
+ * Waits until a predicate returns true.
+ *
+ * @param function predicate
+ *        Invoked once in a while until it returns true.
+ * @param number interval [optional]
+ *        How often the predicate is invoked, in milliseconds.
+ */
+function waitUntil(predicate, interval = 10) {
+  if (predicate()) {
+    return Promise.resolve(true);
+  }
+  return new Promise(resolve => {
+    setTimeout(function() {
+      waitUntil(predicate).then(() => resolve(true));
+    }, interval);
+  });
+}
+
+// EventUtils just doesn't work!
--- a/browser/devtools/styleinspector/computed-view.js
+++ b/browser/devtools/styleinspector/computed-view.js
@@ -147,32 +147,34 @@ function CssHtmlTree(aStyleInspector, aP
   this.focusWindow = this.focusWindow.bind(this);
   this._onContextMenu = this._onContextMenu.bind(this);
   this._contextMenuUpdate = this._contextMenuUpdate.bind(this);
   this._onSelectAll = this._onSelectAll.bind(this);
   this._onClick = this._onClick.bind(this);
   this._onCopy = this._onCopy.bind(this);
   this._onCopyColor = this._onCopyColor.bind(this);
   this._onFilterStyles = this._onFilterStyles.bind(this);
+  this._onFilterKeyPress = this._onFilterKeyPress.bind(this);
   this._onClearSearch = this._onClearSearch.bind(this);
   this._onIncludeBrowserStyles = this._onIncludeBrowserStyles.bind(this);
   this._onFilterTextboxContextMenu = this._onFilterTextboxContextMenu.bind(this);
 
   let doc = this.styleDocument;
   this.root = doc.getElementById("root");
   this.element = doc.getElementById("propertyContainer");
   this.searchField = doc.getElementById("computedview-searchbox");
   this.searchClearButton = doc.getElementById("computedview-searchinput-clear");
   this.includeBrowserStylesCheckbox = doc.getElementById("browser-style-checkbox");
 
   this.styleDocument.addEventListener("mousedown", this.focusWindow);
   this.element.addEventListener("click", this._onClick);
   this.element.addEventListener("copy", this._onCopy);
   this.element.addEventListener("contextmenu", this._onContextMenu);
   this.searchField.addEventListener("input", this._onFilterStyles);
+  this.searchField.addEventListener("keypress", this._onFilterKeyPress);
   this.searchField.addEventListener("contextmenu", this._onFilterTextboxContextMenu);
   this.searchClearButton.addEventListener("click", this._onClearSearch);
   this.includeBrowserStylesCheckbox.addEventListener("command",
     this._onIncludeBrowserStyles);
 
   this.searchClearButton.hidden = true;
 
   // No results text.
@@ -540,16 +542,56 @@ CssHtmlTree.prototype = {
       }
 
       this.refreshPanel();
       this._filterChangeTimeout = null;
     }, filterTimeout);
   },
 
   /**
+   * Handle the search box's keypress event. If the escape key is pressed,
+   * clear the search box field.
+   */
+  _onFilterKeyPress: function(aEvent) {
+    if (aEvent.keyCode === Ci.nsIDOMKeyEvent.DOM_VK_ESCAPE &&
+        this._onClearSearch()) {
+      aEvent.preventDefault();
+      aEvent.stopPropagation();
+    }
+  },
+
+  /**
+   * Context menu handler for filter style search box.
+   */
+  _onFilterTextboxContextMenu: function(event) {
+    try {
+      this.styleDocument.defaultView.focus();
+      let contextmenu = this.inspector.toolbox.textboxContextMenuPopup;
+      contextmenu.openPopupAtScreen(event.screenX, event.screenY, true);
+    } catch(e) {
+      console.error(e);
+    }
+  },
+
+  /**
+   * Called when the user clicks on the clear button in the filter style search
+   * box. Returns true if the search box is cleared and false otherwise.
+   */
+  _onClearSearch: function() {
+    if (this.searchField.value) {
+      this.searchField.value = "";
+      this.searchField.focus();
+      this._onFilterStyles();
+      return true;
+    }
+
+    return false;
+  },
+
+  /**
    * The change event handler for the includeBrowserStyles checkbox.
    *
    * @param {Event} aEvent the DOM Event object.
    */
   _onIncludeBrowserStyles: function(aEvent)
   {
     this.refreshSourceFilter();
     this.refreshPanel();
@@ -826,39 +868,16 @@ CssHtmlTree.prototype = {
    *  Toggle the original sources pref.
    */
   _onToggleOrigSources: function()
   {
     let isEnabled = Services.prefs.getBoolPref(PREF_ORIG_SOURCES);
     Services.prefs.setBoolPref(PREF_ORIG_SOURCES, !isEnabled);
   },
 
-   /**
-   * Context menu handler for filter style search box.
-   */
-  _onFilterTextboxContextMenu: function(event) {
-    try {
-      this.styleDocument.defaultView.focus();
-      let contextmenu = this.inspector.toolbox.textboxContextMenuPopup;
-      contextmenu.openPopupAtScreen(event.screenX, event.screenY, true);
-    } catch(e) {
-      console.error(e);
-    }
-  },
-
-  /**
-   * Called when the user clicks on the clear button in the filter style search
-   * box.
-   */
-  _onClearSearch: function() {
-    this.searchField.value = "";
-    this.searchField.focus();
-    this._onFilterStyles();
-  },
-
   /**
    * Destructor for CssHtmlTree.
    */
   destroy: function CssHtmlTree_destroy()
   {
     this.viewedElement = null;
     this._outputParser = null;
 
@@ -901,16 +920,17 @@ CssHtmlTree.prototype = {
     this.highlighters.destroy();
 
     // Remove bound listeners
     this.styleDocument.removeEventListener("mousedown", this.focusWindow);
     this.element.removeEventListener("click", this._onClick);
     this.element.removeEventListener("copy", this._onCopy);
     this.element.removeEventListener("contextmenu", this._onContextMenu);
     this.searchField.removeEventListener("input", this._onFilterStyles);
+    this.searchField.removeEventListener("keypress", this._onFilterKeyPress);
     this.searchField.removeEventListener("contextmenu", this._onFilterTextboxContextMenu);
     this.searchClearButton.removeEventListener("click", this._onClearSearch);
     this.includeBrowserStylesCheckbox.removeEventListener("command",
       this.includeBrowserStylesChanged);
 
     // Nodes used in templating
     this.root = null;
     this.element = null;
--- a/browser/devtools/styleinspector/rule-view.js
+++ b/browser/devtools/styleinspector/rule-view.js
@@ -1124,28 +1124,30 @@ function CssRuleView(aInspector, aDoc, a
   this._contextMenuUpdate = this._contextMenuUpdate.bind(this);
   this._onAddRule = this._onAddRule.bind(this);
   this._onSelectAll = this._onSelectAll.bind(this);
   this._onCopy = this._onCopy.bind(this);
   this._onCopyColor = this._onCopyColor.bind(this);
   this._onToggleOrigSources = this._onToggleOrigSources.bind(this);
   this._onShowMdnDocs = this._onShowMdnDocs.bind(this);
   this._onFilterStyles = this._onFilterStyles.bind(this);
+  this._onFilterKeyPress = this._onFilterKeyPress.bind(this);
   this._onClearSearch = this._onClearSearch.bind(this);
   this._onFilterTextboxContextMenu = this._onFilterTextboxContextMenu.bind(this);
 
   this.element = this.doc.getElementById("ruleview-container");
   this.searchField = this.doc.getElementById("ruleview-searchbox");
   this.searchClearButton = this.doc.getElementById("ruleview-searchinput-clear");
 
   this.searchClearButton.hidden = true;
 
   this.element.addEventListener("copy", this._onCopy);
   this.element.addEventListener("contextmenu", this._onContextMenu);
   this.searchField.addEventListener("input", this._onFilterStyles);
+  this.searchField.addEventListener("keypress", this._onFilterKeyPress);
   this.searchField.addEventListener("contextmenu", this._onFilterTextboxContextMenu);
   this.searchClearButton.addEventListener("click", this._onClearSearch);
 
   this._handlePrefChange = this._handlePrefChange.bind(this);
   this._onSourcePrefChanged = this._onSourcePrefChanged.bind(this);
 
   this._prefObserver = new PrefObserver("devtools.");
   this._prefObserver.on(PREF_ORIG_SOURCES, this._onSourcePrefChanged);
@@ -1643,36 +1645,53 @@ CssRuleView.prototype = {
 
       this.inspector.emit("ruleview-filtered");
 
       this._filterChangeTimeout = null;
     }, filterTimeout);
   },
 
   /**
+   * Handle the search box's keypress event. If the escape key is pressed,
+   * clear the search box field.
+   */
+  _onFilterKeyPress: function(event) {
+    if (event.keyCode === Ci.nsIDOMKeyEvent.DOM_VK_ESCAPE &&
+        this._onClearSearch()) {
+      event.preventDefault();
+      event.stopPropagation();
+    }
+  },
+
+  /**
    * Context menu handler for filter style search box.
    */
   _onFilterTextboxContextMenu: function(event) {
     try {
       this.doc.defaultView.focus();
       let contextmenu = this.inspector.toolbox.textboxContextMenuPopup;
       contextmenu.openPopupAtScreen(event.screenX, event.screenY, true);
     } catch(e) {
       console.error(e);
     }
   },
 
   /**
    * Called when the user clicks on the clear button in the filter style search
-   * box.
+   * box. Returns true if the search box is cleared and false otherwise.
    */
   _onClearSearch: function() {
-    this.searchField.value = "";
-    this.searchField.focus();
-    this._onFilterStyles();
+    if (this.searchField.value) {
+      this.searchField.value = "";
+      this.searchField.focus();
+      this._onFilterStyles();
+      return true;
+    }
+
+    return false;
   },
 
   destroy: function() {
     this.isDestroyed = true;
     this.clear();
 
     gDummyPromise = null;
 
@@ -1716,16 +1735,17 @@ CssRuleView.prototype = {
 
     this.tooltips.destroy();
     this.highlighters.destroy();
 
     // Remove bound listeners
     this.element.removeEventListener("copy", this._onCopy);
     this.element.removeEventListener("contextmenu", this._onContextMenu);
     this.searchField.removeEventListener("input", this._onFilterStyles);
+    this.searchField.removeEventListener("keypress", this._onFilterKeyPress);
     this.searchField.removeEventListener("contextmenu",
       this._onFilterTextboxContextMenu);
     this.searchClearButton.removeEventListener("click", this._onClearSearch);
     this.searchField = null;
     this.searchClearButton = null;
 
     if (this.element.parentNode) {
       this.element.parentNode.removeChild(this.element);
--- a/browser/devtools/styleinspector/test/browser.ini
+++ b/browser/devtools/styleinspector/test/browser.ini
@@ -37,16 +37,17 @@ support-files =
 [browser_computedview_media-queries.js]
 [browser_computedview_no-results-placeholder.js]
 [browser_computedview_original-source-link.js]
 [browser_computedview_pseudo-element_01.js]
 [browser_computedview_refresh-on-style-change_01.js]
 [browser_computedview_search-filter.js]
 [browser_computedview_search-filter_clear.js]
 [browser_computedview_search-filter_context-menu.js]
+[browser_computedview_search-filter_escape-keypress.js]
 [browser_computedview_select-and-copy-styles.js]
 [browser_computedview_style-editor-link.js]
 [browser_ruleview_add-property-and-reselect.js]
 [browser_ruleview_add-property-cancel_01.js]
 [browser_ruleview_add-property-cancel_02.js]
 [browser_ruleview_add-property-cancel_03.js]
 [browser_ruleview_add-property_01.js]
 [browser_ruleview_add-property_02.js]
@@ -118,16 +119,17 @@ skip-if = e10s # Bug 1090340
 [browser_ruleview_search-filter_05.js]
 [browser_ruleview_search-filter_06.js]
 [browser_ruleview_search-filter_07.js]
 [browser_ruleview_search-filter_08.js]
 [browser_ruleview_search-filter_09.js]
 [browser_ruleview_search-filter_10.js]
 [browser_ruleview_search-filter_clear.js]
 [browser_ruleview_search-filter_context-menu.js]
+[browser_ruleview_search-filter_escape-keypress.js]
 [browser_ruleview_select-and-copy-styles.js]
 [browser_ruleview_selector-highlighter_01.js]
 [browser_ruleview_selector-highlighter_02.js]
 [browser_ruleview_selector-highlighter_03.js]
 [browser_ruleview_style-editor-link.js]
 skip-if = e10s # bug 1040670 Cannot open inline styles in viewSourceUtils
 [browser_ruleview_urls-clickable.js]
 [browser_ruleview_user-agent-styles.js]
new file mode 100644
--- /dev/null
+++ b/browser/devtools/styleinspector/test/browser_computedview_search-filter_escape-keypress.js
@@ -0,0 +1,71 @@
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Tests that search filter escape keypress will clear the search field.
+
+let TEST_URI = [
+  '<style type="text/css">',
+  '  .matches {',
+  '    color: #F00;',
+  '  }',
+  '</style>',
+  '<span id="matches" class="matches">Some styled text</span>'
+].join("\n");
+
+add_task(function*() {
+  yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
+  let {toolbox, inspector, view} = yield openComputedView();
+  yield selectNode("#matches", inspector);
+  yield testAddTextInFilter(inspector, view);
+  yield testEscapeKeypress(inspector, view);
+});
+
+function* testAddTextInFilter(inspector, computedView) {
+  info("Setting filter text to \"background-color\"");
+
+  let win = computedView.styleWindow;
+  let propertyViews = computedView.propertyViews;
+  let searchField = computedView.searchField;
+  let checkbox = computedView.includeBrowserStylesCheckbox;
+
+  info("Include browser styles");
+  checkbox.click();
+  yield inspector.once("computed-view-refreshed");
+
+  searchField.focus();
+  synthesizeKeys("background-color", win);
+  yield inspector.once("computed-view-refreshed");
+
+  info("Check that the correct properties are visible");
+
+  propertyViews.forEach((propView) => {
+    let name = propView.name;
+    is(propView.visible, name.indexOf("background-color") > -1,
+      "span " + name + " property visibility check");
+  });
+}
+
+function* testEscapeKeypress(inspector, computedView) {
+  info("Pressing the escape key on search filter");
+
+  let win = computedView.styleWindow;
+  let propertyViews = computedView.propertyViews;
+  let searchField = computedView.searchField;
+  let onRefreshed = inspector.once("computed-view-refreshed");
+
+  searchField.focus();
+  EventUtils.synthesizeKey("VK_ESCAPE", {}, win);
+  yield onRefreshed;
+
+  info("Check that the correct properties are visible");
+
+  ok(!searchField.value, "Search filter is cleared");
+  propertyViews.forEach((propView) => {
+    let name = propView.name;
+    is(propView.visible, true,
+      "span " + name + " property is visible");
+  });
+}
new file mode 100644
--- /dev/null
+++ b/browser/devtools/styleinspector/test/browser_ruleview_search-filter_escape-keypress.js
@@ -0,0 +1,66 @@
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Tests that the rule view search filter escape keypress will clear the search
+// field.
+
+let TEST_URI = [
+  '<style type="text/css">',
+  '  #testid {',
+  '    background-color: #00F;',
+  '  }',
+  '  .testclass {',
+  '    width: 100%;',
+  '  }',
+  '</style>',
+  '<div id="testid" class="testclass">Styled Node</div>'
+].join("\n");
+
+add_task(function*() {
+  yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
+  let {toolbox, inspector, view} = yield openRuleView();
+  yield selectNode("#testid", inspector);
+  yield testAddTextInFilter(inspector, view);
+  yield testEscapeKeypress(inspector, view);
+});
+
+function* testAddTextInFilter(inspector, ruleView) {
+  info("Setting filter text to \"00F\"");
+
+  let win = ruleView.doc.defaultView;
+  let searchField = ruleView.searchField;
+  let onRuleViewFiltered = inspector.once("ruleview-filtered");
+
+  searchField.focus();
+  synthesizeKeys("00F", win);
+  yield onRuleViewFiltered;
+
+  info("Check that the correct rules are visible");
+  is(ruleView.element.children.length, 2, "Should have 2 rules.");
+  is(getRuleViewRuleEditor(ruleView, 0).rule.selectorText, "element", "First rule is inline element.");
+  is(getRuleViewRuleEditor(ruleView, 1).rule.selectorText, "#testid", "Second rule is #testid.");
+  ok(getRuleViewRuleEditor(ruleView, 1).rule.textProps[0].editor.element.classList.contains("ruleview-highlight"),
+    "background-color text property is correctly highlighted.");
+}
+
+function* testEscapeKeypress(inspector, ruleView) {
+  info("Pressing the escape key on search filter");
+
+  let doc = ruleView.doc;
+  let win = ruleView.doc.defaultView;
+  let searchField = ruleView.searchField;
+  let onRuleViewFiltered = inspector.once("ruleview-filtered");
+
+  searchField.focus();
+  EventUtils.synthesizeKey("VK_ESCAPE", {}, win);
+  yield onRuleViewFiltered;
+
+  info("Check the search filter is cleared and no rules are highlighted");
+  is(ruleView.element.children.length, 3, "Should have 3 rules.");
+  ok(!searchField.value, "Search filter is cleared");
+  ok(!doc.querySelectorAll(".ruleview-highlight").length &&
+     !ruleView._highlightedElements.length, "No rules are higlighted");
+}
deleted file mode 100644
--- a/browser/extensions/Makefile.in
+++ /dev/null
@@ -1,32 +0,0 @@
-# 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 $(topsrcdir)/config/rules.mk
-
-exclude_files = \
-  test \
-  README.mozilla \
-  $(NULL)
-
-$(FINAL_TARGET)/chrome/pdfjs.manifest: $(GLOBAL_DEPS)
-	printf 'manifest pdfjs/chrome.manifest' > $@
-
-libs:: $(FINAL_TARGET)/chrome/pdfjs.manifest
-	$(PYTHON) $(topsrcdir)/config/nsinstall.py \
-	  $(srcdir)/pdfjs \
-          $(foreach exclude,$(exclude_files), -X $(srcdir)/pdfjs/$(exclude)) \
-          $(FINAL_TARGET)/chrome
-	$(call py_action,buildlist,$(FINAL_TARGET)/chrome.manifest 'manifest chrome/pdfjs.manifest')
-
-ifdef NIGHTLY_BUILD
-$(FINAL_TARGET)/chrome/shumway.manifest: $(GLOBAL_DEPS)
-	printf 'manifest shumway/chrome.manifest' > $@
-
-libs:: $(FINAL_TARGET)/chrome/shumway.manifest
-	$(PYTHON) $(topsrcdir)/config/nsinstall.py \
-	  $(srcdir)/shumway \
-          $(foreach exclude,$(exclude_files), -X $(srcdir)/shumway/$(exclude)) \
-          $(FINAL_TARGET)/chrome
-	$(call py_action,buildlist,$(FINAL_TARGET)/chrome.manifest 'manifest chrome/shumway.manifest')
-endif
--- a/browser/extensions/moz.build
+++ b/browser/extensions/moz.build
@@ -1,7 +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/.
 
-BROWSER_CHROME_MANIFESTS += ['pdfjs/test/browser.ini']
+DIRS += [
+    'pdfjs',
+    'shumway',
+]
new file mode 100644
--- /dev/null
+++ b/browser/extensions/pdfjs/jar.mn
@@ -0,0 +1,3 @@
+pdfjs.jar:
+% resource pdf.js %content/
+ content/	(content/*)
new file mode 100644
--- /dev/null
+++ b/browser/extensions/pdfjs/moz.build
@@ -0,0 +1,9 @@
+# -*- 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/.
+
+BROWSER_CHROME_MANIFESTS += ['test/browser.ini']
+
+JAR_MANIFESTS += ['jar.mn']
new file mode 100644
--- /dev/null
+++ b/browser/extensions/shumway/jar.mn
@@ -0,0 +1,5 @@
+shumway.jar:
+% content shumway %chrome/
+% resource shumway %content/
+ chrome/	(chrome/*)
+ content/	(content/*)
new file mode 100644
--- /dev/null
+++ b/browser/extensions/shumway/moz.build
@@ -0,0 +1,7 @@
+# -*- 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/.
+
+JAR_MANIFESTS += ['jar.mn']
--- a/browser/installer/windows/nsis/installer.nsi
+++ b/browser/installer/windows/nsis/installer.nsi
@@ -790,43 +790,34 @@ Function LaunchApp
 !ifndef DEV_EDITION
   ${ManualCloseAppPrompt} "${WindowClass}" "$(WARN_MANUALLY_CLOSE_APP_LAUNCH)"
 !endif
 
   ClearErrors
   ${GetParameters} $0
   ${GetOptions} "$0" "/UAC:" $1
   ${If} ${Errors}
-    StrCpy $1 "0"
-    StrCpy $2 "0"
-    ${If} $1 == "1"
-      Exec "$\"$INSTDIR\${FileMainEXE}$\""
-    ${EndIf}
+    Exec "$\"$INSTDIR\${FileMainEXE}$\""
   ${Else}
     GetFunctionAddress $0 LaunchAppFromElevatedProcess
     UAC::ExecCodeSegment $0
   ${EndIf}
 FunctionEnd
 
 Function LaunchAppFromElevatedProcess
   ; Find the installation directory when launching using GetFunctionAddress
   ; from an elevated installer since $INSTDIR will not be set in this installer
   ${StrFilter} "${FileMainEXE}" "+" "" "" $R9
   ReadRegStr $0 HKLM "Software\Clients\StartMenuInternet\$R9\DefaultIcon" ""
   ${GetPathFromString} "$0" $0
   ${GetParent} "$0" $1
   ; Set our current working directory to the application's install directory
   ; otherwise the 7-Zip temp directory will be in use and won't be deleted.
   SetOutPath "$1"
-  StrCpy $2 "0"
-  StrCpy $3 "0"
-  ${If} $2 == "1"
-    ; Launch into desktop
-    Exec "$\"$0$\""
-  ${EndIf}
+  Exec "$\"$0$\""
 FunctionEnd
 
 ################################################################################
 # Language
 
 !insertmacro MOZ_MUI_LANGUAGE 'baseLocale'
 !verbose push
 !verbose 3
--- a/browser/modules/ContentCrashReporters.jsm
+++ b/browser/modules/ContentCrashReporters.jsm
@@ -109,37 +109,78 @@ this.PluginCrashReporter = {
     if (this.initialized) {
       return;
     }
 
     this.initialized = true;
     this.crashReports = new Map();
 
     Services.obs.addObserver(this, "plugin-crashed", false);
+    Services.obs.addObserver(this, "gmp-plugin-crash", false);
+    Services.obs.addObserver(this, "profile-after-change", false);
+  },
+
+  uninit() {
+    Services.obs.removeObserver(this, "plugin-crashed", false);
+    Services.obs.removeObserver(this, "gmp-plugin-crash", false);
+    Services.obs.removeObserver(this, "profile-after-change", false);
+    this.initialized = false;
   },
 
   observe(subject, topic, data) {
-    if (topic != "plugin-crashed") {
-      return;
-    }
+    switch(topic) {
+      case "plugin-crashed": {
+        let propertyBag = subject;
+        if (!(propertyBag instanceof Ci.nsIPropertyBag2) ||
+            !(propertyBag instanceof Ci.nsIWritablePropertyBag2) ||
+            !propertyBag.hasKey("runID") ||
+            !propertyBag.hasKey("pluginDumpID")) {
+          Cu.reportError("PluginCrashReporter can not read plugin information.");
+          return;
+        }
 
-    let propertyBag = subject;
-    if (!(propertyBag instanceof Ci.nsIPropertyBag2) ||
-        !(propertyBag instanceof Ci.nsIWritablePropertyBag2) ||
-        !propertyBag.hasKey("runID") ||
-        !propertyBag.hasKey("pluginName")) {
-      Cu.reportError("PluginCrashReporter can not read plugin information.");
-      return;
-    }
+        let runID = propertyBag.getPropertyAsUint32("runID");
+        let pluginDumpID = propertyBag.getPropertyAsAString("pluginDumpID");
+        let browserDumpID = propertyBag.getPropertyAsAString("browserDumpID");
+        if (pluginDumpID) {
+          this.crashReports.set(runID, { pluginDumpID, browserDumpID });
+        }
+        break;
+      }
+      case "gmp-plugin-crash": {
+        let propertyBag = subject;
+        if (!(propertyBag instanceof Ci.nsIWritablePropertyBag2) ||
+            !propertyBag.hasKey("pluginID") ||
+            !propertyBag.hasKey("pluginDumpID") ||
+            !propertyBag.hasKey("pluginName")) {
+          Cu.reportError("PluginCrashReporter can not read plugin information.");
+          return;
+        }
 
-    let runID = propertyBag.getPropertyAsUint32("runID");
-    let pluginDumpID = propertyBag.getPropertyAsAString("pluginDumpID");
-    let browserDumpID = propertyBag.getPropertyAsAString("browserDumpID");
-    if (pluginDumpID) {
-      this.crashReports.set(runID, { pluginDumpID, browserDumpID });
+        let pluginID = propertyBag.getPropertyAsUint32("pluginID");
+        let pluginDumpID = propertyBag.getPropertyAsAString("pluginDumpID");
+        if (pluginDumpID) {
+          this.crashReports.set(pluginID, { pluginDumpID });
+        }
+
+        // Only the parent process gets the gmp-plugin-crash observer
+        // notification, so we need to inform any content processes that
+        // the GMP has crashed.
+        if (Cc["@mozilla.org/parentprocessmessagemanager;1"]) {
+          let pluginName = propertyBag.getPropertyAsAString("pluginName");
+          let mm = Cc["@mozilla.org/parentprocessmessagemanager;1"]
+            .getService(Ci.nsIMessageListenerManager);
+          mm.broadcastAsyncMessage("gmp-plugin-crash",
+                                   { pluginName, pluginID });
+        }
+        break;
+      }
+      case "profile-after-change":
+        this.uninit();
+        break;
     }
   },
 
   /**
    * Submit a crash report for a crashed NPAPI plugin.
    *
    * @param runID
    *        The runID of the plugin that crashed. A run ID is a unique
@@ -189,19 +230,9 @@ this.PluginCrashReporter = {
       mm.broadcastAsyncMessage("BrowserPlugins:CrashReportSubmitted",
                                { runID, state });
     }
   },
 
   hasCrashReport(runID) {
     return this.crashReports.has(runID);
   },
-
-  /**
-   * Deprecated mechanism for sending crash reports for GMPs. This
-   * should be removed when bug 1146955 is fixed.
-   */
-  submitGMPCrashReport(pluginDumpID, browserDumpID) {
-    CrashSubmit.submit(pluginDumpID, { recordSubmission: true });
-    if (browserDumpID)
-      CrashSubmit.submit(browserDumpID);
-  },
 };
--- a/browser/modules/PluginContent.jsm
+++ b/browser/modules/PluginContent.jsm
@@ -1026,18 +1026,18 @@ PluginContent.prototype = {
       // notification bar, then remove it because this plugin instance it big
       // enough to serve as in-content notification.
       this.hideNotificationBar("plugin-crashed");
       doc.mozNoPluginCrashedNotification = true;
     } else {
       // If another plugin on the page was large enough to show our UI, we don't
       // want to show a notification bar.
       if (!doc.mozNoPluginCrashedNotification) {
-        this.global.sendAsyncMessage("PluginContent:ShowNPAPIPluginCrashedNotification",
-                                     { message, runID });
+        this.global.sendAsyncMessage("PluginContent:ShowPluginCrashedNotification",
+                                     { messageString: message, pluginID: runID });
         // Remove the notification when the page is reloaded.
         doc.defaultView.top.addEventListener("unload", event => {
           this.hideNotificationBar("plugin-crashed");
         }, false);
       }
     }
   },
 
@@ -1052,42 +1052,33 @@ PluginContent.prototype = {
       if (plugin instanceof Ci.nsIObjectLoadingContent &&
           plugin.runID == runID) {
         let statusDiv = this.getPluginUI(plugin, "submitStatus");
         statusDiv.setAttribute("status", state);
       }
     }
   },
 
-  /**
-   * Currently, GMP crash events are only handled in the non-e10s case.
-   * e10s support for GMP crash events is being tracked in bug 1146955.
-   */
   GMPCrashed: function(aEvent) {
     let target          = aEvent.target;
-    let submittedReport = aEvent.submittedCrashReport;
     let pluginName      = aEvent.pluginName;
-    let pluginDumpID    = aEvent.pluginDumpID;
-    let browserDumpID   = aEvent.browserDumpID;
     let gmpPlugin       = aEvent.gmpPlugin;
+    let pluginID        = aEvent.pluginID;
     let doc             = target.document;
 
     if (!gmpPlugin || !doc) {
       // TODO: Throw exception? How did we get here?
       return;
     }
 
     let messageString =
       gNavigatorBundle.formatStringFromName("crashedpluginsMessage.title",
                                             [pluginName], 1);
 
-    this.global.sendAsyncMessage("PluginContent:ShowGMPCrashedNotification", {
-      messageString: messageString,
-      pluginDumpID: pluginDumpID,
-      browserDumpID: browserDumpID,
-    });
+    this.global.sendAsyncMessage("PluginContent:ShowPluginCrashedNotification",
+                                 { messageString, pluginID });
 
     // Remove the notification when the page is reloaded.
     doc.defaultView.top.addEventListener("unload", event => {
       this.hideNotificationBar("plugin-crashed");
     }, false);
   },
 };
--- a/browser/themes/shared/devtools/animationinspector.css
+++ b/browser/themes/shared/devtools/animationinspector.css
@@ -94,16 +94,46 @@ body {
     background-image: url("debugger-pause@2x.png");
   }
 
   #toggle-all.paused::before {
     background-image: url("debugger-play@2x.png");
   }
 }
 
+/* Animation target node gutter, contains a preview of the dom node */
+
+.animation-target {
+  background-color: var(--theme-toolbar-background);
+  padding: 1px 4px;
+  box-sizing: border-box;
+  overflow: hidden;
+  text-overflow: ellipsis;
+  white-space: nowrap;
+}
+
+.animation-target .attribute-name {
+  padding-left: 4px;
+}
+
+.animation-target .node-selector {
+  background: url("chrome://browser/skin/devtools/vview-open-inspector.png") no-repeat 0 0;
+  padding-left: 16px;
+  margin-right: 5px;
+  cursor: pointer;
+}
+
+.animation-target .node-selector:hover {
+  background-position: -32px 0;
+}
+
+.animation-target .node-selector:active {
+  background-position: -16px 0;
+}
+
 /* Animation title gutter, contains the name, duration, iteration */
 
 .animation-title {
   background-color: var(--theme-toolbar-background);
   border-bottom: 1px solid var(--theme-splitter-color);
   padding: 1px 4px;
   word-wrap: break-word;
   overflow: auto;
--- a/build/docs/jar-manifests.rst
+++ b/build/docs/jar-manifests.rst
@@ -54,16 +54,25 @@ equivalent.
 There is a special source-directory format for localized files (note the
 percent sign in the source file location): this format reads ``localized.dtd``
 from the ``en-US`` directory if building an English version, and reads the
 file from the alternate localization source tree
 ``/l10n/<locale>/path/localized.dtd`` if building a localized version::
 
    locale/path/localized.dtd     (%localized/path/localized.dtd)
 
+The source tree location can also use wildcards, in which case the path in
+jar is expected to be a base directory. Paths before the wildcard are not
+made part of the destination path::
+
+     path/in/jar/                (source/tree/location/*.xul)
+
+The above will install all xul files under ``source/tree/location`` as
+``path/in/jar/*.xul``.
+
 Register Chrome
 ===============
 
 `Chrome Registration <https://developer.mozilla.org/en-US/docs/Chrome_Registration>`_
 instructions are marked with a percent sign (``%``) at the beginning of the
 line, and must be part of the definition of a JAR file. Any additional percents
 signs are replaced with an appropriate relative URL of the JAR file being
 packaged::
--- a/build/mobile/remoteautomation.py
+++ b/build/mobile/remoteautomation.py
@@ -274,18 +274,19 @@ class RemoteAutomation(Automation):
                     self.proc = stdout
                 else:
                     raise Exception("unable to launch process")
             self.procName = cmd[0].split('/')[-1]
             if cmd[0] == 'am' and cmd[1] == "instrument":
                 self.procName = app
                 print "Robocop process name: "+self.procName
 
-            # Setting timeout at 1 hour since on a remote device this takes much longer
-            self.timeout = 3600
+            # Setting timeout at 1 hour since on a remote device this takes much longer.
+            # Temporarily increased to 75 minutes because no more chunks can be created.
+            self.timeout = 4500
             # The benefit of the following sleep is unclear; it was formerly 15 seconds
             time.sleep(1)
 
             # Used to buffer log messages until we meet a line break
             self.logBuffer = ""
 
         @property
         def pid(self):
--- a/configure.in
+++ b/configure.in
@@ -3572,17 +3572,17 @@ dnl = If NSS was not detected in the sys
 dnl = use the one in the source tree (mozilla/security/nss)
 dnl ========================================================
 
 MOZ_ARG_WITH_BOOL(system-nss,
 [  --with-system-nss       Use system installed NSS],
     _USE_SYSTEM_NSS=1 )
 
 if test -n "$_USE_SYSTEM_NSS"; then
-    AM_PATH_NSS(3.18, [MOZ_NATIVE_NSS=1], [AC_MSG_ERROR([you don't have NSS installed or your version is too old])])
+    AM_PATH_NSS(3.19, [MOZ_NATIVE_NSS=1], [AC_MSG_ERROR([you don't have NSS installed or your version is too old])])
 fi
 
 if test -n "$MOZ_NATIVE_NSS"; then
    NSS_LIBS="$NSS_LIBS -lcrmf"
 else
    NSS_CFLAGS='-I$(LIBXUL_DIST)/include/nss'
 
    if test -z "$GNU_CC" -a "$OS_ARCH" = "WINNT"; then
--- a/dom/base/Element.cpp
+++ b/dom/base/Element.cpp
@@ -134,16 +134,17 @@
 #include "nsITextControlElement.h"
 #include "nsITextControlFrame.h"
 #include "nsISupportsImpl.h"
 #include "mozilla/dom/DocumentFragment.h"
 #include "mozilla/IntegerPrintfMacros.h"
 #include "mozilla/dom/WindowBinding.h"
 #include "mozilla/dom/ElementBinding.h"
 #include "mozilla/dom/VRDevice.h"
+#include "nsComputedDOMStyle.h"
 
 using namespace mozilla;
 using namespace mozilla::dom;
 
 nsIAtom*
 nsIContent::DoGetID() const
 {
   MOZ_ASSERT(HasID(), "Unexpected call");
@@ -380,29 +381,26 @@ Element::GetBindingURL(nsIDocument *aDoc
   // If we have a frame the frame has already loaded the binding.  And
   // otherwise, don't do anything else here unless we're dealing with
   // XUL or an HTML element that may have a plugin-related overlay
   // (i.e. object, embed, or applet).
   bool isXULorPluginElement = (IsXULElement() ||
                                IsHTMLElement(nsGkAtoms::object) ||
                                IsHTMLElement(nsGkAtoms::embed) ||
                                IsHTMLElement(nsGkAtoms::applet));
-  nsIPresShell *shell = aDocument->GetShell();
+  nsCOMPtr<nsIPresShell> shell = aDocument->GetShell();
   if (!shell || GetPrimaryFrame() || !isXULorPluginElement) {
     *aResult = nullptr;
 
     return true;
   }
 
   // Get the computed -moz-binding directly from the style context
-  nsPresContext *pctx = shell->GetPresContext();
-  NS_ENSURE_TRUE(pctx, false);
-
-  nsRefPtr<nsStyleContext> sc = pctx->StyleSet()->ResolveStyleFor(this,
-                                                                  nullptr);
+  nsRefPtr<nsStyleContext> sc =
+    nsComputedDOMStyle::GetStyleContextForElementNoFlush(this, nullptr, shell);
   NS_ENSURE_TRUE(sc, false);
 
   *aResult = sc->StyleDisplay()->mBinding;
 
   return true;
 }
 
 JSObject*
--- a/dom/bindings/Codegen.py
+++ b/dom/bindings/Codegen.py
@@ -10393,17 +10393,17 @@ class CGDOMJSProxyHandler_getOwnPropDesc
             namedGet = ""
 
         return fill(
             """
             bool isXray = xpc::WrapperFactory::IsXrayWrapper(proxy);
             $*{getIndexed}
             JS::Rooted<JSObject*> expando(cx);
             if (!isXray && (expando = GetExpandoObject(proxy))) {
-              if (!JS_GetPropertyDescriptorById(cx, expando, id, desc)) {
+              if (!JS_GetOwnPropertyDescriptorById(cx, expando, id, desc)) {
                 return false;
               }
               if (desc.object()) {
                 // Pretend the property lives on the wrapper.
                 desc.object().set(proxy);
                 return true;
               }
             }
--- a/dom/bindings/DOMJSProxyHandler.cpp
+++ b/dom/bindings/DOMJSProxyHandler.cpp
@@ -205,35 +205,22 @@ DOMProxyHandler::set(JSContext *cx, Hand
     return false;
   }
   if (done) {
     return result.succeed();
   }
 
   // Make sure to ignore our named properties when checking for own
   // property descriptors for a set.
-  JS::Rooted<JSPropertyDescriptor> desc(cx);
+  JS::Rooted<JSPropertyDescriptor> ownDesc(cx);
   if (!getOwnPropDescriptor(cx, proxy, id, /* ignoreNamedProps = */ true,
-                            &desc)) {
+                            &ownDesc)) {
     return false;
   }
-  if (!desc.object()) {
-    // Don't just use getPropertyDescriptor, unlike BaseProxyHandler::set,
-    // because that would call getOwnPropertyDescriptor on ourselves.  Instead,
-    // directly delegate to the proto, if any.
-    JS::Rooted<JSObject*> proto(cx);
-    if (!js::GetObjectProto(cx, proxy, &proto)) {
-      return false;
-    }
-    if (proto && !JS_GetPropertyDescriptorById(cx, proto, id, &desc)) {
-      return false;
-    }
-  }
-
-  return js::SetPropertyIgnoringNamedGetter(cx, proxy, id, v, receiver, desc, result);
+  return js::SetPropertyIgnoringNamedGetter(cx, proxy, id, v, receiver, ownDesc, result);
 }
 
 bool
 DOMProxyHandler::delete_(JSContext* cx, JS::Handle<JSObject*> proxy,
                          JS::Handle<jsid> id, JS::ObjectOpResult &result) const
 {
   JS::Rooted<JSObject*> expando(cx);
   if (!xpc::WrapperFactory::IsXrayWrapper(proxy) && (expando = GetExpandoObject(proxy))) {
--- a/dom/cache/TypeUtils.cpp
+++ b/dom/cache/TypeUtils.cpp
@@ -395,17 +395,18 @@ TypeUtils::ProcessURL(nsAString& aUrl, b
   aRv = urlParser->ParseURL(url, flatURL.Length(), &schemePos, &schemeLen,
                             nullptr, nullptr,       // ignore authority
                             &pathPos, &pathLen);
   if (NS_WARN_IF(aRv.Failed())) { return; }
 
   if (aSchemeValidOut) {
     nsAutoCString scheme(Substring(flatURL, schemePos, schemeLen));
     *aSchemeValidOut = scheme.LowerCaseEqualsLiteral("http") ||
-                       scheme.LowerCaseEqualsLiteral("https");
+                       scheme.LowerCaseEqualsLiteral("https") ||
+                       scheme.LowerCaseEqualsLiteral("app");
   }
 
   uint32_t queryPos;
   int32_t queryLen;
   uint32_t refPos;
   int32_t refLen;
 
   aRv = urlParser->ParsePath(url + pathPos, flatURL.Length() - pathPos,
--- a/dom/media/MediaDecoder.cpp
+++ b/dom/media/MediaDecoder.cpp
@@ -285,20 +285,17 @@ void MediaDecoder::Pause()
   }
 
   ChangeState(PLAY_STATE_PAUSED);
 }
 
 void MediaDecoder::SetVolume(double aVolume)
 {
   MOZ_ASSERT(NS_IsMainThread());
-  mInitialVolume = aVolume;
-  if (mDecoderStateMachine) {
-    mDecoderStateMachine->SetVolume(aVolume);
-  }
+  mVolume = aVolume;
 }
 
 void MediaDecoder::ConnectDecodedStreamToOutputStream(OutputStreamData* aStream)
 {
   NS_ASSERTION(!aStream->mPort, "Already connected?");
 
   // The output stream must stay in sync with the decoded stream, so if
   // either stream is blocked, we block the other.
@@ -533,17 +530,17 @@ void MediaDecoder::AddOutputStream(Proce
                                    bool aFinishWhenEnded)
 {
   MOZ_ASSERT(NS_IsMainThread());
   DECODER_LOG("AddOutputStream aStream=%p!", aStream);
 
   {
     ReentrantMonitorAutoEnter mon(GetReentrantMonitor());
     if (mDecoderStateMachine) {
-      mDecoderStateMachine->SetAudioCaptured();
+      mDecoderStateMachine->DispatchAudioCaptured();
     }
     if (!GetDecodedStream()) {
       int64_t t = mDecoderStateMachine ?
                   mDecoderStateMachine->GetCurrentTimeUs() : 0;
       RecreateDecodedStream(t, aStream->Graph());
     }
     OutputStreamData* os = mOutputStreams.AppendElement();
     os->Init(this, aStream);
@@ -597,19 +594,19 @@ bool MediaDecoder::IsInfinite()
 MediaDecoder::MediaDecoder() :
   mWatchManager(this, AbstractThread::MainThread()),
   mNextFrameStatus(AbstractThread::MainThread(),
                    MediaDecoderOwner::NEXT_FRAME_UNINITIALIZED,
                    "MediaDecoder::mNextFrameStatus (Mirror)"),
   mDecoderPosition(0),
   mPlaybackPosition(0),
   mCurrentTime(0.0),
-  mInitialVolume(0.0),
-  mInitialPlaybackRate(1.0),
-  mInitialPreservesPitch(true),
+  mVolume(AbstractThread::MainThread(), 0.0, "MediaDecoder::mVolume (Canonical)"),
+  mPlaybackRate(AbstractThread::MainThread(), 1.0, "MediaDecoder::mPlaybackRate (Canonical)"),
+  mPreservesPitch(AbstractThread::MainThread(), true, "MediaDecoder::mPreservesPitch (Canonical)"),
   mDuration(-1),
   mMediaSeekable(true),
   mSameOriginMedia(false),
   mReentrantMonitor("media.decoder"),
   mPlayState(AbstractThread::MainThread(), PLAY_STATE_LOADING,
              "MediaDecoder::mPlayState (Canonical)"),
   mNextState(AbstractThread::MainThread(), PLAY_STATE_PAUSED,
              "MediaDecoder::mNextState (Canonical)"),
@@ -745,31 +742,33 @@ nsresult MediaDecoder::InitializeStateMa
 
   return ScheduleStateMachineThread();
 }
 
 void MediaDecoder::SetStateMachineParameters()
 {
   ReentrantMonitorAutoEnter mon(GetReentrantMonitor());
   mDecoderStateMachine->SetDuration(mDuration);
-  mDecoderStateMachine->SetVolume(mInitialVolume);
   if (GetDecodedStream()) {
-    mDecoderStateMachine->SetAudioCaptured();
+    mDecoderStateMachine->DispatchAudioCaptured();
   }
-  SetPlaybackRate(mInitialPlaybackRate);
-  mDecoderStateMachine->SetPreservesPitch(mInitialPreservesPitch);
   if (mMinimizePreroll) {
-    mDecoderStateMachine->SetMinimizePrerollUntilPlaybackStarts();
+    mDecoderStateMachine->DispatchMinimizePrerollUntilPlaybackStarts();
   }
 }
 
 void MediaDecoder::SetMinimizePrerollUntilPlaybackStarts()
 {
+  DECODER_LOG("SetMinimizePrerollUntilPlaybackStarts()");
   MOZ_ASSERT(NS_IsMainThread());
   mMinimizePreroll = true;
+
+  // This needs to be called before we init the state machine, otherwise it will
+  // have no effect.
+  MOZ_DIAGNOSTIC_ASSERT(!mDecoderStateMachine);
 }
 
 nsresult MediaDecoder::ScheduleStateMachineThread()
 {
   MOZ_ASSERT(NS_IsMainThread());
   NS_ASSERTION(mDecoderStateMachine,
                "Must have state machine to start state machine thread");
   NS_ENSURE_STATE(mDecoderStateMachine);
@@ -1545,45 +1544,34 @@ void MediaDecoder::UpdatePlaybackOffset(
 
 bool MediaDecoder::OnStateMachineTaskQueue() const
 {
   return mDecoderStateMachine->OnTaskQueue();
 }
 
 void MediaDecoder::SetPlaybackRate(double aPlaybackRate)
 {
-  if (aPlaybackRate == 0.0) {
+  mPlaybackRate = aPlaybackRate;
+  if (mPlaybackRate == 0.0) {
     mPausedForPlaybackRateNull = true;
-    mInitialPlaybackRate = aPlaybackRate;
     Pause();
-    return;
   } else if (mPausedForPlaybackRateNull) {
     // Play() uses mPausedForPlaybackRateNull value, so must reset it first
     mPausedForPlaybackRateNull = false;
     // If the playbackRate is no longer null, restart the playback, iff the
     // media was playing.
     if (mOwner && !mOwner->GetPaused()) {
       Play();
     }
   }
-
-  if (mDecoderStateMachine) {
-    mDecoderStateMachine->SetPlaybackRate(aPlaybackRate);
-  } else {
-    mInitialPlaybackRate = aPlaybackRate;
-  }
 }
 
 void MediaDecoder::SetPreservesPitch(bool aPreservesPitch)
 {
-  if (mDecoderStateMachine) {
-    mDecoderStateMachine->SetPreservesPitch(aPreservesPitch);
-  } else {
-    mInitialPreservesPitch = aPreservesPitch;
-  }
+  mPreservesPitch = aPreservesPitch;
 }
 
 bool MediaDecoder::OnDecodeTaskQueue() const {
   NS_WARN_IF_FALSE(mDecoderStateMachine, "mDecoderStateMachine is null");
   return mDecoderStateMachine ? mDecoderStateMachine->OnDecodeTaskQueue() : false;
 }
 
 void
--- a/dom/media/MediaDecoder.h
+++ b/dom/media/MediaDecoder.h
@@ -1074,24 +1074,32 @@ protected:
   int64_t mPlaybackPosition;
 
   // The current playback position of the media resource in units of
   // seconds. This is updated approximately at the framerate of the
   // video (if it is a video) or the callback period of the audio.
   // It is read and written from the main thread only.
   double mCurrentTime;
 
-  // Volume that playback should start at.  0.0 = muted. 1.0 = full
-  // volume.  Readable/Writeable from the main thread.
-  double mInitialVolume;
+  // Volume of playback.  0.0 = muted. 1.0 = full volume.
+  Canonical<double> mVolume;
+public:
+  AbstractCanonical<double>* CanonicalVolume() { return &mVolume; }
+protected:
 
   // PlaybackRate and pitch preservation status we should start at.
-  // Readable/Writeable from the main thread.
-  double mInitialPlaybackRate;
-  bool mInitialPreservesPitch;
+  Canonical<double> mPlaybackRate;
+public:
+  AbstractCanonical<double>* CanonicalPlaybackRate() { return &mPlaybackRate; }
+protected:
+
+  Canonical<bool> mPreservesPitch;
+public:
+  AbstractCanonical<bool>* CanonicalPreservesPitch() { return &mPreservesPitch; }
+protected:
 
   // Duration of the media resource. Set to -1 if unknown.
   // Set when the metadata is loaded. Accessed on the main thread
   // only.
   int64_t mDuration;
 
   // True if the media is seekable (i.e. supports random access).
   bool mMediaSeekable;
--- a/dom/media/MediaDecoderStateMachine.cpp
+++ b/dom/media/MediaDecoderStateMachine.cpp
@@ -221,19 +221,20 @@ MediaDecoderStateMachine::MediaDecoderSt
   mFragmentEndTime(-1),
   mReader(aReader),
   mCurrentFrameTime(0),
   mAudioStartTime(-1),
   mAudioEndTime(-1),
   mDecodedAudioEndTime(-1),
   mVideoFrameEndTime(-1),
   mDecodedVideoEndTime(-1),
-  mVolume(1.0),
+  mVolume(mTaskQueue, 1.0, "MediaDecoderStateMachine::mVolume (Mirror)"),
   mPlaybackRate(1.0),
-  mPreservesPitch(true),
+  mLogicalPlaybackRate(mTaskQueue, 1.0, "MediaDecoderStateMachine::mLogicalPlaybackRate (Mirror)"),
+  mPreservesPitch(mTaskQueue, true, "MediaDecoderStateMachine::mPreservesPitch (Mirror)"),
   mLowAudioThresholdUsecs(detail::LOW_AUDIO_USECS),
   mAmpleAudioThresholdUsecs(detail::AMPLE_AUDIO_USECS),
   mQuickBufferingLowDataThresholdUsecs(detail::QUICK_BUFFERING_LOW_DATA_USECS),
   mIsAudioPrerolling(false),
   mIsVideoPrerolling(false),
   mAudioCaptured(false),
   mPositionChangeQueued(false),
   mAudioCompleted(false, "MediaDecoderStateMachine::mAudioCompleted"),
@@ -302,37 +303,46 @@ MediaDecoderStateMachine::~MediaDecoderS
 void
 MediaDecoderStateMachine::InitializationTask()
 {
   MOZ_ASSERT(OnTaskQueue());
 
   // Connect mirrors.
   mPlayState.Connect(mDecoder->CanonicalPlayState());
   mNextPlayState.Connect(mDecoder->CanonicalNextPlayState());
+  mVolume.Connect(mDecoder->CanonicalVolume());
+  mLogicalPlaybackRate.Connect(mDecoder->CanonicalPlaybackRate());
+  mPreservesPitch.Connect(mDecoder->CanonicalPreservesPitch());
 
   // Initialize watchers.
   mWatchManager.Watch(mState, &MediaDecoderStateMachine::UpdateNextFrameStatus);
   mWatchManager.Watch(mAudioCompleted, &MediaDecoderStateMachine::UpdateNextFrameStatus);
+  mWatchManager.Watch(mVolume, &MediaDecoderStateMachine::VolumeChanged);
+  mWatchManager.Watch(mLogicalPlaybackRate, &MediaDecoderStateMachine::LogicalPlaybackRateChanged);
+  mWatchManager.Watch(mPreservesPitch, &MediaDecoderStateMachine::PreservesPitchChanged);
+
 }
 
 bool MediaDecoderStateMachine::HasFutureAudio() {
+  MOZ_ASSERT(OnTaskQueue());
   AssertCurrentThreadInMonitor();
   NS_ASSERTION(HasAudio(), "Should only call HasFutureAudio() when we have audio");
   // We've got audio ready to play if:
   // 1. We've not completed playback of audio, and
   // 2. we either have more than the threshold of decoded audio available, or
   //    we've completely decoded all audio (but not finished playing it yet
   //    as per 1).
   return !mAudioCompleted &&
          (AudioDecodedUsecs() >
             mLowAudioThresholdUsecs * mPlaybackRate ||
           AudioQueue().IsFinished());
 }
 
 bool MediaDecoderStateMachine::HaveNextFrameData() {
+  MOZ_ASSERT(OnTaskQueue());
   AssertCurrentThreadInMonitor();
   return (!HasAudio() || HasFutureAudio()) &&
          (!HasVideo() || VideoQueue().GetSize() > 0);
 }
 
 int64_t MediaDecoderStateMachine::GetDecodedAudioDuration() {
   MOZ_ASSERT(OnTaskQueue());
   AssertCurrentThreadInMonitor();
@@ -562,26 +572,28 @@ void MediaDecoderStateMachine::SendStrea
   if (finished && AudioQueue().GetSize() == 0) {
     mAudioCompleted = true;
   }
 }
 
 MediaDecoderStateMachine::WakeDecoderRunnable*
 MediaDecoderStateMachine::GetWakeDecoderRunnable()
 {
+  MOZ_ASSERT(OnTaskQueue());
   AssertCurrentThreadInMonitor();
 
   if (!mPendingWakeDecoder.get()) {
     mPendingWakeDecoder = new WakeDecoderRunnable(this);
   }
   return mPendingWakeDecoder.get();
 }
 
 bool MediaDecoderStateMachine::HaveEnoughDecodedAudio(int64_t aAmpleAudioUSecs)
 {
+  MOZ_ASSERT(OnTaskQueue());
   AssertCurrentThreadInMonitor();
 
   if (AudioQueue().GetSize() == 0 ||
       GetDecodedAudioDuration() < aAmpleAudioUSecs) {
     return false;
   }
   if (!mAudioCaptured) {
     return true;
@@ -599,16 +611,17 @@ bool MediaDecoderStateMachine::HaveEnoug
         TaskQueue(), GetWakeDecoderRunnable());
   }
 
   return true;
 }
 
 bool MediaDecoderStateMachine::HaveEnoughDecodedVideo()
 {
+  MOZ_ASSERT(OnTaskQueue());
   AssertCurrentThreadInMonitor();
 
   if (static_cast<uint32_t>(VideoQueue().GetSize()) < GetAmpleVideoFrames() * mPlaybackRate) {
     return false;
   }
 
   DecodedStreamData* stream = mDecoder->GetDecodedStream();
 
@@ -623,27 +636,29 @@ bool MediaDecoderStateMachine::HaveEnoug
   }
 
   return true;
 }
 
 bool
 MediaDecoderStateMachine::NeedToDecodeVideo()
 {
+  MOZ_ASSERT(OnTaskQueue());
   AssertCurrentThreadInMonitor();
   return IsVideoDecoding() &&
          ((mState == DECODER_STATE_SEEKING && mDecodeToSeekTarget) ||
           (mState == DECODER_STATE_DECODING_FIRSTFRAME &&
            IsVideoDecoding() && VideoQueue().GetSize() == 0) ||
           (!mMinimizePreroll && !HaveEnoughDecodedVideo()));
 }
 
 bool
 MediaDecoderStateMachine::NeedToSkipToNextKeyframe()
 {
+  MOZ_ASSERT(OnTaskQueue());
   AssertCurrentThreadInMonitor();
   if (mState == DECODER_STATE_DECODING_FIRSTFRAME) {
     return false;
   }
   MOZ_ASSERT(mState == DECODER_STATE_DECODING ||
              mState == DECODER_STATE_BUFFERING ||
              mState == DECODER_STATE_SEEKING);
 
@@ -683,16 +698,17 @@ MediaDecoderStateMachine::NeedToSkipToNe
   }
 
   return false;
 }
 
 bool
 MediaDecoderStateMachine::NeedToDecodeAudio()
 {
+  MOZ_ASSERT(OnTaskQueue());
   AssertCurrentThreadInMonitor();
   SAMPLE_LOG("NeedToDecodeAudio() isDec=%d decToTar=%d minPrl=%d seek=%d enufAud=%d",
              IsAudioDecoding(), mDecodeToSeekTarget, mMinimizePreroll,
              mState == DECODER_STATE_SEEKING,
              HaveEnoughDecodedAudio(mAmpleAudioThresholdUsecs * mPlaybackRate));
 
   return IsAudioDecoding() &&
          ((mState == DECODER_STATE_SEEKING && mDecodeToSeekTarget) ||
@@ -701,29 +717,31 @@ MediaDecoderStateMachine::NeedToDecodeAu
           (!mMinimizePreroll &&
           !HaveEnoughDecodedAudio(mAmpleAudioThresholdUsecs * mPlaybackRate) &&
           (mState != DECODER_STATE_SEEKING || mDecodeToSeekTarget)));
 }
 
 bool
 MediaDecoderStateMachine::IsAudioSeekComplete()
 {
+  MOZ_ASSERT(OnTaskQueue());
   AssertCurrentThreadInMonitor();
   SAMPLE_LOG("IsAudioSeekComplete() curTarVal=%d mAudDis=%d aqFin=%d aqSz=%d",
     mCurrentSeek.Exists(), mDropAudioUntilNextDiscontinuity, AudioQueue().IsFinished(), AudioQueue().GetSize());
   return
     !HasAudio() ||
     (mCurrentSeek.Exists() &&
      !mDropAudioUntilNextDiscontinuity &&
      (AudioQueue().IsFinished() || AudioQueue().GetSize() > 0));
 }
 
 bool
 MediaDecoderStateMachine::IsVideoSeekComplete()
 {
+  MOZ_ASSERT(OnTaskQueue());
   AssertCurrentThreadInMonitor();
   SAMPLE_LOG("IsVideoSeekComplete() curTarVal=%d mVidDis=%d vqFin=%d vqSz=%d",
     mCurrentSeek.Exists(), mDropVideoUntilNextDiscontinuity, VideoQueue().IsFinished(), VideoQueue().GetSize());
   return
     !HasVideo() ||
     (mCurrentSeek.Exists() &&
      !mDropVideoUntilNextDiscontinuity &&
      (VideoQueue().IsFinished() || VideoQueue().GetSize() > 0));
@@ -1138,23 +1156,25 @@ MediaDecoderStateMachine::CheckIfSeekCom
     mDecodeToSeekTarget = false;
     SeekCompleted();
   }
 }
 
 bool
 MediaDecoderStateMachine::IsAudioDecoding()
 {
+  MOZ_ASSERT(OnTaskQueue());
   AssertCurrentThreadInMonitor();
   return HasAudio() && !AudioQueue().IsFinished();
 }
 
 bool
 MediaDecoderStateMachine::IsVideoDecoding()
 {
+  MOZ_ASSERT(OnTaskQueue());
   AssertCurrentThreadInMonitor();
   return HasVideo() && !VideoQueue().IsFinished();
 }
 
 void
 MediaDecoderStateMachine::CheckIfDecodeComplete()
 {
   MOZ_ASSERT(OnTaskQueue());
@@ -1338,37 +1358,25 @@ void MediaDecoderStateMachine::SetState(
               gMachineStateStr[mState], gMachineStateStr[aState]);
 
   mState = aState;
 
   // Clear state-scoped state.
   mSentPlaybackEndedEvent = false;
 }
 
-void MediaDecoderStateMachine::SetVolume(double volume)
+void MediaDecoderStateMachine::VolumeChanged()
 {
-  NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
+  MOZ_ASSERT(OnTaskQueue());
   ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
-  mVolume = volume;
   if (mAudioSink) {
     mAudioSink->SetVolume(mVolume);
   }
 }
 
-void MediaDecoderStateMachine::SetAudioCaptured()
-{
-  NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
-  AssertCurrentThreadInMonitor();
-  if (!mAudioCaptured) {
-    mAudioCaptured = true;
-    // Schedule the state machine to send stream data as soon as possible.
-    ScheduleStateMachine();
-  }
-}
-
 double MediaDecoderStateMachine::GetCurrentTime() const
 {
   return static_cast<double>(mCurrentFrameTime) / static_cast<double>(USECS_PER_S);
 }
 
 int64_t MediaDecoderStateMachine::GetCurrentTimeUs() const
 {
   return mCurrentFrameTime;
@@ -1748,16 +1756,17 @@ void MediaDecoderStateMachine::StopAudio
   }
   // Wake up those waiting for audio sink to finish.
   mDecoder->GetReentrantMonitor().NotifyAll();
 }
 
 nsresult
 MediaDecoderStateMachine::EnqueueDecodeFirstFrameTask()
 {
+  MOZ_ASSERT(OnTaskQueue());
   AssertCurrentThreadInMonitor();
   MOZ_ASSERT(mState == DECODER_STATE_DECODING_FIRSTFRAME);
 
   nsCOMPtr<nsIRunnable> task(
     NS_NewRunnableMethod(this, &MediaDecoderStateMachine::CallDecodeFirstFrame));
   TaskQueue()->Dispatch(task.forget());
   return NS_OK;
 }
@@ -2036,16 +2045,17 @@ MediaDecoderStateMachine::StartAudioThre
     mAudioSink->SetPlaybackRate(mPlaybackRate);
     mAudioSink->SetPreservesPitch(mPreservesPitch);
   }
   return NS_OK;
 }
 
 int64_t MediaDecoderStateMachine::AudioDecodedUsecs()
 {
+  MOZ_ASSERT(OnTaskQueue());
   NS_ASSERTION(HasAudio(),
                "Should only call AudioDecodedUsecs() when we have audio");
   // The amount of audio we have decoded is the amount of audio data we've
   // already decoded and pushed to the hardware, plus the amount of audio
   // data waiting to be pushed to the hardware.
   int64_t pushed = (mAudioEndTime != -1) ? (mAudioEndTime - GetMediaTime()) : 0;
 
   // Currently for real time streams, AudioQueue().Duration() produce
@@ -2053,41 +2063,45 @@ int64_t MediaDecoderStateMachine::AudioD
   if (IsRealTime()) {
     return pushed + FramesToUsecs(AudioQueue().FrameCount(), mInfo.mAudio.mRate).value();
   }
   return pushed + AudioQueue().Duration();
 }
 
 bool MediaDecoderStateMachine::HasLowDecodedData(int64_t aAudioUsecs)
 {
+  MOZ_ASSERT(OnTaskQueue());
   AssertCurrentThreadInMonitor();
   MOZ_ASSERT(mReader->UseBufferingHeuristics());
   // We consider ourselves low on decoded data if we're low on audio,
   // provided we've not decoded to the end of the audio stream, or
   // if we're low on video frames, provided
   // we've not decoded to the end of the video stream.
   return ((IsAudioDecoding() && AudioDecodedUsecs() < aAudioUsecs) ||
          (IsVideoDecoding() &&
           static_cast<uint32_t>(VideoQueue().GetSize()) < LOW_VIDEO_FRAMES));
 }
 
 bool MediaDecoderStateMachine::OutOfDecodedAudio()
 {
+    MOZ_ASSERT(OnTaskQueue());
     return IsAudioDecoding() && !AudioQueue().IsFinished() &&
            AudioQueue().GetSize() == 0 &&
            (!mAudioSink || !mAudioSink->HasUnplayedFrames());
 }
 
 bool MediaDecoderStateMachine::HasLowUndecodedData()
 {
+  MOZ_ASSERT(OnTaskQueue());
   return HasLowUndecodedData(mLowDataThresholdUsecs);
 }
 
 bool MediaDecoderStateMachine::HasLowUndecodedData(int64_t aUsecs)
 {
+  MOZ_ASSERT(OnTaskQueue());
   AssertCurrentThreadInMonitor();
   NS_ASSERTION(mState > DECODER_STATE_DECODING_FIRSTFRAME,
                "Must have loaded first frame for GetBuffered() to work");
 
   // If we don't have a duration, GetBuffered is probably not going to produce
   // a useful buffered range. Return false here so that we don't get stuck in
   // buffering mode for live streams.
   if (GetDuration() < 0) {
@@ -2141,19 +2155,19 @@ MediaDecoderStateMachine::DecodeError()
   nsCOMPtr<nsIRunnable> event =
     NS_NewRunnableMethod(mDecoder, &MediaDecoder::DecodeError);
   AbstractThread::MainThread()->Dispatch(event.forget());
 }
 
 void
 MediaDecoderStateMachine::OnMetadataRead(MetadataHolder* aMetadata)
 {
-  ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
   MOZ_ASSERT(OnTaskQueue());
   MOZ_ASSERT(mState == DECODER_STATE_DECODING_METADATA);
+  ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
   mMetadataRequest.Complete();
 
   mDecoder->SetMediaSeekable(mReader->IsMediaSeekable());
   mInfo = aMetadata->mInfo;
   mMetadataTags = aMetadata->mTags.forget();
 
   if (HasVideo()) {
     DECODER_LOG("Video decode isAsync=%d HWAccel=%d videoQueueSize=%d",
@@ -2181,19 +2195,19 @@ MediaDecoderStateMachine::OnMetadataRead
   SetState(DECODER_STATE_DECODING_FIRSTFRAME);
   EnqueueDecodeFirstFrameTask();
   ScheduleStateMachine();
 }
 
 void
 MediaDecoderStateMachine::OnMetadataNotRead(ReadMetadataFailureReason aReason)
 {
-  ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
   MOZ_ASSERT(OnTaskQueue());
   MOZ_ASSERT(mState == DECODER_STATE_DECODING_METADATA);
+  ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
   mMetadataRequest.Complete();
 
   if (aReason == ReadMetadataFailureReason::WAITING_FOR_RESOURCES) {
     SetState(DECODER_STATE_WAIT_FOR_RESOURCES);
   } else {
     MOZ_ASSERT(aReason == ReadMetadataFailureReason::METADATA_ERROR);
     DECODER_WARN("Decode metadata failed, shutting down decoder");
     DecodeError();
@@ -2294,18 +2308,18 @@ MediaDecoderStateMachine::DecodeFirstFra
   }
 
   return NS_OK;
 }
 
 nsresult
 MediaDecoderStateMachine::FinishDecodeFirstFrame()
 {
+  MOZ_ASSERT(OnTaskQueue());
   AssertCurrentThreadInMonitor();
-  MOZ_ASSERT(OnTaskQueue());
   DECODER_LOG("FinishDecodeFirstFrame");
 
   if (IsShutdown()) {
     return NS_ERROR_FAILURE;
   }
 
   if (!IsRealTime() && !mSentFirstFrameLoadedEvent) {
     const VideoData* v = VideoQueue().PeekFront();
@@ -2370,32 +2384,32 @@ MediaDecoderStateMachine::FinishDecodeFi
   }
 
   return NS_OK;
 }
 
 void
 MediaDecoderStateMachine::OnSeekCompleted(int64_t aTime)
 {
+  MOZ_ASSERT(OnTaskQueue());
   ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
-  MOZ_ASSERT(OnTaskQueue());
   mSeekRequest.Complete();
 
   // We must decode the first samples of active streams, so we can determine
   // the new stream time. So dispatch tasks to do that.
   mDecodeToSeekTarget = true;
 
   DispatchDecodeTasksIfNeeded();
 }
 
 void
 MediaDecoderStateMachine::OnSeekFailed(nsresult aResult)
 {
+  MOZ_ASSERT(OnTaskQueue());
   ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
-  MOZ_ASSERT(OnTaskQueue());
   mSeekRequest.Complete();
   MOZ_ASSERT(NS_FAILED(aResult), "Cancels should also disconnect mSeekRequest");
   DecodeError();
 }
 
 void
 MediaDecoderStateMachine::SeekCompleted()
 {
@@ -2522,16 +2536,19 @@ MediaDecoderStateMachine::FinishShutdown
 
   // Now that those threads are stopped, there's no possibility of
   // mPendingWakeDecoder being needed again. Revoke it.
   mPendingWakeDecoder = nullptr;
 
   // Disconnect canonicals and mirrors before shutting down our task queue.
   mPlayState.DisconnectIfConnected();
   mNextPlayState.DisconnectIfConnected();
+  mVolume.DisconnectIfConnected();
+  mLogicalPlaybackRate.DisconnectIfConnected();
+  mPreservesPitch.DisconnectIfConnected();
   mNextFrameStatus.DisconnectAll();
 
   // Shut down the watch manager before shutting down our task queue.
   mWatchManager.Shutdown();
 
   MOZ_ASSERT(mState == DECODER_STATE_SHUTDOWN,
              "How did we escape from the shutdown state?");
   // We must daisy-chain these events to destroy the decoder. We must
@@ -2833,26 +2850,28 @@ void MediaDecoderStateMachine::RenderVid
     }
     container->SetCurrentFrame(aData->mDisplay, aData->mImage, aTarget);
     MOZ_ASSERT(container->GetFrameDelay() >= 0 || IsRealTime());
   }
 }
 
 void MediaDecoderStateMachine::ResyncAudioClock()
 {
+  MOZ_ASSERT(OnTaskQueue());
   AssertCurrentThreadInMonitor();
   if (IsPlaying()) {
     SetPlayStartTime(TimeStamp::Now());
     mPlayDuration = GetAudioClock() - mStartTime;
   }
 }
 
 int64_t
 MediaDecoderStateMachine::GetAudioClock() const
 {
+  MOZ_ASSERT(OnTaskQueue());
   // We must hold the decoder monitor while using the audio stream off the
   // audio sink to ensure that it doesn't get destroyed on the audio sink
   // while we're using it.
   AssertCurrentThreadInMonitor();
   MOZ_ASSERT(HasAudio() && !mAudioCompleted);
   return mAudioStartTime +
          (mAudioSink ? mAudioSink->GetPosition() : 0);
 }
@@ -2869,16 +2888,17 @@ int64_t MediaDecoderStateMachine::GetVid
   int64_t delta = DurationToUsecs(TimeStamp::Now() - mPlayStartTime);
   // Take playback rate into account.
   delta *= mPlaybackRate;
   return mStartTime + mPlayDuration + delta;
 }
 
 int64_t MediaDecoderStateMachine::GetClock() const
 {
+  MOZ_ASSERT(OnTaskQueue());
   AssertCurrentThreadInMonitor();
 
   // Determine the clock time. If we've got audio, and we've not reached
   // the end of the audio, use the audio clock. However if we've finished
   // audio, or don't have audio, use the system clock. If our output is being
   // fed to a MediaStream, use that stream as the source of the clock.
   int64_t clock_time = -1;
   if (!IsPlaying()) {
@@ -3048,16 +3068,17 @@ void MediaDecoderStateMachine::AdvanceFr
   } else {
     ScheduleStateMachine();
   }
 }
 
 nsresult
 MediaDecoderStateMachine::DropVideoUpToSeekTarget(VideoData* aSample)
 {
+  MOZ_ASSERT(OnTaskQueue());
   nsRefPtr<VideoData> video(aSample);
   MOZ_ASSERT(video);
   DECODER_LOG("DropVideoUpToSeekTarget() frame [%lld, %lld] dup=%d",
               video->mTime, video->GetEndTime(), video->mDuplicate);
   MOZ_ASSERT(mCurrentSeek.Exists());
   const int64_t target = mCurrentSeek.mTarget.mTime;
 
   // Duplicate handling: if we're dropping frames up the seek target, we must
@@ -3099,16 +3120,17 @@ MediaDecoderStateMachine::DropVideoUpToS
   }
 
   return NS_OK;
 }
 
 nsresult
 MediaDecoderStateMachine::DropAudioUpToSeekTarget(AudioData* aSample)
 {
+  MOZ_ASSERT(OnTaskQueue());
   nsRefPtr<AudioData> audio(aSample);
   MOZ_ASSERT(audio &&
              mCurrentSeek.Exists() &&
              mCurrentSeek.mTarget.mType == SeekTarget::Accurate);
 
   CheckedInt64 startFrame = UsecsToFrames(audio->mTime,
                                           mInfo.mAudio.mRate);
   CheckedInt64 targetFrame = UsecsToFrames(mCurrentSeek.mTarget.mTime,
@@ -3194,17 +3216,18 @@ void MediaDecoderStateMachine::SetStartT
 
   // Set the audio start time to be start of media. If this lies before the
   // first actual audio frame we have, we'll inject silence during playback
   // to ensure the audio starts at the correct time.
   mAudioStartTime = mStartTime;
   DECODER_LOG("Set media start time to %lld", mStartTime);
 }
 
-void MediaDecoderStateMachine::UpdateNextFrameStatus() {
+void MediaDecoderStateMachine::UpdateNextFrameStatus()
+{
   MOZ_ASSERT(OnTaskQueue());
   ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
 
   MediaDecoderOwner::NextFrameStatus status;
   const char* statusString;
   if (IsBuffering()) {
     status = MediaDecoderOwner::NEXT_FRAME_UNAVAILABLE_BUFFERING;
     statusString = "NEXT_FRAME_UNAVAILABLE_BUFFERING";
@@ -3223,16 +3246,17 @@ void MediaDecoderStateMachine::UpdateNex
     DECODER_LOG("Changed mNextFrameStatus to %s", statusString);
   }
 
   mNextFrameStatus = status;
 }
 
 bool MediaDecoderStateMachine::JustExitedQuickBuffering()
 {
+  MOZ_ASSERT(OnTaskQueue());
   return !mDecodeStartTime.IsNull() &&
     mQuickBuffering &&
     (TimeStamp::Now() - mDecodeStartTime) < TimeDuration::FromMicroseconds(QUICK_BUFFER_THRESHOLD_USECS);
 }
 
 void MediaDecoderStateMachine::StartBuffering()
 {
   MOZ_ASSERT(OnTaskQueue());
@@ -3280,17 +3304,19 @@ void MediaDecoderStateMachine::SetPlaySt
   }
   if (!mPlayStartTime.IsNull()) {
     mAudioSink->StartPlayback();
   } else {
     mAudioSink->StopPlayback();
   }
 }
 
-void MediaDecoderStateMachine::ScheduleStateMachineWithLockAndWakeDecoder() {
+void MediaDecoderStateMachine::ScheduleStateMachineWithLockAndWakeDecoder()
+{
+  MOZ_ASSERT(OnTaskQueue());
   ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
   DispatchAudioDecodeTaskIfNeeded();
   DispatchVideoDecodeTaskIfNeeded();
 }
 
 void
 MediaDecoderStateMachine::ScheduleStateMachine() {
   AssertCurrentThreadInMonitor();
@@ -3348,62 +3374,53 @@ bool MediaDecoderStateMachine::OnTaskQue
   return TaskQueue()->IsCurrentThreadIn();
 }
 
 bool MediaDecoderStateMachine::IsStateMachineScheduled() const
 {
   return mDispatchedStateMachine || mDelayedScheduler.IsScheduled();
 }
 
-void MediaDecoderStateMachine::SetPlaybackRate(double aPlaybackRate)
+void
+MediaDecoderStateMachine::LogicalPlaybackRateChanged()
 {
-  NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
-  NS_ASSERTION(aPlaybackRate != 0,
-      "PlaybackRate == 0 should be handled before this function.");
+  MOZ_ASSERT(OnTaskQueue());
   ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
 
-  if (mPlaybackRate == aPlaybackRate) {
+  if (mLogicalPlaybackRate == 0) {
+    // This case is handled in MediaDecoder by pausing playback.
     return;
   }
 
   // AudioStream will handle playback rate change when we have audio.
   // Do nothing while we are not playing. Change in playback rate will
   // take effect next time we start playing again.
   if (!HasAudio() && IsPlaying()) {
     // Remember how much time we've spent in playing the media
     // for playback rate will change from now on.
     mPlayDuration = GetVideoStreamPosition() - mStartTime;
     SetPlayStartTime(TimeStamp::Now());
   }
 
-  mPlaybackRate = aPlaybackRate;
+  mPlaybackRate = mLogicalPlaybackRate;
   if (mAudioSink) {
     mAudioSink->SetPlaybackRate(mPlaybackRate);
   }
 }
 
-void MediaDecoderStateMachine::SetPreservesPitch(bool aPreservesPitch)
+void MediaDecoderStateMachine::PreservesPitchChanged()
 {
-  NS_ASSERTION(NS_IsMainThread(), "Should be on main thread.");
+  MOZ_ASSERT(OnTaskQueue());
   ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
 
-  mPreservesPitch = aPreservesPitch;
   if (mAudioSink) {
     mAudioSink->SetPreservesPitch(mPreservesPitch);
   }
 }
 
-void
-MediaDecoderStateMachine::SetMinimizePrerollUntilPlaybackStarts()
-{
-  AssertCurrentThreadInMonitor();
-  DECODER_LOG("SetMinimizePrerollUntilPlaybackStarts()");
-  mMinimizePreroll = true;
-}
-
 bool MediaDecoderStateMachine::IsShutdown()
 {
   AssertCurrentThreadInMonitor();
   return mState == DECODER_STATE_ERROR ||
          mState == DECODER_STATE_SHUTDOWN;
 }
 
 void MediaDecoderStateMachine::QueueMetadata(int64_t aPublishTime,
@@ -3466,16 +3483,17 @@ void MediaDecoderStateMachine::OnAudioSi
 
   // Otherwise notify media decoder/element about this error for it makes
   // no sense to play an audio-only file without sound output.
   DecodeError();
 }
 
 uint32_t MediaDecoderStateMachine::GetAmpleVideoFrames() const
 {
+  MOZ_ASSERT(OnTaskQueue());
   AssertCurrentThreadInMonitor();
   return (mReader->IsAsync() && mReader->VideoIsHardwareAccelerated())
     ? std::max<uint32_t>(sVideoQueueHWAccelSize, MIN_VIDEO_QUEUE_SIZE)
     : std::max<uint32_t>(sVideoQueueDefaultSize, MIN_VIDEO_QUEUE_SIZE);
 }
 
 } // namespace mozilla
 
--- a/dom/media/MediaDecoderStateMachine.h
+++ b/dom/media/MediaDecoderStateMachine.h
@@ -147,20 +147,30 @@ public:
     DECODER_STATE_ERROR
   };
 
   State GetState() {
     AssertCurrentThreadInMonitor();
     return mState;
   }
 
-  // Set the audio volume. The decoder monitor must be obtained before
-  // calling this.
-  void SetVolume(double aVolume);
-  void SetAudioCaptured();
+  void DispatchAudioCaptured()
+  {
+    nsRefPtr<MediaDecoderStateMachine> self = this;
+    nsCOMPtr<nsIRunnable> r = NS_NewRunnableFunction([self] () -> void
+    {
+      MOZ_ASSERT(self->OnTaskQueue());
+      ReentrantMonitorAutoEnter mon(self->mDecoder->GetReentrantMonitor());
+      if (!self->mAudioCaptured) {
+        self->mAudioCaptured = true;
+        self->ScheduleStateMachine();
+      }
+    });
+    TaskQueue()->Dispatch(r.forget());
+  }
 
   // Check if the decoder needs to become dormant state.
   bool IsDormantNeeded();
   // Set/Unset dormant state.
   void SetDormant(bool aDormant);
 
 private:
   // Initialization that needs to happen on the task queue. This is the first
@@ -304,19 +314,16 @@ public:
     ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
     if (mStartTime < 0) {
       return NS_OK;
     }
 
     return mReader->GetBuffered(aBuffered);
   }
 
-  void SetPlaybackRate(double aPlaybackRate);
-  void SetPreservesPitch(bool aPreservesPitch);
-
   size_t SizeOfVideoQueue() {
     if (mReader) {
       return mReader->SizeOfVideoQueueInBytes();
     }
     return 0;
   }
 
   size_t SizeOfAudioQueue() {
@@ -387,17 +394,31 @@ public:
   // to begin decoding.
   void NotifyWaitingForResourcesStatusChanged();
 
   // Notifies the state machine that should minimize the number of samples
   // decoded we preroll, until playback starts. The first time playback starts
   // the state machine is free to return to prerolling normally. Note
   // "prerolling" in this context refers to when we decode and buffer decoded
   // samples in advance of when they're needed for playback.
-  void SetMinimizePrerollUntilPlaybackStarts();
+  void DispatchMinimizePrerollUntilPlaybackStarts()
+  {
+    nsRefPtr<MediaDecoderStateMachine> self = this;
+    nsCOMPtr<nsIRunnable> r = NS_NewRunnableFunction([self] () -> void
+    {
+      MOZ_ASSERT(self->OnTaskQueue());
+      ReentrantMonitorAutoEnter mon(self->mDecoder->GetReentrantMonitor());
+      self->mMinimizePreroll = true;
+
+      // Make sure that this arrives before playback starts, otherwise this won't
+      // have the intended effect.
+      MOZ_DIAGNOSTIC_ASSERT(self->mPlayState == MediaDecoder::PLAY_STATE_LOADING);
+    });
+    TaskQueue()->Dispatch(r.forget());
+  }
 
   void OnAudioDecoded(AudioData* aSample);
   void OnVideoDecoded(VideoData* aSample);
   void OnNotDecoded(MediaData::Type aType, MediaDecoderReader::NotDecodedReason aReason);
   void OnAudioNotDecoded(MediaDecoderReader::NotDecodedReason aReason)
   {
     OnNotDecoded(MediaData::AUDIO_DATA, aReason);
   }
@@ -442,16 +463,19 @@ protected:
 
   // Pops MediaData* samples from their respective MediaQueues.
   // Note that the audio queue is also drained on the audio thread,
   // which we can't easily react to - This should be fixed when we
   // remove the audio thread in bug 750596.
   already_AddRefed<AudioData> PopAudio();
   already_AddRefed<VideoData> PopVideo();
 
+  void VolumeChanged();
+  void LogicalPlaybackRateChanged();
+  void PreservesPitchChanged();
 
   class WakeDecoderRunnable : public nsRunnable {
   public:
     explicit WakeDecoderRunnable(MediaDecoderStateMachine* aSM)
       : mMutex("WakeDecoderRunnable"), mStateMachine(aSM) {}
     NS_IMETHOD Run() override
     {
       nsRefPtr<MediaDecoderStateMachine> stateMachine;
@@ -1004,27 +1028,29 @@ protected:
   // The presentation end time of the last video frame which has been displayed
   // in microseconds. Accessed from the state machine thread.
   int64_t mVideoFrameEndTime;
 
   // The end time of the last decoded video frame. Used to check if we are low
   // on decoded video data.
   int64_t mDecodedVideoEndTime;
 
-  // Volume of playback. 0.0 = muted. 1.0 = full volume. Read/Written
-  // from the state machine and main threads. Synchronised via decoder
-  // monitor.
-  double mVolume;
+  // Volume of playback. 0.0 = muted. 1.0 = full volume.
+  Mirror<double> mVolume;
 
-  // Playback rate. 1.0 : normal speed, 0.5 : two times slower. Synchronized via
-  // decoder monitor.
+  // Playback rate. 1.0 : normal speed, 0.5 : two times slower.
+  //
+  // The separation between mPlaybackRate and mLogicalPlaybackRate is a kludge
+  // to preserve existing fragile logic while converting this setup to state-
+  // mirroring. Some hero should clean this up.
   double mPlaybackRate;
+  Mirror<double> mLogicalPlaybackRate;
 
-  // Pitch preservation for the playback rate. Synchronized via decoder monitor.
-  bool mPreservesPitch;
+  // Pitch preservation for the playback rate.
+  Mirror<bool> mPreservesPitch;
 
   // Time at which we started decoding. Synchronised via decoder monitor.
   TimeStamp mDecodeStartTime;
 
   // The maximum number of second we spend buffering when we are short on
   // unbuffered data.
   uint32_t mBufferingWait;
   int64_t  mLowDataThresholdUsecs;
@@ -1075,22 +1101,24 @@ protected:
 
   uint32_t VideoPrerollFrames() const
   {
     return IsRealTime() ? 0 : GetAmpleVideoFrames() / 2;
   }
 
   bool DonePrerollingAudio()
   {
+    MOZ_ASSERT(OnTaskQueue());
     AssertCurrentThreadInMonitor();
     return !IsAudioDecoding() || GetDecodedAudioDuration() >= AudioPrerollUsecs() * mPlaybackRate;
   }
 
   bool DonePrerollingVideo()
   {
+    MOZ_ASSERT(OnTaskQueue());
     AssertCurrentThreadInMonitor();
     return !IsVideoDecoding() ||
            static_cast<uint32_t>(VideoQueue().GetSize()) >= VideoPrerollFrames() * mPlaybackRate;
   }
 
   void StopPrerollingAudio()
   {
     AssertCurrentThreadInMonitor();
--- a/dom/media/PeerConnection.js
+++ b/dom/media/PeerConnection.js
@@ -40,19 +40,24 @@ function GlobalPCList() {
   this._list = {};
   this._networkdown = false; // XXX Need to query current state somehow
   this._lifecycleobservers = {};
   Services.obs.addObserver(this, "inner-window-destroyed", true);
   Services.obs.addObserver(this, "profile-change-net-teardown", true);
   Services.obs.addObserver(this, "network:offline-about-to-go-offline", true);
   Services.obs.addObserver(this, "network:offline-status-changed", true);
   Services.obs.addObserver(this, "gmp-plugin-crash", true);
+  if (Cc["@mozilla.org/childprocessmessagemanager;1"]) {
+    let mm = Cc["@mozilla.org/childprocessmessagemanager;1"].getService(Ci.nsIMessageListenerManager);
+    mm.addMessageListener("gmp-plugin-crash", this);
+  }
 }
 GlobalPCList.prototype = {
   QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver,
+                                         Ci.nsIMessageListener,
                                          Ci.nsISupportsWeakReference,
                                          Ci.IPeerConnectionManager]),
   classID: PC_MANAGER_CID,
   _xpcom_factory: {
     createInstance: function(outer, iid) {
       if (outer) {
         throw Cr.NS_ERROR_NO_AGGREGATION;
       }
@@ -88,16 +93,41 @@ GlobalPCList.prototype = {
     }
   },
 
   hasActivePeerConnection: function(winID) {
     this.removeNullRefs(winID);
     return this._list[winID] ? true : false;
   },
 
+  handleGMPCrash: function(data) {
+    let broadcastPluginCrash = function(list, winID, pluginID, pluginName) {
+      if (list.hasOwnProperty(winID)) {
+        list[winID].forEach(function(pcref) {
+          let pc = pcref.get();
+          if (pc) {
+            pc._pc.pluginCrash(pluginID, pluginName);
+          }
+        });
+      }
+    };
+
+    // a plugin crashed; if it's associated with any of our PCs, fire an
+    // event to the DOM window
+    for (let winId in this._list) {
+      broadcastPluginCrash(this._list, winId, data.pluginID, data.pluginName);
+    }
+  },
+
+  receiveMessage: function(message) {
+    if (message.name == "gmp-plugin-crash") {
+      this.handleGMPCrash(message.data);
+    }
+  },
+
   observe: function(subject, topic, data) {
     let cleanupPcRef = function(pcref) {
       let pc = pcref.get();
       if (pc) {
         pc._pc.close();
         delete pc._observer;
         pc._pc = null;
       }
@@ -105,27 +135,16 @@ GlobalPCList.prototype = {
 
     let cleanupWinId = function(list, winID) {
       if (list.hasOwnProperty(winID)) {
         list[winID].forEach(cleanupPcRef);
         delete list[winID];
       }
     };
 
-    let broadcastPluginCrash = function(list, winID, pluginID, name, crashReportID) {
-      if (list.hasOwnProperty(winID)) {
-        list[winID].forEach(function(pcref) {
-          let pc = pcref.get();
-          if (pc) {
-            pc._pc.pluginCrash(pluginID, name, crashReportID);
-          }
-        });
-      }
-    };
-
     if (topic == "inner-window-destroyed") {
       let winID = subject.QueryInterface(Ci.nsISupportsPRUint64).data;
       cleanupWinId(this._list, winID);
 
       if (this._lifecycleobservers.hasOwnProperty(winID)) {
         delete this._lifecycleobservers[winID];
       }
     } else if (topic == "profile-change-net-teardown" ||
@@ -157,27 +176,21 @@ GlobalPCList.prototype = {
         if (appId != this._list[winId]._appId) {
           continue;
         }
         if (ios.isAppOffline(appId)) {
           cleanupWinId(this._list, winId);
         }
       }
     } else if (topic == "gmp-plugin-crash") {
-      // a plugin crashed; if it's associated with any of our PCs, fire an
-      // event to the DOM window
-      let sep = data.indexOf(' ');
-      let pluginId = data.slice(0, sep);
-      let rest = data.slice(sep+1);
-      // This presumes no spaces in the name!
-      sep = rest.indexOf(' ');
-      let name = rest.slice(0, sep);
-      let crashId = rest.slice(sep+1);
-      for (let winId in this._list) {
-        broadcastPluginCrash(this._list, winId, pluginId, name, crashId);
+      if (subject instanceof Ci.nsIWritablePropertyBag2) {
+        let pluginID = subject.getPropertyAsUint32("pluginID");
+        let pluginName = subject.getPropertyAsAString("pluginName");
+        let data = { pluginID, pluginName };
+        this.handleGMPCrash(data);
       }
     }
   },
 
   _registerPeerConnectionLifecycleCallback: function(winID, cb) {
     this._lifecycleobservers[winID] = cb;
   },
 };
--- a/dom/media/eme/MediaKeys.cpp
+++ b/dom/media/eme/MediaKeys.cpp
@@ -372,32 +372,33 @@ MediaKeys::Init(ErrorResult& aRv)
                inPrivateBrowsing);
 
   return promise.forget();
 }
 
 class CrashHandler : public gmp::GeckoMediaPluginService::PluginCrashCallback
 {
 public:
-  CrashHandler(const nsACString& aPluginId,
+  CrashHandler(const uint32_t aPluginId,
                nsPIDOMWindow* aParentWindow,
                nsIDocument* aDocument)
     : gmp::GeckoMediaPluginService::PluginCrashCallback(aPluginId)
+    , mPluginId(aPluginId)
     , mParentWindowWeakPtr(do_GetWeakReference(aParentWindow))
     , mDocumentWeakPtr(do_GetWeakReference(aDocument))
   {
   }
 
-  virtual void Run(const nsACString& aPluginName, const nsAString& aPluginDumpId) override
+  virtual void Run(const nsACString& aPluginName) override
   {
     PluginCrashedEventInit init;
+    init.mPluginID = mPluginId;
     init.mBubbles = true;
     init.mCancelable = true;
     init.mGmpPlugin = true;
-    init.mPluginDumpID = aPluginDumpId;
     CopyUTF8toUTF16(aPluginName, init.mPluginName);
     init.mSubmittedCrashReport = false;
 
     // The following PluginCrashedEvent fields stay empty:
     // init.mBrowserDumpID
     // init.mPluginFilename
     // TODO: Can/should we fill them?
 
@@ -440,22 +441,23 @@ private:
     }
     nsCOMPtr<nsIDocument> parentWindowDocument = parentWindow->GetExtantDoc();
     if (!parentWindowDocument || document.get() != parentWindowDocument.get()) {
       return false;
     }
     return true;
   }
 
+  uint32_t mPluginId;
   nsWeakPtr mParentWindowWeakPtr;
   nsWeakPtr mDocumentWeakPtr;
 };
 
 void
-MediaKeys::OnCDMCreated(PromiseId aId, const nsACString& aNodeId, const nsACString& aPluginId)
+MediaKeys::OnCDMCreated(PromiseId aId, const nsACString& aNodeId, const uint32_t aPluginId)
 {
   nsRefPtr<Promise> promise(RetrievePromise(aId));
   if (!promise) {
     return;
   }
   mNodeId = aNodeId;
   nsRefPtr<MediaKeys> keys(this);
   EME_LOG("MediaKeys[%p]::OnCDMCreated() resolve promise id=%d", this, aId);
@@ -463,33 +465,33 @@ MediaKeys::OnCDMCreated(PromiseId aId, c
   if (mCreatePromiseId == aId) {
     Release();
   }
 
   MediaKeySystemAccess::NotifyObservers(mParent,
                                         mKeySystem,
                                         MediaKeySystemStatus::Cdm_created);
 
-  if (!aPluginId.IsEmpty()) {
+  if (aPluginId) {
     // Prepare plugin crash reporter.
     nsRefPtr<gmp::GeckoMediaPluginService> service =
       gmp::GeckoMediaPluginService::GetGeckoMediaPluginService();
     if (NS_WARN_IF(!service)) {
       return;
     }
     if (NS_WARN_IF(!mParent)) {
       return;
     }
     nsCOMPtr<nsIDocument> doc = mParent->GetExtantDoc();
     if (NS_WARN_IF(!doc)) {
       return;
     }
     service->AddPluginCrashCallback(new CrashHandler(aPluginId, mParent, doc));
-    EME_LOG("MediaKeys[%p]::OnCDMCreated() registered crash handler for pluginId '%s'",
-            this, aPluginId.Data());
+    EME_LOG("MediaKeys[%p]::OnCDMCreated() registered crash handler for pluginId '%i'",
+            this, aPluginId);
   }
 }
 
 already_AddRefed<MediaKeySession>
 MediaKeys::CreateSession(JSContext* aCx,
                          SessionType aSessionType,
                          ErrorResult& aRv)
 {
--- a/dom/media/eme/MediaKeys.h
+++ b/dom/media/eme/MediaKeys.h
@@ -76,17 +76,17 @@ public:
   already_AddRefed<MediaKeySession> GetSession(const nsAString& aSessionId);
 
   // Removes and returns MediaKeySession from the set of sessions awaiting
   // their sessionId to be assigned.
   already_AddRefed<MediaKeySession> GetPendingSession(uint32_t aToken);
 
   // Called once a Init() operation succeeds.
   void OnCDMCreated(PromiseId aId,
-                    const nsACString& aNodeId, const nsACString& aPluginId);
+                    const nsACString& aNodeId, const uint32_t aPluginId);
 
   // Called once the CDM generates a sessionId while servicing a
   // MediaKeySession.generateRequest() or MediaKeySession.load() call,
   // once the sessionId of a MediaKeySession is known.
   void OnSessionIdReady(MediaKeySession* aSession);
 
   // Called once a LoadSession succeeds.
   void OnSessionLoaded(PromiseId aId, bool aSuccess);
--- a/dom/media/gmp/GMPContentParent.h
+++ b/dom/media/gmp/GMPContentParent.h
@@ -46,21 +46,21 @@ public:
   void SetDisplayName(const nsCString& aDisplayName)
   {
     mDisplayName = aDisplayName;
   }
   const nsCString& GetDisplayName()
   {
     return mDisplayName;
   }
-  void SetPluginId(const nsCString& aPluginId)
+  void SetPluginId(const uint32_t aPluginId)
   {
     mPluginId = aPluginId;
   }
-  const nsCString& GetPluginId()
+  const uint32_t GetPluginId()
   {
     return mPluginId;
   }
 
 private:
   ~GMPContentParent();
 
   virtual void ActorDestroy(ActorDestroyReason aWhy) override;
@@ -87,15 +87,15 @@ private:
 
   nsTArray<nsRefPtr<GMPVideoDecoderParent>> mVideoDecoders;
   nsTArray<nsRefPtr<GMPVideoEncoderParent>> mVideoEncoders;
   nsTArray<nsRefPtr<GMPDecryptorParent>> mDecryptors;
   nsTArray<nsRefPtr<GMPAudioDecoderParent>> mAudioDecoders;
   nsCOMPtr<nsIThread> mGMPThread;
   nsRefPtr<GMPParent> mParent;
   nsCString mDisplayName;
-  nsCString mPluginId;
+  uint32_t mPluginId;
 };
 
 } // namespace gmp
 } // namespace mozilla
 
 #endif // GMPParent_h_
--- a/dom/media/gmp/GMPDecryptorParent.cpp
+++ b/dom/media/gmp/GMPDecryptorParent.cpp
@@ -24,17 +24,17 @@ GMPDecryptorParent::GMPDecryptorParent(G
   MOZ_ASSERT(mPlugin && mGMPThread);
   mPluginId = aPlugin->GetPluginId();
 }
 
 GMPDecryptorParent::~GMPDecryptorParent()
 {
 }
 
-const nsACString&
+const uint32_t
 GMPDecryptorParent::GetPluginId() const
 {
   return mPluginId;
 }
 
 nsresult
 GMPDecryptorParent::Init(GMPDecryptorProxyCallback* aCallback)
 {
--- a/dom/media/gmp/GMPDecryptorParent.h
+++ b/dom/media/gmp/GMPDecryptorParent.h
@@ -24,17 +24,17 @@ class GMPDecryptorParent final : public 
 {
 public:
   NS_INLINE_DECL_REFCOUNTING(GMPDecryptorParent)
 
   explicit GMPDecryptorParent(GMPContentParent *aPlugin);
 
   // GMPDecryptorProxy
 
-  virtual const nsACString& GetPluginId() const override;
+  virtual const uint32_t GetPluginId() const override;
 
   virtual nsresult Init(GMPDecryptorProxyCallback* aCallback) override;
 
   virtual void CreateSession(uint32_t aCreateSessionToken,
                              uint32_t aPromiseId,
                              const nsCString& aInitDataType,
                              const nsTArray<uint8_t>& aInitData,
                              GMPSessionType aSessionType) override;
@@ -108,17 +108,17 @@ private:
 
   virtual void ActorDestroy(ActorDestroyReason aWhy) override;
   virtual bool Recv__delete__() override;
 
   bool mIsOpen;
   bool mShuttingDown;
   bool mActorDestroyed;
   nsRefPtr<GMPContentParent> mPlugin;
-  nsCString mPluginId;
+  uint32_t mPluginId;
   GMPDecryptorProxyCallback* mCallback;
 #ifdef DEBUG
   nsIThread* const mGMPThread;
 #endif
 };
 
 } // namespace gmp
 } // namespace mozilla
--- a/dom/media/gmp/GMPDecryptorProxy.h
+++ b/dom/media/gmp/GMPDecryptorProxy.h
@@ -54,17 +54,17 @@ public:
                          GMPErr aResult,
                          const nsTArray<uint8_t>& aDecryptedData) = 0;
 };
 
 class GMPDecryptorProxy {
 public:
   ~GMPDecryptorProxy() {}
 
-  virtual const nsACString& GetPluginId() const = 0;
+  virtual const uint32_t GetPluginId() const = 0;
 
   virtual nsresult Init(GMPDecryptorProxyCallback* aCallback) = 0;
 
   virtual void CreateSession(uint32_t aCreateSessionToken,
                              uint32_t aPromiseId,
                              const nsCString& aInitDataType,
                              const nsTArray<uint8_t>& aInitData,
                              GMPSessionType aSessionType) = 0;
--- a/dom/media/gmp/GMPParent.cpp
+++ b/dom/media/gmp/GMPParent.cpp
@@ -8,28 +8,31 @@
 #include "nsComponentManagerUtils.h"
 #include "nsComponentManagerUtils.h"
 #include "nsIInputStream.h"
 #include "nsILineInputStream.h"
 #include "nsNetUtil.h"
 #include "nsCharSeparatedTokenizer.h"
 #include "nsThreadUtils.h"
 #include "nsIRunnable.h"
+#include "nsIWritablePropertyBag2.h"
 #include "mozIGeckoMediaPluginService.h"
+#include "mozilla/ipc/GeckoChildProcessHost.h"
 #include "mozilla/SyncRunnable.h"
 #include "mozilla/unused.h"
 #include "nsIObserverService.h"
 #include "GMPTimerParent.h"
 #include "runnable_utils.h"
 #if defined(XP_LINUX) && defined(MOZ_GMP_SANDBOX)
 #include "mozilla/SandboxInfo.h"
 #endif
 
 #include "mozilla/dom/CrashReporterParent.h"
 using mozilla::dom::CrashReporterParent;
+using mozilla::ipc::GeckoChildProcessHost;
 
 #ifdef MOZ_CRASHREPORTER
 using CrashReporter::AnnotationTable;
 using CrashReporter::GetIDFromMinidump;
 #endif
 
 #include "mozilla/Telemetry.h"
 
@@ -59,19 +62,17 @@ GMPParent::GMPParent()
   , mGMPContentChildCount(0)
   , mAsyncShutdownRequired(false)
   , mAsyncShutdownInProgress(false)
 #ifdef PR_LOGGING
   , mChildPid(0)
 #endif
 {
   LOGD("GMPParent ctor");
-  // Use the parent address to identify it.
-  // We could use any unique-to-the-parent value.
-  mPluginId.AppendInt(reinterpret_cast<uint64_t>(this));
+  mPluginId = GeckoChildProcessHost::GetUniqueID();
 }
 
 GMPParent::~GMPParent()
 {
   // Can't Close or Destroy the process here, since destruction is MainThread only
   MOZ_ASSERT(NS_IsMainThread());
   LOGD("GMPParent dtor");
 }
@@ -494,33 +495,32 @@ GMPParent::GetCrashID(nsString& aResult)
     AppendUTF8toUTF16(mVersion, aResult);
     return;
   }
   GetIDFromMinidump(dumpFile, aResult);
   cr->GenerateCrashReportForMinidump(dumpFile, &notes);
 }
 
 static void
-GMPNotifyObservers(const nsACString& aPluginId, const nsACString& aPluginName, const nsAString& aPluginDumpId)
+GMPNotifyObservers(const uint32_t aPluginID, const nsACString& aPluginName, const nsAString& aPluginDumpID)
 {
   nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
-  if (obs) {
-    nsString id;
-    AppendUTF8toUTF16(aPluginId, id);
-    id.Append(NS_LITERAL_STRING(" "));
-    AppendUTF8toUTF16(aPluginName, id);
-    id.Append(NS_LITERAL_STRING(" "));
-    id.Append(aPluginDumpId);
-    obs->NotifyObservers(nullptr, "gmp-plugin-crash", id.Data());
+  nsCOMPtr<nsIWritablePropertyBag2> propbag =
+    do_CreateInstance("@mozilla.org/hash-property-bag;1");
+  if (obs && propbag) {
+    propbag->SetPropertyAsUint32(NS_LITERAL_STRING("pluginID"), aPluginID);
+    propbag->SetPropertyAsACString(NS_LITERAL_STRING("pluginName"), aPluginName);
+    propbag->SetPropertyAsAString(NS_LITERAL_STRING("pluginDumpID"), aPluginDumpID);
+    obs->NotifyObservers(propbag, "gmp-plugin-crash", nullptr);
   }
 
   nsRefPtr<gmp::GeckoMediaPluginService> service =
     gmp::GeckoMediaPluginService::GetGeckoMediaPluginService();
   if (service) {
-    service->RunPluginCrashCallbacks(aPluginId, aPluginName, aPluginDumpId);
+    service->RunPluginCrashCallbacks(aPluginID, aPluginName);
   }
 }
 #endif
 void
 GMPParent::ActorDestroy(ActorDestroyReason aWhy)
 {
   LOGD("%s: (%d)", __FUNCTION__, (int)aWhy);
 #ifdef MOZ_CRASHREPORTER
@@ -815,17 +815,17 @@ GMPParent::GetDisplayName() const
 }
 
 const nsCString&
 GMPParent::GetVersion() const
 {
   return mVersion;
 }
 
-const nsCString&
+const uint32_t
 GMPParent::GetPluginId() const
 {
   return mPluginId;
 }
 
 bool
 GMPParent::RecvAsyncShutdownRequired()
 {
--- a/dom/media/gmp/GMPParent.h
+++ b/dom/media/gmp/GMPParent.h
@@ -118,17 +118,17 @@ public:
   // be shared across NodeIds.
 
   // Specifies that a GMP can only work with the specified NodeIds.
   void SetNodeId(const nsACString& aNodeId);
   const nsACString& GetNodeId() const { return mNodeId; }
 
   const nsCString& GetDisplayName() const;
   const nsCString& GetVersion() const;
-  const nsCString& GetPluginId() const;
+  const uint32_t GetPluginId() const;
 
   // Returns true if a plugin can be or is being used across multiple NodeIds.
   bool CanBeSharedCrossNodeIds() const;
 
   // A GMP can be used from a NodeId if it's already been set to work with
   // that NodeId, or if it's not been set to work with any NodeId and has
   // not yet been loaded (i.e. it's not shared across NodeIds).
   bool CanBeUsedFrom(const nsACString& aNodeId) const;
@@ -187,17 +187,17 @@ private:
   nsresult EnsureAsyncShutdownTimeoutSet();
 
   GMPState mState;
   nsCOMPtr<nsIFile> mDirectory; // plugin directory on disk
   nsString mName; // base name of plugin on disk, UTF-16 because used for paths
   nsCString mDisplayName; // name of plugin displayed to users
   nsCString mDescription; // description of plugin for display to users
   nsCString mVersion;
-  nsCString mPluginId;
+  uint32_t mPluginId;
   nsTArray<nsAutoPtr<GMPCapability>> mCapabilities;
   GMPProcessParent* mProcess;
   bool mDeleteProcessOnlyOnUnload;
   bool mAbnormalShutdownInProgress;
   bool mIsBlockingDeletion;
 
   bool mCanDecrypt;
 
--- a/dom/media/gmp/GMPService.cpp
+++ b/dom/media/gmp/GMPService.cpp
@@ -160,63 +160,60 @@ GeckoMediaPluginService::~GeckoMediaPlug
 
 void
 GeckoMediaPluginService::RemoveObsoletePluginCrashCallbacks()
 {
   MOZ_ASSERT(NS_IsMainThread());
   for (size_t i = mPluginCrashCallbacks.Length(); i != 0; --i) {
     nsRefPtr<PluginCrashCallback>& callback = mPluginCrashCallbacks[i - 1];
     if (!callback->IsStillValid()) {
-      LOGD(("%s::%s - Removing obsolete callback for pluginId %s",
-            __CLASS__, __FUNCTION__,
-            PromiseFlatCString(callback->PluginId()).get()));
+      LOGD(("%s::%s - Removing obsolete callback for pluginId %i",
+            __CLASS__, __FUNCTION__, callback->PluginId()));
       mPluginCrashCallbacks.RemoveElementAt(i - 1);
     }
   }
 }
 
 void
 GeckoMediaPluginService::AddPluginCrashCallback(
   nsRefPtr<PluginCrashCallback> aPluginCrashCallback)
 {
   RemoveObsoletePluginCrashCallbacks();
   mPluginCrashCallbacks.AppendElement(aPluginCrashCallback);
 }
 
 void
-GeckoMediaPluginService::RemovePluginCrashCallbacks(const nsACString& aPluginId)
+GeckoMediaPluginService::RemovePluginCrashCallbacks(const uint32_t aPluginId)
 {
   RemoveObsoletePluginCrashCallbacks();
   for (size_t i = mPluginCrashCallbacks.Length(); i != 0; --i) {
     nsRefPtr<PluginCrashCallback>& callback = mPluginCrashCallbacks[i - 1];
     if (callback->PluginId() == aPluginId) {
       mPluginCrashCallbacks.RemoveElementAt(i - 1);
     }
   }
 }
 
 void
-GeckoMediaPluginService::RunPluginCrashCallbacks(const nsACString& aPluginId,
-                                                 const nsACString& aPluginName,
-                                                 const nsAString& aPluginDumpId)
+GeckoMediaPluginService::RunPluginCrashCallbacks(const uint32_t aPluginId,
+                                                 const nsACString& aPluginName)
 {
   MOZ_ASSERT(NS_IsMainThread());
-  LOGD(("%s::%s(%s)", __CLASS__, __FUNCTION__, aPluginId.Data()));
+  LOGD(("%s::%s(%i)", __CLASS__, __FUNCTION__, aPluginId));
   for (size_t i = mPluginCrashCallbacks.Length(); i != 0; --i) {
     nsRefPtr<PluginCrashCallback>& callback = mPluginCrashCallbacks[i - 1];
-    const nsACString& callbackPluginId = callback->PluginId();
+    const uint32_t callbackPluginId = callback->PluginId();
     if (!callback->IsStillValid()) {
-      LOGD(("%s::%s(%s) - Removing obsolete callback for pluginId %s",
-            __CLASS__, __FUNCTION__, aPluginId.Data(),
-            PromiseFlatCString(callback->PluginId()).get()));
+      LOGD(("%s::%s(%i) - Removing obsolete callback for pluginId %i",
+            __CLASS__, __FUNCTION__, aPluginId, callback->PluginId()));
       mPluginCrashCallbacks.RemoveElementAt(i - 1);
     } else if (callbackPluginId == aPluginId) {
-      LOGD(("%s::%s(%s) - Running #%u",
-          __CLASS__, __FUNCTION__, aPluginId.Data(), i - 1));
-      callback->Run(aPluginName, aPluginDumpId);
+      LOGD(("%s::%s(%i) - Running #%u",
+          __CLASS__, __FUNCTION__, aPluginId, i - 1));
+      callback->Run(aPluginName);
       mPluginCrashCallbacks.RemoveElementAt(i - 1);
     }
   }
 }
 
 nsresult
 GeckoMediaPluginService::Init()
 {
--- a/dom/media/gmp/GMPService.h
+++ b/dom/media/gmp/GMPService.h
@@ -61,38 +61,37 @@ public:
 
   int32_t AsyncShutdownTimeoutMs();
 
   class PluginCrashCallback
   {
   public:
     NS_INLINE_DECL_REFCOUNTING(PluginCrashCallback)
 
-    PluginCrashCallback(const nsACString& aPluginId)
+    PluginCrashCallback(const uint32_t aPluginId)
       : mPluginId(aPluginId)
     {
       MOZ_ASSERT(NS_IsMainThread());
     }
-    const nsACString& PluginId() const { return mPluginId; }
-    virtual void Run(const nsACString& aPluginName, const nsAString& aPluginDumpId) = 0;
+    const uint32_t PluginId() const { return mPluginId; }
+    virtual void Run(const nsACString& aPluginName) = 0;
     virtual bool IsStillValid() = 0; // False if callback has become useless.
   protected:
     virtual ~PluginCrashCallback()
     {
       MOZ_ASSERT(NS_IsMainThread());
     }
   private:
-    const nsCString mPluginId;
+    const uint32_t mPluginId;
   };
   void RemoveObsoletePluginCrashCallbacks(); // Called from add/remove/run.
   void AddPluginCrashCallback(nsRefPtr<PluginCrashCallback> aPluginCrashCallback);
-  void RemovePluginCrashCallbacks(const nsACString& aPluginId);
-  void RunPluginCrashCallbacks(const nsACString& aPluginId,
-                               const nsACString& aPluginName,
-                               const nsAString& aPluginDumpId);
+  void RemovePluginCrashCallbacks(const uint32_t aPluginId);
+  void RunPluginCrashCallbacks(const uint32_t aPluginId,
+                               const nsACString& aPluginName);
 
 protected:
   GeckoMediaPluginService();
   virtual ~GeckoMediaPluginService();
 
   virtual void InitializePlugins() = 0;
   virtual bool GetContentParentFrom(const nsACString& aNodeId,
                                     const nsCString& aAPI,
--- a/dom/media/gmp/GMPServiceChild.cpp
+++ b/dom/media/gmp/GMPServiceChild.cpp
@@ -77,17 +77,17 @@ public:
       return;
     }
 
     nsTArray<base::ProcessId> alreadyBridgedTo;
     aGMPServiceChild->GetAlreadyBridgedTo(alreadyBridgedTo);
 
     base::ProcessId otherProcess;
     nsCString displayName;
-    nsCString pluginId;
+    uint32_t pluginId;
     bool ok = aGMPServiceChild->SendLoadGMP(mNodeId, mAPI, mTags,
                                             alreadyBridgedTo, &otherProcess,
                                             &displayName, &pluginId);
     if (!ok) {
       mCallback->Done(nullptr);
       return;
     }
 
--- a/dom/media/gmp/GMPServiceParent.cpp
+++ b/dom/media/gmp/GMPServiceParent.cpp
@@ -1430,17 +1430,17 @@ GMPServiceParent::~GMPServiceParent()
 
 bool
 GMPServiceParent::RecvLoadGMP(const nsCString& aNodeId,
                               const nsCString& aAPI,
                               nsTArray<nsCString>&& aTags,
                               nsTArray<ProcessId>&& aAlreadyBridgedTo,
                               ProcessId* aId,
                               nsCString* aDisplayName,
-                              nsCString* aPluginId)
+                              uint32_t* aPluginId)
 {
   nsRefPtr<GMPParent> gmp = mService->SelectPluginForAPI(aNodeId, aAPI, aTags);
 
 #ifdef PR_LOGGING
   nsCString api = aTags[0];
   LOGD(("%s: %p returning %p for api %s", __FUNCTION__, (void *)this, (void *)gmp, api.get()));
 #endif
 
--- a/dom/media/gmp/GMPServiceParent.h
+++ b/dom/media/gmp/GMPServiceParent.h
@@ -188,17 +188,17 @@ public:
   virtual ~GMPServiceParent();
 
   virtual bool RecvLoadGMP(const nsCString& aNodeId,
                            const nsCString& aApi,
                            nsTArray<nsCString>&& aTags,
                            nsTArray<ProcessId>&& aAlreadyBridgedTo,
                            base::ProcessId* aID,
                            nsCString* aDisplayName,
-                           nsCString* aPluginId) override;
+                           uint32_t* aPluginId) override;
   virtual bool RecvGetGMPNodeId(const nsString& aOrigin,
                                 const nsString& aTopLevelOrigin,
                                 const bool& aInPrivateBrowsing,
                                 nsCString* aID) override;
   static bool RecvGetGMPPluginVersionForAPI(const nsCString& aAPI,
                                             nsTArray<nsCString>&& aTags,
                                             bool* aHasPlugin,
                                             nsCString* aVersion);
--- a/dom/media/gmp/GMPVideoEncoderParent.cpp
+++ b/dom/media/gmp/GMPVideoEncoderParent.cpp
@@ -211,16 +211,22 @@ GMPVideoEncoderParent::SetPeriodicKeyFra
   if (!SendSetPeriodicKeyFrames(aEnable)) {
     return GMPGenericErr;
   }
 
   // Async IPC, we don't have access to a return value.
   return GMPNoErr;
 }
 
+const uint32_t
+GMPVideoEncoderParent::ParentID()
+{
+  return mPlugin ? mPlugin->GetPluginId() : 0;
+}
+
 // Note: Consider keeping ActorDestroy sync'd up when making changes here.
 void
 GMPVideoEncoderParent::Shutdown()
 {
   LOGD(("%s::%s: %p", __CLASS__, __FUNCTION__, this));
   MOZ_ASSERT(mPlugin->GMPThread() == NS_GetCurrentThread());
 
   if (mShuttingDown) {
--- a/dom/media/gmp/GMPVideoEncoderParent.h
+++ b/dom/media/gmp/GMPVideoEncoderParent.h
@@ -40,17 +40,17 @@ public:
                             int32_t aNumberOfCores,
                             uint32_t aMaxPayloadSize) override;
   virtual GMPErr Encode(GMPUniquePtr<GMPVideoi420Frame> aInputFrame,
                         const nsTArray<uint8_t>& aCodecSpecificInfo,
                         const nsTArray<GMPVideoFrameType>& aFrameTypes) override;
   virtual GMPErr SetChannelParameters(uint32_t aPacketLoss, uint32_t aRTT) override;
   virtual GMPErr SetRates(uint32_t aNewBitRate, uint32_t aFrameRate) override;
   virtual GMPErr SetPeriodicKeyFrames(bool aEnable) override;
-  virtual const uint64_t ParentID() override { return reinterpret_cast<uint64_t>(mPlugin.get()); }
+  virtual const uint32_t ParentID() override;
 
   // GMPSharedMemManager
   virtual bool Alloc(size_t aSize, Shmem::SharedMemory::SharedMemoryType aType, Shmem* aMem) override
   {
 #ifdef GMP_SAFE_SHMEM
     return AllocShmem(aSize, aType, aMem);
 #else
     return AllocUnsafeShmem(aSize, aType, aMem);
--- a/dom/media/gmp/GMPVideoEncoderProxy.h
+++ b/dom/media/gmp/GMPVideoEncoderProxy.h
@@ -41,16 +41,16 @@ public:
                             int32_t aNumberOfCores,
                             uint32_t aMaxPayloadSize) = 0;
   virtual GMPErr Encode(mozilla::GMPUniquePtr<GMPVideoi420Frame> aInputFrame,
                         const nsTArray<uint8_t>& aCodecSpecificInfo,
                         const nsTArray<GMPVideoFrameType>& aFrameTypes) = 0;
   virtual GMPErr SetChannelParameters(uint32_t aPacketLoss, uint32_t aRTT) = 0;
   virtual GMPErr SetRates(uint32_t aNewBitRate, uint32_t aFrameRate) = 0;
   virtual GMPErr SetPeriodicKeyFrames(bool aEnable) = 0;
-  virtual const uint64_t ParentID() = 0;
+  virtual const uint32_t ParentID() = 0;
 
   // Call to tell GMP/plugin the consumer will no longer use this
   // interface/codec.
   virtual void Close() = 0;
 };
 
 #endif // GMPVideoEncoderProxy_h_
--- a/dom/media/gmp/PGMPService.ipdl
+++ b/dom/media/gmp/PGMPService.ipdl
@@ -12,16 +12,16 @@ namespace gmp {
 
 sync protocol PGMPService
 {
   parent spawns PGMP as child;
 
 parent:
   sync LoadGMP(nsCString nodeId, nsCString api, nsCString[] tags,
                ProcessId[] alreadyBridgedTo)
-    returns (ProcessId id, nsCString displayName, nsCString pluginId);
+    returns (ProcessId id, nsCString displayName, uint32_t pluginId);
   sync GetGMPNodeId(nsString origin, nsString topLevelOrigin,
                     bool inPrivateBrowsing)
     returns (nsCString id);
 };
 
 } // namespace gmp
 } // namespace mozilla
--- a/dom/media/omx/MediaOmxCommonDecoder.cpp
+++ b/dom/media/omx/MediaOmxCommonDecoder.cpp
@@ -47,18 +47,18 @@ MediaOmxCommonDecoder::SetPlatformCanOff
 {
   ReentrantMonitorAutoEnter mon(GetReentrantMonitor());
   mCanOffloadAudio = aCanOffloadAudio;
 }
 
 bool
 MediaOmxCommonDecoder::CheckDecoderCanOffloadAudio()
 {
-  return (mCanOffloadAudio && !mFallbackToStateMachine && !mOutputStreams.Length() &&
-      mInitialPlaybackRate == 1.0);
+  return (mCanOffloadAudio && !mFallbackToStateMachine &&
+          !mOutputStreams.Length() && mPlaybackRate == 1.0);
 }
 
 void
 MediaOmxCommonDecoder::FirstFrameLoaded(nsAutoPtr<MediaInfo> aInfo,
                                         MediaDecoderEventVisibility aEventVisibility)
 {
   MOZ_ASSERT(NS_IsMainThread());
 
--- a/dom/plugins/ipc/PluginModuleParent.cpp
+++ b/dom/plugins/ipc/PluginModuleParent.cpp
@@ -10,16 +10,17 @@
 
 #include "mozilla/plugins/PluginModuleParent.h"
 
 #include "base/process_util.h"
 #include "mozilla/Attributes.h"
 #include "mozilla/dom/ContentParent.h"
 #include "mozilla/dom/ContentChild.h"
 #include "mozilla/dom/PCrashReporterParent.h"
+#include "mozilla/ipc/GeckoChildProcessHost.h"
 #include "mozilla/ipc/MessageChannel.h"
 #include "mozilla/plugins/BrowserStreamParent.h"
 #include "mozilla/plugins/PluginAsyncSurrogate.h"
 #include "mozilla/plugins/PluginBridge.h"
 #include "mozilla/plugins/PluginInstanceParent.h"
 #include "mozilla/Preferences.h"
 #include "mozilla/ProcessHangMonitor.h"
 #include "mozilla/Services.h"
@@ -53,16 +54,17 @@
 #include "PluginInterposeOSX.h"
 #include "PluginUtilsOSX.h"
 #endif
 
 using base::KillProcess;
 
 using mozilla::PluginLibrary;
 using mozilla::ipc::MessageChannel;
+using mozilla::ipc::GeckoChildProcessHost;
 using mozilla::dom::PCrashReporterParent;
 using mozilla::dom::CrashReporterParent;
 
 using namespace mozilla;
 using namespace mozilla::plugins;
 using namespace mozilla::plugins::parent;
 
 #ifdef MOZ_CRASHREPORTER
@@ -653,20 +655,16 @@ PluginModuleContentParent::PluginModuleC
     Preferences::RegisterCallback(TimeoutChanged, kContentTimeoutPref, this);
 }
 
 PluginModuleContentParent::~PluginModuleContentParent()
 {
     Preferences::UnregisterCallback(TimeoutChanged, kContentTimeoutPref, this);
 }
 
-// We start the Run IDs at 1 so that we can use 0 as a way of detecting
-// errors in retrieving the run ID.
-uint32_t PluginModuleChromeParent::sNextRunID = 1;
-
 bool PluginModuleChromeParent::sInstantiated = false;
 
 PluginModuleChromeParent::PluginModuleChromeParent(const char* aFilePath, uint32_t aPluginId)
     : PluginModuleParent(true)
     , mSubprocess(new PluginProcessParent(aFilePath))
     , mPluginId(aPluginId)
     , mChromeTaskFactory(this)
     , mHangAnnotationFlags(0)
@@ -688,17 +686,17 @@ PluginModuleChromeParent::PluginModuleCh
 #endif
     , mInitOnAsyncConnect(false)
     , mAsyncInitRv(NS_ERROR_NOT_INITIALIZED)
     , mAsyncInitError(NPERR_NO_ERROR)
     , mContentParent(nullptr)
 {
     NS_ASSERTION(mSubprocess, "Out of memory!");
     sInstantiated = true;
-    mRunID = sNextRunID++;
+    mRunID = GeckoChildProcessHost::GetUniqueID();
 
 #ifdef MOZ_ENABLE_PROFILER_SPS
     InitPluginProfiling();
 #endif
 
     mozilla::HangMonitor::RegisterAnnotator(*this);
 }
 
--- a/dom/plugins/ipc/PluginModuleParent.h
+++ b/dom/plugins/ipc/PluginModuleParent.h
@@ -552,16 +552,15 @@ private:
     friend class LaunchedTask;
 
     bool                mInitOnAsyncConnect;
     nsresult            mAsyncInitRv;
     NPError             mAsyncInitError;
     dom::ContentParent* mContentParent;
     nsCOMPtr<nsIObserver> mOfflineObserver;
     bool mIsBlocklisted;
-    static uint32_t sNextRunID;
     static bool sInstantiated;
 };
 
 } // namespace plugins
 } // namespace mozilla
 
 #endif // mozilla_plugins_PluginModuleParent_h
--- a/dom/webidl/PeerConnectionImpl.webidl
+++ b/dom/webidl/PeerConnectionImpl.webidl
@@ -58,17 +58,17 @@ interface PeerConnectionImpl  {
    */
   [Throws]
   void addIceCandidate(DOMString candidate, DOMString mid, unsigned short level);
 
   /* Puts the SIPCC engine back to 'kIdle', shuts down threads, deletes state */
   void close();
 
   /* Notify DOM window if this plugin crash is ours. */
-  boolean pluginCrash(unsigned long long pluginId, DOMString name, DOMString pluginDumpID);
+  boolean pluginCrash(unsigned long long pluginId, DOMString name);
 
   /* Attributes */
   [Constant]
   readonly attribute DOMString fingerprint;
   readonly attribute DOMString localDescription;
   readonly attribute DOMString remoteDescription;
 
   readonly attribute PCImplIceConnectionState iceConnectionState;
--- a/dom/webidl/PluginCrashedEvent.webidl
+++ b/dom/webidl/PluginCrashedEvent.webidl
@@ -2,25 +2,27 @@
 /* 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/.
  */
 
 [Constructor(DOMString type, optional PluginCrashedEventInit eventInitDict), ChromeOnly]
 interface PluginCrashedEvent : Event
 {
+  readonly attribute unsigned long pluginID;
   readonly attribute DOMString pluginDumpID;
   readonly attribute DOMString pluginName;
   readonly attribute DOMString? browserDumpID;
   readonly attribute DOMString? pluginFilename;
   readonly attribute boolean submittedCrashReport;
   readonly attribute boolean gmpPlugin;
 };
 
 dictionary PluginCrashedEventInit : EventInit
 {
+  unsigned long pluginID = 0;
   DOMString pluginDumpID = "";
   DOMString pluginName = "";
   DOMString? browserDumpID = null;
   DOMString? pluginFilename = null;
   boolean submittedCrashReport = false;
   boolean gmpPlugin = false;
 };
--- a/image/src/imgLoader.cpp
+++ b/image/src/imgLoader.cpp
@@ -203,16 +203,18 @@ private:
     return NS_OK;
   }
 
   static nsresult ReportImage(nsIHandleReportCallback* aHandleReport,
                               nsISupports* aData,
                               const char* aPathPrefix,
                               const ImageMemoryCounter& aCounter)
   {
+    nsresult rv;
+
     nsAutoCString pathPrefix(NS_LITERAL_CSTRING("explicit/"));
     pathPrefix.Append(aPathPrefix);
     pathPrefix.Append(aCounter.Type() == imgIContainer::TYPE_RASTER
                         ? "/raster/"
                         : "/vector/");
     pathPrefix.Append(aCounter.IsUsed() ? "used/" : "unused/");
     pathPrefix.Append("image(");
     pathPrefix.AppendInt(aCounter.IntrinsicSize().width);
@@ -223,17 +225,23 @@ private:
     if (aCounter.URI().IsEmpty()) {
       pathPrefix.Append("<unknown URI>");
     } else {
       pathPrefix.Append(aCounter.URI());
     }
 
     pathPrefix.Append(")/");
 
-    return ReportSurfaces(aHandleReport, aData, pathPrefix, aCounter);
+    rv = ReportSurfaces(aHandleReport, aData, pathPrefix, aCounter);
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    rv = ReportSourceValue(aHandleReport, aData, pathPrefix, aCounter.Values());
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    return NS_OK;
   }
 
   static nsresult ReportSurfaces(nsIHandleReportCallback* aHandleReport,
                                  nsISupports* aData,
                                  const nsACString& aPathPrefix,
                                  const ImageMemoryCounter& aCounter)
   {
     for (const SurfaceMemoryCounter& counter : aCounter.Surfaces()) {
@@ -327,20 +335,17 @@ private:
 
   static nsresult ReportValues(nsIHandleReportCallback* aHandleReport,
                                nsISupports* aData,
                                const nsACString& aPathPrefix,
                                const MemoryCounter& aCounter)
   {
     nsresult rv;
 
-    rv = ReportValue(aHandleReport, aData, KIND_HEAP, aPathPrefix,
-                     "source",
-                     "Raster image source data and vector image documents.",
-                     aCounter.Source());
+    rv = ReportSourceValue(aHandleReport, aData, aPathPrefix, aCounter);
     NS_ENSURE_SUCCESS(rv, rv);
 
     rv = ReportValue(aHandleReport, aData, KIND_HEAP, aPathPrefix,
                      "decoded-heap",
                      "Decoded image data which is stored on the heap.",
                      aCounter.DecodedHeap());
     NS_ENSURE_SUCCESS(rv, rv);
 
@@ -348,16 +353,31 @@ private:
                      "decoded-nonheap",
                      "Decoded image data which isn't stored on the heap.",
                      aCounter.DecodedNonHeap());
     NS_ENSURE_SUCCESS(rv, rv);
 
     return NS_OK;
   }
 
+  static nsresult ReportSourceValue(nsIHandleReportCallback* aHandleReport,
+                                    nsISupports* aData,
+                                    const nsACString& aPathPrefix,
+                                    const MemoryCounter& aCounter)
+  {
+    nsresult rv;
+
+    rv = ReportValue(aHandleReport, aData, KIND_HEAP, aPathPrefix,
+                     "source",
+                     "Raster image source data and vector image documents.",
+                     aCounter.Source());
+
+    return rv;
+  }
+
   static nsresult ReportValue(nsIHandleReportCallback* aHandleReport,
                               nsISupports* aData,
                               int32_t aKind,
                               const nsACString& aPathPrefix,
                               const char* aPathSuffix,
                               const char* aDescription,
                               size_t aValue)
   {
--- a/ipc/glue/GeckoChildProcessHost.cpp
+++ b/ipc/glue/GeckoChildProcessHost.cpp
@@ -249,16 +249,27 @@ uint32_t GeckoChildProcessHost::GetSuppo
     }
     return pluginContainerArchs;
   }
 #endif
 
   return base::GetCurrentProcessArchitecture();
 }
 
+// We start the unique IDs at 1 so that 0 can be used to mean that
+// a component has no unique ID assigned to it.
+uint32_t GeckoChildProcessHost::sNextUniqueID = 1;
+
+/* static */
+uint32_t
+GeckoChildProcessHost::GetUniqueID()
+{
+  return sNextUniqueID++;
+}
+
 void
 GeckoChildProcessHost::PrepareLaunch()
 {
 #ifdef MOZ_CRASHREPORTER
   if (CrashReporter::GetEnabled()) {
     CrashReporter::OOPInit();
   }
 #endif
--- a/ipc/glue/GeckoChildProcessHost.h
+++ b/ipc/glue/GeckoChildProcessHost.h
@@ -42,16 +42,18 @@ public:
                                  ChildPrivileges aPrivileges=base::PRIVILEGES_DEFAULT);
 
   ~GeckoChildProcessHost();
 
   static nsresult GetArchitecturesForBinary(const char *path, uint32_t *result);
 
   static uint32_t GetSupportedArchitecturesForProcessType(GeckoProcessType type);
 
+  static uint32_t GetUniqueID();
+
   // Block until the IPC channel for our subprocess is initialized,
   // but no longer.  The child process may or may not have been
   // created when this method returns.
   bool AsyncLaunch(StringVector aExtraOpts=StringVector(),
                    base::ProcessArchitecture arch=base::GetCurrentProcessArchitecture());
 
   virtual bool WaitUntilConnected(int32_t aTimeoutMs = 0);
 
@@ -191,16 +193,18 @@ private:
   // In between launching the subprocess and handing off its IPC
   // channel, there's a small window of time in which *we* might still
   // be the channel listener, and receive messages.  That's bad
   // because we have no idea what to do with those messages.  So queue
   // them here until we hand off the eventual listener.
   //
   // FIXME/cjones: this strongly indicates bad design.  Shame on us.
   std::queue<IPC::Message> mQueue;
+
+  static uint32_t sNextUniqueID;
 };
 
 #ifdef MOZ_NUWA_PROCESS
 class GeckoExistingProcessHost final : public GeckoChildProcessHost
 {
 public:
   GeckoExistingProcessHost(GeckoProcessType aProcessType,
                            base::ProcessHandle aProcess,
--- a/js/public/Class.h
+++ b/js/public/Class.h
@@ -586,17 +586,17 @@ struct JSClass {
 
 #define JSCLASS_IS_ANONYMOUS            (1<<(JSCLASS_HIGH_FLAGS_SHIFT+0))
 #define JSCLASS_IS_GLOBAL               (1<<(JSCLASS_HIGH_FLAGS_SHIFT+1))
 #define JSCLASS_INTERNAL_FLAG2          (1<<(JSCLASS_HIGH_FLAGS_SHIFT+2))
 #define JSCLASS_INTERNAL_FLAG3          (1<<(JSCLASS_HIGH_FLAGS_SHIFT+3))
 
 #define JSCLASS_IS_PROXY                (1<<(JSCLASS_HIGH_FLAGS_SHIFT+4))
 
-#define JSCLASS_FINALIZE_FROM_NURSERY   (1<<(JSCLASS_HIGH_FLAGS_SHIFT+5))
+#define JSCLASS_SKIP_NURSERY_FINALIZE   (1<<(JSCLASS_HIGH_FLAGS_SHIFT+5))
 
 // Reserved for embeddings.
 #define JSCLASS_USERBIT2                (1<<(JSCLASS_HIGH_FLAGS_SHIFT+6))
 #define JSCLASS_USERBIT3                (1<<(JSCLASS_HIGH_FLAGS_SHIFT+7))
 
 #define JSCLASS_BACKGROUND_FINALIZE     (1<<(JSCLASS_HIGH_FLAGS_SHIFT+8))
 
 // Bits 26 through 31 are reserved for the CACHED_PROTO_KEY mechanism, see
--- a/js/public/MemoryMetrics.h
+++ b/js/public/MemoryMetrics.h
@@ -220,17 +220,17 @@ struct CodeSizes
 
 // Data for tracking GC memory usage.
 struct GCSizes
 {
 #define FOR_EACH_SIZE(macro) \
     macro(_, _, marker) \
     macro(_, _, nurseryCommitted) \
     macro(_, _, nurseryDecommitted) \
-    macro(_, _, nurseryHugeSlots) \
+    macro(_, _, nurseryMallocedBuffers) \
     macro(_, _, storeBufferVals) \
     macro(_, _, storeBufferCells) \
     macro(_, _, storeBufferSlots) \
     macro(_, _, storeBufferWholeCells) \
     macro(_, _, storeBufferRelocVals) \
     macro(_, _, storeBufferRelocCells) \
     macro(_, _, storeBufferGenerics)
 
--- a/js/src/devtools/automation/autospider.sh
+++ b/js/src/devtools/automation/autospider.sh
@@ -100,28 +100,35 @@ elif [ "$OSTYPE" = "linux-gnu" ]; then
     export CXX=$GCCDIR/bin/g++
     if $USE_64BIT; then
       export LD_LIBRARY_PATH=$GCCDIR/lib64
     else
       export LD_LIBRARY_PATH=$GCCDIR/lib
     fi
   fi
 elif [ "$OSTYPE" = "msys" ]; then
-  USE_64BIT=false
-  if [ "$platform" = "win64" ]; then
-      USE_64BIT=true
-  fi
+  case "$platform" in
+  win64*)
+    USE_64BIT=true
+    ;;
+  *)
+    USE_64BIT=false
+    ;;
+  esac
   MAKE=${MAKE:-mozmake}
   source "$ABSDIR/winbuildenv.sh"
 fi
 
 MAKE=${MAKE:-make}
 
 if $USE_64BIT; then
   NSPR64="--enable-64bit"
+  if [ "$OSTYPE" = "msys" ]; then
+    CONFIGURE_ARGS="$CONFIGURE_ARGS --target=x86_64-pc-mingw32 --host=x86_64-pc-mingw32"
+  fi
 else
   NSPR64=""
   if [ "$OSTYPE" != "msys" ]; then
     export CC="${CC:-/usr/bin/gcc} -m32"
     export CXX="${CXX:-/usr/bin/g++} -m32"
     export AR=ar
   fi
 fi
@@ -164,18 +171,24 @@ elif [[ "$VARIANT" = "compacting" ]]; th
     export JSTESTS_EXTRA_ARGS=--exclude-file=$ABSDIR/cgc-jstests-slow.txt
 
     case "$platform" in
     win*)
         RUN_JSTESTS=false
     esac
 fi
 
-if [[ "$VARIANT" = "warnaserr" ]]; then
+if [[ "$VARIANT" = "warnaserr" ||
+      "$VARIANT" = "warnaserrdebug" ||
+      "$VARIANT" = "plain" ]]; then
     export JSTESTS_EXTRA_ARGS=--tbpl
+elif [[ "$VARIANT" = "arm-sim" ||
+        "$VARIANT" = "rootanalysis" ||
+        "$VARIANT" = "plaindebug" ]]; then
+    export JSTESTS_EXTRA_ARGS=--tbpl-debug
 fi
 
 $COMMAND_PREFIX $MAKE check || exit 1
 $COMMAND_PREFIX $MAKE check-jit-test || exit 1
 $COMMAND_PREFIX $OBJDIR/dist/bin/jsapi-tests || exit 1
 if $RUN_JSTESTS; then
     $COMMAND_PREFIX $MAKE check-jstests || exit 1
 fi
--- a/js/src/gc/Nursery-inl.h
+++ b/js/src/gc/Nursery-inl.h
@@ -9,23 +9,68 @@
 #define gc_Nursery_inl_h
 
 #include "gc/Nursery.h"
 
 #include "gc/Heap.h"
 #include "js/TracingAPI.h"
 #include "vm/Runtime.h"
 
+namespace js {
+
 template <typename T>
 MOZ_ALWAYS_INLINE bool
-js::Nursery::getForwardedPointer(T** ref)
+Nursery::getForwardedPointer(T** ref)
 {
     MOZ_ASSERT(ref);
     MOZ_ASSERT(isInside((void*)*ref));
     const gc::RelocationOverlay* overlay = reinterpret_cast<const gc::RelocationOverlay*>(*ref);
     if (!overlay->isForwarded())
         return false;
     /* This static cast from Cell* restricts T to valid (GC thing) types. */
     *ref = static_cast<T*>(overlay->forwardingAddress());
     return true;
 }
 
+// The allocation methods below will not run the garbage collector. If the
+// nursery cannot accomodate the allocation, the malloc heap will be used
+// instead.
+
+template <typename T>
+static inline T*
+AllocateObjectBuffer(ExclusiveContext* cx, uint32_t count)
+{
+    if (cx->isJSContext()) {
+        Nursery& nursery = cx->asJSContext()->runtime()->gc.nursery;
+        return static_cast<T*>(nursery.allocateBuffer(cx->zone(), count * sizeof(T)));
+    }
+    return cx->zone()->pod_malloc<T>(count);
+}
+
+template <typename T>
+static inline T*
+AllocateObjectBuffer(ExclusiveContext* cx, JSObject* obj, uint32_t count)
+{
+    if (cx->isJSContext()) {
+        Nursery& nursery = cx->asJSContext()->runtime()->gc.nursery;
+        return static_cast<T*>(nursery.allocateBuffer(obj, count * sizeof(T)));
+    }
+    return obj->zone()->pod_malloc<T>(count);
+}
+
+// If this returns null then the old buffer will be left alone.
+template <typename T>
+static inline T*
+ReallocateObjectBuffer(ExclusiveContext* cx, JSObject* obj, T* oldBuffer,
+                       uint32_t oldCount, uint32_t newCount)
+{
+    if (cx->isJSContext()) {
+        Nursery& nursery = cx->asJSContext()->runtime()->gc.nursery;
+        return static_cast<T*>(nursery.reallocateBuffer(obj, oldBuffer,
+                                                        oldCount * sizeof(T),
+                                                        newCount * sizeof(T)));
+    }
+    return obj->zone()->pod_realloc<T>(oldBuffer, oldCount, newCount);
+}
+
+} // namespace js
+
 #endif /* gc_Nursery_inl_h */
--- a/js/src/gc/Nursery.cpp
+++ b/js/src/gc/Nursery.cpp
@@ -32,49 +32,49 @@
 
 using namespace js;
 using namespace gc;
 
 using mozilla::ArrayLength;
 using mozilla::PodCopy;
 using mozilla::PodZero;
 
-struct js::Nursery::FreeHugeSlotsTask : public GCParallelTask
+struct js::Nursery::FreeMallocedBuffersTask : public GCParallelTask
 {
-    explicit FreeHugeSlotsTask(FreeOp* fop) : fop_(fop) {}
-    bool init() { return slots_.init(); }
-    void transferSlotsToFree(HugeSlotsSet& slotsToFree);
-    ~FreeHugeSlotsTask() override { join(); }
+    explicit FreeMallocedBuffersTask(FreeOp* fop) : fop_(fop) {}
+    bool init() { return buffers_.init(); }
+    void transferBuffersToFree(MallocedBuffersSet& buffersToFree);
+    ~FreeMallocedBuffersTask() override { join(); }
 
   private:
     FreeOp* fop_;
-    HugeSlotsSet slots_;
+    MallocedBuffersSet buffers_;
 
     virtual void run() override;
 };
 
 bool
 js::Nursery::init(uint32_t maxNurseryBytes)
 {
     /* maxNurseryBytes parameter is rounded down to a multiple of chunk size. */
     numNurseryChunks_ = maxNurseryBytes >> ChunkShift;
 
     /* If no chunks are specified then the nursery is permenantly disabled. */
     if (numNurseryChunks_ == 0)
         return true;
 
-    if (!hugeSlots.init())
+    if (!mallocedBuffers.init())
         return false;
 
     void* heap = MapAlignedPages(nurserySize(), Alignment);
     if (!heap)
         return false;
 
-    freeHugeSlotsTask = js_new<FreeHugeSlotsTask>(runtime()->defaultFreeOp());
-    if (!freeHugeSlotsTask || !freeHugeSlotsTask->init())
+    freeMallocedBuffersTask = js_new<FreeMallocedBuffersTask>(runtime()->defaultFreeOp());
+    if (!freeMallocedBuffersTask || !freeMallocedBuffersTask->init())
         return false;
 
     heapStart_ = uintptr_t(heap);
     heapEnd_ = heapStart_ + nurserySize();
     currentStart_ = start();
     numActiveChunks_ = 1;
     JS_POISON(heap, JS_FRESH_NURSERY_PATTERN, nurserySize());
     setCurrentChunk(0);
@@ -95,17 +95,17 @@ js::Nursery::init(uint32_t maxNurseryByt
     return true;
 }
 
 js::Nursery::~Nursery()
 {
     if (start())
         UnmapPages((void*)start(), nurserySize());
 
-    js_delete(freeHugeSlotsTask);
+    js_delete(freeMallocedBuffersTask);
 }
 
 void
 js::Nursery::updateDecommittedRegion()
 {
 #ifndef JS_GC_ZEAL
     if (numActiveChunks_ < numNurseryChunks_) {
         // Bug 994054: madvise on MacOS is too slow to make this
@@ -170,81 +170,51 @@ js::Nursery::leaveZealMode() {
     if (isEnabled()) {
         MOZ_ASSERT(isEmpty());
         setCurrentChunk(0);
         currentStart_ = start();
     }
 }
 #endif // JS_GC_ZEAL
 
-void
-js::Nursery::verifyFinalizerList()
-{
-#ifdef DEBUG
-    for (ListItem* current = finalizers_; current; current = current->next()) {
-        JSObject* obj = current->get();
-        RelocationOverlay* overlay = RelocationOverlay::fromCell(obj);
-        if (overlay->isForwarded())
-            obj = static_cast<JSObject*>(overlay->forwardingAddress());
-        MOZ_ASSERT(obj);
-        MOZ_ASSERT(obj->group());
-        MOZ_ASSERT(obj->group()->clasp());
-        MOZ_ASSERT(obj->group()->clasp()->finalize);
-        MOZ_ASSERT(obj->group()->clasp()->flags & JSCLASS_FINALIZE_FROM_NURSERY);
-    }
-#endif // DEBUG
-}
-
 JSObject*
 js::Nursery::allocateObject(JSContext* cx, size_t size, size_t numDynamic, const js::Class* clasp)
 {
     /* Ensure there's enough space to replace the contents with a RelocationOverlay. */
     MOZ_ASSERT(size >= sizeof(RelocationOverlay));
-    verifyFinalizerList();
 
-    /* If we have a finalizer, get space for the list entry. */
-    ListItem* listEntry = nullptr;
-    if (clasp->finalize) {
-        listEntry = static_cast<ListItem*>(allocate(sizeof(ListItem)));
-        if (!listEntry)
-            return nullptr;
-    }
+    /*
+     * Classes with JSCLASS_SKIP_NURSERY_FINALIZE will not have their finalizer
+     * called if they are nursery allocated and not promoted to the tenured
+     * heap. The finalizers for these classes must do nothing except free data
+     * which was allocated via Nursery::allocateBuffer.
+     */
+    MOZ_ASSERT_IF(clasp->finalize, clasp->flags & JSCLASS_SKIP_NURSERY_FINALIZE);
 
     /* Make the object allocation. */
     JSObject* obj = static_cast<JSObject*>(allocate(size));
     if (!obj)
         return nullptr;
 
     /* If we want external slots, add them. */
     HeapSlot* slots = nullptr;
     if (numDynamic) {
-        /* Try to allocate in the nursery first. */
-        if (numDynamic <= MaxNurserySlots)
-            slots = static_cast<HeapSlot*>(allocate(numDynamic * sizeof(HeapSlot)));
-
-        /* If we are out of space or too large, use the malloc heap. */
-        if (!slots)
-            slots = allocateHugeSlots(cx->zone(), numDynamic);
-
-        /* It is safe to leave the allocated object uninitialized, since we do
-         * not visit unallocated things. */
-        if (!slots)
+        slots = static_cast<HeapSlot*>(allocateBuffer(cx->zone(), numDynamic * sizeof(HeapSlot)));
+        if (!slots) {
+            /*
+             * It is safe to leave the allocated object uninitialized, since we
+             * do not visit unallocated things in the nursery.
+             */
             return nullptr;
+        }
     }
 
     /* Always initialize the slots field to match the JIT behavior. */
     obj->setInitialSlotsMaybeNonNative(slots);
 
-    /* If we have a finalizer, link it into the finalizer list. */
-    if (clasp->finalize) {
-        MOZ_ASSERT(listEntry);
-        new (listEntry) ListItem(finalizers_, obj);
-        finalizers_ = listEntry;
-    }
-
     TraceNurseryAlloc(obj, size);
     return obj;
 }
 
 void*
 js::Nursery::allocate(size_t size)
 {
     MOZ_ASSERT(isEnabled());
@@ -259,99 +229,82 @@ js::Nursery::allocate(size_t size)
 
     void* thing = (void*)position();
     position_ = position() + size;
 
     JS_EXTRA_POISON(thing, JS_ALLOCATED_NURSERY_PATTERN, size);
     return thing;
 }
 
-/* Internally, this function is used to allocate elements as well as slots. */
-HeapSlot*
-js::Nursery::allocateSlots(JSObject* obj, uint32_t nslots)
+void*
+js::Nursery::allocateBuffer(Zone* zone, uint32_t nbytes)
 {
-    MOZ_ASSERT(obj);
-    MOZ_ASSERT(nslots > 0);
-
-    if (!IsInsideNursery(obj))
-        return obj->zone()->pod_malloc<HeapSlot>(nslots);
+    MOZ_ASSERT(nbytes > 0);
 
-    if (nslots > MaxNurserySlots)
-        return allocateHugeSlots(obj->zone(), nslots);
+    if (nbytes <= MaxNurseryBufferSize) {
+        void* buffer = allocate(nbytes);
+        if (buffer)
+            return buffer;
+    }
 
-    size_t size = sizeof(HeapSlot) * nslots;
-    HeapSlot* slots = static_cast<HeapSlot*>(allocate(size));
-    if (slots)
-        return slots;
-
-    return allocateHugeSlots(obj->zone(), nslots);
+    void* buffer = zone->pod_malloc<uint8_t>(nbytes);
+    if (buffer) {
+        /* If this put fails, we will only leak the slots. */
+        (void)mallocedBuffers.put(buffer);
+    }
+    return buffer;
 }
 
-ObjectElements*
-js::Nursery::allocateElements(JSObject* obj, uint32_t nelems)
+void*
+js::Nursery::allocateBuffer(JSObject* obj, uint32_t nbytes)
 {
-    MOZ_ASSERT(nelems >= ObjectElements::VALUES_PER_HEADER);
-    return reinterpret_cast<ObjectElements*>(allocateSlots(obj, nelems));
+    MOZ_ASSERT(obj);
+    MOZ_ASSERT(nbytes > 0);
+
+    if (!IsInsideNursery(obj))
+        return obj->zone()->pod_malloc<uint8_t>(nbytes);
+    return allocateBuffer(obj->zone(), nbytes);
 }
 
-HeapSlot*
-js::Nursery::reallocateSlots(JSObject* obj, HeapSlot* oldSlots,
-                             uint32_t oldCount, uint32_t newCount)
+void*
+js::Nursery::reallocateBuffer(JSObject* obj, void* oldBuffer,
+                              uint32_t oldBytes, uint32_t newBytes)
 {
     if (!IsInsideNursery(obj))
-        return obj->zone()->pod_realloc<HeapSlot>(oldSlots, oldCount, newCount);
+        return obj->zone()->pod_realloc<uint8_t>((uint8_t*)oldBuffer, oldBytes, newBytes);
 
-    if (!isInside(oldSlots)) {
-        HeapSlot* newSlots = obj->zone()->pod_realloc<HeapSlot>(oldSlots, oldCount, newCount);
-        if (newSlots && oldSlots != newSlots) {
-            hugeSlots.remove(oldSlots);
+    if (!isInside(oldBuffer)) {
+        void* newBuffer = obj->zone()->pod_realloc<uint8_t>((uint8_t*)oldBuffer, oldBytes, newBytes);
+        if (newBuffer && oldBytes != newBytes) {
+            removeMallocedBuffer(oldBuffer);
             /* If this put fails, we will only leak the slots. */
-            (void)hugeSlots.put(newSlots);
+            (void)mallocedBuffers.put(newBuffer);
         }
-        return newSlots;
+        return newBuffer;
     }
 
     /* The nursery cannot make use of the returned slots data. */
-    if (newCount < oldCount)
-        return oldSlots;
+    if (newBytes < oldBytes)
+        return oldBuffer;
 
-    HeapSlot* newSlots = allocateSlots(obj, newCount);
-    if (newSlots)
-        PodCopy(newSlots, oldSlots, oldCount);
-    return newSlots;
-}
-
-ObjectElements*
-js::Nursery::reallocateElements(JSObject* obj, ObjectElements* oldHeader,
-                                uint32_t oldCount, uint32_t newCount)
-{
-    HeapSlot* slots = reallocateSlots(obj, reinterpret_cast<HeapSlot*>(oldHeader),
-                                      oldCount, newCount);
-    return reinterpret_cast<ObjectElements*>(slots);
+    void* newBuffer = allocateBuffer(obj->zone(), newBytes);
+    if (newBuffer)
+        PodCopy((uint8_t*)newBuffer, (uint8_t*)oldBuffer, oldBytes);
+    return newBuffer;
 }
 
 void
-js::Nursery::freeSlots(HeapSlot* slots)
+js::Nursery::freeBuffer(void* buffer)
 {
-    if (!isInside(slots)) {
-        hugeSlots.remove(slots);
-        js_free(slots);
+    if (!isInside(buffer)) {
+        removeMallocedBuffer(buffer);
+        js_free(buffer);
     }
 }
 
-HeapSlot*
-js::Nursery::allocateHugeSlots(JS::Zone* zone, size_t nslots)
-{
-    HeapSlot* slots = zone->pod_malloc<HeapSlot>(nslots);
-    /* If this put fails, we will only leak the slots. */
-    if (slots)
-        (void)hugeSlots.put(slots);
-    return slots;
-}
-
 namespace js {
 namespace gc {
 
 class MinorCollectionTracer : public JS::CallbackTracer
 {
   public:
     Nursery* nursery;
     AutoTraceSession session;
@@ -686,31 +639,38 @@ js::Nursery::moveObjectToTenured(MinorCo
         // The shape's list head may point into the old object. This can only
         // happen for dictionaries, which are native objects.
         if (&nsrc->shape_ == ndst->shape_->listp) {
             MOZ_ASSERT(nsrc->shape_->inDictionary());
             ndst->shape_->listp = &ndst->shape_;
         }
     }
 
-    if (src->is<InlineTypedObject>())
+    if (src->is<InlineTypedObject>()) {
         InlineTypedObject::objectMovedDuringMinorGC(trc, dst, src);
+    } else if (src->is<UnboxedArrayObject>()) {
+        tenuredSize += UnboxedArrayObject::objectMovedDuringMinorGC(trc, dst, src, dstKind);
+    } else {
+        // Objects with JSCLASS_SKIP_NURSERY_FINALIZE need to be handled above
+        // to ensure any additional nursery buffers they hold are moved.
+        MOZ_ASSERT(!(src->getClass()->flags & JSCLASS_SKIP_NURSERY_FINALIZE));
+    }
 
     return tenuredSize;
 }
 
 MOZ_ALWAYS_INLINE size_t
 js::Nursery::moveSlotsToTenured(NativeObject* dst, NativeObject* src, AllocKind dstKind)
 {
     /* Fixed slots have already been copied over. */
     if (!src->hasDynamicSlots())
         return 0;
 
     if (!isInside(src->slots_)) {
-        hugeSlots.remove(src->slots_);
+        removeMallocedBuffer(src->slots_);
         return 0;
     }
 
     Zone* zone = src->zone();
     size_t count = src->numDynamicSlots();
     dst->slots_ = zone->pod_malloc<HeapSlot>(count);
     if (!dst->slots_)
         CrashAtUnhandlableOOM("Failed to allocate slots while tenuring.");
@@ -727,17 +687,17 @@ js::Nursery::moveElementsToTenured(Nativ
 
     Zone* zone = src->zone();
     ObjectElements* srcHeader = src->getElementsHeader();
     ObjectElements* dstHeader;
 
     /* TODO Bug 874151: Prefer to put element data inline if we have space. */
     if (!isInside(srcHeader)) {
         MOZ_ASSERT(src->elements_ == dst->elements_);
-        hugeSlots.remove(reinterpret_cast<HeapSlot*>(srcHeader));
+        removeMallocedBuffer(srcHeader);
         return 0;
     }
 
     size_t nslots = ObjectElements::VALUES_PER_HEADER + srcHeader->capacity;
 
     /* Unlike other objects, Arrays can have fixed elements. */
     if (src->is<ArrayObject>() && nslots <= GetGCKindSlots(dstKind)) {
         dst->as<ArrayObject>().setFixedElements();
@@ -874,23 +834,19 @@ js::Nursery::collect(JSRuntime* rt, JS::
 
     // Update any slot or element pointers whose destination has been tenured.
     TIME_START(updateJitActivations);
     js::jit::UpdateJitActivationsForMinorGC(rt, &trc);
     forwardedBuffers.finish();
     TIME_END(updateJitActivations);
 
     // Sweep.
-    TIME_START(runFinalizers);
-    runFinalizers();
-    TIME_END(runFinalizers);
-
-    TIME_START(freeHugeSlots);
-    freeHugeSlots();
-    TIME_END(freeHugeSlots);
+    TIME_START(freeMallocedBuffers);
+    freeMallocedBuffers();
+    TIME_END(freeMallocedBuffers);
 
     TIME_START(sweep);
     sweep();
     TIME_END(sweep);
 
     TIME_START(clearStoreBuffer);
     rt->gc.storeBuffer.clear();
     TIME_END(clearStoreBuffer);
@@ -942,17 +898,17 @@ js::Nursery::collect(JSRuntime* rt, JS::
         if (!printedHeader) {
             fprintf(stderr,
                     "MinorGC: Reason               PRate  Size Time   mkVals mkClls mkSlts mkWCll mkRVal mkRCll mkGnrc ckTbls mkRntm mkDbgr clrNOC collct swpABO updtIn runFin frSlts clrSB  sweep resize pretnr\n");
             printedHeader = true;
         }
 
 #define FMT " %6" PRIu64
         fprintf(stderr,
-                "MinorGC: %20s %5.1f%% %4d" FMT FMT FMT FMT FMT FMT FMT FMT FMT FMT FMT FMT FMT FMT FMT FMT FMT FMT FMT FMT FMT "\n",
+                "MinorGC: %20s %5.1f%% %4d" FMT FMT FMT FMT FMT FMT FMT FMT FMT FMT FMT FMT FMT FMT FMT FMT FMT FMT FMT FMT "\n",
                 js::gcstats::ExplainReason(reason),
                 promotionRate * 100,
                 numActiveChunks_,
                 totalTime,
                 TIME_TOTAL(markValues),
                 TIME_TOTAL(markCells),
                 TIME_TOTAL(markSlots),
                 TIME_TOTAL(markWholeCells),
@@ -961,87 +917,71 @@ js::Nursery::collect(JSRuntime* rt, JS::
                 TIME_TOTAL(markGenericEntries),
                 TIME_TOTAL(checkHashTables),
                 TIME_TOTAL(markRuntime),
                 TIME_TOTAL(markDebugger),
                 TIME_TOTAL(clearNewObjectCache),
                 TIME_TOTAL(collectToFP),
                 TIME_TOTAL(sweepArrayBufferViewList),
                 TIME_TOTAL(updateJitActivations),
-                TIME_TOTAL(runFinalizers),
-                TIME_TOTAL(freeHugeSlots),
+                TIME_TOTAL(freeMallocedBuffers),
                 TIME_TOTAL(clearStoreBuffer),
                 TIME_TOTAL(sweep),
                 TIME_TOTAL(resize),
                 TIME_TOTAL(pretenure));
 #undef FMT
     }
 }
 
 #undef TIME_START
 #undef TIME_END
 #undef TIME_TOTAL
 
 void
-js::Nursery::FreeHugeSlotsTask::transferSlotsToFree(HugeSlotsSet& slotsToFree)
+js::Nursery::FreeMallocedBuffersTask::transferBuffersToFree(MallocedBuffersSet& buffersToFree)
 {
-    // Transfer the contents of the source set to the task's slots_ member by
+    // Transfer the contents of the source set to the task's buffers_ member by
     // swapping the sets, which also clears the source.
     MOZ_ASSERT(!isRunning());
-    MOZ_ASSERT(slots_.empty());
-    mozilla::Swap(slots_, slotsToFree);
+    MOZ_ASSERT(buffers_.empty());
+    mozilla::Swap(buffers_, buffersToFree);
 }
 
 void
-js::Nursery::FreeHugeSlotsTask::run()
+js::Nursery::FreeMallocedBuffersTask::run()
 {
-    for (HugeSlotsSet::Range r = slots_.all(); !r.empty(); r.popFront())
+    for (MallocedBuffersSet::Range r = buffers_.all(); !r.empty(); r.popFront())
         fop_->free_(r.front());
-    slots_.clear();
+    buffers_.clear();
 }
 
 void
-js::Nursery::freeHugeSlots()
+js::Nursery::freeMallocedBuffers()
 {
-    if (hugeSlots.empty())
+    if (mallocedBuffers.empty())
         return;
 
     bool started;
     {
         AutoLockHelperThreadState lock;
-        freeHugeSlotsTask->joinWithLockHeld();
-        freeHugeSlotsTask->transferSlotsToFree(hugeSlots);
-        started = freeHugeSlotsTask->startWithLockHeld();
+        freeMallocedBuffersTask->joinWithLockHeld();
+        freeMallocedBuffersTask->transferBuffersToFree(mallocedBuffers);
+        started = freeMallocedBuffersTask->startWithLockHeld();
     }
 
     if (!started)
-        freeHugeSlotsTask->runFromMainThread(runtime());
+        freeMallocedBuffersTask->runFromMainThread(runtime());
 
-    MOZ_ASSERT(hugeSlots.empty());
+    MOZ_ASSERT(mallocedBuffers.empty());
 }
 
 void
 js::Nursery::waitBackgroundFreeEnd()
 {
-    freeHugeSlotsTask->join();
-}
-
-void
-js::Nursery::runFinalizers()
-{
-    verifyFinalizerList();
-
-    FreeOp* fop = runtime()->defaultFreeOp();
-    for (ListItem* current = finalizers_; current; current = current->next()) {
-        JSObject* obj = current->get();
-        RelocationOverlay* overlay = RelocationOverlay::fromCell(obj);
-        if (!overlay->isForwarded())
-            obj->getClass()->finalize(fop, obj);
-    }
-    finalizers_ = nullptr;
+    freeMallocedBuffersTask->join();
 }
 
 void
 js::Nursery::sweep()
 {
 #ifdef JS_GC_ZEAL
     /* Poison the nursery contents so touching a freed object will crash. */
     JS_POISON((void*)start(), JS_SWEPT_NURSERY_PATTERN, nurserySize());
--- a/js/src/gc/Nursery.h
+++ b/js/src/gc/Nursery.h
@@ -54,20 +54,19 @@ class Nursery
         position_(0),
         currentStart_(0),
         currentEnd_(0),
         heapStart_(0),
         heapEnd_(0),
         currentChunk_(0),
         numActiveChunks_(0),
         numNurseryChunks_(0),
-        finalizers_(nullptr),
         profileThreshold_(0),
         enableProfiling_(false),
-        freeHugeSlotsTask(nullptr)
+        freeMallocedBuffersTask(nullptr)
     {}
     ~Nursery();
 
     bool init(uint32_t maxNurseryBytes);
 
     bool exists() const { return numNurseryChunks_ != 0; }
     size_t numChunks() const { return numNurseryChunks_; }
     size_t nurserySize() const { return numNurseryChunks_ << ChunkShift; }
@@ -89,32 +88,31 @@ class Nursery
     }
 
     /*
      * Allocate and return a pointer to a new GC object with its |slots|
      * pointer pre-filled. Returns nullptr if the Nursery is full.
      */
     JSObject* allocateObject(JSContext* cx, size_t size, size_t numDynamic, const js::Class* clasp);
 
-    /* Allocate a slots array for the given object. */
-    HeapSlot* allocateSlots(JSObject* obj, uint32_t nslots);
-
-    /* Allocate an elements vector for the given object. */
-    ObjectElements* allocateElements(JSObject* obj, uint32_t nelems);
+    /* Allocate a buffer for a given zone, using the nursery if possible. */
+    void* allocateBuffer(Zone* zone, uint32_t nbytes);
 
-    /* Resize an existing slots array. */
-    HeapSlot* reallocateSlots(JSObject* obj, HeapSlot* oldSlots,
-                              uint32_t oldCount, uint32_t newCount);
+    /*
+     * Allocate a buffer for a given object, using the nursery if possible and
+     * obj is in the nursery.
+     */
+    void* allocateBuffer(JSObject* obj, uint32_t nbytes);
 
-    /* Resize an existing elements vector. */
-    ObjectElements* reallocateElements(JSObject* obj, ObjectElements* oldHeader,
-                                       uint32_t oldCount, uint32_t newCount);
+    /* Resize an existing object buffer. */
+    void* reallocateBuffer(JSObject* obj, void* oldBuffer,
+                           uint32_t oldBytes, uint32_t newBytes);
 
-    /* Free a slots array. */
-    void freeSlots(HeapSlot* slots);
+    /* Free an object buffer. */
+    void freeBuffer(void* buffer);
 
     typedef Vector<ObjectGroup*, 0, SystemAllocPolicy> ObjectGroupList;
 
     /*
      * Do a minor collection, optionally specifying a list to store groups which
      * should be pretenured afterwards.
      */
     void collect(JSRuntime* rt, JS::gcreason::Reason reason, ObjectGroupList* pretenureGroups);
@@ -130,29 +128,34 @@ class Nursery
     /* Forward a slots/elements pointer stored in an Ion frame. */
     void forwardBufferPointer(HeapSlot** pSlotsElems);
 
     void maybeSetForwardingPointer(JSTracer* trc, void* oldData, void* newData, bool direct) {
         if (IsMinorCollectionTracer(trc) && isInside(oldData))
             setForwardingPointer(oldData, newData, direct);
     }
 
+    /* Mark a malloced buffer as no longer needing to be freed. */
+    void removeMallocedBuffer(void* buffer) {
+        mallocedBuffers.remove(buffer);
+    }
+
     void waitBackgroundFreeEnd();
 
     size_t sizeOfHeapCommitted() const {
         return numActiveChunks_ * gc::ChunkSize;
     }
     size_t sizeOfHeapDecommitted() const {
         return (numNurseryChunks_ - numActiveChunks_) * gc::ChunkSize;
     }
-    size_t sizeOfHugeSlots(mozilla::MallocSizeOf mallocSizeOf) const {
+    size_t sizeOfMallocedBuffers(mozilla::MallocSizeOf mallocSizeOf) const {
         size_t total = 0;
-        for (HugeSlotsSet::Range r = hugeSlots.all(); !r.empty(); r.popFront())
+        for (MallocedBuffersSet::Range r = mallocedBuffers.all(); !r.empty(); r.popFront())
             total += mallocSizeOf(r.front());
-        total += hugeSlots.sizeOfExcludingThis(mallocSizeOf);
+        total += mallocedBuffers.sizeOfExcludingThis(mallocSizeOf);
         return total;
     }
 
     MOZ_ALWAYS_INLINE uintptr_t start() const {
         return heapStart_;
     }
 
     MOZ_ALWAYS_INLINE uintptr_t heapEnd() const {
@@ -193,54 +196,44 @@ class Nursery
     int currentChunk_;
 
     /* The index after the last chunk that we will allocate from. */
     int numActiveChunks_;
 
     /* Number of chunks allocated for the nursery. */
     int numNurseryChunks_;
 
-    /* Keep track of objects that need finalization. */
-    class ListItem {
-        ListItem* next_;
-        JSObject* object_;
-      public:
-        ListItem(ListItem* tail, JSObject* obj) : next_(tail), object_(obj) {}
-        ListItem* next() const { return next_; }
-        JSObject* get() { return object_; }
-    } *finalizers_;
-
     /* Report minor collections taking more than this many us, if enabled. */
     int64_t profileThreshold_;
     bool enableProfiling_;
 
     /*
-     * The set of externally malloced slots potentially kept live by objects
-     * stored in the nursery. Any external slots that do not belong to a
+     * The set of externally malloced buffers potentially kept live by objects
+     * stored in the nursery. Any external buffers that do not belong to a
      * tenured thing at the end of a minor GC must be freed.
      */
-    typedef HashSet<HeapSlot*, PointerHasher<HeapSlot*, 3>, SystemAllocPolicy> HugeSlotsSet;
-    HugeSlotsSet hugeSlots;
+    typedef HashSet<void*, PointerHasher<void*, 3>, SystemAllocPolicy> MallocedBuffersSet;
+    MallocedBuffersSet mallocedBuffers;
 
-    /* A task structure used to free the huge slots on a background thread. */
-    struct FreeHugeSlotsTask;
-    FreeHugeSlotsTask* freeHugeSlotsTask;
+    /* A task structure used to free the malloced bufers on a background thread. */
+    struct FreeMallocedBuffersTask;
+    FreeMallocedBuffersTask* freeMallocedBuffersTask;
 
     /*
      * During a collection most hoisted slot and element buffers indicate their
      * new location with a forwarding pointer at the base. This does not work
      * for buffers whose length is less than pointer width, or when different
      * buffers might overlap each other. For these, an entry in the following
      * table is used.
      */
     typedef HashMap<void*, void*, PointerHasher<void*, 1>, SystemAllocPolicy> ForwardedBufferMap;
     ForwardedBufferMap forwardedBuffers;
 
-    /* The maximum number of slots allowed to reside inline in the nursery. */
-    static const size_t MaxNurserySlots = 128;
+    /* The maximum number of bytes allowed to reside in nursery buffers. */
+    static const size_t MaxNurseryBufferSize = 1024;
 
     /* The amount of space in the mapped nursery available to allocations. */
     static const size_t NurseryChunkUsableSize = gc::ChunkSize - sizeof(gc::ChunkTrailer);
 
     struct NurseryChunkLayout {
         char data[NurseryChunkUsableSize];
         gc::ChunkTrailer trailer;
         uintptr_t start() { return uintptr_t(&data); }
@@ -287,27 +280,23 @@ class Nursery
         return (void*)&currentEnd_;
     }
 
     uintptr_t position() const { return position_; }
     void* addressOfPosition() const { return (void*)&position_; }
 
     JSRuntime* runtime() const { return runtime_; }
 
-    /* Allocates and registers external slots with the nursery. */
-    HeapSlot* allocateHugeSlots(JS::Zone* zone, size_t nslots);
-
     /* Allocates a new GC thing from the tenured generation during minor GC. */
     gc::TenuredCell* allocateFromTenured(JS::Zone* zone, gc::AllocKind thingKind);
 
     struct TenureCountCache;
 
     /* Common internal allocator function. */
     void* allocate(size_t size);
-    void verifyFinalizerList();
 
     /*
      * Move the object at |src| in the Nursery to an already-allocated cell
      * |dst| in Tenured.
      */
     void collectToFixedPoint(gc::MinorCollectionTracer* trc, TenureCountCache& tenureCounts);
     MOZ_ALWAYS_INLINE void traceObject(gc::MinorCollectionTracer* trc, JSObject* src);
     MOZ_ALWAYS_INLINE void markSlots(gc::MinorCollectionTracer* trc, HeapSlot* vp, uint32_t nslots);
@@ -324,21 +313,18 @@ class Nursery
 
     /* Handle relocation of slots/elements pointers stored in Ion frames. */
     void setForwardingPointer(void* oldData, void* newData, bool direct);
 
     void setSlotsForwardingPointer(HeapSlot* oldSlots, HeapSlot* newSlots, uint32_t nslots);
     void setElementsForwardingPointer(ObjectElements* oldHeader, ObjectElements* newHeader,
                                       uint32_t nelems);
 
-    /* Run finalizers on all finalizable things in the nursery. */
-    void runFinalizers();
-
     /* Free malloced pointers owned by freed things in the nursery. */
-    void freeHugeSlots();
+    void freeMallocedBuffers();
 
     /*
      * Frees all non-live nursery-allocated things at the end of a minor
      * collection.
      */
     void sweep();
 
     /* Change the allocable space provided by the nursery. */
--- a/js/src/jit/MacroAssembler.cpp
+++ b/js/src/jit/MacroAssembler.cpp
@@ -1080,19 +1080,20 @@ MacroAssembler::shouldNurseryAllocate(gc
 void
 MacroAssembler::nurseryAllocate(Register result, Register temp, gc::AllocKind allocKind,
                                 size_t nDynamicSlots, gc::InitialHeap initialHeap, Label* fail)
 {
     MOZ_ASSERT(IsNurseryAllocable(allocKind));
     MOZ_ASSERT(initialHeap != gc::TenuredHeap);
 
     // We still need to allocate in the nursery, per the comment in
-    // shouldNurseryAllocate; however, we need to insert into hugeSlots, so
-    // bail to do the nursery allocation in the interpreter.
-    if (nDynamicSlots >= Nursery::MaxNurserySlots) {
+    // shouldNurseryAllocate; however, we need to insert into the
+    // mallocedBuffers set, so bail to do the nursery allocation in the
+    // interpreter.
+    if (nDynamicSlots >= Nursery::MaxNurseryBufferSize / sizeof(Value)) {
         jump(fail);
         return;
     }
 
     // No explicit check for nursery.isEnabled() is needed, as the comparison
     // with the nursery's end will always fail in such cases.
     const Nursery& nursery = GetJitContext()->runtime->gcNursery();
     int thingSize = int(gc::Arena::thingSize(allocKind));
--- a/js/src/jsapi-tests/moz.build
+++ b/js/src/jsapi-tests/moz.build
@@ -35,17 +35,16 @@ UNIFIED_SOURCES += [
     'testFunctionProperties.cpp',
     'testGCAllocator.cpp',
     'testGCCellPtr.cpp',
     'testGCChunkPool.cpp',
     'testGCExactRooting.cpp',
     'testGCFinalizeCallback.cpp',
     'testGCHeapPostBarriers.cpp',
     'testGCMarking.cpp',
-    'testGCNursery.cpp',
     'testGCOutOfMemory.cpp',
     'testGCStoreBufferRemoval.cpp',
     'testGetPropertyDescriptor.cpp',
     'testHashTable.cpp',
     'testIndexToString.cpp',
     'testIntern.cpp',
     'testIntString.cpp',
     'testIntTypesABI.cpp',
--- a/js/src/jsobj.cpp
+++ b/js/src/jsobj.cpp
@@ -3960,16 +3960,26 @@ JSObject::allocKindForTenure(const js::N
     MOZ_ASSERT(!IsProxy(this));
 
     // Unboxed plain objects are sized according to the data they store.
     if (is<UnboxedPlainObject>()) {
         size_t nbytes = as<UnboxedPlainObject>().layoutDontCheckGeneration().size();
         return GetGCObjectKindForBytes(UnboxedPlainObject::offsetOfData() + nbytes);
     }
 
+    // Unboxed arrays use inline data if their size is small enough.
+    if (is<UnboxedArrayObject>()) {
+        const UnboxedArrayObject* nobj = &as<UnboxedArrayObject>();
+        size_t nbytes = UnboxedArrayObject::offsetOfInlineElements() +
+                        nobj->capacity() * nobj->elementSize();
+        if (nbytes <= JSObject::MAX_BYTE_SIZE)
+            return GetGCObjectKindForBytes(nbytes);
+        return AllocKind::OBJECT0;
+    }
+
     // Inlined typed objects are followed by their data, so make sure we copy
     // it all over to the new object.
     if (is<InlineTypedObject>()) {
         // Figure out the size of this object, from the prototype's TypeDescr.
         // The objects we are traversing here are all tenured, so we don't need
         // to check forwarding pointers.
         TypeDescr& descr = as<InlineTypedObject>().typeDescr();
         MOZ_ASSERT(!IsInsideNursery(&descr));
--- a/js/src/jsobj.h
+++ b/js/src/jsobj.h
@@ -1107,17 +1107,17 @@ extern const char js_lookupSetter_str[];
 
 namespace js {
 
 inline gc::InitialHeap
 GetInitialHeap(NewObjectKind newKind, const Class* clasp)
 {
     if (newKind != GenericObject)
         return gc::TenuredHeap;
-    if (clasp->finalize && !(clasp->flags & JSCLASS_FINALIZE_FROM_NURSERY))
+    if (clasp->finalize && !(clasp->flags & JSCLASS_SKIP_NURSERY_FINALIZE))
         return gc::TenuredHeap;
     return gc::DefaultHeap;
 }
 
 // Specialized call for constructing |this| with a known function callee,
 // and a known prototype.
 extern JSObject*
 CreateThisForFunctionWithProto(JSContext* cx, js::HandleObject callee, HandleObject proto,
--- a/js/src/jsobjinlines.h
+++ b/js/src/jsobjinlines.h
@@ -262,17 +262,17 @@ JSObject::create(js::ExclusiveContext* c
     MOZ_ASSERT(group->clasp() == shape->getObjectClass());
     MOZ_ASSERT(group->clasp() != &js::ArrayObject::class_);
     MOZ_ASSERT_IF(!js::ClassCanHaveFixedData(group->clasp()),
                   js::gc::GetGCKindSlots(kind, group->clasp()) == shape->numFixedSlots());
     MOZ_ASSERT_IF(group->clasp()->flags & JSCLASS_BACKGROUND_FINALIZE,
                   IsBackgroundFinalized(kind));
     MOZ_ASSERT_IF(group->clasp()->finalize,
                   heap == js::gc::TenuredHeap ||
-                  (group->clasp()->flags & JSCLASS_FINALIZE_FROM_NURSERY));
+                  (group->clasp()->flags & JSCLASS_SKIP_NURSERY_FINALIZE));
     MOZ_ASSERT_IF(group->hasUnanalyzedPreliminaryObjects(),
                   heap == js::gc::TenuredHeap);
 
     // Non-native classes cannot have reserved slots or private data, and the
     // objects can't have any fixed slots, for compatibility with
     // GetReservedOrProxyPrivateSlot.
     MOZ_ASSERT_IF(!group->clasp()->isNative(), JSCLASS_RESERVED_SLOTS(group->clasp()) == 0);
     MOZ_ASSERT_IF(!group->clasp()->isNative(), !group->clasp()->hasPrivate());
--- a/js/src/tests/jstests.py
+++ b/js/src/tests/jstests.py
@@ -8,17 +8,17 @@ See the adjacent README.txt for more det
 from __future__ import print_function
 
 import os, sys, textwrap
 from os.path import abspath, dirname, isfile, realpath
 from copy import copy
 from subprocess import list2cmdline, call
 
 from lib.results import NullTestOutput
-from lib.tests import TestCase, TBPL_FLAGS
+from lib.tests import TestCase, TBPL_FLAGS, TBPL_DEBUG_FLAGS
 from lib.results import ResultsSink
 from lib.progressbar import ProgressBar
 
 if sys.platform.startswith('linux') or sys.platform.startswith('darwin'):
     from lib.tasks_unix import run_all_tests
 else:
     from lib.tasks_win import run_all_tests
 
@@ -92,16 +92,19 @@ def parse_args():
                           ' (in seconds).')
     harness_og.add_option('-a', '--args', dest='shell_args', default='',
                           help='Extra args to pass to the JS shell.')
     harness_og.add_option('--jitflags', default='',
                           help="Obsolete. Does nothing.")
     harness_og.add_option('--tbpl', action='store_true',
                           help='Runs each test in all configurations tbpl'
                           ' tests.')
+    harness_og.add_option('--tbpl-debug', action='store_true',
+                          help='Runs each test in some faster configurations'
+                          ' tbpl tests.')
     harness_og.add_option('-g', '--debug', action='store_true',
                           help='Run a test in debugger.')
     harness_og.add_option('--debugger', default='gdb -q --args',
                           help='Debugger command.')
     harness_og.add_option('-J', '--jorendb', action='store_true',
                           help='Run under JS debugger.')
     harness_og.add_option('--passthrough', action='store_true',
                           help='Run tests with stdin/stdout attached to'
@@ -264,19 +267,19 @@ def load_tests(options, requested_paths,
                               xul_tester)
     skip_list = []
 
     if options.make_manifests:
         manifest.make_manifests(options.make_manifests, test_list)
         sys.exit()
 
     # Create a new test list. Apply each TBPL configuration to every test.
-    if options.tbpl:
+    if options.tbpl or options.tbpl_debug:
         new_test_list = []
-        flags_list = TBPL_FLAGS
+        flags_list = TBPL_FLAGS if options.tbpl else TBPL_DEBUG_FLAGS
         for test in test_list:
             for jitflags in flags_list:
                 tmp_test = copy(test)
                 tmp_test.jitflags = copy(test.jitflags)
                 tmp_test.jitflags.extend(jitflags)
                 new_test_list.append(tmp_test)
         test_list = new_test_list
 
--- a/js/src/tests/lib/tests.py
+++ b/js/src/tests/lib/tests.py
@@ -15,16 +15,22 @@ TBPL_FLAGS = [
     [], # no flags, normal baseline and ion
     ['--ion-eager', '--ion-offthread-compile=off'], # implies --baseline-eager
     ['--ion-eager', '--ion-offthread-compile=off',
      '--ion-check-range-analysis', '--ion-extra-checks', '--no-sse3', '--no-threads'],
     ['--baseline-eager'],
     ['--baseline-eager', '--no-fpu'],
     ['--no-baseline', '--no-ion'],
 ]
+# Run reduced variants on debug builds, since they take longer time.
+TBPL_DEBUG_FLAGS = [
+    [], # no flags, normal baseline and ion
+    ['--ion-eager', '--ion-offthread-compile=off'], # implies --baseline-eager
+    ['--baseline-eager'],
+]
 
 def do_run_cmd(cmd):
     l = [None, None]
     th_run_cmd(cmd, l)
     return l[1]
 
 def set_limits():
     # resource module not supported on all platforms
--- a/js/src/vm/NativeObject.cpp
+++ b/js/src/vm/NativeObject.cpp
@@ -12,23 +12,25 @@
 
 #include "gc/Marking.h"
 #include "js/Value.h"
 #include "vm/Debugger.h"
 #include "vm/TypedArrayCommon.h"
 
 #include "jsobjinlines.h"
 
+#include "gc/Nursery-inl.h"
 #include "vm/ArrayObject-inl.h"
 #include "vm/Shape-inl.h"
 
 using namespace js;
 
 using JS::GenericNaN;
 using mozilla::DebugOnly;
+using mozilla::PodCopy;
 using mozilla::RoundUpPow2;
 
 static const ObjectElements emptyElementsHeader(0, 0);
 
 /* Objects with no elements share one empty set of elements. */
 HeapSlot* const js::emptyObjectElements =
     reinterpret_cast<HeapSlot*>(uintptr_t(&emptyElementsHeader) + sizeof(ObjectElements));
 
@@ -372,97 +374,72 @@ NativeObject::setSlotSpan(ExclusiveConte
 
     if (!updateSlotsForSpan(cx, oldSpan, span))
         return false;
 
     lastProperty()->base()->setSlotSpan(span);
     return true;
 }
 
-// This will not run the garbage collector.  If a nursery cannot accomodate the slot array
-// an attempt will be made to place the array in the tenured area.
-static HeapSlot*
-AllocateSlots(ExclusiveContext* cx, JSObject* obj, uint32_t nslots)
-{
-    if (cx->isJSContext())
-        return cx->asJSContext()->runtime()->gc.nursery.allocateSlots(obj, nslots);
-    return obj->zone()->pod_malloc<HeapSlot>(nslots);
-}
-
-// This will not run the garbage collector.  If a nursery cannot accomodate the slot array
-// an attempt will be made to place the array in the tenured area.
-//
-// If this returns null then the old slots will be left alone.
-static HeapSlot*
-ReallocateSlots(ExclusiveContext* cx, JSObject* obj, HeapSlot* oldSlots,
-                uint32_t oldCount, uint32_t newCount)
-{
-    if (cx->isJSContext()) {
-        return cx->asJSContext()->runtime()->gc.nursery.reallocateSlots(obj, oldSlots,
-                                                                        oldCount, newCount);
-    }
-    return obj->zone()->pod_realloc<HeapSlot>(oldSlots, oldCount, newCount);
-}
-
 bool
 NativeObject::growSlots(ExclusiveContext* cx, uint32_t oldCount, uint32_t newCount)
 {
     MOZ_ASSERT(newCount > oldCount);
     MOZ_ASSERT_IF(!is<ArrayObject>(), newCount >= SLOT_CAPACITY_MIN);
 
     /*
      * Slot capacities are determined by the span of allocated objects. Due to
      * the limited number of bits to store shape slots, object growth is
      * throttled well before the slot capacity can overflow.
      */
     NativeObject::slotsSizeMustNotOverflow();
     MOZ_ASSERT(newCount < NELEMENTS_LIMIT);
 
     if (!oldCount) {
-        slots_ = AllocateSlots(cx, this, newCount);
+        slots_ = AllocateObjectBuffer<HeapSlot>(cx, this, newCount);
         if (!slots_)
             return false;
         Debug_SetSlotRangeToCrashOnTouch(slots_, newCount);
         return true;
     }
 
-    HeapSlot* newslots = ReallocateSlots(cx, this, slots_, oldCount, newCount);
+    HeapSlot* newslots = ReallocateObjectBuffer<HeapSlot>(cx, this, slots_, oldCount, newCount);
     if (!newslots)
         return false;  /* Leave slots at its old size. */
 
     slots_ = newslots;
 
     Debug_SetSlotRangeToCrashOnTouch(slots_ + oldCount, newCount - oldCount);
 
     return true;
 }
 
 static void
 FreeSlots(ExclusiveContext* cx, HeapSlot* slots)
 {
     // Note: threads without a JSContext do not have access to GGC nursery allocated things.
     if (cx->isJSContext())
-        return cx->asJSContext()->runtime()->gc.nursery.freeSlots(slots);
+        return cx->asJSContext()->runtime()->gc.nursery.freeBuffer(slots);
     js_free(slots);
 }
 
 void
 NativeObject::shrinkSlots(ExclusiveContext* cx, uint32_t oldCount, uint32_t newCount)
 {
     MOZ_ASSERT(newCount < oldCount);
 
     if (newCount == 0) {
         FreeSlots(cx, slots_);
         slots_ = nullptr;
         return;
     }
 
     MOZ_ASSERT_IF(!is<ArrayObject>(), newCount >= SLOT_CAPACITY_MIN);
 
-    HeapSlot* newslots = ReallocateSlots(cx, this, slots_, oldCount, newCount);
+    HeapSlot* newslots = ReallocateObjectBuffer<HeapSlot>(cx, this, slots_, oldCount, newCount);
     if (!newslots)
         return;  /* Leave slots at its old size. */
 
     slots_ = newslots;
 }
 
 /* static */ bool
 NativeObject::sparsifyDenseElement(ExclusiveContext* cx, HandleNativeObject obj, uint32_t index)
@@ -659,41 +636,16 @@ NativeObject::maybeDensifySparseElements
      * to grow the object.
      */
     if (!obj->clearFlag(cx, BaseShape::INDEXED))
         return ED_FAILED;
 
     return ED_OK;
 }
 
-// This will not run the garbage collector.  If a nursery cannot accomodate the element array
-// an attempt will be made to place the array in the tenured area.
-static ObjectElements*
-AllocateElements(ExclusiveContext* cx, JSObject* obj, uint32_t nelems)
-{
-    if (cx->isJSContext())
-        return cx->asJSContext()->runtime()->gc.nursery.allocateElements(obj, nelems);
-    return reinterpret_cast<js::ObjectElements*>(obj->zone()->pod_malloc<HeapSlot>(nelems));
-}
-
-// This will not run the garbage collector.  If a nursery cannot accomodate the element array
-// an attempt will be made to place the array in the tenured area.
-static ObjectElements*
-ReallocateElements(ExclusiveContext* cx, JSObject* obj, ObjectElements* oldHeader,
-                   uint32_t oldCount, uint32_t newCount)
-{
-    if (cx->isJSContext()) {
-        return cx->asJSContext()->runtime()->gc.nursery.reallocateElements(obj, oldHeader,
-                                                                           oldCount, newCount);
-    }
-    return reinterpret_cast<js::ObjectElements*>(
-            obj->zone()->pod_realloc<HeapSlot>(reinterpret_cast<HeapSlot*>(oldHeader),
-                                               oldCount, newCount));
-}
-
 // Round up |reqAllocated| to a good size. Up to 1 Mebi (i.e. 1,048,576) the
 // slot count is usually a power-of-two:
 //
 //   8, 16, 32, 64, ..., 256 Ki, 512 Ki, 1 Mi
 //
 // Beyond that, we use this formula:
 //
 //   count(n+1) = Math.ceil(count(n) * 1.125)
@@ -815,29 +767,30 @@ NativeObject::growElements(ExclusiveCont
     MOZ_ASSERT(newCapacity > oldCapacity && newCapacity >= reqCapacity);
 
     // Don't let nelements get close to wrapping around uint32_t.
     if (newCapacity >= NELEMENTS_LIMIT)
         return false;
 
     uint32_t initlen = getDenseInitializedLength();
 
-    ObjectElements* newheader;
+    HeapSlot* oldHeaderSlots = reinterpret_cast<HeapSlot*>(getElementsHeader());
+    HeapSlot* newHeaderSlots;
     if (hasDynamicElements()) {
-        newheader = ReallocateElements(cx, this, getElementsHeader(), oldAllocated, newAllocated);
-        if (!newheader)
+        newHeaderSlots = ReallocateObjectBuffer<HeapSlot>(cx, this, oldHeaderSlots, oldAllocated, newAllocated);
+        if (!newHeaderSlots)
             return false;   // Leave elements at its old size.
     } else {
-        newheader = AllocateElements(cx, this, newAllocated);
-        if (!newheader)
+        newHeaderSlots = AllocateObjectBuffer<HeapSlot>(cx, this, newAllocated);
+        if (!newHeaderSlots)
             return false;   // Leave elements at its old size.
-        js_memcpy(newheader, getElementsHeader(),
-                  (ObjectElements::VALUES_PER_HEADER + initlen) * sizeof(Value));
+        PodCopy(newHeaderSlots, oldHeaderSlots, ObjectElements::VALUES_PER_HEADER + initlen);
     }
 
+    ObjectElements* newheader = reinterpret_cast<ObjectElements*>(newHeaderSlots);
     newheader->capacity = newCapacity;
     elements_ = newheader->elements();
 
     Debug_SetSlotRangeToCrashOnTouch(elements_ + initlen, newCapacity - initlen);
 
     return true;
 }
 
@@ -858,23 +811,25 @@ NativeObject::shrinkElements(ExclusiveCo
     uint32_t reqAllocated = reqCapacity + ObjectElements::VALUES_PER_HEADER;
     uint32_t newAllocated = goodAllocated(reqAllocated);
     if (newAllocated == oldAllocated)
         return;  // Leave elements at its old size.
 
     MOZ_ASSERT(newAllocated > ObjectElements::VALUES_PER_HEADER);
     uint32_t newCapacity = newAllocated - ObjectElements::VALUES_PER_HEADER;
 
-    ObjectElements* newheader = ReallocateElements(cx, this, getElementsHeader(),
-                                                   oldAllocated, newAllocated);
-    if (!newheader) {
+    HeapSlot* oldHeaderSlots = reinterpret_cast<HeapSlot*>(getElementsHeader());
+    HeapSlot* newHeaderSlots = ReallocateObjectBuffer<HeapSlot>(cx, this, oldHeaderSlots,
+                                                                oldAllocated, newAllocated);
+    if (!newHeaderSlots) {
         cx->recoverFromOutOfMemory();
         return;  // Leave elements at its old size.
     }
 
+    ObjectElements* newheader = reinterpret_cast<ObjectElements*>(newHeaderSlots);
     newheader->capacity = newCapacity;
     elements_ = newheader->elements();
 }
 
 /* static */ bool
 NativeObject::CopyElementsForWrite(ExclusiveContext* cx, NativeObject* obj)
 {
     MOZ_ASSERT(obj->denseElementsAreCopyOnWrite());
@@ -888,19 +843,20 @@ NativeObject::CopyElementsForWrite(Exclu
 
     uint32_t newCapacity = newAllocated - ObjectElements::VALUES_PER_HEADER;
 
     if (newCapacity >= NELEMENTS_LIMIT)
         return false;
 
     JSObject::writeBarrierPre(obj->getElementsHeader()->ownerObject());
 
-    ObjectElements* newheader = AllocateElements(cx, obj, newAllocated);
-    if (!newheader)
+    HeapSlot* newHeaderSlots = AllocateObjectBuffer<HeapSlot>(cx, obj, newAllocated);
+    if (!newHeaderSlots)
         return false;
+    ObjectElements* newheader = reinterpret_cast<ObjectElements*>(newHeaderSlots);
     js_memcpy(newheader, obj->getElementsHeader(),
               (ObjectElements::VALUES_PER_HEADER + initlen) * sizeof(Value));
 
     newheader->capacity = newCapacity;
     newheader->clearCopyOnWrite();
     obj->elements_ = newheader->elements();
 
     Debug_SetSlotRangeToCrashOnTouch(obj->elements_ + initlen, newCapacity - initlen);
--- a/js/src/vm/Runtime.cpp
+++ b/js/src/vm/Runtime.cpp
@@ -516,17 +516,17 @@ JSRuntime::addSizeOfIncludingThis(mozill
         rtSizes->scriptData += mallocSizeOf(r.front());
 
     if (jitRuntime_)
         jitRuntime_->execAlloc().addSizeOfCode(&rtSizes->code);
 
     rtSizes->gc.marker += gc.marker.sizeOfExcludingThis(mallocSizeOf);
     rtSizes->gc.nurseryCommitted += gc.nursery.sizeOfHeapCommitted();
     rtSizes->gc.nurseryDecommitted += gc.nursery.sizeOfHeapDecommitted();
-    rtSizes->gc.nurseryHugeSlots += gc.nursery.sizeOfHugeSlots(mallocSizeOf);
+    rtSizes->gc.nurseryMallocedBuffers += gc.nursery.sizeOfMallocedBuffers(mallocSizeOf);
     gc.storeBuffer.addSizeOfExcludingThis(mallocSizeOf, &rtSizes->gc);
 }
 
 static bool
 InvokeInterruptCallback(JSContext* cx)
 {
     MOZ_ASSERT(cx->runtime()->requestDepth >= 1);
 
--- a/js/src/vm/UnboxedObject.cpp
+++ b/js/src/vm/UnboxedObject.cpp
@@ -6,16 +6,17 @@
 
 #include "vm/UnboxedObject.h"
 
 #include "jit/JitCommon.h"
 #include "jit/Linker.h"
 
 #include "jsobjinlines.h"
 
+#include "gc/Nursery-inl.h"
 #include "vm/Shape-inl.h"
 
 using mozilla::ArrayLength;
 using mozilla::DebugOnly;
 using mozilla::PodCopy;
 using mozilla::UniquePtr;
 
 using namespace js;
@@ -1029,26 +1030,28 @@ UnboxedArrayObject::create(ExclusiveCont
         res = NewObjectWithGroup<UnboxedArrayObject>(cx, group, allocKind, newKind);
         if (!res)
             return nullptr;
         res->setInlineElements();
 
         size_t capacity = (GetGCKindBytes(allocKind) - offsetOfInlineElements()) / elementSize;
         res->setCapacityIndex(exactCapacityIndex(capacity));
     } else {
-        UniquePtr<uint8_t[], JS::FreePolicy> elements(
-            cx->zone()->pod_malloc<uint8_t>(length * elementSize));
-        if (!elements)
-            return nullptr;
-
         res = NewObjectWithGroup<UnboxedArrayObject>(cx, group, gc::AllocKind::OBJECT0, newKind);
         if (!res)
             return nullptr;
 
-        res->elements_ = elements.release();
+        res->elements_ = AllocateObjectBuffer<uint8_t>(cx, res, length * elementSize);
+        if (!res->elements_) {
+            // Make the object safe for GC.
+            res->setInlineElements();
+            res->setInitializedLength(0);
+            return nullptr;
+        }
+
         res->setCapacityIndex(CapacityMatchesLengthIndex);
     }
 
     res->setLength(length);
     res->setInitializedLength(0);
     return res;
 }
 
@@ -1140,20 +1143,61 @@ UnboxedArrayObject::objectMoved(JSObject
     // Fix up possible inline data pointer.
     if (src.hasInlineElements())
         dst.setInlineElements();
 }
 
 /* static */ void
 UnboxedArrayObject::finalize(FreeOp* fop, JSObject* obj)
 {
+    MOZ_ASSERT(!IsInsideNursery(obj));
     if (!obj->as<UnboxedArrayObject>().hasInlineElements())
         js_free(obj->as<UnboxedArrayObject>().elements());
 }
 
+/* static */ size_t
+UnboxedArrayObject::objectMovedDuringMinorGC(JSTracer* trc, JSObject* dst, JSObject* src,
+                                             gc::AllocKind allocKind)
+{
+    UnboxedArrayObject* ndst = &dst->as<UnboxedArrayObject>();
+    UnboxedArrayObject* nsrc = &src->as<UnboxedArrayObject>();
+    MOZ_ASSERT(ndst->elements() == nsrc->elements());
+
+    Nursery& nursery = trc->runtime()->gc.nursery;
+
+    if (!nursery.isInside(nsrc->elements())) {
+        nursery.removeMallocedBuffer(nsrc->elements());
+        return 0;
+    }
+
+    // Determine if we can use inline data for the target array. If this is
+    // possible, the nursery will have picked an allocation size that is large
+    // enough.
+    size_t nbytes = nsrc->capacity() * nsrc->elementSize();
+    if (offsetOfInlineElements() + nbytes <= GetGCKindBytes(allocKind)) {
+        ndst->setInlineElements();
+    } else {
+        MOZ_ASSERT(allocKind == gc::AllocKind::OBJECT0);
+
+        uint8_t* data = nsrc->zone()->pod_malloc<uint8_t>(nbytes);
+        if (!data)
+            CrashAtUnhandlableOOM("Failed to allocate unboxed array elements while tenuring.");
+        ndst->elements_ = data;
+    }
+
+    PodCopy(ndst->elements(), nsrc->elements(), nsrc->initializedLength() * nsrc->elementSize());
+
+    // Set a forwarding pointer for the element buffers in case they were
+    // preserved on the stack by Ion.
+    bool direct = nsrc->capacity() * nsrc->elementSize() >= sizeof(uintptr_t);
+    nursery.maybeSetForwardingPointer(trc, nsrc->elements(), ndst->elements(), direct);
+
+    return ndst->hasInlineElements() ? 0 : nbytes;
+}
+
 // Possible capacities for unboxed arrays. Some of these capacities might seem
 // a little weird, but were chosen to allow the inline data of objects of each
 // size to be fully utilized for arrays of the various types on both 32 bit and
 // 64 bit platforms.
 /* static */ const uint32_t
 UnboxedArrayObject::CapacityArray[] = {
     UINT32_MAX, // For CapacityMatchesLengthIndex.
     0, 1, 2, 3, 4, 5, 6, 8, 9, 10, 12, 13, 16, 17, 18, 20, 24, 26, 32, 34, 36, 48, 52, 64, 68,
@@ -1261,23 +1305,24 @@ UnboxedArrayObject::growElements(Exclusi
     MOZ_ASSERT(oldCapacity < cap);
     MOZ_ASSERT(cap <= newCapacity);
 
     // The allocation size computation below cannot have integer overflows.
     JS_STATIC_ASSERT(MaximumCapacity < UINT32_MAX / sizeof(double));
 
     uint8_t* newElements;
     if (hasInlineElements()) {
-        newElements = cx->zone()->pod_malloc<uint8_t>(newCapacity * elementSize());
+        newElements = AllocateObjectBuffer<uint8_t>(cx, this, newCapacity * elementSize());
         if (!newElements)
             return false;
         js_memcpy(newElements, elements(), initializedLength() * elementSize());
     } else {
-        newElements = cx->zone()->pod_realloc<uint8_t>(elements(), oldCapacity * elementSize(),
-                                                       newCapacity * elementSize());
+        newElements = ReallocateObjectBuffer<uint8_t>(cx, this, elements(),
+                                                      oldCapacity * elementSize(),
+                                                      newCapacity * elementSize());
         if (!newElements)
             return false;
     }
 
     elements_ = newElements;
     setCapacityIndex(newCapacityIndex);
 
     return true;
@@ -1294,19 +1339,19 @@ UnboxedArrayObject::shrinkElements(Exclu
     uint32_t newCapacity = computeCapacity(newCapacityIndex, 0);
 
     MOZ_ASSERT(cap < oldCapacity);
     MOZ_ASSERT(cap <= newCapacity);
 
     if (newCapacity >= oldCapacity)
         return;
 
-    uint8_t* newElements =
-        cx->zone()->pod_realloc<uint8_t>(elements(), oldCapacity * elementSize(),
-                                         newCapacity * elementSize());
+    uint8_t* newElements = ReallocateObjectBuffer<uint8_t>(cx, this, elements(),
+                                                           oldCapacity * elementSize(),
+                                                           newCapacity * elementSize());
     if (!newElements)
         return;
 
     elements_ = newElements;
     setCapacityIndex(newCapacityIndex);
 }
 
 bool
@@ -1491,17 +1536,18 @@ UnboxedArrayObject::obj_enumerate(JSCont
     }
     return properties.append(NameToId(cx->names().length));
 }
 
 const Class UnboxedArrayObject::class_ = {
     "Array",
     Class::NON_NATIVE |
     JSCLASS_IMPLEMENTS_BARRIERS |
-    0 /* FIXME using this flag can severely hurt performance: JSCLASS_BACKGROUND_FINALIZE */,
+    JSCLASS_SKIP_NURSERY_FINALIZE |
+    JSCLASS_BACKGROUND_FINALIZE,
     nullptr,        /* addProperty */
     nullptr,        /* delProperty */
     nullptr,        /* getProperty */
     nullptr,        /* setProperty */
     nullptr,        /* enumerate   */
     nullptr,        /* resolve     */
     nullptr,        /* mayResolve  */
     nullptr,        /* convert     */
--- a/js/src/vm/UnboxedObject.h
+++ b/js/src/vm/UnboxedObject.h
@@ -394,16 +394,19 @@ class UnboxedArrayObject : public JSObje
 
     void fillAfterConvert(ExclusiveContext* cx,
                           const AutoValueVector& values, size_t* valueCursor);
 
     static void trace(JSTracer* trc, JSObject* object);
     static void objectMoved(JSObject* obj, const JSObject* old);
     static void finalize(FreeOp* fop, JSObject* obj);
 
+    static size_t objectMovedDuringMinorGC(JSTracer* trc, JSObject* dst, JSObject* src,
+                                           gc::AllocKind allocKind);
+
     uint8_t* elements() {
         return elements_;
     }
 
     bool hasInlineElements() const {
         return elements_ == &inlineElements_[0];
     }
 
--- a/js/xpconnect/src/Sandbox.cpp
+++ b/js/xpconnect/src/Sandbox.cpp
@@ -940,17 +940,21 @@ xpc::CreateSandboxObject(JSContext* cx, 
         JSAutoCompartment ac(cx, sandbox);
 
         if (options.proto) {
             bool ok = JS_WrapObject(cx, &options.proto);
             if (!ok)
                 return NS_ERROR_XPC_UNEXPECTED;
 
             // Now check what sort of thing we've got in |proto|
-            JSObject* unwrappedProto = js::UncheckedUnwrap(options.proto, false);
+            JSObject* unwrappedProto = js::CheckedUnwrap(options.proto, false);
+            if (!unwrappedProto) {
+                JS_ReportError(cx, "Sandbox must subsume sandboxPrototype");
+                return NS_ERROR_INVALID_ARG;
+            }
             const js::Class* unwrappedClass = js::GetObjectClass(unwrappedProto);
             if (IS_WN_CLASS(unwrappedClass) ||
                 mozilla::dom::IsDOMClass(Jsvalify(unwrappedClass))) {
                 // Wrap it up in a proxy that will do the right thing in terms
                 // of this-binding for methods.
                 RootedValue priv(cx, ObjectValue(*options.proto));
                 options.proto = js::NewProxyObject(cx, &xpc::sandboxProxyHandler,
                                                    priv, nullptr);
--- a/js/xpconnect/src/XPCJSRuntime.cpp
+++ b/js/xpconnect/src/XPCJSRuntime.cpp
@@ -2548,18 +2548,18 @@ ReportJSRuntimeExplicitTreeStats(const J
     RREPORT_BYTES(rtPath + NS_LITERAL_CSTRING("runtime/gc/marker"),
         KIND_HEAP, rtStats.runtime.gc.marker,
         "The GC mark stack and gray roots.");
 
     RREPORT_BYTES(rtPath + NS_LITERAL_CSTRING("runtime/gc/nursery-committed"),
         KIND_NONHEAP, rtStats.runtime.gc.nurseryCommitted,
         "Memory being used by the GC's nursery.");
 
-    RREPORT_BYTES(rtPath + NS_LITERAL_CSTRING("runtime/gc/nursery-huge-slots"),
-        KIND_NONHEAP, rtStats.runtime.gc.nurseryHugeSlots,
+    RREPORT_BYTES(rtPath + NS_LITERAL_CSTRING("runtime/gc/nursery-malloced-buffers"),
+        KIND_NONHEAP, rtStats.runtime.gc.nurseryMallocedBuffers,
         "Out-of-line slots and elements belonging to objects in the "
         "nursery.");
 
     RREPORT_BYTES(rtPath + NS_LITERAL_CSTRING("runtime/gc/store-buffer/vals"),
         KIND_HEAP, rtStats.runtime.gc.storeBufferVals,
         "Values in the store buffer.");
 
     RREPORT_BYTES(rtPath + NS_LITERAL_CSTRING("runtime/gc/store-buffer/cells"),
--- a/js/xpconnect/src/XPCShellImpl.cpp
+++ b/js/xpconnect/src/XPCShellImpl.cpp
@@ -22,16 +22,17 @@
 #include "nsArrayEnumerator.h"
 #include "nsCOMArray.h"
 #include "nsDirectoryServiceUtils.h"
 #include "nsIJSRuntimeService.h"
 #include "nsCOMPtr.h"
 #include "nsAutoPtr.h"
 #include "nsJSPrincipals.h"
 #include "xpcpublic.h"
+#include "xpcprivate.h"
 #include "BackstagePass.h"
 #include "nsIScriptSecurityManager.h"
 #include "nsIPrincipal.h"
 #include "nsJSUtils.h"
 
 #include "base/histogram.h"
 
 #ifdef ANDROID
@@ -627,16 +628,45 @@ SimulateActivityCallback(JSContext* cx, 
     if (args.length() != 1 || !args[0].isBoolean()) {
         JS_ReportError(cx, "Wrong number of arguments");
         return false;
     }
     xpc::SimulateActivityCallback(args[0].toBoolean());
     return true;
 }
 
+static bool
+RegisterAppManifest(JSContext* cx, unsigned argc, jsval* vp)
+{
+    JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
+    if (args.length() != 1) {
+        JS_ReportError(cx, "Wrong number of arguments");
+        return false;
+    }
+    if (!args[0].isObject()) {
+        JS_ReportError(cx, "Expected object as argument 1 to registerAppManifest");
+        return false;
+    }
+
+    Rooted<JSObject*> arg1(cx, &args[0].toObject());
+    nsCOMPtr<nsIFile> file;
+    nsresult rv = nsXPConnect::XPConnect()->
+        WrapJS(cx, arg1, NS_GET_IID(nsIFile), getter_AddRefs(file));
+    if (NS_FAILED(rv)) {
+        XPCThrower::Throw(rv, cx);
+        return false;
+    }
+    rv = XRE_AddManifestLocation(NS_APP_LOCATION, file);
+    if (NS_FAILED(rv)) {
+        XPCThrower::Throw(rv, cx);
+        return false;
+    }
+    return true;
+}
+
 static const JSFunctionSpec glob_functions[] = {
     JS_FS("print",           Print,          0,0),
     JS_FS("readline",        ReadLine,       1,0),
     JS_FS("load",            Load,           1,0),
     JS_FS("quit",            Quit,           0,0),
     JS_FS("ignoreReportedErrors", IgnoreReportedErrors, 1,0),
     JS_FS("version",         Version,        1,0),
     JS_FS("build",           BuildDate,      0,0),
@@ -647,16 +677,17 @@ static const JSFunctionSpec glob_functio
     JS_FS("gczeal",          GCZeal,         1,0),
 #endif
     JS_FS("options",         Options,        0,0),
     JS_FS("sendCommand",     SendCommand,    1,0),
     JS_FS("atob",            Atob,           1,0),
     JS_FS("btoa",            Btoa,           1,0),
     JS_FS("setInterruptCallback", SetInterruptCallback, 1,0),
     JS_FS("simulateActivityCallback", SimulateActivityCallback, 1,0),
+    JS_FS("registerAppManifest", RegisterAppManifest, 1, 0),
     JS_FS_END
 };
 
 static bool
 env_setProperty(JSContext* cx, HandleObject obj, HandleId id, MutableHandleValue vp,
                 ObjectOpResult& result)
 {
 /* XXX porting may be easy, but these don't seem to supply setenv by default */
@@ -1336,17 +1367,17 @@ XRE_XPCShellMain(int argc, char** argv, 
             return usage();
 
         nsCOMPtr<nsIFile> lf;
         rv = XRE_GetFileFromPath(argv[2], getter_AddRefs(lf));
         if (NS_FAILED(rv)) {
             printf("Couldn't get manifest file.\n");
             return 1;
         }
-        XRE_AddManifestLocation(NS_COMPONENT_LOCATION, lf);
+        XRE_AddManifestLocation(NS_APP_LOCATION, lf);
 
         argc -= 2;
         argv += 2;
     }
 
 #ifdef MOZ_CRASHREPORTER
     const char* val = getenv("MOZ_CRASHREPORTER");
     if (val && *val) {
--- a/js/xpconnect/tests/unit/test_attributes.js
+++ b/js/xpconnect/tests/unit/test_attributes.js
@@ -3,18 +3,18 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 const Cc = Components.classes;
 const Ci = Components.interfaces;
 
 function run_test() {
 
   // Load the component manifests.
-  Components.manager.autoRegister(do_get_file('../components/native/xpctest.manifest'));
-  Components.manager.autoRegister(do_get_file('../components/js/xpctest.manifest'));
+  registerAppManifest(do_get_file('../components/native/xpctest.manifest'));
+  registerAppManifest(do_get_file('../components/js/xpctest.manifest'));
 
   // Test for each component.
   test_component_readwrite("@mozilla.org/js/xpc/test/native/ObjectReadWrite;1");
   test_component_readwrite("@mozilla.org/js/xpc/test/js/ObjectReadWrite;1");
   test_component_readonly("@mozilla.org/js/xpc/test/native/ObjectReadOnly;1");
   test_component_readonly("@mozilla.org/js/xpc/test/js/ObjectReadOnly;1");
 }
 
new file mode 100644
--- /dev/null
+++ b/js/xpconnect/tests/unit/test_bug1151385.js
@@ -0,0 +1,9 @@
+function run_test()
+{
+  try {
+    var sandbox = new Components.utils.Sandbox(null, {"sandboxPrototype" : {}});
+    do_check_true(false);
+  } catch (e) {
+    do_check_true(/must subsume sandboxPrototype/.test(e));
+  }
+}
--- a/js/xpconnect/tests/unit/test_params.js
+++ b/js/xpconnect/tests/unit/test_params.js
@@ -3,18 +3,18 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 const Cc = Components.classes;
 const Ci = Components.interfaces;
 
 function run_test() {
 
   // Load the component manifests.
-  Components.manager.autoRegister(do_get_file('../components/native/xpctest.manifest'));
-  Components.manager.autoRegister(do_get_file('../components/js/xpctest.manifest'));
+  registerAppManifest(do_get_file('../components/native/xpctest.manifest'));
+  registerAppManifest(do_get_file('../components/js/xpctest.manifest'));
 
   // Test for each component.
   test_component("@mozilla.org/js/xpc/test/native/Params;1");
   test_component("@mozilla.org/js/xpc/test/js/Params;1");
 }
 
 function test_component(contractid) {
 
--- a/js/xpconnect/tests/unit/test_returncode.js
+++ b/js/xpconnect/tests/unit/test_returncode.js
@@ -11,18 +11,18 @@ function getConsoleMessages() {
   let messages = [m.toString() for (m of consoleService.getMessageArray())];
   // reset ready for the next call.
   consoleService.reset();
   return messages;
 }
 
 function run_test() {
   // Load the component manifests.
-  Cm.autoRegister(do_get_file('../components/native/xpctest.manifest'));
-  Cm.autoRegister(do_get_file('../components/js/xpctest.manifest'));
+  registerAppManifest(do_get_file('../components/native/xpctest.manifest'));
+  registerAppManifest(do_get_file('../components/js/xpctest.manifest'));
 
   // and the tests.
   test_simple();
   test_nested();
 }
 
 function test_simple() {
   let parent = Cc["@mozilla.org/js/xpc/test/native/ReturnCodeParent;1"]
--- a/js/xpconnect/tests/unit/xpcshell.ini
+++ b/js/xpconnect/tests/unit/xpcshell.ini
@@ -50,16 +50,17 @@ support-files =
 [test_bug1033253.js]
 [test_bug1033920.js]
 [test_bug1033927.js]
 [test_bug1034262.js]
 [test_bug1082450.js]
 [test_bug1081990.js]
 [test_bug1110546.js]
 [test_bug1150771.js]
+[test_bug1151385.js]
 [test_bug_442086.js]
 [test_callFunctionWithAsyncStack.js]
 [test_file.js]
 [test_blob.js]
 [test_blob2.js]
 [test_file2.js]
 [test_import.js]
 [test_import_fail.js]
--- a/layout/base/nsCSSFrameConstructor.cpp
+++ b/layout/base/nsCSSFrameConstructor.cpp
@@ -2683,17 +2683,17 @@ nsCSSFrameConstructor::ConstructRootFram
 
   // Bind the viewport frame to the root view
   nsView* rootView = mPresShell->GetViewManager()->GetRootView();
   viewportFrame->SetView(rootView);
 
   nsContainerFrame::SyncFrameViewProperties(mPresShell->GetPresContext(), viewportFrame,
                                             viewportPseudoStyle, rootView);
   nsContainerFrame::SyncWindowProperties(mPresShell->GetPresContext(), viewportFrame,
-                                         rootView);
+                                         rootView, nullptr, nsContainerFrame::SET_ASYNC);
 
   // Make it an absolute container for fixed-pos elements
   viewportFrame->AddStateBits(NS_FRAME_CAN_HAVE_ABSPOS_CHILDREN);
   viewportFrame->MarkAsAbsoluteContainingBlock();
 
   return viewportFrame;
 }
 
--- a/layout/base/nsIPresShell.h
+++ b/layout/base/nsIPresShell.h
@@ -135,18 +135,18 @@ typedef struct CapturingContentInfo {
   bool mPointerLock;
   bool mRetargetToElement;
   bool mPreventDrag;
   mozilla::StaticRefPtr<nsIContent> mContent;
 } CapturingContentInfo;
 
 // d910f009-d209-74c1-6b04-30c83c051c78
 #define NS_IPRESSHELL_IID \
-  { 0xd910f009, 0xd209, 0x74c1, \
-    { 0x6b, 0x04, 0x30, 0xc8, 0x3c, 0x05, 0x1c, 0x78 } }
+  { 0x025264c6, 0x0b12, 0x4804, \
+    { 0xa3, 0x3e, 0xb7, 0x73, 0xf2, 0x19, 0x48, 0x90 } }
 
 // debug VerifyReflow flags
 #define VERIFY_REFLOW_ON                    0x01
 #define VERIFY_REFLOW_NOISY                 0x02
 #define VERIFY_REFLOW_ALL                   0x04
 #define VERIFY_REFLOW_DUMP_COMMANDS         0x08
 #define VERIFY_REFLOW_NOISY_RC              0x10
 #define VERIFY_REFLOW_REALLY_NOISY_RC       0x20
@@ -1656,16 +1656,18 @@ public:
 
   void SetNeverPainting(bool aNeverPainting) {
     mIsNeverPainting = aNeverPainting;
   }
 
   bool HasPendingReflow() const
     { return mReflowScheduled || mReflowContinueTimer; }
 
+  void SyncWindowProperties(nsView* aView);
+
 protected:
   friend class nsRefreshDriver;
 
   // IMPORTANT: The ownership implicit in the following member variables
   // has been explicitly checked.  If you add any members to this class,
   // please make the ownership explicit (pinkerton, scc).
 
   // These are the same Document and PresContext owned by the DocViewer.
--- a/layout/base/nsLayoutUtils.cpp
+++ b/layout/base/nsLayoutUtils.cpp
@@ -3111,20 +3111,22 @@ nsLayoutUtils::PaintFrame(nsRenderingCon
     if (ignoreViewportScrolling && presContext->IsRootContentDocument()) {
       if (nsIFrame* rootScrollFrame = presShell->GetRootScrollFrame()) {
         if (nsIContent* content = rootScrollFrame->GetContent()) {
           id = nsLayoutUtils::FindOrCreateIDFor(content);
         }
       }
     }
 #if !defined(MOZ_WIDGET_ANDROID) || defined(MOZ_ANDROID_APZ)
-    else if (presShell->GetDocument() && presShell->GetDocument()->IsRootDisplayDocument()) {
+    else if (presShell->GetDocument() && presShell->GetDocument()->IsRootDisplayDocument()
+        && !presShell->GetRootScrollFrame()) {
       // In cases where the root document is a XUL document, we want to take
       // the ViewID from the root element, as that will be the ViewID of the
-      // root APZC in the tree.
+      // root APZC in the tree. Skip doing this in cases where we know
+      // nsGfxScrollFrame::BuilDisplayList will do it instead.
       if (dom::Element* element = presShell->GetDocument()->GetDocumentElement()) {
         id = nsLayoutUtils::FindOrCreateIDFor(element);
       }
     }
 #endif
 
     nsDisplayListBuilder::AutoCurrentScrollParentIdSetter idSetter(&builder, id);
 
--- a/layout/base/nsPresShell.cpp
+++ b/layout/base/nsPresShell.cpp
@@ -9240,17 +9240,18 @@ PresShell::DoReflow(nsIFrame* target, bo
   // Always use boundsRelativeToTarget here, not desiredSize.GetVisualOverflowArea(),
   // because for root frames (where they could be different, since root frames
   // are allowed to have overflow) the root view bounds need to match the
   // viewport bounds; the view manager "window dimensions" code depends on it.
   nsContainerFrame::SyncFrameViewAfterReflow(mPresContext, target,
                                              target->GetView(),
                                              boundsRelativeToTarget);
   nsContainerFrame::SyncWindowProperties(mPresContext, target,
-                                         target->GetView(), &rcx);
+                                         target->GetView(), &rcx,
+                                         nsContainerFrame::SET_ASYNC);
 
   target->DidReflow(mPresContext, nullptr, nsDidReflowStatus::FINISHED);
   if (target == rootFrame && size.BSize(wm) == NS_UNCONSTRAINEDSIZE) {
     mPresContext->SetVisibleArea(boundsRelativeToTarget);
   }
 
 #ifdef DEBUG
   mCurrentReflowRoot = nullptr;
@@ -11076,8 +11077,18 @@ void
 PresShell::ResumePainting()
 {
   if (GetPresContext()->RefreshDriver()->PresContext() != GetPresContext())
     return;
 
   mPaintingIsFrozen = false;
   GetPresContext()->RefreshDriver()->Thaw();
 }
+
+void
+nsIPresShell::SyncWindowProperties(nsView* aView)
+{
+  nsIFrame* frame = aView->GetFrame();
+  if (frame && mPresContext) {
+    nsRenderingContext rcx(CreateReferenceRenderingContext());
+    nsContainerFrame::SyncWindowProperties(mPresContext, frame, aView, &rcx, 0);
+  }
+}
--- a/layout/generic/nsContainerFrame.cpp
+++ b/layout/generic/nsContainerFrame.cpp
@@ -604,24 +604,25 @@ IsTopLevelWidget(nsIWidget* aWidget)
          windowType == eWindowType_dialog ||
          windowType == eWindowType_sheet;
   // popups aren't toplevel so they're not handled here
 }
 
 void
 nsContainerFrame::SyncWindowProperties(nsPresContext*       aPresContext,
                                        nsIFrame*            aFrame,
-                                       nsView*             aView,
-                                       nsRenderingContext*  aRC)
+                                       nsView*              aView,
+                                       nsRenderingContext*  aRC,
+                                       uint32_t             aFlags)
 {
 #ifdef MOZ_XUL
   if (!aView || !nsCSSRendering::IsCanvasFrame(aFrame) || !aView->HasWidget())
     return;
 
-  nsIWidget* windowWidget = GetPresContextContainerWidget(aPresContext);
+  nsCOMPtr<nsIWidget> windowWidget = GetPresContextContainerWidget(aPresContext);
   if (!windowWidget || !IsTopLevelWidget(windowWidget))
     return;
 
   nsViewManager* vm = aView->GetViewManager();
   nsView* rootView = vm->GetRootView();
 
   if (aView != rootView)
     return;
@@ -645,24 +646,37 @@ nsContainerFrame::SyncWindowProperties(n
     // even if the HTML doesn't have a background-color set.
     return;
   }
 
   nsIFrame *rootFrame = aPresContext->PresShell()->FrameConstructor()->GetRootElementStyleFrame();
   if (!rootFrame)
     return;
 
+  if (aFlags & SET_ASYNC) {
+    aView->SetNeedsWindowPropertiesSync();
+    return;
+  }
+
+  nsRefPtr<nsPresContext> kungFuDeathGrip(aPresContext);
+  nsWeakFrame weak(rootFrame);
+
   nsTransparencyMode mode = nsLayoutUtils::GetFrameTransparency(aFrame, rootFrame);
-  nsIWidget* viewWidget = aView->GetWidget();
+  int32_t shadow = rootFrame->StyleUIReset()->mWindowShadow;
+  nsCOMPtr<nsIWidget> viewWidget = aView->GetWidget();
   viewWidget->SetTransparencyMode(mode);
-  windowWidget->SetWindowShadowStyle(rootFrame->StyleUIReset()->mWindowShadow);
+  windowWidget->SetWindowShadowStyle(shadow);
 
   if (!aRC)
     return;
-  
+
+  if (!weak.IsAlive()) {
+    return;
+  }
+
   nsBoxLayoutState aState(aPresContext, aRC);
   nsSize minSize = rootFrame->GetMinSize(aState);
   nsSize maxSize = rootFrame->GetMaxSize(aState);
 
   SetSizeConstraints(aPresContext, windowWidget, minSize, maxSize);
 #endif
 }
 
--- a/layout/generic/nsContainerFrame.h
+++ b/layout/generic/nsContainerFrame.h
@@ -175,20 +175,26 @@ public:
   static void SyncFrameViewAfterReflow(nsPresContext* aPresContext,
                                        nsIFrame*       aFrame,
                                        nsView*        aView,
                                        const nsRect&   aVisualOverflowArea,
                                        uint32_t        aFlags = 0);
 
   // Syncs properties to the top level view and window, like transparency and
   // shadow.
+  // The SET_ASYNC indicates that the actual nsIWidget calls to sync the window
+  // properties should be done async.
+  enum {
+    SET_ASYNC = 0x01,
+  };
   static void SyncWindowProperties(nsPresContext*       aPresContext,
                                    nsIFrame*            aFrame,
-                                   nsView*             aView,
-                                   nsRenderingContext*  aRC = nullptr);
+                                   nsView*              aView,
+                                   nsRenderingContext*  aRC,
+                                   uint32_t             aFlags);
 
   // Sets the view's attributes from the frame style.
   // - visibility
   // - clip
   // Call this when one of these styles changes or when the view has just
   // been created.
   // @param aStyleContext can be null, in which case the frame's style context is used
   static void SyncFrameViewProperties(nsPresContext*  aPresContext,
--- a/layout/generic/nsHTMLReflowState.cpp
+++ b/layout/generic/nsHTMLReflowState.cpp
@@ -354,16 +354,23 @@ nsHTMLReflowState::Init(nsPresContext* a
     }
   }
 
   NS_WARN_IF_FALSE(AvailableISize() != NS_UNCONSTRAINEDSIZE,
                    "have unconstrained inline-size; this should only result from "
                    "very large sizes, not attempts at intrinsic inline-size "
                    "calculation");
 
+  if (AvailableBSize() != NS_UNCONSTRAINEDSIZE && parentReflowState &&
+      parentReflowState->GetWritingMode().IsOrthogonalTo(mWritingMode)) {
+    // Orthogonal frames are always reflowed with unconstrained block-size,
+    // to avoid incomplete reflow across an orthogonal boundary.
+    AvailableBSize() = NS_UNCONSTRAINEDSIZE;
+  }
+
   mStylePosition = frame->StylePosition();
   mStyleDisplay = frame->StyleDisplay();
   mStyleVisibility = frame->StyleVisibility();
   mStyleBorder = frame->StyleBorder();
   mStyleMargin = frame->StyleMargin();
   mStylePadding = frame->StylePadding();
   mStyleText = frame->StyleText();
 
new file mode 100644
--- /dev/null
+++ b/layout/reftests/writing-mode/1152941-1-orthogonal-blocksize-overflow-ref.html
@@ -0,0 +1,39 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<style>
+html {
+  -webkit-writing-mode: vertical-rl;
+  writing-mode: vertical-rl;
+  font: 16px/24px monospace;
+  padding: 20px;
+}
+body {
+  width: 40em;
+  height: 25em;
+}
+blockquote {
+  -webkit-writing-mode: horizontal-tb;
+  writing-mode: horizontal-tb;
+  width: 20em;
+  color: transparent;
+}
+</style>
+</head>
+
+<body>
+  <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed ac fringilla quam,
+  eu ultricies augue.</p>
+
+  <blockquote>
+    <!-- Short enough text that it won't overflow. -->
+    <p>Ut accumsan dui eu elit dapibus rutrum. Nunc tristique urna eget ex dictum
+    placerat. Nunc venenatis enim sed odio iaculis, consequat consectetur sem
+    elementum.</p>
+  </blockquote>
+
+  <p>Maecenas nec ornare ligula. Phasellus eleifend
+  elit leo, nec vestibulum sapien consectetur quis.</p>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/writing-mode/1152941-1-orthogonal-blocksize-overflow.html
@@ -0,0 +1,53 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<style>
+html {
+  -webkit-writing-mode: vertical-rl;
+  writing-mode: vertical-rl;
+  font: 16px/24px monospace;
+  padding: 20px;
+}
+body {
+  width: 40em;
+  height: 25em;
+}
+blockquote {
+  -webkit-writing-mode: horizontal-tb;
+  writing-mode: horizontal-tb;
+  width: 20em;
+  color: transparent;
+}
+</style>
+</head>
+
+<body>
+  <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed ac fringilla quam,
+  eu ultricies augue.</p>
+
+  <blockquote>
+    <!-- The (invisible) text in the orthogonal block should NOT make following
+         content vanish if it overflows the <body>'s height! -->
+    <p>Ut accumsan dui eu elit dapibus rutrum. Nunc tristique urna eget ex dictum
+    placerat. Nunc venenatis enim sed odio iaculis, consequat consectetur sem
+    elementum.</p>
+
+    <p>Fusce eros eros, eleifend eget eros at, convallis tempor tortor. Cras
+    at gravida leo. Proin ultricies ipsum vitae felis suscipit, a tincidunt orci
+    mattis. Cras in suscipit mauris.</p>
+
+    <p>Etiam eu pellentesque nisi. Quisque
+    ullamcorper dui odio, eu feugiat nunc interdum vitae. Morbi egestas dolor a
+    nulla pellentesque, faucibus tincidunt diam facilisis. Suspendisse at urna
+    varius, pellentesque nibh non, venenatis ante.</p>
+
+    <p>Nullam aliquet orci vel dui
+    dapibus, nec facilisis enim interdum. Morbi condimentum venenatis commodo. Sed
+    viverra diam nec lacinia congue. Etiam ultrices luctus volutpat.</p>
+  </blockquote>
+
+  <p>Maecenas nec ornare ligula. Phasellus eleifend
+  elit leo, nec vestibulum sapien consectetur quis.</p>
+</body>
+</html>
--- a/layout/reftests/writing-mode/reftest.list
+++ b/layout/reftests/writing-mode/reftest.list
@@ -123,11 +123,12 @@ test-pref(dom.meta-viewport.enabled,true
 test-pref(dom.meta-viewport.enabled,true) test-pref(font.size.inflation.emPerLine,15) test-pref(font.size.inflation.forceEnabled,true) test-pref(font.size.inflation.lineThreshold,0) != font-inflation-1d.html font-inflation-1-ref.html
 pref(dom.meta-viewport.enabled,true) pref(font.size.inflation.emPerLine,15) pref(font.size.inflation.forceEnabled,true) pref(font.size.inflation.lineThreshold,0) != font-inflation-1c.html font-inflation-1d.html
 test-pref(dom.meta-viewport.enabled,true) test-pref(font.size.inflation.emPerLine,15) test-pref(font.size.inflation.forceEnabled,true) test-pref(font.size.inflation.lineThreshold,0) == font-inflation-1c.html font-inflation-1c-ref.html
 test-pref(dom.meta-viewport.enabled,true) test-pref(font.size.inflation.emPerLine,15) test-pref(font.size.inflation.forceEnabled,true) test-pref(font.size.inflation.lineThreshold,0) == font-inflation-1d.html font-inflation-1d-ref.html
 
 == 1144501-1a-block-end-margin-orthogonal-size.html 1144501-1-block-end-margin-orthogonal-size-ref.html
 == 1144501-1b-block-end-margin-orthogonal-size.html 1144501-1-block-end-margin-orthogonal-size-ref.html
 == 1151993-1-orthogonal-block-size.html 1151993-1-orthogonal-block-size-ref.html
+== 1152941-1-orthogonal-blocksize-overflow.html 1152941-1-orthogonal-blocksize-overflow-ref.html
 == 1156021-text-indent-percent.html 1156021-text-indent-percent-ref.html
 == 1157752-upright-bidi.html 1157752-upright-bidi-ref.html
 == 1158549-1-vertical-block-size-constraints.html 1158549-1-vertical-block-size-constraints-ref.html
--- a/layout/style/test/chrome/chrome.ini
+++ b/layout/style/test/chrome/chrome.ini
@@ -5,12 +5,13 @@ support-files =
   bug535806-html.html
   bug535806-xul.xul
   hover_helper.html
 
 [test_addSheet.html]
 [test_additional_sheets.html]
 [test_author_specified_style.html]
 [test_bug1157097.html]
+[test_bug1160724.xul]
 [test_bug535806.xul]
 [test_hover.html]
 skip-if = buildapp == 'mulet'
 [test_moz_document_rules.html]
new file mode 100644
--- /dev/null
+++ b/layout/style/test/chrome/test_bug1160724.xul
@@ -0,0 +1,74 @@
+<?xml version="1.0"?>
+<?xml-stylesheet type="text/css" href="chrome://global/skin"?>
+<?xml-stylesheet type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"?>
+
+<?xml-stylesheet href="data:text/css,:root{--test:9px}" type="text/css"?>
+
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1160724
+-->
+<window title="Mozilla Bug 1160724" onload="test()"
+        xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+  <script type="application/javascript"
+          src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
+
+  <!-- test results are displayed in the html:body -->
+  <body xmlns="http://www.w3.org/1999/xhtml">
+  <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=1160724"
+     target="_blank">Mozilla Bug 1160724</a>
+  </body>
+
+  <script type="application/javascript">
+  <![CDATA[
+  var errorLogged = false;
+  const serv = Components.classes["@mozilla.org/consoleservice;1"]
+                         .getService(Components.interfaces.nsIConsoleService);
+  var listener = {
+    QueryInterface(iid) {
+      if (!iid.equals(Components.interfaces.nsISupports) &&
+          !iid.equals(Components.interfaces.nsIConsoleListener)) {
+        throw Components.results.NS_NOINTERFACE;
+      }
+      return this;
+    },
+
+    observe(msg) {
+      errorLogged = true;
+    }
+  };
+  serv.registerListener(listener);
+  ]]>
+  </script>
+
+  <vbox id="w" style="-moz-binding: url(#binding)">
+    <vbox id="v" style="display: none; transform: translateY(var(--test));" />
+  </vbox>
+
+  <bindings xmlns="http://www.mozilla.org/xbl">
+    <binding id="binding">
+      <implementation>
+        <constructor>this.firstChild</constructor>
+      </implementation>
+    </binding>
+  </bindings>
+
+  <!-- test code goes here -->
+  <script type="application/javascript">
+  <![CDATA[
+  /** Test for Bug 1160724 **/
+  SimpleTest.waitForExplicitFinish();
+
+  function test() {
+    var v = document.getElementById("v");
+    is(getComputedStyle(v, "").transform, "matrix(1, 0, 0, 1, 0, 9)");
+
+    // nsIConsoleListeners are notified by a runnable.
+    setTimeout(() => {
+      ok(!errorLogged, "Should be no errors");
+      serv.unregisterListener(listener);
+      SimpleTest.finish();
+    })
+  }
+  ]]>
+  </script>
+</window>
--- a/layout/style/test/test_transitions_events.html
+++ b/layout/style/test/test_transitions_events.html
@@ -78,16 +78,17 @@ var got_two_target = false;
 var got_three_top = false;
 var got_three_right = false;
 var got_three_bottom = false;
 var got_three_left = false;
 var got_four_root = false;
 var got_body = false;
 var did_stops = false;
 var got_before = false;
+var got_after = false;
 
 document.documentElement.addEventListener("transitionend",
   function(event) {
     if (event.target == $("one")) {
       ok(!got_one_root, "transitionend on one on root");
       is(event.propertyName, "border-right-color",
          "propertyName for transitionend on one");
       is(event.elapsedTime, 0.5,
@@ -117,20 +118,23 @@ document.documentElement.addEventListene
          "elapsedTime for transitionend on body");
       got_body = true;
       finished_test();
     } else if (event.target == $("seven")) {
       if (!got_before) {
         got_before = true;
         is(event.pseudoElement, "::before");
       } else {
+        ok(!got_after, "transitionend on #seven::after");
+        got_after = true;
         is(event.pseudoElement, "::after");
       }
       is(event.propertyName, "color");
       is(event.isTrusted, true);
+      finished_test();
     } else {
       if (!did_stops &&
           (event.target == $("five") || event.target == $("six"))) {
         todo(false,
              "timeout to stop transitions firing later than it should be");
         return;
       }
       ok(false,
@@ -198,22 +202,25 @@ document.documentElement.addEventListene
     }
     is(event.elapsedTime, 0.5,
        "elapsedTime for transitionend on one");
     is(cs("one").getPropertyValue(event.propertyName), "rgb(0, 255, 0)",
        "computed style of " + event.propertyName + " for transitionend on one");
     finished_test();
   }, false);
 
-started_test();
-started_test();
-started_test();
-started_test();
-started_test();
-started_test();
+started_test(); // color on #one
+started_test(); // border-top-color on #one
+started_test(); // border-right-color on #one
+started_test(); // border-right-color on #one (listener on root)
+started_test(); // border-bottom-color on #one
+started_test(); // border-left-color on #one
+started_test(); // -moz-column-rule-color on #one
+started_test(); // text-decoration-color on #one
+started_test(); // outline-color on #one
 $("one").style.color = "lime";
 
 
 $("two").addEventListener("transitionend",
   function(event) {
     event.stopPropagation();
 
     ok(!got_two_target, "transitionend on two on target");
@@ -226,17 +233,17 @@ started_test();
     is(event.cancelable, false,
        "transitionend events should not be cancelable");
     is(cs("two").marginLeft, "10px",
        "computed style for transitionend on two");
     got_two_target = true;
     finished_test();
   }, false);
 
-started_test();
+started_test(); // #two
 $("two").className = "bar";
 
 $("three").addEventListener("transitionend",
   function(event) {
     event.stopPropagation();
 
     switch (event.propertyName) {
       case "margin-top":
@@ -261,37 +268,37 @@ started_test();
     }
     is(event.elapsedTime, 0.5,
        "elapsedTime for transitionend on three");
     is(cs("three").getPropertyValue(event.propertyName), "10px",
        "computed style for transitionend on three");
     finished_test();
   }, true);
 
-started_test();
-started_test();
-started_test();
-started_test();
+started_test(); // margin-top on #three
+started_test(); // margin-right on #three
+started_test(); // margin-bottom on #three
+started_test(); // margin-left on #three
 $("three").className = "bar";
 
 // We reverse the transition on four, and we should only get an event
 // at the end of the second transition.
-started_test();
+started_test(); // #four (listener on root)
 $("four").style.color = "lime";
 
 // We cancel the transition on five by changing 'transition-property',
 // and should thus get no event.
 $("five").style.color = "lime";
 
 // We cancel the transition on six by changing 'transition-duration' and
 // then changing the value, so we should get no event.
 $("six").style.color = "lime";
 
-started_test();
-started_test();
+started_test(); // #seven::before (listener on root)
+started_test(); // #seven::after (listener on root)
 $("seven").setAttribute("foo", "bar");
 
 setTimeout(function() {
              if (cs("five") != "rgb(0, 255, 0)" &&
                  cs("six") != "rgb(0, 255, 0)") {
                // The transition hasn't finished already.
                did_stops = true;
              }
@@ -307,17 +314,17 @@ function poll_start_reversal() {
   } else {
     // The forward transition has not started yet.
     setTimeout(poll_start_reversal, 20);
   }
 }
 setTimeout(poll_start_reversal, 200);
 
 // And make our own event to dispatch to the body.
-started_test();
+started_test(); // synthesized event to body (listener on root)
 
 var e = new TransitionEvent("transitionend",
                             {
                               bubbles: true,
                               cancelable: true,
                               propertyName: "some-unknown-prop",
                               elapsedTime: 0.5,
                               pseudoElement: "pseudo"
--- a/media/webrtc/signaling/src/media-conduit/CodecStatistics.cpp
+++ b/media/webrtc/signaling/src/media-conduit/CodecStatistics.cpp
@@ -11,47 +11,54 @@
 
 using namespace mozilla;
 using namespace webrtc;
 
 // use the same tag as VideoConduit
 static const char* logTag ="WebrtcVideoSessionConduit";
 
 VideoCodecStatistics::VideoCodecStatistics(int channel,
-                                           ViECodec* codec,
-                                           bool encoder) :
+                                           ViECodec* codec) :
   mChannel(channel),
   mSentRawFrames(0),
   mPtrViECodec(codec),
   mEncoderDroppedFrames(0),
   mDecoderDiscardedPackets(0),
-  mEncoderMode(encoder),
+  mRegisteredEncode(false),
+  mRegisteredDecode(false),
   mReceiveState(kReceiveStateInitial)
 #ifdef MOZILLA_INTERNAL_API
   , mRecoveredBeforeLoss(0)
   , mRecoveredLosses(0)
 #endif
 {
   MOZ_ASSERT(mPtrViECodec);
-  if (mEncoderMode) {
-    mPtrViECodec->RegisterEncoderObserver(mChannel, *this);
-  } else {
-    mPtrViECodec->RegisterDecoderObserver(mChannel, *this);
-  }
 }
 
 VideoCodecStatistics::~VideoCodecStatistics()
 {
-  if (mEncoderMode) {
+  if (mRegisteredEncode) {
     mPtrViECodec->DeregisterEncoderObserver(mChannel);
-  } else {
+  }
+  if (mRegisteredDecode) {
     mPtrViECodec->DeregisterDecoderObserver(mChannel);
   }
 }
 
+void VideoCodecStatistics::Register(bool encoder)
+{
+  if (encoder && !mRegisteredEncode) {
+    mPtrViECodec->RegisterEncoderObserver(mChannel, *this);
+    mRegisteredEncode = true;
+  } else if (!encoder && !mRegisteredDecode) {
+    mPtrViECodec->RegisterDecoderObserver(mChannel, *this);
+    mRegisteredDecode = true;
+  }
+}
+
 void VideoCodecStatistics::OutgoingRate(const int video_channel,
                                         const uint32_t framerate,
                                         const uint32_t bitrate)
 {
   unsigned int keyFrames, deltaFrames;
   mPtrViECodec->GetSendCodecStatistics(video_channel, keyFrames, deltaFrames);
   uint32_t dropped = mSentRawFrames - (keyFrames + deltaFrames);
   CSFLogDebug(logTag,
--- a/media/webrtc/signaling/src/media-conduit/CodecStatistics.h
+++ b/media/webrtc/signaling/src/media-conduit/CodecStatistics.h
@@ -16,18 +16,19 @@
 namespace mozilla {
 
 // Statistics-gathering observer for Video Encoder and Decoder
 
 class VideoCodecStatistics : public webrtc::ViEEncoderObserver
                            , public webrtc::ViEDecoderObserver
 {
 public:
-  VideoCodecStatistics(int channel, webrtc::ViECodec* vieCodec, bool encoder);
+  VideoCodecStatistics(int channel, webrtc::ViECodec* vieCodec);
   ~VideoCodecStatistics();
+  void Register(bool encoder);
 
   void SentFrame();
   virtual void OutgoingRate(const int video_channel,
     const unsigned int framerate, const unsigned int bitrate) override;
 
   virtual void IncomingCodecChanged(const int video_channel,
     const webrtc::VideoCodec& video_codec) override;
 
@@ -87,17 +88,18 @@ private:
   ScopedCustomReleasePtr<webrtc::ViECodec> mPtrViECodec; // back-pointer
 
   RunningStat mEncoderBitRate;
   RunningStat mEncoderFps;
   uint32_t mEncoderDroppedFrames;
   RunningStat mDecoderBitRate;
   RunningStat mDecoderFps;
   uint32_t mDecoderDiscardedPackets;
-  const bool mEncoderMode;
+  bool mRegisteredEncode;
+  bool mRegisteredDecode;
 
   webrtc::VideoReceiveState mReceiveState;
 #ifdef MOZILLA_INTERNAL_API
   TimeStamp mFirstDecodeTime;
   TimeStamp mReceiveFailureTime;
   TimeDuration mTotalLossTime;
   uint32_t mRecoveredBeforeLoss;
   uint32_t mRecoveredLosses;
--- a/media/webrtc/signaling/src/media-conduit/VideoConduit.cpp
+++ b/media/webrtc/signaling/src/media-conduit/VideoConduit.cpp
@@ -639,18 +639,19 @@ WebrtcVideoConduit::ConfigureSendMediaCo
       return kMediaConduitInvalidSendCodec;
     }
     CSFLogError(logTag, "%s SetSendCodec Failed %d ", __FUNCTION__,
                 mPtrViEBase->LastError());
     return kMediaConduitUnknownError;
   }
 
   if (!mVideoCodecStat) {
-    mVideoCodecStat = new VideoCodecStatistics(mChannel, mPtrViECodec, true);
+    mVideoCodecStat = new VideoCodecStatistics(mChannel, mPtrViECodec);
   }
+  mVideoCodecStat->Register(true);
 
   mSendingWidth = 0;
   mSendingHeight = 0;
   mSendingFramerate = video_codec.maxFramerate;
 
   if(codecConfig->RtcpFbNackIsSet("")) {
     CSFLogDebug(logTag, "Enabling NACK (send) for video stream\n");
     if (mPtrRTP->SetNACKStatus(mChannel, true) != 0)
@@ -795,18 +796,19 @@ WebrtcVideoConduit::ConfigureRecvMediaCo
 
   if(!success)
   {
     CSFLogError(logTag, "%s Setting Receive Codec Failed ", __FUNCTION__);
     return kMediaConduitInvalidReceiveCodec;
   }
 
   if (!mVideoCodecStat) {
-    mVideoCodecStat = new VideoCodecStatistics(mChannel, mPtrViECodec, false);
+    mVideoCodecStat = new VideoCodecStatistics(mChannel, mPtrViECodec);
   }
+  mVideoCodecStat->Register(false);
 
   // XXX Currently, we gather up all of the feedback types that the remote
   // party indicated it supports for all video codecs and configure the entire
   // conduit based on those capabilities. This is technically out of spec,
   // as these values should be configured on a per-codec basis. However,
   // the video engine only provides this API on a per-conduit basis, so that's
   // how we have to do it. The approach of considering the remote capablities
   // for the entire conduit to be a union of all remote codec capabilities
--- a/media/webrtc/signaling/src/peerconnection/PeerConnectionImpl.cpp
+++ b/media/webrtc/signaling/src/peerconnection/PeerConnectionImpl.cpp
@@ -2285,19 +2285,18 @@ PeerConnectionImpl::Close()
   PC_AUTO_ENTER_API_CALL_NO_CHECK();
 
   SetSignalingState_m(PCImplSignalingState::SignalingClosed);
 
   return NS_OK;
 }
 
 bool
-PeerConnectionImpl::PluginCrash(uint64_t aPluginID,
-                                const nsAString& aPluginName,
-                                const nsAString& aPluginDumpID)
+PeerConnectionImpl::PluginCrash(uint32_t aPluginID,
+                                const nsAString& aPluginName)
 {
   // fire an event to the DOM window if this is "ours"
   bool result = mMedia ? mMedia->AnyCodecHasPluginID(aPluginID) : false;
   if (!result) {
     return false;
   }
 
   CSFLogError(logTag, "%s: Our plugin %llu crashed", __FUNCTION__, static_cast<unsigned long long>(aPluginID));
@@ -2305,17 +2304,17 @@ PeerConnectionImpl::PluginCrash(uint64_t
 #if !defined(MOZILLA_EXTERNAL_LINKAGE)
   nsCOMPtr<nsIDocument> doc = mWindow->GetExtantDoc();
   if (!doc) {
     NS_WARNING("Couldn't get document for PluginCrashed event!");
     return true;
   }
 
   PluginCrashedEventInit init;
-  init.mPluginDumpID = aPluginDumpID;
+  init.mPluginID = aPluginID;
   init.mPluginName = aPluginName;
   init.mSubmittedCrashReport = false;
   init.mGmpPlugin = true;
   init.mBubbles = true;
   init.mCancelable = true;
 
   nsRefPtr<PluginCrashedEvent> event =
     PluginCrashedEvent::Constructor(doc, NS_LITERAL_STRING("PluginCrashed"), init);
--- a/media/webrtc/signaling/src/peerconnection/PeerConnectionImpl.h
+++ b/media/webrtc/signaling/src/peerconnection/PeerConnectionImpl.h
@@ -521,19 +521,18 @@ public:
 
   NS_IMETHODIMP Close();
 
   void Close(ErrorResult &rv)
   {
     rv = Close();
   }
 
-  bool PluginCrash(uint64_t aPluginID,
-                   const nsAString& aPluginName,
-                   const nsAString& aPluginDumpID);
+  bool PluginCrash(uint32_t aPluginID,
+                   const nsAString& aPluginName);
 
   nsresult InitializeDataChannel();
 
   NS_IMETHODIMP_TO_ERRORRESULT_RETREF(nsDOMDataChannel,
                                       CreateDataChannel, ErrorResult &rv,
                                       const nsAString& aLabel,
                                       const nsAString& aProtocol,
                                       uint16_t aType,
--- a/mobile/android/base/GuestSession.java
+++ b/mobile/android/base/GuestSession.java
@@ -36,17 +36,17 @@ public class GuestSession {
             return false;
         }
 
         return profile.locked();
     }
 
     private static PendingIntent getNotificationIntent(Context context) {
         Intent intent = new Intent(NOTIFICATION_INTENT);
-        intent.setClass(context, BrowserApp.class);
+        intent.setClassName(context, AppConstants.BROWSER_INTENT_CLASS_NAME);
         return PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
     }
 
     public static void showNotification(Context context) {
         final NotificationCompat.Builder builder = new NotificationCompat.Builder(context);
         final Resources res = context.getResources();
         builder.setContentTitle(res.getString(R.string.guest_browsing_notification_title))
                .setContentText(res.getString(R.string.guest_browsing_notification_text))
--- a/mobile/android/base/tabqueue/TabQueueDispatcher.java
+++ b/mobile/android/base/tabqueue/TabQueueDispatcher.java
@@ -1,17 +1,16 @@
 /* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*-
  * 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/. */
 
 package org.mozilla.gecko.tabqueue;
 
 import org.mozilla.gecko.AppConstants;
-import org.mozilla.gecko.BrowserApp;
 import org.mozilla.gecko.GeckoAppShell;
 import org.mozilla.gecko.GeckoSharedPrefs;
 import org.mozilla.gecko.Locales;
 import org.mozilla.gecko.mozglue.ContextUtils;
 import org.mozilla.gecko.preferences.GeckoPreferences;
 import org.mozilla.gecko.sync.setup.activities.WebURLFinder;
 
 import android.content.Intent;
@@ -64,17 +63,17 @@ public class TabQueueDispatcher extends 
         startService(intent);
         finish();
     }
 
     /**
      * Start fennec with the supplied intent.
      */
     private void loadNormally(Intent intent) {
-        intent.setClass(getApplicationContext(), BrowserApp.class);
+        intent.setClassName(getApplicationContext(), AppConstants.BROWSER_INTENT_CLASS_NAME);
         startActivity(intent);
         finish();
     }
 
     /**
      * Abort as we were started with no URL.
      * @param dataString
      */
--- a/mobile/android/base/tabqueue/TabQueueHelper.java
+++ b/mobile/android/base/tabqueue/TabQueueHelper.java
@@ -1,16 +1,16 @@
 /* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*-
  * 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/. */
 
 package org.mozilla.gecko.tabqueue;
 
-import org.mozilla.gecko.BrowserApp;
+import org.mozilla.gecko.AppConstants;
 import org.mozilla.gecko.GeckoAppShell;
 import org.mozilla.gecko.GeckoEvent;
 import org.mozilla.gecko.GeckoProfile;
 import org.mozilla.gecko.GeckoSharedPrefs;
 import org.mozilla.gecko.R;
 import org.mozilla.gecko.preferences.GeckoPreferences;
 import org.mozilla.gecko.util.ThreadUtils;
 
@@ -149,17 +149,18 @@ public class TabQueueHelper {
      * will be replaced.
      *
      * @param context
      * @param tabsQueued
      */
     public static void showNotification(final Context context, final int tabsQueued) {
         ThreadUtils.assertNotOnUiThread();
 
-        Intent resultIntent = new Intent(context, BrowserApp.class);
+        Intent resultIntent = new Intent();
+        resultIntent.setClassName(context, AppConstants.BROWSER_INTENT_CLASS_NAME);
         resultIntent.setAction(TabQueueHelper.LOAD_URLS_ACTION);
 
         PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, resultIntent, PendingIntent.FLAG_CANCEL_CURRENT);
 
         final String text;
         final Resources resources = context.getResources();
         if (tabsQueued == 1) {
             text = resources.getString(R.string.tab_queue_notification_text_singular);
--- a/mobile/android/base/tabqueue/TabQueueService.java
+++ b/mobile/android/base/tabqueue/TabQueueService.java
@@ -1,16 +1,16 @@
 /* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*-
  * 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/. */
 
 package org.mozilla.gecko.tabqueue;
 
-import org.mozilla.gecko.BrowserApp;
+import org.mozilla.gecko.AppConstants;
 import org.mozilla.gecko.GeckoProfile;
 import org.mozilla.gecko.GeckoSharedPrefs;
 import org.mozilla.gecko.R;
 import org.mozilla.gecko.mozglue.ContextUtils;
 import org.mozilla.gecko.preferences.GeckoPreferences;
 
 import android.app.Service;
 import android.content.Context;
@@ -185,17 +185,17 @@ public class TabQueueService extends Ser
 
         tabQueueHandler.postDelayed(stopServiceRunnable, TOAST_TIMEOUT);
 
         return START_REDELIVER_INTENT;
     }
 
     private void openNow(Intent intent) {
         Intent forwardIntent = new Intent(intent);
-        forwardIntent.setClass(getApplicationContext(), BrowserApp.class);
+        forwardIntent.setClassName(getApplicationContext(), AppConstants.BROWSER_INTENT_CLASS_NAME);
         forwardIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
         startActivity(forwardIntent);
 
         GeckoSharedPrefs.forApp(getApplicationContext()).edit().remove(GeckoPreferences.PREFS_TAB_QUEUE_LAST_SITE)
                                                                .remove(GeckoPreferences.PREFS_TAB_QUEUE_LAST_TIME)
                                                                .apply();
     }
 
deleted file mode 100644
--- a/mobile/android/extensions/Makefile.in
+++ /dev/null
@@ -1,27 +0,0 @@
-# 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 $(topsrcdir)/config/rules.mk
-
-SHUMWAY_BROWSER_EXTENSION = $(topsrcdir)/browser/extensions/shumway
-
-exclude_files = \
-  test \
-  $(NULL)
-
-ifdef NIGHTLY_BUILD
-$(FINAL_TARGET)/chrome/shumway.manifest: $(GLOBAL_DEPS)
-	$(call py_action,buildlist,$@ "manifest shumway/chrome.manifest")
-
-libs:: $(SHUMWAY_BROWSER_EXTENSION) $(GLOBAL_DEPS)
-	$(PYTHON) $(topsrcdir)/config/nsinstall.py \
-	  $(SHUMWAY_BROWSER_EXTENSION) \
-          $(foreach exclude,$(exclude_files), -X $(SHUMWAY_BROWSER_EXTENSION)/$(exclude)) \
-          $(FINAL_TARGET)/chrome
-
-libs:: $(FINAL_TARGET)/chrome/shumway.manifest
-	$(call py_action,buildlist,$(FINAL_TARGET)/chrome.manifest "manifest chrome/shumway.manifest")
-endif
-
-
--- a/mobile/android/extensions/moz.build
+++ b/mobile/android/extensions/moz.build
@@ -1,6 +1,7 @@
 # -*- 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/.
 
+JAR_MANIFESTS += ['../../../browser/extensions/shumway/jar.mn']
--- a/python/mozbuild/mozbuild/jar.py
+++ b/python/mozbuild/mozbuild/jar.py
@@ -19,16 +19,17 @@ from cStringIO import StringIO
 
 from mozbuild.util import (
     lock_file,
     PushbackIter,
 )
 
 from mozbuild.preprocessor import Preprocessor
 from mozbuild.action.buildlist import addEntriesToListFile
+from mozpack.files import FileFinder
 if sys.platform == 'win32':
     from ctypes import windll, WinError
     CreateHardLink = windll.kernel32.CreateHardLinkA
 
 __all__ = ['JarMaker']
 
 
 class ZipEntry(object):
@@ -69,17 +70,17 @@ class JarMaker(object):
       '''
 
     ignore = re.compile('\s*(\#.*)?$')
     jarline = re.compile('(?:(?P<jarfile>[\w\d.\-\_\\\/]+).jar\:)|(?:\s*(\#.*)?)\s*$')
     relsrcline = re.compile('relativesrcdir\s+(?P<relativesrcdir>.+?):')
     regline = re.compile('\%\s+(.*)$')
     entryre = '(?P<optPreprocess>\*)?(?P<optOverwrite>\+?)\s+'
     entryline = re.compile(entryre
-                           + '(?P<output>[\w\d.\-\_\\\/\+\@]+)\s*(\((?P<locale>\%?)(?P<source>[\w\d.\-\_\\\/\@]+)\))?\s*$'
+                           + '(?P<output>[\w\d.\-\_\\\/\+\@]+)\s*(\((?P<locale>\%?)(?P<source>[\w\d.\-\_\\\/\@\*]+)\))?\s*$'
                            )
 
     def __init__(self, outputFormat='flat', useJarfileManifest=True,
         useChromeManifest=False):
 
         self.outputFormat = outputFormat
         self.useJarfileManifest = useJarfileManifest
         self.useChromeManifest = useChromeManifest
@@ -368,16 +369,39 @@ class JarMaker(object):
             # refers to a path relative to topsourcedir, use that as base
             # and strip the leading '/'
             src_base = [self.topsourcedir]
             src = src[1:]
         else:
             # use srcdirs and the objdir (current working dir) for relative paths
             src_base = self.sourcedirs + [os.getcwd()]
 
+        if '*' in src:
+            if not out.endswith('/'):
+                out += '/'
+            def _prefix(s):
+                for p in s.split('/'):
+                    if '*' not in p:
+                        yield p + '/'
+            prefix = ''.join(_prefix(src))
+            fmt = '%s%s %s%%s (%s%%s)' % (
+                m.group('optPreprocess') or '',
+                m.group('optOverwrite') or '',
+                out,
+                m.group('locale') or '',
+            )
+            for _srcdir in src_base:
+                finder = FileFinder(_srcdir, find_executables=False)
+                for path, _ in finder.find(src):
+                    line = fmt % (path[len(prefix):], path)
+                    m = self.entryline.match(line)
+                    if m:
+                        self._processEntryLine(m, outHelper, jf)
+            return
+
         # check if the source file exists
         realsrc = None
         for _srcdir in src_base:
             if os.path.isfile(os.path.join(_srcdir, src)):
                 realsrc = os.path.join(_srcdir, src)
                 break
         if realsrc is None:
             if jf is not None:
--- a/python/mozbuild/mozbuild/test/test_jarmaker.py
+++ b/python/mozbuild/mozbuild/test/test_jarmaker.py
@@ -228,30 +228,89 @@ class TestJarMaker(unittest.TestCase):
         # call JarMaker
         rv = self._jar_and_compare(os.path.join(self.srcdir,'jar.mn'),
                                    sourcedirs = [self.srcdir])
         self.assertTrue(not rv, rv)
 
     def test_a_simple_symlink(self):
         '''Test a simple jar.mn with a symlink'''
         if not symlinks_supported(self.srcdir):
-            return
+            raise unittest.SkipTest('symlinks not supported')
 
         self._create_simple_setup()
         jm = JarMaker(outputFormat='symlink')
         jm.sourcedirs = [self.srcdir]
         jm.topsourcedir = self.srcdir
         jardir = os.path.join(self.builddir, 'chrome')
         jm.makeJar(os.path.join(self.srcdir,'jar.mn'), jardir)
         # All we do is check that srcdir/bar points to builddir/chrome/test/dir/foo
         srcbar = os.path.join(self.srcdir, 'bar')
         destfoo = os.path.join(self.builddir, 'chrome', 'test', 'dir', 'foo')
         self.assertTrue(is_symlink_to(destfoo, srcbar),
                         "{0} is not a symlink to {1}".format(destfoo, srcbar))
 
+    def _create_wildcard_setup(self):
+        # create src content
+        jarf = open(os.path.join(self.srcdir, 'jar.mn'), 'w')
+        jarf.write('''test.jar:
+ dir/bar (*.js)
+ dir/hoge (qux/*)
+''')
+        jarf.close()
+        open(os.path.join(self.srcdir,'foo.js'),'w').write('foo.js\n')
+        open(os.path.join(self.srcdir,'bar.js'),'w').write('bar.js\n')
+        os.makedirs(os.path.join(self.srcdir, 'qux', 'foo'))
+        open(os.path.join(self.srcdir,'qux', 'foo', '1'),'w').write('1\n')
+        open(os.path.join(self.srcdir,'qux', 'foo', '2'),'w').write('2\n')
+        open(os.path.join(self.srcdir,'qux', 'baz'),'w').write('baz\n')
+        # create reference
+        refpath = os.path.join(self.refdir, 'chrome', 'test.jar', 'dir')
+        os.makedirs(os.path.join(refpath, 'bar'))
+        os.makedirs(os.path.join(refpath, 'hoge', 'foo'))
+        open(os.path.join(refpath, 'bar', 'foo.js'), 'w').write('foo.js\n')
+        open(os.path.join(refpath, 'bar', 'bar.js'), 'w').write('bar.js\n')
+        open(os.path.join(refpath, 'hoge', 'foo', '1'), 'w').write('1\n')
+        open(os.path.join(refpath, 'hoge', 'foo', '2'), 'w').write('2\n')
+        open(os.path.join(refpath, 'hoge', 'baz'), 'w').write('baz\n')
+
+    def test_a_wildcard_jar(self):
+        '''Test a wildcard in jar.mn'''
+        self._create_wildcard_setup()
+        # call JarMaker
+        rv = self._jar_and_compare(os.path.join(self.srcdir,'jar.mn'),
+                                   sourcedirs = [self.srcdir])
+        self.assertTrue(not rv, rv)
+
+    def test_a_wildcard_symlink(self):
+        '''Test a wildcard in jar.mn with symlinks'''
+        if not symlinks_supported(self.srcdir):
+            raise unittest.SkipTest('symlinks not supported')
+
+        self._create_wildcard_setup()
+        jm = JarMaker(outputFormat='symlink')
+        jm.sourcedirs = [self.srcdir]
+        jm.topsourcedir = self.srcdir
+        jardir = os.path.join(self.builddir, 'chrome')
+        jm.makeJar(os.path.join(self.srcdir,'jar.mn'), jardir)
+
+        expected_symlinks = {
+            ('bar', 'foo.js'): ('foo.js',),
+            ('bar', 'bar.js'): ('bar.js',),
+            ('hoge', 'foo', '1'): ('qux', 'foo', '1'),
+            ('hoge', 'foo', '2'): ('qux', 'foo', '2'),
+            ('hoge', 'baz'): ('qux', 'baz'),
+        }
+        for dest, src in expected_symlinks.iteritems():
+            srcpath = os.path.join(self.srcdir, *src)
+            destpath = os.path.join(self.builddir, 'chrome', 'test', 'dir',
+                                    *dest)
+            self.assertTrue(is_symlink_to(destpath, srcpath),
+                            "{0} is not a symlink to {1}".format(destpath,
+                                                                 srcpath))
+
 
 class Test_relativesrcdir(unittest.TestCase):
     def setUp(self):
         self.jm = JarMaker()
         self.jm.topsourcedir = '/TOPSOURCEDIR'
         self.jm.relativesrcdir = 'browser/locales'
         self.fake_empty_file = StringIO()
         self.fake_empty_file.name = 'fake_empty_file'
--- a/security/nss/TAG-INFO
+++ b/security/nss/TAG-INFO
@@ -1,1 +1,1 @@
-NSS_3_19_BETA5
+NSS_3_19_RTM
--- a/security/nss/coreconf/coreconf.dep
+++ b/security/nss/coreconf/coreconf.dep
@@ -5,9 +5,8 @@
 
 /*
  * A dummy header file that is a dependency for all the object files.
  * Used to force a full recompilation of NSS in Mozilla's Tinderbox
  * depend builds.  See comments in rules.mk.
  */
 
 #error "Do not include this header file."
-
--- a/security/nss/lib/nss/nss.def
+++ b/security/nss/lib/nss/nss.def
@@ -1065,14 +1065,14 @@ PK11_PrivDecrypt;
 ;+NSS_3.18 { 	# NSS 3.18 release
 ;+    global:
 __PK11_SetCertificateNickname;
 SEC_CheckCrlTimes;
 SEC_GetCrlTimes;
 ;+    local:
 ;+       *;
 ;+};
-;+NSS_3.18.1 { 	# NSS 3.18.1 release
+;+NSS_3.19 { 	# NSS 3.19 release
 ;+    global:
 CERT_GetImposedNameConstraints;
 ;+    local:
 ;+       *;
 ;+};
--- a/security/nss/lib/nss/nss.h
+++ b/security/nss/lib/nss/nss.h
@@ -28,22 +28,22 @@
 
 /*
  * NSS's major version, minor version, patch level, build number, and whether
  * this is a beta release.
  *
  * The format of the version string should be
  *     "<major version>.<minor version>[.<patch level>[.<build number>]][ <ECC>][ <Beta>]"
  */
-#define NSS_VERSION  "3.19" _NSS_ECC_STRING _NSS_CUSTOMIZED " Beta"
+#define NSS_VERSION  "3.19" _NSS_ECC_STRING _NSS_CUSTOMIZED
 #define NSS_VMAJOR   3
 #define NSS_VMINOR   19
 #define NSS_VPATCH   0
 #define NSS_VBUILD   0
-#define NSS_BETA     PR_TRUE
+#define NSS_BETA     PR_FALSE
 
 #ifndef RC_INVOKED
 
 #include "seccomon.h"
 
 typedef struct NSSInitParametersStr NSSInitParameters;
 
 /*
--- a/security/nss/lib/softoken/softkver.h
+++ b/security/nss/lib/softoken/softkver.h
@@ -20,16 +20,16 @@
 
 /*
  * Softoken's major version, minor version, patch level, build number,
  * and whether this is a beta release.
  *
  * The format of the version string should be
  *     "<major version>.<minor version>[.<patch level>[.<build number>]][ <ECC>][ <Beta>]"
  */
-#define SOFTOKEN_VERSION  "3.19" SOFTOKEN_ECC_STRING " Beta"
+#define SOFTOKEN_VERSION  "3.19" SOFTOKEN_ECC_STRING
 #define SOFTOKEN_VMAJOR   3
 #define SOFTOKEN_VMINOR   19
 #define SOFTOKEN_VPATCH   0
 #define SOFTOKEN_VBUILD   0
-#define SOFTOKEN_BETA     PR_TRUE
+#define SOFTOKEN_BETA     PR_FALSE
 
 #endif /* _SOFTKVER_H_ */
--- a/security/nss/lib/util/nssutil.h
+++ b/security/nss/lib/util/nssutil.h
@@ -14,22 +14,22 @@
 
 /*
  * NSS utilities's major version, minor version, patch level, build number,
  * and whether this is a beta release.
  *
  * The format of the version string should be
  *     "<major version>.<minor version>[.<patch level>[.<build number>]][ <Beta>]"
  */
-#define NSSUTIL_VERSION  "3.19 Beta"
+#define NSSUTIL_VERSION  "3.19"
 #define NSSUTIL_VMAJOR   3
 #define NSSUTIL_VMINOR   19
 #define NSSUTIL_VPATCH   0
 #define NSSUTIL_VBUILD   0
-#define NSSUTIL_BETA     PR_TRUE
+#define NSSUTIL_BETA     PR_FALSE
 
 SEC_BEGIN_PROTOS
 
 /*
  * Returns a const string of the UTIL library version.
  */
 extern const char *NSSUTIL_GetVersion(void);
 
--- a/startupcache/test/TestStartupCache.cpp
+++ b/startupcache/test/TestStartupCache.cpp
@@ -433,17 +433,17 @@ int main(int argc, char** argv)
     manifest->AppendNative(
       NS_LITERAL_CSTRING("TestStartupCacheTelemetry.manifest"));
   }
 #else
   manifest->AppendNative(
     NS_LITERAL_CSTRING("TestStartupCacheTelemetry.manifest"));
 #endif
 
-  XRE_AddManifestLocation(NS_COMPONENT_LOCATION, manifest);
+  XRE_AddManifestLocation(NS_APP_LOCATION, manifest);
 
   nsCOMPtr<nsIObserver> telemetryThing =
     do_GetService("@mozilla.org/testing/startup-cache-telemetry.js");
   if (!telemetryThing) {
     fail("telemetryThing");
     return 1;
   }
   scrv = telemetryThing->Observe(nullptr, "save-initial", nullptr);
--- a/testing/marionette/client/marionette/marionette_test.py
+++ b/testing/marionette/client/marionette/marionette_test.py
@@ -641,16 +641,17 @@ class MarionetteTestCase(CommonTestCase)
         CommonTestCase.setUp(self)
         self.marionette.test_name = self.test_name
         self.marionette.execute_script("log('TEST-START: %s:%s')" %
                                        (self.filepath.replace('\\', '\\\\'), self.methodName))
 
     def tearDown(self):
         if not self.marionette.check_for_crash():
            self.marionette.set_context("content")
+           self.marionette.clear_imported_scripts()
            self.marionette.execute_script("log('TEST-END: %s:%s')" %
                                           (self.filepath.replace('\\', '\\\\'), self.methodName))
         self.marionette.test_name = None
         CommonTestCase.tearDown(self)
 
     def get_new_emulator(self):
         self.extra_emulator_index += 1
         if len(self.marionette.extra_emulators) == self.extra_emulator_index:
--- a/testing/marionette/client/marionette/runner/base.py
+++ b/testing/marionette/client/marionette/runner/base.py
@@ -488,16 +488,17 @@ class BaseMarionetteOptions(OptionParser
             handler(options, tests)
 
         return (options, tests)
 
 
 class BaseMarionetteTestRunner(object):
 
     textrunnerclass = MarionetteTextTestRunner
+    driverclass = Marionette
 
     def __init__(self, address=None, emulator=None, emulator_binary=None,
                  emulator_img=None, emulator_res='480x800', homedir=None,
                  app=None, app_args=None, binary=None, profile=None,
                  logger=None, no_window=False, logdir=None, logcat_stdout=False,
                  xml_output=None, repeat=0, testvars=None, tree=None, type=None,
                  device_serial=None, symbols_path=None, timeout=None,
                  shuffle=False, shuffle_seed=random.randint(0, sys.maxint),
@@ -691,17 +692,17 @@ class BaseMarionetteTestRunner(object):
                 'emulator_img': self.emulator_img,
                 'emulator_res': self.emulator_res,
                 'no_window': self.no_window,
                 'sdcard': self.sdcard,
             })
         return kwargs
 
     def start_marionette(self):
-        self.marionette = Marionette(**self._build_kwargs())
+        self.marionette = self.driverclass(**self._build_kwargs())
 
     def launch_test_container(self):
         if self.marionette.session is None:
             self.marionette.start_session()
         self.marionette.set_context(self.marionette.CONTEXT_CONTENT)
 
         result = self.marionette.execute_async_script("""
 if((navigator.mozSettings == undefined) || (navigator.mozSettings == null) || (navigator.mozApps == undefined) || (navigator.mozApps == null)) {
--- a/testing/marionette/client/marionette/runner/mixins/reporting.py
+++ b/testing/marionette/client/marionette/runner/mixins/reporting.py
@@ -250,20 +250,20 @@ class HTMLReportingTestResultMixin(objec
         # In the event we're gathering debug without starting a session, skip marionette commands
         if self.marionette.session is not None:
             try:
                 self.marionette.set_context(self.marionette.CONTEXT_CHROME)
                 debug['screenshot'] = self.marionette.screenshot()
                 self.marionette.set_context(self.marionette.CONTEXT_CONTENT)
                 debug['source'] = self.marionette.page_source
                 self.marionette.switch_to_frame()
+                self.marionette.push_permission('settings-read', True)
+                self.marionette.push_permission('settings-api-read', True)
                 debug['settings'] = json.dumps(self.marionette.execute_async_script("""
-SpecialPowers.addPermission('settings-read', true, document);
-SpecialPowers.addPermission('settings-api-read', true, document);
 var req = window.navigator.mozSettings.createLock().get('*');
 req.onsuccess = function() {
   marionetteScriptFinished(req.result);
-}""", special_powers=True), sort_keys=True, indent=4, separators=(',', ': '))
+}""", sandbox='system'), sort_keys=True, indent=4, separators=(',', ': '))
             except:
                 logger = get_default_logger()
                 logger.warning('Failed to gather test failure debug.', exc_info=True)
         return debug
 
new file mode 100644
--- /dev/null
+++ b/testing/marionette/client/marionette/tests/unit/test_execute_sandboxes.py
@@ -0,0 +1,72 @@
+# 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/.
+
+from marionette import MarionetteTestCase
+from marionette_driver.errors import JavascriptException
+
+
+class TestExecuteSandboxes(MarionetteTestCase):
+    def setUp(self):
+        super(TestExecuteSandboxes, self).setUp()
+
+    def test_execute_system_sandbox(self):
+        # Test that 'system' sandbox has elevated privileges in execute_script
+        result = self.marionette.execute_script("""
+            return Components.interfaces.nsIPermissionManager.ALLOW_ACTION;
+            """, sandbox='system')
+        self.assertEqual(result, 1)
+
+    def test_execute_async_system_sandbox(self):
+        # Test that 'system' sandbox has elevated privileges in
+        # execute_async_script.
+        result = self.marionette.execute_async_script("""
+            let result = Components.interfaces.nsIPermissionManager.ALLOW_ACTION;
+            marionetteScriptFinished(result);
+            """, sandbox='system')
+        self.assertEqual(result, 1)
+
+    def test_execute_switch_sandboxes(self):
+        # Test that sandboxes are retained when switching between them
+        # for execute_script.
+        self.marionette.execute_script("foo = 1;", sandbox='1')
+        self.marionette.execute_script("foo = 2;", sandbox='2')
+        foo = self.marionette.execute_script("return foo;", sandbox='1',
+                                             new_sandbox=False)
+        self.assertEqual(foo, 1)
+        foo = self.marionette.execute_script("return foo;", sandbox='2',
+                                             new_sandbox=False)
+        self.assertEqual(foo, 2)
+
+    def test_execute_new_sandbox(self):
+        # Test that clearing a sandbox does not affect other sandboxes
+        self.marionette.execute_script("foo = 1;", sandbox='1')
+        self.marionette.execute_script("foo = 2;", sandbox='2')
+        self.assertRaises(JavascriptException,
+                          self.marionette.execute_script,
+                          "return foo;", sandbox='1', new_sandbox=True)
+        foo = self.marionette.execute_script("return foo;", sandbox='2',
+                                             new_sandbox=False)
+        self.assertEqual(foo, 2)
+
+    def test_execute_async_switch_sandboxes(self):
+        # Test that sandboxes are retained when switching between them
+        # for execute_async_script.
+        self.marionette.execute_async_script("foo = 1; marionetteScriptFinished()",
+                                             sandbox='1')
+        self.marionette.execute_async_script("foo = 2; marionetteScriptFinished()",
+                                             sandbox='2')
+        foo = self.marionette.execute_async_script("marionetteScriptFinished(foo);",
+                                                   sandbox='1',
+                                                   new_sandbox=False)
+        self.assertEqual(foo, 1)
+        foo = self.marionette.execute_async_script("marionetteScriptFinished(foo);",
+                                                   sandbox='2',
+                                                   new_sandbox=False)
+        self.assertEqual(foo, 2)
+
+
+class TestExecuteSandboxesChrome(TestExecuteSandboxes):
+    def setUp(self):
+        super(TestExecuteSandboxesChrome, self).setUp()
+        self.marionette.set_context("chrome")
--- a/testing/marionette/client/marionette/tests/unit/test_getactiveframe_oop.py
+++ b/testing/marionette/client/marionette/tests/unit/test_getactiveframe_oop.py
@@ -24,17 +24,17 @@ class TestGetActiveFrameOOP(MarionetteTe
             SpecialPowers.setBoolPref('dom.ipc.browser_frames.oop_by_default', true);
             """)
         self.marionette.execute_script("""
             SpecialPowers.setBoolPref('dom.mozBrowserFramesEnabled', true);
             """)
 
     def test_active_frame_oop(self):
         self.marionette.navigate(self.marionette.absolute_url("test.html"))
-        self.marionette.execute_script("SpecialPowers.addPermission('browser', true, document)")
+        self.marionette.push_permission('browser', True)
 
         # Create first OOP frame
         self.marionette.execute_script("""
             let iframe1 = document.createElement("iframe");
             SpecialPowers.wrap(iframe1).mozbrowser = true;
             SpecialPowers.wrap(iframe1).remote = true;
             iframe1.id = "remote_iframe1";
             iframe1.style.height = "100px";
--- a/testing/marionette/client/marionette/tests/unit/test_switch_remote_frame.py
+++ b/testing/marionette/client/marionette/tests/unit/test_switch_remote_frame.py
@@ -28,18 +28,17 @@ class TestSwitchRemoteFrame(MarionetteTe
                 try {
                   return Services.appinfo.browserTabsRemoteAutostart;
                 } catch (e) {
                   return false;
                 }""")
 
     def test_remote_frame(self):
         self.marionette.navigate(self.marionette.absolute_url("test.html"))
-        self.marionette.execute_async_script(
-            "SpecialPowers.pushPermissions([{'type': 'browser', 'allow': true, 'context': document}], marionetteScriptFinished);")
+        self.marionette.push_permission('browser', True)
         self.marionette.execute_script("""
             let iframe = document.createElement("iframe");
             SpecialPowers.wrap(iframe).mozbrowser = true;
             SpecialPowers.wrap(iframe).remote = true;
             iframe.id = "remote_iframe";
             iframe.style.height = "100px";
             iframe.style.width = "100%%";
             iframe.src = "%s";
@@ -50,18 +49,17 @@ class TestSwitchRemoteFrame(MarionetteTe
         main_process = self.marionette.execute_script("""
             return SpecialPowers.isMainProcess();
             """)
         self.assertFalse(main_process)
 
     def test_remote_frame_revisit(self):
         # test if we can revisit a remote frame (this takes a different codepath)
         self.marionette.navigate(self.marionette.absolute_url("test.html"))
-        self.marionette.execute_async_script(
-            "SpecialPowers.pushPermissions([{'type': 'browser', 'allow': true, 'context': document}], marionetteScriptFinished);")
+        self.marionette.push_permission('browser', True)
         self.marionette.execute_script("""
             let iframe = document.createElement("iframe");
             SpecialPowers.wrap(iframe).mozbrowser = true;
             SpecialPowers.wrap(iframe).remote = true;
             iframe.id = "remote_iframe";
             iframe.style.height = "100px";
             iframe.style.width = "100%%";
             iframe.src = "%s";
@@ -84,18 +82,17 @@ class TestSwitchRemoteFrame(MarionetteTe
         main_process = self.marionette.execute_script("""
             return SpecialPowers.isMainProcess();
             """)
         self.assertFalse(main_process)
 
     def test_we_can_switch_to_a_remote_frame_by_index(self):
         # test if we can revisit a remote frame (this takes a different codepath)
         self.marionette.navigate(self.marionette.absolute_url("test.html"))
-        self.marionette.execute_async_script(
-            "SpecialPowers.pushPermissions([{'type': 'browser', 'allow': true, 'context': document}], marionetteScriptFinished);")
+        self.marionette.push_permission('browser', True)
         self.marionette.execute_script("""
             let iframe = document.createElement("iframe");
             SpecialPowers.wrap(iframe).mozbrowser = true;
             SpecialPowers.wrap(iframe).remote = true;
             iframe.id = "remote_iframe";
             iframe.style.height = "100px";
             iframe.style.width = "100%%";
             iframe.src = "%s";
--- a/testing/marionette/client/marionette/tests/unit/unit-tests.ini
+++ b/testing/marionette/client/marionette/tests/unit/unit-tests.ini
@@ -151,8 +151,10 @@ b2g = false
 [test_key_actions.py]
 [test_mouse_action.py]
 b2g = false
 [test_teardown_context_preserved.py]
 b2g = false
 [test_file_upload.py]
 b2g = false
 skip-if = os == "win" # http://bugs.python.org/issue14574
+
+[test_execute_sandboxes.py]
--- a/testing/marionette/driver.js
+++ b/testing/marionette/driver.js
@@ -126,17 +126,17 @@ this.GeckoDriver = function(appName, dev
   this.importedScripts = FileUtils.getFile("TmpD", ["marionetteChromeScripts"]);
   this.importedScriptHashes = {};
   this.importedScriptHashes[Context.CONTENT] = [];
   this.importedScriptHashes[Context.CHROME] = [];
   this.currentFrameElement = null;
   this.testName = null;
   this.mozBrowserClose = null;
   this.enabled_security_pref = false;
-  this.sandbox = null;
+  this.sandboxes = {};
   // frame ID of the current remote frame, used for mozbrowserclose events
   this.oopFrameId = null;
   this.observing = null;
   this._browserIds = new WeakMap();
   this.actions = new ActionChain(utils);
 
   this.sessionCapabilities = {
     // Mandated capabilities
@@ -710,21 +710,27 @@ GeckoDriver.prototype.getContext = funct
  * @param {Object} args
  *     Arguments given by client.
  * @param {boolean} sp
  *     True to enable special powers in the sandbox, false not to.
  *
  * @return {nsIXPCComponents_utils_Sandbox}
  *     Returns the sandbox.
  */
-GeckoDriver.prototype.createExecuteSandbox = function(win, mn, sp) {
-  let sb = new Cu.Sandbox(win,
+GeckoDriver.prototype.createExecuteSandbox = function(win, mn, sp, sandboxName) {
+  let principal = win;
+  if (sandboxName == 'system') {
+    principal = Cc["@mozilla.org/systemprincipal;1"].
+                createInstance(Ci.nsIPrincipal);
+  }
+  let sb = new Cu.Sandbox(principal,
       {sandboxPrototype: win, wantXrays: false, sandboxName: ""});
   sb.global = sb;
   sb.testUtils = utils;
+  sb.proto = win;
 
   mn.exports.forEach(function(fn) {
     if (typeof mn[fn] === 'function') {
       sb[fn] = mn[fn].bind(mn);
     } else {
       sb[fn] = mn[fn];
     }
   });
@@ -735,17 +741,17 @@ GeckoDriver.prototype.createExecuteSandb
     let pow = [
       "chrome://specialpowers/content/specialpowersAPI.js",
       "chrome://specialpowers/content/SpecialPowersObserverAPI.js",
       "chrome://specialpowers/content/ChromePowers.js",
     ];
     pow.map(s => loader.loadSubScript(s, sb));
   }
 
-  return sb;
+  this.sandboxes[sandboxName] = sb;
 };
 
 /**
  * Apply arguments sent from the client to the current (possibly reused)
  * execution sandbox.
  */
 GeckoDriver.prototype.applyArgumentsToSandbox = function(win, sb, args) {
   sb.__marionetteParams = this.curBrowser.elementManager.convertWrappedArguments(args, win);
@@ -815,33 +821,35 @@ GeckoDriver.prototype.execute = function
   let {inactivityTimeout,
        scriptTimeout,
        script,
        newSandbox,
        args,
        specialPowers,
        filename,
        line} = cmd.parameters;
+  let sandboxName = cmd.parameters.sandbox || 'default';
 
   if (!scriptTimeout) {
     scriptTimeout = this.scriptTimeout;
   }
   if (typeof newSandbox == "undefined") {
     newSandbox = true;
   }
 
   if (this.context == Context.CONTENT) {
     resp.value = yield this.listener.executeScript({
       script: script,
       args: args,
       newSandbox: newSandbox,
       timeout: scriptTimeout,
       specialPowers: specialPowers,
       filename: filename,
-      line: line
+      line: line,
+      sandboxName: sandboxName
     });
     return;
   }
 
   // handle the inactivity timeout
   let that = this;
   if (inactivityTimeout) {
     let setTimer = function() {
@@ -855,49 +863,52 @@ GeckoDriver.prototype.execute = function
     setTimer();
     this.heartbeatCallback = function() {
       that.inactivityTimer.cancel();
       setTimer();
     };
   }
 
   let win = this.getCurrentWindow();
-  if (!this.sandbox || newSandbox) {
+  if (newSandbox ||
+      !(sandboxName in this.sandboxes) ||
+      (this.sandboxes[sandboxName].proto != win)) {
     let marionette = new Marionette(
         this,
         win,
         "chrome",
         this.marionetteLog,
         scriptTimeout,
         this.heartbeatCallback,
         this.testName);
-    this.sandbox = this.createExecuteSandbox(
+    this.createExecuteSandbox(
         win,
         marionette,
-        specialPowers);
-    if (!this.sandbox) {
+        specialPowers,
+        sandboxName);
+    if (!this.sandboxes[sandboxName]) {
       return;
     }
   }
-  this.applyArgumentsToSandbox(win, this.sandbox, args);
+  this.applyArgumentsToSandbox(win, this.sandboxes[sandboxName], args);
 
   try {
-    this.sandbox.finish = () => {
+    this.sandboxes[sandboxName].finish = () => {
       if (this.inactivityTimer !== null) {
         this.inactivityTimer.cancel();
       }
-      return this.sandbox.generate_results();
+      return this.sandboxes[sandboxName].generate_results();
     };
 
     if (!directInject) {
       script = "let func = function() { " + script + " }; func.apply(null, __marionetteParams);";
     }
     this.executeScriptInSandbox(
         resp,
-        this.sandbox,
+        this.sandboxes[sandboxName],
         script,
         directInject,
         false /* async */,
         scriptTimeout);
   } catch (e) {
     throw new JavaScriptError(e, "execute_script", filename, line, script);
   }
 };
@@ -946,16 +957,17 @@ GeckoDriver.prototype.executeJSScript = 
         newSandbox: cmd.parameters.newSandbox,
         async: cmd.parameters.async,
         timeout: cmd.parameters.scriptTimeout ?
             cmd.parameters.scriptTimeout : this.scriptTimeout,
         inactivityTimeout: cmd.parameters.inactivityTimeout,
         specialPowers: cmd.parameters.specialPowers,
         filename: cmd.parameters.filename,
         line: cmd.parameters.line,
+        sandboxName: cmd.parameters.sandbox || 'default',
       });
       break;
  }
 };
 
 /**
  * This function is used by executeAsync and executeJSScript to execute
  * a script in a sandbox.
@@ -975,16 +987,17 @@ GeckoDriver.prototype.executeWithCallbac
   let {script,
       args,
       newSandbox,
       inactivityTimeout,
       scriptTimeout,
       specialPowers,
       filename,
       line} = cmd.parameters;
+  let sandboxName = cmd.parameters.sandbox || 'default';
 
   if (!scriptTimeout) {
     scriptTimeout = this.scriptTimeout;
   }
   if (typeof newSandbox == "undefined") {
     newSandbox = true;
   }
 
@@ -993,17 +1006,18 @@ GeckoDriver.prototype.executeWithCallbac
       script: script,
       args: args,
       id: cmd.id,
       newSandbox: newSandbox,
       timeout: scriptTimeout,
       inactivityTimeout: inactivityTimeout,
       specialPowers: specialPowers,
       filename: filename,
-      line: line
+      line: line,
+      sandboxName: sandboxName,
     });
     return;
   }
 
   // handle the inactivity timeout
   let that = this;
   if (inactivityTimeout) {
     this.inactivityTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
@@ -1029,17 +1043,17 @@ GeckoDriver.prototype.executeWithCallbac
 
   let res = yield new Promise(function(resolve, reject) {
     let chromeAsyncReturnFunc = function(val) {
       if (that.emulator.cbs.length > 0) {
         that.emulator.cbs = [];
         throw new WebDriverError("Emulator callback still pending when finish() called");
       }
 
-      if (cmd.id == that.sandbox.command_id) {
+      if (cmd.id == that.sandboxes[sandboxName].command_id) {
         if (that.timer !== null) {
           that.timer.cancel();
           that.timer = null;
         }
 
         win.onerror = origOnError;
 
         if (error.isError(val)) {
@@ -1050,55 +1064,57 @@ GeckoDriver.prototype.executeWithCallbac
       }
 
       if (that.inactivityTimer !== null) {
         that.inactivityTimer.cancel();
       }
     };
 
     let chromeAsyncFinish = function() {
-      let res = that.sandbox.generate_results();
+      let res = that.sandboxes[sandboxName].generate_results();
       chromeAsyncReturnFunc(res);
     };
 
     let chromeAsyncError = function(e, func, file, line, script) {
       let err = new JavaScriptError(e, func, file, line, script);
       chromeAsyncReturnFunc(err);
     };
 
-    if (!this.sandbox || newSandbox) {
+    if (newSandbox || !(sandboxName in this.sandboxes)) {
       let marionette = new Marionette(
           this,
           win,
           "chrome",
           this.marionetteLog,
           scriptTimeout,
           this.heartbeatCallback,
           this.testName);
-      this.sandbox = this.createExecuteSandbox(win, marionette, specialPowers);
-      if (!this.sandbox) {
-        return;
-      }
+      this.createExecuteSandbox(win, marionette,
+                                specialPowers, sandboxName);
     }
-    this.sandbox.command_id = cmd.id;
-    this.sandbox.runEmulatorCmd = (cmd, cb) => {
+    if (!this.sandboxes[sandboxName]) {
+      return;
+    }
+
+    this.sandboxes[sandboxName].command_id = cmd.id;
+    this.sandboxes[sandboxName].runEmulatorCmd = (cmd, cb) => {
       let ecb = new EmulatorCallback();
       ecb.onresult = cb;
       ecb.onerror = chromeAsyncError;
       this.emulator.pushCallback(ecb);
       this.emulator.send({emulator_cmd: cmd, id: ecb.id});
     };
-    this.sandbox.runEmulatorShell = (args, cb) => {
+    this.sandboxes[sandboxName].runEmulatorShell = (args, cb) => {
       let ecb = new EmulatorCallback();
       ecb.onresult = cb;
       ecb.onerror = chromeAsyncError;
       this.emulator.pushCallback(ecb);
       this.emulator.send({emulator_shell: args, id: ecb.id});
     };
-    this.applyArgumentsToSandbox(win, this.sandbox, args);
+    this.applyArgumentsToSandbox(win, this.sandboxes[sandboxName], args);
 
     // NB: win.onerror is not hooked by default due to the inability to
     // differentiate content exceptions from chrome exceptions. See bug
     // 1128760 for more details. A debug_script flag can be set to
     // reenable onerror hooking to help debug test scripts.
     if (cmd.parameters.debug_script) {
       win.onerror = function(msg, url, line) {
         let err = new JavaScriptError(`${msg} at: ${url} line: ${line}`);
@@ -1110,29 +1126,29 @@ GeckoDriver.prototype.executeWithCallbac
     try {
       this.timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
       if (this.timer !== null) {
         this.timer.initWithCallback(function() {
           chromeAsyncReturnFunc(new ScriptTimeoutError("timed out"));
         }, that.timeout, Ci.nsITimer.TYPE_ONE_SHOT);
       }
 
-      this.sandbox.returnFunc = chromeAsyncReturnFunc;
-      this.sandbox.finish = chromeAsyncFinish;
+      this.sandboxes[sandboxName].returnFunc = chromeAsyncReturnFunc;
+      this.sandboxes[sandboxName].finish = chromeAsyncFinish;
 
       if (!directInject) {
         script =  "__marionetteParams.push(returnFunc);" +
             "let marionetteScriptFinished = returnFunc;" +
             "let __marionetteFunc = function() {" + script + "};" +
             "__marionetteFunc.apply(null, __marionetteParams);";
       }
 
       this.executeScriptInSandbox(
           resp,
-          this.sandbox,
+          this.sandboxes[sandboxName],
           script,
           directInject,
           true /* async */,
           scriptTimeout);
     } catch (e) {
       chromeAsyncError(e, "execute_async_script", filename, line, script);
     }
   }.bind(this));
@@ -1516,20 +1532,16 @@ GeckoDriver.prototype.switchToWindow = f
       if (byNameOrId(win.name, outerId)) {
         found = {win: win, outerId: outerId};
         break;
       }
     }
   }
 
   if (found) {
-    // As in content, switching to a new window invalidates a sandbox
-    // for reuse.
-    this.sandbox = null;
-
     // Initialise Marionette if browser has not been seen before,
     // otherwise switch to known browser and activate the tab if it's a
     // content browser.
     if (!(found.outerId in this.browsers)) {
       let registerBrowsers, browserListening;
       if (found.contentId) {
         registerBrowsers = this.registerPromise();
         browserListening = this.listeningPromise();
@@ -2447,16 +2459,17 @@ GeckoDriver.prototype.sessionTearDown = 
   this.deleteFile("marionetteContentScripts");
 
   if (this.observing !== null) {
     for (let topic in this.observing) {
       Services.obs.removeObserver(this.observing[topic], topic);
     }
     this.observing = null;
   }
+  this.sandboxes = {};
 };
 
 /**
  * Processes the "deleteSession" request from the client by tearing down
  * the session and responding "ok".
  */
 GeckoDriver.prototype.deleteSession = function(cmd, resp) {
   this.sessionTearDown();
--- a/testing/marionette/driver/marionette_driver/marionette.py
+++ b/testing/marionette/driver/marionette_driver/marionette.py
@@ -782,16 +782,80 @@ class Marionette(object):
                 val = str(val)
                 for i in range(len(val)):
                     typing.append(val[i])
             else:
                 for i in range(len(val)):
                     typing.append(val[i])
         return typing
 
+    def push_permission(self, perm_type, allow):
+        with self.using_context('content'):
+            perm = self.execute_script("""
+                let allow = arguments[0];
+                if (allow) {
+                  allow = Components.interfaces.nsIPermissionManager.ALLOW_ACTION;
+                }
+                else {
+                  allow = Components.interfaces.nsIPermissionManager.DENY_ACTION;
+                }
+                let perm_type = arguments[1];
+
+                Components.utils.import("resource://gre/modules/Services.jsm");
+                window.wrappedJSObject.permChanged = false;
+                window.wrappedJSObject.permObserver = function(subject, topic, data) {
+                  if (topic == "perm-changed") {
+                    let permission = subject.QueryInterface(Components.interfaces.nsIPermission);
+                    if (perm_type == permission.type) {
+                      Services.obs.removeObserver(window.wrappedJSObject.permObserver, "perm-changed");
+                      window.wrappedJSObject.permChanged = true;
+                    }
+                  }
+                };
+                Services.obs.addObserver(window.wrappedJSObject.permObserver,
+                                         "perm-changed", false);
+
+                let value = {
+                              'url': document.nodePrincipal.URI.spec,
+                              'appId': document.nodePrincipal.appId,
+                              'isInBrowserElement': document.nodePrincipal.isInBrowserElement,
+                              'type': perm_type,
+                              'action': allow
+                            };
+                return value;
+                """, script_args=[allow, perm_type], sandbox='system')
+
+        with self.using_context('chrome'):
+            waiting = self.execute_script("""
+                Components.utils.import("resource://gre/modules/Services.jsm");
+                let perm = arguments[0];
+                let secMan = Services.scriptSecurityManager;
+                let principal = secMan.getAppCodebasePrincipal(Services.io.newURI(perm.url, null, null),
+                                perm.appId, perm.isInBrowserElement);
+                let testPerm = Services.perms.testPermissionFromPrincipal(principal, perm.type, perm.action);
+                if (testPerm == perm.action) {
+                  return false;
+                }
+                Services.perms.addFromPrincipal(principal, perm.type, perm.action);
+                return true;
+                """, script_args=[perm])
+
+        with self.using_context('content'):
+            if waiting:
+                self.execute_async_script("""
+                    waitFor(marionetteScriptFinished, function() {
+                      return window.wrappedJSObject.permChanged;
+                    });
+                    """, sandbox='system')
+            else:
+                self.execute_script("""
+                    Components.utils.import("resource://gre/modules/Services.jsm");
+                    Services.obs.removeObserver(window.wrappedJSObject.permObserver, "perm-changed");
+                    """, sandbox='system')
+
     def enforce_gecko_prefs(self, prefs):
         """
         Checks if the running instance has the given prefs. If not, it will kill the
         currently running instance, and spawn a new instance with the requested preferences.
 
         : param prefs: A dictionary whose keys are preference names.
         """
         if not self.instance:
@@ -1282,17 +1346,17 @@ class Marionette(object):
         else:
             unwrapped = value
 
         return unwrapped
 
     def execute_js_script(self, script, script_args=None, async=True,
                           new_sandbox=True, special_powers=False,
                           script_timeout=None, inactivity_timeout=None,
-                          filename=None):
+                          filename=None, sandbox='default'):
         if script_args is None:
             script_args = []
         args = self.wrapArguments(script_args)
         response = self._send_message('executeJSScript',
                                       'value',
                                       script=script,
                                       args=args,
                                       async=async,
@@ -1300,31 +1364,35 @@ class Marionette(object):
                                       specialPowers=special_powers,
                                       scriptTimeout=script_timeout,
                                       inactivityTimeout=inactivity_timeout,
                                       filename=filename,
                                       line=None)
         return self.unwrapValue(response)
 
     def execute_script(self, script, script_args=None, new_sandbox=True,
-                       special_powers=False, script_timeout=None):
+                       special_powers=False, sandbox='default', script_timeout=None):
         '''
         Executes a synchronous JavaScript script, and returns the result (or None if the script does return a value).
 
         The script is executed in the context set by the most recent
         set_context() call, or to the CONTEXT_CONTENT context if set_context()
         has not been called.
 
         :param script: A string containing the JavaScript to execute.
         :param script_args: A list of arguments to pass to the script.
         :param special_powers: Whether or not you want access to SpecialPowers
          in your script. Set to False by default because it shouldn't really
          be used, since you already have access to chrome-level commands if you
          set context to chrome and do an execute_script. This method was added
          only to help us run existing Mochitests.
+        :param sandbox: A tag referring to the sandbox you wish to use; if
+         you specify a new tag, a new sandbox will be created.  If you use the
+         special tag 'system', the sandbox will be created using the system
+         principal which has elevated privileges.
         :param new_sandbox: If False, preserve global variables from the last
          execute_*script call. This is True by default, in which case no
          globals are preserved.
 
         Simple usage example:
 
         ::
 
@@ -1372,37 +1440,44 @@ class Marionette(object):
         args = self.wrapArguments(script_args)
         stack = traceback.extract_stack()
         frame = stack[-2:-1][0] # grab the second-to-last frame
         response = self._send_message('executeScript',
                                       'value',
                                       script=script,
                                       args=args,
                                       newSandbox=new_sandbox,
+                                      sandbox=sandbox,
                                       specialPowers=special_powers,
                                       scriptTimeout=script_timeout,
                                       line=int(frame[1]),
                                       filename=os.path.basename(frame[0]))
         return self.unwrapValue(response)
 
-    def execute_async_script(self, script, script_args=None, new_sandbox=True, special_powers=False, script_timeout=None, debug_script=False):
+    def execute_async_script(self, script, script_args=None, new_sandbox=True,
+                             sandbox='default', script_timeout=None,
+                             special_powers=False, debug_script=False):
         '''
         Executes an asynchronous JavaScript script, and returns the result (or None if the script does return a value).
 
         The script is executed in the context set by the most recent
         set_context() call, or to the CONTEXT_CONTENT context if set_context()
         has not been called.
 
         :param script: A string containing the JavaScript to execute.
         :param script_args: A list of arguments to pass to the script.
         :param special_powers: Whether or not you want access to SpecialPowers
          in your script. Set to False by default because it shouldn't really
          be used, since you already have access to chrome-level commands if you
          set context to chrome and do an execute_script. This method was added
          only to help us run existing Mochitests.
+        :param sandbox: A tag referring to the sandbox you wish to use; if
+         you specify a new tag, a new sandbox will be created.  If you use the
+         special tag 'system', the sandbox will be created using the system
+         principal which has elevated privileges.
         :param new_sandbox: If False, preserve global variables from the last
          execute_*script call. This is True by default, in which case no
          globals are preserved.
         :param debug_script: Capture javascript exceptions when in
          CONTEXT_CHROME context.
 
         Usage example:
 
@@ -1422,16 +1497,17 @@ class Marionette(object):
         args = self.wrapArguments(script_args)
         stack = traceback.extract_stack()
         frame = stack[-2:-1][0] # grab the second-to-last frame
         response = self._send_message('executeAsyncScript',
                                       'value',
                                       script=script,
                                       args=args,
                                       newSandbox=new_sandbox,
+                                      sandbox=sandbox,
                                       specialPowers=special_powers,
                                       scriptTimeout=script_timeout,
                                       line=int(frame[1]),
                                       filename=os.path.basename(frame[0]),
                                       debug_script=debug_script)
         return self.unwrapValue(response)
 
     def find_element(self, method, target, id=None):
--- a/testing/marionette/listener.js
+++ b/testing/marionette/listener.js
@@ -41,19 +41,20 @@ let listenerId = null; // unique ID of t
 let curFrame = content;
 let isRemoteBrowser = () => curFrame.contentWindow !== null;
 let previousFrame = null;
 let elementManager = new ElementManager([]);
 let accessibility = new Accessibility();
 let actions = new ActionChain(utils, checkForInterrupted);
 let importedScripts = null;
 
-// The sandbox we execute test scripts in. Gets lazily created in
-// createExecuteContentSandbox().
-let sandbox;
+// A dict of sandboxes used this session
+let sandboxes = {};
+// The name of the current sandbox
+let sandboxName = 'default';
 
 // the unload handler
 let onunload;
 
 // Flag to indicate whether an async script is currently running or not.
 let asyncTestRunning = false;
 let asyncTestCommandId;
 let asyncTestTimeoutId;
@@ -80,17 +81,16 @@ logger.info("loaded listener.js");
 let modalHandler = function() {
   // This gets called on the system app only since it receives the mozbrowserprompt event
   sendSyncMessage("Marionette:switchedToFrame", { frameValue: null, storePrevious: true });
   let isLocal = sendSyncMessage("MarionetteFrame:handleModal", {})[0].value;
   if (isLocal) {
     previousFrame = curFrame;
   }
   curFrame = content;
-  sandbox = null;
 };
 
 /**
  * Called when listener is first started up.
  * The listener sends its unique window ID and its current URI to the actor.
  * If the actor returns an ID, we start the listeners. Otherwise, nothing happens.
  */
 function registerSelf() {
@@ -398,17 +398,17 @@ function sendLog(msg) {
 function sendError(err, cmdId) {
   sendToServer("Marionette:error", null, {error: err}, cmdId);
 }
 
 /**
  * Clear test values after completion of test
  */
 function resetValues() {
-  sandbox = null;
+  sandboxes = {};
   curFrame = content;
   actions.mouseEventsOnly = false;
 }
 
 /**
  * Dump a logline to stdout. Prepends logline with a timestamp.
  */
 function dumpLog(logline) {
@@ -432,17 +432,16 @@ function wasInterrupted() {
 }
 
 function checkForInterrupted() {
     if (wasInterrupted()) {
       if (previousFrame) {
         //if previousFrame is set, then we're in a single process environment
         curFrame = actions.frame = previousFrame;
         previousFrame = null;
-        sandbox = null;
       }
       else {
         //else we're in OOP environment, so we'll switch to the original OOP frame
         sendSyncMessage("Marionette:switchToModalOrigin");
       }
       sendSyncMessage("Marionette:switchedToFrame", { restorePrevious: true });
     }
 }
@@ -461,17 +460,22 @@ function createExecuteContentSandbox(win
       "content",
       marionetteLogObj,
       timeout,
       heartbeatCallback,
       marionetteTestName);
   mn.runEmulatorCmd = (cmd, cb) => this.runEmulatorCmd(cmd, cb);
   mn.runEmulatorShell = (args, cb) => this.runEmulatorShell(args, cb);
 
-  let sandbox = new Cu.Sandbox(win, {sandboxPrototype: win});
+  let principal = win;
+  if (sandboxName == 'system') {
+    principal = Cc["@mozilla.org/systemprincipal;1"].
+                createInstance(Ci.nsIPrincipal);
+  }
+  let sandbox = new Cu.Sandbox(principal, {sandboxPrototype: win});
   sandbox.global = sandbox;
   sandbox.window = win;
   sandbox.document = sandbox.window.document;
   sandbox.navigator = sandbox.window.navigator;
   sandbox.testUtils = utils;
   sandbox.asyncTestCommandId = asyncTestCommandId;
   sandbox.marionette = mn;
 
@@ -527,17 +531,17 @@ function createExecuteContentSandbox(win
       sandbox.asyncComplete(mn.generate_results(), sandbox.asyncTestCommandId);
     } else {
       return mn.generate_results();
     }
   };
   sandbox.marionetteScriptFinished = val =>
       sandbox.asyncComplete(val, sandbox.asyncTestCommandId);
 
-  return sandbox;
+  sandboxes[sandboxName] = sandbox;
 }
 
 /**
  * Execute the given script either as a function body (executeScript)
  * or directly (for mochitest like JS Marionette tests).
  */
 function executeScript(msg, directInject) {
   // Set up inactivity timeout.
@@ -552,28 +556,32 @@ function executeScript(msg, directInject
     heartbeatCallback = function() {
       curFrame.clearTimeout(inactivityTimeoutId);
       setTimer();
     };
   }
 
   asyncTestCommandId = msg.json.command_id;
   let script = msg.json.script;
+  sandboxName = msg.json.sandboxName;
 
-  if (msg.json.newSandbox || !sandbox) {
-    sandbox = createExecuteContentSandbox(curFrame,
-                                          msg.json.timeout);
-    if (!sandbox) {
+  if (msg.json.newSandbox ||
+      !(sandboxName in sandboxes) ||
+      (sandboxes[sandboxName].window != curFrame)) {
+    createExecuteContentSandbox(curFrame, msg.json.timeout);
+    if (!sandboxes[sandboxName]) {
       sendError(new WebDriverError("Could not create sandbox!"), asyncTestCommandId);
       return;
     }
   } else {
-    sandbox.asyncTestCommandId = asyncTestCommandId;
+    sandboxes[sandboxName].asyncTestCommandId = asyncTestCommandId;
   }
 
+  let sandbox = sandboxes[sandboxName];
+
   try {
     if (directInject) {
       if (importedScripts.exists()) {
         let stream = Components.classes["@mozilla.org/network/file-input-stream;1"].
                       createInstance(Components.interfaces.nsIFileInputStream);
         stream.init(importedScripts, -1, 0, 0);
         let data = NetUtil.readInputStreamToString(stream, stream.available());
         stream.close();
@@ -675,33 +683,36 @@ function executeWithCallback(msg, useFin
     heartbeatCallback = function() {
       curFrame.clearTimeout(inactivityTimeoutId);
       setTimer();
     };
   }
 
   let script = msg.json.script;
   asyncTestCommandId = msg.json.command_id;
+  sandboxName = msg.json.sandboxName;
 
   onunload = function() {
     sendError(new JavaScriptError("unload was called"), asyncTestCommandId);
   };
   curFrame.addEventListener("unload", onunload, false);
 
-  if (msg.json.newSandbox || !sandbox) {
-    sandbox = createExecuteContentSandbox(curFrame,
-                                          msg.json.timeout);
-    if (!sandbox) {
+  if (msg.json.newSandbox ||
+      !(sandboxName in sandboxes) ||
+      (sandboxes[sandboxName].window != curFrame)) {
+    createExecuteContentSandbox(curFrame, msg.json.timeout);
+    if (!sandboxes[sandboxName]) {
       sendError(new JavaScriptError("Could not create sandbox!"), asyncTestCommandId);
       return;
     }
   }
   else {
-    sandbox.asyncTestCommandId = asyncTestCommandId;
+    sandboxes[sandboxName].asyncTestCommandId = asyncTestCommandId;
   }
+  let sandbox = sandboxes[sandboxName];
   sandbox.tag = script;
 
   asyncTestTimeoutId = curFrame.setTimeout(function() {
     sandbox.asyncComplete(new ScriptTimeoutError("timed out"), asyncTestCommandId);
   }, msg.json.timeout);
 
   originalOnError = curFrame.onerror;
   curFrame.onerror = function errHandler(msg, url, line) {
@@ -1637,17 +1648,17 @@ function switchToFrame(msg) {
   if ((msg.json.id === null || msg.json.id === undefined) && (msg.json.element == null)) {
     // returning to root frame
     sendSyncMessage("Marionette:switchedToFrame", { frameValue: null });
 
     curFrame = content;
     if(msg.json.focus == true) {
       curFrame.focus();
     }
-    sandbox = null;
+
     checkTimer.initWithCallback(checkLoad, 100, Ci.nsITimer.TYPE_ONE_SHOT);
     return;
   }
   if (msg.json.element != undefined) {
     if (elementManager.seenItems[msg.json.element] != undefined) {
       let wantedFrame;
       try {
         wantedFrame = elementManager.getKnownElement(msg.json.element, curFrame); //Frame Element
@@ -1689,17 +1700,17 @@ function switchToFrame(msg) {
         else {
           // If foundFrame is null at this point then we have the top level browsing
           // context so should treat it accordingly.
           sendSyncMessage("Marionette:switchedToFrame", { frameValue: null});
           curFrame = content;
           if(msg.json.focus == true) {
             curFrame.focus();
           }
-          sandbox = null;
+
           checkTimer.initWithCallback(checkLoad, 100, Ci.nsITimer.TYPE_ONE_SHOT);
           return;
         }
       } catch (e) {
         // Since window.frames does not return OOP frames it will throw
         // and we land up here. Let's not give up and check if there are
         // iframes and switch to the indexed frame there
         let iframes = curFrame.document.getElementsByTagName("iframe");
@@ -1711,18 +1722,16 @@ function switchToFrame(msg) {
     }
   }
 
   if (foundFrame === null) {
     sendError(new NoSuchFrameError("Unable to locate frame: " + (msg.json.id || msg.json.element)), command_id);
     return true;
   }
 
-  sandbox = null;
-
   // send a synchronous message to let the server update the currently active
   // frame element (for getActiveFrame)
   let frameValue = elementManager.wrapValue(curFrame.wrappedJSObject)['ELEMENT'];
   sendSyncMessage("Marionette:switchedToFrame", { frameValue: frameValue });
 
   let rv = null;
   if (curFrame.contentWindow === null) {
     // The frame we want to switch to is a remote/OOP frame;
@@ -1872,17 +1881,17 @@ function runEmulatorShell(args, callback
     _emu_cbs[_emu_cb_id] = callback;
   }
   sendAsyncMessage("Marionette:runEmulatorShell", {emulator_shell: args, id: _emu_cb_id});
   _emu_cb_id += 1;
 }
 
 function emulatorCmdResult(msg) {
   let message = msg.json;
-  if (!sandbox) {
+  if (!sandboxes[sandboxName]) {
     return;
   }
   let cb = _emu_cbs[message.id];
   delete _emu_cbs[message.id];
   if (!cb) {
     return;
   }
   try {
--- a/testing/mozbase/mozcrash/mozcrash/mozcrash.py
+++ b/testing/mozbase/mozcrash/mozcrash/mozcrash.py
@@ -330,12 +330,12 @@ def check_for_java_exception(logcat, qui
                 if m and m.group(1):
                     exception_type = m.group(1)
                 m = logre.search(logcat[i+2])
                 if m and m.group(1):
                     exception_location = m.group(1)
                 if not quiet:
                     print "PROCESS-CRASH | java-exception | %s %s" % (exception_type, exception_location)
             else:
-                print "Automation Error: Logcat is truncated!"
+                print "Automation Error: java exception in logcat at line %d of %d: %s" % (i, len(logcat), line)
             break
 
     return found_exception
--- a/testing/mozbase/mozdevice/mozdevice/devicemanager.py
+++ b/testing/mozbase/mozdevice/mozdevice/devicemanager.py
@@ -147,21 +147,23 @@ class DeviceManager(object):
         self.shellCheckOutput(['/system/bin/logcat', '-c'], root=self._logcatNeedsRoot)
 
     def getLogcat(self, filterSpecs=["dalvikvm:I", "ConnectivityService:S",
                                       "WifiMonitor:S", "WifiStateTracker:S",
                                       "wpa_supplicant:S", "NetworkStateTracker:S"],
                   format="time",
                   filterOutRegexps=[]):
         """
-        Returns the contents of the logcat file as a list of strings
+        Returns the contents of the logcat file as a list of
+        '\n' terminated strings
         """
         cmdline = ["/system/bin/logcat", "-v", format, "-d"] + filterSpecs
-        lines = self.shellCheckOutput(cmdline,
-                                      root=self._logcatNeedsRoot).split('\r')
+        output = self.shellCheckOutput(cmdline,
+                                      root=self._logcatNeedsRoot)
+        lines = output.replace('\r\n', '\n').splitlines(True)
 
         for regex in filterOutRegexps:
             lines = [line for line in lines if not re.search(regex, line)]
 
         return lines
 
     def saveScreenshot(self, filename):
         """
--- a/testing/mozbase/mozdevice/tests/sut_logcat.py
+++ b/testing/mozbase/mozdevice/tests/sut_logcat.py
@@ -2,46 +2,46 @@
 
 import mozdevice
 import mozlog
 import unittest
 from sut import MockAgent
 
 
 class TestLogCat(unittest.TestCase):
-    """ Class to test methods assosiated with logcat """
+    """ Class to test methods associated with logcat """
 
     def test_getLogcat(self):
 
-        logcat_output = ("07-17 00:51:10.377 I/SUTAgentAndroid( 2933): onCreate\n\r"
-        "07-17 00:51:10.457 D/dalvikvm( 2933): GC_CONCURRENT freed 351K, 17% free 2523K/3008K, paused 5ms+2ms, total 38ms\n\r"
-        "07-17 00:51:10.497 I/SUTAgentAndroid( 2933): Caught exception creating file in /data/local/tmp: open failed: EACCES (Permission denied)\n\r"
-        "07-17 00:51:10.507 E/SUTAgentAndroid( 2933): ERROR: Cannot access world writeable test root\n\r"
-        "07-17 00:51:10.547 D/GeckoHealthRec( 3253): Initializing profile cache.\n\r"
-        "07-17 00:51:10.607 D/GeckoHealthRec( 3253): Looking for /data/data/org.mozilla.fennec/files/mozilla/c09kfhne.default/times.json\n\r"
-        "07-17 00:51:10.637 D/GeckoHealthRec( 3253): Using times.json for profile creation time.\n\r"
-        "07-17 00:51:10.707 D/GeckoHealthRec( 3253): Incorporating environment: times.json profile creation = 1374026758604\n\r"
-        "07-17 00:51:10.507 D/GeckoHealthRec( 3253): Requested prefs.\n\r"
-        "07-17 06:50:54.907 I/SUTAgentAndroid( 3876): \n\r"
-        "07-17 06:50:54.907 I/SUTAgentAndroid( 3876): Total Private Dirty Memory         3176 kb\n\r"
-        "07-17 06:50:54.907 I/SUTAgentAndroid( 3876): Total Proportional Set Size Memory 5679 kb\n\r"
-        "07-17 06:50:54.907 I/SUTAgentAndroid( 3876): Total Shared Dirty Memory          9216 kb\n\r"
+        logcat_output = ("07-17 00:51:10.377 I/SUTAgentAndroid( 2933): onCreate\r\n"
+        "07-17 00:51:10.457 D/dalvikvm( 2933): GC_CONCURRENT freed 351K, 17% free 2523K/3008K, paused 5ms+2ms, total 38ms\r\n"
+        "07-17 00:51:10.497 I/SUTAgentAndroid( 2933): Caught exception creating file in /data/local/tmp: open failed: EACCES (Permission denied)\r\n"
+        "07-17 00:51:10.507 E/SUTAgentAndroid( 2933): ERROR: Cannot access world writeable test root\r\n"
+        "07-17 00:51:10.547 D/GeckoHealthRec( 3253): Initializing profile cache.\r\n"
+        "07-17 00:51:10.607 D/GeckoHealthRec( 3253): Looking for /data/data/org.mozilla.fennec/files/mozilla/c09kfhne.default/times.json\r\n"
+        "07-17 00:51:10.637 D/GeckoHealthRec( 3253): Using times.json for profile creation time.\r\n"
+        "07-17 00:51:10.707 D/GeckoHealthRec( 3253): Incorporating environment: times.json profile creation = 1374026758604\r\n"
+        "07-17 00:51:10.507 D/GeckoHealthRec( 3253): Requested prefs.\r\n"
+        "07-17 06:50:54.907 I/SUTAgentAndroid( 3876): \r\n"
+        "07-17 06:50:54.907 I/SUTAgentAndroid( 3876): Total Private Dirty Memory         3176 kb\r\n"
+        "07-17 06:50:54.907 I/SUTAgentAndroid( 3876): Total Proportional Set Size Memory 5679 kb\r\n"
+        "07-17 06:50:54.907 I/SUTAgentAndroid( 3876): Total Shared Dirty Memory          9216 kb\r\n"
         "07-17 06:55:21.627 I/SUTAgentAndroid( 3876): 127.0.0.1 : execsu /system/bin/logcat -v time -d dalvikvm:I "
-        "ConnectivityService:S WifiMonitor:S WifiStateTracker:S wpa_supplicant:S NetworkStateTracker:S\n\r"
-        "07-17 06:55:21.827 I/dalvikvm-heap( 3876): Grow heap (frag case) to 3.019MB for 102496-byte allocation\n\r"
+        "ConnectivityService:S WifiMonitor:S WifiStateTracker:S wpa_supplicant:S NetworkStateTracker:S\r\n"
+        "07-17 06:55:21.827 I/dalvikvm-heap( 3876): Grow heap (frag case) to 3.019MB for 102496-byte allocation\r\n"
         "return code [0]")
 
         inp = ("execsu /system/bin/logcat -v time -d "
         "dalvikvm:I ConnectivityService:S WifiMonitor:S "
         "WifiStateTracker:S wpa_supplicant:S NetworkStateTracker:S")
 
         commands = [(inp, logcat_output)]
         m = MockAgent(self, commands=commands)
         d = mozdevice.DroidSUT("127.0.0.1", port=m.port, logLevel=mozlog.DEBUG)
-        self.assertEqual(logcat_output[:-17].split('\r'), d.getLogcat())
+        self.assertEqual(logcat_output[:-17].replace('\r\n', '\n').splitlines(True), d.getLogcat())
 
     def test_recordLogcat(self):
 
         commands = [("execsu /system/bin/logcat -c", "return code [0]")]
 
         m = MockAgent(self, commands=commands)
         d = mozdevice.DroidSUT("127.0.0.1", port=m.port, logLevel=mozlog.DEBUG)
         # No error raised means success
--- a/testing/mozharness/mozharness.json
+++ b/testing/mozharness/mozharness.json
@@ -1,4 +1,4 @@
 {
     "repo": "https://hg.mozilla.org/build/mozharness",
-    "revision": "1da0d44cd6ab"
+    "revision": "c9633e9c344a"
 }
--- a/testing/talos/talos.json
+++ b/testing/talos/talos.json
@@ -1,16 +1,16 @@
 {
     "talos.zip": {
         "url": "http://talos-bundles.pvt.build.mozilla.org/zips/talos.3dc0c42bd761.zip",
         "path": ""
     },
     "global": {
         "talos_repo": "https://hg.mozilla.org/build/talos",
-        "talos_revision": "85d4f8ef4810"
+        "talos_revision": "c1da803b295e"
     },
     "extra_options": {
         "android": [ "--apkPath=%(apk_path)s" ]
     },
     "suites": {
         "chromez": {
             "tests": ["tresize", "tcanvasmark"]
         },
--- a/toolkit/components/telemetry/TelemetryController.jsm
+++ b/toolkit/components/telemetry/TelemetryController.jsm
@@ -191,29 +191,16 @@ this.TelemetryController = Object.freeze
   /**
    * Sets a server to send pings to.
    */
   setServer: function(aServer) {
     return Impl.setServer(aServer);
   },
 
   /**
-   * Adds a ping to the pending ping list by moving it to the saved pings directory
-   * and adding it to the pending ping list.
-   *
-   * @param {String} aPingPath The path of the ping to add to the pending ping list.
-   * @param {Boolean} [aRemoveOriginal] If true, deletes the ping at aPingPath after adding
-   *                  it to the saved pings directory.
-   * @return {Promise} Resolved when the ping is correctly moved to the saved pings directory.
-   */
-  addPendingPingFromFile: function(aPingPath, aRemoveOriginal) {
-    return Impl.addPendingPingFromFile(aPingPath, aRemoveOriginal);
-  },
-
-  /**
    * Submit ping payloads to Telemetry. This will assemble a complete ping, adding
    * environment data, client id and some general info.
    * Depending on configuration, the ping will be sent to the server (immediately or later)
    * and archived locally.
    *
    * @param {String} aType The type of the ping.
    * @param {Object} aPayload The actual data payload for the ping.
    * @param {Object} [aOptions] Options object.
@@ -280,16 +267,26 @@ this.TelemetryController = Object.freeze
     options.addClientId = aOptions.addClientId || false;
     options.addEnvironment = aOptions.addEnvironment || false;
     options.overwrite = aOptions.overwrite || false;
 
     return Impl.addPendingPing(aType, aPayload, options);
   },
 
   /**
+   * Save an aborted-session ping to the pending pings and archive it.
+   *
+   * @param {String} aFilePath The path to the aborted-session checkpoint ping.
+   * @return {Promise} Promise that is resolved when the ping is saved.
+   */
+  addAbortedSessionPing: function addAbortedSessionPing(aFilePath) {
+    return Impl.addAbortedSessionPing(aFilePath);
+  },
+
+  /**
    * Write a ping to a specified location on the disk. Does not add the ping to the
    * pending pings.
    *
    * @param {String} aType The type of the ping.
    * @param {Object} aPayload The actual data payload for the ping.
    * @param {String} aFilePath The path to save the ping to.
    * @param {Object} [aOptions] Options object.
    * @param {Number} [aOptions.retentionDays=14] The number of days to keep the ping on disk
@@ -496,33 +493,16 @@ let Impl = {
    * Track any pending ping send and save tasks through the promise passed here.
    * This is needed to block shutdown on any outstanding ping activity.
    */
   _trackPendingPingTask: function (aPromise) {
     this._connectionsBarrier.client.addBlocker("Waiting for ping task", aPromise);
   },
 
   /**
-   * Adds a ping to the pending ping list by moving it to the saved pings directory
-   * and adding it to the pending ping list.
-   *
-   * @param {String} aPingPath The path of the ping to add to the pending ping list.
-   * @param {Boolean} [aRemoveOriginal] If true, deletes the ping at aPingPath after adding
-   *                  it to the saved pings directory.
-   * @return {Promise} Resolved when the ping is correctly moved to the saved pings directory.
-   */
-  addPendingPingFromFile: function(aPingPath, aRemoveOriginal) {
-    return TelemetryStorage.addPendingPingFromFile(aPingPath).then(() => {
-        if (aRemoveOriginal) {
-          return OS.File.remove(aPingPath);
-        }
-      }, error => this._log.error("addPendingPingFromFile - Unable to add the pending ping", error));
-  },
-
-  /**
    * This helper calculates the next time that we can send pings at.
    * Currently this mostly redistributes ping sends around midnight to avoid submission
    * spikes around local midnight for daily pings.
    *
    * @param now Date The current time.
    * @return Number The next time (ms from UNIX epoch) when we can send pings.
    */
   _getNextPingSendTime: function(now) {
@@ -715,16 +695,36 @@ let Impl = {
   savePing: function savePing(aType, aPayload, aFilePath, aOptions) {
     this._log.trace("savePing - Type " + aType + ", Server " + this._server +
                     ", File Path " + aFilePath + ", aOptions " + JSON.stringify(aOptions));
     let pingData = this.assemblePing(aType, aPayload, aOptions);
     return TelemetryStorage.savePingToFile(pingData, aFilePath, aOptions.overwrite)
                         .then(() => pingData.id);
   },
 
+  /**
+   * Save an aborted-session ping to the pending pings and archive it.
+   *
+   * @param {String} aFilePath The path to the aborted-session checkpoint ping.
+   * @return {Promise} Promise that is resolved when the ping is saved.
+   */
+  addAbortedSessionPing: Task.async(function* addAbortedSessionPing(aFilePath) {
+    this._log.trace("addAbortedSessionPing");
+
+    let ping = yield TelemetryStorage.loadPingFile(aFilePath);
+    try {
+      yield TelemetryStorage.addPendingPing(ping);
+      yield TelemetryArchive.promiseArchivePing(ping);
+    } catch (e) {
+      this._log.error("addAbortedSessionPing - Unable to add the pending ping", e);
+    } finally {
+      yield OS.File.remove(aFilePath);
+    }
+  }),
+
   onPingRequestFinished: function(success, startTime, ping, isPersisted) {
     this._log.trace("onPingRequestFinished - success: " + success + ", persisted: " + isPersisted);
 
     let hping = Telemetry.getHistogramById("TELEMETRY_PING");
     let hsuccess = Telemetry.getHistogramById("TELEMETRY_SUCCESS");
 
     hsuccess.add(success);
     hping.add(new Date() - startTime);
--- a/toolkit/components/telemetry/TelemetrySession.jsm
+++ b/toolkit/components/telemetry/TelemetrySession.jsm
@@ -1527,22 +1527,21 @@ let Impl = {
     // Delay full telemetry initialization to give the browser time to
     // run various late initializers. Otherwise our gathered memory
     // footprint and other numbers would be too optimistic.
     this._delayedInitTaskDeferred = Promise.defer();
     this._delayedInitTask = new DeferredTask(function* () {
       try {
         this._initialized = true;
 
-        let hasLoaded = yield this._loadSessionData();
-        if (!hasLoaded) {
-          // We could not load a valid session data file. Create one.
-          yield this._saveSessionData(this._getSessionDataObject()).catch(() =>
-            this._log.error("setupChromeProcess - Could not write session data to disk."));
-        }
+        yield this._loadSessionData();
+        // Update the session data to keep track of new subsessions created before
+        // the initialization.
+        yield this._saveSessionData(this._getSessionDataObject());
+
         this.attachObservers();
         this.gatherMemory();
 
         Telemetry.asyncFetchTelemetryData(function () {});
 
         if (IS_UNIFIED_TELEMETRY) {
           // Check for a previously written aborted session ping.
           yield this._checkAbortedSessionPing();
@@ -1960,18 +1959,18 @@ let Impl = {
                                 SESSION_STATE_FILE_NAME);
 
     // Try to load the "profileSubsessionCounter" from the state file.
     try {
       let data = yield CommonUtils.readJSON(dataFile);
       if (data &&
           "profileSubsessionCounter" in data &&
           typeof(data.profileSubsessionCounter) == "number" &&
-          "previousSubsessionId" in data) {
-        this._previousSubsessionId = data.previousSubsessionId;
+          "subsessionId" in data) {
+        this._previousSubsessionId = data.subsessionId;
         // Add |_subsessionCounter| to the |_profileSubsessionCounter| to account for
         // new subsession while loading still takes place. This will always be exactly
         // 1 - the current subsessions.
         this._profileSubsessionCounter = data.profileSubsessionCounter +
                                          this._subsessionCounter;
         return true;
       }
     } catch (e) {
@@ -1980,17 +1979,17 @@ let Impl = {
     return false;
   }),
 
   /**
    * Get the session data object to serialise to disk.
    */
   _getSessionDataObject: function() {
     return {
-      previousSubsessionId: this._previousSubsessionId,
+      subsessionId: this._subsessionId,
       profileSubsessionCounter: this._profileSubsessionCounter,
     };
   },
 
   /**
    * Saves session data to disk.
    */
   _saveSessionData: Task.async(function* (sessionData) {
@@ -2004,18 +2003,17 @@ let Impl = {
       this._log.error("_saveSessionData - Failed to write session data to " + filePath, e);
     }
   }),
 
   _onEnvironmentChange: function(reason, oldEnvironment) {
     this._log.trace("_onEnvironmentChange", reason);
     let payload = this.getSessionPayload(REASON_ENVIRONMENT_CHANGE, true);
 
-    let clonedPayload = Cu.cloneInto(payload, myScope);
-    TelemetryScheduler.reschedulePings(REASON_ENVIRONMENT_CHANGE, clonedPayload);
+    TelemetryScheduler.reschedulePings(REASON_ENVIRONMENT_CHANGE, payload);
 
     let options = {
       retentionDays: RETENTION_DAYS,
       addClientId: true,
       addEnvironment: true,
       overrideEnvironment: oldEnvironment,
     };
     TelemetryController.submitExternalPing(getPingType(payload), payload, options);
@@ -2073,34 +2071,34 @@ let Impl = {
     yield OS.File.makeDir(ABORTED_SESSIONS_DIR, { ignoreExisting: true });
 
     const FILE_PATH = OS.Path.join(OS.Constants.Path.profileDir, DATAREPORTING_DIRECTORY,
                                    ABORTED_SESSION_FILE_NAME);
     let abortedExists = yield OS.File.exists(FILE_PATH);
     if (abortedExists) {
       this._log.trace("_checkAbortedSessionPing - aborted session found: " + FILE_PATH);
       yield this._abortedSessionSerializer.enqueueTask(
-        () => TelemetryController.addPendingPingFromFile(FILE_PATH, true));
+        () => TelemetryController.addAbortedSessionPing(FILE_PATH));
     }
   }),
 
   /**
    * Saves the aborted session ping to disk.
    * @param {Object} [aProvidedPayload=null] A payload object to be used as an aborted
    *                 session ping. The reason of this payload is changed to aborted-session.
    *                 If not provided, a new payload is gathered.
    */
   _saveAbortedSessionPing: function(aProvidedPayload = null) {
     const FILE_PATH = OS.Path.join(OS.Constants.Path.profileDir, DATAREPORTING_DIRECTORY,
                                    ABORTED_SESSION_FILE_NAME);
     this._log.trace("_saveAbortedSessionPing - ping path: " + FILE_PATH);
 
     let payload = null;
     if (aProvidedPayload) {
-      payload = aProvidedPayload;
+      payload = Cu.cloneInto(aProvidedPayload, myScope);
       // Overwrite the original reason.
       payload.info.reason = REASON_ABORTED_SESSION;
     } else {
       payload = this.getSessionPayload(REASON_ABORTED_SESSION, false);
     }
 
     let options = {
       retentionDays: RETENTION_DAYS,
new file mode 100644
--- /dev/null
+++ b/toolkit/components/telemetry/tests/unit/test_SubsessionChaining.js
@@ -0,0 +1,227 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/
+*/
+
+Cu.import("resource://gre/modules/Preferences.jsm", this);
+Cu.import("resource://gre/modules/Promise.jsm", this);
+Cu.import("resource://gre/modules/Task.jsm", this);
+Cu.import("resource://gre/modules/TelemetryArchive.jsm", this);
+Cu.import("resource://gre/modules/TelemetryController.jsm", this);
+Cu.import("resource://gre/modules/TelemetryEnvironment.jsm", this);
+Cu.import("resource://gre/modules/osfile.jsm", this);
+Cu.import("resource://gre/modules/XPCOMUtils.jsm", this);
+
+const MS_IN_ONE_HOUR  = 60 * 60 * 1000;
+const MS_IN_ONE_DAY   = 24 * MS_IN_ONE_HOUR;
+
+const PREF_BRANCH = "toolkit.telemetry.";
+const PREF_ENABLED = PREF_BRANCH + "enabled";
+const PREF_ARCHIVE_ENABLED = PREF_BRANCH + "archive.enabled";
+
+const REASON_ABORTED_SESSION = "aborted-session";
+const REASON_DAILY = "daily";
+const REASON_ENVIRONMENT_CHANGE = "environment-change";
+const REASON_SHUTDOWN = "shutdown";
+
+XPCOMUtils.defineLazyGetter(this, "DATAREPORTING_PATH", function() {
+  return OS.Path.join(OS.Constants.Path.profileDir, "datareporting");
+});
+
+let promiseValidateArchivedPings = Task.async(function*(aExpectedReasons) {
+  // The list of ping reasons which mark the session end (and must reset the subsession
+  // count).
+  const SESSION_END_PING_REASONS = new Set([ REASON_ABORTED_SESSION, REASON_SHUTDOWN ]);
+
+  let list = yield TelemetryArchive.promiseArchivedPingList();
+
+  // We're just interested in the "main" pings.
+  list = list.filter(p => p.type == "main");
+
+  Assert.equal(aExpectedReasons.length, list.length, "All the expected pings must be received.");
+
+  let previousPing = yield TelemetryArchive.promiseArchivedPingById(list[0].id);
+  Assert.equal(aExpectedReasons.shift(), previousPing.payload.info.reason,
+               "Telemetry should only get pings with expected reasons.");
+  Assert.equal(previousPing.payload.info.previousSubsessionId, null,
+               "The first subsession must report a null previous subsession id.");
+  Assert.equal(previousPing.payload.info.profileSubsessionCounter, 1,
+               "profileSubsessionCounter must be 1 the first time.");
+  Assert.equal(previousPing.payload.info.subsessionCounter, 1,
+               "subsessionCounter must be 1 the first time.");
+
+  let expectedSubsessionCounter = 1;
+
+  for (let i = 1; i < list.length; i++) {
+    let currentPing = yield TelemetryArchive.promiseArchivedPingById(list[i].id);
+    let currentInfo = currentPing.payload.info;
+    let previousInfo = previousPing.payload.info;
+    do_print("Archive entry " + i + " - id: " + currentPing.id + ", reason: " + currentInfo.reason);
+
+    Assert.equal(aExpectedReasons.shift(), currentInfo.reason,
+                 "Telemetry should only get pings with expected reasons.");
+    Assert.equal(currentInfo.previousSubsessionId, previousInfo.subsessionId,
+                 "Telemetry must correctly chain subsession identifiers.");
+    Assert.equal(currentInfo.profileSubsessionCounter, previousInfo.profileSubsessionCounter + 1,
+                 "Telemetry must correctly track the profile subsessions count.");
+    Assert.equal(currentInfo.subsessionCounter, expectedSubsessionCounter,
+                 "The subsession counter should be monotonically increasing.");
+
+    // Store the current ping as previous.
+    previousPing = currentPing;
+    // Reset the expected subsession counter, if required. Otherwise increment the expected
+    // subsession counter.
+    expectedSubsessionCounter =
+      SESSION_END_PING_REASONS.has(currentInfo.reason) ? 1 : (expectedSubsessionCounter + 1);
+  }
+});
+
+function run_test() {
+  do_test_pending();
+
+  // Addon manager needs a profile directory
+  do_get_profile();
+  loadAddonManager("xpcshell@tests.mozilla.org", "XPCShell", "1", "1.9.2");
+
+  Preferences.set(PREF_ENABLED, true);
+
+  run_next_test();
+}
+
+add_task(function* test_subsessionsChaining() {
+  if (gIsAndroid) {
+    // We don't support subsessions yet on Android, so skip the next checks.
+    return;
+  }
+
+  const PREF_TEST = PREF_BRANCH + "test.pref1";
+  const PREFS_TO_WATCH = new Map([
+    [PREF_TEST, TelemetryEnvironment.RECORD_PREF_VALUE],
+  ]);
+  Preferences.reset(PREF_TEST);
+
+  // Fake the clock data to manually trigger an aborted-session ping and a daily ping.
+  // This is also helpful to make sure we get the archived pings in an expected order.
+  let now = fakeNow(2009, 9, 18, 0, 0, 0);
+
+  let moveClockForward = (minutes) => {
+    now = futureDate(now, minutes * MILLISECONDS_PER_MINUTE);
+    fakeNow(now);
+  }
+
+  // Keep track of the ping reasons we're expecting in this test.
+  let expectedReasons = [];
+
+  // Start and shut down Telemetry. We expect a shutdown ping with profileSubsessionCounter: 1,
+  // subsessionCounter: 1, subsessionId: A,  and previousSubsessionId: null to be archived.
+  yield TelemetrySession.reset();
+  yield TelemetrySession.shutdown();
+  expectedReasons.push(REASON_SHUTDOWN);
+
+  // Start Telemetry but don't wait for it to initialise before shutting down. We expect a
+  // shutdown ping with profileSubsessionCounter: 2, subsessionCounter: 1, subsessionId: B
+  // and previousSubsessionId: A to be archived.
+  moveClockForward(30);
+  TelemetrySession.reset();
+  yield TelemetrySession.shutdown();
+  expectedReasons.push(REASON_SHUTDOWN);
+
+  // Start Telemetry and simulate an aborted-session ping. We expect an aborted-session ping
+  // with profileSubsessionCounter: 3, subsessionCounter: 1, subsessionId: C and
+  // previousSubsessionId: B to be archived.
+  let schedulerTickCallback = null;
+  fakeSchedulerTimer(callback => schedulerTickCallback = callback, () => {});
+  yield TelemetrySession.reset();
+  moveClockForward(6);
+  // Trigger the an aborted session ping save. When testing,we are not saving the aborted-session
+  // ping as soon as Telemetry starts, otherwise we would end up with unexpected pings being
+  // sent when calling |TelemetrySession.reset()|, thus breaking some tests.
+  Assert.ok(!!schedulerTickCallback);
+  yield schedulerTickCallback();
+  expectedReasons.push(REASON_ABORTED_SESSION);
+
+  // Start Telemetry and trigger an environment change through a pref modification. We expect
+  // an environment-change ping with profileSubsessionCounter: 4, subsessionCounter: 1,
+  // subsessionId: D and previousSubsessionId: C to be archived.
+  moveClockForward(30);
+  yield TelemetryController.reset();
+  yield TelemetrySession.reset();
+  TelemetryEnvironment._watchPreferences(PREFS_TO_WATCH);
+  moveClockForward(30);
+  Preferences.set(PREF_TEST, 1);
+  expectedReasons.push(REASON_ENVIRONMENT_CHANGE);
+
+  // Shut down Telemetry. We expect a shutdown ping with profileSubsessionCounter: 5,
+  // subsessionCounter: 2, subsessionId: E and previousSubsessionId: D to be archived.
+  moveClockForward(30);
+  yield TelemetrySession.shutdown();
+  expectedReasons.push(REASON_SHUTDOWN);
+
+  // Start Telemetry and trigger a daily ping. We expect a daily ping with
+  // profileSubsessionCounter: 6, subsessionCounter: 1, subsessionId: F and
+  // previousSubsessionId: E to be archived.
+  moveClockForward(30);
+  yield TelemetrySession.reset();
+
+  // Delay the callback around midnight.
+  now = fakeNow(futureDate(now, MS_IN_ONE_DAY));
+  // Trigger the daily ping.
+  yield schedulerTickCallback();
+  expectedReasons.push(REASON_DAILY);
+
+  // Trigger an environment change ping. We expect an environment-changed ping with
+  // profileSubsessionCounter: 7, subsessionCounter: 2, subsessionId: G and
+  // previousSubsessionId: F to be archived.
+  moveClockForward(30);
+  Preferences.set(PREF_TEST, 0);
+  expectedReasons.push(REASON_ENVIRONMENT_CHANGE);
+
+  // Shut down Telemetry and trigger a shutdown ping.
+  moveClockForward(30);
+  yield TelemetrySession.shutdown();
+  expectedReasons.push(REASON_SHUTDOWN);
+
+  // Start Telemetry and trigger an environment change.
+  yield TelemetrySession.reset();
+  TelemetryEnvironment._watchPreferences(PREFS_TO_WATCH);
+  moveClockForward(30);
+  Preferences.set(PREF_TEST, 1);
+  expectedReasons.push(REASON_ENVIRONMENT_CHANGE);
+
+  // Don't shut down, instead trigger an aborted-session ping.
+  moveClockForward(6);
+  // Trigger the an aborted session ping save.
+  yield schedulerTickCallback();
+  expectedReasons.push(REASON_ABORTED_SESSION);
+
+  // Start Telemetry and trigger a daily ping.
+  moveClockForward(30);
+  yield TelemetryController.reset();
+  yield TelemetrySession.reset();
+  // Delay the callback around midnight.
+  now = futureDate(now, MS_IN_ONE_DAY);
+  fakeNow(now);
+  // Trigger the daily ping.
+  yield schedulerTickCallback();
+  expectedReasons.push(REASON_DAILY);
+
+  // Trigger an environment change.
+  moveClockForward(30);
+  Preferences.set(PREF_TEST, 0);
+  expectedReasons.push(REASON_ENVIRONMENT_CHANGE);
+
+  // And an aborted-session ping again.
+  moveClockForward(6);
+  // Trigger the an aborted session ping save.
+  yield schedulerTickCallback();
+  expectedReasons.push(REASON_ABORTED_SESSION);
+
+  // Make sure the aborted-session ping gets archived.
+  yield TelemetryController.reset();
+  yield TelemetrySession.reset();
+
+  yield promiseValidateArchivedPings(expectedReasons);
+});
+
+add_task(function* () {
+  do_test_finished();
+});
--- a/toolkit/components/telemetry/tests/unit/test_TelemetrySession.js
+++ b/toolkit/components/telemetry/tests/unit/test_TelemetrySession.js
@@ -1162,17 +1162,17 @@ add_task(function* test_savedPingsOnShut
 add_task(function* test_savedSessionData() {
   // Create the directory which will contain the data file, if it doesn't already
   // exist.
   yield OS.File.makeDir(DATAREPORTING_PATH);
 
   // Write test data to the session data file.
   const dataFilePath = OS.Path.join(DATAREPORTING_PATH, "session-state.json");
   const sessionState = {
-    previousSubsessionId: null,
+    subsessionId: null,
     profileSubsessionCounter: 3785,
   };
   yield CommonUtils.writeJSON(sessionState, dataFilePath);
 
   const PREF_TEST = "toolkit.telemetry.test.pref1";
   Preferences.reset(PREF_TEST);
   const PREFS_TO_WATCH = new Map([
     [PREF_TEST, TelemetryEnvironment.RECORD_PREF_VALUE],
@@ -1204,17 +1204,50 @@ add_task(function* test_savedSessionData
 
   let payload = TelemetrySession.getPayload();
   Assert.equal(payload.info.profileSubsessionCounter, expectedSubsessions);
   yield TelemetrySession.shutdown();
 
   // Load back the serialised session data.
   let data = yield CommonUtils.readJSON(dataFilePath);
   Assert.equal(data.profileSubsessionCounter, expectedSubsessions);
-  Assert.equal(data.previousSubsessionId, expectedUUID);
+  Assert.equal(data.subsessionId, expectedUUID);
+});
+
+add_task(function* test_sessionData_ShortSession() {
+  if (gIsAndroid) {
+    // We don't support subsessions yet on Android, so skip the next checks.
+    return;
+  }
+
+  const SESSION_STATE_PATH = OS.Path.join(DATAREPORTING_PATH, "session-state.json");
+
+  // Shut down Telemetry and remove the session state file.
+  yield TelemetrySession.shutdown();
+  yield OS.File.remove(SESSION_STATE_PATH, { ignoreAbsent: true });
+
+  const expectedUUID = "009fd1ad-b85e-4817-b3e5-000000003785";
+  fakeGenerateUUID(generateUUID, () => expectedUUID);
+
+  // We intentionally don't wait for the setup to complete and shut down to simulate
+  // short sessions. We expect the profile subsession counter to be 1.
+  TelemetrySession.reset();
+  yield TelemetrySession.shutdown();
+
+  // Restore the UUID generation functions.
+  fakeGenerateUUID(generateUUID, generateUUID);
+
+  // Start TelemetrySession so that it loads the session data file. We expect the profile
+  // subsession counter to be incremented by 1 again.
+  yield TelemetrySession.reset();
+
+  // We expect 2 profile subsession counter updates.
+  let payload = TelemetrySession.getPayload();
+  Assert.equal(payload.info.profileSubsessionCounter, 2);
+  Assert.equal(payload.info.previousSubsessionId, expectedUUID);
 });
 
 add_task(function* test_invalidSessionData() {
   // Create the directory which will contain the data file, if it doesn't already
   // exist.
   yield OS.File.makeDir(DATAREPORTING_PATH);
 
   // Write test data to the session data file.
@@ -1233,17 +1266,17 @@ add_task(function* test_invalidSessionDa
   yield TelemetrySession.reset();
   let payload = TelemetrySession.getPayload();
   Assert.equal(payload.info.profileSubsessionCounter, expectedSubsessions);
   yield TelemetrySession.shutdown();
 
   // Load back the serialised session data.
   let data = yield CommonUtils.readJSON(dataFilePath);
   Assert.equal(data.profileSubsessionCounter, expectedSubsessions);
-  Assert.equal(data.previousSubsessionId, null);
+  Assert.equal(data.subsessionId, expectedUUID);
 });
 
 add_task(function* test_abortedSession() {
   if (gIsAndroid || gIsGonk) {
     // We don't have the aborted session ping here.
     return;
   }
 
--- a/toolkit/components/telemetry/tests/unit/xpcshell.ini
+++ b/toolkit/components/telemetry/tests/unit/xpcshell.ini
@@ -15,16 +15,17 @@ generated-files =
   dictionary.xpi
   experiment.xpi
   extension.xpi
   extension-2.xpi
   restartless.xpi
   theme.xpi
 
 [test_nsITelemetry.js]
+[test_SubsessionChaining.js]
 [test_TelemetryEnvironment.js]
 # Bug 1144395: crash on Android 4.3
 skip-if = android_version == "18"
 [test_PingAPI.js]
 [test_TelemetryFlagClear.js]
 [test_TelemetryLateWrites.js]
 [test_TelemetryLockCount.js]
 [test_TelemetryLog.js]
--- a/toolkit/devtools/server/actors/inspector.js
+++ b/toolkit/devtools/server/actors/inspector.js
@@ -2880,37 +2880,85 @@ var WalkerActor = protocol.ActorClass({
       objectActorID: Arg(0, "string")
     },
     response: {
       nodeFront: RetVal("nullable:disconnectedNode")
     }
   }),
 
   /**
-   * Given an StyleSheetActor (identified by its ID), commonly used in the
+   * Given a StyleSheetActor (identified by its ID), commonly used in the
    * style-editor, get its ownerNode and return the corresponding walker's
-   * NodeActor
+   * NodeActor.
+   * Note that getNodeFromActor was added later and can now be used instead.
    */
   getStyleSheetOwnerNode: method(function(styleSheetActorID) {
-    let styleSheetActor = this.conn.getActor(styleSheetActorID);
-    let ownerNode = styleSheetActor.ownerNode;
-
-    if (!styleSheetActor || !ownerNode) {
-      return null;
-    }
-
-    return this.attachElement(ownerNode);
+    return this.getNodeFromActor(styleSheetActorID, ["ownerNode"]);
   }, {
     request: {
       styleSheetActorID: Arg(0, "string")
     },
     response: {
       ownerNode: RetVal("nullable:disconnectedNode")
     }
   }),
+
+  /**
+   * This method can be used to retrieve NodeActor for DOM nodes from other
+   * actors in a way that they can later be highlighted in the page, or
+   * selected in the inspector.
+   * If an actor has a reference to a DOM node, and the UI needs to know about
+   * this DOM node (and possibly select it in the inspector), the UI should
+   * first retrieve a reference to the walkerFront:
+   *
+   * // Make sure the inspector/walker have been initialized first.
+   * toolbox.initInspector().then(() => {
+   *  // Retrieve the walker.
+   *  let walker = toolbox.walker;
+   * });
+   *
+   * And then call this method:
+   *
+   * // Get the nodeFront from my actor, passing the ID and properties path.
+   * walker.getNodeFromActor(myActorID, ["element"]).then(nodeFront => {
+   *   // Use the nodeFront, e.g. select the node in the inspector.
+   *   toolbox.getPanel("inspector").selection.setNodeFront(nodeFront);
+   * });
+   *
+   * @param {String} actorID The ID for the actor that has a reference to the
+   * DOM node.
+   * @param {Array} path Where, on the actor, is the DOM node stored. If in the
+   * scope of the actor, the node is available as `this.data.node`, then this
+   * should be ["data", "node"].
+   * @return {NodeActor} The attached NodeActor, or null if it couldn't be found.
+   */
+  getNodeFromActor: method(function(actorID, path) {
+    let actor = this.conn.getActor(actorID);
+    if (!actor) {
+      return null;
+    }
+
+    let obj = actor;
+    for (let name of path) {
+      if (!(name in obj)) {
+        return null;
+      }
+      obj = obj[name];
+    }
+
+    return this.attachElement(obj);
+  }, {
+    request: {
+      actorID: Arg(0, "string"),
+      path: Arg(1, "array:string")
+    },
+    response: {
+      node: RetVal("nullable:disconnectedNode")
+    }
+  })
 });
 
 /**
  * Client side of the DOM walker.
  */
 var WalkerFront = exports.WalkerFront = protocol.FrontClass(WalkerActor, {
   // Set to true if cleanup should be requested after every mutation list.
   autoCleanup: true,
@@ -3058,16 +3106,24 @@ var WalkerFront = exports.WalkerFront = 
   getStyleSheetOwnerNode: protocol.custom(function(styleSheetActorID) {
     return this._getStyleSheetOwnerNode(styleSheetActorID).then(response => {
       return response ? response.node : null;
     });
   }, {
     impl: "_getStyleSheetOwnerNode"
   }),
 
+  getNodeFromActor: protocol.custom(function(actorID, path) {
+    return this._getNodeFromActor(actorID, path).then(response => {
+      return response ? response.node : null;
+    });
+  }, {
+    impl: "_getNodeFromActor"
+  }),
+
   _releaseFront: function(node, force) {
     if (node.retained && !force) {
       node.reparent(null);
       this._retainedOrphans.add(node);
       return;
     }
 
     if (node.retained) {
--- a/toolkit/devtools/server/actors/profiler.js
+++ b/toolkit/devtools/server/actors/profiler.js
@@ -54,16 +54,26 @@ ProfilerActor.prototype = {
    * Returns an array of feature strings, describing the profiler features
    * that are available on this platform. Can be called while the profiler
    * is stopped.
    */
   onGetFeatures: function() {
     return { features: nsIProfilerModule.GetFeatures([]) };
   },
 
+  onGetBufferInfo: function(request) {
+    let position = {}, totalSize = {}, generation = {};
+    nsIProfilerModule.GetBufferInfo(position, totalSize, generation);
+    return {
+      position: position.value,
+      totalSize: totalSize.value,
+      generation: generation.value
+    }
+  },
+
   /**
    * Returns the configuration used that was originally passed in to start up the
    * profiler. Used for tests, and does not account for others using nsIProfiler.
    */
   onGetStartOptions: function() {
     return this._profilerStartOptions || {};
   },
 
@@ -316,16 +326,17 @@ function checkProfilerConsumers() {
 
 /**
  * The request types this actor can handle.
  * At the moment there are two known users of the Profiler actor:
  * the devtools and the Gecko Profiler addon, which uses the debugger
  * protocol to get profiles from Fennec.
  */
 ProfilerActor.prototype.requestTypes = {
+  "getBufferInfo": ProfilerActor.prototype.onGetBufferInfo,
   "getFeatures": ProfilerActor.prototype.onGetFeatures,
   "startProfiler": ProfilerActor.prototype.onStartProfiler,
   "stopProfiler": ProfilerActor.prototype.onStopProfiler,
   "isActive": ProfilerActor.prototype.onIsActive,
   "getSharedLibraryInformation": ProfilerActor.prototype.onGetSharedLibraryInformation,
   "getProfile": ProfilerActor.prototype.onGetProfile,
   "registerEventNotifications": ProfilerActor.prototype.onRegisterEventNotifications,
   "unregisterEventNotifications": ProfilerActor.prototype.onUnregisterEventNotifications,
--- a/toolkit/devtools/server/actors/root.js
+++ b/toolkit/devtools/server/actors/root.js
@@ -167,16 +167,19 @@ RootActor.prototype = {
     // no longer expose tab actors, but also that getProcess forbids
     // exposing actors for security reasons
     get allowChromeProcess() {
       return DebuggerServer.allowChromeProcess;
     },
     // Whether or not `getProfile()` supports specifying a `startTime`
     // and `endTime` to filter out samples. Fx40+
     profilerDataFilterable: true,
+    // Whether or not the profiler has a `getBufferInfo` method
+    // necessary as the profiler does not use the ActorFront class.
+    profilerBufferStatus: true,
   },
 
   /**
    * Return a 'hello' packet as specified by the Remote Debugging Protocol.
    */
   sayHello: function() {
     return {
       from: this.actorID,
--- a/toolkit/devtools/server/tests/mochitest/chrome.ini
+++ b/toolkit/devtools/server/tests/mochitest/chrome.ini
@@ -48,16 +48,17 @@ skip-if = buildapp == 'mulet'
 [test_getProcess.html]
 skip-if = buildapp == 'mulet'
 [test_inspector-anonymous.html]
 [test_inspector-changeattrs.html]
 [test_inspector-changevalue.html]
 [test_inspector-dead-nodes.html]
 [test_inspector_getImageData.html]
 skip-if = buildapp == 'mulet'
+[test_inspector_getNodeFromActor.html]
 [test_inspector-hide.html]
 [test_inspector-insert.html]
 [test_inspector-mutations-attr.html]
 [test_inspector-mutations-childlist.html]
 [test_inspector-mutations-frameload.html]
 [test_inspector-mutations-value.html]
 [test_inspector-pseudoclass-lock.html]
 [test_inspector-release.html]
new file mode 100644
--- /dev/null
+++ b/toolkit/devtools/server/tests/mochitest/test_inspector_getNodeFromActor.html
@@ -0,0 +1,89 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1155653
+-->
+<head>
+  <meta charset="utf-8">
+  <title>Test for Bug 1155653</title>
+
+  <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
+  <script type="application/javascript;version=1.8" src="inspector-helpers.js"></script>
+  <script type="application/javascript;version=1.8">
+Components.utils.import("resource://gre/modules/devtools/Loader.jsm");
+const inspector = devtools.require("devtools/server/actors/inspector");
+
+window.onload = function() {
+  SimpleTest.waitForExplicitFinish();
+  runNextTest();
+}
+
+var gWalker;
+
+addTest(function() {
+  let url = document.getElementById("inspectorContent").href;
+  attachURL(url, function(err, client, tab, doc) {
+    let {InspectorFront} = devtools.require("devtools/server/actors/inspector");
+    let inspector = InspectorFront(client, tab);
+
+    promiseDone(inspector.getWalker().then(walker => {
+      gWalker = walker;
+    }).then(runNextTest));
+  });
+});
+
+addTest(function() {
+  info("Try to get a NodeFront from an invalid actorID");
+  gWalker.getNodeFromActor("invalid", ["node"]).then(node => {
+    ok(!node, "The node returned is null");
+    runNextTest();
+  });
+});
+
+addTest(function() {
+  info("Try to get a NodeFront from a valid actorID but invalid path");
+  gWalker.getNodeFromActor(gWalker.actorID, ["invalid", "path"]).then(node => {
+    ok(!node, "The node returned is null");
+    runNextTest();
+  });
+});
+
+addTest(function() {
+  info("Try to get a NodeFront from a valid actorID and valid path");
+  gWalker.getNodeFromActor(gWalker.actorID, ["rootDoc"]).then(rootDocNode => {
+    ok(rootDocNode, "A node was returned");
+    is(rootDocNode, gWalker.rootNode, "The right node was returned");
+    runNextTest();
+  });
+});
+
+addTest(function() {
+  info("Try to get a NodeFront from a valid actorID and valid complex path");
+  gWalker.getNodeFromActor(gWalker.actorID,
+    ["tabActor", "window", "document", "body"]).then(bodyNode => {
+    ok(bodyNode, "A node was returned");
+    gWalker.querySelector(gWalker.rootNode, "body").then(node => {
+      is(bodyNode, node, "The body node was returned");
+      runNextTest();
+    });
+  });
+});
+
+addTest(function() {
+  gWalker = null;
+  runNextTest();
+});
+  </script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1155653">Mozilla Bug 1155653</a>
+<a id="inspectorContent" target="_blank" href="inspector_getImageData.html">Test Document</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/toolkit/devtools/server/tests/unit/test_profiler_getbufferinfo.js
@@ -0,0 +1,107 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/**
+ * Tests if the profiler actor returns its buffer status via getBufferInfo.
+ */
+
+const Profiler = Cc["@mozilla.org/tools/profiler;1"].getService(Ci.nsIProfiler);
+const INITIAL_WAIT_TIME = 100; // ms
+const MAX_WAIT_TIME = 20000; // ms
+const MAX_PROFILER_ENTRIES = 10000000;
+
+function run_test()
+{
+  get_chrome_actors((client, form) => {
+    let actor = form.profilerActor;
+    activate_profiler(client, actor, startTime => {
+      wait_for_samples(client, actor, () => {
+        check_buffer(client, actor, () => {
+          deactivate_profiler(client, actor, () => {
+            client.close(do_test_finished);
+          });
+        });
+      });
+    });
+  })
+
+  do_test_pending();
+}
+
+function check_buffer(client, actor, callback)
+{
+  client.request({ to: actor, type: "getBufferInfo" }, response => {
+    do_check_true(typeof response.position === "number");
+    do_check_true(typeof response.totalSize === "number");
+    do_check_true(typeof response.generation === "number");
+    do_check_true(response.position > 0 && response.position < response.totalSize);
+    do_check_true(response.totalSize === MAX_PROFILER_ENTRIES);
+    // There's no way we'll fill the buffer in this test.
+    do_check_true(response.generation === 0);
+
+    callback();
+  });
+}
+
+function activate_profiler(client, actor, callback)
+{
+  client.request({ to: actor, type: "startProfiler", entries: MAX_PROFILER_ENTRIES }, response => {
+    do_check_true(response.started);
+    client.request({ to: actor, type: "isActive" }, response => {
+      do_check_true(response.isActive);
+      callback(response.currentTime);
+    });
+  });
+}
+
+function deactivate_profiler(client, actor, callback)
+{
+  client.request({ to: actor, type: "stopProfiler" }, response => {
+    do_check_false(response.started);
+    client.request({ to: actor, type: "isActive" }, response => {
+      do_check_false(response.isActive);
+      callback();
+    });
+  });
+}
+
+function wait_for_samples(client, actor, callback)
+{
+  function attempt(delay)
+  {
+    // No idea why, but Components.stack.sourceLine returns null.
+    let funcLine = Components.stack.lineNumber - 3;
+
+    // Spin for the requested time, then take a sample.
+    let start = Date.now();
+    let stack;
+    do_print("Attempt: delay = " + delay);
+    while (Date.now() - start < delay) { stack = Components.stack; }
+    do_print("Attempt: finished waiting.");
+
+    client.request({ to: actor, type: "getProfile" }, response => {
+      // At this point, we may or may not have samples, depending on
+      // whether the spin loop above has given the profiler enough time
+      // to get started.
+      if (response.profile.threads[0].samples.length == 0) {
+        if (delay < MAX_WAIT_TIME) {
+          // Double the spin-wait time and try again.
+          do_print("Attempt: no samples, going around again.");
+          return attempt(delay * 2);
+        } else {
+          // We've waited long enough, so just fail.
+          do_print("Attempt: waited a long time, but no samples were collected.");
+          do_print("Giving up.");
+          do_check_true(false);
+          return;
+        }
+      }
+      callback();
+    });
+  }
+
+  // Start off with a 100 millisecond delay.
+  attempt(INITIAL_WAIT_TIME);
+}
--- a/toolkit/devtools/server/tests/unit/xpcshell.ini
+++ b/toolkit/devtools/server/tests/unit/xpcshell.ini
@@ -211,16 +211,17 @@ skip-if = toolkit == "gonk"
 reason = bug 820380
 [test_breakpoint-actor-map.js]
 [test_profiler_activation-01.js]
 [test_profiler_activation-02.js]
 [test_profiler_close.js]
 [test_profiler_data.js]
 [test_profiler_events-01.js]
 [test_profiler_events-02.js]
+[test_profiler_getbufferinfo.js]
 [test_profiler_getfeatures.js]
 [test_profiler_getsharedlibraryinformation.js]
 [test_unsafeDereference.js]
 [test_add_actors.js]
 [test_trace_actor-01.js]
 [test_trace_actor-02.js]
 [test_trace_actor-03.js]
 [test_trace_actor-04.js]
--- a/toolkit/themes/shared/extensions/extensions.inc.css
+++ b/toolkit/themes/shared/extensions/extensions.inc.css
@@ -960,43 +960,39 @@ setting[type="radio"] > radiogroup {
   display: block !important;
 }
 
 button.button-link {
   -moz-appearance: none;
   background: transparent;
   border: none;
   box-shadow: none;
-  text-decoration: underline;
   color: #0095dd;
   cursor: pointer;
   min-width: 0;
   height: 20px;
   margin: 0 6px;
 }
 
 button.button-link:not(:-moz-focusring) > .button-box {
   border-width: 0;
   margin: 1px;
 }
 
-.text-link {
-  color: #0095dd;
-  font-size: inherit;
-}
-
-button.button-link:hover,
-.text-link:hover {
-  color: #4cb1ff;
+button.button-link:hover {
   background-color: transparent;
+  color: #178ce5;
+  text-decoration: underline;
 }
 
 /* Needed to override normal button style from inContent.css */
 button.button-link:not([disabled="true"]):active:hover {
   background-color: transparent;
+  color: #ff9500;
+  text-decoration: none;
 }
 
 
 /*** telemetry experiments ***/
 
 #detail-experiment-container {
   font-size: 80%;
   margin-bottom: 1em;
--- a/toolkit/xre/nsXREDirProvider.cpp
+++ b/toolkit/xre/nsXREDirProvider.cpp
@@ -630,17 +630,17 @@ nsXREDirProvider::LoadExtensionBundleDir
 
     nsINIParser parser;
     nsresult rv = parser.Init(extensionsINILF);
     if (NS_FAILED(rv))
       return;
 
     RegisterExtensionInterpositions(parser);
     LoadExtensionDirectories(parser, "ExtensionDirs", mExtensionDirectories,
-                             NS_COMPONENT_LOCATION);
+                             NS_EXTENSION_LOCATION);
     LoadExtensionDirectories(parser, "ThemeDirs", mThemeDirectories,
                              NS_SKIN_LOCATION);
   }
 }
 
 void
 nsXREDirProvider::LoadAppBundleDirs()
 {
@@ -662,17 +662,17 @@ nsXREDirProvider::LoadAppBundleDirs()
     return;
 
   nsCOMPtr<nsIFile> subdir;
   while (NS_SUCCEEDED(files->GetNextFile(getter_AddRefs(subdir))) && subdir) {
     mAppBundleDirectories.AppendObject(subdir);
 
     nsCOMPtr<nsIFile> manifest =
       CloneAndAppend(subdir, "chrome.manifest");
-    XRE_AddManifestLocation(NS_COMPONENT_LOCATION, manifest);
+    XRE_AddManifestLocation(NS_EXTENSION_LOCATION, manifest);
   }
 }
 
 static const char *const kAppendPrefDir[] = { "defaults", "preferences", nullptr };
 
 #ifdef DEBUG_bsmedberg
 static void
 DumpFileArray(const char *key,
--- a/view/nsView.cpp
+++ b/view/nsView.cpp
@@ -668,16 +668,26 @@ nsView::InitializeWindow(bool aEnableDra
 
   //make sure visibility state is accurate
 
   if (aResetVisibility) {
     SetVisibility(GetVisibility());
   }
 }
 
+void
+nsView::SetNeedsWindowPropertiesSync()
+{
+  mNeedsWindowPropertiesSync = true;
+  if (mViewManager) {
+    mViewManager->PostPendingUpdate();
+  }
+}
+
+
 // Attach to a top level widget and start receiving mirrored events.
 nsresult nsView::AttachToTopLevelWidget(nsIWidget* aWidget)
 {
   NS_PRECONDITION(nullptr != aWidget, "null widget ptr");
   /// XXXjimm This is a temporary workaround to an issue w/document
   // viewer (bug 513162).
   nsIWidgetListener* listener = aWidget->GetAttachedWidgetListener();
   if (listener) {
--- a/view/nsView.h
+++ b/view/nsView.h
@@ -285,16 +285,18 @@ public:
    * Returns true if the view has a widget associated with it.
    */
   bool HasWidget() const { return mWindow != nullptr; }
   
   void SetForcedRepaint(bool aForceRepaint) { 
     mForcedRepaint = aForceRepaint; 
   }
 
+  void SetNeedsWindowPropertiesSync();
+
   /**
    * Make aWidget direct its events to this view.
    * The caller must call DetachWidgetEventHandler before this view
    * is destroyed.
    */
   void AttachWidgetEventHandler(nsIWidget* aWidget);
   /**
    * Stop aWidget directing its events to this view.
@@ -458,11 +460,12 @@ private:
   nscoord           mPosX, mPosY;
   // relative to parent, but in our appunits
   nsRect            mDimBounds;
   // in our appunits
   nsPoint           mViewToWidgetOffset;
   uint32_t          mVFlags;
   bool              mWidgetIsTopLevel;
   bool              mForcedRepaint;
+  bool              mNeedsWindowPropertiesSync;
 };
 
 #endif
--- a/view/nsViewManager.cpp
+++ b/view/nsViewManager.cpp
@@ -363,16 +363,27 @@ nsViewManager::ProcessPendingUpdatesForV
   }
 
   nsCOMPtr<nsIPresShell> rootShell(mPresShell);
   nsTArray<nsCOMPtr<nsIWidget> > widgets;
   aView->GetViewManager()->ProcessPendingUpdatesRecurse(aView, widgets);
   for (uint32_t i = 0; i < widgets.Length(); ++i) {
     nsView* view = nsView::GetViewFor(widgets[i]);
     if (view) {
+      if (view->mNeedsWindowPropertiesSync) {
+        view->mNeedsWindowPropertiesSync = false;
+        if (nsViewManager* vm = view->GetViewManager()) {
+          if (nsIPresShell* ps = vm->GetPresShell()) {
+            ps->SyncWindowProperties(view);
+          }
+        }
+      }
+    }
+    view = nsView::GetViewFor(widgets[i]);
+    if (view) {
       view->ResetWidgetBounds(false, true);
     }
   }
   if (rootShell->GetViewManager() != this) {
     return; // 'this' might have been destroyed
   }
   if (aFlushDirtyRegion) {
     profiler_tracing("Paint", "DisplayList", TRACING_INTERVAL_START);
--- a/widget/gtk/mozgtk/mozgtk.c
+++ b/widget/gtk/mozgtk/mozgtk.c
@@ -37,16 +37,17 @@ STUB(gdk_event_put)
 STUB(gdk_flush)
 STUB(gdk_get_default_root_window)
 STUB(gdk_get_display)
 STUB(gdk_get_display_arg_name)
 STUB(gdk_get_program_class)
 STUB(gdk_keymap_get_default)
 STUB(gdk_keymap_get_direction)
 STUB(gdk_keymap_get_entries_for_keyval)
+STUB(gdk_keymap_get_for_display)
 STUB(gdk_keymap_have_bidi_layouts)
 STUB(gdk_keymap_translate_keyboard_state)
 STUB(gdk_keyval_name)
 STUB(gdk_keyval_to_unicode)
 STUB(gdk_pango_context_get)
 STUB(gdk_pointer_grab)
 STUB(gdk_pointer_ungrab)
 STUB(gdk_property_get)
--- a/widget/gtk/nsBidiKeyboard.cpp
+++ b/widget/gtk/nsBidiKeyboard.cpp
@@ -17,17 +17,23 @@ nsBidiKeyboard::nsBidiKeyboard()
     Reset();
 }
 
 NS_IMETHODIMP
 nsBidiKeyboard::Reset()
 {
     // NB: The default keymap can be null (e.g. in xpcshell). In that case,
     // simply assume that we don't have bidi keyboards.
-    GdkKeymap *keymap = gdk_keymap_get_default();
+    mHaveBidiKeyboards = false;
+
+    GdkDisplay *display = gdk_display_get_default();
+    if (!display)
+        return NS_OK;
+
+    GdkKeymap *keymap = gdk_keymap_get_for_display(display);
     mHaveBidiKeyboards = keymap && gdk_keymap_have_bidi_layouts(keymap);
     return NS_OK;
 }
 
 nsBidiKeyboard::~nsBidiKeyboard()
 {
 }
 
--- a/xpcom/build/nsXULAppAPI.h
+++ b/xpcom/build/nsXULAppAPI.h
@@ -235,25 +235,29 @@ XRE_API(nsresult,
  * Register XPCOM components found in an array of files/directories.
  * This method may be called at any time before or after XRE_main or
  * XRE_InitEmbedding.
  *
  * @param aFiles An array of files or directories.
  * @param aFileCount the number of items in the aFiles array.
  * @note appdir/components is registered automatically.
  *
- * NS_COMPONENT_LOCATION specifies a location to search for binary XPCOM
+ * NS_APP_LOCATION specifies a location to search for binary XPCOM
  * components as well as component/chrome manifest files.
  *
+ * NS_EXTENSION_LOCATION excludes binary XPCOM components but allows other
+ * manifest instructions.
+ *
  * NS_SKIN_LOCATION specifies a location to search for chrome manifest files
  * which are only allowed to register only skin packages and style overlays.
  */
 enum NSLocationType
 {
-  NS_COMPONENT_LOCATION,
+  NS_APP_LOCATION,
+  NS_EXTENSION_LOCATION,
   NS_SKIN_LOCATION,
   NS_BOOTSTRAPPED_LOCATION
 };
 
 XRE_API(nsresult,
         XRE_AddManifestLocation, (NSLocationType aType,
                                   nsIFile* aLocation))
 
--- a/xpcom/components/ManifestParser.cpp
+++ b/xpcom/components/ManifestParser.cpp
@@ -52,18 +52,20 @@
 
 using namespace mozilla;
 
 struct ManifestDirective
 {
   const char* directive;
   int argc;
 
-  // Some directives should only be delivered for NS_COMPONENT_LOCATION
-  // manifests.
+  // Binary components are only allowed for APP locations.
+  bool apponly;
+
+  // Some directives should only be delivered for APP or EXTENSION locations.
   bool componentonly;
 
   bool ischrome;
 
   bool allowbootstrap;
 
   // The platform/contentaccessible flags only apply to content directives.
   bool contentflags;
@@ -84,65 +86,65 @@ struct ManifestDirective
 #else
   void* xptonlyfunc;
 #endif
 
   bool isContract;
 };
 static const ManifestDirective kParsingTable[] = {
   {
-    "manifest",         1, false, true, true, false,
+    "manifest",         1, false, false, true, true, false,
     &nsComponentManagerImpl::ManifestManifest, nullptr, XPTONLY_MANIFEST
   },
   {
-    "binary-component", 1, true, false, false, false,
+    "binary-component", 1, true, true, false, false, false,
     &nsComponentManagerImpl::ManifestBinaryComponent, nullptr, nullptr
   },
   {
-    "interfaces",       1, true, false, false, false,
+    "interfaces",       1, false, true, false, false, false,
     &nsComponentManagerImpl::ManifestXPT, nullptr, XPTONLY_XPT
   },
   {
-    "component",        2, true, false, false, false,
+    "component",        2, false, true, false, false, false,
     &nsComponentManagerImpl::ManifestComponent, nullptr, nullptr
   },
   {
-    "contract",         2, true, false, false, false,
+    "contract",         2, false, true, false, false, false,
     &nsComponentManagerImpl::ManifestContract, nullptr, nullptr, true
   },
   {
-    "category",         3, true, false, false, false,
+    "category",         3, false, true, false, false, false,
     &nsComponentManagerImpl::ManifestCategory, nullptr, nullptr
   },
   {
-    "content"