Merge m-c to b2g-inbound. a=merge
authorRyan VanderMeulen <ryanvm@gmail.com>
Fri, 18 Apr 2014 10:03:35 -0400
changeset 179704 e256e72a2174b01f1f430ae5b44878643b0a25f7
parent 179703 ad6cc6b98a9dfc37e2fcfd3f3ef263c8ff24253c (current diff)
parent 179639 45ba19361b97d3154fa8dc26b707f48fab65fa99 (diff)
child 179705 5b3389d6934a459fa9d95cb749f10b6ace77353c
push id272
push userpvanderbeken@mozilla.com
push dateMon, 05 May 2014 16:31:18 +0000
reviewersmerge
milestone31.0a1
Merge m-c to b2g-inbound. a=merge
browser/devtools/inspector/test/browser_inspector_bug_952294_tooltips_dimensions.js
--- a/browser/components/preferences/sync.xul
+++ b/browser/components/preferences/sync.xul
@@ -203,54 +203,51 @@
           <groupbox id="fxaGroup">
             <caption label="&syncBrand.fxAccount.label;"/>
 
             <deck id="fxaLoginStatus">
 
               <!-- logged in and verified and all is good -->
               <hbox>
                 <label id="fxaEmailAddress1"/>
-                <vbox>
+                <vbox flex="1">
                   <label class="text-link"
                          onclick="gSyncPane.manageFirefoxAccount();"
                          value="&manage.label;"/>
                 </vbox>
-                <spacer flex="1"/>
                 <vbox>
                   <button id="fxaUnlinkButton"
                           oncommand="gSyncPane.unlinkFirefoxAccount(true);"
                           label="&disconnect.label;"/>
                 </vbox>
               </hbox>
 
               <!-- logged in to an unverified account -->
               <hbox flex="1">
-                <description>
+                <description flex="1">
                   &signedInUnverified.beforename.label;
                   <span id="fxaEmailAddress2"></span>
                   &signedInUnverified.aftername.label;
                 </description>
-                <spacer flex="1"/>
                 <vbox align="end">
                   <button oncommand="gSyncPane.verifyFirefoxAccount();"
                           label="&verify.label;"/>
                   <label class="text-link"
                          onclick="/* no warning as account can't have previously synced */ gSyncPane.unlinkFirefoxAccount(false);"
                          value="&forget.label;"/>
                 </vbox>
               </hbox>
 
               <!-- logged in locally but server rejected credentials -->
               <hbox flex="1">
-                <description>
+                <description flex="1">
                   &signedInLoginFailure.beforename.label;
                   <span id="fxaEmailAddress3"></span>
                   &signedInLoginFailure.aftername.label;
                 </description>
-                <spacer flex="1"/>
                 <vbox align="end">
                   <button oncommand="gSyncPane.reSignIn();"
                          label="&signIn.label;"/>
                   <label class="text-link"
                          onclick="gSyncPane.unlinkFirefoxAccount(true);"
                          value="&forget.label;"/>
                 </vbox>
               </hbox>
--- a/browser/devtools/inspector/test/browser.ini
+++ b/browser/devtools/inspector/test/browser.ini
@@ -42,13 +42,12 @@ support-files =
 [browser_inspector_pseudoClass_menu.js]
 [browser_inspector_pseudoclass_lock.js]
 [browser_inspector_reload.js]
 [browser_inspector_scrolling.js]
 [browser_inspector_select_last_selected.js]
 [browser_inspector_sidebarstate.js]
 [browser_inspector_bug_848731_reset_selection_on_delete.js]
 [browser_inspector_bug_922125_destroy_on_navigate.js]
-[browser_inspector_bug_952294_tooltips_dimensions.js]
 [browser_inspector_bug_958456_highlight_comments.js]
 [browser_inspector_bug_958169_switch_to_inspector_on_pick.js]
 [browser_inspector_bug_961771_picker_stops_on_tool_select.js]
 [browser_inspector_bug_962478_picker_stops_on_destroy.js]
--- a/browser/devtools/responsivedesign/test/browser_responsivecomputedview.js
+++ b/browser/devtools/responsivedesign/test/browser_responsivecomputedview.js
@@ -10,17 +10,17 @@ function test() {
   waitForExplicitFinish();
 
   gBrowser.selectedTab = gBrowser.addTab();
   gBrowser.selectedBrowser.addEventListener("load", function onload() {
     gBrowser.selectedBrowser.removeEventListener("load", onload, true);
     waitForFocus(startTest, content);
   }, true);
 
-  content.location = "data:text/html,<html><style>" +
+  content.location = "data:text/html;charset=utf-8,<html><style>" +
     "div {" +
     "  width: 500px;" +
     "  height: 10px;" +
     "  background: purple;" +
     "} " +
     "@media screen and (max-width: 200px) {" +
     "  div { " +
     "    width: 100px;" +
@@ -48,34 +48,31 @@ function test() {
 
     instance.stack.setAttribute("notransition", "true");
     registerCleanupFunction(function() {
       instance.stack.removeAttribute("notransition");
     });
 
     instance.setSize(500, 500);
 
-    openView("computedview", onInspectorUIOpen);
+    openComputedView().then(onInspectorUIOpen);
   }
 
-  function onInspectorUIOpen(aInspector, aComputedView) {
-    inspector = aInspector;
+  function onInspectorUIOpen(args) {
+    inspector = args.inspector;
+    computedView = args.view;
     ok(inspector, "Got inspector instance");
 
     let div = content.document.getElementsByTagName("div")[0];
 
     inspector.selection.setNode(div);
     inspector.once("inspector-updated", testShrink);
-
   }
 
   function testShrink() {
-    computedView = inspector.sidebar.getWindowForTab("computedview").computedview.view;
-    ok(computedView, "We have access to the Computed View object");
-
     is(computedWidth(), "500px", "Should show 500px initially.");
 
     inspector.once("computed-view-refreshed", function onShrink() {
       is(computedWidth(), "100px", "div should be 100px after shrinking.");
       testGrow();
     });
 
     instance.setSize(100, 100);
--- a/browser/devtools/responsivedesign/test/browser_responsiveruleview.js
+++ b/browser/devtools/responsivedesign/test/browser_responsiveruleview.js
@@ -11,17 +11,17 @@ function test() {
   waitForExplicitFinish();
 
   gBrowser.selectedTab = gBrowser.addTab();
   gBrowser.selectedBrowser.addEventListener("load", function onload() {
     gBrowser.selectedBrowser.removeEventListener("load", onload, true);
     waitForFocus(startTest, content);
   }, true);
 
-  content.location = "data:text/html,<html><style>" +
+  content.location = "data:text/html;charset=utf-8,<html><style>" +
     "div {" +
     "  width: 500px;" +
     "  height: 10px;" +
     "  background: purple;" +
     "} " +
     "@media screen and (max-width: 200px) {" +
     "  div { " +
     "    width: 100px;" +
@@ -44,22 +44,22 @@ function test() {
 
     instance.stack.setAttribute("notransition", "true");
     registerCleanupFunction(function() {
       instance.stack.removeAttribute("notransition");
     });
 
     instance.setSize(500, 500);
 
-    openView("ruleview", onInspectorUIOpen);
+    openRuleView().then(onInspectorUIOpen);
   }
 
-  function onInspectorUIOpen(aInspector, aRuleView) {
-    inspector = aInspector;
-    ruleView = aRuleView;
+  function onInspectorUIOpen(args) {
+    inspector = args.inspector;
+    ruleView = args.view;
     ok(inspector, "Got inspector instance");
 
     let div = content.document.getElementsByTagName("div")[0];
     inspector.selection.setNode(div);
     inspector.once("inspector-updated", testShrink);
   }
 
   function testShrink() {
--- a/browser/devtools/responsivedesign/test/head.js
+++ b/browser/devtools/responsivedesign/test/head.js
@@ -10,32 +10,114 @@ let TargetFactory = devtools.TargetFacto
 let testDir = gTestPath.substr(0, gTestPath.lastIndexOf("/"));
 Services.scriptloader.loadSubScript(testDir + "../../../commandline/test/helpers.js", this);
 
 gDevTools.testing = true;
 SimpleTest.registerCleanupFunction(() => {
   gDevTools.testing = false;
 });
 
-function openInspector(callback)
-{
+/**
+ * Open the toolbox, with the inspector tool visible.
+ * @return a promise that resolves when the inspector is ready
+ */
+let openInspector = Task.async(function*() {
+  info("Opening the inspector");
   let target = TargetFactory.forTab(gBrowser.selectedTab);
-  gDevTools.showToolbox(target, "inspector").then(function(toolbox) {
-    callback(toolbox.getCurrentPanel());
-  });
+
+  let inspector, toolbox;
+
+  // Checking if the toolbox and the inspector are already loaded
+  // The inspector-updated event should only be waited for if the inspector
+  // isn't loaded yet
+  toolbox = gDevTools.getToolbox(target);
+  if (toolbox) {
+    inspector = toolbox.getPanel("inspector");
+    if (inspector) {
+      info("Toolbox and inspector already open");
+      return {
+        toolbox: toolbox,
+        inspector: inspector
+      };
+    }
+  }
+
+  info("Opening the toolbox");
+  toolbox = yield gDevTools.showToolbox(target, "inspector");
+  yield waitForToolboxFrameFocus(toolbox);
+  inspector = toolbox.getPanel("inspector");
+
+  info("Waiting for the inspector to update");
+  yield inspector.once("inspector-updated");
+
+  return {
+    toolbox: toolbox,
+    inspector: inspector
+  };
+});
+
+/**
+ * Wait for the toolbox frame to receive focus after it loads
+ * @param {Toolbox} toolbox
+ * @return a promise that resolves when focus has been received
+ */
+function waitForToolboxFrameFocus(toolbox) {
+  info("Making sure that the toolbox's frame is focused");
+  let def = promise.defer();
+  let win = toolbox.frame.contentWindow;
+  waitForFocus(def.resolve, win);
+  return def.promise;
 }
 
-function openView(name, callback)
-{
-  openInspector(inspector => {
-    function onReady() {
-      inspector.sidebar.select(name);
-      let { view } = inspector.sidebar.getWindowForTab(name)[name];
-      callback(inspector, view);
-    }
+/**
+ * Open the toolbox, with the inspector tool visible, and the sidebar that
+ * corresponds to the given id selected
+ * @return a promise that resolves when the inspector is ready and the sidebar
+ * view is visible and ready
+ */
+let openInspectorSideBar = Task.async(function*(id) {
+  let {toolbox, inspector} = yield openInspector();
+
+  if (!hasSideBarTab(inspector, id)) {
+    info("Waiting for the " + id + " sidebar to be ready");
+    yield inspector.sidebar.once(id + "-ready");
+  }
+
+  info("Selecting the " + id + " sidebar");
+  inspector.sidebar.select(id);
+
+  return {
+    toolbox: toolbox,
+    inspector: inspector,
+    view: inspector.sidebar.getWindowForTab(id)[id].view
+  };
+});
 
-    if (inspector.sidebar.getTab(name)) {
-      onReady();
-    } else {
-      inspector.sidebar.once(name + "-ready", onReady);
-    }
-  });
+/**
+ * Checks whether the inspector's sidebar corresponding to the given id already
+ * exists
+ * @param {InspectorPanel}
+ * @param {String}
+ * @return {Boolean}
+ */
+function hasSideBarTab(inspector, id) {
+  return !!inspector.sidebar.getWindowForTab(id);
 }
+
+/**
+ * Open the toolbox, with the inspector tool visible, and the computed-view
+ * sidebar tab selected.
+ * @return a promise that resolves when the inspector is ready and the computed
+ * view is visible and ready
+ */
+function openComputedView() {
+  return openInspectorSideBar("computedview");
+}
+
+/**
+ * Open the toolbox, with the inspector tool visible, and the rule-view
+ * sidebar tab selected.
+ * @return a promise that resolves when the inspector is ready and the rule
+ * view is visible and ready
+ */
+function openRuleView() {
+  return openInspectorSideBar("ruleview");
+}
--- a/browser/devtools/styleinspector/test/browser.ini
+++ b/browser/devtools/styleinspector/test/browser.ini
@@ -85,9 +85,10 @@ skip-if = os == "win" && debug # bug 963
 [browser_styleinspector_csslogic-inherited-properties.js]
 [browser_styleinspector_csslogic-specificity.js]
 [browser_styleinspector_inplace-editor.js]
 [browser_styleinspector_output-parser.js]
 [browser_styleinspector_tooltip-background-image.js]
 [browser_styleinspector_tooltip-closes-on-new-selection.js]
 [browser_styleinspector_tooltip-longhand-fontfamily.js]
 [browser_styleinspector_tooltip-shorthand-fontfamily.js]
+[browser_styleinspector_tooltip-size.js]
 [browser_styleinspector_tooltip-transform.js]
rename from browser/devtools/inspector/test/browser_inspector_bug_952294_tooltips_dimensions.js
rename to browser/devtools/styleinspector/test/browser_styleinspector_tooltip-size.js
--- a/browser/devtools/inspector/test/browser_inspector_bug_952294_tooltips_dimensions.js
+++ b/browser/devtools/styleinspector/test/browser_styleinspector_tooltip-size.js
@@ -1,202 +1,119 @@
-/* vim: set ts=2 et sw=2 tw=80: */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
 /* Any copyright is dedicated to the Public Domain.
  http://creativecommons.org/publicdomain/zero/1.0/ */
+
 "use strict";
 
-let contentDoc;
-let inspector;
-let ruleView;
-let markupView;
+// Checking tooltips dimensions, to make sure their big enough to display their
+// content
 
-const PAGE_CONTENT = [
+const TEST_PAGE = [
+  'data:text/html;charset=utf-8,',
   '<style type="text/css">',
   '  div {',
   '    width: 300px;height: 300px;border-radius: 50%;',
   '    transform: skew(45deg);',
   '    background: red url(chrome://global/skin/icons/warning-64.png);',
   '  }',
   '</style>',
   '<div></div>'
 ].join("\n");
 
-function test() {
-  waitForExplicitFinish();
+let test = asyncTest(function*() {
+  yield addTab(TEST_PAGE);
+  let {toolbox, inspector, view} = yield openRuleView();
 
-  gBrowser.selectedTab = gBrowser.addTab();
-  gBrowser.selectedBrowser.addEventListener("load", function onload(evt) {
-    gBrowser.selectedBrowser.removeEventListener("load", onload, true);
-    contentDoc = content.document;
-    waitForFocus(createDocument, content);
-  }, true);
-
-  content.location = "data:text/html,tooltip dimension test";
-}
-
-function createDocument() {
-  contentDoc.body.innerHTML = PAGE_CONTENT;
+  yield selectNode("div", inspector);
 
-  openInspector(aInspector => {
-    inspector = aInspector;
-    markupView = inspector.markup;
+  yield testTransformDimension(view);
+  yield testImageDimension(view);
+  yield testPickerDimension(view);
+});
 
-    waitForView("ruleview", () => {
-      ruleView = inspector.sidebar.getWindowForTab("ruleview").ruleview.view;
-      inspector.sidebar.select("ruleview");
-      startTests();
-    });
-  });
-}
+function* testTransformDimension(ruleView) {
+  info("Testing css transform tooltip dimensions");
 
-function endTests() {
-  contentDoc = inspector = ruleView = markupView = null;
-  gBrowser.removeCurrentTab();
-  finish();
-}
+  let tooltip = ruleView.previewTooltip;
+  let panel = tooltip.panel;
+  let {valueSpan} = getRuleViewProperty(ruleView, "div", "transform");
 
-function startTests() {
-  Task.spawn(function() {
-    yield selectDiv();
-    yield testTransformDimension();
-    yield testImageDimension();
-    yield testPickerDimension();
-    endTests();
-  }).then(null, Cu.reportError);
-}
+  // Make sure there is a hover tooltip for this property, this also will fill
+  // the tooltip with its content
+  yield assertHoverTooltipOn(tooltip, valueSpan);
 
-function selectDiv() {
-  let deferred = promise.defer();
-
-  inspector.selection.setNode(contentDoc.querySelector("div"));
-  inspector.once("inspector-updated", () => {
-    deferred.resolve();
-  });
-
-  return deferred.promise;
-}
+  info("Showing the tooltip");
+  let onShown = tooltip.once("shown");
+  tooltip.show();
+  yield onShown;
 
-function testTransformDimension() {
-  return Task.spawn(function*() {
-    let tooltip = ruleView.previewTooltip;
-    let panel = tooltip.panel;
-
-    info("Testing css transform tooltip dimensions");
-    let {valueSpan} = getRuleViewProperty("transform");
-
-    yield assertTooltipShownOn(tooltip, valueSpan);
+  // Let's not test for a specific size, but instead let's make sure it's at
+  // least as big as the preview canvas
+  let canvas = panel.querySelector("canvas");
+  let w = canvas.width;
+  let h = canvas.height;
+  let panelRect = panel.getBoundingClientRect();
 
-    // Let's not test for a specific size, but instead let's make sure it's at
-    // least as big as the preview canvas
-    let canvas = panel.querySelector("canvas");
-    let w = canvas.width;
-    let h = canvas.height;
-    let panelRect = panel.getBoundingClientRect();
+  ok(panelRect.width >= w, "The panel is wide enough to show the canvas");
+  ok(panelRect.height >= h, "The panel is high enough to show the canvas");
 
-    ok(panelRect.width >= w, "The panel is wide enough to show the canvas");
-    ok(panelRect.height >= h, "The panel is high enough to show the canvas");
-
-    let onHidden = tooltip.once("hidden");
-    tooltip.hide();
-    yield onHidden;
-  });
+  let onHidden = tooltip.once("hidden");
+  tooltip.hide();
+  yield onHidden;
 }
 
-function testImageDimension() {
-  return Task.spawn(function*() {
-    info("Testing background-image tooltip dimensions");
+function* testImageDimension(ruleView) {
+  info("Testing background-image tooltip dimensions");
 
-    let tooltip = ruleView.previewTooltip;
-    let panel = tooltip.panel;
+  let tooltip = ruleView.previewTooltip;
+  let panel = tooltip.panel;
+  let {valueSpan} = getRuleViewProperty(ruleView, "div", "background");
+  let uriSpan = valueSpan.querySelector(".theme-link");
 
-    let {valueSpan} = getRuleViewProperty("background");
-    let uriSpan = valueSpan.querySelector(".theme-link");
-
-    yield assertTooltipShownOn(tooltip, uriSpan);
+  // Make sure there is a hover tooltip for this property, this also will fill
+  // the tooltip with its content
+  yield assertHoverTooltipOn(tooltip, uriSpan);
 
-    // Let's not test for a specific size, but instead let's make sure it's at
-    // least as big as the image
-    let imageRect = panel.querySelector("image").getBoundingClientRect();
-    let panelRect = panel.getBoundingClientRect();
+  info("Showing the tooltip");
+  let onShown = tooltip.once("shown");
+  tooltip.show();
+  yield onShown;
 
-    ok(panelRect.width >= imageRect.width,
-      "The panel is wide enough to show the image");
-    ok(panelRect.height >= imageRect.height,
-      "The panel is high enough to show the image");
+  // Let's not test for a specific size, but instead let's make sure it's at
+  // least as big as the image
+  let imageRect = panel.querySelector("image").getBoundingClientRect();
+  let panelRect = panel.getBoundingClientRect();
 
-    let onHidden = tooltip.once("hidden");
-    tooltip.hide();
-    yield onHidden;
-  });
+  ok(panelRect.width >= imageRect.width,
+    "The panel is wide enough to show the image");
+  ok(panelRect.height >= imageRect.height,
+    "The panel is high enough to show the image");
+
+  let onHidden = tooltip.once("hidden");
+  tooltip.hide();
+  yield onHidden;
 }
 
-function testPickerDimension() {
-  return Task.spawn(function*() {
-    info("Testing color-picker tooltip dimensions");
-
-    let {valueSpan} = getRuleViewProperty("background");
-    let swatch = valueSpan.querySelector(".ruleview-colorswatch");
-    let cPicker = ruleView.colorPicker;
-
-    let onShown = cPicker.tooltip.once("shown");
-    swatch.click();
-    yield onShown;
+function* testPickerDimension(ruleView) {
+  info("Testing color-picker tooltip dimensions");
 
-    // The colorpicker spectrum's iframe has a fixed width height, so let's
-    // make sure the tooltip is at least as big as that
-    let w = cPicker.tooltip.panel.querySelector("iframe").width;
-    let h = cPicker.tooltip.panel.querySelector("iframe").height;
-    let panelRect = cPicker.tooltip.panel.getBoundingClientRect();
-
-    ok(panelRect.width >= w, "The panel is wide enough to show the picker");
-    ok(panelRect.height >= h, "The panel is high enough to show the picker");
+  let {valueSpan} = getRuleViewProperty(ruleView, "div", "background");
+  let swatch = valueSpan.querySelector(".ruleview-colorswatch");
+  let cPicker = ruleView.colorPicker;
 
-    let onHidden = cPicker.tooltip.once("hidden");
-    cPicker.hide();
-    yield onHidden;
-  });
-}
-
-/**
- * @return a promise that resolves when the tooltip is shown
- */
-function assertTooltipShownOn(tooltip, element) {
-  return Task.spawn(function*() {
-    let isTarget = yield isHoverTooltipTarget(tooltip, element);
-    ok(isTarget, "The element is a tooltip target and content has been inserted");
+  let onShown = cPicker.tooltip.once("shown");
+  swatch.click();
+  yield onShown;
 
-    info("Showing the tooltip now that content has been inserted by isValidHoverTarget");
-    let onShown = tooltip.once("shown");
-    tooltip.show();
-    yield onShown;
-  });
-}
+  // The colorpicker spectrum's iframe has a fixed width height, so let's
+  // make sure the tooltip is at least as big as that
+  let w = cPicker.tooltip.panel.querySelector("iframe").width;
+  let h = cPicker.tooltip.panel.querySelector("iframe").height;
+  let panelRect = cPicker.tooltip.panel.getBoundingClientRect();
 
-/**
- * Given a tooltip object instance (see Tooltip.js), checks if it is set to
- * toggle and hover and if so, checks if the given target is a valid hover target.
- * This won't actually show the tooltip (the less we interact with XUL panels
- * during test runs, the better).
- * @return a promise that resolves when the answer is known. Also, this will
- * delete to a function in the rule-view which will insert content into the
- * tooltip
- */
-function isHoverTooltipTarget(tooltip, target) {
-  if (!tooltip._basedNode || !tooltip.panel) {
-    return promise.reject(new Error("The tooltip passed isn't set to toggle on hover or is not a tooltip"));
-  }
-  // The tooltip delegates to a user defined cb that inserts content in the tooltip
-  // when calling isValidHoverTarget
-  return tooltip.isValidHoverTarget(target);
+  ok(panelRect.width >= w, "The panel is wide enough to show the picker");
+  ok(panelRect.height >= h, "The panel is high enough to show the picker");
+
+  let onHidden = cPicker.tooltip.once("hidden");
+  cPicker.hide();
+  yield onHidden;
 }
-
-function getRuleViewProperty(name) {
-  let prop = null;
-  [].forEach.call(ruleView.doc.querySelectorAll(".ruleview-property"), property => {
-    let nameSpan = property.querySelector(".ruleview-propertyname");
-    let valueSpan = property.querySelector(".ruleview-propertyvalue");
-
-    if (nameSpan.textContent === name) {
-      prop = {nameSpan: nameSpan, valueSpan: valueSpan};
-    }
-  });
-  return prop;
-}
--- a/configure.in
+++ b/configure.in
@@ -4816,18 +4816,21 @@ then
     dnl ========================================================
     MOZ_ARG_DISABLE_BOOL(gio,
     [  --disable-gio           Disable GIO support],
         MOZ_ENABLE_GIO=,
         MOZ_ENABLE_GIO=force)
 
     if test "$MOZ_ENABLE_GIO" -a "$MOZ_ENABLE_GTK"
     then
-        PKG_CHECK_MODULES(_GTKCHECK, gtk+-2.0 >= 2.14, ,
-                          [AC_MSG_ERROR([* * * Could not find gtk+-2.0 > 2.14. Required for build with gio.])])
+        if test "$MOZ_ENABLE_GTK2"
+        then
+            PKG_CHECK_MODULES(_GTKCHECK, gtk+-2.0 >= 2.14, ,
+                              [AC_MSG_ERROR([* * * Could not find gtk+-2.0 > 2.14. Required for build with gio.])])
+        fi
         PKG_CHECK_MODULES(MOZ_GIO, gio-2.0 >= $GIO_VERSION,[
             MOZ_GIO_LIBS=`echo $MOZ_GIO_LIBS | sed 's/-llinc\>//'`
             MOZ_ENABLE_GIO=1
             AC_DEFINE(MOZ_ENABLE_GIO)
         ],[
             if test "$MOZ_ENABLE_GIO" = "force"
             then
                 AC_MSG_ERROR([* * * Could not find gio-2.0 >= $GIO_VERSION])
--- a/content/base/test/csp/test_bug949549.html
+++ b/content/base/test/csp/test_bug949549.html
@@ -15,22 +15,22 @@
   // Ensure that `setRequestContext` doesn't throw with app:// URLs
 
   const csp = SpecialPowers.Cc["@mozilla.org/contentsecuritypolicy;1"]
               .createInstance(SpecialPowers.Ci.nsIContentSecurityPolicy);
 
   const gManifestURL = "http://www.example.com/chrome/dom/tests/mochitest/webapps/apps/basic.webapp";
 
   SimpleTest.waitForExplicitFinish();
-  var launchableValue, app;
+  var app;
 
   function setupTest() {
     // We have to install an app in order for the app URL to be valid
     // (otherwise we get a "DummyChannel" that throws NS_NOT_IMPLEMENTED)
-    launchableValue = SpecialPowers.setAllAppsLaunchable(true);
+    SpecialPowers.setAllAppsLaunchable(true);
     SpecialPowers.addPermission("webapps-manage", true, document);
     SpecialPowers.autoConfirmAppInstall(function () {
       let req = navigator.mozApps.install(gManifestURL);
       req.onsuccess = function () {
         app = this.result;
         runTest();
       }
     });
@@ -49,17 +49,16 @@
     } catch(e) {
       ok(false, "setRequestContext throws");
     }
 
     cleanup()
   }
 
   function cleanup() {
-    SpecialPowers.setAllAppsLaunchable(launchableValue);
     let req = navigator.mozApps.mgmt.uninstall(app);
     req.onsuccess = function () {
       SimpleTest.finish();
     };
   }
 
   setupTest();
 </script>
--- a/content/media/AudioNodeEngine.h
+++ b/content/media/AudioNodeEngine.h
@@ -39,16 +39,22 @@ public:
 
   struct Storage {
     Storage()
     {
       mDataToFree = nullptr;
       mSampleData = nullptr;
     }
     ~Storage() { free(mDataToFree); }
+    size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const
+    {
+      // NB: mSampleData might not be owned, if it is it just points to
+      //     mDataToFree.
+      return aMallocSizeOf(mDataToFree);
+    }
     void* mDataToFree;
     const float* mSampleData;
   };
 
   /**
    * This can be called on any thread.
    */
   uint32_t GetChannels() const { return mContents.Length(); }
@@ -69,19 +75,25 @@ public:
     s->mSampleData = aData;
   }
 
   /**
    * Put this object into an error state where there are no channels.
    */
   void Clear() { mContents.Clear(); }
 
-  size_t SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const
+  virtual size_t SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const MOZ_OVERRIDE
   {
-    return mContents.SizeOfExcludingThis(aMallocSizeOf);
+    size_t amount = ThreadSharedObject::SizeOfExcludingThis(aMallocSizeOf);
+    amount += mContents.SizeOfExcludingThis(aMallocSizeOf);
+    for (size_t i = 0; i < mContents.Length(); i++) {
+      amount += mContents[i].SizeOfExcludingThis(aMallocSizeOf);
+    }
+
+    return amount;
   }
 
 private:
   AutoFallibleTArray<Storage,2> mContents;
 };
 
 /**
  * Allocates an AudioChunk with fresh buffers of WEBAUDIO_BLOCK_SIZE float samples.
@@ -323,16 +335,35 @@ public:
     MOZ_ASSERT(mNode != nullptr);
     mNodeMutex.AssertCurrentThreadOwns();
     mNode = nullptr;
   }
 
   uint16_t InputCount() const { return mInputCount; }
   uint16_t OutputCount() const { return mOutputCount; }
 
+  virtual size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const
+  {
+    // NB: |mNode| is tracked separately so it is excluded here.
+    return 0;
+  }
+
+  virtual size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const
+  {
+    return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
+  }
+
+  void SizeOfIncludingThis(MallocSizeOf aMallocSizeOf,
+                           AudioNodeSizes& aUsage) const
+  {
+    aUsage.mEngine = SizeOfIncludingThis(aMallocSizeOf);
+    aUsage.mDomNode = mNode->SizeOfIncludingThis(aMallocSizeOf);
+    aUsage.mNodeType = mNode->NodeType();
+  }
+
 private:
   dom::AudioNode* mNode;
   Mutex mNodeMutex;
   const uint16_t mInputCount;
   const uint16_t mOutputCount;
 };
 
 }
--- a/content/media/AudioNodeStream.cpp
+++ b/content/media/AudioNodeStream.cpp
@@ -25,16 +25,54 @@ namespace mozilla {
  * Note: This must be a different value than MEDIA_STREAM_DEST_TRACK_ID
  */
 
 AudioNodeStream::~AudioNodeStream()
 {
   MOZ_COUNT_DTOR(AudioNodeStream);
 }
 
+size_t
+AudioNodeStream::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const
+{
+  size_t amount = 0;
+
+  // Not reported:
+  // - mEngine
+
+  amount += ProcessedMediaStream::SizeOfExcludingThis(aMallocSizeOf);
+  amount += mLastChunks.SizeOfExcludingThis(aMallocSizeOf);
+  for (size_t i = 0; i < mLastChunks.Length(); i++) {
+    // NB: This is currently unshared only as there are instances of
+    //     double reporting in DMD otherwise.
+    amount += mLastChunks[i].SizeOfExcludingThisIfUnshared(aMallocSizeOf);
+  }
+
+  return amount;
+}
+
+size_t
+AudioNodeStream::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const
+{
+  return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
+}
+
+void
+AudioNodeStream::SizeOfAudioNodesIncludingThis(MallocSizeOf aMallocSizeOf,
+                                               AudioNodeSizes& aUsage) const
+{
+  // Explicitly separate out the stream memory.
+  aUsage.mStream = SizeOfIncludingThis(aMallocSizeOf);
+
+  if (mEngine) {
+    // This will fill out the rest of |aUsage|.
+    mEngine->SizeOfIncludingThis(aMallocSizeOf, aUsage);
+  }
+}
+
 void
 AudioNodeStream::SetStreamTimeParameter(uint32_t aIndex, AudioContext* aContext,
                                         double aStreamTime)
 {
   class Message : public ControlMessage {
   public:
     Message(AudioNodeStream* aStream, uint32_t aIndex, MediaStream* aRelativeToStream,
             double aStreamTime)
--- a/content/media/AudioNodeStream.h
+++ b/content/media/AudioNodeStream.h
@@ -150,16 +150,22 @@ public:
                                       double aSeconds);
   /**
    * Get the destination stream time in seconds corresponding to a position on
    * this stream.
    */
   double DestinationTimeFromTicks(AudioNodeStream* aDestination,
                                   TrackTicks aPosition);
 
+  size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const MOZ_OVERRIDE;
+  size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const MOZ_OVERRIDE;
+
+  void SizeOfAudioNodesIncludingThis(MallocSizeOf aMallocSizeOf,
+                                     AudioNodeSizes& aUsage) const;
+
 protected:
   void AdvanceOutputSegment();
   void FinishOutput();
   void AccumulateInputChunk(uint32_t aInputIndex, const AudioChunk& aChunk,
                             AudioChunk* aBlock,
                             nsTArray<float>* aDownmixBuffer);
   void UpMixDownMixChunk(const AudioChunk* aChunk, uint32_t aOutputChannelCount,
                          nsTArray<const void*>& aOutputChannels,
--- a/content/media/AudioSegment.cpp
+++ b/content/media/AudioSegment.cpp
@@ -170,17 +170,17 @@ AudioSegment::WriteTo(uint64_t aID, Audi
     uint32_t frames = c.mDuration;
 
     // If we have written data in the past, or we have real (non-silent) data
     // to write, we can proceed. Otherwise, it means we just started the
     // AudioStream, and we don't have real data to write to it (just silence).
     // To avoid overbuffering in the AudioStream, we simply drop the silence,
     // here. The stream will underrun and output silence anyways.
     if (c.mBuffer || aOutput->GetWritten()) {
-      if (c.mBuffer) {
+      if (c.mBuffer && c.mBufferFormat != AUDIO_FORMAT_SILENCE) {
         channelData.SetLength(c.mChannelData.Length());
         for (uint32_t i = 0; i < channelData.Length(); ++i) {
           channelData[i] = c.mChannelData[i];
         }
 
         if (channelData.Length() < outputChannels) {
           // Up-mix. Note that this might actually make channelData have more
           // than outputChannels temporarily.
--- a/content/media/AudioSegment.h
+++ b/content/media/AudioSegment.h
@@ -14,21 +14,38 @@
 #include "mozilla/TimeStamp.h"
 #endif
 
 namespace mozilla {
 
 template<typename T>
 class SharedChannelArrayBuffer : public ThreadSharedObject {
 public:
-  SharedChannelArrayBuffer(nsTArray<nsTArray<T>>* aBuffers)
+  SharedChannelArrayBuffer(nsTArray<nsTArray<T> >* aBuffers)
   {
     mBuffers.SwapElements(*aBuffers);
   }
-  nsTArray<nsTArray<T>> mBuffers;
+
+  virtual size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const MOZ_OVERRIDE
+  {
+    size_t amount = 0;
+    amount += mBuffers.SizeOfExcludingThis(aMallocSizeOf);
+    for (size_t i = 0; i < mBuffers.Length(); i++) {
+      amount += mBuffers[i].SizeOfExcludingThis(aMallocSizeOf);
+    }
+
+    return amount;
+  }
+
+  virtual size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const MOZ_OVERRIDE
+  {
+    return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
+  }
+
+  nsTArray<nsTArray<T> > mBuffers;
 };
 
 class AudioStream;
 class AudioMixer;
 
 /**
  * For auto-arrays etc, guess this as the common number of channels.
  */
@@ -109,16 +126,37 @@ struct AudioChunk {
     mBuffer = nullptr;
     mChannelData.Clear();
     mDuration = aDuration;
     mVolume = 1.0f;
     mBufferFormat = AUDIO_FORMAT_SILENCE;
   }
   int ChannelCount() const { return mChannelData.Length(); }
 
+  size_t SizeOfExcludingThisIfUnshared(MallocSizeOf aMallocSizeOf) const
+  {
+    return SizeOfExcludingThis(aMallocSizeOf, true);
+  }
+
+  size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf, bool aUnshared) const
+  {
+    size_t amount = 0;
+
+    // Possibly owned:
+    // - mBuffer - Can hold data that is also in the decoded audio queue. If it
+    //             is not shared, or unshared == false it gets counted.
+    if (mBuffer && (!aUnshared || !mBuffer->IsShared())) {
+      amount += mBuffer->SizeOfIncludingThis(aMallocSizeOf);
+    }
+
+    // Memory in the array is owned by mBuffer.
+    amount += mChannelData.SizeOfExcludingThis(aMallocSizeOf);
+    return amount;
+  }
+
   TrackTicks mDuration; // in frames within the buffer
   nsRefPtr<ThreadSharedObject> mBuffer; // the buffer object whose lifetime is managed; null means data is all zeroes
   nsTArray<const void*> mChannelData; // one pointer per channel; empty if and only if mBuffer is null
   float mVolume; // volume multiplier to apply (1.0f if mBuffer is nonnull)
   SampleFormat mBufferFormat; // format of frames in mBuffer (only meaningful if mBuffer is nonnull)
 #ifdef MOZILLA_INTERNAL_API
   mozilla::TimeStamp mTimeStamp;           // time at which this has been fetched from the MediaEngine
 #endif
@@ -226,13 +264,18 @@ public:
 
   int ChannelCount() {
     NS_WARN_IF_FALSE(!mChunks.IsEmpty(),
         "Cannot query channel count on a AudioSegment with no chunks.");
     return mChunks.IsEmpty() ? 0 : mChunks[0].mChannelData.Length();
   }
 
   static Type StaticType() { return AUDIO; }
+
+  virtual size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const MOZ_OVERRIDE
+  {
+    return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
+  }
 };
 
 }
 
 #endif /* MOZILLA_AUDIOSEGMENT_H_ */
--- a/content/media/AudioStream.cpp
+++ b/content/media/AudioStream.cpp
@@ -167,16 +167,32 @@ AudioStream::~AudioStream()
 {
   LOG(("AudioStream: delete %p, state %d", this, mState));
   Shutdown();
   if (mDumpFile) {
     fclose(mDumpFile);
   }
 }
 
+size_t
+AudioStream::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const
+{
+  size_t amount = aMallocSizeOf(this);
+
+  // Possibly add in the future:
+  // - mTimeStretcher
+  // - mLatencyLog
+  // - mCubebStream
+
+  amount += mInserts.SizeOfExcludingThis(aMallocSizeOf);
+  amount += mBuffer.SizeOfExcludingThis(aMallocSizeOf);
+
+  return amount;
+}
+
 /*static*/ void AudioStream::InitLibrary()
 {
 #ifdef PR_LOGGING
   gAudioStreamLog = PR_NewLogModule("AudioStream");
 #endif
   PrefChanged(PREF_VOLUME_SCALE, nullptr);
   Preferences::RegisterCallback(PrefChanged, PREF_VOLUME_SCALE);
   PrefChanged(PREF_CUBEB_LATENCY, nullptr);
--- a/content/media/AudioStream.h
+++ b/content/media/AudioStream.h
@@ -164,16 +164,23 @@ public:
       return mCount;
     }
     mStart += (mCount - aSize);
     mCount = aSize;
     mStart %= mCapacity;
     return mCount;
   }
 
+  size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const
+  {
+    size_t amount = 0;
+    amount += mBuffer.SizeOfExcludingThis(aMallocSizeOf);
+    return amount;
+  }
+
 private:
   nsAutoArrayPtr<uint8_t> mBuffer;
   uint32_t mCapacity;
   uint32_t mStart;
   uint32_t mCount;
 };
 
 class AudioInitTask;
@@ -275,16 +282,18 @@ public:
   // This should be called before attempting to use the time stretcher.
   nsresult EnsureTimeStretcherInitialized();
   // Set playback rate as a multiple of the intrinsic playback rate. This is to
   // be called only with aPlaybackRate > 0.0.
   nsresult SetPlaybackRate(double aPlaybackRate);
   // Switch between resampling (if false) and time stretching (if true, default).
   nsresult SetPreservesPitch(bool aPreservesPitch);
 
+  size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const;
+
 private:
   friend class AudioInitTask;
 
   // So we can call it asynchronously from AudioInitTask
   nsresult OpenCubeb(cubeb_stream_params &aParams,
                      LatencyRequest aLatencyRequest);
 
   void CheckForStart();
--- a/content/media/MediaDecoder.cpp
+++ b/content/media/MediaDecoder.cpp
@@ -1765,16 +1765,18 @@ MediaMemoryTracker::CollectReports(nsIHa
 
   REPORT("explicit/media/decoded/audio", audio,
          "Memory used by decoded audio chunks.");
 
   REPORT("explicit/media/resources", resources,
          "Memory used by media resources including streaming buffers, caches, "
          "etc.");
 
+#undef REPORT
+
   return NS_OK;
 }
 
 MediaDecoderOwner*
 MediaDecoder::GetOwner()
 {
   MOZ_ASSERT(NS_IsMainThread());
   return mOwner;
--- a/content/media/MediaSegment.h
+++ b/content/media/MediaSegment.h
@@ -113,16 +113,26 @@ public:
    * Replace contents with disabled data of the same duration
    */
   virtual void ReplaceWithDisabled() = 0;
   /**
    * Remove all contents, setting duration to 0.
    */
   virtual void Clear() = 0;
 
+  virtual size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const
+  {
+    return 0;
+  }
+
+  virtual size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const
+  {
+    return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
+  }
+
 protected:
   MediaSegment(Type aType) : mDuration(0), mType(aType)
   {
     MOZ_COUNT_CTOR(MediaSegment);
   }
 
   TrackTicks mDuration; // total of mDurations of all chunks
   Type mType;
@@ -240,16 +250,30 @@ public:
   }
 
 #ifdef MOZILLA_INTERNAL_API
   void GetStartTime(TimeStamp &aTime) {
     aTime = mChunks[0].mTimeStamp;
   }
 #endif
 
+  virtual size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const MOZ_OVERRIDE
+  {
+    size_t amount = mChunks.SizeOfExcludingThis(aMallocSizeOf);
+    for (size_t i = 0; i < mChunks.Length(); i++) {
+      amount += mChunks[i].SizeOfExcludingThisIfUnshared(aMallocSizeOf);
+    }
+    return amount;
+  }
+
+  virtual size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const MOZ_OVERRIDE
+  {
+    return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
+  }
+
 protected:
   MediaSegmentBase(Type aType) : MediaSegment(aType) {}
 
   /**
    * Appends the contents of aSource to this segment, clearing aSource.
    */
   void AppendFromInternal(MediaSegmentBase<C, Chunk>* aSource)
   {
--- a/content/media/MediaStreamGraph.cpp
+++ b/content/media/MediaStreamGraph.cpp
@@ -8,18 +8,20 @@
 #include "mozilla/MathAlgorithms.h"
 #include "mozilla/unused.h"
 
 #include "AudioSegment.h"
 #include "VideoSegment.h"
 #include "nsContentUtils.h"
 #include "nsIAppShell.h"
 #include "nsIObserver.h"
+#include "nsPrintfCString.h"
 #include "nsServiceManagerUtils.h"
 #include "nsWidgetsCID.h"
+#include "prerror.h"
 #include "prlog.h"
 #include "mozilla/Attributes.h"
 #include "TrackUnionStream.h"
 #include "ImageContainer.h"
 #include "AudioChannelService.h"
 #include "AudioNodeEngine.h"
 #include "AudioNodeStream.h"
 #include "AudioNodeExternalInputStream.h"
@@ -1233,16 +1235,35 @@ MediaStreamGraphImpl::RunThread()
   }
   NS_ASSERTION(!messageQueue.IsEmpty(),
                "Shouldn't have started a graph with empty message queue!");
 
   uint32_t ticksProcessed = 0;
   AutoProfilerUnregisterThread autoUnregister;
 
   for (;;) {
+    // Check if a memory report has been requested.
+    {
+      MonitorAutoLock lock(mMemoryReportMonitor);
+      if (mNeedsMemoryReport) {
+        mNeedsMemoryReport = false;
+
+        for (uint32_t i = 0; i < mStreams.Length(); ++i) {
+          AudioNodeStream* stream = mStreams[i]->AsAudioNodeStream();
+          if (stream) {
+            AudioNodeSizes usage;
+            stream->SizeOfAudioNodesIncludingThis(MallocSizeOf, usage);
+            mAudioStreamSizes.AppendElement(usage);
+          }
+        }
+
+        lock.Notify();
+      }
+    }
+
     // Update mCurrentTime to the min of the playing audio times, or using the
     // wall-clock time change if no audio is playing.
     UpdateCurrentTime();
 
     // Calculate independent action times for each batch of messages (each
     // batch corresponding to an event loop task). This isolates the performance
     // of different scripts to some extent.
     for (uint32_t i = 0; i < messageQueue.Length(); ++i) {
@@ -1514,17 +1535,17 @@ public:
     NS_ASSERTION(mGraph->mDetectedNotRunning,
                  "We should know the graph thread control loop isn't running!");
 
     mGraph->ShutdownThreads();
 
     // mGraph's thread is not running so it's OK to do whatever here
     if (mGraph->IsEmpty()) {
       // mGraph is no longer needed, so delete it.
-      delete mGraph;
+      mGraph->Destroy();
     } else {
       // The graph is not empty.  We must be in a forced shutdown, or a
       // non-realtime graph that has finished processing.  Some later
       // AppendMessage will detect that the manager has been emptied, and
       // delete it.
       NS_ASSERTION(mGraph->mForceShutDown || !mGraph->mRealtime,
                    "Not in forced shutdown?");
       for (uint32_t i = 0; i < mGraph->mStreams.Length(); ++i) {
@@ -1744,17 +1765,17 @@ MediaStreamGraphImpl::AppendMessage(Cont
     // graph has finished processing.
     aMessage->RunDuringShutdown();
     delete aMessage;
     if (IsEmpty() &&
         mLifecycleState >= LIFECYCLE_WAITING_FOR_STREAM_DESTRUCTION) {
       if (gGraph == this) {
         gGraph = nullptr;
       }
-      delete this;
+      Destroy();
     }
     return;
   }
 
   mCurrentTaskMessageQueue.AppendElement(aMessage);
   EnsureRunInStableState();
 }
 
@@ -1777,16 +1798,55 @@ MediaStream::MediaStream(DOMMediaStream*
   MOZ_COUNT_CTOR(MediaStream);
   // aWrapper should not already be connected to a MediaStream! It needs
   // to be hooked up to this stream, and since this stream is only just
   // being created now, aWrapper must not be connected to anything.
   NS_ASSERTION(!aWrapper || !aWrapper->GetStream(),
                "Wrapper already has another media stream hooked up to it!");
 }
 
+size_t
+MediaStream::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const
+{
+  size_t amount = 0;
+
+  // Not owned:
+  // - mGraph - Not reported here
+  // - mConsumers - elements
+  // Future:
+  // - mWrapper
+  // - mVideoOutputs - elements
+  // - mLastPlayedVideoFrame
+  // - mListeners - elements
+  // - mAudioOutputStreams - elements
+
+  amount += mBuffer.SizeOfExcludingThis(aMallocSizeOf);
+  amount += mAudioOutputs.SizeOfExcludingThis(aMallocSizeOf);
+  amount += mVideoOutputs.SizeOfExcludingThis(aMallocSizeOf);
+  amount += mExplicitBlockerCount.SizeOfExcludingThis(aMallocSizeOf);
+  amount += mListeners.SizeOfExcludingThis(aMallocSizeOf);
+  amount += mMainThreadListeners.SizeOfExcludingThis(aMallocSizeOf);
+  amount += mDisabledTrackIDs.SizeOfExcludingThis(aMallocSizeOf);
+  amount += mBlocked.SizeOfExcludingThis(aMallocSizeOf);
+  amount += mGraphUpdateIndices.SizeOfExcludingThis(aMallocSizeOf);
+  amount += mConsumers.SizeOfExcludingThis(aMallocSizeOf);
+  amount += mAudioOutputStreams.SizeOfExcludingThis(aMallocSizeOf);
+  for (size_t i = 0; i < mAudioOutputStreams.Length(); i++) {
+    amount += mAudioOutputStreams[i].SizeOfExcludingThis(aMallocSizeOf);
+  }
+
+  return amount;
+}
+
+size_t
+MediaStream::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const
+{
+  return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
+}
+
 void
 MediaStream::Init()
 {
   MediaStreamGraphImpl* graph = GraphImpl();
   mBlocked.SetAtAndAfter(graph->mCurrentTime, true);
   mExplicitBlockerCount.SetAtAndAfter(graph->mCurrentTime, true);
   mExplicitBlockerCount.SetAtAndAfter(graph->mStateComputedTime, false);
 }
@@ -2589,24 +2649,40 @@ MediaStreamGraphImpl::MediaStreamGraphIm
   , mPostedRunInStableStateEvent(false)
   , mDetectedNotRunning(false)
   , mPostedRunInStableState(false)
   , mRealtime(aRealtime)
   , mNonRealtimeProcessing(false)
   , mStreamOrderDirty(false)
   , mLatencyLog(AsyncLatencyLogger::Get())
   , mMixer(nullptr)
+  , mMemoryReportMonitor("MSGIMemory")
+  , mSelfRef(MOZ_THIS_IN_INITIALIZER_LIST())
+  , mAudioStreamSizes()
+  , mNeedsMemoryReport(false)
 {
 #ifdef PR_LOGGING
   if (!gMediaStreamGraphLog) {
     gMediaStreamGraphLog = PR_NewLogModule("MediaStreamGraph");
   }
 #endif
 
   mCurrentTimeStamp = mInitialTimeStamp = mLastMainThreadUpdate = TimeStamp::Now();
+
+  RegisterWeakMemoryReporter(this);
+}
+
+void
+MediaStreamGraphImpl::Destroy()
+{
+  // First unregister from memory reporting.
+  UnregisterWeakMemoryReporter(this);
+
+  // Clear the self reference which will destroy this instance.
+  mSelfRef = nullptr;
 }
 
 NS_IMPL_ISUPPORTS1(MediaStreamGraphShutdownObserver, nsIObserver)
 
 static bool gShutdownObserverRegistered = false;
 
 NS_IMETHODIMP
 MediaStreamGraphShutdownObserver::Observe(nsISupports *aSubject,
@@ -2630,30 +2706,32 @@ MediaStreamGraph::GetInstance()
 
   if (!gGraph) {
     if (!gShutdownObserverRegistered) {
       gShutdownObserverRegistered = true;
       nsContentUtils::RegisterShutdownObserver(new MediaStreamGraphShutdownObserver());
     }
 
     gGraph = new MediaStreamGraphImpl(true);
+
     STREAM_LOG(PR_LOG_DEBUG, ("Starting up MediaStreamGraph %p", gGraph));
 
     AudioStream::InitPreferredSampleRate();
   }
 
   return gGraph;
 }
 
 MediaStreamGraph*
 MediaStreamGraph::CreateNonRealtimeInstance()
 {
   NS_ASSERTION(NS_IsMainThread(), "Main thread only");
 
   MediaStreamGraphImpl* graph = new MediaStreamGraphImpl(false);
+
   return graph;
 }
 
 void
 MediaStreamGraph::DestroyNonRealtimeInstance(MediaStreamGraph* aGraph)
 {
   NS_ASSERTION(NS_IsMainThread(), "Main thread only");
   MOZ_ASSERT(aGraph->IsNonRealtime(), "Should not destroy the global graph here");
@@ -2664,16 +2742,86 @@ MediaStreamGraph::DestroyNonRealtimeInst
 
   if (!graph->mNonRealtimeProcessing) {
     // Start the graph, but don't produce anything
     graph->StartNonRealtimeProcessing(1, 0);
   }
   graph->ForceShutDown();
 }
 
+NS_IMPL_ISUPPORTS1(MediaStreamGraphImpl, nsIMemoryReporter)
+
+struct ArrayClearer
+{
+  ArrayClearer(nsTArray<AudioNodeSizes>& aArray) : mArray(aArray) {}
+  ~ArrayClearer() { mArray.Clear(); }
+  nsTArray<AudioNodeSizes>& mArray;
+};
+
+NS_IMETHODIMP
+MediaStreamGraphImpl::CollectReports(nsIHandleReportCallback* aHandleReport,
+                                     nsISupports* aData)
+{
+  // Clears out the report array after we're done with it.
+  ArrayClearer reportCleanup(mAudioStreamSizes);
+
+  {
+    MonitorAutoLock memoryReportLock(mMemoryReportMonitor);
+    mNeedsMemoryReport = true;
+
+    {
+      // Wake up the MSG thread.
+      MonitorAutoLock monitorLock(mMonitor);
+      EnsureImmediateWakeUpLocked(monitorLock);
+    }
+
+    // Wait for the report to complete.
+    nsresult rv;
+    while ((rv = memoryReportLock.Wait()) != NS_OK) {
+      if (PR_GetError() != PR_PENDING_INTERRUPT_ERROR) {
+        return rv;
+      }
+    }
+  }
+
+#define REPORT(_path, _amount, _desc)                                       \
+  do {                                                                      \
+    nsresult rv;                                                            \
+    rv = aHandleReport->Callback(EmptyCString(), _path,                     \
+                                 KIND_HEAP, UNITS_BYTES, _amount,           \
+                                 NS_LITERAL_CSTRING(_desc), aData);         \
+    NS_ENSURE_SUCCESS(rv, rv);                                              \
+  } while (0)
+
+  for (size_t i = 0; i < mAudioStreamSizes.Length(); i++) {
+    const AudioNodeSizes& usage = mAudioStreamSizes[i];
+    const char* const nodeType =  usage.mNodeType.get();
+
+    nsPrintfCString domNodePath("explicit/webaudio/audio-node/%s/dom-nodes",
+                                  nodeType);
+    REPORT(domNodePath, usage.mDomNode,
+           "Memory used by AudioNode DOM objects (Web Audio).");
+
+    nsPrintfCString enginePath("explicit/webaudio/audio-node/%s/engine-objects",
+                                nodeType);
+    REPORT(enginePath, usage.mEngine,
+           "Memory used by AudioNode engine objects (Web Audio).");
+
+    nsPrintfCString streamPath("explicit/webaudio/audio-node/%s/stream-objects",
+                                nodeType);
+    REPORT(streamPath, usage.mStream,
+           "Memory used by AudioNode stream objects (Web Audio).");
+
+  }
+
+#undef REPORT
+
+  return NS_OK;
+}
+
 SourceMediaStream*
 MediaStreamGraph::CreateSourceStream(DOMMediaStream* aWrapper)
 {
   SourceMediaStream* stream = new SourceMediaStream(aWrapper);
   NS_ADDREF(stream);
   MediaStreamGraphImpl* graph = static_cast<MediaStreamGraphImpl*>(this);
   stream->SetGraphImpl(graph);
   graph->AppendMessage(new CreateMessage(stream));
--- a/content/media/MediaStreamGraph.h
+++ b/content/media/MediaStreamGraph.h
@@ -221,16 +221,27 @@ public:
  * The listener is allowed to synchronously remove itself from the stream, but
  * not add or remove any other listeners.
  */
 class MainThreadMediaStreamListener {
 public:
   virtual void NotifyMainThreadStateChanged() = 0;
 };
 
+/**
+ * Helper struct used to keep track of memory usage by AudioNodes.
+ */
+struct AudioNodeSizes
+{
+  size_t mDomNode;
+  size_t mStream;
+  size_t mEngine;
+  nsCString mNodeType;
+};
+
 class MediaStreamGraphImpl;
 class SourceMediaStream;
 class ProcessedMediaStream;
 class MediaInputPort;
 class AudioNodeEngine;
 class AudioNodeExternalInputStream;
 class AudioNodeStream;
 struct AudioChunk;
@@ -505,16 +516,19 @@ public:
   }
 
   // Return true if the main thread needs to observe updates from this stream.
   virtual bool MainThreadNeedsUpdates() const
   {
     return true;
   }
 
+  virtual size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const;
+  virtual size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const;
+
 protected:
   virtual void AdvanceTimeVaryingValuesToCurrentTime(GraphTime aCurrentTime, GraphTime aBlockedTime)
   {
     mBufferStartTime += aBlockedTime;
     mGraphUpdateIndices.InsertTimeAtStart(aBlockedTime);
     mGraphUpdateIndices.AdvanceCurrentTime(aCurrentTime);
     mExplicitBlockerCount.AdvanceCurrentTime(aCurrentTime);
 
@@ -572,16 +586,23 @@ protected:
     GraphTime mAudioPlaybackStartTime;
     // Amount of time that we've wanted to play silence because of the stream
     // blocking.
     MediaTime mBlockedAudioTime;
     // Last tick written to the audio output.
     TrackTicks mLastTickWritten;
     RefPtr<AudioStream> mStream;
     TrackID mTrackID;
+
+    size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const
+    {
+      size_t amount = 0;
+      amount += mStream->SizeOfIncludingThis(aMallocSizeOf);
+      return amount;
+    }
   };
   nsTArray<AudioOutputStream> mAudioOutputStreams;
 
   /**
    * When true, this means the stream will be finished once all
    * buffered data has been consumed.
    */
   bool mFinished;
@@ -915,16 +936,32 @@ public:
    */
   MediaStreamGraphImpl* GraphImpl();
   MediaStreamGraph* Graph();
   /**
    * Sets the graph that owns this stream.  Should only be called once.
    */
   void SetGraphImpl(MediaStreamGraphImpl* aGraph);
 
+  size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const
+  {
+    size_t amount = 0;
+
+    // Not owned:
+    // - mSource
+    // - mDest
+    // - mGraph
+    return amount;
+  }
+
+  size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const
+  {
+    return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
+  }
+
 private:
   friend class MediaStreamGraphImpl;
   friend class MediaStream;
   friend class ProcessedMediaStream;
   // Never modified after Init()
   MediaStream* mSource;
   ProcessedMediaStream* mDest;
   uint32_t mFlags;
@@ -1011,16 +1048,29 @@ public:
 
   /**
    * Forward SetTrackEnabled() to the input MediaStream(s) and translate the ID
    */
   virtual void ForwardTrackEnabled(TrackID aOutputID, bool aEnabled) {};
 
   bool InCycle() const { return mInCycle; }
 
+  virtual size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const MOZ_OVERRIDE
+  {
+    size_t amount = MediaStream::SizeOfExcludingThis(aMallocSizeOf);
+    // Not owned:
+    // - mInputs elements
+    amount += mInputs.SizeOfExcludingThis(aMallocSizeOf);
+    return amount;
+  }
+
+  virtual size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const MOZ_OVERRIDE
+  {
+    return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
+  }
 
 protected:
   // This state is all accessed only on the media graph thread.
 
   // The list of all inputs that are currently enabled or waiting to be enabled.
   nsTArray<MediaInputPort*> mInputs;
   bool mAutofinish;
   // True if and only if this stream is in a cycle.
--- a/content/media/MediaStreamGraphImpl.h
+++ b/content/media/MediaStreamGraphImpl.h
@@ -5,16 +5,17 @@
 
 #ifndef MOZILLA_MEDIASTREAMGRAPHIMPL_H_
 #define MOZILLA_MEDIASTREAMGRAPHIMPL_H_
 
 #include "MediaStreamGraph.h"
 
 #include "mozilla/Monitor.h"
 #include "mozilla/TimeStamp.h"
+#include "nsIMemoryReporter.h"
 #include "nsIThread.h"
 #include "nsIRunnable.h"
 #include "Latency.h"
 #include "mozilla/WeakPtr.h"
 
 namespace mozilla {
 
 template <typename T>
@@ -101,27 +102,36 @@ protected:
 /**
  * The implementation of a media stream graph. This class is private to this
  * file. It's not in the anonymous namespace because MediaStream needs to
  * be able to friend it.
  *
  * Currently we have one global instance per process, and one per each
  * OfflineAudioContext object.
  */
-class MediaStreamGraphImpl : public MediaStreamGraph {
+class MediaStreamGraphImpl : public MediaStreamGraph,
+                             public nsIMemoryReporter {
 public:
+  NS_DECL_ISUPPORTS
+  NS_DECL_NSIMEMORYREPORTER
+
   /**
    * Set aRealtime to true in order to create a MediaStreamGraph which provides
    * support for real-time audio and video.  Set it to false in order to create
    * a non-realtime instance which just churns through its inputs and produces
    * output.  Those objects currently only support audio, and are used to
    * implement OfflineAudioContext.  They do not support MediaStream inputs.
    */
   explicit MediaStreamGraphImpl(bool aRealtime);
-  virtual ~MediaStreamGraphImpl();
+
+  /**
+   * Unregisters memory reporting and deletes this instance. This should be
+   * called instead of calling the destructor directly.
+   */
+  void Destroy();
 
   // Main thread only.
   /**
    * This runs every time we need to sync state from the media graph thread
    * to the main thread while the main thread is not in the middle
    * of a script. It runs during a "stable state" (per HTML5) or during
    * an event posted to the main thread.
    */
@@ -573,13 +583,39 @@ public:
   /**
    * Hold a ref to the Latency logger
    */
   nsRefPtr<AsyncLatencyLogger> mLatencyLog;
   /**
    * If this is not null, all the audio output for the MSG will be mixed down.
    */
   nsAutoPtr<AudioMixer> mMixer;
+
+private:
+  virtual ~MediaStreamGraphImpl();
+
+  MOZ_DEFINE_MALLOC_SIZE_OF(MallocSizeOf)
+
+  /**
+   * Used to signal that a memory report has been requested.
+   */
+  Monitor mMemoryReportMonitor;
+  /**
+   * This class uses manual memory management, and all pointers to it are raw
+   * pointers. However, in order for it to implement nsIMemoryReporter, it needs
+   * to implement nsISupports and so be ref-counted. So it maintains a single
+   * 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;
+  /**
+   * Indicates that the MSG thread should gather data for a memory report.
+   */
+  bool mNeedsMemoryReport;
 };
 
 }
 
 #endif /* MEDIASTREAMGRAPHIMPL_H_ */
--- a/content/media/SharedBuffer.h
+++ b/content/media/SharedBuffer.h
@@ -16,16 +16,27 @@ namespace mozilla {
 /**
  * Base class for objects with a thread-safe refcount and a virtual
  * destructor.
  */
 class ThreadSharedObject {
 public:
   NS_INLINE_DECL_THREADSAFE_REFCOUNTING(ThreadSharedObject)
 
+  bool IsShared() { return mRefCnt.get() > 1; }
+
+  virtual size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const
+  {
+    return 0;
+  }
+
+  virtual size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const
+  {
+    return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
+  }
 protected:
   // Protected destructor, to discourage deletion outside of Release():
   virtual ~ThreadSharedObject() {}
 };
 
 /**
  * Heap-allocated chunk of arbitrary data with threadsafe refcounting.
  * Typically you would allocate one of these, fill it in, and then treat it as
@@ -47,18 +58,19 @@ public:
     }
     void* m = moz_xmalloc(size.value());
     nsRefPtr<SharedBuffer> p = new (m) SharedBuffer();
     NS_ASSERTION((reinterpret_cast<char*>(p.get() + 1) - reinterpret_cast<char*>(p.get())) % 4 == 0,
                  "SharedBuffers should be at least 4-byte aligned");
     return p.forget();
   }
 
-  size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const {
-    return aMallocSizeOf(this);
+  virtual size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const MOZ_OVERRIDE
+  {
+    return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
   }
 
 private:
   SharedBuffer() {}
 };
 
 }
 
--- a/content/media/StreamBuffer.h
+++ b/content/media/StreamBuffer.h
@@ -158,16 +158,25 @@ public:
     {
       return mSegment.forget();
     }
     void ForgetUpTo(TrackTicks aTime)
     {
       mSegment->ForgetUpTo(aTime);
     }
 
+    size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const
+    {
+      size_t amount = aMallocSizeOf(this);
+      if (mSegment) {
+        amount += mSegment->SizeOfIncludingThis(aMallocSizeOf);
+      }
+      return amount;
+    }
+
   protected:
     friend class StreamBuffer;
 
     // Start offset is in ticks at rate mRate
     TrackTicks mStart;
     // The segment data starts at the start of the owning StreamBuffer, i.e.,
     // there's mStart silence/no video at the beginning.
     nsAutoPtr<MediaSegment> mSegment;
@@ -193,16 +202,26 @@ public:
   {
     MOZ_COUNT_CTOR(StreamBuffer);
   }
   ~StreamBuffer()
   {
     MOZ_COUNT_DTOR(StreamBuffer);
   }
 
+  size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const
+  {
+    size_t amount = 0;
+    amount += mTracks.SizeOfExcludingThis(aMallocSizeOf);
+    for (size_t i = 0; i < mTracks.Length(); i++) {
+      amount += mTracks[i]->SizeOfIncludingThis(aMallocSizeOf);
+    }
+    return amount;
+  }
+
   /**
    * Takes ownership of aSegment. Don't do this while iterating, or while
    * holding a Track reference.
    * aSegment must have aStart worth of null data.
    */
   Track& AddTrack(TrackID aID, TrackRate aRate, TrackTicks aStart, MediaSegment* aSegment)
   {
     NS_ASSERTION(TimeToTicksRoundDown(aRate, mTracksKnownTime) <= aStart,
--- a/content/media/TimeVarying.h
+++ b/content/media/TimeVarying.h
@@ -210,16 +210,21 @@ public:
     NS_ASSERTION(&aOther != this, "Can't self-append");
     SetAtAndAfter(aTimeOffset, aOther.mCurrent);
     for (uint32_t i = 0; i < aOther.mChanges.Length(); ++i) {
       const Entry& e = aOther.mChanges[i];
       SetAtAndAfter(aTimeOffset + e.mTime, e.mValue);
     }
   }
 
+  size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const
+  {
+    return mChanges.SizeOfExcludingThis(aMallocSizeOf);
+  }
+
 private:
   struct Entry {
     Entry(Time aTime, const T& aValue) : mTime(aTime), mValue(aValue) {}
 
     // The time at which the value changes to mValue
     Time mTime;
     T mValue;
   };
--- a/content/media/VideoSegment.h
+++ b/content/media/VideoSegment.h
@@ -71,16 +71,23 @@ struct VideoChunk {
   void SetNull(TrackTicks aDuration)
   {
     mDuration = aDuration;
     mFrame.SetNull();
     mTimeStamp = TimeStamp();
   }
   void SetForceBlack(bool aForceBlack) { mFrame.SetForceBlack(aForceBlack); }
 
+  size_t SizeOfExcludingThisIfUnshared(MallocSizeOf aMallocSizeOf) const
+  {
+    // Future:
+    // - mFrame
+    return 0;
+  }
+
   TrackTicks mDuration;
   VideoFrame mFrame;
   mozilla::TimeStamp mTimeStamp;
 };
 
 class VideoSegment : public MediaSegmentBase<VideoSegment, VideoChunk> {
 public:
   typedef mozilla::layers::Image Image;
@@ -116,13 +123,18 @@ public:
          !i.IsEnded(); i.Next()) {
       VideoChunk& chunk = *i;
       chunk.SetForceBlack(true);
     }
   }
 
   // Segment-generic methods not in MediaSegmentBase
   static Type StaticType() { return VIDEO; }
+
+  virtual size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const MOZ_OVERRIDE
+  {
+    return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
+  }
 };
 
 }
 
 #endif /* MOZILLA_VIDEOSEGMENT_H_ */
--- a/content/media/webaudio/AnalyserNode.cpp
+++ b/content/media/webaudio/AnalyserNode.cpp
@@ -67,16 +67,21 @@ public:
     MutexAutoLock lock(NodeMutex());
 
     if (Node() &&
         aInput.mChannelData.Length() > 0) {
       nsRefPtr<TransferBuffer> transfer = new TransferBuffer(aStream, aInput);
       NS_DispatchToMainThread(transfer);
     }
   }
+
+  virtual size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const MOZ_OVERRIDE
+  {
+    return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
+  }
 };
 
 AnalyserNode::AnalyserNode(AudioContext* aContext)
   : AudioNode(aContext,
               1,
               ChannelCountMode::Explicit,
               ChannelInterpretation::Speakers)
   , mAnalysisBlock(2048)
@@ -85,16 +90,32 @@ AnalyserNode::AnalyserNode(AudioContext*
   , mSmoothingTimeConstant(.8)
   , mWriteIndex(0)
 {
   mStream = aContext->Graph()->CreateAudioNodeStream(new AnalyserNodeEngine(this),
                                                      MediaStreamGraph::INTERNAL_STREAM);
   AllocateBuffer();
 }
 
+size_t
+AnalyserNode::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const
+{
+  size_t amount = AudioNode::SizeOfExcludingThis(aMallocSizeOf);
+  amount += mAnalysisBlock.SizeOfExcludingThis(aMallocSizeOf);
+  amount += mBuffer.SizeOfExcludingThis(aMallocSizeOf);
+  amount += mOutputBuffer.SizeOfExcludingThis(aMallocSizeOf);
+  return amount;
+}
+
+size_t
+AnalyserNode::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const
+{
+  return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
+}
+
 JSObject*
 AnalyserNode::WrapObject(JSContext* aCx)
 {
   return AnalyserNodeBinding::Wrap(aCx, this);
 }
 
 void
 AnalyserNode::SetFftSize(uint32_t aValue, ErrorResult& aRv)
--- a/content/media/webaudio/AnalyserNode.h
+++ b/content/media/webaudio/AnalyserNode.h
@@ -48,16 +48,24 @@ public:
   }
   void SetMaxDecibels(double aValue, ErrorResult& aRv);
   double SmoothingTimeConstant() const
   {
     return mSmoothingTimeConstant;
   }
   void SetSmoothingTimeConstant(double aValue, ErrorResult& aRv);
 
+  virtual const char* NodeType() const
+  {
+    return "AnalyserNode";
+  }
+
+  virtual size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const MOZ_OVERRIDE;
+  virtual size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const MOZ_OVERRIDE;
+
 private:
   friend class AnalyserNodeEngine;
   void AppendChunk(const AudioChunk& aChunk);
   bool AllocateBuffer();
   bool FFTAnalysis();
   void ApplyBlackmanWindow(float* aBuffer, uint32_t aSize);
 
 private:
--- a/content/media/webaudio/AudioBufferSourceNode.cpp
+++ b/content/media/webaudio/AudioBufferSourceNode.cpp
@@ -477,16 +477,41 @@ public:
     // We've finished if we've gone past mStop, or if we're past mDuration when
     // looping is disabled.
     if (streamPosition >= mStop ||
         (!mLoop && mBufferPosition >= mBufferEnd && !mRemainingResamplerTail)) {
       *aFinished = true;
     }
   }
 
+  virtual size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const MOZ_OVERRIDE
+  {
+    // Not owned:
+    // - mBuffer - shared w/ AudioNode
+    // - mPlaybackRateTimeline - shared w/ AudioNode
+
+    size_t amount = AudioNodeEngine::SizeOfExcludingThis(aMallocSizeOf);
+
+    // NB: We need to modify speex if we want the full memory picture, internal
+    //     fields that need measuring noted below.
+    // - mResampler->mem
+    // - mResampler->sinc_table
+    // - mResampler->last_sample
+    // - mResampler->magic_samples
+    // - mResampler->samp_frac_num
+    amount += aMallocSizeOf(mResampler);
+
+    return amount;
+  }
+
+  virtual size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const MOZ_OVERRIDE
+  {
+    return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
+  }
+
   double mStart; // including the fractional position between ticks
   // Low pass filter effects from the resampler mean that samples before the
   // start time are influenced by resampling the buffer.  mBeginProcessing
   // includes the extent of this filter.  The special value of -TRACK_TICKS_MAX
   // indicates that the resampler has begun processing.
   TrackTicks mBeginProcessing;
   TrackTicks mStop;
   nsRefPtr<ThreadSharedFloatArrayBufferList> mBuffer;
@@ -529,16 +554,34 @@ AudioBufferSourceNode::AudioBufferSource
 
 AudioBufferSourceNode::~AudioBufferSourceNode()
 {
   if (Context()) {
     Context()->UnregisterAudioBufferSourceNode(this);
   }
 }
 
+size_t
+AudioBufferSourceNode::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const
+{
+  size_t amount = AudioNode::SizeOfExcludingThis(aMallocSizeOf);
+  if (mBuffer) {
+    amount += mBuffer->SizeOfIncludingThis(aMallocSizeOf);
+  }
+
+  amount += mPlaybackRate->SizeOfIncludingThis(aMallocSizeOf);
+  return amount;
+}
+
+size_t
+AudioBufferSourceNode::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const
+{
+  return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
+}
+
 JSObject*
 AudioBufferSourceNode::WrapObject(JSContext* aCx)
 {
   return AudioBufferSourceNodeBinding::Wrap(aCx, this);
 }
 
 void
 AudioBufferSourceNode::Start(double aWhen, double aOffset,
--- a/content/media/webaudio/AudioBufferSourceNode.h
+++ b/content/media/webaudio/AudioBufferSourceNode.h
@@ -103,16 +103,24 @@ public:
     SendLoopParametersToStream();
   }
   void SendDopplerShiftToStream(double aDopplerShift);
 
   IMPL_EVENT_HANDLER(ended)
 
   virtual void NotifyMainThreadStateChanged() MOZ_OVERRIDE;
 
+  virtual const char* NodeType() const
+  {
+    return "AudioBufferSourceNode";
+  }
+
+  virtual size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const MOZ_OVERRIDE;
+  virtual size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const MOZ_OVERRIDE;
+
 private:
   friend class AudioBufferSourceNodeEngine;
   // START is sent during Start().
   // STOP is sent during Stop().
   // BUFFERSTART and BUFFEREND are sent when SetBuffer() and Start() have
   // been called (along with sending the buffer).
   enum EngineParameters {
     SAMPLE_RATE,
--- a/content/media/webaudio/AudioDestinationNode.cpp
+++ b/content/media/webaudio/AudioDestinationNode.cpp
@@ -142,16 +142,28 @@ public:
     }
 
     nsRefPtr<OfflineAudioCompletionEvent> event =
         new OfflineAudioCompletionEvent(context, nullptr, nullptr);
     event->InitEvent(renderedBuffer);
     context->DispatchTrustedEvent(event);
   }
 
+  virtual size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const MOZ_OVERRIDE
+  {
+    size_t amount = AudioNodeEngine::SizeOfExcludingThis(aMallocSizeOf);
+    amount += mInputChannels.SizeOfExcludingThis(aMallocSizeOf);
+    return amount;
+  }
+
+  virtual size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const MOZ_OVERRIDE
+  {
+    return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
+  }
+
 private:
   // The input to the destination node is recorded in the mInputChannels buffer.
   // When this buffer fills up with mLength frames, the buffered input is sent
   // to the main thread in order to dispatch OfflineAudioCompletionEvent.
   InputChannels mInputChannels;
   // An index representing the next offset in mInputChannels to be written to.
   uint32_t mWriteIndex;
   // How many frames the OfflineAudioContext intends to produce.
@@ -183,16 +195,21 @@ public:
       mVolume = aParam;
     }
   }
 
   enum Parameters {
     VOLUME,
   };
 
+  virtual size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const MOZ_OVERRIDE
+  {
+    return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
+  }
+
 private:
   float mVolume;
 };
 
 static bool UseAudioChannelService()
 {
   return Preferences::GetBool("media.useAudioChannelService");
 }
@@ -251,16 +268,31 @@ AudioDestinationNode::AudioDestinationNo
                                      /* useCapture = */ true,
                                      /* wantsUntrusted = */ false);
     }
 
     CreateAudioChannelAgent();
   }
 }
 
+size_t
+AudioDestinationNode::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const
+{
+  size_t amount = AudioNode::SizeOfExcludingThis(aMallocSizeOf);
+  // Might be useful in the future:
+  // - mAudioChannelAgent
+  return amount;
+}
+
+size_t
+AudioDestinationNode::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const
+{
+  return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
+}
+
 void
 AudioDestinationNode::DestroyMediaStream()
 {
   if (mAudioChannelAgent && !Context()->IsOffline()) {
     mAudioChannelAgent->StopPlaying();
     mAudioChannelAgent = nullptr;
 
     nsCOMPtr<nsIDOMEventTarget> target = do_QueryInterface(GetOwner());
--- a/content/media/webaudio/AudioDestinationNode.h
+++ b/content/media/webaudio/AudioDestinationNode.h
@@ -69,16 +69,24 @@ public:
 
   // An amount that should be added to the MediaStream's current time to
   // get the AudioContext.currentTime.
   double ExtraCurrentTime();
 
   // When aIsOnlyNode is true, this is the only node for the AudioContext.
   void SetIsOnlyNodeForContext(bool aIsOnlyNode);
 
+  virtual const char* NodeType() const
+  {
+    return "AudioDestinationNode";
+  }
+
+  virtual size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const MOZ_OVERRIDE;
+  virtual size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const MOZ_OVERRIDE;
+
 private:
   bool CheckAudioChannelPermissions(AudioChannel aValue);
   void CreateAudioChannelAgent();
 
   void SetCanPlay(bool aCanPlay);
 
   void NotifyStableState();
   void ScheduleStableStateNotification();
--- a/content/media/webaudio/AudioNode.cpp
+++ b/content/media/webaudio/AudioNode.cpp
@@ -72,16 +72,47 @@ AudioNode::~AudioNode()
   MOZ_ASSERT(mInputNodes.IsEmpty());
   MOZ_ASSERT(mOutputNodes.IsEmpty());
   MOZ_ASSERT(mOutputParams.IsEmpty());
   if (mContext) {
     mContext->UpdateNodeCount(-1);
   }
 }
 
+size_t
+AudioNode::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const
+{
+  // Not owned:
+  // - mContext
+  // - mStream
+  size_t amount = 0;
+
+  amount += mInputNodes.SizeOfExcludingThis(aMallocSizeOf);
+  for (size_t i = 0; i < mInputNodes.Length(); i++) {
+    amount += mInputNodes[i].SizeOfExcludingThis(aMallocSizeOf);
+  }
+
+  // Just measure the array. The entire audio node graph is measured via the
+  // MediaStreamGraph's streams, so we don't want to double-count the elements.
+  amount += mOutputNodes.SizeOfExcludingThis(aMallocSizeOf);
+
+  amount += mOutputParams.SizeOfExcludingThis(aMallocSizeOf);
+  for (size_t i = 0; i < mOutputParams.Length(); i++) {
+    amount += mOutputParams[i]->SizeOfIncludingThis(aMallocSizeOf);
+  }
+
+  return amount;
+}
+
+size_t
+AudioNode::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const
+{
+  return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
+}
+
 template <class InputNode>
 static uint32_t
 FindIndexOfNode(const nsTArray<InputNode>& aInputNodes, const AudioNode* aNode)
 {
   for (uint32_t i = 0; i < aInputNodes.Length(); ++i) {
     if (aInputNodes[i].mInputNode == aNode) {
       return i;
     }
--- a/content/media/webaudio/AudioNode.h
+++ b/content/media/webaudio/AudioNode.h
@@ -10,16 +10,17 @@
 #include "mozilla/DOMEventTargetHelper.h"
 #include "mozilla/dom/AudioNodeBinding.h"
 #include "nsCycleCollectionParticipant.h"
 #include "nsAutoPtr.h"
 #include "nsTArray.h"
 #include "AudioContext.h"
 #include "MediaStreamGraph.h"
 #include "WebAudioUtils.h"
+#include "mozilla/MemoryReporting.h"
 
 namespace mozilla {
 
 namespace dom {
 
 class AudioContext;
 class AudioBufferSourceNode;
 class AudioParam;
@@ -165,16 +166,26 @@ public:
   struct InputNode {
     ~InputNode()
     {
       if (mStreamPort) {
         mStreamPort->Destroy();
       }
     }
 
+    size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const
+    {
+      size_t amount = 0;
+      if (mStreamPort) {
+        amount += mStreamPort->SizeOfIncludingThis(aMallocSizeOf);
+      }
+
+      return amount;
+    }
+
     // Weak reference.
     AudioNode* mInputNode;
     nsRefPtr<MediaInputPort> mStreamPort;
     // The index of the input port this node feeds into.
     // This is not used for connections to AudioParams.
     uint32_t mInputPort;
     // The index of the output port this node comes out of.
     uint32_t mOutputPort;
@@ -203,16 +214,21 @@ public:
   void MarkActive() { Context()->RegisterActiveNode(this); }
   // Active nodes call MarkInactive() when they have finished producing sound
   // for the foreseeable future.
   // Do not call MarkInactive from a node destructor.  If the destructor is
   // called, then the node is already inactive.
   // MarkInactive() may delete |this|.
   void MarkInactive() { Context()->UnregisterActiveNode(this); }
 
+  virtual size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const;
+  virtual size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const;
+
+  virtual const char* NodeType() const = 0;
+
 private:
   friend class AudioBufferSourceNode;
   // This could possibly delete 'this'.
   void DisconnectFromGraph();
 
 protected:
   static void Callback(AudioNode* aNode) { /* not implemented */ }
 
--- a/content/media/webaudio/AudioParam.h
+++ b/content/media/webaudio/AudioParam.h
@@ -148,16 +148,37 @@ public:
     return mInputNodes.AppendElement();
   }
 
   void DisconnectFromGraphAndDestroyStream();
 
   // May create the stream if it doesn't exist
   MediaStream* Stream();
 
+  virtual size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const MOZ_OVERRIDE
+  {
+    size_t amount = AudioParamTimeline::SizeOfExcludingThis(aMallocSizeOf);
+    // Not owned:
+    // - mNode
+
+    // Just count the array, actual nodes are counted in mNode.
+    amount += mInputNodes.SizeOfExcludingThis(aMallocSizeOf);
+
+    if (mNodeStreamPort) {
+      amount += mNodeStreamPort->SizeOfIncludingThis(aMallocSizeOf);
+    }
+
+    return amount;
+  }
+
+  virtual size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const MOZ_OVERRIDE
+  {
+    return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
+  }
+
 protected:
   nsCycleCollectingAutoRefCnt mRefCnt;
   NS_DECL_OWNINGTHREAD
 
 private:
   nsRefPtr<AudioNode> mNode;
   // For every InputNode, there is a corresponding entry in mOutputParams of the
   // InputNode's mInputNode.
--- a/content/media/webaudio/AudioParamTimeline.h
+++ b/content/media/webaudio/AudioParamTimeline.h
@@ -47,16 +47,27 @@ public:
   // Get the value of the AudioParam at time aTime + aCounter.
   // aCounter here is an offset to aTime if we try to get the value in ticks,
   // otherwise it should always be zero.  aCounter is meant to be used when
   // getting the value of an a-rate AudioParam for each tick inside an
   // AudioNodeEngine implementation.
   template<class TimeType>
   float GetValueAtTime(TimeType aTime, size_t aCounter = 0);
 
+  virtual size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const
+  {
+    return mStream ? mStream->SizeOfIncludingThis(aMallocSizeOf) : 0;
+  }
+
+  virtual size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const
+  {
+    return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
+  }
+
+
 private:
   float AudioNodeInputValue(size_t aCounter) const;
 
 protected:
   // This is created lazily when needed.
   nsRefPtr<MediaStream> mStream;
 };
 
--- a/content/media/webaudio/BiquadFilterNode.cpp
+++ b/content/media/webaudio/BiquadFilterNode.cpp
@@ -206,16 +206,32 @@ public:
       SetParamsOnBiquad(mBiquads[i], aStream->SampleRate(), mType, freq, q, gain, detune);
 
       mBiquads[i].process(input,
                           static_cast<float*>(const_cast<void*>(aOutput->mChannelData[i])),
                           aInput.GetDuration());
     }
   }
 
+  virtual size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const MOZ_OVERRIDE
+  {
+    // Not owned:
+    // - mSource - probably not owned
+    // - mDestination - probably not owned
+    // - AudioParamTimelines - counted in the AudioNode
+    size_t amount = AudioNodeEngine::SizeOfExcludingThis(aMallocSizeOf);
+    amount += mBiquads.SizeOfExcludingThis(aMallocSizeOf);
+    return amount;
+  }
+
+  virtual size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const MOZ_OVERRIDE
+  {
+    return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
+  }
+
 private:
   AudioNodeStream* mSource;
   AudioNodeStream* mDestination;
   BiquadFilterType mType;
   AudioParamTimeline mFrequency;
   AudioParamTimeline mDetune;
   AudioParamTimeline mQ;
   AudioParamTimeline mGain;
@@ -237,16 +253,47 @@ BiquadFilterNode::BiquadFilterNode(Audio
   , mGain(new AudioParam(MOZ_THIS_IN_INITIALIZER_LIST(),
                          SendGainToStream, 0.f))
 {
   BiquadFilterNodeEngine* engine = new BiquadFilterNodeEngine(this, aContext->Destination());
   mStream = aContext->Graph()->CreateAudioNodeStream(engine, MediaStreamGraph::INTERNAL_STREAM);
   engine->SetSourceStream(static_cast<AudioNodeStream*> (mStream.get()));
 }
 
+
+size_t
+BiquadFilterNode::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const
+{
+  size_t amount = AudioNode::SizeOfExcludingThis(aMallocSizeOf);
+
+  if (mFrequency) {
+    amount += mFrequency->SizeOfIncludingThis(aMallocSizeOf);
+  }
+
+  if (mDetune) {
+    amount += mDetune->SizeOfIncludingThis(aMallocSizeOf);
+  }
+
+  if (mQ) {
+    amount += mQ->SizeOfIncludingThis(aMallocSizeOf);
+  }
+
+  if (mGain) {
+    amount += mGain->SizeOfIncludingThis(aMallocSizeOf);
+  }
+
+  return amount;
+}
+
+size_t
+BiquadFilterNode::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const
+{
+  return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
+}
+
 JSObject*
 BiquadFilterNode::WrapObject(JSContext* aCx)
 {
   return BiquadFilterNodeBinding::Wrap(aCx, this);
 }
 
 void
 BiquadFilterNode::SetType(BiquadFilterType aType)
--- a/content/media/webaudio/BiquadFilterNode.h
+++ b/content/media/webaudio/BiquadFilterNode.h
@@ -51,16 +51,24 @@ public:
   {
     return mGain;
   }
 
   void GetFrequencyResponse(const Float32Array& aFrequencyHz,
                             const Float32Array& aMagResponse,
                             const Float32Array& aPhaseResponse);
 
+  virtual const char* NodeType() const
+  {
+    return "BiquadFilterNode";
+  }
+
+  virtual size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const MOZ_OVERRIDE;
+  virtual size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const MOZ_OVERRIDE;
+
 private:
   static void SendFrequencyToStream(AudioNode* aNode);
   static void SendDetuneToStream(AudioNode* aNode);
   static void SendQToStream(AudioNode* aNode);
   static void SendGainToStream(AudioNode* aNode);
 
 private:
   BiquadFilterType mType;
--- a/content/media/webaudio/ChannelMergerNode.cpp
+++ b/content/media/webaudio/ChannelMergerNode.cpp
@@ -48,16 +48,21 @@ public:
         AudioBlockCopyChannelWithScale(
             static_cast<const float*>(aInput[i].mChannelData[j]),
             aInput[i].mVolume,
             static_cast<float*>(const_cast<void*>(aOutput[0].mChannelData[channelIndex])));
         ++channelIndex;
       }
     }
   }
+
+  virtual size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const MOZ_OVERRIDE
+  {
+    return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
+  }
 };
 
 ChannelMergerNode::ChannelMergerNode(AudioContext* aContext,
                                      uint16_t aInputCount)
   : AudioNode(aContext,
               2,
               ChannelCountMode::Max,
               ChannelInterpretation::Speakers)
--- a/content/media/webaudio/ChannelMergerNode.h
+++ b/content/media/webaudio/ChannelMergerNode.h
@@ -21,16 +21,26 @@ public:
                     uint16_t aInputCount);
 
   NS_DECL_ISUPPORTS_INHERITED
 
   virtual JSObject* WrapObject(JSContext* aCx) MOZ_OVERRIDE;
 
   virtual uint16_t NumberOfInputs() const MOZ_OVERRIDE { return mInputCount; }
 
+  virtual const char* NodeType() const
+  {
+    return "ChannelMergerNode";
+  }
+
+  virtual size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const MOZ_OVERRIDE
+  {
+    return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
+  }
+
 private:
   const uint16_t mInputCount;
 };
 
 }
 }
 
 #endif
--- a/content/media/webaudio/ChannelSplitterNode.cpp
+++ b/content/media/webaudio/ChannelSplitterNode.cpp
@@ -40,16 +40,21 @@ public:
             aInput[0].mVolume,
             static_cast<float*>(const_cast<void*>(aOutput[i].mChannelData[0])));
       } else {
         // Pad with silent channels if needed
         aOutput[i].SetNull(WEBAUDIO_BLOCK_SIZE);
       }
     }
   }
+
+  virtual size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const MOZ_OVERRIDE
+  {
+    return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
+  }
 };
 
 ChannelSplitterNode::ChannelSplitterNode(AudioContext* aContext,
                                          uint16_t aOutputCount)
   : AudioNode(aContext,
               2,
               ChannelCountMode::Max,
               ChannelInterpretation::Speakers)
--- a/content/media/webaudio/ChannelSplitterNode.h
+++ b/content/media/webaudio/ChannelSplitterNode.h
@@ -21,16 +21,26 @@ public:
                       uint16_t aOutputCount);
 
   NS_DECL_ISUPPORTS_INHERITED
 
   virtual JSObject* WrapObject(JSContext* aCx) MOZ_OVERRIDE;
 
   virtual uint16_t NumberOfOutputs() const MOZ_OVERRIDE { return mOutputCount; }
 
+  virtual const char* NodeType() const
+  {
+    return "ChannelSplitterNode";
+  }
+
+  virtual size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const MOZ_OVERRIDE
+  {
+    return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
+  }
+
 private:
   const uint16_t mOutputCount;
 };
 
 }
 }
 
 #endif
--- a/content/media/webaudio/ConvolverNode.cpp
+++ b/content/media/webaudio/ConvolverNode.cpp
@@ -149,16 +149,35 @@ public:
       mLeftOverData = mBufferLength;
       MOZ_ASSERT(mLeftOverData > 0);
     }
     AllocateAudioBlock(2, aOutput);
 
     mReverb->process(&input, aOutput, WEBAUDIO_BLOCK_SIZE);
   }
 
+  virtual size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const MOZ_OVERRIDE
+  {
+    size_t amount = AudioNodeEngine::SizeOfExcludingThis(aMallocSizeOf);
+    if (mBuffer && !mBuffer->IsShared()) {
+      amount += mBuffer->SizeOfIncludingThis(aMallocSizeOf);
+    }
+
+    if (mReverb) {
+      amount += mReverb->sizeOfIncludingThis(aMallocSizeOf);
+    }
+
+    return amount;
+  }
+
+  virtual size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const MOZ_OVERRIDE
+  {
+    return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
+  }
+
 private:
   nsRefPtr<ThreadSharedFloatArrayBufferList> mBuffer;
   nsAutoPtr<WebCore::Reverb> mReverb;
   int32_t mBufferLength;
   int32_t mLeftOverData;
   float mSampleRate;
   bool mUseBackgroundThreads;
   bool mNormalize;
@@ -170,16 +189,34 @@ ConvolverNode::ConvolverNode(AudioContex
               ChannelCountMode::Clamped_max,
               ChannelInterpretation::Speakers)
   , mNormalize(true)
 {
   ConvolverNodeEngine* engine = new ConvolverNodeEngine(this, mNormalize);
   mStream = aContext->Graph()->CreateAudioNodeStream(engine, MediaStreamGraph::INTERNAL_STREAM);
 }
 
+size_t
+ConvolverNode::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const
+{
+  size_t amount = AudioNode::SizeOfExcludingThis(aMallocSizeOf);
+  if (mBuffer) {
+    // NB: mBuffer might be shared with the associated engine, by convention
+    //     the AudioNode will report.
+    amount += mBuffer->SizeOfIncludingThis(aMallocSizeOf);
+  }
+  return amount;
+}
+
+size_t
+ConvolverNode::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const
+{
+  return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
+}
+
 JSObject*
 ConvolverNode::WrapObject(JSContext* aCx)
 {
   return ConvolverNodeBinding::Wrap(aCx, this);
 }
 
 void
 ConvolverNode::SetBuffer(JSContext* aCx, AudioBuffer* aBuffer, ErrorResult& aRv)
--- a/content/media/webaudio/ConvolverNode.h
+++ b/content/media/webaudio/ConvolverNode.h
@@ -49,16 +49,24 @@ public:
   {
     if (aMode == ChannelCountMode::Max) {
       aRv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
       return;
     }
     AudioNode::SetChannelCountModeValue(aMode, aRv);
   }
 
+  virtual const char* NodeType() const
+  {
+    return "ConvolverNode";
+  }
+
+  virtual size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const MOZ_OVERRIDE;
+  virtual size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const MOZ_OVERRIDE;
+
 private:
   nsRefPtr<AudioBuffer> mBuffer;
   bool mNormalize;
 };
 
 
 } //end namespace dom
 } //end namespace mozilla
--- a/content/media/webaudio/DelayBuffer.cpp
+++ b/content/media/webaudio/DelayBuffer.cpp
@@ -7,16 +7,29 @@
 #include "DelayBuffer.h"
 
 #include "mozilla/PodOperations.h"
 #include "AudioChannelFormat.h"
 #include "AudioNodeEngine.h"
 
 namespace mozilla {
 
+size_t
+DelayBuffer::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const
+{
+  size_t amount = 0;
+  amount += mChunks.SizeOfExcludingThis(aMallocSizeOf);
+  for (size_t i = 0; i < mChunks.Length(); i++) {
+    amount += mChunks[i].SizeOfExcludingThis(aMallocSizeOf, false);
+  }
+
+  amount += mUpmixChannels.SizeOfExcludingThis(aMallocSizeOf);
+  return amount;
+}
+
 void
 DelayBuffer::Write(const AudioChunk& aInputChunk)
 {
   // We must have a reference to the buffer if there are channels
   MOZ_ASSERT(aInputChunk.IsNull() == !aInputChunk.mChannelData.Length());
 #ifdef DEBUG
   MOZ_ASSERT(!mHaveWrittenBlock);
   mHaveWrittenBlock = true;
--- a/content/media/webaudio/DelayBuffer.h
+++ b/content/media/webaudio/DelayBuffer.h
@@ -70,16 +70,18 @@ public:
 
   void Reset() {
     mChunks.Clear();
     mCurrentDelay = -1.0;
   };
 
   int MaxDelayTicks() const { return mMaxDelayTicks; }
 
+  size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const;
+
 private:
   void ReadChannels(const double aPerFrameDelays[WEBAUDIO_BLOCK_SIZE],
                     const AudioChunk* aOutputChunk,
                     uint32_t aFirstChannel, uint32_t aNumChannelsToRead,
                     ChannelInterpretation aChannelInterpretation);
   bool EnsureBuffer();
   int PositionForDelay(int aDelay);
   int ChunkForPosition(int aPosition);
--- a/content/media/webaudio/DelayNode.cpp
+++ b/content/media/webaudio/DelayNode.cpp
@@ -154,16 +154,32 @@ public:
   {
     if (mLeftOverData <= 0) {
       aOutput->SetNull(WEBAUDIO_BLOCK_SIZE);
     } else {
       UpdateOutputBlock(aOutput);
     }
   }
 
+  virtual size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const MOZ_OVERRIDE
+  {
+    size_t amount = AudioNodeEngine::SizeOfExcludingThis(aMallocSizeOf);
+    // Not owned:
+    // - mSource - probably not owned
+    // - mDestination - probably not owned
+    // - mDelay - shares ref with AudioNode, don't count
+    amount += mBuffer.SizeOfExcludingThis(aMallocSizeOf);
+    return amount;
+  }
+
+  virtual size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const MOZ_OVERRIDE
+  {
+    return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
+  }
+
   AudioNodeStream* mSource;
   AudioNodeStream* mDestination;
   AudioParamTimeline mDelay;
   DelayBuffer mBuffer;
   double mMaxDelay;
   TrackTicks mLastOutputPosition;
   // How much data we have in our buffer which needs to be flushed out when our inputs
   // finish.
@@ -180,16 +196,30 @@ DelayNode::DelayNode(AudioContext* aCont
 {
   DelayNodeEngine* engine =
     new DelayNodeEngine(this, aContext->Destination(),
                         aContext->SampleRate() * aMaxDelay);
   mStream = aContext->Graph()->CreateAudioNodeStream(engine, MediaStreamGraph::INTERNAL_STREAM);
   engine->SetSourceStream(static_cast<AudioNodeStream*> (mStream.get()));
 }
 
+size_t
+DelayNode::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const
+{
+  size_t amount = AudioNode::SizeOfExcludingThis(aMallocSizeOf);
+  amount += mDelay->SizeOfIncludingThis(aMallocSizeOf);
+  return amount;
+}
+
+size_t
+DelayNode::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const
+{
+  return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
+}
+
 JSObject*
 DelayNode::WrapObject(JSContext* aCx)
 {
   return DelayNodeBinding::Wrap(aCx, this);
 }
 
 void
 DelayNode::SendDelayToStream(AudioNode* aNode)
--- a/content/media/webaudio/DelayNode.h
+++ b/content/media/webaudio/DelayNode.h
@@ -30,16 +30,24 @@ public:
     return mDelay;
   }
 
   virtual const DelayNode* AsDelayNode() const MOZ_OVERRIDE
   {
     return this;
   }
 
+  virtual const char* NodeType() const
+  {
+    return "DelayNode";
+  }
+
+  virtual size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const MOZ_OVERRIDE;
+  virtual size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const MOZ_OVERRIDE;
+
 private:
   static void SendDelayToStream(AudioNode* aNode);
   friend class DelayNodeEngine;
 
 private:
   nsRefPtr<AudioParam> mDelay;
 };
 
--- a/content/media/webaudio/DynamicsCompressorNode.cpp
+++ b/content/media/webaudio/DynamicsCompressorNode.cpp
@@ -125,16 +125,33 @@ public:
 
     AllocateAudioBlock(channelCount, aOutput);
     mCompressor->process(&aInput, aOutput, aInput.GetDuration());
 
     SendReductionParamToMainThread(aStream,
                                    mCompressor->parameterValue(DynamicsCompressor::ParamReduction));
   }
 
+  virtual size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const MOZ_OVERRIDE
+  {
+    // Not owned:
+    // - mSource (probably)
+    // - mDestination (probably)
+    // - Don't count the AudioParamTimelines, their inner refs are owned by the
+    // AudioNode.
+    size_t amount = AudioNodeEngine::SizeOfExcludingThis(aMallocSizeOf);
+    amount += mCompressor->sizeOfIncludingThis(aMallocSizeOf);
+    return amount;
+  }
+
+  virtual size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const MOZ_OVERRIDE
+  {
+    return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
+  }
+
 private:
   void SendReductionParamToMainThread(AudioNodeStream* aStream, float aReduction)
   {
     MOZ_ASSERT(!NS_IsMainThread());
 
     class Command : public nsRunnable
     {
     public:
@@ -200,16 +217,34 @@ DynamicsCompressorNode::DynamicsCompress
   , mRelease(new AudioParam(MOZ_THIS_IN_INITIALIZER_LIST(),
                             SendReleaseToStream, 0.25f))
 {
   DynamicsCompressorNodeEngine* engine = new DynamicsCompressorNodeEngine(this, aContext->Destination());
   mStream = aContext->Graph()->CreateAudioNodeStream(engine, MediaStreamGraph::INTERNAL_STREAM);
   engine->SetSourceStream(static_cast<AudioNodeStream*> (mStream.get()));
 }
 
+size_t
+DynamicsCompressorNode::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const
+{
+  size_t amount = AudioNode::SizeOfExcludingThis(aMallocSizeOf);
+  amount += mThreshold->SizeOfIncludingThis(aMallocSizeOf);
+  amount += mKnee->SizeOfIncludingThis(aMallocSizeOf);
+  amount += mRatio->SizeOfIncludingThis(aMallocSizeOf);
+  amount += mAttack->SizeOfIncludingThis(aMallocSizeOf);
+  amount += mRelease->SizeOfIncludingThis(aMallocSizeOf);
+  return amount;
+}
+
+size_t
+DynamicsCompressorNode::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const
+{
+  return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
+}
+
 JSObject*
 DynamicsCompressorNode::WrapObject(JSContext* aCx)
 {
   return DynamicsCompressorNodeBinding::Wrap(aCx, this);
 }
 
 void
 DynamicsCompressorNode::SendThresholdToStream(AudioNode* aNode)
--- a/content/media/webaudio/DynamicsCompressorNode.h
+++ b/content/media/webaudio/DynamicsCompressorNode.h
@@ -51,16 +51,24 @@ public:
   }
 
   // Called GetRelease to prevent clashing with the nsISupports::Release name
   AudioParam* GetRelease() const
   {
     return mRelease;
   }
 
+  virtual const char* NodeType() const
+  {
+    return "DynamicsCompressorNode";
+  }
+
+  virtual size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const MOZ_OVERRIDE;
+  virtual size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const MOZ_OVERRIDE;
+
 private:
   static void SendThresholdToStream(AudioNode* aNode);
   static void SendKneeToStream(AudioNode* aNode);
   static void SendRatioToStream(AudioNode* aNode);
   static void SendAttackToStream(AudioNode* aNode);
   static void SendReleaseToStream(AudioNode* aNode);
 
 private:
--- a/content/media/webaudio/FFTBlock.h
+++ b/content/media/webaudio/FFTBlock.h
@@ -123,16 +123,30 @@ public:
   {
     return mOutputBuffer[aIndex].r;
   }
   float ImagData(uint32_t aIndex) const
   {
     return mOutputBuffer[aIndex].i;
   }
 
+  size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const
+  {
+    size_t amount = 0;
+    amount += aMallocSizeOf(mFFT);
+    amount += aMallocSizeOf(mIFFT);
+    amount += mOutputBuffer.SizeOfExcludingThis(aMallocSizeOf);
+    return amount;
+  }
+
+  size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const
+  {
+    return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
+  }
+
 private:
   FFTBlock(const FFTBlock& other) MOZ_DELETE;
   void operator=(const FFTBlock& other) MOZ_DELETE;
 
   void EnsureFFT()
   {
     if (!mFFT) {
       mFFT = kiss_fftr_alloc(mFFTSize, 0, nullptr, nullptr);
--- a/content/media/webaudio/GainNode.cpp
+++ b/content/media/webaudio/GainNode.cpp
@@ -96,16 +96,30 @@ public:
         const float* inputBuffer = static_cast<const float*> (aInput.mChannelData[channel]);
         float* buffer = static_cast<float*> (const_cast<void*>
                           (aOutput->mChannelData[channel]));
         AudioBlockCopyChannelWithScale(inputBuffer, computedGain, buffer);
       }
     }
   }
 
+  virtual size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const MOZ_OVERRIDE
+  {
+    // Not owned:
+    // - mSource (probably)
+    // - mDestination (probably)
+    // - mGain - Internal ref owned by AudioNode
+    return AudioNodeEngine::SizeOfExcludingThis(aMallocSizeOf);
+  }
+
+  virtual size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const MOZ_OVERRIDE
+  {
+    return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
+  }
+
   AudioNodeStream* mSource;
   AudioNodeStream* mDestination;
   AudioParamTimeline mGain;
 };
 
 GainNode::GainNode(AudioContext* aContext)
   : AudioNode(aContext,
               2,
@@ -114,16 +128,30 @@ GainNode::GainNode(AudioContext* aContex
   , mGain(new AudioParam(MOZ_THIS_IN_INITIALIZER_LIST(),
                          SendGainToStream, 1.0f))
 {
   GainNodeEngine* engine = new GainNodeEngine(this, aContext->Destination());
   mStream = aContext->Graph()->CreateAudioNodeStream(engine, MediaStreamGraph::INTERNAL_STREAM);
   engine->SetSourceStream(static_cast<AudioNodeStream*> (mStream.get()));
 }
 
+size_t
+GainNode::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const
+{
+  size_t amount = AudioNode::SizeOfExcludingThis(aMallocSizeOf);
+  amount += mGain->SizeOfIncludingThis(aMallocSizeOf);
+  return amount;
+}
+
+size_t
+GainNode::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const
+{
+  return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
+}
+
 JSObject*
 GainNode::WrapObject(JSContext* aCx)
 {
   return GainNodeBinding::Wrap(aCx, this);
 }
 
 void
 GainNode::SendGainToStream(AudioNode* aNode)
--- a/content/media/webaudio/GainNode.h
+++ b/content/media/webaudio/GainNode.h
@@ -25,16 +25,24 @@ public:
 
   virtual JSObject* WrapObject(JSContext* aCx) MOZ_OVERRIDE;
 
   AudioParam* Gain() const
   {
     return mGain;
   }
 
+  virtual const char* NodeType() const
+  {
+    return "GainNode";
+  }
+
+  virtual size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const MOZ_OVERRIDE;
+  virtual size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const MOZ_OVERRIDE;
+
 private:
   static void SendGainToStream(AudioNode* aNode);
 
 private:
   nsRefPtr<AudioParam> mGain;
 };
 
 }
--- a/content/media/webaudio/MediaElementAudioSourceNode.h
+++ b/content/media/webaudio/MediaElementAudioSourceNode.h
@@ -14,14 +14,24 @@ namespace dom {
 
 class MediaElementAudioSourceNode : public MediaStreamAudioSourceNode
 {
 public:
   MediaElementAudioSourceNode(AudioContext* aContext,
                               DOMMediaStream* aStream);
 
   virtual JSObject* WrapObject(JSContext* aCx) MOZ_OVERRIDE;
+
+  virtual const char* NodeType() const
+  {
+    return "MediaElementAudioSourceNode";
+  }
+
+  virtual size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const MOZ_OVERRIDE
+  {
+    return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
+  }
 };
 
 }
 }
 
 #endif
--- a/content/media/webaudio/MediaStreamAudioDestinationNode.cpp
+++ b/content/media/webaudio/MediaStreamAudioDestinationNode.cpp
@@ -43,16 +43,21 @@ public:
   {
     *aOutput = aInput;
     StreamBuffer::Track* track = mOutputStream->EnsureTrack(MEDIA_STREAM_DEST_TRACK_ID,
                                                             aStream->SampleRate());
     AudioSegment* segment = track->Get<AudioSegment>();
     segment->AppendAndConsumeChunk(aOutput);
   }
 
+  virtual size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const MOZ_OVERRIDE
+  {
+    return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
+  }
+
 private:
   ProcessedMediaStream* mOutputStream;
 };
 
 // This callback is used to ensure that only the audio data for this track is audible
 static bool FilterAudioNodeStreamTrack(StreamBuffer::Track* aTrack)
 {
   return aTrack->GetID() == MEDIA_STREAM_DEST_TRACK_ID;
@@ -76,16 +81,32 @@ MediaStreamAudioDestinationNode::MediaSt
   mPort = tus->AllocateInputPort(mStream, 0);
 
   nsIDocument* doc = aContext->GetParentObject()->GetExtantDoc();
   if (doc) {
     mDOMStream->CombineWithPrincipal(doc->NodePrincipal());
   }
 }
 
+size_t
+MediaStreamAudioDestinationNode::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const
+{
+  // Future:
+  // - mDOMStream
+  size_t amount = AudioNode::SizeOfExcludingThis(aMallocSizeOf);
+  amount += mPort->SizeOfIncludingThis(aMallocSizeOf);
+  return amount;
+}
+
+size_t
+MediaStreamAudioDestinationNode::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const
+{
+  return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
+}
+
 void
 MediaStreamAudioDestinationNode::DestroyMediaStream()
 {
   AudioNode::DestroyMediaStream();
   if (mPort) {
     mPort->Destroy();
     mPort = nullptr;
   }
--- a/content/media/webaudio/MediaStreamAudioDestinationNode.h
+++ b/content/media/webaudio/MediaStreamAudioDestinationNode.h
@@ -29,16 +29,24 @@ public:
 
   virtual void DestroyMediaStream() MOZ_OVERRIDE;
 
   DOMMediaStream* DOMStream() const
   {
     return mDOMStream;
   }
 
+  virtual const char* NodeType() const
+  {
+    return "MediaStreamAudioDestinationNode";
+  }
+
+  virtual size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const MOZ_OVERRIDE;
+  virtual size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const MOZ_OVERRIDE;
+
 private:
   nsRefPtr<DOMMediaStream> mDOMStream;
   nsRefPtr<MediaInputPort> mPort;
 };
 
 }
 }
 
--- a/content/media/webaudio/MediaStreamAudioSourceNode.cpp
+++ b/content/media/webaudio/MediaStreamAudioSourceNode.cpp
@@ -44,16 +44,32 @@ MediaStreamAudioSourceNode::MediaStreamA
                                                MediaInputPort::FLAG_BLOCK_INPUT);
   mInputStream->AddConsumerToKeepAlive(this);
 }
 
 MediaStreamAudioSourceNode::~MediaStreamAudioSourceNode()
 {
 }
 
+size_t
+MediaStreamAudioSourceNode::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const
+{
+  // Future:
+  // - mInputStream
+  size_t amount = AudioNode::SizeOfExcludingThis(aMallocSizeOf);
+  amount += mInputPort->SizeOfIncludingThis(aMallocSizeOf);
+  return amount;
+}
+
+size_t
+MediaStreamAudioSourceNode::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const
+{
+  return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
+}
+
 void
 MediaStreamAudioSourceNode::DestroyMediaStream()
 {
   if (mInputPort) {
     mInputPort->Destroy();
     mInputPort = nullptr;
   }
   AudioNode::DestroyMediaStream();
--- a/content/media/webaudio/MediaStreamAudioSourceNode.h
+++ b/content/media/webaudio/MediaStreamAudioSourceNode.h
@@ -26,16 +26,24 @@ public:
   NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(MediaStreamAudioSourceNode, AudioNode)
 
   virtual JSObject* WrapObject(JSContext* aCx) MOZ_OVERRIDE;
 
   virtual void DestroyMediaStream() MOZ_OVERRIDE;
 
   virtual uint16_t NumberOfInputs() const MOZ_OVERRIDE { return 0; }
 
+  virtual const char* NodeType() const
+  {
+    return "MediaStreamAudioSourceNode";
+  }
+
+  virtual size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const MOZ_OVERRIDE;
+  virtual size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const MOZ_OVERRIDE;
+
 private:
   nsRefPtr<MediaInputPort> mInputPort;
   nsRefPtr<DOMMediaStream> mInputStream;
 };
 
 }
 }
 
--- a/content/media/webaudio/OscillatorNode.cpp
+++ b/content/media/webaudio/OscillatorNode.cpp
@@ -458,16 +458,42 @@ public:
         ComputeCustom(output, ticks, start, end);
         break;
       default:
         ComputeSilence(aOutput);
     };
 
   }
 
+  virtual size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const MOZ_OVERRIDE
+  {
+    size_t amount = AudioNodeEngine::SizeOfExcludingThis(aMallocSizeOf);
+
+    // Not owned:
+    // - mSource
+    // - mDestination
+    // - mFrequency (internal ref owned by node)
+    // - mDetune (internal ref owned by node)
+
+    if (mCustom) {
+      amount += mCustom->SizeOfIncludingThis(aMallocSizeOf);
+    }
+
+    if (mPeriodicWave) {
+      amount += mPeriodicWave->sizeOfIncludingThis(aMallocSizeOf);
+    }
+
+    return amount;
+  }
+
+  virtual size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const MOZ_OVERRIDE
+  {
+    return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
+  }
+
   DCBlocker mDCBlocker;
   AudioNodeStream* mSource;
   AudioNodeStream* mDestination;
   TrackTicks mStart;
   TrackTicks mStop;
   AudioParamTimeline mFrequency;
   AudioParamTimeline mDetune;
   OscillatorType mType;
@@ -505,16 +531,34 @@ OscillatorNode::OscillatorNode(AudioCont
   engine->SetSourceStream(static_cast<AudioNodeStream*> (mStream.get()));
   mStream->AddMainThreadListener(this);
 }
 
 OscillatorNode::~OscillatorNode()
 {
 }
 
+size_t
+OscillatorNode::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const
+{
+  size_t amount = AudioNode::SizeOfExcludingThis(aMallocSizeOf);
+
+  // For now only report if we know for sure that it's not shared.
+  amount += mPeriodicWave->SizeOfExcludingThisIfNotShared(aMallocSizeOf);
+  amount += mFrequency->SizeOfIncludingThis(aMallocSizeOf);
+  amount += mDetune->SizeOfIncludingThis(aMallocSizeOf);
+  return amount;
+}
+
+size_t
+OscillatorNode::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const
+{
+  return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
+}
+
 JSObject*
 OscillatorNode::WrapObject(JSContext* aCx)
 {
   return OscillatorNodeBinding::Wrap(aCx, this);
 }
 
 void
 OscillatorNode::SendFrequencyToStream(AudioNode* aNode)
--- a/content/media/webaudio/OscillatorNode.h
+++ b/content/media/webaudio/OscillatorNode.h
@@ -112,16 +112,24 @@ public:
     mType = OscillatorType::Custom;
     SendTypeToStream();
   }
 
   IMPL_EVENT_HANDLER(ended)
 
   virtual void NotifyMainThreadStateChanged() MOZ_OVERRIDE;
 
+  virtual const char* NodeType() const
+  {
+    return "OscillatorNode";
+  }
+
+  virtual size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const MOZ_OVERRIDE;
+  virtual size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const MOZ_OVERRIDE;
+
 private:
   static void SendFrequencyToStream(AudioNode* aNode);
   static void SendDetuneToStream(AudioNode* aNode);
   void SendTypeToStream();
   void SendPeriodicWaveToStream();
 
 private:
   OscillatorType mType;
--- a/content/media/webaudio/PannerNode.cpp
+++ b/content/media/webaudio/PannerNode.cpp
@@ -183,16 +183,31 @@ public:
 
   void EqualPowerPanningFunction(const AudioChunk& aInput, AudioChunk* aOutput);
   void HRTFPanningFunction(const AudioChunk& aInput, AudioChunk* aOutput);
 
   float LinearGainFunction(float aDistance);
   float InverseGainFunction(float aDistance);
   float ExponentialGainFunction(float aDistance);
 
+  virtual size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const MOZ_OVERRIDE
+  {
+    size_t amount = AudioNodeEngine::SizeOfExcludingThis(aMallocSizeOf);
+    if (mHRTFPanner) {
+      amount += mHRTFPanner->sizeOfIncludingThis(aMallocSizeOf);
+    }
+
+    return amount;
+  }
+
+  virtual size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const MOZ_OVERRIDE
+  {
+    return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
+  }
+
   nsAutoPtr<HRTFPanner> mHRTFPanner;
   typedef void (PannerNodeEngine::*PanningModelFunction)(const AudioChunk& aInput, AudioChunk* aOutput);
   PanningModelFunction mPanningModelFunction;
   typedef float (PannerNodeEngine::*DistanceModelFunction)(float aDistance);
   DistanceModelFunction mDistanceModelFunction;
   ThreeDPoint mPosition;
   ThreeDPoint mOrientation;
   ThreeDPoint mVelocity;
@@ -237,16 +252,30 @@ PannerNode::PannerNode(AudioContext* aCo
 
 PannerNode::~PannerNode()
 {
   if (Context()) {
     Context()->UnregisterPannerNode(this);
   }
 }
 
+size_t
+PannerNode::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const
+{
+  size_t amount = AudioNode::SizeOfExcludingThis(aMallocSizeOf);
+  amount += mSources.SizeOfExcludingThis(aMallocSizeOf);
+  return amount;
+}
+
+size_t
+PannerNode::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const
+{
+  return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
+}
+
 JSObject*
 PannerNode::WrapObject(JSContext* aCx)
 {
   return PannerNodeBinding::Wrap(aCx, this);
 }
 
 void PannerNode::DestroyMediaStream()
 {
--- a/content/media/webaudio/PannerNode.h
+++ b/content/media/webaudio/PannerNode.h
@@ -240,16 +240,24 @@ public:
     SendDoubleParameterToStream(CONE_OUTER_GAIN, mConeOuterGain);
   }
 
   float ComputeDopplerShift();
   void SendDopplerToSourcesIfNeeded();
   void FindConnectedSources();
   void FindConnectedSources(AudioNode* aNode, nsTArray<AudioBufferSourceNode*>& aSources, std::set<AudioNode*>& aSeenNodes);
 
+  virtual const char* NodeType() const
+  {
+    return "PannerNode";
+  }
+
+  virtual size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const MOZ_OVERRIDE;
+  virtual size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const MOZ_OVERRIDE;
+
 private:
   friend class AudioListener;
   friend class PannerNodeEngine;
   enum EngineParameters {
     LISTENER_POSITION,
     LISTENER_FRONT_VECTOR, // unit length
     LISTENER_RIGHT_VECTOR, // unit length, orthogonal to LISTENER_FRONT_VECTOR
     LISTENER_VELOCITY,
--- a/content/media/webaudio/PeriodicWave.cpp
+++ b/content/media/webaudio/PeriodicWave.cpp
@@ -39,16 +39,29 @@ PeriodicWave::PeriodicWave(AudioContext*
     return;
   }
   PodCopy(buffer, aRealData, aLength);
   mCoefficients->SetData(0, buffer, buffer);
   PodCopy(buffer+aLength, aImagData, aLength);
   mCoefficients->SetData(1, nullptr, buffer+aLength);
 }
 
+size_t
+PeriodicWave::SizeOfExcludingThisIfNotShared(MallocSizeOf aMallocSizeOf) const
+{
+  // Not owned:
+  // - mContext
+  size_t amount = 0;
+  if (!mCoefficients->IsShared()) {
+    amount += mCoefficients->SizeOfIncludingThis(aMallocSizeOf);
+  }
+
+  return amount;
+}
+
 JSObject*
 PeriodicWave::WrapObject(JSContext* aCx)
 {
   return PeriodicWaveBinding::Wrap(aCx, this);
 }
 
 } // namespace dom
 } // namespace mozilla
--- a/content/media/webaudio/PeriodicWave.h
+++ b/content/media/webaudio/PeriodicWave.h
@@ -42,16 +42,18 @@ public:
     return mLength;
   }
 
   ThreadSharedFloatArrayBufferList* GetThreadSharedBuffer() const
   {
     return mCoefficients;
   }
 
+  size_t SizeOfExcludingThisIfNotShared(MallocSizeOf aMallocSizeOf) const;
+
 private:
   nsRefPtr<AudioContext> mContext;
   nsRefPtr<ThreadSharedFloatArrayBufferList> mCoefficients;
   uint32_t mLength;
 };
 
 }
 }
--- a/content/media/webaudio/ScriptProcessorNode.cpp
+++ b/content/media/webaudio/ScriptProcessorNode.cpp
@@ -33,17 +33,29 @@ class SharedBuffers
 private:
   class OutputQueue
   {
   public:
     explicit OutputQueue(const char* aName)
       : mMutex(aName)
     {}
 
-    Mutex& Lock() { return mMutex; }
+    size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const
+    {
+      mMutex.AssertCurrentThreadOwns();
+
+      size_t amount = 0;
+      for (size_t i = 0; i < mBufferList.size(); i++) {
+        amount += mBufferList[i].SizeOfExcludingThis(aMallocSizeOf, false);
+      }
+
+      return amount;
+    }
+
+    Mutex& Lock() const { return const_cast<OutputQueue*>(this)->mMutex; }
 
     size_t ReadyToConsume() const
     {
       mMutex.AssertCurrentThreadOwns();
       MOZ_ASSERT(!NS_IsMainThread());
       return mBufferList.size();
     }
 
@@ -90,16 +102,28 @@ public:
     : mOutputQueue("SharedBuffers::outputQueue")
     , mDelaySoFar(TRACK_TICKS_MAX)
     , mSampleRate(aSampleRate)
     , mLatency(0.0)
     , mDroppingBuffers(false)
   {
   }
 
+  size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const
+  {
+    size_t amount = aMallocSizeOf(this);
+
+    {
+      MutexAutoLock lock(mOutputQueue.Lock());
+      amount += mOutputQueue.SizeOfExcludingThis(aMallocSizeOf);
+    }
+
+    return amount;
+  }
+
   // main thread
   void FinishProducingOutputBuffer(ThreadSharedFloatArrayBufferList* aBuffer,
                                    uint32_t aBufferSize)
   {
     MOZ_ASSERT(NS_IsMainThread());
 
     TimeStamp now = TimeStamp::Now();
 
@@ -288,16 +312,36 @@ public:
     if (mInputWriteIndex >= mBufferSize) {
       SendBuffersToMainThread(aStream);
       mInputWriteIndex -= mBufferSize;
       mSeenNonSilenceInput = false;
       AllocateInputBlock();
     }
   }
 
+  virtual size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const MOZ_OVERRIDE
+  {
+    // Not owned:
+    // - mSharedBuffers
+    // - mSource (probably)
+    // - mDestination (probably)
+    size_t amount = AudioNodeEngine::SizeOfExcludingThis(aMallocSizeOf);
+    amount += mInputChannels.SizeOfExcludingThis(aMallocSizeOf);
+    for (size_t i = 0; i < mInputChannels.Length(); i++) {
+      amount += mInputChannels[i].SizeOfExcludingThis(aMallocSizeOf);
+    }
+
+    return amount;
+  }
+
+  virtual size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const MOZ_OVERRIDE
+  {
+    return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
+  }
+
 private:
   void AllocateInputBlock()
   {
     for (unsigned i = 0; i < mInputChannels.Length(); ++i) {
       if (!mInputChannels[i]) {
         mInputChannels[i] = new float[mBufferSize];
       }
     }
@@ -445,16 +489,30 @@ ScriptProcessorNode::ScriptProcessorNode
   mStream = aContext->Graph()->CreateAudioNodeStream(engine, MediaStreamGraph::INTERNAL_STREAM);
   engine->SetSourceStream(static_cast<AudioNodeStream*> (mStream.get()));
 }
 
 ScriptProcessorNode::~ScriptProcessorNode()
 {
 }
 
+size_t
+ScriptProcessorNode::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const
+{
+  size_t amount = AudioNode::SizeOfExcludingThis(aMallocSizeOf);
+  amount += mSharedBuffers->SizeOfIncludingThis(aMallocSizeOf);
+  return amount;
+}
+
+size_t
+ScriptProcessorNode::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const
+{
+  return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
+}
+
 JSObject*
 ScriptProcessorNode::WrapObject(JSContext* aCx)
 {
   return ScriptProcessorNodeBinding::Wrap(aCx, this);
 }
 
 }
 }
--- a/content/media/webaudio/ScriptProcessorNode.h
+++ b/content/media/webaudio/ScriptProcessorNode.h
@@ -84,16 +84,24 @@ public:
 
   uint32_t NumberOfOutputChannels() const
   {
     return mNumberOfOutputChannels;
   }
 
   using DOMEventTargetHelper::DispatchTrustedEvent;
 
+  virtual const char* NodeType() const
+  {
+    return "ScriptProcessorNode";
+  }
+
+  virtual size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const MOZ_OVERRIDE;
+  virtual size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const MOZ_OVERRIDE;
+
 private:
   nsAutoPtr<SharedBuffers> mSharedBuffers;
   const uint32_t mBufferSize;
   const uint32_t mNumberOfOutputChannels;
 };
 
 }
 }
--- a/content/media/webaudio/WaveShaperNode.cpp
+++ b/content/media/webaudio/WaveShaperNode.cpp
@@ -125,16 +125,26 @@ public:
 
     WebAudioUtils::SpeexResamplerProcess(mDownSampler, aChannel,
                                          inputData, &inSamples,
                                          aOutputData, &outSamples);
 
     MOZ_ASSERT(inSamples == WEBAUDIO_BLOCK_SIZE*aBlocks && outSamples == WEBAUDIO_BLOCK_SIZE);
   }
 
+  size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const
+  {
+    size_t amount = 0;
+    // Future: properly measure speex memory
+    amount += aMallocSizeOf(mUpSampler);
+    amount += aMallocSizeOf(mDownSampler);
+    amount += mBuffer.SizeOfExcludingThis(aMallocSizeOf);
+    return amount;
+  }
+
 private:
   void Destroy()
   {
     if (mUpSampler) {
       speex_resampler_destroy(mUpSampler);
       mUpSampler = nullptr;
     }
     if (mDownSampler) {
@@ -239,16 +249,29 @@ public:
         mResampler.DownSample(i, outputBuffer, 4);
         break;
       default:
         NS_NOTREACHED("We should never reach here");
       }
     }
   }
 
+  virtual size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const MOZ_OVERRIDE
+  {
+    size_t amount = AudioNodeEngine::SizeOfExcludingThis(aMallocSizeOf);
+    amount += mCurve.SizeOfExcludingThis(aMallocSizeOf);
+    amount += mResampler.SizeOfExcludingThis(aMallocSizeOf);
+    return amount;
+  }
+
+  virtual size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const MOZ_OVERRIDE
+  {
+    return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
+  }
+
 private:
   nsTArray<float> mCurve;
   OverSampleType mType;
   Resampler mResampler;
 };
 
 WaveShaperNode::WaveShaperNode(AudioContext* aContext)
   : AudioNode(aContext,
--- a/content/media/webaudio/WaveShaperNode.h
+++ b/content/media/webaudio/WaveShaperNode.h
@@ -34,16 +34,33 @@ public:
   void SetCurve(const Nullable<Float32Array>& aData);
 
   OverSampleType Oversample() const
   {
     return mType;
   }
   void SetOversample(OverSampleType aType);
 
+  virtual size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const MOZ_OVERRIDE
+  {
+    // Possibly track in the future:
+    // - mCurve
+    return AudioNode::SizeOfExcludingThis(aMallocSizeOf);
+  }
+
+  virtual const char* NodeType() const
+  {
+    return "WaveShaperNode";
+  }
+
+  virtual size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const MOZ_OVERRIDE
+  {
+    return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
+  }
+
 private:
   void ClearCurve();
 
 private:
   JS::Heap<JSObject*> mCurve;
   OverSampleType mType;
 };
 
--- a/content/media/webaudio/blink/DirectConvolver.h
+++ b/content/media/webaudio/blink/DirectConvolver.h
@@ -25,27 +25,36 @@
  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
 
 #ifndef DirectConvolver_h
 #define DirectConvolver_h
 
 #include "nsTArray.h"
+#include "mozilla/MemoryReporting.h"
 
 namespace WebCore {
 
 class DirectConvolver {
 public:
     DirectConvolver(size_t inputBlockSize);
 
     void process(const nsTArray<float>* convolutionKernel, const float* sourceP, float* destP, size_t framesToProcess);
 
     void reset();
 
+    size_t sizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const
+    {
+        size_t amount = aMallocSizeOf(this);
+        amount += m_buffer.SizeOfExcludingThis(aMallocSizeOf);
+        return amount;
+    }
+
+
 private:
     size_t m_inputBlockSize;
 
     nsTArray<float> m_buffer;
 };
 
 } // namespace WebCore
 
--- a/content/media/webaudio/blink/DynamicsCompressor.cpp
+++ b/content/media/webaudio/blink/DynamicsCompressor.cpp
@@ -47,16 +47,39 @@ DynamicsCompressor::DynamicsCompressor(f
     m_lastFilterStageRatio = -1;
     m_lastAnchor = -1;
     m_lastFilterStageGain = -1;
 
     setNumberOfChannels(numberOfChannels);
     initializeParameters();
 }
 
+size_t DynamicsCompressor::sizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const
+{
+    size_t amount = aMallocSizeOf(this);
+    amount += m_preFilterPacks.SizeOfExcludingThis(aMallocSizeOf);
+    for (size_t i = 0; i < m_preFilterPacks.Length(); i++) {
+        if (m_preFilterPacks[i]) {
+            amount += m_preFilterPacks[i]->sizeOfIncludingThis(aMallocSizeOf);
+        }
+    }
+
+    amount += m_postFilterPacks.SizeOfExcludingThis(aMallocSizeOf);
+    for (size_t i = 0; i < m_postFilterPacks.Length(); i++) {
+        if (m_postFilterPacks[i]) {
+            amount += m_postFilterPacks[i]->sizeOfIncludingThis(aMallocSizeOf);
+        }
+    }
+
+    amount += m_sourceChannels.SizeOfExcludingThis(aMallocSizeOf);
+    amount += m_destinationChannels.SizeOfExcludingThis(aMallocSizeOf);
+    amount += m_compressor.sizeOfExcludingThis(aMallocSizeOf);
+    return amount;
+}
+
 void DynamicsCompressor::setParameterValue(unsigned parameterID, float value)
 {
     MOZ_ASSERT(parameterID < ParamLast);
     if (parameterID < ParamLast)
         m_parameters[parameterID] = value;
 }
 
 void DynamicsCompressor::initializeParameters()
--- a/content/media/webaudio/blink/DynamicsCompressor.h
+++ b/content/media/webaudio/blink/DynamicsCompressor.h
@@ -29,16 +29,17 @@
 #ifndef DynamicsCompressor_h
 #define DynamicsCompressor_h
 
 #include "DynamicsCompressorKernel.h"
 #include "ZeroPole.h"
 
 #include "nsTArray.h"
 #include "nsAutoPtr.h"
+#include "mozilla/MemoryReporting.h"
 
 namespace mozilla {
 struct AudioChunk;
 }
 
 namespace WebCore {
 
 using mozilla::AudioChunk;
@@ -81,32 +82,38 @@ public:
     float parameterValue(unsigned parameterID);
 
     float sampleRate() const { return m_sampleRate; }
     float nyquist() const { return m_sampleRate / 2; }
 
     double tailTime() const { return 0; }
     double latencyTime() const { return m_compressor.latencyFrames() / static_cast<double>(sampleRate()); }
 
+    size_t sizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const;
+
 protected:
     unsigned m_numberOfChannels;
 
     // m_parameters holds the tweakable compressor parameters.
     float m_parameters[ParamLast];
     void initializeParameters();
 
     float m_sampleRate;
 
     // Emphasis filter controls.
     float m_lastFilterStageRatio;
     float m_lastAnchor;
     float m_lastFilterStageGain;
 
     typedef struct {
         ZeroPole filters[4];
+        size_t sizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const
+        {
+            return aMallocSizeOf(this);
+        }
     } ZeroPoleFilterPack4;
 
     // Per-channel emphasis filters.
     nsTArray<nsAutoPtr<ZeroPoleFilterPack4> > m_preFilterPacks;
     nsTArray<nsAutoPtr<ZeroPoleFilterPack4> > m_postFilterPacks;
 
     nsAutoArrayPtr<const float*> m_sourceChannels;
     nsAutoArrayPtr<float*> m_destinationChannels;
--- a/content/media/webaudio/blink/DynamicsCompressorKernel.cpp
+++ b/content/media/webaudio/blink/DynamicsCompressorKernel.cpp
@@ -68,16 +68,27 @@ DynamicsCompressorKernel::DynamicsCompre
 
     // Initializes most member variables
     reset();
 
     m_meteringReleaseK =
         static_cast<float>(WebAudioUtils::DiscreteTimeConstantForSampleRate(meteringReleaseTimeConstant, sampleRate));
 }
 
+size_t DynamicsCompressorKernel::sizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const
+{
+    size_t amount = 0;
+    amount += m_preDelayBuffers.SizeOfExcludingThis(aMallocSizeOf);
+    for (size_t i = 0; i < m_preDelayBuffers.Length(); i++) {
+        amount += m_preDelayBuffers[i].SizeOfExcludingThis(aMallocSizeOf);
+    }
+
+    return amount;
+}
+
 void DynamicsCompressorKernel::setNumberOfChannels(unsigned numberOfChannels)
 {
     if (m_preDelayBuffers.Length() == numberOfChannels)
         return;
 
     m_preDelayBuffers.Clear();
     for (unsigned i = 0; i < numberOfChannels; ++i)
         m_preDelayBuffers.AppendElement(new float[MaxPreDelayFrames]);
--- a/content/media/webaudio/blink/DynamicsCompressorKernel.h
+++ b/content/media/webaudio/blink/DynamicsCompressorKernel.h
@@ -26,16 +26,17 @@
  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
 
 #ifndef DynamicsCompressorKernel_h
 #define DynamicsCompressorKernel_h
 
 #include "nsTArray.h"
 #include "nsAutoPtr.h"
+#include "mozilla/MemoryReporting.h"
 
 namespace WebCore {
 
 class DynamicsCompressorKernel {
 public:
     DynamicsCompressorKernel(float sampleRate, unsigned numberOfChannels);
 
     void setNumberOfChannels(unsigned);
@@ -64,16 +65,18 @@ public:
     void reset();
 
     unsigned latencyFrames() const { return m_lastPreDelayFrames; }
 
     float sampleRate() const { return m_sampleRate; }
 
     float meteringGain() const { return m_meteringGain; }
 
+    size_t sizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const;
+
 protected:
     float m_sampleRate;
 
     float m_detectorAverage;
     float m_compressorGain;
 
     // Metering
     float m_meteringReleaseK;
--- a/content/media/webaudio/blink/FFTConvolver.cpp
+++ b/content/media/webaudio/blink/FFTConvolver.cpp
@@ -40,16 +40,31 @@ FFTConvolver::FFTConvolver(size_t fftSiz
   m_inputBuffer.SetLength(fftSize);
   PodZero(m_inputBuffer.Elements(), fftSize);
   m_outputBuffer.SetLength(fftSize);
   PodZero(m_outputBuffer.Elements(), fftSize);
   m_lastOverlapBuffer.SetLength(fftSize / 2);
   PodZero(m_lastOverlapBuffer.Elements(), fftSize / 2);
 }
 
+size_t FFTConvolver::sizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const
+{
+    size_t amount = 0;
+    amount += m_frame.SizeOfExcludingThis(aMallocSizeOf);
+    amount += m_inputBuffer.SizeOfExcludingThis(aMallocSizeOf);
+    amount += m_outputBuffer.SizeOfExcludingThis(aMallocSizeOf);
+    amount += m_lastOverlapBuffer.SizeOfExcludingThis(aMallocSizeOf);
+    return amount;
+}
+
+size_t FFTConvolver::sizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const
+{
+  return aMallocSizeOf(this) + sizeOfExcludingThis(aMallocSizeOf);
+}
+
 void FFTConvolver::process(FFTBlock* fftKernel, const float* sourceP, float* destP, size_t framesToProcess)
 {
     size_t halfSize = fftSize() / 2;
 
     // framesToProcess must be an exact multiple of halfSize,
     // or halfSize is a multiple of framesToProcess when halfSize > framesToProcess.
     bool isGood = !(halfSize % framesToProcess && framesToProcess % halfSize);
     MOZ_ASSERT(isGood);
--- a/content/media/webaudio/blink/FFTConvolver.h
+++ b/content/media/webaudio/blink/FFTConvolver.h
@@ -26,16 +26,17 @@
  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
 
 #ifndef FFTConvolver_h
 #define FFTConvolver_h
 
 #include "nsTArray.h"
 #include "mozilla/FFTBlock.h"
+#include "mozilla/MemoryReporting.h"
 
 namespace WebCore {
 
 typedef nsTArray<float> AudioFloatArray;
 using mozilla::FFTBlock;
 
 class FFTConvolver {
 public:
@@ -52,16 +53,19 @@ public:
     //
     // Processing in-place is allowed...
     void process(FFTBlock* fftKernel, const float* sourceP, float* destP, size_t framesToProcess);
 
     void reset();
 
     size_t fftSize() const { return m_frame.FFTSize(); }
 
+    size_t sizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const;
+    size_t sizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const;
+
 private:
     FFTBlock m_frame;
 
     // Buffer input until we get fftSize / 2 samples then do an FFT
     size_t m_readWriteIndex;
     AudioFloatArray m_inputBuffer;
 
     // Stores output which we read a little at a time
--- a/content/media/webaudio/blink/HRTFDatabase.cpp
+++ b/content/media/webaudio/blink/HRTFDatabase.cpp
@@ -74,16 +74,27 @@ HRTFDatabase::HRTFDatabase(float sampleR
                 float x = static_cast<float>(jj) / static_cast<float>(InterpolationFactor);
                 m_elevations[i + jj] = HRTFElevation::createByInterpolatingSlices(m_elevations[i].get(), m_elevations[j].get(), x, sampleRate);
                 MOZ_ASSERT(m_elevations[i + jj].get());
             }
         }
     }
 }
 
+size_t HRTFDatabase::sizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const
+{
+    size_t amount = aMallocSizeOf(this);
+    amount += m_elevations.SizeOfExcludingThis(aMallocSizeOf);
+    for (size_t i = 0; i < m_elevations.Length(); i++) {
+      amount += m_elevations[i]->sizeOfIncludingThis(aMallocSizeOf);
+    }
+
+    return amount;
+}
+
 void HRTFDatabase::getKernelsFromAzimuthElevation(double azimuthBlend, unsigned azimuthIndex, double elevationAngle, HRTFKernel* &kernelL, HRTFKernel* &kernelR,
                                                   double& frameDelayL, double& frameDelayR)
 {
     unsigned elevationIndex = indexFromElevationAngle(elevationAngle);
     MOZ_ASSERT(elevationIndex < m_elevations.Length() && m_elevations.Length() > 0);
     
     if (!m_elevations.Length()) {
         kernelL = 0;
--- a/content/media/webaudio/blink/HRTFDatabase.h
+++ b/content/media/webaudio/blink/HRTFDatabase.h
@@ -27,16 +27,17 @@
  */
 
 #ifndef HRTFDatabase_h
 #define HRTFDatabase_h
 
 #include "HRTFElevation.h"
 #include "nsAutoRef.h"
 #include "nsTArray.h"
+#include "mozilla/MemoryReporting.h"
 
 namespace WebCore {
 
 class HRTFKernel;
 
 class HRTFDatabase {
 public:
     static nsReturnRef<HRTFDatabase> create(float sampleRate);
@@ -50,16 +51,18 @@ public:
     // Returns the number of different azimuth angles.
     static unsigned numberOfAzimuths() { return HRTFElevation::NumberOfTotalAzimuths; }
 
     float sampleRate() const { return m_sampleRate; }
 
     // Number of elevations loaded from resource.
     static const unsigned NumberOfRawElevations;
 
+    size_t sizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const;
+
 private:
     HRTFDatabase(const HRTFDatabase& other) MOZ_DELETE;
     void operator=(const HRTFDatabase& other) MOZ_DELETE;
 
     explicit HRTFDatabase(float sampleRate);
 
     // Minimum and maximum elevation angles (inclusive) for a HRTFDatabase.
     static const int MinElevation;
--- a/content/media/webaudio/blink/HRTFDatabaseLoader.cpp
+++ b/content/media/webaudio/blink/HRTFDatabaseLoader.cpp
@@ -83,16 +83,30 @@ HRTFDatabaseLoader::~HRTFDatabaseLoader(
         s_loaderMap->RemoveEntry(m_databaseSampleRate);
         if (s_loaderMap->Count() == 0) {
             delete s_loaderMap;
             s_loaderMap = nullptr;
         }
     }
 }
 
+size_t HRTFDatabaseLoader::sizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const
+{
+    size_t amount = aMallocSizeOf(this);
+
+    // NB: Need to make sure we're not competing with the loader thread.
+    const_cast<HRTFDatabaseLoader*>(this)->waitForLoaderThreadCompletion();
+
+    if (m_hrtfDatabase) {
+        amount += m_hrtfDatabase->sizeOfIncludingThis(aMallocSizeOf);
+    }
+
+    return amount;
+}
+
 class HRTFDatabaseLoader::ProxyReleaseEvent MOZ_FINAL : public nsRunnable {
 public:
     explicit ProxyReleaseEvent(HRTFDatabaseLoader* loader) : mLoader(loader) {}
     NS_IMETHOD Run() MOZ_OVERRIDE
     {
         mLoader->MainThreadRelease();
         return NS_OK;
     }
--- a/content/media/webaudio/blink/HRTFDatabaseLoader.h
+++ b/content/media/webaudio/blink/HRTFDatabaseLoader.h
@@ -26,16 +26,17 @@
  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
 
 #ifndef HRTFDatabaseLoader_h
 #define HRTFDatabaseLoader_h
 
 #include "nsHashKeys.h"
 #include "mozilla/RefPtr.h"
+#include "mozilla/MemoryReporting.h"
 #include "mozilla/Mutex.h"
 #include "HRTFDatabase.h"
 
 template <class EntryType> class nsTHashtable;
 template <class T> class nsAutoRef;
 
 namespace WebCore {
 
@@ -88,16 +89,18 @@ public:
 
     float databaseSampleRate() const { return m_databaseSampleRate; }
 
     static void shutdown();
     
     // Called in asynchronous loading thread.
     void load();
 
+    size_t sizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const;
+
 private:
     // Both constructor and destructor must be called from the main thread.
     explicit HRTFDatabaseLoader(float sampleRate);
     ~HRTFDatabaseLoader();
     
     void ProxyRelease(); // any thread
     void MainThreadRelease(); // main thread only
     class ProxyReleaseEvent;
--- a/content/media/webaudio/blink/HRTFElevation.cpp
+++ b/content/media/webaudio/blink/HRTFElevation.cpp
@@ -45,16 +45,28 @@ const int numberOfElevations = MOZ_ARRAY
 
 const unsigned HRTFElevation::NumberOfTotalAzimuths = 360 / 15 * 8;
 
 const int rawSampleRate = irc_composite_c_r0195_sample_rate;
 
 // Number of frames in an individual impulse response.
 const size_t ResponseFrameSize = 256;
 
+size_t HRTFElevation::sizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const
+{
+    size_t amount = aMallocSizeOf(this);
+
+    amount += m_kernelListL.SizeOfExcludingThis(aMallocSizeOf);
+    for (size_t i = 0; i < m_kernelListL.Length(); i++) {
+        amount += m_kernelListL[i]->sizeOfIncludingThis(aMallocSizeOf);
+    }
+
+    return amount;
+}
+
 size_t HRTFElevation::fftSizeForSampleRate(float sampleRate)
 {
     // The IRCAM HRTF impulse responses were 512 sample-frames @44.1KHz,
     // but these have been truncated to 256 samples.
     // An FFT-size of twice impulse response size is used (for convolution).
     // So for sample rates of 44.1KHz an FFT size of 512 is good.
     // We double the FFT-size only for sample rates at least double this.
     // If the FFT size is too large then the impulse response will be padded
--- a/content/media/webaudio/blink/HRTFElevation.h
+++ b/content/media/webaudio/blink/HRTFElevation.h
@@ -26,16 +26,17 @@
  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
 
 #ifndef HRTFElevation_h
 #define HRTFElevation_h
 
 #include "HRTFKernel.h"
 #include "nsAutoRef.h"
+#include "mozilla/MemoryReporting.h"
 
 struct SpeexResamplerState_;
 typedef struct SpeexResamplerState_ SpeexResamplerState;
 
 namespace WebCore {
 
 // HRTFElevation contains all of the HRTFKernels (one left ear and one right ear per azimuth angle) for a particular elevation.
 
@@ -58,16 +59,18 @@ public:
     // The interpolated delays based on azimuthBlend: 0 -> 1 are returned in frameDelayL and frameDelayR.
     void getKernelsFromAzimuth(double azimuthBlend, unsigned azimuthIndex, HRTFKernel* &kernelL, HRTFKernel* &kernelR, double& frameDelayL, double& frameDelayR);
     
     // Total number of azimuths after interpolation.
     static const unsigned NumberOfTotalAzimuths;
 
     static size_t fftSizeForSampleRate(float sampleRate);
 
+    size_t sizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const;
+
 private:
     HRTFElevation(const HRTFElevation& other) MOZ_DELETE;
     void operator=(const HRTFElevation& other) MOZ_DELETE;
 
     HRTFElevation(HRTFKernelList *kernelListL, int elevation, float sampleRate)
         : m_elevationAngle(elevation)
         , m_sampleRate(sampleRate)
     {
--- a/content/media/webaudio/blink/HRTFKernel.h
+++ b/content/media/webaudio/blink/HRTFKernel.h
@@ -28,16 +28,17 @@
 
 #ifndef HRTFKernel_h
 #define HRTFKernel_h
 
 #include "nsAutoPtr.h"
 #include "nsAutoRef.h"
 #include "nsTArray.h"
 #include "mozilla/FFTBlock.h"
+#include "mozilla/MemoryReporting.h"
 
 namespace WebCore {
 
 using mozilla::FFTBlock;
 
 // HRTF stands for Head-Related Transfer Function.
 // HRTFKernel is a frequency-domain representation of an impulse-response used as part of the spatialized panning system.
 // For a given azimuth / elevation angle there will be one HRTFKernel for the left ear transfer function, and one for the right ear.
@@ -59,16 +60,23 @@ public:
     FFTBlock* fftFrame() { return m_fftFrame.get(); }
     
     size_t fftSize() const { return m_fftFrame->FFTSize(); }
     float frameDelay() const { return m_frameDelay; }
 
     float sampleRate() const { return m_sampleRate; }
     double nyquist() const { return 0.5 * sampleRate(); }
 
+    size_t sizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const
+    {
+        size_t amount = aMallocSizeOf(this);
+        amount += m_fftFrame->SizeOfIncludingThis(aMallocSizeOf);
+        return amount;
+    }
+
 private:
     HRTFKernel(const HRTFKernel& other) MOZ_DELETE;
     void operator=(const HRTFKernel& other) MOZ_DELETE;
 
     // Note: this is destructive on the passed in |impulseResponse|.
     HRTFKernel(float* impulseResponse, size_t fftSize, float sampleRate);
     
     HRTFKernel(nsAutoPtr<FFTBlock> fftFrame, float frameDelay, float sampleRate)
--- a/content/media/webaudio/blink/HRTFPanner.cpp
+++ b/content/media/webaudio/blink/HRTFPanner.cpp
@@ -65,16 +65,37 @@ HRTFPanner::HRTFPanner(float sampleRate,
     m_tempR2.SetLength(RenderingQuantum);
 }
 
 HRTFPanner::~HRTFPanner()
 {
     MOZ_COUNT_DTOR(HRTFPanner);
 }
 
+size_t HRTFPanner::sizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const
+{
+    size_t amount = aMallocSizeOf(this);
+
+    if (m_databaseLoader) {
+        m_databaseLoader->sizeOfIncludingThis(aMallocSizeOf);
+    }
+
+    amount += m_convolverL1.sizeOfExcludingThis(aMallocSizeOf);
+    amount += m_convolverR1.sizeOfExcludingThis(aMallocSizeOf);
+    amount += m_convolverL2.sizeOfExcludingThis(aMallocSizeOf);
+    amount += m_convolverR2.sizeOfExcludingThis(aMallocSizeOf);
+    amount += m_delayLine.SizeOfExcludingThis(aMallocSizeOf);
+    amount += m_tempL1.SizeOfExcludingThis(aMallocSizeOf);
+    amount += m_tempL2.SizeOfExcludingThis(aMallocSizeOf);
+    amount += m_tempR1.SizeOfExcludingThis(aMallocSizeOf);
+    amount += m_tempR2.SizeOfExcludingThis(aMallocSizeOf);
+
+    return amount;
+}
+
 void HRTFPanner::reset()
 {
     m_azimuthIndex1 = UninitializedAzimuth;
     m_azimuthIndex2 = UninitializedAzimuth;
     // m_elevation1 and m_elevation2 are initialized in pan()
     m_crossfadeSelection = CrossfadeSelection1;
     m_crossfadeX = 0.0f;
     m_crossfadeIncr = 0.0f;
--- a/content/media/webaudio/blink/HRTFPanner.h
+++ b/content/media/webaudio/blink/HRTFPanner.h
@@ -22,16 +22,17 @@
  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
 
 #ifndef HRTFPanner_h
 #define HRTFPanner_h
 
 #include "FFTConvolver.h"
 #include "DelayBuffer.h"
+#include "mozilla/MemoryReporting.h"
 
 namespace mozilla {
 struct AudioChunk;
 }
 
 namespace WebCore {
 
 class HRTFDatabaseLoader;
@@ -48,16 +49,18 @@ public:
     void reset();
 
     size_t fftSize() const { return m_convolverL1.fftSize(); }
 
     float sampleRate() const { return m_sampleRate; }
 
     int maxTailFrames() const;
 
+    size_t sizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const;
+
 private:
     // Given an azimuth angle in the range -180 -> +180, returns the corresponding azimuth index for the database,
     // and azimuthBlend which is an interpolation value from 0 -> 1.
     int calculateDesiredAzimuthIndexAndBlend(double azimuth, double& azimuthBlend);
 
     mozilla::RefPtr<HRTFDatabaseLoader> m_databaseLoader;
 
     float m_sampleRate;
--- a/content/media/webaudio/blink/PeriodicWave.cpp
+++ b/content/media/webaudio/blink/PeriodicWave.cpp
@@ -90,16 +90,30 @@ PeriodicWave::PeriodicWave(float sampleR
     , m_numberOfRanges(NumberOfRanges)
     , m_centsPerRange(CentsPerRange)
 {
     float nyquist = 0.5 * m_sampleRate;
     m_lowestFundamentalFrequency = nyquist / maxNumberOfPartials();
     m_rateScale = m_periodicWaveSize / m_sampleRate;
 }
 
+size_t PeriodicWave::sizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const
+{
+    size_t amount = aMallocSizeOf(this);
+
+    amount += m_bandLimitedTables.SizeOfExcludingThis(aMallocSizeOf);
+    for (size_t i = 0; i < m_bandLimitedTables.Length(); i++) {
+        if (m_bandLimitedTables[i]) {
+            amount += m_bandLimitedTables[i]->SizeOfIncludingThis(aMallocSizeOf);
+        }
+    }
+
+    return amount;
+}
+
 void PeriodicWave::waveDataForFundamentalFrequency(float fundamentalFrequency, float* &lowerWaveData, float* &higherWaveData, float& tableInterpolationFactor)
 {
     // Negative frequencies are allowed, in which case we alias
     // to the positive frequency.
     fundamentalFrequency = fabsf(fundamentalFrequency);
 
     // Calculate the pitch range.
     float ratio = fundamentalFrequency > 0 ? fundamentalFrequency / m_lowestFundamentalFrequency : 0.5;
--- a/content/media/webaudio/blink/PeriodicWave.h
+++ b/content/media/webaudio/blink/PeriodicWave.h
@@ -27,16 +27,17 @@
  */
 
 #ifndef PeriodicWave_h
 #define PeriodicWave_h
 
 #include "mozilla/dom/OscillatorNodeBinding.h"
 #include <nsAutoPtr.h>
 #include <nsTArray.h>
+#include "mozilla/MemoryReporting.h"
 
 namespace WebCore {
 
 typedef nsTArray<float> AudioFloatArray;
 
 class PeriodicWave {
 public:
     static PeriodicWave* createSine(float sampleRate);
@@ -63,16 +64,18 @@ public:
 
     // Returns the scalar multiplier to the oscillator frequency to calculate
     // wave buffer phase increment.
     float rateScale() const { return m_rateScale; }
 
     unsigned periodicWaveSize() const { return m_periodicWaveSize; }
     float sampleRate() const { return m_sampleRate; }
 
+    size_t sizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const;
+
 private:
     explicit PeriodicWave(float sampleRate);
 
     void generateBasicWaveform(mozilla::dom::OscillatorType);
 
     float m_sampleRate;
     unsigned m_periodicWaveSize;
     unsigned m_numberOfRanges;
--- a/content/media/webaudio/blink/Reverb.cpp
+++ b/content/media/webaudio/blink/Reverb.cpp
@@ -100,16 +100,31 @@ Reverb::Reverb(ThreadSharedFloatArrayBuf
             }
         }
     }
 
     initialize(irChannels, impulseResponseBufferLength, renderSliceSize,
                maxFFTSize, numberOfChannels, useBackgroundThreads);
 }
 
+size_t Reverb::sizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const
+{
+    size_t amount = aMallocSizeOf(this);
+    amount += m_convolvers.SizeOfExcludingThis(aMallocSizeOf);
+    for (size_t i = 0; i < m_convolvers.Length(); i++) {
+        if (m_convolvers[i]) {
+            amount += m_convolvers[i]->sizeOfIncludingThis(aMallocSizeOf);
+        }
+    }
+
+    amount += m_tempBuffer.SizeOfExcludingThis(aMallocSizeOf, false);
+    return amount;
+}
+
+
 void Reverb::initialize(const nsTArray<const float*>& impulseResponseBuffer,
                         size_t impulseResponseBufferLength, size_t renderSliceSize,
                         size_t maxFFTSize, size_t numberOfChannels, bool useBackgroundThreads)
 {
     m_impulseResponseLength = impulseResponseBufferLength;
 
     // The reverb can handle a mono impulse response and still do stereo processing
     size_t numResponseChannels = impulseResponseBuffer.Length();
--- a/content/media/webaudio/blink/Reverb.h
+++ b/content/media/webaudio/blink/Reverb.h
@@ -28,16 +28,17 @@
 
 #ifndef Reverb_h
 #define Reverb_h
 
 #include "ReverbConvolver.h"
 #include "nsAutoPtr.h"
 #include "nsTArray.h"
 #include "AudioSegment.h"
+#include "mozilla/MemoryReporting.h"
 
 namespace mozilla {
 class ThreadSharedFloatArrayBufferList;
 }
 
 namespace WebCore {
 
 class DirectConvolver;
@@ -53,16 +54,18 @@ public:
     Reverb(mozilla::ThreadSharedFloatArrayBufferList* impulseResponseBuffer, size_t impulseResponseBufferLength, size_t renderSliceSize, size_t maxFFTSize, size_t numberOfChannels, bool useBackgroundThreads, bool normalize, float sampleRate);
 
     void process(const mozilla::AudioChunk* sourceBus, mozilla::AudioChunk* destinationBus, size_t framesToProcess);
     void reset();
 
     size_t impulseResponseLength() const { return m_impulseResponseLength; }
     size_t latencyFrames() const;
 
+    size_t sizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const;
+
 private:
     void initialize(const nsTArray<const float*>& impulseResponseBuffer, size_t impulseResponseBufferLength, size_t renderSliceSize, size_t maxFFTSize, size_t numberOfChannels, bool useBackgroundThreads);
 
     size_t m_impulseResponseLength;
 
     nsTArray<nsAutoPtr<ReverbConvolver> > m_convolvers;
 
     // For "True" stereo processing
--- a/content/media/webaudio/blink/ReverbAccumulationBuffer.h
+++ b/content/media/webaudio/blink/ReverbAccumulationBuffer.h
@@ -25,16 +25,17 @@
  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
 
 #ifndef ReverbAccumulationBuffer_h
 #define ReverbAccumulationBuffer_h
 
 #include "nsTArray.h"
+#include "mozilla/MemoryReporting.h"
 
 namespace WebCore {
 
 typedef nsTArray<float> AudioFloatArray;
 
 // ReverbAccumulationBuffer is a circular delay buffer with one client reading from it and multiple clients
 // writing/accumulating to it at different delay offsets from the read position.  The read operation will zero the memory
 // just read from the buffer, so it will be ready for accumulation the next time around.
@@ -53,16 +54,21 @@ public:
 
     size_t readIndex() const { return m_readIndex; }
     void updateReadIndex(int* readIndex, size_t numberOfFrames) const;
 
     size_t readTimeFrame() const { return m_readTimeFrame; }
 
     void reset();
 
+    size_t sizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const
+    {
+        return m_buffer.SizeOfExcludingThis(aMallocSizeOf);
+    }
+
 private:
     AudioFloatArray m_buffer;
     size_t m_readIndex;
     size_t m_readTimeFrame; // for debugging (frame on continuous timeline)
 };
 
 } // namespace WebCore
 
--- a/content/media/webaudio/blink/ReverbConvolver.cpp
+++ b/content/media/webaudio/blink/ReverbConvolver.cpp
@@ -146,16 +146,45 @@ ReverbConvolver::~ReverbConvolver()
             m_moreInputBuffered = true;
             m_backgroundThreadCondition.Signal();
         }
 
         m_backgroundThread.Stop();
     }
 }
 
+size_t ReverbConvolver::sizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const
+{
+    size_t amount = aMallocSizeOf(this);
+    amount += m_stages.SizeOfExcludingThis(aMallocSizeOf);
+    for (size_t i = 0; i < m_stages.Length(); i++) {
+        if (m_stages[i]) {
+            amount += m_stages[i]->sizeOfIncludingThis(aMallocSizeOf);
+        }
+    }
+
+    amount += m_backgroundStages.SizeOfExcludingThis(aMallocSizeOf);
+    for (size_t i = 0; i < m_backgroundStages.Length(); i++) {
+        if (m_backgroundStages[i]) {
+            amount += m_backgroundStages[i]->sizeOfIncludingThis(aMallocSizeOf);
+        }
+    }
+
+    // NB: The buffer sizes are static, so even though they might be accessed
+    //     in another thread it's safe to measure them.
+    amount += m_accumulationBuffer.sizeOfExcludingThis(aMallocSizeOf);
+    amount += m_inputBuffer.sizeOfExcludingThis(aMallocSizeOf);
+
+    // Possible future measurements:
+    // - m_backgroundThread
+    // - m_backgroundThreadLock
+    // - m_backgroundThreadCondition
+    return amount;
+}
+
 void ReverbConvolver::backgroundThreadEntry()
 {
     while (!m_wantsToExit) {
         // Wait for realtime thread to give us more input
         m_moreInputBuffered = false;
         {
             AutoLock locker(m_backgroundThreadLock);
             while (!m_moreInputBuffered && !m_wantsToExit)
--- a/content/media/webaudio/blink/ReverbConvolver.h
+++ b/content/media/webaudio/blink/ReverbConvolver.h
@@ -27,16 +27,17 @@
  */
 
 #ifndef ReverbConvolver_h
 #define ReverbConvolver_h
 
 #include "ReverbAccumulationBuffer.h"
 #include "ReverbInputBuffer.h"
 #include "nsAutoPtr.h"
+#include "mozilla/MemoryReporting.h"
 #ifdef LOG
 #undef LOG
 #endif
 #include "base/condition_variable.h"
 #include "base/lock.h"
 #include "base/thread.h"
 
 namespace WebCore {
@@ -60,16 +61,18 @@ public:
     size_t impulseResponseLength() const { return m_impulseResponseLength; }
 
     ReverbInputBuffer* inputBuffer() { return &m_inputBuffer; }
 
     bool useBackgroundThreads() const { return m_useBackgroundThreads; }
     void backgroundThreadEntry();
 
     size_t latencyFrames() const;
+
+    size_t sizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const;
 private:
     nsTArray<nsAutoPtr<ReverbConvolverStage> > m_stages;
     nsTArray<nsAutoPtr<ReverbConvolverStage> > m_backgroundStages;
     size_t m_impulseResponseLength;
 
     ReverbAccumulationBuffer m_accumulationBuffer;
 
     // One or more background threads read from this input buffer which is fed from the realtime thread.
--- a/content/media/webaudio/blink/ReverbConvolverStage.cpp
+++ b/content/media/webaudio/blink/ReverbConvolverStage.cpp
@@ -82,16 +82,39 @@ ReverbConvolverStage::ReverbConvolverSta
     m_framesProcessed = 0; // total frames processed so far
 
     size_t delayBufferSize = m_preDelayLength < fftSize ? fftSize : m_preDelayLength;
     delayBufferSize = delayBufferSize < renderSliceSize ? renderSliceSize : delayBufferSize;
     m_preDelayBuffer.SetLength(delayBufferSize);
     PodZero(m_preDelayBuffer.Elements(), m_preDelayBuffer.Length());
 }
 
+size_t ReverbConvolverStage::sizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const
+{
+    size_t amount = aMallocSizeOf(this);
+
+    if (m_fftKernel) {
+        amount += m_fftKernel->SizeOfIncludingThis(aMallocSizeOf);
+    }
+
+    if (m_fftConvolver) {
+        amount += m_fftConvolver->sizeOfIncludingThis(aMallocSizeOf);
+    }
+
+    amount += m_preDelayBuffer.SizeOfExcludingThis(aMallocSizeOf);
+    amount += m_temporaryBuffer.SizeOfExcludingThis(aMallocSizeOf);
+    amount += m_directKernel.SizeOfExcludingThis(aMallocSizeOf);
+
+    if (m_directConvolver) {
+        amount += m_directConvolver->sizeOfIncludingThis(aMallocSizeOf);
+    }
+
+    return amount;
+}
+
 void ReverbConvolverStage::processInBackground(ReverbConvolver* convolver, size_t framesToProcess)
 {
     ReverbInputBuffer* inputBuffer = convolver->inputBuffer();
     float* source = inputBuffer->directReadFrom(&m_inputReadIndex, framesToProcess);
     process(source, framesToProcess);
 }
 
 void ReverbConvolverStage::process(const float* source, size_t framesToProcess)
--- a/content/media/webaudio/blink/ReverbConvolverStage.h
+++ b/content/media/webaudio/blink/ReverbConvolverStage.h
@@ -29,16 +29,17 @@
 #ifndef ReverbConvolverStage_h
 #define ReverbConvolverStage_h
 
 #include "DirectConvolver.h"
 #include "FFTConvolver.h"
 
 #include "nsTArray.h"
 #include "mozilla/FFTBlock.h"
+#include "mozilla/MemoryReporting.h"
 
 namespace WebCore {
 
 using mozilla::FFTBlock;
 
 class ReverbAccumulationBuffer;
 class ReverbConvolver;
 
@@ -55,16 +56,18 @@ public:
 
     void processInBackground(ReverbConvolver* convolver, size_t framesToProcess);
 
     void reset();
 
     // Useful for background processing
     int inputReadIndex() const { return m_inputReadIndex; }
 
+    size_t sizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const;
+
 private:
     nsAutoPtr<FFTBlock> m_fftKernel;
     nsAutoPtr<FFTConvolver> m_fftConvolver;
 
     nsTArray<float> m_preDelayBuffer;
 
     ReverbAccumulationBuffer* m_accumulationBuffer;
     int m_accumulationReadIndex;
--- a/content/media/webaudio/blink/ReverbInputBuffer.h
+++ b/content/media/webaudio/blink/ReverbInputBuffer.h
@@ -25,16 +25,17 @@
  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
 
 #ifndef ReverbInputBuffer_h
 #define ReverbInputBuffer_h
 
 #include "nsTArray.h"
+#include "mozilla/MemoryReporting.h"
 
 namespace WebCore {
 
 // ReverbInputBuffer is used to buffer input samples for deferred processing by the background threads.
 class ReverbInputBuffer {
 public:
     ReverbInputBuffer(size_t length);
 
@@ -49,16 +50,22 @@ public:
     // The individual background threads read here (and hope that they can keep up with the buffer writing).
     // readIndex is updated with the next readIndex to read from...
     // The assumption is that the buffer's length is evenly divisible by numberOfFrames.
     // FIXME: remove numberOfFrames restriction...
     float* directReadFrom(int* readIndex, size_t numberOfFrames);
 
     void reset();
 
+    size_t sizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const
+    {
+        return m_buffer.SizeOfExcludingThis(aMallocSizeOf);
+    }
+
+
 private:
     nsTArray<float> m_buffer;
     size_t m_writeIndex;
 };
 
 } // namespace WebCore
 
 #endif // ReverbInputBuffer_h
--- a/content/svg/content/src/SVGMotionSMILAnimationFunction.cpp
+++ b/content/svg/content/src/SVGMotionSMILAnimationFunction.cpp
@@ -372,30 +372,26 @@ SVGMotionSMILAnimationFunction::CheckKey
   if (!HasAttr(nsGkAtoms::keyPoints))
     return;
 
   // attribute is ignored for calcMode="paced" (even if it's got errors)
   if (GetCalcMode() == CALC_PACED) {
     SetKeyPointsErrorFlag(false);
   }
 
-  if (mKeyPoints.IsEmpty()) {
-    // keyPoints attr is set, but array is empty => it failed preliminary checks
+  if (mKeyPoints.Length() != mKeyTimes.Length()) {
+    // there must be exactly as many keyPoints as keyTimes
     SetKeyPointsErrorFlag(true);
     return;
   }
 
   // Nothing else to check -- we can catch all keyPoints errors elsewhere.
   // -  Formatting & range issues will be caught in SetKeyPoints, and will
   //  result in an empty mKeyPoints array, which will drop us into the error
   //  case above.
-  // -  Number-of-entries issues will be caught in CheckKeyTimes (and flagged
-  //  as a problem with |keyTimes|), since we use our keyPoints entries to
-  //  populate the "values" list, and that list's count gets passed to
-  //  CheckKeyTimes.
 }
 
 nsresult
 SVGMotionSMILAnimationFunction::SetKeyPoints(const nsAString& aKeyPoints,
                                              nsAttrValue& aResult)
 {
   mKeyPoints.Clear();
   aResult.SetTo(aKeyPoints);
--- a/dom/apps/tests/test_app_update.html
+++ b/dom/apps/tests/test_app_update.html
@@ -13,17 +13,16 @@ https://bugzilla.mozilla.org/show_bug.cg
   /** Test for Bug 826058 **/
 
   SimpleTest.waitForExplicitFinish();
 
   var gBaseURL = 'http://test/tests/dom/apps/tests/';
   var gHostedManifestURL = gBaseURL + 'file_app.sjs?apptype=hosted&getmanifest=true';
   var gCachedManifestURL = gBaseURL + 'file_app.sjs?apptype=cached&getmanifest=true';
   var gGenerator = runTest();
-  var launchableValue = undefined;
 
   function go() {
     SpecialPowers.pushPermissions(
       [{ "type": "browser", "allow": 1, "context": document },
        { "type": "embed-apps", "allow": 1, "context": document },
        { "type": "webapps-manage", "allow": 1, "context": document }],
       function() { gGenerator.next() });
   }
@@ -51,17 +50,17 @@ https://bugzilla.mozilla.org/show_bug.cg
   function xhrAbort(url) {
     ok(false, "XHR abort loading " + url);
     finish();
   }
 
   function runTest() {
     // Set up.
 
-    launchableValue = SpecialPowers.setAllAppsLaunchable(true);
+    SpecialPowers.setAllAppsLaunchable(true);
     SpecialPowers.setBoolPref("dom.mozBrowserFramesEnabled", true);
 
     // Test Bug 927699 - navigator.mozApps.install(url) lets NS_ERROR_FAILURE
     //                   onto the web
     var request = navigator.mozApps.install("");
     request.onerror = function() {
       ok(request.error.name == "INVALID_URL", "Got expected INVALID_URL");
       continueTest();
@@ -269,17 +268,16 @@ https://bugzilla.mozilla.org/show_bug.cg
   function getAppURL(app) {
     if (!app)
       return gBaseURL + "file_app.sjs?apptype=hosted";
     return app.origin + app.manifest.launch_path;
   }
 
   function finish() {
     SpecialPowers.clearUserPref("dom.mozBrowserFramesEnabled");
-    //SpecialPowers.setAllAppsLaunchable(launchableValue);
     SimpleTest.finish();
   }
 
   function doReload() {
     window.location.reload(true);
   }
 
   </script>
--- a/dom/apps/tests/test_bug_795164.html
+++ b/dom/apps/tests/test_bug_795164.html
@@ -13,40 +13,39 @@ https://bugzilla.mozilla.org/show_bug.cg
   /** Test for Bug 795164 **/
 
   SimpleTest.waitForExplicitFinish();
 
   var url1 = 'http://test1.example.com/tests/dom/apps/tests/file_app.sjs?apptype=hosted&getmanifest=true';
   var url2 = 'http://test2.example.com/tests/dom/apps/tests/file_app.sjs?apptype=hosted&getmanifest=true';
 
   var gGenerator = runTest();
-  var launchableValue = undefined;
 
   function go() {
     SpecialPowers.pushPermissions(
       [{ "type": "webapps-manage", "allow": 1, "context": document }],
       function() { gGenerator.next() });
   }
 
   function continueTest() {
     try {
       gGenerator.next();
     } catch (e if e instanceof StopIteration) {
-      finish();
+      SimpleTest.finish();
     }
   }
 
   function mozAppsError() {
     ok(false, "mozApps error: " + this.error.name);
-    finish();
+    SimpleTest.finish();
   }
 
   function runTest() {
     // Set up.
-    launchableValue = SpecialPowers.setAllAppsLaunchable(true);
+    SpecialPowers.setAllAppsLaunchable(true);
     SpecialPowers.autoConfirmAppInstall(continueTest);
     yield undefined;
 
     // Keeping track of the number of times `mozApps.mgmt.onuninstall` gets triggered
     let uninstallCount = 0;
 
     navigator.mozApps.mgmt.onuninstall = function() {
       uninstallCount++;
@@ -87,21 +86,16 @@ https://bugzilla.mozilla.org/show_bug.cg
       continueTest();
     };
     yield undefined;
 
     is(uninstallCount, 2, "mgmt.onuninstall got triggered only twice");
 
     navigator.mozApps.mgmt.onuninstall = null;
   }
-
-  function finish() {
-    SpecialPowers.setAllAppsLaunchable(launchableValue);
-    SimpleTest.finish();
-  }
   </script>
 </head>
 <body onload="go()">
 <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=795164">Mozilla Bug 795164</a>
 <p id="display"></p>
 <div id="content" style="display: none">
 
 </div>
--- a/dom/apps/tests/test_install_receipts.html
+++ b/dom/apps/tests/test_install_receipts.html
@@ -47,17 +47,17 @@ function finish() {
 function cbError(aError) {
   ok(false, "Error callback invoked " + aError);
   finish();
 }
 
 SimpleTest.waitForExplicitFinish();
 
 function runTest() {
-  launchableValue = SpecialPowers.setAllAppsLaunchable(true);
+  SpecialPowers.setAllAppsLaunchable(true);
 
   SpecialPowers.autoConfirmAppInstall(continueTest);
   yield undefined;
 
   // Test install with three valid receipts
   let valid_receipt1 = 'eyJhbGciOiAiSFMyNTYiLCAidHlwIjogIkpXVCJ9.eyJwcm9kdWN0IjogeyJ1cmwiOiAiaHR0cHM6Ly93d3cubW96aWxsYS5vcmciLCAic3RvcmVkYXRhIjogIjUxNjkzMTQzNTYifSwgInJlaXNzdWUiOiAiaHR0cDovL21vY2hpLnRlc3Q6ODg4OC9yZWlzc3VlLzUxNjkzMTQzNTYiLCAidXNlciI6IHsidHlwZSI6ICJkaXJlY3RlZC1pZGVudGlmaWVyIiwgInZhbHVlIjogIjRmYjM1MTUxLTJiOWItNGJhMi04MjgzLWM0OWQzODE2NDBiZCJ9LCAidmVyaWZ5IjogImh0dHA6Ly9tb2NoaS50ZXN0Ojg4ODgvdmVyaWZ5LzUxNjkzMTQzNTYiLCAiaXNzIjogImh0dHA6Ly9tb2NoaS50ZXN0Ojg4ODgiLCAiaWF0IjogMTMxMzYwMTg4LCAidHlwIjogInB1cmNoYXNlLXJlY2VpcHQiLCAibmJmIjogMTMxMzYwMTg1LCAiZGV0YWlsIjogImh0dHA6Ly9tb2NoaS50ZXN0Ojg4ODgvcmVjZWlwdC81MTY5MzE0MzU2In0.eZpTEnCLUR3iP3rm9WyJOqx1k66mQaAxqcrvX11r5E0';
 
   let valid_receipt2 = 'eyJhbGciOiAiSFMyNTYiLCAidHlwIjogIkpXVCJ9.eyJwcm9kdWN0IjogeyJ1cmwiOiAiaHR0cHM6Ly93d3cubW96aWxsYS5vcmciLCAic3RvcmVkYXRhIjogIjUxNjkzMTQzNTcifSwgInJlaXNzdWUiOiAiaHR0cDovL21vY2hpLnRlc3Q6ODg4OC9yZWlzc3VlLzUxNjkzMTQzNTYiLCAidXNlciI6IHsidHlwZSI6ICJkaXJlY3RlZC1pZGVudGlmaWVyIiwgInZhbHVlIjogIjRmYjM1MTUxLTJiOWItNGJhMi04MjgzLWM0OWQzODE2NDBiZCJ9LCAidmVyaWZ5IjogImh0dHA6Ly9tb2NoaS50ZXN0Ojg4ODgvdmVyaWZ5LzUxNjkzMTQzNTYiLCAiaXNzIjogImh0dHA6Ly9tb2NoaS50ZXN0Ojg4ODgiLCAiaWF0IjogMTMxMzYwMTg4LCAidHlwIjogInB1cmNoYXNlLXJlY2VpcHQiLCAibmJmIjogMTMxMzYwMTg1LCAiZGV0YWlsIjogImh0dHA6Ly9tb2NoaS50ZXN0Ojg4ODgvcmVjZWlwdC81MTY5MzE0MzU2In0.k7tI0PTaMJf0w0keAHJR6couypGY-EtA38q2xOtSv6k';
--- a/dom/apps/tests/test_operator_app_install.js
+++ b/dom/apps/tests/test_operator_app_install.js
@@ -136,17 +136,16 @@ function next() {
   }
 }
 
 function go() {
   next();
 }
 
 function finish() {
-  SpecialPowers.setBoolPref('dom.mozApps.auto_confirm_install', false);
   SimpleTest.finish();
 }
 
 function mozAppsError() {
   ok(false, "mozApps error: " + this.error.name);
   finish();
 }
 
--- a/dom/apps/tests/test_packaged_app_common.js
+++ b/dom/apps/tests/test_packaged_app_common.js
@@ -1,16 +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/. */
 
 var PackagedTestHelper = (function PackagedTestHelper() {
   "use strict";
 
-  var launchableValue;
   var steps;
   var index = -1;
   var gSJSPath = "tests/dom/apps/tests/file_packaged_app.sjs";
   var gSJS = "http://test/" + gSJSPath;
   var gAppName = "appname";
   var gApp = null;
   var gInstallOrigin = "http://mochi.test:8888";
   var timeoutID;
@@ -47,17 +46,16 @@ var PackagedTestHelper = (function Packa
   function start() {
     next();
   }
 
   function finish() {
     if (timeoutID) {
       clearTimeout(timeoutID);
     }
-    SpecialPowers.setAllAppsLaunchable(launchableValue);
     SpecialPowers.removePermission("webapps-manage", document);
     SimpleTest.finish();
   }
 
   function mozAppsError() {
     ok(false, "mozApps error: " + this.error.name);
     finish();
   }
@@ -237,13 +235,12 @@ var PackagedTestHelper = (function Packa
     checkAppState: checkAppState,
     checkAppDownloadError: checkAppDownloadError,
     get gSJSPath() { return gSJSPath; },
     set gSJSPath(aValue) { gSJSPath = aValue },
     get gSJS() { return gSJS; },
     get gAppName() { return gAppName;},
     get gApp() { return gApp; },
     set gApp(aValue) { gApp = aValue; },
-    gInstallOrigin: gInstallOrigin,
-    launchableValue: launchableValue
+    gInstallOrigin: gInstallOrigin
   };
 
 })();
--- a/dom/apps/tests/test_packaged_app_install.html
+++ b/dom/apps/tests/test_packaged_app_install.html
@@ -76,18 +76,17 @@ function checkInstalledApp(aMiniManifest
   };
 }
 
 SimpleTest.waitForExplicitFinish();
 
 var steps = [
   function() {
     // Set up
-    PackagedTestHelper.launchableValue =
-      SpecialPowers.setAllAppsLaunchable(true);
+    SpecialPowers.setAllAppsLaunchable(true);
     SpecialPowers.addPermission("webapps-manage", true, document);
     ok(true, "Set up");
     PackagedTestHelper.next();
   },
   function() {
     ok(true, "autoConfirmAppInstall");
     SpecialPowers.autoConfirmAppInstall(PackagedTestHelper.next);
   },
--- a/dom/apps/tests/test_packaged_app_update.html
+++ b/dom/apps/tests/test_packaged_app_update.html
@@ -90,18 +90,17 @@ function updateApp(aExpectedReady, aPrev
                    true);
 
 }
 
 
 var steps = [
   function() {
     // Set up
-    PackagedTestHelper.launchableValue =
-      SpecialPowers.setAllAppsLaunchable(true);
+    SpecialPowers.setAllAppsLaunchable(true);
     SpecialPowers.addPermission("webapps-manage", true, document);
     ok(true, "Set up");
     PackagedTestHelper.next();
   },
   function() {
     ok(true, "autoConfirmAppInstall");
     SpecialPowers.autoConfirmAppInstall(PackagedTestHelper.next);
   },
--- a/dom/apps/tests/test_receipt_operations.html
+++ b/dom/apps/tests/test_receipt_operations.html
@@ -47,17 +47,17 @@ function finish() {
 function cbError(aError) {
   ok(false, "Error callback invoked " + aError);
   finish();
 }
 
 SimpleTest.waitForExplicitFinish();
 
 function runTest() {
-  launchableValue = SpecialPowers.setAllAppsLaunchable(true);
+  SpecialPowers.setAllAppsLaunchable(true);
 
   SpecialPowers.autoConfirmAppInstall(continueTest);
   yield undefined;
 
   var request = navigator.mozApps.install(gManifestURL);
   request.onerror = cbError;
   request.onsuccess = continueTest;
   yield undefined;
--- a/dom/apps/tests/test_signed_pkg_install.html
+++ b/dom/apps/tests/test_signed_pkg_install.html
@@ -22,17 +22,16 @@ https://bugzilla.mozilla.org/show_bug.cg
 <script class="testbody" type="text/javascript">
 
 "use strict";
 
 const Ci = SpecialPowers.Ci;
 const Cc = SpecialPowers.Cc;
 const Cu = SpecialPowers.Cu;
 
-var launchableValue;
 var index = -1;
 var gDebug = false;
 var gApp = null;
 var gAppName = "Simple App";
 var gInstallOrigin = "http://mochi.test:8888/";
 var gSJSPath = "tests/dom/apps/tests/signed_app.sjs";
 var gSJS = gInstallOrigin + gSJSPath;
 var gPackagePath = gInstallOrigin + "tests/dom/apps/tests/";
@@ -90,17 +89,17 @@ var steps = [
     // Set up
     info("Test Initial Setup");
     gSignedAppOriginsStr = SpecialPowers.getCharPref("dom.mozApps.signed_apps_installable_from");
     var signedAppOriginsStr = gSignedAppOriginsStr.concat("," + gInstallOrigin.slice(0, -1));
     SpecialPowers.pushPrefEnv({'set': [['dom.mozApps.signed_apps_installable_from', signedAppOriginsStr]]}, function() {
       var url = SimpleTest.getTestFileURL("chromeAddCert.js");
       var script = SpecialPowers.loadChromeScript(url);
       script.addMessageListener("addCertCompleted", function() {
-        launchableValue = SpecialPowers.setAllAppsLaunchable(true);
+        SpecialPowers.setAllAppsLaunchable(true);
         SpecialPowers.addPermission("webapps-manage", true, document);
         info("Test CA Certificate Selected");
         PackagedTestHelper.next();
         script.destroy();
       });
     });
   },
   function() {
--- a/dom/apps/tests/test_uninstall_errors.html
+++ b/dom/apps/tests/test_uninstall_errors.html
@@ -13,40 +13,39 @@ https://bugzilla.mozilla.org/show_bug.cg
   /** Test for Bug 830258 **/
 
   SimpleTest.waitForExplicitFinish();
 
   var url1 = 'http://test1.example.com/tests/dom/apps/tests/file_app.sjs?apptype=hosted&getmanifest=true';
   var url2 = 'http://test2.example.com/tests/dom/apps/tests/file_app.sjs?apptype=hosted&getmanifest=true';
 
   var gGenerator = runTest();
-  var launchableValue = undefined;
 
   function go() {
     SpecialPowers.pushPermissions(
       [{ "type": "webapps-manage", "allow": 1, "context": document }],
       function() { gGenerator.next() });
   }
 
   function continueTest() {
     try {
       gGenerator.next();
     } catch (e if e instanceof StopIteration) {
-      finish();
+      SimpleTest.finish();
     }
   }
 
   function mozAppsError() {
     ok(false, "mozApps error: " + this.error.name);
-    finish();
+    SimpleTest.finish();
   }
 
   function runTest() {
     // Set up.
-    launchableValue = SpecialPowers.setAllAppsLaunchable(true);
+    SpecialPowers.setAllAppsLaunchable(true);
     SpecialPowers.autoConfirmAppInstall(continueTest);
     yield undefined;
 
     let request = navigator.mozApps.install(url1);
     request.onerror = mozAppsError;
     request.onsuccess = continueTest;
     yield undefined;
     let app1 = request.result;
@@ -87,21 +86,16 @@ https://bugzilla.mozilla.org/show_bug.cg
       continueTest();
     };
     request.onerror = function() {
       ok(false, "Fail to uninstall the app2");
       continueTest();
     };
     yield undefined;
   }
-
-  function finish() {
-    SpecialPowers.setAllAppsLaunchable(launchableValue);
-    SimpleTest.finish();
-  }
   </script>
 </head>
 <body onload="go()">
 <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=830258">Mozilla Bug 830258</a>
 <p id="display"></p>
 <div id="content" style="display: none">
 
 </div>
--- a/dom/browser-element/mochitest/browserElement_AppFramePermission.js
+++ b/dom/browser-element/mochitest/browserElement_AppFramePermission.js
@@ -3,28 +3,17 @@
 
 // Bug 777384 - Test mozapp permission.
 "use strict";
 
 SimpleTest.waitForExplicitFinish();
 browserElementTestHelpers.setEnabledPref(true);
 browserElementTestHelpers.addPermission();
 
-function makeAllAppsLaunchable() {
-  var originalValue = SpecialPowers.setAllAppsLaunchable(true);
-
-  // Clean up after ourselves once tests are done so the test page is unloaded.
-  window.addEventListener("unload", function restoreAllAppsLaunchable(event) {
-    if (event.target == window.document) {
-      window.removeEventListener("unload", restoreAllAppsLaunchable, false);
-      SpecialPowers.setAllAppsLaunchable(originalValue);
-    }
-  }, false);
-}
-makeAllAppsLaunchable();
+SpecialPowers.setAllAppsLaunchable(true);
 
 function testAppElement(expectAnApp, callback) {
   var iframe = document.createElement('iframe');
   SpecialPowers.wrap(iframe).mozbrowser = true;
   iframe.setAttribute('mozapp', 'http://example.org/manifest.webapp');
   iframe.addEventListener('mozbrowsershowmodalprompt', function(e) {
     is(e.detail.message == 'app', expectAnApp, e.detail.message);
     SimpleTest.executeSoon(callback);
--- a/dom/browser-element/mochitest/priority/test_Preallocated.html
+++ b/dom/browser-element/mochitest/priority/test_Preallocated.html
@@ -56,17 +56,17 @@ function runTest()
               "false for this test to work.");
     SimpleTest.finish();
     return;
   }
 
   // Ensure that the preallocated process initially gets BACKGROUND priority.
   // That's it.
   expectProcessCreated().then(function(childID) {
-    return expectPriorityChange(childID, 'PREALLOC');
+    return expectPriorityChange(childID, 'PREALLOC', 'CPU_LOW');
   }).then(function() {
     // We need to set the pref asynchoronously or the preallocated process won't
     // be shut down.
     SimpleTest.executeSoon(function(){
       cleanUp();
       SimpleTest.finish();
     });
   });
--- a/dom/indexedDB/test/webapp_clearBrowserData.js
+++ b/dom/indexedDB/test/webapp_clearBrowserData.js
@@ -116,26 +116,25 @@ function start()
     return;
   }
 
   SpecialPowers.addPermission("browser", true, document);
   SpecialPowers.addPermission("browser", true, { manifestURL: manifestURL,
                                                  isInBrowserElement: false });
   SpecialPowers.addPermission("embed-apps", true, document);
 
-  let originalAllAppsLaunchable = SpecialPowers.setAllAppsLaunchable(true);
+  SpecialPowers.setAllAppsLaunchable(true);
 
   window.addEventListener("unload", function cleanup(event) {
     if (event.target == document) {
       window.removeEventListener("unload", cleanup, false);
 
       SpecialPowers.removePermission("browser", location.href);
       SpecialPowers.removePermission("browser",
                                      location.protocol + "//" + appDomain);
       SpecialPowers.removePermission("embed-apps", location.href);
-      SpecialPowers.setAllAppsLaunchable(originalAllAppsLaunchable);
     }
   }, false);
 
   SpecialPowers.pushPrefEnv({
     "set": [["dom.mozBrowserFramesEnabled", true]]
   }, runTest);
 }
--- a/dom/ipc/PreallocatedProcessManager.cpp
+++ b/dom/ipc/PreallocatedProcessManager.cpp
@@ -55,26 +55,24 @@ public:
   void PublishSpareProcess(ContentParent* aContent);
   void MaybeForgetSpare(ContentParent* aContent);
   void OnNuwaReady();
   bool PreallocatedProcessReady();
   already_AddRefed<ContentParent> GetSpareProcess();
   void RunAfterPreallocatedProcessReady(nsIRunnable* aRunnable);
 
 private:
-  void OnNuwaForkTimeout();
   void NuwaFork();
 
   // initialization off the critical path of app startup.
   CancelableTask* mPreallocateAppProcessTask;
 
   // The array containing the preallocated processes. 4 as the inline storage size
   // should be enough so we don't need to grow the nsAutoTArray.
   nsAutoTArray<nsRefPtr<ContentParent>, 4> mSpareProcesses;
-  nsTArray<CancelableTask*> mNuwaForkWaitTasks;
 
   nsTArray<nsCOMPtr<nsIRunnable> > mDelayedContentParentRequests;
 
   // Nuwa process is ready for creating new process.
   bool mIsNuwaReady;
 #endif
 
 private:
@@ -307,21 +305,16 @@ PreallocatedProcessManagerImpl::PublishS
     AutoJSContext cx;
     nsCOMPtr<nsIMessageBroadcaster> ppmm =
       do_GetService("@mozilla.org/parentprocessmessagemanager;1");
     nsresult rv = ppmm->BroadcastAsyncMessage(
       NS_LITERAL_STRING("TEST-ONLY:nuwa-add-new-process"),
       JS::NullHandleValue, JS::NullHandleValue, cx, 1);
   }
 
-  if (!mNuwaForkWaitTasks.IsEmpty()) {
-    mNuwaForkWaitTasks.ElementAt(0)->Cancel();
-    mNuwaForkWaitTasks.RemoveElementAt(0);
-  }
-
   mSpareProcesses.AppendElement(aContent);
 
   if (!mDelayedContentParentRequests.IsEmpty()) {
     nsCOMPtr<nsIRunnable> runnable = mDelayedContentParentRequests[0];
     mDelayedContentParentRequests.RemoveElementAt(0);
     NS_DispatchToMainThread(runnable);
   }
 }
@@ -370,37 +363,18 @@ PreallocatedProcessManagerImpl::OnNuwaRe
 bool
 PreallocatedProcessManagerImpl::PreallocatedProcessReady()
 {
   return !mSpareProcesses.IsEmpty();
 }
 
 
 void
-PreallocatedProcessManagerImpl::OnNuwaForkTimeout()
-{
-  if (!mNuwaForkWaitTasks.IsEmpty()) {
-    mNuwaForkWaitTasks.RemoveElementAt(0);
-  }
-
-  // We haven't RecvAddNewProcess() after NuwaFork(). Maybe the main
-  // thread of the Nuwa process is in deadlock.
-  MOZ_ASSERT(false, "Can't fork from the nuwa process.");
-}
-
-void
 PreallocatedProcessManagerImpl::NuwaFork()
 {
-  CancelableTask* nuwaForkTimeoutTask = NewRunnableMethod(
-    this, &PreallocatedProcessManagerImpl::OnNuwaForkTimeout);
-  mNuwaForkWaitTasks.AppendElement(nuwaForkTimeoutTask);
-
-  MessageLoop::current()->PostDelayedTask(FROM_HERE,
-                                          nuwaForkTimeoutTask,
-                                          NUWA_FORK_WAIT_DURATION_MS);
   mPreallocatedAppProcess->SendNuwaFork();
 }
 #endif
 
 void
 PreallocatedProcessManagerImpl::Disable()
 {
   if (!mEnabled) {
--- a/dom/ipc/ProcessPriorityManager.cpp
+++ b/dom/ipc/ProcessPriorityManager.cpp
@@ -253,17 +253,17 @@ public:
   void OnAudioChannelProcessChanged(nsISupports* aSubject);
   void OnRemoteBrowserFrameShown(nsISupports* aSubject);
   void OnTabParentDestroyed(nsISupports* aSubject);
   void OnFrameloaderVisibleChanged(nsISupports* aSubject);
   void OnChannelConnected(nsISupports* aSubject);
 
   ProcessPriority CurrentPriority();
   ProcessPriority ComputePriority();
-  ProcessCPUPriority ComputeCPUPriority();
+  ProcessCPUPriority ComputeCPUPriority(ProcessPriority aPriority);
 
   void ScheduleResetPriority(const char* aTimeoutPref);
   void ResetPriority();
   void ResetPriorityNow();
   void ResetCPUPriorityNow();
 
   /**
    * This overload is equivalent to SetPriorityNow(aPriority,
@@ -951,23 +951,23 @@ ParticularProcessPriorityManager::Comput
   }
 
   return HasAppType("homescreen") ?
          PROCESS_PRIORITY_BACKGROUND_HOMESCREEN :
          PROCESS_PRIORITY_BACKGROUND;
 }
 
 ProcessCPUPriority
-ParticularProcessPriorityManager::ComputeCPUPriority()
+ParticularProcessPriorityManager::ComputeCPUPriority(ProcessPriority aPriority)
 {
-  if (mPriority == PROCESS_PRIORITY_PREALLOC) {
+  if (aPriority == PROCESS_PRIORITY_PREALLOC) {
     return PROCESS_CPU_PRIORITY_LOW;
   }
 
-  if (mPriority >= PROCESS_PRIORITY_FOREGROUND_HIGH) {
+  if (aPriority >= PROCESS_PRIORITY_FOREGROUND_HIGH) {
     return PROCESS_CPU_PRIORITY_NORMAL;
   }
 
   return ProcessPriorityManagerImpl::GetSingleton()->
     OtherProcessHasHighPriority(this) ?
     PROCESS_CPU_PRIORITY_LOW :
     PROCESS_CPU_PRIORITY_NORMAL;
 }
@@ -977,17 +977,17 @@ ParticularProcessPriorityManager::ResetC
 {
   SetPriorityNow(mPriority);
 }
 
 void
 ParticularProcessPriorityManager::SetPriorityNow(ProcessPriority aPriority,
                                                  uint32_t aBackgroundLRU)
 {
-  SetPriorityNow(aPriority, ComputeCPUPriority(), aBackgroundLRU);
+  SetPriorityNow(aPriority, ComputeCPUPriority(aPriority), aBackgroundLRU);
 }
 
 void
 ParticularProcessPriorityManager::SetPriorityNow(ProcessPriority aPriority,
                                                  ProcessCPUPriority aCPUPriority,
                                                  uint32_t aBackgroundLRU)
 {
   if (aPriority == PROCESS_PRIORITY_UNKNOWN) {
--- a/dom/ipc/tests/mochitest.ini
+++ b/dom/ipc/tests/mochitest.ini
@@ -1,3 +1,5 @@
 [test_NuwaProcessCreation.html]
 run-if = toolkit == 'gonk'
+[test_NuwaProcessDeadlock.html]
+run-if = toolkit == 'gonk'
 [test_child_docshell.html]
new file mode 100644
--- /dev/null
+++ b/dom/ipc/tests/test_NuwaProcessDeadlock.html
@@ -0,0 +1,101 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+Test if Nuwa process created successfully.
+-->
+<head>
+  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+
+<script type="application/javascript;version=1.7">
+"use strict";
+
+SimpleTest.waitForExplicitFinish();
+
+function TestLoader() {}
+
+TestLoader.prototype = {
+  _waitingTask: 0,
+  onTestReady: null,
+  unlockTestReady: function() {
+    this._waitingTask--;
+    this._maybeLoadTest();
+  },
+  lockTestReady: function() {
+    this._waitingTask++;
+  },
+  _maybeLoadTest: function() {
+    if (this._waitingTask == 0) {
+      this.onTestReady();
+    }
+  }
+}
+
+var testLoader = new TestLoader();
+testLoader.lockTestReady();
+window.addEventListener('load', function() {
+  testLoader.unlockTestReady();
+});
+
+function setPref(pref, value) {
+  testLoader.lockTestReady();
+  if (value !== undefined && value !== null) {
+    SpecialPowers.pushPrefEnv({'set': [[pref, value]]}, function() { testLoader.unlockTestReady(); });
+  } else {
+    SpecialPowers.pushPrefEnv({'clear': [[pref]]}, function() { testLoader.unlockTestReady(); });
+  }
+}
+
+setPref('dom.ipc.processPriorityManager.testMode', true);
+setPref('dom.ipc.processPriorityManager.enabled', true);
+setPref('dom.ipc.processPriorityManager.backgroundLRUPoolLevels', 2);
+setPref('dom.ipc.processPrelaunch.testMode', true);  // For testing deadlock.
+
+function runTest()
+{
+  // Shutdown preallocated process.
+  SpecialPowers.setBoolPref('dom.ipc.processPrelaunch.enabled', false);
+  let cpmm = SpecialPowers.Cc["@mozilla.org/childprocessmessagemanager;1"]
+                          .getService(SpecialPowers.Ci.nsISyncMessageSender);
+  let seenNuwaReady = false;
+  let msgHandler = {
+    receiveMessage: function receiveMessage(msg) {
+      msg = SpecialPowers.wrap(msg);
+      if (msg.name == 'TEST-ONLY:nuwa-ready') {
+        ok(true, "Got nuwa-ready");
+        is(seenNuwaReady, false, "Already received nuwa ready");
+        seenNuwaReady = true;
+      } else if (msg.name == 'TEST-ONLY:nuwa-add-new-process') {
+        ok(true, "Got nuwa-add-new-process");
+        is(seenNuwaReady, true, "Receive nuwa-add-new-process before nuwa-ready");
+        testEnd();
+      }
+    }
+  };
+  let timeout = setTimeout(function() {
+    ok(false, "Nuwa process is not launched");
+    testEnd();
+  }, 90000);
+
+  function testEnd() {
+    cpmm.removeMessageListener("TEST-ONLY:nuwa-ready", msgHandler);
+    cpmm.removeMessageListener("TEST-ONLY:nuwa-add-new-process", msgHandler);
+    clearTimeout(timeout);
+    setPref('dom.ipc.processPrelaunch.testMode', false);
+    SimpleTest.finish();
+  }
+
+  cpmm.addMessageListener("TEST-ONLY:nuwa-ready", msgHandler);
+  cpmm.addMessageListener("TEST-ONLY:nuwa-add-new-process", msgHandler);
+
+
+  // Setting this pref to true should cause us to prelaunch a process.
+  SpecialPowers.setBoolPref('dom.ipc.processPrelaunch.enabled', true);
+}
+
+testLoader.onTestReady = runTest;
+</script>
+</body>
+</html>
--- a/dom/smil/test/smilAnimateMotionValueLists.js
+++ b/dom/smil/test/smilAnimateMotionValueLists.js
@@ -103,18 +103,21 @@ const gValidKeyPoints = [
   "0; 0; 1",
   "0; 1; 1",
   "0; 0; 1;", // Trailing semicolons are allowed
   "0; 0; 1; ",
   "0; 0.000; 1",
   "0; 0.000001; 1",
 ];
 
+// Should have 3 values to be valid.
+// Same as number of keyTimes values
 const gInvalidKeyPoints = [
   "0; 1",
+  "0; 0.5; 0.75; 1",
   "0; 1;",
   "0",
   "1",
   "a",
   "",
   "  ",
   "0; -0.1; 1",
   "0; 1.1; 1",
--- a/dom/smil/test/smilTestUtils.js
+++ b/dom/smil/test/smilTestUtils.js
@@ -14,32 +14,16 @@ const MPATH_TARGET_ID = "smilTestUtilsTe
 function extend(child, supertype)
 {
    child.prototype.__proto__ = supertype.prototype;
 }
 
 // General Utility Methods
 var SMILUtil =
 {
-  // Returns true if SMIL is enabled, false otherwise
-  // XXXdholbert There should be a "nicer" way to do this - right now this will
-  // trigger a 'NotYetImplemented' assertion on STDOUT, if SMIL is disabled.
-  isSMILEnabled : function()
-  {
-    var svg = SMILUtil.getSVGRoot();
-    try {
-      SMILUtil.getSVGRoot().animationsPaused();
-    } catch(e) {
-      // Exception --> SMIL disabled
-      return false;
-    }
-    // No exceptions --> SMIL enabled
-    return true;
-  },
-
   // Returns the first matched <svg> node in the document
   getSVGRoot : function()
   {
     return SMILUtil.getFirstElemWithTag("svg");
   },
 
   // Returns the first element in the document with the matching tag
   getFirstElemWithTag : function(aTargetTag)
--- a/dom/smil/test/test_smilAnimateMotion.xhtml
+++ b/dom/smil/test/test_smilAnimateMotion.xhtml
@@ -27,22 +27,16 @@ https://bugzilla.mozilla.org/show_bug.cg
 <pre id="test">
 <script class="testbody" type="text/javascript">
 <![CDATA[
 
 SimpleTest.waitForExplicitFinish();
 
 function main()
 {
-  if (!SMILUtil.isSMILEnabled()) {
-    ok(false, "SMIL dosn't seem to be enabled");
-    SimpleTest.finish();
-    return;
-  }
-
   // Start out with document paused
   var svg = SMILUtil.getSVGRoot();
   ok(svg.animationsPaused(), "should be paused by <svg> load handler");
   is(svg.getCurrentTime(), 0, "should be paused at 0 in <svg> load handler");
 
   var timingData = new SMILTimingData(1.0, 6.0);
   testBundleList(gMotionBundles, timingData);
 
--- a/dom/smil/test/test_smilAnimateMotionInvalidValues.xhtml
+++ b/dom/smil/test/test_smilAnimateMotionInvalidValues.xhtml
@@ -41,17 +41,17 @@ function createAnim()
   return gRect.appendChild(anim);
 }
 
 function removeElem(aElem)
 {
   aElem.parentNode.removeChild(aElem);
 }
 
-function testAttr(aAttrName, aAttrValueArray, aIsValid, aIsTodo)
+function testAttr(aAttrName, aAttrValueArray, aIsValid)
 {
   var componentsToCheck;
 
   for (var i in aAttrValueArray) {
     var curVal = aAttrValueArray[i];
     var anim = createAnim();
     anim.setAttribute(aAttrName, curVal);
     if (aAttrName == "rotate") {
@@ -73,22 +73,22 @@ function testAttr(aAttrName, aAttrValueA
       }
     }
 
     var curCTM = gRect.getCTM();
     if (aIsValid) {
       var errMsg = "CTM should have changed when applying animateMotion " +
         "with '" + aAttrName + "' set to valid value '" + curVal + "'";
       CTMUtil.assertCTMNotEqual(curCTM, gUnAnimatedCTM, componentsToCheck,
-                                errMsg, aIsTodo);
+                                errMsg, false);
     } else {
       var errMsg = "CTM should not have changed when applying animateMotion " +
         "with '" + aAttrName + "' set to invalid value '" + curVal + "'";
       CTMUtil.assertCTMEqual(curCTM, gUnAnimatedCTM, componentsToCheck,
-                             errMsg, aIsTodo);
+                             errMsg, false);
     }
     removeElem(anim);
   }
 }
 
 function createPath(aPathDescription)
 {
   var path = document.createElementNS(SVGNS, "path");
@@ -99,17 +99,17 @@ function createPath(aPathDescription)
 
 function createMpath(aAnimElement)
 {
   var mpath = document.createElementNS(SVGNS, "mpath");
   mpath.setAttributeNS(XLINKNS, "href", "#thePath");
   return aAnimElement.appendChild(mpath);
 }
 
-function testMpathElem(aPathValueArray, aIsValid, aIsTodo)
+function testMpathElem(aPathValueArray, aIsValid)
 {
   for (var i in aPathValueArray) {
     var curVal = aPathValueArray[i];
     var anim = createAnim();
     var mpath = createMpath(anim);
     var path = createPath(curVal);
 
     // Apply a supplementary rotation to make sure that we don't apply it if
@@ -117,64 +117,58 @@ function testMpathElem(aPathValueArray, 
     anim.setAttribute("rotate", Math.PI/4);
     componentsToCheck = CTMUtil.CTM_COMPONENTS_ALL;
 
     if (aIsValid) {
       var errMsg = "CTM should have changed when applying animateMotion " +
         "with mpath linking to a path with valid value '" + curVal + "'";
 
       CTMUtil.assertCTMNotEqual(gRect.getCTM(), gUnAnimatedCTM,
-                                componentsToCheck, errMsg, aIsTodo);
+                                componentsToCheck, errMsg, false);
     } else {
       var errMsg = "CTM should not have changed when applying animateMotion " +
         "with mpath linking to a path with invalid value '" + curVal + "'";
       CTMUtil.assertCTMEqual(gRect.getCTM(), gUnAnimatedCTM,
-                             componentsToCheck, errMsg, aIsTodo);
+                             componentsToCheck, errMsg, false);
     }
     removeElem(anim);
     removeElem(path);
     removeElem(mpath);
  } 
 }
 
 // Main Function
 function main()
 {
-  if (!SMILUtil.isSMILEnabled()) {
-    ok(false, "SMIL dosn't seem to be enabled");
-    SimpleTest.finish();
-    return;
-  }
-
   // Start out with document paused
   var svg = SMILUtil.getSVGRoot();
   ok(svg.animationsPaused(), "should be paused by <svg> load handler");
   is(svg.getCurrentTime(), 0, "should be paused at 0 in <svg> load handler");
 
-  testAttr("values", gValidValues, true, false);
-  testAttr("values", gInvalidValues, false, false);
+  testAttr("values", gValidValues, true);
+  testAttr("values", gInvalidValues, false);
 
-  testAttr("rotate", gValidRotate, true, false);
-  testAttr("rotate", gInvalidRotate, false, false);
+  testAttr("rotate", gValidRotate, true);
+  testAttr("rotate", gInvalidRotate, false);
 
-  testAttr("to", gValidToBy, true, false);
-  testAttr("to", gInvalidToBy, false, false);
+  testAttr("to", gValidToBy, true);
+  testAttr("to", gInvalidToBy, false);
 
-  testAttr("by", gValidToBy, true, false);
-  testAttr("by", gInvalidToBy, false, false);
+  testAttr("by", gValidToBy, true);
+  testAttr("by", gInvalidToBy, false);
 
-  testAttr("path", gValidPath, true, false);
-  testAttr("path", gInvalidPath, false, false);
-  testAttr("path", gValidPathWithErrors, true, false);
+  testAttr("path", gValidPath, true);
+  testAttr("path", gInvalidPath, false);
+  testAttr("path", gValidPathWithErrors, true);
 
-  testAttr("keyPoints", gValidKeyPoints, true, false);
-  testAttr("keyPoints", gInvalidKeyPoints, false, false);
+  testAttr("keyPoints", gValidKeyPoints, true);
+  testAttr("keyPoints", gInvalidKeyPoints, false);
 
-  testMpathElem(gValidPath, true, false);
-  testMpathElem(gInvalidPath, false, false);
+  testMpathElem(gValidPath, true);
+  testMpathElem(gInvalidPath, false);
 
   SimpleTest.finish();
 }
 
 window.addEventListener("load", main, false);
 ]]>
 </script>
 </pre>
--- a/dom/smil/test/test_smilAnimateMotionOverrideRules.xhtml
+++ b/dom/smil/test/test_smilAnimateMotionOverrideRules.xhtml
@@ -193,22 +193,16 @@ function testAttrSettings(aAttrValueHash
   // CLEAN UP
   SMILUtil.getSVGRoot().setCurrentTime(0);
   removeElem(animElement);
 }
 
 // Main Function
 function main()
 {
-  if (!SMILUtil.isSMILEnabled()) {
-    ok(false, "SMIL dosn't seem to be enabled");
-    SimpleTest.finish();
-    return;
-  }
-
   // Start out with document paused
   var svg = SMILUtil.getSVGRoot();
   ok(svg.animationsPaused(), "should be paused by <svg> load handler");
   is(svg.getCurrentTime(), 0, "should be paused at 0 in <svg> load handler");
 
   runTest();
   SimpleTest.finish();
 }
--- a/dom/smil/test/test_smilCSSFontStretchRelative.xhtml
+++ b/dom/smil/test/test_smilCSSFontStretchRelative.xhtml
@@ -75,22 +75,16 @@ function testFontStretchValue(baseValue,
 
   // Removing animation should clear animated effects
   textElem.removeChild(animElem);
   svg.removeChild(gElem);
 }
 
 function main()
 {
-  if (!SMILUtil.isSMILEnabled()) {
-    ok(false, "SMIL dosn't seem to be enabled");
-    SimpleTest.finish();
-    return;
-  }
-
   var valuesList = gFontStretchValues;
   for (var baseIdx in valuesList) {
     // 'narrower' and 'wider' are expected to shift us by one slot, but not
     // past the ends of the list of possible values.
     var narrowerIdx = Math.max(baseIdx - 1, 0);
     var widerIdx =    Math.min(baseIdx + 1, valuesList.length - 1);
 
     testFontStretchValue(valuesList[baseIdx],
--- a/dom/smil/test/test_smilCSSFromBy.xhtml
+++ b/dom/smil/test/test_smilCSSFromBy.xhtml
@@ -26,22 +26,16 @@
 <pre id="test">
 <script class="testbody" type="text/javascript">
 <![CDATA[
 
 SimpleTest.waitForExplicitFinish();
 
 function main()
 {
-  if (!SMILUtil.isSMILEnabled()) {
-    ok(false, "SMIL dosn't seem to be enabled");
-    SimpleTest.finish();
-    return;
-  }
-
   // Start out with document paused
   var svg = SMILUtil.getSVGRoot();
   ok(svg.animationsPaused(), "should be paused by <svg> load handler");
   is(svg.getCurrentTime(), 0, "should be paused at 0 in <svg> load handler");
 
   testBundleList(gFromByBundles, new SMILTimingData(1.0, 1.0));
 
   // Set "display:none" on everything and run the tests again
--- a/dom/smil/test/test_smilCSSFromTo.xhtml
+++ b/dom/smil/test/test_smilCSSFromTo.xhtml
@@ -49,22 +49,16 @@ function checkForUntestedProperties(bund
   // Warn about remaining (untested) properties
   for (var untestedProp in propertySet) {
     ok(false, "No tests for property '" + untestedProp + "'");
   }
 }
 
 function main()
 {
-  if (!SMILUtil.isSMILEnabled()) {
-    ok(false, "SMIL dosn't seem to be enabled");
-    SimpleTest.finish();
-    return;
-  }
-
   // Start out with document paused
   var svg = SMILUtil.getSVGRoot();
   ok(svg.animationsPaused(), "should be paused by <svg> load handler");
   is(svg.getCurrentTime(), 0, "should be paused at 0 in <svg> load handler");
 
   // FIRST: Warn about any properties that are missing tests
   checkForUntestedProperties(gFromToBundles);
 
--- a/dom/smil/test/test_smilCSSInherit.xhtml
+++ b/dom/smil/test/test_smilCSSInherit.xhtml
@@ -51,22 +51,16 @@
 </svg>
 </div>
 <pre id="test">
 <script class="testbody" type="text/javascript">
 <![CDATA[
 SimpleTest.waitForExplicitFinish();
 
 function main() {
-  if (!SMILUtil.isSMILEnabled()) {
-    ok(false, "SMIL dosn't seem to be enabled");
-    SimpleTest.finish();
-    return;
-  }
-
   // Pause & seek to halfway through animation
   var svg = SMILUtil.getSVGRoot();
   ok(svg.animationsPaused(), "should be paused by <svg> load handler");
   is(svg.getCurrentTime(), 0, "should be paused at 0 in <svg> load handler");
   svg.setCurrentTime(0.5);
 
   var text = document.getElementsByTagName("text")[0];
   var computedVal = SMILUtil.getComputedStyleSimple(text, "font-size");
--- a/dom/smil/test/test_smilCSSInvalidValues.xhtml
+++ b/dom/smil/test/test_smilCSSInvalidValues.xhtml
@@ -35,22 +35,16 @@ var invalidTestcaseBundles = [
     new AnimTestcaseFromTo("greeeen", "red",  { noEffect: true }),
     new AnimTestcaseFromTo("rgb(red, 255, 255)", "red", { noEffect: true }),
     new AnimTestcaseFromTo("#FFFFFFF", "red", { noEffect: true }),
     new AnimTestcaseFromTo("bogus", "bogus",  { noEffect: true }),
   ]),
 ];
 function main()
 {
-  if (!SMILUtil.isSMILEnabled()) {
-    ok(false, "SMIL dosn't seem to be enabled");
-    SimpleTest.finish();
-    return;
-  }
-
   // Start out with document paused
   var svg = SMILUtil.getSVGRoot();
   ok(svg.animationsPaused(), "should be paused by <svg> load handler");
   is(svg.getCurrentTime(), 0, "should be paused at 0 in <svg> load handler");
 
   // Run the tests
   testBundleList(invalidTestcaseBundles, new SMILTimingData(1.0, 1.0));
 
--- a/dom/smil/test/test_smilCSSPaced.xhtml
+++ b/dom/smil/test/test_smilCSSPaced.xhtml
@@ -21,22 +21,16 @@
 <pre id="test">
 <script class="testbody" type="text/javascript">
 <![CDATA[
 
 SimpleTest.waitForExplicitFinish();
 
 function main()
 {
-  if (!SMILUtil.isSMILEnabled()) {
-    ok(false, "SMIL dosn't seem to be enabled");
-    SimpleTest.finish();
-    return;
-  }
-
   // Start out with document paused
   var svg = SMILUtil.getSVGRoot();
   ok(svg.animationsPaused(), "should be paused by <svg> load handler");
   is(svg.getCurrentTime(), 0, "should be paused at 0 in <svg> load handler");
 
   testBundleList(gPacedBundles, new SMILTimingData(1.0, 6.0));
   // Set "display:none" on everything and run the tests again
   SMILUtil.hideSubtree(SMILUtil.getSVGRoot(), false, false);
--- a/dom/smil/test/test_smilDynamicDelayedBeginElement.xhtml
+++ b/dom/smil/test/test_smilDynamicDelayedBeginElement.xhtml
@@ -45,22 +45,16 @@ function createAnim() {
   a.setAttribute('begin', 'indefinite');
   a.setAttribute('dur', '3s');
   a.setAttribute('fill', 'freeze');
   return a;
 }
 
 // Main Functions
 function main() {
-  if (!SMILUtil.isSMILEnabled()) {
-    ok(false, "SMIL dosn't seem to be enabled");
-    SimpleTest.finish();
-    return;
-  }
-
   // In unpatched Firefox builds, we'll only trigger Bug 699143 if we insert
   // an animation and call beginElement() **after** the document start-time.
   // Hence, we use executeSoon here to allow some time to pass.  (And then
   // we'll use a short busy-loop, for good measure.)
   SimpleTest.executeSoon(runTest);
 }
 
 function runTest() {
--- a/dom/smil/test/test_smilMappedAttrFromBy.xhtml
+++ b/dom/smil/test/test_smilMappedAttrFromBy.xhtml
@@ -27,22 +27,16 @@
 <pre id="test">
 <script class="testbody" type="text/javascript">
 <![CDATA[
 
 SimpleTest.waitForExplicitFinish();
 
 function main()
 {
-  if (!SMILUtil.isSMILEnabled()) {
-    ok(false, "SMIL dosn't seem to be enabled");
-    SimpleTest.finish();
-    return;
-  }
-
   // Start out with document paused
   var svg = SMILUtil.getSVGRoot();
   ok(svg.animationsPaused(), "should be paused by <svg> load handler");
   is(svg.getCurrentTime(), 0, "should be paused at 0 in <svg> load handler");
 
   var testBundles = convertCSSBundlesToMappedAttr(gFromByBundles);
   testBundleList(testBundles, new SMILTimingData(1.0, 1.0));
 
--- a/dom/smil/test/test_smilMappedAttrFromTo.xhtml
+++ b/dom/smil/test/test_smilMappedAttrFromTo.xhtml
@@ -50,22 +50,16 @@ function checkForUntestedAttributes(bund
   // Warn about remaining (untested) properties
   for (var untestedProp in attributeSet) {
     ok(false, "No tests for attribute '" + untestedProp + "'");
   }
 }
 
 function main()
 {
-  if (!SMILUtil.isSMILEnabled()) {
-    ok(false, "SMIL dosn't seem to be enabled");
-    SimpleTest.finish();
-    return;
-  }
-
   // Start out with document paused
   var svg = SMILUtil.getSVGRoot();
   ok(svg.animationsPaused(), "should be paused by <svg> load handler");
   is(svg.getCurrentTime(), 0, "should be paused at 0 in <svg> load handler");
 
   var testBundles = convertCSSBundlesToMappedAttr(gFromToBundles);
 
   // FIRST: Warn about any attributes that are missing tests
--- a/dom/smil/test/test_smilMappedAttrPaced.xhtml
+++ b/dom/smil/test/test_smilMappedAttrPaced.xhtml
@@ -22,22 +22,16 @@
 <pre id="test">
 <script class="testbody" type="text/javascript">
 <![CDATA[
 
 SimpleTest.waitForExplicitFinish();
 
 function main()
 {
-  if (!SMILUtil.isSMILEnabled()) {
-    ok(false, "SMIL dosn't seem to be enabled");
-    SimpleTest.finish();
-    return;
-  }
-
   // Start out with document paused
   var svg = SMILUtil.getSVGRoot();
   ok(svg.animationsPaused(), "should be paused by <svg> load handler");
   is(svg.getCurrentTime(), 0, "should be paused at 0 in <svg> load handler");
 
   var testBundles = convertCSSBundlesToMappedAttr(gPacedBundles);
   testBundleList(testBundles, new SMILTimingData(1.0, 6.0));
 
--- a/dom/smil/test/test_smilTextZoom.xhtml
+++ b/dom/smil/test/test_smilTextZoom.xhtml
@@ -30,22 +30,16 @@ SimpleTest.waitForExplicitFinish();
 function verifyStyle(aNode, aPropertyName, aExpectedVal)
 {
   var computedVal = SMILUtil.getComputedStyleSimple(aNode, aPropertyName);
   is(computedVal, aExpectedVal, "computed value of " + aPropertyName);
 }
 
 function main()
 {
-  if (!SMILUtil.isSMILEnabled()) {
-    ok(false, "SMIL dosn't seem to be enabled");
-    SimpleTest.finish();
-    return;
-  }
-
   // Start out pause
   var svg = SMILUtil.getSVGRoot();
   ok(svg.animationsPaused(), "should be paused by <svg> load handler");
   is(svg.getCurrentTime(), 0, "should be paused at 0 in <svg> load handler");
 
   // Set text zoom to 2x
   var origTextZoom =  SpecialPowers.getTextZoom(window);
   SpecialPowers.setTextZoom(window, 2);
--- a/dom/tests/mochitest/beacon/test_beaconContentPolicy.html
+++ b/dom/tests/mochitest/beacon/test_beaconContentPolicy.html
@@ -25,73 +25,84 @@ const Ci = SpecialPowers.Ci;
 // not enabled by default yet.
 SimpleTest.waitForExplicitFinish();
 
 var policy = setupPolicy();
 
 SpecialPowers.pushPrefEnv({'set': [["beacon.enabled", true]]}, beginTest);
 
 function setupPolicy() {
+  info("creating the policy");
   var policyID = SpecialPowers.wrap(SpecialPowers.Components).ID("{b80e19d0-878f-d41b-2654-194714a4115c}");
   var policyName = "@mozilla.org/testpolicy;1";
   var policy = {
     // nsISupports implementation
     QueryInterface: function(iid) {
+      info("QueryInterface called " + iid);
       iid = SpecialPowers.wrap(iid);
       if (iid.equals(Ci.nsISupports) ||
         iid.equals(Ci.nsIFactory) ||
         iid.equals(Ci.nsIContentPolicy))
         return this;
+      info("unknown interface!");
       throw SpecialPowers.Cr.NS_ERROR_NO_INTERFACE;
     },
     
     // nsIFactory implementation
     createInstance: function(outer, iid) {
+      info("createInstance called " + iid);
       return this.QueryInterface(iid);
     },
 
     // nsIContentPolicy implementation
     shouldLoad: function(contentType, contentLocation, requestOrigin, context, mimeTypeGuess, extra) {
+      info("shouldLoad");
+      info("url: " + SpecialPowers.wrap(contentLocation).spec);
       // Remember last content type seen for the test url
 
       if (SpecialPowers.wrap(contentLocation).spec == beaconUrl) {
         is(contentType,  Ci.nsIContentPolicy.TYPE_BEACON, "Beacon content type should match expected.  is: " + contentType + " should be: " + Ci.nsIContentPolicy.TYPE_BEACON);
         teardownPolicy();
         SimpleTest.finish();
       }
 
       return Ci.nsIContentPolicy.ACCEPT;
     },
 
     shouldProcess: function(contentType, contentLocation, requestOrigin, context, mimeTypeGuess, extra) {
+      info("shouldProcess");
+      info("url: " + SpecialPowers.wrap(contentLocation).spec);
       return Ci.nsIContentPolicy.ACCEPT;
     }
   }
   policy = SpecialPowers.wrapCallbackObject(policy);
 
   // Register content policy
+  info("registering the policy");
   var componentManager = SpecialPowers.wrap(SpecialPowers.Components).manager.QueryInterface(Ci.nsIComponentRegistrar);
   componentManager.registerFactory(policyID, "Test content policy", policyName, policy);
 
   var categoryManager = Cc["@mozilla.org/categorymanager;1"].getService(Ci.nsICategoryManager);
   categoryManager.addCategoryEntry("content-policy", policyName, policyName, false, true);
 
+  info("returning the policy");
   return { 'policy': policy, 'policyID': policyID, 'policyName': policyName };
 }
 
 function teardownPolicy() {
   setTimeout(function() {
     // policy will not be removed from the category correctly
     var componentManager = SpecialPowers.wrap(SpecialPowers.Components).manager.QueryInterface(Ci.nsIComponentRegistrar);
     componentManager.unregisterFactory(policy.policyID, policy.policy);
     var categoryManager = Cc["@mozilla.org/categorymanager;1"].getService(Ci.nsICategoryManager);
     categoryManager.deleteCategoryEntry("content-policy", policy.policyName, false);
   }, 0);
 }
 
 function beginTest() {
+  info("sending the beacon");
   navigator.sendBeacon(beaconUrl, "bacon would have been a better name than beacon");
 }
 
 </script>
 </pre>
 </body>
 </html>
--- a/dom/tests/mochitest/localstorage/test_app_uninstall.html
+++ b/dom/tests/mochitest/localstorage/test_app_uninstall.html
@@ -44,93 +44,63 @@ const Cu = Components.utils;
 SimpleTest.waitForExplicitFinish();
 
 var permManager = Cc["@mozilla.org/permissionmanager;1"]
                     .getService(Ci.nsIPermissionManager);
 var appsService = Cc['@mozilla.org/AppsService;1']
                     .getService(Ci.nsIAppsService);
 
 /**
- * This function will make sure that the next applications we try to install
- * will be installed. That means it will behave like if the user allowed the app
- * to be installed in the door hanger.
- */
-function confirmNextInstall() {
-  var panel = window.top.QueryInterface(Ci.nsIInterfaceRequestor)
-                        .getInterface(Ci.nsIWebNavigation)
-                        .QueryInterface(Ci.nsIDocShell)
-                        .chromeEventHandler.ownerDocument.defaultView
-                        .PopupNotifications.panel
-
-  panel.addEventListener("popupshown", function() {
-    panel.removeEventListener("popupshown", arguments.callee, false);
-    this.childNodes[0].button.doCommand();
-  }, false);
-}
-
-/**
  * Initialize the |storage| that has been given with "foo" => "bar".
  * Checks that the storage wasn't initialized and checks that the initialization
  * was successful.
  */
 function setupStorage(storage) {
   is(storage.getItem("foo"), null, "no data");
 
   storage.setItem("foo", "bar");
   is(storage.getItem("foo"), "bar", "data written");
 }
 
 permManager.addFromPrincipal(window.document.nodePrincipal, "webapps-manage",
                              Ci.nsIPermissionManager.ALLOW_ACTION);
 permManager.addFromPrincipal(window.document.nodePrincipal, "browser",
                              Ci.nsIPermissionManager.ALLOW_ACTION);
 
-var previousPrefs = {
-  mozBrowserFramesEnabled: null,
-  installerDryRun: null,
-};
+SimpleTest.registerCleanupFunction(() => {
+  gWitnessStorage.localStorage.clear();
+  gWitnessStorage.sessionStorage.clear();
 
-// Save the prefs we want to change (so we can re-set them later) and set them
-// to the needed value.
-try {
-  previousPrefs.mozBrowserFramesEnabled = SpecialPowers.getBoolPref('dom.mozBrowserFramesEnabled');
-} catch(e)
-{
-}
-SpecialPowers.setBoolPref('dom.mozBrowserFramesEnabled', true);
-
-try {
-  previousPrefs.installerDryRun = SpecialPowers.getBoolPref('browser.mozApps.installer.dry_run');
-} catch(e) {
-}
-SpecialPowers.setBoolPref('browser.mozApps.installer.dry_run', true);
+  permManager.removeFromPrincipal(window.document.nodePrincipal, "webapps-manage",
+                                  Ci.nsIPermissionManager.ALLOW_ACTION);
+  permManager.removeFromPrincipal(window.document.nodePrincipal, "browser",
+                                  Ci.nsIPermissionManager.ALLOW_ACTION);
+});
 
 // URL of the manifest of the app we want to install.
 const gManifestURL = "http://www.example.com/chrome/dom/tests/mochitest/webapps/apps/basic.webapp";
 // ID of the installed app.
 var gTestAppId = 0;
 // Cookies currently in the system.
 var gCurrentCookiesCount = 0;
 // Storages from a non-app to make sure we do not remove cookies from everywhere.
 var gWitnessStorage = {};
 // Storages for the app.
 var gAppStorage = {};
 // Storage for a mozbrowser inside the app.
 var gBrowserStorage = {};
 
-addLoadEvent(function() {
+function runTest() {
   /*
    * We are setuping the witness storage (non-app) and will install the
    * application.
    * When the application is installed, we will insert it in an iframe and wait
    * for the load event. to be fired.
    */
 
-  confirmNextInstall();
-
   gWitnessStorage.localStorage = window.frames[0].localStorage;
   gWitnessStorage.sessionStorage = window.frames[0].sessionStorage;
 
   setupStorage(gWitnessStorage.localStorage);
   setupStorage(gWitnessStorage.sessionStorage);
 
   navigator.mozApps.install(gManifestURL, null).onsuccess = function() {
     gTestAppId = appsService.getAppLocalIdByManifestURL(gManifestURL);
@@ -140,17 +110,17 @@ addLoadEvent(function() {
     frame.setAttribute('mozapp', gManifestURL);
     frame.src = 'http://example.com/tests/error404';
     frame.name = 'app';
 
     frame.addEventListener('load', appFrameLoadEvent);
 
     document.body.appendChild(frame);
   };
-});
+}
 
 function appFrameLoadEvent() {
   /*
    * The app frame has been loaded. We can now add permissions for the app to
    * create browsers and we will load a page in this browser and wait for the
    * load event.
    */
   permManager.addFromPrincipal(window.frames[1].document.nodePrincipal, "browser",
@@ -197,40 +167,27 @@ function browserLoadEvent() {
           is(gBrowserStorage.localStorage.getItem("foo"), null, "localstorage data have been deleted");
 
           is(gAppStorage.sessionStorage.getItem("foo"), "bar", "sessionstorage data have not been deleted");
           is(gBrowserStorage.sessionStorage.getItem("foo"), "bar", "sessionstorage data have not been deleted");
 
           is(gWitnessStorage.localStorage.getItem("foo"), "bar", "data are still there");
           is(gWitnessStorage.sessionStorage.getItem("foo"), "bar", "data are still there");
 
-          finish();
+          SimpleTest.finish();
 
           return;
         };
       }
     }
   };
 }
 
-/**
- * This method will be called when the test will be done. It is going to clear
- * all storage data, permissions, etc.
- */
-function finish() {
-  gWitnessStorage.localStorage.clear();
-  gWitnessStorage.sessionStorage.clear();
-
-  permManager.removeFromPrincipal(window.document.nodePrincipal, "webapps-manage",
-                                  Ci.nsIPermissionManager.ALLOW_ACTION);
-  permManager.removeFromPrincipal(window.document.nodePrincipal, "browser",
-                                  Ci.nsIPermissionManager.ALLOW_ACTION);
-
-  SpecialPowers.setBoolPref('dom.mozBrowserFramesEnabled', previousPrefs.mozBrowserFramesEnabled);
-  SpecialPowers.setBoolPref('browser.mozApps.installer.dry_run', previousPrefs.installerDryRun);
-
-  SimpleTest.finish();
-}
+addLoadEvent(() =>
+  SpecialPowers.pushPrefEnv({set: [['dom.mozBrowserFramesEnabled', true]]}, () =>
+    SpecialPowers.autoConfirmAppInstall(runTest)
+  )
+);
 
 </script>
 </pre>
 </body>
 </html>
--- a/dom/tests/mochitest/localstorage/test_clear_browser_data.html
+++ b/dom/tests/mochitest/localstorage/test_clear_browser_data.html
@@ -44,37 +44,16 @@ const Cu = Components.utils;
 
 SimpleTest.waitForExplicitFinish();
 
 var permManager = Cc["@mozilla.org/permissionmanager;1"]
                     .getService(Ci.nsIPermissionManager);
 var appsService = Cc['@mozilla.org/AppsService;1']
                     .getService(Ci.nsIAppsService);
 
-var Webapps = {};
-Cu.import("resource://gre/modules/Webapps.jsm", Webapps);
-
-/**
- * This function will make sure that the next applications we try to install
- * will be installed. That means it will behave like if the user allowed the app
- * to be installed in the door hanger.
- */
-function confirmNextInstall() {
-  var panel = window.top.QueryInterface(Ci.nsIInterfaceRequestor)
-                        .getInterface(Ci.nsIWebNavigation)
-                        .QueryInterface(Ci.nsIDocShell)
-                        .chromeEventHandler.ownerDocument.defaultView
-                        .PopupNotifications.panel
-
-  panel.addEventListener("popupshown", function() {
-    panel.removeEventListener("popupshown", arguments.callee, false);
-    this.childNodes[0].button.doCommand();
-  }, false);
-}
-
 /**
  * Initialize the |storage| that has been given with "foo" => "bar".
  * Checks that the storage wasn't initialized and checks that the initialization
  * was successful.
  */
 function setupStorage(storage) {
   is(storage.getItem("foo"), null, "no data");
 
@@ -82,63 +61,50 @@ function setupStorage(storage) {
   is(storage.getItem("foo"), "bar", "data written");
 }
 
 permManager.addFromPrincipal(window.document.nodePrincipal, "webapps-manage",
                              Ci.nsIPermissionManager.ALLOW_ACTION);
 permManager.addFromPrincipal(window.document.nodePrincipal, "browser",
                              Ci.nsIPermissionManager.ALLOW_ACTION);
 
-var previousPrefs = {
-  mozBrowserFramesEnabled: null,
-  installerDryRun: null,
-};
+SimpleTest.registerCleanupFunction(() => {
+  gWitnessStorage.localStorage.clear();
+  gWitnessStorage.sessionStorage.clear();
 
-// Save the prefs we want to change (so we can re-set them later) and set them
-// to the needed value.
-try {
-  previousPrefs.mozBrowserFramesEnabled = SpecialPowers.getBoolPref('dom.mozBrowserFramesEnabled');
-} catch(e)
-{
-}
-SpecialPowers.setBoolPref('dom.mozBrowserFramesEnabled', true);
-
-try {
-  previousPrefs.installerDryRun = SpecialPowers.getBoolPref('browser.mozApps.installer.dry_run');
-} catch(e) {
-}
-SpecialPowers.setBoolPref('browser.mozApps.installer.dry_run', true);
+  permManager.removeFromPrincipal(window.document.nodePrincipal, "webapps-manage",
+                                  Ci.nsIPermissionManager.ALLOW_ACTION);
+  permManager.removeFromPrincipal(window.document.nodePrincipal, "browser",
+                                  Ci.nsIPermissionManager.ALLOW_ACTION);
+});
 
 // We want to simulate that all apps are launchable, for testing purpose.
-var gPreviousLaunchableValue = Webapps.DOMApplicationRegistry.allAppsLaunchable;
-Webapps.DOMApplicationRegistry.allAppsLaunchable = true;
+SpecialPowers.setAllAppsLaunchable(true);
 
 // URL of the manifest of the app we want to install.
 const gManifestURL = "http://www.example.com/chrome/dom/tests/mochitest/webapps/apps/basic.webapp";
 // ID of the installed app.
 var gTestAppId = 0;
 // Cookies currently in the system.
 var gCurrentCookiesCount = 0;
 // Storages from a non-app to make sure we do not remove cookies from everywhere.
 var gWitnessStorage = {};
 // Storages for the app.
 var gAppStorage = {};
 // Storage for a mozbrowser inside the app.
 var gBrowserStorage = {};
 
-addLoadEvent(function() {
+function runTest() {
   /*
    * We are setuping the witness storage (non-app) and will install the
    * application.
    * When the application is installed, we will insert it in an iframe and wait
    * for the load event. to be fired.
    */
 
-  confirmNextInstall();
-
   gWitnessStorage.localStorage = window.frames[0].localStorage;
   gWitnessStorage.sessionStorage = window.frames[0].sessionStorage;
 
   setupStorage(gWitnessStorage.localStorage);
   setupStorage(gWitnessStorage.sessionStorage);
 
   navigator.mozApps.install(gManifestURL, null).onsuccess = function() {
     gTestAppId = appsService.getAppLocalIdByManifestURL(gManifestURL);
@@ -148,17 +114,17 @@ addLoadEvent(function() {
     frame.setAttribute('mozapp', gManifestURL);
     frame.src = 'http://www.example.com/chrome/dom/tests/mochitest/localstorage/frame_clear_browser_data.html';
     frame.name = 'app';
 
     frame.addEventListener('load', appFrameLoadEvent);
 
     document.body.appendChild(frame);
   };
-});
+}
 
 function appFrameLoadEvent() {
   /*
    * The app frame has been loaded. We can now add permissions for the app to
    * create browsers and we will load a page in this browser and wait for the
    * load event.
    */
   permManager.addFromPrincipal(window.frames[1].document.nodePrincipal, "browser",
@@ -214,42 +180,27 @@ function checks() {
         is(gBrowserStorage.sessionStorage.getItem("foo"), "bar", "sessionstorage data have not been deleted");
 
         is(gAppStorage.localStorage.getItem("foo"), "bar", "data are still there");
         is(gAppStorage.sessionStorage.getItem("foo"), "bar", "data are still there");
 
         is(gWitnessStorage.localStorage.getItem("foo"), "bar", "data are still there");
         is(gWitnessStorage.sessionStorage.getItem("foo"), "bar", "data are still there");
 
-        Webapps.DOMApplicationRegistry.allAppsLaunchable = gPreviousLaunchableValue;
-
         // Now we uninstall the app and make sure everything is clean.
         navigator.mozApps.mgmt.uninstall(app).onsuccess = function() {
-          finish();
+          SimpleTest.finish();
         };
       }
     }
   };
 }
 
-/**
- * This method will be called when the test will be done. It is going to clear
- * all storage data, permissions, etc.
- */
-function finish() {
-  gWitnessStorage.localStorage.clear();
-  gWitnessStorage.sessionStorage.clear();
-
-  permManager.removeFromPrincipal(window.document.nodePrincipal, "webapps-manage",
-                                  Ci.nsIPermissionManager.ALLOW_ACTION);
-  permManager.removeFromPrincipal(window.document.nodePrincipal, "browser",
-                                  Ci.nsIPermissionManager.ALLOW_ACTION);
-
-  SpecialPowers.setBoolPref('dom.mozBrowserFramesEnabled', previousPrefs.mozBrowserFramesEnabled);
-  SpecialPowers.setBoolPref('browser.mozApps.installer.dry_run', previousPrefs.installerDryRun);
-
-  SimpleTest.finish();
-}
+addLoadEvent(() =>
+  SpecialPowers.pushPrefEnv({set: [['dom.mozBrowserFramesEnabled', true]]}, () =>
+    SpecialPowers.autoConfirmAppInstall(runTest)
+  )
+);
 
 </script>
 </pre>
 </body>
 </html>
--- a/dom/tests/mochitest/webapps/file_bug_779982.js
+++ b/dom/tests/mochitest/webapps/file_bug_779982.js
@@ -1,10 +1,10 @@
 SimpleTest.waitForExplicitFinish();
-var originalAllAppsLaunchable = SpecialPowers.setAllAppsLaunchable(true);
+SpecialPowers.setAllAppsLaunchable(true);
 
 var fileTestOnCurrentOrigin = 'http://example.org/tests/dom/tests/mochitest/webapps/file_bug_779982.html';
 
 var gData = [
   // APP 1
   {
     app: 'http://example.org/manifest.webapp',
     action: 'getSelf',
@@ -63,17 +63,16 @@ function runTest() {
     }
 
     if (data.app || data.browser) {
       iframe.addEventListener('mozbrowsershowmodalprompt', function(e) {
         is(e.detail.message, 'success', data.message);
 
         i++;
         if (i >= gData.length) {
-          SpecialPowers.setAllAppsLaunchable(originalAllAppsLaunchable);
           SimpleTest.finish();
         } else {
           gTestRunner.next();
         }
       });
     }
 
     iframe.src = data.src + '?' + data.action + '&' + data.isnull;
--- a/extensions/cookie/test/test_app_uninstall_cookies.html
+++ b/extensions/cookie/test/test_app_uninstall_cookies.html
@@ -45,45 +45,16 @@ var cookies = [
     loadContext: null },
   { cookieName: 'LCC_App_BrowT_PrivF',
     loadContext: null },
   { cookieName: 'AppUninstall_Witness',
     loadContext: new LoadContextCallback(0, false, false, 1) },
 ];
 var counter = 0;
 
-function confirmNextInstall() {
-  var panel = window.top.QueryInterface(Ci.nsIInterfaceRequestor)
-                        .getInterface(Ci.nsIWebNavigation)
-                        .QueryInterface(Ci.nsIDocShell)
-                        .chromeEventHandler.ownerDocument.defaultView
-                        .PopupNotifications.panel
-
-  panel.addEventListener("popupshown", function() {
-    panel.removeEventListener("popupshown", arguments.callee, false);
-    this.childNodes[0].button.doCommand();
-  }, false);
-}
-
-// If aAppId = -1, returns permissions count, regardless of app.
-function getPermissionCountForApp(aAppId) {
-  var nbPermissions = 0;
-  var enumerator = permManager.enumerator;
-
-  while (enumerator.hasMoreElements()) {
-    var permission = enumerator.getNext().QueryInterface(Ci.nsIPermission);
-
-    if (permission.appId == aAppId || aAppId == -1) {
-      nbPermissions++;
-    }
-  }
-
-  return nbPermissions;
-}
-
 function getCookiesCountForApp(aAppId) {
   var nbCookies = 0;
   var enumerator = cookieMng.getCookiesForApp(aAppId, false);
 
   while (enumerator.hasMoreElements()) {
     enumerator.getNext();
     nbCookies++;
   }
@@ -140,52 +111,43 @@ function setNextCookie(request, data, co
     // all cookies set: switch to checking them
     counter = 0;
     checkCookie();
   } else {
     setCookie();
   }
 }
 
-var previousDryRunValue = null;
-try {
-  previousDryRunValue = SpecialPowers.getBoolPref('browser.mozApps.installer.dry_run');
-} catch(e) {
-}
-
-SpecialPowers.setBoolPref('browser.mozApps.installer.dry_run', true);
-
-var previousCookiePrefValue = SpecialPowers.getIntPref('network.cookie.cookieBehavior');
-
-SpecialPowers.setIntPref('network.cookie.cookieBehavior', 0);
-
 permManager.addFromPrincipal(window.document.nodePrincipal, "webapps-manage",
                              Ci.nsIPermissionManager.ALLOW_ACTION);
 
-var gManifestURL = "http://www.example.com/chrome/dom/tests/mochitest/webapps/apps/basic.webapp";
+SimpleTest.registerCleanupFunction(() =>
+  permManager.removeFromPrincipal(window.document.nodePrincipal, "webapps-manage",
+                                  Ci.nsIPermissionManager.ALLOW_ACTION)
+);
 
-confirmNextInstall();
+var gManifestURL = "http://www.example.com/chrome/dom/tests/mochitest/webapps/apps/basic.webapp";
 
 var gTestAppId = 0;
 var gCurrentCookiesCount = 0;
 
-navigator.mozApps.install(gManifestURL, null).onsuccess = function() {
+function onInstall() {
   gTestAppId = appsService.getAppLocalIdByManifestURL(gManifestURL);
 
   cookies[0].loadContext = new LoadContextCallback(gTestAppId, false, false, 1);
   cookies[1].loadContext = new LoadContextCallback(gTestAppId, true, false, 1);
 
   is(getCookiesCountForApp(gTestAppId), 0, "App should have no cookies");
 
   httpserver.registerPathHandler(cookieSetPath, cookieSetHandler);
   httpserver.registerPathHandler(cookieCheckPath, cookieCheckHandler);
   httpserver.start(4444);
 
   setCookie();
-};
+}
 
 function checkCookie() {
   var appCookiesCount = getCookiesCountForApp(gTestAppId);
   is(appCookiesCount, 2, "App should have two cookies");
 
   gCurrentCookiesCount = getCookiesCount() - appCookiesCount;
 
   // Not installed means not installed as native app.
@@ -194,28 +156,26 @@ function checkCookie() {
       var app = this.result[i];
       if (app.manifestURL == gManifestURL) {
         navigator.mozApps.mgmt.uninstall(app).onsuccess = function() {
           is(getCookiesCountForApp(gTestAppId), 0, "App should have no cookies");
 
           is(getCookiesCount(), gCurrentCookiesCount,
              "Number of cookies should not have changed");
 
-          SpecialPowers.setBoolPref('browser.mozApps.installer.dry_run', previousDryRunValue);
-          SpecialPowers.setIntPref('network.cookie.cookieBehavior', previousCookiePrefValue);
-          permManager.removeFromPrincipal(window.document.nodePrincipal, "webapps-manage",
-                                       Ci.nsIPermissionManager.ALLOW_ACTION);
-
           httpserver.stop(function() {
             SimpleTest.finish();
           });
-
-          return;
         };
       }
     }
   };
 }
 
+SpecialPowers.pushPrefEnv({set: [['network.cookie.cookieBehavior', 0]]}, () =>
+  SpecialPowers.autoConfirmAppInstall(() =>
+    navigator.mozApps.install(gManifestURL, null).onsuccess = onInstall
+  )
+);
 </script>
 </pre>
 </body>
 </html>
--- a/extensions/cookie/test/test_app_uninstall_permissions.html
+++ b/extensions/cookie/test/test_app_uninstall_permissions.html
@@ -29,60 +29,43 @@ var permManager = Cc["@mozilla.org/permi
                     .getService(Ci.nsIPermissionManager);
 var appsService = Cc['@mozilla.org/AppsService;1']
                     .getService(Ci.nsIAppsService);
 var secMan = Cc['@mozilla.org/scriptsecuritymanager;1']
                .getService(Ci.nsIScriptSecurityManager);
 var ioService = Cc["@mozilla.org/network/io-service;1"]
                   .getService(Components.interfaces.nsIIOService);
 
-function confirmNextInstall() {
-  var panel = window.top.QueryInterface(Ci.nsIInterfaceRequestor)
-                        .getInterface(Ci.nsIWebNavigation)
-                        .QueryInterface(Ci.nsIDocShell)
-                        .chromeEventHandler.ownerDocument.defaultView
-                        .PopupNotifications.panel
-
-  panel.addEventListener("popupshown", function() {
-    panel.removeEventListener("popupshown", arguments.callee, false);
-    this.childNodes[0].button.doCommand();
-  }, false);
-}
-
 // If aAppId = -1, returns permissions count, regardless of app.
 function getPermissionCountForApp(aAppId) {
   var nbPermissions = 0;
   var enumerator = permManager.enumerator;
 
   while (enumerator.hasMoreElements()) {
     var permission = enumerator.getNext().QueryInterface(Ci.nsIPermission);
 
     if (permission.appId == aAppId || aAppId == -1) {
       nbPermissions++;
     }
   }
 
   return nbPermissions;
 }
 
-var previousDryRunValue = null;
-try {
-  previousDryRunValue = SpecialPowers.getBoolPref('browser.mozApps.installer.dry_run');
-} catch(e) {
-}
-
-SpecialPowers.setBoolPref('browser.mozApps.installer.dry_run', true);
 permManager.addFromPrincipal(window.document.nodePrincipal, "webapps-manage",
                              Ci.nsIPermissionManager.ALLOW_ACTION);
 
+SimpleTest.registerCleanupFunction(() =>
+  permManager.removeFromPrincipal(window.document.nodePrincipal, "webapps-manage",
+                                  Ci.nsIPermissionManager.ALLOW_ACTION)
+);
+
 var gManifestURL = "http://www.example.com/chrome/dom/tests/mochitest/webapps/apps/basic.webapp";
 
-confirmNextInstall();
-
-navigator.mozApps.install(gManifestURL, null).onsuccess = function() {
+function onInstall() {
   var testAppId = appsService.getAppLocalIdByManifestURL(gManifestURL);
 
   is(getPermissionCountForApp(testAppId), 0, "App should have no permission");
 
   var currentPermissionCount = getPermissionCountForApp(-1);
 
   var principal = secMan.getAppCodebasePrincipal(ioService.newURI("http://www.example.com", null, null),
                                                  testAppId, false);
@@ -107,23 +90,23 @@ navigator.mozApps.install(gManifestURL, 
       var app = this.result[i];
       if (app.manifestURL == gManifestURL) {
         navigator.mozApps.mgmt.uninstall(app).onsuccess = function() {
           is(getPermissionCountForApp(testAppId), 0, "App should have no permissions");
 
           is(getPermissionCountForApp(-1), currentPermissionCount,
              "Number of permissions should not have changed");
 
-          SpecialPowers.setBoolPref('browser.mozApps.installer.dry_run', previousDryRunValue);
-          permManager.removeFromPrincipal(window.document.nodePrincipal, "webapps-manage",
-                                       Ci.nsIPermissionManager.ALLOW_ACTION);
           SimpleTest.finish();
-          return;
         };
       }
     }
   };
-};
+}
+
+SpecialPowers.autoConfirmAppInstall(() =>
+  navigator.mozApps.install(gManifestURL, null).onsuccess = onInstall
+);
 
 </script>
 </pre>
 </body>
 </html>
--- a/gfx/2d/SourceSurfaceSkia.cpp
+++ b/gfx/2d/SourceSurfaceSkia.cpp
@@ -68,22 +68,17 @@ SourceSurfaceSkia::InitFromData(unsigned
   temp.setConfig(GfxFormatToSkiaConfig(aFormat), aSize.width, aSize.height, aStride);
   temp.setPixels(aData);
 
   if (!temp.copyTo(&mBitmap, GfxFormatToSkiaColorType(aFormat))) {
     return false;
   }
 
   if (aFormat == SurfaceFormat::B8G8R8X8) {
-    mBitmap.lockPixels();
-    // We have to manually set the A channel to be 255 as Skia doesn't understand BGRX
-    ConvertBGRXToBGRA(reinterpret_cast<unsigned char*>(mBitmap.getPixels()), aSize, mBitmap.rowBytes());
-    mBitmap.unlockPixels();
-    mBitmap.notifyPixelsChanged();
-    mBitmap.setAlphaType(kOpaque_SkAlphaType);
+    mBitmap.setAlphaType(kIgnore_SkAlphaType);
   }
 
   mSize = aSize;
   mFormat = aFormat;
   mStride = mBitmap.rowBytes();
   return true;
 }
 
--- a/gfx/layers/ipc/ImageBridgeChild.cpp
+++ b/gfx/layers/ipc/ImageBridgeChild.cpp
@@ -303,30 +303,39 @@ bool ImageBridgeChild::IsCreated()
 }
 
 void ImageBridgeChild::StartUp()
 {
   NS_ASSERTION(NS_IsMainThread(), "Should be on the main Thread!");
   ImageBridgeChild::StartUpOnThread(new Thread("ImageBridgeChild"));
 }
 
+#ifdef MOZ_NUWA_PROCESS
+#include "ipc/Nuwa.h"
+#endif
+
 static void
 ConnectImageBridgeInChildProcess(Transport* aTransport,
                                  ProcessHandle aOtherProcess)
 {
   // Bind the IPC channel to the image bridge thread.
   sImageBridgeChildSingleton->Open(aTransport, aOtherProcess,
                                    XRE_GetIOMessageLoop(),
                                    ipc::ChildSide);
+#ifdef MOZ_NUWA_PROCESS
+  if (IsNuwaProcess()) {
+    sImageBridgeChildThread
+      ->message_loop()->PostTask(FROM_HERE,
+                                 NewRunnableFunction(NuwaMarkCurrentThread,
+                                                     (void (*)(void *))nullptr,
+                                                     (void *)nullptr));
+  }
+#endif
 }
 
-#ifdef MOZ_NUWA_PROCESS
-#include "ipc/Nuwa.h"
-#endif
-
 static void ReleaseImageClientNow(ImageClient* aClient)
 {
   MOZ_ASSERT(InImageBridgeChildThread());
   aClient->Release();
 }
 
 // static
 void ImageBridgeChild::DispatchReleaseImageClient(ImageClient* aClient)
@@ -523,26 +532,16 @@ ImageBridgeChild::StartUpInChildProcess(
     return nullptr;
   }
 
   sImageBridgeChildThread = new Thread("ImageBridgeChild");
   if (!sImageBridgeChildThread->Start()) {
     return nullptr;
   }
 
-#ifdef MOZ_NUWA_PROCESS
-  if (IsNuwaProcess()) {
-    sImageBridgeChildThread
-      ->message_loop()->PostTask(FROM_HERE,
-                                 NewRunnableFunction(NuwaMarkCurrentThread,
-                                                     (void (*)(void *))nullptr,
-                                                     (void *)nullptr));
-  }
-#endif
-
   sImageBridgeChildSingleton = new ImageBridgeChild();
   sImageBridgeChildSingleton->GetMessageLoop()->PostTask(
     FROM_HERE,
     NewRunnableFunction(ConnectImageBridgeInChildProcess,
                         aTransport, processHandle));
 
   return sImageBridgeChildSingleton;
 }
--- a/gfx/thebes/gfxFont.cpp
+++ b/gfx/thebes/gfxFont.cpp
@@ -1118,17 +1118,17 @@ gfxFontFamily::FindFontForChar(GlobalFon
         int32_t rank = 0;
 
         if (fe->TestCharacterMap(aMatchData->mCh)) {
             rank += RANK_MATCHED_CMAP;
             aMatchData->mCount++;
 #ifdef PR_LOGGING
             PRLogModuleInfo *log = gfxPlatform::GetLog(eGfxLog_textrun);
 
-            if (MOZ_UNLIKELY(log)) {
+            if (MOZ_UNLIKELY(PR_LOG_TEST(log, PR_LOG_DEBUG))) {
                 uint32_t unicodeRange = FindCharUnicodeRange(aMatchData->mCh);
                 uint32_t script = GetScriptCode(aMatchData->mCh);
                 PR_LOG(log, PR_LOG_DEBUG,\
                        ("(textrun-systemfallback-fonts) char: u+%6.6x "
                         "unicode-range: %d script: %d match: [%s]\n",
                         aMatchData->mCh,
                         unicodeRange, script,
                         NS_ConvertUTF16toUTF8(fe->Name()).get()));
@@ -5012,17 +5012,17 @@ gfxFontGroup::InitTextRun(gfxContext *aC
     PRLogModuleInfo *log = (mStyle.systemFont ?
                             gfxPlatform::GetLog(eGfxLog_textrunui) :
                             gfxPlatform::GetLog(eGfxLog_textrun));
 #endif
 
     if (sizeof(T) == sizeof(uint8_t) && !transformedString) {
 
 #ifdef PR_LOGGING
-        if (MOZ_UNLIKELY(log)) {
+        if (MOZ_UNLIKELY(PR_LOG_TEST(log, PR_LOG_WARNING))) {
             nsAutoCString lang;
             mStyle.language->ToUTF8String(lang);
             nsAutoCString str((const char*)aString, aLength);
             PR_LOG(log, PR_LOG_WARNING,\
                    ("(%s) fontgroup: [%s] lang: %s script: %d len %d "
                     "weight: %d width: %d style: %s size: %6.2f %d-byte "
                     "TEXTRUN [%s] ENDTEXTRUN\n",
                     (mStyle.systemFont ? "textrunui" : "textrun"),
@@ -5056,17 +5056,17 @@ gfxFontGroup::InitTextRun(gfxContext *aC
         // the font matching process below
         gfxScriptItemizer scriptRuns(textPtr, aLength);
 
         uint32_t runStart = 0, runLimit = aLength;
         int32_t runScript = MOZ_SCRIPT_LATIN;
         while (scriptRuns.Next(runStart, runLimit, runScript)) {
 
 #ifdef PR_LOGGING
-            if (MOZ_UNLIKELY(log)) {
+            if (MOZ_UNLIKELY(PR_LOG_TEST(log, PR_LOG_WARNING))) {
                 nsAutoCString lang;
                 mStyle.language->ToUTF8String(lang);
                 uint32_t runLen = runLimit - runStart;
                 PR_LOG(log, PR_LOG_WARNING,\
                        ("(%s) fontgroup: [%s] lang: %s script: %d len %d "
                         "weight: %d width: %d style: %s size: %6.2f %d-byte "
                         "TEXTRUN [%s] ENDTEXTRUN\n",
                         (mStyle.systemFont ? "textrunui" : "textrun"),
--- a/gfx/thebes/gfxPlatformFontList.cpp
+++ b/gfx/thebes/gfxPlatformFontList.cpp
@@ -443,17 +443,17 @@ gfxPlatformFontList::SystemFindFontForCh
         fontEntry = GlobalFontFallback(aCh, aRunScript, aStyle, cmapCount,
                                        &fallbackFamily);
     }
     TimeDuration elapsed = TimeStamp::Now() - start;
 
 #ifdef PR_LOGGING
     PRLogModuleInfo *log = gfxPlatform::GetLog(eGfxLog_textrun);
 
-    if (MOZ_UNLIKELY(log)) {
+    if (MOZ_UNLIKELY(PR_LOG_TEST(log, PR_LOG_WARNING))) {
         uint32_t unicodeRange = FindCharUnicodeRange(aCh);
         int32_t script = mozilla::unicode::GetScriptCode(aCh);
         PR_LOG(log, PR_LOG_WARNING,\
                ("(textrun-systemfallback-%s) char: u+%6.6x "
                  "unicode-range: %d script: %d match: [%s]"
                 " time: %dus cmaps: %d\n",
                 (common ? "common" : "global"), aCh,
                  unicodeRange, script,
--- a/ipc/glue/MessageLink.cpp
+++ b/ipc/glue/MessageLink.cpp
@@ -5,16 +5,21 @@
  * 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/ipc/MessageLink.h"
 #include "mozilla/ipc/MessageChannel.h"
 #include "mozilla/ipc/BrowserProcessSubThread.h"
 #include "mozilla/ipc/ProtocolUtils.h"
 
+#ifdef MOZ_NUWA_PROCESS
+#include "ipc/Nuwa.h"
+#include "mozilla/Preferences.h"
+#endif
+
 #include "nsDebug.h"
 #include "nsISupportsImpl.h"
 #include "nsXULAppAPI.h"
 
 using namespace mozilla;
 using namespace std;
 
 template<>
@@ -119,16 +124,27 @@ ProcessLink::Open(mozilla::ipc::Transpor
             // Transport::Connect() has already been called.  Take
             // over the channel from the previous listener and process
             // any queued messages.
             mIOLoop->PostTask(
                 FROM_HERE,
                 NewRunnableMethod(this, &ProcessLink::OnTakeConnectedChannel));
         }
 
+#ifdef MOZ_NUWA_PROCESS
+        if (IsNuwaProcess() &&
+            Preferences::GetBool("dom.ipc.processPrelaunch.testMode")) {
+            // The pref value is turned on in a deadlock test against the Nuwa
+            // process. The sleep here makes it easy to trigger the deadlock
+            // that an IPC channel is still opening but the worker loop is
+            // already frozen.
+            sleep(5);
+        }
+#endif
+
         // Should not wait here if something goes wrong with the channel.
         while (!mChan->Connected() && mChan->mChannelState != ChannelError) {
             mChan->mMonitor->Wait();
         }
     }
 }
 
 void
new file mode 100644
--- /dev/null
+++ b/layout/reftests/bugs/926155-1-ref.html
@@ -0,0 +1,8 @@
+<!DOCTYPE html>
+<html>
+  <body>
+    <div style="overflow-x:hidden">
+      <div id="testdiv" style="width:200px;margin-top:50px;height:10px;background-color:green"></div>
+    </div>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/bugs/926155-1.html
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+  <body>
+    <div style="overflow:hidden;height:100px">
+      <div style="overflow-x:hidden;position:sticky;top:50px">
+        <div id="testdiv" style="width:10px;height:10px;background-color:green"></div>
+      </div>
+    </div>
+  <script>
+    function doTest() {
+      var x = document.getElementById('testdiv');
+      x.style.width = "200px";
+      document.documentElement.className = "";
+    }
+    document.addEventListener("MozReftestInvalidate", doTest, false);
+  </script>
+  </body>
+</html>
--- a/layout/reftests/bugs/reftest.list
+++ b/layout/reftests/bugs/reftest.list
@@ -1776,16 +1776,17 @@ skip-if(B2G&&browserIsRemote) == 858803-
 == 883987-1f.html 883987-1-ref.html
 == 890495-1.html 890495-1-ref.html
 == 894931-1.html 894931-1-ref.html
 == 897491-1.html 897491-1-ref.html
 == 897491-2.html 897491-2-ref.html
 fuzzy(1,10000) fuzzy-if(Android&&AndroidVersion>=15,5,10000) == 902330-1.html 902330-1-ref.html
 fuzzy-if(Android,8,400) == 906199-1.html 906199-1-ref.html
 == 921716-1.html 921716-1-ref.html
+== 926155-1.html 926155-1-ref.html
 fuzzy-if(cocoaWidget,1,40) == 928607-1.html 928607-1-ref.html
 == 931464-1.html 931464-1-ref.html
 == 931853.html 931853-ref.html
 == 931853-quirks.html 931853-quirks-ref.html
 fuzzy-if(OSX==10.6,2,30) skip-if(B2G&&browserIsRemote) == 933264-1.html 933264-1-ref.html
 == 936670-1.svg 936670-1-ref.svg
 == 941940-1.html 941940-1-ref.html
 fails == 942017.html 942017-ref.html # bug 942017
--- a/media/webrtc/signaling/src/mediapipeline/MediaPipeline.cpp
+++ b/media/webrtc/signaling/src/mediapipeline/MediaPipeline.cpp
@@ -982,26 +982,27 @@ void MediaPipelineTransmit::PipelineList
         }
         break;
       case AUDIO_FORMAT_S16:
         {
           const short* buf = static_cast<const short *>(chunk.mChannelData[0]);
           ConvertAudioSamplesWithScale(buf, samples, chunk.mDuration, chunk.mVolume);
         }
         break;
+      case AUDIO_FORMAT_SILENCE:
+        memset(samples, 0, chunk.mDuration * sizeof(samples[0]));
+        break;
       default:
         MOZ_ASSERT(PR_FALSE);
         return;
         break;
     }
   } else {
     // This means silence.
-    for (uint32_t i = 0; i < chunk.mDuration; ++i) {
-      samples[i] = 0;
-    }
+    memset(samples, 0, chunk.mDuration * sizeof(samples[0]));
   }
 
   MOZ_ASSERT(!(rate%100)); // rate should be a multiple of 100
 
   // Check if the rate has changed since the last time we came through
   // I realize it may be overkill to check if the rate has changed, but
   // I believe it is possible (e.g. if we change sources) and it costs us
   // very little to handle this case
--- a/mobile/android/components/MobileComponents.manifest
+++ b/mobile/android/components/MobileComponents.manifest
@@ -93,19 +93,21 @@ contract @mozilla.org/dom/site-specific-
 # PaymentsUI.js
 component {3c6c9575-f57e-427b-a8aa-57bc3cbff48f} PaymentsUI.js
 contract @mozilla.org/payment/ui-glue;1 {3c6c9575-f57e-427b-a8aa-57bc3cbff48f}
 
 # FilePicker.js
 component {18a4e042-7c7c-424b-a583-354e68553a7f} FilePicker.js
 contract @mozilla.org/filepicker;1 {18a4e042-7c7c-424b-a583-354e68553a7f}
 
+#ifndef RELEASE_BUILD
 # TabSource.js
 component {5850c76e-b916-4218-b99a-31f004e0a7e7} TabSource.js
 contract @mozilla.org/tab-source-service;1 {5850c76e-b916-4218-b99a-31f004e0a7e7}
+#endif
 
 # Snippets.js
 component {a78d7e59-b558-4321-a3d6-dffe2f1e76dd} Snippets.js
 contract @mozilla.org/snippets;1 {a78d7e59-b558-4321-a3d6-dffe2f1e76dd}
 category profile-after-change Snippets @mozilla.org/snippets;1
 category update-timer Snippets @mozilla.org/snippets;1,getService,snippets-update-timer,browser.snippets.updateInterval,86400
 
 #ifdef MOZ_ANDROID_SYNTHAPKS
--- a/mobile/android/installer/package-manifest.in
+++ b/mobile/android/installer/package-manifest.in
@@ -329,17 +329,19 @@
 @BINPATH@/components/NetworkGeolocationProvider.js
 @BINPATH@/components/nsSidebar.manifest
 @BINPATH@/components/nsSidebar.js
 @BINPATH@/components/extensions.manifest
 @BINPATH@/components/addonManager.js
 @BINPATH@/components/amContentHandler.js
 @BINPATH@/components/amWebInstallListener.js
 @BINPATH@/components/nsBlocklistService.js
+#ifndef RELEASE_BUILD
 @BINPATH@/components/TabSource.js
+#endif
 @BINPATH@/components/webvtt.xpt
 @BINPATH@/components/WebVTT.manifest
 @BINPATH@/components/WebVTTParserWrapper.js
 
 #ifdef MOZ_UPDATER
 @BINPATH@/components/nsUpdateService.manifest
 @BINPATH@/components/nsUpdateService.js
 @BINPATH@/components/nsUpdateServiceStub.js
--- a/mobile/locales/en-US/searchplugins/bing.xml
+++ b/mobile/locales/en-US/searchplugins/bing.xml
@@ -8,18 +8,18 @@
 <Url type="application/x-suggestions+json" template="http://api.bing.com/osjson.aspx">
   <Param name="query" value="{searchTerms}"/>
   <Param name="form" value="OSDJAS"/>
   <Param name="language" value="{moz:locale}"/>
 </Url>
 <!-- this is effectively x-moz-phonesearch, but search service expects a text/html entry -->
 <Url type="text/html" method="GET" template="http://www.bing.com/search">
   <Param name="q" value="{searchTerms}" />
-  <MozParam name="pc" value="MOZB" />
-  <MozParam name="form" value="MOZMBA" />
+  <Param name="pc" value="MOZB" />
+  <Param name="form" value="MOZMBA" />
 </Url>
 <Url type="application/x-moz-tabletsearch" method="GET" template="http://www.bing.com/search">
   <Param name="q" value="{searchTerms}" />
-  <MozParam name="pc" value="MOZA" />
-  <MozParam name="form" value="MOZAT" />
+  <Param name="pc" value="MOZA" />
+  <Param name="form" value="MOZAT" />
 </Url>
 <SearchForm>http://www.bing.com</SearchForm>
 </SearchPlugin>
--- a/mobile/locales/en-US/searchplugins/yahoo.xml
+++ b/mobile/locales/en-US/searchplugins/yahoo.xml
@@ -6,12 +6,12 @@
 <ShortName>Yahoo</ShortName>
 <InputEncoding>UTF-8</InputEncoding>
 <Image width="16" height="16">data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAEJGlDQ1BJQ0MgUHJvZmlsZQAAOBGFVd9v21QUPolvUqQWPyBYR4eKxa9VU1u5GxqtxgZJk6XtShal6dgqJOQ6N4mpGwfb6baqT3uBNwb8AUDZAw9IPCENBmJ72fbAtElThyqqSUh76MQPISbtBVXhu3ZiJ1PEXPX6yznfOec7517bRD1fabWaGVWIlquunc8klZOnFpSeTYrSs9RLA9Sr6U4tkcvNEi7BFffO6+EdigjL7ZHu/k72I796i9zRiSJPwG4VHX0Z+AxRzNRrtksUvwf7+Gm3BtzzHPDTNgQCqwKXfZwSeNHHJz1OIT8JjtAq6xWtCLwGPLzYZi+3YV8DGMiT4VVuG7oiZpGzrZJhcs/hL49xtzH/Dy6bdfTsXYNY+5yluWO4D4neK/ZUvok/17X0HPBLsF+vuUlhfwX4j/rSfAJ4H1H0qZJ9dN7nR19frRTeBt4Fe9FwpwtN+2p1MXscGLHR9SXrmMgjONd1ZxKzpBeA71b4tNhj6JGoyFNp4GHgwUp9qplfmnFW5oTdy7NamcwCI49kv6fN5IAHgD+0rbyoBc3SOjczohbyS1drbq6pQdqumllRC/0ymTtej8gpbbuVwpQfyw66dqEZyxZKxtHpJn+tZnpnEdrYBbueF9qQn93S7HQGGHnYP7w6L+YGHNtd1FJitqPAR+hERCNOFi1i1alKO6RQnjKUxL1GNjwlMsiEhcPLYTEiT9ISbN15OY/jx4SMshe9LaJRpTvHr3C/ybFYP1PZAfwfYrPsMBtnE6SwN9ib7AhLwTrBDgUKcm06FSrTfSj187xPdVQWOk5Q8vxAfSiIUc7Z7xr6zY/+hpqwSyv0I0/QMTRb7RMgBxNodTfSPqdraz/sDjzKBrv4zu2+a2t0/HHzjd2Lbcc2sG7GtsL42K+xLfxtUgI7YHqKlqHK8HbCCXgjHT1cAdMlDetv4FnQ2lLasaOl6vmB0CMmwT/IPszSueHQqv6i/qluqF+oF9TfO2qEGTumJH0qfSv9KH0nfS/9TIp0Wboi/SRdlb6RLgU5u++9nyXYe69fYRPdil1o1WufNSdTTsp75BfllPy8/LI8G7AUuV8ek6fkvfDsCfbNDP0dvRh0CrNqTbV7LfEEGDQPJQadBtfGVMWEq3QWWdufk6ZSNsjG2PQjp3ZcnOWWing6noonSInvi0/Ex+IzAreevPhe+CawpgP1/pMTMDo64G0sTCXIM+KdOnFWRfQKdJvQzV1+Bt8OokmrdtY2yhVX2a+qrykJfMq4Ml3VR4cVzTQVz+UoNne4vcKLoyS+gyKO6EHe+75Fdt0Mbe5bRIf/wjvrVmhbqBN97RD1vxrahvBOfOYzoosH9bq94uejSOQGkVM6sN/7HelL4t10t9F4gPdVzydEOx83Gv+uNxo7XyL/FtFl8z9ZAHF4bBsrEwAAAAlwSFlzAAALEwAACxMBAJqcGAAAByVJREFUWAm1l1uIldcVx9d3ruMZZzRaay+pCjFJH6LSRqxQqA1NH0pBiH3Qp774kEAg4EOkxKdQSCjUFvpm6YsNVNoSaGjFtmga2yZgCIIawdv04g2kM7Uz6lzO+c758v/t/9lzTB/61Oxhn7332muv9V+3vb8pnooDVRkzZ4oY/LmK6mQZa05frX6yFJ9Ae7x4qd2IuV1FFM9WMfhaI9Z+pQBAL+aiEZ0QgNBm2YuZmxHF9VZMXqmivFaLweUyuteWYvHGVPWr2f+F7YvF/ola9DZGVJsHUXs8YvBEK1ZrXt9URDwqxY1BdGMQvWjGqkgA+iLUtazHuADUoowHYugKTilaR7SIpZjWqOMRfY090RbasS4JglpFtzWIcqwZa+pSqnWVcLLXijXpZCFpvbgb/VhMe8huMLPylWkci8/oSD8xJq7hj4WUWvXrlbqVrUyKtBYdpX3Bh9YbzsdErwRgbZKyFP+KdqxPssu4l2hDAOOxIj6bCHigKWRNCcpMCHHHB4TJLc+TXxKHnC51Ct+Qgxl/TZ0qE5Be/EdWTwjqQuJJAPIB8qAZk4kZoXJnvHH+27Hq0+0YX12PH+w7E3/8zbWkitN2M8pS7kCKZ761OV55c2fcm+nG7J1e7N/+e3m2nbyKQcAhnHWZLC86B1rxiFRvSIkIgJHFVWzZ+qk4fG5HEr4wV8buVb+Vuv5QeVZsi/HeW//eHZ1HbNfLT5+Jc2dndBav9KXugfqc+pLsv6Xxvk6kVheumnpDnXlTVMZWfHh+Li6cdOKvmGzEC69+WTskzwr1SfUJ9ZWp7z/0pWXlF9+ejQtnUdCWnAxQ+al5Tdz80lIVEP8x9eZQWCQwOTAhNc34Re+rUW8U0S+r2Ns8nWzBKgONBOeX3V3RaCpPRN7XeFcO7yYl+InML2U3VdBVHszHzbSXYLBJkuTSQzBuphoYZ7X/u8O30gFAHHxzi+Yop8ETcfDXW5JyKMd/fFuO9l3mYuwLAl5gbMg8QuKdYQg4Zjcxo7HikMeIn37vcizes9Ide9bGhs9NLPN9YX0ndnzHpbZ4vx9HXr6kc6Sobo2hIkuzOnIh0xMFRlvc0waWL+p3UePCQ/Myjjx/JSnl59CJbUkJgl75g+ZD/D978Yrc7EuMPe4ESo6OYsaasiiX7tADAyny5cGtyMHsDxzFnP0Tx6Z0SfsW27B1PHZ+c13seGZdbNo2Lo6Iu7e7cfznfxc/8ggNQBhZI9dSs2c5k+rFaHBXmZhd32xTGdlZPvzDvefj9XddlgeObYVpuf1o3zkpyrEnCJwBDjlmr9i7XP3jgrYkDamhEqRA8UOBxZ53tcOtBbgyzr53M65f8DU6sVZ1o067cfFBvP+XGzrDOa5s+JkTShIc+dBtlLOLlRpqAUDc+yqQMnViNq81edDVnPixno/vP/dXjn2svbbnPa1RiqXEHVkYQ06RWygnFEtpbZDLAJws2X1OHgfCv+hiRkZU8Y+pmbjwzjTE1D48PR1TV+5IMErgsjex2A8TJrqCHH9Cw6U0BGBkPUWrKTZnPq4L9WqIOFvEO8ml+vbRvyUB/Jw6OiUa9GydM58qQl6lTrNHyiENrwyTkOvXLziVkMlOOsesVKyIFtZB1zfDAGvdyj4xtkD7yHQ8Ynn4hCrwvYA+DOJCSlXAZl3MjNQobNzVPK7gJm0AiPsQyEg0c6s1cbEB5X08AmDz1TTLucApzHHyJgADvUqVysJMKOSicLRQl+emOIvbnaw+ot2pSTzl5zzJVjPaZ6ix7zCSN4E1shOAWnqbyYH8bOqd1h9AGJ0qtl6LRBubcBKxbo6xh60kWlbLjgG4NJ2ETkwqbl7SeUXVSCq+BF1C2bWEgEO4CxBGvOydGmu3ooXv7AEogLFqn2JtWKO8yc9xAmDxjhGiWMOQXe63zCvHtIjOpGOIwvGJlhRQepyzaiu0MQ4MnFhuT7CiJQC+sUg4jtOYO+1IH9OdCwgBSmOkP2r60CarHeXMjxw3PGyvOBnN670EgOPOc1yEYgDYCxbqTPDXki1srChi4R6lpQ+uDmVFDtkA5GH1qJEvQFgacqCFT37pyP+Y+DMJs0Y54NgbiIVn61jhEUrNARuNIi3vOQf8iUeQuNzILe4b/jFZ7RDYJhTbVRaJTxyWh8PgO93hQJCBsSa2GQyyoLlBzWDxgnm9l0JgADgNgVxElCH22xs4NCsaieSUyzWXaSTLDAPlGQB0Kt6JaqpzYjkJQT9id60aNwqZjVqlz9Kqp+JcfDjOAqhirNoCI6MelpVPAjZ/CbFv45Y9YNcicqDMKm/Xo/FPJdMlqZ9SIK7qSrrci9mbl6q3/DGQ5f7XuK347rgKeuMgiicEfLPmT0rGY1K5SdI/ryritlMbJrr/PZ8+I8qf9PF8qhMrT39QHfHLkhj/fz/bi+eb83F/VxX1b6jWvt6KdTs/AvvCmqXE235jAAAAAElFTkSuQmCC</Image>
 <Url type="application/x-suggestions+json" method="GET"
      template="https://search.yahoo.com/sugg/ff?output=fxjson&amp;appid=ffm&amp;command={searchTerms}" />
 <Url type="text/html" method="GET" template="https://search.yahoo.com/search">
   <Param name="p" value="{searchTerms}" />
   <Param name="ei" value="UTF-8" />
-  <MozParam name="fr" value="mozilla_mobile_search" />
+  <Param name="fr" value="mozilla_mobile_search" />
 </Url>
 <SearchForm>https://search.yahoo.com/</SearchForm>
 </SearchPlugin>
--- a/netwerk/streamconv/converters/nsIndexedToHTML.cpp
+++ b/netwerk/streamconv/converters/nsIndexedToHTML.cpp
@@ -242,20 +242,16 @@ nsIndexedToHTML::DoOnStartRequest(nsIReq
             uri->SetPath(path);
         }
         if (!path.EqualsLiteral("/")) {
             rv = uri->Resolve(NS_LITERAL_CSTRING(".."), parentStr);
             if (NS_FAILED(rv)) return rv;
         }
     }
 
-    nsXPIDLCString encoding;
-    rv = uri->GetOriginCharset(encoding);
-    if (NS_FAILED(rv)) return rv;
-
     buffer.AppendLiteral("<style type=\"text/css\">\n"
                          ":root {\n"
                          "  font-family: sans-serif;\n"
                          "}\n"
                          "img {\n"
                          "  border: 0;\n"
                          "}\n"
                          "th {\n"
@@ -484,16 +480,23 @@ nsIndexedToHTML::DoOnStartRequest(nsIReq
     // Everything needs to end in a /,
     // otherwise we end up linking to file:///foo/dirfile
 
     if (!mTextToSubURI) {
         mTextToSubURI = do_GetService(NS_ITEXTTOSUBURI_CONTRACTID, &rv);
         if (NS_FAILED(rv)) return rv;
     }
 
+    nsXPIDLCString encoding;
+    rv = uri->GetOriginCharset(encoding);
+    if (NS_FAILED(rv)) return rv;
+    if (encoding.IsEmpty()) {
+      encoding.AssignLiteral("UTF-8");
+    }
+
     nsXPIDLString unEscapeSpec;
     rv = mTextToSubURI->UnEscapeAndConvert(encoding, titleUri.get(),
                                            getter_Copies(unEscapeSpec));
     // unescape may fail because
     // 1. file URL may be encoded in platform charset for backward compatibility
     // 2. query part may not be encoded in UTF-8 (see bug 261929)
     // so try the platform's default if this is file url
     if (NS_FAILED(rv) && isSchemeFile) {
--- a/security/manager/ssl/src/TransportSecurityInfo.cpp
+++ b/security/manager/ssl/src/TransportSecurityInfo.cpp
@@ -787,17 +787,17 @@ AppendErrorTextMismatch(const nsString &
     // currently CERT_FindNSStringExtension is not being exported by NSS.
     // If it gets exported, enable the following line.
     //   certName = CERT_FindNSStringExtension(nssCert, SEC_OID_NS_CERT_EXT_SSL_SERVER_NAME);
     // However, it has been discussed to treat the extension as obsolete and ignore it.
     if (!certName)
       certName = CERT_GetCommonName(&nssCert->subject);
     if (certName) {
       ++nameCount;
-      allNames.AssignASCII(certName);
+      allNames.Assign(NS_ConvertUTF8toUTF16(certName));
       PORT_Free(certName);
     }
   }
 
   if (nameCount > 1) {
     nsString message;
     rv = component->GetPIPNSSBundleString("certErrorMismatchMultiple", 
                                           message);
--- a/security/manager/ssl/tests/unit/test_ocsp_stapling.js
+++ b/security/manager/ssl/tests/unit/test_ocsp_stapling.js
@@ -40,16 +40,17 @@ function add_tests_in_mode(useMozillaPKI
   add_ocsp_test("ocsp-stapling-unknown.example.com", Cr.NS_OK, false);
   add_ocsp_test("ocsp-stapling-good-other.example.com", Cr.NS_OK, false);
   add_ocsp_test("ocsp-stapling-none.example.com", Cr.NS_OK, false);
   add_ocsp_test("ocsp-stapling-expired.example.com", Cr.NS_OK, false);
   add_ocsp_test("ocsp-stapling-expired-fresh-ca.example.com", Cr.NS_OK, false);
   add_ocsp_test("ocsp-stapling-skip-responseBytes.example.com", Cr.NS_OK, false);
   add_ocsp_test("ocsp-stapling-critical-extension.example.com", Cr.NS_OK, false);
   add_ocsp_test("ocsp-stapling-noncritical-extension.example.com", Cr.NS_OK, false);
+  add_ocsp_test("ocsp-stapling-empty-extensions.example.com", Cr.NS_OK, false);
 
   // Now test OCSP stapling
   // The following error codes are defined in security/nss/lib/util/SECerrs.h
 
   add_ocsp_test("ocsp-stapling-good.example.com", Cr.NS_OK, true);
 
   add_ocsp_test("ocsp-stapling-revoked.example.com",
                 getXPCOMStatusFromNSS(SEC_ERROR_REVOKED_CERTIFICATE), true);
@@ -121,16 +122,18 @@ function add_tests_in_mode(useMozillaPKI
   }
 
   add_ocsp_test("ocsp-stapling-critical-extension.example.com",
                 useMozillaPKIX
                   ? getXPCOMStatusFromNSS(SEC_ERROR_UNKNOWN_CRITICAL_EXTENSION)
                   : Cr.NS_OK, // TODO(bug 987426): NSS doesn't handle unknown critical extensions
                 true);
   add_ocsp_test("ocsp-stapling-noncritical-extension.example.com", Cr.NS_OK, true);
+  // TODO(bug 997994): Disallow empty Extensions in responses
+  add_ocsp_test("ocsp-stapling-empty-extensions.example.com", Cr.NS_OK, true);
 
   add_ocsp_test("ocsp-stapling-delegated-included.example.com", Cr.NS_OK, true);
   add_ocsp_test("ocsp-stapling-delegated-included-last.example.com", Cr.NS_OK, true);
   add_ocsp_test("ocsp-stapling-delegated-missing.example.com",
                 getXPCOMStatusFromNSS(SEC_ERROR_OCSP_INVALID_SIGNING_CERT), true);
   add_ocsp_test("ocsp-stapling-delegated-missing-multiple.example.com",
                 getXPCOMStatusFromNSS(SEC_ERROR_OCSP_INVALID_SIGNING_CERT), true);
   add_ocsp_test("ocsp-stapling-delegated-no-extKeyUsage.example.com",
@@ -148,18 +151,18 @@ function add_tests_in_mode(useMozillaPKI
 }
 
 function check_ocsp_stapling_telemetry() {
   let histogram = Cc["@mozilla.org/base/telemetry;1"]
                     .getService(Ci.nsITelemetry)
                     .getHistogramById("SSL_OCSP_STAPLING")
                     .snapshot();
   do_check_eq(histogram.counts[0], 2 * 0); // histogram bucket 0 is unused
-  do_check_eq(histogram.counts[1], 4 + 5); // 4 or 5 connections with a good response (bug 987426)
-  do_check_eq(histogram.counts[2], 2 * 17); // 17 connections with no stapled resp.
+  do_check_eq(histogram.counts[1], 5 + 6); // 5 or 6 connections with a good response (bug 987426)
+  do_check_eq(histogram.counts[2], 2 * 18); // 18 connections with no stapled resp.
   do_check_eq(histogram.counts[3], 2 * 0); // 0 connections with an expired response
   do_check_eq(histogram.counts[4], 19 + 17); // 19 or 17 connections with bad responses (bug 979070, bug 987426)
   run_next_test();
 }
 
 function run_test() {
   do_get_profile();
 
--- a/security/manager/ssl/tests/unit/tlsserver/cmd/OCSPStaplingServer.cpp
+++ b/security/manager/ssl/tests/unit/tlsserver/cmd/OCSPStaplingServer.cpp
@@ -35,16 +35,17 @@ const OCSPHost sOCSPHosts[] =
   { "ocsp-stapling-trylater.example.com", ORTTryLater, nullptr },
   { "ocsp-stapling-needssig.example.com", ORTNeedsSig, nullptr },
   { "ocsp-stapling-unauthorized.example.com", ORTUnauthorized, nullptr },
   { "ocsp-stapling-with-intermediate.example.com", ORTGood, "ocspEEWithIntermediate" },
   { "ocsp-stapling-bad-signature.example.com", ORTBadSignature, nullptr },
   { "ocsp-stapling-skip-responseBytes.example.com", ORTSkipResponseBytes, nullptr },
   { "ocsp-stapling-critical-extension.example.com", ORTCriticalExtension, nullptr },
   { "ocsp-stapling-noncritical-extension.example.com", ORTNoncriticalExtension, nullptr },
+  { "ocsp-stapling-empty-extensions.example.com", ORTEmptyExtensions, nullptr },
   { "ocsp-stapling-delegated-included.example.com", ORTDelegatedIncluded, "delegatedSigner" },
   { "ocsp-stapling-delegated-included-last.example.com", ORTDelegatedIncludedLast, "delegatedSigner" },
   { "ocsp-stapling-delegated-missing.example.com", ORTDelegatedMissing, "delegatedSigner" },
   { "ocsp-stapling-delegated-missing-multiple.example.com", ORTDelegatedMissingMultiple, "delegatedSigner" },
   { "ocsp-stapling-delegated-no-extKeyUsage.example.com", ORTDelegatedIncluded, "invalidDelegatedSignerNoExtKeyUsage" },
   { "ocsp-stapling-delegated-from-intermediate.example.com", ORTDelegatedIncluded, "invalidDelegatedSignerFromIntermediate" },
   { "ocsp-stapling-delegated-keyUsage-crlSigning.example.com", ORTDelegatedIncluded, "invalidDelegatedSignerKeyUsageCrlSigning" },
   { "ocsp-stapling-delegated-wrong-extKeyUsage.example.com", ORTDelegatedIncluded, "invalidDelegatedSignerWrongExtKeyUsage" },
--- a/security/manager/ssl/tests/unit/tlsserver/lib/OCSPCommon.cpp
+++ b/security/manager/ssl/tests/unit/tlsserver/lib/OCSPCommon.cpp
@@ -139,16 +139,19 @@ GetOCSPResponseForType(OCSPResponseType 
     }
     extension.critical = (aORT == ORTCriticalExtension);
     static const uint8_t value[2] = { 0x05, 0x00 };
     extension.value.data = const_cast<uint8_t*>(value);
     extension.value.len = PR_ARRAY_SIZE(value);
     extension.next = nullptr;
     context.extensions = &extension;
   }
+  if (aORT == ORTEmptyExtensions) {
+    context.includeEmptyExtensions = true;
+  }
 
   if (!context.signerCert) {
     context.signerCert = CERT_DupCertificate(context.issuerCert.get());
   }
 
   SECItem* response = CreateEncodedOCSPResponse(context);
   if (!response) {
     PrintPRError("CreateEncodedOCSPResponse failed");
--- a/security/manager/ssl/tests/unit/tlsserver/lib/OCSPCommon.h
+++ b/security/manager/ssl/tests/unit/tlsserver/lib/OCSPCommon.h
@@ -27,16 +27,17 @@ enum OCSPResponseType
   ORTSrverr,           // the response indicates there was a server error
   ORTTryLater,         // the responder replied with "try again later"
   ORTNeedsSig,         // the response needs a signature
   ORTUnauthorized,     // the responder is not authorized for this certificate
   ORTBadSignature,     // the response has a signature that does not verify
   ORTSkipResponseBytes, // the response does not include responseBytes
   ORTCriticalExtension, // the response includes a critical extension
   ORTNoncriticalExtension, // the response includes an extension that is not critical
+  ORTEmptyExtensions,  // the response includes a SEQUENCE OF Extension that is empty
   ORTDelegatedIncluded, // the response is signed by an included delegated responder
   ORTDelegatedIncludedLast, // same, but multiple other certificates are included
   ORTDelegatedMissing, // the response is signed by a not included delegated responder
   ORTDelegatedMissingMultiple, // same, but multiple other certificates are included
 };
 
 struct OCSPHost
 {
--- a/security/pkix/lib/pkixocsp.cpp
+++ b/security/pkix/lib/pkixocsp.cpp
@@ -853,21 +853,25 @@ CheckExtensionForCriticality(der::Input&
 
   if (ExpectTagAndGetLength(input, der::OCTET_STRING, toSkip)
         != der::Success) {
     return der::Failure;
   }
   return input.Skip(toSkip);
 }
 
+// Extensions ::= SEQUENCE SIZE (1..MAX) OF Extension
 static der::Result
 CheckExtensionsForCriticality(der::Input& input)
 {
+  // TODO(bug 997994): some responders include an empty SEQUENCE OF
+  // Extension, which is invalid (der::MayBeEmpty should really be
+  // der::MustNotBeEmpty).
   return der::NestedOf(input, der::SEQUENCE, der::SEQUENCE,
-                       der::MustNotBeEmpty, CheckExtensionForCriticality);
+                       der::MayBeEmpty, CheckExtensionForCriticality);
 }
 
 //   1. The certificate identified in a received response corresponds to
 //      the certificate that was identified in the corresponding request;
 //   2. The signature on the response is valid;
 //   3. The identity of the signer matches the intended recipient of the
 //      request;
 //   4. The signer is currently authorized to provide a response for the
--- a/security/pkix/test/lib/pkixtestutil.cpp
+++ b/security/pkix/test/lib/pkixtestutil.cpp
@@ -125,16 +125,17 @@ OCSPResponseContext::OCSPResponseContext
   , nextUpdate(time + 10 * PR_USEC_PER_SEC)
   , includeNextUpdate(true)
   , certIDHashAlg(SEC_OID_SHA1)
   , certStatus(0)
   , revocationTime(0)
   , badSignature(false)
   , responderIDType(ByKeyHash)
   , extensions(nullptr)
+  , includeEmptyExtensions(false)
 {
   for (size_t i = 0; i < MaxIncludedCertificates; i++) {
     includedCertificates[i] = nullptr;
   }
 }
 
 static SECItem* ResponseBytes(OCSPResponseContext& context);
 static SECItem* BasicOCSPResponse(OCSPResponseContext& context);
@@ -508,17 +509,17 @@ ResponseData(OCSPResponseContext& contex
     return nullptr;
   }
   SECItem* responsesNested = EncodeNested(context.arena, der::SEQUENCE,
                                           responses);
   if (!responsesNested) {
     return nullptr;
   }
   SECItem* responseExtensions = nullptr;
-  if (context.extensions) {
+  if (context.extensions || context.includeEmptyExtensions) {
     responseExtensions = Extensions(context);
   }
 
   Output output;
   if (output.Add(responderID) != der::Success) {
     return nullptr;
   }
   if (output.Add(producedAtEncoded) != der::Success) {
--- a/security/pkix/test/lib/pkixtestutil.h
+++ b/security/pkix/test/lib/pkixtestutil.h
@@ -62,16 +62,19 @@ public:
 
   enum ResponderIDType {
     ByName = 1,
     ByKeyHash = 2
   };
   ResponderIDType responderIDType;
 
   OCSPResponseExtension* extensions;
+  bool includeEmptyExtensions; // If true, include the extension wrapper
+                               // regardless of if there are any actual
+                               // extensions.
 };
 
 // The return value, if non-null, is owned by the arena in the context
 // and MUST NOT be freed.
 // This function does its best to respect the NSPR error code convention
 // (that is, if it returns null, calling PR_GetError() will return the
 // error of the failed operation). However, this is not guaranteed.
 SECItem* CreateEncodedOCSPResponse(OCSPResponseContext& context);
--- a/security/sandbox/linux/SandboxFilter.cpp
+++ b/security/sandbox/linux/SandboxFilter.cpp
@@ -91,16 +91,19 @@ static struct sock_filter seccomp_filter
    * argument filtering */
   ALLOW_SYSCALL(ioctl),
   ALLOW_SYSCALL(close),
   ALLOW_SYSCALL(munmap),
   ALLOW_SYSCALL(mprotect),
   ALLOW_SYSCALL(writev),
   ALLOW_SYSCALL(clone),
   ALLOW_SYSCALL(brk),
+#if SYSCALL_EXISTS(set_thread_area)
+  ALLOW_SYSCALL(set_thread_area),
+#endif
 
   ALLOW_SYSCALL(getpid),
   ALLOW_SYSCALL(gettid),
   ALLOW_SYSCALL(getrusage),
   ALLOW_SYSCALL(madvise),
   ALLOW_SYSCALL(dup),
   ALLOW_SYSCALL(nanosleep),
   ALLOW_SYSCALL(poll),
--- a/testing/mochitest/browser-test.js
+++ b/testing/mochitest/browser-test.js
@@ -91,16 +91,38 @@ function Tester(aTests, aDumper, aCallba
   this.Task = Components.utils.import("resource://gre/modules/Task.jsm", null).Task;
   this.Promise = Components.utils.import("resource://gre/modules/Promise.jsm", null).Promise;
   this.Assert = Components.utils.import("resource://testing-common/Assert.jsm", null).Assert;
 
   this.SimpleTestOriginal = {};
   SIMPLETEST_OVERRIDES.forEach(m => {
     this.SimpleTestOriginal[m] = this.SimpleTest[m];
   });
+
+  this._uncaughtErrorObserver = function({message, date, fileName, stack, lineNumber}) {
+    let text = "Once bug 991040 has landed, THIS ERROR WILL CAUSE A TEST FAILURE.\n" + message;
+    let error = text;
+    if (fileName || lineNumber) {
+      error = {
+        fileName: fileName,
+        lineNumber: lineNumber,
+        message: text,
+        toString: function() {
+          return text;
+        }
+      };
+    }
+    this.currentTest.addResult(
+      new testResult(
+	/*success*/ true,
+        /*name*/"A promise chain failed to handle a rejection",
+        /*error*/error,
+        /*known*/true,
+        /*stack*/stack));
+    }.bind(this);
 }
 Tester.prototype = {
   EventUtils: {},
   SimpleTest: {},
   Task: null,
   Promise: null,
   Assert: null,
 
@@ -146,16 +168,19 @@ Tester.prototype = {
     this._globalProperties = Object.keys(window);
     this._globalPropertyWhitelist = [
       "navigator", "constructor", "top",
       "Application",
       "__SS_tabsToRestore", "__SSi",
       "webConsoleCommandController",
     ];
 
+    this.Promise.Debugging.clearUncaughtErrorObservers();
+    this.Promise.Debugging.addUncaughtErrorObserver(this._uncaughtErrorObserver);
+
     if (this.tests.length)
       this.nextTest();
     else
       this.finish();
   },
 
   waitForWindowsState: function Tester_waitForWindowsState(aCallback) {
     let timedOut = this.currentTest && this.currentTest.timedOut;
@@ -198,42 +223,44 @@ Tester.prototype = {
       }
     }
 
     // Make sure the window is raised before each test.
     this.SimpleTest.waitForFocus(aCallback);
   },
 
   finish: function Tester_finish(aSkipSummary) {
+    this.Promise.Debugging.flushUncaughtErrors();
+
     var passCount = this.tests.reduce(function(a, f) a + f.passCount, 0);
     var failCount = this.tests.reduce(function(a, f) a + f.failCount, 0);
     var todoCount = this.tests.reduce(function(a, f) a + f.todoCount, 0);
 
     if (this.repeat > 0) {
       --this.repeat;
       this.currentTestIndex = -1;
       this.nextTest();
     }
     else{
       Services.console.unregisterListener(this);
       Services.obs.removeObserver(this, "chrome-document-global-created");
       Services.obs.removeObserver(this, "content-document-global-created");
-  
+      this.Promise.Debugging.clearUncaughtErrorObservers();
       this.dumper.dump("\nINFO TEST-START | Shutdown\n");
+
       if (this.tests.length) {
         this.dumper.dump("Browser Chrome Test Summary\n");
   
         this.dumper.dump("\tPassed: " + passCount + "\n" +
                          "\tFailed: " + failCount + "\n" +
                          "\tTodo: " + todoCount + "\n");
       } else {
         this.dumper.dump("TEST-UNEXPECTED-FAIL | (browser-test.js) | " +
                          "No tests to run. Did you pass an invalid --test-path?\n");
       }
-  
       this.dumper.dump("\n*** End BrowserChrome Test Results ***\n");
   
       this.dumper.done();
   
       // Tests complete, notify the callback and return
       this.callback(this.tests);
       this.callback = null;
       this.tests = null;
@@ -297,16 +324,18 @@ Tester.prototype = {
         try {
           func.apply(testScope);
         }
         catch (ex) {
           this.currentTest.addResult(new testResult(false, "Cleanup function threw an exception", ex, false));
         }
       };
 
+      this.Promise.Debugging.flushUncaughtErrors();
+
       let winUtils = window.QueryInterface(Ci.nsIInterfaceRequestor)
                            .getInterface(Ci.nsIDOMWindowUtils);
       if (winUtils.isTestControllingRefreshes) {
         this.currentTest.addResult(new testResult(false, "test left refresh driver under test control", "", false));
         winUtils.restoreNormalRefresh();
       }
 
       if (this.SimpleTest.isExpectingUncaughtException()) {
@@ -561,17 +590,17 @@ Tester.prototype = {
        this.currentTest.addResult(new testResult(false, "head.js import threw an exception", ex, false));
       }
     }
 
     // Import the test script.
     try {
       this._scriptLoader.loadSubScript(this.currentTest.path,
                                        this.currentTest.scope);
-
+      this.Promise.Debugging.flushUncaughtErrors();
       // Run the test
       this.lastStartTime = Date.now();
       if (this.currentTest.scope.__tasks) {
         // This test consists of tasks, added via the `add_task()` API.
         if ("test" in this.currentTest.scope) {
           throw "Cannot run both a add_task test and a normal test at the same time.";
         }
         this.Task.spawn(function() {
@@ -582,16 +611,17 @@ Tester.prototype = {
               yield task();
             } catch (ex) {
               let isExpected = !!this.SimpleTest.isExpectingUncaughtException();
               let stack = (typeof ex == "object" && "stack" in ex)?ex.stack:null;
               let name = "Uncaught exception";
               let result = new testResult(isExpected, name, ex, false, stack);
               currentTest.addResult(result);
             }
+            this.Promise.Debugging.flushUncaughtErrors();
             this.SimpleTest.info("Leaving test " + task.name);
           }
           this.finish();
         }.bind(currentScope));
       } else if ("generatorTest" in this.currentTest.scope) {
         if ("test" in this.currentTest.scope) {
           throw "Cannot run both a generator test and a normal test at the same time.";
         }
--- a/testing/mochitest/runtests.py
+++ b/testing/mochitest/runtests.py
@@ -1290,17 +1290,17 @@ class Mochitest(MochitestUtilsMixin):
       elif mozinfo.isLinux and self.perl:
         # Run logsource through fix-linux-stack.pl (uses addr2line)
         # This method is preferred for developer machines, so we don't have to run "make buildsymbols".
         stackFixerCommand = [self.perl, os.path.join(self.utilityPath, "fix-linux-stack.pl")]
         stackFixerProcess = subprocess.Popen(stackFixerCommand, stdin=subprocess.PIPE,
                                              stdout=subprocess.PIPE)
         def fixFunc(line):
           stackFixerProcess.stdin.write(line + '\n')
-          return stackFixerProcess.stdout.readline().strip()
+          return stackFixerProcess.stdout.readline().rstrip()
 
         stackFixerFunction = fixFunc
 
       return (stackFixerFunction, stackFixerProcess)
 
     def finish(self, didTimeout):
       if self.stackFixerProcess:
         self.stackFixerProcess.communicate()
--- a/testing/mochitest/tests/SimpleTest/SimpleTest.js
+++ b/testing/mochitest/tests/SimpleTest/SimpleTest.js
@@ -886,16 +886,17 @@ SimpleTest.finish = function () {
                            + "SimpleTest.waitForExplicitFinish() if you need "
                            + "it.)");
     }
 
     if (parentRunner) {
         /* We're running in an iframe, and the parent has a TestRunner */
         parentRunner.testFinished(SimpleTest._tests);
     } else {
+        SpecialPowers.flushAllAppsLaunchable();
         SpecialPowers.flushPermissions(function () {
           SpecialPowers.flushPrefEnv(function() {
             SimpleTest.showReport();
           });
         });
     }
 };
 
--- a/testing/mochitest/tests/SimpleTest/TestRunner.js
+++ b/testing/mochitest/tests/SimpleTest/TestRunner.js
@@ -473,16 +473,17 @@ TestRunner.testFinished = function(tests
         } else {
             interstitialURL = "/tests/SimpleTest/iframe-between-tests.html";
         }
         TestRunner._makeIframe(interstitialURL, 0);
     }
 
     SpecialPowers.executeAfterFlushingMessageQueue(function() {
         cleanUpCrashDumpFiles();
+        SpecialPowers.flushAllAppsLaunchable();
         SpecialPowers.flushPermissions(function () { SpecialPowers.flushPrefEnv(runNextTest); });
     });
 };
 
 TestRunner.testUnloaded = function() {
     // If we're in a debug build, check assertion counts.  This code is
     // similar to the code in Tester_nextTest in browser-test.js used
     // for browser-chrome mochitests.
--- a/testing/specialpowers/content/specialpowersAPI.js
+++ b/testing/specialpowers/content/specialpowersAPI.js
@@ -1026,21 +1026,28 @@ SpecialPowersAPI.prototype = {
   // The provided callback is invoked once the prompt is disabled.
   autoConfirmAppInstall: function(cb) {
     this.pushPrefEnv({set: [['dom.mozApps.auto_confirm_install', true]]}, cb);
   },
 
   // Allow tests to disable the per platform app validity checks so we can
   // test higher level WebApp functionality without full platform support.
   setAllAppsLaunchable: function(launchable) {
-    var message = {
+    this._sendSyncMessage("SPWebAppService", {
       op: "set-launchable",
       launchable: launchable
-    };
-    return this._sendSyncMessage("SPWebAppService", message);
+    });
+  },
+
+  // Restore the launchable property to its default value.
+  flushAllAppsLaunchable: function() {
+    this._sendSyncMessage("SPWebAppService", {
+      op: "set-launchable",
+      launchable: false
+    });
   },
 
   _proxiedObservers: {
     "specialpowers-http-notify-request": function(aMessage) {
       let uri = aMessage.json.uri;
       Services.obs.notifyObservers(null, "specialpowers-http-notify-request", uri);
     },
   },
--- a/toolkit/components/downloads/ApplicationReputation.cpp
+++ b/toolkit/components/downloads/ApplicationReputation.cpp
@@ -44,19 +44,17 @@
 
 using mozilla::Preferences;
 using mozilla::TimeStamp;
 using mozilla::Telemetry::Accumulate;
 using safe_browsing::ClientDownloadRequest;
 using safe_browsing::ClientDownloadRequest_SignatureInfo;
 using safe_browsing::ClientDownloadRequest_CertificateChain;
 
-// Preferences that we need to initialize the query. We may need another
-// preference than browser.safebrowsing.malware.enabled, or simply use
-// browser.safebrowsing.appRepURL. See bug 887041.
+// Preferences that we need to initialize the query.
 #define PREF_SB_APP_REP_URL "browser.safebrowsing.appRepURL"
 #define PREF_SB_MALWARE_ENABLED "browser.safebrowsing.malware.enabled"
 #define PREF_GENERAL_LOCALE "general.useragent.locale"
 #define PREF_DOWNLOAD_BLOCK_TABLE "urlclassifier.downloadBlockTable"
 #define PREF_DOWNLOAD_ALLOW_TABLE "urlclassifier.downloadAllowTable"
 
 // NSPR_LOG_MODULES=ApplicationReputation:5
 #if defined(PR_LOGGING)
@@ -123,16 +121,19 @@ private:
   // the Windows Authenticode API, if the binary is signed.
   ClientDownloadRequest_SignatureInfo mSignatureInfo;
 
   // The response from the application reputation query. This is read in chunks
   // as part of our nsIStreamListener implementation and may contain embedded
   // NULLs.
   nsCString mResponse;
 
+  // Returns true if the file is likely to be binary on Windows.
+  bool IsBinaryFile();
+
   // Clean up and call the callback. PendingLookup must not be used after this
   // function is called.
   nsresult OnComplete(bool shouldBlock, nsresult rv);
 
   // Wrapper function for nsIStreamListener.onStopRequest to make it easy to
   // guarantee calling the callback
   nsresult OnStopRequestInternal(nsIRequest *aRequest,
                                  nsISupports *aContext,
@@ -328,16 +329,44 @@ PendingLookup::PendingLookup(nsIApplicat
   LOG(("Created pending lookup [this = %p]", this));
 }
 
 PendingLookup::~PendingLookup()
 {
   LOG(("Destroying pending lookup [this = %p]", this));
 }
 
+bool
+PendingLookup::IsBinaryFile()
+{
+  nsString fileName;
+  nsresult rv = mQuery->GetSuggestedFileName(fileName);
+  if (NS_FAILED(rv)) {
+    return false;
+  }
+  return
+    // Executable extensions for MS Windows, from
+    // https://code.google.com/p/chromium/codesearch#chromium/src/chrome/common/safe_browsing/download_protection_util.cc&l=14
+    StringEndsWith(fileName, NS_LITERAL_STRING(".apk")) ||
+    StringEndsWith(fileName, NS_LITERAL_STRING(".bas")) ||
+    StringEndsWith(fileName, NS_LITERAL_STRING(".bat")) ||
+    StringEndsWith(fileName, NS_LITERAL_STRING(".cab")) ||
+    StringEndsWith(fileName, NS_LITERAL_STRING(".cmd")) ||
+    StringEndsWith(fileName, NS_LITERAL_STRING(".com")) ||
+    StringEndsWith(fileName, NS_LITERAL_STRING(".exe")) ||
+    StringEndsWith(fileName, NS_LITERAL_STRING(".hta")) ||
+    StringEndsWith(fileName, NS_LITERAL_STRING(".msi")) ||
+    StringEndsWith(fileName, NS_LITERAL_STRING(".pif")) ||
+    StringEndsWith(fileName, NS_LITERAL_STRING(".reg")) ||
+    StringEndsWith(fileName, NS_LITERAL_STRING(".scr")) ||
+    StringEndsWith(fileName, NS_LITERAL_STRING(".vb")) ||
+    StringEndsWith(fileName, NS_LITERAL_STRING(".vbs")) ||
+    StringEndsWith(fileName, NS_LITERAL_STRING(".zip"));
+}
+
 nsresult
 PendingLookup::LookupNext()
 {
   // We must call LookupNext or SendRemoteQuery upon return.
   // Look up all of the URLs that could whitelist this download.
   // Blacklist first.
   int index = mAnylistSpecs.Length() - 1;
   nsCString spec;
@@ -354,20 +383,24 @@ PendingLookup::LookupNext()
       spec = mAllowlistSpecs[index];
       mAllowlistSpecs.RemoveElementAt(index);
     }
   }
   if (index >= 0) {
     nsRefPtr<PendingDBLookup> lookup(new PendingDBLookup(this));
     return lookup->LookupSpec(spec, allowlistOnly);
   }
-  // There are no more URIs to check against local list, so send the remote
-  // query if we can.
-  // Revert to just ifdef XP_WIN when remote lookups are enabled (bug 933432)
-#if 0
+#ifdef XP_WIN
+  // There are no more URIs to check against local list. If the file is not
+  // eligible for remote lookup, bail.
+  if (!IsBinaryFile()) {
+    LOG(("Not eligible for remote lookups [this=%x]", this));
+    return OnComplete(false, NS_OK);
+  }
+  // Send the remote query if we are on Windows.
   nsresult rv = SendRemoteQuery();
   if (NS_FAILED(rv)) {
     return OnComplete(false, rv);
   }
   return NS_OK;
 #else
   return OnComplete(false, NS_OK);
 #endif
--- a/toolkit/components/downloads/test/unit/test_app_rep_windows.js
+++ b/toolkit/components/downloads/test/unit/test_app_rep_windows.js
@@ -200,30 +200,31 @@ add_task(function test_setup()
     return blob;
   }
 
   gHttpServer.registerPathHandler("/throw", function(request, response) {
     do_throw("We shouldn't be getting here");
   });
 
   gHttpServer.registerPathHandler("/download", function(request, response) {
+    do_print("Querying remote server for verdict");
     response.setHeader("Content-Type", "application/octet-stream", false);
     let buf = NetUtil.readInputStreamToString(
       request.bodyInputStream,
       request.bodyInputStream.available());
     do_print("Request length: " + buf.length);
     // A garbage response. By default this produces NS_CANNOT_CONVERT_DATA as
     // the callback status.
     let blob = "this is not a serialized protocol buffer";
     // We can't actually parse the protocol buffer here, so just switch on the
     // length instead of inspecting the contents.
-    if (buf.length == 35) {
+    if (buf.length == 45) {
       // evil.com
       blob = createVerdict(true);
-    } else if (buf.length == 38) {
+    } else if (buf.length == 48) {
       // mozilla.com
       blob = createVerdict(false);
     }
     response.bodyOutputStream.write(blob, blob.length);
   });
 
   gHttpServer.start(4444);
 });
@@ -289,23 +290,27 @@ function promiseQueryReputation(query, e
     do_check_eq(Cr.NS_OK, aStatus);
     do_check_eq(aShouldBlock, expectedShouldBlock);
     deferred.resolve(true);
   }
   gAppRep.queryReputation(query, onComplete);
   return deferred.promise;
 }
 
+add_task(function()
+{
+  // Wait for Safebrowsing local list updates to complete.
+  yield waitForUpdates();
+});
+
 add_task(function test_signature_whitelists()
 {
   // We should never get to the remote server.
   Services.prefs.setCharPref("browser.safebrowsing.appRepURL",
                              "http://localhost:4444/throw");
-  // Wait for Safebrowsing local list updates to complete.
-  yield waitForUpdates();
 
   // Use BackgroundFileSaver to extract the signature on Windows.
   let destFile = getTempFile(TEST_FILE_NAME_1);
 
   let data = readFileToString("data/signed_win.exe");
   let saver = new BackgroundFileSaverOutputStream();
   let completionPromise = promiseSaverComplete(saver);
   saver.enableSignatureInfo();
@@ -320,12 +325,44 @@ add_task(function test_signature_whiteli
 
   // evil.com is not on the allowlist, but this binary is signed by an entity
   // whose certificate information is on the allowlist.
   yield promiseQueryReputation({sourceURI: createURI("http://evil.com"),
                                 signatureInfo: saver.signatureInfo,
                                 fileSize: 12}, false);
 });
 
+add_task(function test_blocked_binary()
+{
+  // We should reach the remote server for a verdict.
+  Services.prefs.setCharPref("browser.safebrowsing.appRepURL",
+                             "http://localhost:4444/download");
+  // evil.com should return a malware verdict from the remote server.
+  yield promiseQueryReputation({sourceURI: createURI("http://evil.com"),
+                                suggestedFileName: "noop.bat",
+                                fileSize: 12}, true);
+});
+
+add_task(function test_non_binary()
+{
+  // We should not reach the remote server for a verdict for non-binary files.
+  Services.prefs.setCharPref("browser.safebrowsing.appRepURL",
+                             "http://localhost:4444/throw");
+  yield promiseQueryReputation({sourceURI: createURI("http://evil.com"),
+                                suggestedFileName: "noop.txt",
+                                fileSize: 12}, false);
+});
+
+add_task(function test_good_binary()
+{
+  // We should reach the remote server for a verdict.
+  Services.prefs.setCharPref("browser.safebrowsing.appRepURL",
+                             "http://localhost:4444/download");
+  // mozilla.com should return a not-guilty verdict from the remote server.
+  yield promiseQueryReputation({sourceURI: createURI("http://mozilla.com"),
+                                suggestedFileName: "noop.bat",
+                                fileSize: 12}, false);
+});
+
 add_task(function test_teardown()
 {
   gStillRunning = false;
 });
--- a/toolkit/components/places/tests/head_common.js
+++ b/toolkit/components/places/tests/head_common.js
@@ -844,18 +844,16 @@ NavHistoryObserver.prototype = {
  * Generic nsINavHistoryResultObserver that doesn't implement anything, but
  * provides dummy methods to prevent errors about an object not having a certain
  * method.
  */
 function NavHistoryResultObserver() {}
 
 NavHistoryResultObserver.prototype = {
   batching: function () {},
-  containerClosed: function () {},
-  containerOpened: function () {},
   containerStateChanged: function () {},
   invalidateContainer: function () {},
   nodeAnnotationChanged: function () {},
   nodeDateAddedChanged: function () {},
   nodeHistoryDetailsChanged: function () {},
   nodeIconChanged: function () {},
   nodeInserted: function () {},
   nodeKeywordChanged: function () {},
--- a/toolkit/components/places/tests/unit/test_nsINavHistoryViewer.js
+++ b/toolkit/components/places/tests/unit/test_nsINavHistoryViewer.js
@@ -92,17 +92,16 @@ add_test(function check_history_query() 
   options.sortingMode = options.SORT_BY_DATE_DESCENDING;
   options.resultType = options.RESULTS_AS_VISIT;
   var query = histsvc.getNewQuery();
   var result = histsvc.executeQuery(query, options);
   result.addObserver(resultObserver, false);
   var root = result.root;
   root.containerOpen = true;
 
-  // nsINavHistoryResultObserver.containerOpened
   do_check_neq(resultObserver.openedContainer, null);
 
   // nsINavHistoryResultObserver.nodeInserted
   // add a visit
   promiseAddVisits(testURI).then(function() {
     do_check_eq(testURI.spec, resultObserver.insertedNode.uri);
 
     // nsINavHistoryResultObserver.nodeHistoryDetailsChanged
@@ -143,17 +142,16 @@ add_test(function check_history_query() 
         do_check_false(resultObserver.inBatchMode);
         bmsvc.runInBatchMode({
           runBatched: function (aUserData) {
             do_check_true(resultObserver.inBatchMode);
           }
         }, null);
         do_check_false(resultObserver.inBatchMode);
 
-        // nsINavHistoryResultObserver.containerClosed
         root.containerOpen = false;
         do_check_eq(resultObserver.closedContainer, resultObserver.openedContainer);
         result.removeObserver(resultObserver);
         resultObserver.reset();
         promiseAsyncUpdates().then(run_next_test);
       });
     });
   });
@@ -163,17 +161,16 @@ add_test(function check_bookmarks_query(
   var options = histsvc.getNewQueryOptions();
   var query = histsvc.getNewQuery();
   query.setFolders([bmsvc.bookmarksMenuFolder], 1);
   var result = histsvc.executeQuery(query, options);
   result.addObserver(resultObserver, false);
   var root = result.root;
   root.containerOpen = true;
 
-  // nsINavHistoryResultObserver.containerOpened
   do_check_neq(resultObserver.openedContainer, null);
 
   // nsINavHistoryResultObserver.nodeInserted
   // add a bookmark
   var testBookmark = bmsvc.insertBookmark(bmsvc.bookmarksMenuFolder, testURI, bmsvc.DEFAULT_INDEX, "foo");
   do_check_eq("foo", resultObserver.insertedNode.title);
   do_check_eq(testURI.spec, resultObserver.insertedNode.uri);
 
@@ -212,34 +209,32 @@ add_test(function check_bookmarks_query(
   do_check_false(resultObserver.inBatchMode);
   bmsvc.runInBatchMode({
     runBatched: function (aUserData) {
       do_check_true(resultObserver.inBatchMode);
     }
   }, null);
   do_check_false(resultObserver.inBatchMode);
 
-  // nsINavHistoryResultObserver.containerClosed
   root.containerOpen = false;
   do_check_eq(resultObserver.closedContainer, resultObserver.openedContainer);
   result.removeObserver(resultObserver);
   resultObserver.reset();
   promiseAsyncUpdates().then(run_next_test);
 });
 
 add_test(function check_mixed_query() {
   var options = histsvc.getNewQueryOptions();
   var query = histsvc.getNewQuery();
   query.onlyBookmarked = true;
   var result = histsvc.executeQuery(query, options);
   result.addObserver(resultObserver, false);
   var root = result.root;
   root.containerOpen = true;
 
-  // nsINavHistoryResultObserver.containerOpened
   do_check_neq(resultObserver.openedContainer, null);
 
   // nsINavHistoryResultObserver.batching
   do_check_false(resultObserver.inBatchMode);
   histsvc.runInBatchMode({
     runBatched: function (aUserData) {
       do_check_true(resultObserver.inBatchMode);
     }
@@ -247,15 +242,14 @@ add_test(function check_mixed_query() {
   do_check_false(resultObserver.inBatchMode);
   bmsvc.runInBatchMode({
     runBatched: function (aUserData) {
       do_check_true(resultObserver.inBatchMode);
     }
   }, null);
   do_check_false(resultObserver.inBatchMode);
 
-  // nsINavHistoryResultObserver.containerClosed
   root.containerOpen = false;
   do_check_eq(resultObserver.closedContainer, resultObserver.openedContainer);
   result.removeObserver(resultObserver);
   resultObserver.reset();
   promiseAsyncUpdates().then(run_next_test);
 });
--- a/toolkit/components/search/nsSearchService.js
+++ b/toolkit/components/search/nsSearchService.js
@@ -1805,16 +1805,25 @@ Engine.prototype = {
           // Ignore failure
           LOG("_parseURL: Url element has an invalid param");
         }
       } else if (param.localName == "MozParam" &&
                  // We only support MozParams for default search engines
                  this._isDefault) {
         var value;
         let condition = param.getAttribute("condition");
+
+        // MozParams must have a condition to be valid
+        if (!condition) {
+          let engineLoc = this._location;
+          let paramName = param.getAttribute("name");
+          LOG("_parseURL: MozParam (" + paramName + ") without a condition attribute found parsing engine: " + engineLoc);
+          continue;
+        }
+
         switch (condition) {
           case "purpose":
             url.addParam(param.getAttribute("name"),
                          param.getAttribute("value"),
                          param.getAttribute("purpose"));
             // _addMozParam is not needed here since it can be serialized fine without. _addMozParam
             // also requires a unique "name" which is not normally the case when @purpose is used.
             break;
--- a/toolkit/devtools/apps/app-actor-front.js
+++ b/toolkit/devtools/apps/app-actor-front.js
@@ -134,19 +134,17 @@ function uploadPackage(client, webappsAc
   return deferred.promise;
 }
 
 function removeServerTemporaryFile(client, fileActor) {
   let request = {
     to: fileActor,
     type: "remove"
   };
-  client.request(request, function (aResponse) {
-    console.error("Failed removing server side temporary package file", aResponse);
-  });
+  client.request(request);
 }
 
 function installPackaged(client, webappsActor, packagePath, appId) {
   let deferred = promise.defer();
   let file = FileUtils.File(packagePath);
   let packagePromise;
   if (file.isDirectory()) {
     let tmpZipFile = FileUtils.getDir("TmpD", [], true);
@@ -178,17 +176,19 @@ function installPackaged(client, webapps
               deferred.resolve({appId: res.appId});
           });
           // Ensure deleting the temporary package file, but only if that a temporary
           // package created when we pass a directory as `packagePath`
           if (zipFile != file)
             zipFile.remove(false);
           // In case of success or error, ensure deleting the temporary package file
           // also created on the device, but only once install request is done
-          deferred.promise.then(removeServerTemporaryFile, removeServerTemporaryFile);
+          deferred.promise.then(
+            () => removeServerTemporaryFile(client, fileActor),
+            () => removeServerTemporaryFile(client, fileActor));
         });
   });
   return deferred.promise;
 }
 exports.installPackaged = installPackaged;
 
 function installHosted(client, webappsActor, appId, metadata, manifest) {
   let deferred = promise.defer();
--- a/toolkit/devtools/apps/tests/test_webapps_actor.html
+++ b/toolkit/devtools/apps/tests/test_webapps_actor.html
@@ -16,18 +16,16 @@ https://bugzilla.mozilla.org/show_bug.cg
 <div id="content" style="display: none">
 
 </div>
 <pre id="test">
 <script class="testbody" type="application/javascript;version=1.8">
 
 "use strict";
 
-var launchableValue = undefined;
-
 var index = -1;
 
 function debug(aMsg) {
   //dump("== Tests debug == " + aMsg + "\n");
 }
 
 function next() {
   index += 1;
@@ -42,17 +40,16 @@ function next() {
   }
 }
 
 function start() {
   next();
 }
 
 function finish() {
-  SpecialPowers.setAllAppsLaunchable(launchableValue);
   SpecialPowers.removePermission("webapps-manage", document);
   SimpleTest.finish();
 }
 
 function cbError(aError) {
   ok(false, "Error callback invoked " + aError);
   finish();
 }
@@ -68,17 +65,17 @@ const PACKAGED_APP_MANIFEST = PACKAGED_A
 const CERTIFIED_APP_ID = "test-certified-id";
 const CERTIFIED_APP_ORIGIN = "app://" + CERTIFIED_APP_ID;
 const CERTIFIED_APP_MANIFEST = CERTIFIED_APP_ORIGIN + "/manifest.webapp";
 
 var steps = [
   function() {
     info("== SETUP ==");
     // Set up
-    launchableValue = SpecialPowers.setAllAppsLaunchable(true);
+    SpecialPowers.setAllAppsLaunchable(true);
     SpecialPowers.addPermission("webapps-manage", true, document);
     SpecialPowers.addPermission("browser", true, document);
     SpecialPowers.addPermission("embed-apps", true, document);
 
     // Required on firefox as these prefs are only set on b2g:
     SpecialPowers.pushPrefEnv({
       set: [["dom.mozBrowserFramesEnabled", true],
             ["security.apps.privileged.CSP.default",
--- a/toolkit/devtools/gcli/source/lib/gcli/ui/menu.js
+++ b/toolkit/devtools/gcli/source/lib/gcli/ui/menu.js
@@ -55,25 +55,25 @@ function Menu(options) {
   if (menuCssPromise == null) {
     menuCssPromise = host.staticRequire(module, './menu.css');
   }
   menuCssPromise.then(function(menuCss) {
     // Pull the HTML into the DOM, but don't add it to the document
     if (menuCss != null) {
       util.importCss(menuCss, this.document, 'gcli-menu');
     }
-  }.bind(this));
+  }.bind(this), console.error);
 
   this.templateOptions = { blankNullUndefined: true, stack: 'menu.html' };
   if (menuHtmlPromise == null) {
     menuHtmlPromise = host.staticRequire(module, './menu.html');
   }
   menuHtmlPromise.then(function(menuHtml) {
     this.template = util.toDom(this.document, menuHtml);
-  }.bind(this));
+  }.bind(this), console.error);
 
   // Contains the items that should be displayed
   this.items = [];
 
   this.onItemClick = util.createEvent('Menu.onItemClick');
 }
 
 /**
--- a/toolkit/devtools/gcli/source/lib/gcli/util/host.js
+++ b/toolkit/devtools/gcli/source/lib/gcli/util/host.js
@@ -61,27 +61,43 @@ exports.Highlighter = Highlighter;
 /**
  * See docs in lib/gcli/util/host.js:exec
  */
 exports.exec = function(execSpec) {
   throw new Error('Not supported');
 };
 
 /**
+ * When dealing with module paths on windows we want to use the unix
+ * directory separator rather than the windows one, so we avoid using
+ * OS.Path.dirname, and use unix version on all platforms.
+ */
+let resourceDirName = function(path) {
+  let index = path.lastIndexOf("/");
+  if (index == -1) {
+    return ".";
+  }
+  while (index >= 0 && path[index] == "/") {
+    --index;
+  }
+  return path.slice(0, index + 1);
+};
+
+/**
  * Asynchronously load a text resource
  * @see lib/gcli/util/host.js
  */
 exports.staticRequire = function(requistingModule, name) {
   var deferred = promise.defer();
 
   if (name.match(/\.css$/)) {
     deferred.resolve('');
   }
   else {
-    var filename = OS.Path.dirname(requistingModule.id) + '/' + name;
+    var filename = resourceDirName(requistingModule.id) + '/' + name;
     filename = filename.replace(/\/\.\//g, '/');
     filename = 'resource://gre/modules/devtools/' + filename;
 
     var xhr = Cc['@mozilla.org/xmlextras/xmlhttprequest;1']
                 .createInstance(Ci.nsIXMLHttpRequest);
 
     xhr.onload = function onload() {
       deferred.resolve(xhr.responseText);
--- a/toolkit/webapps/WebappOSUtils.jsm
+++ b/toolkit/webapps/WebappOSUtils.jsm
@@ -1,20 +1,18 @@
 /* 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/. */
 
-const Cc = Components.classes;
-const Ci = Components.interfaces;
-const CC = Components.Constructor;
-const Cu = Components.utils;
+const { classes: Cc, interfaces: Ci, results: Cr, utils: Cu, Constructor: CC } = Components;
 
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/FileUtils.jsm");
 Cu.import("resource://gre/modules/osfile.jsm");
+Cu.import("resource://gre/modules/Promise.jsm");
 
 this.EXPORTED_SYMBOLS = ["WebappOSUtils"];
 
 // Returns the MD5 hash of a string.
 function computeHash(aString) {
   let converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"].
                   createInstance(Ci.nsIScriptableUnicodeConverter);
   converter.charset = "UTF-8";
@@ -38,75 +36,93 @@ function computeHash(aString) {
 }
 
 this.WebappOSUtils = {
   getUniqueName: function(aApp) {
     return this.sanitizeStringForFilename(aApp.name).toLowerCase() + "-" +
            computeHash(aApp.manifestURL);
   },
 
+#ifdef XP_WIN
+  /**
+   * Returns the registry key associated to the given app and a boolean that
+   * specifies whether we're using the old naming scheme or the new one.
+   */
+  getAppRegKey: function(aApp) {
+    let regKey = Cc["@mozilla.org/windows-registry-key;1"].
+                 createInstance(Ci.nsIWindowsRegKey);
+
+    try {
+      regKey.open(Ci.nsIWindowsRegKey.ROOT_KEY_CURRENT_USER,
+                  "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\" +
+                  this.getUniqueName(aApp), Ci.nsIWindowsRegKey.ACCESS_READ);
+
+      return { value: regKey,
+               namingSchemeVersion: 2};
+    } catch (ex) {}
+
+    // Fall back to the old installation naming scheme
+    try {
+      regKey.open(Ci.nsIWindowsRegKey.ROOT_KEY_CURRENT_USER,
+                  "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\" +
+                  aApp.origin, Ci.nsIWindowsRegKey.ACCESS_READ);
+
+      return { value: regKey,
+               namingSchemeVersion: 1 };
+    } catch (ex) {}
+
+    return null;
+  },
+#endif
+
   /**
    * Returns the executable of the given app, identifying it by its unique name,
    * which is in either the new format or the old format.
    * On Mac OS X, it returns the identifier of the app.
    *
    * The new format ensures a readable and unique name for an app by combining
    * its name with a hash of its manifest URL.  The old format uses its origin,
    * which is only unique until we support multiple apps per origin.
    */
   getLaunchTarget: function(aApp) {
-    let uniqueName = this.getUniqueName(aApp);
+#ifdef XP_WIN
+    let appRegKey = this.getAppRegKey(aApp);
 
-#ifdef XP_WIN
-    let isOldNamingScheme = false;
-    let appRegKey;
-    try {
-      let open = CC("@mozilla.org/windows-registry-key;1",
-                    "nsIWindowsRegKey", "open");
-      appRegKey = open(Ci.nsIWindowsRegKey.ROOT_KEY_CURRENT_USER,
-                       "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\" +
-                       uniqueName, Ci.nsIWindowsRegKey.ACCESS_READ);
-    } catch (ex) {
-      // Fall back to the old installation naming scheme
-      try {
-        appRegKey = open(Ci.nsIWindowsRegKey.ROOT_KEY_CURRENT_USER,
-                         "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\" +
-                         aApp.origin, Ci.nsIWindowsRegKey.ACCESS_READ);
-        isOldNamingScheme = true;
-      } catch (ex) {
-        return null;
-      }
+    if (!appRegKey) {
+      return null;
     }
 
     let appFilename, installLocation;
     try {
-      appFilename = appRegKey.readStringValue("AppFilename");
-      installLocation = appRegKey.readStringValue("InstallLocation");
+      appFilename = appRegKey.value.readStringValue("AppFilename");
+      installLocation = appRegKey.value.readStringValue("InstallLocation");
     } catch (ex) {
       return null;
     } finally {
-      appRegKey.close();
+      appRegKey.value.close();
     }
 
     installLocation = installLocation.substring(1, installLocation.length - 1);
 
-    if (isOldNamingScheme &&
+    if (appRegKey.namingSchemeVersion == 1 &&
         !this.isOldInstallPathValid(aApp, installLocation)) {
       return null;
     }
 
     let initWithPath = CC("@mozilla.org/file/local;1",
                           "nsILocalFile", "initWithPath");
     let launchTarget = initWithPath(installLocation);
     launchTarget.append(appFilename + ".exe");
 
     return launchTarget;
 #elifdef XP_MACOSX
-    let mwaUtils = Cc["@mozilla.org/widget/mac-web-app-utils;1"]
-                     .createInstance(Ci.nsIMacWebAppUtils);
+    let uniqueName = this.getUniqueName(aApp);
+
+    let mwaUtils = Cc["@mozilla.org/widget/mac-web-app-utils;1"].
+                   createInstance(Ci.nsIMacWebAppUtils);
 
     try {
       let path;
       if (path = mwaUtils.pathForAppWithIdentifier(uniqueName)) {
         return [ uniqueName, path ];
       }
     } catch(ex) {}
 
@@ -116,16 +132,18 @@ this.WebappOSUtils = {
       if ((path = mwaUtils.pathForAppWithIdentifier(aApp.origin)) &&
            this.isOldInstallPathValid(aApp, path)) {
         return [ aApp.origin, path ];
       }
     } catch(ex) {}
 
     return [ null, null ];
 #elifdef XP_UNIX
+    let uniqueName = this.getUniqueName(aApp);
+
     let exeFile = Services.dirsvc.get("Home", Ci.nsIFile);
     exeFile.append("." + uniqueName);
     exeFile.append("webapprt-stub");
 
     // Fall back to the old installation naming scheme
     if (!exeFile.exists()) {
       exeFile = Services.dirsvc.get("Home", Ci.nsIFile);
 
@@ -217,40 +235,40 @@ this.WebappOSUtils = {
 
     return packagePath;
   },
 
   launch: function(aApp) {
     let uniqueName = this.getUniqueName(aApp);
 
 #ifdef XP_WIN
-    let initProcess = CC("@mozilla.org/process/util;1",
-                         "nsIProcess", "init");
-
     let launchTarget = this.getLaunchTarget(aApp);
     if (!launchTarget) {
       return false;
     }
 
     try {
-      let process = initProcess(launchTarget);
+      let process = Cc["@mozilla.org/process/util;1"].
+                    createInstance(Ci.nsIProcess);
+
+      process.init(launchTarget);
       process.runwAsync([], 0);
     } catch (e) {
       return false;
     }
 
     return true;
 #elifdef XP_MACOSX
     let [ launchIdentifier, path ] = this.getLaunchTarget(aApp);
     if (!launchIdentifier) {
       return false;
     }
 
-    let mwaUtils = Cc["@mozilla.org/widget/mac-web-app-utils;1"]
-                     .createInstance(Ci.nsIMacWebAppUtils);
+    let mwaUtils = Cc["@mozilla.org/widget/mac-web-app-utils;1"].
+                   createInstance(Ci.nsIMacWebAppUtils);
 
     try {
       mwaUtils.launchAppWithIdentifier(launchIdentifier);
     } catch(e) {
       return false;
     }
 
     return true;
@@ -270,37 +288,95 @@ this.WebappOSUtils = {
       return false;
     }
 
     return true;
 #endif
   },
 
   uninstall: function(aApp) {
-    let uniqueName = this.getUniqueName(aApp);
+#ifdef XP_WIN
+    let appRegKey = this.getAppRegKey(aApp);
+
+    if (!appRegKey) {
+      return Promise.reject("App registry key not found");
+    }
+
+    let deferred = Promise.defer();
+
+    try {
+      let uninstallerPath = appRegKey.value.readStringValue("UninstallString");
+      uninstallerPath = uninstallerPath.substring(1, uninstallerPath.length - 1);
+
+      let uninstaller = Cc["@mozilla.org/file/local;1"].
+                        createInstance(Ci.nsIFile);
+      uninstaller.initWithPath(uninstallerPath);
 
-#ifdef XP_UNIX
-#ifndef XP_MACOSX
+      let process = Cc["@mozilla.org/process/util;1"].
+                    createInstance(Ci.nsIProcess);
+      process.init(uninstaller);
+      process.runwAsync(["/S"], 1, (aSubject, aTopic) => {
+        if (aTopic == "process-finished") {
+          deferred.resolve(true);
+        } else {
+          deferred.reject("Uninstaller failed with exit code: " + aSubject.exitValue);
+        }
+      });
+    } catch (e) {
+      deferred.reject(e);
+    } finally {
+      appRegKey.value.close();
+    }
+
+    return deferred.promise;
+#elifdef XP_MACOSX
+    let [ , path ] = this.getLaunchTarget(aApp);
+    if (!path) {
+      return Promise.reject("App not found");
+    }
+
+    let deferred = Promise.defer();
+
+    let mwaUtils = Cc["@mozilla.org/widget/mac-web-app-utils;1"].
+                   createInstance(Ci.nsIMacWebAppUtils);
+
+    mwaUtils.trashApp(path, (aResult) => {
+      if (aResult == Cr.NS_OK) {
+        deferred.resolve(true);
+      } else {
+        deferred.resolve("Error moving the app to the Trash: " + aResult);
+      }
+    });
+
+    return deferred.promise;
+#elifdef XP_UNIX
     let exeFile = this.getLaunchTarget(aApp);
     if (!exeFile) {
-      return false;
+      return Promise.reject("App executable file not found");
     }
 
+    let deferred = Promise.defer();
+
     try {
       let process = Cc["@mozilla.org/process/util;1"]
                       .createInstance(Ci.nsIProcess);
 
       process.init(exeFile);
-      process.runAsync(["-remove"], 1);
+      process.runAsync(["-remove"], 1, (aSubject, aTopic) => {
+        if (aTopic == "process-finished") {
+          deferred.resolve(true);
+        } else {
+          deferred.reject("Uninstaller failed with exit code: " + aSubject.exitValue);
+        }
+      });
     } catch (e) {
-      return false;
+      deferred.reject(e);
     }
 
-    return true;
-#endif
+    return deferred.promise;
 #endif
   },
 
   /**
    * Returns true if the given install path (in the old naming scheme) actually
    * belongs to the given application.
    */
   isOldInstallPathValid: function(aApp, aInstallPath) {
--- a/webapprt/win/webapp-uninstaller.nsi.in
+++ b/webapprt/win/webapp-uninstaller.nsi.in
@@ -3,20 +3,17 @@
 # You can obtain one at http://mozilla.org/MPL/2.0/.
 
 # Required Plugins:
 # ShellLink   http://nsis.sourceforge.net/ShellLink_plug-in
 
 ; Set verbosity to 3 (e.g. no script) to lessen the noise in the build logs
 !verbose 3
 
-; 7-Zip provides better compression than the lzma from NSIS so we add the files
-; uncompressed and let the application installer compress it.
 SetDatablockOptimize on
-SetCompress off
 CRCCheck on
 SilentInstall silent
 
 RequestExecutionLevel user
 
 !addplugindir ./
 
 ; prevents compiling of the reg write logging.
@@ -65,59 +62,67 @@ Name "Mozilla Web App Runtime App"
 OutFile "${UninstallerFilename}"
 ShowUnInstDetails nevershow
 
 # Create a blank page so that the default pages (instfiles) don't appear
 UninstPage custom un.blankPage
 
 ################################################################################
 # Install Sections
-# The "installer" that is generated by this file will be run during the build
-# process to generate an uninstaller.  We call `WriteUninstaller` during
-# `onInit` so this section is empty.
+# The "installer" that is generated by this file is a stub that generates the
+# uninstaller at runtime in a temp directory and launches it.
+# We call `WriteUninstaller` during `onInit` so this section is empty.
 Section ""
 SectionEnd
 
 ################################################################################
 # This is where uninstallation happens
 ################################################################################
-Function un.blankPage
-  MessageBox MB_OKCANCEL "$(UN_CONFIRM_UNINSTALL)" /SD IDOK IDCANCEL done
-
+Function un.webappUninstall
   ; Delete the app exe to prevent launching the app while we are uninstalling.
   ClearErrors
   ${DeleteFile} "$INSTDIR\${FileMainEXE}"
   ${If} ${Errors}
     ; If the app is running, rename the EXE out of the way
     CreateDirectory "$AppRTTempDir"
     Rename "$INSTDIR\${FileMainEXE}" "$AppRTTempDir\${FileMainEXE}"
     ClearErrors
   ${EndIf}
 
-
   SetShellVarContext current  ; Set SHCTX to HKCU
 
   ; Remove our entry in the "Uninstall" key
   ${un.RegCleanUninstall}
 
   ; Remove our shortcuts from start menu, desktop, and taskbar
   ${un.DeleteShortcuts}
 
   ; Parse the uninstall log to remove all installed
   ; files / directories this install is responsible for.
   ${un.ParseUninstallLog}
 
-  ; Remove the uninstall directory that we control
-  RmDir /r "$INSTDIR\uninstall"
+  ; Remove the uninstall directory that we control.
+  ; The installer is in the uninstall directory, it generates
+  ; the uninstaller in a temp directory and waits for its
+  ; execution. Thus, we can't remove the uninstall directory
+  ; now and need to wait for a restart.
+  ; See bug 994965.
+  RmDir /r /REBOOTOK "$INSTDIR\uninstall"
 
   ; Remove the installation directory if it is empty
   ${RemoveDir} "$INSTDIR"
 
   ; Refresh shell icons to reflect the changes we've made
   ${un.RefreshShellIcons}
+FunctionEnd
+
+Function un.blankPage
+  MessageBox MB_OKCANCEL "$(UN_CONFIRM_UNINSTALL)" /SD IDOK IDCANCEL done
+
+  Call un.webappUninstall
 
   done:
 FunctionEnd
 
 ################################################################################
 # Language
 
 !verbose push
@@ -139,18 +144,18 @@ Function .onInit
   Delete "$0"
   CreateDirectory "$0"
   SetOutPath "$0"
 
   StrCpy $1 "$0\${UninstallerFilename}"
   WriteUninstaller "$1"
 
   ${GetParameters} $2
-  StrCpy $2 "_?=$EXEDIR $2"
-  Exec '"$1" $2'
+  StrCpy $2 "$2 _?=$EXEDIR"
+  ExecWait '"$1" $2'
   Quit
 FunctionEnd
 
 Function un.onInit
   ; Remove the current exe directory from the search order.
   ; This only effects LoadLibrary calls and not implicitly loaded DLLs.
   System::Call 'kernel32::SetDllDirectoryW(w "")'
 
@@ -163,9 +168,13 @@ Function un.onInit
   ReadINIStr $AppName "$INSTDIR\webapp.ini" "Webapp" "Name"
 
   ${Unless} ${FileExists} "$INSTDIR\${FileMainEXE}"
     Abort
   ${EndUnless}
 
   StrCpy $AppRTTempDir "$TEMP\moz_webapprt"
   RmDir /r "$AppRTTempDir"
+
+  ${If} ${Silent}
+    Call un.webappUninstall
+  ${EndIf}
 FunctionEnd
--- a/widget/cocoa/nsMacWebAppUtils.mm
+++ b/widget/cocoa/nsMacWebAppUtils.mm
@@ -52,8 +52,31 @@ NS_IMETHODIMP nsMacWebAppUtils::LaunchAp
                         options: (NSWorkspaceLaunchOptions)0
                         additionalEventParamDescriptor: nil
                         launchIdentifier: NULL];
 
   return success ? NS_OK : NS_ERROR_FAILURE;
 
   NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
 }
+
+NS_IMETHODIMP nsMacWebAppUtils::TrashApp(const nsAString& path, nsITrashAppCallback* aCallback) {
+  NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+  if (NS_WARN_IF(!aCallback)) {
+    return NS_ERROR_INVALID_ARG;
+  }
+
+  nsCOMPtr<nsITrashAppCallback> callback = aCallback;
+
+  NSString* tempString = [NSString stringWithCharacters:reinterpret_cast<const unichar*>(((nsString)path).get())
+                                   length:path.Length()];
+
+  [[NSWorkspace sharedWorkspace] recycleURLs: [NSArray arrayWithObject:[NSURL fileURLWithPath:tempString]]
+    completionHandler: ^(NSDictionary *newURLs, NSError *error) {
+      nsresult rv = (error == nil) ? NS_OK : NS_ERROR_FAILURE;
+      callback->TrashAppFinished(rv);
+    }];
+
+  return NS_OK;
+
+  NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}
--- a/widget/nsIMacWebAppUtils.idl
+++ b/widget/nsIMacWebAppUtils.idl
@@ -1,25 +1,35 @@
 /* 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 nsIMacWebAppUtils;
 
+[scriptable, function, uuid(8c899c4f-58c1-4b74-9034-3bb64e484b68)]
+interface nsITrashAppCallback : nsISupports
+{
+  void trashAppFinished(in nsresult rv);
+};
+
 /**
  * Allow MozApps API to locate and manipulate natively installed apps
  */
 
-[scriptable, uuid(e9096367-ddd9-45e4-b762-49c0c18b7119)]
+[scriptable, uuid(c69cf343-ea41-428b-b161-4655fd54d8e7)]
 interface nsIMacWebAppUtils : nsISupports {
   /**
    * Find the path for an app with the given signature.
    */
   AString pathForAppWithIdentifier(in AString bundleIdentifier);
 
   /**
    * Launch the app with the given identifier, if it exists.
    */
   void launchAppWithIdentifier(in AString bundleIdentifier);
 
+  /**
+   * Move the app from the given directory to the Trash.
+   */
+  void trashApp(in AString path, in nsITrashAppCallback callback);
 };
--- a/xpcom/glue/tests/gtest/moz.build
+++ b/xpcom/glue/tests/gtest/moz.build
@@ -8,13 +8,15 @@ UNIFIED_SOURCES += [
     'TestFileUtils.cpp',
     'TestGCPostBarriers.cpp',
 ]
 
 LOCAL_INCLUDES = [
     '../..',
 ]
 
+FAIL_ON_WARNINGS = True
+
 LIBRARY_NAME = 'xpcom_glue_gtest'
 
 EXPORT_LIBRARY = True
 
 FINAL_LIBRARY = 'xul-gtest'