Merge m-c to b2g-inbound. a=merge
authorRyan VanderMeulen <ryanvm@gmail.com>
Mon, 27 Jul 2015 15:45:38 -0400
changeset 286464 13354b41439620ff3628db12d1451f77fd5db3ad
parent 286463 71ad4ff41fe7d8f5f97cc2fafc2ae5876914de76 (current diff)
parent 286453 7d5d2d96f19b1dbbd6a46fd5a5e9790798581a99 (diff)
child 286465 4768c0ddb601133f9576ebade7dca1d6fc3e264c
push id5067
push userraliiev@mozilla.com
push dateMon, 21 Sep 2015 14:04:52 +0000
treeherdermozilla-beta@14221ffe5b2f [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone42.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 m-c to b2g-inbound. a=merge
--- a/b2g/app/b2g.js
+++ b/b2g/app/b2g.js
@@ -1095,21 +1095,24 @@ pref("gfx.canvas.willReadFrequently.enab
 pref("browser.autofocus", false);
 
 // Enable wakelock
 pref("dom.wakelock.enabled", true);
 
 // Enable webapps add-ons
 pref("dom.apps.customization.enabled", true);
 
-// Enable touch caret by default
-pref("touchcaret.enabled", true);
+// Original caret implementation on collapsed selection.
+pref("touchcaret.enabled", false);
 
-// Enable selection caret by default
-pref("selectioncaret.enabled", true);
+// Original caret implementation on non-collapsed selection.
+pref("selectioncaret.enabled", false);
+
+// New implementation to unify touch-caret and selection-carets.
+pref("layout.accessiblecaret.enabled", true);
 
 // Enable sync and mozId with Firefox Accounts.
 pref("services.sync.fxaccounts.enabled", true);
 pref("identity.fxaccounts.enabled", true);
 
 // Mobile Identity API.
 pref("services.mobileid.server.uri", "https://msisdn.services.mozilla.com");
 
--- a/browser/base/content/contentSearchUI.js
+++ b/browser/base/content/contentSearchUI.js
@@ -453,16 +453,18 @@ ContentSearchUIController.prototype = {
     }
     this._pendingOneOffRefresh = true;
   },
 
   _onMsgStrings: function (strings) {
     this._strings = strings;
     this._updateDefaultEngineHeader();
     this._updateSearchWithHeader();
+    document.getElementById("contentSearchSettingsButton").textContent =
+      this._strings.searchSettings;
   },
 
   _updateDefaultEngineHeader: function () {
     let header = document.getElementById("contentSearchDefaultEngineHeader");
     if (this.defaultEngine.icon) {
       header.firstChild.setAttribute("src", this.defaultEngine.icon);
     }
     if (!this._strings) {
@@ -479,22 +481,20 @@ ContentSearchUIController.prototype = {
     if (!this._strings) {
       return;
     }
     let searchWithHeader = document.getElementById("contentSearchSearchWithHeader");
     while (searchWithHeader.firstChild) {
       searchWithHeader.firstChild.remove();
     }
     if (this.input.value) {
-      searchWithHeader.appendChild(document.createTextNode(this._strings.searchFor));
-      let span = document.createElementNS(HTML_NS, "span");
-      span.setAttribute("class", "contentSearchSearchWithHeaderSearchText");
-      span.appendChild(document.createTextNode(" " + this.input.value + " "));
-      searchWithHeader.appendChild(span);
-      searchWithHeader.appendChild(document.createTextNode(this._strings.searchWith));
+      let html = "<span class='contentSearchSearchWithHeaderSearchText'>" +
+                 this.input.value + "</span>";
+      html = this._strings.searchForKeywordsWith.replace("%S", html);
+      searchWithHeader.innerHTML = html;
       return;
     }
     searchWithHeader.appendChild(document.createTextNode(this._strings.searchWithHeader));
   },
 
   _speculativeConnect: function () {
     if (this.defaultEngine) {
       this._sendMsg("SpeculativeConnect", this.defaultEngine.name);
@@ -649,17 +649,16 @@ ContentSearchUIController.prototype = {
     header = document.createElementNS(HTML_NS, "td");
     headerRow.setAttribute("class", "contentSearchHeaderRow");
     header.setAttribute("class", "contentSearchHeader");
     headerRow.appendChild(header);
     header.id = "contentSearchSearchWithHeader";
     this._oneOffsTable.appendChild(headerRow);
 
     let button = document.createElementNS(HTML_NS, "button");
-    button.appendChild(document.createTextNode("Change Search Settings"));
     button.setAttribute("class", "contentSearchSettingsButton");
     button.classList.add("contentSearchHeaderRow");
     button.classList.add("contentSearchHeader");
     button.id = "contentSearchSettingsButton";
     button.addEventListener("click", this);
     button.addEventListener("mousemove", this);
     this._table.appendChild(button);
 
--- a/browser/base/content/pageinfo/security.js
+++ b/browser/base/content/pageinfo/security.js
@@ -1,15 +1,18 @@
 /* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 Components.utils.import("resource://gre/modules/BrowserUtils.jsm");
 
+XPCOMUtils.defineLazyModuleGetter(this, "LoginHelper",
+                                  "resource://gre/modules/LoginHelper.jsm");
+
 var security = {
   // Display the server certificate (static)
   viewCert : function () {
     var cert = security._cert;
     viewCertHelper(window, cert);
   },
 
   _getSecurityInfo : function() {
@@ -153,29 +156,18 @@ var security = {
     else
       window.openDialog("chrome://browser/content/preferences/cookies.xul",
                         "Browser:Cookies", "", {filterString : eTLD});
   },
 
   /**
    * Open the login manager window
    */
-  viewPasswords : function()
-  {
-    var wm = Components.classes["@mozilla.org/appshell/window-mediator;1"]
-                       .getService(Components.interfaces.nsIWindowMediator);
-    var win = wm.getMostRecentWindow("Toolkit:PasswordManager");
-    if (win) {
-      win.setFilter(this._getSecurityInfo().hostName);
-      win.focus();
-    }
-    else
-      window.openDialog("chrome://passwordmgr/content/passwordManager.xul",
-                        "Toolkit:PasswordManager", "",
-                        {filterString : this._getSecurityInfo().hostName});
+  viewPasswords : function() {
+    LoginHelper.openPasswordManager(window, this._getSecurityInfo().hostName);
   },
 
   _cert : null
 };
 
 function securityOnLoad() {
   var info = security._getSecurityInfo();
   if (!info) {
--- a/browser/base/content/test/general/browser_selectpopup.js
+++ b/browser/base/content/test/general/browser_selectpopup.js
@@ -1,52 +1,64 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 // This test checks that a <select> with an <optgroup> opens and can be navigated
 // in a child process. This is different than single-process as a <menulist> is used
 // to implement the dropdown list.
 
 const PAGECONTENT =
-  "<html><body onload='document.body.firstChild.focus()'><select>" +
+  "<html><body onload='gChangeEvents = 0; document.body.firstChild.focus()'><select onchange='gChangeEvents++'>" +
   "  <optgroup label='First Group'>" +
   "    <option value=One>One" +
   "    <option value=Two>Two" +
   "  </optgroup>" +
   "  <option value=Three>Three" +
   "  <optgroup label='Second Group' disabled='true'>" +
   "    <option value=Four>Four" +
   "    <option value=Five>Five" +
   "  </optgroup>" +
   "  <option value=Six disabled='true'>Six" +
   "  <optgroup label='Third Group'>" +
   "    <option value=Seven>Seven" +
   "    <option value=Eight>Eight" +
-  "  </optgroup>" +
+  "  </optgroup></select><input>" +
   "</body></html>";
 
-function openSelectPopup(selectPopup)
+function openSelectPopup(selectPopup, withMouse)
 {
-  return new Promise((resolve, reject) => {
-    selectPopup.addEventListener("popupshown", function popupListener(event) {
-      selectPopup.removeEventListener("popupshown", popupListener, false)
-      resolve();
-    }, false);
-    setTimeout(() => EventUtils.synthesizeKey("KEY_ArrowDown", { altKey: true, code: "ArrowDown" }), 1500);
-  });
+  let popupShownPromise = BrowserTestUtils.waitForEvent(selectPopup, "popupshown");
+
+  if (withMouse) {
+    return Promise.all([popupShownPromise,
+                        BrowserTestUtils.synthesizeMouseAtCenter("select", { }, gBrowser.selectedBrowser)]);
+  }
+
+  setTimeout(() => EventUtils.synthesizeKey("KEY_ArrowDown", { altKey: true, code: "ArrowDown" }), 1500);
+  return popupShownPromise;
 }
 
-function hideSelectPopup(selectPopup)
+function hideSelectPopup(selectPopup, withEscape)
 {
-  return new Promise((resolve, reject) => {
-    selectPopup.addEventListener("popuphidden", function popupListener(event) {
-      selectPopup.removeEventListener("popuphidden", popupListener, false)
-      resolve();
-    }, false);
+  let popupShownPromise = BrowserTestUtils.waitForEvent(selectPopup, "popuphidden");
+
+  if (withEscape) {
+    EventUtils.synthesizeKey("KEY_Escape", { code: "Escape" });
+  }
+  else {
     EventUtils.synthesizeKey("KEY_Enter", { code: "Enter" });
+  }
+
+  return popupShownPromise;
+}
+
+function getChangeEvents()
+{
+  return ContentTask.spawn(gBrowser.selectedBrowser, {}, function() {
+    return content.wrappedJSObject.gChangeEvents;
   });
 }
 
 add_task(function*() {
   let tab = gBrowser.selectedTab = gBrowser.addTab();
   let browser = gBrowser.getBrowserForTab(tab);
   yield promiseTabLoadEvent(tab, "data:text/html," + escape(PAGECONTENT));
 
@@ -85,15 +97,34 @@ add_task(function*() {
   for (let i = 0; i < 10; i++) {
     is(menulist.getItemAtIndex(i).disabled, i >= 4 && i <= 7, "item " + i + " disabled")
   }
 
   EventUtils.synthesizeKey("KEY_ArrowUp", { code: "ArrowUp" });
   is(menulist.menuBoxObject.activeChild, menulist.getItemAtIndex(3), "Select item 3 again");
   is(menulist.selectedIndex, isWindows ? 3 : 1, "Select item 3 selectedIndex");
 
+  is((yield getChangeEvents()), 0, "Before closed - number of change events");
+
   yield hideSelectPopup(selectPopup);
 
   is(menulist.selectedIndex, 3, "Item 3 still selected");
+  is((yield getChangeEvents()), 1, "After closed - number of change events");
+
+  // Opening and closing the popup without changing the value should not fire a change event.
+  yield openSelectPopup(selectPopup, true);
+  yield hideSelectPopup(selectPopup, true);
+  is((yield getChangeEvents()), 1, "Open and close with no change - number of change events");
+  EventUtils.synthesizeKey("VK_TAB", { });
+  EventUtils.synthesizeKey("VK_TAB", { shiftKey: true });
+  is((yield getChangeEvents()), 1, "Tab away from select with no change - number of change events");
+
+  yield openSelectPopup(selectPopup, true);
+  EventUtils.synthesizeKey("KEY_ArrowDown", { code: "ArrowDown" });
+  yield hideSelectPopup(selectPopup, true);
+  is((yield getChangeEvents()), isWindows ? 2 : 1, "Open and close with change - number of change events");
+  EventUtils.synthesizeKey("VK_TAB", { });
+  EventUtils.synthesizeKey("VK_TAB", { shiftKey: true });
+  is((yield getChangeEvents()), isWindows ? 2 : 1, "Tab away from select with change - number of change events");
 
   gBrowser.removeCurrentTab();
 });
 
--- a/browser/components/preferences/in-content/main.js
+++ b/browser/components/preferences/in-content/main.js
@@ -363,28 +363,28 @@ var gMainPane = {
     win = wm.getMostRecentWindow("navigator:browser");
 
     if (win && win.document.documentElement
                   .getAttribute("windowtype") == "navigator:browser") {
       // We should only include visible & non-pinned tabs
 
       tabs = win.gBrowser.visibleTabs.slice(win.gBrowser._numPinnedTabs);
       
-      tabs = tabs.filter(this.isAboutPreferences);
+      tabs = tabs.filter(this.isNotAboutPreferences);
     }
     
     return tabs;
   },
   
   /**
    * Check to see if a tab is not about:preferences
    */
-  isAboutPreferences: function (aElement, aIndex, aArray)
+  isNotAboutPreferences: function (aElement, aIndex, aArray)
   {
-    return (aElement.linkedBrowser.currentURI.spec != "about:preferences");
+    return (aElement.linkedBrowser.currentURI.spec.startsWith != "about:preferences");
   },
 
   /**
    * Restores the default home page as the user's home page.
    */
   restoreDefaultHomePage: function ()
   {
     var homePage = document.getElementById("browser.startup.homepage");
--- a/browser/components/search/test/browser_abouthome_behavior.js
+++ b/browser/components/search/test/browser_abouthome_behavior.js
@@ -85,17 +85,17 @@ function test() {
       name: "Search with Google from about:home",
       searchURL: replaceUrl("https://www.google.com/search?q=foo&ie=utf-8&oe=utf-8"),
       run: function () {
         verify_about_home_search("Google");
       }
     },
     {
       name: "Search with Amazon.com from about:home",
-      searchURL: replaceUrl("http://www.amazon.com/exec/obidos/external-search/?field-keywords=foo&mode=blended&tag=mozilla-20&sourceid=Mozilla-search"),
+      searchURL: replaceUrl("https://www.amazon.com/exec/obidos/external-search/?field-keywords=foo&mode=blended&tag=mozilla-20&sourceid=Mozilla-search"),
       run: function () {
         verify_about_home_search("Amazon.com");
       }
     }
   ];
 
   function nextTest() {
     if (gTests.length) {
--- a/browser/components/search/test/browser_amazon.js
+++ b/browser/components/search/test/browser_amazon.js
@@ -8,49 +8,49 @@
 "use strict";
 
 const BROWSER_SEARCH_PREF = "browser.search.";
 
 function test() {
   let engine = Services.search.getEngineByName("Amazon.com");
   ok(engine, "Amazon.com");
 
-  let base = "http://www.amazon.com/exec/obidos/external-search/?field-keywords=foo&mode=blended&tag=mozilla-20&sourceid=Mozilla-search";
+  let base = "https://www.amazon.com/exec/obidos/external-search/?field-keywords=foo&mode=blended&tag=mozilla-20&sourceid=Mozilla-search";
   let url;
 
   // Test search URLs (including purposes).
   url = engine.getSubmission("foo").uri.spec;
   is(url, base, "Check search URL for 'foo'");
 
   // Check search suggestion URL.
   url = engine.getSubmission("foo", "application/x-suggestions+json").uri.spec;
   is(url, "https://completion.amazon.com/search/complete?q=foo&search-alias=aps&mkt=1", "Check search suggestion URL for 'foo'");
 
   // Check all other engine properties.
   const EXPECTED_ENGINE = {
     name: "Amazon.com",
     alias: null,
     description: "Amazon.com Search",
-    searchForm: "http://www.amazon.com/exec/obidos/external-search/?field-keywords=&mode=blended&tag=mozilla-20&sourceid=Mozilla-search",
+    searchForm: "https://www.amazon.com/exec/obidos/external-search/?field-keywords=&mode=blended&tag=mozilla-20&sourceid=Mozilla-search",
     type: Ci.nsISearchEngine.TYPE_MOZSEARCH,
     hidden: false,
     wrappedJSObject: {
       queryCharset: "UTF-8",
       "_iconURL": "data:image/x-icon;base64,AAABAAIAEBAAAAAAAAC0AQAAJgAAACAgAAAAAAAA6QIAANoBAACJUE5HDQoaCgAAAA1JSERSAAAAEAAAABAIBgAAAB/z/2EAAAF7SURBVDjLlZPLasJAFIaFRF+iVV+h6hO0GF+gVB9AaHwDt64qCG03tQgtdCFIuyhUelmGli66MXThSt24kNiFBUlAYi6ezjnNxSuawB/ITP7v/HNmJgQAEaZzpgHs/gwcTyTEXuXl2U6nA8ViEbK5HKler28CVRAwnB9ptVrAh8MrQuCaZ4iA8fzIqSgCxwzpTIaSuN/RWGwdYLwCUBQFZFkGSZLgqdmEE7YEN8VOAKyaSKUW4nNBAFmnYiKZpDRX1WqwBBzP089n5f/NEQsFL4WqqtsBWJlzDAJr5PwSMM1awEzzdxIbGI3Hvc6jCZeVFgRQRwpY7Qcw3ktgfpR8wLRxCPaot/X4GS95MppfF6DX9n2A3f+kAZycaT8bAZjU6r6B/duD6d3BYg9wQq/tkYzHY1blEiz5lmQyGc95mrO6r2CxgpjCBXgNsJVviolpXJiraeOIjJRE10juUa4sR8V+mO17VvmGqtuOcdNlwut8zTQJcJ0njifyB2bgTdKh6w4BAAAAAElFTkSuQmCCiVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAACsElEQVRYw71XQWsTURBe2LQgeNKLB+tVemt6txcteNSD/QGC6VEIGDx5s+eKPQqFgJhLNdFLBWMP7cU0oSAWjB70koC9WHbVQ5SO8+XtS14mr7svyaYDH9m87Jv55puZt1nPi4yIzjMeMj7T9OwjI88455nGC1cZX+nsDESumJmPFDwIAqrX6z00Gg1qt9vjkJgFgUeuO16Vy3RjeZkyMzM9+MY1fsM9I9h9zyV7ZAznZrA4FAoFVwJ1z+WuOysrg1lnMolkHJX4k0igzI5sARYWF7vEZEk0rvO6iyUSuJfLJUqM7zYSqRDIra4OOUZPmNZsNrsl8UVTpkJAjh1GzmaSpJ8mAWmYeZB5urHRhW5SNOfUCCDo47W1bvPZsp2qAhipy3Nz1kaLG8dUCEBqM5AvpgElqFar01NgIZsdco7Zb7VasU2YigIYL5tjqCL7Q5YkFQXKlcqQ7DbHthIALk/IWAKor82xPIhshxWABCYioDMz51sexcVi0XoG4DPLIyvJjkTArK3scDQnRvO0MdTrUHGiKZCP4tNgO6BAEI08EQH9Z2Qow0hyPypJGIa9p6JWKCn4SA8jSKmJIDgyRvPJkcRxjfUwNGr/i8+Mo32iHzWiThBD4NM60bet9P77/ubA728RlTjMiwiH6zEEfvIrwdZFtQmMJ7W/ofIDBZD5m3mVZGwJcOP2kmILIlCkE45HoPWurwCSg0+UQRD4ZyXxId+T7gQb9+4q9sioY5ltrOG3L5vqXiiJffDx/aUi83ZJ7jr2ohcEu8Hh6/m+I7OWGiVxbWKHsz+O3vSOakqFQdsFgQeJUiKD7Wv9YKXBgCeSUC3v2kM5EJhlHDh3NcgcPlG1BXZu98sDmTuBa4fsMnz9fniJUaGzs+eMC540XuR0aDO2L8Y3qPyMcdOM+R/8XcqRA3qp9gAAAABJRU5ErkJggg==",
       _urls : [
         {
           type: "application/x-suggestions+json",
           method: "GET",
           template: "https://completion.amazon.com/search/complete?q={searchTerms}&search-alias=aps&mkt=1",
           params: "",
         },
         {
           type: "text/html",
           method: "GET",
-          template: "http://www.amazon.com/exec/obidos/external-search/",
+          template: "https://www.amazon.com/exec/obidos/external-search/",
           params: [
             {
               name: "field-keywords",
               value: "{searchTerms}",
               purpose: undefined,
             },
             {
               name: "mode",
--- a/browser/components/search/test/browser_amazon_behavior.js
+++ b/browser/components/search/test/browser_amazon_behavior.js
@@ -13,17 +13,17 @@ const BROWSER_SEARCH_PREF = "browser.sea
 function test() {
   let engine = Services.search.getEngineByName("Amazon.com");
   ok(engine, "Amazon is installed");
 
   let previouslySelectedEngine = Services.search.currentEngine;
   Services.search.currentEngine = engine;
   engine.alias = "a";
 
-  let base = "http://www.amazon.com/exec/obidos/external-search/?field-keywords=foo&mode=blended&tag=mozilla-20&sourceid=Mozilla-search";
+  let base = "https://www.amazon.com/exec/obidos/external-search/?field-keywords=foo&mode=blended&tag=mozilla-20&sourceid=Mozilla-search";
   let url;
 
   // Test search URLs (including purposes).
   url = engine.getSubmission("foo").uri.spec;
   is(url, base, "Check search URL for 'foo'");
 
   waitForExplicitFinish();
 
--- a/browser/devtools/canvasdebugger/test/browser_canvas-frontend-call-stack-01.js
+++ b/browser/devtools/canvasdebugger/test/browser_canvas-frontend-call-stack-01.js
@@ -23,18 +23,20 @@ function* ifTestingSupported() {
     "There should be no stack container available yet for the draw call.");
 
   let callStackDisplayed = once(window, EVENTS.CALL_STACK_DISPLAYED);
   EventUtils.sendMouseEvent({ type: "mousedown" }, locationLink, window);
   yield callStackDisplayed;
 
   isnot($(".call-item-stack", callItem.target), null,
     "There should be a stack container available now for the draw call.");
-  is($all(".call-item-stack-fn", callItem.target).length, 4,
-    "There should be 4 functions on the stack for the draw call.");
+  // We may have more than 4 functions, depending on whether async
+  // stacks are available.
+  ok($all(".call-item-stack-fn", callItem.target).length >= 4,
+     "There should be at least 4 functions on the stack for the draw call.");
 
   ok($all(".call-item-stack-fn-name", callItem.target)[0].getAttribute("value")
     .includes("C()"),
     "The first function on the stack has the correct name.");
   ok($all(".call-item-stack-fn-name", callItem.target)[1].getAttribute("value")
     .includes("B()"),
     "The second function on the stack has the correct name.");
   ok($all(".call-item-stack-fn-name", callItem.target)[2].getAttribute("value")
--- a/browser/devtools/canvasdebugger/test/browser_canvas-frontend-call-stack-02.js
+++ b/browser/devtools/canvasdebugger/test/browser_canvas-frontend-call-stack-02.js
@@ -24,18 +24,20 @@ function* ifTestingSupported() {
     "There should be no stack container available yet for the draw call.");
 
   let callStackDisplayed = once(window, EVENTS.CALL_STACK_DISPLAYED);
   EventUtils.sendMouseEvent({ type: "mousedown" }, locationLink, window);
   yield callStackDisplayed;
 
   isnot($(".call-item-stack", callItem.target), null,
     "There should be a stack container available now for the draw call.");
-  is($all(".call-item-stack-fn", callItem.target).length, 4,
-    "There should be 4 functions on the stack for the draw call.");
+  // We may have more than 4 functions, depending on whether async
+  // stacks are available.
+  ok($all(".call-item-stack-fn", callItem.target).length >= 4,
+     "There should be at least 4 functions on the stack for the draw call.");
 
   let jumpedToSource = once(window, EVENTS.SOURCE_SHOWN_IN_JS_DEBUGGER);
   EventUtils.sendMouseEvent({ type: "mousedown" }, $(".call-item-location", callItem.target));
   yield jumpedToSource;
 
   let toolbox = yield gDevTools.getToolbox(target);
   let { panelWin: { DebuggerView: view } } = toolbox.getPanel("jsdebugger");
 
--- a/browser/devtools/canvasdebugger/test/browser_canvas-frontend-call-stack-03.js
+++ b/browser/devtools/canvasdebugger/test/browser_canvas-frontend-call-stack-03.js
@@ -35,27 +35,31 @@ function* ifTestingSupported() {
   is(view.hasAttribute("call-stack-populated"), true,
     "The call item's view should have the stack populated now.");
   is(view.getAttribute("call-stack-expanded"), "true",
     "The call item's view should have the stack expanded now.");
   isnot($(".call-item-stack", callItem.target), null,
     "There should be a stack container available now for the draw call.");
   is($(".call-item-stack", callItem.target).hidden, false,
     "The stack container should now be visible.");
-  is($all(".call-item-stack-fn", callItem.target).length, 4,
-    "There should be 4 functions on the stack for the draw call.");
+  // We may have more than 4 functions, depending on whether async
+  // stacks are available.
+  ok($all(".call-item-stack-fn", callItem.target).length >= 4,
+     "There should be at least 4 functions on the stack for the draw call.");
 
   EventUtils.sendMouseEvent({ type: "dblclick" }, contents, window);
 
   is(view.hasAttribute("call-stack-populated"), true,
     "The call item's view should still have the stack populated.");
   is(view.getAttribute("call-stack-expanded"), "false",
     "The call item's view should not have the stack expanded anymore.");
   isnot($(".call-item-stack", callItem.target), null,
     "There should still be a stack container available for the draw call.");
   is($(".call-item-stack", callItem.target).hidden, true,
     "The stack container should now be hidden.");
-  is($all(".call-item-stack-fn", callItem.target).length, 4,
-    "There should still be 4 functions on the stack for the draw call.");
+  // We may have more than 4 functions, depending on whether async
+  // stacks are available.
+  ok($all(".call-item-stack-fn", callItem.target).length >= 4,
+     "There should still be at least 4 functions on the stack for the draw call.");
 
   yield teardown(panel);
   finish();
 }
--- a/browser/devtools/shared/widgets/filter-widget.css
+++ b/browser/devtools/shared/widgets/filter-widget.css
@@ -134,17 +134,18 @@ html, body {
 }
 
 .filter-value input {
   flex-grow: 1;
 }
 
 
 .theme-light .add,
-.theme-light .remove-button {
+.theme-light .remove-button,
+.theme-light #toggle-presets {
   filter: invert(1);
 }
 
 .preset {
   display: flex;
   margin-bottom: 10px;
   cursor: pointer;
   padding: 3px 5px;
@@ -212,18 +213,32 @@ html, body {
 }
 
 /* message shown when there's no filter specified */
 #container p {
   text-align: center;
   line-height: 20px;
 }
 
-.add {
-  background: url(chrome://browser/skin/devtools/add.svg);
+.add,
+#toggle-presets {
   background-size: cover;
   border: none;
   width: 16px;
   height: 16px;
   font-size: 0;
   vertical-align: middle;
   cursor: pointer;
+  margin: 0 5px;
 }
+
+.add {
+  background: url(chrome://browser/skin/devtools/add.svg);
+}
+
+#toggle-presets {
+  background: url(chrome://browser/skin/devtools/pseudo-class.svg#pseudo-class);
+}
+
+.show-presets #toggle-presets {
+  background: url(chrome://browser/skin/devtools/pseudo-class.svg#pseudo-class-checked);
+  filter: none;
+}
--- a/browser/locales/en-US/chrome/browser/browser.dtd
+++ b/browser/locales/en-US/chrome/browser/browser.dtd
@@ -753,16 +753,17 @@ you can use these alternative items. Oth
 <!ENTITY social.markpageMenu.label "Save Page To…">
 <!ENTITY social.marklinkMenu.accesskey "L">
 <!ENTITY social.marklinkMenu.label "Save Link To…">
 
 <!ENTITY getUserMedia.selectCamera.label "Camera to share:">
 <!ENTITY getUserMedia.selectCamera.accesskey "C">
 <!ENTITY getUserMedia.selectMicrophone.label "Microphone to share:">
 <!ENTITY getUserMedia.selectMicrophone.accesskey "M">
+<!ENTITY getUserMedia.audioCapture.label "Audio from the tab will be shared.">
 <!ENTITY getUserMedia.allWindowsShared.message "All visible windows on your screen will be shared.">
 
 <!-- Bad Content Blocker Doorhanger Notification -->
 <!ENTITY badContentBlocked.moreinfo "Most websites will work properly even if content is blocked.">
 
 <!ENTITY mixedContentBlocked2.message "Insecure content">
 <!ENTITY mixedContentBlocked2.moreinfo "Some unencrypted elements on this website have been blocked.">
 <!ENTITY mixedContentBlocked2.learnMore "Learn More">
--- a/browser/locales/en-US/chrome/browser/browser.properties
+++ b/browser/locales/en-US/chrome/browser/browser.properties
@@ -551,23 +551,27 @@ identity.next.accessKey = n
 # LOCALIZATION NOTE: shown in the popup notification when a user successfully logs into a website
 # LOCALIZATION NOTE (identity.loggedIn.description): %S is the user's identity (e.g. user@example.com)
 identity.loggedIn.description = Signed in as: %S
 identity.loggedIn.signOut.label = Sign Out
 identity.loggedIn.signOut.accessKey = O
 
 # LOCALIZATION NOTE (getUserMedia.shareCamera.message, getUserMedia.shareMicrophone.message,
 #                    getUserMedia.shareScreen.message, getUserMedia.shareCameraAndMicrophone.message,
-#                    getUserMedia.shareScreenAndMicrophone.message):
+#                    getUserMedia.shareScreenAndMicrophone.message, getUserMedia.shareCameraAndAudioCapture.message,
+#                    getUserMedia.shareAudioCapture.message, getUserMedia.shareScreenAndAudioCapture.message):
 #  %S is the website origin (e.g. www.mozilla.org)
 getUserMedia.shareCamera.message = Would you like to share your camera with %S?
 getUserMedia.shareMicrophone.message = Would you like to share your microphone with %S?
 getUserMedia.shareScreen.message = Would you like to share your screen with %S?
 getUserMedia.shareCameraAndMicrophone.message = Would you like to share your camera and microphone with %S?
+getUserMedia.shareCameraAndAudioCapture.message = Would you like to share your camera and this tab's audio with %S?
 getUserMedia.shareScreenAndMicrophone.message = Would you like to share your microphone and screen with %S?
+getUserMedia.shareScreenAndAudioCapture.message = Would you like to share this tab's audio and your screen with %S?
+getUserMedia.shareAudioCapture.message = Would you like to share this tab's audio with %S?
 getUserMedia.selectWindow.label=Window to share:
 getUserMedia.selectWindow.accesskey=W
 getUserMedia.selectScreen.label=Screen to share:
 getUserMedia.selectScreen.accesskey=S
 getUserMedia.selectApplication.label=Application to share:
 getUserMedia.selectApplication.accesskey=A
 getUserMedia.noVideo.label = No Video
 getUserMedia.noApplication.label = No Application
@@ -599,62 +603,79 @@ getUserMedia.never.label = Never Share
 getUserMedia.never.accesskey = N
 getUserMedia.sharingCamera.message2 = You are currently sharing your camera with this page.
 getUserMedia.sharingMicrophone.message2 = You are currently sharing your microphone with this page.
 getUserMedia.sharingCameraAndMicrophone.message2 = You are currently sharing your camera and microphone with this page.
 getUserMedia.sharingApplication.message = You are currently sharing an application with this page.
 getUserMedia.sharingScreen.message = You are currently sharing your screen with this page.
 getUserMedia.sharingWindow.message = You are currently sharing a window with this page.
 getUserMedia.sharingBrowser.message = You are currently sharing a tab with this page.
+getUserMedia.sharingAudioCapture.message = You are currently sharing a tab's audio with this page.
 getUserMedia.continueSharing.label = Continue Sharing
 getUserMedia.continueSharing.accesskey = C
 getUserMedia.stopSharing.label = Stop Sharing
 getUserMedia.stopSharing.accesskey = S
 
 getUserMedia.sharingMenu.label = Tabs sharing devices
 getUserMedia.sharingMenu.accesskey = d
 # LOCALIZATION NOTE (getUserMedia.sharingMenuCamera
 #                    getUserMedia.sharingMenuMicrophone,
+#                    getUserMedia.sharingMenuAudioCapture,
 #                    getUserMedia.sharingMenuApplication,
 #                    getUserMedia.sharingMenuScreen,
 #                    getUserMedia.sharingMenuWindow,
 #                    getUserMedia.sharingMenuBrowser,
 #                    getUserMedia.sharingMenuCameraMicrophone,
 #                    getUserMedia.sharingMenuCameraMicrophoneApplication,
 #                    getUserMedia.sharingMenuCameraMicrophoneScreen,
 #                    getUserMedia.sharingMenuCameraMicrophoneWindow,
 #                    getUserMedia.sharingMenuCameraMicrophoneBrowser,
+#                    getUserMedia.sharingMenuCameraAudioCapture,
+#                    getUserMedia.sharingMenuCameraAudioCaptureApplication,
+#                    getUserMedia.sharingMenuCameraAudioCaptureScreen,
+#                    getUserMedia.sharingMenuCameraAudioCaptureWindow,
+#                    getUserMedia.sharingMenuCameraAudioCaptureBrowser,
 #                    getUserMedia.sharingMenuCameraApplication,
 #                    getUserMedia.sharingMenuCameraScreen,
 #                    getUserMedia.sharingMenuCameraWindow,
 #                    getUserMedia.sharingMenuCameraBrowser,
 #                    getUserMedia.sharingMenuMicrophoneApplication,
 #                    getUserMedia.sharingMenuMicrophoneScreen,
 #                    getUserMedia.sharingMenuMicrophoneWindow,
 #                    getUserMedia.sharingMenuMicrophoneBrowser):
 # %S is the website origin (e.g. www.mozilla.org)
 getUserMedia.sharingMenuCamera = %S (camera)
 getUserMedia.sharingMenuMicrophone = %S (microphone)
+getUserMedia.sharingMenuAudioCapture = %S (tab audio)
 getUserMedia.sharingMenuApplication = %S (application)
 getUserMedia.sharingMenuScreen = %S (screen)
 getUserMedia.sharingMenuWindow = %S (window)
 getUserMedia.sharingMenuBrowser = %S (tab)
 getUserMedia.sharingMenuCameraMicrophone = %S (camera and microphone)
 getUserMedia.sharingMenuCameraMicrophoneApplication = %S (camera, microphone and application)
 getUserMedia.sharingMenuCameraMicrophoneScreen = %S (camera, microphone and screen)
 getUserMedia.sharingMenuCameraMicrophoneWindow = %S (camera, microphone and window)
 getUserMedia.sharingMenuCameraMicrophoneBrowser = %S (camera, microphone and tab)
+getUserMedia.sharingMenuCameraAudioCapture = %S (camera and tab audio)
+getUserMedia.sharingMenuCameraAudioCaptureApplication = %S (camera, tab audio and application)
+getUserMedia.sharingMenuCameraAudioCaptureScreen = %S (camera, tab audio and screen)
+getUserMedia.sharingMenuCameraAudioCaptureWindow = %S (camera, tab audio and window)
+getUserMedia.sharingMenuCameraAudioCaptureBrowser = %S (camera, tab audio and tab)
 getUserMedia.sharingMenuCameraApplication = %S (camera and application)
 getUserMedia.sharingMenuCameraScreen = %S (camera and screen)
 getUserMedia.sharingMenuCameraWindow = %S (camera and window)
 getUserMedia.sharingMenuCameraBrowser = %S (camera and tab)
 getUserMedia.sharingMenuMicrophoneApplication = %S (microphone and application)
 getUserMedia.sharingMenuMicrophoneScreen = %S (microphone and screen)
 getUserMedia.sharingMenuMicrophoneWindow = %S (microphone and window)
 getUserMedia.sharingMenuMicrophoneBrowser = %S (microphone and tab)
+getUserMedia.sharingMenuMicrophoneApplication = %S (tab audio and application)
+getUserMedia.sharingMenuMicrophoneScreen = %S (tab audio and screen)
+getUserMedia.sharingMenuMicrophoneWindow = %S (tab audio and window)
+getUserMedia.sharingMenuMicrophoneBrowser = %S (tab audio and tab)
 # LOCALIZATION NOTE(getUserMedia.sharingMenuUnknownHost): this is used for the website
 # origin for the sharing menu if no readable origin could be deduced from the URL.
 getUserMedia.sharingMenuUnknownHost = Unknown origin
 
 # LOCALIZATION NOTE(emeNotifications.drmContentPlaying.message2): %S is brandShortName.
 emeNotifications.drmContentPlaying.message2 = Some audio or video on this site uses DRM software, which may limit what %S can let you do with it.
 emeNotifications.drmContentPlaying.button.label = Configure…
 emeNotifications.drmContentPlaying.button.accesskey = C
--- a/browser/locales/en-US/chrome/browser/search.properties
+++ b/browser/locales/en-US/chrome/browser/search.properties
@@ -28,19 +28,22 @@ cmd_showSuggestions_accesskey=S
 # menuitem at the bottom of the search panel.
 cmd_addFoundEngine=Add "%S"
 # LOCALIZATION NOTE (cmd_addFoundEngineMenu): When more than 5 engines
 # are offered by a web page, instead of listing all of them in the
 # search panel using the cmd_addFoundEngine string, they will be
 # grouped in a submenu using cmd_addFoundEngineMenu as a label.
 cmd_addFoundEngineMenu=Add search engine
 
-# LOCALIZATION NOTE (searchFor, searchWith):
-# These two strings are used to build the header above the list of one-click
+# LOCALIZATION NOTE (searchForKeywordsWith):
+# This string is used to build the header above the list of one-click
 # search providers:  "Search for <user-typed keywords> with:"
-searchFor=Search for 
-searchWith= with:
+searchForKeywordsWith=Search for %S with:
 
 # LOCALIZATION NOTE (searchWithHeader):
 # The wording of this string should be as close as possible to
-# searchFor and searchWith. This string will be used instead of
-# them when the user has not typed any keyword.
+# searchForKeywordsWith. This string will be used when the user
+# has not typed anything.
 searchWithHeader=Search with:
+
+# LOCALIZATION NOTE (searchSettings):
+# This is the label for the button that opens Search preferences.
+searchSettings=Change Search Settings
--- a/browser/locales/en-US/searchplugins/amazondotcom.xml
+++ b/browser/locales/en-US/searchplugins/amazondotcom.xml
@@ -5,15 +5,15 @@
 <SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
 <ShortName>Amazon.com</ShortName>
 <Description>Amazon.com Search</Description>
 <InputEncoding>UTF-8</InputEncoding>
 <Image width="16" height="16">data:image/x-icon;base64,AAABAAIAEBAAAAAAAAC0AQAAJgAAACAgAAAAAAAA6QIAANoBAACJUE5HDQoaCgAAAA1JSERSAAAAEAAAABAIBgAAAB/z/2EAAAF7SURBVDjLlZPLasJAFIaFRF+iVV+h6hO0GF+gVB9AaHwDt64qCG03tQgtdCFIuyhUelmGli66MXThSt24kNiFBUlAYi6ezjnNxSuawB/ITP7v/HNmJgQAEaZzpgHs/gwcTyTEXuXl2U6nA8ViEbK5HKler28CVRAwnB9ptVrAh8MrQuCaZ4iA8fzIqSgCxwzpTIaSuN/RWGwdYLwCUBQFZFkGSZLgqdmEE7YEN8VOAKyaSKUW4nNBAFmnYiKZpDRX1WqwBBzP089n5f/NEQsFL4WqqtsBWJlzDAJr5PwSMM1awEzzdxIbGI3Hvc6jCZeVFgRQRwpY7Qcw3ktgfpR8wLRxCPaot/X4GS95MppfF6DX9n2A3f+kAZycaT8bAZjU6r6B/duD6d3BYg9wQq/tkYzHY1blEiz5lmQyGc95mrO6r2CxgpjCBXgNsJVviolpXJiraeOIjJRE10juUa4sR8V+mO17VvmGqtuOcdNlwut8zTQJcJ0njifyB2bgTdKh6w4BAAAAAElFTkSuQmCCiVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAACsElEQVRYw71XQWsTURBe2LQgeNKLB+tVemt6txcteNSD/QGC6VEIGDx5s+eKPQqFgJhLNdFLBWMP7cU0oSAWjB70koC9WHbVQ5SO8+XtS14mr7svyaYDH9m87Jv55puZt1nPi4yIzjMeMj7T9OwjI88455nGC1cZX+nsDESumJmPFDwIAqrX6z00Gg1qt9vjkJgFgUeuO16Vy3RjeZkyMzM9+MY1fsM9I9h9zyV7ZAznZrA4FAoFVwJ1z+WuOysrg1lnMolkHJX4k0igzI5sARYWF7vEZEk0rvO6iyUSuJfLJUqM7zYSqRDIra4OOUZPmNZsNrsl8UVTpkJAjh1GzmaSpJ8mAWmYeZB5urHRhW5SNOfUCCDo47W1bvPZsp2qAhipy3Nz1kaLG8dUCEBqM5AvpgElqFar01NgIZsdco7Zb7VasU2YigIYL5tjqCL7Q5YkFQXKlcqQ7DbHthIALk/IWAKor82xPIhshxWABCYioDMz51sexcVi0XoG4DPLIyvJjkTArK3scDQnRvO0MdTrUHGiKZCP4tNgO6BAEI08EQH9Z2Qow0hyPypJGIa9p6JWKCn4SA8jSKmJIDgyRvPJkcRxjfUwNGr/i8+Mo32iHzWiThBD4NM60bet9P77/ubA728RlTjMiwiH6zEEfvIrwdZFtQmMJ7W/ofIDBZD5m3mVZGwJcOP2kmILIlCkE45HoPWurwCSg0+UQRD4ZyXxId+T7gQb9+4q9sioY5ltrOG3L5vqXiiJffDx/aUi83ZJ7jr2ohcEu8Hh6/m+I7OWGiVxbWKHsz+O3vSOakqFQdsFgQeJUiKD7Wv9YKXBgCeSUC3v2kM5EJhlHDh3NcgcPlG1BXZu98sDmTuBa4fsMnz9fniJUaGzs+eMC540XuR0aDO2L8Y3qPyMcdOM+R/8XcqRA3qp9gAAAABJRU5ErkJggg==</Image>
 <Image width="65" height="26">data:image/x-icon;base64,iVBORw0KGgoAAAANSUhEUgAAAEEAAAAaCAYAAADovjFxAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyNpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuNS1jMDIxIDc5LjE1NDkxMSwgMjAxMy8xMC8yOS0xMTo0NzoxNiAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENDIChNYWNpbnRvc2gpIiB4bXBNTTpJbnN0YW5jZUlEPSJ4bXAuaWlkOjk1RjU4MDYwQkU0RDExRTNBNkI0RTM0MDNENDQ0Q0QxIiB4bXBNTTpEb2N1bWVudElEPSJ4bXAuZGlkOjk1RjU4MDYxQkU0RDExRTNBNkI0RTM0MDNENDQ0Q0QxIj4gPHhtcE1NOkRlcml2ZWRGcm9tIHN0UmVmOmluc3RhbmNlSUQ9InhtcC5paWQ6OTVGNTgwNUVCRTREMTFFM0E2QjRFMzQwM0Q0NDRDRDEiIHN0UmVmOmRvY3VtZW50SUQ9InhtcC5kaWQ6OTVGNTgwNUZCRTREMTFFM0E2QjRFMzQwM0Q0NDRDRDEiLz4gPC9yZGY6RGVzY3JpcHRpb24+IDwvcmRmOlJERj4gPC94OnhtcG1ldGE+IDw/eHBhY2tldCBlbmQ9InIiPz4FxGXPAAAGU0lEQVR42uyYe2xURRSHZ+7dF7tCW6q8WhTCM1TkIUgsBUFErUZ5qFExGkKiwahEwSBEA4oviAkCEg1GMQoVUFFACa9gJEil8tSyFYqIlFrElkcjULa7946/2Z67Hi53UUvgHzvJtzP33DMzZ86cedyVSinxf0+GaEpNTmhyAiVfwOdzytPBg6AzqAe7wTtgEdN/A/QGi8EKyvuDKBhGOq+Ah0EdeBMscPV5B3gW5NMkHAKrwdNM50bwHLAB37S0safB46CWZIPBZGqvGiwDL1JdnTLIhkwwA1jgXZJvAM8Iv2n6wHqg0lAIBBEnWSWocOktBjM96o9m9cddoJ+lTG/GBfQ0uaT3WJr3P4BWpNORyXd46K7WSh8wwULQy2VEKTPuJ1cDGz0aLQFV7HkL1e3GZDUgHxSAo0zel3TzwGwwB0wAu1zta51sJisHg8EkJvuI9HJAgsn1GNay55igTjawSg5OxYNpnJBPss+Y7HOS9WayzSTrClaBL0EH1uY8pjvKZYPDAabTmWTjmewRpltLsmP0HGZjOcb0ikl2Wq+xeYRen2PAINAPmLSmYh57SQ0opvIWcA+VF1N+gNaeyeqXg7uprPt4lPaT611rnicJjoEsep4CfqbyMKZXzco/ggLQEnQiW5y0k5X30N5jOZ3qjWQSuIKeFRuE123qDCu3YOXmrtydbgOvgb5MVs/Klkt/LXPASjCLvWvNytzGU6x8tcsJNiuH+RE5h04G7YBVIAeEmEGWx2BMFinSJXfLnHI3GpR2wElQSO/mpzFyJLiVRdtIlw3xNEe9SqPD7TvHRl15PAv7EaAK+AnB8sYmx4lPMtkUcogzW076k/Ku4EMmPwxGg5uZLJpmGV3j0pH/5rIUZOVsKr/OKut1dd1FOMGZmRwma88Gey+Tj6J+Z7uW2QNgOdgItpHNPIJGUN4K9KDyfnCC9oZ/dMJuNuO7wHrwVCp+DMOMW2pywrJ12Qm7AGvD71E2WOiFKP+U6T0P1oB9LnueABFDykzdH/oVCUucilsSiOQz3vWjDa2c2T6O7C5mbU2lPMJsCXhEj9+gmTjMZmg4mKs700fJ2bgqw9RMRPco25lWgzMybFsbaAsppeGxpvk+EqF8iev2eTs4TifEkoDP1O2NxoBPxxKyEv0tCAf9mdd2at18UK/c5q1bRiTsGBJLiBLoRWwr2dVA8DW1N5yi9izdKJd77DMhD7tCUg+UXupGmoEjYDMGqgfdvaBn+0PTR4brvtsvxfSlZXkwrg0mGmtXfS8R6Yj1Nn5D5WmnSEPuhG0IQWUE/b6Blm1rz/8Ox0X1mvCZhmN4Lp0wX2FAyu8zM+oTSk/AnoeGdhaRkCn65hhiSJfYnR2y6gZIU9g1pwL791UHi6atrBPfllb60HdCjw/9CPQzjPYC3eYmGgOf8QKKBi0vI3l3WqL1jhPOSzr8wkGfOP7WVcIIqfdUXJ45WhuYsG5vWBQftESHbAODkuKFoigcYbqCQdKKUKltQaZ+vbaMBv2X7u8ipt51QihLjheWnIWmWqjk1wPeS9T2YxCWaLdsd6aogqunFJUJPRHk3EanCzqhT5e2Yis+TZQSNwlbvi1NlQm/LoRBL6Pvem17tCIiSiqC4mRdMipEfkdL7Dliiu2HEmLvkToRwoob0TsiItjKNpXHxcHqs0JHcouwT+S1C4o+uYY4UKNwg8NX0C21cID2o9RLpJI+ktrSRhhNzqSh2smAjjp8Oc0yxdZoFfqQOhoujRN6dGwldk6l93ZyFj8B95HKXPC+9KlSz0DAzGE2/w4K/j41+So1y1qgYudEylCwFCykTU7RsTk2aXjIFsu3Z4kx8/cJn0zAKcZlc4JOPUER5TpVgB16fdPxFU1tRtLj+iI9rzXZtOMPoQ0zj+SL6DNZO+RjWvcVDedPQ+V+MxF5v/yRjKRG/5/QiDqldG/QN7iZdBO8ms54QWFcmjz+VPLi9Rtdgs7QwPX5fyVoQ/cEzQ2uPvTmNRGsY/9RrEk5gCZFNrNFYa8sOKHGdQhceic4aQXRny4rY2m3zSUK/2N7eynCvnDdBnNoSbx6Xg27IWK9P28ujxOctI2YRtdafVwNoNnOoA8gH1sIMbrJ6e+HX8FW8A0oSdN+lacDKCXsix+AbPrLvemP1iYnOOkvAQYAhXFLHTlsPyYAAAAASUVORK5CYII=</Image>
 <Image width="130" height="52">data:image/x-icon;base64,iVBORw0KGgoAAAANSUhEUgAAAIIAAAA0CAYAAABGkOCVAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyNpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuNS1jMDIxIDc5LjE1NDkxMSwgMjAxMy8xMC8yOS0xMTo0NzoxNiAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENDIChNYWNpbnRvc2gpIiB4bXBNTTpJbnN0YW5jZUlEPSJ4bXAuaWlkOjk1RjU4MDVDQkU0RDExRTNBNkI0RTM0MDNENDQ0Q0QxIiB4bXBNTTpEb2N1bWVudElEPSJ4bXAuZGlkOjk1RjU4MDVEQkU0RDExRTNBNkI0RTM0MDNENDQ0Q0QxIj4gPHhtcE1NOkRlcml2ZWRGcm9tIHN0UmVmOmluc3RhbmNlSUQ9InhtcC5paWQ6OTVGNTgwNUFCRTREMTFFM0E2QjRFMzQwM0Q0NDRDRDEiIHN0UmVmOmRvY3VtZW50SUQ9InhtcC5kaWQ6OTVGNTgwNUJCRTREMTFFM0E2QjRFMzQwM0Q0NDRDRDEiLz4gPC9yZGY6RGVzY3JpcHRpb24+IDwvcmRmOlJERj4gPC94OnhtcG1ldGE+IDw/eHBhY2tldCBlbmQ9InIiPz5AOR4OAAAOI0lEQVR42uxcC3BU1Rn+7+4mJCSECEgAEwKI8kgNikqtGtCOgkVtRSogamGUSrVoUUF0bKso1qlF6vtRmdGiUlSoQpX6qgqodBwURd4BiYTXggjkAUk2u7f/v/tdvLk55+7dZE1i3X/mm7Dnnvs65/uf51wM0zQpJSkxUkRISYoIKUkRISUpIqQkRYSUpIiQEncipPn9zraujF8yzmecxDjWduwA43PGW4z5+K2TkYyBjHqGD39fYZQzOjAm4R59GIcZXzCeY7zjuM4JjKsZpzN6MvYwPmXMw18vIs9xKeMcxgBGtu3YbsbHjCWMxYyQy3V+zjgR7+JVAoxtjEUuffIZYxhnM3ox2jOCjM2MpYzX4jxXf4y3Ccg9VzI+xPGfMcYyfoSx38r4gDGXsTfaQ4hgwxTGLobpAWWMMY7zLbRjbFScM5VRwFijuWaEcavtOpMY32j6HmFM09zfQjbjYUaNx3daySjSXKsjY4fH6zixj5GjuKaPcTtjb5zzVzFKXN7zfsU5r+PYLJfrbmOcIf0ClmVg3M+YlgDTCxkLGGEF2w2N1uQwnoWlUVooxp8Zn+D8p13un8H4C2MXrJNTOjJeZ5yVwDudAQ38KbTGLmmMSBMtbwjn20VM8fOMcR7OP5XxJvouURwPK9q+gZW5w+W6Yn1elOv70DAxQRLYJ242zI1TVMHHBAxyPHmI8bDHZ5AXbadofyBBElgi7meWi5lvivgV4zHLIwksyQThT/PYv4Bxj8f3vdIHf6RjzUKQ5ArGP1xYNdTjw/Xx2K+IUeyxr8QQ/RxtxSCdU44wHoW//A3jvy7xTb6jrZpRxqhl1DhwGNCJxD8Hbb8l3pmh6CfXegrH/q04nsV4TGFdVHI24hkvMlwYPoxxvOKgsOmPtt/zMQgTFX1Pghn2InWYjLWMMxEI+jR9j8A9SLBVgoCPFCa7q6PtCoX21iEIXmprE9P8kYJ0OdCoHY5nuYDRXaHdMi5P4rhTKhg3OdzKbbCmTgsq5H0Jv8VVP864ztFvCOMiBN7xrBBhnB/E/W+BkjmlXwARtET/x9gOHMKDOGWBhgh5CZi4W0AEkWfwd5LGr45BxEx4maWIgL24oSpHdrDMQQJLy1/TWJ8szaRWKNpHMc7TvO/tsAh2q6h6h/dtJCDbO1/DSFe42Fc8jHU5niuI35IprALR7dJFiPAy0qcujE4IAoMYSNVAkMeJUElQEdjN1xBhpY0ElizRDKJTfo/UqDPSX9HuFZq+ezTtXsldCHOtih9ehVbbZRj8vVPeVbRJwLpJEVwPxfMF4zzbU44+pbCATstlWg//FUCaSL8HiHJuM+sWexDNepmINYq2vQlE6aWAymR2hjvp6hJ8GR6DwEfhLpwi9Ykpinbd/Uo12UCpggjHwMTHI4KK/Nu8RsES/I1AKnUySNBBw+JEZZeLL3OKKvjyNfG+p0ALhqCo0gmmv10z32cK/LVKrmfsVLT3drGWpCGULkiPpwzbPKaaDYggWnI341cO35pMSaQiZyThfifjnS5sBol0cpJLmvkE3IJKumvaKxNQCJHj4jxfBEGsp3EN2AKYJZqI0pJ9iNBzm1PSppaTi1GG1j2vaMbXsAyJEj8TMYjqvHWM6S7ntte0+xIcMyOZ4+2zFSqKNH7rJuS9pyD3/j7IQLyTigQfIPMZjHd6qAnXvxNuRpUaT0I24mayEyECJam/qwSQx/9Yk85cSg0XloLfEyLcqdFWie5vdOT03yR47XOQAqvkbpcild3nq5Tu2ASf41AyB0xYNU7D2tuo8eqiv5npY0tInibFlKxoGjVeL0gkCBYL8zeXVPFeD9fYpmnvkmAauy3ZFmGgol1852pFe584Vay2INZSq1PWU6yE65Qil2CLFAWeEzT9pejzCALirRi/tQrN/Uxzvq4crLpfjSa9bhYRAhpLofJBv9BcZ1AbIkKXBMgqGl6i6d/X8VvWHya43HekRmsXo6hk1QmWgywBRaHJKT1BbKfInpAvk+0aDM1gOotH40lf1TtDE2e0hhx2IaszdZtJjReXLJngCDbHNOFZpGYwlWJVUmtCxUp8pBlD5/hO18Q685I9aEKEco32PIWawlDky8+6RKqyN+DJZqaWyZKNmphFfO1CaK7U319A4KgTcYNzbJakOQU1qdHYV0gf0MyF7ND6HcXWLiSw/a2i3xY8e1JFzNObmjhB6vN/T7B4UwzT15oiQaFsbFGVcmW18/UErjUaGcIBavqmFEvsxTSp2byksDKdEYe4ydRkZwwWEYR5V7n4VmfKch9MVmdHYDUdOXprSx0s2Kse+3+CmoNTS4MYlwOKGCMMy7M6Eo58ESbjEKo3RsBvFKA+IUpxnEvefx2U7ScJvNv0BImcEBG2gvmy8aSHS1/ZKDqZYsuYsk7/PNqleDKxPmwuFHvs59Hw+aJhh6qClplA9SvgsU0VCC6GJt9HjZdw7SJb7GRNQBazejFusPlxiYnsy8dSWd1VH44sMMmYzw+9mt83kpudSf0LYzoUqjfp0007Lb+Uw31K0gO+qyOmOUyhxVK/kNL30xh/N5Es7ma4Dl3G4pR2mrFVra9k23cx9wBLxX92gp8NwdQuBlHsS9OTDMMYG6oPzzTJ98G4YX1pcM80uv25dWweDF9awDfDNE2nef4XYo0GD0GxfYrdHO3inz9URPL3OghRg5qHKtaRiug1cBMd8E7Wjml5nzcchJRtd7JJRyqoe2Srf/1Rh2DIil+gsFvuvuLeneiqIenkZx3vlh2mUwswLKZBb2/OiZqLzXsNembFftpQFqQ0H2u+Yewm/VqLLFxNhCXJx8TuRzwgO5WknL3ThSiySHitwkXOoMaVzOFQaLtsV21ntybHhAms0Tq9cIQ6dcyiy0t60uyLK+UrCVpVnk03vlTFmrE7egkjClZZv6/BeVH19vtayl0EENCacB2huA6dn1Ge+5KzT6CMgEGhiEn5uQGaUlLHf2uIOZKPuKiAIkZ3TF4N+UxZVt9gGLRqf3Va9ei5EVq5tjw6DnbG+dXvngMllDWdSrinFinW6YjgSUJMk5lj+9Ntww+RGTIKMRDBnQcz6MHl6bSnop5WrAtSZdURqq79dvUz75gsSk8P0I7gwegA+VuOEJ4kzCQQ+s4cN4BmnHfo6FTw5Gez1l/Jv6UaO1hTuLKklC8xMmwaW+Ys60CPvlEefWeRyuqa6Jj42tB7N5kIojH9CvPo/ZszKTejXgbrNeTCd/EAPGP4zGr5iKqyNkCrd7SnD8sMase3CnPb6OI6OjY7RDcsbEcLl5dSvY3zAR4bdjm4R+yAGdUik9IDfoq4fJll9Q/4vS26yX1C9RGyxzbiDsKs/bPGF9G0cw+SGT56rYtRGMpPYJgm8IPPE0tZXQtv5jNp9rvZdN+ijZTWhuqxTSaCWIMbLuxHsy+psAZLCjYvIl+WiPpP+F0nL99AzNjgMlnon2s60px3KqOTZ/IEfLy+nCKIcfr36kpDjs+lkr4Bumfxbtq+5yDcTWyyZdLk36FIzNyeNiA2R6s27DhKHr+vMSnqbYngwN551DErnVZv2kU18lJ81gWn96LF19baSUAIPiXC3wx/LS6g1pFuX+XIAi6n2D7P2APGHoq+PpxG586poi3l+1rSPX53RLh+5In011GV9gHLQ+1hBH5/hsKUDMZBdUmrIUmWbc2h2nqZZKJhfaooI52TM0OCr/b00HIffbU/RPsr63jidkQnWbR3yMACmjw0h8YPjt1i/qe59PSKCvq6ooa28mD7HGQoGVQYPbekbyZNPvMw5WaGaOVXHWjpOh9tDNbSE5dFqEtWqCneuQeysAz8Pg3pacNB95v06wXZNO8/pW3GKiSbCFZgJjugb7K1laEs+hwiYZdap230I0bDmJ5NrJCiptZHa4PtWZvY1bB2Dz6uOnrsaH+5BludYGU67a5MI8NhFAb1qIq2Rb2MWCfTcV+rzV0yUBLuAMsn1kH2QG5HiiYp3wD8bfSOy7bk0EUPbIpZNcP4vySCvSr3iKO+fwTpkKRubze5QgZSKAnj1i9ef28iW9Quo9hHMtaK4TBUVKUcvwxtbyNV0xJ+xGMBWvb5drYKrU+E79JBLUJJ9xVHQUk2u7yMos1cBGGJbcowMZkWvPaL118vUoK/FZMsy8t/sJFASsXWkrD9E7u4lc3hRdnUVuS7jlTKMPETFAWffBR7liCWWIiiyKA41cCWkO4orEmhS3YcfY5/D7VVMfejADfWFv+MsB1b5E5mg87vV0/tOaWMRFp/X0+ghe4zDzVyKZPKilpHRZA1GggjjlgPc7ueYmv520mzFbuZko2KpfwfA8XQ6v7U+DO6o44Fsc5dILolYiGspfi5FG9bH899cfcqGnVWb3rhvVLy/UCIYGnJHSDFVKRaqs/K/EhBBaPQVoHzJSL/EuXTIFK4QzgmJDngqIRmoVonlTrrS65uQC/cQzZ/dPZohd6AZXhfcWwMAsgylKo9yYVFASYC/WAsgl02waTKZ++y4/dKF+2zl14Fuo9D6qCpIYfVCIAERjPczWFYs8c1BLAyCGv30mRlpqCRI6HWDxRbiwiWbECRRjRMvlIej8pkU9KYdNuEJEs2Iqidj3+7ifyfD8cjxnmLvocSaAPPsBfaJjgFWYSsxp1M3v4fgGTKGsQlEuh9BEvjRSSTGEz6jakpIiQoq4G74b9lY+k5FNvv15+a/61ig1IIglBZkpY9he/BSoWacK3dpP9GMUWEJMQSmxCBCwHyEJnL9nPZT1iAts4IBNM0Ef4+BJESWMqGGtlZvA7ZyF4UuX7wkvp/FlOSIkJKUkRISYoIKUkRISUpIqTEXf4nwABZ4DL1UAe0XwAAAABJRU5ErkJggg==</Image>
 <Url type="application/x-suggestions+json" method="GET" template="https://completion.amazon.com/search/complete?q={searchTerms}&amp;search-alias=aps&amp;mkt=1"/>
-<Url type="text/html" method="GET" template="http://www.amazon.com/exec/obidos/external-search/" rel="searchform">
+<Url type="text/html" method="GET" template="https://www.amazon.com/exec/obidos/external-search/" rel="searchform">
   <Param name="field-keywords" value="{searchTerms}"/>
   <Param name="mode" value="blended"/>
   <Param name="tag" value="mozilla-20"/>
   <Param name="sourceid" value="Mozilla-search"/>
 </Url>
 </SearchPlugin>
--- a/browser/modules/ContentSearch.jsm
+++ b/browser/modules/ContentSearch.jsm
@@ -109,18 +109,18 @@ this.ContentSearch = {
   },
 
   get searchSuggestionUIStrings() {
     if (this._searchSuggestionUIStrings) {
       return this._searchSuggestionUIStrings;
     }
     this._searchSuggestionUIStrings = {};
     let searchBundle = Services.strings.createBundle("chrome://browser/locale/search.properties");
-    let stringNames = ["searchHeader", "searchPlaceholder", "searchFor",
-                       "searchWith", "searchWithHeader"];
+    let stringNames = ["searchHeader", "searchPlaceholder", "searchForKeywordsWith",
+                       "searchWithHeader", "searchSettings"];
     for (let name of stringNames) {
       this._searchSuggestionUIStrings[name] = searchBundle.GetStringFromName(name);
     }
     return this._searchSuggestionUIStrings;
   },
 
   destroy: function () {
     if (this._destroyedPromise) {
--- a/browser/modules/ContentWebRTC.jsm
+++ b/browser/modules/ContentWebRTC.jsm
@@ -81,24 +81,31 @@ function handleRequest(aSubject, aTopic,
 
 function prompt(aContentWindow, aWindowID, aCallID, aConstraints, aDevices, aSecure) {
   let audioDevices = [];
   let videoDevices = [];
   let devices = [];
 
   // MediaStreamConstraints defines video as 'boolean or MediaTrackConstraints'.
   let video = aConstraints.video || aConstraints.picture;
+  let audio = aConstraints.audio;
   let sharingScreen = video && typeof(video) != "boolean" &&
                       video.mediaSource != "camera";
+  let sharingAudio = audio && typeof(audio) != "boolean" &&
+                     audio.mediaSource != "microphone";
   for (let device of aDevices) {
     device = device.QueryInterface(Ci.nsIMediaDevice);
     switch (device.type) {
       case "audio":
-        if (aConstraints.audio) {
-          audioDevices.push({name: device.name, deviceIndex: devices.length});
+        // Check that if we got a microphone, we have not requested an audio
+        // capture, and if we have requested an audio capture, we are not
+        // getting a microphone instead.
+        if (audio && (device.mediaSource == "microphone") != sharingAudio) {
+          audioDevices.push({name: device.name, deviceIndex: devices.length,
+                             mediaSource: device.mediaSource});
           devices.push(device);
         }
         break;
       case "video":
         // Verify that if we got a camera, we haven't requested a screen share,
         // or that if we requested a screen share we aren't getting a camera.
         if (video && (device.mediaSource == "camera") != sharingScreen) {
           videoDevices.push({name: device.name, deviceIndex: devices.length,
@@ -108,17 +115,17 @@ function prompt(aContentWindow, aWindowI
         break;
     }
   }
 
   let requestTypes = [];
   if (videoDevices.length)
     requestTypes.push(sharingScreen ? "Screen" : "Camera");
   if (audioDevices.length)
-    requestTypes.push("Microphone");
+    requestTypes.push(sharingAudio ? "AudioCapture" : "Microphone");
 
   if (!requestTypes.length) {
     denyRequest({callID: aCallID}, "NotFoundError");
     return;
   }
 
   if (!aContentWindow.pendingGetUserMediaRequests) {
     aContentWindow.pendingGetUserMediaRequests = new Map();
@@ -128,16 +135,17 @@ function prompt(aContentWindow, aWindowI
 
   let request = {
     callID: aCallID,
     windowID: aWindowID,
     documentURI: aContentWindow.document.documentURI,
     secure: aSecure,
     requestTypes: requestTypes,
     sharingScreen: sharingScreen,
+    sharingAudio: sharingAudio,
     audioDevices: audioDevices,
     videoDevices: videoDevices
   };
 
   let mm = getMessageManagerForWindow(aContentWindow);
   mm.sendAsyncMessage("webrtc:Request", request);
 }
 
--- a/browser/modules/webrtcUI.jsm
+++ b/browser/modules/webrtcUI.jsm
@@ -183,30 +183,30 @@ function getHost(uri, href) {
       host = bundle.GetStringFromName("getUserMedia.sharingMenuUnknownHost");
     }
   }
   return host;
 }
 
 function prompt(aBrowser, aRequest) {
   let {audioDevices: audioDevices, videoDevices: videoDevices,
-       sharingScreen: sharingScreen, requestTypes: requestTypes} = aRequest;
+       sharingScreen: sharingScreen, sharingAudio: sharingAudio,
+       requestTypes: requestTypes} = aRequest;
   let uri = Services.io.newURI(aRequest.documentURI, null, null);
   let host = getHost(uri);
   let chromeDoc = aBrowser.ownerDocument;
   let chromeWin = chromeDoc.defaultView;
   let stringBundle = chromeWin.gNavigatorBundle;
   let stringId = "getUserMedia.share" + requestTypes.join("And") + ".message";
   let message = stringBundle.getFormattedString(stringId, [host]);
 
   let mainLabel;
-  if (sharingScreen) {
+  if (sharingScreen || sharingAudio) {
     mainLabel = stringBundle.getString("getUserMedia.shareSelectedItems.label");
-  }
-  else {
+  } else {
     let string = stringBundle.getString("getUserMedia.shareSelectedDevices.label");
     mainLabel = PluralForm.get(requestTypes.length, string);
   }
 
   let notification; // Used by action callbacks.
   let mainAction = {
     label: mainLabel,
     accessKey: stringBundle.getString("getUserMedia.shareSelectedDevices.accesskey"),
@@ -220,38 +220,38 @@ function prompt(aBrowser, aRequest) {
     {
       label: stringBundle.getString("getUserMedia.denyRequest.label"),
       accessKey: stringBundle.getString("getUserMedia.denyRequest.accesskey"),
       callback: function () {
         denyRequest(notification.browser, aRequest);
       }
     }
   ];
-
-  if (!sharingScreen) { // Bug 1037438: implement 'never' for screen sharing.
+  // Bug 1037438: implement 'never' for screen sharing.
+  if (!sharingScreen && !sharingAudio) {
     secondaryActions.push({
       label: stringBundle.getString("getUserMedia.never.label"),
       accessKey: stringBundle.getString("getUserMedia.never.accesskey"),
       callback: function () {
         denyRequest(notification.browser, aRequest);
         // Let someone save "Never" for http sites so that they can be stopped from
         // bothering you with doorhangers.
         let perms = Services.perms;
         if (audioDevices.length)
           perms.add(uri, "microphone", perms.DENY_ACTION);
         if (videoDevices.length)
           perms.add(uri, "camera", perms.DENY_ACTION);
       }
     });
   }
 
-  if (aRequest.secure && !sharingScreen) {
+  if (aRequest.secure && !sharingScreen && !sharingAudio) {
     // Don't show the 'Always' action if the connection isn't secure, or for
-    // screen sharing (because we can't guess which window the user wants to
-    // share without prompting).
+    // screen/audio sharing (because we can't guess which window the user wants
+    // to share without prompting).
     secondaryActions.unshift({
       label: stringBundle.getString("getUserMedia.always.label"),
       accessKey: stringBundle.getString("getUserMedia.always.accesskey"),
       callback: function () {
         mainAction.callback(true);
       }
     });
   }
@@ -261,17 +261,18 @@ function prompt(aBrowser, aRequest) {
       if (aTopic == "swapping")
         return true;
 
       let chromeDoc = this.browser.ownerDocument;
 
       if (aTopic == "shown") {
         let PopupNotifications = chromeDoc.defaultView.PopupNotifications;
         let popupId = "Devices";
-        if (requestTypes.length == 1 && requestTypes[0] == "Microphone")
+        if (requestTypes.length == 1 && (requestTypes[0] == "Microphone" ||
+                                         requestTypes[0] == "AudioCapture"))
           popupId = "Microphone";
         if (requestTypes.indexOf("Screen") != -1)
           popupId = "Screen";
         PopupNotifications.panel.firstChild.setAttribute("popupid", "webRTC-share" + popupId);
       }
 
       if (aTopic != "showing")
         return false;
@@ -379,31 +380,35 @@ function prompt(aBrowser, aRequest) {
         menuitem.setAttribute("tooltiptext", deviceName);
         if (type)
           menuitem.setAttribute("devicetype", type);
         menupopup.appendChild(menuitem);
       }
 
       chromeDoc.getElementById("webRTC-selectCamera").hidden = !videoDevices.length || sharingScreen;
       chromeDoc.getElementById("webRTC-selectWindowOrScreen").hidden = !sharingScreen || !videoDevices.length;
-      chromeDoc.getElementById("webRTC-selectMicrophone").hidden = !audioDevices.length;
+      chromeDoc.getElementById("webRTC-selectMicrophone").hidden = !audioDevices.length || sharingAudio;
 
       let camMenupopup = chromeDoc.getElementById("webRTC-selectCamera-menupopup");
       let windowMenupopup = chromeDoc.getElementById("webRTC-selectWindow-menupopup");
       let micMenupopup = chromeDoc.getElementById("webRTC-selectMicrophone-menupopup");
       if (sharingScreen)
         listScreenShareDevices(windowMenupopup, videoDevices);
       else
         listDevices(camMenupopup, videoDevices);
-      listDevices(micMenupopup, audioDevices);
+
+      if (!sharingAudio)
+        listDevices(micMenupopup, audioDevices);
+
       if (requestTypes.length == 2) {
         let stringBundle = chromeDoc.defaultView.gNavigatorBundle;
         if (!sharingScreen)
           addDeviceToList(camMenupopup, stringBundle.getString("getUserMedia.noVideo.label"), "-1");
-        addDeviceToList(micMenupopup, stringBundle.getString("getUserMedia.noAudio.label"), "-1");
+        if (!sharingAudio)
+          addDeviceToList(micMenupopup, stringBundle.getString("getUserMedia.noAudio.label"), "-1");
       }
 
       this.mainAction.callback = function(aRemember) {
         let allowedDevices = [];
         let perms = Services.perms;
         if (videoDevices.length) {
           let listId = "webRTC-select" + (sharingScreen ? "Window" : "Camera") + "-menulist";
           let videoDeviceIndex = chromeDoc.getElementById(listId).value;
@@ -411,23 +416,28 @@ function prompt(aBrowser, aRequest) {
           if (allowCamera)
             allowedDevices.push(videoDeviceIndex);
           if (aRemember) {
             perms.add(uri, "camera",
                       allowCamera ? perms.ALLOW_ACTION : perms.DENY_ACTION);
           }
         }
         if (audioDevices.length) {
-          let audioDeviceIndex = chromeDoc.getElementById("webRTC-selectMicrophone-menulist").value;
-          let allowMic = audioDeviceIndex != "-1";
-          if (allowMic)
-            allowedDevices.push(audioDeviceIndex);
-          if (aRemember) {
-            perms.add(uri, "microphone",
-                      allowMic ? perms.ALLOW_ACTION : perms.DENY_ACTION);
+          if (!sharingAudio) {
+            let audioDeviceIndex = chromeDoc.getElementById("webRTC-selectMicrophone-menulist").value;
+            let allowMic = audioDeviceIndex != "-1";
+            if (allowMic)
+              allowedDevices.push(audioDeviceIndex);
+            if (aRemember) {
+              perms.add(uri, "microphone",
+                        allowMic ? perms.ALLOW_ACTION : perms.DENY_ACTION);
+            }
+          } else {
+            // Only one device possible for audio capture.
+            allowedDevices.push(0);
           }
         }
 
         if (!allowedDevices.length) {
           denyRequest(notification.browser, aRequest);
           return;
         }
 
--- a/configure.in
+++ b/configure.in
@@ -1856,28 +1856,16 @@ Darwin)
     ;;
 esac
 
 if test -n "$MOZ_ENABLE_PROFILER_SPS"; then
     AC_DEFINE(MOZ_ENABLE_PROFILER_SPS)
 fi
 
 dnl ========================================================
-dnl shark
-dnl ========================================================
-MOZ_ARG_ENABLE_BOOL(shark,
-[  --enable-shark          Enable shark remote profiling. Implies --enable-profiling.],
-    MOZ_SHARK=1,
-    MOZ_SHARK= )
-if test -n "$MOZ_SHARK"; then
-    MOZ_PROFILING=1
-    AC_DEFINE(MOZ_SHARK)
-fi
-
-dnl ========================================================
 dnl instruments
 dnl ========================================================
 MOZ_ARG_ENABLE_BOOL(instruments,
 [  --enable-instruments    Enable instruments remote profiling. Implies --enable-profiling.],
     MOZ_INSTRUMENTS=1,
     MOZ_INSTRUMENTS= )
 if test -n "$MOZ_INSTRUMENTS"; then
     MOZ_PROFILING=1
@@ -8637,17 +8625,16 @@ AC_SUBST(MOZ_DEBUG)
 AC_SUBST(MOZ_DEBUG_SYMBOLS)
 AC_SUBST(MOZ_DEBUG_ENABLE_DEFS)
 AC_SUBST(MOZ_DEBUG_DISABLE_DEFS)
 AC_SUBST(MOZ_DEBUG_LDFLAGS)
 AC_SUBST(WARNINGS_AS_ERRORS)
 AC_SUBST(MOZ_EXTENSIONS)
 AC_SUBST(MOZ_ENABLE_PROFILER_SPS)
 AC_SUBST(MOZ_JPROF)
-AC_SUBST(MOZ_SHARK)
 AC_SUBST(MOZ_INSTRUMENTS)
 AC_SUBST(MOZ_CALLGRIND)
 AC_SUBST(MOZ_VTUNE)
 AC_SUBST(MOZ_PROFILING)
 AC_SUBST(LIBICONV)
 AC_SUBST(MOZ_PLACES)
 AC_SUBST(MOZ_SOCIAL)
 AC_SUBST(MOZ_TOOLKIT_SEARCH)
--- a/docshell/test/browser/browser_timelineMarkers-frame-05.js
+++ b/docshell/test/browser/browser_timelineMarkers-frame-05.js
@@ -89,17 +89,17 @@ if (Services.prefs.getBoolPref("javascri
       resolvePromise(resolver);
     },
     check: function(markers) {
       markers = markers.filter(m => m.name == "ConsoleTime");
       ok(markers.length > 0, "Promise marker includes stack");
 
       let frame = markers[0].endStack;
       ok(frame.parent.asyncParent !== null, "Parent frame has async parent");
-      is(frame.parent.asyncParent.asyncCause, "Promise",
+      is(frame.parent.asyncParent.asyncCause, "promise callback",
          "Async parent has correct cause");
       is(frame.parent.asyncParent.functionDisplayName, "makePromise",
          "Async parent has correct function name");
     }
   });
 }
 
 timelineContentTest(TESTS);
--- a/dom/audiochannel/AudioChannelAgent.cpp
+++ b/dom/audiochannel/AudioChannelAgent.cpp
@@ -30,16 +30,17 @@ NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(
   NS_INTERFACE_MAP_ENTRY(nsISupports)
 NS_INTERFACE_MAP_END
 
 NS_IMPL_CYCLE_COLLECTING_ADDREF(AudioChannelAgent)
 NS_IMPL_CYCLE_COLLECTING_RELEASE(AudioChannelAgent)
 
 AudioChannelAgent::AudioChannelAgent()
   : mAudioChannelType(AUDIO_AGENT_CHANNEL_ERROR)
+  , mInnerWindowID(0)
   , mIsRegToService(false)
 {
 }
 
 AudioChannelAgent::~AudioChannelAgent()
 {
   Shutdown();
 }
@@ -99,16 +100,20 @@ AudioChannelAgent::InitInternal(nsIDOMWi
 
   if (mAudioChannelType != AUDIO_AGENT_CHANNEL_ERROR ||
       aChannelType > AUDIO_AGENT_CHANNEL_PUBLICNOTIFICATION ||
       aChannelType < AUDIO_AGENT_CHANNEL_NORMAL) {
     return NS_ERROR_FAILURE;
   }
 
   if (aWindow) {
+    nsCOMPtr<nsPIDOMWindow> pInnerWindow = do_QueryInterface(aWindow);
+    MOZ_ASSERT(pInnerWindow->IsInnerWindow());
+    mInnerWindowID = pInnerWindow->WindowID();
+
     nsCOMPtr<nsIDOMWindow> topWindow;
     aWindow->GetScriptableTop(getter_AddRefs(topWindow));
     mWindow = do_QueryInterface(topWindow);
     if (mWindow) {
       mWindow = mWindow->GetOuterWindow();
     }
   }
 
@@ -186,8 +191,23 @@ AudioChannelAgent::WindowVolumeChanged()
   callback->WindowVolumeChanged(volume, muted);
 }
 
 uint64_t
 AudioChannelAgent::WindowID() const
 {
   return mWindow ? mWindow->WindowID() : 0;
 }
+
+void
+AudioChannelAgent::WindowAudioCaptureChanged(uint64_t aInnerWindowID)
+{
+  if (aInnerWindowID != mInnerWindowID) {
+    return;
+  }
+
+  nsCOMPtr<nsIAudioChannelAgentCallback> callback = GetCallback();
+  if (!callback) {
+    return;
+  }
+
+  callback->WindowAudioCaptureChanged();
+}
--- a/dom/audiochannel/AudioChannelAgent.h
+++ b/dom/audiochannel/AudioChannelAgent.h
@@ -29,16 +29,17 @@ public:
   NS_DECL_CYCLE_COLLECTING_ISUPPORTS
   NS_DECL_NSIAUDIOCHANNELAGENT
 
   NS_DECL_CYCLE_COLLECTION_CLASS(AudioChannelAgent)
 
   AudioChannelAgent();
 
   void WindowVolumeChanged();
+  void WindowAudioCaptureChanged(uint64_t aInnerWindowID);
 
   nsPIDOMWindow* Window() const
   {
     return mWindow;
   }
 
   uint64_t WindowID() const;
 
@@ -56,16 +57,17 @@ private:
   void Shutdown();
 
   nsCOMPtr<nsPIDOMWindow> mWindow;
   nsCOMPtr<nsIAudioChannelAgentCallback> mCallback;
 
   nsWeakPtr mWeakCallback;
 
   int32_t mAudioChannelType;
+  uint64_t mInnerWindowID;
   bool mIsRegToService;
 };
 
 } // namespace dom
 } // namespace mozilla
 
 
 #endif
--- a/dom/audiochannel/AudioChannelService.cpp
+++ b/dom/audiochannel/AudioChannelService.cpp
@@ -541,16 +541,48 @@ AudioChannelService::RefreshAgentsVolume
 
   nsTObserverArray<AudioChannelAgent*>::ForwardIterator
     iter(winData->mAgents);
   while (iter.HasMore()) {
     iter.GetNext()->WindowVolumeChanged();
   }
 }
 
+void
+AudioChannelService::RefreshAgentsCapture(nsPIDOMWindow* aWindow,
+                                          uint64_t aInnerWindowID)
+{
+  MOZ_ASSERT(aWindow);
+  MOZ_ASSERT(aWindow->IsOuterWindow());
+
+  nsCOMPtr<nsIDOMWindow> topWindow;
+  aWindow->GetScriptableTop(getter_AddRefs(topWindow));
+  nsCOMPtr<nsPIDOMWindow> pTopWindow = do_QueryInterface(topWindow);
+  if (!pTopWindow) {
+    return;
+  }
+
+  AudioChannelWindow* winData = GetWindowData(pTopWindow->WindowID());
+
+  // This can happen, but only during shutdown, because the the outer window
+  // changes ScriptableTop, so that its ID is different.
+  // In this case either we are capturing, and it's too late because the window
+  // has been closed anyways, or we are un-capturing, and everything has already
+  // been cleaned up by the HTMLMediaElements or the AudioContexts.
+  if (!winData) {
+    return;
+  }
+
+  nsTObserverArray<AudioChannelAgent*>::ForwardIterator
+    iter(winData->mAgents);
+  while (iter.HasMore()) {
+    iter.GetNext()->WindowAudioCaptureChanged(aInnerWindowID);
+  }
+}
+
 /* static */ const nsAttrValue::EnumTable*
 AudioChannelService::GetAudioChannelTable()
 {
   return kMozAudioChannelAttributeTable;
 }
 
 /* static */ AudioChannel
 AudioChannelService::GetAudioChannel(const nsAString& aChannel)
--- a/dom/audiochannel/AudioChannelService.h
+++ b/dom/audiochannel/AudioChannelService.h
@@ -97,16 +97,24 @@ public:
    */
   virtual void SetDefaultVolumeControlChannel(int32_t aChannel,
                                               bool aVisible);
 
   bool AnyAudioChannelIsActive();
 
   void RefreshAgentsVolume(nsPIDOMWindow* aWindow);
 
+  // This method needs to know the inner window that wants to capture audio. We
+  // group agents per top outer window, but we can have multiple innerWindow per
+  // top outerWindow (subiframes, etc.) and we have to identify all the agents
+  // just for a particular innerWindow.
+  void RefreshAgentsCapture(nsPIDOMWindow* aWindow,
+                            uint64_t aInnerWindowID);
+
+
 #ifdef MOZ_WIDGET_GONK
   void RegisterSpeakerManager(SpeakerManagerService* aSpeakerManager)
   {
     if (!mSpeakerManager.Contains(aSpeakerManager)) {
       mSpeakerManager.AppendElement(aSpeakerManager);
     }
   }
 
--- a/dom/audiochannel/nsIAudioChannelAgent.idl
+++ b/dom/audiochannel/nsIAudioChannelAgent.idl
@@ -1,23 +1,28 @@
 /* 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 "nsISupports.idl"
 
 interface nsIDOMWindow;
 
-[uuid(4f537c88-3722-4946-9a09-ce559fa0591d)]
+[uuid(5fe83b24-38b9-4901-a4a1-d1bd57d3fe18)]
 interface nsIAudioChannelAgentCallback : nsISupports
 {
   /**
    * Notified when the window volume/mute is changed
    */
   void windowVolumeChanged(in float aVolume, in bool aMuted);
+
+  /**
+   * Notified when the capture state is changed.
+   */
+  void windowAudioCaptureChanged();
 };
 
 /**
  * This interface provides an agent for gecko components to participate
  * in the audio channel service. Gecko components are responsible for
  *   1. Indicating what channel type they are using (via the init() member
  *      function).
  *   2. Before playing, checking the playable status of the channel.
--- a/dom/base/nsDocumentWarningList.h
+++ b/dom/base/nsDocumentWarningList.h
@@ -5,9 +5,9 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 // IWYU pragma: private, include "nsIDocument.h"
 
 /*
  * This file contains the list of document DOM operations warnings.  It is
  * designed to be used as input to the C preprocessor *only*.
  */
 
-DOCUMENT_WARNING(WillChangeBudget)
+DOCUMENT_WARNING(WillChangeOverBudgetIgnored)
--- a/dom/base/nsGlobalWindow.cpp
+++ b/dom/base/nsGlobalWindow.cpp
@@ -559,17 +559,17 @@ nsPIDOMWindow::nsPIDOMWindow(nsPIDOMWind
 : mFrameElement(nullptr), mDocShell(nullptr), mModalStateDepth(0),
   mRunningTimeout(nullptr), mMutationBits(0), mIsDocumentLoaded(false),
   mIsHandlingResizeEvent(false), mIsInnerWindow(aOuterWindow != nullptr),
   mMayHavePaintEventListener(false), mMayHaveTouchEventListener(false),
   mMayHaveMouseEnterLeaveEventListener(false),
   mMayHavePointerEnterLeaveEventListener(false),
   mIsModalContentWindow(false),
   mIsActive(false), mIsBackground(false),
-  mAudioMuted(false), mAudioVolume(1.0),
+  mAudioMuted(false), mAudioVolume(1.0), mAudioCaptured(false),
   mDesktopModeViewport(false), mInnerWindow(nullptr),
   mOuterWindow(aOuterWindow),
   // Make sure no actual window ends up with mWindowID == 0
   mWindowID(NextWindowID()), mHasNotifiedGlobalCreated(false),
   mMarkedCCGeneration(0), mServiceWorkersTestingEnabled(false)
  {}
 
 nsPIDOMWindow::~nsPIDOMWindow() {}
@@ -3740,16 +3740,36 @@ nsPIDOMWindow::SetAudioVolume(float aVol
 
 void
 nsPIDOMWindow::RefreshMediaElements()
 {
   nsRefPtr<AudioChannelService> service = AudioChannelService::GetOrCreate();
   service->RefreshAgentsVolume(GetOuterWindow());
 }
 
+bool
+nsPIDOMWindow::GetAudioCaptured() const
+{
+  MOZ_ASSERT(IsInnerWindow());
+  return mAudioCaptured;
+}
+
+nsresult
+nsPIDOMWindow::SetAudioCapture(bool aCapture)
+{
+  MOZ_ASSERT(IsInnerWindow());
+
+  mAudioCaptured = aCapture;
+
+  nsRefPtr<AudioChannelService> service = AudioChannelService::GetOrCreate();
+  service->RefreshAgentsCapture(GetOuterWindow(), mWindowID);
+
+  return NS_OK;
+}
+
 // nsISpeechSynthesisGetter
 
 #ifdef MOZ_WEBSPEECH
 SpeechSynthesis*
 nsGlobalWindow::GetSpeechSynthesis(ErrorResult& aError)
 {
   MOZ_RELEASE_ASSERT(IsInnerWindow());
 
@@ -5528,17 +5548,17 @@ nsGlobalWindow::RequestAnimationFrame(JS
 {
   FORWARD_TO_INNER(RequestAnimationFrame, (aCallback, cx, aHandle), NS_ERROR_UNEXPECTED);
   if (!aCallback.isObject() || !JS::IsCallable(&aCallback.toObject())) {
     return NS_ERROR_INVALID_ARG;
   }
 
   JS::Rooted<JSObject*> callbackObj(cx, &aCallback.toObject());
   nsRefPtr<FrameRequestCallback> callback =
-    new FrameRequestCallback(callbackObj, GetIncumbentGlobal());
+    new FrameRequestCallback(cx, callbackObj, GetIncumbentGlobal());
 
   ErrorResult rv;
   *aHandle = RequestAnimationFrame(*callback, rv);
 
   return rv.StealNSResult();
 }
 
 void
--- a/dom/base/nsPIDOMWindow.h
+++ b/dom/base/nsPIDOMWindow.h
@@ -180,16 +180,19 @@ public:
 
   // Audio API
   bool GetAudioMuted() const;
   void SetAudioMuted(bool aMuted);
 
   float GetAudioVolume() const;
   nsresult SetAudioVolume(float aVolume);
 
+  bool GetAudioCaptured() const;
+  nsresult SetAudioCapture(bool aCapture);
+
   virtual void SetServiceWorkersTestingEnabled(bool aEnabled)
   {
     MOZ_ASSERT(IsOuterWindow());
     mServiceWorkersTestingEnabled = aEnabled;
   }
 
   bool GetServiceWorkersTestingEnabled()
   {
@@ -817,16 +820,18 @@ protected:
   // Tracks whether our docshell is active.  If it is, mIsBackground
   // is false.  Too bad we have so many different concepts of
   // "active".  Only used on outer windows.
   bool                   mIsBackground;
 
   bool                   mAudioMuted;
   float                  mAudioVolume;
 
+  bool                   mAudioCaptured;
+
   // current desktop mode flag.
   bool                   mDesktopModeViewport;
 
   // And these are the references between inner and outer windows.
   nsPIDOMWindow* MOZ_NON_OWNING_REF mInnerWindow;
   nsCOMPtr<nsPIDOMWindow> mOuterWindow;
 
   // the element within the document that is currently focused when this
--- a/dom/base/test/mochitest.ini
+++ b/dom/base/test/mochitest.ini
@@ -243,16 +243,18 @@ support-files =
   referrer_helper.js
   referrer_testserver.sjs
 
 [test_anonymousContent_api.html]
 [test_anonymousContent_append_after_reflow.html]
 [test_anonymousContent_insert.html]
 [test_anonymousContent_manipulate_content.html]
 [test_appname_override.html]
+[test_async_setTimeout_stack.html]
+[test_async_setTimeout_stack_across_globals.html]
 [test_audioWindowUtils.html]
 [test_audioNotification.html]
 skip-if = buildapp == 'mulet'
 [test_audioNotificationStopOnNavigation.html]
 skip-if = buildapp == 'mulet'
 [test_bug1091883.html]
 [test_bug116083.html]
 [test_bug793311.html]
new file mode 100644
--- /dev/null
+++ b/dom/base/test/test_async_setTimeout_stack.html
@@ -0,0 +1,60 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1142577
+-->
+<head>
+  <meta charset="utf-8">
+  <title>Test for Bug 1142577 - Async stacks for setTimeout</title>
+  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+  <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1142577">Mozilla Bug 1142577</a>
+  <pre id="stack"></pre>
+  <script type="application/javascript">
+  SimpleTest.waitForExplicitFinish();
+  SimpleTest.requestFlakyTimeout("Testing async stacks across setTimeout");
+
+  function getFunctionName(frame) {
+    return frame.slice(0, frame.indexOf("@"));
+  }
+
+  function a() { b() }
+  function b() { c() }
+  function c() { setTimeout(d, 1) }
+  function d() { e() }
+  function e() { f() }
+  function f() { setTimeout(g, 1) }
+  function g() { h() }
+  function h() { i() }
+  function i() {
+    var stackString = Error().stack;
+    document.getElementById("stack").textContent = stackString;
+
+    var frames = stackString
+            .split("\n")
+            .map(getFunctionName)
+            .filter(function (name) { return !!name; });
+
+    is(frames[0], "i");
+    is(frames[1], "h");
+    is(frames[2], "g");
+    is(frames[3], "setTimeout handler*SimpleTest_setTimeoutShim");
+    is(frames[4], "f");
+    is(frames[5], "e");
+    is(frames[6], "d");
+    is(frames[7], "setTimeout handler*SimpleTest_setTimeoutShim");
+    is(frames[8], "c");
+    is(frames[9], "b");
+    is(frames[10], "a");
+
+    SimpleTest.finish();
+  }
+
+  SpecialPowers.pushPrefEnv(
+    {"set": [['javascript.options.asyncstack', true]]},
+    a);
+  </script>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/base/test/test_async_setTimeout_stack_across_globals.html
@@ -0,0 +1,60 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1142577
+-->
+<head>
+  <meta charset="utf-8">
+  <title>Test for Bug 1142577 - Async stacks for setTimeout</title>
+  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+  <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1142577">Mozilla Bug 1142577</a>
+  <pre id="stack"></pre>
+  <iframe id="iframe"></iframe>
+  <script type="application/javascript">
+  SimpleTest.waitForExplicitFinish();
+
+  var otherGlobal = document.getElementById("iframe").contentWindow;
+
+  function getFunctionName(frame) {
+    return frame.slice(0, frame.indexOf("@"));
+  }
+
+  function a() { b() }
+  function b() { c() }
+  function c() { otherGlobal.setTimeout(d, 1) }
+  function d() { e() }
+  function e() { f() }
+  function f() { otherGlobal.setTimeout(g, 1) }
+  function g() { h() }
+  function h() { i() }
+  function i() {
+    var stackString = Error().stack;
+    document.getElementById("stack").textContent = stackString;
+
+    var frames = stackString
+            .split("\n")
+            .map(getFunctionName)
+            .filter(function (name) { return !!name; });
+
+    is(frames[0], "i");
+    is(frames[1], "h");
+    is(frames[2], "g");
+    is(frames[3], "setTimeout handler*f");
+    is(frames[4], "e");
+    is(frames[5], "d");
+    is(frames[6], "setTimeout handler*c");
+    is(frames[7], "b");
+    is(frames[8], "a");
+
+    SimpleTest.finish();
+  }
+
+  SpecialPowers.pushPrefEnv(
+    {"set": [['javascript.options.asyncstack', true]]},
+    a);
+  </script>
+</body>
+</html>
--- a/dom/bindings/CallbackFunction.h
+++ b/dom/bindings/CallbackFunction.h
@@ -20,19 +20,20 @@
 #include "mozilla/dom/CallbackObject.h"
 
 namespace mozilla {
 namespace dom {
 
 class CallbackFunction : public CallbackObject
 {
 public:
-  explicit CallbackFunction(JS::Handle<JSObject*> aCallable,
+  // See CallbackObject for an explanation of the arguments.
+  explicit CallbackFunction(JSContext* aCx, JS::Handle<JSObject*> aCallable,
                             nsIGlobalObject* aIncumbentGlobal)
-    : CallbackObject(aCallable, aIncumbentGlobal)
+    : CallbackObject(aCx, aCallable, aIncumbentGlobal)
   {
   }
 
   JS::Handle<JSObject*> Callable() const
   {
     return Callback();
   }
 
--- a/dom/bindings/CallbackInterface.h
+++ b/dom/bindings/CallbackInterface.h
@@ -19,19 +19,20 @@
 #include "mozilla/dom/CallbackObject.h"
 
 namespace mozilla {
 namespace dom {
 
 class CallbackInterface : public CallbackObject
 {
 public:
-  explicit CallbackInterface(JS::Handle<JSObject*> aCallback,
+  // See CallbackObject for an explanation of the arguments.
+  explicit CallbackInterface(JSContext* aCx, JS::Handle<JSObject*> aCallback,
                              nsIGlobalObject *aIncumbentGlobal)
-    : CallbackObject(aCallback, aIncumbentGlobal)
+    : CallbackObject(aCx, aCallback, aIncumbentGlobal)
   {
   }
 
 protected:
   bool GetCallableProperty(JSContext* cx, JS::Handle<jsid> aPropId,
                            JS::MutableHandle<JS::Value> aCallable);
 
 };
--- a/dom/bindings/CallbackObject.cpp
+++ b/dom/bindings/CallbackObject.cpp
@@ -38,16 +38,17 @@ NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(Ca
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mIncumbentGlobal)
 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(CallbackObject)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE_SCRIPT_OBJECTS
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mIncumbentGlobal)
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
 NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(CallbackObject)
   NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mCallback)
+  NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mCreationStack)
   NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mIncumbentJSGlobal)
 NS_IMPL_CYCLE_COLLECTION_TRACE_END
 
 CallbackObject::CallSetup::CallSetup(CallbackObject* aCallback,
                                      ErrorResult& aRv,
                                      const char* aExecutionReason,
                                      ExceptionHandling aExceptionHandling,
                                      JSCompartment* aCompartment,
@@ -164,16 +165,26 @@ CallbackObject::CallSetup::CallSetup(Cal
     bool allowed = nsContentUtils::GetSecurityManager()->
       ScriptAllowed(js::GetGlobalForObjectCrossCompartment(realCallback));
 
     if (!allowed) {
       return;
     }
   }
 
+  mAsyncStack.emplace(cx, aCallback->GetCreationStack());
+  if (*mAsyncStack) {
+    mAsyncCause.emplace(cx, JS_NewStringCopyZ(cx, aExecutionReason));
+    if (*mAsyncCause) {
+      mAsyncStackSetter.emplace(cx, *mAsyncStack, *mAsyncCause);
+    } else {
+      JS_ClearPendingException(cx);
+    }
+  }
+
   // Enter the compartment of our callback, so we can actually work with it.
   //
   // Note that if the callback is a wrapper, this will not be the same
   // compartment that we ended up in with mAutoEntryScript above, because the
   // entry point is based off of the unwrapped callback (realCallback).
   mAc.emplace(cx, *mRootedCallable);
 
   // And now we're ready to go.
--- a/dom/bindings/CallbackObject.h
+++ b/dom/bindings/CallbackObject.h
@@ -25,16 +25,17 @@
 #include "mozilla/ErrorResult.h"
 #include "mozilla/HoldDropJSObjects.h"
 #include "mozilla/MemoryReporting.h"
 #include "mozilla/dom/ScriptSettings.h"
 #include "nsContentUtils.h"
 #include "nsWrapperCache.h"
 #include "nsJSEnvironment.h"
 #include "xpcpublic.h"
+#include "jsapi.h"
 
 namespace mozilla {
 namespace dom {
 
 #define DOM_CALLBACKOBJECT_IID \
 { 0xbe74c190, 0x6d76, 0x4991, \
  { 0x84, 0xb9, 0x65, 0x06, 0x99, 0xe6, 0x93, 0x2b } }
 
@@ -44,28 +45,49 @@ public:
   NS_DECLARE_STATIC_IID_ACCESSOR(DOM_CALLBACKOBJECT_IID)
 
   NS_DECL_CYCLE_COLLECTING_ISUPPORTS
   NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(CallbackObject)
 
   // The caller may pass a global object which will act as an override for the
   // incumbent script settings object when the callback is invoked (overriding
   // the entry point computed from aCallback). If no override is required, the
-  // caller should pass null.
-  explicit CallbackObject(JS::Handle<JSObject*> aCallback, nsIGlobalObject *aIncumbentGlobal)
+  // caller should pass null.  |aCx| is used to capture the current
+  // stack, which is later used as an async parent when the callback
+  // is invoked.  aCx can be nullptr, in which case no stack is
+  // captured.
+  explicit CallbackObject(JSContext* aCx, JS::Handle<JSObject*> aCallback,
+                          nsIGlobalObject *aIncumbentGlobal)
   {
-    Init(aCallback, aIncumbentGlobal);
+    if (aCx && JS::RuntimeOptionsRef(aCx).asyncStack()) {
+      JS::RootedObject stack(aCx);
+      if (!JS::CaptureCurrentStack(aCx, &stack)) {
+        JS_ClearPendingException(aCx);
+      }
+      Init(aCallback, stack, aIncumbentGlobal);
+    } else {
+      Init(aCallback, nullptr, aIncumbentGlobal);
+    }
   }
 
   JS::Handle<JSObject*> Callback() const
   {
     JS::ExposeObjectToActiveJS(mCallback);
     return CallbackPreserveColor();
   }
 
+  JSObject* GetCreationStack() const
+  {
+    JSObject* result = mCreationStack;
+    if (result) {
+      JS::ExposeObjectToActiveJS(result);
+    }
+    return result;
+  }
+
   /*
    * This getter does not change the color of the JSObject meaning that the
    * object returned is not guaranteed to be kept alive past the next CC.
    *
    * This should only be called if you are certain that the return value won't
    * be passed into a JS API function and that it won't be stored without being
    * rooted (or otherwise signaling the stored value to the CC).
    */
@@ -103,57 +125,62 @@ public:
 protected:
   virtual ~CallbackObject()
   {
     DropJSObjects();
   }
 
   explicit CallbackObject(CallbackObject* aCallbackObject)
   {
-    Init(aCallbackObject->mCallback, aCallbackObject->mIncumbentGlobal);
+    Init(aCallbackObject->mCallback, aCallbackObject->mCreationStack,
+         aCallbackObject->mIncumbentGlobal);
   }
 
   bool operator==(const CallbackObject& aOther) const
   {
     JSObject* thisObj =
       js::UncheckedUnwrap(CallbackPreserveColor());
     JSObject* otherObj =
       js::UncheckedUnwrap(aOther.CallbackPreserveColor());
     return thisObj == otherObj;
   }
 
 private:
-  inline void Init(JSObject* aCallback, nsIGlobalObject* aIncumbentGlobal)
+  inline void Init(JSObject* aCallback, JSObject* aCreationStack,
+                   nsIGlobalObject* aIncumbentGlobal)
   {
     MOZ_ASSERT(aCallback && !mCallback);
     // Set script objects before we hold, on the off chance that a GC could
     // somehow happen in there... (which would be pretty odd, granted).
     mCallback = aCallback;
+    mCreationStack = aCreationStack;
     if (aIncumbentGlobal) {
       mIncumbentGlobal = aIncumbentGlobal;
       mIncumbentJSGlobal = aIncumbentGlobal->GetGlobalJSObject();
     }
     mozilla::HoldJSObjects(this);
   }
 
   CallbackObject(const CallbackObject&) = delete;
   CallbackObject& operator =(const CallbackObject&) = delete;
 
 protected:
   void DropJSObjects()
   {
     MOZ_ASSERT_IF(mIncumbentJSGlobal, mCallback);
     if (mCallback) {
       mCallback = nullptr;
+      mCreationStack = nullptr;
       mIncumbentJSGlobal = nullptr;
       mozilla::DropJSObjects(this);
     }
   }
 
   JS::Heap<JSObject*> mCallback;
+  JS::Heap<JSObject*> mCreationStack;
   // Ideally, we'd just hold a reference to the nsIGlobalObject, since that's
   // what we need to pass to AutoIncumbentScript. Unfortunately, that doesn't
   // hold the actual JS global alive. So we maintain an additional pointer to
   // the JS global itself so that we can trace it.
   //
   // At some point we should consider trying to make native globals hold their
   // scripted global alive, at which point we can get rid of the duplication
   // here.
@@ -204,16 +231,21 @@ protected:
     // And now members whose construction/destruction order we need to control.
     Maybe<AutoEntryScript> mAutoEntryScript;
     Maybe<AutoIncumbentScript> mAutoIncumbentScript;
 
     // Constructed the rooter within the scope of mCxPusher above, so that it's
     // always within a request during its lifetime.
     Maybe<JS::Rooted<JSObject*> > mRootedCallable;
 
+    // Members which are used to set the async stack.
+    Maybe<JS::Rooted<JSObject*>> mAsyncStack;
+    Maybe<JS::Rooted<JSString*>> mAsyncCause;
+    Maybe<JS::AutoSetAsyncStackForNewCalls> mAsyncStackSetter;
+
     // Can't construct a JSAutoCompartment without a JSContext either.  Also,
     // Put mAc after mAutoEntryScript so that we exit the compartment before
     // we pop the JSContext. Though in practice we'll often manually order
     // those two things.
     Maybe<JSAutoCompartment> mAc;
 
     // An ErrorResult to possibly re-throw exceptions on and whether
     // we should re-throw them.
--- a/dom/bindings/Codegen.py
+++ b/dom/bindings/Codegen.py
@@ -3971,17 +3971,17 @@ class FailureFatalCastableObjectUnwrappe
             isCallbackReturnValue)
 
 
 class CGCallbackTempRoot(CGGeneric):
     def __init__(self, name):
         define = dedent("""
             { // Scope for tempRoot
               JS::Rooted<JSObject*> tempRoot(cx, &${val}.toObject());
-              ${declName} = new %s(tempRoot, mozilla::dom::GetIncumbentGlobal());
+              ${declName} = new %s(cx, tempRoot, mozilla::dom::GetIncumbentGlobal());
             }
             """) % name
         CGGeneric.__init__(self, define=define)
 
 
 class JSToNativeConversionInfo():
     """
     An object representing information about a JS-to-native conversion.
@@ -13903,17 +13903,17 @@ class CGJSImplClass(CGBindingImplClass):
             decorators = ""
             # We need a protected virtual destructor our subclasses can use
             destructor = ClassDestructor(virtual=True, visibility="protected")
         else:
             decorators = "final"
             destructor = ClassDestructor(virtual=False, visibility="private")
 
         baseConstructors = [
-            ("mImpl(new %s(aJSImplObject, /* aIncumbentGlobal = */ nullptr))" %
+            ("mImpl(new %s(nullptr, aJSImplObject, /* aIncumbentGlobal = */ nullptr))" %
              jsImplName(descriptor.name)),
             "mParent(aParent)"]
         parentInterface = descriptor.interface.parent
         while parentInterface:
             if parentInterface.isJSImplemented():
                 baseConstructors.insert(
                     0, "%s(aJSImplObject, aParent)" % parentClass)
                 break
@@ -14048,23 +14048,24 @@ class CGCallback(CGClass):
         if (not self.idlObject.isInterface() and
             not self.idlObject._treatNonObjectAsNull):
             body = "MOZ_ASSERT(JS::IsCallable(mCallback));\n"
         else:
             # Not much we can assert about it, other than not being null, and
             # CallbackObject does that already.
             body = ""
         return [ClassConstructor(
-            [Argument("JS::Handle<JSObject*>", "aCallback"),
+            [Argument("JSContext*", "aCx"),
+             Argument("JS::Handle<JSObject*>", "aCallback"),
              Argument("nsIGlobalObject*", "aIncumbentGlobal")],
             bodyInHeader=True,
             visibility="public",
             explicit=True,
             baseConstructors=[
-                "%s(aCallback, aIncumbentGlobal)" % self.baseName,
+                "%s(aCx, aCallback, aIncumbentGlobal)" % self.baseName,
             ],
             body=body)]
 
     def getMethodImpls(self, method):
         assert method.needThisHandling
         args = list(method.args)
         # Strip out the JSContext*/JSObject* args
         # that got added.
--- a/dom/bindings/test/mochitest.ini
+++ b/dom/bindings/test/mochitest.ini
@@ -3,16 +3,17 @@ support-files =
   file_InstanceOf.html
   file_bug707564.html
   file_bug775543.html
   file_document_location_set_via_xray.html
   file_dom_xrays.html
   file_proxies_via_xray.html
   forOf_iframe.html
 
+[test_async_stacks.html]
 [test_ByteString.html]
 [test_InstanceOf.html]
 [test_bug560072.html]
 [test_bug707564.html]
 [test_bug742191.html]
 [test_bug759621.html]
 [test_bug773326.html]
 [test_bug788369.html]
new file mode 100644
--- /dev/null
+++ b/dom/bindings/test/test_async_stacks.html
@@ -0,0 +1,108 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1148593
+-->
+<head>
+  <meta charset="utf-8">
+  <title>Test for Bug 1148593</title>
+  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+  <script type="application/javascript">
+
+  /** Test for Bug 1148593 **/
+
+  SimpleTest.waitForExplicitFinish();
+
+  var TESTS;
+
+  function nextTest() {
+    var t = TESTS.pop();
+    if (t) {
+      t();
+    } else {
+      SimpleTest.finish();
+    }
+  }
+
+  function checkStack(functionName) {
+    try {
+      noSuchFunction();
+    } catch (e) {
+      ok(e.stack.indexOf(functionName) >= 0, "stack includes " + functionName);
+    }
+    nextTest();
+  }
+
+  function eventListener() {
+    checkStack("registerEventListener");
+  }
+  function registerEventListener(link) {
+    link.onload = eventListener;
+  }
+  function eventTest() {
+    var link = document.createElement("link");
+    link.rel = "stylesheet";
+    link.href = "data:text/css,";
+    registerEventListener(link);
+    document.body.appendChild(link);
+  }
+
+  function xhrListener() {
+    checkStack("xhrTest");
+  }
+  function xhrTest() {
+    var ourFile = location.href;
+    var x = new XMLHttpRequest();
+    x.onload = xhrListener;
+    x.open("get", ourFile, true);
+    x.send();
+  }
+
+  function rafListener() {
+    checkStack("rafTest");
+  }
+  function rafTest() {
+    requestAnimationFrame(rafListener);
+  }
+
+  var intervalId;
+  function intervalHandler() {
+    clearInterval(intervalId);
+    checkStack("intervalTest");
+  }
+  function intervalTest() {
+    intervalId = setInterval(intervalHandler, 5);
+  }
+
+  function postMessageHandler(ev) {
+    ev.stopPropagation();
+    checkStack("postMessageTest");
+  }
+  function postMessageTest() {
+    window.addEventListener("message", postMessageHandler, true);
+    window.postMessage("whatever", "*");
+  }
+
+  function runTests() {
+    TESTS = [postMessageTest, intervalTest, rafTest, xhrTest, eventTest];
+    nextTest();
+  }
+
+  addLoadEvent(function() {
+    SpecialPowers.pushPrefEnv(
+      {"set": [['javascript.options.asyncstack', true]]},
+      runTests);
+  });
+  </script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1148593">Mozilla Bug 1148593</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+</body>
+</html>
--- a/dom/bindings/test/test_exception_options_from_jsimplemented.html
+++ b/dom/bindings/test/test_exception_options_from_jsimplemented.html
@@ -10,129 +10,145 @@ https://bugzilla.mozilla.org/show_bug.cg
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
   <script type="application/javascript">
 
   /** Test for Bug 1107592 **/
 
   SimpleTest.waitForExplicitFinish();
 
   function doTest() {
+    var file = location.href;
+    var asyncFrame;
+    /* Async parent frames from pushPrefEnv don't show up in e10s.  */
+    var isE10S = !SpecialPowers.Services.wm.getMostRecentWindow("navigator:browser");
+    if (!isE10S && SpecialPowers.getBoolPref("javascript.options.asyncstack")) {
+      asyncFrame = `Async*@${file}:153:1
+`;
+    } else {
+      asyncFrame = "";
+    }
+
     var t = new TestInterfaceJS();
     try {
       t.testThrowError();
     } catch (e) {
       ok(e instanceof Error, "Should have an Error here");
       ok(!(e instanceof DOMException), "Should not have DOMException here");
       ok(!("code" in e), "Should not have a 'code' property");
       is(e.name, "Error", "Should not have an interesting name here");
       is(e.message, "We are an Error", "Should have the right message");
       is(e.stack,
-         "doTest@http://mochi.test:8888/tests/dom/bindings/test/test_exception_options_from_jsimplemented.html:20:7\n",
+         `doTest@${file}:31:7
+${asyncFrame}`,
          "Exception stack should still only show our code");
       is(e.fileName,
-         "http://mochi.test:8888/tests/dom/bindings/test/test_exception_options_from_jsimplemented.html",
+         file,
          "Should have the right file name");
-      is(e.lineNumber, 20, "Should have the right line number");
+      is(e.lineNumber, 31, "Should have the right line number");
       is(e.columnNumber, 7, "Should have the right column number");
     }
 
     try {
       t.testThrowDOMException();
     } catch (e) {
       ok(e instanceof Error, "Should also have an Error here");
       ok(e instanceof DOMException, "Should have DOMException here");
       is(e.name, "NotSupportedError", "Should have the right name here");
       is(e.message, "We are a DOMException",
          "Should also have the right message");
       is(e.code, DOMException.NOT_SUPPORTED_ERR,
          "Should have the right 'code'");
       is(e.stack,
-         "doTest@http://mochi.test:8888/tests/dom/bindings/test/test_exception_options_from_jsimplemented.html:38:7\n",
+         `doTest@${file}:50:7
+${asyncFrame}`,
          "Exception stack should still only show our code");
       is(e.filename,
-         "http://mochi.test:8888/tests/dom/bindings/test/test_exception_options_from_jsimplemented.html",
+         file,
          "Should still have the right file name");
-      is(e.lineNumber, 38, "Should still have the right line number");
+      is(e.lineNumber, 50, "Should still have the right line number");
       todo_isnot(e.columnNumber, 0,
                  "No column number support for DOMException yet");
     }
 
     try {
       t.testThrowTypeError();
     } catch (e) {
       ok(e instanceof TypeError, "Should have a TypeError here");
       ok(!(e instanceof DOMException), "Should not have DOMException here (2)");
       ok(!("code" in e), "Should not have a 'code' property (2)");
       is(e.name, "TypeError", "Should be named TypeError");
       is(e.message, "We are a TypeError",
          "Should also have the right message (2)");
       is(e.stack,
-         "doTest@http://mochi.test:8888/tests/dom/bindings/test/test_exception_options_from_jsimplemented.html:59:7\n",
+         `doTest@${file}:72:7
+${asyncFrame}`,
          "Exception stack for TypeError should only show our code");
       is(e.fileName,
-         "http://mochi.test:8888/tests/dom/bindings/test/test_exception_options_from_jsimplemented.html",
+         file,
          "Should still have the right file name for TypeError");
-      is(e.lineNumber, 59, "Should still have the right line number for TypeError");
+      is(e.lineNumber, 72, "Should still have the right line number for TypeError");
       is(e.columnNumber, 7, "Should have the right column number for TypeError");
     }
 
     try {
       t.testThrowCallbackError(function() { Array.indexOf() });
     } catch (e) {
       ok(e instanceof TypeError, "Should have a TypeError here (3)");
       ok(!(e instanceof DOMException), "Should not have DOMException here (3)");
       ok(!("code" in e), "Should not have a 'code' property (3)");
       is(e.name, "TypeError", "Should be named TypeError (3)");
       is(e.message, "missing argument 0 when calling function Array.indexOf",
          "Should also have the right message (3)");
       is(e.stack,
-         "doTest/<@http://mochi.test:8888/tests/dom/bindings/test/test_exception_options_from_jsimplemented.html:78:45\n" +
-         "doTest@http://mochi.test:8888/tests/dom/bindings/test/test_exception_options_from_jsimplemented.html:78:7\n"
-  ,
+         `doTest/<@${file}:92:45
+doTest@${file}:92:7
+${asyncFrame}`,
          "Exception stack for TypeError should only show our code (3)");
       is(e.fileName,
-         "http://mochi.test:8888/tests/dom/bindings/test/test_exception_options_from_jsimplemented.html",
+         file,
          "Should still have the right file name for TypeError (3)");
-      is(e.lineNumber, 78, "Should still have the right line number for TypeError (3)");
+      is(e.lineNumber, 92, "Should still have the right line number for TypeError (3)");
       is(e.columnNumber, 45, "Should have the right column number for TypeError (3)");
     }
 
     try {
       t.testThrowXraySelfHosted();
     } catch (e) {
       ok(!(e instanceof Error), "Should have an Exception here (4)");
       ok(!(e instanceof DOMException), "Should not have DOMException here (4)");
       ok(!("code" in e), "Should not have a 'code' property (4)");
       is(e.name, "NS_ERROR_UNEXPECTED", "Name should be sanitized (4)");
       is(e.message, "", "Message should be sanitized (5)");
       is(e.stack,
-         "doTest@http://mochi.test:8888/tests/dom/bindings/test/test_exception_options_from_jsimplemented.html:99:7\n",
+         `doTest@${file}:113:7
+${asyncFrame}`,
          "Exception stack for sanitized exception should only show our code (4)");
       is(e.filename,
-         "http://mochi.test:8888/tests/dom/bindings/test/test_exception_options_from_jsimplemented.html",
+         file,
          "Should still have the right file name for sanitized exception (4)");
-      is(e.lineNumber, 99, "Should still have the right line number for sanitized exception (4)");
+      is(e.lineNumber, 113, "Should still have the right line number for sanitized exception (4)");
       todo_isnot(e.columnNumber, 0, "Should have the right column number for sanitized exception (4)");
     }
 
     try {
       t.testThrowSelfHosted();
     } catch (e) {
       ok(!(e instanceof Error), "Should have an Exception here (5)");
       ok(!(e instanceof DOMException), "Should not have DOMException here (5)");
       ok(!("code" in e), "Should not have a 'code' property (5)");
       is(e.name, "NS_ERROR_UNEXPECTED", "Name should be sanitized (5)");
       is(e.message, "", "Message should be sanitized (5)");
       is(e.stack,
-         "doTest@http://mochi.test:8888/tests/dom/bindings/test/test_exception_options_from_jsimplemented.html:117:7\n",
+         `doTest@${file}:132:7
+${asyncFrame}`,
          "Exception stack for sanitized exception should only show our code (5)");
       is(e.filename,
-         "http://mochi.test:8888/tests/dom/bindings/test/test_exception_options_from_jsimplemented.html",
+         file,
          "Should still have the right file name for sanitized exception (5)");
-      is(e.lineNumber, 117, "Should still have the right line number for sanitized exception (5)");
+      is(e.lineNumber, 132, "Should still have the right line number for sanitized exception (5)");
       todo_isnot(e.columnNumber, 0, "Should have the right column number for sanitized exception (5)");
     }
 
     SimpleTest.finish();
   }
 
   SpecialPowers.pushPrefEnv({set: [['dom.expose_test_interfaces', true]]},
                             doTest);
--- a/dom/bindings/test/test_promise_rejections_from_jsimplemented.html
+++ b/dom/bindings/test/test_promise_rejections_from_jsimplemented.html
@@ -32,67 +32,85 @@ https://bugzilla.mozilla.org/show_bug.cg
   }
 
   function ensurePromiseFail(testNumber, value) {
     ok(false, "Test " + testNumber + " should not have a fulfilled promise");
   }
 
   function doTest() {
     var t = new TestInterfaceJS();
+    /* Async parent frames from pushPrefEnv don't show up in e10s.  */
+    var isE10S = !SpecialPowers.Services.wm.getMostRecentWindow("navigator:browser");
     var asyncStack = SpecialPowers.getBoolPref("javascript.options.asyncstack");
-    var ourFile = "http://mochi.test:8888/tests/dom/bindings/test/test_promise_rejections_from_jsimplemented.html";
+    var ourFile = location.href;
+    var parentFrame = (asyncStack && !isE10S) ? `Async*@${ourFile}:121:1
+` : "";
 
     Promise.all([
       t.testPromiseWithThrowingChromePromiseInit().then(
           ensurePromiseFail.bind(null, 1),
-          checkExn.bind(null, 44, "NS_ERROR_UNEXPECTED", "", undefined,
+          checkExn.bind(null, 48, "NS_ERROR_UNEXPECTED", "", undefined,
                         ourFile, 1,
-                        "doTest@http://mochi.test:8888/tests/dom/bindings/test/test_promise_rejections_from_jsimplemented.html:44:7\n")),
+                        `doTest@${ourFile}:48:7
+` +
+                        parentFrame)),
       t.testPromiseWithThrowingContentPromiseInit(function() {
           thereIsNoSuchContentFunction1();
         }).then(
           ensurePromiseFail.bind(null, 2),
-          checkExn.bind(null, 50, "ReferenceError",
+          checkExn.bind(null, 56, "ReferenceError",
                         "thereIsNoSuchContentFunction1 is not defined",
                         undefined, ourFile, 2,
-                        "doTest/<@http://mochi.test:8888/tests/dom/bindings/test/test_promise_rejections_from_jsimplemented.html:50:11\ndoTest@http://mochi.test:8888/tests/dom/bindings/test/test_promise_rejections_from_jsimplemented.html:49:7\n")),
+                        `doTest/<@${ourFile}:56:11
+doTest@${ourFile}:55:7
+` +
+                        parentFrame)),
       t.testPromiseWithThrowingChromeThenFunction().then(
           ensurePromiseFail.bind(null, 3),
           checkExn.bind(null, 0, "NS_ERROR_UNEXPECTED", "", undefined, "", 3, "")),
       t.testPromiseWithThrowingContentThenFunction(function() {
           thereIsNoSuchContentFunction2();
         }).then(
           ensurePromiseFail.bind(null, 4),
-          checkExn.bind(null, 61, "ReferenceError",
+          checkExn.bind(null, 70, "ReferenceError",
                         "thereIsNoSuchContentFunction2 is not defined",
                         undefined, ourFile, 4,
-                        "doTest/<@http://mochi.test:8888/tests/dom/bindings/test/test_promise_rejections_from_jsimplemented.html:61:11\n" + (asyncStack ? "Async*doTest@http://mochi.test:8888/tests/dom/bindings/test/test_promise_rejections_from_jsimplemented.html:60:7\n" : ""))),
+                        `doTest/<@${ourFile}:70:11
+` +
+                        (asyncStack ? `Async*doTest@${ourFile}:69:7
+` : "") +
+                        parentFrame)),
       t.testPromiseWithThrowingChromeThenable().then(
           ensurePromiseFail.bind(null, 5),
           checkExn.bind(null, 0, "NS_ERROR_UNEXPECTED", "", undefined, "", 5, "")),
       t.testPromiseWithThrowingContentThenable({
             then: function() { thereIsNoSuchContentFunction3(); }
         }).then(
           ensurePromiseFail.bind(null, 6),
-          checkExn.bind(null, 72, "ReferenceError",
+          checkExn.bind(null, 85, "ReferenceError",
                         "thereIsNoSuchContentFunction3 is not defined",
                         undefined, ourFile, 6,
-                        "doTest/<.then@http://mochi.test:8888/tests/dom/bindings/test/test_promise_rejections_from_jsimplemented.html:72:32\n")),
+                        `doTest/<.then@${ourFile}:85:32
+`)),
       t.testPromiseWithDOMExceptionThrowingPromiseInit().then(
           ensurePromiseFail.bind(null, 7),
-          checkExn.bind(null, 79, "NotFoundError",
+          checkExn.bind(null, 93, "NotFoundError",
                         "We are a second DOMException",
                         DOMException.NOT_FOUND_ERR, ourFile, 7,
-                        "doTest@http://mochi.test:8888/tests/dom/bindings/test/test_promise_rejections_from_jsimplemented.html:79:7\n")),
+                        `doTest@${ourFile}:93:7
+` +
+                        parentFrame)),
       t.testPromiseWithDOMExceptionThrowingThenFunction().then(
           ensurePromiseFail.bind(null, 8),
-          checkExn.bind(null, asyncStack ? 85 : 0, "NetworkError",
+          checkExn.bind(null, asyncStack ? 101 : 0, "NetworkError",
                          "We are a third DOMException",
                         DOMException.NETWORK_ERR, asyncStack ? ourFile : "", 8,
-                        asyncStack ? "Async*doTest@http://mochi.test:8888/tests/dom/bindings/test/test_promise_rejections_from_jsimplemented.html:85:7\n" : "")),
+                        (asyncStack ? `Async*doTest@${ourFile}:101:7
+` +
+                         parentFrame : ""))),
       t.testPromiseWithDOMExceptionThrowingThenable().then(
           ensurePromiseFail.bind(null, 9),
           checkExn.bind(null, 0, "TypeMismatchError",
                         "We are a fourth DOMException",
                          DOMException.TYPE_MISMATCH_ERR, "", 9, "")),
     ]).then(SimpleTest.finish,
             function() {
               ok(false, "One of our catch statements totally failed");
--- a/dom/events/DOMEventTargetHelper.cpp
+++ b/dom/events/DOMEventTargetHelper.cpp
@@ -282,17 +282,17 @@ DOMEventTargetHelper::DispatchTrustedEve
 nsresult
 DOMEventTargetHelper::SetEventHandler(nsIAtom* aType,
                                       JSContext* aCx,
                                       const JS::Value& aValue)
 {
   nsRefPtr<EventHandlerNonNull> handler;
   JS::Rooted<JSObject*> callable(aCx);
   if (aValue.isObject() && JS::IsCallable(callable = &aValue.toObject())) {
-    handler = new EventHandlerNonNull(callable, dom::GetIncumbentGlobal());
+    handler = new EventHandlerNonNull(aCx, callable, dom::GetIncumbentGlobal());
   }
   SetEventHandler(aType, EmptyString(), handler);
   return NS_OK;
 }
 
 void
 DOMEventTargetHelper::GetEventHandler(nsIAtom* aType,
                                       JSContext* aCx,
--- a/dom/events/EventListenerManager.cpp
+++ b/dom/events/EventListenerManager.cpp
@@ -948,25 +948,25 @@ EventListenerManager::CompileEventHandle
   result = nsJSUtils::CompileFunction(jsapi, scopeChain, options,
                                       nsAtomCString(typeAtom),
                                       argCount, argNames, *body, handler.address());
   NS_ENSURE_SUCCESS(result, result);
   NS_ENSURE_TRUE(handler, NS_ERROR_FAILURE);
 
   if (jsEventHandler->EventName() == nsGkAtoms::onerror && win) {
     nsRefPtr<OnErrorEventHandlerNonNull> handlerCallback =
-      new OnErrorEventHandlerNonNull(handler, /* aIncumbentGlobal = */ nullptr);
+      new OnErrorEventHandlerNonNull(nullptr, handler, /* aIncumbentGlobal = */ nullptr);
     jsEventHandler->SetHandler(handlerCallback);
   } else if (jsEventHandler->EventName() == nsGkAtoms::onbeforeunload && win) {
     nsRefPtr<OnBeforeUnloadEventHandlerNonNull> handlerCallback =
-      new OnBeforeUnloadEventHandlerNonNull(handler, /* aIncumbentGlobal = */ nullptr);
+      new OnBeforeUnloadEventHandlerNonNull(nullptr, handler, /* aIncumbentGlobal = */ nullptr);
     jsEventHandler->SetHandler(handlerCallback);
   } else {
     nsRefPtr<EventHandlerNonNull> handlerCallback =
-      new EventHandlerNonNull(handler, /* aIncumbentGlobal = */ nullptr);
+      new EventHandlerNonNull(nullptr, handler, /* aIncumbentGlobal = */ nullptr);
     jsEventHandler->SetHandler(handlerCallback);
   }
 
   return result;
 }
 
 nsresult
 EventListenerManager::HandleEventSubType(Listener* aListener,
--- a/dom/events/test/test_clickevent_on_input.html
+++ b/dom/events/test/test_clickevent_on_input.html
@@ -47,17 +47,18 @@ function isEnabledMiddleClickPaste()
   } catch (e) {
     return false;
   }
 }
 
 function isEnabledTouchCaret()
 {
   try {
-    return SpecialPowers.getBoolPref("touchcaret.enabled");
+    return SpecialPowers.getBoolPref("touchcaret.enabled") ||
+           SpecialPowers.getBoolPref("layout.accessiblecaret.enabled");
   } catch (e) {
     return false;
   }
 }
 
 function doTest(aButton)
 {
   // NOTE #1: Right click causes a context menu to popup, then, the click event
--- a/dom/fmradio/FMRadio.cpp
+++ b/dom/fmradio/FMRadio.cpp
@@ -466,16 +466,22 @@ FMRadio::EnableAudioChannelAgent()
 NS_IMETHODIMP
 FMRadio::WindowVolumeChanged(float aVolume, bool aMuted)
 {
   IFMRadioService::Singleton()->EnableAudio(!aMuted);
   // TODO: what about the volume?
   return NS_OK;
 }
 
+NS_IMETHODIMP
+FMRadio::WindowAudioCaptureChanged()
+{
+  return NS_OK;
+}
+
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(FMRadio)
   NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
   NS_INTERFACE_MAP_ENTRY(nsIAudioChannelAgentCallback)
 NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper)
 
 NS_IMPL_ADDREF_INHERITED(FMRadio, DOMEventTargetHelper)
 NS_IMPL_RELEASE_INHERITED(FMRadio, DOMEventTargetHelper)
 
--- a/dom/html/HTMLMediaElement.cpp
+++ b/dom/html/HTMLMediaElement.cpp
@@ -2025,16 +2025,17 @@ HTMLMediaElement::HTMLMediaElement(alrea
     mAutoplaying(true),
     mAutoplayEnabled(true),
     mPaused(true),
     mMuted(0),
     mStatsShowing(false),
     mAllowCasting(false),
     mIsCasting(false),
     mAudioCaptured(false),
+    mAudioCapturedByWindow(false),
     mPlayingBeforeSeek(false),
     mPlayingThroughTheAudioChannelBeforeSeek(false),
     mPausedForInactiveDocumentOrChannel(false),
     mEventDeliveryPaused(false),
     mWaitingFired(false),
     mIsRunningLoadMethod(false),
     mIsDoingExplicitLoad(false),
     mIsLoadingFromSourceChildren(false),
@@ -2092,16 +2093,21 @@ HTMLMediaElement::~HTMLMediaElement()
   }
   if (mProgressTimer) {
     StopProgress();
   }
   if (mSrcStream) {
     EndSrcMediaStreamPlayback();
   }
 
+  if (mCaptureStreamPort) {
+    mCaptureStreamPort->Destroy();
+    mCaptureStreamPort = nullptr;
+  }
+
   NS_ASSERTION(MediaElementTableCount(this, mLoadingSrc) == 0,
     "Destroyed media element should no longer be in element table");
 
   if (mChannel) {
     mChannel->Cancel(NS_BINDING_ABORTED);
   }
 
   WakeLockRelease();
@@ -4470,45 +4476,48 @@ void HTMLMediaElement::UpdateAudioChanne
   if (!UseAudioChannelService()) {
     return;
   }
 
   bool playingThroughTheAudioChannel =
      (!mPaused &&
       (HasAttr(kNameSpaceID_None, nsGkAtoms::loop) ||
        (mReadyState >= nsIDOMHTMLMediaElement::HAVE_CURRENT_DATA &&
-        !IsPlaybackEnded() &&
-        (!mSrcStream || HasAudio())) ||
+        !IsPlaybackEnded()) ||
        mPlayingThroughTheAudioChannelBeforeSeek));
   if (playingThroughTheAudioChannel != mPlayingThroughTheAudioChannel) {
     mPlayingThroughTheAudioChannel = playingThroughTheAudioChannel;
 
     // If we are not playing, we don't need to create a new audioChannelAgent.
     if (!mAudioChannelAgent && !mPlayingThroughTheAudioChannel) {
        return;
     }
 
     if (!mAudioChannelAgent) {
       nsresult rv;
       mAudioChannelAgent = do_CreateInstance("@mozilla.org/audiochannelagent;1", &rv);
       if (!mAudioChannelAgent) {
         return;
       }
-      mAudioChannelAgent->InitWithWeakCallback(OwnerDoc()->GetWindow(),
+      mAudioChannelAgent->InitWithWeakCallback(OwnerDoc()->GetInnerWindow(),
                                                static_cast<int32_t>(mAudioChannel),
                                                this);
     }
 
     NotifyAudioChannelAgent(mPlayingThroughTheAudioChannel);
   }
 }
 
 void
 HTMLMediaElement::NotifyAudioChannelAgent(bool aPlaying)
 {
+  // Immediately check if this should go to the MSG instead of the normal
+  // media playback route.
+  WindowAudioCaptureChanged();
+
   // This is needed to pass nsContentUtils::IsCallerChrome().
   // AudioChannel API should not called from content but it can happen that
   // this method has some content JS in its stack.
   AutoNoJSAPI nojsapi;
 
   if (aPlaying) {
     float volume = 0.0;
     bool muted = true;
@@ -4670,16 +4679,63 @@ HTMLMediaElement::GetTopLevelPrincipal()
   if (!doc) {
     return nullptr;
   }
   principal = doc->NodePrincipal();
   return principal.forget();
 }
 #endif // MOZ_EME
 
+NS_IMETHODIMP HTMLMediaElement::WindowAudioCaptureChanged()
+{
+   MOZ_ASSERT(mAudioChannelAgent);
+
+  if (!OwnerDoc()->GetInnerWindow()) {
+    return NS_OK;
+  }
+  bool captured = OwnerDoc()->GetInnerWindow()->GetAudioCaptured();
+
+  if (captured != mAudioCapturedByWindow) {
+    if (captured) {
+      mAudioCapturedByWindow = true;
+      nsCOMPtr<nsPIDOMWindow> window =
+        do_QueryInterface(OwnerDoc()->GetParentObject());
+      uint64_t id = window->WindowID();
+      MediaStreamGraph* msg = MediaStreamGraph::GetInstance();
+
+      if (!mPlaybackStream) {
+        nsRefPtr<DOMMediaStream> stream = CaptureStreamInternal(false, msg);
+        mCaptureStreamPort = msg->ConnectToCaptureStream(id, stream->GetStream());
+      } else {
+        mCaptureStreamPort = msg->ConnectToCaptureStream(id, mPlaybackStream->GetStream());
+      }
+    } else {
+      mAudioCapturedByWindow = false;
+      if (mDecoder) {
+        ProcessedMediaStream* ps =
+          mCaptureStreamPort->GetSource()->AsProcessedStream();
+        MOZ_ASSERT(ps);
+
+        for (uint32_t i = 0; i < mOutputStreams.Length(); i++) {
+          if (mOutputStreams[i].mStream->GetStream() == ps) {
+            mOutputStreams.RemoveElementAt(i);
+            break;
+          }
+        }
+
+        mDecoder->RemoveOutputStream(ps);
+      }
+      mCaptureStreamPort->Destroy();
+      mCaptureStreamPort = nullptr;
+    }
+  }
+
+   return NS_OK;
+}
+
 AudioTrackList*
 HTMLMediaElement::AudioTracks()
 {
   if (!mAudioTrackList) {
     nsCOMPtr<nsPIDOMWindow> window = do_QueryInterface(OwnerDoc()->GetParentObject());
     mAudioTrackList = new AudioTrackList(window, this);
   }
   return mAudioTrackList;
--- a/dom/html/HTMLMediaElement.h
+++ b/dom/html/HTMLMediaElement.h
@@ -1069,16 +1069,19 @@ protected:
   // Holds a reference to the DOM wrapper for the MediaStream that we're
   // actually playing.
   // At most one of mDecoder and mSrcStream can be non-null.
   nsRefPtr<DOMMediaStream> mSrcStream;
 
   // Holds a reference to a MediaInputPort connecting mSrcStream to mPlaybackStream.
   nsRefPtr<MediaInputPort> mPlaybackStreamInputPort;
 
+  // Holds a reference to the stream connecting this stream to the capture sink.
+  nsRefPtr<MediaInputPort> mCaptureStreamPort;
+
   // Holds a reference to a stream with mSrcStream as input but intended for
   // playback. Used so we don't block playback of other video elements
   // playing the same mSrcStream.
   nsRefPtr<DOMMediaStream> mPlaybackStream;
 
   // Holds references to the DOM wrappers for the MediaStreams that we're
   // writing to.
   struct OutputMediaStream {
@@ -1278,16 +1281,19 @@ protected:
   // True if casting is currently allowed
   bool mAllowCasting;
   // True if currently casting this video
   bool mIsCasting;
 
   // True if the sound is being captured.
   bool mAudioCaptured;
 
+  // True if the sound is being captured by the window.
+  bool mAudioCapturedByWindow;
+
   // If TRUE then the media element was actively playing before the currently
   // in progress seeking. If FALSE then the media element is either not seeking
   // or was not actively playing before the current seek. Used to decide whether
   // to raise the 'waiting' event as per 4.7.1.8 in HTML 5 specification.
   bool mPlayingBeforeSeek;
 
   // if TRUE then the seek started while content was in active playing state
   // if FALSE then the seek started while the content was not playing.
--- a/dom/locales/en-US/chrome/dom/dom.properties
+++ b/dom/locales/en-US/chrome/dom/dom.properties
@@ -150,18 +150,18 @@ ImplicitMetaViewportTagFallback=No meta-
 # LOCALIZATION NOTE: Do not translate "DataContainerEvent" or "CustomEvent"
 DataContainerEventWarning=Use of DataContainerEvent is deprecated. Use CustomEvent instead.
 # LOCALIZATION NOTE: Do not translate "window.controllers"
 Window_ControllersWarning=window.controllers is deprecated. Do not use it for UA detection.
 ImportXULIntoContentWarning=Importing XUL nodes into a content document is deprecated. This functionality may be removed soon.
 XMLDocumentLoadPrincipalMismatch=Use of document.load forbidden on Documents that come from other Windows. Only the Window in which a Document was created is allowed to call .load on that Document. Preferably, use XMLHttpRequest instead.
 # LOCALIZATION NOTE: Do not translate "IndexedDB".
 IndexedDBTransactionAbortNavigation=An IndexedDB transaction that was not yet complete has been aborted due to page navigation.
-# LOCALIZATION NOTE (WillChangeBudgetWarning): Do not translate Will-change, %1$S,%2$S,%3$S are numbers.
-WillChangeBudgetWarning=Will-change memory consumption is too high. Surface area covers %1$S pixels, budget is the document surface area multiplied by %2$S (%3$S pixels). Occurences of will-change over the budget will be ignored.
+# LOCALIZATION NOTE: Do not translate Will-change, %1$S,%2$S,%3$S are numbers.
+WillChangeOverBudgetIgnoredWarning=Will-change memory consumption is too high. Surface area covers %1$S px, budget is the document surface area multiplied by %2$S (%3$S px). Occurences of will-change over the budget will be ignored.
 # LOCALIZATION NOTE: Do not translate "ServiceWorker".
 HittingMaxWorkersPerDomain=A ServiceWorker could not be started immediately because other documents in the same origin are already using the maximum number of workers. The ServiceWorker is now queued and will be started after some of the other workers have completed.
 # LOCALIZATION NOTE: Do no translate "setVelocity", "PannerNode", "AudioListener", "speedOfSound" and "dopplerFactor"
 PannerNodeDopplerWarning=Use of setVelocity on the PannerNode and AudioListener, and speedOfSound and dopplerFactor on the AudioListener are deprecated and those members will be removed. For more help https://developer.mozilla.org/en-US/docs/Web/API/AudioListener#Deprecated_features
 # LOCALIZATION NOTE: Do not translate "Worker".
 EmptyWorkerSourceWarning=Attempting to create a Worker from an empty source. This is probably unintentional.
 # LOCALIZATION NOTE: Do not translate "ServiceWorker".
 InterceptionFailed=ServiceWorker network interception failed due to an unexpected error.
new file mode 100644
--- /dev/null
+++ b/dom/media/AudioCaptureStream.cpp
@@ -0,0 +1,133 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-*/
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "MediaStreamGraphImpl.h"
+#include "mozilla/MathAlgorithms.h"
+#include "mozilla/unused.h"
+
+#include "AudioSegment.h"
+#include "mozilla/Logging.h"
+#include "mozilla/Attributes.h"
+#include "AudioCaptureStream.h"
+#include "ImageContainer.h"
+#include "AudioNodeEngine.h"
+#include "AudioNodeStream.h"
+#include "AudioNodeExternalInputStream.h"
+#include "webaudio/MediaStreamAudioDestinationNode.h"
+#include <algorithm>
+#include "DOMMediaStream.h"
+
+using namespace mozilla::layers;
+using namespace mozilla::dom;
+using namespace mozilla::gfx;
+
+namespace mozilla
+{
+
+// We are mixing to mono until PeerConnection can accept stereo
+static const uint32_t MONO = 1;
+
+AudioCaptureStream::AudioCaptureStream(DOMMediaStream* aWrapper)
+  : ProcessedMediaStream(aWrapper), mTrackCreated(false)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  MOZ_COUNT_CTOR(AudioCaptureStream);
+  mMixer.AddCallback(this);
+}
+
+AudioCaptureStream::~AudioCaptureStream()
+{
+  MOZ_COUNT_DTOR(AudioCaptureStream);
+  mMixer.RemoveCallback(this);
+}
+
+void
+AudioCaptureStream::ProcessInput(GraphTime aFrom, GraphTime aTo,
+                                 uint32_t aFlags)
+{
+  uint32_t inputCount = mInputs.Length();
+  StreamBuffer::Track* track = EnsureTrack(AUDIO_TRACK);
+  // Notify the DOM everything is in order.
+  if (!mTrackCreated) {
+    for (uint32_t i = 0; i < mListeners.Length(); i++) {
+      MediaStreamListener* l = mListeners[i];
+      AudioSegment tmp;
+      l->NotifyQueuedTrackChanges(
+        Graph(), AUDIO_TRACK, 0, MediaStreamListener::TRACK_EVENT_CREATED, tmp);
+      l->NotifyFinishedTrackCreation(Graph());
+    }
+    mTrackCreated = true;
+  }
+
+  // If the captured stream is connected back to a object on the page (be it an
+  // HTMLMediaElement with a stream as source, or an AudioContext), a cycle
+  // situation occur. This can work if it's an AudioContext with at least one
+  // DelayNode, but the MSG will mute the whole cycle otherwise.
+  bool blocked = mFinished || mBlocked.GetAt(aFrom);
+  if (blocked || InMutedCycle() || inputCount == 0) {
+    track->Get<AudioSegment>()->AppendNullData(aTo - aFrom);
+  } else {
+    // We mix down all the tracks of all inputs, to a stereo track. Everything
+    // is {up,down}-mixed to stereo.
+    mMixer.StartMixing();
+    AudioSegment output;
+    for (uint32_t i = 0; i < inputCount; i++) {
+      MediaStream* s = mInputs[i]->GetSource();
+      StreamBuffer::TrackIter tracks(s->GetStreamBuffer(), MediaSegment::AUDIO);
+      while (!tracks.IsEnded()) {
+        AudioSegment* inputSegment = tracks->Get<AudioSegment>();
+        StreamTime inputStart = s->GraphTimeToStreamTime(aFrom);
+        StreamTime inputEnd = s->GraphTimeToStreamTime(aTo);
+        AudioSegment toMix;
+        toMix.AppendSlice(*inputSegment, inputStart, inputEnd);
+        // Care for streams blocked in the [aTo, aFrom] range.
+        if (inputEnd - inputStart < aTo - aFrom) {
+          toMix.AppendNullData((aTo - aFrom) - (inputEnd - inputStart));
+        }
+        toMix.Mix(mMixer, MONO, Graph()->GraphRate());
+        tracks.Next();
+      }
+    }
+    // This calls MixerCallback below
+    mMixer.FinishMixing();
+  }
+
+  // Regardless of the status of the input tracks, we go foward.
+  mBuffer.AdvanceKnownTracksTime(GraphTimeToStreamTime((aTo)));
+}
+
+void
+AudioCaptureStream::MixerCallback(AudioDataValue* aMixedBuffer,
+                                  AudioSampleFormat aFormat, uint32_t aChannels,
+                                  uint32_t aFrames, uint32_t aSampleRate)
+{
+  nsAutoTArray<nsTArray<AudioDataValue>, MONO> output;
+  nsAutoTArray<const AudioDataValue*, MONO> bufferPtrs;
+  output.SetLength(MONO);
+  bufferPtrs.SetLength(MONO);
+
+  uint32_t written = 0;
+  // We need to copy here, because the mixer will reuse the storage, we should
+  // not hold onto it. Buffers are in planar format.
+  for (uint32_t channel = 0; channel < aChannels; channel++) {
+    AudioDataValue* out = output[channel].AppendElements(aFrames);
+    PodCopy(out, aMixedBuffer + written, aFrames);
+    bufferPtrs[channel] = out;
+    written += aFrames;
+  }
+  AudioChunk chunk;
+  chunk.mBuffer = new mozilla::SharedChannelArrayBuffer<AudioDataValue>(&output);
+  chunk.mDuration = aFrames;
+  chunk.mBufferFormat = aFormat;
+  chunk.mVolume = 1.0f;
+  chunk.mChannelData.SetLength(MONO);
+  for (uint32_t channel = 0; channel < aChannels; channel++) {
+    chunk.mChannelData[channel] = bufferPtrs[channel];
+  }
+
+  // Now we have mixed data, simply append it to out track.
+  EnsureTrack(AUDIO_TRACK)->Get<AudioSegment>()->AppendAndConsumeChunk(&chunk);
+}
+}
new file mode 100644
--- /dev/null
+++ b/dom/media/AudioCaptureStream.h
@@ -0,0 +1,40 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-*/
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef MOZILLA_AUDIOCAPTURESTREAM_H_
+#define MOZILLA_AUDIOCAPTURESTREAM_H_
+
+#include "MediaStreamGraph.h"
+#include "AudioMixer.h"
+#include <algorithm>
+
+namespace mozilla
+{
+
+class DOMMediaStream;
+
+/**
+ * See MediaStreamGraph::CreateAudioCaptureStream.
+ */
+class AudioCaptureStream : public ProcessedMediaStream,
+                           public MixerCallbackReceiver
+{
+public:
+  explicit AudioCaptureStream(DOMMediaStream* aWrapper);
+  virtual ~AudioCaptureStream();
+
+  void ProcessInput(GraphTime aFrom, GraphTime aTo, uint32_t aFlags) override;
+
+protected:
+  enum { AUDIO_TRACK = 1 };
+  void MixerCallback(AudioDataValue* aMixedBuffer, AudioSampleFormat aFormat,
+                     uint32_t aChannels, uint32_t aFrames,
+                     uint32_t aSampleRate) override;
+  AudioMixer mMixer;
+  bool mTrackCreated;
+};
+}
+
+#endif /* MOZILLA_AUDIOCAPTURESTREAM_H_ */
--- a/dom/media/AudioChannelFormat.cpp
+++ b/dom/media/AudioChannelFormat.cpp
@@ -1,34 +1,19 @@
 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "AudioChannelFormat.h"
-#include "nsTArray.h"
 
 #include <algorithm>
 
 namespace mozilla {
 
-enum {
-  SURROUND_L,
-  SURROUND_R,
-  SURROUND_C,
-  SURROUND_LFE,
-  SURROUND_SL,
-  SURROUND_SR
-};
-
-static const uint32_t CUSTOM_CHANNEL_LAYOUTS = 6;
-
-static const int IGNORE = CUSTOM_CHANNEL_LAYOUTS;
-static const float IGNORE_F = 0.0f;
-
 uint32_t
 GetAudioChannelsSuperset(uint32_t aChannels1, uint32_t aChannels2)
 {
   return std::max(aChannels1, aChannels2);
 }
 
 /**
  * UpMixMatrix represents a conversion matrix by exploiting the fact that
@@ -58,31 +43,28 @@ gUpMixMatrices[CUSTOM_CHANNEL_LAYOUTS*(C
   { { 0, 1, 2, IGNORE, IGNORE, IGNORE } },
   // Upmixes from quad
   { { 0, 1, 2, 3, IGNORE } },
   { { 0, 1, IGNORE, IGNORE, 2, 3 } },
   // Upmixes from 5-channel
   { { 0, 1, 2, 3, 4, IGNORE } }
 };
 
-static const int gMixingMatrixIndexByChannels[CUSTOM_CHANNEL_LAYOUTS - 1] =
-  { 0, 5, 9, 12, 14 };
-
 void
 AudioChannelsUpMix(nsTArray<const void*>* aChannelArray,
                    uint32_t aOutputChannelCount,
                    const void* aZeroChannel)
 {
   uint32_t inputChannelCount = aChannelArray->Length();
   uint32_t outputChannelCount =
     GetAudioChannelsSuperset(aOutputChannelCount, inputChannelCount);
   NS_ASSERTION(outputChannelCount > inputChannelCount,
                "No up-mix needed");
-  NS_ASSERTION(inputChannelCount > 0, "Bad number of channels");
-  NS_ASSERTION(outputChannelCount > 0, "Bad number of channels");
+  MOZ_ASSERT(inputChannelCount > 0, "Bad number of channels");
+  MOZ_ASSERT(outputChannelCount > 0, "Bad number of channels");
 
   aChannelArray->SetLength(outputChannelCount);
 
   if (inputChannelCount < CUSTOM_CHANNEL_LAYOUTS &&
       outputChannelCount <= CUSTOM_CHANNEL_LAYOUTS) {
     const UpMixMatrix& m = gUpMixMatrices[
       gMixingMatrixIndexByChannels[inputChannelCount - 1] +
       outputChannelCount - inputChannelCount - 1];
@@ -103,99 +85,9 @@ AudioChannelsUpMix(nsTArray<const void*>
     return;
   }
 
   for (uint32_t i = inputChannelCount; i < outputChannelCount; ++i) {
     aChannelArray->ElementAt(i) = aZeroChannel;
   }
 }
 
-/**
- * DownMixMatrix represents a conversion matrix efficiently by exploiting the
- * fact that each input channel contributes to at most one output channel,
- * except possibly for the C input channel in layouts that have one. Also,
- * every input channel is multiplied by the same coefficient for every output
- * channel it contributes to.
- */
-struct DownMixMatrix {
-  // Every input channel c is copied to output channel mInputDestination[c]
-  // after multiplying by mInputCoefficient[c].
-  uint8_t mInputDestination[CUSTOM_CHANNEL_LAYOUTS];
-  // If not IGNORE, then the C channel is copied to this output channel after
-  // multiplying by its coefficient.
-  uint8_t mCExtraDestination;
-  float mInputCoefficient[CUSTOM_CHANNEL_LAYOUTS];
-};
-
-static const DownMixMatrix
-gDownMixMatrices[CUSTOM_CHANNEL_LAYOUTS*(CUSTOM_CHANNEL_LAYOUTS - 1)/2] =
-{
-  // Downmixes to mono
-  { { 0, 0 }, IGNORE, { 0.5f, 0.5f } },
-  { { 0, IGNORE, IGNORE }, IGNORE, { 1.0f, IGNORE_F, IGNORE_F } },
-  { { 0, 0, 0, 0 }, IGNORE, { 0.25f, 0.25f, 0.25f, 0.25f } },
-  { { 0, IGNORE, IGNORE, IGNORE, IGNORE }, IGNORE, { 1.0f, IGNORE_F, IGNORE_F, IGNORE_F, IGNORE_F } },
-  { { 0, 0, 0, IGNORE, 0, 0 }, IGNORE, { 0.7071f, 0.7071f, 1.0f, IGNORE_F, 0.5f, 0.5f } },
-  // Downmixes to stereo
-  { { 0, 1, IGNORE }, IGNORE, { 1.0f, 1.0f, IGNORE_F } },
-  { { 0, 1, 0, 1 }, IGNORE, { 0.5f, 0.5f, 0.5f, 0.5f } },
-  { { 0, 1, IGNORE, IGNORE, IGNORE }, IGNORE, { 1.0f, 1.0f, IGNORE_F, IGNORE_F, IGNORE_F } },
-  { { 0, 1, 0, IGNORE, 0, 1 }, 1, { 1.0f, 1.0f, 0.7071f, IGNORE_F, 0.7071f, 0.7071f } },
-  // Downmixes to 3-channel
-  { { 0, 1, 2, IGNORE }, IGNORE, { 1.0f, 1.0f, 1.0f, IGNORE_F } },
-  { { 0, 1, 2, IGNORE, IGNORE }, IGNORE, { 1.0f, 1.0f, 1.0f, IGNORE_F, IGNORE_F } },
-  { { 0, 1, 2, IGNORE, IGNORE, IGNORE }, IGNORE, { 1.0f, 1.0f, 1.0f, IGNORE_F, IGNORE_F, IGNORE_F } },
-  // Downmixes to quad
-  { { 0, 1, 2, 3, IGNORE }, IGNORE, { 1.0f, 1.0f, 1.0f, 1.0f, IGNORE_F } },
-  { { 0, 1, 0, IGNORE, 2, 3 }, 1, { 1.0f, 1.0f, 0.7071f, IGNORE_F, 1.0f, 1.0f } },
-  // Downmixes to 5-channel
-  { { 0, 1, 2, 3, 4, IGNORE }, IGNORE, { 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, IGNORE_F } }
-};
-
-void
-AudioChannelsDownMix(const nsTArray<const void*>& aChannelArray,
-                     float** aOutputChannels,
-                     uint32_t aOutputChannelCount,
-                     uint32_t aDuration)
-{
-  uint32_t inputChannelCount = aChannelArray.Length();
-  const void* const* inputChannels = aChannelArray.Elements();
-  NS_ASSERTION(inputChannelCount > aOutputChannelCount, "Nothing to do");
-
-  if (inputChannelCount > 6) {
-    // Just drop the unknown channels.
-    for (uint32_t o = 0; o < aOutputChannelCount; ++o) {
-      memcpy(aOutputChannels[o], inputChannels[o], aDuration*sizeof(float));
-    }
-    return;
-  }
-
-  // Ignore unknown channels, they're just dropped.
-  inputChannelCount = std::min<uint32_t>(6, inputChannelCount);
-
-  const DownMixMatrix& m = gDownMixMatrices[
-    gMixingMatrixIndexByChannels[aOutputChannelCount - 1] +
-    inputChannelCount - aOutputChannelCount - 1];
-
-  // This is slow, but general. We can define custom code for special
-  // cases later.
-  for (uint32_t s = 0; s < aDuration; ++s) {
-    // Reserve an extra junk channel at the end for the cases where we
-    // want an input channel to contribute to nothing
-    float outputChannels[CUSTOM_CHANNEL_LAYOUTS + 1];
-    memset(outputChannels, 0, sizeof(float)*(CUSTOM_CHANNEL_LAYOUTS));
-    for (uint32_t c = 0; c < inputChannelCount; ++c) {
-      outputChannels[m.mInputDestination[c]] +=
-        m.mInputCoefficient[c]*(static_cast<const float*>(inputChannels[c]))[s];
-    }
-    // Utilize the fact that in every layout, C is the third channel.
-    if (m.mCExtraDestination != IGNORE) {
-      outputChannels[m.mCExtraDestination] +=
-        m.mInputCoefficient[SURROUND_C]*(static_cast<const float*>(inputChannels[SURROUND_C]))[s];
-    }
-
-    for (uint32_t c = 0; c < aOutputChannelCount; ++c) {
-      aOutputChannels[c][s] = outputChannels[c];
-    }
-  }
-}
-
 } // namespace mozilla
--- a/dom/media/AudioChannelFormat.h
+++ b/dom/media/AudioChannelFormat.h
@@ -4,16 +4,18 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 #ifndef MOZILLA_AUDIOCHANNELFORMAT_H_
 #define MOZILLA_AUDIOCHANNELFORMAT_H_
 
 #include <stdint.h>
 
 #include "nsTArrayForwardDeclare.h"
+#include "AudioSampleFormat.h"
+#include "nsTArray.h"
 
 namespace mozilla {
 
 /*
  * This file provides utilities for upmixing and downmixing channels.
  *
  * The channel layouts, upmixing and downmixing are consistent with the
  * Web Audio spec.
@@ -24,16 +26,36 @@ namespace mozilla {
  *          { L, R, C }
  *   quad   { L, R, SL, SR }
  *          { L, R, C, SL, SR }
  *   5.1    { L, R, C, LFE, SL, SR }
  *
  * Only 1, 2, 4 and 6 are currently defined in Web Audio.
  */
 
+enum {
+  SURROUND_L,
+  SURROUND_R,
+  SURROUND_C,
+  SURROUND_LFE,
+  SURROUND_SL,
+  SURROUND_SR
+};
+
+const uint32_t CUSTOM_CHANNEL_LAYOUTS = 6;
+
+// This is defined by some Windows SDK header.
+#undef IGNORE
+
+const int IGNORE = CUSTOM_CHANNEL_LAYOUTS;
+const float IGNORE_F = 0.0f;
+
+const int gMixingMatrixIndexByChannels[CUSTOM_CHANNEL_LAYOUTS - 1] =
+  { 0, 5, 9, 12, 14 };
+
 /**
  * Return a channel count whose channel layout includes all the channels from
  * aChannels1 and aChannels2.
  */
 uint32_t
 GetAudioChannelsSuperset(uint32_t aChannels1, uint32_t aChannels2);
 
 /**
@@ -48,25 +70,108 @@ GetAudioChannelsSuperset(uint32_t aChann
  * GetAudioChannelsSuperset calls resulting in aOutputChannelCount,
  * no downmixing will be required.
  */
 void
 AudioChannelsUpMix(nsTArray<const void*>* aChannelArray,
                    uint32_t aOutputChannelCount,
                    const void* aZeroChannel);
 
+
 /**
- * Given an array of input channels (which must be float format!),
- * downmix to aOutputChannelCount, and copy the results to the
- * channel buffers in aOutputChannels.
- * Don't call this with input count <= output count.
+ * DownMixMatrix represents a conversion matrix efficiently by exploiting the
+ * fact that each input channel contributes to at most one output channel,
+ * except possibly for the C input channel in layouts that have one. Also,
+ * every input channel is multiplied by the same coefficient for every output
+ * channel it contributes to.
+ */
+struct DownMixMatrix {
+  // Every input channel c is copied to output channel mInputDestination[c]
+  // after multiplying by mInputCoefficient[c].
+  uint8_t mInputDestination[CUSTOM_CHANNEL_LAYOUTS];
+  // If not IGNORE, then the C channel is copied to this output channel after
+  // multiplying by its coefficient.
+  uint8_t mCExtraDestination;
+  float mInputCoefficient[CUSTOM_CHANNEL_LAYOUTS];
+};
+
+static const DownMixMatrix
+gDownMixMatrices[CUSTOM_CHANNEL_LAYOUTS*(CUSTOM_CHANNEL_LAYOUTS - 1)/2] =
+{
+  // Downmixes to mono
+  { { 0, 0 }, IGNORE, { 0.5f, 0.5f } },
+  { { 0, IGNORE, IGNORE }, IGNORE, { 1.0f, IGNORE_F, IGNORE_F } },
+  { { 0, 0, 0, 0 }, IGNORE, { 0.25f, 0.25f, 0.25f, 0.25f } },
+  { { 0, IGNORE, IGNORE, IGNORE, IGNORE }, IGNORE, { 1.0f, IGNORE_F, IGNORE_F, IGNORE_F, IGNORE_F } },
+  { { 0, 0, 0, IGNORE, 0, 0 }, IGNORE, { 0.7071f, 0.7071f, 1.0f, IGNORE_F, 0.5f, 0.5f } },
+  // Downmixes to stereo
+  { { 0, 1, IGNORE }, IGNORE, { 1.0f, 1.0f, IGNORE_F } },
+  { { 0, 1, 0, 1 }, IGNORE, { 0.5f, 0.5f, 0.5f, 0.5f } },
+  { { 0, 1, IGNORE, IGNORE, IGNORE }, IGNORE, { 1.0f, 1.0f, IGNORE_F, IGNORE_F, IGNORE_F } },
+  { { 0, 1, 0, IGNORE, 0, 1 }, 1, { 1.0f, 1.0f, 0.7071f, IGNORE_F, 0.7071f, 0.7071f } },
+  // Downmixes to 3-channel
+  { { 0, 1, 2, IGNORE }, IGNORE, { 1.0f, 1.0f, 1.0f, IGNORE_F } },
+  { { 0, 1, 2, IGNORE, IGNORE }, IGNORE, { 1.0f, 1.0f, 1.0f, IGNORE_F, IGNORE_F } },
+  { { 0, 1, 2, IGNORE, IGNORE, IGNORE }, IGNORE, { 1.0f, 1.0f, 1.0f, IGNORE_F, IGNORE_F, IGNORE_F } },
+  // Downmixes to quad
+  { { 0, 1, 2, 3, IGNORE }, IGNORE, { 1.0f, 1.0f, 1.0f, 1.0f, IGNORE_F } },
+  { { 0, 1, 0, IGNORE, 2, 3 }, 1, { 1.0f, 1.0f, 0.7071f, IGNORE_F, 1.0f, 1.0f } },
+  // Downmixes to 5-channel
+  { { 0, 1, 2, 3, 4, IGNORE }, IGNORE, { 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, IGNORE_F } }
+};
+
+/**
+ * Given an array of input channels, downmix to aOutputChannelCount, and copy
+ * the results to the channel buffers in aOutputChannels.  Don't call this with
+ * input count <= output count.
  */
-void
-AudioChannelsDownMix(const nsTArray<const void*>& aChannelArray,
-                     float** aOutputChannels,
+template<typename T>
+void AudioChannelsDownMix(const nsTArray<const void*>& aChannelArray,
+                     T** aOutputChannels,
                      uint32_t aOutputChannelCount,
-                     uint32_t aDuration);
+                     uint32_t aDuration)
+{
+  uint32_t inputChannelCount = aChannelArray.Length();
+  const void* const* inputChannels = aChannelArray.Elements();
+  NS_ASSERTION(inputChannelCount > aOutputChannelCount, "Nothing to do");
+
+  if (inputChannelCount > 6) {
+    // Just drop the unknown channels.
+    for (uint32_t o = 0; o < aOutputChannelCount; ++o) {
+      memcpy(aOutputChannels[o], inputChannels[o], aDuration*sizeof(T));
+    }
+    return;
+  }
+
+  // Ignore unknown channels, they're just dropped.
+  inputChannelCount = std::min<uint32_t>(6, inputChannelCount);
+
+  const DownMixMatrix& m = gDownMixMatrices[
+    gMixingMatrixIndexByChannels[aOutputChannelCount - 1] +
+    inputChannelCount - aOutputChannelCount - 1];
 
-// A version of AudioChannelsDownMix that downmixes int16_ts may be required.
+  // This is slow, but general. We can define custom code for special
+  // cases later.
+  for (uint32_t s = 0; s < aDuration; ++s) {
+    // Reserve an extra junk channel at the end for the cases where we
+    // want an input channel to contribute to nothing
+    T outputChannels[CUSTOM_CHANNEL_LAYOUTS + 1];
+    memset(outputChannels, 0, sizeof(T)*(CUSTOM_CHANNEL_LAYOUTS));
+    for (uint32_t c = 0; c < inputChannelCount; ++c) {
+      outputChannels[m.mInputDestination[c]] +=
+        m.mInputCoefficient[c]*(static_cast<const T*>(inputChannels[c]))[s];
+    }
+    // Utilize the fact that in every layout, C is the third channel.
+    if (m.mCExtraDestination != IGNORE) {
+      outputChannels[m.mCExtraDestination] +=
+        m.mInputCoefficient[SURROUND_C]*(static_cast<const T*>(inputChannels[SURROUND_C]))[s];
+    }
+
+    for (uint32_t c = 0; c < aOutputChannelCount; ++c) {
+      aOutputChannels[c][s] = outputChannels[c];
+    }
+  }
+}
+
 
 } // namespace mozilla
 
 #endif /* MOZILLA_AUDIOCHANNELFORMAT_H_ */
--- a/dom/media/AudioMixer.h
+++ b/dom/media/AudioMixer.h
@@ -21,17 +21,19 @@ struct MixerCallbackReceiver {
                              uint32_t aFrames,
                              uint32_t aSampleRate) = 0;
 };
 /**
  * This class mixes multiple streams of audio together to output a single audio
  * stream.
  *
  * AudioMixer::Mix is to be called repeatedly with buffers that have the same
- * length, sample rate, sample format and channel count.
+ * length, sample rate, sample format and channel count. This class works with
+ * interleaved and plannar buffers, but the buffer mixed must be of the same
+ * type during a mixing cycle.
  *
  * When all the tracks have been mixed, calling FinishMixing will call back with
  * a buffer containing the mixed audio data.
  *
  * This class is not thread safe.
  */
 class AudioMixer
 {
@@ -66,17 +68,17 @@ public:
                                    mChannels,
                                    mFrames,
                                    mSampleRate);
     }
     PodZero(mMixedAudio.Elements(), mMixedAudio.Length());
     mSampleRate = mChannels = mFrames = 0;
   }
 
-  /* Add a buffer to the mix. aSamples is interleaved. */
+  /* Add a buffer to the mix. */
   void Mix(AudioDataValue* aSamples,
            uint32_t aChannels,
            uint32_t aFrames,
            uint32_t aSampleRate) {
     if (!mFrames && !mChannels) {
       mFrames = aFrames;
       mChannels = aChannels;
       mSampleRate = aSampleRate;
--- a/dom/media/AudioSegment.cpp
+++ b/dom/media/AudioSegment.cpp
@@ -141,16 +141,113 @@ void AudioSegment::ResampleChunks(SpeexR
       Resample<int16_t>(aResampler, aInRate, aOutRate);
     break;
     default:
       MOZ_ASSERT(false);
     break;
   }
 }
 
+// This helps to to safely get a pointer to the position we want to start
+// writing a planar audio buffer, depending on the channel and the offset in the
+// buffer.
+static AudioDataValue*
+PointerForOffsetInChannel(AudioDataValue* aData, size_t aLengthSamples,
+                          uint32_t aChannelCount, uint32_t aChannel,
+                          uint32_t aOffsetSamples)
+{
+  size_t samplesPerChannel = aLengthSamples / aChannelCount;
+  size_t beginningOfChannel = samplesPerChannel * aChannel;
+  MOZ_ASSERT(aChannel * samplesPerChannel + aOffsetSamples < aLengthSamples,
+             "Offset request out of bounds.");
+  return aData + beginningOfChannel + aOffsetSamples;
+}
+
+void
+AudioSegment::Mix(AudioMixer& aMixer, uint32_t aOutputChannels,
+                  uint32_t aSampleRate)
+{
+  nsAutoTArray<AudioDataValue, AUDIO_PROCESSING_FRAMES* GUESS_AUDIO_CHANNELS>
+  buf;
+  nsAutoTArray<const void*, GUESS_AUDIO_CHANNELS> channelData;
+  uint32_t offsetSamples = 0;
+  uint32_t duration = GetDuration();
+
+  if (duration <= 0) {
+    MOZ_ASSERT(duration == 0);
+    return;
+  }
+
+  uint32_t outBufferLength = duration * aOutputChannels;
+  buf.SetLength(outBufferLength);
+
+  for (ChunkIterator ci(*this); !ci.IsEnded(); ci.Next()) {
+    AudioChunk& c = *ci;
+    uint32_t frames = c.mDuration;
+
+    // If the chunk is silent, simply write the right number of silence in the
+    // buffers.
+    if (c.mBufferFormat == AUDIO_FORMAT_SILENCE) {
+      for (uint32_t channel = 0; channel < aOutputChannels; channel++) {
+        AudioDataValue* ptr =
+          PointerForOffsetInChannel(buf.Elements(), outBufferLength,
+                                    aOutputChannels, channel, offsetSamples);
+        PodZero(ptr, frames);
+      }
+    } else {
+      // Othewise, we need to upmix or downmix appropriately, depending on the
+      // desired input and output channels.
+      channelData.SetLength(c.mChannelData.Length());
+      for (uint32_t i = 0; i < channelData.Length(); ++i) {
+        channelData[i] = c.mChannelData[i];
+      }
+      if (channelData.Length() < aOutputChannels) {
+        // Up-mix.
+        AudioChannelsUpMix(&channelData, aOutputChannels, gZeroChannel);
+        for (uint32_t channel = 0; channel < aOutputChannels; channel++) {
+          AudioDataValue* ptr =
+            PointerForOffsetInChannel(buf.Elements(), outBufferLength,
+                                      aOutputChannels, channel, offsetSamples);
+          PodCopy(ptr, reinterpret_cast<const AudioDataValue*>(channelData[channel]),
+                  frames);
+        }
+        MOZ_ASSERT(channelData.Length() == aOutputChannels);
+      } else if (channelData.Length() > aOutputChannels) {
+        // Down mix.
+        nsAutoTArray<AudioDataValue*, GUESS_AUDIO_CHANNELS> outChannelPtrs;
+        outChannelPtrs.SetLength(aOutputChannels);
+        uint32_t offsetSamples = 0;
+        for (uint32_t channel = 0; channel < aOutputChannels; channel++) {
+          outChannelPtrs[channel] =
+            PointerForOffsetInChannel(buf.Elements(), outBufferLength,
+                                      aOutputChannels, channel, offsetSamples);
+        }
+        AudioChannelsDownMix(channelData, outChannelPtrs.Elements(),
+                             aOutputChannels, frames);
+      } else {
+        // The channel count is already what we want, just copy it over.
+        for (uint32_t channel = 0; channel < aOutputChannels; channel++) {
+          AudioDataValue* ptr =
+            PointerForOffsetInChannel(buf.Elements(), outBufferLength,
+                                      aOutputChannels, channel, offsetSamples);
+          PodCopy(ptr, reinterpret_cast<const AudioDataValue*>(channelData[channel]),
+                  frames);
+        }
+      }
+    }
+    offsetSamples += frames;
+  }
+
+  if (offsetSamples) {
+    MOZ_ASSERT(offsetSamples == outBufferLength / aOutputChannels,
+               "We forgot to write some samples?");
+    aMixer.Mix(buf.Elements(), aOutputChannels, offsetSamples, aSampleRate);
+  }
+}
+
 void
 AudioSegment::WriteTo(uint64_t aID, AudioMixer& aMixer, uint32_t aOutputChannels, uint32_t aSampleRate)
 {
   nsAutoTArray<AudioDataValue,AUDIO_PROCESSING_FRAMES*GUESS_AUDIO_CHANNELS> buf;
   nsAutoTArray<const void*,GUESS_AUDIO_CHANNELS> channelData;
   // Offset in the buffer that will end up sent to the AudioStream, in samples.
   uint32_t offset = 0;
 
--- a/dom/media/AudioSegment.h
+++ b/dom/media/AudioSegment.h
@@ -294,17 +294,24 @@ public:
     chunk->mVolume = aChunk->mVolume;
     chunk->mBufferFormat = aChunk->mBufferFormat;
 #ifdef MOZILLA_INTERNAL_API
     chunk->mTimeStamp = TimeStamp::Now();
 #endif
     return chunk;
   }
   void ApplyVolume(float aVolume);
-  void WriteTo(uint64_t aID, AudioMixer& aMixer, uint32_t aChannelCount, uint32_t aSampleRate);
+  // Mix the segment into a mixer, interleaved. This is useful to output a
+  // segment to a system audio callback. It up or down mixes to aChannelCount
+  // channels.
+  void WriteTo(uint64_t aID, AudioMixer& aMixer, uint32_t aChannelCount,
+               uint32_t aSampleRate);
+  // Mix the segment into a mixer, keeping it planar, up or down mixing to
+  // aChannelCount channels.
+  void Mix(AudioMixer& aMixer, uint32_t aChannelCount, uint32_t aSampleRate);
 
   int ChannelCount() {
     NS_WARN_IF_FALSE(!mChunks.IsEmpty(),
         "Cannot query channel count on a AudioSegment with no chunks.");
     // Find the first chunk that has non-zero channels. A chunk that hs zero
     // channels is just silence and we can simply discard it.
     for (ChunkIterator ci(*this); !ci.IsEnded(); ci.Next()) {
       if (ci->ChannelCount()) {
--- a/dom/media/DOMMediaStream.cpp
+++ b/dom/media/DOMMediaStream.cpp
@@ -297,16 +297,28 @@ DOMMediaStream::InitTrackUnionStream(nsI
 
   if (!aGraph) {
     aGraph = MediaStreamGraph::GetInstance();
   }
   InitStreamCommon(aGraph->CreateTrackUnionStream(this));
 }
 
 void
+DOMMediaStream::InitAudioCaptureStream(nsIDOMWindow* aWindow,
+                                       MediaStreamGraph* aGraph)
+{
+  mWindow = aWindow;
+
+  if (!aGraph) {
+    aGraph = MediaStreamGraph::GetInstance();
+  }
+  InitStreamCommon(aGraph->CreateAudioCaptureStream(this));
+}
+
+void
 DOMMediaStream::InitStreamCommon(MediaStream* aStream)
 {
   mStream = aStream;
 
   // Setup track listener
   mListener = new StreamListener(this);
   aStream->AddListener(mListener);
 }
@@ -324,16 +336,25 @@ already_AddRefed<DOMMediaStream>
 DOMMediaStream::CreateTrackUnionStream(nsIDOMWindow* aWindow,
                                        MediaStreamGraph* aGraph)
 {
   nsRefPtr<DOMMediaStream> stream = new DOMMediaStream();
   stream->InitTrackUnionStream(aWindow, aGraph);
   return stream.forget();
 }
 
+already_AddRefed<DOMMediaStream>
+DOMMediaStream::CreateAudioCaptureStream(nsIDOMWindow* aWindow,
+                                         MediaStreamGraph* aGraph)
+{
+  nsRefPtr<DOMMediaStream> stream = new DOMMediaStream();
+  stream->InitAudioCaptureStream(aWindow, aGraph);
+  return stream.forget();
+}
+
 void
 DOMMediaStream::SetTrackEnabled(TrackID aTrackID, bool aEnabled)
 {
   if (mStream) {
     mStream->SetTrackEnabled(aTrackID, aEnabled);
   }
 }
 
@@ -648,16 +669,25 @@ already_AddRefed<DOMLocalMediaStream>
 DOMLocalMediaStream::CreateTrackUnionStream(nsIDOMWindow* aWindow,
                                             MediaStreamGraph* aGraph)
 {
   nsRefPtr<DOMLocalMediaStream> stream = new DOMLocalMediaStream();
   stream->InitTrackUnionStream(aWindow, aGraph);
   return stream.forget();
 }
 
+already_AddRefed<DOMLocalMediaStream>
+DOMLocalMediaStream::CreateAudioCaptureStream(nsIDOMWindow* aWindow,
+                                              MediaStreamGraph* aGraph)
+{
+  nsRefPtr<DOMLocalMediaStream> stream = new DOMLocalMediaStream();
+  stream->InitAudioCaptureStream(aWindow, aGraph);
+  return stream.forget();
+}
+
 DOMAudioNodeMediaStream::DOMAudioNodeMediaStream(AudioNode* aNode)
 : mStreamNode(aNode)
 {
 }
 
 DOMAudioNodeMediaStream::~DOMAudioNodeMediaStream()
 {
 }
--- a/dom/media/DOMMediaStream.h
+++ b/dom/media/DOMMediaStream.h
@@ -193,16 +193,23 @@ public:
                                                              MediaStreamGraph* aGraph = nullptr);
 
   /**
    * Create an nsDOMMediaStream whose underlying stream is a TrackUnionStream.
    */
   static already_AddRefed<DOMMediaStream> CreateTrackUnionStream(nsIDOMWindow* aWindow,
                                                                  MediaStreamGraph* aGraph = nullptr);
 
+  /**
+   * Create an nsDOMMediaStream whose underlying stream is an
+   * AudioCaptureStream
+   */
+  static already_AddRefed<DOMMediaStream> CreateAudioCaptureStream(
+    nsIDOMWindow* aWindow, MediaStreamGraph* aGraph = nullptr);
+
   void SetLogicalStreamStartTime(StreamTime aTime)
   {
     mLogicalStreamStartTime = aTime;
   }
 
   // Notifications from StreamListener.
   // BindDOMTrack should only be called when it's safe to run script.
   MediaStreamTrack* BindDOMTrack(TrackID aTrackID, MediaSegment::Type aType);
@@ -256,16 +263,18 @@ public:
 protected:
   virtual ~DOMMediaStream();
 
   void Destroy();
   void InitSourceStream(nsIDOMWindow* aWindow,
                         MediaStreamGraph* aGraph = nullptr);
   void InitTrackUnionStream(nsIDOMWindow* aWindow,
                             MediaStreamGraph* aGraph = nullptr);
+  void InitAudioCaptureStream(nsIDOMWindow* aWindow,
+                              MediaStreamGraph* aGraph = nullptr);
   void InitStreamCommon(MediaStream* aStream);
   already_AddRefed<AudioTrack> CreateAudioTrack(AudioStreamTrack* aStreamTrack);
   already_AddRefed<VideoTrack> CreateVideoTrack(VideoStreamTrack* aStreamTrack);
 
   // Called when MediaStreamGraph has finished an iteration where tracks were
   // created.
   void TracksCreated();
 
@@ -346,16 +355,22 @@ public:
 
   /**
    * Create an nsDOMLocalMediaStream whose underlying stream is a TrackUnionStream.
    */
   static already_AddRefed<DOMLocalMediaStream>
   CreateTrackUnionStream(nsIDOMWindow* aWindow,
                          MediaStreamGraph* aGraph = nullptr);
 
+  /**
+   * Create an nsDOMLocalMediaStream whose underlying stream is an
+   * AudioCaptureStream. */
+  static already_AddRefed<DOMLocalMediaStream> CreateAudioCaptureStream(
+    nsIDOMWindow* aWindow, MediaStreamGraph* aGraph = nullptr);
+
 protected:
   virtual ~DOMLocalMediaStream();
 };
 
 NS_DEFINE_STATIC_IID_ACCESSOR(DOMLocalMediaStream,
                               NS_DOMLOCALMEDIASTREAM_IID)
 
 class DOMAudioNodeMediaStream : public DOMMediaStream
--- a/dom/media/DecodedStream.cpp
+++ b/dom/media/DecodedStream.cpp
@@ -284,16 +284,24 @@ DecodedStream::RecreateData(MediaStreamG
 nsTArray<OutputStreamData>&
 DecodedStream::OutputStreams()
 {
   MOZ_ASSERT(NS_IsMainThread());
   GetReentrantMonitor().AssertCurrentThreadIn();
   return mOutputStreams;
 }
 
+bool
+DecodedStream::HasConsumers() const
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  ReentrantMonitorAutoEnter mon(GetReentrantMonitor());
+  return mOutputStreams.IsEmpty();
+}
+
 ReentrantMonitor&
 DecodedStream::GetReentrantMonitor() const
 {
   return mMonitor;
 }
 
 void
 DecodedStream::Connect(OutputStreamData* aStream)
--- a/dom/media/DecodedStream.h
+++ b/dom/media/DecodedStream.h
@@ -109,16 +109,17 @@ public:
   void DestroyData();
   void RecreateData();
   void Connect(ProcessedMediaStream* aStream, bool aFinishWhenEnded);
   void Remove(MediaStream* aStream);
   void SetPlaying(bool aPlaying);
   int64_t AudioEndTime() const;
   int64_t GetPosition() const;
   bool IsFinished() const;
+  bool HasConsumers() const;
 
   // Return true if stream is finished.
   bool SendData(double aVolume, bool aIsSameOrigin);
 
 protected:
   virtual ~DecodedStream() {}
 
 private:
--- a/dom/media/MediaDecoder.cpp
+++ b/dom/media/MediaDecoder.cpp
@@ -321,16 +321,23 @@ void MediaDecoder::SetVolume(double aVol
 void MediaDecoder::AddOutputStream(ProcessedMediaStream* aStream,
                                    bool aFinishWhenEnded)
 {
   MOZ_ASSERT(NS_IsMainThread());
   MOZ_ASSERT(mDecoderStateMachine, "Must be called after Load().");
   mDecoderStateMachine->AddOutputStream(aStream, aFinishWhenEnded);
 }
 
+void MediaDecoder::RemoveOutputStream(MediaStream* aStream)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  MOZ_ASSERT(mDecoderStateMachine, "Must be called after Load().");
+  mDecoderStateMachine->RemoveOutputStream(aStream);
+}
+
 double MediaDecoder::GetDuration()
 {
   MOZ_ASSERT(NS_IsMainThread());
   return mDuration;
 }
 
 AbstractCanonical<media::NullableTimeUnit>*
 MediaDecoder::CanonicalDurationOrNull()
--- a/dom/media/MediaDecoder.h
+++ b/dom/media/MediaDecoder.h
@@ -394,16 +394,18 @@ public:
   // captureStream(UntilEnded). Seeking creates a new source stream, as does
   // replaying after the input as ended. In the latter case, the new source is
   // not connected to streams created by captureStreamUntilEnded.
 
   // Add an output stream. All decoder output will be sent to the stream.
   // The stream is initially blocked. The decoder is responsible for unblocking
   // it while it is playing back.
   virtual void AddOutputStream(ProcessedMediaStream* aStream, bool aFinishWhenEnded);
+  // Remove an output stream added with AddOutputStream.
+  virtual void RemoveOutputStream(MediaStream* aStream);
 
   // Return the duration of the video in seconds.
   virtual double GetDuration();
 
   // A media stream is assumed to be infinite if the metadata doesn't
   // contain the duration, and range requests are not supported, and
   // no headers give a hint of a possible duration (Content-Length,
   // Content-Duration, and variants), and we cannot seek in the media
--- a/dom/media/MediaDecoderStateMachine.cpp
+++ b/dom/media/MediaDecoderStateMachine.cpp
@@ -3135,25 +3135,54 @@ void MediaDecoderStateMachine::DispatchA
         self->mDecodedStream->StartPlayback(self->GetMediaTime(), self->mInfo);
       }
       self->ScheduleStateMachine();
     }
   });
   OwnerThread()->Dispatch(r.forget());
 }
 
+void MediaDecoderStateMachine::DispatchAudioUncaptured()
+{
+  nsRefPtr<MediaDecoderStateMachine> self = this;
+  nsCOMPtr<nsIRunnable> r = NS_NewRunnableFunction([self] () -> void
+  {
+    MOZ_ASSERT(self->OnTaskQueue());
+    ReentrantMonitorAutoEnter mon(self->mDecoder->GetReentrantMonitor());
+    if (self->mAudioCaptured) {
+      // Start again the audio sink
+      self->mAudioCaptured = false;
+      if (self->IsPlaying()) {
+        self->StartAudioThread();
+      }
+      self->ScheduleStateMachine();
+    }
+  });
+  OwnerThread()->Dispatch(r.forget());
+}
+
 void MediaDecoderStateMachine::AddOutputStream(ProcessedMediaStream* aStream,
                                                bool aFinishWhenEnded)
 {
   MOZ_ASSERT(NS_IsMainThread());
   DECODER_LOG("AddOutputStream aStream=%p!", aStream);
   mDecodedStream->Connect(aStream, aFinishWhenEnded);
   DispatchAudioCaptured();
 }
 
+void MediaDecoderStateMachine::RemoveOutputStream(MediaStream* aStream)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  DECODER_LOG("RemoveOutputStream=%p!", aStream);
+  mDecodedStream->Remove(aStream);
+  if (!mDecodedStream->HasConsumers()) {
+    DispatchAudioUncaptured();
+  }
+}
+
 } // namespace mozilla
 
 // avoid redefined macro in unified build
 #undef LOG
 #undef DECODER_LOG
 #undef VERBOSE_LOG
 #undef DECODER_WARN
 #undef DECODER_WARN_HELPER
--- a/dom/media/MediaDecoderStateMachine.h
+++ b/dom/media/MediaDecoderStateMachine.h
@@ -143,27 +143,30 @@ public:
     DECODER_STATE_SEEKING,
     DECODER_STATE_BUFFERING,
     DECODER_STATE_COMPLETED,
     DECODER_STATE_SHUTDOWN,
     DECODER_STATE_ERROR
   };
 
   void AddOutputStream(ProcessedMediaStream* aStream, bool aFinishWhenEnded);
+  // Remove an output stream added with AddOutputStream.
+  void RemoveOutputStream(MediaStream* aStream);
 
   // Set/Unset dormant state.
   void SetDormant(bool aDormant);
 
 private:
   // Initialization that needs to happen on the task queue. This is the first
   // task that gets run on the task queue, and is dispatched from the MDSM
   // constructor immediately after the task queue is created.
   void InitializationTask();
 
   void DispatchAudioCaptured();
+  void DispatchAudioUncaptured();
 
   void Shutdown();
 public:
 
   void DispatchShutdown()
   {
     nsCOMPtr<nsIRunnable> runnable =
       NS_NewRunnableMethod(this, &MediaDecoderStateMachine::Shutdown);
--- a/dom/media/MediaManager.cpp
+++ b/dom/media/MediaManager.cpp
@@ -295,30 +295,29 @@ protected:
 };
 
 /**
  * nsIMediaDevice implementation.
  */
 NS_IMPL_ISUPPORTS(MediaDevice, nsIMediaDevice)
 
 MediaDevice::MediaDevice(MediaEngineSource* aSource, bool aIsVideo)
-  : mSource(aSource)
+  : mMediaSource(aSource->GetMediaSource())
+  , mSource(aSource)
   , mIsVideo(aIsVideo)
 {
   mSource->GetName(mName);
   nsCString id;
   mSource->GetUUID(id);
   CopyUTF8toUTF16(id, mID);
 }
 
 VideoDevice::VideoDevice(MediaEngineVideoSource* aSource)
   : MediaDevice(aSource, true)
-{
-  mMediaSource = aSource->GetMediaSource();
-}
+{}
 
 /**
  * Helper functions that implement the constraints algorithm from
  * http://dev.w3.org/2011/webrtc/editor/getusermedia.html#methods-5
  */
 
 bool
 MediaDevice::StringsContain(const OwningStringOrStringSequence& aStrings,
@@ -434,16 +433,18 @@ MediaDevice::SetId(const nsAString& aID)
   mID.Assign(aID);
 }
 
 NS_IMETHODIMP
 MediaDevice::GetMediaSource(nsAString& aMediaSource)
 {
   if (mMediaSource == dom::MediaSourceEnum::Microphone) {
     aMediaSource.Assign(NS_LITERAL_STRING("microphone"));
+  } else if (mMediaSource == dom::MediaSourceEnum::AudioCapture) {
+    aMediaSource.Assign(NS_LITERAL_STRING("audioCapture"));
   } else if (mMediaSource == dom::MediaSourceEnum::Window) { // this will go away
     aMediaSource.Assign(NS_LITERAL_STRING("window"));
   } else { // all the rest are shared
     aMediaSource.Assign(NS_ConvertUTF8toUTF16(
       dom::MediaSourceEnumValues::strings[uint32_t(mMediaSource)].value));
   }
   return NS_OK;
 }
@@ -779,89 +780,103 @@ public:
         branch->GetBoolPref("media.getusermedia.agc_enabled", &agc_on);
         branch->GetIntPref("media.getusermedia.agc", &agc);
         branch->GetBoolPref("media.getusermedia.noise_enabled", &noise_on);
         branch->GetIntPref("media.getusermedia.noise", &noise);
         branch->GetIntPref("media.getusermedia.playout_delay", &playout_delay);
       }
     }
 #endif
-    // Create a media stream.
-    nsRefPtr<nsDOMUserMediaStream> trackunion =
-      nsDOMUserMediaStream::CreateTrackUnionStream(window, mListener,
-                                                   mAudioSource, mVideoSource);
-    if (!trackunion || sInShutdown) {
+
+    MediaStreamGraph* msg = MediaStreamGraph::GetInstance();
+    nsRefPtr<SourceMediaStream> stream = msg->CreateSourceStream(nullptr);
+
+    nsRefPtr<DOMLocalMediaStream> domStream;
+    // AudioCapture is a special case, here, in the sense that we're not really
+    // using the audio source and the SourceMediaStream, which acts as
+    // placeholders. We re-route a number of stream internaly in the MSG and mix
+    // them down instead.
+    if (mAudioSource &&
+        mAudioSource->GetMediaSource() == dom::MediaSourceEnum::AudioCapture) {
+      domStream = DOMLocalMediaStream::CreateAudioCaptureStream(window);
+      // It should be possible to pipe the capture stream to anything. CORS is
+      // not a problem here, we got explicit user content.
+      domStream->SetPrincipal(window->GetExtantDoc()->NodePrincipal());
+      msg->RegisterCaptureStreamForWindow(
+            mWindowID, domStream->GetStream()->AsProcessedStream());
+      window->SetAudioCapture(true);
+    } else {
+      // Normal case, connect the source stream to the track union stream to
+      // avoid us blocking
+      nsRefPtr<nsDOMUserMediaStream> trackunion =
+        nsDOMUserMediaStream::CreateTrackUnionStream(window, mListener,
+                                                     mAudioSource, mVideoSource);
+      trackunion->GetStream()->AsProcessedStream()->SetAutofinish(true);
+      nsRefPtr<MediaInputPort> port = trackunion->GetStream()->AsProcessedStream()->
+        AllocateInputPort(stream, MediaInputPort::FLAG_BLOCK_OUTPUT);
+      trackunion->mSourceStream = stream;
+      trackunion->mPort = port.forget();
+      // Log the relationship between SourceMediaStream and TrackUnion stream
+      // Make sure logger starts before capture
+      AsyncLatencyLogger::Get(true);
+      LogLatency(AsyncLatencyLogger::MediaStreamCreate,
+          reinterpret_cast<uint64_t>(stream.get()),
+          reinterpret_cast<int64_t>(trackunion->GetStream()));
+
+      nsCOMPtr<nsIPrincipal> principal;
+      if (mPeerIdentity) {
+        principal = nsNullPrincipal::Create();
+        trackunion->SetPeerIdentity(mPeerIdentity.forget());
+      } else {
+        principal = window->GetExtantDoc()->NodePrincipal();
+      }
+      trackunion->CombineWithPrincipal(principal);
+
+      domStream = trackunion.forget();
+    }
+
+    if (!domStream || sInShutdown) {
       nsCOMPtr<nsIDOMGetUserMediaErrorCallback> onFailure = mOnFailure.forget();
       LOG(("Returning error for getUserMedia() - no stream"));
 
       nsGlobalWindow* window = nsGlobalWindow::GetInnerWindowWithId(mWindowID);
       if (window) {
         nsRefPtr<MediaStreamError> error = new MediaStreamError(window,
             NS_LITERAL_STRING("InternalError"),
             sInShutdown ? NS_LITERAL_STRING("In shutdown") :
                           NS_LITERAL_STRING("No stream."));
         onFailure->OnError(error);
       }
       return NS_OK;
     }
-    trackunion->AudioConfig(aec_on, (uint32_t) aec,
-                            agc_on, (uint32_t) agc,
-                            noise_on, (uint32_t) noise,
-                            playout_delay);
-
-
-    MediaStreamGraph* gm = MediaStreamGraph::GetInstance();
-    nsRefPtr<SourceMediaStream> stream = gm->CreateSourceStream(nullptr);
-
-    // connect the source stream to the track union stream to avoid us blocking
-    trackunion->GetStream()->AsProcessedStream()->SetAutofinish(true);
-    nsRefPtr<MediaInputPort> port = trackunion->GetStream()->AsProcessedStream()->
-      AllocateInputPort(stream, MediaInputPort::FLAG_BLOCK_OUTPUT);
-    trackunion->mSourceStream = stream;
-    trackunion->mPort = port.forget();
-    // Log the relationship between SourceMediaStream and TrackUnion stream
-    // Make sure logger starts before capture
-    AsyncLatencyLogger::Get(true);
-    LogLatency(AsyncLatencyLogger::MediaStreamCreate,
-               reinterpret_cast<uint64_t>(stream.get()),
-               reinterpret_cast<int64_t>(trackunion->GetStream()));
-
-    nsCOMPtr<nsIPrincipal> principal;
-    if (mPeerIdentity) {
-      principal = nsNullPrincipal::Create();
-      trackunion->SetPeerIdentity(mPeerIdentity.forget());
-    } else {
-      principal = window->GetExtantDoc()->NodePrincipal();
-    }
-    trackunion->CombineWithPrincipal(principal);
 
     // The listener was added at the beginning in an inactive state.
     // Activate our listener. We'll call Start() on the source when get a callback
     // that the MediaStream has started consuming. The listener is freed
     // when the page is invalidated (on navigation or close).
     mListener->Activate(stream.forget(), mAudioSource, mVideoSource);
 
     // Note: includes JS callbacks; must be released on MainThread
     TracksAvailableCallback* tracksAvailableCallback =
-      new TracksAvailableCallback(mManager, mOnSuccess, mWindowID, trackunion);
+      new TracksAvailableCallback(mManager, mOnSuccess, mWindowID, domStream);
 
     mListener->AudioConfig(aec_on, (uint32_t) aec,
                            agc_on, (uint32_t) agc,
                            noise_on, (uint32_t) noise,
                            playout_delay);
 
     // Dispatch to the media thread to ask it to start the sources,
     // because that can take a while.
     // Pass ownership of trackunion to the MediaOperationTask
     // to ensure it's kept alive until the MediaOperationTask runs (at least).
-    MediaManager::PostTask(FROM_HERE,
-      new MediaOperationTask(MEDIA_START, mListener, trackunion,
-                             tracksAvailableCallback,
-                             mAudioSource, mVideoSource, false, mWindowID,
-                             mOnFailure.forget()));
+    MediaManager::PostTask(
+      FROM_HERE, new MediaOperationTask(MEDIA_START, mListener, domStream,
+                                        tracksAvailableCallback, mAudioSource,
+                                        mVideoSource, false, mWindowID,
+                                        mOnFailure.forget()));
     // We won't need mOnFailure now.
     mOnFailure = nullptr;
 
     if (!MediaManager::IsPrivateBrowsing(window)) {
       // Call GetOriginKey again, this time w/persist = true, to promote
       // deviceIds to persistent, in case they're not already. Fire'n'forget.
       nsRefPtr<Pledge<nsCString>> p = media::GetOriginKey(mOrigin, false, true);
     }
@@ -1240,17 +1255,19 @@ static auto& MediaManager_ToJSArray = Me
 static auto& MediaManager_AnonymizeDevices = MediaManager::AnonymizeDevices;
 
 /**
  * EnumerateRawDevices - Enumerate a list of audio & video devices that
  * satisfy passed-in constraints. List contains raw id's.
  */
 
 already_AddRefed<MediaManager::PledgeSourceSet>
-MediaManager::EnumerateRawDevices(uint64_t aWindowId, MediaSourceEnum aVideoType,
+MediaManager::EnumerateRawDevices(uint64_t aWindowId,
+                                  MediaSourceEnum aVideoType,
+                                  MediaSourceEnum aAudioType,
                                   bool aFake, bool aFakeTracks)
 {
   MOZ_ASSERT(NS_IsMainThread());
   nsRefPtr<PledgeSourceSet> p = new PledgeSourceSet();
   uint32_t id = mOutstandingPledges.Append(*p);
 
   // Check if the preference for using audio/video loopback devices is
   // enabled. This is currently used for automated media tests only.
@@ -1270,17 +1287,18 @@ MediaManager::EnumerateRawDevices(uint64
       }
     } else {
       aFake = false;
     }
   }
 
   MediaManager::PostTask(FROM_HERE, NewTaskFrom([id, aWindowId, audioLoopDev,
                                                  videoLoopDev, aVideoType,
-                                                 aFake, aFakeTracks]() mutable {
+                                                 aAudioType, aFake,
+                                                 aFakeTracks]() mutable {
     nsRefPtr<MediaEngine> backend;
     if (aFake) {
       backend = new MediaEngineDefault(aFakeTracks);
     } else {
       nsRefPtr<MediaManager> manager = MediaManager_GetInstance();
       backend = manager->GetBackend(aWindowId);
     }
 
@@ -1289,17 +1307,17 @@ MediaManager::EnumerateRawDevices(uint64
     nsTArray<nsRefPtr<VideoDevice>> videos;
     GetSources(backend, aVideoType, &MediaEngine::EnumerateVideoDevices, videos,
                videoLoopDev);
     for (auto& source : videos) {
       result->AppendElement(source);
     }
 
     nsTArray<nsRefPtr<AudioDevice>> audios;
-    GetSources(backend, dom::MediaSourceEnum::Microphone,
+    GetSources(backend, aAudioType,
                &MediaEngine::EnumerateAudioDevices, audios, audioLoopDev);
     for (auto& source : audios) {
       result->AppendElement(source);
     }
 
     SourceSet* handoff = result.forget();
     NS_DispatchToMainThread(do_AddRef(NewRunnableFrom([id, handoff]() mutable {
       ScopedDeletePtr<SourceSet> result(handoff); // grab result
@@ -1611,16 +1629,17 @@ MediaManager::GetUserMedia(nsPIDOMWindow
     return rv;
   }
 
   if (!Preferences::GetBool("media.navigator.video.enabled", true)) {
     c.mVideo.SetAsBoolean() = false;
   }
 
   MediaSourceEnum videoType = dom::MediaSourceEnum::Camera;
+  MediaSourceEnum audioType = dom::MediaSourceEnum::Microphone;
 
   if (c.mVideo.IsMediaTrackConstraints()) {
     auto& vc = c.mVideo.GetAsMediaTrackConstraints();
     videoType = StringToEnum(dom::MediaSourceEnumValues::strings,
                              vc.mMediaSource,
                              videoType);
     switch (videoType) {
       case dom::MediaSourceEnum::Camera:
@@ -1699,16 +1718,33 @@ MediaManager::GetUserMedia(nsPIDOMWindow
     // Loop has implicit permissions within Firefox, as it is built-in,
     // and will manage the active tab and provide appropriate UI.
     if (loop && (videoType == dom::MediaSourceEnum::Window ||
                  videoType == dom::MediaSourceEnum::Application ||
                  videoType == dom::MediaSourceEnum::Screen)) {
        privileged = false;
     }
   }
+
+  if (c.mAudio.IsMediaTrackConstraints()) {
+    auto& ac = c.mAudio.GetAsMediaTrackConstraints();
+    audioType = StringToEnum(dom::MediaSourceEnumValues::strings,
+                             ac.mMediaSource,
+                             audioType);
+    // Only enable AudioCapture if the pref is enabled. If it's not, we can deny
+    // right away.
+    if (audioType == dom::MediaSourceEnum::AudioCapture &&
+        !Preferences::GetBool("media.getusermedia.audiocapture.enabled")) {
+      nsRefPtr<MediaStreamError> error =
+        new MediaStreamError(aWindow,
+            NS_LITERAL_STRING("PermissionDeniedError"));
+      onFailure->OnError(error);
+      return NS_OK;
+    }
+  }
   StreamListeners* listeners = AddWindowID(windowID);
 
   // Create a disabled listener to act as a placeholder
   nsRefPtr<GetUserMediaCallbackMediaStreamListener> listener =
     new GetUserMediaCallbackMediaStreamListener(mMediaThread, windowID);
 
   // No need for locking because we always do this in the main thread.
   listeners->AppendElement(listener);
@@ -1761,17 +1797,18 @@ MediaManager::GetUserMedia(nsPIDOMWindow
       Preferences::GetBool("media.navigator.streams.fake");
 
   bool fakeTracks = c.mFakeTracks.WasPassed()? c.mFakeTracks.Value() : false;
 
   bool askPermission = !privileged &&
       (!fake || Preferences::GetBool("media.navigator.permission.fake"));
 
   nsRefPtr<PledgeSourceSet> p = EnumerateDevicesImpl(windowID, videoType,
-                                                     fake, fakeTracks);
+                                                     audioType, fake,
+                                                     fakeTracks);
   p->Then([this, onSuccess, onFailure, windowID, c, listener, askPermission,
            prefs, isHTTPS, callID, origin](SourceSet*& aDevices) mutable {
     ScopedDeletePtr<SourceSet> devices(aDevices); // grab result
 
     // Ensure this pointer is still valid, and window is still alive.
     nsRefPtr<MediaManager> mgr = MediaManager::GetInstance();
     nsRefPtr<nsPIDOMWindow> window = static_cast<nsPIDOMWindow*>
         (nsGlobalWindow::GetInnerWindowWithId(windowID));
@@ -1917,17 +1954,19 @@ MediaManager::ToJSArray(SourceSet& aDevi
     }
   } else {
     var->SetAsEmptyArray(); // because SetAsArray() fails on zero length arrays.
   }
   return var.forget();
 }
 
 already_AddRefed<MediaManager::PledgeSourceSet>
-MediaManager::EnumerateDevicesImpl(uint64_t aWindowId, MediaSourceEnum aVideoType,
+MediaManager::EnumerateDevicesImpl(uint64_t aWindowId,
+                                   MediaSourceEnum aVideoType,
+                                   MediaSourceEnum aAudioType,
                                    bool aFake, bool aFakeTracks)
 {
   MOZ_ASSERT(NS_IsMainThread());
   nsPIDOMWindow *window = static_cast<nsPIDOMWindow*>
       (nsGlobalWindow::GetInnerWindowWithId(aWindowId));
 
   // This function returns a pledge, a promise-like object with the future result
   nsRefPtr<PledgeSourceSet> pledge = new PledgeSourceSet();
@@ -1946,22 +1985,23 @@ MediaManager::EnumerateDevicesImpl(uint6
 
   // GetOriginKey is an async API that returns a pledge (a promise-like
   // pattern). We use .Then() to pass in a lambda to run back on this same
   // thread later once GetOriginKey resolves. Needed variables are "captured"
   // (passed by value) safely into the lambda.
 
   nsRefPtr<Pledge<nsCString>> p = media::GetOriginKey(origin, privateBrowsing,
                                                       persist);
-  p->Then([id, aWindowId, aVideoType,
+  p->Then([id, aWindowId, aVideoType, aAudioType,
            aFake, aFakeTracks](const nsCString& aOriginKey) mutable {
     MOZ_ASSERT(NS_IsMainThread());
     nsRefPtr<MediaManager> mgr = MediaManager_GetInstance();
 
-    nsRefPtr<PledgeSourceSet> p = mgr->EnumerateRawDevices(aWindowId, aVideoType,
+    nsRefPtr<PledgeSourceSet> p = mgr->EnumerateRawDevices(aWindowId,
+                                                           aVideoType, aAudioType,
                                                            aFake, aFakeTracks);
     p->Then([id, aWindowId, aOriginKey](SourceSet*& aDevices) mutable {
       ScopedDeletePtr<SourceSet> devices(aDevices); // secondary result
 
       // Only run if window is still on our active list.
       nsRefPtr<MediaManager> mgr = MediaManager_GetInstance();
       if (!mgr) {
         return NS_OK;
@@ -1990,16 +2030,17 @@ MediaManager::EnumerateDevices(nsPIDOMWi
   uint64_t windowId = aWindow->WindowID();
 
   AddWindowID(windowId);
 
   bool fake = Preferences::GetBool("media.navigator.streams.fake");
 
   nsRefPtr<PledgeSourceSet> p = EnumerateDevicesImpl(windowId,
                                                      dom::MediaSourceEnum::Camera,
+                                                     dom::MediaSourceEnum::Microphone,
                                                      fake);
   p->Then([onSuccess](SourceSet*& aDevices) mutable {
     ScopedDeletePtr<SourceSet> devices(aDevices); // grab result
     nsCOMPtr<nsIWritableVariant> array = MediaManager_ToJSArray(*devices);
     onSuccess->OnSuccess(array);
   }, [onFailure](MediaStreamError& reason) mutable {
     onFailure->OnError(&reason);
   });
@@ -2070,17 +2111,17 @@ StopSharingCallback(MediaManager *aThis,
     auto length = aListeners->Length();
     for (size_t i = 0; i < length; ++i) {
       GetUserMediaCallbackMediaStreamListener *listener = aListeners->ElementAt(i);
 
       if (listener->Stream()) { // aka HasBeenActivate()ed
         listener->Invalidate();
       }
       listener->Remove();
-      listener->StopScreenWindowSharing();
+      listener->StopSharing();
     }
     aListeners->Clear();
     aThis->RemoveWindowID(aWindowID);
   }
 }
 
 
 void
@@ -2393,17 +2434,17 @@ MediaManager::Observe(nsISupports* aSubj
   } else if (!strcmp(aTopic, "getUserMedia:revoke")) {
     nsresult rv;
     // may be windowid or screen:windowid
     nsDependentString data(aData);
     if (Substring(data, 0, strlen("screen:")).EqualsLiteral("screen:")) {
       uint64_t windowID = PromiseFlatString(Substring(data, strlen("screen:"))).ToInteger64(&rv);
       MOZ_ASSERT(NS_SUCCEEDED(rv));
       if (NS_SUCCEEDED(rv)) {
-        LOG(("Revoking Screeen/windowCapture access for window %llu", windowID));
+        LOG(("Revoking Screen/windowCapture access for window %llu", windowID));
         StopScreensharing(windowID);
       }
     } else {
       uint64_t windowID = nsString(aData).ToInteger64(&rv);
       MOZ_ASSERT(NS_SUCCEEDED(rv));
       if (NS_SUCCEEDED(rv)) {
         LOG(("Revoking MediaCapture access for window %llu", windowID));
         OnNavigation(windowID);
@@ -2574,17 +2615,17 @@ static void
 StopScreensharingCallback(MediaManager *aThis,
                           uint64_t aWindowID,
                           StreamListeners *aListeners,
                           void *aData)
 {
   if (aListeners) {
     auto length = aListeners->Length();
     for (size_t i = 0; i < length; ++i) {
-      aListeners->ElementAt(i)->StopScreenWindowSharing();
+      aListeners->ElementAt(i)->StopSharing();
     }
   }
 }
 
 void
 MediaManager::StopScreensharing(uint64_t aWindowID)
 {
   // We need to stop window/screensharing for all streams in all innerwindows that
@@ -2736,29 +2777,36 @@ GetUserMediaCallbackMediaStreamListener:
                            this, nullptr, nullptr,
                            mAudioSource, mVideoSource,
                            mFinished, mWindowID, nullptr));
 }
 
 // Doesn't kill audio
 // XXX refactor to combine with Invalidate()?
 void
-GetUserMediaCallbackMediaStreamListener::StopScreenWindowSharing()
+GetUserMediaCallbackMediaStreamListener::StopSharing()
 {
   NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
   if (mVideoSource && !mStopped &&
       (mVideoSource->GetMediaSource() == dom::MediaSourceEnum::Screen ||
        mVideoSource->GetMediaSource() == dom::MediaSourceEnum::Application ||
        mVideoSource->GetMediaSource() == dom::MediaSourceEnum::Window)) {
     // Stop the whole stream if there's no audio; just the video track if we have both
     MediaManager::PostTask(FROM_HERE,
       new MediaOperationTask(mAudioSource ? MEDIA_STOP_TRACK : MEDIA_STOP,
                              this, nullptr, nullptr,
                              nullptr, mVideoSource,
                              mFinished, mWindowID, nullptr));
+  } else if (mAudioSource &&
+             mAudioSource->GetMediaSource() == dom::MediaSourceEnum::AudioCapture) {
+    nsCOMPtr<nsPIDOMWindow> window = nsGlobalWindow::GetInnerWindowWithId(mWindowID);
+    MOZ_ASSERT(window);
+    window->SetAudioCapture(false);
+    MediaStreamGraph::GetInstance()->UnregisterCaptureStreamForWindow(mWindowID);
+    mStream->Destroy();
   }
 }
 
 // Stop backend for track
 
 void
 GetUserMediaCallbackMediaStreamListener::StopTrack(TrackID aID, bool aIsAudio)
 {
--- a/dom/media/MediaManager.h
+++ b/dom/media/MediaManager.h
@@ -98,17 +98,17 @@ public:
   {
     NS_ASSERTION(mStream,"Getting stream from never-activated GUMCMSListener");
     if (!mStream) {
       return nullptr;
     }
     return mStream->AsSourceStream();
   }
 
-  void StopScreenWindowSharing();
+  void StopSharing();
 
   void StopTrack(TrackID aID, bool aIsAudio);
 
   // mVideo/AudioSource are set by Activate(), so we assume they're capturing
   // if set and represent a real capture device.
   bool CapturingVideo()
   {
     NS_ASSERTION(NS_IsMainThread(), "Only call on main thread");
@@ -592,20 +592,24 @@ private:
   static bool IsLoop(nsIURI* aDocURI);
   static nsresult GenerateUUID(nsAString& aResult);
   static nsresult AnonymizeId(nsAString& aId, const nsACString& aOriginKey);
 public: // TODO: make private once we upgrade to GCC 4.8+ on linux.
   static void AnonymizeDevices(SourceSet& aDevices, const nsACString& aOriginKey);
   static already_AddRefed<nsIWritableVariant> ToJSArray(SourceSet& aDevices);
 private:
   already_AddRefed<PledgeSourceSet>
-  EnumerateRawDevices(uint64_t aWindowId, dom::MediaSourceEnum aSrcType,
+  EnumerateRawDevices(uint64_t aWindowId,
+                      dom::MediaSourceEnum aVideoType,
+                      dom::MediaSourceEnum aAudioType,
                       bool aFake, bool aFakeTracks);
   already_AddRefed<PledgeSourceSet>
-  EnumerateDevicesImpl(uint64_t aWindowId, dom::MediaSourceEnum aSrcType,
+  EnumerateDevicesImpl(uint64_t aWindowId,
+                       dom::MediaSourceEnum aVideoSrcType,
+                       dom::MediaSourceEnum aAudioSrcType,
                        bool aFake = false, bool aFakeTracks = false);
 
   StreamListeners* AddWindowID(uint64_t aWindowId);
   WindowTable *GetActiveWindows() {
     NS_ASSERTION(NS_IsMainThread(), "Only access windowlist on main thread");
     return &mActiveWindows;
   }
 
--- a/dom/media/MediaStreamGraph.cpp
+++ b/dom/media/MediaStreamGraph.cpp
@@ -13,16 +13,17 @@
 #include "nsIObserver.h"
 #include "nsPrintfCString.h"
 #include "nsServiceManagerUtils.h"
 #include "prerror.h"
 #include "mozilla/Logging.h"
 #include "mozilla/Attributes.h"
 #include "TrackUnionStream.h"
 #include "ImageContainer.h"
+#include "AudioCaptureStream.h"
 #include "AudioChannelService.h"
 #include "AudioNodeEngine.h"
 #include "AudioNodeStream.h"
 #include "AudioNodeExternalInputStream.h"
 #include "mozilla/dom/AudioContextBinding.h"
 #include <algorithm>
 #include "DOMMediaStream.h"
 #include "GeckoProfiler.h"
@@ -3187,16 +3188,27 @@ MediaStreamGraph::CreateTrackUnionStream
   TrackUnionStream* stream = new TrackUnionStream(aWrapper);
   NS_ADDREF(stream);
   MediaStreamGraphImpl* graph = static_cast<MediaStreamGraphImpl*>(this);
   stream->SetGraphImpl(graph);
   graph->AppendMessage(new CreateMessage(stream));
   return stream;
 }
 
+ProcessedMediaStream*
+MediaStreamGraph::CreateAudioCaptureStream(DOMMediaStream* aWrapper)
+{
+  AudioCaptureStream* stream = new AudioCaptureStream(aWrapper);
+  NS_ADDREF(stream);
+  MediaStreamGraphImpl* graph = static_cast<MediaStreamGraphImpl*>(this);
+  stream->SetGraphImpl(graph);
+  graph->AppendMessage(new CreateMessage(stream));
+  return stream;
+}
+
 AudioNodeExternalInputStream*
 MediaStreamGraph::CreateAudioNodeExternalInputStream(AudioNodeEngine* aEngine, TrackRate aSampleRate)
 {
   MOZ_ASSERT(NS_IsMainThread());
   if (!aSampleRate) {
     aSampleRate = aEngine->NodeMainThread()->Context()->SampleRate();
   }
   AudioNodeExternalInputStream* stream = new AudioNodeExternalInputStream(
@@ -3551,9 +3563,70 @@ MediaStreamGraph::StartNonRealtimeProces
 
 void
 ProcessedMediaStream::AddInput(MediaInputPort* aPort)
 {
   mInputs.AppendElement(aPort);
   GraphImpl()->SetStreamOrderDirty();
 }
 
+void
+MediaStreamGraph::RegisterCaptureStreamForWindow(
+    uint64_t aWindowId, ProcessedMediaStream* aCaptureStream)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  MediaStreamGraphImpl* graphImpl = static_cast<MediaStreamGraphImpl*>(this);
+  graphImpl->RegisterCaptureStreamForWindow(aWindowId, aCaptureStream);
+}
+
+void
+MediaStreamGraphImpl::RegisterCaptureStreamForWindow(
+  uint64_t aWindowId, ProcessedMediaStream* aCaptureStream)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  WindowAndStream winAndStream;
+  winAndStream.mWindowId = aWindowId;
+  winAndStream.mCaptureStreamSink = aCaptureStream;
+  mWindowCaptureStreams.AppendElement(winAndStream);
+}
+
+void
+MediaStreamGraph::UnregisterCaptureStreamForWindow(uint64_t aWindowId)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  MediaStreamGraphImpl* graphImpl = static_cast<MediaStreamGraphImpl*>(this);
+  graphImpl->UnregisterCaptureStreamForWindow(aWindowId);
+}
+
+void
+MediaStreamGraphImpl::UnregisterCaptureStreamForWindow(uint64_t aWindowId)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  for (uint32_t i = 0; i < mWindowCaptureStreams.Length(); i++) {
+    if (mWindowCaptureStreams[i].mWindowId == aWindowId) {
+      mWindowCaptureStreams.RemoveElementAt(i);
+    }
+  }
+}
+
+already_AddRefed<MediaInputPort>
+MediaStreamGraph::ConnectToCaptureStream(uint64_t aWindowId,
+                                         MediaStream* aMediaStream)
+{
+  return aMediaStream->GraphImpl()->ConnectToCaptureStream(aWindowId,
+                                                           aMediaStream);
+}
+
+already_AddRefed<MediaInputPort>
+MediaStreamGraphImpl::ConnectToCaptureStream(uint64_t aWindowId,
+                                             MediaStream* aMediaStream)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  for (uint32_t i = 0; i < mWindowCaptureStreams.Length(); i++) {
+    if (mWindowCaptureStreams[i].mWindowId == aWindowId) {
+      ProcessedMediaStream* sink = mWindowCaptureStreams[i].mCaptureStreamSink;
+      return sink->AllocateInputPort(aMediaStream, 0);
+    }
+  }
+  return nullptr;
+}
+
 } // namespace mozilla
--- a/dom/media/MediaStreamGraph.h
+++ b/dom/media/MediaStreamGraph.h
@@ -1257,16 +1257,20 @@ public:
    * removed tracks immediately end.
    * For each added track, the track ID of the output track is the track ID
    * of the input track or one plus the maximum ID of all previously added
    * tracks, whichever is greater.
    * TODO at some point we will probably need to add API to select
    * particular tracks of each input stream.
    */
   ProcessedMediaStream* CreateTrackUnionStream(DOMMediaStream* aWrapper);
+  /**
+   * Create a stream that will mix all its audio input.
+   */
+  ProcessedMediaStream* CreateAudioCaptureStream(DOMMediaStream* aWrapper);
   // Internal AudioNodeStreams can only pass their output to another
   // AudioNode, whereas external AudioNodeStreams can pass their output
   // to an nsAudioStream for playback.
   enum AudioNodeStreamKind { SOURCE_STREAM, INTERNAL_STREAM, EXTERNAL_STREAM };
   /**
    * Create a stream that will process audio for an AudioNode.
    * Takes ownership of aEngine.  aSampleRate is the sampling rate used
    * for the stream.  If 0 is passed, the sampling rate of the engine's
@@ -1313,16 +1317,22 @@ public:
     *mPendingUpdateRunnables.AppendElement() = aRunnable;
   }
 
   /**
    * Returns graph sample rate in Hz.
    */
   TrackRate GraphRate() const { return mSampleRate; }
 
+  void RegisterCaptureStreamForWindow(uint64_t aWindowId,
+                                      ProcessedMediaStream* aCaptureStream);
+  void UnregisterCaptureStreamForWindow(uint64_t aWindowId);
+  already_AddRefed<MediaInputPort> ConnectToCaptureStream(
+    uint64_t aWindowId, MediaStream* aMediaStream);
+
 protected:
   explicit MediaStreamGraph(TrackRate aSampleRate)
     : mNextGraphUpdateIndex(1)
     , mSampleRate(aSampleRate)
   {
     MOZ_COUNT_CTOR(MediaStreamGraph);
   }
   virtual ~MediaStreamGraph()
--- a/dom/media/MediaStreamGraphImpl.h
+++ b/dom/media/MediaStreamGraphImpl.h
@@ -527,16 +527,23 @@ public:
   void EnsureNextIterationLocked()
   {
     mNeedAnotherIteration = true; // atomic
     if (mGraphDriverAsleep) { // atomic
       CurrentDriver()->WakeUp(); // Might not be the same driver; might have woken already
     }
   }
 
+  // Capture Stream API. This allows to get a mixed-down output for a window.
+  void RegisterCaptureStreamForWindow(uint64_t aWindowId,
+                                      ProcessedMediaStream* aCaptureStream);
+  void UnregisterCaptureStreamForWindow(uint64_t aWindowId);
+  already_AddRefed<MediaInputPort>
+  ConnectToCaptureStream(uint64_t aWindowId, MediaStream* aMediaStream);
+
   // Data members
   //
   /**
    * Graphs own owning references to their driver, until shutdown. When a driver
    * switch occur, previous driver is either deleted, or it's ownership is
    * passed to a event that will take care of the asynchronous cleanup, as
    * audio stream can take some time to shut down.
    */
@@ -750,16 +757,26 @@ private:
    * nsRefPtr to itself, giving it a ref-count of 1 during its entire lifetime,
    * and Destroy() nulls this self-reference in order to trigger self-deletion.
    */
   nsRefPtr<MediaStreamGraphImpl> mSelfRef;
   /**
    * Used to pass memory report information across threads.
    */
   nsTArray<AudioNodeSizes> mAudioStreamSizes;
+
+  struct WindowAndStream
+  {
+    uint64_t mWindowId;
+    nsRefPtr<ProcessedMediaStream> mCaptureStreamSink;
+  };
+  /**
+   * Stream for window audio capture.
+   */
+  nsTArray<WindowAndStream> mWindowCaptureStreams;
   /**
    * Indicates that the MSG thread should gather data for a memory report.
    */
   bool mNeedsMemoryReport;
 
 #ifdef DEBUG
   /**
    * Used to assert when AppendMessage() runs ControlMessages synchronously.
--- a/dom/media/moz.build
+++ b/dom/media/moz.build
@@ -191,16 +191,17 @@ EXPORTS.mozilla.dom += [
     'VideoPlaybackQuality.h',
     'VideoStreamTrack.h',
     'VideoTrack.h',
     'VideoTrackList.h',
 ]
 
 UNIFIED_SOURCES += [
     'AbstractThread.cpp',
+    'AudioCaptureStream.cpp',
     'AudioChannelFormat.cpp',
     'AudioCompactor.cpp',
     'AudioSegment.cpp',
     'AudioSink.cpp',
     'AudioStream.cpp',
     'AudioStreamTrack.cpp',
     'AudioTrack.cpp',
     'AudioTrackList.cpp',
--- a/dom/media/tests/mochitest/head.js
+++ b/dom/media/tests/mochitest/head.js
@@ -15,16 +15,124 @@ try {
   dump('TEST DEVICES: Using media devices:\n');
   dump('audio: ' + audioDevice + '\nvideo: ' + videoDevice + '\n');
   FAKE_ENABLED = false;
 } catch (e) {
   dump('TEST DEVICES: No test devices found (in media.{audio,video}_loopback_dev, using fake streams.\n');
   FAKE_ENABLED = true;
 }
 
+/**
+ * This class provides helpers around analysing the audio content in a stream
+ * using WebAudio AnalyserNodes.
+ *
+ * @constructor
+ * @param {object} stream
+ *                 A MediaStream object whose audio track we shall analyse.
+ */
+function AudioStreamAnalyser(ac, stream) {
+  if (stream.getAudioTracks().length === 0) {
+    throw new Error("No audio track in stream");
+  }
+  this.audioContext = ac;
+  this.stream = stream;
+  this.sourceNode = this.audioContext.createMediaStreamSource(this.stream);
+  this.analyser = this.audioContext.createAnalyser();
+  this.sourceNode.connect(this.analyser);
+  this.data = new Uint8Array(this.analyser.frequencyBinCount);
+}
+
+AudioStreamAnalyser.prototype = {
+  /**
+   * Get an array of frequency domain data for our stream's audio track.
+   *
+   * @returns {array} A Uint8Array containing the frequency domain data.
+   */
+  getByteFrequencyData: function() {
+    this.analyser.getByteFrequencyData(this.data);
+    return this.data;
+  },
+
+  /**
+   * Append a canvas to the DOM where the frequency data are drawn.
+   * Useful to debug tests.
+   */
+  enableDebugCanvas: function() {
+    var cvs = document.createElement("canvas");
+    document.getElementById("content").appendChild(cvs);
+
+    // Easy: 1px per bin
+    cvs.width = this.analyser.frequencyBinCount;
+    cvs.height = 256;
+    cvs.style.border = "1px solid red";
+
+    var c = cvs.getContext('2d');
+
+    var self = this;
+    function render() {
+      c.clearRect(0, 0, cvs.width, cvs.height);
+      var array = self.getByteFrequencyData();
+      for (var i = 0; i < array.length; i++) {
+        c.fillRect(i, (256 - (array[i])), 1, 256);
+      }
+      requestAnimationFrame(render);
+    }
+    requestAnimationFrame(render);
+  },
+
+  /**
+   * Return a Promise, that will be resolved when the function passed as
+   * argument, when called, returns true (meaning the analysis was a
+   * success).
+   *
+   * @param {function} analysisFunction
+   *        A fonction that performs an analysis, and returns true if the
+   *        analysis was a success (i.e. it found what it was looking for)
+   */
+  waitForAnalysisSuccess: function(analysisFunction) {
+    var self = this;
+    return new Promise((resolve, reject) => {
+      function analysisLoop() {
+        var success = analysisFunction(self.getByteFrequencyData());
+        if (success) {
+          resolve();
+          return;
+        }
+        // else, we need more time
+        requestAnimationFrame(analysisLoop);
+      }
+      analysisLoop();
+    });
+  },
+
+  /**
+   * Return the FFT bin index for a given frequency.
+   *
+   * @param {double} frequency
+   *        The frequency for whicht to return the bin number.
+   * @returns {integer} the index of the bin in the FFT array.
+   */
+  binIndexForFrequency: function(frequency) {
+    return 1 + Math.round(frequency *
+                          this.analyser.fftSize /
+                          this.audioContext.sampleRate);
+  },
+
+  /**
+   * Reverse operation, get the frequency for a bin index.
+   *
+   * @param {integer} index an index in an FFT array
+   * @returns {double} the frequency for this bin
+   */
+  frequencyForBinIndex: function(index) {
+    return (index - 1) *
+           this.audioContext.sampleRate /
+           this.analyser.fftSize;
+  }
+};
 
 /**
  * Create the necessary HTML elements for head and body as used by Mochitests
  *
  * @param {object} meta
  *        Meta information of the test
  * @param {string} meta.title
  *        Description of the test
@@ -131,17 +239,20 @@ function setupEnvironment() {
       ['media.peerconnection.enabled', true],
       ['media.peerconnection.identity.enabled', true],
       ['media.peerconnection.identity.timeout', 120000],
       ['media.peerconnection.ice.stun_client_maximum_transmits', 14],
       ['media.peerconnection.ice.trickle_grace_period', 30000],
       ['media.navigator.permission.disabled', true],
       ['media.navigator.streams.fake', FAKE_ENABLED],
       ['media.getusermedia.screensharing.enabled', true],
-      ['media.getusermedia.screensharing.allowed_domains', "mochi.test"]
+      ['media.getusermedia.screensharing.allowed_domains', "mochi.test"],
+      ['media.getusermedia.audiocapture.enabled', true],
+      ['media.useAudioChannelService', true],
+      ['media.recorder.audio_node.enabled', true]
     ]
   }, setTestOptions);
 
   // We don't care about waiting for this to complete, we just want to ensure
   // that we don't build up a huge backlog of GC work.
   SpecialPowers.exactGC(window);
 }
 
--- a/dom/media/tests/mochitest/mochitest.ini
+++ b/dom/media/tests/mochitest/mochitest.ini
@@ -25,16 +25,18 @@ skip-if = toolkit == 'gonk' || buildapp 
 [test_dataChannel_basicDataOnly.html]
 [test_dataChannel_basicVideo.html]
 skip-if = toolkit == 'gonk' || buildapp == 'mulet' # b2g(Bug 960442, video support for WebRTC is disabled on b2g)
 [test_dataChannel_bug1013809.html]
 skip-if = toolkit == 'gonk' || buildapp == 'mulet' # b2g emulator seems to be too slow (Bug 1016498 and 1008080)
 [test_dataChannel_noOffer.html]
 [test_enumerateDevices.html]
 skip-if = buildapp == 'mulet'
+[test_getUserMedia_audioCapture.html]
+skip-if = toolkit == 'gonk' || buildapp == 'mulet' # b2g emulator seems to be too slow (Bug 1016498 and 1008080)
 [test_getUserMedia_basicAudio.html]
 skip-if = (toolkit == 'gonk' || buildapp == 'mulet' && debug) # debug-only failure
 [test_getUserMedia_basicVideo.html]
 skip-if = (toolkit == 'gonk' || buildapp == 'mulet' && debug) # debug-only failure
 [test_getUserMedia_basicVideo_playAfterLoadedmetadata.html]
 skip-if = (toolkit == 'gonk' || buildapp == 'mulet' && debug) # debug-only failure
 [test_getUserMedia_basicScreenshare.html]
 skip-if = buildapp == 'mulet' || buildapp == 'b2g' || toolkit == 'android' # no screenshare on b2g/android # Bug 1141029 Mulet parity with B2G Desktop for TC
--- a/dom/media/tests/mochitest/pc.js
+++ b/dom/media/tests/mochitest/pc.js
@@ -638,49 +638,16 @@ DataChannelWrapper.prototype = {
    */
   toString: function() {
     return "DataChannelWrapper (" + this._pc.label + '_' + this._channel.label + ")";
   }
 };
 
 
 /**
- * This class provides helpers around analysing the audio content in a stream
- * using WebAudio AnalyserNodes.
- *
- * @constructor
- * @param {object} stream
- *                 A MediaStream object whose audio track we shall analyse.
- */
-function AudioStreamAnalyser(stream) {
-  if (stream.getAudioTracks().length === 0) {
-    throw new Error("No audio track in stream");
-  }
-  this.stream = stream;
-  this.audioContext = new AudioContext();
-  this.sourceNode = this.audioContext.createMediaStreamSource(this.stream);
-  this.analyser = this.audioContext.createAnalyser();
-  this.sourceNode.connect(this.analyser);
-  this.data = new Uint8Array(this.analyser.frequencyBinCount);
-}
-
-AudioStreamAnalyser.prototype = {
-  /**
-   * Get an array of frequency domain data for our stream's audio track.
-   *
-   * @returns {array} A Uint8Array containing the frequency domain data.
-   */
-  getByteFrequencyData: function() {
-    this.analyser.getByteFrequencyData(this.data);
-    return this.data;
-  }
-};
-
-
-/**
  * This class acts as a wrapper around a PeerConnection instance.
  *
  * @constructor
  * @param {string} label
  *        Description for the peer connection instance
  * @param {object} configuration
  *        Configuration for the peer connection instance
  */
@@ -1554,30 +1521,30 @@ PeerConnectionWrapper.prototype = {
    * audio data in the frequency domain.
    *
    * @param {object} from
    *        A PeerConnectionWrapper whose audio RTPSender we use as source for
    *        the audio flow check.
    * @returns {Promise}
    *        A promise that resolves when we're receiving the tone from |from|.
    */
-  checkReceivingToneFrom : function(from) {
+  checkReceivingToneFrom : function(audiocontext, from) {
     var inputElem = from.localMediaElements[0];
 
     // As input we use the stream of |from|'s first available audio sender.
     var inputSenderTracks = from._pc.getSenders().map(sn => sn.track);
     var inputAudioStream = from._pc.getLocalStreams()
       .find(s => s.getAudioTracks().some(t => inputSenderTracks.some(t2 => t == t2)));
-    var inputAnalyser = new AudioStreamAnalyser(inputAudioStream);
+    var inputAnalyser = new AudioStreamAnalyser(audiocontext, inputAudioStream);
 
     // It would have been nice to have a working getReceivers() here, but until
     // we do, let's use what remote streams we have.
     var outputAudioStream = this._pc.getRemoteStreams()
       .find(s => s.getAudioTracks().length > 0);
-    var outputAnalyser = new AudioStreamAnalyser(outputAudioStream);
+    var outputAnalyser = new AudioStreamAnalyser(audiocontext, outputAudioStream);
 
     var maxWithIndex = (a, b, i) => (b >= a.value) ? { value: b, index: i } : a;
     var initial = { value: -1, index: -1 };
 
     return new Promise((resolve, reject) => inputElem.ontimeupdate = () => {
       var inputData = inputAnalyser.getByteFrequencyData();
       var outputData = outputAnalyser.getByteFrequencyData();
 
new file mode 100644
--- /dev/null
+++ b/dom/media/tests/mochitest/test_getUserMedia_audioCapture.html
@@ -0,0 +1,110 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <title>Test AudioCapture </title>
+  <script type="application/javascript" src="mediaStreamPlayback.js"></script>
+</head>
+<body>
+<pre id="test">
+<script>
+
+createHTML({
+  bug: "1156472",
+  title: "Test AudioCapture with regular HTMLMediaElement, AudioContext, and HTMLMediaElement playing a MediaStream",
+  visible: true
+});
+
+scriptsReady
+.then(() => FAKE_ENABLED = false)
+.then(() => {
+  runTestWhenReady(function() {
+    // Get an opus file containing a sine wave at maximum amplitude, of duration
+    // `lengthSeconds`, and of frequency `frequency`.
+    function getSineWaveFile(frequency, lengthSeconds, callback) {
+      var chunks = [];
+      var off = new OfflineAudioContext(1, lengthSeconds * 48000, 48000);
+      var osc = off.createOscillator();
+      var rec = new MediaRecorder(osc);
+      rec.ondataavailable = function(e) {
+        chunks.push(e.data);
+      };
+      rec.onstop = function(e) {
+        var blob = new Blob(chunks, { 'type' : 'audio/ogg; codecs=opus' });
+        callback(blob);
+      }
+      osc.frequency.value = frequency;
+      osc.start();
+      rec.start();
+      off.startRendering().then(function(buffer) {
+        rec.stop();
+      });
+    }
+    /**
+     * Get two HTMLMediaElements:
+     * - One playing a sine tone from a blob (of an opus file created on the fly)
+     * - One being the output for an AudioContext's OscillatorNode, connected to
+     *   a MediaSourceDestinationNode.
+     *
+     * Also, use the AudioContext playing through its AudioDestinationNode another
+     * tone, using another OscillatorNode.
+     *
+     * Capture the output of the document, feed that back into the AudioContext,
+     * with an AnalyserNode, and check the frequency content to make sure we
+     * have recorded the three sources.
+     *
+     * The three sine tones have frequencies far apart from each other, so that we
+     * can check that the spectrum of the capture stream contains three
+     * components with a high magnitude.
+     */
+    var wavtone = createMediaElement("audio", "WaveTone");
+    var acTone = createMediaElement("audio", "audioContextTone");
+    var ac = new AudioContext();
+
+    var oscThroughMediaElement = ac.createOscillator();
+    oscThroughMediaElement.frequency.value = 1000;
+    var oscThroughAudioDestinationNode = ac.createOscillator();
+    oscThroughAudioDestinationNode.frequency.value = 5000;
+    var msDest = ac.createMediaStreamDestination();
+
+    oscThroughMediaElement.connect(msDest);
+    oscThroughAudioDestinationNode.connect(ac.destination);
+
+    acTone.mozSrcObject = msDest.stream;
+
+    getSineWaveFile(10000, 10, function(blob) {
+      wavtone.src = URL.createObjectURL(blob);
+      oscThroughMediaElement.start();
+      oscThroughAudioDestinationNode.start();
+      wavtone.loop = true;
+      wavtone.play();
+      acTone.play();
+    });
+
+    var constraints = {audio: {mediaSource: "audioCapture"}};
+
+    return getUserMedia(constraints).then((stream) => {
+      checkMediaStreamTracks(constraints, stream);
+      window.grip = stream;
+      var analyser = new AudioStreamAnalyser(ac, stream);
+      analyser.enableDebugCanvas();
+      return analyser.waitForAnalysisSuccess(function(array) {
+        // We want to find three frequency components here, around 1000, 5000
+        // and 10000Hz. Frequency are logarithmic. Also make sure we have low
+        // energy in between, not just a flat white noise.
+        return (array[analyser.binIndexForFrequency(50)]    < 50 &&
+                array[analyser.binIndexForFrequency(1000)]  > 200 &&
+                array[analyser.binIndexForFrequency(2500)]  < 50 &&
+                array[analyser.binIndexForFrequency(5000)]  > 200 &&
+                array[analyser.binIndexForFrequency(7500)]  < 50 &&
+                array[analyser.binIndexForFrequency(10000)] > 200);
+      }).then(finish);
+    }).catch(finish);
+  });
+});
+
+
+
+</script>
+</pre>
+</body>
+</html>
--- a/dom/media/tests/mochitest/test_peerConnection_replaceTrack.html
+++ b/dom/media/tests/mochitest/test_peerConnection_replaceTrack.html
@@ -131,17 +131,17 @@
             ok(pc.getLocalStreams().some(s => s.getTracks()
                                                .some(t => t == sender.track)),
                "track exists among pc's local streams");
           });
       }
     ]);
     test.chain.append([
       function PC_LOCAL_CHECK_WEBAUDIO_FLOW_PRESENT(test) {
-        return test.pcRemote.checkReceivingToneFrom(test.pcLocal);
+        return test.pcRemote.checkReceivingToneFrom(test.audioCtx, test.pcLocal);
       }
     ]);
     test.chain.append([
       function PC_LOCAL_INVALID_ADD_VIDEOTRACKS(test) {
         var stream = test.pcLocal._pc.getLocalStreams()[0];
         var track = stream.getVideoTracks()[0];
         try {
           test.pcLocal._pc.addTrack(track, stream);
--- a/dom/media/tests/mochitest/test_peerConnection_webAudio.html
+++ b/dom/media/tests/mochitest/test_peerConnection_webAudio.html
@@ -27,17 +27,17 @@ runNetworkTest(function() {
       oscillator.start();
       var dest = test.audioContext.createMediaStreamDestination();
       oscillator.connect(dest);
       test.pcLocal.attachMedia(dest.stream, 'audio', 'local');
     }
   ]);
   test.chain.append([
     function CHECK_AUDIO_FLOW(test) {
-      return test.pcRemote.checkReceivingToneFrom(test.pcLocal);
+      return test.pcRemote.checkReceivingToneFrom(test.audioContext, test.pcLocal);
     }
   ]);
   test.run();
 });
 </script>
 </pre>
 </body>
 </html>
--- a/dom/media/webaudio/AudioDestinationNode.cpp
+++ b/dom/media/webaudio/AudioDestinationNode.cpp
@@ -308,29 +308,27 @@ NS_INTERFACE_MAP_END_INHERITING(AudioNod
 
 NS_IMPL_ADDREF_INHERITED(AudioDestinationNode, AudioNode)
 NS_IMPL_RELEASE_INHERITED(AudioDestinationNode, AudioNode)
 
 AudioDestinationNode::AudioDestinationNode(AudioContext* aContext,
                                            bool aIsOffline,
                                            AudioChannel aChannel,
                                            uint32_t aNumberOfChannels,
-                                           uint32_t aLength,
-                                           float aSampleRate)
-  : AudioNode(aContext,
-              aIsOffline ? aNumberOfChannels : 2,
-              ChannelCountMode::Explicit,
-              ChannelInterpretation::Speakers)
+                                           uint32_t aLength, float aSampleRate)
+  : AudioNode(aContext, aIsOffline ? aNumberOfChannels : 2,
+              ChannelCountMode::Explicit, ChannelInterpretation::Speakers)
   , mFramesToProduce(aLength)
   , mAudioChannel(AudioChannel::Normal)
   , mIsOffline(aIsOffline)
   , mAudioChannelAgentPlaying(false)
   , mExtraCurrentTime(0)
   , mExtraCurrentTimeSinceLastStartedBlocking(0)
   , mExtraCurrentTimeUpdatedSinceLastStableState(false)
+  , mCaptured(false)
 {
   bool startWithAudioDriver = true;
   MediaStreamGraph* graph = aIsOffline ?
                             MediaStreamGraph::CreateNonRealtimeInstance(aSampleRate) :
                             MediaStreamGraph::GetInstance(startWithAudioDriver, aChannel);
   AudioNodeEngine* engine = aIsOffline ?
                             new OfflineDestinationNodeEngine(this, aNumberOfChannels,
                                                              aLength, aSampleRate) :
@@ -500,16 +498,43 @@ AudioDestinationNode::WindowVolumeChange
                 : NS_LITERAL_STRING("mozinterruptbegin"));
     }
   }
 
   SetCanPlay(aVolume, aMuted);
   return NS_OK;
 }
 
+NS_IMETHODIMP
+AudioDestinationNode::WindowAudioCaptureChanged()
+{
+  MOZ_ASSERT(mAudioChannelAgent);
+
+  if (!mStream || Context()->IsOffline()) {
+    return NS_OK;
+  }
+
+  bool captured = GetOwner()->GetAudioCaptured();
+
+  if (captured != mCaptured) {
+    if (captured) {
+      nsCOMPtr<nsPIDOMWindow> window = Context()->GetParentObject();
+      uint64_t id = window->WindowID();
+      mCaptureStreamPort =
+        mStream->Graph()->ConnectToCaptureStream(id, mStream);
+    } else {
+      mCaptureStreamPort->Disconnect();
+      mCaptureStreamPort->Destroy();
+    }
+    mCaptured = captured;
+  }
+
+  return NS_OK;
+}
+
 AudioChannel
 AudioDestinationNode::MozAudioChannelType() const
 {
   return mAudioChannel;
 }
 
 void
 AudioDestinationNode::SetMozAudioChannelType(AudioChannel aValue, ErrorResult& aRv)
@@ -586,16 +611,18 @@ AudioDestinationNode::CreateAudioChannel
   mAudioChannelAgent = new AudioChannelAgent();
   mAudioChannelAgent->InitWithWeakCallback(GetOwner(),
                                            static_cast<int32_t>(mAudioChannel),
                                            this);
 
   // The AudioChannelAgent must start playing immediately in order to avoid
   // race conditions with mozinterruptbegin/end events.
   InputMuted(false);
+
+  WindowAudioCaptureChanged();
 }
 
 void
 AudioDestinationNode::NotifyStableState()
 {
   mExtraCurrentTimeUpdatedSinceLastStableState = false;
 }
 
@@ -677,13 +704,14 @@ AudioDestinationNode::InputMuted(bool aM
 
   float volume = 0.0;
   bool muted = true;
   nsresult rv = mAudioChannelAgent->NotifyStartedPlaying(&volume, &muted);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return;
   }
 
+  WindowAudioCaptureChanged();
   WindowVolumeChanged(volume, muted);
 }
 
 } // namespace dom
 } // namespace mozilla
--- a/dom/media/webaudio/AudioDestinationNode.h
+++ b/dom/media/webaudio/AudioDestinationNode.h
@@ -94,27 +94,29 @@ private:
 
   void NotifyStableState();
   void ScheduleStableStateNotification();
 
   SelfReference<AudioDestinationNode> mOfflineRenderingRef;
   uint32_t mFramesToProduce;
 
   nsCOMPtr<nsIAudioChannelAgent> mAudioChannelAgent;
+  nsRefPtr<MediaInputPort> mCaptureStreamPort;
 
   nsRefPtr<Promise> mOfflineRenderingPromise;
 
   // Audio Channel Type.
   AudioChannel mAudioChannel;
   bool mIsOffline;
   bool mAudioChannelAgentPlaying;
 
   TimeStamp mStartedBlockingDueToBeingOnlyNode;
   double mExtraCurrentTime;
   double mExtraCurrentTimeSinceLastStartedBlocking;
   bool mExtraCurrentTimeUpdatedSinceLastStableState;
+  bool mCaptured;
 };
 
 } // namespace dom
 } // namespace mozilla
 
 #endif
 
--- a/dom/media/webrtc/MediaEngineWebRTC.cpp
+++ b/dom/media/webrtc/MediaEngineWebRTC.cpp
@@ -286,16 +286,23 @@ void
 MediaEngineWebRTC::EnumerateAudioDevices(dom::MediaSourceEnum aMediaSource,
                                          nsTArray<nsRefPtr<MediaEngineAudioSource> >* aASources)
 {
   ScopedCustomReleasePtr<webrtc::VoEBase> ptrVoEBase;
   ScopedCustomReleasePtr<webrtc::VoEHardware> ptrVoEHw;
   // We spawn threads to handle gUM runnables, so we must protect the member vars
   MutexAutoLock lock(mMutex);
 
+  if (aMediaSource == dom::MediaSourceEnum::AudioCapture) {
+    nsRefPtr<MediaEngineWebRTCAudioCaptureSource> audioCaptureSource =
+      new MediaEngineWebRTCAudioCaptureSource(nullptr);
+    aASources->AppendElement(audioCaptureSource);
+    return;
+  }
+
 #ifdef MOZ_WIDGET_ANDROID
   jobject context = mozilla::AndroidBridge::Bridge()->GetGlobalContextRef();
 
   // get the JVM
   JavaVM *jvm = mozilla::AndroidBridge::Bridge()->GetVM();
   JNIEnv *env = GetJNIForThread();
 
   if (webrtc::VoiceEngine::SetAndroidObjects(jvm, env, (void*)context) != 0) {
@@ -353,25 +360,24 @@ MediaEngineWebRTC::EnumerateAudioDevices
     }
 
     if (uniqueId[0] == '\0') {
       // Mac and Linux don't set uniqueId!
       MOZ_ASSERT(sizeof(deviceName) == sizeof(uniqueId)); // total paranoia
       strcpy(uniqueId,deviceName); // safe given assert and initialization/error-check
     }
 
-    nsRefPtr<MediaEngineWebRTCAudioSource> aSource;
+    nsRefPtr<MediaEngineAudioSource> aSource;
     NS_ConvertUTF8toUTF16 uuid(uniqueId);
     if (mAudioSources.Get(uuid, getter_AddRefs(aSource))) {
       // We've already seen this device, just append.
       aASources->AppendElement(aSource.get());
     } else {
-      aSource = new MediaEngineWebRTCAudioSource(
-        mThread, mVoiceEngine, i, deviceName, uniqueId
-      );
+      aSource = new MediaEngineWebRTCMicrophoneSource(mThread, mVoiceEngine, i,
+                                                      deviceName, uniqueId);
       mAudioSources.Put(uuid, aSource); // Hashtable takes ownership.
       aASources->AppendElement(aSource);
     }
   }
 }
 
 static PLDHashOperator
 ClearVideoSource (const nsAString&, // unused
@@ -380,19 +386,18 @@ ClearVideoSource (const nsAString&, // u
 {
   if (aData) {
     aData->Shutdown();
   }
   return PL_DHASH_NEXT;
 }
 
 static PLDHashOperator
-ClearAudioSource (const nsAString&, // unused
-                  MediaEngineWebRTCAudioSource* aData,
-                  void *userArg)
+ClearAudioSource(const nsAString &, // unused
+                 MediaEngineAudioSource *aData, void *userArg)
 {
   if (aData) {
     aData->Shutdown();
   }
   return PL_DHASH_NEXT;
 }
 
 void
--- a/dom/media/webrtc/MediaEngineWebRTC.h
+++ b/dom/media/webrtc/MediaEngineWebRTC.h
@@ -128,23 +128,87 @@ private:
 
   int mMinFps; // Min rate we want to accept
   dom::MediaSourceEnum mMediaSource; // source of media (camera | application | screen)
 
   size_t NumCapabilities() override;
   void GetCapability(size_t aIndex, webrtc::CaptureCapability& aOut) override;
 };
 
-class MediaEngineWebRTCAudioSource : public MediaEngineAudioSource,
-                                     public webrtc::VoEMediaProcess,
-                                     private MediaConstraintsHelper
+class MediaEngineWebRTCAudioCaptureSource : public MediaEngineAudioSource
 {
 public:
-  MediaEngineWebRTCAudioSource(nsIThread* aThread, webrtc::VoiceEngine* aVoiceEnginePtr,
-                               int aIndex, const char* name, const char* uuid)
+  NS_DECL_THREADSAFE_ISUPPORTS
+
+  explicit MediaEngineWebRTCAudioCaptureSource(const char* aUuid)
+    : MediaEngineAudioSource(kReleased)
+  {
+  }
+  void GetName(nsAString& aName) override;
+  void GetUUID(nsACString& aUUID) override;
+  nsresult Allocate(const dom::MediaTrackConstraints& aConstraints,
+                    const MediaEnginePrefs& aPrefs,
+                    const nsString& aDeviceId) override
+  {
+    // Nothing to do here, everything is managed in MediaManager.cpp
+    return NS_OK;
+  }
+  nsresult Deallocate() override
+  {
+    // Nothing to do here, everything is managed in MediaManager.cpp
+    return NS_OK;
+  }
+  void Shutdown() override
+  {
+    // Nothing to do here, everything is managed in MediaManager.cpp
+  }
+  nsresult Start(SourceMediaStream* aMediaStream, TrackID aId) override;
+  nsresult Stop(SourceMediaStream* aMediaStream, TrackID aId) override;
+  void SetDirectListeners(bool aDirect) override
+  {}
+  nsresult Config(bool aEchoOn, uint32_t aEcho, bool aAgcOn,
+                  uint32_t aAGC, bool aNoiseOn, uint32_t aNoise,
+                  int32_t aPlayoutDelay) override
+  {
+    return NS_OK;
+  }
+  void NotifyPull(MediaStreamGraph* aGraph, SourceMediaStream* aSource,
+                  TrackID aID, StreamTime aDesiredTime) override
+  {}
+  const dom::MediaSourceEnum GetMediaSource() override
+  {
+    return dom::MediaSourceEnum::AudioCapture;
+  }
+  bool IsFake() override
+  {
+    return false;
+  }
+  nsresult TakePhoto(PhotoCallback* aCallback) override
+  {
+    return NS_ERROR_NOT_IMPLEMENTED;
+  }
+  uint32_t GetBestFitnessDistance(
+    const nsTArray<const dom::MediaTrackConstraintSet*>& aConstraintSets,
+    const nsString& aDeviceId) override;
+
+protected:
+  virtual ~MediaEngineWebRTCAudioCaptureSource() { Shutdown(); }
+  nsCString mUUID;
+};
+
+class MediaEngineWebRTCMicrophoneSource : public MediaEngineAudioSource,
+                                          public webrtc::VoEMediaProcess,
+                                          private MediaConstraintsHelper
+{
+public:
+  MediaEngineWebRTCMicrophoneSource(nsIThread* aThread,
+                                    webrtc::VoiceEngine* aVoiceEnginePtr,
+                                    int aIndex,
+                                    const char* name,
+                                    const char* uuid)
     : MediaEngineAudioSource(kReleased)
     , mVoiceEngine(aVoiceEnginePtr)
     , mMonitor("WebRTCMic.Monitor")
     , mThread(aThread)
     , mCapIndex(aIndex)
     , mChannel(-1)
     , mInitDone(false)
     , mStarted(false)
@@ -202,17 +266,17 @@ public:
                int16_t audio10ms[], int length,
                int samplingFreq, bool isStereo) override;
 
   NS_DECL_THREADSAFE_ISUPPORTS
 
   virtual void Shutdown() override;
 
 protected:
-  ~MediaEngineWebRTCAudioSource() { Shutdown(); }
+  ~MediaEngineWebRTCMicrophoneSource() { Shutdown(); }
 
 private:
   void Init();
 
   webrtc::VoiceEngine* mVoiceEngine;
   ScopedCustomReleasePtr<webrtc::VoEBase> mVoEBase;
   ScopedCustomReleasePtr<webrtc::VoEExternalMedia> mVoERender;
   ScopedCustomReleasePtr<webrtc::VoENetwork> mVoENetwork;
@@ -289,14 +353,14 @@ private:
   bool mBrowserEngineInit;
   bool mWinEngineInit;
   bool mAppEngineInit;
   bool mHasTabVideoSource;
 
   // Store devices we've already seen in a hashtable for quick return.
   // Maps UUID to MediaEngineSource (one set for audio, one for video).
   nsRefPtrHashtable<nsStringHashKey, MediaEngineVideoSource> mVideoSources;
-  nsRefPtrHashtable<nsStringHashKey, MediaEngineWebRTCAudioSource> mAudioSources;
+  nsRefPtrHashtable<nsStringHashKey, MediaEngineAudioSource> mAudioSources;
 };
 
 }
 
 #endif /* NSMEDIAENGINEWEBRTC_H_ */
--- a/dom/media/webrtc/MediaEngineWebRTCAudio.cpp
+++ b/dom/media/webrtc/MediaEngineWebRTCAudio.cpp
@@ -36,19 +36,20 @@ namespace mozilla {
 #undef LOG
 #endif
 
 extern PRLogModuleInfo* GetMediaManagerLog();
 #define LOG(msg) MOZ_LOG(GetMediaManagerLog(), mozilla::LogLevel::Debug, msg)
 #define LOG_FRAMES(msg) MOZ_LOG(GetMediaManagerLog(), mozilla::LogLevel::Verbose, msg)
 
 /**
- * Webrtc audio source.
+ * Webrtc microphone source source.
  */
-NS_IMPL_ISUPPORTS0(MediaEngineWebRTCAudioSource)
+NS_IMPL_ISUPPORTS0(MediaEngineWebRTCMicrophoneSource)
+NS_IMPL_ISUPPORTS0(MediaEngineWebRTCAudioCaptureSource)
 
 // XXX temp until MSG supports registration
 StaticRefPtr<AudioOutputObserver> gFarendObserver;
 
 AudioOutputObserver::AudioOutputObserver()
   : mPlayoutFreq(0)
   , mPlayoutChannels(0)
   , mChunkSize(0)
@@ -172,40 +173,40 @@ AudioOutputObserver::InsertFarEnd(const 
         mSaved = nullptr;
         mSamplesSaved = 0;
       }
     }
   }
 }
 
 void
-MediaEngineWebRTCAudioSource::GetName(nsAString& aName)
+MediaEngineWebRTCMicrophoneSource::GetName(nsAString& aName)
 {
   if (mInitDone) {
     aName.Assign(mDeviceName);
   }
 
   return;
 }
 
 void
-MediaEngineWebRTCAudioSource::GetUUID(nsACString& aUUID)
+MediaEngineWebRTCMicrophoneSource::GetUUID(nsACString& aUUID)
 {
   if (mInitDone) {
     aUUID.Assign(mDeviceUUID);
   }
 
   return;
 }
 
 nsresult
-MediaEngineWebRTCAudioSource::Config(bool aEchoOn, uint32_t aEcho,
-                                     bool aAgcOn, uint32_t aAGC,
-                                     bool aNoiseOn, uint32_t aNoise,
-                                     int32_t aPlayoutDelay)
+MediaEngineWebRTCMicrophoneSource::Config(bool aEchoOn, uint32_t aEcho,
+                                          bool aAgcOn, uint32_t aAGC,
+                                          bool aNoiseOn, uint32_t aNoise,
+                                          int32_t aPlayoutDelay)
 {
   LOG(("Audio config: aec: %d, agc: %d, noise: %d",
        aEchoOn ? aEcho : -1,
        aAgcOn ? aAGC : -1,
        aNoiseOn ? aNoise : -1));
 
   bool update_echo = (mEchoOn != aEchoOn);
   bool update_agc = (mAgcOn != aAgcOn);
@@ -262,33 +263,33 @@ MediaEngineWebRTCAudioSource::Config(boo
 // GetBestFitnessDistance returns the best distance the capture device can offer
 // as a whole, given an accumulated number of ConstraintSets.
 // Ideal values are considered in the first ConstraintSet only.
 // Plain values are treated as Ideal in the first ConstraintSet.
 // Plain values are treated as Exact in subsequent ConstraintSets.
 // Infinity = UINT32_MAX e.g. device cannot satisfy accumulated ConstraintSets.
 // A finite result may be used to calculate this device's ranking as a choice.
 
-uint32_t MediaEngineWebRTCAudioSource::GetBestFitnessDistance(
+uint32_t MediaEngineWebRTCMicrophoneSource::GetBestFitnessDistance(
     const nsTArray<const dom::MediaTrackConstraintSet*>& aConstraintSets,
     const nsString& aDeviceId)
 {
   uint32_t distance = 0;
 
   for (const MediaTrackConstraintSet* cs : aConstraintSets) {
     distance = GetMinimumFitnessDistance(*cs, false, aDeviceId);
     break; // distance is read from first entry only
   }
   return distance;
 }
 
 nsresult
-MediaEngineWebRTCAudioSource::Allocate(const dom::MediaTrackConstraints &aConstraints,
-                                       const MediaEnginePrefs &aPrefs,
-                                       const nsString& aDeviceId)
+MediaEngineWebRTCMicrophoneSource::Allocate(const dom::MediaTrackConstraints &aConstraints,
+                                            const MediaEnginePrefs &aPrefs,
+                                            const nsString& aDeviceId)
 {
   if (mState == kReleased) {
     if (mInitDone) {
       ScopedCustomReleasePtr<webrtc::VoEHardware> ptrVoEHw(webrtc::VoEHardware::GetInterface(mVoiceEngine));
       if (!ptrVoEHw || ptrVoEHw->SetRecordingDevice(mCapIndex)) {
         return NS_ERROR_FAILURE;
       }
       mState = kAllocated;
@@ -304,17 +305,17 @@ MediaEngineWebRTCAudioSource::Allocate(c
     } else {
       LOG(("Audio device %d allocated shared", mCapIndex));
     }
   }
   return NS_OK;
 }
 
 nsresult
-MediaEngineWebRTCAudioSource::Deallocate()
+MediaEngineWebRTCMicrophoneSource::Deallocate()
 {
   bool empty;
   {
     MonitorAutoLock lock(mMonitor);
     empty = mSources.IsEmpty();
   }
   if (empty) {
     // If empty, no callbacks to deliver data should be occuring
@@ -326,17 +327,18 @@ MediaEngineWebRTCAudioSource::Deallocate
     LOG(("Audio device %d deallocated", mCapIndex));
   } else {
     LOG(("Audio device %d deallocated but still in use", mCapIndex));
   }
   return NS_OK;
 }
 
 nsresult
-MediaEngineWebRTCAudioSource::Start(SourceMediaStream* aStream, TrackID aID)
+MediaEngineWebRTCMicrophoneSource::Start(SourceMediaStream *aStream,
+                                         TrackID aID)
 {
   if (!mInitDone || !aStream) {
     return NS_ERROR_FAILURE;
   }
 
   {
     MonitorAutoLock lock(mMonitor);
     mSources.AppendElement(aStream);
@@ -379,17 +381,17 @@ MediaEngineWebRTCAudioSource::Start(Sour
 
   // Attach external media processor, so this::Process will be called.
   mVoERender->RegisterExternalMediaProcessing(mChannel, webrtc::kRecordingPerChannel, *this);
 
   return NS_OK;
 }
 
 nsresult
-MediaEngineWebRTCAudioSource::Stop(SourceMediaStream *aSource, TrackID aID)
+MediaEngineWebRTCMicrophoneSource::Stop(SourceMediaStream *aSource, TrackID aID)
 {
   {
     MonitorAutoLock lock(mMonitor);
 
     if (!mSources.RemoveElement(aSource)) {
       // Already stopped - this is allowed
       return NS_OK;
     }
@@ -416,27 +418,27 @@ MediaEngineWebRTCAudioSource::Stop(Sourc
   }
   if (mVoEBase->StopReceive(mChannel)) {
     return NS_ERROR_FAILURE;
   }
   return NS_OK;
 }
 
 void
-MediaEngineWebRTCAudioSource::NotifyPull(MediaStreamGraph* aGraph,
-                                         SourceMediaStream *aSource,
-                                         TrackID aID,
-                                         StreamTime aDesiredTime)
+MediaEngineWebRTCMicrophoneSource::NotifyPull(MediaStreamGraph *aGraph,
+                                              SourceMediaStream *aSource,
+                                              TrackID aID,
+                                              StreamTime aDesiredTime)
 {
   // Ignore - we push audio data
   LOG_FRAMES(("NotifyPull, desired = %ld", (int64_t) aDesiredTime));
 }
 
 void
-MediaEngineWebRTCAudioSource::Init()
+MediaEngineWebRTCMicrophoneSource::Init()
 {
   mVoEBase = webrtc::VoEBase::GetInterface(mVoiceEngine);
 
   mVoEBase->Init();
 
   mVoERender = webrtc::VoEExternalMedia::GetInterface(mVoiceEngine);
   if (!mVoERender) {
     return;
@@ -491,17 +493,17 @@ MediaEngineWebRTCAudioSource::Init()
   codec.pltype = 0; // Default payload type
 
   if (!ptrVoECodec->SetSendCodec(mChannel, codec)) {
     mInitDone = true;
   }
 }
 
 void
-MediaEngineWebRTCAudioSource::Shutdown()
+MediaEngineWebRTCMicrophoneSource::Shutdown()
 {
   if (!mInitDone) {
     // duplicate these here in case we failed during Init()
     if (mChannel != -1 && mVoENetwork) {
       mVoENetwork->DeRegisterExternalTransport(mChannel);
     }
 
     delete mNullTransport;
@@ -546,19 +548,20 @@ MediaEngineWebRTCAudioSource::Shutdown()
 
   mState = kReleased;
   mInitDone = false;
 }
 
 typedef int16_t sample;
 
 void
-MediaEngineWebRTCAudioSource::Process(int channel,
-  webrtc::ProcessingTypes type, sample* audio10ms,
-  int length, int samplingFreq, bool isStereo)
+MediaEngineWebRTCMicrophoneSource::Process(int channel,
+                                           webrtc::ProcessingTypes type,
+                                           sample *audio10ms, int length,
+                                           int samplingFreq, bool isStereo)
 {
   // On initial capture, throw away all far-end data except the most recent sample
   // since it's already irrelevant and we want to keep avoid confusing the AEC far-end
   // input code with "old" audio.
   if (!mStarted) {
     mStarted  = true;
     while (gFarendObserver->Size() > 1) {
       free(gFarendObserver->Pop()); // only call if size() > 0
@@ -613,9 +616,60 @@ MediaEngineWebRTCAudioSource::Process(in
                                           mTrackID, segment, (AudioSegment *) nullptr),
                     NS_DISPATCH_NORMAL);
     }
   }
 
   return;
 }
 
+void
+MediaEngineWebRTCAudioCaptureSource::GetName(nsAString &aName)
+{
+  aName.AssignLiteral("AudioCapture");
 }
+void
+MediaEngineWebRTCAudioCaptureSource::GetUUID(nsACString &aUUID)
+{
+  nsID uuid;
+  char uuidBuffer[NSID_LENGTH];
+  nsCString asciiString;
+  ErrorResult rv;
+
+  rv = nsContentUtils::GenerateUUIDInPlace(uuid);
+  if (rv.Failed()) {
+    aUUID.AssignLiteral("");
+    return;
+  }
+
+
+  uuid.ToProvidedString(uuidBuffer);
+  asciiString.AssignASCII(uuidBuffer);
+
+  // Remove {} and the null terminator
+  aUUID.Assign(Substring(asciiString, 1, NSID_LENGTH - 3));
+}
+
+nsresult
+MediaEngineWebRTCAudioCaptureSource::Start(SourceMediaStream *aMediaStream,
+                                           TrackID aId)
+{
+  aMediaStream->AddTrack(aId, 0, new AudioSegment());
+  return NS_OK;
+}
+
+nsresult
+MediaEngineWebRTCAudioCaptureSource::Stop(SourceMediaStream *aMediaStream,
+                                          TrackID aId)
+{
+  aMediaStream->EndAllTrackAndFinish();
+  return NS_OK;
+}
+
+uint32_t
+MediaEngineWebRTCAudioCaptureSource::GetBestFitnessDistance(
+    const nsTArray<const dom::MediaTrackConstraintSet*>& aConstraintSets,
+    const nsString& aDeviceId)
+{
+  // There is only one way of capturing audio for now, and it's always adequate.
+  return 0;
+}
+}
--- a/dom/plugins/test/mochitest/mochitest.ini
+++ b/dom/plugins/test/mochitest/mochitest.ini
@@ -1,10 +1,10 @@
 [DEFAULT]
-skip-if = ((buildapp == 'mulet' || buildapp == 'b2g') && toolkit != 'gonk') || (e10s && debug) #b2g-desktop(tests that use plugins)
+skip-if = ((buildapp == 'mulet' || buildapp == 'b2g') && toolkit != 'gonk') || (e10s && debug) || (os == "mac" && os_version == "10.6" && debug) #b2g-desktop(tests that use plugins), bug 1188052 for OSX 10.6
 support-files =
   307-xo-redirect.sjs
   crashing_subpage.html
   file_bug738396.html
   file_bug771202.html
   file_bug863792.html
   large-pic.jpg
   loremipsum.txt
--- a/dom/promise/Promise.cpp
+++ b/dom/promise/Promise.cpp
@@ -1307,17 +1307,17 @@ Promise::ResolveInternal(JSContext* aCx,
         nsRefPtr<PromiseCallback> rejectCb = new RejectPromiseCallback(this, glob);
         nsRefPtr<FastThenableResolverTask> task =
           new FastThenableResolverTask(resolveCb, rejectCb, nextPromise);
         DispatchToMicroTask(task);
         return;
       }
 
       nsRefPtr<PromiseInit> thenCallback =
-        new PromiseInit(thenObj, mozilla::dom::GetIncumbentGlobal());
+        new PromiseInit(nullptr, thenObj, mozilla::dom::GetIncumbentGlobal());
       nsRefPtr<ThenableResolverTask> task =
         new ThenableResolverTask(this, valueObj, thenCallback);
       DispatchToMicroTask(task);
       return;
     }
   }
 
   MaybeSettle(aValue, Resolved);
--- a/dom/webidl/Constraints.webidl
+++ b/dom/webidl/Constraints.webidl
@@ -20,16 +20,17 @@ enum VideoFacingModeEnum {
 
 enum MediaSourceEnum {
     "camera",
     "screen",
     "application",
     "window",
     "browser",
     "microphone",
+    "audioCapture",
     "other"
 };
 
 dictionary ConstrainLongRange {
     long min;
     long max;
     long exact;
     long ideal;
--- a/dom/workers/ServiceWorkerEvents.cpp
+++ b/dom/workers/ServiceWorkerEvents.cpp
@@ -381,17 +381,21 @@ FetchEvent::RespondWith(const ResponseOr
 already_AddRefed<ServiceWorkerClient>
 FetchEvent::GetClient()
 {
   if (!mClient) {
     if (!mClientInfo) {
       return nullptr;
     }
 
-    mClient = new ServiceWorkerClient(GetParentObject(), *mClientInfo);
+    WorkerPrivate* worker = GetCurrentThreadWorkerPrivate();
+    MOZ_ASSERT(worker);
+    nsRefPtr<nsIGlobalObject> global = worker->GlobalScope();
+
+    mClient = new ServiceWorkerClient(global, *mClientInfo);
   }
   nsRefPtr<ServiceWorkerClient> client = mClient;
   return client.forget();
 }
 
 NS_IMPL_ADDREF_INHERITED(FetchEvent, Event)
 NS_IMPL_RELEASE_INHERITED(FetchEvent, Event)
 
new file mode 100644
--- /dev/null
+++ b/dom/workers/test/serviceworkers/fetch_event_client.js
@@ -0,0 +1,6 @@
+var CLIENT_URL =
+  "http://mochi.test:8888/tests/dom/workers/test/serviceworkers/sw_clients/dummy.html"
+
+self.addEventListener("fetch", function(event) {
+  event.client.postMessage({status: event.client.url === CLIENT_URL});
+});
--- a/dom/workers/test/serviceworkers/mochitest.ini
+++ b/dom/workers/test/serviceworkers/mochitest.ini
@@ -149,16 +149,18 @@ support-files =
   register_https.html
   gzip_redirect_worker.js
   sw_clients/navigator.html
   eval_worker.js
   test_eval_not_allowed.html^headers^
   opaque_intercept_worker.js
   notify_loaded.js
   test_request_context.js
+  fetch_event_client.js
+  sw_clients/dummy.html
 
 [test_app_protocol.html]
 skip-if = release_build
 [test_bug1151916.html]
 [test_claim.html]
 [test_claim_fetch.html]
 [test_claim_oninstall.html]
 [test_client_focus.html]
@@ -235,8 +237,9 @@ skip-if = release_build
 [test_skip_waiting.html]
 [test_strict_mode_error.html]
 [test_third_party_iframes.html]
 [test_unregister.html]
 [test_workerUnregister.html]
 [test_workerUpdate.html]
 [test_workerupdatefoundevent.html]
 [test_opaque_intercept.html]
+[test_fetch_event_client_postmessage.html]
new file mode 100644
--- /dev/null
+++ b/dom/workers/test/serviceworkers/sw_clients/dummy.html
@@ -0,0 +1,19 @@
+<!--
+  Any copyright is dedicated to the Public Domain.
+  http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<!DOCTYPE HTML>
+<html>
+<head>
+  <title>Bug 1158735 - Dummy page</title>
+</head>
+<body>
+  <script type="text/javascript" >
+    window.onload = function() {
+      navigator.serviceWorker.ready.then(function(swr) {
+        fetch("foo.txt");
+      });
+    }
+  </script>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/workers/test/serviceworkers/test_fetch_event_client_postmessage.html
@@ -0,0 +1,71 @@
+<!--
+  Any copyright is dedicated to the Public Domain.
+  http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<!DOCTYPE HTML>
+<html>
+<head>
+  <title>Bug 1158735 - FetchEvent.client asserting in onFetch when there's no document.</title>
+  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+  <p id="display"></p>
+  <div id="content" style="display: none"></div>
+  <pre id="test"></pre>
+  <script class="testbody" type="text/javascript">
+    SimpleTest.requestCompleteLog();
+
+    var registration;
+    function start() {
+      return navigator.serviceWorker.register("fetch_event_client.js", { scope: "./"})
+        .then((swr) => registration = swr);
+    }
+
+    function unregister() {
+      return registration.unregister().then(function(result) {
+        ok(result, "Unregister should return true.");
+      }, function(e) {
+        dump("Unregistering the SW failed with " + e + "\n");
+      });
+    }
+
+    function testFetchEvent() {
+      var p = new Promise(function(resolve, reject) {
+        var content = document.getElementById("content");
+        ok(content, "parent exists.");
+
+        var iframe = document.createElement('iframe');
+        iframe.setAttribute('src', "sw_clients/dummy.html");
+        content.appendChild(iframe);
+
+        var w = iframe.contentWindow;
+        w.navigator.serviceWorker.onmessage = function(msg) {
+          ok(msg.data.status, "Receive message posted by client successfully");
+
+          resolve();
+        }
+      });
+
+      return p;
+    }
+
+    function runTest() {
+      start()
+        .then(testFetchEvent)
+        .then(unregister)
+        .then(function() {
+        }).catch(function(e) {
+          ok(false, "Some test failed with error " + e);
+        }).then(SimpleTest.finish);
+    }
+
+    SimpleTest.waitForExplicitFinish();
+    SpecialPowers.pushPrefEnv({"set": [
+      ["dom.serviceWorkers.exemptFromPerDomainMax", true],
+      ["dom.serviceWorkers.enabled", true],
+      ["dom.serviceWorkers.testing.enabled", true],
+    ]}, runTest);
+  </script>
+</body>
+</html>
--- a/dom/xbl/nsXBLPrototypeHandler.cpp
+++ b/dom/xbl/nsXBLPrototypeHandler.cpp
@@ -311,17 +311,17 @@ nsXBLPrototypeHandler::ExecuteHandler(Ev
   NS_ENSURE_TRUE(ok, NS_ERROR_OUT_OF_MEMORY);
 
   // Next, clone the generic handler with our desired scope chain.
   JS::Rooted<JSObject*> bound(cx, JS::CloneFunctionObject(cx, genericHandler,
                                                           scopeChain));
   NS_ENSURE_TRUE(bound, NS_ERROR_FAILURE);
 
   nsRefPtr<EventHandlerNonNull> handlerCallback =
-    new EventHandlerNonNull(bound, /* aIncumbentGlobal = */ nullptr);
+    new EventHandlerNonNull(nullptr, bound, /* aIncumbentGlobal = */ nullptr);
 
   TypedEventHandler typedHandler(handlerCallback);
 
   // Execute it.
   nsCOMPtr<JSEventHandler> jsEventHandler;
   rv = NS_NewJSEventHandler(scriptTarget, onEventAtom,
                             typedHandler,
                             getter_AddRefs(jsEventHandler));
--- a/editor/reftests/reftest.list
+++ b/editor/reftests/reftest.list
@@ -99,25 +99,25 @@ skip-if(Android||B2G||Mulet) needs-focus
 == readonly-editable.html readonly-editable-ref.html
 == dynamic-overflow-change.html dynamic-overflow-change-ref.html
 == 694880-1.html 694880-ref.html
 == 694880-2.html 694880-ref.html
 == 694880-3.html 694880-ref.html
 == 388980-1.html 388980-1-ref.html
 needs-focus == spellcheck-superscript-1.html spellcheck-superscript-1-ref.html
 skip-if(B2G||Mulet) fails-if(Android) needs-focus != spellcheck-superscript-2.html spellcheck-superscript-2-ref.html # bug 783658 # Initial mulet triage: parity with B2G/B2G Desktop
-needs-focus pref(selectioncaret.enabled,false) == 824080-1.html 824080-1-ref.html
-needs-focus pref(selectioncaret.enabled,false) == 824080-2.html 824080-2-ref.html
-needs-focus pref(selectioncaret.enabled,false) == 824080-3.html 824080-3-ref.html
+needs-focus pref(selectioncaret.enabled,false) pref(layout.accessiblecaret.enabled,false) == 824080-1.html 824080-1-ref.html
+needs-focus pref(selectioncaret.enabled,false) pref(layout.accessiblecaret.enabled,false) == 824080-2.html 824080-2-ref.html
+needs-focus pref(selectioncaret.enabled,false) pref(layout.accessiblecaret.enabled,false) == 824080-3.html 824080-3-ref.html
 needs-focus != 824080-2.html 824080-3.html
-needs-focus pref(selectioncaret.enabled,false) == 824080-4.html 824080-4-ref.html
-needs-focus pref(selectioncaret.enabled,false) == 824080-5.html 824080-5-ref.html
+needs-focus pref(selectioncaret.enabled,false) pref(layout.accessiblecaret.enabled,false) == 824080-4.html 824080-4-ref.html
+needs-focus pref(selectioncaret.enabled,false) pref(layout.accessiblecaret.enabled,false) == 824080-5.html 824080-5-ref.html
 needs-focus != 824080-4.html 824080-5.html
 needs-focus == 824080-6.html 824080-6-ref.html
-needs-focus pref(selectioncaret.enabled,false) == 824080-7.html 824080-7-ref.html
+needs-focus pref(selectioncaret.enabled,false) pref(layout.accessiblecaret.enabled,false) == 824080-7.html 824080-7-ref.html
 needs-focus != 824080-6.html 824080-7.html
 # Bug 674927: copy spellcheck-textarea tests to contenteditable
 == spellcheck-contenteditable-attr.html spellcheck-contenteditable-nofocus-ref.html
 fails-if(Android||B2G||Mulet) needs-focus != spellcheck-contenteditable-attr.html spellcheck-contenteditable-ref.html # B2G no spellcheck underline # Initial mulet triage: parity with B2G/B2G Desktop
 needs-focus == spellcheck-contenteditable-focused.html spellcheck-contenteditable-ref.html
 needs-focus == spellcheck-contenteditable-focused-reframe.html spellcheck-contenteditable-ref.html
 == spellcheck-contenteditable-nofocus.html spellcheck-contenteditable-disabled-ref.html
 == spellcheck-contenteditable-disabled.html spellcheck-contenteditable-disabled-ref.html
--- a/ipc/glue/MessageChannel.cpp
+++ b/ipc/glue/MessageChannel.cpp
@@ -848,16 +848,17 @@ MessageChannel::Send(Message* aMsg, Mess
     AssertWorkerThread();
     mMonitor->AssertNotCurrentThreadOwns();
 
     if (mCurrentTransaction == 0)
         mListener->OnBeginSyncTransaction();
 
 #ifdef OS_WIN
     SyncStackFrame frame(this, false);
+    NeuteredWindowRegion neuteredRgn(mFlags & REQUIRE_DEFERRED_MESSAGE_PROTECTION);
 #endif
 
     CxxStackFrame f(*this, OUT_MESSAGE, msg);
 
     MonitorAutoLock lock(*mMonitor);
 
     if (mTimedOutMessageSeqno) {
         // Don't bother sending another sync message if a previous one timed out
@@ -989,16 +990,17 @@ MessageChannel::Send(Message* aMsg, Mess
 bool
 MessageChannel::Call(Message* aMsg, Message* aReply)
 {
     AssertWorkerThread();
     mMonitor->AssertNotCurrentThreadOwns();
 
 #ifdef OS_WIN
     SyncStackFrame frame(this, true);
+    NeuteredWindowRegion neuteredRgn(mFlags & REQUIRE_DEFERRED_MESSAGE_PROTECTION);
 #endif
 
     // This must come before MonitorAutoLock, as its destructor acquires the
     // monitor lock.
     CxxStackFrame cxxframe(*this, OUT_MESSAGE, aMsg);
 
     MonitorAutoLock lock(*mMonitor);
     if (!Connected()) {
@@ -1027,16 +1029,22 @@ MessageChannel::Call(Message* aMsg, Mess
         // might have already processed the OnError event. if so,
         // trying another loop iteration will be futile because
         // channel state will have been cleared
         if (!Connected()) {
             ReportConnectionError("MessageChannel::Call");
             return false;
         }
 
+#ifdef OS_WIN
+        /* We should pump messages at this point to ensure that the IPC peer
+           does not become deadlocked on a pending inter-thread SendMessage() */
+        neuteredRgn.PumpOnce();
+#endif
+
         // Now might be the time to process a message deferred because of race
         // resolution.
         MaybeUndeferIncall();
 
         // Wait for an event to occur.
         while (!InterruptEventOccurred()) {
             bool maybeTimedOut = !WaitForInterruptNotify();
 
@@ -1143,16 +1151,17 @@ MessageChannel::Call(Message* aMsg, Mess
     return true;
 }
 
 bool
 MessageChannel::WaitForIncomingMessage()
 {
 #ifdef OS_WIN
     SyncStackFrame frame(this, true);
+    NeuteredWindowRegion neuteredRgn(mFlags & REQUIRE_DEFERRED_MESSAGE_PROTECTION);
 #endif
 
     { // Scope for lock
         MonitorAutoLock lock(*mMonitor);
         AutoEnterWaitForIncoming waitingForIncoming(*this);
         if (mChannelState != ChannelConnected) {
             return false;
         }
--- a/ipc/glue/MessageChannel.h
+++ b/ipc/glue/MessageChannel.h
@@ -10,16 +10,19 @@
 
 #include "base/basictypes.h"
 #include "base/message_loop.h"
 
 #include "mozilla/DebugOnly.h"
 #include "mozilla/Monitor.h"
 #include "mozilla/Vector.h"
 #include "mozilla/WeakPtr.h"
+#if defined(OS_WIN)
+#include "mozilla/ipc/Neutering.h"
+#endif // defined(OS_WIN)
 #include "mozilla/ipc/Transport.h"
 #include "MessageLink.h"
 #include "nsAutoPtr.h"
 
 #include <deque>
 #include <stack>
 #include <math.h>
 
new file mode 100644
--- /dev/null
+++ b/ipc/glue/Neutering.h
@@ -0,0 +1,64 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_ipc_Neutering_h
+#define mozilla_ipc_Neutering_h
+
+#include "mozilla/GuardObjects.h"
+
+/**
+ * This header declares RAII wrappers for Window neutering. See
+ * WindowsMessageLoop.cpp for more details.
+ */
+
+namespace mozilla {
+namespace ipc {
+
+/**
+ * This class is a RAII wrapper around Window neutering. As long as a
+ * NeuteredWindowRegion object is instantiated, Win32 windows belonging to the
+ * current thread will be neutered. It is safe to nest multiple instances of
+ * this class.
+ */
+class MOZ_STACK_CLASS NeuteredWindowRegion
+{
+public:
+  explicit NeuteredWindowRegion(bool aDoNeuter MOZ_GUARD_OBJECT_NOTIFIER_PARAM);
+  ~NeuteredWindowRegion();
+
+  /**
+   * This function clears any backlog of nonqueued messages that are pending for
+   * the current thread.
+   */
+  void PumpOnce();
+
+private:
+  MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER
+  bool mNeuteredByThis;
+};
+
+/**
+ * This class is analagous to MutexAutoUnlock for Mutex; it is an RAII class
+ * that is to be instantiated within a NeuteredWindowRegion, thus temporarily
+ * disabling neutering for the remainder of its enclosing block.
+ * @see NeuteredWindowRegion
+ */
+class MOZ_STACK_CLASS DeneuteredWindowRegion
+{
+public:
+  DeneuteredWindowRegion(MOZ_GUARD_OBJECT_NOTIFIER_ONLY_PARAM);
+  ~DeneuteredWindowRegion();
+
+private:
+  MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER
+  bool mReneuter;
+};
+
+} // namespace ipc
+} // namespace mozilla
+
+#endif // mozilla_ipc_Neutering_h
+
--- a/ipc/glue/WindowsMessageLoop.cpp
+++ b/ipc/glue/WindowsMessageLoop.cpp
@@ -3,16 +3,17 @@
  */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "mozilla/DebugOnly.h"
 
 #include "WindowsMessageLoop.h"
+#include "Neutering.h"
 #include "MessageChannel.h"
 
 #include "nsAutoPtr.h"
 #include "nsServiceManagerUtils.h"
 #include "nsString.h"
 #include "nsIXULAppInfo.h"
 #include "WinUtils.h"
 
@@ -857,16 +858,95 @@ MessageChannel::SpinInternalEventLoop()
 
 static inline bool
 IsTimeoutExpired(PRIntervalTime aStart, PRIntervalTime aTimeout)
 {
   return (aTimeout != PR_INTERVAL_NO_TIMEOUT) &&
     (aTimeout <= (PR_IntervalNow() - aStart));
 }
 
+static HHOOK gWindowHook;
+
+static inline void
+StartNeutering()
+{
+  MOZ_ASSERT(gUIThreadId);
+  MOZ_ASSERT(!gWindowHook);
+  NS_ASSERTION(!MessageChannel::IsPumpingMessages(),
+               "Shouldn't be pumping already!");
+  MessageChannel::SetIsPumpingMessages(true);
+  gWindowHook = ::SetWindowsHookEx(WH_CALLWNDPROC, CallWindowProcedureHook,
+                                   nullptr, gUIThreadId);
+  NS_ASSERTION(gWindowHook, "Failed to set hook!");
+}
+
+static void
+StopNeutering()
+{
+  MOZ_ASSERT(MessageChannel::IsPumpingMessages());
+  ::UnhookWindowsHookEx(gWindowHook);
+  gWindowHook = NULL;
+  ::UnhookNeuteredWindows();
+  // Before returning we need to set a hook to run any deferred messages that
+  // we received during the IPC call. The hook will unset itself as soon as
+  // someone else calls GetMessage, PeekMessage, or runs code that generates
+  // a "nonqueued" message.
+  ::ScheduleDeferredMessageRun();
+  MessageChannel::SetIsPumpingMessages(false);
+}
+
+NeuteredWindowRegion::NeuteredWindowRegion(bool aDoNeuter MOZ_GUARD_OBJECT_NOTIFIER_PARAM_IN_IMPL)
+  : mNeuteredByThis(!gWindowHook)
+{
+  MOZ_GUARD_OBJECT_NOTIFIER_INIT;
+  if (aDoNeuter && mNeuteredByThis) {
+    StartNeutering();
+  }
+}
+
+NeuteredWindowRegion::~NeuteredWindowRegion()
+{
+  if (gWindowHook && mNeuteredByThis) {
+    StopNeutering();
+  }
+}
+
+void
+NeuteredWindowRegion::PumpOnce()
+{
+  MSG msg = {0};
+  if (gCOMWindow) {
+    // Pump any COM messages so that we don't hang due to STA marshaling.
+    // This call will also expunge any nonqueued messages on the current thread
+    if (::PeekMessageW(&msg, gCOMWindow, 0, 0, PM_REMOVE)) {
+      ::TranslateMessage(&msg);
+      ::DispatchMessageW(&msg);
+    }
+  } else {
+    // Expunge any nonqueued messages on the current thread
+    ::PeekMessageW(&msg, nullptr, 0, 0, PM_NOREMOVE);
+  }
+}
+
+DeneuteredWindowRegion::DeneuteredWindowRegion(MOZ_GUARD_OBJECT_NOTIFIER_ONLY_PARAM_IN_IMPL)
+  : mReneuter(gWindowHook != NULL)
+{
+  MOZ_GUARD_OBJECT_NOTIFIER_INIT;
+  if (mReneuter) {
+    StopNeutering();
+  }
+}
+
+DeneuteredWindowRegion::~DeneuteredWindowRegion()
+{
+  if (mReneuter) {
+    StartNeutering();
+  }
+}
+
 bool
 MessageChannel::WaitForSyncNotify()
 {
   mMonitor->AssertCurrentThreadOwns();
 
   MOZ_ASSERT(gUIThreadId, "InitUIThread was not called!");
 
   // Use a blocking wait if this channel does not require
@@ -911,25 +991,16 @@ MessageChannel::WaitForSyncNotify()
     InitTimeoutData(&timeoutData, mTimeoutMs);
 
     // We only do this to ensure that we won't get stuck in
     // MsgWaitForMultipleObjects below.
     timerId = SetTimer(nullptr, 0, mTimeoutMs, nullptr);
     NS_ASSERTION(timerId, "SetTimer failed!");
   }
 
-  // Setup deferred processing of native events while we wait for a response.
-  NS_ASSERTION(!MessageChannel::IsPumpingMessages(),
-               "Shouldn't be pumping already!");
-
-  MessageChannel::SetIsPumpingMessages(true);
-  HHOOK windowHook = SetWindowsHookEx(WH_CALLWNDPROC, CallWindowProcedureHook,
-                                      nullptr, gUIThreadId);
-  NS_ASSERTION(windowHook, "Failed to set hook!");
-
   {
     while (1) {
       MSG msg = { 0 };
       // Don't get wrapped up in here if the child connection dies.
       {
         MonitorAutoLock lock(*mMonitor);
         if (!Connected()) {
           break;
@@ -993,35 +1064,21 @@ MessageChannel::WaitForSyncNotify()
       if (!PeekMessageW(&msg, nullptr, 0, 0, PM_NOREMOVE) &&
           !haveSentMessagesPending) {
         // Message was for child, we should wait a bit.
         SwitchToThread();
       }
     }
   }
 
-  // Unhook the neutered window procedure hook.
-  UnhookWindowsHookEx(windowHook);
-
-  // Unhook any neutered windows procedures so messages can be delivered
-  // normally.
-  UnhookNeuteredWindows();
-
-  // Before returning we need to set a hook to run any deferred messages that
-  // we received during the IPC call. The hook will unset itself as soon as
-  // someone else calls GetMessage, PeekMessage, or runs code that generates
-  // a "nonqueued" message.
-  ScheduleDeferredMessageRun();
-
   if (timerId) {
     KillTimer(nullptr, timerId);
+    timerId = 0;
   }
 
-  MessageChannel::SetIsPumpingMessages(false);
-
   return WaitResponse(timedout);
 }
 
 bool
 MessageChannel::WaitForInterruptNotify()
 {
   mMonitor->AssertCurrentThreadOwns();
 
@@ -1045,66 +1102,38 @@ MessageChannel::WaitForInterruptNotify()
 
   MonitorAutoUnlock unlock(*mMonitor);
 
   bool timedout = false;
 
   UINT_PTR timerId = 0;
   TimeoutData timeoutData = { 0 };
 
-  // windowHook is used as a flag variable for the loop below: if it is set
+  // gWindowHook is used as a flag variable for the loop below: if it is set
   // and we start to spin a nested event loop, we need to clear the hook and
   // process deferred/pending messages.
-  // If windowHook is nullptr, MessageChannel::IsPumpingMessages should be false.
-  HHOOK windowHook = nullptr;
-
   while (1) {
-    NS_ASSERTION((!!windowHook) == MessageChannel::IsPumpingMessages(),
-                 "windowHook out of sync with reality");
+    NS_ASSERTION((!!gWindowHook) == MessageChannel::IsPumpingMessages(),
+                 "gWindowHook out of sync with reality");
 
     if (mTopFrame->mSpinNestedEvents) {
-      if (windowHook) {
-        UnhookWindowsHookEx(windowHook);
-        windowHook = nullptr;
-
-        if (timerId) {
-          KillTimer(nullptr, timerId);
-          timerId = 0;
-        }
-
-        // Used by widget to assert on incoming native events
-        MessageChannel::SetIsPumpingMessages(false);
-
-        // Unhook any neutered windows procedures so messages can be delievered
-        // normally.
-        UnhookNeuteredWindows();
-
-        // Send all deferred "nonqueued" message to the intended receiver.
-        // We're dropping into SpinInternalEventLoop so we should be fairly
-        // certain these will get delivered soohn.
-        ScheduleDeferredMessageRun();
+      if (gWindowHook && timerId) {
+        KillTimer(nullptr, timerId);
+        timerId = 0;
       }
+      DeneuteredWindowRegion deneuteredRgn;
       SpinInternalEventLoop();
       ResetEvent(mEvent);
       return true;
     }
 
-    if (!windowHook) {
-      MessageChannel::SetIsPumpingMessages(true);
-      windowHook = SetWindowsHookEx(WH_CALLWNDPROC, CallWindowProcedureHook,
-                                    nullptr, gUIThreadId);
-      NS_ASSERTION(windowHook, "Failed to set hook!");
-
-      NS_ASSERTION(!timerId, "Timer already initialized?");
-
-      if (mTimeoutMs != kNoTimeout) {
-        InitTimeoutData(&timeoutData, mTimeoutMs);
-        timerId = SetTimer(nullptr, 0, mTimeoutMs, nullptr);
-        NS_ASSERTION(timerId, "SetTimer failed!");
-      }
+    if (mTimeoutMs != kNoTimeout && !timerId) {
+      InitTimeoutData(&timeoutData, mTimeoutMs);
+      timerId = SetTimer(nullptr, 0, mTimeoutMs, nullptr);
+      NS_ASSERTION(timerId, "SetTimer failed!");
     }
 
     MSG msg = { 0 };
 
     // Don't get wrapped up in here if the child connection dies.
     {
       MonitorAutoLock lock(*mMonitor);
       if (!Connected()) {
@@ -1146,37 +1175,21 @@ MessageChannel::WaitForInterruptNotify()
     // MsgWaitForMultipleObjects every time.
     if (!PeekMessageW(&msg, nullptr, 0, 0, PM_NOREMOVE) &&
         !haveSentMessagesPending) {
       // Message was for child, we should wait a bit.
       SwitchToThread();
     }
   }
 
-  if (windowHook) {
-    // Unhook the neutered window procedure hook.
-    UnhookWindowsHookEx(windowHook);
-
-    // Unhook any neutered windows procedures so messages can be delivered
-    // normally.
-    UnhookNeuteredWindows();
-
-    // Before returning we need to set a hook to run any deferred messages that
-    // we received during the IPC call. The hook will unset itself as soon as
-    // someone else calls GetMessage, PeekMessage, or runs code that generates
-    // a "nonqueued" message.
-    ScheduleDeferredMessageRun();
-
-    if (timerId) {
-      KillTimer(nullptr, timerId);
-    }
+  if (timerId) {
+    KillTimer(nullptr, timerId);
+    timerId = 0;
   }
 
-  MessageChannel::SetIsPumpingMessages(false);
-
   return WaitResponse(timedout);
 }
 
 void
 MessageChannel::NotifyWorkerThread()
 {
   mMonitor->AssertCurrentThreadOwns();
 
--- a/ipc/glue/moz.build
+++ b/ipc/glue/moz.build
@@ -20,16 +20,17 @@ EXPORTS.mozilla.ipc += [
     'FileDescriptorSetChild.h',
     'FileDescriptorSetParent.h',
     'FileDescriptorUtils.h',
     'GeckoChildProcessHost.h',
     'InputStreamUtils.h',
     'IOThreadChild.h',
     'MessageChannel.h',
     'MessageLink.h',
+    'Neutering.h',
     'ProcessChild.h',
     'ProtocolUtils.h',
     'ScopedXREEmbed.h',
     'SharedMemory.h',
     'SharedMemoryBasic.h',
     'SharedMemorySysV.h',
     'Shmem.h',
     'Transport.h',
--- a/js/public/HashTable.h
+++ b/js/public/HashTable.h
@@ -12,16 +12,17 @@
 #include "mozilla/Attributes.h"
 #include "mozilla/Casting.h"
 #include "mozilla/MemoryReporting.h"
 #include "mozilla/Move.h"
 #include "mozilla/PodOperations.h"
 #include "mozilla/ReentrancyGuard.h"
 #include "mozilla/TemplateLib.h"
 #include "mozilla/TypeTraits.h"
+#include "mozilla/UniquePtr.h"
 
 #include "js/Utility.h"
 
 namespace js {
 
 class TempAllocPolicy;
 template <class> struct DefaultHasher;
 template <class, class> class HashMapEntry;
@@ -581,16 +582,35 @@ struct DefaultHasher
 };
 
 // Specialize hashing policy for pointer types. It assumes that the type is
 // at least word-aligned. For types with smaller size use PointerHasher.
 template <class T>
 struct DefaultHasher<T*> : PointerHasher<T*, mozilla::tl::FloorLog2<sizeof(void*)>::value>
 {};
 
+// Specialize hashing policy for mozilla::UniquePtr<T> to proxy the UniquePtr's
+// raw pointer to PointerHasher.
+template <class T>
+struct DefaultHasher<mozilla::UniquePtr<T>>
+{
+    using Lookup = mozilla::UniquePtr<T>;
+    using PtrHasher = PointerHasher<T*, mozilla::tl::FloorLog2<sizeof(void*)>::value>;
+
+    static HashNumber hash(const Lookup& l) {
+        return PtrHasher::hash(l.get());
+    }
+    static bool match(const mozilla::UniquePtr<T>& k, const Lookup& l) {
+        return PtrHasher::match(k.get(), l.get());
+    }
+    static void rekey(mozilla::UniquePtr<T>& k, mozilla::UniquePtr<T>&& newKey) {
+        k = mozilla::Move(newKey);
+    }
+};
+
 // For doubles, we can xor the two uint32s.
 template <>
 struct DefaultHasher<double>
 {
     typedef double Lookup;
     static HashNumber hash(double d) {
         static_assert(sizeof(HashNumber) == 4,
                       "subsequent code assumes a four-byte hash");
--- a/js/src/builtin/Profilers.cpp
+++ b/js/src/builtin/Profilers.cpp
@@ -13,19 +13,16 @@
 #ifdef MOZ_CALLGRIND
 # include <valgrind/callgrind.h>
 #endif
 
 #ifdef __APPLE__
 #ifdef MOZ_INSTRUMENTS
 # include "devtools/Instruments.h"
 #endif
-#ifdef MOZ_SHARK
-# include "devtools/sharkctl.h"
-#endif
 #endif
 
 #ifdef XP_WIN
 # include <process.h>
 # define getpid _getpid
 #endif
 
 #include "vm/Probes.h"
@@ -63,20 +60,16 @@ JS_UnsafeGetLastProfilingError()
 }
 
 #ifdef __APPLE__
 static bool
 StartOSXProfiling(const char* profileName, pid_t pid)
 {
     bool ok = true;
     const char* profiler = nullptr;
-#ifdef MOZ_SHARK
-    ok = Shark::Start();
-    profiler = "Shark";
-#endif
 #ifdef MOZ_INSTRUMENTS
     ok = Instruments::Start(pid);
     profiler = "Instruments";
 #endif
     if (!ok) {
         if (profileName)
             UnsafeError("Failed to start %s for %s", profiler, profileName);
         else
@@ -101,19 +94,16 @@ JS_StartProfiling(const char* profileNam
     return ok;
 }
 
 JS_PUBLIC_API(bool)
 JS_StopProfiling(const char* profileName)
 {
     bool ok = true;
 #ifdef __APPLE__
-#ifdef MOZ_SHARK
-    Shark::Stop();
-#endif
 #ifdef MOZ_INSTRUMENTS
     Instruments::Stop(profileName);
 #endif
 #endif
 #ifdef __linux__
     if (!js_StopPerf())
         ok = false;
 #endif
@@ -126,22 +116,18 @@ JS_StopProfiling(const char* profileName
  */
 static bool
 ControlProfilers(bool toState)
 {
     bool ok = true;
 
     if (! probes::ProfilingActive && toState) {
 #ifdef __APPLE__
-#if defined(MOZ_SHARK) || defined(MOZ_INSTRUMENTS)
+#if defined(MOZ_INSTRUMENTS)
         const char* profiler;
-#ifdef MOZ_SHARK
-        ok = Shark::Start();
-        profiler = "Shark";
-#endif
 #ifdef MOZ_INSTRUMENTS
         ok = Instruments::Resume();
         profiler = "Instruments";
 #endif
         if (!ok) {
             UnsafeError("Failed to start %s", profiler);
         }
 #endif
@@ -149,19 +135,16 @@ ControlProfilers(bool toState)
 #ifdef MOZ_CALLGRIND
         if (! js_StartCallgrind()) {
             UnsafeError("Failed to start Callgrind");
             ok = false;
         }
 #endif
     } else if (probes::ProfilingActive && ! toState) {
 #ifdef __APPLE__
-#ifdef MOZ_SHARK
-        Shark::Stop();
-#endif
 #ifdef MOZ_INSTRUMENTS
         Instruments::Pause();
 #endif
 #endif
 #ifdef MOZ_CALLGRIND
         if (! js_StopCallgrind()) {
             UnsafeError("failed to stop Callgrind");
             ok = false;
@@ -342,17 +325,17 @@ GetMaxGCPauseSinceClear(JSContext* cx, u
 static bool
 ClearMaxGCPauseAccumulator(JSContext* cx, unsigned argc, Value* vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
     args.rval().setNumber(uint32_t(cx->runtime()->gc.stats.clearMaxGCPauseAccumulator()));
     return true;
 }
 
-#if defined(MOZ_SHARK) || defined(MOZ_INSTRUMENTS)
+#if defined(MOZ_INSTRUMENTS)
 
 static bool
 IgnoreAndReturnTrue(JSContext* cx, unsigned argc, Value* vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
     args.rval().setBoolean(true);
     return true;
 }
@@ -397,17 +380,17 @@ DumpCallgrind(JSContext* cx, unsigned ar
 static const JSFunctionSpec profiling_functions[] = {
     JS_FN("startProfiling",  StartProfiling,      1,0),
     JS_FN("stopProfiling",   StopProfiling,       1,0),
     JS_FN("pauseProfilers",  PauseProfilers,      1,0),
     JS_FN("resumeProfilers", ResumeProfilers,     1,0),
     JS_FN("dumpProfile",     DumpProfile,         2,0),
     JS_FN("getMaxGCPauseSinceClear",    GetMaxGCPauseSinceClear,    0, 0),
     JS_FN("clearMaxGCPauseAccumulator", ClearMaxGCPauseAccumulator, 0, 0),
-#if defined(MOZ_SHARK) || defined(MOZ_INSTRUMENTS)
+#if defined(MOZ_INSTRUMENTS)
     /* Keep users of the old shark API happy. */
     JS_FN("connectShark",    IgnoreAndReturnTrue, 0,0),
     JS_FN("disconnectShark", IgnoreAndReturnTrue, 0,0),
     JS_FN("startShark",      StartProfiling,      0,0),
     JS_FN("stopShark",       StopProfiling,       0,0),
 #endif
 #ifdef MOZ_CALLGRIND
     JS_FN("startCallgrind", StartCallgrind,       0,0),
--- a/js/src/builtin/TestingFunctions.cpp
+++ b/js/src/builtin/TestingFunctions.cpp
@@ -938,17 +938,18 @@ CallFunctionWithAsyncStack(JSContext* cx
         JS_ReportError(cx, "The third argument should be a non-empty string.");
         return false;
     }
 
     RootedObject function(cx, &args[0].toObject());
     RootedObject stack(cx, &args[1].toObject());
     RootedString asyncCause(cx, args[2].toString());
 
-    JS::AutoSetAsyncStackForNewCalls sas(cx, stack, asyncCause);
+    JS::AutoSetAsyncStackForNewCalls sas(cx, stack, asyncCause,
+                                         JS::AutoSetAsyncStackForNewCalls::AsyncCallKind::EXPLICIT);
     return Call(cx, UndefinedHandleValue, function,
                 JS::HandleValueArray::empty(), args.rval());
 }
 
 static bool
 EnableTrackAllocations(JSContext* cx, unsigned argc, Value* vp)
 {
     SetObjectMetadataCallback(cx, SavedStacksMetadataCallback);
--- a/js/src/configure.in
+++ b/js/src/configure.in
@@ -3216,28 +3216,16 @@ MOZ_ARG_ENABLE_BOOL(jprof,
     MOZ_JPROF=1,
     MOZ_JPROF= )
 if test -n "$MOZ_JPROF"; then
     MOZ_PROFILING=1
     AC_DEFINE(MOZ_JPROF)
 fi
 
 dnl ========================================================
-dnl shark
-dnl ========================================================
-MOZ_ARG_ENABLE_BOOL(shark,
-[  --enable-shark          Enable shark remote profiling. Implies --enable-profiling.],
-    MOZ_SHARK=1,
-    MOZ_SHARK= )
-if test -n "$MOZ_SHARK"; then
-    MOZ_PROFILING=1
-    AC_DEFINE(MOZ_SHARK)
-fi
-
-dnl ========================================================
 dnl instruments
 dnl ========================================================
 MOZ_ARG_ENABLE_BOOL(instruments,
 [  --enable-instruments    Enable instruments remote profiling. Implies --enable-profiling.],
     MOZ_INSTRUMENTS=1,
     MOZ_INSTRUMENTS= )
 if test -n "$MOZ_INSTRUMENTS"; then
     MOZ_PROFILING=1
@@ -3694,17 +3682,16 @@ AC_SUBST(FILTER)
 AC_SUBST(BIN_FLAGS)
 AC_SUBST(MOZ_DEBUG)
 AC_SUBST(MOZ_DEBUG_SYMBOLS)
 AC_SUBST(MOZ_DEBUG_ENABLE_DEFS)
 AC_SUBST(MOZ_DEBUG_DISABLE_DEFS)
 AC_SUBST(MOZ_DEBUG_LDFLAGS)
 AC_SUBST(WARNINGS_AS_ERRORS)
 AC_SUBST(MOZ_JPROF)
-AC_SUBST(MOZ_SHARK)
 AC_SUBST(MOZ_INSTRUMENTS)
 AC_SUBST(MOZ_CALLGRIND)
 AC_SUBST(MOZ_VTUNE)
 AC_SUBST(MOZ_PROFILING)
 AC_SUBST(LIBICONV)
 
 AC_SUBST(ENABLE_TESTS)
 
--- a/js/src/gc/Nursery.cpp
+++ b/js/src/gc/Nursery.cpp
@@ -545,49 +545,55 @@ js::Nursery::collect(JSRuntime* rt, JS::
     rt->addTelemetry(JS_TELEMETRY_GC_MINOR_US, totalTime);
     rt->addTelemetry(JS_TELEMETRY_GC_MINOR_REASON, reason);
     if (totalTime > 1000)
         rt->addTelemetry(JS_TELEMETRY_GC_MINOR_REASON_LONG, reason);
 
     TraceMinorGCEnd();
 
     if (enableProfiling_ && totalTime >= profileThreshold_) {
-        static bool printedHeader = false;
-        if (!printedHeader) {
-            fprintf(stderr,
-                    "MinorGC: Reason               PRate  Size Time   mkVals mkClls mkSlts mkWCll mkGnrc ckTbls mkRntm mkDbgr clrNOC collct swpABO updtIn runFin frSlts clrSB  sweep resize pretnr logPtT\n");
-            printedHeader = true;
+        struct {
+            const char* name;
+            int64_t time;
+        } PrintList[] = {
+            {"canIon", TIME_TOTAL(cancelIonCompilations)},
+            {"mkVals", TIME_TOTAL(traceValues)},
+            {"mkClls", TIME_TOTAL(traceCells)},
+            {"mkSlts", TIME_TOTAL(traceSlots)},
+            {"mcWCll", TIME_TOTAL(traceWholeCells)},
+            {"mkGnrc", TIME_TOTAL(traceGenericEntries)},
+            {"ckTbls", TIME_TOTAL(checkHashTables)},
+            {"mkRntm", TIME_TOTAL(markRuntime)},
+            {"mkDbgr", TIME_TOTAL(markDebugger)},
+            {"clrNOC", TIME_TOTAL(clearNewObjectCache)},
+            {"collct", TIME_TOTAL(collectToFP)},
+            {"swpABO", TIME_TOTAL(sweepArrayBufferViewList)},
+            {"updtIn", TIME_TOTAL(updateJitActivations)},
+            {"frSlts", TIME_TOTAL(freeMallocedBuffers)},
+            {" clrSB", TIME_TOTAL(clearStoreBuffer)},
+            {" sweep", TIME_TOTAL(sweep)},
+            {"resize", TIME_TOTAL(resize)},
+            {"pretnr", TIME_TOTAL(pretenure)},
+            {"logPtT", TIME_TOTAL(logPromotionsToTenured)}
+        };
+        static int printedHeader = 0;
+        if ((printedHeader++ % 200) == 0) {
+            fprintf(stderr, "MinorGC:               Reason  PRate Size    Time");
+            for (auto &entry : PrintList)
+                fprintf(stderr, " %s", entry.name);
+            fprintf(stderr, "\n");
         }
 
 #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 "\n",
-                js::gcstats::ExplainReason(reason),
-                promotionRate * 100,
-                numActiveChunks_,
-                totalTime,
-                TIME_TOTAL(cancelIonCompilations),
-                TIME_TOTAL(traceValues),
-                TIME_TOTAL(traceCells),
-                TIME_TOTAL(traceSlots),
-                TIME_TOTAL(traceWholeCells),
-                TIME_TOTAL(traceGenericEntries),
-                TIME_TOTAL(checkHashTables),
-                TIME_TOTAL(markRuntime),
-                TIME_TOTAL(markDebugger),
-                TIME_TOTAL(clearNewObjectCache),
-                TIME_TOTAL(collectToFP),
-                TIME_TOTAL(sweepArrayBufferViewList),
-                TIME_TOTAL(updateJitActivations),
-                TIME_TOTAL(freeMallocedBuffers),
-                TIME_TOTAL(clearStoreBuffer),
-                TIME_TOTAL(sweep),
-                TIME_TOTAL(resize),
-                TIME_TOTAL(pretenure),
-                TIME_TOTAL(logPromotionsToTenured));
+        fprintf(stderr, "MinorGC: %20s %5.1f%% %4d " FMT, js::gcstats::ExplainReason(reason),
+                promotionRate * 100, numActiveChunks_, totalTime);
+        for (auto &entry : PrintList) {
+            fprintf(stderr, FMT, entry.time);
+        }
+        fprintf(stderr, "\n");
 #undef FMT
     }
 }
 
 #undef TIME_START
 #undef TIME_END
 #undef TIME_TOTAL
 
--- a/js/src/jsapi.cpp
+++ b/js/src/jsapi.cpp
@@ -312,46 +312,82 @@ IterPerformanceStats(JSContext* cx,
                     js::DefaultHasher<js::PerformanceGroup*>,
                     js::SystemAllocPolicy> Set;
     Set set;
     if (!set.init(100)) {
         return false;
     }
 
     JSRuntime* rt = JS_GetRuntime(cx);
-    for (CompartmentsIter c(rt, WithAtoms); !c.done(); c.next()) {
+
+    // First report the shared groups
+    for (CompartmentsIter c(rt, SkipAtoms); !c.done(); c.next()) {
         JSCompartment* compartment = c.get();
-        if (!compartment->performanceMonitoring.isLinked()) {
+        if (!c->principals()) {
+            // Compartments without principals could show up here, but
+            // reporting them doesn't really make sense.
+            continue;
+        }
+        if (!c->performanceMonitoring.hasSharedGroup()) {
             // Don't report compartments that do not even have a PerformanceGroup.
             continue;
         }
-
         js::AutoCompartment autoCompartment(cx, compartment);
-        PerformanceGroup* group = compartment->performanceMonitoring.getGroup(cx);
-
+        PerformanceGroup* group = compartment->performanceMonitoring.getSharedGroup(cx);
         if (group->data.ticks == 0) {
             // Don't report compartments that have never been used.
             continue;
         }
 
         Set::AddPtr ptr = set.lookupForAdd(group);
         if (ptr) {
             // Don't report the same group twice.
             continue;
         }
 
-        if (!(*walker)(cx, group->data, group->uid, closure)) {
+        if (!(*walker)(cx,
+                       group->data, group->uid, nullptr,
+                       closure)) {
             // Issue in callback
             return false;
         }
         if (!set.add(ptr, group)) {
             // Memory issue
             return false;
         }
     }
+
+    // Then report the own groups
+    for (CompartmentsIter c(rt, SkipAtoms); !c.done(); c.next()) {
+        JSCompartment* compartment = c.get();
+        if (!c->principals()) {
+            // Compartments without principals could show up here, but
+            // reporting them doesn't really make sense.
+            continue;
+        }
+        if (!c->performanceMonitoring.hasOwnGroup()) {
+            // Don't report compartments that do not even have a PerformanceGroup.
+            continue;
+        }
+        js::AutoCompartment autoCompartment(cx, compartment);
+        PerformanceGroup* ownGroup = compartment->performanceMonitoring.getOwnGroup(cx);
+        if (ownGroup->data.ticks == 0) {
+            // Don't report compartments that have never been used.
+            continue;
+        }
+        PerformanceGroup* sharedGroup = compartment->performanceMonitoring.getSharedGroup(cx);
+        if (!(*walker)(cx,
+                       ownGroup->data, ownGroup->uid, &sharedGroup->uid,
+                       closure)) {
+            // Issue in callback
+            return false;
+        }
+    }
+
+    // Finally, report the process stats
     *processStats = rt->stopwatch.performance;
     return true;
 }
 
 void
 AssertHeapIsIdle(JSRuntime* rt)
 {
     MOZ_ASSERT(!rt->isHeapBusy());
@@ -4725,41 +4761,45 @@ JS_PUBLIC_API(void)
 JS_RestoreFrameChain(JSContext* cx)
 {
     AssertHeapIsIdleOrIterating(cx);
     CHECK_REQUEST(cx);
     cx->restoreFrameChain();
 }
 
 JS::AutoSetAsyncStackForNewCalls::AutoSetAsyncStackForNewCalls(
-  JSContext* cx, HandleObject stack, HandleString asyncCause)
+  JSContext* cx, HandleObject stack, HandleString asyncCause,
+  JS::AutoSetAsyncStackForNewCalls::AsyncCallKind kind)
   : cx(cx),
     oldAsyncStack(cx, cx->runtime()->asyncStackForNewActivations),
-    oldAsyncCause(cx, cx->runtime()->asyncCauseForNewActivations)
+    oldAsyncCause(cx, cx->runtime()->asyncCauseForNewActivations),
+    oldAsyncCallIsExplicit(cx->runtime()->asyncCallIsExplicit)
 {
     CHECK_REQUEST(cx);
 
     // The option determines whether we actually use the new values at this
     // point. It will not affect restoring the previous values when the object
     // is destroyed, so if the option changes it won't cause consistency issues.
     if (!cx->runtime()->options().asyncStack())
         return;
 
     SavedFrame* asyncStack = &stack->as<SavedFrame>();
     MOZ_ASSERT(!asyncCause->empty());
 
     cx->runtime()->asyncStackForNewActivations = asyncStack;
     cx->runtime()->asyncCauseForNewActivations = asyncCause;
+    cx->runtime()->asyncCallIsExplicit = kind == AsyncCallKind::EXPLICIT;
 }
 
 JS::AutoSetAsyncStackForNewCalls::~AutoSetAsyncStackForNewCalls()
 {
     cx->runtime()->asyncCauseForNewActivations = oldAsyncCause;
     cx->runtime()->asyncStackForNewActivations =
       oldAsyncStack ? &oldAsyncStack->as<SavedFrame>() : nullptr;
+    cx->runtime()->asyncCallIsExplicit = oldAsyncCallIsExplicit;
 }
 
 /************************************************************************/
 JS_PUBLIC_API(JSString*)
 JS_NewStringCopyN(JSContext* cx, const char* s, size_t n)
 {
     AssertHeapIsIdle(cx);
     CHECK_REQUEST(cx);
--- a/js/src/jsapi.h
+++ b/js/src/jsapi.h
@@ -4022,24 +4022,35 @@ namespace JS {
  * See also `js/src/doc/SavedFrame/SavedFrame.md` for documentation on async
  * stack frames.
  */
 class MOZ_STACK_CLASS JS_PUBLIC_API(AutoSetAsyncStackForNewCalls)
 {
     JSContext* cx;
     RootedObject oldAsyncStack;
     RootedString oldAsyncCause;
+    bool oldAsyncCallIsExplicit;
 
   public:
+    enum class AsyncCallKind {
+        // The ordinary kind of call, where we may apply an async
+        // parent if there is no ordinary parent.
+        IMPLICIT,
+        // An explicit async parent, e.g., callFunctionWithAsyncStack,
+        // where we always want to override any ordinary parent.
+        EXPLICIT
+    };
+
     // The stack parameter cannot be null by design, because it would be
     // ambiguous whether that would clear any scheduled async stack and make the
     // normal stack reappear in the new call, or just keep the async stack
     // already scheduled for the new call, if any.
     AutoSetAsyncStackForNewCalls(JSContext* cx, HandleObject stack,
-                                 HandleString asyncCause);
+                                 HandleString asyncCause,
+                                 AsyncCallKind kind = AsyncCallKind::IMPLICIT);
     ~AutoSetAsyncStackForNewCalls();
 };
 
 } // namespace JS
 
 /************************************************************************/
 
 /*
@@ -5404,17 +5415,17 @@ BuildStackString(JSContext* cx, HandleOb
 
 } /* namespace JS */
 
 
 /* Stopwatch-based CPU monitoring. */
 
 namespace js {
 
-struct AutoStopwatch;
+class AutoStopwatch;
 
 // Container for performance data
 // All values are monotonic.
 struct PerformanceData {
     // Number of times we have spent at least 2^n consecutive
     // milliseconds executing code in this group.
     // durations[0] is increased whenever we spend at least 1 ms
     // executing code in this group
@@ -5535,55 +5546,67 @@ struct PerformanceGroup {
     friend struct PerformanceGroupHolder;
 
 private:
     // A reference counter. Maintained by PerformanceGroupHolder.
     uint64_t refCount_;
 };
 
 //
-// Indirection towards a PerformanceGroup.
-// This structure handles reference counting for instances of PerformanceGroup.
+// Each PerformanceGroupHolder handles:
+// - a reference-counted indirection towards a PerformanceGroup shared
+//   by several compartments
+// - a owned PerformanceGroup representing the performance of a single
+//   compartment.
 //
 struct PerformanceGroupHolder {
-    // Get the group.
+    // Get the shared group.
     // On first call, this causes a single Hashtable lookup.
     // Successive calls do not require further lookups.
-    js::PerformanceGroup* getGroup(JSContext*);
-
-    // `true` if the this holder is currently associated to a
+    js::PerformanceGroup* getSharedGroup(JSContext*);
+
+    // Get the own group.
+    js::PerformanceGroup* getOwnGroup(JSContext*);
+
+    // `true` if the this holder is currently associated to a shared
     // PerformanceGroup, `false` otherwise. Use this method to avoid
     // instantiating a PerformanceGroup if you only need to get
     // available performance data.
-    inline bool isLinked() const {
-        return group_ != nullptr;
+    inline bool hasSharedGroup() const {
+        return sharedGroup_ != nullptr;
+    }
+    inline bool hasOwnGroup() const {
+        return ownGroup_ != nullptr;
     }
 
     // Remove the link to the PerformanceGroup. This method is designed
     // as an invalidation mechanism if the JSCompartment changes nature
     // (new values of `isSystem()`, `principals()` or `addonId`).
     void unlink();
 
     explicit PerformanceGroupHolder(JSRuntime* runtime)
       : runtime_(runtime)
-      , group_(nullptr)
+      , sharedGroup_(nullptr)
+      , ownGroup_(nullptr)
     {   }
     ~PerformanceGroupHolder();
-private:
+
+  private:
     // Return the key representing this PerformanceGroup in
     // Runtime::Stopwatch.
     // Do not deallocate the key.
     void* getHashKey(JSContext* cx);
 
     JSRuntime *runtime_;
 
-    // The PerformanceGroup held by this object.
-    // Initially set to `nullptr` until the first cal to `getGroup`.
+    // The PerformanceGroups held by this object.
+    // Initially set to `nullptr` until the first call to `getGroup`.
     // May be reset to `nullptr` by a call to `unlink`.
-    js::PerformanceGroup* group_;
+    js::PerformanceGroup* sharedGroup_;
+    js::PerformanceGroup* ownGroup_;
 };
 
 /**
  * Reset any stopwatch currently measuring.
  *
  * This function is designed to be called when we process a new event.
  */
 extern JS_PUBLIC_API(void)
@@ -5599,28 +5622,34 @@ ResetStopwatches(JSRuntime*);
 extern JS_PUBLIC_API(bool)
 SetStopwatchIsMonitoringCPOW(JSRuntime*, bool);
 extern JS_PUBLIC_API(bool)
 GetStopwatchIsMonitoringCPOW(JSRuntime*);
 extern JS_PUBLIC_API(bool)
 SetStopwatchIsMonitoringJank(JSRuntime*, bool);
 extern JS_PUBLIC_API(bool)
 GetStopwatchIsMonitoringJank(JSRuntime*);
+extern JS_PUBLIC_API(bool)
+SetStopwatchIsMonitoringPerCompartment(JSRuntime*, bool);
+extern JS_PUBLIC_API(bool)
+GetStopwatchIsMonitoringPerCompartment(JSRuntime*);
 
 extern JS_PUBLIC_API(bool)
 IsStopwatchActive(JSRuntime*);
 
 /**
  * Access the performance information stored in a compartment.
  */
 extern JS_PUBLIC_API(PerformanceData*)
 GetPerformanceData(JSRuntime*);
 
 typedef bool
-(PerformanceStatsWalker)(JSContext* cx, const PerformanceData& stats, uint64_t uid, void* closure);
+(PerformanceStatsWalker)(JSContext* cx,
+                         const PerformanceData& stats, uint64_t uid,
+                         const uint64_t* parentId, void* closure);
 
 /**
  * Extract the performance statistics.
  *
  * Note that before calling `walker`, we enter the corresponding context.
  */
 extern JS_PUBLIC_API(bool)
 IterPerformanceStats(JSContext* cx, PerformanceStatsWalker* walker, js::PerformanceData* process, void* closure);
--- a/js/src/vm/Interpreter.cpp
+++ b/js/src/vm/Interpreter.cpp
@@ -367,147 +367,259 @@ InvokeState::pushInterpreterFrame(JSCont
 
 InterpreterFrame*
 ExecuteState::pushInterpreterFrame(JSContext* cx)
 {
     return cx->runtime()->interpreterStack().pushExecuteFrame(cx, script_, thisv_, newTargetValue_,
                                                               scopeChain_, type_, evalInFrame_);
 }
 namespace js {
-
 // Implementation of per-performance group performance measurement.
 //
 //
 // All mutable state is stored in `Runtime::stopwatch` (per-process
 // performance stats and logistics) and in `PerformanceGroup` (per
 // group performance stats).
-struct AutoStopwatch final
-{
+class AutoStopwatch final
+{
+    // The context with which this object was initialized.
+    // Non-null.
+    JSContext* const cx_;
+
+    // An indication of the number of times we have entered the event
+    // loop.  Used only for comparison.
+    uint64_t iteration_;
+
+    // `true` if this object is currently used to monitor performance
+    // for a shared PerformanceGroup, `false` otherwise, i.e. if the
+    // stopwatch mechanism is off or if another stopwatch is already
+    // in charge of monitoring for the same PerformanceGroup.
+    bool isMonitoringForGroup_;
+
+    // `true` if this object is currently used to monitor performance
+    // for a single compartment, `false` otherwise, i.e. if the
+    // stopwatch mechanism is off or if another stopwatch is already
+    // in charge of monitoring for the same PerformanceGroup.
+    bool isMonitoringForSelf_;
+
+    // `true` if this stopwatch is the topmost stopwatch on the stack
+    // for this event, `false` otherwise.
+    bool isMonitoringForTop_;
+
+    // `true` if we are monitoring jank, `false` otherwise.
+    bool isMonitoringJank_;
+    // `true` if we are monitoring CPOW, `false` otherwise.
+    bool isMonitoringCPOW_;
+
+    // Timestamps captured while starting the stopwatch.
+    uint64_t userTimeStart_;
+    uint64_t systemTimeStart_;
+    uint64_t CPOWTimeStart_;
+
+   public:
     // If the stopwatch is active, constructing an instance of
     // AutoStopwatch causes it to become the current owner of the
     // stopwatch.
     //
     // Previous owner is restored upon destruction.
     explicit inline AutoStopwatch(JSContext* cx MOZ_GUARD_OBJECT_NOTIFIER_PARAM)
       : cx_(cx)
       , iteration_(0)
-      , isActive_(false)
-      , isTop_(false)
+      , isMonitoringForGroup_(false)
+      , isMonitoringForSelf_(false)
+      , isMonitoringForTop_(false)
+      , isMonitoringJank_(false)
+      , isMonitoringCPOW_(false)
       , userTimeStart_(0)
       , systemTimeStart_(0)
       , CPOWTimeStart_(0)
     {
         MOZ_GUARD_OBJECT_NOTIFIER_INIT;
 
-        JSRuntime* runtime = JS_GetRuntime(cx_);
-        if (!runtime->stopwatch.isMonitoringJank())
-            return;
-
         JSCompartment* compartment = cx_->compartment();
         if (compartment->scheduledForDestruction)
             return;
 
+        JSRuntime* runtime = cx_->runtime();
         iteration_ = runtime->stopwatch.iteration;
 
-        PerformanceGroup *group = compartment->performanceMonitoring.getGroup(cx);
-        MOZ_ASSERT(group);
-
-        if (group->hasStopwatch(iteration_)) {
-            // Someone is already monitoring this group during this
-            // tick, no need for further monitoring.
+        PerformanceGroup* sharedGroup = compartment->performanceMonitoring.getSharedGroup(cx);
+        if (!sharedGroup) {
+            // Either this Runtime is not configured for Performance Monitoring, or we couldn't
+            // allocate the group, or there was a problem with the hashtable.
             return;
         }
 
-        // Start the stopwatch.
-        if (!this->getTimes(runtime, &userTimeStart_, &systemTimeStart_))
-            return;
-        isActive_ = true;
-        CPOWTimeStart_ = runtime->stopwatch.performance.totalCPOWTime;
-
-        // We are now in charge of monitoring this group for the tick,
-        // until destruction of `this` or until we enter a nested event
-        // loop and `iteration_` is incremented.
-        group->acquireStopwatch(iteration_, this);
+        if (!sharedGroup->hasStopwatch(iteration_)) {
+            // We are now in charge of monitoring this group for the tick,
+            // until destruction of `this` or until we enter a nested event
+            // loop and `iteration_` is incremented.
+            sharedGroup->acquireStopwatch(iteration_, this);
+            isMonitoringForGroup_ = true;
+        }
+
+        PerformanceGroup* ownGroup = nullptr;
+        if (runtime->stopwatch.isMonitoringPerCompartment()) {
+            // As above, but for the group representing just this compartment.
+            ownGroup = compartment->performanceMonitoring.getOwnGroup(cx);
+            if (!ownGroup->hasStopwatch(iteration_)) {
+                ownGroup->acquireStopwatch(iteration_, this);
+                isMonitoringForSelf_ = true;
+            }
+        }
 
         if (runtime->stopwatch.isEmpty) {
             // This is the topmost stopwatch on the stack.
             // It will be in charge of updating the per-process
             // performance data.
             runtime->stopwatch.isEmpty = false;
-            runtime->stopwatch.performance.ticks++;
-            isTop_ = true;
+            isMonitoringForTop_ = true;
+
+            MOZ_ASSERT(isMonitoringForGroup_);
         }
-    }
-    inline ~AutoStopwatch() {
-        if (!isActive_) {
+
+        if (!isMonitoringForGroup_ && !isMonitoringForSelf_) {
             // We are not in charge of monitoring anything.
+            // (isMonitoringForTop_ implies isMonitoringForGroup_,
+            // so we do not need to check it)
             return;
         }
 
-        JSRuntime* runtime = JS_GetRuntime(cx_);
-        JSCompartment* compartment = cx_->compartment();
-
-        MOZ_ASSERT(!compartment->scheduledForDestruction);
-
-        if (!runtime->stopwatch.isMonitoringJank()) {
-            // Monitoring has been stopped while we were
-            // executing the code. Drop everything.
+        enter();
+    }
+    ~AutoStopwatch() {
+        if (!isMonitoringForGroup_ && !isMonitoringForSelf_) {
+            // We are not in charge of monitoring anything.
+            // (isMonitoringForTop_ implies isMonitoringForGroup_,
+            // so we do not need to check it)
             return;
         }
 
+        JSCompartment* compartment = cx_->compartment();
+        if (compartment->scheduledForDestruction)
+            return;
+
+        JSRuntime* runtime = cx_->runtime();
         if (iteration_ != runtime->stopwatch.iteration) {
             // We have entered a nested event loop at some point.
             // Any information we may have is obsolete.
             return;
         }
 
-        PerformanceGroup *group = compartment->performanceMonitoring.getGroup(cx_);
-        MOZ_ASSERT(group);
-
-        // Compute time spent.
-        group->releaseStopwatch(iteration_, this);
-        uint64_t userTimeEnd, systemTimeEnd;
-        if (!this->getTimes(runtime, &userTimeEnd, &systemTimeEnd))
-            return;
-
-        uint64_t userTimeDelta = userTimeEnd - userTimeStart_;
-        uint64_t systemTimeDelta = systemTimeEnd - systemTimeStart_;
-        uint64_t CPOWTimeDelta = runtime->stopwatch.performance.totalCPOWTime - CPOWTimeStart_;
-        group->data.totalUserTime += userTimeDelta;
-        group->data.totalSystemTime += systemTimeDelta;
-        group->data.totalCPOWTime += CPOWTimeDelta;
+        // Finish and commit measures
+        exit();
+
+        // Now release groups.
+        if (isMonitoringForGroup_) {
+            PerformanceGroup* sharedGroup = compartment->performanceMonitoring.getSharedGroup(cx_);
+            MOZ_ASSERT(sharedGroup);
+            sharedGroup->releaseStopwatch(iteration_, this);
+        }
+
+        if (isMonitoringForSelf_) {
+            PerformanceGroup* ownGroup = compartment->performanceMonitoring.getOwnGroup(cx_);
+            MOZ_ASSERT(ownGroup);
+            ownGroup->releaseStopwatch(iteration_, this);
+        }
+
+        if (isMonitoringForTop_)
+            runtime->stopwatch.isEmpty = true;
+    }
+   private:
+    void enter() {
+        JSRuntime* runtime = cx_->runtime();
+
+        if (runtime->stopwatch.isMonitoringCPOW()) {
+            CPOWTimeStart_ = runtime->stopwatch.performance.totalCPOWTime;
+            isMonitoringCPOW_ = true;
+        }
+
+        if (runtime->stopwatch.isMonitoringJank()) {
+            if (this->getTimes(runtime, &userTimeStart_, &systemTimeStart_)) {
+                isMonitoringJank_ = true;
+            }
+        }
+
+    }
+
+    void exit() {
+        JSRuntime* runtime = cx_->runtime();
+
+        uint64_t userTimeDelta = 0;
+        uint64_t systemTimeDelta = 0;
+        if (isMonitoringJank_ && runtime->stopwatch.isMonitoringJank()) {
+            // We were monitoring jank when we entered and we still are.
+            uint64_t userTimeEnd, systemTimeEnd;
+            if (!this->getTimes(runtime, &userTimeEnd, &systemTimeEnd)) {
+                // We make no attempt to recover from this error. If
+                // we bail out here, we lose nothing of value, plus
+                // I'm nearly sure that this error cannot happen in
+                // practice.
+                return;
+            }
+            userTimeDelta = userTimeEnd - userTimeStart_;
+            systemTimeDelta = systemTimeEnd - systemTimeStart_;
+        }
+
+        uint64_t CPOWTimeDelta = 0;
+        if (isMonitoringCPOW_ && runtime->stopwatch.isMonitoringCPOW()) {
+            // We were monitoring CPOW when we entered and we still are.
+            CPOWTimeDelta = runtime->stopwatch.performance.totalCPOWTime - CPOWTimeStart_;
+
+        }
+        commitDeltasToGroups(userTimeDelta, systemTimeDelta, CPOWTimeDelta);
+    }
+
+    void commitDeltasToGroups(uint64_t userTimeDelta,
+                              uint64_t systemTimeDelta,
+                              uint64_t CPOWTimeDelta)
+    {
+        JSCompartment* compartment = cx_->compartment();
+
+        PerformanceGroup* sharedGroup = compartment->performanceMonitoring.getSharedGroup(cx_);
+        MOZ_ASSERT(sharedGroup);
+        applyDeltas(userTimeDelta, systemTimeDelta, CPOWTimeDelta, sharedGroup->data);
+
+        if (isMonitoringForSelf_) {
+            PerformanceGroup* ownGroup = compartment->performanceMonitoring.getOwnGroup(cx_);
+            MOZ_ASSERT(ownGroup);
+            applyDeltas(userTimeDelta, systemTimeDelta, CPOWTimeDelta, ownGroup->data);
+        }
+
+        if (isMonitoringForTop_) {
+            JSRuntime* runtime = cx_->runtime();
+            applyDeltas(userTimeDelta, systemTimeDelta, CPOWTimeDelta, runtime->stopwatch.performance);
+        }
+    }
+
+
+    void applyDeltas(uint64_t userTimeDelta,
+                     uint64_t systemTimeDelta,
+                     uint64_t CPOWTimeDelta,
+                     PerformanceData& data) const {
+
+        data.ticks++;
 
         uint64_t totalTimeDelta = userTimeDelta + systemTimeDelta;
-        updateDurations(totalTimeDelta, group->data.durations);
-        group->data.ticks++;
-
-        if (isTop_) {
-            // This is the topmost stopwatch on the stack.
-            // Record the timing information.
-            runtime->stopwatch.performance.totalUserTime = userTimeEnd;
-            runtime->stopwatch.performance.totalSystemTime = systemTimeEnd;
-            updateDurations(totalTimeDelta, runtime->stopwatch.performance.durations);
-            runtime->stopwatch.isEmpty = true;
-        }
-    }
-
- private:
-
-    // Update an array containing the number of times we have missed
-    // at least 2^0 successive ms, 2^1 successive ms, ...
-    // 2^i successive ms.
-    template<int N>
-    void updateDurations(uint64_t totalTimeDelta, uint64_t (&array)[N]) const {
+        data.totalUserTime += userTimeDelta;
+        data.totalSystemTime += systemTimeDelta;
+        data.totalCPOWTime += CPOWTimeDelta;
+
+        // Update an array containing the number of times we have missed
+        // at least 2^0 successive ms, 2^1 successive ms, ...
+        // 2^i successive ms.
+
         // Duration of one frame, i.e. 16ms in museconds
         size_t i = 0;
         uint64_t duration = 1000;
         for (i = 0, duration = 1000;
-             i < N && duration < totalTimeDelta;
-             ++i, duration *= 2) {
-            array[i]++;
+             i < ArrayLength(data.durations) && duration < totalTimeDelta;
+             ++i, duration *= 2)
+        {
+            data.durations[i]++;
         }
     }
 
     // Get the OS-reported time spent in userland/systemland, in
     // microseconds. On most platforms, this data is per-thread,
     // but on some platforms we need to fall back to per-process.
     bool getTimes(JSRuntime* runtime, uint64_t* userTime, uint64_t* systemTime) const {
         MOZ_ASSERT(userTime);
@@ -580,41 +692,19 @@ struct AutoStopwatch final
         // Convert 100 ns to 1 us, make sure that the result is monotonic
         *userTime = runtime->stopwatch.userTimeFix.monotonize(userTimeInt.QuadPart / 10);
 
 #endif // defined(XP_MACOSX) || defined(XP_UNIX) || defined(XP_WIN)
 
         return true;
     }
 
-  private:
-    // The context with which this object was initialized.
-    // Non-null.
-    JSContext* const cx_;
-
-    // An indication of the number of times we have entered the event
-    // loop.  Used only for comparison.
-    uint64_t iteration_;
-
-    // `true` if this object is currently used to monitor performance,
-    // `false` otherwise, i.e. if the stopwatch mechanism is off or if
-    // another stopwatch is already in charge of monitoring for the
-    // same PerformanceGroup.
-    bool isActive_;
-
-    // `true` if this stopwatch is the topmost stopwatch on the stack
-    // for this event, `false` otherwise.
-    bool isTop_;
-
-    // Timestamps captured while starting the stopwatch.
-    uint64_t userTimeStart_;
-    uint64_t systemTimeStart_;
-    uint64_t CPOWTimeStart_;
-
-    MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER
+
+private:
+    MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER;
 };
 
 } // namespace js
 
 // MSVC with PGO inlines a lot of functions in RunScript, resulting in large
 // stack frames and stack overflow issues, see bug 1167883. Turn off PGO to
 // avoid this.
 #ifdef _MSC_VER
--- a/js/src/vm/Runtime.cpp
+++ b/js/src/vm/Runtime.cpp
@@ -123,16 +123,17 @@ JSRuntime::JSRuntime(JSRuntime* parentRu
     jitStackLimit_(0xbad),
     activation_(nullptr),
     profilingActivation_(nullptr),
     profilerSampleBufferGen_(0),
     profilerSampleBufferLapCount_(1),
     asmJSActivationStack_(nullptr),
     asyncStackForNewActivations(nullptr),
     asyncCauseForNewActivations(nullptr),
+    asyncCallIsExplicit(false),
     entryMonitor(nullptr),
     parentRuntime(parentRuntime),
     interrupt_(false),
     telemetryCallback(nullptr),
     handlingSignal(false),
     interruptCallback(nullptr),
     exclusiveAccessLock(nullptr),
     exclusiveAccessOwner(nullptr),
@@ -894,16 +895,27 @@ js::SetStopwatchIsMonitoringCPOW(JSRunti
     return rt->stopwatch.setIsMonitoringCPOW(value);
 }
 bool
 js::GetStopwatchIsMonitoringCPOW(JSRuntime* rt)
 {
     return rt->stopwatch.isMonitoringCPOW();
 }
 
+bool
+js::SetStopwatchIsMonitoringPerCompartment(JSRuntime* rt, bool value)
+{
+    return rt->stopwatch.setIsMonitoringPerCompartment(value);
+}
+bool
+js::GetStopwatchIsMonitoringPerCompartment(JSRuntime* rt)
+{
+    return rt->stopwatch.isMonitoringPerCompartment();
+}
+
 js::PerformanceGroupHolder::~PerformanceGroupHolder()
 {
     unlink();
 }
 
 void*
 js::PerformanceGroupHolder::getHashKey(JSContext* cx)
 {
@@ -913,57 +925,81 @@ js::PerformanceGroupHolder::getHashKey(J
 
     // As a fallback, put everything in the same PerformanceGroup.
     return nullptr;
 }
 
 void
 js::PerformanceGroupHolder::unlink()
 {
-    if (!group_) {
+    if (ownGroup_) {
+        js_delete(ownGroup_);
+        ownGroup_ = nullptr;
+    }
+
+    if (!sharedGroup_) {
         // The group has never been instantiated.
         return;
     }
 
-    js::PerformanceGroup* group = group_;
-    group_ = nullptr;
+    js::PerformanceGroup* group = sharedGroup_;
+    sharedGroup_ = nullptr;
 
     if (group->decRefCount() > 0) {
         // The group has at least another owner.
         return;
     }
 
 
     JSRuntime::Stopwatch::Groups::Ptr ptr =
-        runtime_->stopwatch.groups_.lookup(group->key_);
+        runtime_->stopwatch.groups().lookup(group->key_);
     MOZ_ASSERT(ptr);
-    runtime_->stopwatch.groups_.remove(ptr);
+    runtime_->stopwatch.groups().remove(ptr);
     js_delete(group);
 }
 
 PerformanceGroup*
-js::PerformanceGroupHolder::getGroup(JSContext* cx)
+js::PerformanceGroupHolder::getOwnGroup(JSContext* cx)
 {
-    if (group_)
-        return group_;
+    if (ownGroup_)
+        return ownGroup_;
+
+    ownGroup_ = runtime_->new_<PerformanceGroup>(cx, nullptr);
+    return ownGroup_;
+}
+
+PerformanceGroup*
+js::PerformanceGroupHolder::getSharedGroup(JSContext* cx)
+{
+    if (sharedGroup_)
+        return sharedGroup_;
+
+    if (!runtime_->stopwatch.groups().initialized())
+        return nullptr;
 
     void* key = getHashKey(cx);
-    JSRuntime::Stopwatch::Groups::AddPtr ptr =
-        runtime_->stopwatch.groups_.lookupForAdd(key);
+    JSRuntime::Stopwatch::Groups::AddPtr ptr = runtime_->stopwatch.groups().lookupForAdd(key);
     if (ptr) {
-        group_ = ptr->value();
-        MOZ_ASSERT(group_);
+        sharedGroup_ = ptr->value();
+        MOZ_ASSERT(sharedGroup_);
     } else {
-        group_ = runtime_->new_<PerformanceGroup>(cx, key);
-        runtime_->stopwatch.groups_.add(ptr, key, group_);
+        sharedGroup_ = runtime_->new_<PerformanceGroup>(cx, key);
+        if (!sharedGroup_)
+            return nullptr;
+
+        if (!runtime_->stopwatch.groups().add(ptr, key, sharedGroup_)) {
+            js_delete(sharedGroup_);
+            sharedGroup_ = nullptr;
+            return nullptr;
+        }
     }
 
-    group_->incRefCount();
+    sharedGroup_->incRefCount();
 
-    return group_;
+    return sharedGroup_;
 }
 
 PerformanceData*
 js::GetPerformanceData(JSRuntime* rt)
 {
     return &rt->stopwatch.performance;
 }
 
--- a/js/src/vm/Runtime.h
+++ b/js/src/vm/Runtime.h
@@ -692,16 +692,22 @@ struct JSRuntime : public JS::shadow::Ru
      */
     js::SavedFrame* asyncStackForNewActivations;
 
     /*
      * Value of asyncCause to be attached to asyncStackForNewActivations.
      */
     JSString* asyncCauseForNewActivations;
 
+    /*
+     * True if the async call was explicitly requested, e.g. via
+     * callFunctionWithAsyncStack.
+     */
+    bool asyncCallIsExplicit;
+
     /* If non-null, report JavaScript entry points to this monitor. */
     JS::dbg::AutoEntryMonitor* entryMonitor;
 
     js::Activation* const* addressOfActivation() const {
         return &activation_;
     }
     static unsigned offsetOfActivation() {
         return offsetof(JSRuntime, activation_);
@@ -1479,16 +1485,39 @@ struct JSRuntime : public JS::shadow::Ru
 
   public:
 
     /* ------------------------------------------
        Performance measurements
        ------------------------------------------ */
     struct Stopwatch {
         /**
+         * A map used to collapse compartments belonging to the same
+         * add-on (respectively to the same webpage, to the platform)
+         * into a single group.
+         *
+         * Keys: for system compartments, a `JSAddonId*` (which may be
+         * `nullptr`), and for webpages, a `JSPrincipals*` (which may
+         * not). Note that compartments may start as non-system
+         * compartments and become compartments later during their
+         * lifetime, which requires an invalidation.
+         *
+         * This map is meant to be accessed only by instances of
+         * PerformanceGroupHolder, which handle both reference-counting
+         * of the values and invalidation of the key/value pairs.
+         */
+        typedef js::HashMap<void*, js::PerformanceGroup*,
+                            js::DefaultHasher<void*>,
+                            js::SystemAllocPolicy> Groups;
+
+        Groups& groups() {
+            return groups_;
+        }
+
+        /**
          * The number of times we have entered the event loop.
          * Used to reset counters whenever we enter the loop,
          * which may be caused either by having completed the
          * previous run of the event loop, or by entering a
          * nested loop.
          *
          * Always incremented by 1, may safely overflow.
          */
@@ -1518,16 +1547,17 @@ struct JSRuntime : public JS::shadow::Ru
         JSCurrentPerfGroupCallback currentPerfGroupCallback;
 
         Stopwatch()
           : iteration(0)
           , isEmpty(true)
           , currentPerfGroupCallback(nullptr)
           , isMonitoringJank_(false)
           , isMonitoringCPOW_(false)
+          , isMonitoringPerCompartment_(false)
           , idCounter_(0)
         { }
 
         /**
          * Reset the stopwatch.
          *
          * This method is meant to be called whenever we start processing
          * an event, to ensure that stop any ongoing measurement that would
@@ -1559,16 +1589,31 @@ struct JSRuntime : public JS::shadow::Ru
 
             isMonitoringJank_ = value;
             return true;
         }
         bool isMonitoringJank() const {
             return isMonitoringJank_;
         }
 
+        bool setIsMonitoringPerCompartment(bool value) {
+            if (isMonitoringPerCompartment_ != value)
+                reset();
+
+            if (value && !groups_.initialized()) {
+                if (!groups_.init(128))
+                    return false;
+            }
+
+            isMonitoringPerCompartment_ = value;
+            return true;
+        }
+        bool isMonitoringPerCompartment() const {
+            return isMonitoringPerCompartment_;
+        }
 
         /**
          * Activate/deactivate stopwatch measurement of CPOW.
          */
         bool setIsMonitoringCPOW(bool value) {
             isMonitoringCPOW_ = value;
             return true;
         }
@@ -1602,43 +1647,25 @@ struct JSRuntime : public JS::shadow::Ru
             }
           private:
             uint64_t latestGood_;
         };
         MonotonicTimeStamp systemTimeFix;
         MonotonicTimeStamp userTimeFix;
 
     private:
-        /**
-         * A map used to collapse compartments belonging to the same
-         * add-on (respectively to the same webpage, to the platform)
-         * into a single group.
-         *
-         * Keys: for system compartments, a `JSAddonId*` (which may be
-         * `nullptr`), and for webpages, a `JSPrincipals*` (which may
-         * not). Note that compartments may start as non-system
-         * compartments and become compartments later during their
-         * lifetime, which requires an invalidation.
-         *
-         * This map is meant to be accessed only by instances of
-         * PerformanceGroupHolder, which handle both reference-counting
-         * of the values and invalidation of the key/value pairs.
-         */
-        typedef js::HashMap<void*, js::PerformanceGroup*,
-                            js::DefaultHasher<void*>,
-                            js::SystemAllocPolicy> Groups;
-
         Groups groups_;
         friend struct js::PerformanceGroupHolder;
 
         /**
          * `true` if stopwatch monitoring is active, `false` otherwise.
          */
         bool isMonitoringJank_;
         bool isMonitoringCPOW_;
+        bool isMonitoringPerCompartment_;
 
         /**
          * A counter used to generate unique identifiers for groups.
          */
         uint64_t idCounter_;
     };
     Stopwatch stopwatch;
 };
--- a/js/src/vm/SavedStacks.cpp
+++ b/js/src/vm/SavedStacks.cpp
@@ -907,31 +907,38 @@ SavedStacks::insertFrames(JSContext* cx,
     RootedSavedFrame asyncStack(cx, nullptr);
     RootedString asyncCause(cx, nullptr);
 
     // Accumulate the vector of Lookup objects in |stackChain|.
     SavedFrame::AutoLookupVector stackChain(cx);
     while (!iter.done()) {
         Activation& activation = *iter.activation();
 
+        if (asyncActivation && asyncActivation != &activation) {
+            // We found an async stack in the previous activation, and we
+            // walked past the oldest frame of that activation, we're done.
+            // However, we only want to use the async parent if it was
+            // explicitly requested; if we got here otherwise, we have
+            // a direct parent, which we prefer.
+            if (asyncActivation->asyncCallIsExplicit())
+                break;
+            asyncActivation = nullptr;
+        }
+
         if (!asyncActivation) {
             asyncStack = activation.asyncStack();
             if (asyncStack) {
                 // While walking from the youngest to the oldest frame, we found
                 // an activation that has an async stack set. We will use the
                 // youngest frame of the async stack as the parent of the oldest
                 // frame of this activation. We still need to iterate over other
                 // frames in this activation before reaching the oldest frame.
                 asyncCause = activation.asyncCause();
                 asyncActivation = &activation;
             }
-        } else if (asyncActivation != &activation) {
-            // We found an async stack in the previous activation, and we
-            // walked past the oldest frame of that activation, we're done.
-            break;
         }
 
         AutoLocationValueRooter location(cx);
         {
             AutoCompartment ac(cx, iter.compartment());
             if (!cx->compartment()->savedStacks().getLocation(cx, iter, &location))
                 return false;
         }
--- a/js/src/vm/Stack-inl.h
+++ b/js/src/vm/Stack-inl.h
@@ -863,34 +863,37 @@ Activation::Activation(JSContext* cx, Ki
   : cx_(cx),
     compartment_(cx->compartment()),
     prev_(cx->runtime_->activation_),
     prevProfiling_(prev_ ? prev_->mostRecentProfiling() : nullptr),
     savedFrameChain_(0),
     hideScriptedCallerCount_(0),
     asyncStack_(cx, cx->runtime_->asyncStackForNewActivations),
     asyncCause_(cx, cx->runtime_->asyncCauseForNewActivations),
+    asyncCallIsExplicit_(cx->runtime_->asyncCallIsExplicit),
     entryMonitor_(cx->runtime_->entryMonitor),
     kind_(kind)
 {
     cx->runtime_->asyncStackForNewActivations = nullptr;
     cx->runtime_->asyncCauseForNewActivations = nullptr;
+    cx->runtime_->asyncCallIsExplicit = false;
     cx->runtime_->entryMonitor = nullptr;
     cx->runtime_->activation_ = this;
 }
 
 Activation::~Activation()
 {
     MOZ_ASSERT_IF(isProfiling(), this != cx_->runtime()->profilingActivation_);
     MOZ_ASSERT(cx_->runtime_->activation_ == this);
     MOZ_ASSERT(hideScriptedCallerCount_ == 0);
     cx_->runtime_->activation_ = prev_;
     cx_->runtime_->entryMonitor = entryMonitor_;
     cx_->runtime_->asyncCauseForNewActivations = asyncCause_;
     cx_->runtime_->asyncStackForNewActivations = asyncStack_;
+    cx_->runtime_->asyncCallIsExplicit = asyncCallIsExplicit_;
 }
 
 bool
 Activation::isProfiling() const
 {
     if (isInterpreter())
         return asInterpreter()->isProfiling();
 
--- a/js/src/vm/Stack.h
+++ b/js/src/vm/Stack.h
@@ -1125,16 +1125,20 @@ class Activation
     //
     // Usually this is nullptr, meaning that normal stack capture will occur.
     // When this is set, the stack of any previous activation is ignored.
     Rooted<SavedFrame*> asyncStack_;
 
     // Value of asyncCause to be attached to asyncStack_.
     RootedString asyncCause_;
 
+    // True if the async call was explicitly requested, e.g. via
+    // callFunctionWithAsyncStack.
+    bool asyncCallIsExplicit_;
+
     // The entry point monitor that was set on cx_->runtime() when this
     // Activation was created. Subclasses should report their entry frame's
     // function or script here.
     JS::dbg::AutoEntryMonitor* entryMonitor_;
 
     enum Kind { Interpreter, Jit, AsmJS };
     Kind kind_;
 
@@ -1210,16 +1214,20 @@ class Activation
     SavedFrame* asyncStack() {
         return asyncStack_;
     }
 
     JSString* asyncCause() {
         return asyncCause_;
     }
 
+    bool asyncCallIsExplicit() const {
+        return asyncCallIsExplicit_;
+    }
+
   private:
     Activation(const Activation& other) = delete;
     void operator=(const Activation& other) = delete;
 };
 
 // This variable holds a special opcode value which is greater than all normal
 // opcodes, and is chosen such that the bitwise or of this value with any
 // opcode is this value.
--- a/js/xpconnect/shell/moz.build
+++ b/js/xpconnect/shell/moz.build
@@ -10,18 +10,16 @@ SOURCES += [
     'xpcshell.cpp',
 ]
 
 if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'cocoa':
     SOURCES += [
         'xpcshellMacUtils.mm',
     ]
 
-if CONFIG['MOZ_SHARK']:
-    DEFINES['MOZ_SHARK'] = True
 if CONFIG['MOZ_CALLGRIND']:
     DEFINES['MOZ_CALLGRIND'] = True
 
 include('/ipc/chromium/chromium-config.mozbuild')
 
 LOCAL_INCLUDES += [
     '/toolkit/xre',
 ]
--- a/js/xpconnect/src/XPCComponents.cpp
+++ b/js/xpconnect/src/XPCComponents.cpp
@@ -2819,17 +2819,18 @@ nsXPCComponents_Utils::CallFunctionWithA
     }
 
     JS::Rooted<JSObject*> asyncStackObj(cx, &asyncStack.toObject());
     JS::Rooted<JSString*> asyncCauseString(cx, JS_NewUCStringCopyN(cx, asyncCause.BeginReading(),
                                                                        asyncCause.Length()));
     if (!asyncCauseString)
         return NS_ERROR_OUT_OF_MEMORY;
 
-    JS::AutoSetAsyncStackForNewCalls sas(cx, asyncStackObj, asyncCauseString);
+    JS::AutoSetAsyncStackForNewCalls sas(cx, asyncStackObj, asyncCauseString,
+                                         JS::AutoSetAsyncStackForNewCalls::AsyncCallKind::EXPLICIT);
 
     if (!JS_CallFunctionValue(cx, nullptr, function,
                               JS::HandleValueArray::empty(), retval))
     {
         return NS_ERROR_XPC_JAVASCRIPT_ERROR;
     }
 
     return NS_OK;
--- a/layout/base/nsDisplayList.cpp
+++ b/layout/base/nsDisplayList.cpp
@@ -1240,17 +1240,17 @@ nsDisplayListBuilder::IsInWillChangeBudg
     nsString limitStr;
     nsRect area = aFrame->PresContext()->GetVisibleArea();
     uint32_t budgetLimit = nsPresContext::AppUnitsToIntCSSPixels(area.width) *
       nsPresContext::AppUnitsToIntCSSPixels(area.height);
     limitStr.AppendInt(budgetLimit);
 
     const char16_t* params[] = { usageStr.get(), multiplierStr.get(), limitStr.get() };
     aFrame->PresContext()->Document()->WarnOnceAbout(
-      nsIDocument::eWillChangeBudget, false,
+      nsIDocument::eWillChangeOverBudgetIgnored, false,
       params, ArrayLength(params));
   }
   return onBudget;
 }
 
 void
 nsDisplayListBuilder::EnterSVGEffectsContents(nsDisplayList* aHoistedItemsStorage)
 {
--- a/layout/base/tests/test_bug558663.html
+++ b/layout/base/tests/test_bug558663.html
@@ -23,15 +23,16 @@
   <script>
   if (navigator.platform.startsWith("Linux")) {
     // For e10s issue of bug 966157
     SimpleTest.expectAssertions(0, 2);
   }
   SimpleTest.waitForExplicitFinish();
   // Selection caret's pref is checked only when PresShell is initialized. To turn
   // off the pref, we test bug 558663 in an iframe.
-  SpecialPowers.pushPrefEnv({"set": [['selectioncaret.enabled', false]]}, function() {
+  SpecialPowers.pushPrefEnv({"set": [['selectioncaret.enabled', false],
+                                     ['layout.accessiblecaret.enabled', false]]}, function() {
     var iframe = document.createElement("iframe");
     iframe.src = "bug558663.html";
     document.getElementById('container').appendChild(iframe);
   });
   </script>
 </html>
--- a/layout/base/tests/test_reftests_with_caret.html
+++ b/layout/base/tests/test_reftests_with_caret.html
@@ -104,18 +104,19 @@ var tests = [
     [ 'bug106855-2.html' , 'bug106855-1-ref.html' ] ,
     [ 'bug389321-2.html' , 'bug389321-2-ref.html' ] ,
     [ 'bug613807-1.html' , 'bug613807-1-ref.html' ] ,
     [ 'bug1082486-1.html', 'bug1082486-1-ref.html'] ,
     [ 'bug1082486-2.html', 'bug1082486-2-ref.html'] ,
     // The following test cases are all involving with one sending
     // synthesizeKey(), the other without. They fail when the touch
     // or selection caret is enabled. Test them with these preferences off.
-    function() {SpecialPowers.pushPrefEnv({'set': [['touchcaret.enabled', false]]}, nextTest);} ,
-    function() {SpecialPowers.pushPrefEnv({'set': [['selectioncaret.enabled', false]]}, nextTest);} ,
+    function() {SpecialPowers.pushPrefEnv({'set': [['touchcaret.enabled', false],
+                                                   ['selectioncaret.enabled', false],
+                                                   ['layout.accessiblecaret.enabled', false]]}, nextTest);} ,
     [ 'bug240933-1.html' , 'bug240933-1-ref.html' ] ,
     [ 'bug240933-2.html' , 'bug240933-1-ref.html' ] ,
     [ 'bug389321-1.html' , 'bug389321-1-ref.html' ] ,
     [ 'bug389321-3.html' , 'bug389321-3-ref.html' ] ,
     [ 'bug482484.html'   , 'bug482484-ref.html'   ] ,
     [ 'bug503399.html'   , 'bug503399-ref.html'   ] ,
     [ 'bug585922.html'   , 'bug585922-ref.html'   ] ,
     [ 'bug597519-1.html' , 'bug597519-1-ref.html' ] ,
@@ -151,18 +152,19 @@ var tests = [
     [ 'bug1061468.html'  , 'bug1061468-ref.html'  ] ,
     [ 'bug1097242-1.html', 'bug1097242-1-ref.html'] ,
     [ 'bug1109968-1.html', 'bug1109968-1-ref.html'] ,
     [ 'bug1109968-2.html', 'bug1109968-2-ref.html'] ,
     // [ 'bug1123067-1.html' , 'bug1123067-ref.html'  ] , TODO: bug 1129205
     [ 'bug1123067-2.html' , 'bug1123067-ref.html'  ] ,
     [ 'bug1123067-3.html' , 'bug1123067-ref.html'  ] ,
     [ 'bug1132768-1.html' , 'bug1132768-1-ref.html'] ,
-    function() {SpecialPowers.pushPrefEnv({'clear': [['touchcaret.enabled']]}, nextTest);} ,
-    function() {SpecialPowers.pushPrefEnv({'clear': [['selectioncaret.enabled']]}, nextTest);} ,
+    function() {SpecialPowers.pushPrefEnv({'clear': [['touchcaret.enabled'],
+                                                     ['selectioncaret.enabled'],
+                                                     ['layout.accessiblecaret.enabled']]}, nextTest);} ,
 ];
 
 if (navigator.appVersion.indexOf("Android") == -1 &&
   SpecialPowers.Services.appinfo.name != "B2G") {
   tests.push(function() {SpecialPowers.pushPrefEnv({'set': [['touchcaret.enabled', false]]}, nextTest);});
   tests.push([ 'bug512295-1.html' , 'bug512295-1-ref.html' ]);
   tests.push([ 'bug512295-2.html' , 'bug512295-2-ref.html' ]);
   tests.push([ 'bug923376.html'   , 'bug923376-ref.html'   ]);
--- a/layout/forms/nsComboboxControlFrame.cpp
+++ b/layout/forms/nsComboboxControlFrame.cpp
@@ -728,17 +728,17 @@ nsComboboxControlFrame::GetIntrinsicISiz
 {
   // get the scrollbar width, we'll use this later
   nscoord scrollbarWidth = 0;
   nsPresContext* presContext = PresContext();
   if (mListControlFrame) {
     nsIScrollableFrame* scrollable = do_QueryFrame(mListControlFrame);
     NS_ASSERTION(scrollable, "List must be a scrollable frame");
     scrollbarWidth = scrollable->GetNondisappearingScrollbarWidth(
-      presContext, aRenderingContext);
+      presContext, aRenderingContext, GetWritingMode());
   }
 
   nscoord displayISize = 0;
   if (MOZ_LIKELY(mDisplayFrame)) {
     displayISize = nsLayoutUtils::IntrinsicForContainer(aRenderingContext,
                                                         mDisplayFrame,
                                                         aType);
   }
@@ -843,39 +843,39 @@ nsComboboxControlFrame::Reflow(nsPresCon
   if (NS_SUCCEEDED(aPresContext->PresShell()->PostReflowCallback(resize))) {
     // The reflow callback queue doesn't AddRef so we keep it alive until
     // it's released in its ReflowFinished / ReflowCallbackCanceled.
     unused << resize.forget();
   }
 
   // Get the width of the vertical scrollbar.  That will be the inline
   // size of the dropdown button.
+  WritingMode wm = aReflowState.GetWritingMode();
   nscoord buttonISize;
   const nsStyleDisplay *disp = StyleDisplay();
   if ((IsThemed(disp) && !aPresContext->GetTheme()->ThemeNeedsComboboxDropmarker()) ||
       StyleDisplay()->mAppearance == NS_THEME_NONE) {
     buttonISize = 0;
   }
   else {
     nsIScrollableFrame* scrollable = do_QueryFrame(mListControlFrame);
     NS_ASSERTION(scrollable, "List must be a scrollable frame");
     buttonISize = scrollable->GetNondisappearingScrollbarWidth(
-      PresContext(), aReflowState.rendContext);
+      PresContext(), aReflowState.rendContext, wm);
     if (buttonISize > aReflowState.ComputedISize()) {
       buttonISize = 0;
     }
   }
 
   mDisplayISize = aReflowState.ComputedISize() - buttonISize;
 
   nsBlockFrame::Reflow(aPresContext, aDesiredSize, aReflowState, aStatus);
 
   // The button should occupy the same space as a scrollbar
-  WritingMode wm = aReflowState.GetWritingMode();
-  nsSize containerSize = aReflowState.ComputedSizeAsContainerIfConstrained();
+  nsSize containerSize = aDesiredSize.PhysicalSize();
   LogicalRect buttonRect = mButtonFrame->GetLogicalRect(containerSize);
 
   buttonRect.IStart(wm) =
     aReflowState.ComputedLogicalBorderPadding().IStartEnd(wm) +
     mDisplayISize -
     (aReflowState.ComputedLogicalBorderPadding().IEnd(wm) -
      aReflowState.ComputedLogicalPadding().IEnd(wm));
   buttonRect.ISize(wm) = buttonISize;
--- a/layout/generic/nsGfxScrollFrame.cpp
+++ b/layout/generic/nsGfxScrollFrame.cpp
@@ -1037,44 +1037,48 @@ ScrollFrameHelper::GetDesiredScrollbarSi
     // at the top. (Are there any?)
     result.bottom = size.height;
   }
 
   return result;
 }
 
 nscoord
-ScrollFrameHelper::GetNondisappearingScrollbarWidth(nsBoxLayoutState* aState)
+ScrollFrameHelper::GetNondisappearingScrollbarWidth(nsBoxLayoutState* aState,
+                                                    WritingMode aWM)
 {
   NS_ASSERTION(aState && aState->GetRenderingContext(),
                "Must have rendering context in layout state for size "
                "computations");
 
+  bool verticalWM = aWM.IsVertical();
   if (LookAndFeel::GetInt(LookAndFeel::eIntID_UseOverlayScrollbars) != 0) {
     // We're using overlay scrollbars, so we need to get the width that
     // non-disappearing scrollbars would have.
     nsITheme* theme = aState->PresContext()->GetTheme();
     if (theme &&
         theme->ThemeSupportsWidget(aState->PresContext(),
-                                   mVScrollbarBox,
+                                   verticalWM ? mHScrollbarBox
+                                              : mVScrollbarBox,
                                    NS_THEME_SCROLLBAR_NON_DISAPPEARING)) {
       LayoutDeviceIntSize size;
       bool canOverride = true;
       theme->GetMinimumWidgetSize(aState->PresContext(),
-                                  mVScrollbarBox,
+                                  verticalWM ? mHScrollbarBox
+                                             : mVScrollbarBox,
                                   NS_THEME_SCROLLBAR_NON_DISAPPEARING,
                                   &size,
                                   &canOverride);
-      if (size.width) {
-        return aState->PresContext()->DevPixelsToAppUnits(size.width);
-      }
+      return aState->PresContext()->
+             DevPixelsToAppUnits(verticalWM ? size.height : size.width);
     }
   }
 
-  return GetDesiredScrollbarSizes(aState).LeftRight();
+  nsMargin sizes(GetDesiredScrollbarSizes(aState));
+  return verticalWM ? sizes.TopBottom() : sizes.LeftRight();
 }
 
 void
 ScrollFrameHelper::HandleScrollbarStyleSwitching()
 {
   // Check if we switched between scrollbar styles.
   if (mScrollbarActivity &&
       LookAndFeel::GetInt(LookAndFeel::eIntID_UseOverlayScrollbars) == 0) {
--- a/layout/generic/nsGfxScrollFrame.h
+++ b/layout/generic/nsGfxScrollFrame.h
@@ -299,17 +299,18 @@ public:
                                  const nsSize& aScrollPortSize) const;
 
   uint32_t GetScrollbarVisibility() const {
     return (mHasVerticalScrollbar ? nsIScrollableFrame::VERTICAL : 0) |
            (mHasHorizontalScrollbar ? nsIScrollableFrame::HORIZONTAL : 0);
   }
   nsMargin GetActualScrollbarSizes() const;
   nsMargin GetDesiredScrollbarSizes(nsBoxLayoutState* aState);
-  nscoord GetNondisappearingScrollbarWidth(nsBoxLayoutState* aState);
+  nscoord GetNondisappearingScrollbarWidth(nsBoxLayoutState* aState,
+                                           mozilla::WritingMode aVerticalWM);
   bool IsLTR() const;
   bool IsScrollbarOnRight() const;
   bool IsScrollingActive(nsDisplayListBuilder* aBuilder) const;
   bool IsMaybeScrollingActive() const;
   bool IsProcessingAsyncScroll() const {
     return mAsyncScroll != nullptr || mAsyncSmoothMSDScroll != nullptr;
   }
   void ResetScrollPositionForLayerPixelAlignment()
@@ -686,19 +687,19 @@ public:
     return mHelper.GetDesiredScrollbarSizes(aState);
   }
   virtual nsMargin GetDesiredScrollbarSizes(nsPresContext* aPresContext,
           nsRenderingContext* aRC) override {
     nsBoxLayoutState bls(aPresContext, aRC, 0);
     return GetDesiredScrollbarSizes(&bls);
   }
   virtual nscoord GetNondisappearingScrollbarWidth(nsPresContext* aPresContext,
-          nsRenderingContext* aRC) override {
+          nsRenderingContext* aRC, mozilla::WritingMode aWM) override {
     nsBoxLayoutState bls(aPresContext, aRC, 0);
-    return mHelper.GetNondisappearingScrollbarWidth(&bls);
+    return mHelper.GetNondisappearingScrollbarWidth(&bls, aWM);
   }
   virtual nsRect GetScrolledRect() const override {
     return mHelper.GetScrolledRect();
   }
   virtual nsRect GetScrollPortRect() const override {
     return mHelper.GetScrollPortRect();
   }
   virtual nsPoint GetScrollPosition() const override {
@@ -1090,19 +1091,19 @@ public:
     return mHelper.GetDesiredScrollbarSizes(aState);
   }
   virtual nsMargin GetDesiredScrollbarSizes(nsPresContext* aPresContext,
           nsRenderingContext* aRC) override {
     nsBoxLayoutState bls(aPresContext, aRC, 0);
     return GetDesiredScrollbarSizes(&bls);
   }
   virtual nscoord GetNondisappearingScrollbarWidth(nsPresContext* aPresContext,
-          nsRenderingContext* aRC) override {
+          nsRenderingContext* aRC, mozilla::WritingMode aWM) override {
     nsBoxLayoutState bls(aPresContext, aRC, 0);
-    return mHelper.GetNondisappearingScrollbarWidth(&bls);
+    return mHelper.GetNondisappearingScrollbarWidth(&bls, aWM);
   }
   virtual nsRect GetScrolledRect() const override {
     return mHelper.GetScrolledRect();
   }
   virtual nsRect GetScrollPortRect() const override {
     return mHelper.GetScrollPortRect();
   }
   virtual nsPoint GetScrollPosition() const override {
--- a/layout/generic/nsIScrollableFrame.h
+++ b/layout/generic/nsIScrollableFrame.h
@@ -102,18 +102,20 @@ public:
    * be visible due to overflowing content, are. This can be called during reflow
    * of the scrolled contents.
    */
   virtual nsMargin GetDesiredScrollbarSizes(nsPresContext* aPresContext,
                                             nsRenderingContext* aRC) = 0;
   /**
    * Return the width for non-disappearing scrollbars.
    */
-  virtual nscoord GetNondisappearingScrollbarWidth(nsPresContext* aPresContext,
-                                                   nsRenderingContext* aRC) = 0;
+  virtual nscoord
+  GetNondisappearingScrollbarWidth(nsPresContext* aPresContext,
+                                   nsRenderingContext* aRC,
+                                   mozilla::WritingMode aWM) = 0;
   /**
    * GetScrolledRect is designed to encapsulate deciding which
    * directions of overflow should be reachable by scrolling and which
    * should not.  Callers should NOT depend on it having any particular
    * behavior (although nsXULScrollFrame currently does).
    *
    * This should only be called when the scrolled frame has been
    * reflowed with the scroll port size given in mScrollPort.
--- a/media/libstagefright/system/core/libutils/VectorImpl.cpp
+++ b/media/libstagefright/system/core/libutils/VectorImpl.cpp
@@ -11,26 +11,31 @@
  * distributed under the License is distributed on an "AS IS" BASIS,
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
 
 #define LOG_TAG "Vector"
 
+#include <limits.h>
 #include <string.h>
 #include <stdlib.h>
 #include <stdio.h>
 
 #include <cutils/log.h>
 
 #include <utils/Errors.h>
 #include <utils/SharedBuffer.h>
 #include <utils/VectorImpl.h>
 
+#if !defined(SSIZE_MAX)
+#define SSIZE_MAX ((ssize_t)(SIZE_MAX/2))
+#endif
+
 /*****************************************************************************/
 
 
 namespace stagefright {
 
 // ----------------------------------------------------------------------------
 
 const size_t kMinVectorCapacity = 4;
@@ -320,22 +325,23 @@ const void* VectorImpl::itemLocation(siz
             return reinterpret_cast<const char*>(buffer) + index*mItemSize;
         }
     }
     return 0;
 }
 
 ssize_t VectorImpl::setCapacity(size_t new_capacity)
 {
-    size_t current_capacity = capacity();
-    ssize_t amount = new_capacity - size();
-    if (amount <= 0) {
+    if (new_capacity <= size()) {
         // we can't reduce the capacity
-        return current_capacity;
-    } 
+        return capacity();
+    }
+    if (new_capacity >= (SSIZE_MAX / mItemSize)) {
+        return NO_MEMORY;
+    }
     SharedBuffer* sb = SharedBuffer::alloc(new_capacity * mItemSize);
     if (sb) {
         void* array = sb->data();
         _do_copy(array, mStorage, size());
         release_storage();
         mStorage = const_cast<void*>(array);
     } else {
         return NO_MEMORY;
--- a/mobile/android/base/BrowserApp.java
+++ b/mobile/android/base/BrowserApp.java
@@ -473,18 +473,16 @@ public class BrowserApp extends GeckoApp
             case LOCATION_CHANGE:
                 // fall through
             case SELECTED:
                 if (Tabs.getInstance().isSelectedTab(tab)) {
                     updateHomePagerForTab(tab);
                 }
 
                 mHideDynamicToolbarOnActionModeEnd = false;
-
-                mProgressView.setPrivateMode(tab.isPrivate());
                 break;
             case START:
                 if (Tabs.getInstance().isSelectedTab(tab)) {
                     invalidateOptionsMenu();
 
                     if (mDynamicToolbar.isEnabled()) {
                         mDynamicToolbar.setVisible(true, VisibilityTransition.ANIMATE);
                     }
--- a/mobile/android/base/IntentHelper.java
+++ b/mobile/android/base/IntentHelper.java
@@ -14,19 +14,20 @@ import org.json.JSONArray;
 import org.json.JSONException;
 import org.json.JSONObject;
 
 import android.app.Activity;
 import android.content.Intent;
 import android.net.Uri;
 import android.text.TextUtils;
 import android.util.Log;
-import android.widget.Toast;
 
+import java.io.UnsupportedEncodingException;
 import java.net.URISyntaxException;
+import java.net.URLEncoder;
 import java.util.Arrays;
 import java.util.List;
 
 public final class IntentHelper implements GeckoEventListener {
     private static final String LOGTAG = "GeckoIntentHelper";
     private static final String[] EVENTS = {
         "Intent:GetHandlers",
         "Intent:Open",
@@ -34,16 +35,19 @@ public final class IntentHelper implemen
         "Intent:OpenNoHandler",
         "WebActivity:Open"
     };
 
     // via http://developer.android.com/distribute/tools/promote/linking.html
     private static String MARKET_INTENT_URI_PACKAGE_PREFIX = "market://details?id=";
     private static String EXTRA_BROWSER_FALLBACK_URL = "browser_fallback_url";
 
+    /** A partial URI to an error page - the encoded error URI should be appended before loading. */
+    private static String UNKNOWN_PROTOCOL_URI_PREFIX = "about:neterror?e=unknownProtocolFound&u=";
+
     private static IntentHelper instance;
 
     private final Activity activity;
 
     private IntentHelper(Activity activity) {
         this.activity = activity;
         EventDispatcher.getInstance().registerGeckoThreadListener(this, EVENTS);
     }
@@ -128,29 +132,34 @@ public final class IntentHelper implemen
      * and we can bring the user directly to the application page in an app market. If a package is
      * not specified and there is a fallback url in the intent extras, we open that url. If neither
      * is present, we alert the user that we were unable to open the link.
      */
     private void openNoHandler(final JSONObject msg) {
         final String uri = msg.optString("uri");
 
         if (TextUtils.isEmpty(uri)) {
-            displayToastCannotOpenLink();
-            Log.w(LOGTAG, "Received empty URL. Ignoring...");
+            openUnknownProtocolErrorPage("");
+            Log.w(LOGTAG, "Received empty URL - loading about:neterror");
             return;
         }
 
         final Intent intent;
         try {
             // TODO (bug 1173626): This will not handle android-app uris on non 5.1 devices.
             intent = Intent.parseUri(uri, 0);
         } catch (final URISyntaxException e) {
-            displayToastCannotOpenLink();
+            try {
+                openUnknownProtocolErrorPage(URLEncoder.encode(uri, "UTF-8"));
+            } catch (final UnsupportedEncodingException encodingE) {
+                openUnknownProtocolErrorPage("");
+            }
+
             // Don't log the exception to prevent leaking URIs.
-            Log.w(LOGTAG, "Unable to parse Intent URI");
+            Log.w(LOGTAG, "Unable to parse Intent URI - loading about:neterror");
             return;
         }
 
         // For this flow, we follow Chrome's lead:
         //   https://developer.chrome.com/multidevice/android/intents
         //
         // Note on alternative flows: we could get the intent package from a component, however, for
         // security reasons, components are ignored when opening URIs (bug 1168998) so we should
@@ -167,25 +176,31 @@ public final class IntentHelper implemen
             marketIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
             activity.startActivity(marketIntent);
 
         } else if (intent.hasExtra(EXTRA_BROWSER_FALLBACK_URL)) {
             final String fallbackUrl = intent.getStringExtra(EXTRA_BROWSER_FALLBACK_URL);
             Tabs.getInstance().loadUrl(fallbackUrl);
 
         }  else {
-            displayToastCannotOpenLink();
+            openUnknownProtocolErrorPage(intent.getData().toString());
             // Don't log the URI to prevent leaking it.
-            Log.w(LOGTAG, "Unable to handle URI");
+            Log.w(LOGTAG, "Unable to open URI, default case - loading about:neterror");
         }
     }
 
-    private void displayToastCannotOpenLink() {
-        final String errText = activity.getResources().getString(R.string.intent_uri_cannot_open);
-        Toast.makeText(activity, errText, Toast.LENGTH_LONG).show();
+    /**
+     * Opens about:neterror with the unknownProtocolFound text.
+     * @param encodedUri The encoded uri. While the page does not open correctly without specifying
+     *                   a uri parameter, it happily accepts the empty String so this argument may
+     *                   be the empty String.
+     */
+    private void openUnknownProtocolErrorPage(final String encodedUri) {
+        final String errorUri = UNKNOWN_PROTOCOL_URI_PREFIX + encodedUri;
+        Tabs.getInstance().loadUrl(errorUri);
     }
 
     private void openWebActivity(JSONObject message) throws JSONException {
         final Intent intent = WebActivityMapper.getIntentForWebActivity(message.getJSONObject("activity"));
         ActivityHandlerHelper.startIntentForActivity(activity, intent, new ResultHandler(message));
     }
 
     private static class ResultHandler implements ActivityResultHandler {
--- a/mobile/android/base/ZoomedView.java
+++ b/mobile/android/base/ZoomedView.java
@@ -497,23 +497,25 @@ public class ZoomedView extends FrameLay
         // the position of the zoomed view will be calculated.
         animationStart.x = (float) leftFromGecko * metrics.zoomFactor + offset.x;
         animationStart.y = (float) topFromGecko * metrics.zoomFactor + offset.y;
 
         moveUsingGeckoPosition(leftFromGecko, topFromGecko);
     }
 
     private void stopZoomDisplay(boolean withAnimation) {
-        shouldSetVisibleOnUpdate = false;
-        hideZoomedView(withAnimation);
-        ThreadUtils.removeCallbacksFromUiThread(requestRenderRunnable);
-        if (layerView != null) {
-            layerView.setOnMetricsChangedZoomedViewportListener(null);
-            layerView.removeZoomedViewListener(this);
-            layerView = null;
+        if (getVisibility() == View.VISIBLE) {
+            shouldSetVisibleOnUpdate = false;
+            hideZoomedView(withAnimation);
+            ThreadUtils.removeCallbacksFromUiThread(requestRenderRunnable);
+            if (layerView != null) {
+                layerView.setOnMetricsChangedZoomedViewportListener(null);
+                layerView.removeZoomedViewListener(this);
+                layerView = null;
+            }
         }
     }
 
     private void changeZoomFactor() {
         if (currentZoomFactorIndex < ZOOM_FACTORS_LIST.length - 1) {
             currentZoomFactorIndex++;
         } else {
             currentZoomFactorIndex = 0;
--- a/mobile/android/base/toolbar/BrowserToolbar.java
+++ b/mobile/android/base/toolbar/BrowserToolbar.java
@@ -529,16 +529,17 @@ public abstract class BrowserToolbar ext
         if (selectedTab != null) {
             updateProgressVisibility(selectedTab, selectedTab.getLoadProgress());
         }
     }
 
     private void updateProgressVisibility(Tab selectedTab, int progress) {
         if (!isEditing() && selectedTab.getState() == Tab.STATE_LOADING) {
             progressBar.setProgress(progress);
+            progressBar.setPrivateMode(selectedTab.isPrivate());
             progressBar.setVisibility(View.VISIBLE);
         } else {
             progressBar.setVisibility(View.GONE);
         }
     }
 
     protected boolean isVisible() {
         return ViewHelper.getTranslationY(this) == 0;
--- a/mobile/android/tests/browser/robocop/helpers/WaitHelper.java
+++ b/mobile/android/tests/browser/robocop/helpers/WaitHelper.java
@@ -124,26 +124,26 @@ public final class WaitHelper {
     }
 
     /**
      * Implementations of this interface verify that the state of the test has changed from
      * the invocation of storeState to the invocation of hasStateChanged. A boolean will be
      * returned from hasStateChanged, indicating this change of status.
      */
     private interface ChangeVerifier {
-        public String getLogTag();
+        String getLogTag();
 
         /**
          * Stores the initial state of the system. This system state is used to diff against
          * the end state to determine if the system has changed. Since this is just a diff
          * (with a timeout), this method could potentially store state inconsistent with
          * what is visible to the user.
          */
-        public void storeState();
-        public boolean hasStateChanged();
+        void storeState();
+        boolean hasStateChanged();
     }
 
     private static class ToolbarTitleTextChangeVerifier implements ChangeVerifier {
         private static final String LOGTAG = ToolbarTitleTextChangeVerifier.class.getSimpleName();
 
         // A regex that matches the page title that shows up while the page is loading.
         private static final Pattern LOADING_PREFIX = Pattern.compile("[A-Za-z]{3,9}://");
 
--- a/mobile/locales/en-US/searchplugins/amazondotcom.xml
+++ b/mobile/locales/en-US/searchplugins/amazondotcom.xml
@@ -1,15 +1,15 @@
 <!-- This Source Code Form is subject to the terms of the Mozilla Public
    - License, v. 2.0. If a copy of the MPL was not distributed with this
    - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
 
 <SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
 <ShortName>Amazon.com</ShortName>
 <InputEncoding>ISO-8859-1</InputEncoding>
 <Image width="16" height="16">data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGAAAABgCAYAAADimHc4AAAAAXNSR0IArs4c6QAACixJREFUeAHtXQ1MldcZfuRXlIr4A1MoYAf+xKBswlKmdTJ1Q7RZdc5kM7XWsXaGWk2WTDPc0qlzOmZqnHGZoxrntuiydB1UXZSRqS1h03WTVdDa+osov/I3qAicve93ufTC/eHc673f+ejOmxzu933n/c77nuc5/+fcC/CJjKDLXArlFFopCB38igFjytgyxoz1APkM3ZVQ0KCbgwFjzZgbwmxo8M0B3rGAM+YjgunPdyhspKDFXASeInPVTMABCvHm2tbW+hCI5eaHO4cnNCRKEGhjArhd0qIIgSBFdrXZPgQ0AYqLgiZAE6AYAcXmdQ3QBChGQLF5XQM0AYoRUGxe1wBNgGIEFJvXNUAToBgBxeZ1DdAEKEZAsfkQxfZ9Nh8SEoKwsDD09PTg4cOHPqej+sVhQcC4ceOwdOlSZGRkICUlxQhJSUkIDuYNPaCpqQm3bt3CzZs3UVlZiVOnTqG8vNwgRzXAMvYdN4otdT1//nxBYIpHjx4Jb6W2tlbk5+eLqKgoS+WJCBnsj9ODwQqm38fHx4uSkhJvMXep39zcLNauXWt6HlwA7c4HaxGwZMkS0dDQ4BLMx3m4b98+dwCofm4dAhYuXOhTcyNLzObNm1WD7cq+NQiYNm2a4OYikNLZ2SmSk5NdgaDymTUIOHnyZCCx70/70KFDKsF2sm2JYyk02sHZs2dlRmyoqanB4cOHcfnyZbS2tmLq1KlYs2YN0tLSpN5vbGxEbGyspYaoTqx40YP75d2jR4/2l1BPFzt37hQjR450shkUFCR27drl6dUBcenp6U5pmJ1nB3tqmyAGr76+fgBArm4KCgqGBI0mX65edXq2YsWKIdNyACigusoX41JTUzFhwgTKr3uhzhnbtm1zr9AXc+TIkSF1WIHmGVJ6ZigpJ+DatWtG+9/d3e02v8eOHUNbW5vbeHtERUWF/dLjJy9tWEWUrwV1dHRgwYIFxrpOXFwcEhMTMWXKFCPwek9oaCi2b98uhVd7e7uUHi/kWUUs4wmvat6+fdsI58+f9wkfT7XIMUHqdxxvlV5bxxM/wEC9rVQqsnpSiT2mkmVqgEw+uDnicf/MmTMxY8YMozOdPHkyOEyaNAkTJ06UScZSOpYnIDMzEzk5OcjOzjYmW1Zqv/3BpCUJCA8Px+rVq7Fp0ybwMPXTLJYjYN68eSgsLAQtzn2ace/Pm6U64S1btuDcuXP/N+AzC5apAVu3bpUe77PjPJLh/V9enKOlDNAmjjFnWL9+PUd7FCuNgtjRgK51yKS/aNEiwkRO6urqxMaNG0VMTIyT39OnT5dKZMeOHU7vyvgZCB3lNSAiIgIHDx6kvA0tpaWlWL58ubEM7UrbfkrCVZxVnyknYOXKlcayw1AAXbhwAcuWLQPtarlVHY4EKO+E161b5xZQxwg6YuIRfNYdjnMEpQSMHj0avBs2lNy5cwdnzpwZSm1YzoSVEsCTLJmFMdllZj41JyPR0dEyaqboKCUgISFBKpOe2n3HBPj4oozwsrdVRCkBtL8rhYNMieWZ8+LFi6XSmzNnDkaM4PMI6kUpAXTmUwoB2kQ3JlmelGnDvv+wric9juMtSVmyhkrLH/HKJiVZWVlSEydW2rBhg1s/aQlDOh274qVLlwQtb7tNk4A1K840Q04Zos140dvba8fE42dXV5egFVJBTUd/OrQPIPbu3evxPU+Re/bs6U/LRMAH21RHAGeaRjieMHKKq6qqErRJL06fPi3oixlO8d4+sMAZIbUEcNOiUmjkNLhEmn2vloDIyEhx7949v3Fw8eJFo4bIJLh7926zwXZlTy0B3AzRdqN0X+AJ2OLiYkGza8GkXr161a0qnZ4QtGztCgwVz9QTwCTk5uYKBsZX2b9/v6DFuH4A6UyRqK6udkqODngJCzQ7/X5S3q1BAPvB+wJXrlxxAs3Tg7KyMsHfJXOVDzo5MeDcKa0pidmzZ7vUdXyfjg2J1DiI59IgXsmCePXLEHn0mZMKkTDOv3jxdJAd8lqSY4DrDUBvr9evenyBj56sWrXKCPSNGfCCnaPQsBXUvIAPbxUVFeHEiROO0U7XPOs9cOCAsXOWl5dnfDop9T1IiQW+/xXga3TSfWKkOy3gj+8B3/iV+3hvYnwm4P3XbL9Anf8W8Na/vTHpnS4f3OXzPnzssKWlxTgjSjXCu0QktHetAL5HKxkhEmsDnTSBH/WKRKISKj4TkDge+BNtv37uSeDvN4D8PwN/rZKwaFGVl2lVvPVj4E4Tfe+4A2im0Eb3wUTIdPqZ7Re/CLz0jM35FtoTGrvJPxnxmQA2HxEGvLEG+GaGzZmKu8AvSoHf/QPo7PKPg1ZJZewo4MHrNm/OXQO+9HP/eCZR4dwbYpC/VQhsfpP6AmoVZtEq76+fB+7uBn72dSDJ87F/9wkrjmG/f5ADHH8JGN/XF0QTAXZ59yP71eN/PlYNcDSfReeo3ngBmEJNk12YlGI6sv8b+rcFpyuBdqrSVpXYMcCqdFttznzK5iX7n7ETeO828PzTlI8Xbc+X7gdO/sc/OfEbAezOKGqSfvIcQMM2BHHKDtLVA5z9AHibCHmbnL9e7xCp6DKaBljL0wj0LwBcgIIdfL7RCLz8W+AMFRyWQmpqvz0XuEnPU34IdFN+/CF+JcDu0NNUgn65Gkjz8E2gqvs2Mt75EPgnlbC7D+xvB+6TxvCYmwzMozD3swCN9Z0KSncv8HoJ8Fox0NHXj4XSb4LcK6DmiAh79bitn/OXlwEhgJ3jDSeu0j9+FphG4+uhpLaNiLhlq+4f1gFcAm80ADXNQA+B4o3EUHOSNJ5KaozNNo9iuFA86WErmFobFF0CflQEVFQPtEYTMmPEV9NCaW79hJiBWr7dBYwAuzs8jHshkzrqbGAqAeKtcDvc9F+gjgh60GHLvL1kcjMXFgJEhtM/QKDdzbER9I9ZougZlVhZ6aH0j18AfvoX4H0axbkSHm7TLNgY+ZRfd6Xh+7OAE2B3jWsEt7PfnQ9wieJqrVLutwJ/uAjsKwU+qnfvCfcT9wuAvN9TP/COez1fY0wjwNFBHnFwx/fsLOAZao/NIqORatKb/wKOUYn/Gw0IZJZRRoYCcxKBd6mvCoQoIcAxI1HUbHx1JpBNIZ0yOmOS3HKAYxrurhvagTJqMspo3M4Alt/w3+jFnU1vnysnYLDDXOJmxQOfT7B1oHFjgTjqPGOfAMYQWWOorQ+ndp/7ho8f0XIB/VxcIwFdT4E7bW5OOHCH/kHt4NStd285AmQg4o7d25GRTLoqdIYlASqACpRNKktaVCKgCVCJPtnWBGgCFCOg2LyuAZoAxQgoNq9rgCZAMQKKzesaoAlQjIBi87oGaAIUI6DYvK4BmgDFCCg2zzWAtru1KEKgjQmoVGRcmyXsmYBCjYQyBAzseVeshALtsupgIgaMOWNvCJ0d0yQQBmYVQgafMR8gzEYuhXIKdGzJNGfMyrRqO4wpY8sY95f8/wEKvBLprcz3zwAAAABJRU5ErkJggg==</Image>
-<Url type="text/html" method="GET" template="http://www.amazon.com/gp/aw/s">
+<Url type="text/html" method="GET" template="https://www.amazon.com/gp/aw/s">
   <Param name="k" value="{searchTerms}"/>
   <Param name="sourceid" value="Mozilla-search"/>
   <Param name="tag" value="mozilla-20"/>
 </Url>
-<SearchForm>http://www.amazon.com/</SearchForm>
+<SearchForm>https://www.amazon.com/</SearchForm>
 </SearchPlugin>
--- a/modules/libpref/init/all.js
+++ b/modules/libpref/init/all.js
@@ -440,16 +440,18 @@ pref("media.getusermedia.screensharing.e
 pref("media.getusermedia.screensharing.allowed_domains", "webex.com,*.webex.com,ciscospark.com,*.ciscospark.com,projectsquared.com,*.projectsquared.com,*.room.co,room.co,beta.talky.io,talky.io,*.clearslide.com,appear.in,*.appear.in,tokbox.com,*.tokbox.com,*.sso.francetelecom.fr,*.si.francetelecom.fr,*.sso.infra.ftgroup,*.multimedia-conference.orange-business.com,*.espacecollaboration.orange-business.com,free.gotomeeting.com,g2m.me,*.g2m.me,example.com,*.mypurecloud.com,*.mypurecloud.com.au,spreed.me,*.spreed.me,*.spreed.com");
 #else
  // temporary value, not intended for release - bug 1049087
 pref("media.getusermedia.screensharing.allowed_domains", "mozilla.github.io,webex.com,*.webex.com,ciscospark.com,*.ciscospark.com,projectsquared.com,*.projectsquared.com,*.room.co,room.co,beta.talky.io,talky.io,*.clearslide.com,appear.in,*.appear.in,tokbox.com,*.tokbox.com,*.sso.francetelecom.fr,*.si.francetelecom.fr,*.sso.infra.ftgroup,*.multimedia-conference.orange-business.com,*.espacecollaboration.orange-business.com,free.gotomeeting.com,g2m.me,*.g2m.me,example.com,*.mypurecloud.com,*.mypurecloud.com.au,spreed.me,*.spreed.me,*.spreed.com");
 #endif
 // OS/X 10.6 and XP have screen/window sharing off by default due to various issues - Caveat emptor
 pref("media.getusermedia.screensharing.allow_on_old_platforms", false);
 
+pref("media.getusermedia.audiocapture.enabled", false);
+
 // TextTrack support
 pref("media.webvtt.enabled", true);
 pref("media.webvtt.regions.enabled", false);
 
 // AudioTrack and VideoTrack support
 pref("media.track.enabled", false);
 
 // Whether to enable MediaSource support.
@@ -4351,18 +4353,18 @@ pref("dom.webnotifications.enabled", tru
 // Alert animation effect, name is disableSlidingEffect for backwards-compat.
 pref("alerts.disableSlidingEffect", false);
 
 // DOM full-screen API.
 pref("full-screen-api.enabled", false);
 pref("full-screen-api.allow-trusted-requests-only", true);
 pref("full-screen-api.pointer-lock.enabled", true);
 // transition duration of fade-to-black and fade-from-black, unit: ms
-pref("full-screen-api.transition-duration.enter", "400 400");
-pref("full-screen-api.transition-duration.leave", "400 400");
+pref("full-screen-api.transition-duration.enter", "200 200");
+pref("full-screen-api.transition-duration.leave", "200 200");
 
 // DOM idle observers API
 pref("dom.idle-observers-api.enabled", true);
 
 // Time limit, in milliseconds, for EventStateManager::IsHandlingUserInput().
 // Used to detect long running handlers of user-generated events.
 pref("dom.event.handling-user-input-time-limit", 1000);
 
@@ -4734,29 +4736,29 @@ pref("urlclassifier.disallow_completions
 // checks.
 pref("urlclassifier.trackingTable", "mozpub-track-digest256");
 pref("browser.trackingprotection.updateURL", "https://tracking.services.mozilla.com/downloads?client=SAFEBROWSING_ID&appver=%VERSION%&pver=2.2");
 pref("browser.trackingprotection.gethashURL", "https://tracking.services.mozilla.com/gethash?client=SAFEBROWSING_ID&appver=%VERSION%&pver=2.2");
 
 // Turn off Spatial navigation by default.
 pref("snav.enabled", false);
 
-// Turn off touch caret by default.
+// Original caret implementation on collapsed selection.
 pref("touchcaret.enabled", false);
 
 // This will inflate the size of the touch caret frame when checking if user
 // clicks on the caret or not. In app units.
 pref("touchcaret.inflatesize.threshold", 40);
 
 // We'll start to increment time when user release the control of touch caret.
 // When time exceed this expiration time, we'll hide touch caret.
 // In milliseconds. (0 means disable this feature)
 pref("touchcaret.expiration.time", 3000);
 
-// Turn off selection caret by default
+// Original caret implementation on non-collapsed selection.
 pref("selectioncaret.enabled", false);
 
 // This will inflate size of selection caret frame when we checking if
 // user click on selection caret or not. In app units.
 pref("selectioncaret.inflatesize.threshold", 40);
 
 // Selection carets will fall-back to internal LongTap detector.
 pref("selectioncaret.detects.longtap", true);
--- a/testing/mozharness/scripts/firefox_ui_updates.py
+++ b/testing/mozharness/scripts/firefox_ui_updates.py
@@ -119,16 +119,17 @@ class FirefoxUIUpdates(FirefoxUITests):
             # We want to make sure that anyone trying to reproduce a job will
             # is using the exact tools tag for reproducibility's sake
             self.fatal('Make sure to specify the --tools-tag')
 
         self.tools_repo = self.config['tools_repo']
         self.tools_tag = self.config['tools_tag']
 
         if self.config.get('update_verify_config'):
+            self.update_verify_config = self.config['update_verify_config']
             self.updates_config_file = os.path.join(
                 dirs['abs_tools_dir'], 'release', 'updates',
                 self.config['update_verify_config']
             )
         else:
             self.fatal('Make sure to specify --update-verify-config. '
                        'See under the directory release/updates in %s.' % self.tools_repo)
 
@@ -247,27 +248,30 @@ class FirefoxUIUpdates(FirefoxUITests):
         # packages are.
         # We want this:
         # https://ftp.mozilla.org/pub/mozilla.org/firefox/candidates/40.0b1-candidates/build1/mac/en-US/Firefox%2040.0b1.crashreporter-symbols.zip
         # https://ftp.mozilla.org/pub/mozilla.org//firefox/releases/40.0b1/mac/en-US/Firefox%2040.0b1.crashreporter-symbols.zip
         installer_from = rel_info['from']
         version = (re.search('/firefox/releases/(%s.*)\/.*\/.*\/.*' % rel_info['release'], installer_from)).group(1)
 
         temp_from = installer_from.replace(version, '%s-candidates/build%s' % (version, self.config["build_number"]), 1).replace('releases', 'candidates')
+        temp_url = rel_info["ftp_server_from"] + urllib.quote(temp_from.replace('%locale%', 'en-US'))
+        self.info('Installer url under stage/candidates dir %s' % temp_url)
 
-        return rel_info["ftp_server_from"] + urllib.quote(temp_from.replace('%locale%', 'en-US'))
+        return temp_url
 
 
     def _query_symbols_url(self, installer_url):
         for suffix in INSTALLER_SUFFIXES:
             if installer_url.endswith(suffix):
                 symbols_url = installer_url[:-len(suffix)] + '.crashreporter-symbols.zip'
                 continue
 
         if symbols_url:
+            self.info('Candidate symbols_url: %s' % symbols_url)
             if not symbols_url.startswith('http'):
                 return symbols_url
 
             try:
                 # Let's see if the symbols are available
                 urllib2.urlopen(symbols_url)
                 return symbols_url
 
@@ -316,16 +320,19 @@ class FirefoxUIUpdates(FirefoxUITests):
             cmd += ['--update-channel', update_channel]
 
         return_code = self.run_command(cmd, cwd=dirs['abs_work_dir'],
                                        output_timeout=300,
                                        env=env)
 
         # Return more output if we fail
         if return_code != 0:
+            self.info('Internally this is the command fx-ui-updates executed')
+            self.info('%s' % ' '.join(map(str, cmd)))
+
             if os.path.exists(gecko_log):
                 contents = self.read_from_file(gecko_log, verbose=False)
                 self.warning('== Dumping gecko output ==')
                 self.warning(contents)
                 self.warning('== End of gecko output ==')
             else:
                 # We're outputting to stdout with --gecko-log=- so there is not log to
                 # complaing about. Remove the commented line below when changing
@@ -382,47 +389,52 @@ class FirefoxUIUpdates(FirefoxUITests):
                 for locale in rel_info['locales']:
                     self.info("Running %s %s" % (build_id, locale))
 
                     # Safe temp hack to determine symbols URL from en-US build1 in the candidates dir
                     ftp_candidates_installer_url = self._modify_url(rel_info)
                     symbols_url = self._query_symbols_url(installer_url=ftp_candidates_installer_url)
 
                     # Determine from where to download the file
-                    url = '%s/%s' % (
+                    installer_url = '%s/%s' % (
                         rel_info['ftp_server_from'],
                         urllib.quote(rel_info['from'].replace('%locale%', locale))
                     )
                     installer_path = self.download_file(
-                        url=url,
+                        url=installer_url,
                         parent_dir=dirs['abs_work_dir']
                     )
 
                     marionette_port += 1
 
                     retcode = self._run_test(
                         installer_path=installer_path,
                         symbols_url=symbols_url,
                         update_channel=self.channel,
                         marionette_port=marionette_port)
 
                     if retcode != 0:
                         self.warning('FAIL: firefox-ui-update has failed.' )
-                        self.info('You can run the following command on the same machine to reproduce the issue:')
-                        self.info('python scripts/firefox_ui_updates.py --cfg generic_releng_config.py '
-                                  '--firefox-ui-branch %s --update-verify-config %s '
-                                  '--tools-tag %s --installer-url %s '
-                                  '--determine-testing-configuration --run-tests '
-                                  % (self.firefox_ui_branch, self.updates_config_file, self.tools_tag, url))
-                        self.info('If you want to run this on your development machine:')
-                        self.info('python scripts/firefox_ui_updates.py '
-                                  '--firefox-ui-branch %s --update-verify-config %s '
-                                  '--tools-tag %s --installer-url %s '
-                                  '--cfg developer_config.py '
-                                  % (self.firefox_ui_branch, self.updates_config_file, self.tools_tag, url))
+
+                        base_cmd = 'python scripts/firefox_ui_updates.py'
+                        for c in self.config['config_files']:
+                            base_cmd += ' --cfg %s' % c
+
+                        base_cmd += ' --firefox-ui-branch %s --update-verify-config %s --tools-tag %s' % \
+                            (self.firefox_ui_branch, self.update_verify_config, self.tools_tag)
+
+                        base_cmd += ' --installer-url %s' % installer_url
+                        if symbols_url:
+                            base_cmd += ' --symbols-path %s' % symbols_url
+
+                        self.info('You can run the *specific* locale on the same machine with:')
+                        self.info('%s' % base_cmd)
+
+                        self.info('You can run the *specific* locale on *your* machine with:')
+                        self.info('%s --cfg developer_config.py' % base_cmd)
 
                     results[build_id][locale] = retcode
 
             # Determine which locales have failed and set scripts exit code
             exit_status = TBPL_SUCCESS
             for build_id in sorted(results.keys()):
                 failed_locales = []
                 for locale in sorted(results[build_id].keys()):
--- a/toolkit/components/console/content/consoleBindings.xml
+++ b/toolkit/components/console/content/consoleBindings.xml
@@ -533,15 +533,15 @@
     </content>
 
     <handlers>
       <handler event="click" phase="capturing" button="0" preventdefault="true">
         <![CDATA[
           var url = document.getBindingParent(this).mSourceName;
           url = url.substring(url.lastIndexOf(" ") + 1);
           var line = getAttribute("line");
-          gViewSourceUtils.viewSource(url, null, null, line);
+          gViewSourceUtils.viewSource({URL: url, lineNumber: line});
         ]]>
       </handler>
     </handlers>
   </binding>
 
 </bindings>
--- a/toolkit/components/passwordmgr/LoginHelper.jsm
+++ b/toolkit/components/passwordmgr/LoginHelper.jsm
@@ -253,9 +253,30 @@ this.LoginHelper = {
       throw new Error("Can't add a login without a httpRealm or formSubmitURL.");
     }
 
     // Throws if there are bogus values.
     this.checkLoginValues(newLogin);
 
     return newLogin;
   },
+
+  /**
+   * Open the password manager window.
+   *
+   * @param {Window} window
+   *                 the window from where we want to open the dialog
+   *
+   * @param {string} [filterString=""]
+   *                 the filterString parameter to pass to the login manager dialog
+   */
+  openPasswordManager(window, filterString = "") {
+    let win = Services.wm.getMostRecentWindow("Toolkit:PasswordManager");
+    if (win) {
+      win.setFilter(filterString);
+      win.focus();
+    } else {
+      window.openDialog("chrome://passwordmgr/content/passwordManager.xul",
+                        "Toolkit:PasswordManager", "",
+                        {filterString : filterString});
+    }
+  },
 };
--- a/toolkit/components/perfmonitoring/PerformanceStats.jsm
+++ b/toolkit/components/perfmonitoring/PerformanceStats.jsm
@@ -141,16 +141,26 @@ Probe.prototype = {
       throw new TypeError();
     }
     if (b == null) {
       return a;
     }
     return this._impl.subtract(a, b);
   },
 
+  importChildCompartments: function(parent, children) {
+    if (!Array.isArray(children)) {
+      throw new TypeError();
+    }
+    if (!parent || !(parent instanceof PerformanceData)) {
+      throw new TypeError();
+    }
+    return this._impl.importChildCompartments(parent, children);
+  },
+
   /**
    * The name of the probe.
    */
   get name() {
     return this._name;
   }
 };
 
@@ -223,17 +233,18 @@ let Probes = {
         durations: [],
         longestDuration: -1,
       };
       for (let i = 0; i < a.durations.length; ++i) {
         result.durations[i] = a.durations[i] - b.durations[i];
       }
       result.longestDuration = lastNonZero(result.durations);
       return result;
-    }
+    },
+    importChildCompartments: function() { /* nothing to do */ },
   }),
 
   /**
    * A probe measuring CPOW activity.
    *
    * Data provided by this probe uses the following format:
    *
    * @field {number} totalCPOWTime The amount of wallclock time
@@ -253,17 +264,18 @@ let Probes = {
     },
     isEqual: function(a, b) {
       return a.totalCPOWTime == b.totalCPOWTime;
     },
     subtract: function(a, b) {
       return {
         totalCPOWTime: a.totalCPOWTime - b.totalCPOWTime
       };
-    }
+    },
+    importChildCompartments: function() { /* nothing to do */ },
   }),
 
   /**
    * A probe measuring activations, i.e. the number
    * of times code execution has entered a given
    * PerformanceGroup.
    *
    * Note that this probe is always active.
@@ -282,17 +294,18 @@ let Probes = {
     },
     isEqual: function(a, b) {
       return a.ticks == b.ticks;
     },
     subtract: function(a, b) {
       return {
         ticks: a.ticks - b.ticks
       };
-    }
+    },
+    importChildCompartments: function() { /* nothing to do */ },
   }),
 
   "jank-content": new Probe("jank-content", {
     _isActive: false,
     set isActive(x) {
       this._isActive = x;
       if (x) {
         Process.broadcast("acquire", ["jank"]);
@@ -306,17 +319,18 @@ let Probes = {
     extract: function(xpcom) {
       return {};
     },
     isEqual: function(a, b) {
       return true;
     },
     subtract: function(a, b) {
       return null;
-    }
+    },
+    importChildCompartments: function() { /* nothing to do */ },
   }),
 
   "cpow-content": new Probe("cpow-content", {
     _isActive: false,
     set isActive(x) {
       this._isActive = x;
       if (x) {
         Process.broadcast("acquire", ["cpow"]);
@@ -330,36 +344,65 @@ let Probes = {
     extract: function(xpcom) {
       return {};
     },
     isEqual: function(a, b) {
       return true;
     },
     subtract: function(a, b) {
       return null;
-    }
+    },
+    importChildCompartments: function() { /* nothing to do */ },
   }),
 
   "ticks-content": new Probe("ticks-content", {
+    _isActive: false,
     set isActive(x) {
-      // Ignore: This probe is always active.
+      this._isActive = x;
+      if (x) {
+        Process.broadcast("acquire", ["ticks"]);
+      } else {
+        Process.broadcast("release", ["ticks"]);
+      }
     },
     get isActive() {
-      return true;
+      return this._isActive;
     },
     extract: function(xpcom) {
       return {};
     },
     isEqual: function(a, b) {
       return true;
     },
     subtract: function(a, b) {
       return null;
-    }
+    },
+    importChildCompartments: function() { /* nothing to do */ },
   }),
+
+  compartments: new Probe("compartments", {
+    set isActive(x) {
+      performanceStatsService.isMonitoringPerCompartment = x;
+    },
+    get isActive() {
+      return performanceStatsService.isMonitoringPerCompartment;
+    },
+    extract: function(xpcom) {
+      return null;
+    },
+    isEqual: function(a, b) {
+      return true;
+    },
+    subtract: function(a, b) {
+      return true;
+    },
+    importChildCompartments: function(parent, children) {
+      parent.children = children;
+    },
+  })
 };
 
 
 /**
  * A monitor for a set of probes.
  *
  * Keeping probes active when they are unused is often a bad
  * idea for performance reasons. Upon destruction, or whenever
@@ -652,26 +695,42 @@ function PerformanceDiff(current, old = 
  *
  * @param {nsIPerformanceSnapshot} xpcom The data acquired from this process.
  * @param {Array<Object>} childProcesses The data acquired from children processes.
  * @param {Array<Probe>} probes The active probes.
  */
 function Snapshot({xpcom, childProcesses, probes}) {
   this.componentsData = [];
 
-  // Parent process
+  // Current process
   if (xpcom) {
+    let children = new Map();
     let enumeration = xpcom.getComponentsData().enumerate();
     while (enumeration.hasMoreElements()) {
       let xpcom = enumeration.getNext().QueryInterface(Ci.nsIPerformanceStats);
-      this.componentsData.push(new PerformanceData({xpcom, probes}));
+      let stat = new PerformanceData({xpcom, probes});
+      if (!stat.parentId) {
+        this.componentsData.push(stat);
+      } else {
+        let siblings = children.get(stat.parentId);
+        if (!siblings) {
+          children.set(stat.parentId, (siblings = []));
+        }
+        siblings.push(stat);
+      }
+    }
+
+    for (let parent of this.componentsData) {
+      for (let probe of probes) {
+        probe.importChildCompartments(parent, children.get(parent.groupId) || []);
+      }
     }
   }
 
-  // Content processes
+  // Child processes
   if (childProcesses) {
     for (let {componentsData} of childProcesses) {
       // We are only interested in `componentsData` for the time being.
       for (let json of componentsData) {
         this.componentsData.push(new PerformanceData({json, probes}));
       }
     }
   }
--- a/toolkit/components/perfmonitoring/nsIPerformanceStats.idl
+++ b/toolkit/components/perfmonitoring/nsIPerformanceStats.idl
@@ -12,32 +12,42 @@
  * Mechanisms for querying the current process about performance
  * information.
  *
  * JavaScript clients should rather use PerformanceStats.jsm.
  */
 
 /**
  * Snapshot of the performance of a component, e.g. an add-on, a web
- * page, system built-ins, or the entire process itself.
+ * page, system built-ins, a module or the entire process itself.
  *
  * All values are monotonic and are updated only when
  * `nsIPerformanceStatsService.isStopwatchActive` is `true`.
  */
-[scriptable, uuid(47f8d36d-1d67-43cb-befd-d2f4720ac568)]
+[scriptable, uuid(1bc2d016-e9ae-4186-97c6-9478eddda245)]
 interface nsIPerformanceStats: nsISupports {
   /**
    * An identifier unique to the component.
    *
    * This identifier is somewhat human-readable to aid with debugging,
    * but clients should not rely upon the format.
    */
   readonly attribute AString groupId;
 
   /**
+   * If this component is part of a larger component, the larger
+   * component. Otherwise, null.
+   *
+   * As of this writing, there can be at most two levels of components:
+   * - compartments (a single module, iframe, etc.);
+   * - groups (an entire add-on, an entire webpage, etc.).
+   */
+  readonly attribute AString parentId;
+
+  /**
    * The name of the component:
    * - for the process itself, "<process>";
    * - for platform code, "<platform>";
    * - for an add-on, the identifier of the addon (e.g. "myaddon@foo.bar");
    * - for a webpage, the url of the page.
    */
   readonly attribute AString name;
 
@@ -107,29 +117,36 @@ interface nsIPerformanceSnapshot: nsISup
    * This contains the total amount of time spent executing JS code,
    * the total amount of time spent waiting for system calls while
    * executing JS code, the total amount of time performing blocking
    * inter-process calls, etc.
    */
   nsIPerformanceStats getProcessData();
 };
 
-[scriptable, builtinclass, uuid(0469e6af-95c3-4961-a385-4bc009128939)]
+[scriptable, builtinclass, uuid(60973d54-13e2-455c-a3c6-84dea5dfc8b9)]
 interface nsIPerformanceStatsService : nsISupports {
   /**
    * `true` if we should monitor CPOW, `false` otherwise.
    */
   [implicit_jscontext] attribute bool isMonitoringCPOW;
 
   /**
    * `true` if we should monitor jank, `false` otherwise.
    */
   [implicit_jscontext] attribute bool isMonitoringJank;
 
   /**
+   * `true` if all compartments need to be monitored individually,
+   * `false` if only performance groups (i.e. entire add-ons, entire
+   * webpages, etc.) need to be monitored.
+   */
+  [implicit_jscontext] attribute bool isMonitoringPerCompartment;
+
+  /**
    * Capture a snapshot of the performance data.
    */
   [implicit_jscontext] nsIPerformanceSnapshot getSnapshot();
 };
 
 %{C++
 #define NS_TOOLKIT_PERFORMANCESTATSSERVICE_CID {0xfd7435d4, 0x9ec4, 0x4699, \
       {0xad, 0xd4, 0x1b, 0xe8, 0x3d, 0xd6, 0x8e, 0xf3} }
--- a/toolkit/components/perfmonitoring/nsPerformanceStats.cpp
+++ b/toolkit/components/perfmonitoring/nsPerformanceStats.cpp
@@ -25,30 +25,35 @@
 #include "windows.h"
 #else
 #include <unistd.h>
 #endif
 
 class nsPerformanceStats: public nsIPerformanceStats {
 public:
   nsPerformanceStats(const nsAString& aName,
+                     nsIPerformanceStats* aParent,
                      const nsAString& aGroupId,
                      const nsAString& aAddonId,
                      const nsAString& aTitle,
                      const uint64_t aWindowId,
                      const bool aIsSystem,
                      const js::PerformanceData& aPerformanceData)
     : mName(aName)
     , mGroupId(aGroupId)
     , mAddonId(aAddonId)
     , mTitle(aTitle)
     , mWindowId(aWindowId)
     , mIsSystem(aIsSystem)
     , mPerformanceData(aPerformanceData)
   {
+    if (aParent) {
+      mozilla::DebugOnly<nsresult> rv = aParent->GetGroupId(mParentId);
+      MOZ_ASSERT(NS_SUCCEEDED(rv));
+    }
   }
   explicit nsPerformanceStats() {}
 
   NS_DECL_ISUPPORTS
 
   /* readonly attribute AString name; */
   NS_IMETHOD GetName(nsAString& aName) override {
     aName.Assign(mName);
@@ -56,16 +61,22 @@ public:
   };
 
   /* readonly attribute AString groupId; */
   NS_IMETHOD GetGroupId(nsAString& aGroupId) override {
     aGroupId.Assign(mGroupId);
     return NS_OK;
   };
 
+  /* readonly attribute AString parentId; */
+  NS_IMETHOD GetParentId(nsAString& aParentId) override {
+    aParentId.Assign(mParentId);
+    return NS_OK;
+  };
+
   /* readonly attribute AString addonId; */
   NS_IMETHOD GetAddonId(nsAString& aAddonId) override {
     aAddonId.Assign(mAddonId);
     return NS_OK;
   };
 
   /* readonly attribute uint64_t windowId; */
   NS_IMETHOD GetWindowId(uint64_t *aWindowId) override {
@@ -119,16 +130,17 @@ public:
     for (size_t i = 0; i < length; ++i) {
       (*aNumberOfOccurrences)[i] = mPerformanceData.durations[i];
     }
     return NS_OK;
   };
 
 private:
   nsString mName;
+  nsString mParentId;
   nsString mGroupId;
   nsString mAddonId;
   nsString mTitle;
   uint64_t mWindowId;
   bool mIsSystem;
 
   js::PerformanceData mPerformanceData;
 
@@ -154,45 +166,56 @@ private:
    *
    * Precondition: this method assumes that we have entered the JSCompartment for which data `c`
    * has been collected.
    *
    * `cx` may be `nullptr` if we are importing the statistics for the
    * entire process, rather than the statistics for a specific set of
    * compartments.
    */
-  already_AddRefed<nsIPerformanceStats> ImportStats(JSContext* cx, const js::PerformanceData& data, uint64_t uid);
+  already_AddRefed<nsIPerformanceStats> ImportStats(JSContext* cx, const js::PerformanceData& data, uint64_t uid, nsIPerformanceStats* parent);
 
   /**
    * Callbacks for iterating through the `PerformanceStats` of a runtime.
    */
-  bool IterPerformanceStatsCallbackInternal(JSContext* cx, const js::PerformanceData& stats, uint64_t uid);
-  static bool IterPerformanceStatsCallback(JSContext* cx, const js::PerformanceData& stats, uint64_t uid, void* self);
+  bool IterPerformanceStatsCallbackInternal(JSContext* cx,
+                                            const js::PerformanceData& ownStats, const uint64_t ownId,
+                                            const uint64_t* parentId);
+  static bool IterPerformanceStatsCallback(JSContext* cx,
+                                           const js::PerformanceData& ownStats, const uint64_t ownId,
+                                           const uint64_t* parentId,
+                                           void* self);
 
   // If the context represents a window, extract the title and window ID.
   // Otherwise, extract "" and 0.
   static void GetWindowData(JSContext*,
                             nsString& title,
                             uint64_t* windowId);
   void GetGroupId(JSContext*,
                   uint64_t uid,
                   nsString& groupId);
+
+  static void GetName(JSContext*,
+                      JS::Handle<JSObject*> global,
+                      nsString& name);
+
   // If the context presents an add-on, extract the addon ID.
   // Otherwise, extract "".
   static void GetAddonId(JSContext*,
                          JS::Handle<JSObject*> global,
                          nsAString& addonId);
 
   // Determine whether a context is part of the system principals.
   static bool GetIsSystem(JSContext*,
                           JS::Handle<JSObject*> global);
 
 private:
   nsCOMArray<nsIPerformanceStats> mComponentsData;
   nsCOMPtr<nsIPerformanceStats> mProcessData;
+  nsBaseHashtable<nsUint64HashKey, nsCOMPtr<nsIPerformanceStats>, nsCOMPtr<nsIPerformanceStats> > mCachedStats;
   uint64_t mProcessId;
 };
 
 NS_IMPL_ISUPPORTS(nsPerformanceSnapshot, nsIPerformanceSnapshot)
 
 nsPerformanceSnapshot::nsPerformanceSnapshot()
 {
 }
@@ -268,18 +291,54 @@ nsPerformanceSnapshot::GetGroupId(JSCont
 
 /* static */ bool
 nsPerformanceSnapshot::GetIsSystem(JSContext*,
                                    JS::Handle<JSObject*> global)
 {
   return nsContentUtils::IsSystemPrincipal(nsContentUtils::ObjectPrincipal(global));
 }
 
+/* static */
+void
+nsPerformanceSnapshot::GetName(JSContext* cx,
+                               JS::Handle<JSObject*> global,
+                               nsString& name)
+{
+  nsAutoCString cname;
+  do {
+    // Attempt to use the URL as name.
+    nsCOMPtr<nsIPrincipal> principal = nsContentUtils::ObjectPrincipal(global);
+    if (!principal) {
+      break;
+    }
+
+    nsCOMPtr<nsIURI> uri;
+    nsresult rv = principal->GetURI(getter_AddRefs(uri));
+    if (NS_FAILED(rv) || !uri) {
+      break;
+    }
+
+    rv = uri->GetSpec(cname);
+    if (NS_FAILED(rv)) {
+      break;
+    }
+
+    name.Assign(NS_ConvertUTF8toUTF16(cname));
+    return;
+  } while(false);
+  xpc::GetCurrentCompartmentName(cx, cname);
+  name.Assign(NS_ConvertUTF8toUTF16(cname));
+}
+
 already_AddRefed<nsIPerformanceStats>
-nsPerformanceSnapshot::ImportStats(JSContext* cx, const js::PerformanceData& performance, const uint64_t uid) {
+nsPerformanceSnapshot::ImportStats(JSContext* cx, const js::PerformanceData& performance, const uint64_t uid, nsIPerformanceStats* parent) {
+  if (performance.ticks == 0) {
+    // Ignore compartments with no activity.
+    return nullptr;
+  }
   JS::RootedObject global(cx, JS::CurrentGlobalOrNull(cx));
 
   if (!global) {
     // While it is possible for a compartment to have no global
     // (e.g. atoms), this compartment is not very interesting for us.
     return nullptr;
   }
 
@@ -288,53 +347,63 @@ nsPerformanceSnapshot::ImportStats(JSCon
 
   nsString addonId;
   GetAddonId(cx, global, addonId);
 
   nsString title;
   uint64_t windowId;
   GetWindowData(cx, title, &windowId);
 
-  nsAutoString name;
-  nsAutoCString cname;
-  xpc::GetCurrentCompartmentName(cx, cname);
-  name.Assign(NS_ConvertUTF8toUTF16(cname));
+  nsString name;
+  GetName(cx, global, name);
 
   bool isSystem = GetIsSystem(cx, global);
 
   nsCOMPtr<nsIPerformanceStats> result =
-    new nsPerformanceStats(name, groupId, addonId, title, windowId, isSystem, performance);
+    new nsPerformanceStats(name, parent, groupId, addonId, title, windowId, isSystem, performance);
   return result.forget();
 }
 
 /*static*/ bool
-nsPerformanceSnapshot::IterPerformanceStatsCallback(JSContext* cx, const js::PerformanceData& stats, const uint64_t uid, void* self) {
-  return reinterpret_cast<nsPerformanceSnapshot*>(self)->IterPerformanceStatsCallbackInternal(cx, stats, uid);
+nsPerformanceSnapshot::IterPerformanceStatsCallback(JSContext* cx,
+                                                    const js::PerformanceData& stats, const uint64_t id,
+                                                    const uint64_t* parentId,
+                                                    void* self) {
+  return reinterpret_cast<nsPerformanceSnapshot*>(self)->IterPerformanceStatsCallbackInternal(cx, stats, id, parentId);
 }
 
 bool
-nsPerformanceSnapshot::IterPerformanceStatsCallbackInternal(JSContext* cx, const js::PerformanceData& stats, const uint64_t uid) {
-  nsCOMPtr<nsIPerformanceStats> result = ImportStats(cx, stats, uid);
+nsPerformanceSnapshot::IterPerformanceStatsCallbackInternal(JSContext* cx,
+                                                            const js::PerformanceData& stats, const uint64_t id,
+                                                            const uint64_t* parentId) {
+
+  nsCOMPtr<nsIPerformanceStats> parent = parentId ? mCachedStats.Get(*parentId) : nullptr;
+  nsCOMPtr<nsIPerformanceStats> result = ImportStats(cx, stats, id, parent);
   if (result) {
     mComponentsData.AppendElement(result);
+    mCachedStats.Put(id, result);
   }
 
   return true;
 }
 
 nsresult
 nsPerformanceSnapshot::Init(JSContext* cx, uint64_t processId) {
   mProcessId = processId;
   js::PerformanceData processStats;
   if (!js::IterPerformanceStats(cx, nsPerformanceSnapshot::IterPerformanceStatsCallback, &processStats, this)) {
     return NS_ERROR_UNEXPECTED;
   }
 
+  nsAutoString processGroupId;
+  processGroupId.AssignLiteral("process: ");
+  processGroupId.AppendInt(processId);
   mProcessData = new nsPerformanceStats(NS_LITERAL_STRING("<process>"), // name
-                                        NS_LITERAL_STRING("<process:?>"), // group id
+                                        nullptr,                        // parent
+                                        processGroupId,                 // group id
                                         NS_LITERAL_STRING(""),          // add-on id
                                         NS_LITERAL_STRING(""),          // title
                                         0,                              // window id
                                         true,                           // isSystem
                                         processStats);
   return NS_OK;
 }
 
@@ -400,16 +469,30 @@ NS_IMETHODIMP nsPerformanceStatsService:
 NS_IMETHODIMP nsPerformanceStatsService::SetIsMonitoringJank(JSContext* cx, bool aIsStopwatchActive)
 {
   JSRuntime *runtime = JS_GetRuntime(cx);
   if (!js::SetStopwatchIsMonitoringJank(runtime, aIsStopwatchActive)) {
     return NS_ERROR_OUT_OF_MEMORY;
   }
   return NS_OK;
 }
+NS_IMETHODIMP nsPerformanceStatsService::GetIsMonitoringPerCompartment(JSContext* cx, bool *aIsStopwatchActive)
+{
+  JSRuntime *runtime = JS_GetRuntime(cx);
+  *aIsStopwatchActive = js::GetStopwatchIsMonitoringPerCompartment(runtime);
+  return NS_OK;
+}
+NS_IMETHODIMP nsPerformanceStatsService::SetIsMonitoringPerCompartment(JSContext* cx, bool aIsStopwatchActive)
+{
+  JSRuntime *runtime = JS_GetRuntime(cx);
+  if (!js::SetStopwatchIsMonitoringPerCompartment(runtime, aIsStopwatchActive)) {
+    return NS_ERROR_OUT_OF_MEMORY;
+  }
+  return NS_OK;
+}
 
 /* readonly attribute nsIPerformanceSnapshot snapshot; */
 NS_IMETHODIMP nsPerformanceStatsService::GetSnapshot(JSContext* cx, nsIPerformanceSnapshot * *aSnapshot)
 {
   nsRefPtr<nsPerformanceSnapshot> snapshot = new nsPerformanceSnapshot();
   nsresult rv = snapshot->Init(cx, mProcessId);
   if (NS_FAILED(rv)) {
     return rv;
--- a/toolkit/components/perfmonitoring/tests/browser/browser_compartments.js
+++ b/toolkit/components/perfmonitoring/tests/browser/browser_compartments.js
@@ -23,17 +23,17 @@ function frameScript() {
     const { utils: Cu, classes: Cc, interfaces: Ci } = Components;
     Cu.import("resource://gre/modules/PerformanceStats.jsm");
 
     let performanceStatsService =
       Cc["@mozilla.org/toolkit/performance-stats-service;1"].
       getService(Ci.nsIPerformanceStatsService);
 
     // Make sure that the stopwatch is now active.
-    let monitor = PerformanceStats.getMonitor(["jank", "cpow", "ticks"]);
+    let monitor = PerformanceStats.getMonitor(["jank", "cpow", "ticks", "compartments"]);
 
     addMessageListener("compartments-test:getStatistics", () => {
       try {
         monitor.promiseSnapshot().then(snapshot => {
           sendAsyncMessage("compartments-test:getStatistics", snapshot);
         });
       } catch (ex) {
         Cu.reportError("Error in content (getStatistics): " + ex);
@@ -153,27 +153,47 @@ function monotinicity_tester(source, tes
     // Sanity check on components data.
     let map = new Map();
     for (let item of snapshot.componentsData) {
       for (let [probe, k] of [
         ["jank", "totalUserTime"],
         ["jank", "totalSystemTime"],
         ["cpow", "totalCPOWTime"]
       ]) {
-        SilentAssert.leq(item[probe][k], snapshot.processData[probe][k],
-          `Sanity check (${testName}): component has a lower ${k} than process`);
+        // Note that we cannot expect components data to be always smaller
+        // than process data, as `getrusage` & co are not monotonic.
+        SilentAssert.leq(item[probe][k], 2 * snapshot.processData[probe][k],
+          `Sanity check (${testName}): ${k} of component is not impossibly larger than that of process`);
       }
 
       let key = item.groupId;
       if (map.has(key)) {
         let old = map.get(key);
         Assert.ok(false, `Component ${key} has already been seen. Latest: ${item.title||item.addonId||item.name}, previous: ${old.title||old.addonId||old.name}`);
       }
       map.set(key, item);
     }
+    for (let item of snapshot.componentsData) {
+      if (!item.parentId) {
+        continue;
+      }
+      let parent = map.get(item.parentId);
+      SilentAssert.ok(parent, `The parent exists ${item.parentId}`);
+
+      for (let [probe, k] of [
+        ["jank", "totalUserTime"],
+        ["jank", "totalSystemTime"],
+        ["cpow", "totalCPOWTime"]
+      ]) {
+        // Note that we cannot expect components data to be always smaller
+        // than parent data, as `getrusage` & co are not monotonic.
+        SilentAssert.leq(item[probe][k], 2 * parent[probe][k],
+          `Sanity check (${testName}): ${k} of component is not impossibly larger than that of parent`);
+      }
+    }
     for (let [key, item] of map) {
       sanityCheck(previous.componentsMap.get(key), item);
       previous.componentsMap.set(key, item);
     }
   });
   let interval = window.setInterval(frameCheck, 300);
   registerCleanupFunction(() => {
     window.clearInterval(interval);
--- a/toolkit/components/perfmonitoring/tests/xpcshell/test_compartments.js
+++ b/toolkit/components/perfmonitoring/tests/xpcshell/test_compartments.js
@@ -14,37 +14,47 @@ let promiseStatistics = Task.async(funct
   yield Promise.resolve(); // Make sure that we wait until
   // statistics have been updated.
   let service = Cc["@mozilla.org/toolkit/performance-stats-service;1"].
     getService(Ci.nsIPerformanceStatsService);
   let snapshot = service.getSnapshot();
   let componentsData = [];
   let componentsEnum = snapshot.getComponentsData().enumerate();
   while (componentsEnum.hasMoreElements()) {
-    componentsData.push(componentsEnum.getNext().QueryInterface(Ci.nsIPerformanceStats));
+    let data = componentsEnum.getNext().QueryInterface(Ci.nsIPerformanceStats);
+    let normalized = JSON.parse(JSON.stringify(data));
+    componentsData.push(data);
   }
   return {
-    processData: snapshot.getProcessData(),
+    processData: JSON.parse(JSON.stringify(snapshot.getProcessData())),
     componentsData
   };
 });
 
 let promiseSetMonitoring = Task.async(function*(to) {
   let service = Cc["@mozilla.org/toolkit/performance-stats-service;1"].
     getService(Ci.nsIPerformanceStatsService);
   service.isMonitoringJank = to;
   service.isMonitoringCPOW = to;
   yield Promise.resolve();
 });
 
+let promiseSetPerCompartment = Task.async(function*(to) {
+  let service = Cc["@mozilla.org/toolkit/performance-stats-service;1"].
+    getService(Ci.nsIPerformanceStatsService);
+  service.isMonitoringPerCompartment = to;
+  yield Promise.resolve();
+});
+
 function getBuiltinStatistics(name, snapshot) {
   let stats = snapshot.componentsData.find(stats =>
     stats.isSystem && !stats.addonId
   );
   do_print(`Built-in statistics for ${name} were ${stats?"":"not "}found`);
+  do_print(JSON.stringify(snapshot.componentsData, null, "\t"));
   return stats;
 }
 
 function burnCPU(ms) {
   do_print("Burning CPU");
   let counter = 0;
   let ignored = [];
   let start = Date.now();
@@ -52,27 +62,35 @@ function burnCPU(ms) {
     ignored.push(0);
     ignored.shift();
     ++counter;
   }
   do_print("Burning CPU over, after " + counter + " iterations");
 }
 
 function ensureEquals(snap1, snap2, name) {
-  Assert.equal(
-    JSON.stringify(snap1.processData),
-    JSON.stringify(snap2.processData),
-    "Same process data: " + name);
+  for (let k of Object.keys(snap1.processData)) {
+    if (k == "ticks") {
+      // Ticks monitoring cannot be deactivated
+      continue;
+    }
+    Assert.equal(snap1.processData[k], snap2.processData[k], `Same process data value ${k} (${name})`)
+  }
   let stats1 = snap1.componentsData.sort((a, b) => a.name <= b.name);
   let stats2 = snap2.componentsData.sort((a, b) => a.name <= b.name);
-  Assert.equal(
-    JSON.stringify(stats1),
-    JSON.stringify(stats2),
-    "Same components data: " + name
-  );
+  Assert.equal(stats1.length, stats2.length, `Same number of components (${name})`);
+  for (let i = 0; i < stats1.length; ++i) {
+    for (let k of Object.keys(stats1[i])) {
+      if (k == "ticks") {
+        // Ticks monitoring cannot be deactivated
+        continue;
+      }
+      Assert.equal(stats1[i][k], stats1[i][k], `Same component data value ${i} ${k} (${name})`)
+    }
+  }
 }
 
 function hasLowPrecision() {
   let [sysName, sysVersion] = [Services.sysinfo.getPropertyAsAString("name"), Services.sysinfo.getPropertyAsDouble("version")];
   do_print(`Running ${sysName} version ${sysVersion}`);
 
   if (sysName == "Windows_NT" && sysVersion < 6) {
     do_print("Running old Windows, need to deactivate tests due to bad precision.");
@@ -83,16 +101,17 @@ function hasLowPrecision() {
     return true;
   }
   do_print("This platform has good precision.")
   return false;
 }
 
 add_task(function* test_measure() {
   let skipPrecisionTests = hasLowPrecision();
+  yield promiseSetPerCompartment(false);
 
   do_print("Burn CPU without the stopwatch");
   yield promiseSetMonitoring(false);
   let stats0 = yield promiseStatistics("Initial state");
   burnCPU(300);
   let stats1 = yield promiseStatistics("Initial state + burn, without stopwatch");
 
   do_print("Burn CPU with the stopwatch");
@@ -132,9 +151,19 @@ add_task(function* test_measure() {
     do_print("Skipping totalUserTime check under Windows XP, as timer is not always updated by the OS.")
   } else {
     Assert.ok(builtin2.totalUserTime - builtin1.totalUserTime >= 10000, `At least 10ms counted for built-in statistics (${builtin2.totalUserTime - builtin1.totalUserTime})`);
   }
   Assert.equal(builtin2.totalCPOWTime, builtin1.totalCPOWTime, "We haven't used any CPOW time during the first burn for the built-in");
   Assert.equal(builtin2.totalCPOWTime, builtin1.totalCPOWTime, "No CPOW for built-in statistics");
   Assert.equal(builtin4.totalUserTime, builtin3.totalUserTime, "After deactivating the stopwatch, we didn't count any time for the built-in");
   Assert.equal(builtin4.totalCPOWTime, builtin3.totalCPOWTime, "After deactivating the stopwatch, we didn't count any CPOW time for the built-in");
+
+  // Ideally, we should be able to look for test_compartments.js, but
+  // it doesn't have its own compartment.
+  for (let stats of [stats1, stats2, stats3, stats4]) {
+    Assert.ok(!stats.componentsData.find(x => x.name.includes("Task.jsm")), "At this stage, Task.jsm doesn't show up in the components data");
+  }
+  yield promiseSetPerCompartment(true);
+  burnCPU(300);
+  let stats5 = yield promiseStatistics("With per-compartment monitoring");
+  Assert.ok(stats5.componentsData.find(x => x.name.includes("Task.jsm")), "With per-compartment monitoring, test_compartments.js shows up");
 });
--- a/toolkit/components/perfmonitoring/tests/xpcshell/xpcshell.ini
+++ b/toolkit/components/perfmonitoring/tests/xpcshell/xpcshell.ini
@@ -1,5 +1,6 @@
 [DEFAULT]
 head=
 tail=
 
 [test_compartments.js]
+skip-if = toolkit == 'gonk' # Fails on b2g emulator, bug 1147664
--- a/toolkit/modules/AppConstants.jsm
+++ b/toolkit/modules/AppConstants.jsm
@@ -75,27 +75,16 @@ this.AppConstants = Object.freeze({
 
   MOZ_SANDBOX:
 #ifdef MOZ_SANDBOX
   true,
 #else
   false,
 #endif
 
-  MOZ_SHARK:
-#ifdef XP_MACOSX
-#ifdef MOZ_SHARK
-  true,
-#else
-  false,
-#endif
-#else
-  false,
-#endif
-
   MOZ_TELEMETRY_REPORTING:
 #ifdef MOZ_TELEMETRY_REPORTING
   true,
 #else
   false,
 #endif
 
   MOZ_UPDATER:
--- a/toolkit/modules/Promise-backend.js
+++ b/toolkit/modules/Promise-backend.js
@@ -38,16 +38,20 @@
 // If the file is loaded as a CommonJS module on a worker thread, the instance
 // of Cu obtained from the chrome module will be null. The reason for this is
 // that Components is not defined in worker threads, so no instance of Cu can
 // be obtained.
 
 let Cu = this.require ? require("chrome").Cu : Components.utils;
 let Cc = this.require ? require("chrome").Cc : Components.classes;
 let Ci = this.require ? require("chrome").Ci : Components.interfaces;
+// If we can access Components, then we use it to capture an async
+// parent stack trace; see scheduleWalkerLoop.  However, as it might
+// not be available (see above), users of this must check it first.
+let Components_ = this.require ? require("chrome").components : Components;
 
 // If Cu is defined, use it to lazily define the FinalizationWitnessService.
 if (Cu) {
   Cu.import("resource://gre/modules/Services.jsm");
   Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 
   XPCOMUtils.defineLazyServiceGetter(this, "FinalizationWitnessService",
                                      "@mozilla.org/toolkit/finalizationwitness;1",
@@ -732,17 +736,25 @@ this.PromiseWalker = {
     // server should be avoided when it is running on a worker thread. Because
     // it is still necessary to be able to schedule runnables on the event
     // queue, the worker loader defines the function setImmediate as a
     // per-module global for this purpose.
     //
     // If Cu is defined, this file is loaded on the main thread. Otherwise, it
     // is loaded on the worker thread.
     if (Cu) {
-      DOMPromise.resolve().then(() => this.walkerLoop());
+      let stack = Components_ ? Components_.stack : null;
+      if (stack) {
+        DOMPromise.resolve().then(() => {
+          Cu.callFunctionWithAsyncStack(this.walkerLoop.bind(this), stack,
+                                        "Promise")
+        });
+      } else {
+        DOMPromise.resolve().then(() => this.walkerLoop());
+      }
     } else {
       setImmediate(this.walkerLoop);
     }
   },
 
   /**
    * Schedules the resolution or rejection handlers registered on the provided
    * promise for processing.
--- a/toolkit/modules/SelectContentHelper.jsm
+++ b/toolkit/modules/SelectContentHelper.jsm
@@ -61,17 +61,17 @@ this.SelectContentHelper.prototype = {
 
   receiveMessage: function(message) {
     switch (message.name) {
       case "Forms:SelectDropDownItem":
         this.element.selectedIndex = message.data.value;
         break;
 
       case "Forms:DismissedDropDown":
-        if (this.initialSelection != this.element.item[this.element.selectedIndex]) {
+        if (this.initialSelection != this.element.item(this.element.selectedIndex)) {
           let event = this.element.ownerDocument.createEvent("Events");
           event.initEvent("change", true, true);
           this.element.dispatchEvent(event);
         }
 
         this.uninit();
         break;
     }
--- a/toolkit/mozapps/update/nsUpdateService.js
+++ b/toolkit/mozapps/update/nsUpdateService.js
@@ -240,16 +240,19 @@ XPCOMUtils.defineLazyGetter(this, "gABI"
     // Mac universal build should report a different ABI than either macppc
     // or mactel.
     let macutils = Cc["@mozilla.org/xpcom/mac-utils;1"].
                    getService(Ci.nsIMacUtils);
 
     if (macutils.isUniversalBinary) {
       abi += "-u-" + macutils.architecturesInBinary;
     }
+  } else if (AppConstants.platform == "win") {
+    // Windows build should report the CPU architecture that it's running on.
+    abi += "-" + gWinCPUArch;
   }
   return abi;
 });
 
 XPCOMUtils.defineLazyGetter(this, "gOSVersion", function aus_gOSVersion() {
   let osVersion;
   try {
     osVersion = Services.sysinfo.getProperty("name") + " " +
@@ -305,17 +308,17 @@ XPCOMUtils.defineLazyGetter(this, "gOSVe
       let kernel32 = false;
       try {
         kernel32 = ctypes.open("Kernel32");
       } catch (e) {
         LOG("gOSVersion - Unable to open kernel32! " + e);
         osVersion += ".unknown (unknown)";
       }
 
-      if(kernel32) {
+      if (kernel32) {
         try {
           // Get Service pack info
           try {
             let GetVersionEx = kernel32.declare("GetVersionExW",
                                                 ctypes.default_abi,
                                                 BOOL,
                                                 OSVERSIONINFOEXW.ptr);
             let winVer = OSVERSIONINFOEXW();
@@ -327,62 +330,101 @@ XPCOMUtils.defineLazyGetter(this, "gOSVe
             } else {
               LOG("gOSVersion - Unknown failure in GetVersionEX (returned 0)");
               osVersion += ".unknown";
             }
           } catch (e) {
             LOG("gOSVersion - error getting service pack information. Exception: " + e);
             osVersion += ".unknown";
           }
-
-          // Get processor architecture
-          let arch = "unknown";
-          try {
-            let GetNativeSystemInfo = kernel32.declare("GetNativeSystemInfo",
-                                                       ctypes.default_abi,
-                                                       ctypes.void_t,
-                                                       SYSTEM_INFO.ptr);
-            let winSystemInfo = SYSTEM_INFO();
-            // Default to unknown
-            winSystemInfo.wProcessorArchitecture = 0xffff;
-
-            GetNativeSystemInfo(winSystemInfo.address());
-            switch(winSystemInfo.wProcessorArchitecture) {
-              case 9:
-                arch = "x64";
-                break;
-              case 6:
-                arch = "IA64";
-                break;
-              case 0:
-                arch = "x86";
-                break;
-            }
-          } catch (e) {
-            LOG("gOSVersion - error getting processor architecture.  Exception: " + e);
-          } finally {
-            osVersion += " (" + arch + ")";
-          }
         } finally {
           kernel32.close();
         }
+
+        // Add processor architecture
+        osVersion += " (" + gWinCPUArch + ")";
       }
     }
 
     try {
       osVersion += " (" + Services.sysinfo.getProperty("secondaryLibrary") + ")";
     }
     catch (e) {
       // Not all platforms have a secondary widget library, so an error is nothing to worry about.
     }
     osVersion = encodeURIComponent(osVersion);
   }
   return osVersion;
 });
 
+/* Windows only getter that returns the processor architecture. */
+XPCOMUtils.defineLazyGetter(this, "gWinCPUArch", function aus_gWinCPUArch() {
+  // Get processor architecture
+  let arch = "unknown";
+
+  const WORD = ctypes.uint16_t;
+  const DWORD = ctypes.uint32_t;
+
+  // This structure is described at:
+  // http://msdn.microsoft.com/en-us/library/ms724958%28v=vs.85%29.aspx
+  const SYSTEM_INFO = new ctypes.StructType('SYSTEM_INFO',
+      [
+      {wProcessorArchitecture: WORD},
+      {wReserved: WORD},
+      {dwPageSize: DWORD},
+      {lpMinimumApplicationAddress: ctypes.voidptr_t},
+      {lpMaximumApplicationAddress: ctypes.voidptr_t},
+      {dwActiveProcessorMask: DWORD.ptr},
+      {dwNumberOfProcessors: DWORD},
+      {dwProcessorType: DWORD},
+      {dwAllocationGranularity: DWORD},
+      {wProcessorLevel: WORD},
+      {wProcessorRevision: WORD}
+      ]);
+
+  let kernel32 = false;
+  try {
+    kernel32 = ctypes.open("Kernel32");
+  } catch (e) {
+    LOG("gWinCPUArch - Unable to open kernel32! Exception: " + e);
+  }
+
+  if (kernel32) {
+    try {
+      let GetNativeSystemInfo = kernel32.declare("GetNativeSystemInfo",
+                                                 ctypes.default_abi,
+                                                 ctypes.void_t,
+                                                 SYSTEM_INFO.ptr);
+      let winSystemInfo = SYSTEM_INFO();
+      // Default to unknown
+      winSystemInfo.wProcessorArchitecture = 0xffff;
+
+      GetNativeSystemInfo(winSystemInfo.address());
+      switch (winSystemInfo.wProcessorArchitecture) {
+        case 9:
+          arch = "x64";
+          break;
+        case 6:
+          arch = "IA64";
+          break;
+        case 0:
+          arch = "x86";
+          break;
+      }
+    } catch (e) {
+      LOG("gWinCPUArch - error getting processor architecture. " +
+          "Exception: " + e);
+    } finally {
+      kernel32.close();
+    }
+  }
+
+  return arch;
+});
+
 /**
  * Tests to make sure that we can write to a given directory.
  *
  * @param updateTestFile a test file in the directory that needs to be tested.
  * @param createDirectory whether a test directory should be created.
  * @throws if we don't have right access to the directory.
  */
 function testWriteAccess(updateTestFile, createDirectory) {
--- a/toolkit/mozapps/update/tests/unit_aus_update/urlConstruction.js
+++ b/toolkit/mozapps/update/tests/unit_aus_update/urlConstruction.js
@@ -107,16 +107,19 @@ function check_test_pt4() {
     // or mactel. This is necessary since nsUpdateService.js will set the ABI to
     // Universal-gcc3 for Mac universal builds.
     let macutils = Cc["@mozilla.org/xpcom/mac-utils;1"].
                    getService(Ci.nsIMacUtils);
 
     if (macutils.isUniversalBinary) {
       abi += "-u-" + macutils.architecturesInBinary;
     }
+  } else if (IS_WIN) {
+    // Windows build should report the CPU architecture that it's running on.
+    abi += "-" + getProcArchitecture();
   }
 
   Assert.equal(getResult(gRequestURL), gAppInfo.OS + "_" + abi,
                "the url param for %BUILD_TARGET%" + MSG_SHOULD_EQUAL);
   run_test_pt5();
 }
 
 // url constructed with %LOCALE%
--- a/widget/cocoa/nsPrintOptionsX.mm
+++ b/widget/cocoa/nsPrintOptionsX.mm
@@ -141,21 +141,20 @@ nsPrintOptionsX::DeserializeToPrintSetti
     [NSMutableDictionary dictionaryWithDictionary:sharedDict];
   if (NS_WARN_IF(!newPrintInfoDict)) {
     return NS_ERROR_OUT_OF_MEMORY;
   }
 
   NSString* printerName = nsCocoaUtils::ToNSString(data.printerName());
   if (printerName) {
     NSPrinter* printer = [NSPrinter printerWithName: printerName];
-    if (NS_WARN_IF(!printer)) {
-      return NS_ERROR_FAILURE;
+    if (printer) {
+      [newPrintInfoDict setObject: printer forKey: NSPrintPrinter];
+      [newPrintInfoDict setObject: printerName forKey: NSPrintPrinterName];
     }
-    [newPrintInfoDict setObject: printer forKey: NSPrintPrinter];
-    [newPrintInfoDict setObject: printerName forKey: NSPrintPrinterName];
   }
 
   [newPrintInfoDict setObject: [NSNumber numberWithInt: data.numCopies()]
                     forKey: NSPrintCopies];
   [newPrintInfoDict setObject: [NSNumber numberWithBool: data.printAllPages()]
                     forKey: NSPrintAllPages];
   [newPrintInfoDict setObject: [NSNumber numberWithInt: data.startPageRange()]
                     forKey: NSPrintFirstPage];
--- a/widget/gonk/nsWindow.cpp
+++ b/widget/gonk/nsWindow.cpp
@@ -768,16 +768,22 @@ nsWindow::DestroyCompositor()
 {
     if (mCompositorParent && mScreen->IsPrimaryScreen()) {
         // Unset CompositorParent
         mComposer2D->SetCompositorParent(nullptr);
     }
     nsBaseWidget::DestroyCompositor();
 }
 
+CompositorParent*
+nsWindow::NewCompositorParent(int aSurfaceWidth, int aSurfaceHeight)
+{
+    return new CompositorParent(this, true, aSurfaceWidth, aSurfaceHeight);
+}
+
 void
 nsWindow::BringToTop()
 {
     const nsTArray<nsWindow*>& windows = mScreen->GetTopWindows();
     if (!windows.IsEmpty()) {
         if (nsIWidgetListener* listener = windows[0]->GetWidgetListener()) {
             listener->WindowDeactivated();
         }
--- a/widget/gonk/nsWindow.h
+++ b/widget/gonk/nsWindow.h
@@ -110,16 +110,18 @@ public:
     virtual double GetDefaultScaleInternal();
     virtual mozilla::layers::LayerManager*
         GetLayerManager(PLayerTransactionChild* aShadowManager = nullptr,
                         LayersBackend aBackendHint = mozilla::layers::LayersBackend::LAYERS_NONE,
                         LayerManagerPersistence aPersistence = LAYER_MANAGER_CURRENT,
                         bool* aAllowRetaining = nullptr);
     virtual void DestroyCompositor();
 
+    virtual CompositorParent* NewCompositorParent(int aSurfaceWidth, int aSurfaceHeight);
+
     NS_IMETHOD_(void) SetInputContext(const InputContext& aContext,
                                       const InputContextAction& aAction);
     NS_IMETHOD_(InputContext) GetInputContext();
 
     virtual uint32_t GetGLFrameBufferFormat() override;
 
     virtual nsIntRect GetNaturalBounds() override;
     virtual bool NeedsPaint();
new file mode 100644
--- /dev/null
+++ b/xpcom/ds/Tokenizer.cpp
@@ -0,0 +1,389 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "Tokenizer.h"
+
+#include "nsUnicharUtils.h"
+#include "mozilla/CheckedInt.h"
+
+namespace mozilla {
+
+static const char sWhitespaces[] = " \t";
+
+Tokenizer::Tokenizer(const nsACString& aSource)
+  : mPastEof(false)
+  , mHasFailed(false)
+  , mWhitespaces(sWhitespaces)
+{
+  aSource.BeginReading(mCursor);
+  mRecord = mRollback = mCursor;
+  aSource.EndReading(mEnd);
+}
+
+bool
+Tokenizer::Next(Token& aToken)
+{
+  if (!HasInput()) {
+    mHasFailed = true;
+    return false;
+  }
+
+  mRollback = mCursor;
+  mCursor = Parse(aToken);
+  mPastEof = aToken.Type() == TOKEN_EOF;
+  mHasFailed = false;
+  return true;
+}
+
+bool
+Tokenizer::Check(const TokenType aTokenType, Token& aResult)
+{
+  if (!HasInput()) {
+    mHasFailed = true;
+    return false;
+  }
+
+  nsACString::const_char_iterator next = Parse(aResult);
+  if (aTokenType != aResult.Type()) {
+    mHasFailed = true;
+    return false;
+  }
+
+  mRollback = mCursor;
+  mCursor = next;
+  mPastEof = aResult.Type() == TOKEN_EOF;
+  return true;
+}
+
+bool
+Tokenizer::Check(const Token& aToken)
+{
+  if (!HasInput()) {
+    mHasFailed = true;
+    return false;
+  }
+
+  Token parsed;
+  nsACString::const_char_iterator next = Parse(parsed);
+  if (!aToken.Equals(parsed)) {
+    mHasFailed = true;
+    return false;
+  }
+
+  mRollback = mCursor;
+  mCursor = next;
+  mPastEof = parsed.Type() == TOKEN_EOF;
+  return true;
+}
+
+bool
+Tokenizer::HasFailed() const
+{
+  return mHasFailed;
+}
+
+void
+Tokenizer::SkipWhites()
+{
+  if (!CheckWhite()) {
+    return;
+  }
+
+  nsACString::const_char_iterator rollback = mRollback;
+  while (CheckWhite()) {
+  }
+
+  mHasFailed = false;
+  mRollback = rollback;
+}
+
+bool
+Tokenizer::CheckChar(bool (*aClassifier)(const char aChar))
+{
+  if (!aClassifier) {
+    MOZ_ASSERT(false);
+    return false;
+  }
+
+  if (!HasInput() || mCursor == mEnd) {
+    mHasFailed = true;
+    return false;
+  }
+
+  if (!aClassifier(*mCursor)) {
+    mHasFailed = true;
+    return false;
+  }
+
+  mRollback = mCursor;
+  ++mCursor;
+  mHasFailed = false;
+  return true;
+}
+
+void
+Tokenizer::Rollback()
+{
+  MOZ_ASSERT(mCursor > mRollback || mPastEof,
+             "Tokenizer::Rollback() cannot use twice or before any parsing");
+
+  mPastEof = false;
+  mHasFailed = false;
+  mCursor = mRollback;
+}
+
+void
+Tokenizer::Record(ClaimInclusion aInclude)
+{
+  mRecord = aInclude == INCLUDE_LAST
+    ? mRollback
+    : mCursor;
+}
+
+void
+Tokenizer::Claim(nsACString& aResult, ClaimInclusion aInclusion)
+{
+  nsACString::const_char_iterator close = aInclusion == EXCLUDE_LAST
+    ? mRollback
+    : mCursor;
+  aResult.Assign(Substring(mRecord, close));
+}
+
+// protected
+
+bool
+Tokenizer::HasInput() const
+{
+  return !mPastEof;
+}
+
+nsACString::const_char_iterator
+Tokenizer::Parse(Token& aToken) const
+{
+  if (mCursor == mEnd) {
+    aToken = Token::EndOfFile();
+    return mEnd;
+  }
+
+  nsACString::const_char_iterator next = mCursor;
+
+  enum State {
+    PARSE_INTEGER,
+    PARSE_WORD,
+    PARSE_CRLF,
+    PARSE_LF,
+    PARSE_WS,
+    PARSE_CHAR,
+  } state;
+
+  if (IsWordFirst(*next)) {
+    state = PARSE_WORD;
+  } else if (IsNumber(*next)) {
+    state = PARSE_INTEGER;
+  } else if (*next == '\r') {
+    state = PARSE_CRLF;
+  } else if (*next == '\n') {
+    state = PARSE_LF;
+  } else if (strchr(mWhitespaces, *next)) { // not UTF-8 friendly?
+    state = PARSE_WS;
+  } else {
+    state = PARSE_CHAR;
+  }
+
+  mozilla::CheckedInt64 resultingNumber = 0;
+
+  while (next < mEnd) {
+    switch (state) {
+    case PARSE_INTEGER:
+      // Keep it simple for now
+      resultingNumber *= 10;
+      resultingNumber += static_cast<int64_t>(*next - '0');
+
+      ++next;
+      if (IsEnd(next) || !IsNumber(*next)) {
+        if (!resultingNumber.isValid()) {
+          aToken = Token::Error();
+        } else {
+          aToken = Token::Number(resultingNumber.value());
+        }
+        return next;
+      }
+      break;
+
+    case PARSE_WORD:
+      ++next;
+      if (IsEnd(next) || !IsWord(*next)) {
+        aToken = Token::Word(Substring(mCursor, next));
+        return next;
+      }
+      break;
+
+    case PARSE_CRLF:
+      ++next;
+      if (!IsEnd(next) && *next == '\n') { // LF is optional
+        ++next;
+      }
+      aToken = Token::NewLine();
+      return next;
+
+    case PARSE_LF:
+      ++next;
+      aToken = Token::NewLine();
+      return next;
+
+    case PARSE_WS:
+      ++next;
+      aToken = Token::Whitespace();
+      return next;
+
+    case PARSE_CHAR:
+      ++next;
+      aToken = Token::Char(*mCursor);
+      return next;
+    } // switch (state)
+  } // while (next < end)
+
+  return next;
+}
+
+bool
+Tokenizer::IsEnd(const nsACString::const_char_iterator& caret) const
+{
+  return caret == mEnd;
+}
+
+bool
+Tokenizer::IsWordFirst(const char aInput) const
+{
+  // TODO: make this fully work with unicode
+  return (ToLowerCase(static_cast<uint32_t>(aInput)) !=
+          ToUpperCase(static_cast<uint32_t>(aInput))) ||
+          '_' == aInput;
+}
+
+bool
+Tokenizer::IsWord(const char aInput) const
+{
+  return IsWordFirst(aInput) || IsNumber(aInput);
+}
+
+bool
+Tokenizer::IsNumber(const char aInput) const
+{
+  // TODO: are there unicode numbers?
+  return aInput >= '0' && aInput <= '9';
+}
+
+// Tokenizer::Token
+
+// static
+Tokenizer::Token
+Tokenizer::Token::Word(const nsACString& aValue)
+{
+  Token t;
+  t.mType = TOKEN_WORD;
+  t.mWord = aValue;
+  return t;
+}
+
+// static
+Tokenizer::Token
+Tokenizer::Token::Char(const char aValue)
+{
+  Token t;
+  t.mType = TOKEN_CHAR;
+  t.mChar = aValue;
+  return t;
+}
+
+// static
+Tokenizer::Token
+Tokenizer::Token::Number(const int64_t aValue)
+{
+  Token t;
+  t.mType = TOKEN_INTEGER;
+  t.mInteger = aValue;
+  return t;
+}
+
+// static
+Tokenizer::Token
+Tokenizer::Token::Whitespace()
+{
+  Token t;
+  t.mType = TOKEN_WS;
+  t.mChar = '\0';
+  return t;
+}
+
+// static
+Tokenizer::Token
+Tokenizer::Token::NewLine()
+{
+  Token t;
+  t.mType = TOKEN_EOL;
+  return t;
+}
+
+// static
+Tokenizer::Token
+Tokenizer::Token::EndOfFile()
+{
+  Token t;
+  t.mType = TOKEN_EOF;
+  return t;
+}
+
+// static
+Tokenizer::Token
+Tokenizer::Token::Error()
+{
+  Token t;
+  t.mType = TOKEN_ERROR;
+  return t;
+}
+
+bool
+Tokenizer::Token::Equals(const Token& aOther) const
+{
+  if (mType != aOther.mType) {
+    return false;
+  }
+
+  switch (mType) {
+  case TOKEN_INTEGER:
+    return AsInteger() == aOther.AsInteger();
+  case TOKEN_WORD:
+    return AsString() == aOther.AsString();
+  case TOKEN_CHAR:
+    return AsChar() == aOther.AsChar();
+  default:
+    return true;
+  }
+}
+
+char
+Tokenizer::Token::AsChar() const
+{
+  MOZ_ASSERT(mType == TOKEN_CHAR || mType == TOKEN_WS);
+  return mChar;
+}
+
+nsCString
+Tokenizer::Token::AsString() const
+{
+  MOZ_ASSERT(mType == TOKEN_WORD);
+  return mWord;
+}
+
+int64_t
+Tokenizer::Token::AsInteger() const
+{
+  MOZ_ASSERT(mType == TOKEN_INTEGER);
+  return mInteger;
+}
+
+} // mozilla
new file mode 100644
--- /dev/null
+++ b/xpcom/ds/Tokenizer.h
@@ -0,0 +1,225 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef Tokenizer_h__
+#define Tokenizer_h__
+
+#include "nsString.h"
+
+namespace mozilla {
+
+/**
+ * This is a simple implementation of a lexical analyzer or maybe better
+ * called a tokenizer.  It doesn't allow any user dictionaries or
+ * user define token types.
+ *
+ * It is limited only to ASCII input for now. UTF-8 or any other input
+ * encoding must yet be implemented.
+ */
+class Tokenizer {
+public:
+  /**
+   * The analyzer works with elements in the input cut to a sequence of token
+   * where each token has an elementary type
+   */
+  enum TokenType {
+    TOKEN_UNKNOWN,
+    TOKEN_ERROR,
+    TOKEN_INTEGER,
+    TOKEN_WORD,
+    TOKEN_CHAR,
+    TOKEN_WS,
+    TOKEN_EOL,
+    TOKEN_EOF
+  };
+
+  /**
+   * Class holding the type and the value of a token.  It can be manually created
+   * to allow checks against it via methods of Tokenizer or are results of some of
+   * the Tokenizer's methods.
+   */
+  class Token {
+    TokenType mType;
+    nsCString mWord;
+    char mChar;
+    int64_t mInteger;
+
+  public:
+    Token() : mType(TOKEN_UNKNOWN), mChar(0), mInteger(0) {}
+
+    // Static constructors of tokens by type and value
+    static Token Word(const nsACString& aWord);
+    static Token Char(const char aChar);
+    static Token Number(const int64_t aNumber);
+    static Token Whitespace();
+    static Token NewLine();
+    static Token EndOfFile();
+    static Token Error();
+
+    // Compares the two tokens, type must be identical and value
+    // of one of the tokens must be 'any' or equal.
+    bool Equals(const Token& aOther) const;
+
+    TokenType Type() const { return mType; }
+    char AsChar() const;
+    nsCString AsString() const;
+    int64_t AsInteger() const;
+  };
+
+public:
+  explicit Tokenizer(const nsACString& aSource);
+
+  /**
+   * Some methods are collecting the input as it is being parsed to obtain
+   * a substring between particular syntax bounderies defined by any recursive
+   * descent parser or simple parser the Tokenizer is used to read the input for.
+   */
+  enum ClaimInclusion {
+    /**
+     * Include resulting (or passed) token of the last lexical analyzer operation in the result.
+     */
+    INCLUDE_LAST,
+    /**
+     * Do not include it.
+     */
+    EXCLUDE_LAST
+  };
+
+  /**
+   * When there is still anything to read from the input, tokenize it, store the token type
+   * and value to aToken result and shift the cursor past this just parsed token.  Each call
+   * to Next() reads another token from the input and shifts the cursor.
+   * Returns false if we have passed the end of the input.
+   */
+  MOZ_WARN_UNUSED_RESULT
+  bool Next(Token& aToken);
+
+  /**
+   * Parse the token on the input read cursor position, check its type is equal to aTokenType
+   * and if so, put it into aResult, shift the cursor and return true.  Otherwise, leave
+   * the input read cursor position intact and return false.
+   */
+  MOZ_WARN_UNUSED_RESULT
+  bool Check(const TokenType aTokenType, Token& aResult);
+  /**
+   * Same as above method, just compares both token type and token value passed in aToken.
+   * When both the type and the value equals, shift the cursor and return true.  Otherwise
+   * return false.
+   */
+  MOZ_WARN_UNUSED_RESULT
+  bool Check(const Token& aToken);
+
+  /**
+   * Return false iff the last Check*() call has returned false or when we've read past
+   * the end of the input string.
+   */
+  MOZ_WARN_UNUSED_RESULT
+  bool HasFailed() const;
+
+  /**
+   * Skips any occurence of whitespaces specified in mWhitespaces member.
+   */
+  void SkipWhites();
+
+  // These are mostly shortcuts for the Check() methods above.
+
+  /**
+   * Check whitespace character is present.
+   */
+  MOZ_WARN_UNUSED_RESULT
+  bool CheckWhite() { return Check(Token::Whitespace()); }
+  /**
+   * Check there is a single character on the read cursor position.  If so, shift the read
+   * cursor position and return true.  Otherwise false.
+   */
+  MOZ_WARN_UNUSED_RESULT
+  bool CheckChar(const char aChar) { return Check(Token::Char(aChar)); }
+  /**
+   * This is a customizable version of CheckChar.  aClassifier is a function called with
+   * value of the character on the current input read position.  If this user function
+   * returns true, read cursor is shifted and true returned.  Otherwise false.
+   * The user classifiction function is not called when we are at or past the end and
+   * false is immediately returned.
+   */
+  MOZ_WARN_UNUSED_RESULT
+  bool CheckChar(bool (*aClassifier)(const char aChar));
+  /**
+   * Shortcut for direct word check.
+   */
+  template <size_t N>
+  MOZ_WARN_UNUSED_RESULT
+  bool CheckWord(const char (&aWord)[N]) { return Check(Token::Word(nsLiteralCString(aWord))); }
+  /**
+   * Checks \r, \n or \r\n.
+   */
+  MOZ_WARN_UNUSED_RESULT
+  bool CheckEOL() { return Check(Token::NewLine()); }
+  /**
+   * Checks we are at the end of the input string reading.  If so, shift past the end
+   * and returns true.  Otherwise does nothing and returns false.
+   */
+  MOZ_WARN_UNUSED_RESULT
+  bool CheckEOF() { return Check(Token::EndOfFile()); }
+
+  /**
+   * Returns the read cursor position back as it was before the last call of any parsing
+   * method of Tokenizer (Next, Check*, Skip*) so that the last operation can be repeated.
+   * Rollback cannot be used multiple times, it only reverts the last successfull parse
+   * operation.  It also cannot be used before any parsing operation has been called
+   * on the Tokenizer.
+   */
+  void Rollback();
+