Merge inbound to m-c. a=merge
authorRyan VanderMeulen <ryanvm@gmail.com>
Tue, 14 Apr 2015 16:01:51 -0400
changeset 270417 2ee2da378d1201e371f111b41af48e9486d411f8
parent 270346 459352500f98c49d6cf49ca48ec1df09494d9a42 (current diff)
parent 270416 6f35b7d9755a4796de6e767ff3757b2ba7997b35 (diff)
child 270449 c3e4ff7d62c61ca3c454c9acfbdb1a6c67be0abf
push id863
push userraliiev@mozilla.com
push dateMon, 03 Aug 2015 13:22:43 +0000
treeherdermozilla-release@f6321b14228d [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone40.0a1
first release with
nightly linux32
2ee2da378d12 / 40.0a1 / 20150414130909 / files
nightly linux64
2ee2da378d12 / 40.0a1 / 20150414130908 / files
nightly mac
2ee2da378d12 / 40.0a1 / 20150414130908 / files
nightly win32
2ee2da378d12 / 40.0a1 / 20150414130911 / files
nightly win64
2ee2da378d12 / 40.0a1 / 20150414130909 / files
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
releases
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge inbound to m-c. a=merge
--- a/accessible/tests/mochitest/common.js
+++ b/accessible/tests/mochitest/common.js
@@ -420,19 +420,19 @@ function testAccessibleTree(aAccOrElmOrI
       is(accTree[prop], acc.DOMNode.tagName, msg);
       break;
 
     case "textAttrs": {
       var prevOffset = -1;
       for (var offset in accTree[prop]) {
         if (prevOffset !=- 1) {
           var attrs = accTree[prop][prevOffset];
-          testTextAttrs(acc, prevOffset, attrs, { }, prevOffset, offset, true);
+          testTextAttrs(acc, prevOffset, attrs, { }, prevOffset, +offset, true);
         }
-        prevOffset = offset;
+        prevOffset = +offset;
       }
 
       if (prevOffset != -1) {
         var charCount = getAccessible(acc, [nsIAccessibleText]).characterCount;
         var attrs = accTree[prop][prevOffset];
         testTextAttrs(acc, prevOffset, attrs, { }, prevOffset, charCount, true);
       }
 
--- a/accessible/tests/mochitest/events/test_text_alg.html
+++ b/accessible/tests/mochitest/events/test_text_alg.html
@@ -15,18 +15,18 @@
           src="../common.js"></script>
   <script type="application/javascript"
           src="../events.js"></script>
 
   <script type="application/javascript">
     ////////////////////////////////////////////////////////////////////////////
     // Invokers
 
-    const kRemoval = 0;
-    const kInsertion = 1;
+    const kRemoval = false;
+    const kInsertion = true;
     const kUnexpected = true;
 
     function changeText(aContainerID, aValue, aEventList)
     {
       this.containerNode = getNode(aContainerID);
       this.textNode = this.containerNode.firstChild;
       this.textData = this.textNode.data;
 
--- a/accessible/tests/mochitest/events/test_tree.xul
+++ b/accessible/tests/mochitest/events/test_tree.xul
@@ -77,17 +77,17 @@
           startCol = null;
         }
         is(startCol, aStartCol,
            "Wrong 'startcolumn' of 'treeInvalidated' event on " + aMsg);
 
         try {
           var endCol = propBag.getPropertyAsInt32("endcolumn");
         } catch (e if e.name == 'NS_ERROR_NOT_AVAILABLE') {
-          startCol = null;
+          endCol = null;
         }
         is(endCol, aEndCol,
            "Wrong 'endcolumn' of 'treeInvalidated' event on " + aMsg);
       }
       this.getID = function getID()
       {
         return "TreeInvalidated on " + aMsg;
       }
--- a/accessible/tests/mochitest/states.js
+++ b/accessible/tests/mochitest/states.js
@@ -47,17 +47,17 @@ const EXT_STATE_MULTI_LINE = nsIAccessib
 const EXT_STATE_PINNED = nsIAccessibleStates.EXT_STATE_PINNED;
 const EXT_STATE_SENSITIVE = nsIAccessibleStates.EXT_STATE_SENSITIVE;
 const EXT_STATE_SINGLE_LINE = nsIAccessibleStates.EXT_STATE_SINGLE_LINE;
 const EXT_STATE_STALE = nsIAccessibleStates.EXT_STATE_STALE;
 const EXT_STATE_SUPPORTS_AUTOCOMPLETION =
   nsIAccessibleStates.EXT_STATE_SUPPORTS_AUTOCOMPLETION;
 const EXT_STATE_VERTICAL = nsIAccessibleStates.EXT_STATE_VERTICAL;
 
-const kOrdinalState = 0;
+const kOrdinalState = false;
 const kExtraState = 1;
 
 ////////////////////////////////////////////////////////////////////////////////
 // Test functions
 
 /**
  * Tests the states and extra states of the given accessible.
  * Also tests for unwanted states and extra states.
--- a/accessible/tests/mochitest/table/test_sels_listbox.xul
+++ b/accessible/tests/mochitest/table/test_sels_listbox.xul
@@ -72,17 +72,17 @@
       // getSelectedColumns
       var selColsCount = {}, selCols = {};
       aAcc.getSelectedColumnIndices(selColsCount, selCols);
 
       is(selColsCount.value, aSelCount,
          aId + ": wrong number of selected columns");
 
       if (!aSelIndexesArray) {
-        is(selCols.value, null,
+        is(selCols.value, undefined,
            aId + ": no columns should be selected");
       } else {
         for (var i = 0; i < selCols.length; i++) {
           is(selCols[i], aSelIndexesArray[i],
              aId + ": wrong selected column index " + i);
         }
       }
     }
@@ -111,17 +111,17 @@
       // getSelectedRows
       var selColsCount = {}, selCols = {};
       aAcc.getSelectedRowIndices(selColsCount, selCols);
 
       is(selColsCount.value, aSelCount,
          aId + ": wrong number of selected rows");
 
       if (!aSelIndexesArray) {
-        is(selCols.value, null,
+        is(selCols.value, undefined,
            aId + ": no row should be selected");
       } else {
         for (var i = 0; i < selCols.length; i++) {
           is(selCols[i], aSelIndexesArray[i],
              aId + ": wrong selected row index " + i);
         }
       }
     }
@@ -154,17 +154,17 @@
       // getSelectedCells
       var selColsCount = {}, selCols = {};
       aAcc.getSelectedCellIndices(selColsCount, selCols);
 
       is(selColsCount.value, aSelCount,
          aId + ": wrong number of selected cells");
 
       if (!aSelIndexesArray) {
-        is(selCols.value, null,
+        is(selCols.value, undefined,
            aId + ": no cells should be selected");
       } else {
         for (var i = 0; i < selCols.length; i++) {
           is(selCols[i], aSelIndexesArray[i],
              aId + ": wrong selected cell index " + i);
         }
       }
     }
--- a/b2g/app/b2g.js
+++ b/b2g/app/b2g.js
@@ -1119,28 +1119,13 @@ pref("dom.mozSettings.SettingsService.ve
 // Controlling whether we want to allow forcing some Settings
 // IndexedDB transactions to be opened as readonly or keep everything as
 // readwrite.
 pref("dom.mozSettings.allowForceReadOnly", false);
 
 // RequestSync API is enabled by default on B2G.
 pref("dom.requestSync.enabled", true);
 
-// Only enable for kit kat and above devices
-// kit kat == 19, L = 21, 20 is kit-kat for wearables
-// 15 is for the ICS emulators which will fallback to software vsync
-#if ANDROID_VERSION == 19 || ANDROID_VERSION == 21 || ANDROID_VERSION == 15
+// Use vsync aligned rendering
 pref("gfx.vsync.hw-vsync.enabled", true);
 pref("gfx.vsync.compositor", true);
 pref("gfx.touch.resample", true);
-#else
-pref("gfx.vsync.hw-vsync.enabled", false);
-pref("gfx.vsync.compositor", false);
-pref("gfx.touch.resample", false);
-#endif
-
-// Bug 1147753 - Weird issues with vsync refresh driver on L devices
-// so disable them on L, but enable on KK and ICS
-#if ANDROID_VERSION == 19 || ANDROID_VERSION == 15
 pref("gfx.vsync.refreshdriver", true);
-#else
-pref("gfx.vsync.refreshdriver", false);
-#endif
--- a/b2g/components/ContentPermissionPrompt.js
+++ b/b2g/components/ContentPermissionPrompt.js
@@ -287,60 +287,55 @@ ContentPermissionPrompt.prototype = {
        return;
     }
 
     // prompt PROMPT_ACTION request or request with options.
     typesInfo = typesInfo.filter(function(type) {
       return !type.deny && (type.action == Ci.nsIPermissionManager.PROMPT_ACTION || type.options.length > 0) ;
     });
 
-    let frame = request.element;
-
-    if (!frame) {
+    if (!request.element) {
       this.delegatePrompt(request, typesInfo);
       return;
     }
 
-    frame = frame.wrappedJSObject;
     var cancelRequest = function() {
-      frame.removeEventListener("mozbrowservisibilitychange", onVisibilityChange);
+      request.requester.onVisibilityChange = null;
       request.cancel();
     }
 
     var self = this;
-    var onVisibilityChange = function(evt) {
-      if (evt.detail.visible === true)
-        return;
-
-      self.cancelPrompt(request, typesInfo);
-      cancelRequest();
-    }
 
     // If the request was initiated from a hidden iframe
     // we don't forward it to content and cancel it right away
-    let domRequest = frame.getVisible();
-    domRequest.onsuccess = function gv_success(evt) {
-      if (!evt.target.result) {
-        cancelRequest();
-        return;
-      }
+    request.requester.getVisibility( {
+      notifyVisibility: function(isVisible) {
+        if (!isVisible) {
+          cancelRequest();
+          return;
+        }
 
-      // Monitor the frame visibility and cancel the request if the frame goes
-      // away but the request is still here.
-      frame.addEventListener("mozbrowservisibilitychange", onVisibilityChange);
+        // Monitor the frame visibility and cancel the request if the frame goes
+        // away but the request is still here.
+        request.requester.onVisibilityChange = {
+          notifyVisibility: function(isVisible) {
+            if (isVisible)
+              return;
 
-      self.delegatePrompt(request, typesInfo, function onCallback() {
-        frame.removeEventListener("mozbrowservisibilitychange", onVisibilityChange);
-      });
-    };
+            self.cancelPrompt(request, typesInfo);
+            cancelRequest();
+          }
+        }
 
-    // Something went wrong. Let's cancel the request just in case.
-    domRequest.onerror = function gv_error() {
-      cancelRequest();
-    }
+        self.delegatePrompt(request, typesInfo, function onCallback() {
+          request.requester.onVisibilityChange = null;
+        });
+      }
+    });
+
   },
 
   cancelPrompt: function(request, typesInfo) {
     this.sendToBrowserWindow("cancel-permission-prompt", request,
                              typesInfo);
   },
 
   delegatePrompt: function(request, typesInfo, callback) {
--- a/b2g/components/test/mochitest/mochitest.ini
+++ b/b2g/components/test/mochitest/mochitest.ini
@@ -11,8 +11,9 @@ support-files =
 [test_filepicker_path.html]
 [test_permission_deny.html]
 [test_permission_gum_remember.html]
 skip-if = true # Bug 1019572 - frequent timeouts
 [test_sandbox_permission.html]
 [test_screenshot.html]
 [test_systemapp.html]
 [test_presentation_device_prompt.html]
+[test_permission_visibilitychange.html]
new file mode 100644
--- /dev/null
+++ b/b2g/components/test/mochitest/test_permission_visibilitychange.html
@@ -0,0 +1,57 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=951997
+-->
+<head>
+  <meta charset="utf-8">
+  <title>Permission Prompt Test</title>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1020179">Permission prompt visibilitychange test</a>
+<script type="application/javascript;version=1.8">
+
+"use strict";
+
+var gUrl = SimpleTest.getTestFileURL("permission_handler_chrome.js");
+var gScript = SpecialPowers.loadChromeScript(gUrl);
+
+function testDone() {
+  gScript.sendAsyncMessage("teardown", "");
+  gScript.destroy();
+  SimpleTest.finish();
+  alert("setVisible::true");
+}
+
+function runTest() {
+  navigator.geolocation.getCurrentPosition(
+    function (pos) {
+      ok(false, "unexpected success, permission request should be canceled");
+      testDone();
+    }, function (err) {
+      ok(true, "success, permission request is canceled");
+      testDone();
+  });
+}
+
+gScript.addMessageListener("permission-request", function (detail) {
+  info("got permission-request!!!!\n");
+  alert("setVisible::false");
+});
+
+// Add permissions to this app. We use ALLOW_ACTION here. The ContentPermissionPrompt
+// should prompt for permission, not allow it without prompt.
+SpecialPowers.pushPrefEnv({"set": [["media.navigator.permission.disabled", false]]},
+  function() {
+    SpecialPowers.addPermission("geolocation",
+      SpecialPowers.Ci.nsIPermissionManager.PROMPT_ACTION, document);
+    runTest();
+  });
+
+SimpleTest.waitForExplicitFinish();
+</script>
+</pre>
+</body>
+</html>
--- a/browser/base/content/test/general/test_no_mcb_on_http_site_font.html
+++ b/browser/base/content/test/general/test_no_mcb_on_http_site_font.html
@@ -14,29 +14,29 @@
   function checkLoadStates() {
    var ui = SpecialPowers.wrap(window)
             .QueryInterface(SpecialPowers.Ci.nsIInterfaceRequestor)
              .getInterface(SpecialPowers.Ci.nsIWebNavigation)
              .QueryInterface(SpecialPowers.Ci.nsIDocShell)
              .securityUI;
 
    var loadedMixedActive = ui &&
-     (ui.state & SpecialPowers.Ci.nsIWebProgressListener.STATE_LOADED_MIXED_ACTIVE_CONTENT);
+     !!(ui.state & SpecialPowers.Ci.nsIWebProgressListener.STATE_LOADED_MIXED_ACTIVE_CONTENT);
    is(loadedMixedActive, false, "OK: Should not load mixed active content!");
 
    var blockedMixedActive = ui &&
-       (ui.state & SpecialPowers.Ci.nsIWebProgressListener.STATE_BLOCKED_MIXED_ACTIVE_CONTENT);
+     !!(ui.state & SpecialPowers.Ci.nsIWebProgressListener.STATE_BLOCKED_MIXED_ACTIVE_CONTENT);
    is(blockedMixedActive, false, "OK: Should not block mixed active content!");
 
    var loadedMixedDisplay = ui &&
-     (ui.state & SpecialPowers.Ci.nsIWebProgressListener.STATE_LOADED_MIXED_DISPLAY_CONTENT);
+     !!(ui.state & SpecialPowers.Ci.nsIWebProgressListener.STATE_LOADED_MIXED_DISPLAY_CONTENT);
    is(loadedMixedDisplay, false, "OK: Should not load mixed display content!");
 
    var blockedMixedDisplay = ui &&
-     (ui.state & SpecialPowers.Ci.nsIWebProgressListener.STATE_BLOCKED_MIXED_DISPLAY_CONTENT);
+     !!(ui.state & SpecialPowers.Ci.nsIWebProgressListener.STATE_BLOCKED_MIXED_DISPLAY_CONTENT);
    is(blockedMixedDisplay, false, "OK: Should not block mixed display content!");
 
    var newValue = "Verifying MCB does not trigger warning/error for an http page with https css that includes http font";
    document.getElementById("testDiv").innerHTML = newValue;
   }
 </script>
 </head>
 <body onload="checkLoadStates()">
--- a/browser/base/content/test/general/test_no_mcb_on_http_site_font2.html
+++ b/browser/base/content/test/general/test_no_mcb_on_http_site_font2.html
@@ -14,29 +14,29 @@
   function checkLoadStates() {
    var ui = SpecialPowers.wrap(window)
         .QueryInterface(SpecialPowers.Ci.nsIInterfaceRequestor)
              .getInterface(SpecialPowers.Ci.nsIWebNavigation)
              .QueryInterface(SpecialPowers.Ci.nsIDocShell)
              .securityUI;
 
    var loadedMixedActive = ui &&
-     (ui.state & SpecialPowers.Ci.nsIWebProgressListener.STATE_LOADED_MIXED_ACTIVE_CONTENT);
+     !!(ui.state & SpecialPowers.Ci.nsIWebProgressListener.STATE_LOADED_MIXED_ACTIVE_CONTENT);
    is(loadedMixedActive, false, "OK: Should not load mixed active content!");
 
    var blockedMixedActive = ui &&
-       (ui.state & SpecialPowers.Ci.nsIWebProgressListener.STATE_BLOCKED_MIXED_ACTIVE_CONTENT);
+     !!(ui.state & SpecialPowers.Ci.nsIWebProgressListener.STATE_BLOCKED_MIXED_ACTIVE_CONTENT);
    is(blockedMixedActive, false, "OK: Should not block mixed active content!");
 
    var loadedMixedDisplay = ui &&
-     (ui.state & SpecialPowers.Ci.nsIWebProgressListener.STATE_LOADED_MIXED_DISPLAY_CONTENT);
+     !!(ui.state & SpecialPowers.Ci.nsIWebProgressListener.STATE_LOADED_MIXED_DISPLAY_CONTENT);
    is(loadedMixedDisplay, false, "OK: Should not load mixed display content!");
 
    var blockedMixedDisplay = ui &&
-     (ui.state & SpecialPowers.Ci.nsIWebProgressListener.STATE_BLOCKED_MIXED_DISPLAY_CONTENT);
+     !!(ui.state & SpecialPowers.Ci.nsIWebProgressListener.STATE_BLOCKED_MIXED_DISPLAY_CONTENT);
    is(blockedMixedDisplay, false, "OK: Should not block mixed display content!");
 
    var newValue = "Verifying MCB does not trigger warning/error for an http page ";
     newValue += "with https css that imports another http css which includes http font";
    document.getElementById("testDiv").innerHTML = newValue;
   }
 </script>
 </head>
--- a/browser/base/content/test/general/test_no_mcb_on_http_site_img.html
+++ b/browser/base/content/test/general/test_no_mcb_on_http_site_img.html
@@ -14,29 +14,29 @@
   function checkLoadStates() {
    var ui = SpecialPowers.wrap(window)
             .QueryInterface(SpecialPowers.Ci.nsIInterfaceRequestor)
              .getInterface(SpecialPowers.Ci.nsIWebNavigation)
              .QueryInterface(SpecialPowers.Ci.nsIDocShell)
              .securityUI;
 
    var loadedMixedActive = ui &&
-     (ui.state & SpecialPowers.Ci.nsIWebProgressListener.STATE_LOADED_MIXED_ACTIVE_CONTENT);
+     !!(ui.state & SpecialPowers.Ci.nsIWebProgressListener.STATE_LOADED_MIXED_ACTIVE_CONTENT);
    is(loadedMixedActive, false, "OK: Should not load mixed active content!");
 
    var blockedMixedActive = ui &&
-       (ui.state & SpecialPowers.Ci.nsIWebProgressListener.STATE_BLOCKED_MIXED_ACTIVE_CONTENT);
+     !!(ui.state & SpecialPowers.Ci.nsIWebProgressListener.STATE_BLOCKED_MIXED_ACTIVE_CONTENT);
    is(blockedMixedActive, false, "OK: Should not block mixed active content!");
 
    var loadedMixedDisplay = ui &&
-     (ui.state & SpecialPowers.Ci.nsIWebProgressListener.STATE_LOADED_MIXED_DISPLAY_CONTENT);
+     !!(ui.state & SpecialPowers.Ci.nsIWebProgressListener.STATE_LOADED_MIXED_DISPLAY_CONTENT);
    is(loadedMixedDisplay, false, "OK: Should not load mixed display content!");
 
    var blockedMixedDisplay = ui &&
-     (ui.state & SpecialPowers.Ci.nsIWebProgressListener.STATE_BLOCKED_MIXED_DISPLAY_CONTENT);
+     !!(ui.state & SpecialPowers.Ci.nsIWebProgressListener.STATE_BLOCKED_MIXED_DISPLAY_CONTENT);
    is(blockedMixedDisplay, false, "OK: Should not block mixed display content!");
 
    var newValue = "Verifying MCB does not trigger warning/error for an http page with https css that includes http image";
    document.getElementById("testDiv").innerHTML = newValue;
   }
 </script>
 </head>
 <body onload="checkLoadStates()">
--- a/browser/base/content/test/newtab/head.js
+++ b/browser/base/content/test/newtab/head.js
@@ -509,31 +509,29 @@ function startAndCompleteDragOperation(a
     synthesizeNativeMouseLDown(aSource);
     synthesizeNativeMouseDrag(aDest);
     // In some tests, aSource and aDest are at the same position, so to ensure
     // a drag session is created (instead of it just turning into a click) we
     // move the mouse 10 pixels away and then back.
     synthesizeNativeMouseDrag(aDest, 10);
     synthesizeNativeMouseDrag(aDest);
     // Finally, release the drag and have it run the callback when done.
-    synthesizeNativeMouseLUp(aDest);
-    aCallback();
+    synthesizeNativeMouseLUp(aDest).then(aCallback, Cu.reportError);
   } else if (isWindows) {
     // on Windows once the drag is initiated, Windows doesn't spin our
     // message loop at all, so with async event synthesization the async
     // messages never get processed while a drag is in progress. So if
     // we did a mousedown followed by a mousemove, we would never be able
     // to successfully dispatch the mouseup. Instead, we just skip the move
     // entirely, so and just generate the up at the destination. This way
     // Windows does the drag and also terminates it right away. Note that
     // this only works for tests where aSource and aDest are sufficiently
     // far to trigger a drag, otherwise it may just end up doing a click.
     synthesizeNativeMouseLDown(aSource);
-    synthesizeNativeMouseLUp(aDest);
-    aCallback();
+    synthesizeNativeMouseLUp(aDest).then(aCallback, Cu.reportError);
   } else if (isLinux) {
     // Start by pressing the left mouse button.
     synthesizeNativeMouseLDown(aSource);
 
     // Move the mouse in 5px steps until the drag operation starts.
     // Note that we need to do this with pauses in between otherwise the
     // synthesized events get coalesced somewhere in the guts of GTK. In order
     // to successfully initiate a drag session in the case where aSource and
@@ -559,18 +557,17 @@ function startAndCompleteDragOperation(a
     // the mousemove synthesization is "more async" than the mouseup
     // synthesization - they use different gdk APIs. If we don't wait, the
     // up could get processed before the moves, dropping the item in the
     // wrong position.
     aDest.addEventListener("dragenter", function onDragEnter() {
       aDest.removeEventListener("dragenter", onDragEnter);
 
       // Finish the drop operation.
-      synthesizeNativeMouseLUp(aDest, null);
-      aCallback();
+      synthesizeNativeMouseLUp(aDest).then(aCallback, Cu.reportError);
     });
   } else {
     throw "Unsupported platform";
   }
 }
 
 /**
  * Helper function that creates a temporary iframe in the about:newtab
@@ -616,17 +613,17 @@ function synthesizeNativeMouseLDown(aEle
 }
 
 /**
  * Fires a synthetic 'mouseup' event on the current about:newtab page.
  * @param aElement The element used to determine the cursor position.
  */
 function synthesizeNativeMouseLUp(aElement) {
   let msg = isWindows ? 4 : (isMac ? 2 : 7);
-  synthesizeNativeMouseEvent(aElement, msg);
+  return synthesizeNativeMouseEvent(aElement, msg);
 }
 
 /**
  * Fires a synthetic mouse drag event on the current about:newtab page.
  * @param aElement The element used to determine the cursor position.
  * @param aOffsetX The left offset that is added to the position.
  */
 function synthesizeNativeMouseDrag(aElement, aOffsetX) {
@@ -645,26 +642,35 @@ function synthesizeNativeMouseMove(aElem
 
 /**
  * Fires a synthetic mouse event on the current about:newtab page.
  * @param aElement The element used to determine the cursor position.
  * @param aOffsetX The left offset that is added to the position (optional).
  * @param aOffsetY The top offset that is added to the position (optional).
  */
 function synthesizeNativeMouseEvent(aElement, aMsg, aOffsetX = 0, aOffsetY = 0) {
-  let rect = aElement.getBoundingClientRect();
-  let win = aElement.ownerDocument.defaultView;
-  let x = aOffsetX + win.mozInnerScreenX + rect.left + rect.width / 2;
-  let y = aOffsetY + win.mozInnerScreenY + rect.top + rect.height / 2;
+  return new Promise((resolve, reject) => {
+    let rect = aElement.getBoundingClientRect();
+    let win = aElement.ownerDocument.defaultView;
+    let x = aOffsetX + win.mozInnerScreenX + rect.left + rect.width / 2;
+    let y = aOffsetY + win.mozInnerScreenY + rect.top + rect.height / 2;
+
+    let utils = win.QueryInterface(Ci.nsIInterfaceRequestor)
+                   .getInterface(Ci.nsIDOMWindowUtils);
 
-  let utils = win.QueryInterface(Ci.nsIInterfaceRequestor)
-                 .getInterface(Ci.nsIDOMWindowUtils);
-
-  let scale = utils.screenPixelsPerCSSPixel;
-  utils.sendNativeMouseEvent(x * scale, y * scale, aMsg, 0, null);
+    let scale = utils.screenPixelsPerCSSPixel;
+    let observer = {
+      observe: function(aSubject, aTopic, aData) {
+        if (aTopic == "mouseevent") {
+          resolve();
+        }
+      }
+    };
+    utils.sendNativeMouseEvent(x * scale, y * scale, aMsg, 0, null, observer);
+  });
 }
 
 /**
  * Sends a custom drag event to a given DOM element.
  * @param aEventType The drag event's type.
  * @param aTarget The DOM element that the event is dispatched to.
  * @param aData The event's drag data (optional).
  */
--- a/browser/devtools/app-manager/test/test_connection_store.html
+++ b/browser/devtools/app-manager/test/test_connection_store.html
@@ -53,17 +53,17 @@ Bug 901519 - [app manager] data store fo
         let port = root.querySelector("#port");
         let template = new Template(root, store, () => {});
         template.start();
 
         connection.host = "foobar";
         connection.port = 42;
 
         is(host.textContent, "foobar", "host updated");
-        is(port.textContent, 42, "port updated");
+        is(port.textContent, "42", "port updated");
 
         let been_through_connecting = false;
         let been_through_connected = false;
         let been_through_disconnected = false;
 
         is(status.textContent, "disconnected", "status updated (diconnected)");
 
         connection.once("connecting", (e) => {
--- a/browser/devtools/webide/test/device_front_shared.js
+++ b/browser/devtools/webide/test/device_front_shared.js
@@ -97,28 +97,28 @@ function addNewFieldInteger() {
   customValue.value = 1;
   found = false;
 
   customBtn.click();
   newField = doc.querySelector("#new-integer-field");
   if (newField) {
     found = true;
     is(newField.type, "number", "Custom type is a number");
-    is(newField.value, 1, "Custom integer value is correct");
+    is(newField.value, "1", "Custom integer value is correct");
   }
   ok(found, "Found new integer field line");
   is(customName.value, "", "Custom integer name reset");
-  is(customValue.value, 0, "Custom integer value reset");
+  is(customValue.value, "", "Custom integer value reset");
 }
 
 let editFieldInteger = Task.async(function*() {
   // Edit existing custom integer preference
   newField.value = 3;
   newField.click();
-  is(newField.value, 3, "Custom integer existing value is correct");
+  is(newField.value, "3", "Custom integer existing value is correct");
 
   // Reset a custom field
   let resetBtn = doc.querySelector("#btn-new-integer-field");
   resetBtn.click();
 
   try {
     yield iframe.contentWindow.configView._defaultField;
   } catch(err) {
--- a/browser/devtools/webide/test/test_simulators.html
+++ b/browser/devtools/webide/test/test_simulators.html
@@ -228,17 +228,17 @@
 
           yield set(form.version, "custom");
 
           // Test `device`.
 
           let defaults = Simulator.prototype._defaults;
 
           for (let param in defaults) {
-            is(form[param].value, defaults[param], "Default value for device " + param);
+            is(form[param].value, String(defaults[param]), "Default value for device " + param);
           }
 
           let width = 5000, height = 4000;
           yield set(form.width, width);
           yield set(form.height, height);
 
           is(form.device.value, "custom", "Device selector is custom");
 
@@ -271,41 +271,41 @@
 
           yield set(form.version, sim20.addonID);
 
           is(form.name.value, customName + "2.0", "Name deduplication was undone when possible");
 
           // Test `device`.
 
           for (let param in defaults) {
-            is(form[param].value, defaults[param], "Default value for device " + param);
+            is(form[param].value, String(defaults[param]), "Default value for device " + param);
           }
 
           let devices = yield GetDevices();
           devices = devices[devices.TYPES[0]];
           let device = devices[devices.length - 1];
 
           yield set(form.device, device.name);
 
           is(form.device.value, device.name, "Device selector was changed");
-          is(form.width.value, device.width, "New device width is correct");
-          is(form.height.value, device.height, "New device height is correct");
+          is(form.width.value, String(device.width), "New device width is correct");
+          is(form.height.value, String(device.height), "New device height is correct");
 
           params = yield runSimulator(1);
 
           sid = params.args.indexOf("-screen");
           ok(params.args[sid + 1].contains(device.width + "x" + device.height), "Simulator screen resolution looks right");
 
           // Restore default simulator options.
 
           doc.querySelector("#reset").click();
           yield nextTick();
 
           for (let param in defaults) {
-            is(form[param].value, defaults[param], "Default value for device " + param);
+            is(form[param].value, String(defaults[param]), "Default value for device " + param);
           }
 
           // Uninstall the 2.0 addon and watch its Simulator object disappear.
 
           sim20.uninstall();
 
           yield addonStatus(sim20, "uninstalled");
 
--- a/caps/tests/mochitest/test_disableScript.xul
+++ b/caps/tests/mochitest/test_disableScript.xul
@@ -72,17 +72,17 @@ https://bugzilla.mozilla.org/show_bug.cg
   function addFrame(parentWin, name, expectOnload) {
     let ifr = parentWin.document.createElement('iframe');
     parentWin.document.body.appendChild(ifr);
     ifr.setAttribute('name', name);
     let deferred = Promise.defer();
     // We need to append 'name' to avoid running afoul of recursive frame detection.
     let frameURI = uri + "?name=" + name;
     navigateFrame(ifr, frameURI).then(function() {
-      is(ifr.contentWindow.location, frameURI, "Successful load");
+      is(String(ifr.contentWindow.location), frameURI, "Successful load");
       is(!!ifr.contentWindow.wrappedJSObject.gFiredOnload, expectOnload,
          "onload should only fire when scripts are enabled");
       deferred.resolve();
     });
     return deferred.promise;
   }
 
   function checkScriptEnabled(win, expectEnabled) {
--- a/docshell/test/navigation/test_bug430723.html
+++ b/docshell/test/navigation/test_bug430723.html
@@ -54,17 +54,17 @@ var nextTest =function() {
   switch (testNum) {
     case 1: setTimeout(step1, 0); break;
     case 2: setTimeout(step2, 0); break;
     case 3: setTimeout(step3, 0); break;
   };
 }
 
 var step1 =function() {
-  window.is(testWindow.location, gTallRedBoxURI, "Ensure red page loaded.");
+  window.is(String(testWindow.location), gTallRedBoxURI, "Ensure red page loaded.");
   
   // Navigate down and up.
   is(testWindow.document.body.scrollTop, 0,
      "Page1: Ensure the scrollpane is at the top before we start scrolling.");
   testWindow.addEventListener("scroll", function () {
     testWindow.removeEventListener("scroll", arguments.callee, true);
     isnot(testWindow.document.body.scrollTop, 0,
           "Page1: Ensure we can scroll down.");
@@ -82,17 +82,17 @@ var step1 =function() {
       testWindow.location = gTallBlueBoxURI;
     }, true);
     sendKey('UP', testWindow);
   }
 }
 
 
 var step2 =function() {    
-  window.is(testWindow.location, gTallBlueBoxURI, "Ensure blue page loaded.");
+  window.is(String(testWindow.location), gTallBlueBoxURI, "Ensure blue page loaded.");
 
   // Scroll around a bit.
   is(testWindow.document.body.scrollTop, 0,
      "Page2: Ensure the scrollpane is at the top before we start scrolling.");
 
   var count = 0;
   testWindow.addEventListener("scroll", function () {
     if (++count < 2) {
@@ -106,17 +106,17 @@ var step2 =function() {
       // Navigate backwards. This should fire step3.
       testWindow.history.back();
     }
   }, true);
   sendKey('DOWN', testWindow);
 }
 
 var step3 =function() {
-  window.is(testWindow.location, gTallRedBoxURI,
+  window.is(String(testWindow.location), gTallRedBoxURI,
             "Ensure red page restored from history.");
 
   // Check we can still scroll with the keys.
   is(testWindow.document.body.scrollTop, 0,
      "Page1Again: Ensure scroll pane at top before we scroll.");  
   testWindow.addEventListener("scroll", function () {
     testWindow.removeEventListener("scroll", arguments.callee, true);
 
--- a/dom/apps/tests/test_import_export.html
+++ b/dom/apps/tests/test_import_export.html
@@ -224,17 +224,17 @@ function runTest() {
         progress: 0,
         installState: "installed",
         downloadAvailable: false,
         downloading: false,
         downloadSize: 0,
         size: 0,
         readyToApplyDownload: false
       };
-      PackagedTestHelper.checkAppState(PackagedTestHelper.gApp, 2, expected,
+      PackagedTestHelper.checkAppState(PackagedTestHelper.gApp, "2", expected,
                                        true, false, continueTest);
     };
   };
 
   var request = navigator.mozApps.installPackage(miniManifestURL);
   request.onerror = PackagedTestHelper.mozAppsError;
   request.onsuccess = function() {
     info("Packaged Application installed");
--- a/dom/apps/tests/test_packaged_app_install.html
+++ b/dom/apps/tests/test_packaged_app_install.html
@@ -53,34 +53,16 @@ function checkUninstallApp(aApp) {
     PackagedTestHelper.next();
   };
   req.onerror = function(evt) {
     ok(false, "Got unexpected " + evt.target.error.name);
     PackagedTestHelper.finish();
   };
 }
 
-function checkInstalledApp(aMiniManifestURL,
-                           aVersion,
-                           aExpectedApp,
-                           aLaunchable,
-                           aCb) {
-  var req = navigator.mozApps.checkInstalled(aMiniManifestURL);
-  req.onsuccess = function(evt) {
-    ok(req.result, "The app is installed");
-
-    if (!req.result) {
-      PackagedTestHelper.finish();
-    }
-
-    PackagedTestHelper.checkAppState(evt.application, aVersion, aExpectedApp,
-                                     aLaunchable, false, aCb);
-  };
-}
-
 var gIconData =
 "\x89\x50\x4E\x47\x0D\x0A\x1A\x0A\x00\x00\x00\x0D\x49\x48\x44\x52" +
 "\x00\x00\x00\x0F\x00\x00\x00\x0F\x08\x03\x00\x00\x00\x0C\x08\x65" +
 "\x78\x00\x00\x00\x04\x67\x41\x4D\x41\x00\x00\xD6\xD8\xD4\x4F\x58" +
 "\x32\x00\x00\x00\x19\x74\x45\x58\x74\x53\x6F\x66\x74\x77\x61\x72" +
 "\x65\x00\x41\x64\x6F\x62\x65\x20\x49\x6D\x61\x67\x65\x52\x65\x61" +
 "\x64\x79\x71\xC9\x65\x3C\x00\x00\x00\x39\x50\x4C\x54\x45\xB5\x42" + 
 "\x42\xCE\x94\x94\xCE\x84\x84\x9C\x21\x21\xAD\x21\x21\xCE\x73\x73" +
@@ -235,17 +217,17 @@ var steps = [
           progress: 0,
           installState: "installed",
           downloadAvailable: false,
           downloading: false,
           downloadSize: 0,
           size: 0,
           readyToApplyDownload: false
         };
-        PackagedTestHelper.checkAppState(PackagedTestHelper.gApp, 2, expected,
+        PackagedTestHelper.checkAppState(PackagedTestHelper.gApp, "2", expected,
                                          true, false, PackagedTestHelper.next);
       };
     };
 
     var request = navigator.mozApps.installPackage(miniManifestURL);
     request.onerror = PackagedTestHelper.mozAppsError;
     request.onsuccess = function() {
       info("Application installed");
@@ -322,17 +304,17 @@ var steps = [
           progress: 0,
           installState: alreadyCanceled?"pending":"installed",
           downloadAvailable: false,
           downloading: false,
           downloadSize: 0,
           size: 0,
           readyToApplyDownload: alreadyCanceled
         };
-        PackagedTestHelper.checkAppState(PackagedTestHelper.gApp, 3, expected,
+        PackagedTestHelper.checkAppState(PackagedTestHelper.gApp, "3", expected,
                                          true, false, function() {});
       };
 
       PackagedTestHelper.gApp.ondownloadapplied = function() {
         info("App download applied.");
         var expected = {
           name: PackagedTestHelper.gAppName,
           manifestURL: miniManifestURL,
@@ -340,17 +322,17 @@ var steps = [
           progress: 0,
           installState: "installed",
           downloadAvailable: false,
           downloading: false,
           downloadSize: 0,
           size: 0,
           readyToApplyDownload: false
         };
-        PackagedTestHelper.checkAppState(PackagedTestHelper.gApp, 3, expected,
+        PackagedTestHelper.checkAppState(PackagedTestHelper.gApp, "3", expected,
                                          true, false, PackagedTestHelper.next);
       }
 
     };
 
     var request = navigator.mozApps.installPackage(miniManifestURL);
     request.onerror = PackagedTestHelper.mozAppsError;
     request.onsuccess = function() {
--- a/dom/apps/tests/test_packaged_app_update.html
+++ b/dom/apps/tests/test_packaged_app_update.html
@@ -168,17 +168,17 @@ var steps = [
       info("Got oninstall event");
       PackagedTestHelper.gApp = evt.application;
       PackagedTestHelper.gApp.ondownloaderror = function() {
         ok(false, "Download error " + PackagedTestHelper.gApp.downloadError.name);
         PackagedTestHelper.finish();
       };
       PackagedTestHelper.gApp.ondownloadsuccess =
         checkLastAppState.bind(undefined, miniManifestURL, false, false,
-                               2, PackagedTestHelper.next);
+                               "2", PackagedTestHelper.next);
     };
 
     var request = navigator.mozApps.installPackage(miniManifestURL);
     request.onerror = PackagedTestHelper.mozAppsError;
     request.onsuccess = function() {
       info("Application installed");
     };
   },
@@ -227,17 +227,17 @@ var steps = [
     validatePermissions(permissionsToCheck, true /*dontFail*/);
     PackagedTestHelper.next();
   },
   function() {
     PackagedTestHelper.setAppVersion(3, PackagedTestHelper.next);
   },
   function() {
     info("== TEST == Update packaged app");
-    updateApp(true, 2, 3);
+    updateApp(true, "2", "3");
   },
   function() {
     info("== TEST == Check that saved permissions were kept");
     validatePermissions(permissionsToCheck);
     PackagedTestHelper.next();
   },
   function() {
     info("== TEST == Check for Update after getting a new package");
@@ -246,24 +246,24 @@ var steps = [
   function() {
     PackagedTestHelper.setAppVersion(4, PackagedTestHelper.next);
   },
   function() {
     PackagedTestHelper.setAppNameSuffix("NEWVERSION", PackagedTestHelper.next);
   },
   function() {
     info("== TEST == Check that name changes in an update are ignored");
-    updateApp(true, 3, 4);
+    updateApp(true, "3", "4");
   },
   function() {
     PackagedTestHelper.setAppVersion(5, PackagedTestHelper.next, true);
   },
   function() {
     info("== TEST == Update packaged app - same package");
-    updateApp(false, 4, 4, true);
+    updateApp(false, "4", "4", true);
   },
   function() {
     info("== TEST == Check for Update after getting the same package");
     checkForUpdate(false);
   },
 
   function() {
     PackagedTestHelper.setAppVersion(6, PackagedTestHelper.next,
--- a/dom/asmjscache/AsmJSCache.cpp
+++ b/dom/asmjscache/AsmJSCache.cpp
@@ -1830,37 +1830,25 @@ public:
                          const nsACString& aOrigin)
                          override
   { }
 
   virtual void
   ReleaseIOThreadObjects() override
   { }
 
-  virtual bool
-  IsFileServiceUtilized() override
-  {
-    return false;
-  }
-
-  virtual bool
-  IsTransactionServiceActivated() override
-  {
-    return false;
-  }
-
   virtual void
   WaitForStoragesToComplete(nsTArray<nsIOfflineStorage*>& aStorages,
                             nsIRunnable* aCallback) override
   {
     MOZ_ASSERT_UNREACHABLE("There are no storages");
   }
 
   virtual void
-  ShutdownTransactionService() override
+  ShutdownWorkThreads() override
   { }
 
 private:
   nsAutoRefCnt mRefCnt;
   NS_DECL_OWNINGTHREAD
 };
 
 NS_IMPL_ADDREF(asmjscache::Client)
--- a/dom/audiochannel/AudioChannelService.cpp
+++ b/dom/audiochannel/AudioChannelService.cpp
@@ -787,17 +787,17 @@ PLDHashOperator
 AudioChannelService::WindowDestroyedEnumerator(AudioChannelAgent* aAgent,
                                                nsAutoPtr<AudioChannelAgentData>& aData,
                                                void* aPtr)
 {
   auto* data = static_cast<WindowDestroyedEnumeratorData*>(aPtr);
   MOZ_ASSERT(data);
 
   nsCOMPtr<nsPIDOMWindow> window = do_QueryInterface(aAgent->Window());
-  if (!window->IsInnerWindow()) {
+  if (window && !window->IsInnerWindow()) {
     window = window->GetCurrentInnerWindow();
   }
 
   if (!window || window->WindowID() != data->mInnerID) {
     return PL_DHASH_NEXT;
   }
 
   AudioChannelService* service = AudioChannelService::GetAudioChannelService();
--- a/dom/base/nsContentPermissionHelper.cpp
+++ b/dom/base/nsContentPermissionHelper.cpp
@@ -1,39 +1,112 @@
 /* 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 <map>
 #ifdef MOZ_WIDGET_GONK
 #include "GonkPermission.h"
-#include "mozilla/dom/ContentParent.h"
 #endif // MOZ_WIDGET_GONK
 #include "nsCOMPtr.h"
 #include "nsIDOMElement.h"
 #include "nsIPrincipal.h"
+#include "mozilla/dom/ContentChild.h"
+#include "mozilla/dom/ContentParent.h"
 #include "mozilla/dom/Element.h"
 #include "mozilla/dom/PContentPermission.h"
 #include "mozilla/dom/PermissionMessageUtils.h"
 #include "mozilla/dom/PContentPermissionRequestParent.h"
 #include "mozilla/dom/ScriptSettings.h"
 #include "mozilla/dom/TabChild.h"
 #include "mozilla/dom/TabParent.h"
 #include "mozilla/unused.h"
 #include "nsComponentManagerUtils.h"
 #include "nsArrayUtils.h"
 #include "nsIMutableArray.h"
 #include "nsContentPermissionHelper.h"
 #include "nsJSUtils.h"
 #include "nsISupportsPrimitives.h"
 #include "nsServiceManagerUtils.h"
+#include "nsIDocument.h"
+#include "nsIDOMEvent.h"
+#include "nsWeakPtr.h"
 
 using mozilla::unused;          // <snicker>
 using namespace mozilla::dom;
 using namespace mozilla;
 
+#define kVisibilityChange "visibilitychange"
+
+NS_IMPL_ISUPPORTS(VisibilityChangeListener, nsIDOMEventListener)
+
+VisibilityChangeListener::VisibilityChangeListener(nsPIDOMWindow* aWindow)
+{
+  MOZ_ASSERT(aWindow);
+
+  mWindow = do_GetWeakReference(aWindow);
+  nsCOMPtr<nsIDocument> doc = aWindow->GetExtantDoc();
+  if (doc) {
+    doc->AddSystemEventListener(NS_LITERAL_STRING(kVisibilityChange),
+                                /* listener */ this,
+                                /* use capture */ true,
+                                /* wants untrusted */ false);
+  }
+
+}
+
+NS_IMETHODIMP
+VisibilityChangeListener::HandleEvent(nsIDOMEvent* aEvent)
+{
+  nsAutoString type;
+  aEvent->GetType(type);
+  if (!type.EqualsLiteral(kVisibilityChange)) {
+    return NS_ERROR_FAILURE;
+  }
+
+  nsCOMPtr<nsIDocument> doc =
+    do_QueryInterface(aEvent->InternalDOMEvent()->GetTarget());
+  MOZ_ASSERT(doc);
+
+  if (mCallback) {
+    mCallback->NotifyVisibility(!doc->Hidden());
+  }
+
+  return NS_OK;
+}
+
+void
+VisibilityChangeListener::RemoveListener()
+{
+  nsCOMPtr<nsPIDOMWindow> window = do_QueryReferent(mWindow);
+  if (!window) {
+    return;
+  }
+
+  nsCOMPtr<EventTarget> target = do_QueryInterface(window->GetExtantDoc());
+  if (target) {
+    target->RemoveSystemEventListener(NS_LITERAL_STRING(kVisibilityChange),
+                                      /* listener */ this,
+                                      /* use capture */ true);
+  }
+}
+
+void
+VisibilityChangeListener::SetCallback(nsIContentPermissionRequestCallback *aCallback)
+{
+  mCallback = aCallback;
+}
+
+already_AddRefed<nsIContentPermissionRequestCallback>
+VisibilityChangeListener::GetCallback()
+{
+  nsCOMPtr<nsIContentPermissionRequestCallback> callback = mCallback;
+  return callback.forget();
+}
+
 namespace mozilla {
 namespace dom {
 
 class ContentPermissionRequestParent : public PContentPermissionRequestParent
 {
  public:
   ContentPermissionRequestParent(const nsTArray<PermissionRequest>& aRequests,
                                  Element* element,
@@ -44,16 +117,17 @@ class ContentPermissionRequestParent : p
 
   nsCOMPtr<nsIPrincipal> mPrincipal;
   nsCOMPtr<Element> mElement;
   nsRefPtr<nsContentPermissionRequestProxy> mProxy;
   nsTArray<PermissionRequest> mRequests;
 
  private:
   virtual bool Recvprompt();
+  virtual bool RecvNotifyVisibility(const bool& aIsVisible);
   virtual void ActorDestroy(ActorDestroyReason why);
 };
 
 ContentPermissionRequestParent::ContentPermissionRequestParent(const nsTArray<PermissionRequest>& aRequests,
                                                                Element* aElement,
                                                                const IPC::Principal& aPrincipal)
 {
   MOZ_COUNT_CTOR(ContentPermissionRequestParent);
@@ -74,31 +148,41 @@ ContentPermissionRequestParent::Recvprom
   mProxy = new nsContentPermissionRequestProxy();
   NS_ASSERTION(mProxy, "Alloc of request proxy failed");
   if (NS_FAILED(mProxy->Init(mRequests, this))) {
     mProxy->Cancel();
   }
   return true;
 }
 
+bool
+ContentPermissionRequestParent::RecvNotifyVisibility(const bool& aIsVisible)
+{
+  if (!mProxy) {
+    return false;
+  }
+  mProxy->NotifyVisibility(aIsVisible);
+  return true;
+}
+
 void
 ContentPermissionRequestParent::ActorDestroy(ActorDestroyReason why)
 {
   if (mProxy) {
     mProxy->OnParentDestroyed();
   }
 }
 
 bool
 ContentPermissionRequestParent::IsBeingDestroyed()
 {
-  // When TabParent::Destroy() is called, we are being destroyed. It's unsafe
-  // to send out any message now.
-  TabParent* tabParent = TabParent::GetFrom(Manager());
-  return tabParent->IsDestroyed();
+  // When ContentParent::MarkAsDead() is called, we are being destroyed.
+  // It's unsafe to send out any message now.
+  ContentParent* contentParent = static_cast<ContentParent*>(Manager());
+  return !contentParent->IsAlive();
 }
 
 NS_IMPL_ISUPPORTS(ContentPermissionType, nsIContentPermissionType)
 
 ContentPermissionType::ContentPermissionType(const nsACString& aType,
                                              const nsACString& aAccess,
                                              const nsTArray<nsString>& aOptions)
 {
@@ -200,16 +284,23 @@ nsContentPermissionUtils::ConvertArrayTo
       }
     }
 
     aDesArray.AppendElement(PermissionRequest(type, access, options));
   }
   return len;
 }
 
+static std::map<PContentPermissionRequestParent*, TabId>&
+ContentPermissionRequestParentMap()
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  static std::map<PContentPermissionRequestParent*, TabId> sPermissionRequestParentMap;
+  return sPermissionRequestParentMap;
+}
 
 /* static */ nsresult
 nsContentPermissionUtils::CreatePermissionArray(const nsACString& aType,
                                                 const nsACString& aAccess,
                                                 const nsTArray<nsString>& aOptions,
                                                 nsIArray** aTypesArray)
 {
   nsCOMPtr<nsIMutableArray> types = do_CreateInstance(NS_ARRAY_CONTRACTID);
@@ -220,19 +311,24 @@ nsContentPermissionUtils::CreatePermissi
   types.forget(aTypesArray);
 
   return NS_OK;
 }
 
 /* static */ PContentPermissionRequestParent*
 nsContentPermissionUtils::CreateContentPermissionRequestParent(const nsTArray<PermissionRequest>& aRequests,
                                                                Element* element,
-                                                               const IPC::Principal& principal)
+                                                               const IPC::Principal& principal,
+                                                               const TabId& aTabId)
 {
-  return new ContentPermissionRequestParent(aRequests, element, principal);
+  PContentPermissionRequestParent* parent =
+    new ContentPermissionRequestParent(aRequests, element, principal);
+  ContentPermissionRequestParentMap()[parent] = aTabId;
+
+  return parent;
 }
 
 /* static */ nsresult
 nsContentPermissionUtils::AskPermission(nsIContentPermissionRequest* aRequest, nsPIDOMWindow* aWindow)
 {
   MOZ_ASSERT(!aWindow || aWindow->IsInnerWindow());
   NS_ENSURE_STATE(aWindow && aWindow->IsCurrentInnerWindow());
 
@@ -254,36 +350,168 @@ nsContentPermissionUtils::AskPermission(
     nsTArray<PermissionRequest> permArray;
     ConvertArrayToPermissionRequest(typeArray, permArray);
 
     nsCOMPtr<nsIPrincipal> principal;
     rv = aRequest->GetPrincipal(getter_AddRefs(principal));
     NS_ENSURE_SUCCESS(rv, rv);
 
     req->IPDLAddRef();
-    child->SendPContentPermissionRequestConstructor(req,
-                                                    permArray,
-                                                    IPC::Principal(principal));
+    ContentChild::GetSingleton()->SendPContentPermissionRequestConstructor(
+      req,
+      permArray,
+      IPC::Principal(principal),
+      child->GetTabId());
 
     req->Sendprompt();
     return NS_OK;
   }
 
   // for chrome process
   nsCOMPtr<nsIContentPermissionPrompt> prompt =
     do_GetService(NS_CONTENT_PERMISSION_PROMPT_CONTRACTID);
   if (prompt) {
     prompt->Prompt(aRequest);
   }
   return NS_OK;
 }
 
+/* static */ nsTArray<PContentPermissionRequestParent*>
+nsContentPermissionUtils::GetContentPermissionRequestParentById(const TabId& aTabId)
+{
+  nsTArray<PContentPermissionRequestParent*> parentArray;
+  for (auto& it : ContentPermissionRequestParentMap()) {
+    if (it.second == aTabId) {
+      parentArray.AppendElement(it.first);
+    }
+  }
+
+  return Move(parentArray);
+}
+
+/* static */ void
+nsContentPermissionUtils::NotifyRemoveContentPermissionRequestParent(
+  PContentPermissionRequestParent* aParent)
+{
+  auto it = ContentPermissionRequestParentMap().find(aParent);
+  MOZ_ASSERT(it != ContentPermissionRequestParentMap().end());
+
+  ContentPermissionRequestParentMap().erase(it);
+}
+
+NS_IMPL_ISUPPORTS(nsContentPermissionRequester, nsIContentPermissionRequester)
+
+nsContentPermissionRequester::nsContentPermissionRequester(nsPIDOMWindow* aWindow)
+  : mWindow(aWindow)
+{
+  mListener = new VisibilityChangeListener(mWindow);
+}
+
+nsContentPermissionRequester::~nsContentPermissionRequester()
+{
+  mListener->RemoveListener();
+  mListener = nullptr;
+}
+
+NS_IMETHODIMP
+nsContentPermissionRequester::GetVisibility(nsIContentPermissionRequestCallback* aCallback)
+{
+  NS_ENSURE_ARG_POINTER(aCallback);
+
+  if (!mWindow) {
+    MOZ_ASSERT(false);
+    return NS_ERROR_FAILURE;
+  }
+
+  nsCOMPtr<nsIDocShell> docshell = mWindow->GetDocShell();
+  if (!docshell) {
+    return NS_ERROR_FAILURE;
+  }
+
+  bool isActive = false;
+  docshell->GetIsActive(&isActive);
+  aCallback->NotifyVisibility(isActive);
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+nsContentPermissionRequester::SetOnVisibilityChange(nsIContentPermissionRequestCallback* aCallback)
+{
+  mListener->SetCallback(aCallback);
+
+  if (!aCallback) {
+    mListener->RemoveListener();
+  }
+
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+nsContentPermissionRequester::GetOnVisibilityChange(nsIContentPermissionRequestCallback** aCallback)
+{
+  NS_ENSURE_ARG_POINTER(aCallback);
+
+  nsCOMPtr<nsIContentPermissionRequestCallback> callback = mListener->GetCallback();
+  callback.forget(aCallback);
+  return NS_OK;
+}
+
 } // namespace dom
 } // namespace mozilla
 
+NS_IMPL_ISUPPORTS(nsContentPermissionRequestProxy::nsContentPermissionRequesterProxy,
+                  nsIContentPermissionRequester)
+
+NS_IMETHODIMP
+nsContentPermissionRequestProxy::nsContentPermissionRequesterProxy
+  ::GetVisibility(nsIContentPermissionRequestCallback* aCallback)
+{
+  NS_ENSURE_ARG_POINTER(aCallback);
+
+  mGetCallback = aCallback;
+  mWaitGettingResult = true;
+  unused << mParent->SendGetVisibility();
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+nsContentPermissionRequestProxy::nsContentPermissionRequesterProxy
+  ::SetOnVisibilityChange(nsIContentPermissionRequestCallback* aCallback)
+{
+  mOnChangeCallback = aCallback;
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+nsContentPermissionRequestProxy::nsContentPermissionRequesterProxy
+  ::GetOnVisibilityChange(nsIContentPermissionRequestCallback** aCallback)
+{
+  NS_ENSURE_ARG_POINTER(aCallback);
+
+  nsCOMPtr<nsIContentPermissionRequestCallback> callback = mOnChangeCallback;
+  callback.forget(aCallback);
+  return NS_OK;
+}
+
+void
+nsContentPermissionRequestProxy::nsContentPermissionRequesterProxy
+  ::NotifyVisibilityResult(const bool& aIsVisible)
+{
+  if (mWaitGettingResult) {
+    MOZ_ASSERT(mGetCallback);
+    mWaitGettingResult = false;
+    mGetCallback->NotifyVisibility(aIsVisible);
+    return;
+  }
+
+  if (mOnChangeCallback) {
+    mOnChangeCallback->NotifyVisibility(aIsVisible);
+  }
+}
+
 nsContentPermissionRequestProxy::nsContentPermissionRequestProxy()
 {
   MOZ_COUNT_CTOR(nsContentPermissionRequestProxy);
 }
 
 nsContentPermissionRequestProxy::~nsContentPermissionRequestProxy()
 {
   MOZ_COUNT_DTOR(nsContentPermissionRequestProxy);
@@ -291,29 +519,31 @@ nsContentPermissionRequestProxy::~nsCont
 
 nsresult
 nsContentPermissionRequestProxy::Init(const nsTArray<PermissionRequest>& requests,
                                       ContentPermissionRequestParent* parent)
 {
   NS_ASSERTION(parent, "null parent");
   mParent = parent;
   mPermissionRequests = requests;
+  mRequester = new nsContentPermissionRequesterProxy(mParent);
 
   nsCOMPtr<nsIContentPermissionPrompt> prompt = do_GetService(NS_CONTENT_PERMISSION_PROMPT_CONTRACTID);
   if (!prompt) {
     return NS_ERROR_FAILURE;
   }
 
   prompt->Prompt(this);
   return NS_OK;
 }
 
 void
 nsContentPermissionRequestProxy::OnParentDestroyed()
 {
+  mRequester = nullptr;
   mParent = nullptr;
 }
 
 NS_IMPL_ISUPPORTS(nsContentPermissionRequestProxy, nsIContentPermissionRequest)
 
 NS_IMETHODIMP
 nsContentPermissionRequestProxy::GetTypes(nsIArray** aTypes)
 {
@@ -392,22 +622,22 @@ nsContentPermissionRequestProxy::Allow(J
   }
 
 #ifdef MOZ_WIDGET_GONK
   uint32_t len = mPermissionRequests.Length();
   for (uint32_t i = 0; i < len; i++) {
     if (mPermissionRequests[i].type().EqualsLiteral("audio-capture")) {
       GonkPermissionService::GetInstance()->addGrantInfo(
         "android.permission.RECORD_AUDIO",
-        TabParent::GetFrom(mParent->Manager())->Manager()->AsContentParent()->Pid());
+        static_cast<ContentParent*>(mParent->Manager())->Pid());
     }
     if (mPermissionRequests[i].type().EqualsLiteral("video-capture")) {
       GonkPermissionService::GetInstance()->addGrantInfo(
         "android.permission.CAMERA",
-        TabParent::GetFrom(mParent->Manager())->Manager()->AsContentParent()->Pid());
+        static_cast<ContentParent*>(mParent->Manager())->Pid());
     }
   }
 #endif
 
   nsTArray<PermissionChoice> choices;
   if (aChoices.isNullOrUndefined()) {
     // No choice is specified.
   } else if (aChoices.isObject()) {
@@ -437,27 +667,47 @@ nsContentPermissionRequestProxy::Allow(J
     return NS_ERROR_FAILURE;
   }
 
   unused << ContentPermissionRequestParent::Send__delete__(mParent, true, choices);
   mParent = nullptr;
   return NS_OK;
 }
 
+void
+nsContentPermissionRequestProxy::NotifyVisibility(const bool& aIsVisible)
+{
+  MOZ_ASSERT(mRequester);
+
+  mRequester->NotifyVisibilityResult(aIsVisible);
+}
+
+NS_IMETHODIMP
+nsContentPermissionRequestProxy::GetRequester(nsIContentPermissionRequester** aRequester)
+{
+  NS_ENSURE_ARG_POINTER(aRequester);
+
+  nsRefPtr<nsContentPermissionRequesterProxy> requester = mRequester;
+  requester.forget(aRequester);
+  return NS_OK;
+}
+
 // RemotePermissionRequest
 
-NS_IMPL_ISUPPORTS0(RemotePermissionRequest)
+NS_IMPL_ISUPPORTS(RemotePermissionRequest, nsIContentPermissionRequestCallback);
 
 RemotePermissionRequest::RemotePermissionRequest(
   nsIContentPermissionRequest* aRequest,
   nsPIDOMWindow* aWindow)
   : mRequest(aRequest)
   , mWindow(aWindow)
   , mIPCOpen(false)
 {
+  mListener = new VisibilityChangeListener(mWindow);
+  mListener->SetCallback(this);
 }
 
 void
 RemotePermissionRequest::DoCancel()
 {
   NS_ASSERTION(mRequest, "We need a request");
   mRequest->Cancel();
 }
@@ -469,16 +719,19 @@ RemotePermissionRequest::DoAllow(JS::Han
   mRequest->Allow(aChoices);
 }
 
 // PContentPermissionRequestChild
 bool
 RemotePermissionRequest::Recv__delete__(const bool& aAllow,
                                         InfallibleTArray<PermissionChoice>&& aChoices)
 {
+  mListener->RemoveListener();
+  mListener = nullptr;
+
   if (aAllow && mWindow->IsCurrentInnerWindow()) {
     // Use 'undefined' if no choice is provided.
     if (aChoices.IsEmpty()) {
       DoAllow(JS::UndefinedHandleValue);
       return true;
     }
 
     // Convert choices to a JS val if any.
@@ -502,8 +755,33 @@ RemotePermissionRequest::Recv__delete__(
     }
     JS::RootedValue val(cx, JS::ObjectValue(*obj));
     DoAllow(val);
   } else {
     DoCancel();
   }
   return true;
 }
+
+bool
+RemotePermissionRequest::RecvGetVisibility()
+{
+  nsCOMPtr<nsIDocShell> docshell = mWindow->GetDocShell();
+  if (!docshell) {
+    return false;
+  }
+
+  bool isActive = false;
+  docshell->GetIsActive(&isActive);
+  unused << SendNotifyVisibility(isActive);
+  return true;
+}
+
+NS_IMETHODIMP
+RemotePermissionRequest::NotifyVisibility(bool isVisible)
+{
+  if (!mIPCOpen) {
+    return NS_OK;
+  }
+
+  unused << SendNotifyVisibility(isVisible);
+  return NS_OK;
+}
--- a/dom/base/nsContentPermissionHelper.h
+++ b/dom/base/nsContentPermissionHelper.h
@@ -4,16 +4,19 @@
 
 #ifndef nsContentPermissionHelper_h
 #define nsContentPermissionHelper_h
 
 #include "nsIContentPermissionPrompt.h"
 #include "nsTArray.h"
 #include "nsIMutableArray.h"
 #include "mozilla/dom/PContentPermissionRequestChild.h"
+#include "mozilla/dom/ipc/IdType.h"
+#include "nsIDOMEventListener.h"
+
 // Microsoft's API Name hackery sucks
 // XXXbz Doing this in a header is a gigantic footgun. See
 // https://bugzilla.mozilla.org/show_bug.cgi?id=932421#c3 for why.
 #undef LoadImage
 
 class nsPIDOMWindow;
 class nsContentPermissionRequestProxy;
 
@@ -22,16 +25,35 @@ class nsContentPermissionRequestProxy;
 // "windows.h" and it defines
 //   #define CreateEvent CreateEventW
 //   #define LoadImage LoadImageW
 // That will mess up windows build.
 namespace IPC {
 class Principal;
 }
 
+class VisibilityChangeListener final : public nsIDOMEventListener
+{
+public:
+  NS_DECL_ISUPPORTS
+  NS_DECL_NSIDOMEVENTLISTENER
+
+  explicit VisibilityChangeListener(nsPIDOMWindow* aWindow);
+
+  void RemoveListener();
+  void SetCallback(nsIContentPermissionRequestCallback* aCallback);
+  already_AddRefed<nsIContentPermissionRequestCallback> GetCallback();
+
+private:
+  virtual ~VisibilityChangeListener() {}
+
+  nsWeakPtr mWindow;
+  nsCOMPtr<nsIContentPermissionRequestCallback> mCallback;
+};
+
 namespace mozilla {
 namespace dom {
 
 class Element;
 class PermissionRequest;
 class ContentPermissionRequestParent;
 class PContentPermissionRequestParent;
 
@@ -68,62 +90,112 @@ public:
   CreatePermissionArray(const nsACString& aType,
                         const nsACString& aAccess,
                         const nsTArray<nsString>& aOptions,
                         nsIArray** aTypesArray);
 
   static PContentPermissionRequestParent*
   CreateContentPermissionRequestParent(const nsTArray<PermissionRequest>& aRequests,
                                        Element* element,
-                                       const IPC::Principal& principal);
+                                       const IPC::Principal& principal,
+                                       const TabId& aTabId);
 
   static nsresult
   AskPermission(nsIContentPermissionRequest* aRequest, nsPIDOMWindow* aWindow);
+
+  static nsTArray<PContentPermissionRequestParent*>
+  GetContentPermissionRequestParentById(const TabId& aTabId);
+
+  static void
+  NotifyRemoveContentPermissionRequestParent(PContentPermissionRequestParent* aParent);
+};
+
+class nsContentPermissionRequester final : public nsIContentPermissionRequester
+{
+public:
+  NS_DECL_ISUPPORTS
+  NS_DECL_NSICONTENTPERMISSIONREQUESTER
+
+  explicit nsContentPermissionRequester(nsPIDOMWindow* aWindow);
+
+private:
+  virtual ~nsContentPermissionRequester();
+
+  nsCOMPtr<nsPIDOMWindow> mWindow;
+  nsRefPtr<VisibilityChangeListener> mListener;
 };
 
 } // namespace dom
 } // namespace mozilla
 
+using mozilla::dom::ContentPermissionRequestParent;
+
 class nsContentPermissionRequestProxy : public nsIContentPermissionRequest
 {
- public:
+public:
   NS_DECL_ISUPPORTS
   NS_DECL_NSICONTENTPERMISSIONREQUEST
 
   nsContentPermissionRequestProxy();
 
   nsresult Init(const nsTArray<mozilla::dom::PermissionRequest>& requests,
-                mozilla::dom::ContentPermissionRequestParent* parent);
+                ContentPermissionRequestParent* parent);
 
   void OnParentDestroyed();
 
- private:
+  void NotifyVisibility(const bool& aIsVisible);
+
+private:
+  class nsContentPermissionRequesterProxy final : public nsIContentPermissionRequester {
+  public:
+    NS_DECL_ISUPPORTS
+    NS_DECL_NSICONTENTPERMISSIONREQUESTER
+
+    explicit nsContentPermissionRequesterProxy(ContentPermissionRequestParent* aParent)
+      : mParent(aParent)
+      , mWaitGettingResult(false) {}
+
+    void NotifyVisibilityResult(const bool& aIsVisible);
+
+  private:
+    virtual ~nsContentPermissionRequesterProxy() {}
+
+    ContentPermissionRequestParent* mParent;
+    bool mWaitGettingResult;
+    nsCOMPtr<nsIContentPermissionRequestCallback> mGetCallback;
+    nsCOMPtr<nsIContentPermissionRequestCallback> mOnChangeCallback;
+  };
+
   virtual ~nsContentPermissionRequestProxy();
 
   // Non-owning pointer to the ContentPermissionRequestParent object which owns this proxy.
-  mozilla::dom::ContentPermissionRequestParent* mParent;
+  ContentPermissionRequestParent* mParent;
   nsTArray<mozilla::dom::PermissionRequest> mPermissionRequests;
+  nsRefPtr<nsContentPermissionRequesterProxy> mRequester;
 };
 
 /**
  * RemotePermissionRequest will send a prompt ipdl request to b2g process.
  */
-class RemotePermissionRequest final : public nsISupports
+class RemotePermissionRequest final : public nsIContentPermissionRequestCallback
                                     , public mozilla::dom::PContentPermissionRequestChild
 {
 public:
   NS_DECL_ISUPPORTS
+  NS_DECL_NSICONTENTPERMISSIONREQUESTCALLBACK
 
   RemotePermissionRequest(nsIContentPermissionRequest* aRequest,
                           nsPIDOMWindow* aWindow);
 
   // It will be called when prompt dismissed.
   virtual bool Recv__delete__(const bool &aAllow,
                               InfallibleTArray<PermissionChoice>&& aChoices) override;
 
+  virtual bool RecvGetVisibility() override;
+
   void IPDLAddRef()
   {
     mIPCOpen = true;
     AddRef();
   }
 
   void IPDLRelease()
   {
@@ -138,12 +210,13 @@ private:
   }
 
   void DoAllow(JS::HandleValue aChoices);
   void DoCancel();
 
   nsCOMPtr<nsIContentPermissionRequest> mRequest;
   nsCOMPtr<nsPIDOMWindow>               mWindow;
   bool                                  mIPCOpen;
+  nsRefPtr<VisibilityChangeListener>    mListener;
 };
 
 #endif // nsContentPermissionHelper_h
 
--- a/dom/base/nsDOMWindowUtils.cpp
+++ b/dom/base/nsDOMWindowUtils.cpp
@@ -1296,123 +1296,145 @@ nsDOMWindowUtils::SendKeyEvent(const nsA
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsDOMWindowUtils::SendNativeKeyEvent(int32_t aNativeKeyboardLayout,
                                      int32_t aNativeKeyCode,
                                      int32_t aModifiers,
                                      const nsAString& aCharacters,
-                                     const nsAString& aUnmodifiedCharacters)
+                                     const nsAString& aUnmodifiedCharacters,
+                                     nsIObserver* aObserver)
 {
   MOZ_RELEASE_ASSERT(nsContentUtils::IsCallerChrome());
 
   // get the widget to send the event to
   nsCOMPtr<nsIWidget> widget = GetWidget();
   if (!widget)
     return NS_ERROR_FAILURE;
 
-  return widget->SynthesizeNativeKeyEvent(aNativeKeyboardLayout, aNativeKeyCode,
-                                          aModifiers, aCharacters, aUnmodifiedCharacters);
+  NS_DispatchToMainThread(NS_NewRunnableMethodWithArgs
+    <int32_t, int32_t, uint32_t, nsString, nsString, nsIObserver*>
+    (widget, &nsIWidget::SynthesizeNativeKeyEvent, aNativeKeyboardLayout,
+    aNativeKeyCode, aModifiers, aCharacters, aUnmodifiedCharacters, aObserver));
+  return NS_OK;
 }
 
 NS_IMETHODIMP
 nsDOMWindowUtils::SendNativeMouseEvent(int32_t aScreenX,
                                        int32_t aScreenY,
                                        int32_t aNativeMessage,
                                        int32_t aModifierFlags,
-                                       nsIDOMElement* aElement)
+                                       nsIDOMElement* aElement,
+                                       nsIObserver* aObserver)
 {
   MOZ_RELEASE_ASSERT(nsContentUtils::IsCallerChrome());
 
   // get the widget to send the event to
   nsCOMPtr<nsIWidget> widget = GetWidgetForElement(aElement);
   if (!widget)
     return NS_ERROR_FAILURE;
 
-  return widget->SynthesizeNativeMouseEvent(LayoutDeviceIntPoint(aScreenX, aScreenY),
-                                            aNativeMessage, aModifierFlags);
+  NS_DispatchToMainThread(NS_NewRunnableMethodWithArgs
+    <LayoutDeviceIntPoint, int32_t, int32_t, nsIObserver*>
+    (widget, &nsIWidget::SynthesizeNativeMouseEvent,
+    LayoutDeviceIntPoint(aScreenX, aScreenY), aNativeMessage, aModifierFlags,
+    aObserver));
+  return NS_OK;
 }
 
 NS_IMETHODIMP
 nsDOMWindowUtils::SendNativeMouseScrollEvent(int32_t aScreenX,
                                              int32_t aScreenY,
                                              uint32_t aNativeMessage,
                                              double aDeltaX,
                                              double aDeltaY,
                                              double aDeltaZ,
                                              uint32_t aModifierFlags,
                                              uint32_t aAdditionalFlags,
-                                             nsIDOMElement* aElement)
+                                             nsIDOMElement* aElement,
+                                             nsIObserver* aObserver)
 {
   MOZ_RELEASE_ASSERT(nsContentUtils::IsCallerChrome());
 
   // get the widget to send the event to
   nsCOMPtr<nsIWidget> widget = GetWidgetForElement(aElement);
   if (!widget) {
     return NS_ERROR_FAILURE;
   }
 
-  return widget->SynthesizeNativeMouseScrollEvent(LayoutDeviceIntPoint(aScreenX,
-                                                                       aScreenY),
-                                                  aNativeMessage,
-                                                  aDeltaX, aDeltaY, aDeltaZ,
-                                                  aModifierFlags,
-                                                  aAdditionalFlags);
+  NS_DispatchToMainThread(NS_NewRunnableMethodWithArgs
+    <mozilla::LayoutDeviceIntPoint, uint32_t, double, double, double, uint32_t, uint32_t, nsIObserver*>
+    (widget, &nsIWidget::SynthesizeNativeMouseScrollEvent,
+    LayoutDeviceIntPoint(aScreenX, aScreenY), aNativeMessage, aDeltaX, aDeltaY,
+    aDeltaZ, aModifierFlags, aAdditionalFlags, aObserver));
+  return NS_OK;
 }
 
 NS_IMETHODIMP
 nsDOMWindowUtils::SendNativeTouchPoint(uint32_t aPointerId,
                                        uint32_t aTouchState,
                                        int32_t aScreenX,
                                        int32_t aScreenY,
                                        double aPressure,
-                                       uint32_t aOrientation)
+                                       uint32_t aOrientation,
+                                       nsIObserver* aObserver)
 {
   MOZ_RELEASE_ASSERT(nsContentUtils::IsCallerChrome());
 
   nsCOMPtr<nsIWidget> widget = GetWidget();
   if (!widget) {
     return NS_ERROR_FAILURE;
   }
 
   if (aPressure < 0 || aPressure > 1 || aOrientation > 359) {
     return NS_ERROR_INVALID_ARG;
   }
 
-  return widget->SynthesizeNativeTouchPoint(aPointerId,
-                                            (nsIWidget::TouchPointerState)aTouchState,
-                                            nsIntPoint(aScreenX, aScreenY),
-                                            aPressure, aOrientation);
+  NS_DispatchToMainThread(NS_NewRunnableMethodWithArgs
+    <uint32_t, nsIWidget::TouchPointerState, nsIntPoint, double, uint32_t, nsIObserver*>
+    (widget, &nsIWidget::SynthesizeNativeTouchPoint, aPointerId,
+    (nsIWidget::TouchPointerState)aTouchState, nsIntPoint(aScreenX, aScreenY),
+    aPressure, aOrientation, aObserver));
+  return NS_OK;
 }
 
 NS_IMETHODIMP
 nsDOMWindowUtils::SendNativeTouchTap(int32_t aScreenX,
                                      int32_t aScreenY,
-                                     bool aLongTap)
+                                     bool aLongTap,
+                                     nsIObserver* aObserver)
 {
   MOZ_RELEASE_ASSERT(nsContentUtils::IsCallerChrome());
 
   nsCOMPtr<nsIWidget> widget = GetWidget();
   if (!widget) {
     return NS_ERROR_FAILURE;
   }
-  return widget->SynthesizeNativeTouchTap(nsIntPoint(aScreenX, aScreenY), aLongTap);
+
+  NS_DispatchToMainThread(NS_NewRunnableMethodWithArgs
+    <nsIntPoint, bool, nsIObserver*>
+    (widget, &nsIWidget::SynthesizeNativeTouchTap,
+    nsIntPoint(aScreenX, aScreenY), aLongTap, aObserver));
+  return NS_OK;
 }
 
 NS_IMETHODIMP
-nsDOMWindowUtils::ClearNativeTouchSequence()
+nsDOMWindowUtils::ClearNativeTouchSequence(nsIObserver* aObserver)
 {
   MOZ_RELEASE_ASSERT(nsContentUtils::IsCallerChrome());
 
   nsCOMPtr<nsIWidget> widget = GetWidget();
   if (!widget) {
     return NS_ERROR_FAILURE;
   }
-  return widget->ClearNativeTouchSequence();
+
+  NS_DispatchToMainThread(NS_NewRunnableMethodWithArgs<nsIObserver*>
+    (widget, &nsIWidget::ClearNativeTouchSequence, aObserver));
+  return NS_OK;
 }
 
 NS_IMETHODIMP
 nsDOMWindowUtils::ActivateNativeMenuItemAt(const nsAString& indexString)
 {
   MOZ_RELEASE_ASSERT(nsContentUtils::IsCallerChrome());
 
   // get the widget to send the event to
--- a/dom/base/nsDocument.cpp
+++ b/dom/base/nsDocument.cpp
@@ -12004,17 +12004,23 @@ mozilla::StaticRefPtr<nsPointerLockPermi
 
 class nsPointerLockPermissionRequest : public nsRunnable,
                                        public nsIContentPermissionRequest
 {
 public:
   nsPointerLockPermissionRequest(Element* aElement, bool aUserInputOrChromeCaller)
   : mElement(do_GetWeakReference(aElement)),
     mDocument(do_GetWeakReference(aElement->OwnerDoc())),
-    mUserInputOrChromeCaller(aUserInputOrChromeCaller) {}
+    mUserInputOrChromeCaller(aUserInputOrChromeCaller)
+  {
+    nsCOMPtr<nsIDocument> doc = do_QueryReferent(mDocument);
+    if (doc) {
+      mRequester = new nsContentPermissionRequester(doc->GetInnerWindow());
+    }
+  }
 
   NS_DECL_ISUPPORTS_INHERITED
   NS_DECL_NSICONTENTPERMISSIONREQUEST
 
   NS_IMETHOD Run() override
   {
     nsCOMPtr<Element> e = do_QueryReferent(mElement);
     nsCOMPtr<nsIDocument> d = do_QueryReferent(mDocument);
@@ -12064,16 +12070,17 @@ public:
   }
 
   nsWeakPtr mElement;
   nsWeakPtr mDocument;
   bool mUserInputOrChromeCaller;
 
 protected:
   virtual ~nsPointerLockPermissionRequest() {}
+  nsCOMPtr<nsIContentPermissionRequester> mRequester;
 };
 
 NS_IMPL_ISUPPORTS_INHERITED(nsPointerLockPermissionRequest,
                             nsRunnable,
                             nsIContentPermissionRequest)
 
 NS_IMETHODIMP
 nsPointerLockPermissionRequest::GetTypes(nsIArray** aTypes)
@@ -12169,16 +12176,26 @@ nsPointerLockPermissionRequest::Allow(JS
   NS_ASSERTION(EventStateManager::sPointerLockedElement &&
                EventStateManager::sPointerLockedDoc,
                "aElement and this should support weak references!");
 
   DispatchPointerLockChange(d);
   return NS_OK;
 }
 
+NS_IMETHODIMP
+nsPointerLockPermissionRequest::GetRequester(nsIContentPermissionRequester** aRequester)
+{
+  NS_ENSURE_ARG_POINTER(aRequester);
+
+  nsCOMPtr<nsIContentPermissionRequester> requester = mRequester;
+  requester.forget(aRequester);
+  return NS_OK;
+}
+
 void
 nsDocument::SetApprovedForFullscreen(bool aIsApproved)
 {
   mIsApprovedForFullscreen = aIsApproved;
 }
 
 nsresult
 nsDocument::Observe(nsISupports *aSubject,
--- a/dom/base/test/csp/test_csp_report.html
+++ b/dom/base/test/csp/test_csp_report.html
@@ -55,17 +55,17 @@ window.checkResults = function(reportObj
   is(cspReport["original-policy"], "default-src 'none'; report-uri http://mochi.test:8888/foo.sjs",
      "Incorrect original-policy");
 
   is(cspReport["source-file"], docUri, "Incorrect source-file");
 
   is(cspReport["script-sample"], "\n    var foo = \"propEscFoo\";\n    var bar...",
      "Incorrect script-sample");
 
-  is(cspReport["line-number"], "7", "Incorrect line-number");
+  is(cspReport["line-number"], 7, "Incorrect line-number");
 }
 
 // This is used to watch requests go out so we can see if the report is
 // sent correctly
 function examiner() {
   SpecialPowers.addObserver(this, "http-on-opening-request", false);
 }
 examiner.prototype  = {
--- a/dom/base/test/file_bug426646-1.html
+++ b/dom/base/test/file_bug426646-1.html
@@ -20,17 +20,17 @@ function doe2() {
 }
 
 function doe3() {
   window.frames[0].frameElement.onload = soon(doe4);
   history.go(-1);
 }
 
 function doe4() {
-  opener.is(window.frames[0].location, url1, "History.go(-1) didn't work?");
-  opener.is(window.frames[1].location, "about:blank",
+  opener.is(String(window.frames[0].location), url1, "History.go(-1) didn't work?");
+  opener.is(String(window.frames[1].location), "about:blank",
             "History.go(-1) didn't work?");
   close();
 }
 </script>
 </head>
 <body onload="doe();" onunload="opener.nextTest();">
 </body></html>
--- a/dom/base/test/file_bug426646-2.html
+++ b/dom/base/test/file_bug426646-2.html
@@ -50,15 +50,15 @@ function doe3() {
 }
 
 function doe4() {
   win0.frameElement.onload = soon(doe5);
   history.go(-1);
 }
 
 function doe5() {
-  opener.is(win0.location, url1, "History.go(-1) didn't work?");
+  opener.is(String(win0.location), url1, "History.go(-1) didn't work?");
   close();
 }
 </script>
 </head>
 <body onload="setTimeout(doe, 0);" onunload="opener.nextTest();">
 </body></html>
--- a/dom/base/test/fileutils.js
+++ b/dom/base/test/fileutils.js
@@ -50,17 +50,17 @@ function testFile(file, contents, test) 
   // Send file to server using plain XMLHttpRequest
   var xhr = new XMLHttpRequest;
   xhr.open("POST", "file_XHRSendData.sjs");
   xhr.onload = function (event) {
     is(event.target.getResponseHeader("Result-Content-Type"),
        file.type ? file.type : null,
        "request content-type in XMLHttpRequest send of " + test);
     is(event.target.getResponseHeader("Result-Content-Length"),
-       file.size,
+       String(file.size),
        "request content-length in XMLHttpRequest send of " + test);
   };
   xhr.addEventListener("load",
                        getXHRLoadHandler(contents, contents.length,
                                          "XMLHttpRequest send of " + test),
                        false);
   xhr.overrideMimeType('text/plain; charset=x-user-defined');
   xhr.send(file);
--- a/dom/base/test/test_CrossSiteXHR.html
+++ b/dom/base/test/test_CrossSiteXHR.html
@@ -689,17 +689,17 @@ function runTest() {
            "wrong responseText in test for " + test.toSource());
       }
       if (test.responseHeaders) {
         for (header in test.responseHeaders) {
           if (test.expectedResponseHeaders.indexOf(header) == -1) {
             is(res.responseHeaders[header], null,
                "|xhr.getResponseHeader()|wrong response header (" + header + ") in test for " +
                test.toSource());
-	    is(res.allResponseHeaders[header], null,
+            is(res.allResponseHeaders[header], undefined,
               "|xhr.getAllResponseHeaderss()|wrong response header (" + header + ") in test for " +
               test.toSource());
           }
           else {
             is(res.responseHeaders[header], test.responseHeaders[header],
                "|xhr.getResponseHeader()|wrong response header (" + header + ") in test for " +
                test.toSource());
             is(res.allResponseHeaders[header], test.responseHeaders[header],
--- a/dom/base/test/test_XHRSendData.html
+++ b/dom/base/test/test_XHRSendData.html
@@ -227,17 +227,19 @@ try {
       is(xhr.getResponseHeader("Result-Content-Type"), test.resContentType,
          "Wrong Content-Type sent");
     }
     else {
       is(xhr.getResponseHeader("Result-Content-Type"), null);
     }
 
     if (test.resContentLength) {
-      is(xhr.getResponseHeader("Result-Content-Length"), test.resContentLength, "Wrong Content-Length sent");
+      is(xhr.getResponseHeader("Result-Content-Length"),
+         String(test.resContentLength),
+         "Wrong Content-Length sent");
     }
 
     if (test.resType == "arraybuffer") {
       is_identical_arraybuffer(xhr.response, test.resBody);
     }
     else if (test.body instanceof Document) {
       is(xhr.responseText.replace("\r\n", "\n"), test.resBody, "Wrong body");
     }
--- a/dom/base/test/test_audioWindowUtils.html
+++ b/dom/base/test/test_audioWindowUtils.html
@@ -18,20 +18,20 @@ function runTest() {
   is(utils.audioMuted, false, "By default utils.audioMuted is false");
   utils.audioMuted = true;
   is(utils.audioMuted, true, "utils.audioMuted is true");
   utils.audioMuted = false;
   is(utils.audioMuted, false, "utils.audioMuted is true");
 
   is(utils.audioVolume, 1.0, "By default utils.audioVolume is 1.0");
   utils.audioVolume = 0.4;
-  is(utils.audioVolume.toFixed(2), 0.4, "utils.audioVolume is ok");
+  is(utils.audioVolume.toFixed(2), "0.40", "utils.audioVolume is ok");
   utils.audioMuted = true;
   is(utils.audioMuted, true, "utils.audioMuted is true");
-  is(utils.audioVolume.toFixed(2), 0.4, "utils.audioVolume is ok");
+  is(utils.audioVolume.toFixed(2), "0.40", "utils.audioVolume is ok");
   utils.audioMuted = false;
 
   utils.audioVolume = 2.0;
   is(utils.audioVolume, 2.0, "utils.audioVolume is ok");
 
   try {
     utils.audioVolume = -42;
     ok(false, "This should throw");
@@ -55,20 +55,20 @@ function runTest() {
   is(utils.audioMuted, false, "By default utils.audioMuted is false");
   utils.audioMuted = true;
   is(utils.audioMuted, true, "utils.audioMuted is true");
   utils.audioMuted = false;
   is(utils.audioMuted, false, "utils.audioMuted is true");
 
   is(utils.audioVolume, 1.0, "By default utils.audioVolume is 1.0");
   utils.audioVolume = 0.4;
-  is(utils.audioVolume.toFixed(2), 0.4, "utils.audioVolume is ok");
+  is(utils.audioVolume.toFixed(2), "0.40", "utils.audioVolume is ok");
   utils.audioMuted = true;
   is(utils.audioMuted, true, "utils.audioMuted is true");
-  is(utils.audioVolume.toFixed(2), 0.4, "utils.audioVolume is ok");
+  is(utils.audioVolume.toFixed(2), "0.40", "utils.audioVolume is ok");
   utils.audioMuted = false;
 
   utils.audioVolume = 2.0;
   is(utils.audioVolume, 2.0, "utils.audioVolume is ok");
 
   try {
     utils.audioVolume = -42;
     ok(false, "This should throw");
--- a/dom/base/test/test_bug346485.html
+++ b/dom/base/test/test_bug346485.html
@@ -20,57 +20,58 @@ https://bugzilla.mozilla.org/show_bug.cg
 <script type="application/javascript">
 
 /** Test for Bug 346485 **/
 
 /**
  * This test is testing DOMSettableTokenList used by the output element.
  */
 
+function checkHtmlFor(htmlFor, list, msg) {
+  var length = htmlFor.length;
+  is(length, list.length, htmlFor + ": incorrect htmlFor length (" + msg + ")");
+  for (var i = 0; i < length; ++i) {
+    is(htmlFor[i], list[i], htmlFor + ": wrong element at " + i + " (" + msg + ")");
+  }
+}
+
 var o = document.getElementById('o');
 
-is(o.htmlFor, 'a b',
+is(String(o.htmlFor), 'a b',
   "htmlFor IDL attribute should reflect for content attribute");
 
 is(o.htmlFor.value, 'a b',
   "value should return the underlying string");
 
 is(o.htmlFor.length, 2, "Size should be '2'");
 
 ok(o.htmlFor.contains('a'), "It should contain 'a' token'");
 ok(!o.htmlFor.contains('c'), "It should not contain 'c' token");
 
 is(o.htmlFor.item(0), 'a', "First item is 'a' token'");
 is(o.htmlFor.item(42), null, "Out-of-range should return null");
 
 o.htmlFor.add('c');
-is(o.htmlFor, 'a b c', "'c' token should have been added");
-is(o.htmlFor.length, 3, "Size should be '3'");
+checkHtmlFor(o.htmlFor, ['a', 'b', 'c'], "'c' token should have been added");
 
 o.htmlFor.add('a');
-is(o.htmlFor, 'a b c', "Nothing should have changed");
-is(o.htmlFor.length, 3, "Size should be '3'");
+checkHtmlFor(o.htmlFor, ['a', 'b', 'c'], "Nothing should have changed");
 
 o.htmlFor.remove('a');
-is(o.htmlFor, 'b c', "'a' token should have been removed");
-is(o.htmlFor.length, 2, "Size should be '2'");
+checkHtmlFor(o.htmlFor, ['b', 'c'], "'a' token should have been removed");
 
 o.htmlFor.remove('d');
-is(o.htmlFor, 'b c', "Nothing should have been removed");
-is(o.htmlFor.length, 2, "Size should be '2'");
+checkHtmlFor(o.htmlFor, ['b', 'c'], "Nothing should have been removed");
 
 o.htmlFor.toggle('a');
-is(o.htmlFor, 'b c a', "'a' token should have been added");
-is(o.htmlFor.length, 3, "Size should be '3'");
+checkHtmlFor(o.htmlFor, ['b', 'c', 'a'], "'a' token should have been added");
 
 o.htmlFor.toggle('b');
-is(o.htmlFor, 'c a', "Nothing should have changed");
-is(o.htmlFor.length, 2, "Size should be '2'");
+checkHtmlFor(o.htmlFor, ['c', 'a'], "Nothing should have changed");
 
 o.htmlFor.value = "foo bar";
-is(o.htmlFor, 'foo bar', "The underlying string should have changed");
-is(o.htmlFor.length, 2, "Size should be '2'");
+checkHtmlFor(o.htmlFor, ['foo', 'bar'], "The underlying string should have changed");
 ok(o.htmlFor.contains('foo'), "It should contain 'foo'");
 
 </script>
 </pre>
 </body>
 </html>
--- a/dom/base/test/test_bug461555.html
+++ b/dom/base/test/test_bug461555.html
@@ -21,17 +21,17 @@ SimpleTest.waitForExplicitFinish();
 function writeIt(n) {
   document.write("<span>" + n + "</span>");
 }
 
 function done() {
   nodes = document.getElementsByTagName('span');
   is(nodes.length, 3, "wrong length");
   for (i = 0; i < nodes.length; ++i) {
-    is(nodes[i].textContent, i+1, "wrong order");
+    is(nodes[i].textContent, String(i + 1), "wrong order");
   }
   SimpleTest.finish();
 }
 
 document.addEventListener("DOMContentLoaded", function() {
   done();
 }, false);
 
--- a/dom/base/test/test_bug622088.html
+++ b/dom/base/test/test_bug622088.html
@@ -25,58 +25,58 @@ const kOriginalLocation = location.href;
 
 SimpleTest.waitForExplicitFinish();
 
 var innerFinishedLoading = false;
 function innerLoaded(inner) {
   // Here, we're being called through inner's onload handler, so our referrer
   // should be inner's URL.
   var referrer = inner.doXHR();
-  is (referrer, inner.document.location, 'Expected inner frame location');
+  is (referrer, String(inner.document.location), 'Expected inner frame location');
 
   // Now change the location of the inner frame.  This should be reflected in
   // the XHR's referrer.
   inner.history.pushState('', '', Math.random());
   referrer = inner.doXHR();
-  is (referrer, inner.document.location, 'Expected inner frame location after pushstate');
+  is (referrer, String(inner.document.location), 'Expected inner frame location after pushstate');
 
   innerFinishedLoading = true;
 }
 
 var dataWindowFinishedLoading = false;
 function dataWindowLoaded() {
   dataWindowFinishedLoading = true;
 }
 
 function callXHR() {
   if (innerFinishedLoading && dataWindowFinishedLoading) {
     var inner = document.getElementById('iframe').contentWindow;
     var referrer = inner.doXHR();
-    is (referrer, inner.document.location,
+    is (referrer, String(inner.document.location),
         'Expected inner frame location when called from outer frame.');
 
     var referrer = inner.doXHR(new XMLHttpRequest());
-    is (referrer, document.location,
+    is (referrer, String(document.location),
         "Expected outer frame location when called with outer's XHR object.");
 
     // Now do a request within the inner window using an XMLHttpRequest
     // retrieved from a data: URI.  The referrer should be this window, not the
     // data: URI.
     var dataWindow = document.getElementById('dataWindow').contentWindow;
     var referrer = inner.doXHR(dataWindow.getXHRObject());
-    is (referrer, document.location,
+    is (referrer, String(document.location),
         "Expected outer frame location when called with data's XHR object.");
 
     // Now do that test again, but after having changed the outer window's URI.
     // This currently fails, due to basically bug 631949.  It's not even clear
     // what the right behavior is.  So marking as a todo for now.
     history.replaceState('', '', Math.random());
 
     var referrer = inner.doXHR(dataWindow.getXHRObject());
-    todo_is (referrer, document.location,
+    todo_is (referrer, String(document.location),
              "Expected outer frame location when called with data's XHR object " +
              "after replaceState.");
 
     // In case you're temped, you probably don't want to do history.pushState
     // here and test again with the outer frame.  Calling pushState on the
     // outer frame messes up Mochitest in subtle ways.
 
     history.replaceState('', '', kOriginalLocation);
--- a/dom/base/test/test_domrequesthelper.xul
+++ b/dom/base/test/test_domrequesthelper.xul
@@ -38,35 +38,35 @@
     };
 
     var dummy = new DummyHelperSubclass();
 
     /**
      * Init & destroy.
      */
     function initDOMRequestHelperTest(aMessages) {
-      is(dummy._requests, undefined, "Request is undefined");
+      is_loosely(dummy._requests, undefined, "Request is undefined");
       is(dummy._messages, undefined, "Messages is undefined");
-      is(dummy._window, undefined, "Window is undefined");
+      is_loosely(dummy._window, undefined, "Window is undefined");
 
       dummy.initDOMRequestHelper(window, aMessages);
 
       ok(dummy._window, "Window exists");
       is(dummy._window, window, "Correct window");
       if (aMessages) {
         is(typeof dummy._listeners, "object", "Listeners is an object");
       }
     }
 
     function destroyDOMRequestHelperTest() {
       dummy.destroyDOMRequestHelper();
 
-      is(dummy._requests, undefined, "Request is undefined");
+      is_loosely(dummy._requests, undefined, "Request is undefined");
       is(dummy._messages, undefined, "Messages is undefined");
-      is(dummy._window, undefined, "Window is undefined");
+      is_loosely(dummy._window, undefined, "Window is undefined");
     }
 
     /**
      * Message listeners.
      */
     function checkMessageListeners(aExpectedListeners, aCount) {
       info("Checking message listeners\n" + "Expected listeners " +
            JSON.stringify(aExpectedListeners) + " \nExpected count " + aCount);
@@ -383,17 +383,17 @@
         info("== Test getRequestId(), removeRequest() and getRequest()");
         var req = dummy.createRequest();
         var id = dummy.getRequestId(req);
         is(typeof id, "string", "id is a string");
         var req_ = dummy.getRequest(id);
         is(req, req_, "Got correct request");
         dummy.removeRequest(id);
         req = dummy.getRequest(id);
-        is(req, null, "No request");
+        is(req, undefined, "No request");
         next();
       },
       function() {
         info("== Test createPromise()");
         ok(Promise, "Promise object exists");
         var promise = dummy.createPromise(function(resolve, reject) {
           resolve(true);
         });
--- a/dom/base/test/test_url.html
+++ b/dom/base/test/test_url.html
@@ -306,17 +306,17 @@
 
     url = new URL("http://localhost/");
     url.hostname = "[::]";
     is(url.hostname, "[::]", "IPv6 hostname");
 
     url = new URL("http://localhost/");
     url.host = "[2001::1]:30";
     is(url.hostname, "[2001::1]", "IPv6 hostname");
-    is(url.port, 30, "Port");
+    is(url.port, "30", "Port");
     is(url.host, "[2001::1]:30", "IPv6 host");
 
     url = new URL("http://localhost/");
     // This should silently fail since it's missing the brackets
     url.hostname = "2001::1";
     is(url.hostname, "localhost", "Setting bad hostname fails");
   </script>
 
--- a/dom/base/test/test_url_empty_port.html
+++ b/dom/base/test/test_url_empty_port.html
@@ -19,33 +19,33 @@ https://bugzilla.mozilla.org/show_bug.cg
 </div>
 <pre id="test">
 </pre>
   <a id="link" href="http://www.example.com:8080">foobar</a>
   <area id="area" href="http://www.example.com:8080" />
   <script type="application/javascript">
 
   var url = new URL('http://www.example.com:8080');
-  is(url.port, 8080, 'URL.port is 8080');
+  is(url.port, '8080', 'URL.port is 8080');
   url.port = '';
   ise(url.port, '', 'URL.port is \'\'');
   url.port = 0;
   ise(url.port, '0', 'URL.port is 0');
 
   var link = document.getElementById("link");
-  is(link.port, 8080, 'URL.port is 8080');
+  is(link.port, '8080', 'URL.port is 8080');
   link.port = '';
   is(link.href, 'http://www.example.com/', "link.href matches");
   ise(link.port, '', 'URL.port is \'\'');
   link.port = 0;
   is(link.href, 'http://www.example.com:0/', "link.href matches");
   ise(link.port, '0', 'URL.port is 0');
 
   var area = document.getElementById("area");
-  is(area.port, 8080, 'URL.port is 8080');
+  is(area.port, '8080', 'URL.port is 8080');
   area.port = '';
   is(area.href, 'http://www.example.com/', "area.href matches");
   ise(area.port, '', 'URL.port is \'\'');
   area.port = 0;
   is(area.href, 'http://www.example.com:0/', "area.href matches");
   ise(area.port, '0', 'URL.port is 0');
 
   </script>
--- a/dom/bindings/Codegen.py
+++ b/dom/bindings/Codegen.py
@@ -6657,16 +6657,32 @@ class CGPerSignatureCall(CGThing):
                     """
                     if (!mozilla::dom::EnforceNotInPrerendering(cx, obj)) {
                         // Return false from the JSNative in order to trigger
                         // an uncatchable exception.
                         MOZ_ASSERT(!JS_IsExceptionPending(cx));
                         return false;
                     }
                     """)))
+
+        if idlNode.getExtendedAttribute("Deprecated"):
+            cgThings.append(CGGeneric(dedent(
+                """
+                {
+                  GlobalObject global(cx, obj);
+                  if (global.Failed()) {
+                    return false;
+                  }
+                  nsCOMPtr<nsPIDOMWindow> pWindow = do_QueryInterface(global.GetAsSupports());
+                  if (pWindow && pWindow->GetExtantDoc()) {
+                    pWindow->GetExtantDoc()->WarnOnceAbout(nsIDocument::e%s);
+                  }
+                }
+                """ % idlNode.getExtendedAttribute("Deprecated")[0])))
+
         lenientFloatCode = None
         if idlNode.getExtendedAttribute('LenientFloat') is not None:
             if setter:
                 lenientFloatCode = "return true;\n"
             elif idlNode.isMethod():
                 lenientFloatCode = ("args.rval().setUndefined();\n"
                                     "return true;\n")
 
@@ -12400,16 +12416,22 @@ class CGBindingRoot(CGThing):
         bindingDeclareHeaders["jsapi.h"] = any(descriptorHasCrossOriginProperties(d) for d in descriptors)
         bindingDeclareHeaders["jspubtd.h"] = not bindingDeclareHeaders["jsapi.h"]
         bindingDeclareHeaders["js/RootingAPI.h"] = not bindingDeclareHeaders["jsapi.h"]
 
         def descriptorRequiresPreferences(desc):
             iface = desc.interface
             return any(m.getExtendedAttribute("Pref") for m in iface.members + [iface])
 
+        def descriptorDeprecated(desc):
+            iface = desc.interface
+            return any(m.getExtendedAttribute("Deprecated") for m in iface.members + [iface])
+
+        bindingHeaders["nsIDocument.h"] = any(
+            descriptorDeprecated(d) for d in descriptors)
         bindingHeaders["mozilla/Preferences.h"] = any(
             descriptorRequiresPreferences(d) for d in descriptors)
         bindingHeaders["mozilla/dom/DOMJSProxyHandler.h"] = any(
             d.concrete and d.proxy for d in descriptors)
 
         def descriptorHasChromeOnly(desc):
             return (any(isChromeOnly(a) for a in desc.interface.members) or
                     desc.interface.getExtendedAttribute("ChromeOnly") is not None or
--- a/dom/bindings/parser/WebIDL.py
+++ b/dom/bindings/parser/WebIDL.py
@@ -3499,16 +3499,17 @@ class IDLAttribute(IDLInterfaceMember):
                                   [attr.location])
             if (attr.value() != "Everything" and attr.value() != "DOMState" and
                 not self.readonly):
                 raise WebIDLError("[DependsOn=%s] only allowed on "
                                   "readonly attributes" % attr.value(),
                                   [attr.location, self.location])
             self._setDependsOn(attr.value())
         elif (identifier == "Pref" or
+              identifier == "Deprecated" or
               identifier == "SetterThrows" or
               identifier == "Throws" or
               identifier == "GetterThrows" or
               identifier == "ChromeOnly" or
               identifier == "Func" or
               identifier == "Frozen" or
               identifier == "AvailableIn" or
               identifier == "NewObject" or
@@ -4134,16 +4135,17 @@ class IDLMethod(IDLInterfaceMember, IDLS
                 raise WebIDLError("[Alias] takes an identifier or string",
                                   [attr.location])
             self._addAlias(attr.value())
         elif (identifier == "Throws" or
               identifier == "NewObject" or
               identifier == "ChromeOnly" or
               identifier == "UnsafeInPrerendering" or
               identifier == "Pref" or
+              identifier == "Deprecated" or
               identifier == "Func" or
               identifier == "AvailableIn" or
               identifier == "CheckPermissions" or
               identifier == "BinaryName"):
             # Known attributes that we don't need to do anything with here
             pass
         else:
             raise WebIDLError("Unknown extended attribute %s on method" % identifier,
--- a/dom/bindings/test/TestBindingHeader.h
+++ b/dom/bindings/test/TestBindingHeader.h
@@ -732,22 +732,34 @@ public:
   void ReceiveDictContainingSequence(JSContext*, DictContainingSequence&);
   void PassVariadicDictionary(JSContext*, const Sequence<Dict>&);
 
   // Typedefs
   void ExerciseTypedefInterfaces1(TestInterface&);
   already_AddRefed<TestInterface> ExerciseTypedefInterfaces2(TestInterface*);
   void ExerciseTypedefInterfaces3(TestInterface&);
 
+  // Deprecated methods and attributes
+  int8_t DeprecatedAttribute();
+  int8_t SetDeprecatedAttribute(int8_t);
+  int8_t DeprecatedMethod();
+  int8_t DeprecatedMethodWithContext(JSContext*, JS::Value);
+
   // Static methods and attributes
   static void StaticMethod(const GlobalObject&, bool);
   static void StaticMethodWithContext(const GlobalObject&, JS::Value);
   static bool StaticAttribute(const GlobalObject&);
   static void SetStaticAttribute(const GlobalObject&, bool);
 
+  // Deprecated static methods and attributes
+  static int8_t StaticDeprecatedAttribute(const GlobalObject&);
+  static int8_t SetStaticDeprecatedAttribute(const GlobalObject&, int8_t);
+  static int8_t StaticDeprecatedMethod(const GlobalObject&);
+  static int8_t StaticDeprecatedMethodWithContext(const GlobalObject&, JS::Value);
+
   // Overload resolution tests
   bool Overload1(TestInterface&);
   TestInterface* Overload1(const nsAString&, TestInterface&);
   void Overload2(TestInterface&);
   void Overload2(JSContext*, const Dict&);
   void Overload2(bool);
   void Overload2(const nsAString&);
   void Overload2(Date);
--- a/dom/bindings/test/TestCodeGen.webidl
+++ b/dom/bindings/test/TestCodeGen.webidl
@@ -720,21 +720,37 @@ interface TestInterface {
   [Clamp] attribute byte clampedByte;
 
   // Typedefs
   const myLong myLongConstant = 5;
   void exerciseTypedefInterfaces1(AnotherNameForTestInterface arg);
   AnotherNameForTestInterface exerciseTypedefInterfaces2(NullableTestInterface arg);
   void exerciseTypedefInterfaces3(YetAnotherNameForTestInterface arg);
 
+  // Deprecated methods and attributes
+  [Deprecated="GetAttributeNode"]
+  attribute byte deprecatedAttribute;
+  [Deprecated="GetAttributeNode"]
+  byte deprecatedMethod();
+  [Deprecated="GetAttributeNode"]
+  byte deprecatedMethodWithContext(any arg);
+
   // Static methods and attributes
   static attribute boolean staticAttribute;
   static void staticMethod(boolean arg);
   static void staticMethodWithContext(any arg);
 
+  // Deprecated static methods and attributes
+  [Deprecated="GetAttributeNode"]
+  static attribute byte staticDeprecatedAttribute;
+  [Deprecated="GetAttributeNode"]
+  static void staticDeprecatedMethod();
+  [Deprecated="GetAttributeNode"]
+  static void staticDeprecatedMethodWithContext(any arg);
+
   // Overload resolution tests
   //void overload1(DOMString... strs);
   boolean overload1(TestInterface arg);
   TestInterface overload1(DOMString strs, TestInterface arg);
   void overload2(TestInterface arg);
   void overload2(optional Dict arg);
   void overload2(boolean arg);
   void overload2(DOMString arg);
--- a/dom/bindings/test/TestExampleGen.webidl
+++ b/dom/bindings/test/TestExampleGen.webidl
@@ -584,21 +584,37 @@ interface TestExampleInterface {
   [Clamp] attribute byte clampedByte;
 
   // Typedefs
   const myLong myLongConstant = 5;
   void exerciseTypedefInterfaces1(AnotherNameForTestInterface arg);
   AnotherNameForTestInterface exerciseTypedefInterfaces2(NullableTestInterface arg);
   void exerciseTypedefInterfaces3(YetAnotherNameForTestInterface arg);
 
+  // Deprecated methods and attributes
+  [Deprecated="GetAttributeNode"]
+  attribute boolean deprecatedAttribute;
+  [Deprecated="GetAttributeNode"]
+  void deprecatedMethod(boolean arg);
+  [Deprecated="GetAttributeNode"]
+  void deprecatedMethodWithContext(any arg);
+
   // Static methods and attributes
   static attribute boolean staticAttribute;
   static void staticMethod(boolean arg);
   static void staticMethodWithContext(any arg);
 
+  // Deprecated methods and attributes;
+  [Deprecated="GetAttributeNode"]
+  static attribute boolean staticDeprecatedAttribute;
+  [Deprecated="GetAttributeNode"]
+  static void staticDeprecatedMethod(boolean arg);
+  [Deprecated="GetAttributeNode"]
+  static void staticDeprecatedMethodWithContext(any arg);
+
   // Overload resolution tests
   //void overload1(DOMString... strs);
   boolean overload1(TestInterface arg);
   TestInterface overload1(DOMString strs, TestInterface arg);
   void overload2(TestInterface arg);
   void overload2(optional Dict arg);
   void overload2(boolean arg);
   void overload2(DOMString arg);
--- a/dom/bindings/test/TestJSImplGen.webidl
+++ b/dom/bindings/test/TestJSImplGen.webidl
@@ -597,22 +597,38 @@ interface TestJSImplInterface {
   [Clamp] attribute byte clampedByte;
 
   // Typedefs
   const myLong myLongConstant = 5;
   void exerciseTypedefInterfaces1(AnotherNameForTestJSImplInterface arg);
   AnotherNameForTestJSImplInterface exerciseTypedefInterfaces2(NullableTestJSImplInterface arg);
   void exerciseTypedefInterfaces3(YetAnotherNameForTestJSImplInterface arg);
 
+  // Deprecated methods and attributes
+  [Deprecated="GetAttributeNode"]
+  attribute byte deprecatedAttribute;
+  [Deprecated="GetAttributeNode"]
+  byte deprecatedMethod();
+  [Deprecated="GetAttributeNode"]
+  void deprecatedMethodWithContext(any arg);
+
   // Static methods and attributes
   // FIXME: Bug 863952 Static things are not supported yet
   /*
   static attribute boolean staticAttribute;
   static void staticMethod(boolean arg);
   static void staticMethodWithContext(any arg);
+
+  // Deprecated static methods and attributes
+  [Deprecated="GetAttributeNode"]
+  static attribute byte staticDeprecatedAttribute;
+  [Deprecated="GetAttributeNode"]
+  static byte staticDeprecatedMethod();
+  [Deprecated="GetAttributeNode"]
+  static byte staticDeprecatedMethodWithContext();
   */
 
   // Overload resolution tests
   //void overload1(DOMString... strs);
   boolean overload1(TestJSImplInterface arg);
   TestJSImplInterface overload1(DOMString strs, TestJSImplInterface arg);
   void overload2(TestJSImplInterface arg);
   void overload2(optional Dict arg);
--- a/dom/browser-element/mochitest/browserElement_PromptCheck.js
+++ b/dom/browser-element/mochitest/browserElement_PromptCheck.js
@@ -17,17 +17,17 @@ browserElementTestHelpers.addPermission(
 function runTest()
 {
   var iframe = document.createElement('iframe');
   iframe.setAttribute('mozbrowser', 'true');
   document.body.appendChild(iframe);
 
   var numPrompts = 0;
   iframe.addEventListener('mozbrowsershowmodalprompt', function(e) {
-    is(e.detail.message, numPrompts, "prompt message");
+    is(e.detail.message, String(numPrompts), "prompt message");
     if (numPrompts / 10 < 1) {
       is(e.detail.promptType, 'alert');
     }
     else if (numPrompts / 10 < 2) {
       is(e.detail.promptType, 'confirm');
     }
     else {
       is(e.detail.promptType, 'prompt');
--- a/dom/browser-element/mochitest/browserElement_PromptConfirm.js
+++ b/dom/browser-element/mochitest/browserElement_PromptConfirm.js
@@ -13,25 +13,25 @@ browserElementTestHelpers.setEnabledPref
 browserElementTestHelpers.addPermission();
 
 function runTest() {
   var iframe = document.createElement('iframe');
   iframe.setAttribute('mozbrowser', 'true');
   document.body.appendChild(iframe);
 
   var prompts = [
-    {msg: 1, type: 'alert', rv: 42, expected: 'undefined'},
-    {msg: 2, type: 'confirm', rv: true, expected: 'true'},
-    {msg: 3, type: 'confirm', rv: false, expected: 'false'},
+    {msg: '1', type: 'alert', rv: 42, expected: 'undefined'},
+    {msg: '2', type: 'confirm', rv: true, expected: 'true'},
+    {msg: '3', type: 'confirm', rv: false, expected: 'false'},
 
     // rv == 42 should be coerced to 'true' for confirm.
-    {msg: 4, type: 'confirm', rv: 42, expected: 'true'},
-    {msg: 5, type: 'prompt', rv: 'worked', expected: 'worked'},
-    {msg: 6, type: 'prompt', rv: null, expected: 'null'},
-    {msg: 7, type: 'prompt', rv: '', expected: ''}
+    {msg: '4', type: 'confirm', rv: 42, expected: 'true'},
+    {msg: '5', type: 'prompt', rv: 'worked', expected: 'worked'},
+    {msg: '6', type: 'prompt', rv: null, expected: 'null'},
+    {msg: '7', type: 'prompt', rv: '', expected: ''}
   ];
 
   iframe.addEventListener("mozbrowsershowmodalprompt", function(e) {
     var curPrompt = prompts[0];
     if (!curPrompt.waitingForResponse) {
       curPrompt.waitingForResponse = true;
 
       is(e.detail.message, curPrompt.msg, "prompt message");
--- a/dom/browser-element/mochitest/browserElement_ReloadPostRequest.js
+++ b/dom/browser-element/mochitest/browserElement_ReloadPostRequest.js
@@ -95,17 +95,17 @@ function runTest() {
       is(e.detail.returnValue.checked, undefined);
       is(e.detail.buttons[0].messageType, 'custom');
       is(e.detail.buttons[0].message, expectedMessage.resend);
       is(e.detail.buttons[1].messageType, 'builtin');
       is(e.detail.buttons[1].message, 'cancel');
       is(e.detail.message, expectedMessage.message);
       is(e.detail.buttons.length, 2);
       is(e.detail.showCheckbox, false);
-      is(e.detail.checkMessage, null);
+      is(e.detail.checkboxMessage, null);
       e.detail.unblock();
 
       if (!doRepost) {
         // To make sure the page doesn't reload in 1 sec.
         timer = window.setTimeout(function() {
           iframe.removeEventListener('mozbrowserloadend', failBecauseReloaded);
           SimpleTest.finish();
         }, 1000);
--- a/dom/browser-element/mochitest/priority/test_ForegroundLRU.html
+++ b/dom/browser-element/mochitest/priority/test_ForegroundLRU.html
@@ -47,26 +47,26 @@ function runTest() {
     document.body.appendChild(iframe2);
 
     return p;
   }).then(function() {
     // Then wait for the third one and for the first one's LRU value to be
     // increased by one.
     var p = Promise.all([
       expectProcessCreated('FOREGROUND'),
-      expectPriorityWithLRUSet(childID, 'FOREGROUND', 1)
+      expectPriorityWithLRUSet(childID, 'FOREGROUND', '1')
     ]);
 
     document.body.appendChild(iframe2);
 
     return p;
   }).then(function() {
     // Now hide the second and third processes, this will send them into the
     // background and make the first process LRU value to be decreased.
-    var p = expectPriorityWithLRUSet(childID, 'FOREGROUND', 0)
+    var p = expectPriorityWithLRUSet(childID, 'FOREGROUND', '0')
 
     iframe2.setVisible(false);
 
     return p;
   }).then(function() {
     SimpleTest.finish();
   });
 
--- a/dom/cache/QuotaClient.cpp
+++ b/dom/cache/QuotaClient.cpp
@@ -193,28 +193,16 @@ public:
 
   virtual void
   ReleaseIOThreadObjects() override
   {
     // Nothing to do here as the Context handles cleaning everything up
     // automatically.
   }
 
-  virtual bool
-  IsFileServiceUtilized() override
-  {
-    return false;
-  }
-
-  virtual bool
-  IsTransactionServiceActivated() override
-  {
-    return true;
-  }
-
   virtual void
   WaitForStoragesToComplete(nsTArray<nsIOfflineStorage*>& aStorages,
                             nsIRunnable* aCallback) override
   {
     MOZ_ASSERT(NS_IsMainThread());
     MOZ_ASSERT(!aStorages.IsEmpty());
 
     nsCOMPtr<nsIRunnable> callback =
@@ -226,17 +214,17 @@ public:
       nsRefPtr<OfflineStorage> storage =
         static_cast<OfflineStorage*>(aStorages[i]);
       storage->AddDestroyCallback(callback);
     }
   }
 
 
   virtual void
-  ShutdownTransactionService() override
+  ShutdownWorkThreads() override
   {
     MOZ_ASSERT(NS_IsMainThread());
 
     // spins the event loop and synchronously shuts down all Managers
     Manager::ShutdownAllOnMainThread();
   }
 
 private:
--- a/dom/camera/DOMCameraManager.cpp
+++ b/dom/camera/DOMCameraManager.cpp
@@ -146,30 +146,32 @@ public:
                           const CameraConfiguration& aInitialConfig,
                           nsRefPtr<Promise> aPromise)
     : mPrincipal(aPrincipal)
     , mWindow(aWindow)
     , mCameraManager(aManager)
     , mCameraId(aCameraId)
     , mInitialConfig(aInitialConfig)
     , mPromise(aPromise)
+    , mRequester(new nsContentPermissionRequester(mWindow))
   { }
 
 protected:
   virtual ~CameraPermissionRequest() { }
 
   nsresult DispatchCallback(uint32_t aPermission);
   void CallAllow();
   void CallCancel();
   nsCOMPtr<nsIPrincipal> mPrincipal;
   nsCOMPtr<nsPIDOMWindow> mWindow;
   nsRefPtr<nsDOMCameraManager> mCameraManager;
   uint32_t mCameraId;
   CameraConfiguration mInitialConfig;
   nsRefPtr<Promise> mPromise;
+  nsCOMPtr<nsIContentPermissionRequester> mRequester;
 };
 
 NS_IMPL_CYCLE_COLLECTION(CameraPermissionRequest, mWindow, mPromise)
 
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(CameraPermissionRequest)
   NS_INTERFACE_MAP_ENTRY(nsIContentPermissionRequest)
   NS_INTERFACE_MAP_ENTRY(nsIRunnable)
   NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIContentPermissionRequest)
@@ -213,16 +215,26 @@ CameraPermissionRequest::Cancel()
 
 NS_IMETHODIMP
 CameraPermissionRequest::Allow(JS::HandleValue aChoices)
 {
   MOZ_ASSERT(aChoices.isUndefined());
   return DispatchCallback(nsIPermissionManager::ALLOW_ACTION);
 }
 
+NS_IMETHODIMP
+CameraPermissionRequest::GetRequester(nsIContentPermissionRequester** aRequester)
+{
+  NS_ENSURE_ARG_POINTER(aRequester);
+
+  nsCOMPtr<nsIContentPermissionRequester> requester = mRequester;
+  requester.forget(aRequester);
+  return NS_OK;
+}
+
 nsresult
 CameraPermissionRequest::DispatchCallback(uint32_t aPermission)
 {
   nsCOMPtr<nsIRunnable> callbackRunnable;
   if (aPermission == nsIPermissionManager::ALLOW_ACTION) {
     callbackRunnable = NS_NewRunnableMethod(this, &CameraPermissionRequest::CallAllow);
   } else {
     callbackRunnable = NS_NewRunnableMethod(this, &CameraPermissionRequest::CallCancel);
--- a/dom/contacts/tests/test_contacts_basics.html
+++ b/dom/contacts/tests/test_contacts_basics.html
@@ -145,25 +145,25 @@ var steps = [
     req.onsuccess = function () {
       is(req.result.length, 1, "Found exactly 1 contact.");
       findResult1 = req.result[0];
       ok(findResult1.id == sample_id1, "Same ID");
       checkContacts(createResult1, properties1);
       // Some manual testing. Testint the testfunctions
       // tel: [{type: ["work"], value: "123456", carrier: "testCarrier"} , {type: ["home", "fax"], value: "+55 (31) 9876-3456"}],
       is(findResult1.tel[0].carrier, "testCarrier", "Same Carrier");
-      is(findResult1.tel[0].type, "work", "Same type");
+      is(String(findResult1.tel[0].type), "work", "Same type");
       is(findResult1.tel[0].value, "123456", "Same Value");
       is(findResult1.tel[1].type[1], "fax", "Same type");
       is(findResult1.tel[1].value, "+55 (31) 9876-3456", "Same Value");
 
       is(findResult1.adr[0].countryName, "country 1", "Same country");
 
       // email: [{type: ["work"], value: "x@y.com"}]
-      is(findResult1.email[0].type, "work", "Same Type");
+      is(String(findResult1.email[0].type), "work", "Same Type");
       is(findResult1.email[0].value, "x@y.com", "Same Value");
       next();
     };
     req.onerror = onFailure;
   },
   function () {
     ok(true, "Searching for exact email");
     var options = {filterBy: ["email"],
--- a/dom/contacts/tests/test_contacts_basics2.html
+++ b/dom/contacts/tests/test_contacts_basics2.html
@@ -225,17 +225,17 @@ var steps = [
     ok(cloned.id != createResult1.id, "Cloned contact has new ID");
     cloned.email = [{value: "new email!"}];
     cloned.givenName = ["Tom"];
     req = mozContacts.save(cloned);
     req.onsuccess = function () {
       ok(cloned.id, "The contact now has an ID.");
       is(cloned.email[0].value, "new email!", "Same Email");
       isnot(createResult1.email[0].value, cloned.email[0].value, "Clone has different email");
-      is(cloned.givenName, "Tom", "New Name");
+      is(String(cloned.givenName), "Tom", "New Name");
       next();
     }
     req.onerror = onFailure;
   },
   function () {
     ok(true, "Retrieving all contacts");
     var options = {filterBy: ["givenName"],
                    filterOp: "startsWith",
--- a/dom/devicestorage/nsDeviceStorage.cpp
+++ b/dom/devicestorage/nsDeviceStorage.cpp
@@ -2171,16 +2171,17 @@ nsDOMDeviceStorageCursor::nsDOMDeviceSto
                                                    nsIPrincipal* aPrincipal,
                                                    DeviceStorageFile* aFile,
                                                    PRTime aSince)
   : DOMCursor(aWindow, nullptr)
   , mOkToCallContinue(false)
   , mSince(aSince)
   , mFile(aFile)
   , mPrincipal(aPrincipal)
+  , mRequester(new nsContentPermissionRequester(GetOwner()))
 {
 }
 
 nsDOMDeviceStorageCursor::~nsDOMDeviceStorageCursor()
 {
 }
 
 void
@@ -2260,16 +2261,26 @@ nsDOMDeviceStorageCursor::Allow(JS::Hand
     = do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID);
   MOZ_ASSERT(target);
 
   nsCOMPtr<nsIRunnable> event = new InitCursorEvent(this, mFile);
   target->Dispatch(event, NS_DISPATCH_NORMAL);
   return NS_OK;
 }
 
+NS_IMETHODIMP
+nsDOMDeviceStorageCursor::GetRequester(nsIContentPermissionRequester** aRequester)
+{
+  NS_ENSURE_ARG_POINTER(aRequester);
+
+  nsCOMPtr<nsIContentPermissionRequester> requester = mRequester;
+  requester.forget(aRequester);
+  return NS_OK;
+}
+
 void
 nsDOMDeviceStorageCursor::Continue(ErrorResult& aRv)
 {
   if (!mOkToCallContinue) {
     aRv.Throw(NS_ERROR_UNEXPECTED);
     return;
   }
 
@@ -2846,16 +2857,17 @@ public:
                        DOMRequest* aRequest,
                        nsDOMDeviceStorage* aDeviceStorage)
     : mRequestType(aRequestType)
     , mWindow(aWindow)
     , mPrincipal(aPrincipal)
     , mFile(aFile)
     , mRequest(aRequest)
     , mDeviceStorage(aDeviceStorage)
+    , mRequester(new nsContentPermissionRequester(mWindow))
   {
     MOZ_ASSERT(mWindow);
     MOZ_ASSERT(mPrincipal);
     MOZ_ASSERT(mFile);
     MOZ_ASSERT(mRequest);
     MOZ_ASSERT(mDeviceStorage);
   }
 
@@ -2866,16 +2878,17 @@ public:
                        DOMRequest* aRequest,
                        nsIDOMBlob* aBlob = nullptr)
     : mRequestType(aRequestType)
     , mWindow(aWindow)
     , mPrincipal(aPrincipal)
     , mFile(aFile)
     , mRequest(aRequest)
     , mBlob(aBlob)
+    , mRequester(new nsContentPermissionRequester(mWindow))
   {
     MOZ_ASSERT(mWindow);
     MOZ_ASSERT(mPrincipal);
     MOZ_ASSERT(mFile);
     MOZ_ASSERT(mRequest);
   }
 
   DeviceStorageRequest(const DeviceStorageRequestType aRequestType,
@@ -2885,16 +2898,17 @@ public:
                        DOMRequest* aRequest,
                        DeviceStorageFileDescriptor* aDSFileDescriptor)
     : mRequestType(aRequestType)
     , mWindow(aWindow)
     , mPrincipal(aPrincipal)
     , mFile(aFile)
     , mRequest(aRequest)
     , mDSFileDescriptor(aDSFileDescriptor)
+    , mRequester(new nsContentPermissionRequester(mWindow))
   {
     MOZ_ASSERT(mRequestType == DEVICE_STORAGE_REQUEST_CREATEFD);
     MOZ_ASSERT(mWindow);
     MOZ_ASSERT(mPrincipal);
     MOZ_ASSERT(mFile);
     MOZ_ASSERT(mRequest);
     MOZ_ASSERT(mDSFileDescriptor);
   }
@@ -3294,28 +3308,38 @@ public:
         = do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID);
       MOZ_ASSERT(target);
       target->Dispatch(r, NS_DISPATCH_NORMAL);
     }
 
     return NS_OK;
   }
 
+  NS_IMETHODIMP GetRequester(nsIContentPermissionRequester** aRequester)
+  {
+    NS_ENSURE_ARG_POINTER(aRequester);
+
+    nsCOMPtr<nsIContentPermissionRequester> requester = mRequester;
+    requester.forget(aRequester);
+    return NS_OK;
+  }
+
 private:
   ~DeviceStorageRequest() {}
 
   int32_t mRequestType;
   nsCOMPtr<nsPIDOMWindow> mWindow;
   nsCOMPtr<nsIPrincipal> mPrincipal;
   nsRefPtr<DeviceStorageFile> mFile;
 
   nsRefPtr<DOMRequest> mRequest;
   nsCOMPtr<nsIDOMBlob> mBlob;
   nsRefPtr<nsDOMDeviceStorage> mDeviceStorage;
   nsRefPtr<DeviceStorageFileDescriptor> mDSFileDescriptor;
+  nsCOMPtr<nsIContentPermissionRequester> mRequester;
 };
 
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(DeviceStorageRequest)
   NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIContentPermissionRequest)
   NS_INTERFACE_MAP_ENTRY(nsIContentPermissionRequest)
   NS_INTERFACE_MAP_ENTRY(nsIRunnable)
 NS_INTERFACE_MAP_END
 
--- a/dom/devicestorage/nsDeviceStorage.h
+++ b/dom/devicestorage/nsDeviceStorage.h
@@ -215,16 +215,17 @@ public:
 
   void RequestComplete() override;
 
 private:
   ~nsDOMDeviceStorageCursor();
 
   nsRefPtr<DeviceStorageFile> mFile;
   nsCOMPtr<nsIPrincipal> mPrincipal;
+  nsCOMPtr<nsIContentPermissionRequester> mRequester;
 };
 
 //helpers
 bool
 StringToJsval(nsPIDOMWindow* aWindow, nsAString& aString,
               JS::MutableHandle<JS::Value> result);
 
 JS::Value
--- a/dom/events/EventStateManager.cpp
+++ b/dom/events/EventStateManager.cpp
@@ -467,25 +467,31 @@ EventStateManager::OnStopObservingConten
   NS_ENSURE_TRUE_VOID(mIMEContentObserver == aIMEContentObserver);
   mIMEContentObserver = nullptr;
 }
 
 nsresult
 EventStateManager::PreHandleEvent(nsPresContext* aPresContext,
                                   WidgetEvent* aEvent,
                                   nsIFrame* aTargetFrame,
+                                  nsIContent* aTargetContent,
                                   nsEventStatus* aStatus)
 {
   NS_ENSURE_ARG_POINTER(aStatus);
   NS_ENSURE_ARG(aPresContext);
   if (!aEvent) {
     NS_ERROR("aEvent is null.  This should never happen.");
     return NS_ERROR_NULL_POINTER;
   }
 
+  NS_WARN_IF_FALSE(!aTargetFrame ||
+                   !aTargetFrame->GetContent() ||
+                   aTargetFrame->GetContent() == aTargetContent,
+                   "aTargetContent should be related with aTargetFrame");
+
   mCurrentTarget = aTargetFrame;
   mCurrentTargetContent = nullptr;
 
   // Do not take account NS_MOUSE_ENTER/EXIT so that loading a page
   // when user is not active doesn't change the state to active.
   WidgetMouseEvent* mouseEvent = aEvent->AsMouseEvent();
   if (aEvent->mFlags.mIsTrusted &&
       ((mouseEvent && mouseEvent->IsReal() &&
@@ -502,20 +508,19 @@ EventStateManager::PreHandleEvent(nsPres
       }
     }
     ++gMouseOrKeyboardEventCounter;
   }
 
   WheelTransaction::OnEvent(aEvent);
 
   // Focus events don't necessarily need a frame.
-  if (NS_EVENT_NEEDS_FRAME(aEvent)) {
-    if (!mCurrentTarget) {
-      return NS_ERROR_NULL_POINTER;
-    }
+  if (!mCurrentTarget && !aTargetContent) {
+    NS_ERROR("mCurrentTarget and aTargetContent are null");
+    return NS_ERROR_NULL_POINTER;
   }
 #ifdef DEBUG
   if (aEvent->HasDragEventMessage() && sIsPointerLocked) {
     NS_ASSERTION(sIsPointerLocked,
       "sIsPointerLocked is true. Drag events should be suppressed when "
       "the pointer is locked.");
   }
 #endif
@@ -1510,20 +1515,22 @@ EventStateManager::BeginTrackingDragGest
 {
   if (!inDownEvent->widget)
     return;
 
   // Note that |inDownEvent| could be either a mouse down event or a
   // synthesized mouse move event.
   mGestureDownPoint = inDownEvent->refPoint + inDownEvent->widget->WidgetToScreenOffset();
 
-  inDownFrame->GetContentForEvent(inDownEvent,
-                                  getter_AddRefs(mGestureDownContent));
-
-  mGestureDownFrameOwner = inDownFrame->GetContent();
+  if (inDownFrame) {
+    inDownFrame->GetContentForEvent(inDownEvent,
+                                    getter_AddRefs(mGestureDownContent));
+
+    mGestureDownFrameOwner = inDownFrame->GetContent();
+  }
   mGestureModifiers = inDownEvent->modifiers;
   mGestureDownButtons = inDownEvent->buttons;
 
   if (Prefs::ClickHoldContextMenu()) {
     // fire off a timer to track click-hold
     CreateClickHoldTimer(aPresContext, inDownFrame, inDownEvent);
   }
 }
@@ -4083,17 +4090,17 @@ EventStateManager::GenerateMouseEnterExi
         if (aMouseEvent->refPoint != center) {
           // Mouse move doesn't finish at the center of the window. Dispatch a
           // synthetic native mouse event to move the pointer back to the center
           // of the window, to faciliate more movement. But first, record that
           // we've dispatched a synthetic mouse movement, so we can cancel it
           // in the other branch here.
           sSynthCenteringPoint = center;
           aMouseEvent->widget->SynthesizeNativeMouseMove(
-            center + aMouseEvent->widget->WidgetToScreenOffset());
+            center + aMouseEvent->widget->WidgetToScreenOffset(), nullptr);
         } else if (aMouseEvent->refPoint == sSynthCenteringPoint) {
           // This is the "synthetic native" event we dispatched to re-center the
           // pointer. Cancel it so we don't expose the centering move to content.
           aMouseEvent->mFlags.mPropagationStopped = true;
           // Clear sSynthCenteringPoint so we don't cancel other events
           // targeted at the center.
           sSynthCenteringPoint = kInvalidRefPoint;
         }
@@ -4218,32 +4225,34 @@ EventStateManager::SetPointerLock(nsIWid
     mPreLockPoint = sLastRefPoint;
 
     // Fire a synthetic mouse move to ensure event state is updated. We first
     // set the mouse to the center of the window, so that the mouse event
     // doesn't report any movement.
     sLastRefPoint = GetWindowInnerRectCenter(aElement->OwnerDoc()->GetWindow(),
                                              aWidget,
                                              mPresContext);
-    aWidget->SynthesizeNativeMouseMove(sLastRefPoint + aWidget->WidgetToScreenOffset());
+    aWidget->SynthesizeNativeMouseMove(sLastRefPoint + aWidget->WidgetToScreenOffset(),
+                                       nullptr);
 
     // Retarget all events to this element via capture.
     nsIPresShell::SetCapturingContent(aElement, CAPTURE_POINTERLOCK);
 
     // Suppress DnD
     if (dragService) {
       dragService->Suppress();
     }
   } else {
     // Unlocking, so return pointer to the original position by firing a
     // synthetic mouse event. We first reset sLastRefPoint to its
     // pre-pointerlock position, so that the synthetic mouse event reports
     // no movement.
     sLastRefPoint = mPreLockPoint;
-    aWidget->SynthesizeNativeMouseMove(mPreLockPoint + aWidget->WidgetToScreenOffset());
+    aWidget->SynthesizeNativeMouseMove(mPreLockPoint + aWidget->WidgetToScreenOffset(),
+                                       nullptr);
 
     // Don't retarget events to this element any more.
     nsIPresShell::SetCapturingContent(nullptr, CAPTURE_POINTERLOCK);
 
     // Unsuppress DnD
     if (dragService) {
       dragService->Unsuppress();
     }
@@ -4390,17 +4399,19 @@ EventStateManager::UpdateDragDataTransfe
 
 nsresult
 EventStateManager::SetClickCount(nsPresContext* aPresContext,
                                  WidgetMouseEvent* aEvent,
                                  nsEventStatus* aStatus)
 {
   nsCOMPtr<nsIContent> mouseContent;
   nsIContent* mouseContentParent = nullptr;
-  mCurrentTarget->GetContentForEvent(aEvent, getter_AddRefs(mouseContent));
+  if (mCurrentTarget) {
+    mCurrentTarget->GetContentForEvent(aEvent, getter_AddRefs(mouseContent));
+  }
   if (mouseContent) {
     if (mouseContent->IsNodeOfType(nsINode::eTEXT)) {
       mouseContent = mouseContent->GetParent();
     }
     if (mouseContent && mouseContent->IsRootOfNativeAnonymousSubtree()) {
       mouseContentParent = mouseContent->GetParent();
     }
   }
--- a/dom/events/EventStateManager.h
+++ b/dom/events/EventStateManager.h
@@ -89,16 +89,17 @@ public:
    * cancelled should occur here.  Any processing which is intended to
    * be conditional based on either DOM or frame processing should occur in
    * PostHandleEvent.  Any centralized event processing which must occur before
    * DOM or frame event handling should occur here as well.
    */
   nsresult PreHandleEvent(nsPresContext* aPresContext,
                           WidgetEvent* aEvent,
                           nsIFrame* aTargetFrame,
+                          nsIContent* aTargetContent,
                           nsEventStatus* aStatus);
 
   /* The PostHandleEvent method should contain all system processing which
    * should occur conditionally based on DOM or frame processing.  It should
    * also contain any centralized event processing which must occur after
    * DOM and frame processing.
    */
   nsresult PostHandleEvent(nsPresContext* aPresContext,
--- a/dom/events/test/test_bug689564.html
+++ b/dom/events/test/test_bug689564.html
@@ -26,17 +26,17 @@ is(div.onclick.toString(), "function onc
 
 div.setAttribute("onscroll", "div");
 is(window.onscroll, null, "div should not forward onscroll");
 is(div.onscroll.toString(), "function onscroll(event) {\ndiv\n}",
    "div should have an onscroll handler");
 
 div.setAttribute("onpopstate", "div");
 is(window.onpopstate, null, "div should not forward onpopstate");
-is(div.onpopstate, null, "div should not have onpopstate handler");
+is("onpopstate" in div, false, "div should not have onpopstate handler");
 
 var body = document.createElement("body");
 body.setAttribute("onclick", "body");
 is(window.onclick, null, "body should not forward onclick");
 is(body.onclick.toString(), "function onclick(event) {\nbody\n}",
    "body should have an onclick handler");
 body.setAttribute("onscroll", "body");
 is(window.onscroll.toString(), "function onscroll(event) {\nbody\n}",
--- a/dom/events/test/test_dragstart.html
+++ b/dom/events/test/test_dragstart.html
@@ -163,17 +163,17 @@ function test_DataTransfer(dt)
   dt.getData("text/plain", "", "empty setDataAt index too high getData");
 
   // if the type is '', do nothing, or return ''
   dt.setData("", "Invalid Type");
   is(dt.types.length, 0, "invalid type setData");
   is(dt.getData(""), "", "invalid type getData"),
   dt.mozSetDataAt("", "Invalid Type", 0);
   is(dt.types.length, 0, "invalid type setDataAt");
-  is(dt.mozGetDataAt("", 0), null, "invalid type getDataAt"),
+  is(dt.mozGetDataAt("", 0), undefined, "invalid type getDataAt"),
 
   // similar with clearDataAt and getDataAt
   expectError(function() dt.mozGetDataAt("text/plain", 1),
               "IndexSizeError", "getDataAt index too high");
   expectError(function() dt.mozClearDataAt("text/plain", 1),
               "IndexSizeError", "clearDataAt index too high");
 
   dt.setData("text/plain", "Sample Text");
@@ -496,18 +496,18 @@ function onDragStartDraggable(event)
   gExtraDragTests++;
 }
 
 function checkOneDataItem(dt, expectedtypes, expecteddata, index, testid)
 {
   checkTypes(dt, expectedtypes, index, testid);
   for (var f = 0; f < expectedtypes.length; f++) {
     if (index == 0)
-      is(dt.getData(expectedtypes[f]), expecteddata[f], testid + " getData " + expectedtypes[f]);
-    is(dt.mozGetDataAt(expectedtypes[f], index), expecteddata[f] ? expecteddata[f] : null,
+      is_loosely(dt.getData(expectedtypes[f]), expecteddata[f], testid + " getData " + expectedtypes[f]);
+    is_loosely(dt.mozGetDataAt(expectedtypes[f], index), expecteddata[f] ? expecteddata[f] : null,
        testid + " getDataAt " + expectedtypes[f]);
   }
 }
 
 function checkTypes(dt, expectedtypes, index, testid)
 {
   if (index == 0) {
     var types = dt.types;
--- a/dom/events/test/test_messageEvent.html
+++ b/dom/events/test/test_messageEvent.html
@@ -34,17 +34,17 @@ https://bugzilla.mozilla.org/show_bug.cg
 
       var e = new MessageEvent('message', test);
       ok(e, "MessageEvent created");
       is(e.type, 'message', 'MessageEvent.type is right');
 
       is(e.data, 'data' in test ? test.data : undefined, 'MessageEvent.data is ok');
       is(e.origin, 'origin' in test ? test.origin : '', 'MessageEvent.origin is ok');
       is(e.lastEventId, 'lastEventId' in test ? test.lastEventId : '', 'MessageEvent.lastEventId is ok');
-      is(e.source, 'source' in test ? test.source : undefined, 'MessageEvent.source is ok');
+      is(e.source, 'source' in test ? test.source : null, 'MessageEvent.source is ok');
 
       if (test.ports != undefined) {
         is(e.ports.length, test.ports.length, 'MessageEvent.ports is ok');
         is(e.ports, e.ports, 'MessageEvent.ports is ok');
       } else {
         ok(!('ports' in test) || test.ports == null, 'MessageEvent.ports is ok');
       }
     }
--- a/dom/filehandle/AsyncHelper.cpp
+++ b/dom/filehandle/AsyncHelper.cpp
@@ -27,17 +27,17 @@ AsyncHelper::AsyncWork(nsIRequestObserve
     // build proxy for observer events
     rv = NS_NewRequestObserverProxy(getter_AddRefs(mObserver), aObserver, aCtxt);
     NS_ENSURE_SUCCESS(rv, rv);
   }
 
   FileService* service = FileService::GetOrCreate();
   NS_ENSURE_TRUE(service, NS_ERROR_FAILURE);
 
-  nsIEventTarget* target = service->StreamTransportTarget();
+  nsIEventTarget* target = service->ThreadPoolTarget();
 
   rv = target->Dispatch(this, NS_DISPATCH_NORMAL);
   NS_ENSURE_SUCCESS(rv, rv);
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
--- a/dom/filehandle/FileHandle.cpp
+++ b/dom/filehandle/FileHandle.cpp
@@ -587,17 +587,17 @@ FileHandleBase::Finish()
 {
   MOZ_ASSERT(NS_IsMainThread(), "Wrong thread!");
 
   nsRefPtr<FinishHelper> helper(new FinishHelper(this));
 
   FileService* service = FileService::Get();
   MOZ_ASSERT(service, "This should never be null");
 
-  nsIEventTarget* target = service->StreamTransportTarget();
+  nsIEventTarget* target = service->ThreadPoolTarget();
 
   nsresult rv = target->Dispatch(helper, NS_DISPATCH_NORMAL);
   NS_ENSURE_SUCCESS(rv, rv);
 
   return NS_OK;
 }
 
 // static
@@ -734,17 +734,17 @@ ReadHelper::DoAsyncRun(nsISupports* aStr
   uint32_t flags = FileStreamWrapper::NOTIFY_PROGRESS;
 
   nsCOMPtr<nsIInputStream> istream =
     new FileInputStreamWrapper(aStream, this, mLocation, mSize, flags);
 
   FileService* service = FileService::Get();
   MOZ_ASSERT(service, "This should never be null");
 
-  nsIEventTarget* target = service->StreamTransportTarget();
+  nsIEventTarget* target = service->ThreadPoolTarget();
 
   nsCOMPtr<nsIAsyncStreamCopier> copier;
   nsresult rv =
     NS_NewAsyncStreamCopier(getter_AddRefs(copier), istream, mStream, target,
                             false, true, STREAM_COPY_BLOCK_SIZE);
   NS_ENSURE_SUCCESS(rv, rv);
 
   rv = copier->AsyncCopy(this, nullptr);
@@ -809,17 +809,17 @@ WriteHelper::DoAsyncRun(nsISupports* aSt
   uint32_t flags = FileStreamWrapper::NOTIFY_PROGRESS;
 
   nsCOMPtr<nsIOutputStream> ostream =
     new FileOutputStreamWrapper(aStream, this, mLocation, mLength, flags);
 
   FileService* service = FileService::Get();
   MOZ_ASSERT(service, "This should never be null");
 
-  nsIEventTarget* target = service->StreamTransportTarget();
+  nsIEventTarget* target = service->ThreadPoolTarget();
 
   nsCOMPtr<nsIAsyncStreamCopier> copier;
   nsresult rv =
     NS_NewAsyncStreamCopier(getter_AddRefs(copier), mStream, ostream, target,
                             true, false, STREAM_COPY_BLOCK_SIZE);
   NS_ENSURE_SUCCESS(rv, rv);
 
   rv = copier->AsyncCopy(this, nullptr);
--- a/dom/filehandle/FileService.cpp
+++ b/dom/filehandle/FileService.cpp
@@ -7,70 +7,90 @@
 #include "FileService.h"
 
 #include "FileHandle.h"
 #include "MainThreadUtils.h"
 #include "mozilla/Assertions.h"
 #include "MutableFile.h"
 #include "nsError.h"
 #include "nsIEventTarget.h"
-#include "nsIObserverService.h"
-#include "nsIOfflineStorage.h"
 #include "nsNetCID.h"
 #include "nsServiceManagerUtils.h"
+#include "nsThreadPool.h"
 #include "nsThreadUtils.h"
 
 namespace mozilla {
 namespace dom {
 
 namespace {
 
-FileService* gInstance = nullptr;
+const uint32_t kThreadLimit = 5;
+const uint32_t kIdleThreadLimit = 1;
+const uint32_t kIdleThreadTimeoutMs = 30000;
+
+StaticAutoPtr<FileService> gInstance;
 bool gShutdown = false;
 
 } // anonymous namespace
 
 FileService::FileService()
 {
-  NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
-  NS_ASSERTION(!gInstance, "More than one instance!");
+  MOZ_ASSERT(NS_IsMainThread());
+  MOZ_ASSERT(!gInstance);
 }
 
 FileService::~FileService()
 {
-  NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
-  NS_ASSERTION(!gInstance, "More than one instance!");
+  MOZ_ASSERT(NS_IsMainThread());
 }
 
 nsresult
 FileService::Init()
 {
-  nsresult rv;
-  mStreamTransportTarget =
-    do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID, &rv);
+  mThreadPool = new nsThreadPool();
+
+  nsresult rv = mThreadPool->SetName(NS_LITERAL_CSTRING("FileHandleTrans"));
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  rv = mThreadPool->SetThreadLimit(kThreadLimit);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
 
-  return rv;
+  rv = mThreadPool->SetIdleThreadLimit(kIdleThreadLimit);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  rv = mThreadPool->SetIdleThreadTimeout(kIdleThreadTimeoutMs);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  return NS_OK;
 }
 
 nsresult
 FileService::Cleanup()
 {
-  NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
+  MOZ_ASSERT(NS_IsMainThread());
 
   nsIThread* thread = NS_GetCurrentThread();
-  while (mStorageInfos.Count()) {
-    if (!NS_ProcessNextEvent(thread)) {
-      NS_ERROR("Failed to process next event!");
-      break;
-    }
+  MOZ_ASSERT(thread);
+
+  nsresult rv = mThreadPool->Shutdown();
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
   }
 
   // Make sure the service is still accessible while any generated callbacks
   // are processed.
-  nsresult rv = NS_ProcessPendingEvents(thread);
+  rv = NS_ProcessPendingEvents(thread);
   NS_ENSURE_SUCCESS(rv, rv);
 
   if (!mCompleteCallbacks.IsEmpty()) {
     // Run all callbacks manually now.
     for (uint32_t index = 0; index < mCompleteCallbacks.Length(); index++) {
       mCompleteCallbacks[index].mCallback->Run();
     }
     mCompleteCallbacks.Clear();
@@ -90,30 +110,22 @@ FileService::GetOrCreate()
   NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
 
   if (gShutdown) {
     NS_WARNING("Calling GetOrCreate() after shutdown!");
     return nullptr;
   }
 
   if (!gInstance) {
-    nsRefPtr<FileService> service(new FileService);
+    nsAutoPtr<FileService> service(new FileService());
 
     nsresult rv = service->Init();
     NS_ENSURE_SUCCESS(rv, nullptr);
 
-    nsCOMPtr<nsIObserverService> obs =
-      do_GetService(NS_OBSERVERSERVICE_CONTRACTID, &rv);
-    NS_ENSURE_SUCCESS(rv, nullptr);
-
-    rv = obs->AddObserver(service, "profile-before-change", false);
-    NS_ENSURE_SUCCESS(rv, nullptr);
-
-    // The observer service now owns us.
-    gInstance = service;
+    gInstance = service.forget();
   }
 
   return gInstance;
 }
 
 // static
 FileService*
 FileService::Get()
@@ -249,74 +261,48 @@ FileService::NotifyFileHandleCompleted(F
       else {
         index++;
       }
     }
   }
 }
 
 void
-FileService::WaitForStoragesToComplete(
-                                 nsTArray<nsCOMPtr<nsIOfflineStorage> >& aStorages,
-                                 nsIRunnable* aCallback)
+FileService::WaitForStoragesToComplete(nsTArray<nsCString>& aStorageIds,
+                                       nsIRunnable* aCallback)
 {
-  NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
-  NS_ASSERTION(!aStorages.IsEmpty(), "No databases to wait on!");
-  NS_ASSERTION(aCallback, "Null pointer!");
+  MOZ_ASSERT(NS_IsMainThread());
+  MOZ_ASSERT(!aStorageIds.IsEmpty());
+  MOZ_ASSERT(aCallback);
 
   StoragesCompleteCallback* callback = mCompleteCallbacks.AppendElement();
   callback->mCallback = aCallback;
-  callback->mStorages.SwapElements(aStorages);
+  callback->mStorageIds.SwapElements(aStorageIds);
 
   if (MaybeFireCallback(*callback)) {
     mCompleteCallbacks.RemoveElementAt(mCompleteCallbacks.Length() - 1);
   }
 }
 
-void
-FileService::AbortFileHandlesForStorage(nsIOfflineStorage* aStorage)
+nsIEventTarget*
+FileService::ThreadPoolTarget() const
 {
-  MOZ_ASSERT(NS_IsMainThread(), "Wrong thread!");
-  MOZ_ASSERT(aStorage, "Null pointer!");
-
-  StorageInfo* storageInfo;
-  if (!mStorageInfos.Get(aStorage->Id(), &storageInfo)) {
-    return;
-  }
-
-  nsAutoTArray<nsRefPtr<FileHandleBase>, 10> fileHandles;
-  storageInfo->CollectRunningAndDelayedFileHandles(aStorage, fileHandles);
+  MOZ_ASSERT(NS_IsMainThread());
+  MOZ_ASSERT(mThreadPool);
 
-  for (uint32_t index = 0; index < fileHandles.Length(); index++) {
-    ErrorResult ignored;
-    fileHandles[index]->Abort(ignored);
-  }
-}
-
-NS_IMPL_ISUPPORTS(FileService, nsIObserver)
-
-NS_IMETHODIMP
-FileService::Observe(nsISupports* aSubject, const char*  aTopic,
-                     const char16_t* aData)
-{
-  NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
-  NS_ASSERTION(!strcmp(aTopic, "profile-before-change"), "Wrong topic!");
-
-  Shutdown();
-
-  return NS_OK;
+  return mThreadPool;
 }
 
 bool
 FileService::MaybeFireCallback(StoragesCompleteCallback& aCallback)
 {
   NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
 
-  for (uint32_t index = 0; index < aCallback.mStorages.Length(); index++) {
-    if (mStorageInfos.Get(aCallback.mStorages[index]->Id(), nullptr)) {
+  for (uint32_t index = 0; index < aCallback.mStorageIds.Length(); index++) {
+    if (mStorageInfos.Get(aCallback.mStorageIds[index], nullptr)) {
       return false;
     }
   }
 
   aCallback.mCallback->Run();
   return true;
 }
 
@@ -484,30 +470,10 @@ FileService::StorageInfo::CreateDelayedE
                                                    FileHelper* aFileHelper)
 {
   DelayedEnqueueInfo* info = mDelayedEnqueueInfos.AppendElement();
   info->mFileHandle = aFileHandle;
   info->mFileHelper = aFileHelper;
   return info;
 }
 
-void
-FileService::StorageInfo::CollectRunningAndDelayedFileHandles(
-                               nsIOfflineStorage* aStorage,
-                               nsTArray<nsRefPtr<FileHandleBase>>& aFileHandles)
-{
-  for (uint32_t index = 0; index < mFileHandleQueues.Length(); index++) {
-    FileHandleBase* fileHandle = mFileHandleQueues[index]->mFileHandle;
-    if (fileHandle->MutableFile()->Storage() == aStorage) {
-      aFileHandles.AppendElement(fileHandle);
-    }
-  }
-
-  for (uint32_t index = 0; index < mDelayedEnqueueInfos.Length(); index++) {
-    FileHandleBase* fileHandle = mDelayedEnqueueInfos[index].mFileHandle;
-    if (fileHandle->MutableFile()->Storage() == aStorage) {
-      aFileHandles.AppendElement(fileHandle);
-    }
-  }
-}
-
 } // namespace dom
 } // namespace mozilla
--- a/dom/filehandle/FileService.h
+++ b/dom/filehandle/FileService.h
@@ -4,41 +4,41 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef mozilla_dom_FileService_h
 #define mozilla_dom_FileService_h
 
 #include "mozilla/Attributes.h"
 #include "mozilla/dom/FileHelper.h"
+#include "mozilla/StaticPtr.h"
 #include "nsClassHashtable.h"
-#include "nsIObserver.h"
 #include "nsAutoPtr.h"
 #include "nsCOMPtr.h"
 #include "nsDebug.h"
 #include "nsHashKeys.h"
 #include "nsTArray.h"
 #include "nsTHashtable.h"
 
 class nsAString;
 class nsIEventTarget;
-class nsIOfflineStorage;
 class nsIRunnable;
+class nsThreadPool;
 
 namespace mozilla {
 namespace dom {
 
 class FileHandleBase;
 
-class FileService final : public nsIObserver
+class FileService final
 {
+  friend class nsAutoPtr<FileService>;
+  friend class StaticAutoPtr<FileService>;
+
 public:
-  NS_DECL_ISUPPORTS
-  NS_DECL_NSIOBSERVER
-
   // Returns a non-owning reference!
   static FileService*
   GetOrCreate();
 
   // Returns a non-owning reference!
   static FileService*
   Get();
 
@@ -51,28 +51,21 @@ public:
 
   nsresult
   Enqueue(FileHandleBase* aFileHandle, FileHelper* aFileHelper);
 
   void
   NotifyFileHandleCompleted(FileHandleBase* aFileHandle);
 
   void
-  WaitForStoragesToComplete(nsTArray<nsCOMPtr<nsIOfflineStorage> >& aStorages,
+  WaitForStoragesToComplete(nsTArray<nsCString>& aStorageIds,
                             nsIRunnable* aCallback);
 
-  void
-  AbortFileHandlesForStorage(nsIOfflineStorage* aStorage);
-
   nsIEventTarget*
-  StreamTransportTarget()
-  {
-    NS_ASSERTION(mStreamTransportTarget, "This should never be null!");
-    return mStreamTransportTarget;
-  }
+  ThreadPoolTarget() const;
 
 private:
   class FileHandleQueue final : public FileHelperListener
   {
     friend class FileService;
 
   public:
     NS_IMETHOD_(MozExternalRefCountType)
@@ -131,21 +124,16 @@ private:
     {
       return !mFileHandleQueues.IsEmpty();
     }
 
     inline DelayedEnqueueInfo*
     CreateDelayedEnqueueInfo(FileHandleBase* aFileHandle,
                              FileHelper* aFileHelper);
 
-    inline void
-    CollectRunningAndDelayedFileHandles(
-                              nsIOfflineStorage* aStorage,
-                              nsTArray<nsRefPtr<FileHandleBase>>& aFileHandles);
-
     void
     LockFileForReading(const nsAString& aFileName)
     {
       mFilesReading.PutEntry(aFileName);
     }
 
     void
     LockFileForWriting(const nsAString& aFileName)
@@ -173,33 +161,33 @@ private:
     nsTArray<nsRefPtr<FileHandleQueue>> mFileHandleQueues;
     nsTArray<DelayedEnqueueInfo> mDelayedEnqueueInfos;
     nsTHashtable<nsStringHashKey> mFilesReading;
     nsTHashtable<nsStringHashKey> mFilesWriting;
   };
 
   struct StoragesCompleteCallback
   {
-    nsTArray<nsCOMPtr<nsIOfflineStorage> > mStorages;
+    nsTArray<nsCString> mStorageIds;
     nsCOMPtr<nsIRunnable> mCallback;
   };
 
   FileService();
   ~FileService();
 
   nsresult
   Init();
 
   nsresult
   Cleanup();
 
   bool
   MaybeFireCallback(StoragesCompleteCallback& aCallback);
 
-  nsCOMPtr<nsIEventTarget> mStreamTransportTarget;
+  nsRefPtr<nsThreadPool> mThreadPool;
   nsClassHashtable<nsCStringHashKey, StorageInfo> mStorageInfos;
   nsTArray<StoragesCompleteCallback> mCompleteCallbacks;
 };
 
 } // namespace dom
 } // namespace mozilla
 
 #endif // mozilla_dom_FileService_h
--- a/dom/filehandle/moz.build
+++ b/dom/filehandle/moz.build
@@ -30,8 +30,12 @@ FAIL_ON_WARNINGS = True
 
 include('/ipc/chromium/chromium-config.mozbuild')
 
 LOCAL_INCLUDES += [
     '../base',
 ]
 
 FINAL_LIBRARY = 'xul'
+
+LOCAL_INCLUDES += [
+    '/xpcom/threads',
+]
--- a/dom/filesystem/FileSystemPermissionRequest.cpp
+++ b/dom/filesystem/FileSystemPermissionRequest.cpp
@@ -50,16 +50,17 @@ FileSystemPermissionRequest::FileSystemP
   }
 
   nsCOMPtr<nsIDocument> doc = mWindow->GetDoc();
   if (!doc) {
     return;
   }
 
   mPrincipal = doc->NodePrincipal();
+  mRequester = new nsContentPermissionRequester(mWindow);
 }
 
 FileSystemPermissionRequest::~FileSystemPermissionRequest()
 {
 }
 
 NS_IMETHODIMP
 FileSystemPermissionRequest::GetTypes(nsIArray** aTypes)
@@ -130,10 +131,20 @@ FileSystemPermissionRequest::Run()
     Cancel();
     return NS_OK;
   }
 
   nsContentPermissionUtils::AskPermission(this, mWindow);
   return NS_OK;
 }
 
+NS_IMETHODIMP
+FileSystemPermissionRequest::GetRequester(nsIContentPermissionRequester** aRequester)
+{
+  NS_ENSURE_ARG_POINTER(aRequester);
+
+  nsCOMPtr<nsIContentPermissionRequester> requester = mRequester;
+  requester.forget(aRequester);
+  return NS_OK;
+}
+
 } /* namespace dom */
 } /* namespace mozilla */
--- a/dom/filesystem/FileSystemPermissionRequest.h
+++ b/dom/filesystem/FileSystemPermissionRequest.h
@@ -37,14 +37,15 @@ private:
   virtual
   ~FileSystemPermissionRequest();
 
   nsCString mPermissionType;
   nsCString mPermissionAccess;
   nsRefPtr<FileSystemTaskBase> mTask;
   nsCOMPtr<nsPIDOMWindow> mWindow;
   nsCOMPtr<nsIPrincipal> mPrincipal;
+  nsCOMPtr<nsIContentPermissionRequester> mRequester;
 };
 
 } // namespace dom
 } // namespace mozilla
 
 #endif // mozilla_dom_FileSystemPermissionRequest_h
--- a/dom/geolocation/nsGeolocation.cpp
+++ b/dom/geolocation/nsGeolocation.cpp
@@ -106,16 +106,17 @@ class nsGeolocationRequest final
   GeoPositionCallback mCallback;
   GeoPositionErrorCallback mErrorCallback;
   nsAutoPtr<PositionOptions> mOptions;
 
   nsRefPtr<Geolocation> mLocator;
 
   int32_t mWatchId;
   bool mShutdown;
+  nsCOMPtr<nsIContentPermissionRequester> mRequester;
 };
 
 static PositionOptions*
 CreatePositionOptionsCopy(const PositionOptions& aOptions)
 {
   nsAutoPtr<PositionOptions> geoOptions(new PositionOptions());
 
   geoOptions->mEnableHighAccuracy = aOptions.mEnableHighAccuracy;
@@ -351,16 +352,23 @@ nsGeolocationRequest::nsGeolocationReque
   : mIsWatchPositionRequest(aWatchPositionRequest),
     mCallback(aCallback),
     mErrorCallback(aErrorCallback),
     mOptions(aOptions),
     mLocator(aLocator),
     mWatchId(aWatchId),
     mShutdown(false)
 {
+  nsCOMPtr<nsIDOMWindow> win = do_QueryReferent(mLocator->GetOwner());
+  if (win) {
+    nsCOMPtr<nsPIDOMWindow> window = do_QueryInterface(win);
+    if (window) {
+      mRequester = new nsContentPermissionRequester(window);
+    }
+  }
 }
 
 nsGeolocationRequest::~nsGeolocationRequest()
 {
 }
 
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsGeolocationRequest)
   NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIContentPermissionRequest)
@@ -495,16 +503,26 @@ nsGeolocationRequest::Allow(JS::HandleVa
     mLocator->NotifyAllowedRequest(this);
   }
 
   SetTimeoutTimer();
 
   return NS_OK;
 }
 
+NS_IMETHODIMP
+nsGeolocationRequest::GetRequester(nsIContentPermissionRequester** aRequester)
+{
+  NS_ENSURE_ARG_POINTER(aRequester);
+
+  nsCOMPtr<nsIContentPermissionRequester> requester = mRequester;
+  requester.forget(aRequester);
+  return NS_OK;
+}
+
 void
 nsGeolocationRequest::SetTimeoutTimer()
 {
   StopTimeoutTimer();
 
   int32_t timeout;
   if (mOptions && (timeout = mOptions->mTimeout) != 0) {
 
--- a/dom/html/HTMLInputElement.cpp
+++ b/dom/html/HTMLInputElement.cpp
@@ -104,16 +104,17 @@
 
 #include <limits>
 
 #include "nsIColorPicker.h"
 #include "nsIStringEnumerator.h"
 #include "HTMLSplitOnSpacesTokenizer.h"
 #include "nsIController.h"
 #include "nsIMIMEInfo.h"
+#include "nsFrameSelection.h"
 
 // input type=date
 #include "js/Date.h"
 
 NS_IMPL_NS_NEW_HTML_ELEMENT_CHECK_PARSER(Input)
 
 // XXX align=left, hspace, vspace, border? other nav4 attrs
 
@@ -3280,16 +3281,29 @@ HTMLInputElement::Select()
   // XXX Bug?  We have to give the input focus before contents can be
   // selected
 
   FocusTristate state = FocusState();
   if (state == eUnfocusable) {
     return NS_OK;
   }
 
+  nsTextEditorState* tes = GetEditorState();
+  if (tes) {
+    nsFrameSelection* fs = tes->GetConstFrameSelection();
+    if (fs && fs->MouseDownRecorded()) {
+      // This means that we're being called while the frame selection has a mouse
+      // down event recorded to adjust the caret during the mouse up event.
+      // We are probably called from the focus event handler.  We should override
+      // the delayed caret data in this case to ensure that this select() call
+      // takes effect.
+      fs->SetDelayedCaretData(nullptr);
+    }
+  }
+
   nsIFocusManager* fm = nsFocusManager::GetFocusManager();
 
   nsRefPtr<nsPresContext> presContext = GetPresContext(eForComposedDoc);
   if (state == eInactiveWindow) {
     if (fm)
       fm->SetFocus(this, nsIFocusManager::FLAG_NOSCROLL);
     SelectAll(presContext);
     return NS_OK;
--- a/dom/html/test/forms/test_change_event.html
+++ b/dom/html/test/forms/test_change_event.html
@@ -177,25 +177,25 @@ https://bugzilla.mozilla.org/show_bug.cg
     is(numberChange, 0, "Change event shouldn't be dispatched on number input element for key changes that don't change its value");
     number.value = "";
     number.focus();
     synthesizeKey("1", {});
     synthesizeKey("2", {});
     is(numberChange, 0, "Change event shouldn't be dispatched on number input element for keyboard input until it loses focus");
     number.blur();
     is(numberChange, 1, "Change event should be dispatched on number input element on blur");
-    is(number.value, 12, "Sanity check that number keys were actually handled");
+    is(number.value, "12", "Sanity check that number keys were actually handled");
     if (isDesktop) { // up/down arrow keys not supported on android/b2g
       number.value = "";
       number.focus();
       synthesizeKey("VK_UP", {});
       synthesizeKey("VK_UP", {});
       synthesizeKey("VK_DOWN", {});
       is(numberChange, 4, "Change event should be dispatched on number input element for up/down arrow keys (a special case)");
-      is(number.value, 1, "Sanity check that number and arrow keys were actually handled");
+      is(number.value, "1", "Sanity check that number and arrow keys were actually handled");
     }
 
     // Special case type=range
     var range = document.getElementById("input_range");
     range.focus();
     synthesizeKey("a", {});
     range.blur();
     is(rangeChange, 0, "Change event shouldn't be dispatched on range input element for key changes that don't change its value");
--- a/dom/html/test/forms/test_input_event.html
+++ b/dom/html/test/forms/test_input_event.html
@@ -186,21 +186,21 @@ https://bugzilla.mozilla.org/show_bug.cg
     // are tested in test_input_number_mouse_events.html
     var number = document.getElementById("input_number");
 
     if (isDesktop) { // up/down arrow keys not supported on android/b2g
       number.value = "";
       number.focus();
       synthesizeKey("KEY_ArrowUp", { code: "ArrowUp" });
       is(numberInput, 1, "input event should be dispatched for up/down arrow key keypress");
-      is(number.value, 1, "sanity check value of number control after keypress");
+      is(number.value, "1", "sanity check value of number control after keypress");
 
       synthesizeKey("KEY_ArrowDown", { code: "ArrowDown", repeat: 3 });
       is(numberInput, 4, "input event should be dispatched for each up/down arrow key keypress event, even when rapidly repeated");
-      is(number.value, -2, "sanity check value of number control after multiple keydown events");
+      is(number.value, "-2", "sanity check value of number control after multiple keydown events");
 
       number.blur();
       is(numberInput, 4, "input event shouldn't be dispatched on blur");
     }
 
     MockFilePicker.cleanup();
     SimpleTest.finish();
   }
--- a/dom/html/test/forms/test_input_number_key_events.html
+++ b/dom/html/test/forms/test_input_number_key_events.html
@@ -154,83 +154,83 @@ function test() {
   var defaultValue = 0;
   var oldVal, expectedVal;
 
   for (key of ["VK_UP", "VK_DOWN"]) {
     // Start at middle:
     oldVal = elem.value = -1;
     expectedVal = expectedValAfterKeyEvent(key, elem);
     synthesizeKey(key, {});
-    is(elem.value, expectedVal, "Test " + key + " for number control with value set between min/max (" + oldVal + ")");
+    is(elem.value, String(expectedVal), "Test " + key + " for number control with value set between min/max (" + oldVal + ")");
 
     // Same again:
     expectedVal = expectedValAfterKeyEvent(key, elem);
     synthesizeKey(key, {});
-    is(elem.value, expectedVal, "Test repeat of " + key + " for number control");
+    is(elem.value, String(expectedVal), "Test repeat of " + key + " for number control");
 
     // Start at maximum:
     oldVal = elem.value = elem.max;
     expectedVal = expectedValAfterKeyEvent(key, elem);
     synthesizeKey(key, {});
-    is(elem.value, expectedVal, "Test " + key + " for number control with value set to the maximum (" + oldVal + ")");
+    is(elem.value, String(expectedVal), "Test " + key + " for number control with value set to the maximum (" + oldVal + ")");
 
     // Same again:
     expectedVal = expectedValAfterKeyEvent(key, elem);
     synthesizeKey(key, {});
-    is(elem.value, expectedVal, "Test repeat of " + key + " for number control");
+    is(elem.value, String(expectedVal), "Test repeat of " + key + " for number control");
 
     // Start at minimum:
     oldVal = elem.value = elem.min;
     expectedVal = expectedValAfterKeyEvent(key, elem);
     synthesizeKey(key, {});
-    is(elem.value, expectedVal, "Test " + key + " for number control with value set to the minimum (" + oldVal + ")");
+    is(elem.value, String(expectedVal), "Test " + key + " for number control with value set to the minimum (" + oldVal + ")");
 
     // Same again:
     expectedVal = expectedValAfterKeyEvent(key, elem);
     synthesizeKey(key, {});
-    is(elem.value, expectedVal, "Test repeat of " + key + " for number control");
+    is(elem.value, String(expectedVal), "Test repeat of " + key + " for number control");
 
     // Test preventDefault():
     elem.addEventListener("keypress", function(evt) {
       evt.preventDefault();
       elem.removeEventListener("keypress", arguments.callee, false);
     }, false);
     oldVal = elem.value = 0;
     expectedVal = 0;
     synthesizeKey(key, {});
-    is(elem.value, expectedVal, "Test " + key + " for number control where scripted preventDefault() should prevent the value changing");
+    is(elem.value, String(expectedVal), "Test " + key + " for number control where scripted preventDefault() should prevent the value changing");
 
     // Test step="any" behavior:
     var oldStep = elem.step;
     elem.step = "any";
     oldVal = elem.value = 0;
     expectedVal = expectedValAfterKeyEvent(key, elem);
     synthesizeKey(key, {});
-    is(elem.value, expectedVal, "Test " + key + " for number control with value set to the midpoint and step='any' (" + oldVal + ")");
+    is(elem.value, String(expectedVal), "Test " + key + " for number control with value set to the midpoint and step='any' (" + oldVal + ")");
     elem.step = oldStep; // restore
 
     // Test that invalid input blocks UI initiated stepping:
     oldVal = elem.value = "";
     elem.select();
     sendString("abc");
     synthesizeKey(key, {});
     is(elem.value, "", "Test " + key + " does nothing when the input is invalid");
 
     // Test that no value does not block UI initiated stepping:
     oldVal = elem.value = "";
     elem.setAttribute("required", "required");
     elem.select();
     expectedVal = expectedValAfterKeyEvent(key, elem);
     synthesizeKey(key, {});
-    is(elem.value, expectedVal, "Test " + key + " for number control with value set to the empty string and with the 'required' attribute set");
+    is(elem.value, String(expectedVal), "Test " + key + " for number control with value set to the empty string and with the 'required' attribute set");
 
     // Same again:
     expectedVal = expectedValAfterKeyEvent(key, elem);
     synthesizeKey(key, {});
-    is(elem.value, expectedVal, "Test repeat of " + key + " for number control");
+    is(elem.value, String(expectedVal), "Test repeat of " + key + " for number control");
 
     // Reset 'required' attribute:
     elem.removeAttribute("required");
   }
 
   // Test that key events are correctly dispatched
   elem.max = "";
   elem.value = "";
--- a/dom/html/test/forms/test_input_number_l10n.html
+++ b/dom/html/test/forms/test_input_number_l10n.html
@@ -35,23 +35,23 @@ SimpleTest.waitForFocus(function() {
 var elem;
 
 function runTest(test) {
   elem.lang = test.langTag;
   elem.value = 0;
   elem.focus();
   elem.select();
   sendString(test.inputWithGrouping);
-  is(elem.value, test.value, "Test " + test.desc + " ('" + test.langTag +
-                             "') localization with grouping separator");
+  is(elem.value, String(test.value), "Test " + test.desc + " ('" + test.langTag +
+                                     "') localization with grouping separator");
   elem.value = 0;
   elem.select();
   sendString(test.inputWithoutGrouping);
-  is(elem.value, test.value, "Test " + test.desc + " ('" + test.langTag +
-                             "') localization without grouping separator");
+  is(elem.value, String(test.value), "Test " + test.desc + " ('" + test.langTag +
+                                     "') localization without grouping separator");
 }
 
 function startTests() {
   elem = document.getElementById("input");
   for (var test of tests) {
     runTest(test, elem);
   }
 }
--- a/dom/html/test/forms/test_input_number_mouse_events.html
+++ b/dom/html/test/forms/test_input_number_mouse_events.html
@@ -48,50 +48,50 @@ const SPIN_UP_Y = 3;
 const SPIN_DOWN_X = inputRect.width - 3;
 const SPIN_DOWN_Y = inputRect.height - 3;
 
 function test() {
   input.value = 0;
 
   // Test click on spin-up button:
   synthesizeMouse(input, SPIN_UP_X, SPIN_UP_Y, { type: "mousedown" });
-  is(input.value, 1, "Test step-up on mousedown on spin-up button");
+  is(input.value, "1", "Test step-up on mousedown on spin-up button");
   synthesizeMouse(input, SPIN_UP_X, SPIN_UP_Y, { type: "mouseup" });
-  is(input.value, 1, "Test mouseup on spin-up button");
+  is(input.value, "1", "Test mouseup on spin-up button");
 
   // Test click on spin-down button:
   synthesizeMouse(input, SPIN_DOWN_X, SPIN_DOWN_Y, { type: "mousedown" });
-  is(input.value, 0, "Test step-down on mousedown on spin-down button");
+  is(input.value, "0", "Test step-down on mousedown on spin-down button");
   synthesizeMouse(input, SPIN_DOWN_X, SPIN_DOWN_Y, { type: "mouseup" });
-  is(input.value, 0, "Test mouseup on spin-down button");
+  is(input.value, "0", "Test mouseup on spin-down button");
 
   // Test step="any" behavior:
   input.value = 0;
   var oldStep = input.step;
   input.step = "any";
   synthesizeMouse(input, SPIN_UP_X, SPIN_UP_Y, { type: "mousedown" });
-  is(input.value, 1, "Test step-up on mousedown on spin-up button with step='any'");
+  is(input.value, "1", "Test step-up on mousedown on spin-up button with step='any'");
   synthesizeMouse(input, SPIN_UP_X, SPIN_UP_Y, { type: "mouseup" });
-  is(input.value, 1, "Test mouseup on spin-up button with step='any'");
+  is(input.value, "1", "Test mouseup on spin-up button with step='any'");
   synthesizeMouse(input, SPIN_DOWN_X, SPIN_DOWN_Y, { type: "mousedown" });
-  is(input.value, 0, "Test step-down on mousedown on spin-down button with step='any'");
+  is(input.value, "0", "Test step-down on mousedown on spin-down button with step='any'");
   synthesizeMouse(input, SPIN_DOWN_X, SPIN_DOWN_Y, { type: "mouseup" });
-  is(input.value, 0, "Test mouseup on spin-down button with step='any'");
+  is(input.value, "0", "Test mouseup on spin-down button with step='any'");
   input.step = oldStep; // restore
 
   // Test that preventDefault() works:
   function preventDefault(e) {
     e.preventDefault();
   }
   input.value = 1;
   input.addEventListener("mousedown", preventDefault, false);
   synthesizeMouse(input, SPIN_UP_X, SPIN_UP_Y, {});
-  is(input.value, 1, "Test that preventDefault() works for click on spin-up button");
+  is(input.value, "1", "Test that preventDefault() works for click on spin-up button");
   synthesizeMouse(input, SPIN_DOWN_X, SPIN_DOWN_Y, {});
-  is(input.value, 1, "Test that preventDefault() works for click on spin-down button");
+  is(input.value, "1", "Test that preventDefault() works for click on spin-down button");
   input.removeEventListener("mousedown", preventDefault, false);
 
   // Run the spin tests:
   runNextSpinTest();
 }
 
 function runNextSpinTest() {
   var test = spinTests.shift();
@@ -108,40 +108,40 @@ var spinTests = [
   // Test spining when the mouse button is kept depressed on the spin-up
   // button, then moved over the spin-down button:
   function() {
     var inputEventCount = 0;
     input.value = 0;
     input.addEventListener("input", function(evt) {
       ++inputEventCount;
       if (inputEventCount == 3) {
-        ok(input.value, 3, "Testing spin-up button");
+        is(input.value, "3", "Testing spin-up button");
         synthesizeMouse(input, SPIN_DOWN_X, SPIN_DOWN_Y, { type: "mousemove" });
       } else if (inputEventCount == 6) {
-        ok(input.value, 0, "Testing spin direction is reversed after mouse moves from spin-up button to spin-down button");
+        is(input.value, "0", "Testing spin direction is reversed after mouse moves from spin-up button to spin-down button");
         input.removeEventListener("input", arguments.callee, false);
         synthesizeMouse(input, SPIN_DOWN_X, SPIN_DOWN_Y, { type: "mouseup" });
         runNextSpinTest();
       }
     }, false);
     synthesizeMouse(input, SPIN_UP_X, SPIN_UP_Y, { type: "mousedown" });
   },
 
   // Test spining when the mouse button is kept depressed on the spin-down
   // button, then moved over the spin-up button:
   function() {
     var inputEventCount = 0;
     input.value = 0;
     input.addEventListener("input", function(evt) {
       ++inputEventCount;
       if (inputEventCount == 3) {
-        ok(input.value, -3, "Testing spin-down button");
+        is(input.value, "-3", "Testing spin-down button");
         synthesizeMouse(input, SPIN_UP_X, SPIN_UP_Y, { type: "mousemove" });
       } else if (inputEventCount == 6) {
-        ok(input.value, 0, "Testing spin direction is reversed after mouse moves from spin-down button to spin-up button");
+        is(input.value, "0", "Testing spin direction is reversed after mouse moves from spin-down button to spin-up button");
         input.removeEventListener("input", arguments.callee, false);
         synthesizeMouse(input, SPIN_UP_X, SPIN_UP_Y, { type: "mouseup" });
         runNextSpinTest();
       }
     }, false);
     synthesizeMouse(input, SPIN_DOWN_X, SPIN_DOWN_Y, { type: "mousedown" });
   },
 
@@ -151,18 +151,18 @@ var spinTests = [
     var inputEventCount = 0;
     input.value = 0;
     input.addEventListener("input", function(evt) {
       ++inputEventCount;
       if (inputEventCount == 3) {
         synthesizeMouse(input, -1, -1, { type: "mousemove" });
         var eventHandler = arguments.callee;
         setTimeout(function() {
-          ok(input.value, 3, "Testing moving the mouse outside the spin buttons stops the spin");
-          ok(inputEventCount, 3, "Testing moving the mouse outside the spin buttons stops the spin input events");
+          is(input.value, "3", "Testing moving the mouse outside the spin buttons stops the spin");
+          is(inputEventCount, 3, "Testing moving the mouse outside the spin buttons stops the spin input events");
           input.removeEventListener("input", eventHandler, false);
           synthesizeMouse(input, -1, -1, { type: "mouseup" });
           runNextSpinTest();
         }, SETTIMEOUT_DELAY);
       }
     }, false);
     synthesizeMouse(input, SPIN_UP_X, SPIN_UP_Y, { type: "mousedown" });
   },
@@ -172,18 +172,18 @@ var spinTests = [
     var inputEventCount = 0;
     input.value = 0;
     input.addEventListener("input", function(evt) {
       ++inputEventCount;
       if (inputEventCount == 3) {
         input.type = "text"
         var eventHandler = arguments.callee;
         setTimeout(function() {
-          ok(input.value, 3, "Testing changing input type during a spin stops the spin");
-          ok(inputEventCount, 3, "Testing changing input type during a spin stops the spin input events");
+          is(input.value, "-3", "Testing changing input type during a spin stops the spin");
+          is(inputEventCount, 3, "Testing changing input type during a spin stops the spin input events");
           input.removeEventListener("input", eventHandler, false);
           synthesizeMouse(input, SPIN_DOWN_X, SPIN_DOWN_Y, { type: "mouseup" });
           input.type = "number"; // restore
           runNextSpinTest();
         }, SETTIMEOUT_DELAY);
       }
     }, false);
     synthesizeMouse(input, SPIN_DOWN_X, SPIN_DOWN_Y, { type: "mousedown" });
--- a/dom/html/test/forms/test_input_range_key_events.html
+++ b/dom/html/test/forms/test_input_range_key_events.html
@@ -153,56 +153,56 @@ function test() {
     elem.step = "2";
     elem.style.direction = dir;
     var flush = document.body.clientWidth;
 
     // Start at middle:
     elem.value = oldVal = defaultValue(elem);
     expectedVal = expectedFunc(elem);
     synthesizeKey(key, {});
-    is(elem.value, expectedVal, "Test " + key + " for " + dir + " range with value set to the midpoint (" + oldVal + ")");
+    is(elem.value, String(expectedVal), "Test " + key + " for " + dir + " range with value set to the midpoint (" + oldVal + ")");
 
     // Same again:
     expectedVal = expectedFunc(elem);
     synthesizeKey(key, {});
-    is(elem.value, expectedVal, "Test repeat of " + key + " for " + dir + " range");
+    is(elem.value, String(expectedVal), "Test repeat of " + key + " for " + dir + " range");
 
     // Start at maximum:
     elem.value = oldVal = maximum(elem);
     expectedVal = expectedFunc(elem);
     synthesizeKey(key, {});
-    is(elem.value, expectedVal, "Test " + key + " for " + dir + " range with value set to the maximum (" + oldVal + ")");
+    is(elem.value, String(expectedVal), "Test " + key + " for " + dir + " range with value set to the maximum (" + oldVal + ")");
 
     // Same again:
     expectedVal = expectedFunc(elem);
     synthesizeKey(key, {});
-    is(elem.value, expectedVal, "Test repeat of " + key + " for " + dir + " range");
+    is(elem.value, String(expectedVal), "Test repeat of " + key + " for " + dir + " range");
 
     // Start at minimum:
     elem.value = oldVal = minimum(elem);
     expectedVal = expectedFunc(elem);
     synthesizeKey(key, {});
-    is(elem.value, expectedVal, "Test " + key + " for " + dir + " range with value set to the minimum (" + oldVal + ")");
+    is(elem.value, String(expectedVal), "Test " + key + " for " + dir + " range with value set to the minimum (" + oldVal + ")");
 
     // Same again:
     expectedVal = expectedFunc(elem);
     synthesizeKey(key, {});
-    is(elem.value, expectedVal, "Test repeat of " + key + " for " + dir + " range");
+    is(elem.value, String(expectedVal), "Test repeat of " + key + " for " + dir + " range");
 
     // Test for a step value that is greater than 10% of the range:
     elem.step = 20;
     elem.value = 60;
     expectedVal = expectedFunc(elem);
     synthesizeKey(key, {});
-    is(elem.value, expectedVal, "Test " + key + " for " + dir + " range with a step that is greater than 10% of the range (step=" + elem.step + ")");
+    is(elem.value, String(expectedVal), "Test " + key + " for " + dir + " range with a step that is greater than 10% of the range (step=" + elem.step + ")");
 
     // Same again:
     expectedVal = expectedFunc(elem);
     synthesizeKey(key, {});
-    is(elem.value, expectedVal, "Test repeat of " + key + " for " + dir + " range");
+    is(elem.value, String(expectedVal), "Test repeat of " + key + " for " + dir + " range");
 
     // reset step:
     elem.step = 2;
   }
 }
 
 </script>
 </pre>
--- a/dom/html/test/forms/test_input_range_mouse_and_touch_events.html
+++ b/dom/html/test/forms/test_input_range_mouse_and_touch_events.html
@@ -38,21 +38,21 @@ https://bugzilla.mozilla.org/show_bug.cg
  **/
 SimpleTest.waitForExplicitFinish();
 SimpleTest.waitForFocus(function() {
   test(synthesizeMouse, "click", "mousedown", "mousemove", "mouseup");
   test(synthesizeTouch, "tap", "touchstart", "touchmove", "touchend");
   SimpleTest.finish();
 });
 
-const MIDDLE_OF_RANGE = 50;
-const MINIMUM_OF_RANGE = 0;
-const MAXIMUM_OF_RANGE = 100;
-const QUARTER_OF_RANGE = 25;
-const THREE_QUARTERS_OF_RANGE = 75;
+const MIDDLE_OF_RANGE = "50";
+const MINIMUM_OF_RANGE = "0";
+const MAXIMUM_OF_RANGE = "100";
+const QUARTER_OF_RANGE = "25";
+const THREE_QUARTERS_OF_RANGE = "75";
 
 function flush() {
   // Flush style, specifically to flush the 'direction' property so that the
   // browser uses the new value for thumb positioning.
   var flush = document.body.clientWidth;
 }
 
 function test(synthesizeFunc, clickOrTap, startName, moveName, endName) {
--- a/dom/html/test/forms/test_label_input_controls.html
+++ b/dom/html/test/forms/test_label_input_controls.html
@@ -42,26 +42,26 @@ https://bugzilla.mozilla.org/show_bug.cg
       }
 
       function test(type, isLabelable) {
         inputH.type = type;
         if (isLabelable) {
           testControl(label,     inputH, type, true);
           testControl(labelForH, inputH, type, true);
         } else {
-          testControl(label,     inputI,    type, false);
-          testControl(labelForH, undefined, type, false);
+          testControl(label,     inputI, type, false);
+          testControl(labelForH, null,   type, false);
 
           inputH.type = "text";
           testControl(label,     inputH, "text", true);
           testControl(labelForH, inputH, "text", true);
 
           inputH.type = type;
-          testControl(label,     inputI,    type, false);
-          testControl(labelForH, undefined, type, false);
+          testControl(label,     inputI, type, false);
+          testControl(labelForH, null,   type, false);
 
           label.removeChild(inputH);
           testControl(label, inputI, "text", true);
 
           var element = document.createElement('input');
           element.type = type;
           label.insertBefore(element, inputI);
           testControl(label, inputI, "text", true);
--- a/dom/html/test/forms/test_meter_element.html
+++ b/dom/html/test/forms/test_meter_element.html
@@ -26,17 +26,17 @@ https://bugzilla.mozilla.org/show_bug.cg
 function checkFormIDLAttribute(aElement)
 {
   is('form' in aElement, false, "<meter> shouldn't have a form attribute");
 }
 
 function checkAttribute(aElement, aAttribute, aNewValue, aExpectedValueForIDL)
 {
   var expectedValueForIDL = aNewValue;
-  var expectedValueForContent = aNewValue;
+  var expectedValueForContent = String(aNewValue);
 
   if (aExpectedValueForIDL !== undefined) {
     expectedValueForIDL = aExpectedValueForIDL;
   }
 
   if (aNewValue != null) {
     aElement.setAttribute(aAttribute, aNewValue);
     is(aElement.getAttribute(aAttribute), expectedValueForContent,
@@ -48,18 +48,18 @@ function checkAttribute(aElement, aAttri
       aElement[aAttribute] = aNewValue;
       is(aElement.getAttribute(aAttribute), expectedValueForContent,
          aAttribute + " content attribute should be " + expectedValueForContent);
       is(aElement[aAttribute], parseFloat(expectedValueForIDL),
          aAttribute + " IDL attribute should be " + parseFloat(expectedValueForIDL));
     }
   } else {
     aElement.removeAttribute(aAttribute);
-    is(aElement.getAttribute(aAttribute), expectedValueForContent,
-       aAttribute + " content attribute should be " + expectedValueForContent);
+    is(aElement.getAttribute(aAttribute), null,
+       aAttribute + " content attribute should be null");
     is(aElement[aAttribute], expectedValueForIDL,
        aAttribute + " IDL attribute should be " + expectedValueForIDL);
   }
 }
 
 function checkValueAttribute()
 {
   var tests = [
--- a/dom/html/test/forms/test_output_element.html
+++ b/dom/html/test/forms/test_output_element.html
@@ -101,22 +101,22 @@ function checkDescendantChanged(element)
 function checkFormIDLAttribute(element)
 {
   is(element.form, document.getElementById('f'),
     "form IDL attribute is invalid");
 }
 
 function checkHtmlForIDLAttribute(element)
 {
-  is(element.htmlFor, 'a b',
+  is(String(element.htmlFor), 'a b',
     "htmlFor IDL attribute should reflect the for content attribute");
 
   // DOMSettableTokenList is tested in another bug so we just test assignation
   element.htmlFor.value = 'a b c';
-  is(element.htmlFor, 'a b c', "htmlFor should have changed");
+  is(String(element.htmlFor), 'a b c', "htmlFor should have changed");
 }
 
 function submitForm()
 {
   // Setting the values for the submit.
   document.getElementById('o').value = 'foo';
   document.getElementById('a').value = 'afield';
   document.getElementById('b').value = 'bfield';
--- a/dom/html/test/forms/test_progress_element.html
+++ b/dom/html/test/forms/test_progress_element.html
@@ -30,17 +30,17 @@ SimpleTest.expectAssertions(0, 1);
 function checkFormIDLAttribute(aElement)
 {
   is("form" in aElement, false, "<progress> shouldn't have a form attribute");
 }
 
 function checkAttribute(aElement, aAttribute, aNewValue, aExpectedValueForIDL)
 {
   var expectedValueForIDL = aNewValue;
-  var expectedValueForContent = aNewValue;
+  var expectedValueForContent = String(aNewValue);
 
   if (aExpectedValueForIDL !== undefined) {
     expectedValueForIDL = aExpectedValueForIDL;
   }
 
   if (aNewValue != null) {
     aElement.setAttribute(aAttribute, aNewValue);
     is(aElement.getAttribute(aAttribute), expectedValueForContent,
@@ -52,18 +52,18 @@ function checkAttribute(aElement, aAttri
       aElement[aAttribute] = aNewValue;
       is(aElement.getAttribute(aAttribute), expectedValueForContent,
          aAttribute + " content attribute should be " + expectedValueForContent);
       is(aElement[aAttribute], parseFloat(expectedValueForIDL),
          aAttribute + " IDL attribute should be " + parseFloat(expectedValueForIDL));
     }
   } else {
     aElement.removeAttribute(aAttribute);
-    is(aElement.getAttribute(aAttribute), expectedValueForContent,
-       aAttribute + " content attribute should be " + expectedValueForContent);
+    is(aElement.getAttribute(aAttribute), null,
+       aAttribute + " content attribute should be null");
     is(aElement[aAttribute], expectedValueForIDL,
        aAttribute + " IDL attribute should be " + expectedValueForIDL);
   }
 }
 
 function checkValueAttribute()
 {
   var tests = [
--- a/dom/html/test/forms/test_step_attribute.html
+++ b/dom/html/test/forms/test_step_attribute.html
@@ -418,208 +418,208 @@ for (var test of data) {
       break;
     case 'range':
       // Range is special in that it clamps to valid values, so it is much
       // rarer for it to be invalid.
 
       // When step=0, the allowed value step is 1.
       input.step = '0';
       input.value = '1.2';
-      is(input.value, 1, "check that the value changes to the nearest valid step, choosing the higher step if both are equally close");
+      is(input.value, '1', "check that the value changes to the nearest valid step, choosing the higher step if both are equally close");
       checkValidity(input, true, apply);
 
       input.value = '1';
-      is(input.value, 1, "check that the value coincides with a step");
+      is(input.value, '1', "check that the value coincides with a step");
       checkValidity(input, true, apply);
 
       input.value = '0';
-      is(input.value, 0, "check that the value coincides with a step");
+      is(input.value, '0', "check that the value coincides with a step");
       checkValidity(input, true, apply);
 
       // When step is NaN, the allowed step value is 1.
       input.step = 'foo';
       input.value = '1';
-      is(input.value, 1, "check that the value coincides with a step");
+      is(input.value, '1', "check that the value coincides with a step");
       checkValidity(input, true, apply);
 
       input.value = '1.5';
-      is(input.value, 2, "check that the value changes to the nearest valid step, choosing the higher step if both are equally close");
+      is(input.value, '2', "check that the value changes to the nearest valid step, choosing the higher step if both are equally close");
       checkValidity(input, true, apply);
 
       // When step is negative, the allowed step value is 1.
       input.step = '-0.1';
-      is(input.value, 2, "check that the value still coincides with a step");
+      is(input.value, '2', "check that the value still coincides with a step");
       checkValidity(input, true, apply);
 
       input.value = '1';
-      is(input.value, 1, "check that the value coincides with a step");
+      is(input.value, '1', "check that the value coincides with a step");
       checkValidity(input, true, apply);
 
       // When step is missing, the allowed step value is 1.
       input.removeAttribute('step');
       input.value = '1.5';
-      is(input.value, 2, "check that the value changes to the nearest valid step, choosing the higher step if both are equally close");
+      is(input.value, '2', "check that the value changes to the nearest valid step, choosing the higher step if both are equally close");
       checkValidity(input, true, apply);
 
       input.value = '1';
-      is(input.value, 1, "check that the value coincides with a step");
+      is(input.value, '1', "check that the value coincides with a step");
       checkValidity(input, true, apply);
 
       // When step is 'any', all values are fine wrt to step.
       input.step = 'any';
       checkValidity(input, true, apply);
 
       input.step = 'aNy';
       input.value = '97';
-      is(input.value, 97, "check that the value for step=aNy is unchanged");
+      is(input.value, '97', "check that the value for step=aNy is unchanged");
       checkValidity(input, true, apply);
 
       input.step = 'AnY';
       input.value = '0.1';
-      is(input.value, 0.1, "check that a positive fractional value with step=AnY is unchanged");
+      is(input.value, '0.1', "check that a positive fractional value with step=AnY is unchanged");
       checkValidity(input, true, apply);
 
       input.step = 'ANY';
       input.min = -100;
       input.value = '-13.37';
-      is(input.value, -13.37, "check that a negative fractional value with step=ANY is unchanged");
+      is(input.value, '-13.37', "check that a negative fractional value with step=ANY is unchanged");
       checkValidity(input, true, apply);
 
       // When min is set to a valid float, there is a step base.
       input.min = '1'; // the step base
       input.step = '2';
       input.value = '3';
-      is(input.value, 3, "check that the value coincides with a step");
+      is(input.value, '3', "check that the value coincides with a step");
       checkValidity(input, true, apply);
 
       input.value = '2';
-      is(input.value, 3, "check that the value changes to the nearest valid step, choosing the higher step if both are equally close");
+      is(input.value, '3', "check that the value changes to the nearest valid step, choosing the higher step if both are equally close");
       checkValidity(input, true, apply);
 
       input.value = '1.99';
-      is(input.value, 1, "check that the value changes to the nearest valid step, choosing the higher step if both are equally close");
+      is(input.value, '1', "check that the value changes to the nearest valid step, choosing the higher step if both are equally close");
       checkValidity(input, true, apply);
 
       input.removeAttribute('step'); // step = 1
       input.min = '0.5'; // step base
       input.value = '5.5';
-      is(input.value, 5.5, "check that the value coincides with a step");
+      is(input.value, '5.5', "check that the value coincides with a step");
       checkValidity(input, true, apply);
 
       input.value = '1';
-      is(input.value, 1.5, "check that the value changes to the nearest valid step, choosing the higher step if both are equally close");
+      is(input.value, '1.5', "check that the value changes to the nearest valid step, choosing the higher step if both are equally close");
       checkValidity(input, true, apply);
 
       input.min = '-0.1'; // step base
       input.step = '1';
       input.value = '0.9';
-      is(input.value, 0.9, "the value should be a valid step");
+      is(input.value, '0.9', "the value should be a valid step");
       checkValidity(input, true, apply);
 
       input.value = '0.1';
-      is(input.value, -0.1, "check that the value changes to the nearest valid step, choosing the higher step if both are equally close");
+      is(input.value, '-0.1', "check that the value changes to the nearest valid step, choosing the higher step if both are equally close");
       checkValidity(input, true, apply);
 
       // When min is set to NaN, the step base is the value.
       input.min = 'foo';
       input.step = '1';
       input.value = '1';
-      is(input.value, 1, "check that the value coincides with a step");
+      is(input.value, '1', "check that the value coincides with a step");
       checkValidity(input, true, apply);
 
       input.value = '0.5';
-      is(input.value, 1, "check that the value changes to the nearest valid step, choosing the higher step if both are equally close");
+      is(input.value, '1', "check that the value changes to the nearest valid step, choosing the higher step if both are equally close");
       checkValidity(input, true, apply);
 
       input.min = '';
       input.value = '1';
-      is(input.value, 1, "check that the value coincides with a step");
+      is(input.value, '1', "check that the value coincides with a step");
       checkValidity(input, true, apply);
 
       input.value = '0.5';
-      is(input.value, 1, "check that the value changes to the nearest valid step, choosing the higher step if both are equally close");
+      is(input.value, '1', "check that the value changes to the nearest valid step, choosing the higher step if both are equally close");
       checkValidity(input, true, apply);
 
       input.removeAttribute('min');
 
       // Test when the value isn't a number
       input.value = '';
-      is(input.value, 50, "value be should default to the value midway between the minimum (0) and the maximum (100)");
+      is(input.value, '50', "value be should default to the value midway between the minimum (0) and the maximum (100)");
       checkValidity(input, true, apply);
 
       // Regular situations.
       input.step = '2';
       input.value = '1.5';
-      is(input.value, 2, "check that the value changes to the nearest valid step, choosing the higher step if both are equally close");
+      is(input.value, '2', "check that the value changes to the nearest valid step, choosing the higher step if both are equally close");
       checkValidity(input, true, apply);
 
       input.value = '42.0';
-      is(input.value, 42, "check that the value coincides with a step");
+      is(input.value, '42.0', "check that the value coincides with a step");
       checkValidity(input, true, apply);
 
       input.step = '0.1';
       input.value = '-0.1';
-      is(input.value, 0, "check that the value changes to the nearest valid step, choosing the higher step if both are equally close");
+      is(input.value, '0', "check that the value changes to the nearest valid step, choosing the higher step if both are equally close");
       checkValidity(input, true, apply);
 
       input.step = '2';
       input.removeAttribute('min');
       input.max = '10';
       input.value = '-9';
-      is(input.value, 0, "check the value is clamped to the minimum's default of zero");
+      is(input.value, '0', "check the value is clamped to the minimum's default of zero");
       checkValidity(input, true, apply);
 
       // If @value is defined but not @min, the step base is @value.
       input = getFreshElement(test.type);
       input.setAttribute('value', '1');
       input.step = 2;
-      is(input.value, 1, "check that the value changes to the nearest valid step, choosing the higher step if both are equally close");
+      is(input.value, '1', "check that the value changes to the nearest valid step, choosing the higher step if both are equally close");
       checkValidity(input, true, apply);
 
       input.value = 3;
-      is(input.value, 3, "check that the value coincides with a step");
+      is(input.value, '3', "check that the value coincides with a step");
       checkValidity(input, true, apply);
 
       input.value = 2;
-      is(input.value, 3, "check that the value changes to the nearest valid step, choosing the higher step if both are equally close");
+      is(input.value, '3', "check that the value changes to the nearest valid step, choosing the higher step if both are equally close");
       checkValidity(input, true, apply);
 
       // Should also work with defaultValue.
       input = getFreshElement(test.type);
       input.defaultValue = 1;
       input.step = 2;
-      is(input.value, 1, "check that the value coincides with a step");
+      is(input.value, '1', "check that the value coincides with a step");
       checkValidity(input, true, apply);
 
       input.value = 3;
-      is(input.value, 3, "check that the value coincides with a step");
+      is(input.value, '3', "check that the value coincides with a step");
       checkValidity(input, true, apply);
 
       input.value = 2;
-      is(input.value, 3, "check that the value changes to the nearest valid step, choosing the higher step if both are equally close");
+      is(input.value, '3', "check that the value changes to the nearest valid step, choosing the higher step if both are equally close");
       checkValidity(input, true, apply);
 
       // Check contrived error case where there are no valid steps in range:
       // No @min, so the step base is the default minimum, zero, the valid
       // range is 0-1, -1 gets clamped to zero.
       input = getFreshElement(test.type);
       input.step = '3';
       input.max = '1';
       input.defaultValue = '-1';
-      is(input.value, 0, "the value should have been clamped to the default minimum, zero");
+      is(input.value, '0', "the value should have been clamped to the default minimum, zero");
       checkValidity(input, false, apply, {low: -1, high: -1});
 
       // Check that when the closest of the two steps that the value is between
       // is greater than the maximum we sanitize to the lower step.
       input = getFreshElement(test.type);
       input.step = '2';
       input.min = '1';
       input.max = '10.9';
       input.value = '10.8'; // closest step in 11, but 11 > maximum
-      is(input.value, 9, "check that the value coincides with a step");
+      is(input.value, '9', "check that the value coincides with a step");
 
       // The way that step base is defined, the converse (the value not being
       // on a step, and the nearest step being a value that would be underflow)
       // is not possible, so nothing to test there.
 
       is(input.validationMessage, "",
          "The validation message should be empty.");
       break;
--- a/dom/html/test/forms/test_valueasdate_attribute.html
+++ b/dom/html/test/forms/test_valueasdate_attribute.html
@@ -180,19 +180,25 @@ function checkDateGet()
     element.value = data[0];
     is(element.valueAsDate.valueOf(), data[1],
        "valueAsDate should return the " +
        "valid date object representing this date");
   }
 
   for (data of invalidData) {
     element.value = data[0];
-    is(element.valueAsDate, data[1] ? "Invalid Date" : null,
-       "valueAsDate should return null "  +
-       "when the element value is not a valid date");
+    if (data[1]) {
+      is(String(element.valueAsDate), "Invalid Date",
+         "valueAsDate should return an invalid Date object "  +
+         "when the element value is not a valid date");
+    } else {
+      is(element.valueAsDate, null,
+         "valueAsDate should return null "  +
+         "when the element value is not a valid date");
+    }
   }
 
 }
 
 function checkDateSet()
 {
   var testData =
   [
--- a/dom/html/test/forms/test_valueasnumber_attribute.html
+++ b/dom/html/test/forms/test_valueasnumber_attribute.html
@@ -145,21 +145,21 @@ function checkNumberSet()
     [123.123456789123, "123.123456789123"], // double precision
     [1e2, "100"], // e should be usable
     [2e1, "20"],
     [1e-1, "0.1"], // value after e can be negative
     [1E2, "100"], // E can be used instead of e
     // Setting a string will set NaN.
     ["foo", ""],
     // "" is converted to 0.
-    ["", 0],
+    ["", "0"],
     [42, "42"], // Keep this here, it is used by the next test.
     // Setting Infinity should throw and not change the current value.
-    [Infinity, 42, true],
-    [-Infinity, 42, true],
+    [Infinity, "42", true],
+    [-Infinity, "42", true],
     // Setting NaN should change the value to the empty string.
     [NaN, ""],
   ];
 
   var element = document.createElement('input');
   element.type = "number";
   for (data of testData) {
     var caught = false;
@@ -220,17 +220,17 @@ function checkRangeGet()
        "floating point representation of the value");
   }
 }
 
 function checkRangeSet()
 {
   var min = -200;
   var max = 200;
-  var defaultValue = min + (max - min)/2;
+  var defaultValue = String(min + (max - min)/2);
 
   var testData =
   [
     [42, "42"],
     [-42, "-42"], // should work for negative values
     [42.1234, "42.1234"],
     [123.123456789123, "123.123456789123"], // double precision
     [1e2, "100"], // e should be usable
--- a/dom/html/test/test_bug1045270.html
+++ b/dom/html/test/test_bug1045270.html
@@ -29,17 +29,17 @@ https://bugzilla.mozilla.org/show_bug.cg
         document.body.style.display = "none";
         document.body.style.display = "";
         document.body.offsetLeft; // flush
       }, false);
       synthesizeKey("1", {});
       SimpleTest.executeSoon(function() {
         synthesizeKey("2", {});
         SimpleTest.executeSoon(function() {
-          is(input.value, 12, "Reframe should restore focus and selection properly");
+          is(input.value, "12", "Reframe should restore focus and selection properly");
           SimpleTest.finish();
         });
       });
     });
 
     </script>
     </pre>
   </body>
--- a/dom/html/test/test_bug332893-1.html
+++ b/dom/html/test/test_bug332893-1.html
@@ -20,19 +20,19 @@
       var form2 = document.getElementById("form2");
 	var newInput = document.createElement("input");
 	newInput.value = "13";
       form1.insertBefore(newInput, form1.firstChild);     
 	var F2I2 = document.getElementById("F2I2");
      	form2.insertBefore(newInput, F2I2);
 	form1.insertBefore(newInput, form1.firstChild);
 
-	is(form1.elements.length, "3", "Form 1 has the correct length");
+	is(form1.elements.length, 3, "Form 1 has the correct length");
 	is(form1.elements[0].value, "13", "Form 1 element 1 is correct");
 	is(form1.elements[1].value, "11", "Form 1 element 2 is correct");
 	is(form1.elements[2].value, "12", "Form 1 element 3 is correct");
 
-	is(form2.elements.length, "2", "Form 2 has the correct length");
+	is(form2.elements.length, 2, "Form 2 has the correct length");
 	is(form2.elements[0].value, "21", "Form 2 element 1 is correct");
 	is(form2.elements[1].value, "22", "Form 2 element 2 is correct");
 </script>
 </body>
 </html>
--- a/dom/html/test/test_bug332893-3.html
+++ b/dom/html/test/test_bug332893-3.html
@@ -40,19 +40,19 @@
 			  
 	var table2 = document.getElementById("table2");
 	table2.insertBefore(F1I0, table2.firstChild);
 	table2.insertBefore(F1I1, table2.firstChild);
               
 	var form1 = document.getElementById("form1");
 	var form2 = document.getElementById("form2");
 
-	is(form1.elements.length, "2", "Form 1 has the correct length");
+	is(form1.elements.length, 2, "Form 1 has the correct length");
 	is(form1.elements[0].value, "12", "Form 1 element 1 is correct");
 	is(form1.elements[1].value, "10", "Form 1 element 2 is correct");
 
-	is(form2.elements.length, "3", "Form 2 has the correct length");
+	is(form2.elements.length, 3, "Form 2 has the correct length");
 	is(form2.elements[0].value, "11", "Form 2 element 1 is correct");
 	is(form2.elements[1].value, "21", "Form 2 element 2 is correct");
 	is(form2.elements[2].value, "22", "Form 2 element 2 is correct");
 </script>
 </body>
 </html>
--- a/dom/html/test/test_bug332893-4.html
+++ b/dom/html/test/test_bug332893-4.html
@@ -11,17 +11,17 @@
    <input id="input3" type="input" name="input" value="3"/>
 </form>
 <script>
 	var input1 = document.getElementById("input1");
       var input2 = document.getElementById("input2");
       var form1 = document.getElementById("form1");
       form1.insertBefore(input2, input1);
 
-	is(form1.elements["input"].length, "3", "Form 1 'input' has the correct length");
+	is(form1.elements["input"].length, 3, "Form 1 'input' has the correct length");
 	is(form1.elements["input"][0].value, "2", "Form 1 element 1 is correct");
 	is(form1.elements["input"][1].value, "1", "Form 1 element 2 is correct");
 	is(form1.elements["input"][2].value, "3", "Form 1 element 3 is correct");
 
 	is(form1.elements["input"][0].id, "input2", "Form 1 element 1 id is correct");
 	is(form1.elements["input"][1].id, "input1", "Form 1 element 2 id is correct");
 	is(form1.elements["input"][2].id, "input3", "Form 1 element 3 id is correct");
 </script>
--- a/dom/html/test/test_bug332893-5.html
+++ b/dom/html/test/test_bug332893-5.html
@@ -11,17 +11,17 @@
    <input id="input3" type="input" name="input" value="3"/>
 </form>
 <script>
 	var input1 = document.getElementById("input1");
       var input2 = document.getElementById("input");
       var form1 = document.getElementById("form1");
       form1.insertBefore(input2, input1);
 
-	is(form1.elements["input"].length, "3", "Form 1 'input' has the correct length");
+	is(form1.elements["input"].length, 3, "Form 1 'input' has the correct length");
 	is(form1.elements["input"][0].value, "2", "Form 1 element 1 is correct");
 	is(form1.elements["input"][1].value, "1", "Form 1 element 2 is correct");
 	is(form1.elements["input"][2].value, "3", "Form 1 element 3 is correct");
 
 	is(form1.elements["input"][0].id, "input", "Form 1 element 1 id is correct");
 	is(form1.elements["input"][1].id, "input1", "Form 1 element 2 id is correct");
 	is(form1.elements["input"][2].id, "input3", "Form 1 element 3 id is correct");
 </script>
--- a/dom/html/test/test_bug332893-6.html
+++ b/dom/html/test/test_bug332893-6.html
@@ -11,17 +11,17 @@
    <input id="input3" type="input" name="input" value="3"/>
 </form>
 <script>
 	var input1 = document.getElementById("input1");
       var input2 = document.getElementById("input");
       var form1 = document.getElementById("form1");
       form1.insertBefore(input2, input1);
 
-	is(form1.elements["input"].length, "3", "Form 1 'input' has the correct length");
+	is(form1.elements["input"].length, 3, "Form 1 'input' has the correct length");
 	is(form1.elements["input"][0].value, "2", "Form 1 element 1 is correct");
 	is(form1.elements["input"][1].value, "1", "Form 1 element 2 is correct");
 
 	is(form1.elements["input"][0].id, "input", "Form 1 element 1 id is correct");
 	is(form1.elements["input"][1].id, "input1", "Form 1 element 2 id is correct");
 </script>
 </body>
 </html>
--- a/dom/html/test/test_bug332893-7.html
+++ b/dom/html/test/test_bug332893-7.html
@@ -36,17 +36,17 @@
 
 	var form1 = document.getElementById("form1");
 
 	form1.insertBefore(input2, input1);
 	form1.insertBefore(input10, input6);
 	form1.insertBefore(input8, input4);
 	form1.insertBefore(input9, input2);
 
-	is(form1.elements["input"].length, "10", "Form 1 'input' has the correct length");
+	is(form1.elements["input"].length, 10, "Form 1 'input' has the correct length");
 	is(form1.elements["input"][0].value, "9", "Form 1 element 1 is correct");
 	is(form1.elements["input"][1].value, "2", "Form 1 element 2 is correct");
 	is(form1.elements["input"][2].value, "1", "Form 1 element 3 is correct");
 	is(form1.elements["input"][3].value, "3", "Form 1 element 4 is correct");
 	is(form1.elements["input"][4].value, "8", "Form 1 element 5 is correct");
 	is(form1.elements["input"][5].value, "4", "Form 1 element 6 is correct");
 	is(form1.elements["input"][6].value, "5", "Form 1 element 7 is correct");
 	is(form1.elements["input"][7].value, "10", "Form 1 element 8 is correct");
--- a/dom/html/test/test_bug405242.html
+++ b/dom/html/test/test_bug405242.html
@@ -23,13 +23,13 @@ sel.appendChild(new Option());
 sel.appendChild(new Option());
 opt = new Option();
 opt.value = 10;
 sel.appendChild(opt);
 sel.options.remove(0);
 sel.options.remove(1000);
 sel.options.remove(-1);
 is(sel.length, 1, "Unexpected option collection length");
-is(sel[0].value, 10, "Unexpected remained option");
+is(sel[0].value, "10", "Unexpected remained option");
 </script>
 </pre>
 </body>
 </html>
--- a/dom/html/test/test_bug536891.html
+++ b/dom/html/test/test_bug536891.html
@@ -23,17 +23,17 @@ https://bugzilla.mozilla.org/show_bug.cg
 
 function checkNegativeMaxLength(element)
 {
   /* maxLength is set to -2 initially in the document, see above */
   is(element.maxLength, -1, "negative maxLength should be considered invalid and represented as -1");
 
   element.setAttribute('maxLength', -15);
   is(element.maxLength, -1, "negative maxLength is not processed correctly when set dynamically");
-  is(element.getAttribute('maxLength'), -15, "maxLength attribute doesn't return the correct value");
+  is(element.getAttribute('maxLength'), "-15", "maxLength attribute doesn't return the correct value");
 
   element.setAttribute('maxLength', 0);
   is(element.maxLength, 0, "negative maxLength is not processed correctly");
   element.setAttribute('maxLength', 2147483647); /* PR_INT32_MAX */
   is(element.maxLength, 2147483647, "negative maxLength is not processed correctly");
   element.setAttribute('maxLength', -2147483648); /* PR_INT32_MIN */
   is(element.maxLength, -1, "negative maxLength is not processed correctly");
   element.setAttribute('maxLength', 'non-numerical-value');
--- a/dom/html/test/test_bug536895.html
+++ b/dom/html/test/test_bug536895.html
@@ -35,17 +35,17 @@ function checkNegativeMaxLengthException
   try {
     element.maxLength = -20;
   } catch(e) {
     is(e.name, "IndexSizeError", "Should be an IndexSizeError exception");
     caught = true;
   }
   ok(caught, "Setting negative maxLength from the DOM should throw an exception");
 
-  is(element.getAttribute('maxLength'), -10, "When the exception is raised, the maxLength attribute shouldn't change");
+  is(element.getAttribute('maxLength'), "-10", "When the exception is raised, the maxLength attribute shouldn't change");
 }
 
 /* TODO: correct behavior may be checked for email, telephone, url and search input types */
 checkNegativeMaxLengthException(document.getElementById('t'));
 checkNegativeMaxLengthException(document.getElementById('i'));
 checkNegativeMaxLengthException(document.getElementById('p'));
 
 </script>
--- a/dom/html/test/test_bug551846.html
+++ b/dom/html/test/test_bug551846.html
@@ -27,53 +27,53 @@ https://bugzilla.mozilla.org/show_bug.cg
 
 function checkSizeReflection(element, defaultValue)
 {
   is(element.size, defaultValue, "Default size should be " + defaultValue);
 
   element.setAttribute('size', -15);
   is(element.size, defaultValue,
     "The reflecting IDL attribute should return the default value when content attribute value is invalid");
-  is(element.getAttribute('size'), -15,
+  is(element.getAttribute('size'), "-15",
     "The content attribute should containt the previously set value");
 
   element.setAttribute('size', 0);
   is(element.size, 0,
     "0 should be considered as a valid value");
-  is(element.getAttribute('size'), 0,
+  is(element.getAttribute('size'), "0",
     "The content attribute should containt the previously set value");
 
   element.setAttribute('size', 2147483647); /* PR_INT32_MAX */
   is(element.size, 2147483647,
     "PR_INT32_MAX should be considered as a valid value");
-  is(element.getAttribute('size'), 2147483647,
+  is(element.getAttribute('size'), "2147483647",
     "The content attribute should containt the previously set value");
 
   element.setAttribute('size', -2147483648); /* PR_INT32_MIN */
   is(element.size, defaultValue,
     "The reflecting IDL attribute should return the default value when content attribute value is invalid");
-  is(element.getAttribute('size'), -2147483648,
+  is(element.getAttribute('size'), "-2147483648",
     "The content attribute should containt the previously set value");
 
   element.setAttribute('size', 'non-numerical-value');
   is(element.size, defaultValue,
     "The reflecting IDL attribute should return the default value when content attribute value is invalid");
   is(element.getAttribute('size'), 'non-numerical-value',
     "The content attribute should containt the previously set value");
 
   element.setAttribute('size', 4294967294); /* PR_INT32_MAX * 2 */
   is(element.size, defaultValue,
     "Value greater than PR_INT32_MAX should be considered as invalid");
-  is(element.getAttribute('size'), 4294967294,
+  is(element.getAttribute('size'), "4294967294",
     "The content attribute should containt the previously set value");
 
   element.setAttribute('size', -4294967296); /* PR_INT32_MIN * 2 */
   is(element.size, defaultValue,
     "The reflecting IDL attribute should return the default value when content attribute value is invalid");
-  is(element.getAttribute('size'), -4294967296,
+  is(element.getAttribute('size'), "-4294967296",
     "The content attribute should containt the previously set value");
 
   element.size = defaultValue + 1;
   element.removeAttribute('size');
   is(element.size, defaultValue,
     "When the attribute is removed, the size should be the default size");
 
   element.setAttribute('size', 'foobar');
@@ -130,25 +130,25 @@ function checkSetSizeException(element)
 function checkSizeWhenChangeMultiple(element, aDefaultNonMultiple, aDefaultMultiple)
 {
   s.setAttribute('size', -1)
   is(s.size, aDefaultNonMultiple, "Size IDL attribute should be 1");
 
   s.multiple = true;
   is(s.size, aDefaultMultiple, "Size IDL attribute should be 4");
 
-  is(s.getAttribute('size'), -1, "Size content attribute should be -1");
+  is(s.getAttribute('size'), "-1", "Size content attribute should be -1");
 
   s.setAttribute('size', -2);
   is(s.size, aDefaultMultiple, "Size IDL attribute should be 4");
 
   s.multiple = false;
   is(s.size, aDefaultNonMultiple, "Size IDL attribute should be 1");
 
-  is(s.getAttribute('size'), -2, "Size content attribute should be -2");
+  is(s.getAttribute('size'), "-2", "Size content attribute should be -2");
 }
 
 var s = document.getElementById('s');
 
 checkSizeReflection(s, 0);
 checkSetSizeException(s);
 
 s.setAttribute('multiple', 'true');
--- a/dom/html/test/test_bug596350.html
+++ b/dom/html/test/test_bug596350.html
@@ -21,28 +21,28 @@ https://bugzilla.mozilla.org/show_bug.cg
 
 /** Test for Bug 596350 **/
 
 SimpleTest.waitForExplicitFinish();
 addLoadEvent(runTests);
 
 var testData = [
 // Object 0
-  [ 0, null, "-1" ],
-  [ 0, "1", "1" ],
-  [ 0, "-1", "-1" ],
-  [ 0, "0", "0" ],
-  [ 0, "foo", "-1" ],
+  [ 0, null, -1 ],
+  [ 0, "1", 1 ],
+  [ 0, "-1", -1 ],
+  [ 0, "0", 0 ],
+  [ 0, "foo", -1 ],
 // Object 1
-  [ 1, null, "-1" ],
-  [ 1, "1", "1" ],
+  [ 1, null, -1 ],
+  [ 1, "1", 1 ],
 // Object 2
-  [ 2, null, "0" ],
-  [ 2, "1", "1" ],
-  [ 2, "-1", "-1" ],
+  [ 2, null, 0 ],
+  [ 2, "1", 1 ],
+  [ 2, "-1", -1 ],
 ];
 
 var objects = document.getElementsByTagName("object");
 
 function runTests()
 {
   for (var data of testData) {
     var obj = objects[data[0]];
index 076507c998f2cd35b91651d80bb34a5938553590..b662103cdb249ebed87efc908c12efd4b8259159
GIT binary patch
literal 2704
zc$~dfVQ<qg5WUa+6<!3aLnG0IK(Mt;jCJC}CMInN@oDmslm^-~YSN8?_M7<&ygS>8
z(`MR02t`THj_=*``Q4>7pba{t5A=@C=`)?uh!nkteL~0dhI%xh2~{+ssU2<5Gh98H
zt*D@qb}7PjNlW@dKPbb>DQODfGjEOOw7`l89uwHtbYXo$it)^7A9q4SdPzI<0@rKY
zJ&ERcq`*mJq;L%oAq58wJH}fL{8DCy6acxRF??UaZagZ)sK6)1+&P{lMhfego1z2Z
zqgo6GVC;K2q!KX~m@fpOq8nJLMAonhtj@4Pi|?FrpnS#X3b*eWz_-LG7Eg|3IF+!^
zux2JmY0b5JHUxGFf5UC{TF=c#cplZc8OWJ5oSHrI9&-A2FNVUK6svi@lw3@O8J}2c
zcj;${cllj*6Ju33^NC=NADow}&V|E_tZt%~at2V{9QKVJY2l_tn{IEcOx7&#Jy_xL
ze{|31a?--NTb<hOq`*6a6yTX5ULl@0UImzQHF}@HHWf6Hj0|bh=3uiUC;m9_B8_FF
zu@|Rwp3PyMfiLUk6mQuttUF1YkKx2hE2L_Dt!&m>VNN-aW_CW2+R`%5RW*gLd86qU
zES``4AkIW~lwg#v#kyG0|3wEnpM=Y6dp^Vb!lL=OcjvUK>(cjiYA&qBXME*(#ghF>
zcHlaTtHpIq><Sn5NWJqg5e%|Z)k1hT+)SMX_Wi%=gN)Y0+o!nH1ADWQ)sc91_r+_(
z;of~DC?hDzF*38PhD<86Pg}GnNLqAo3=g(O;A%432bU$RM(OOM=%>vc<MiP%$BLz#
zSZ@1~(T=H+6xNxX_%?i+KDvXQ9Uxj@c`)K5;2AY+Ngv6<-LO)~u)2p&!>JN=m%_!@
z$S2u<B3&sIZ2zRfy6<hT_2=scdWQ0AG#A6_dkS)W&_7YXN32Kv`|G}rv|sm=A@0Zc
zH}o9Ow>X)Uo^;gzu=V}h>fjn68u!&D&=dKFXi<`qGgwGoVo|t@tmKQ6(CXJ#*)NAf
zy0j=$(KhQWk;<Fb@7mn<JZ{}pGVZ7YUVSU|&?s?il~{tyC2%sS3bXzezrD`?5~gP@
Nybjj>0!RKo%-^#ErUL)~
--- a/dom/html/test/test_documentAll.html
+++ b/dom/html/test/test_documentAll.html
@@ -26,17 +26,18 @@ Tests for document.all
         style="display: none"></iframe>
 <pre id="test">
 <script class="testbody" type="text/javascript">
 
 p = document.getElementById("content");
 
 // Test that several elements with the same id or name behave correctly
 function testNumSame() {
-  is(document.all.id0, null, "no ids");
+  is(document.all.id0, undefined, "no ids");
+  is(document.all.namedItem("id0"), null, "no ids");
   is(document.all.id1, p.children[0], "one id");
   is(document.all.id2[0], p.children[1], "two ids");
   is(document.all.id2[1], p.children[2], "two ids");
   is(document.all.id2.length, 2, "two length");
   is(document.all.id3[0], p.children[3], "three ids");
   is(document.all.id3[1], p.children[4], "three ids");
   is(document.all.id3[2], p.children[5], "three ids");
   is(document.all.id3.length, 3, "three length");
@@ -74,17 +75,18 @@ is(document.all.id1.length, 3, "now thre
 is(document.all.id3[1], child[5], "now just two ids");
 is(document.all.id3.length, 2, "now two length");
 
 // Remove all elements from a list and check that it goes empty
 id3list = document.all.id3;
 rC(child[3]);
 is(id3list.length, 1, "now one length");
 rC(child[5]);
-is(document.all.id3, null, "now none");
+is(document.all.id3, undefined, "now none");
+is(document.all.namedItem("id3"), null, "now none (namedItem)");
 is(id3list.length, 0, "now none length");
 
 // Give an element both a name and id and check that it appears in two lists
 p.insertBefore(child[1], child[2]); // restore previously removed
 id1list = document.all.id1;
 id2list = document.all.id2;
 child[1].id = "id1";
 is(id1list[0], child[0], "now four ids");
@@ -130,17 +132,18 @@ elementNames.forEach(function (name) {
   p.appendChild(e);
   e.setAttribute('name', nameval);
 
   if (name == hasName[0]) {
     is(document.all[nameval], e, "should have name");
     hasName.shift();
   }
   else {
-    is(document.all[nameval], null, "shouldn't have name");
+    is(document.all[nameval], undefined, "shouldn't have name");
+    is(document.all.namedItem(nameval), null, "shouldn't have name (namedItem)");
   }
 });
 is(hasName.length, 0, "found all names");
 
 SimpleTest.waitForExplicitFinish();
 addLoadEvent(function() {
   var subdoc = $("subframe").contentDocument;
   is(subdoc.all.x, subdoc.body.firstChild,
--- a/dom/indexedDB/ActorsParent.cpp
+++ b/dom/indexedDB/ActorsParent.cpp
@@ -24,16 +24,17 @@
 #include "mozilla/Maybe.h"
 #include "mozilla/Preferences.h"
 #include "mozilla/Services.h"
 #include "mozilla/StaticPtr.h"
 #include "mozilla/storage.h"
 #include "mozilla/unused.h"
 #include "mozilla/dom/ContentParent.h"
 #include "mozilla/dom/File.h"
+#include "mozilla/dom/FileService.h"
 #include "mozilla/dom/StructuredCloneTags.h"
 #include "mozilla/dom/TabParent.h"
 #include "mozilla/dom/indexedDB/PBackgroundIDBCursorParent.h"
 #include "mozilla/dom/indexedDB/PBackgroundIDBDatabaseParent.h"
 #include "mozilla/dom/indexedDB/PBackgroundIDBDatabaseFileParent.h"
 #include "mozilla/dom/indexedDB/PBackgroundIDBFactoryParent.h"
 #include "mozilla/dom/indexedDB/PBackgroundIDBFactoryRequestParent.h"
 #include "mozilla/dom/indexedDB/PBackgroundIDBRequestParent.h"
@@ -5447,16 +5448,76 @@ private:
                                    PBackgroundIDBFactoryRequestParent* aRequest)
                                    override;
 
   virtual bool
   DeallocPBackgroundIDBDatabaseParent(PBackgroundIDBDatabaseParent* aActor)
                                       override;
 };
 
+class WaitForTransactionsHelper final
+  : public nsRunnable
+{
+  nsCOMPtr<nsIEventTarget> mOwningThread;
+  const nsCString mDatabaseId;
+  nsCOMPtr<nsIRunnable> mCallback;
+
+  enum
+  {
+    State_Initial = 0,
+    State_WaitingForTransactions,
+    State_DispatchToMainThread,
+    State_WaitingForFileHandles,
+    State_DispatchToOwningThread,
+    State_Complete
+  } mState;
+
+public:
+  WaitForTransactionsHelper(const nsCString& aDatabaseId,
+                            nsIRunnable* aCallback)
+    : mOwningThread(NS_GetCurrentThread())
+    , mDatabaseId(aDatabaseId)
+    , mCallback(aCallback)
+    , mState(State_Initial)
+  {
+    AssertIsOnBackgroundThread();
+    MOZ_ASSERT(!aDatabaseId.IsEmpty());
+    MOZ_ASSERT(aCallback);
+  }
+
+  void
+  WaitForTransactions();
+
+  NS_DECL_ISUPPORTS_INHERITED
+
+private:
+  ~WaitForTransactionsHelper()
+  {
+    MOZ_ASSERT(!mCallback);
+    MOZ_ASSERT(mState = State_Complete);
+  }
+
+  void
+  MaybeWaitForTransactions();
+
+  void
+  DispatchToMainThread();
+
+  void
+  MaybeWaitForFileHandles();
+
+  void
+  DispatchToOwningThread();
+
+  void
+  CallCallback();
+
+  NS_DECL_NSIRUNNABLE
+};
+
 class Database final
   : public PBackgroundIDBDatabaseParent
 {
   friend class VersionChangeTransaction;
 
   class StartTransactionOp;
 
 private:
@@ -5466,16 +5527,17 @@ private:
   nsRefPtr<DatabaseOfflineStorage> mOfflineStorage;
   nsTHashtable<nsPtrHashKey<TransactionBase>> mTransactions;
   nsRefPtr<DatabaseConnection> mConnection;
   const PrincipalInfo mPrincipalInfo;
   const nsCString mGroup;
   const nsCString mOrigin;
   const nsCString mId;
   const nsString mFilePath;
+  uint32_t mFileHandleCount;
   const PersistenceType mPersistenceType;
   const bool mChromeWriteAccessAllowed;
   bool mClosed;
   bool mInvalidated;
   bool mActorWasAlive;
   bool mActorDestroyed;
   bool mMetadataCleanedUp;
 
@@ -5646,17 +5708,17 @@ private:
     MOZ_ASSERT(mClosed);
     MOZ_ASSERT_IF(mActorWasAlive, mActorDestroyed);
   }
 
   bool
   CloseInternal();
 
   void
-  CloseConnection();
+  MaybeCloseConnection();
 
   void
   ConnectionClosedCallback();
 
   void
   CleanupMetadata();
 
   // IPDL methods are only called by IPDL.
@@ -5706,16 +5768,22 @@ private:
   virtual bool
   RecvDeleteMe() override;
 
   virtual bool
   RecvBlocked() override;
 
   virtual bool
   RecvClose() override;
+
+  virtual bool
+  RecvNewFileHandle() override;
+
+  virtual bool
+  RecvFileHandleFinished() override;
 };
 
 class Database::StartTransactionOp final
   : public TransactionDatabaseOperationBase
 {
   friend class Database;
 
 private:
@@ -6329,21 +6397,21 @@ protected:
 
     // Waiting to do/doing work on the "work thread". This involves waiting for
     // the VersionChangeOp (OpenDatabaseOp and DeleteDatabaseOp each have a
     // different implementation) to do its work. Eventually the state will
     // transition to State_SendingResults.
     State_DatabaseWorkVersionChange,
 
     // Waiting to send/sending results on the PBackground thread. Next step is
-    // UnblockingQuotaManager.
+    // State_UnblockingQuotaManager.
     State_SendingResults,
 
     // Notifying the QuotaManager that it can proceed to the next operation on
-    // the main thread. Next step is Completed.
+    // the main thread. Next step is State_Completed.
     State_UnblockingQuotaManager,
 
     // All done.
     State_Completed
   };
 
   // Must be released on the background thread!
   nsRefPtr<Factory> mFactory;
@@ -7625,26 +7693,26 @@ public:
 private:
   ~NonMainThreadHackBlobImpl()
   { }
 };
 
 class QuotaClient final
   : public mozilla::dom::quota::Client
 {
-  class ShutdownTransactionThreadPoolRunnable;
-  friend class ShutdownTransactionThreadPoolRunnable;
+  class ShutdownWorkThreadsRunnable;
+  friend class ShutdownWorkThreadsRunnable;
 
   class WaitForTransactionsRunnable;
   friend class WaitForTransactionsRunnable;
 
   static QuotaClient* sInstance;
 
   nsCOMPtr<nsIEventTarget> mBackgroundThread;
-  nsRefPtr<ShutdownTransactionThreadPoolRunnable> mShutdownRunnable;
+  nsRefPtr<ShutdownWorkThreadsRunnable> mShutdownRunnable;
 
   bool mShutdownRequested;
 
 public:
   QuotaClient();
 
   static QuotaClient*
   GetInstance()
@@ -7705,65 +7773,59 @@ public:
   virtual void
   OnOriginClearCompleted(PersistenceType aPersistenceType,
                          const nsACString& aOrigin)
                          override;
 
   virtual void
   ReleaseIOThreadObjects() override;
 
-  virtual bool
-  IsFileServiceUtilized() override;
-
-  virtual bool
-  IsTransactionServiceActivated() override;
-
   virtual void
   WaitForStoragesToComplete(nsTArray<nsIOfflineStorage*>& aStorages,
                             nsIRunnable* aCallback) override;
 
   virtual void
-  ShutdownTransactionService() override;
+  ShutdownWorkThreads() override;
 
 private:
   ~QuotaClient();
 
   nsresult
   GetDirectory(PersistenceType aPersistenceType,
                const nsACString& aOrigin,
                nsIFile** aDirectory);
 
   nsresult
   GetUsageForDirectoryInternal(nsIFile* aDirectory,
                                UsageInfo* aUsageInfo,
                                bool aDatabaseFiles);
 };
 
-class QuotaClient::ShutdownTransactionThreadPoolRunnable final
+class QuotaClient::ShutdownWorkThreadsRunnable final
   : public nsRunnable
 {
   nsRefPtr<QuotaClient> mQuotaClient;
   bool mHasRequestedShutDown;
 
 public:
 
-  explicit ShutdownTransactionThreadPoolRunnable(QuotaClient* aQuotaClient)
+  explicit ShutdownWorkThreadsRunnable(QuotaClient* aQuotaClient)
     : mQuotaClient(aQuotaClient)
     , mHasRequestedShutDown(false)
   {
     MOZ_ASSERT(NS_IsMainThread());
     MOZ_ASSERT(aQuotaClient);
     MOZ_ASSERT(QuotaClient::GetInstance() == aQuotaClient);
     MOZ_ASSERT(aQuotaClient->mShutdownRequested);
   }
 
   NS_DECL_ISUPPORTS_INHERITED
 
 private:
-  ~ShutdownTransactionThreadPoolRunnable()
+  ~ShutdownWorkThreadsRunnable()
   {
     MOZ_ASSERT(!mQuotaClient);
   }
 
   NS_DECL_NSIRUNNABLE
 };
 
 class QuotaClient::WaitForTransactionsRunnable final
@@ -7772,17 +7834,18 @@ class QuotaClient::WaitForTransactionsRu
   nsRefPtr<QuotaClient> mQuotaClient;
   nsTArray<nsCString> mDatabaseIds;
   nsCOMPtr<nsIRunnable> mCallback;
 
   enum
   {
     State_Initial = 0,
     State_WaitingForTransactions,
-    State_CallingCallback,
+    State_DispatchToMainThread,
+    State_WaitingForFileHandles,
     State_Complete
   } mState;
 
 public:
   WaitForTransactionsRunnable(QuotaClient* aQuotaClient,
                               nsTArray<nsCString>& aDatabaseIds,
                               nsIRunnable* aCallback)
     : mQuotaClient(aQuotaClient)
@@ -7804,20 +7867,23 @@ private:
   ~WaitForTransactionsRunnable()
   {
     MOZ_ASSERT(!mQuotaClient);
     MOZ_ASSERT(!mCallback);
     MOZ_ASSERT(mState = State_Complete);
   }
 
   void
-  MaybeWait();
-
-  void
-  SendToMainThread();
+  MaybeWaitForTransactions();
+
+  void
+  DispatchToMainThread();
+
+  void
+  MaybeWaitForFileHandles();
 
   void
   CallCallback();
 
   NS_DECL_NSIRUNNABLE
 };
 
 class DatabaseOfflineStorage final
@@ -11234,16 +11300,145 @@ Factory::DeallocPBackgroundIDBDatabasePa
   AssertIsOnBackgroundThread();
   MOZ_ASSERT(aActor);
 
   nsRefPtr<Database> database = dont_AddRef(static_cast<Database*>(aActor));
   return true;
 }
 
 /*******************************************************************************
+ * WaitForTransactionsHelper
+ ******************************************************************************/
+
+void
+WaitForTransactionsHelper::WaitForTransactions()
+{
+  MOZ_ASSERT(mState == State_Initial);
+
+  unused << this->Run();
+}
+
+void
+WaitForTransactionsHelper::MaybeWaitForTransactions()
+{
+  AssertIsOnBackgroundThread();
+  MOZ_ASSERT(mState == State_Initial);
+
+  nsRefPtr<ConnectionPool> connectionPool = gConnectionPool.get();
+  if (connectionPool) {
+    nsTArray<nsCString> ids(1);
+    ids.AppendElement(mDatabaseId);
+
+    mState = State_WaitingForTransactions;
+
+    connectionPool->WaitForDatabasesToComplete(Move(ids), this);
+    return;
+  }
+
+  DispatchToMainThread();
+}
+
+void
+WaitForTransactionsHelper::DispatchToMainThread()
+{
+  AssertIsOnBackgroundThread();
+  MOZ_ASSERT(mState == State_Initial || mState == State_WaitingForTransactions);
+
+  mState = State_DispatchToMainThread;
+
+  MOZ_ALWAYS_TRUE(NS_SUCCEEDED(NS_DispatchToMainThread(this)));
+}
+
+void
+WaitForTransactionsHelper::MaybeWaitForFileHandles()
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  MOZ_ASSERT(mState == State_DispatchToMainThread);
+
+  FileService* service = FileService::Get();
+  if (service) {
+    nsTArray<nsCString> ids(1);
+    ids.AppendElement(mDatabaseId);
+
+    mState = State_WaitingForFileHandles;
+
+    service->WaitForStoragesToComplete(ids, this);
+
+    MOZ_ASSERT(ids.IsEmpty());
+    return;
+  }
+
+  DispatchToOwningThread();
+}
+
+void
+WaitForTransactionsHelper::DispatchToOwningThread()
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  MOZ_ASSERT(mState == State_DispatchToMainThread ||
+             mState == State_WaitingForFileHandles);
+
+  mState = State_DispatchToOwningThread;
+
+  MOZ_ALWAYS_TRUE(NS_SUCCEEDED(mOwningThread->Dispatch(this,
+                                                       NS_DISPATCH_NORMAL)));
+}
+
+void
+WaitForTransactionsHelper::CallCallback()
+{
+  AssertIsOnBackgroundThread();
+  MOZ_ASSERT(mState == State_DispatchToOwningThread);
+
+  nsCOMPtr<nsIRunnable> callback;
+  mCallback.swap(callback);
+
+  callback->Run();
+
+  mState = State_Complete;
+}
+
+NS_IMPL_ISUPPORTS_INHERITED0(WaitForTransactionsHelper,
+                             nsRunnable)
+
+NS_IMETHODIMP
+WaitForTransactionsHelper::Run()
+{
+  MOZ_ASSERT(mState != State_Complete);
+  MOZ_ASSERT(mCallback);
+
+  switch (mState) {
+    case State_Initial:
+      MaybeWaitForTransactions();
+      break;
+
+    case State_WaitingForTransactions:
+      DispatchToMainThread();
+      break;
+
+    case State_DispatchToMainThread:
+      MaybeWaitForFileHandles();
+      break;
+
+    case State_WaitingForFileHandles:
+      DispatchToOwningThread();
+      break;
+
+    case State_DispatchToOwningThread:
+      CallCallback();
+      break;
+
+    default:
+      MOZ_CRASH("Should never get here!");
+  }
+
+  return NS_OK;
+}
+
+/*******************************************************************************
  * Database
  ******************************************************************************/
 
 Database::Database(Factory* aFactory,
                    const PrincipalInfo& aPrincipalInfo,
                    const nsACString& aGroup,
                    const nsACString& aOrigin,
                    FullDatabaseMetadata* aMetadata,
@@ -11254,16 +11449,17 @@ Database::Database(Factory* aFactory,
   , mMetadata(aMetadata)
   , mFileManager(aFileManager)
   , mOfflineStorage(Move(aOfflineStorage))
   , mPrincipalInfo(aPrincipalInfo)
   , mGroup(aGroup)
   , mOrigin(aOrigin)
   , mId(aMetadata->mDatabaseId)
   , mFilePath(aMetadata->mFilePath)
+  , mFileHandleCount(0)
   , mPersistenceType(aMetadata->mCommonMetadata.persistenceType())
   , mChromeWriteAccessAllowed(aChromeWriteAccessAllowed)
   , mClosed(false)
   , mInvalidated(false)
   , mActorWasAlive(false)
   , mActorDestroyed(false)
   , mMetadataCleanedUp(false)
 {
@@ -11399,19 +11595,17 @@ void
 Database::UnregisterTransaction(TransactionBase* aTransaction)
 {
   AssertIsOnBackgroundThread();
   MOZ_ASSERT(aTransaction);
   MOZ_ASSERT(mTransactions.GetEntry(aTransaction));
 
   mTransactions.RemoveEntry(aTransaction);
 
-  if (!mTransactions.Count() && IsClosed() && mOfflineStorage) {
-    CloseConnection();
-  }
+  MaybeCloseConnection();
 }
 
 void
 Database::SetActorAlive()
 {
   AssertIsOnBackgroundThread();
   MOZ_ASSERT(!mActorWasAlive);
   MOZ_ASSERT(!mActorDestroyed);
@@ -11450,50 +11644,48 @@ Database::CloseInternal()
   MOZ_ASSERT(info->mLiveDatabases.Contains(this));
 
   if (info->mWaitingFactoryOp) {
     info->mWaitingFactoryOp->NoteDatabaseClosed(this);
   }
 
   if (mOfflineStorage) {
     mOfflineStorage->CloseOnOwningThread();
-
-    if (!mTransactions.Count()) {
-      CloseConnection();
-    }
-  }
-
-  return true;
-}
-
-void
-Database::CloseConnection()
-{
-  AssertIsOnBackgroundThread();
-  MOZ_ASSERT(mClosed);
-  MOZ_ASSERT(!mTransactions.Count());
-
-  if (gConnectionPool) {
-    nsTArray<nsCString> ids(1);
-    ids.AppendElement(Id());
-
+  }
+
+  MaybeCloseConnection();
+
+  return true;
+}
+
+void
+Database::MaybeCloseConnection()
+{
+  AssertIsOnBackgroundThread();
+
+  if (!mTransactions.Count() &&
+      !mFileHandleCount &&
+      IsClosed() &&
+      mOfflineStorage) {
     nsCOMPtr<nsIRunnable> callback =
       NS_NewRunnableMethod(this, &Database::ConnectionClosedCallback);
-    gConnectionPool->WaitForDatabasesToComplete(Move(ids), callback);
-  } else {
-    ConnectionClosedCallback();
+
+    nsRefPtr<WaitForTransactionsHelper> helper =
+      new WaitForTransactionsHelper(Id(), callback);
+    helper->WaitForTransactions();
   }
 }
 
 void
 Database::ConnectionClosedCallback()
 {
   AssertIsOnBackgroundThread();
   MOZ_ASSERT(mClosed);
   MOZ_ASSERT(!mTransactions.Count());
+  MOZ_ASSERT(!mFileHandleCount);
 
   if (mOfflineStorage) {
     DatabaseOfflineStorage::UnregisterOnOwningThread(mOfflineStorage.forget());
   }
 
   CleanupMetadata();
 }
 
@@ -11799,16 +11991,53 @@ Database::RecvClose()
   if (NS_WARN_IF(!CloseInternal())) {
     ASSERT_UNLESS_FUZZING();
     return false;
   }
 
   return true;
 }
 
+bool
+Database::RecvNewFileHandle()
+{
+  AssertIsOnBackgroundThread();
+
+  if (!mOfflineStorage) {
+    ASSERT_UNLESS_FUZZING();
+    return false;
+  }
+
+  if (mFileHandleCount == UINT32_MAX) {
+    ASSERT_UNLESS_FUZZING();
+    return false;
+  }
+
+  ++mFileHandleCount;
+
+  return true;
+}
+
+bool
+Database::RecvFileHandleFinished()
+{
+  AssertIsOnBackgroundThread();
+
+  if (mFileHandleCount == 0) {
+    ASSERT_UNLESS_FUZZING();
+    return false;
+  }
+
+  --mFileHandleCount;
+
+  MaybeCloseConnection();
+
+  return true;
+}
+
 void
 Database::
 StartTransactionOp::RunOnConnectionThread()
 {
   MOZ_ASSERT(!IsOnBackgroundThread());
   MOZ_ASSERT(Transaction());
   MOZ_ASSERT(NS_SUCCEEDED(mResultCode));
 
@@ -14756,32 +14985,16 @@ QuotaClient::ReleaseIOThreadObjects()
 {
   AssertIsOnIOThread();
 
   if (IndexedDatabaseManager* mgr = IndexedDatabaseManager::Get()) {
     mgr->InvalidateAllFileManagers();
   }
 }
 
-bool
-QuotaClient::IsFileServiceUtilized()
-{
-  MOZ_ASSERT(NS_IsMainThread());
-
-  return true;
-}
-
-bool
-QuotaClient::IsTransactionServiceActivated()
-{
-  MOZ_ASSERT(NS_IsMainThread());
-
-  return true;
-}
-
 void
 QuotaClient::WaitForStoragesToComplete(nsTArray<nsIOfflineStorage*>& aStorages,
                                        nsIRunnable* aCallback)
 {
   MOZ_ASSERT(NS_IsMainThread());
   MOZ_ASSERT(!aStorages.IsEmpty());
   MOZ_ASSERT(aCallback);
 
@@ -14822,42 +15035,44 @@ QuotaClient::WaitForStoragesToComplete(n
 
   nsCOMPtr<nsIRunnable> runnable =
     new WaitForTransactionsRunnable(this, databaseIds, aCallback);
   MOZ_ALWAYS_TRUE(NS_SUCCEEDED(
     backgroundThread->Dispatch(runnable, NS_DISPATCH_NORMAL)));
 }
 
 void
-QuotaClient::ShutdownTransactionService()
+QuotaClient::ShutdownWorkThreads()
 {
   MOZ_ASSERT(NS_IsMainThread());
   MOZ_ASSERT(!mShutdownRunnable);
   MOZ_ASSERT(!mShutdownRequested);
 
   mShutdownRequested = true;
 
   if (mBackgroundThread) {
-    nsRefPtr<ShutdownTransactionThreadPoolRunnable> runnable =
-      new ShutdownTransactionThreadPoolRunnable(this);
+    nsRefPtr<ShutdownWorkThreadsRunnable> runnable =
+      new ShutdownWorkThreadsRunnable(this);
 
     if (NS_FAILED(mBackgroundThread->Dispatch(runnable, NS_DISPATCH_NORMAL))) {
       // This can happen if the thread has shut down already.
       return;
     }
 
     nsIThread* currentThread = NS_GetCurrentThread();
     MOZ_ASSERT(currentThread);
 
     mShutdownRunnable.swap(runnable);
 
     while (mShutdownRunnable) {
       MOZ_ALWAYS_TRUE(NS_ProcessNextEvent(currentThread));
     }
   }
+
+  FileService::Shutdown();
 }
 
 nsresult
 QuotaClient::GetDirectory(PersistenceType aPersistenceType,
                           const nsACString& aOrigin, nsIFile** aDirectory)
 {
   QuotaManager* quotaManager = QuotaManager::Get();
   NS_ASSERTION(quotaManager, "This should never fail!");
@@ -14972,53 +15187,78 @@ QuotaClient::GetUsageForDirectoryInterna
   }
 
   return NS_OK;
 }
 
 
 void
 QuotaClient::
-WaitForTransactionsRunnable::MaybeWait()
+WaitForTransactionsRunnable::MaybeWaitForTransactions()
 {
   AssertIsOnBackgroundThread();
   MOZ_ASSERT(mState == State_Initial);
   MOZ_ASSERT(mQuotaClient);
 
   nsRefPtr<ConnectionPool> connectionPool = gConnectionPool.get();
   if (connectionPool) {
+    // Have to copy here in case the file service needs a list too.
+    nsTArray<nsCString> databaseIds(mDatabaseIds);
+
     mState = State_WaitingForTransactions;
 
-    connectionPool->WaitForDatabasesToComplete(Move(mDatabaseIds), this);
+    connectionPool->WaitForDatabasesToComplete(Move(databaseIds), this);
+    return;
+  }
+
+  DispatchToMainThread();
+}
+
+void
+QuotaClient::
+WaitForTransactionsRunnable::DispatchToMainThread()
+{
+  AssertIsOnBackgroundThread();
+  MOZ_ASSERT(mState == State_Initial || mState == State_WaitingForTransactions);
+
+  mState = State_DispatchToMainThread;
+
+  MOZ_ALWAYS_TRUE(NS_SUCCEEDED(NS_DispatchToMainThread(this)));
+}
+
+void
+QuotaClient::
+WaitForTransactionsRunnable::MaybeWaitForFileHandles()
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  MOZ_ASSERT(mState == State_DispatchToMainThread);
+
+  FileService* service = FileService::Get();
+  if (service) {
+    mState = State_WaitingForFileHandles;
+
+    service->WaitForStoragesToComplete(mDatabaseIds, this);
+
+    MOZ_ASSERT(mDatabaseIds.IsEmpty());
     return;
   }
 
   mDatabaseIds.Clear();
 
-  SendToMainThread();
-}
-
-void
-QuotaClient::
-WaitForTransactionsRunnable::SendToMainThread()
-{
-  AssertIsOnBackgroundThread();
-  MOZ_ASSERT(mState == State_Initial || mState == State_WaitingForTransactions);
-
-  mState = State_CallingCallback;
-
-  MOZ_ALWAYS_TRUE(NS_SUCCEEDED(NS_DispatchToMainThread(this)));
+  mState = State_WaitingForFileHandles;
+
+  CallCallback();
 }
 
 void
 QuotaClient::
 WaitForTransactionsRunnable::CallCallback()
 {
   MOZ_ASSERT(NS_IsMainThread());
-  MOZ_ASSERT(mState == State_CallingCallback);
+  MOZ_ASSERT(mState == State_WaitingForFileHandles);
 
   nsRefPtr<QuotaClient> quotaClient;
   mQuotaClient.swap(quotaClient);
 
   nsCOMPtr<nsIRunnable> callback;
   mCallback.swap(callback);
 
   callback->Run();
@@ -15033,40 +15273,44 @@ NS_IMETHODIMP
 QuotaClient::
 WaitForTransactionsRunnable::Run()
 {
   MOZ_ASSERT(mState != State_Complete);
   MOZ_ASSERT(mCallback);
 
   switch (mState) {
     case State_Initial:
-      MaybeWait();
+      MaybeWaitForTransactions();
       break;
 
     case State_WaitingForTransactions:
-      SendToMainThread();
-      break;
-
-    case State_CallingCallback:
+      DispatchToMainThread();
+      break;
+
+    case State_DispatchToMainThread:
+      MaybeWaitForFileHandles();
+      break;
+
+    case State_WaitingForFileHandles:
       CallCallback();
       break;
 
     default:
       MOZ_CRASH("Should never get here!");
   }
 
   return NS_OK;
 }
 
-NS_IMPL_ISUPPORTS_INHERITED0(QuotaClient::ShutdownTransactionThreadPoolRunnable,
+NS_IMPL_ISUPPORTS_INHERITED0(QuotaClient::ShutdownWorkThreadsRunnable,
                              nsRunnable)
 
 NS_IMETHODIMP
 QuotaClient::
-ShutdownTransactionThreadPoolRunnable::Run()
+ShutdownWorkThreadsRunnable::Run()
 {
   if (NS_IsMainThread()) {
     MOZ_ASSERT(mHasRequestedShutDown);
     MOZ_ASSERT(QuotaClient::GetInstance() == mQuotaClient);
     MOZ_ASSERT(mQuotaClient->mShutdownRunnable == this);
 
     mQuotaClient->mShutdownRunnable = nullptr;
     mQuotaClient = nullptr;
@@ -16341,26 +16585,21 @@ FactoryOp::WaitForTransactions()
   MOZ_ASSERT(mState == State_BeginVersionChange ||
              mState == State_WaitingForOtherDatabasesToClose);
   MOZ_ASSERT(!mDatabaseId.IsEmpty());
   MOZ_ASSERT(!IsActorDestroyed());
 
   nsTArray<nsCString> databaseIds;
   databaseIds.AppendElement(mDatabaseId);
 
-  nsRefPtr<ConnectionPool> connectionPool = gConnectionPool.get();
-  MOZ_ASSERT(connectionPool);
-
-  // WaitForDatabasesToComplete() will run this op immediately if there are no
-  // transactions blocking it, so be sure to set the next state here before
-  // calling it.
   mState = State_WaitingForTransactionsToComplete;
 
-  connectionPool->WaitForDatabasesToComplete(Move(databaseIds), this);
-  return;
+  nsRefPtr<WaitForTransactionsHelper> helper =
+    new WaitForTransactionsHelper(mDatabaseId, this);
+  helper->WaitForTransactions();
 }
 
 void
 FactoryOp::FinishSendResults()
 {
   AssertIsOnOwningThread();
   MOZ_ASSERT(mState == State_SendingResults);
   MOZ_ASSERT(mFactory);
@@ -17638,27 +17877,22 @@ OpenDatabaseOp::SendResults()
 
     unused <<
       PBackgroundIDBFactoryRequestParent::Send__delete__(this, response);
   }
 
   if (NS_FAILED(mResultCode) && mOfflineStorage) {
     mOfflineStorage->CloseOnOwningThread();
 
-    if (gConnectionPool) {
-      nsTArray<nsCString> ids(1);
-      ids.AppendElement(mDatabaseId);
-
-      nsCOMPtr<nsIRunnable> callback =
-        NS_NewRunnableMethod(this, &OpenDatabaseOp::ConnectionClosedCallback);
-
-      gConnectionPool->WaitForDatabasesToComplete(Move(ids), callback);
-    } else {
-      ConnectionClosedCallback();
-    }
+    nsCOMPtr<nsIRunnable> callback =
+      NS_NewRunnableMethod(this, &OpenDatabaseOp::ConnectionClosedCallback);
+
+    nsRefPtr<WaitForTransactionsHelper> helper =
+      new WaitForTransactionsHelper(mDatabaseId, callback);
+    helper->WaitForTransactions();
   }
 
   // Make sure to release the database on this thread.
   nsRefPtr<Database> database;
   mDatabase.swap(database);
 
   FinishSendResults();
 }
--- a/dom/indexedDB/IDBDatabase.cpp
+++ b/dom/indexedDB/IDBDatabase.cpp
@@ -1279,16 +1279,36 @@ IDBDatabase::NoteFinishedMutableFile(IDB
 
   // It's ok if this is called more than once, so don't assert that aMutableFile
   // is in the list already.
 
   mLiveMutableFiles.RemoveElement(aMutableFile);
 }
 
 void
+IDBDatabase::OnNewFileHandle()
+{
+  MOZ_ASSERT(IndexedDatabaseManager::IsMainProcess());
+  MOZ_ASSERT(NS_IsMainThread());
+  MOZ_ASSERT(mBackgroundActor);
+
+  mBackgroundActor->SendNewFileHandle();
+}
+
+void
+IDBDatabase::OnFileHandleFinished()
+{
+  MOZ_ASSERT(IndexedDatabaseManager::IsMainProcess());
+  MOZ_ASSERT(NS_IsMainThread());
+  MOZ_ASSERT(mBackgroundActor);
+
+  mBackgroundActor->SendFileHandleFinished();
+}
+
+void
 IDBDatabase::InvalidateMutableFiles()
 {
   if (!mLiveMutableFiles.IsEmpty()) {
     MOZ_ASSERT(IndexedDatabaseManager::IsMainProcess());
     MOZ_ASSERT(NS_IsMainThread());
 
     for (uint32_t count = mLiveMutableFiles.Length(), index = 0;
          index < count;
--- a/dom/indexedDB/IDBDatabase.h
+++ b/dom/indexedDB/IDBDatabase.h
@@ -195,16 +195,22 @@ public:
   GetQuotaInfo(nsACString& aOrigin, PersistenceType* aPersistenceType);
 
   void
   NoteLiveMutableFile(IDBMutableFile* aMutableFile);
 
   void
   NoteFinishedMutableFile(IDBMutableFile* aMutableFile);
 
+  void
+  OnNewFileHandle();
+
+  void
+  OnFileHandleFinished();
+
   nsPIDOMWindow*
   GetParentObject() const;
 
   already_AddRefed<DOMStringList>
   ObjectStoreNames() const;
 
   already_AddRefed<IDBObjectStore>
   CreateObjectStore(const nsAString& aName,
--- a/dom/indexedDB/IDBFileHandle.cpp
+++ b/dom/indexedDB/IDBFileHandle.cpp
@@ -68,16 +68,18 @@ IDBFileHandle::Create(FileMode aMode,
     return nullptr;
   }
 
   rv = service->Enqueue(fileHandle, nullptr);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return nullptr;
   }
 
+  aMutableFile->Database()->OnNewFileHandle();
+
   return fileHandle.forget();
 }
 
 mozilla::dom::MutableFileBase*
 IDBFileHandle::MutableFile() const
 {
   return mMutableFile;
 }
@@ -168,16 +170,18 @@ IDBFileHandle::OnCompleteOrAbort(bool aA
     return NS_ERROR_DOM_FILEHANDLE_UNKNOWN_ERR;
   }
 
   bool dummy;
   if (NS_FAILED(DispatchEvent(event, &dummy))) {
     NS_WARNING("Dispatch failed!");
   }
 
+  mMutableFile->Database()->OnFileHandleFinished();
+
   return NS_OK;
 }
 
 bool
 IDBFileHandle::CheckWindow()
 {
   return GetOwner();
 }
--- a/dom/indexedDB/PBackgroundIDBDatabase.ipdl
+++ b/dom/indexedDB/PBackgroundIDBDatabase.ipdl
@@ -39,16 +39,20 @@ protocol PBackgroundIDBDatabase
 
 parent:
   DeleteMe();
 
   Blocked();
 
   Close();
 
+  NewFileHandle();
+
+  FileHandleFinished();
+
   PBackgroundIDBDatabaseFile(PBlob blob);
 
   PBackgroundIDBTransaction(nsString[] objectStoreNames, Mode mode);
 
 child:
   __delete__();
 
   VersionChange(uint64_t oldVersion, NullableVersion newVersion);
--- a/dom/indexedDB/test/helpers.js
+++ b/dom/indexedDB/test/helpers.js
@@ -327,17 +327,17 @@ function scheduleGC()
 function workerScript() {
   "use strict";
 
   self.repr = function(_thing_) {
     if (typeof(_thing_) == "undefined") {
       return "undefined";
     }
 
-    if (o === null) {
+    if (_thing_ === null) {
       return "null";
     }
 
     let str;
 
     try {
       str = _thing_ + "";
     } catch (e) {
--- a/dom/indexedDB/test/test_persistenceType.html
+++ b/dom/indexedDB/test/test_persistenceType.html
@@ -66,17 +66,17 @@
 
         objectStore = db.transaction([objectStoreName], "readwrite")
                         .objectStore(objectStoreName);
 
         request = objectStore.get(data.key);
         request.onsuccess = grabEventAndContinueHandler;
         event = yield undefined;
 
-        is(event.target.result, null, "Got no data");
+        is(event.target.result, undefined, "Got no data");
 
         request = objectStore.add(data.value, data.key);
         request.onsuccess = grabEventAndContinueHandler;
         event = yield undefined;
 
         is(event.target.result, data.key, "Got correct key");
       }
 
--- a/dom/indexedDB/test/unit/test_deleteDatabase_interactions.js
+++ b/dom/indexedDB/test/unit/test_deleteDatabase_interactions.js
@@ -44,17 +44,17 @@ function testSteps()
 
   let openRequest = indexedDB.open(name, 1);
   openRequest.onerror = errorHandler;
   openRequest.onsuccess = unexpectedSuccessHandler;
 
   event = yield undefined;
   is(event.type, "success", "expect a success event");
   is(event.target, request, "event has right target");
-  is(event.target.result, null, "event should have no result");
+  is(event.target.result, undefined, "event should have no result");
 
   openRequest.onsuccess = grabEventAndContinueHandler;
 
   event = yield undefined;
   is(event.target.result.version, 1, "DB has proper version");
   is(event.target.result.objectStoreNames.length, 0, "DB should have no object stores");
 
   finishTest();
--- a/dom/indexedDB/test/unit/test_keys.js
+++ b/dom/indexedDB/test/unit/test_keys.js
@@ -197,17 +197,17 @@ function testSteps()
        "Read back key should cmp as equal");
     ok(compareKeys(cursor.key, keys[i]),
        "Read back key should actually be equal");
     is(cursor.value, i, "Stored with right value");
 
     cursor.continue();
   }
   event = yield undefined;
-  is(event.target.result, undefined, "no more results expected");
+  is(event.target.result, null, "no more results expected");
 
   var nan = 0/0;
   var invalidKeys = [
     nan,
     undefined,
     null,
     /x/,
     {},
--- a/dom/indexedDB/test/unit/test_multientry.js
+++ b/dom/indexedDB/test/unit/test_multientry.js
@@ -85,29 +85,29 @@ function testSteps()
     req.onsuccess = grabEventAndContinueHandler;
     for (let j = 0; j < test.indexes.length; ++j) {
       e = yield undefined;
       is(req.result.key, test.indexes[j].v, "found expected index key at index " + j + testName);
       is(req.result.primaryKey, test.indexes[j].k, "found expected index primary key at index " + j + testName);
       req.result.continue();
     }
     e = yield undefined;
-    is(req.result, undefined, "exhausted indexes");
+    ok(req.result == null, "exhausted indexes");
 
     let tempIndex = store.createIndex("temp index", "x", { multiEntry: true });
     req = tempIndex.openKeyCursor();
     req.onsuccess = grabEventAndContinueHandler;
     for (let j = 0; j < test.indexes.length; ++j) {
       e = yield undefined;
       is(req.result.key, test.indexes[j].v, "found expected temp index key at index " + j + testName);
       is(req.result.primaryKey, test.indexes[j].k, "found expected temp index primary key at index " + j + testName);
       req.result.continue();
     }
     e = yield undefined;
-    is(req.result, undefined, "exhausted temp index");
+    ok(req.result == null, "exhausted temp index");
     store.deleteIndex("temp index");
   }
 
   // Unique indexes
   tests =
     [{ add:     { x: 1, id: 1 },
        indexes:[{ v: 1, k: 1 }] },
      { add:     { x: [2, 3], id: 2 },
@@ -181,29 +181,29 @@ function testSteps()
     req.onsuccess = grabEventAndContinueHandler;
     for (let j = 0; j < indexes.length; ++j) {
       e = yield undefined;
       is(req.result.key, indexes[j].v, "found expected index key at index " + j + testName);
       is(req.result.primaryKey, indexes[j].k, "found expected index primary key at index " + j + testName);
       req.result.continue();
     }
     e = yield undefined;
-    is(req.result, undefined, "exhausted indexes");
+    ok(req.result == null, "exhausted indexes");
 
     let tempIndex = store.createIndex("temp index", "x", { multiEntry: true, unique: true });
     req = tempIndex.openKeyCursor();
     req.onsuccess = grabEventAndContinueHandler;
     for (let j = 0; j < indexes.length; ++j) {
       e = yield undefined;
       is(req.result.key, indexes[j].v, "found expected temp index key at index " + j + testName);
       is(req.result.primaryKey, indexes[j].k, "found expected temp index primary key at index " + j + testName);
       req.result.continue();
     }
     e = yield undefined;
-    is(req.result, undefined, "exhausted temp index");
+    ok(req.result == null, "exhausted temp index");
     store.deleteIndex("temp index");
   }
 
 
   openRequest.onsuccess = grabEventAndContinueHandler;
   yield undefined;
 
   let trans = db.transaction(["mystore"], "readwrite");
--- a/dom/indexedDB/test/unit/test_persistenceType.js
+++ b/dom/indexedDB/test/unit/test_persistenceType.js
@@ -46,17 +46,17 @@ function testSteps()
 
   objectStore = db.transaction([objectStoreName], "readwrite")
                   .objectStore(objectStoreName);
 
   request = objectStore.get(data.key);
   request.onsuccess = grabEventAndContinueHandler;
   event = yield undefined;
 
-  is(event.target.result, null, "Got no data");
+  is(event.target.result, undefined, "Got no data");
 
   request = objectStore.add(data.value, data.key);
   request.onsuccess = grabEventAndContinueHandler;
   event = yield undefined;
 
   is(event.target.result, data.key, "Got correct key");
 
   request = indexedDB.open(name, { version: version,
--- a/dom/indexedDB/test/unit/test_remove_objectStore.js
+++ b/dom/indexedDB/test/unit/test_remove_objectStore.js
@@ -68,17 +68,17 @@ function testSteps()
   is(db.objectStoreNames.length, 1, "Correct objectStoreNames list");
   is(db.objectStoreNames.item(0), objectStoreName, "Correct name");
   is(trans.objectStore(objectStoreName), objectStore, "Correct new objectStore");
   isnot(oldObjectStore, objectStore, "Old objectStore is not new objectStore");
 
   request = objectStore.openCursor();
   request.onerror = errorHandler;
   request.onsuccess = function(event) {
-    is(event.target.result, undefined, "ObjectStore shouldn't have any items");
+    is(event.target.result, null, "ObjectStore shouldn't have any items");
     testGenerator.send(event);
   }
   event = yield undefined;
 
   db.deleteObjectStore(objectStore.name);
   is(db.objectStoreNames.length, 0, "Correct objectStores list");
 
   continueToNextStep();
--- a/dom/indexedDB/test/unit/test_setVersion_abort.js
+++ b/dom/indexedDB/test/unit/test_setVersion_abort.js
@@ -41,17 +41,16 @@ function testSteps()
   } catch (e) {
     ok(true, "Expect an exception");
     is(e.name, "InvalidStateError", "Expect an InvalidStateError");
   }
 
   event = yield undefined;
   is(event.type, "abort", "Got transaction abort event");
   is(event.target, transaction, "Right target");
-  is(event.target.transaction, null, "No transaction");
 
   is(db.version, 0, "Correct version");
   is(db.objectStoreNames.length, 0, "Correct objectStoreNames length");
   is(objectStore.indexNames.length, 0, "Correct indexNames length");
 
   request.onerror = grabEventAndContinueHandler;
   request.onupgradeneeded = unexpectedSuccessHandler;
 
--- a/dom/indexedDB/test/unit/test_setVersion_events.js
+++ b/dom/indexedDB/test/unit/test_setVersion_events.js
@@ -30,17 +30,17 @@ function testSteps()
 
   let versionChangeEventCount = 0;
   let db1, db2, db3;
 
   db1 = event.target.result;
   db1.addEventListener("versionchange", function(event) {
     ok(true, "Got version change event");
     ok(event instanceof IDBVersionChangeEvent, "Event is of the right type");
-    is(event.target.source, null, "Correct source");
+    is("source" in event.target, false, "Correct source");
     is(event.target, db1, "Correct target");
     is(event.target.version, 1, "Correct db version");
     is(event.oldVersion, 1, "Correct event oldVersion");
     is(event.newVersion, 2, "Correct event newVersion");
     is(versionChangeEventCount++, 0, "Correct count");
     db1.close();
   }, false);
 
@@ -65,17 +65,17 @@ function testSteps()
 
   request.onupgradeneeded = errorHandler;
   request.onsuccess = grabEventAndContinueHandler;
   event = yield undefined;
 
   db2.addEventListener("versionchange", function(event) {
     ok(true, "Got version change event");
     ok(event instanceof IDBVersionChangeEvent, "Event is of the right type");
-    is(event.target.source, null, "Correct source");
+    is("source" in event.target, false, "Correct source");
     is(event.target, db2, "Correct target");
     is(event.target.version, 2, "Correct db version");
     is(event.oldVersion, 2, "Correct event oldVersion");
     is(event.newVersion, 3, "Correct event newVersion");
     is(versionChangeEventCount++, 1, "Correct count");
   }, false);
 
   // Test opening the existing version again
--- a/dom/interfaces/base/nsIContentPermissionPrompt.idl
+++ b/dom/interfaces/base/nsIContentPermissionPrompt.idl
@@ -28,21 +28,49 @@ interface nsIContentPermissionType : nsI
 
   /**
    * The array of available options.
    */
   readonly attribute nsIArray options; // ["choice1", "choice2"]
 };
 
 /**
+ *  Interface provides the callback type.
+ */
+[scriptable, uuid(5fb5bb60-7069-11e4-9803-0800200c9a66)]
+interface nsIContentPermissionRequestCallback : nsISupports {
+  /**
+   * The callback of the visibility result.
+   */
+  void notifyVisibility(in boolean isVisible);
+};
+
+/**
+ *  Interface provides the way to get the visibility and
+ *  the notification.
+ */
+[scriptable, uuid(f8577124-6a5f-486f-ae04-c5bcae911eb5)]
+interface nsIContentPermissionRequester : nsISupports {
+  /**
+   * The function to get the visibility.
+   */
+  void getVisibility(in nsIContentPermissionRequestCallback callback);
+
+  /**
+   * The callback to get the notification of visibility change.
+   */
+  attribute nsIContentPermissionRequestCallback onVisibilityChange;
+};
+
+/**
  * Interface allows access to a content to request
  * permission to perform a privileged operation such as
  * geolocation.
  */
-[scriptable, uuid(69a39d88-d1c4-4ba9-9b19-bafc7a1bb783)]
+[scriptable, uuid(408c8fcd-1420-4aff-94d8-39bf74d79219)]
 interface nsIContentPermissionRequest : nsISupports {
   /**
    *  The array will include the request types. Elements of this array are
    *  nsIContentPermissionType object.
    */
   readonly attribute nsIArray types;
 
   /*
@@ -55,16 +83,22 @@ interface nsIContentPermissionRequest : 
    *  originated in.  Typically the element will be non-null
    *  in when using out of process content.  window or
    *  element can be null but not both.
    */
   readonly attribute nsIDOMWindow window;
   readonly attribute nsIDOMElement element;
 
   /**
+   *  The requester to get the required information of
+   *  the window.
+   */
+  readonly attribute nsIContentPermissionRequester requester;
+
+  /**
    * allow or cancel the request
    */
 
   void cancel();
   void allow([optional] in jsval choices); // {"type1": "choice1", "type2": "choiceA"}
 };
 
 /**
--- a/dom/interfaces/base/nsIDOMWindowUtils.idl
+++ b/dom/interfaces/base/nsIDOMWindowUtils.idl
@@ -44,18 +44,19 @@ interface nsIDOMFile;
 interface nsIFile;
 interface nsIDOMClientRect;
 interface nsIURI;
 interface nsIDOMEventTarget;
 interface nsIRunnable;
 interface nsITranslationNodeList;
 interface nsIJSRAIIHelper;
 interface nsIContentPermissionRequest;
+interface nsIObserver;
 
-[scriptable, uuid(7f2f44ab-2857-4cc2-8c9d-3d9816f5a4d6)]
+[scriptable, uuid(7ecfd6e7-120a-4567-85f7-14277f4c6d9f)]
 interface nsIDOMWindowUtils : nsISupports {
 
   /**
    * Image animation mode of the window. When this attribute's value
    * is changed, the implementation should set all images in the window
    * to the given value. That is, when set to kDontAnimMode, all images
    * will stop animating. The attribute's value must be one of the
    * animationMode values from imgIContainer.
@@ -566,35 +567,45 @@ interface nsIDOMWindowUtils : nsISupport
 
   /**
    * See nsIWidget::SynthesizeNativeKeyEvent
    *
    * Cannot be accessed from unprivileged context (not content-accessible)
    * Will throw a DOM security error if called without chrome privileges.
    *
    * When you use this for tests, use the constants defined in NativeKeyCodes.js
+   *
+   * NOTE: The synthesized native event will be fired asynchronously, and upon
+   * completion the observer, if provided, will be notified with a "keyevent"
+   * topic.
    */
   void sendNativeKeyEvent(in long aNativeKeyboardLayout,
                           in long aNativeKeyCode,
                           in long aModifierFlags,
                           in AString aCharacters,
-                          in AString aUnmodifiedCharacters);
+                          in AString aUnmodifiedCharacters,
+                          [optional] in nsIObserver aObserver);
 
   /**
    * See nsIWidget::SynthesizeNativeMouseEvent
    *
    * Will be called on the widget that contains aElement.
    * Cannot be accessed from unprivileged context (not content-accessible)
    * Will throw a DOM security error if called without chrome privileges.
+   *
+   * NOTE: The synthesized native event will be fired asynchronously, and upon
+   * completion the observer, if provided, will be notified with a "mouseevent"
+   * topic.
    */
   void sendNativeMouseEvent(in long aScreenX,
                             in long aScreenY,
                             in long aNativeMessage,
                             in long aModifierFlags,
-                            in nsIDOMElement aElement);
+                            in nsIDOMElement aElement,
+                            [optional] in nsIObserver aObserver);
 
   /**
    * The values for sendNativeMouseScrollEvent's aAdditionalFlags.
    */
 
   /**
    * If MOUSESCROLL_PREFER_WIDGET_AT_POINT is set, widget will dispatch
    * the event to a widget which is under the cursor.  Otherwise, dispatch to
@@ -615,31 +626,34 @@ interface nsIDOMWindowUtils : nsISupport
 
   /**
    * See nsIWidget::SynthesizeNativeMouseScrollEvent
    *
    * Will be called on the widget that contains aElement.
    * Cannot be accessed from unprivileged context (not content-accessible)
    * Will throw a DOM security error if called without chrome privileges.
    *
-   * NOTE: The synthesized native event may be fired asynchronously.
+   * NOTE: The synthesized native event will be fired asynchronously, and upon
+   * completion the observer, if provided, will be notified with a
+   * "mousescrollevent" topic.
    *
    * @param aNativeMessage
    *   On Windows:  WM_MOUSEWHEEL (0x020A), WM_MOUSEHWHEEL(0x020E),
    *                WM_VSCROLL (0x0115) or WM_HSCROLL (0x114).
    */
   void sendNativeMouseScrollEvent(in long aScreenX,
                                   in long aScreenY,
                                   in unsigned long aNativeMessage,
                                   in double aDeltaX,
                                   in double aDeltaY,
                                   in double aDeltaZ,
                                   in unsigned long aModifierFlags,
                                   in unsigned long aAdditionalFlags,
-                                  in nsIDOMElement aElement);
+                                  in nsIDOMElement aElement,
+                                  [optional] in nsIObserver aObserver);
 
   /**
    * Touch states for sendNativeTouchPoint. These values match
    * nsIWidget's TouchPointerState.
    */
 
   // The pointer is in a hover state above the digitizer
   const long TOUCH_HOVER   = 0x01;
@@ -660,61 +674,75 @@ interface nsIDOMWindowUtils : nsISupport
    * sent as individual calls. For example:
    * tap - msg1:TOUCH_CONTACT, msg2:TOUCH_REMOVE
    * drag - msg1-n:TOUCH_CONTACT (moving), msgn+1:TOUCH_REMOVE
    * hover drag - msg1-n:TOUCH_HOVER (moving), msgn+1:TOUCH_REMOVE
    *
    * Widget support: Windows 8.0+, Winrt/Win32. Gonk supports CONTACT, REMOVE,
    * and CANCEL but no HOVER. Other widgets will throw.
    *
+   * NOTE: The synthesized native event will be fired asynchronously, and upon
+   * completion the observer, if provided, will be notified with a "touchpoint"
+   * topic.
+   *
    * @param aPointerId The touch point id to create or update.
    * @param aTouchState one or more of the touch states listed above
    * @param aScreenX, aScreenY screen coords of this event
    * @param aPressure 0.0 -> 1.0 float val indicating pressure
    * @param aOrientation 0 -> 359 degree value indicating the
    * orientation of the pointer. Use 90 for normal taps.
    */
   void sendNativeTouchPoint(in unsigned long aPointerId,
                             in unsigned long aTouchState,
                             in long aScreenX,
                             in long aScreenY,
                             in double aPressure,
-                            in unsigned long aOrientation);
+                            in unsigned long aOrientation,
+                            [optional] in nsIObserver aObserver);
 
   /**
    * Simulates native touch based taps on the input digitizer. Events
    * triggered by this call are injected at the os level. Events do not
    * bypass widget level input processing and as such can be used to
    * test widget event logic and async pan-zoom controller functionality.
    * Cannot be accessed from an unprivileged context.
    *
    * Long taps (based on the aLongTap parameter) will be completed
    * asynchrnously after the call returns. Long tap delay is based on
    * the ui.click_hold_context_menus.delay pref or 1500 msec if pref
    * is not set.
    *
    * Widget support: Windows 8.0+, Winrt/Win32. Other widgets will
    * throw.
    *
+   * NOTE: The synthesized native event will be fired asynchronously, and upon
+   * completion the observer, if provided, will be notified, with a "touchtap"
+   * topic.
+   *
    * @param aScreenX, aScreenY screen coords of this event
    * @param aLongTap true if the tap should be long, false for a short
    * tap.
    */
   void sendNativeTouchTap(in long aScreenX,
                           in long aScreenY,
-                          in boolean aLongTap);
+                          in boolean aLongTap,
+                          [optional] in nsIObserver aObserver);
 
   /**
    * Cancel any existing touch points or long tap delays. Calling this is safe
    * even if you're sure there aren't any pointers recorded. You should call
    * this when tests shut down to reset the digitizer driver. Not doing so can
    * leave the digitizer in an undetermined state which can screw up subsequent
    * tests and native input.
+   *
+   * NOTE: The synthesized native event will be fired asynchronously, and upon
+   * completion the observer, if provided, will be notified with a "cleartouch"
+   * topic.
    */
-  void clearNativeTouchSequence();
+  void clearNativeTouchSequence([optional] in nsIObserver aObserver);
 
   /**
    * See nsIWidget::ActivateNativeMenuItemAt
    *
    * Cannot be accessed from unprivileged context (not content-accessible)
    * Will throw a DOM security error if called without chrome privileges.
    */
   void activateNativeMenuItemAt(in AString indexString);
--- a/dom/ipc/ContentChild.cpp
+++ b/dom/ipc/ContentChild.cpp
@@ -93,16 +93,17 @@
 #include "nsIJSRuntimeService.h"
 #include "nsThreadManager.h"
 #include "nsAnonymousTemporaryFile.h"
 #include "nsISpellChecker.h"
 #include "nsClipboardProxy.h"
 #include "nsISystemMessageCache.h"
 #include "nsDirectoryServiceUtils.h"
 #include "nsDirectoryServiceDefs.h"
+#include "nsContentPermissionHelper.h"
 
 #include "IHistory.h"
 #include "nsNetUtil.h"
 
 #include "base/message_loop.h"
 #include "base/process_util.h"
 #include "base/task.h"
 
@@ -2714,16 +2715,33 @@ ContentChild::RecvUpdateWindow(const uin
   }
   return true;
 #else
   NS_NOTREACHED("ContentChild::RecvUpdateWindow calls unexpected on this platform.");
   return false;
 #endif
 }
 
+PContentPermissionRequestChild*
+ContentChild::AllocPContentPermissionRequestChild(const InfallibleTArray<PermissionRequest>& aRequests,
+                                                  const IPC::Principal& aPrincipal,
+                                                  const TabId& aTabId)
+{
+    NS_RUNTIMEABORT("unused");
+    return nullptr;
+}
+
+bool
+ContentChild::DeallocPContentPermissionRequestChild(PContentPermissionRequestChild* actor)
+{
+    auto child = static_cast<RemotePermissionRequest*>(actor);
+    child->IPDLRelease();
+    return true;
+}
+
 // This code goes here rather than nsGlobalWindow.cpp because nsGlobalWindow.cpp
 // can't include ContentChild.h since it includes windows.h.
 
 static uint64_t gNextWindowID = 0;
 
 // We use only 53 bits for the window ID so that it can be converted to and from
 // a JS value without loss of precision. The upper bits of the window ID hold the
 // process ID. The lower bits identify the window.
--- a/dom/ipc/ContentChild.h
+++ b/dom/ipc/ContentChild.h
@@ -452,16 +452,23 @@ public:
     virtual POfflineCacheUpdateChild* AllocPOfflineCacheUpdateChild(
             const URIParams& manifestURI,
             const URIParams& documentURI,
             const bool& stickDocument,
             const TabId& aTabId) override;
     virtual bool
     DeallocPOfflineCacheUpdateChild(POfflineCacheUpdateChild* offlineCacheUpdate) override;
 
+    virtual PContentPermissionRequestChild*
+    AllocPContentPermissionRequestChild(const InfallibleTArray<PermissionRequest>& aRequests,
+                                        const IPC::Principal& aPrincipal,
+                                        const TabId& aTabId) override;
+    virtual bool
+    DeallocPContentPermissionRequestChild(PContentPermissionRequestChild* actor) override;
+
 private:
     virtual void ActorDestroy(ActorDestroyReason why) override;
 
     virtual void ProcessingError(Result aCode, const char* aReason) override;
 
     /**
      * Exit *now*.  Do not shut down XPCOM, do not pass Go, do not run
      * static destructors, do not collect $200.
--- a/dom/ipc/ContentParent.cpp
+++ b/dom/ipc/ContentParent.cpp
@@ -41,16 +41,17 @@
 #include "mozilla/dom/DataTransfer.h"
 #include "mozilla/dom/DOMStorageIPC.h"
 #include "mozilla/dom/Element.h"
 #include "mozilla/dom/File.h"
 #include "mozilla/dom/ExternalHelperAppParent.h"
 #include "mozilla/dom/FileSystemRequestParent.h"
 #include "mozilla/dom/GeolocationBinding.h"
 #include "mozilla/dom/PContentBridgeParent.h"
+#include "mozilla/dom/PContentPermissionRequestParent.h"
 #include "mozilla/dom/PCycleCollectWithLogsParent.h"
 #include "mozilla/dom/PFMRadioParent.h"
 #include "mozilla/dom/PMemoryReportRequestParent.h"
 #include "mozilla/dom/asmjscache/AsmJSCache.h"
 #include "mozilla/dom/bluetooth/PBluetoothParent.h"
 #include "mozilla/dom/cellbroadcast/CellBroadcastParent.h"
 #include "mozilla/dom/devicestorage/DeviceStorageRequestParent.h"
 #include "mozilla/dom/icc/IccParent.h"
@@ -95,16 +96,17 @@
 #include "nsContentUtils.h"
 #include "nsDebugImpl.h"
 #include "nsFrameMessageManager.h"
 #include "nsGeolocationSettings.h"
 #include "nsHashPropertyBag.h"
 #include "nsIAlertsService.h"
 #include "nsIAppsService.h"
 #include "nsIClipboard.h"
+#include "nsContentPermissionHelper.h"
 #include "nsICycleCollectorListener.h"
 #include "nsIDocument.h"
 #include "nsIDOMGeoGeolocation.h"
 #include "nsIDOMGeoPositionError.h"
 #include "nsIDragService.h"
 #include "mozilla/dom/WakeLock.h"
 #include "nsIDOMWindow.h"
 #include "nsIExternalProtocolService.h"
@@ -2062,16 +2064,28 @@ ContentParent::StartForceKillTimer()
 void
 ContentParent::NotifyTabDestroyed(PBrowserParent* aTab,
                                   bool aNotifiedDestroying)
 {
     if (aNotifiedDestroying) {
         --mNumDestroyingTabs;
     }
 
+    TabId id = static_cast<TabParent*>(aTab)->GetTabId();
+    nsTArray<PContentPermissionRequestParent*> parentArray =
+        nsContentPermissionUtils::GetContentPermissionRequestParentById(id);
+
+    // Need to close undeleted ContentPermissionRequestParents before tab is closed.
+    for (auto& permissionRequestParent : parentArray) {
+        nsTArray<PermissionChoice> emptyChoices;
+        unused << PContentPermissionRequestParent::Send__delete__(permissionRequestParent,
+                                                                  false,
+                                                                  emptyChoices);
+    }
+
     // There can be more than one PBrowser for a given app process
     // because of popup windows.  When the last one closes, shut
     // us down.
     if (ManagedPBrowserParent().Length() == 1) {
         // In the case of normal shutdown, send a shutdown message to child to
         // allow it to perform shutdown tasks.
         MessageLoop::current()->PostTask(
             FROM_HERE,
@@ -4878,16 +4892,42 @@ ContentParent::RecvUpdateDropEffect(cons
     if (dt) {
       dt->SetDropEffectInt(aDropEffect);
     }
     dragSession->UpdateDragEffect();
   }
   return true;
 }
 
+PContentPermissionRequestParent*
+ContentParent::AllocPContentPermissionRequestParent(const InfallibleTArray<PermissionRequest>& aRequests,
+                                                    const IPC::Principal& aPrincipal,
+                                                    const TabId& aTabId)
+{
+    ContentProcessManager* cpm = ContentProcessManager::GetSingleton();
+    nsRefPtr<TabParent> tp = cpm->GetTopLevelTabParentByProcessAndTabId(this->ChildID(),
+                                                                        aTabId);
+    if (!tp) {
+        return nullptr;
+    }
+
+    return nsContentPermissionUtils::CreateContentPermissionRequestParent(aRequests,
+                                                                          tp->GetOwnerElement(),
+                                                                          aPrincipal,
+                                                                          aTabId);
+}
+
+bool
+ContentParent::DeallocPContentPermissionRequestParent(PContentPermissionRequestParent* actor)
+{
+    nsContentPermissionUtils::NotifyRemoveContentPermissionRequestParent(actor);
+    delete actor;
+    return true;
+}
+
 } // namespace dom
 } // namespace mozilla
 
 NS_IMPL_ISUPPORTS(ParentIdleListener, nsIObserver)
 
 NS_IMETHODIMP
 ParentIdleListener::Observe(nsISupports*, const char* aTopic, const char16_t* aData) {
     mozilla::unused << mParent->SendNotifyIdleObserver(mObserver,
--- a/dom/ipc/ContentParent.h
+++ b/dom/ipc/ContentParent.h
@@ -367,16 +367,24 @@ public:
     virtual bool
     DeallocPOfflineCacheUpdateParent(POfflineCacheUpdateParent* aActor) override;
 
     virtual bool RecvSetOfflinePermission(const IPC::Principal& principal) override;
 
     virtual bool RecvFinishShutdown() override;
 
     void MaybeInvokeDragSession(TabParent* aParent);
+
+    virtual PContentPermissionRequestParent*
+    AllocPContentPermissionRequestParent(const InfallibleTArray<PermissionRequest>& aRequests,
+                                         const IPC::Principal& aPrincipal,
+                                         const TabId& aTabId) override;
+    virtual bool
+    DeallocPContentPermissionRequestParent(PContentPermissionRequestParent* actor) override;
+
 protected:
     void OnChannelConnected(int32_t pid) override;
     virtual void ActorDestroy(ActorDestroyReason why) override;
     void OnNuwaForkTimeout();
 
     bool ShouldContinueFromReplyTimeout() override;
 
 private:
--- a/dom/ipc/PBrowser.ipdl
+++ b/dom/ipc/PBrowser.ipdl
@@ -5,25 +5,23 @@
  * 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 protocol PBlob;
 include protocol PColorPicker;
 include protocol PContent;
 include protocol PContentBridge;
 include protocol PDocumentRenderer;
-include protocol PContentPermissionRequest;
 include protocol PFilePicker;
 include protocol PIndexedDBPermissionRequest;
 include protocol PRenderFrame;
 include protocol PPluginWidget;
 include DOMTypes;
 include JavaScriptTypes;
 include URIParams;
-include PContentPermission;
 include ServiceWorkerRegistrarTypes;
 
 
 using class IPC::Principal from "mozilla/dom/PermissionMessageUtils.h";
 using class mozilla::gfx::Matrix from "mozilla/gfx/Matrix.h";
 using struct gfxSize from "gfxPoint.h";
 using CSSRect from "Units.h";
 using LayoutDeviceIntRect from "Units.h";
@@ -54,16 +52,17 @@ using mozilla::dom::ScreenOrientation fr
 using struct mozilla::layers::TextureFactoryIdentifier from "mozilla/layers/CompositorTypes.h";
 using mozilla::CSSPoint from "Units.h";
 using mozilla::CSSToScreenScale from "Units.h";
 using mozilla::CommandInt from "mozilla/EventForwards.h";
 using mozilla::Modifiers from "mozilla/EventForwards.h";
 using mozilla::layers::GeckoContentController::APZStateChange from "mozilla/layers/GeckoContentController.h";
 using mozilla::WritingMode from "mozilla/WritingModes.h";
 using mozilla::layers::TouchBehaviorFlags from "mozilla/layers/APZUtils.h";
+using nsIWidget::TouchPointerState from "nsIWidget.h";
 
 namespace mozilla {
 namespace dom {
 
 struct NativeKeyBinding
 {
   CommandInt[] singleLineCommands;
   CommandInt[] multiLineCommands;
@@ -97,17 +96,16 @@ struct FrameScriptInfo
 };
 
 prio(normal upto urgent) sync protocol PBrowser
 {
     manager PContent or PContentBridge;
 
     manages PColorPicker;
     manages PDocumentRenderer;
-    manages PContentPermissionRequest;
     manages PFilePicker;
     manages PIndexedDBPermissionRequest;
     manages PRenderFrame;
     manages PPluginWidget;
 
 both:
     AsyncMessage(nsString aMessage, ClonedMessageData aData, CpowEntry[] aCpows,
                  Principal aPrincipal);
@@ -330,24 +328,16 @@ parent:
                                        nsString inputmode,
                                        nsString actionHint,
                                        int32_t cause,
                                        int32_t focusChange);
 
     sync IsParentWindowMainWidgetVisible() returns (bool visible);
 
     /**
-     * Content process forward from PuppetWidget for synth mouse move
-     * events.
-     *
-     * aPoint a synth point relative to the window
-     */
-    sync SynthesizeNativeMouseMove(LayoutDeviceIntPoint aPoint);
-
-    /**
      * Returns the offset of this tab from the top level window
      * origin in device pixels.
      *
      * aPoint offset values in device pixels.
      */
     sync GetTabOffset() returns (LayoutDeviceIntPoint aPoint);
 
     /**
@@ -386,31 +376,16 @@ parent:
     HideTooltip();
 
     /**
      * Create an asynchronous color picker on the parent side,
      * but don't open it yet.
      */
     PColorPicker(nsString title, nsString initialColor);
 
-    /**
-     * Initiates an asynchronous request for permission for the
-     * provided principal.
-     *
-     * @param aRequests
-     *   The array of permissions to request.
-     * @param aPrincipal
-     *   The principal of the request.
-     *
-     * NOTE: The principal is untrusted in the parent process. Only
-     *       principals that can live in the content process should
-     *       provided.
-     */
-    PContentPermissionRequest(PermissionRequest[] aRequests, Principal aPrincipal);
-
     PFilePicker(nsString aTitle, int16_t aMode);
 
     /**
      * Initiates an asynchronous request for one of the special indexedDB
      * permissions for the provided principal.
      *
      * @param principal
      *   The principal of the request.
@@ -486,16 +461,51 @@ parent:
 
     ReplyKeyEvent(WidgetKeyboardEvent event);
 
     DispatchAfterKeyboardEvent(WidgetKeyboardEvent event);
 
     sync RequestNativeKeyBindings(WidgetKeyboardEvent event)
         returns (MaybeNativeKeyBinding bindings);
 
+    SynthesizeNativeKeyEvent(int32_t aNativeKeyboardLayout,
+                             int32_t aNativeKeyCode,
+                             uint32_t aModifierFlags,
+                             nsString aCharacters,
+                             nsString aUnmodifiedCharacters,
+                             uint64_t aObserverId);
+    SynthesizeNativeMouseEvent(LayoutDeviceIntPoint aPoint,
+                               uint32_t aNativeMessage,
+                               uint32_t aModifierFlags,
+                               uint64_t aObserverId);
+    SynthesizeNativeMouseMove(LayoutDeviceIntPoint aPoint,
+                              uint64_t aObserverId);
+    SynthesizeNativeMouseScrollEvent(LayoutDeviceIntPoint aPoint,
+                                     uint32_t aNativeMessage,
+                                     double aDeltaX,
+                                     double aDeltaY,
+                                     double aDeltaZ,
+                                     uint32_t aModifierFlags,
+                                     uint32_t aAdditionalFlags,
+                                     uint64_t aObserverId);
+    SynthesizeNativeTouchPoint(uint32_t aPointerId,
+                               TouchPointerState aPointerState,
+                               nsIntPoint aPointerScreenPoint,
+                               double aPointerPressure,
+                               uint32_t aPointerOrientation,
+                               uint64_t aObserverId);
+    SynthesizeNativeTouchTap(nsIntPoint aPointerScreenPoint,
+                             bool aLongTap,
+                             uint64_t aObserverId);
+    ClearNativeTouchSequence(uint64_t aObserverId);
+child:
+    NativeSynthesisResponse(uint64_t aObserverId, nsCString aResponse);
+
+parent:
+
     /**
      * Child informs the parent that the graphics objects are ready for
      * compositing.  This is sent when all pending changes have been
      * sent to the compositor and are ready to be shown on the next composite.
      * @see PCompositor
      * @see RequestNotifyAfterRemotePaint
      */
     async RemotePaintIsReady();
--- a/dom/ipc/PContent.ipdl
+++ b/dom/ipc/PContent.ipdl
@@ -7,16 +7,17 @@
 include protocol PAsmJSCacheEntry;
 include protocol PBackground;
 include protocol PBlob;
 include protocol PBluetooth;
 include protocol PBrowser;
 include protocol PCellBroadcast;
 include protocol PCompositor;
 include protocol PContentBridge;
+include protocol PContentPermissionRequest;
 include protocol PCycleCollectWithLogs;
 include protocol PCrashReporter;
 include protocol PDocAccessible;
 include protocol PExternalHelperApp;
 include protocol PDeviceStorageRequest;
 include protocol PFileDescriptorSet;
 include protocol PFMRadio;
 include protocol PFileSystemRequest;
@@ -50,16 +51,17 @@ include protocol PJavaScript;
 include protocol PRemoteSpellcheckEngine;
 include DOMTypes;
 include JavaScriptTypes;
 include InputStreamParams;
 include PTabContext;
 include URIParams;
 include PluginTypes;
 include ProtocolTypes;
+include PContentPermission;
 
 // Workaround to prevent error if PContentChild.cpp & PContentBridgeParent.cpp
 // are put into different UnifiedProtocolsXX.cpp files.
 // XXX Remove this once bug 1069073 is fixed
 include "mozilla/dom/PContentBridgeParent.h";
 
 include "mozilla/dom/indexedDB/SerializationHelpers.h";
 
@@ -375,16 +377,17 @@ prio(normal upto urgent) sync protocol P
     parent opens PGMPService;
     child opens PBackground;
 
     manages PAsmJSCacheEntry;
     manages PBlob;
     manages PBluetooth;
     manages PBrowser;
     manages PCellBroadcast;
+    manages PContentPermissionRequest;
     manages PCrashReporter;
     manages PCycleCollectWithLogs;
     manages PDocAccessible;
     manages PDeviceStorageRequest;
     manages PFileSystemRequest;
     manages PExternalHelperApp;
     manages PFileDescriptorSet;
     manages PFMRadio;
@@ -946,15 +949,34 @@ parent:
 
     /**
      * Notifies the parent to continue shutting down after the child performs
      * its shutdown tasks.
      */
     async FinishShutdown();
 
     UpdateDropEffect(uint32_t aDragAction, uint32_t aDropEffect);
+
+    /**
+     * Initiates an asynchronous request for permission for the
+     * provided principal.
+     *
+     * @param aRequests
+     *   The array of permissions to request.
+     * @param aPrincipal
+     *   The principal of the request.
+     * @param tabId
+     *   To identify which tab issues this request.
+     *
+     * NOTE: The principal is untrusted in the parent process. Only
+     *       principals that can live in the content process should
+     *       provided.
+     */
+    PContentPermissionRequest(PermissionRequest[] aRequests, Principal aPrincipal,
+                              TabId tabId);
+
 both:
      AsyncMessage(nsString aMessage, ClonedMessageData aData,
                   CpowEntry[] aCpows, Principal aPrincipal);
 };
 
 }
 }
--- a/dom/ipc/PContentPermissionRequest.ipdl
+++ b/dom/ipc/PContentPermissionRequest.ipdl
@@ -1,24 +1,26 @@
 /* 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 protocol PBrowser;
+include protocol PContent;
 include PContentPermission;
 
 namespace mozilla {
 namespace dom {
 
 protocol PContentPermissionRequest
 {
-  manager PBrowser;
+  manager PContent;
 
 parent:
   prompt();
+  NotifyVisibility(bool visibility);
 
 child:
+  GetVisibility();
   __delete__(bool allow, PermissionChoice[] choices);
 };
 
 
 } // namespace dom
 } // namespace mozilla
--- a/dom/ipc/TabChild.cpp
+++ b/dom/ipc/TabChild.cpp
@@ -66,17 +66,16 @@
 #include "nsPIDOMWindow.h"
 #include "nsPIWindowRoot.h"
 #include "nsLayoutUtils.h"
 #include "nsPrintfCString.h"
 #include "nsThreadUtils.h"
 #include "nsWeakReference.h"
 #include "nsWindowWatcher.h"
 #include "PermissionMessageUtils.h"
-#include "nsContentPermissionHelper.h"
 #include "PuppetWidget.h"
 #include "StructuredCloneUtils.h"
 #include "nsViewportInfo.h"
 #include "nsILoadContext.h"
 #include "ipc/nsGUIEventIPC.h"
 #include "mozilla/gfx/Matrix.h"
 #include "UnitTransforms.h"
 #include "ClientLayerManager.h"
@@ -795,32 +794,16 @@ TabChild::Create(nsIContentChild* aManag
         return child.forget();
     }
 
     nsRefPtr<TabChild> iframe = new TabChild(aManager, aTabId,
                                              aContext, aChromeFlags);
     return NS_SUCCEEDED(iframe->Init()) ? iframe.forget() : nullptr;
 }
 
-class TabChildSetTargetAPZCCallback : public SetTargetAPZCCallback {
-public:
-  explicit TabChildSetTargetAPZCCallback(TabChild* aTabChild)
-    : mTabChild(do_GetWeakReference(static_cast<nsITabChild*>(aTabChild)))
-  {}
-
-  void Run(uint64_t aInputBlockId, const nsTArray<ScrollableLayerGuid>& aTargets) const override {
-    if (nsCOMPtr<nsITabChild> tabChild = do_QueryReferent(mTabChild)) {
-      static_cast<TabChild*>(tabChild.get())->SendSetTargetAPZC(aInputBlockId, aTargets);
-    }
-  }
-
-private:
-  nsWeakPtr mTabChild;
-};
-
 class TabChildSetAllowedTouchBehaviorCallback : public SetAllowedTouchBehaviorCallback {
 public:
   explicit TabChildSetAllowedTouchBehaviorCallback(TabChild* aTabChild)
     : mTabChild(do_GetWeakReference(static_cast<nsITabChild*>(aTabChild)))
   {}
 
   void Run(uint64_t aInputBlockId, const nsTArray<TouchBehaviorFlags>& aFlags) const override {
     if (nsCOMPtr<nsITabChild> tabChild = do_QueryReferent(mTabChild)) {
@@ -861,17 +844,16 @@ TabChild::TabChild(nsIContentChild* aMan
   , mAppPackageFileDescriptorRecved(false)
   , mLastBackgroundColor(NS_RGB(255, 255, 255))
   , mDidFakeShow(false)
   , mNotified(false)
   , mTriedBrowserInit(false)
   , mOrientation(eScreenOrientation_PortraitPrimary)
   , mUpdateHitRegion(false)
   , mIgnoreKeyPressEvent(false)
-  , mSetTargetAPZCCallback(new TabChildSetTargetAPZCCallback(this))
   , mSetAllowedTouchBehaviorCallback(new TabChildSetAllowedTouchBehaviorCallback(this))
   , mHasValidInnerSize(false)
   , mDestroyed(false)
   , mUniqueId(aTabId)
   , mDPI(0)
   , mDefaultScale(0)
   , mIPCOpen(true)
   , mParentIsActive(false)
@@ -2188,17 +2170,17 @@ TabChild::RecvRealMouseButtonEvent(const
 bool
 TabChild::RecvMouseWheelEvent(const WidgetWheelEvent& aEvent,
                               const ScrollableLayerGuid& aGuid,
                               const uint64_t& aInputBlockId)
 {
   if (gfxPrefs::AsyncPanZoomEnabled()) {
     nsCOMPtr<nsIDocument> document(GetDocument());
     APZCCallbackHelper::SendSetTargetAPZCNotification(WebWidget(), document, aEvent, aGuid,
-        aInputBlockId, mSetTargetAPZCCallback);
+        aInputBlockId);
   }
 
   WidgetWheelEvent event(aEvent);
   event.widget = mWidget;
   APZCCallbackHelper::DispatchWidgetEvent(event);
 
   if (gfxPrefs::AsyncPanZoomEnabled()) {
     mAPZEventState->ProcessWheelEvent(event, aGuid, aInputBlockId);
@@ -2383,17 +2365,17 @@ TabChild::RecvRealTouchEvent(const Widge
 
   if (localEvent.message == NS_TOUCH_START && gfxPrefs::AsyncPanZoomEnabled()) {
     if (gfxPrefs::TouchActionEnabled()) {
       APZCCallbackHelper::SendSetAllowedTouchBehaviorNotification(WebWidget(),
           localEvent, aInputBlockId, mSetAllowedTouchBehaviorCallback);
     }
     nsCOMPtr<nsIDocument> document = GetDocument();
     APZCCallbackHelper::SendSetTargetAPZCNotification(WebWidget(), document,
-        localEvent, aGuid, aInputBlockId, mSetTargetAPZCCallback);
+        localEvent, aGuid, aInputBlockId);
   }
 
   // Dispatch event to content (potentially a long-running operation)
   nsEventStatus status = APZCCallbackHelper::DispatchWidgetEvent(localEvent);
 
   if (!gfxPrefs::AsyncPanZoomEnabled()) {
     UpdateTapState(localEvent, status);
     return true;
@@ -2465,16 +2447,24 @@ TabChild::RequestNativeKeyBindings(AutoC
                       bindings.multiLineCommands(),
                       bindings.richTextCommands());
   } else {
     aAutoCache->CacheNoCommands();
   }
 }
 
 bool
+TabChild::RecvNativeSynthesisResponse(const uint64_t& aObserverId,
+                                      const nsCString& aResponse)
+{
+  mozilla::widget::AutoObserverNotifier::NotifySavedObserver(aObserverId, aResponse.get());
+  return true;
+}
+
+bool
 TabChild::RecvRealKeyEvent(const WidgetKeyboardEvent& event,
                            const MaybeNativeKeyBinding& aBindings)
 {
   PuppetWidget* widget = static_cast<PuppetWidget*>(mWidget.get());
   AutoCacheNativeKeyCommands autoCache(widget);
 
   if (event.message == NS_KEY_PRESS) {
     if (aBindings.type() == MaybeNativeKeyBinding::TNativeKeyBinding) {
@@ -2605,32 +2595,16 @@ TabChild::AllocPColorPickerChild(const n
 bool
 TabChild::DeallocPColorPickerChild(PColorPickerChild* aColorPicker)
 {
   nsColorPickerProxy* picker = static_cast<nsColorPickerProxy*>(aColorPicker);
   NS_RELEASE(picker);
   return true;
 }
 
-PContentPermissionRequestChild*
-TabChild::AllocPContentPermissionRequestChild(const InfallibleTArray<PermissionRequest>& aRequests,
-                                              const IPC::Principal& aPrincipal)
-{
-  NS_RUNTIMEABORT("unused");
-  return nullptr;
-}
-
-bool
-TabChild::DeallocPContentPermissionRequestChild(PContentPermissionRequestChild* actor)
-{
-  RemotePermissionRequest* child = static_cast<RemotePermissionRequest*>(actor);
-  child->IPDLRelease();
-  return true;
-}
-
 PFilePickerChild*
 TabChild::AllocPFilePickerChild(const nsString&, const int16_t&)
 {
   NS_RUNTIMEABORT("unused");
   return nullptr;
 }
 
 bool
--- a/dom/ipc/TabChild.h
+++ b/dom/ipc/TabChild.h
@@ -366,16 +366,18 @@ public:
                                         const uint64_t& aInputBlockId) override;
     virtual bool RecvKeyEvent(const nsString& aType,
                               const int32_t&  aKeyCode,
                               const int32_t&  aCharCode,
                               const int32_t&  aModifiers,
                               const bool&     aPreventDefault) override;
     virtual bool RecvMouseScrollTestEvent(const FrameMetrics::ViewID& aScrollId,
                                           const nsString& aEvent) override;
+    virtual bool RecvNativeSynthesisResponse(const uint64_t& aObserverId,
+                                             const nsCString& aResponse) override;
     virtual bool RecvCompositionEvent(const mozilla::WidgetCompositionEvent& event) override;
     virtual bool RecvSelectionEvent(const mozilla::WidgetSelectionEvent& event) override;
     virtual bool RecvActivateFrameEvent(const nsString& aType, const bool& capture) override;
     virtual bool RecvLoadRemoteScript(const nsString& aURL,
                                       const bool& aRunInGlobalScope) override;
     virtual bool RecvAsyncMessage(const nsString& aMessage,
                                   const ClonedMessageData& aData,
                                   InfallibleTArray<CpowEntry>&& aCpows,
@@ -396,22 +398,16 @@ public:
                                                   const uint32_t& renderFlags,
                                                   const bool& flushLayout,
                                                   const nsIntSize& renderSize) override;
 
     virtual PColorPickerChild*
     AllocPColorPickerChild(const nsString& title, const nsString& initialColor) override;
     virtual bool DeallocPColorPickerChild(PColorPickerChild* actor) override;
 
-    virtual PContentPermissionRequestChild*
-    AllocPContentPermissionRequestChild(const InfallibleTArray<PermissionRequest>& aRequests,
-                                        const IPC::Principal& aPrincipal) override;
-    virtual bool
-    DeallocPContentPermissionRequestChild(PContentPermissionRequestChild* actor) override;
-
     virtual PFilePickerChild*
     AllocPFilePickerChild(const nsString& aTitle, const int16_t& aMode) override;
     virtual bool
     DeallocPFilePickerChild(PFilePickerChild* actor) override;
 
     virtual PIndexedDBPermissionRequestChild*
     AllocPIndexedDBPermissionRequestChild(const Principal& aPrincipal)
                                           override;
@@ -618,17 +614,16 @@ private:
     bool mDidFakeShow;
     bool mNotified;
     bool mTriedBrowserInit;
     ScreenOrientation mOrientation;
     bool mUpdateHitRegion;
 
     bool mIgnoreKeyPressEvent;
     nsRefPtr<APZEventState> mAPZEventState;
-    nsRefPtr<SetTargetAPZCCallback> mSetTargetAPZCCallback;
     nsRefPtr<SetAllowedTouchBehaviorCallback> mSetAllowedTouchBehaviorCallback;
     bool mHasValidInnerSize;
     bool mDestroyed;
     // Position of tab, relative to parent widget (typically the window)
     nsIntPoint mChromeDisp;
     TabId mUniqueId;
     float mDPI;
     double mDefaultScale;
--- a/dom/ipc/TabParent.cpp
+++ b/dom/ipc/TabParent.cpp
@@ -8,17 +8,16 @@
 
 #include "TabParent.h"
 
 #include "AppProcessChecker.h"
 #include "mozIApplication.h"
 #include "mozilla/BrowserElementParent.h"
 #include "mozilla/dom/ContentParent.h"
 #include "mozilla/dom/DataTransfer.h"
-#include "mozilla/dom/PContentPermissionRequestParent.h"
 #include "mozilla/dom/ServiceWorkerRegistrar.h"
 #include "mozilla/dom/indexedDB/ActorsParent.h"
 #include "mozilla/plugins/PluginWidgetParent.h"
 #include "mozilla/EventStateManager.h"
 #include "mozilla/gfx/2D.h"
 #include "mozilla/Hal.h"
 #include "mozilla/ipc/DocumentRendererParent.h"
 #include "mozilla/jsipc/CrossProcessObjectWrappers.h"
@@ -29,17 +28,16 @@
 #include "mozilla/net/NeckoChild.h"
 #include "mozilla/Preferences.h"
 #include "mozilla/TextEvents.h"
 #include "mozilla/TouchEvents.h"
 #include "mozilla/unused.h"
 #include "BlobParent.h"
 #include "nsCOMPtr.h"
 #include "nsContentAreaDragDrop.h"
-#include "nsContentPermissionHelper.h"
 #include "nsContentUtils.h"
 #include "nsDebug.h"
 #include "nsFocusManager.h"
 #include "nsFrameLoader.h"
 #include "nsIBaseWindow.h"
 #include "nsIContent.h"
 #include "nsIDocShell.h"
 #include "nsIDocShellTreeOwner.h"
@@ -1068,30 +1066,16 @@ TabParent::AllocPDocumentRendererParent(
 
 bool
 TabParent::DeallocPDocumentRendererParent(PDocumentRendererParent* actor)
 {
     delete actor;
     return true;
 }
 
-PContentPermissionRequestParent*
-TabParent::AllocPContentPermissionRequestParent(const InfallibleTArray<PermissionRequest>& aRequests,
-                                                const IPC::Principal& aPrincipal)
-{
-  return nsContentPermissionUtils::CreateContentPermissionRequestParent(aRequests, mFrameElement, aPrincipal);
-}
-
-bool
-TabParent::DeallocPContentPermissionRequestParent(PContentPermissionRequestParent* actor)
-{
-  delete actor;
-  return true;
-}
-
 PFilePickerParent*
 TabParent::AllocPFilePickerParent(const nsString& aTitle, const int16_t& aMode)
 {
   return new FilePickerParent(aTitle, aMode);
 }
 
 bool
 TabParent::DeallocPFilePickerParent(PFilePickerParent* actor)
@@ -1341,16 +1325,187 @@ TabParent::RecvRequestNativeKeyBindings(
 
   if (!singleLine.IsEmpty() || !multiLine.IsEmpty() || !richText.IsEmpty()) {
     *aBindings = NativeKeyBinding(singleLine, multiLine, richText);
   }
 
   return true;
 }
 
+class SynthesizedEventObserver : public nsIObserver
+{
+  NS_DECL_ISUPPORTS
+
+public:
+  SynthesizedEventObserver(TabParent* aTabParent, const uint64_t& aObserverId)
+    : mTabParent(aTabParent)
+    , mObserverId(aObserverId)
+  {
+    MOZ_ASSERT(mTabParent);
+  }
+
+  NS_IMETHODIMP Observe(nsISupports* aSubject,
+                        const char* aTopic,
+                        const char16_t* aData)
+  {
+    if (!mTabParent) {
+      // We already sent the notification
+      return NS_OK;
+    }
+
+    if (!mTabParent->SendNativeSynthesisResponse(mObserverId, nsCString(aTopic))) {
+      NS_WARNING("Unable to send native event synthesization response!");
+    }
+    // Null out tabparent to indicate we already sent the response
+    mTabParent = nullptr;
+    return NS_OK;
+  }
+
+private:
+  virtual ~SynthesizedEventObserver() { }
+
+  nsRefPtr<TabParent> mTabParent;
+  uint64_t mObserverId;
+};
+
+NS_IMPL_ISUPPORTS(SynthesizedEventObserver, nsIObserver)
+
+class MOZ_STACK_CLASS AutoSynthesizedEventResponder
+{
+public:
+  AutoSynthesizedEventResponder(TabParent* aTabParent,
+                                const uint64_t& aObserverId,
+                                const char* aTopic)
+    : mObserver(new SynthesizedEventObserver(aTabParent, aObserverId))
+    , mTopic(aTopic)
+  { }
+
+  ~AutoSynthesizedEventResponder()
+  {
+    // This may be a no-op if the observer already sent a response.
+    mObserver->Observe(nullptr, mTopic, nullptr);
+  }
+
+  nsIObserver* GetObserver()
+  {
+    return mObserver;
+  }
+
+private:
+  nsCOMPtr<nsIObserver> mObserver;
+  const char* mTopic;
+};
+
+bool
+TabParent::RecvSynthesizeNativeKeyEvent(const int32_t& aNativeKeyboardLayout,
+                                        const int32_t& aNativeKeyCode,
+                                        const uint32_t& aModifierFlags,
+                                        const nsString& aCharacters,
+                                        const nsString& aUnmodifiedCharacters,
+                                        const uint64_t& aObserverId)
+{
+  AutoSynthesizedEventResponder responder(this, aObserverId, "keyevent");
+  nsCOMPtr<nsIWidget> widget = GetWidget();
+  if (widget) {
+    widget->SynthesizeNativeKeyEvent(aNativeKeyboardLayout, aNativeKeyCode,
+      aModifierFlags, aCharacters, aUnmodifiedCharacters,
+      responder.GetObserver());
+  }
+  return true;
+}
+
+bool
+TabParent::RecvSynthesizeNativeMouseEvent(const LayoutDeviceIntPoint& aPoint,
+                                          const uint32_t& aNativeMessage,
+                                          const uint32_t& aModifierFlags,
+                                          const uint64_t& aObserverId)
+{
+  AutoSynthesizedEventResponder responder(this, aObserverId, "mouseevent");
+  nsCOMPtr<nsIWidget> widget = GetWidget();
+  if (widget) {
+    widget->SynthesizeNativeMouseEvent(aPoint, aNativeMessage, aModifierFlags,
+      responder.GetObserver());
+  }
+  return true;
+}
+
+bool
+TabParent::RecvSynthesizeNativeMouseMove(const LayoutDeviceIntPoint& aPoint,
+                                         const uint64_t& aObserverId)
+{
+  AutoSynthesizedEventResponder responder(this, aObserverId, "mousemove");
+  nsCOMPtr<nsIWidget> widget = GetWidget();
+  if (widget) {
+    widget->SynthesizeNativeMouseMove(aPoint, responder.GetObserver());
+  }
+  return true;
+}
+
+bool
+TabParent::RecvSynthesizeNativeMouseScrollEvent(const LayoutDeviceIntPoint& aPoint,
+                                                const uint32_t& aNativeMessage,
+                                                const double& aDeltaX,
+                                                const double& aDeltaY,
+                                                const double& aDeltaZ,
+                                                const uint32_t& aModifierFlags,
+                                                const uint32_t& aAdditionalFlags,
+                                                const uint64_t& aObserverId)
+{
+  AutoSynthesizedEventResponder responder(this, aObserverId, "mousescrollevent");
+  nsCOMPtr<nsIWidget> widget = GetWidget();
+  if (widget) {
+    widget->SynthesizeNativeMouseScrollEvent(aPoint, aNativeMessage,
+      aDeltaX, aDeltaY, aDeltaZ, aModifierFlags, aAdditionalFlags,
+      responder.GetObserver());
+  }
+  return true;
+}
+
+bool
+TabParent::RecvSynthesizeNativeTouchPoint(const uint32_t& aPointerId,
+                                          const TouchPointerState& aPointerState,
+                                          const nsIntPoint& aPointerScreenPoint,
+                                          const double& aPointerPressure,
+                                          const uint32_t& aPointerOrientation,
+                                          const uint64_t& aObserverId)
+{
+  AutoSynthesizedEventResponder responder(this, aObserverId, "touchpoint");
+  nsCOMPtr<nsIWidget> widget = GetWidget();
+  if (widget) {
+    widget->SynthesizeNativeTouchPoint(aPointerId, aPointerState, aPointerScreenPoint,
+      aPointerPressure, aPointerOrientation, responder.GetObserver());
+  }
+  return true;
+}
+
+bool
+TabParent::RecvSynthesizeNativeTouchTap(const nsIntPoint& aPointerScreenPoint,
+                                        const bool& aLongTap,
+                                        const uint64_t& aObserverId)
+{
+  AutoSynthesizedEventResponder responder(this, aObserverId, "touchtap");
+  nsCOMPtr<nsIWidget> widget = GetWidget();
+  if (widget) {
+    widget->SynthesizeNativeTouchTap(aPointerScreenPoint, aLongTap,
+      responder.GetObserver());
+  }
+  return true;
+}
+
+bool
+TabParent::RecvClearNativeTouchSequence(const uint64_t& aObserverId)
+{
+  AutoSynthesizedEventResponder responder(this, aObserverId, "cleartouch");
+  nsCOMPtr<nsIWidget> widget = GetWidget();
+  if (widget) {
+    widget->ClearNativeTouchSequence(responder.GetObserver());
+  }
+  return true;
+}
+
 bool TabParent::SendRealKeyEvent(WidgetKeyboardEvent& event)
 {
   if (mIsDestroyed) {
     return false;
   }
   event.refPoint += GetChildProcessOffset();
 
   MaybeNativeKeyBinding bindings;
@@ -2244,27 +2399,16 @@ TabParent::RecvIsParentWindowMainWidgetV
     return true;
   nsCOMPtr<nsIDOMWindowUtils> windowUtils =
     do_QueryInterface(frame->OwnerDoc()->GetWindow());
   nsresult rv = windowUtils->GetIsParentWindowMainWidgetVisible(aIsVisible);
   return NS_SUCCEEDED(rv);
 }
 
 bool
-TabParent::RecvSynthesizeNativeMouseMove(const mozilla::LayoutDeviceIntPoint& aPoint)
-{
-  // The widget associated with the browser window
-  nsCOMPtr<nsIWidget> widget = GetWidget();
-  if (widget) {
-    widget->SynthesizeNativeMouseMove(aPoint);
-  }
-  return true;
-}
-
-bool
 TabParent::RecvGetDPI(float* aValue)
 {
   TryCacheDPIAndScale();
 
   MOZ_ASSERT(mDPI > 0,
              "Must not ask for DPI before OwnerElement is received!");
   *aValue = mDPI;
   return true;
@@ -2978,17 +3122,18 @@ TabParent::RecvInvokeDragSession(nsTArra
   if (Manager()->IsContentParent()) {
     nsCOMPtr<nsIDragService> dragService =
       do_GetService("@mozilla.org/widget/dragservice;1");
     if (dragService) {
       dragService->MaybeAddChildProcess(Manager()->AsContentParent());
     }
   }
 
-  if (aVisualDnDData.IsEmpty()) {
+  if (aVisualDnDData.IsEmpty() ||
+      (aVisualDnDData.Length() < aHeight * aStride)) {
     mDnDVisualization = nullptr;
   } else {
     mDnDVisualization =
       new mozilla::gfx::SourceSurfaceRawData();
     mozilla::gfx::SourceSurfaceRawData* raw =
       static_cast<mozilla::gfx::SourceSurfaceRawData*>(mDnDVisualization.get());
     raw->InitWrappingData(
       reinterpret_cast<uint8_t*>(const_cast<nsCString&>(aVisualDnDData).BeginWriting()),
--- a/dom/ipc/TabParent.h
+++ b/dom/ipc/TabParent.h
@@ -201,17 +201,16 @@ public:
     virtual bool RecvRequestFocus(const bool& aCanRaise) override;
     virtual bool RecvEnableDisableCommands(const nsString& aAction,
                                            nsTArray<nsCString>&& aEnabledCommands,
                                            nsTArray<nsCString>&& aDisabledCommands) override;
     virtual bool RecvSetCursor(const uint32_t& aValue, const bool& aForce) override;
     virtual bool RecvSetBackgroundColor(const nscolor& aValue) override;
     virtual bool RecvSetStatus(const uint32_t& aType, const nsString& aStatus) override;
     virtual bool RecvIsParentWindowMainWidgetVisible(bool* aIsVisible) override;
-    virtual bool RecvSynthesizeNativeMouseMove(const mozilla::LayoutDeviceIntPoint& aPoint) override;
     virtual bool RecvShowTooltip(const uint32_t& aX, const uint32_t& aY, const nsString& aTooltip) override;
     virtual bool RecvHideTooltip() override;
     virtual bool RecvGetTabOffset(LayoutDeviceIntPoint* aPoint) override;
     virtual bool RecvGetDPI(float* aValue) override;
     virtual bool RecvGetDefaultScale(double* aValue) override;
     virtual bool RecvGetWidgetNativeData(WindowsHandle* aValue) override;
     virtual bool RecvZoomToRect(const uint32_t& aPresShellId,
                                 const ViewID& aViewId,
@@ -266,16 +265,47 @@ public:
     bool MapEventCoordinatesForChildProcess(mozilla::WidgetEvent* aEvent);
     void MapEventCoordinatesForChildProcess(const LayoutDeviceIntPoint& aOffset,
                                             mozilla::WidgetEvent* aEvent);
     LayoutDeviceToCSSScale GetLayoutDeviceToCSSScale();
 
     virtual bool RecvRequestNativeKeyBindings(const mozilla::WidgetKeyboardEvent& aEvent,
                                               MaybeNativeKeyBinding* aBindings) override;
 
+    virtual bool RecvSynthesizeNativeKeyEvent(const int32_t& aNativeKeyboardLayout,
+                                              const int32_t& aNativeKeyCode,
+                                              const uint32_t& aModifierFlags,
+                                              const nsString& aCharacters,
+                                              const nsString& aUnmodifiedCharacters,
+                                              const uint64_t& aObserverId) override;
+    virtual bool RecvSynthesizeNativeMouseEvent(const LayoutDeviceIntPoint& aPoint,
+                                                const uint32_t& aNativeMessage,
+                                                const uint32_t& aModifierFlags,
+                                                const uint64_t& aObserverId) override;
+    virtual bool RecvSynthesizeNativeMouseMove(const LayoutDeviceIntPoint& aPoint,
+                                               const uint64_t& aObserverId) override;
+    virtual bool RecvSynthesizeNativeMouseScrollEvent(const LayoutDeviceIntPoint& aPoint,
+                                                      const uint32_t& aNativeMessage,
+                                                      const double& aDeltaX,
+                                                      const double& aDeltaY,
+                                                      const double& aDeltaZ,
+                                                      const uint32_t& aModifierFlags,
+                                                      const uint32_t& aAdditionalFlags,
+                                                      const uint64_t& aObserverId) override;
+    virtual bool RecvSynthesizeNativeTouchPoint(const uint32_t& aPointerId,
+                                                const TouchPointerState& aPointerState,
+                                                const nsIntPoint& aPointerScreenPoint,
+                                                const double& aPointerPressure,
+                                                const uint32_t& aPointerOrientation,
+                                                const uint64_t& aObserverId) override;
+    virtual bool RecvSynthesizeNativeTouchTap(const nsIntPoint& aPointerScreenPoint,
+                                              const bool& aLongTap,
+                                              const uint64_t& aObserverId) override;
+    virtual bool RecvClearNativeTouchSequence(const uint64_t& aObserverId) override;
+
     void SendMouseEvent(const nsAString& aType, float aX, float aY,
                         int32_t aButton, int32_t aClickCount,
                         int32_t aModifiers, bool aIgnoreRootScrollFrame);
     void SendKeyEvent(const nsAString& aType, int32_t aKeyCode,
                       int32_t aCharCode, int32_t aModifiers,
                       bool aPreventDefault);
     bool SendRealMouseEvent(mozilla::WidgetMouseEvent& event);
     bool SendRealDragEvent(mozilla::WidgetDragEvent& aEvent, uint32_t aDragAction,
@@ -291,22 +321,16 @@ public:
     AllocPDocumentRendererParent(const nsRect& documentRect,
                                  const gfx::Matrix& transform,
                                  const nsString& bgcolor,
                                  const uint32_t& renderFlags,
                                  const bool& flushLayout,
                                  const nsIntSize& renderSize) override;
     virtual bool DeallocPDocumentRendererParent(PDocumentRendererParent* actor) override;
 
-    virtual PContentPermissionRequestParent*
-    AllocPContentPermissionRequestParent(const InfallibleTArray<PermissionRequest>& aRequests,
-                                         const IPC::Principal& aPrincipal) override;
-    virtual bool
-    DeallocPContentPermissionRequestParent(PContentPermissionRequestParent* actor) override;
-
     virtual PFilePickerParent*
     AllocPFilePickerParent(const nsString& aTitle,
                            const int16_t& aMode) override;
     virtual bool DeallocPFilePickerParent(PFilePickerParent* actor) override;
 
     virtual PIndexedDBPermissionRequestParent*
     AllocPIndexedDBPermissionRequestParent(const Principal& aPrincipal)
                                            override;
--- a/dom/media/MediaPermissionGonk.cpp
+++ b/dom/media/MediaPermissionGonk.cpp
@@ -124,16 +124,17 @@ protected:
 private:
   nsresult DoAllow(const nsString &audioDevice, const nsString &videoDevice);
 
   bool mAudio; // Request for audio permission
   bool mVideo; // Request for video permission
   nsRefPtr<dom::GetUserMediaRequest> mRequest;
   nsTArray<nsCOMPtr<nsIMediaDevice> > mAudioDevices; // candidate audio devices
   nsTArray<nsCOMPtr<nsIMediaDevice> > mVideoDevices; // candidate video devices
+  nsCOMPtr<nsIContentPermissionRequester> mRequester;
 };
 
 // MediaPermissionRequest
 NS_IMPL_ISUPPORTS(MediaPermissionRequest, nsIContentPermissionRequest)
 
 MediaPermissionRequest::MediaPermissionRequest(nsRefPtr<dom::GetUserMediaRequest> &aRequest,
                                                nsTArray<nsCOMPtr<nsIMediaDevice> > &aDevices)
   : mRequest(aRequest)
@@ -150,16 +151,19 @@ MediaPermissionRequest::MediaPermissionR
     device->GetType(deviceType);
     if (mAudio && deviceType.EqualsLiteral("audio")) {
       mAudioDevices.AppendElement(device);
     }
     if (mVideo && deviceType.EqualsLiteral("video")) {
       mVideoDevices.AppendElement(device);
     }
   }
+
+  nsCOMPtr<nsPIDOMWindow> window = GetOwner();
+  mRequester = new nsContentPermissionRequester(window.get());
 }
 
 // nsIContentPermissionRequest methods
 NS_IMETHODIMP
 MediaPermissionRequest::GetTypes(nsIArray** aTypes)
 {
   nsCOMPtr<nsIMutableArray> types = do_CreateInstance(NS_ARRAY_CONTRACTID);
   //XXX append device list
@@ -269,16 +273,26 @@ MediaPermissionRequest::Allow(JS::Handle
       return NS_ERROR_FAILURE;
     }
     videoDevice = deviceName;
   }
 
   return DoAllow(audioDevice, videoDevice);
 }
 
+NS_IMETHODIMP
+MediaPermissionRequest::GetRequester(nsIContentPermissionRequester** aRequester)
+{
+  NS_ENSURE_ARG_POINTER(aRequester);
+
+  nsCOMPtr<nsIContentPermissionRequester> requester = mRequester;
+  requester.forget(aRequester);
+  return NS_OK;
+}
+
 nsresult
 MediaPermissionRequest::DoAllow(const nsString &audioDevice,
                                 const nsString &videoDevice)
 {
   nsTArray<nsCOMPtr<nsIMediaDevice> > selectedDevices;
   if (mAudio) {
     nsCOMPtr<nsIMediaDevice> device =
       FindDeviceByName(mAudioDevices, audioDevice);
--- a/dom/media/encoder/moz.build
+++ b/dom/media/encoder/moz.build
@@ -1,14 +1,17 @@
 # -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
 # vim: set filetype=python:
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
+with Files('*'):
+    BUG_COMPONENT = ('Core', 'Video/Audio: Recording')
+
 if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'gonk':
     DIRS += ['fmp4_muxer']
 
 EXPORTS += [
     'ContainerWriter.h',
     'EncodedFrameContainer.h',
     'MediaEncoder.h',
     'OpusTrackEncoder.h',
--- a/dom/media/fmp4/ffmpeg/libav53/moz.build
+++ b/dom/media/fmp4/ffmpeg/libav53/moz.build
@@ -13,8 +13,13 @@ UNIFIED_SOURCES += [
 LOCAL_INCLUDES += [
     '..',
     'include',
 ]
 
 FINAL_LIBRARY = 'xul'
 
 FAIL_ON_WARNINGS = True
+
+if CONFIG['CLANG_CXX']:
+  CXXFLAGS += [
+    '-Wno-unknown-attributes',
+  ]
--- a/dom/media/fmp4/ffmpeg/libav54/moz.build
+++ b/dom/media/fmp4/ffmpeg/libav54/moz.build
@@ -13,8 +13,13 @@ UNIFIED_SOURCES += [
 LOCAL_INCLUDES += [
     '..',
     'include',
 ]
 
 FINAL_LIBRARY = 'xul'
 
 FAIL_ON_WARNINGS = True
+
+if CONFIG['CLANG_CXX']:
+  CXXFLAGS += [
+    '-Wno-unknown-attributes',
+  ]
--- a/dom/media/fmp4/ffmpeg/libav55/moz.build
+++ b/dom/media/fmp4/ffmpeg/libav55/moz.build
@@ -14,8 +14,13 @@ LOCAL_INCLUDES += [
     '..',
     'include',
 ]
 CXXFLAGS += [ '-Wno-deprecated-declarations' ]
 
 FINAL_LIBRARY = 'xul'
 
 FAIL_ON_WARNINGS = True
+
+if CONFIG['CLANG_CXX']:
+  CXXFLAGS += [
+    '-Wno-unknown-attributes',
+  ]
--- a/dom/media/moz.build
+++ b/dom/media/moz.build
@@ -1,14 +1,29 @@
 # -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
 # vim: set filetype=python:
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
+with Files('*'):
+    BUG_COMPONENT = ('Core', 'Video/Audio')
+
+component_signaling = ('Core', 'WebRTC: Signaling')
+with Files('IdpSandbox.jsm'):
+    BUG_COMPONENT = component_signaling
+with Files('PeerConnection*'):
+    BUG_COMPONENT = component_signaling
+with Files('RTC*'):
+    BUG_COMPONENT = component_signaling
+
+component_av = ('Core', 'WebRTC: Audio/Video')
+with Files('GetUserMedia*'):
+    BUG_COMPONENT = component_av
+
 DIRS += [
     'encoder',
     'gmp',
     'gmp-plugin',
     'imagecapture',
     'mediasource',
     'ogg',
     'systemservices',
--- a/dom/media/test/test_audio2.html
+++ b/dom/media/test/test_audio2.html
@@ -16,17 +16,17 @@ var tmpAudio = new Audio();
 var manager = new MediaTestManager;
 
 function startTest(test, token) {
   if (!tmpAudio.canPlayType(test.type))
     return;
   manager.started(token);
   var a1 = new Audio(test.name);
   is(a1.getAttribute("preload"), "auto", "Preload automatically set to auto");
-  is(a1.src.match("/[^/]+$"), "/" + test.name, "src OK");
+  ok(a1.src.endsWith("/" + test.name), "src OK");
   manager.finished(token);
 }
 
 manager.runTests(gAudioTests, startTest);
 
 </script>
 </pre>
 </body>
--- a/dom/media/test/test_preload_attribute.html
+++ b/dom/media/test/test_preload_attribute.html
@@ -19,20 +19,20 @@ https://bugzilla.mozilla.org/show_bug.cg
 <video id='v2' preload="auto"></video><audio id='a2' preload="auto"></audio>
 
 <pre id="test">
 <script type="application/javascript">
 var v1 = document.getElementById('v1');
 var a1 = document.getElementById('a1');
 var v2 = document.getElementById('v2');
 var a2 = document.getElementById('a2');
-is(v1.getAttribute("preload"), undefined, "video preload via getAttribute should be undefined by default");
-is(a1.getAttribute("preload"), undefined, "video preload via getAttribute should be undefined by default");
-is(v1.preload, "", "v1.preload should be undefined by default");
-is(a1.preload, "", "a1.preload should be undefined by default");
+is(v1.getAttribute("preload"), null, "video preload via getAttribute should be null by default");
+is(a1.getAttribute("preload"), null, "video preload via getAttribute should be null by default");
+is(v1.preload, "", "v1.preload should be empty by default");
+is(a1.preload, "", "a1.preload should be empty by default");
 is(v2.preload, "auto", "v2.preload should be auto");
 is(a2.preload, "auto", "a2.preload should be auto");
 
 v1.preload = "auto";
 a1.preload = "auto";
 is(v1.preload, "auto", "video.preload should be auto");
 is(a1.preload, "auto", "audio.preload should be auto");
 is(v1.getAttribute("preload"), "auto", "video preload attribute should be auto");
--- a/dom/media/test/test_texttrackcue.html
+++ b/dom/media/test/test_texttrackcue.html
@@ -182,17 +182,17 @@ SpecialPowers.pushPrefEnv({"set": [["med
       is(cue.line, "auto", "Cue's line value should initially be auto.");
       cue.line = 12410
       is(cue.line, 12410, "Cue's line value should now be 12410.");
       cue.line = "auto";
       is(cue.line, "auto", "Cue's line value should now be auto.");
 
       // Check that we can create and add new VTTCues
       var vttCue = new VTTCue(3.999, 4, "foo");
-      is(vttCue.track, undefined, "Cue's track should be undefined.");
+      is(vttCue.track, null, "Cue's track should be null.");
       trackElement.track.addCue(vttCue);
       is(cue.track, trackElement.track, "Cue's track should be defined.");
       is(cueList.length, 7, "Cue list length should now be 7.");
 
       // Check that new VTTCue was added correctly
       cue = cueList[6];
       is(cue.startTime, 3.999, "Cue's start time should be 3.999.");
       is(cue.endTime, 4, "Cue's end time should be 4.");
--- a/dom/media/tests/mochitest/dataChannel.js
+++ b/dom/media/tests/mochitest/dataChannel.js
@@ -48,18 +48,18 @@ var commandsCheckDataChannel = [
     var message = "Lorem ipsum dolor sit amet";
 
     return test.send(message).then(result => {
       is(result.data, message, "Message correctly transmitted from pcLocal to pcRemote.");
     });
   },
 
   function SEND_BLOB(test) {
-    var contents = ["At vero eos et accusam et justo duo dolores et ea rebum."];
-    var blob = new Blob(contents, { "type" : "text/plain" });
+    var contents = "At vero eos et accusam et justo duo dolores et ea rebum.";
+    var blob = new Blob([contents], { "type" : "text/plain" });
 
     return test.send(blob).then(result => {
       ok(result.data instanceof Blob, "Received data is of instance Blob");
       is(result.data.size, blob.size, "Received data has the correct size.");
 
       return getBlobContent(result.data);
     }).then(recv_contents =>
             is(recv_contents, contents, "Received data has the correct content."));
--- a/dom/media/webaudio/AudioBufferSourceNode.cpp
+++ b/dom/media/webaudio/AudioBufferSourceNode.cpp
@@ -9,38 +9,41 @@
 #include "mozilla/dom/AudioParam.h"
 #include "mozilla/FloatingPoint.h"
 #include "nsMathUtils.h"
 #include "AudioNodeEngine.h"
 #include "AudioNodeStream.h"
 #include "AudioDestinationNode.h"
 #include "AudioParamTimeline.h"
 #include <limits>
+#include <algorithm>
 
 namespace mozilla {
 namespace dom {
 
 NS_IMPL_CYCLE_COLLECTION_CLASS(AudioBufferSourceNode)
 
 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(AudioBufferSourceNode)
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mBuffer)
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mPlaybackRate)
+  NS_IMPL_CYCLE_COLLECTION_UNLINK(mDetune)
   if (tmp->Context()) {
     // AudioNode's Unlink implementation disconnects us from the graph
     // too, but we need to do this right here to make sure that
     // UnregisterAudioBufferSourceNode can properly untangle us from
     // the possibly connected PannerNodes.
     tmp->DisconnectFromGraph();
     tmp->Context()->UnregisterAudioBufferSourceNode(tmp);
   }
 NS_IMPL_CYCLE_COLLECTION_UNLINK_END_INHERITED(AudioNode)
 
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(AudioBufferSourceNode, AudioNode)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mBuffer)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPlaybackRate)
+  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDetune)
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
 
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(AudioBufferSourceNode)
 NS_INTERFACE_MAP_END_INHERITING(AudioNode)
 
 NS_IMPL_ADDREF_INHERITED(AudioBufferSourceNode, AudioNode)
 NS_IMPL_RELEASE_INHERITED(AudioBufferSourceNode, AudioNode)
 
@@ -59,17 +62,19 @@ public:
     mStart(0.0), mBeginProcessing(0),
     mStop(STREAM_TIME_MAX),
     mResampler(nullptr), mRemainingResamplerTail(0),
     mBufferEnd(0),
     mLoopStart(0), mLoopEnd(0),
     mBufferSampleRate(0), mBufferPosition(0), mChannels(0),
     mDopplerShift(1.0f),
     mDestination(static_cast<AudioNodeStream*>(aDestination->Stream())),
-    mPlaybackRateTimeline(1.0f), mLoop(false)
+    mPlaybackRateTimeline(1.0f),
+    mDetuneTimeline(0.0f),
+    mLoop(false)
   {}
 
   ~AudioBufferSourceNodeEngine()
   {
     if (mResampler) {
       speex_resampler_destroy(mResampler);
     }
   }
@@ -83,16 +88,20 @@ public:
                                     const dom::AudioParamTimeline& aValue,
                                     TrackRate aSampleRate) override
   {
     switch (aIndex) {
     case AudioBufferSourceNode::PLAYBACKRATE:
       mPlaybackRateTimeline = aValue;
       WebAudioUtils::ConvertAudioParamToTicks(mPlaybackRateTimeline, mSource, mDestination);
       break;
+    case AudioBufferSourceNode::DETUNE:
+      mDetuneTimeline = aValue;
+      WebAudioUtils::ConvertAudioParamToTicks(mDetuneTimeline, mSource, mDestination);
+      break;
     default:
       NS_ERROR("Bad AudioBufferSourceNodeEngine TimelineParameter");
     }
   }
   virtual void SetStreamTimeParameter(uint32_t aIndex, StreamTime aParam) override
   {
     switch (aIndex) {
     case AudioBufferSourceNode::STOP: mStop = aParam; break;
@@ -221,17 +230,17 @@ public:
       float* baseChannelData = static_cast<float*>(const_cast<void*>(aOutput->mChannelData[i]));
       memcpy(baseChannelData + aOffsetWithinBlock,
              mBuffer->GetData(i) + mBufferPosition,
              aNumberOfFrames * sizeof(float));
     }
   }
 
   // Resamples input data to an output buffer, according to |mBufferSampleRate| and
-  // the playbackRate.
+  // the playbackRate/detune.
   // The number of frames consumed/produced depends on the amount of space
   // remaining in both the input and output buffer, and the playback rate (that
   // is, the ratio between the output samplerate and the input samplerate).
   void CopyFromInputBufferWithResampling(AudioNodeStream* aStream,
                                          AudioChunk* aOutput,
                                          uint32_t aChannels,
                                          uint32_t* aOffsetWithinBlock,
                                          StreamTime* aCurrentPosition,
@@ -392,40 +401,49 @@ public:
         *aCurrentPosition += numFrames;
         mBufferPosition += numFrames;
       } else {
         CopyFromInputBufferWithResampling(aStream, aOutput, aChannels, aOffsetWithinBlock, aCurrentPosition, aBufferMax);
       }
     }
   }
 
-  int32_t ComputeFinalOutSampleRate(float aPlaybackRate)
+  int32_t ComputeFinalOutSampleRate(float aPlaybackRate, float aDetune)
   {
+    float computedPlaybackRate = aPlaybackRate * pow(2, aDetune / 1200.f);
     // Make sure the playback rate and the doppler shift are something
     // our resampler can work with.
     int32_t rate = WebAudioUtils::
       TruncateFloatToInt<int32_t>(mSource->SampleRate() /
-                                  (aPlaybackRate * mDopplerShift));
+                                  (computedPlaybackRate * mDopplerShift));
     return rate ? rate : mBufferSampleRate;
   }
 
   void UpdateSampleRateIfNeeded(uint32_t aChannels)
   {
     float playbackRate;
+    float detune;
 
     if (mPlaybackRateTimeline.HasSimpleValue()) {
       playbackRate = mPlaybackRateTimeline.GetValue();
     } else {
       playbackRate = mPlaybackRateTimeline.GetValueAtTime(mSource->GetCurrentPosition());
     }
+    if (mDetuneTimeline.HasSimpleValue()) {
+      detune = mDetuneTimeline.GetValue();
+    } else {
+      detune = mDetuneTimeline.GetValueAtTime(mSource->GetCurrentPosition());
+    }
     if (playbackRate <= 0 || mozilla::IsNaN(playbackRate)) {
       playbackRate = 1.0f;
     }
 
-    int32_t outRate = ComputeFinalOutSampleRate(playbackRate);
+    detune = std::min(std::max(-1200.f, detune), 1200.f);
+
+    int32_t outRate = ComputeFinalOutSampleRate(playbackRate, detune);
     UpdateResampler(outRate, aChannels);
   }
 
   virtual void ProcessBlock(AudioNodeStream* aStream,
                             const AudioChunk& aInput,
                             AudioChunk* aOutput,
                             bool* aFinished) override
   {
@@ -435,19 +453,16 @@ public:
     }
 
     uint32_t channels = mBuffer->GetChannels();
     if (!channels) {
       aOutput->SetNull(WEBAUDIO_BLOCK_SIZE);
       return;
     }
 
-    // WebKit treats the playbackRate as a k-rate parameter in their code,
-    // despite the spec saying that it should be an a-rate parameter. We treat
-    // it as k-rate. Spec bug: https://www.w3.org/Bugs/Public/show_bug.cgi?id=21592
     UpdateSampleRateIfNeeded(channels);
 
     uint32_t written = 0;
     StreamTime streamPosition = aStream->GetCurrentPosition();
     while (written < WEBAUDIO_BLOCK_SIZE) {
       if (mStop != STREAM_TIME_MAX &&
           streamPosition >= mStop) {
         FillWithZeroes(aOutput, channels, &written, &streamPosition, STREAM_TIME_MAX);
@@ -483,16 +498,17 @@ public:
     }
   }
 
   virtual size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const override
   {
     // Not owned:
     // - mBuffer - shared w/ AudioNode
     // - mPlaybackRateTimeline - shared w/ AudioNode
+    // - mDetuneTimeline - 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
@@ -525,28 +541,30 @@ public:
   int32_t mLoopEnd;
   int32_t mBufferSampleRate;
   int32_t mBufferPosition;
   uint32_t mChannels;
   float mDopplerShift;
   AudioNodeStream* mDestination;
   AudioNodeStream* mSource;
   AudioParamTimeline mPlaybackRateTimeline;
+  AudioParamTimeline mDetuneTimeline;
   bool mLoop;
 };
 
 AudioBufferSourceNode::AudioBufferSourceNode(AudioContext* aContext)
   : AudioNode(aContext,
               2,
               ChannelCountMode::Max,
               ChannelInterpretation::Speakers)
   , mLoopStart(0.0)
   , mLoopEnd(0.0)
   // mOffset and mDuration are initialized in Start().
-  , mPlaybackRate(new AudioParam(this, SendPlaybackRateToStream, 1.0f))
+  , mPlaybackRate(new AudioParam(this, SendPlaybackRateToStream, 1.0f, "playbackRate"))
+  , mDetune(new AudioParam(this, SendDetuneToStream, 0.0f, "detune"))
   , mLoop(false)
   , mStartCalled(false)
   , mStopped(false)
 {
   AudioBufferSourceNodeEngine* engine = new AudioBufferSourceNodeEngine(this, aContext->Destination());
   mStream = aContext->Graph()->CreateAudioNodeStream(engine, MediaStreamGraph::SOURCE_STREAM);
   engine->SetSourceStream(static_cast<AudioNodeStream*>(mStream.get()));
   mStream->AddMainThreadListener(this);
@@ -563,16 +581,17 @@ size_t
 AudioBufferSourceNode::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const
 {
   size_t amount = AudioNode::SizeOfExcludingThis(aMallocSizeOf);
   if (mBuffer) {
     amount += mBuffer->SizeOfIncludingThis(aMallocSizeOf);
   }
 
   amount += mPlaybackRate->SizeOfIncludingThis(aMallocSizeOf);
+  amount += mDetune->SizeOfIncludingThis(aMallocSizeOf);
   return amount;
 }
 
 size_t
 AudioBufferSourceNode::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const
 {
   return aMallocSizeOf(this) + SizeOfExcludingThis(aMallocSizeOf);
 }
@@ -728,16 +747,23 @@ AudioBufferSourceNode::NotifyMainThreadS
 void
 AudioBufferSourceNode::SendPlaybackRateToStream(AudioNode* aNode)
 {
   AudioBufferSourceNode* This = static_cast<AudioBufferSourceNode*>(aNode);
   SendTimelineParameterToStream(This, PLAYBACKRATE, *This->mPlaybackRate);
 }
 
 void
+AudioBufferSourceNode::SendDetuneToStream(AudioNode* aNode)
+{
+  AudioBufferSourceNode* This = static_cast<AudioBufferSourceNode*>(aNode);
+  SendTimelineParameterToStream(This, DETUNE, *This->mDetune);
+}
+
+void
 AudioBufferSourceNode::SendDopplerShiftToStream(double aDopplerShift)
 {
   SendDoubleParameterToStream(DOPPLERSHIFT, aDopplerShift);
 }
 
 void
 AudioBufferSourceNode::SendLoopParametersToStream()
 {
--- a/dom/media/webaudio/AudioBufferSourceNode.h
+++ b/dom/media/webaudio/AudioBufferSourceNode.h
@@ -54,16 +54,20 @@ public:
     mBuffer = aBuffer;
     SendBufferParameterToStream(aCx);
     SendLoopParametersToStream();
   }
   AudioParam* PlaybackRate() const
   {
     return mPlaybackRate;
   }
+  AudioParam* Detune() const
+  {
+    return mDetune;
+  }
   bool Loop() const
   {
     return mLoop;
   }
   void SetLoop(bool aLoop)
   {
     mLoop = aLoop;
     SendLoopParametersToStream();
@@ -118,31 +122,34 @@ private:
     BUFFERSTART,
     // BUFFEREND is the sum of "offset" and "duration" passed to start(),
     // multiplied by buffer.sampleRate, or the size of the buffer, if smaller.
     BUFFEREND,
     LOOP,
     LOOPSTART,
     LOOPEND,
     PLAYBACKRATE,
+    DETUNE,
     DOPPLERSHIFT
   };
 
   void SendLoopParametersToStream();
   void SendBufferParameterToStream(JSContext* aCx);
   void SendOffsetAndDurationParametersToStream(AudioNodeStream* aStream);
   static void SendPlaybackRateToStream(AudioNode* aNode);
+  static void SendDetuneToStream(AudioNode* aNode);
 
 private:
   double mLoopStart;
   double mLoopEnd;
   double mOffset;
   double mDuration;
   nsRefPtr<AudioBuffer> mBuffer;
   nsRefPtr<AudioParam> mPlaybackRate;
+  nsRefPtr<AudioParam> mDetune;
   bool mLoop;
   bool mStartCalled;
   bool mStopped;
 };
 
 }
 }
 
--- a/dom/media/webaudio/AudioParam.cpp
+++ b/dom/media/webaudio/AudioParam.cpp
@@ -40,21 +40,23 @@ AudioParam::Release()
   NS_IMPL_CC_NATIVE_RELEASE_BODY(AudioParam)
 }
 
 NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(AudioParam, AddRef)
 NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(AudioParam, Release)
 
 AudioParam::AudioParam(AudioNode* aNode,
                        AudioParam::CallbackType aCallback,
-                       float aDefaultValue)
+                       float aDefaultValue,
+                       const char* aName)
   : AudioParamTimeline(aDefaultValue)
   , mNode(aNode)
   , mCallback(aCallback)
   , mDefaultValue(aDefaultValue)
+  , mName(aName)
 {
 }
 
 AudioParam::~AudioParam()
 {
   MOZ_ASSERT(mInputNodes.IsEmpty());
 }
 
--- a/dom/media/webaudio/AudioParam.h
+++ b/dom/media/webaudio/AudioParam.h
@@ -25,17 +25,18 @@ class AudioParam final : public nsWrappe
 {
   virtual ~AudioParam();
 
 public:
   typedef void (*CallbackType)(AudioNode*);
 
   AudioParam(AudioNode* aNode,
              CallbackType aCallback,
-             float aDefaultValue);
+             float aDefaultValue,
+             const char* aName);
 
   NS_IMETHOD_(MozExternalRefCountType) AddRef(void);
   NS_IMETHOD_(MozExternalRefCountType) Release(void);
   NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_NATIVE_CLASS(AudioParam)
 
   AudioContext* GetParentObject() const
   {
     return mNode->Context();
@@ -116,16 +117,26 @@ public:
     if (!WebAudioUtils::IsTimeValid(aStartTime)) {
       aRv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
       return;
     }
     AudioParamTimeline::CancelScheduledValues(DOMTimeToStreamTime(aStartTime));
     mCallback(mNode);
   }
 
+  uint32_t ParentNodeId()
+  {
+    return mNode->Id();
+  }
+
+  void GetName(nsAString& aName)
+  {
+    aName.AssignASCII(mName);
+  }
+
   float DefaultValue() const
   {
     return mDefaultValue;
   }
 
   AudioNode* Node() const
   {
     return mNode;
@@ -178,16 +189,17 @@ protected:
 
 private:
   nsRefPtr<AudioNode> mNode;
   // For every InputNode, there is a corresponding entry in mOutputParams of the
   // InputNode's mInputNode.
   nsTArray<AudioNode::InputNode> mInputNodes;
   CallbackType mCallback;
   const float mDefaultValue;
+  const char* mName;
   // The input port used to connect the AudioParam's stream to its node's stream
   nsRefPtr<MediaInputPort> mNodeStreamPort;
 };
 
 }
 }
 
 #endif
--- a/dom/media/webaudio/BiquadFilterNode.cpp
+++ b/dom/media/webaudio/BiquadFilterNode.cpp
@@ -239,20 +239,20 @@ private:
 };
 
 BiquadFilterNode::BiquadFilterNode(AudioContext* aContext)
   : AudioNode(aContext,
               2,
               ChannelCountMode::Max,
               ChannelInterpretation::Speakers)
   , mType(BiquadFilterType::Lowpass)
-  , mFrequency(new AudioParam(this, SendFrequencyToStream, 350.f))
-  , mDetune(new AudioParam(this, SendDetuneToStream, 0.f))
-  , mQ(new AudioParam(this, SendQToStream, 1.f))
-  , mGain(new AudioParam(this, SendGainToStream, 0.f))
+  , mFrequency(new AudioParam(this, SendFrequencyToStream, 350.f, "frequency"))
+  , mDetune(new AudioParam(this, SendDetuneToStream, 0.f, "detune"))
+  , mQ(new AudioParam(this, SendQToStream, 1.f, "Q"))
+  , mGain(new AudioParam(this, SendGainToStream, 0.f, "gain"))
 {
   BiquadFilterNodeEngine* engine = new BiquadFilterNodeEngine(this, aContext->Destination());
   mStream = aContext->Graph()->CreateAudioNodeStream(engine, MediaStreamGraph::INTERNAL_STREAM);
   engine->SetSourceStream(static_cast<AudioNodeStream*> (mStream.get()));
 }
 
 BiquadFilterNode::~BiquadFilterNode()
 {
--- a/dom/media/webaudio/DelayNode.cpp
+++ b/dom/media/webaudio/DelayNode.cpp
@@ -185,17 +185,17 @@ public:
   int32_t mLeftOverData;
 };
 
 DelayNode::DelayNode(AudioContext* aContext, double aMaxDelay)
   : AudioNode(aContext,
               2,
               ChannelCountMode::Max,
               ChannelInterpretation::Speakers)
-  , mDelay(new AudioParam(this, SendDelayToStream, 0.0f))
+  , mDelay(new AudioParam(this, SendDelayToStream, 0.0f, "delayTime"))
 {
   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()));
 }
 
--- a/dom/media/webaudio/DynamicsCompressorNode.cpp
+++ b/dom/media/webaudio/DynamicsCompressorNode.cpp
@@ -196,22 +196,22 @@ private:
   nsAutoPtr<DynamicsCompressor> mCompressor;
 };
 
 DynamicsCompressorNode::DynamicsCompressorNode(AudioContext* aContext)
   : AudioNode(aContext,
               2,
               ChannelCountMode::Explicit,
               ChannelInterpretation::Speakers)
-  , mThreshold(new AudioParam(this, SendThresholdToStream, -24.f))
-  , mKnee(new AudioParam(this, SendKneeToStream, 30.f))
-  , mRatio(new AudioParam(this, SendRatioToStream, 12.f))
+  , mThreshold(new AudioParam(this, SendThresholdToStream, -24.f, "threshold"))
+  , mKnee(new AudioParam(this, SendKneeToStream, 30.f, "knee"))
+  , mRatio(new AudioParam(this, SendRatioToStream, 12.f, "ratio"))
   , mReduction(0)
-  , mAttack(new AudioParam(this, SendAttackToStream, 0.003f))
-  , mRelease(new AudioParam(this, SendReleaseToStream, 0.25f))
+  , mAttack(new AudioParam(this, SendAttackToStream, 0.003f, "attack"))
+  , mRelease(new AudioParam(this, SendReleaseToStream, 0.25f, "release"))
 {
   DynamicsCompressorNodeEngine* engine = new DynamicsCompressorNodeEngine(this, aContext->Destination());
   mStream = aContext->Graph()->CreateAudioNodeStream(engine, MediaStreamGraph::INTERNAL_STREAM);
   engine->SetSourceStream(static_cast<AudioNodeStream*> (mStream.get()));
 }
 
 DynamicsCompressorNode::~DynamicsCompressorNode()
 {
--- a/dom/media/webaudio/GainNode.cpp
+++ b/dom/media/webaudio/GainNode.cpp
@@ -120,17 +120,17 @@ public:
   AudioParamTimeline mGain;
 };
 
 GainNode::GainNode(AudioContext* aContext)
   : AudioNode(aContext,
               2,
               ChannelCountMode::Max,
               ChannelInterpretation::Speakers)
-  , mGain(new AudioParam(this, SendGainToStream, 1.0f))
+  , mGain(new AudioParam(this, SendGainToStream, 1.0f, "gain"))
 {
   GainNodeEngine* engine = new GainNodeEngine(this, aContext->Destination());
   mStream = aContext->Graph()->CreateAudioNodeStream(engine, MediaStreamGraph::INTERNAL_STREAM);
   engine->SetSourceStream(static_cast<AudioNodeStream*> (mStream.get()));
 }
 
 GainNode::~GainNode()
 {
--- a/dom/media/webaudio/OscillatorNode.cpp
+++ b/dom/media/webaudio/OscillatorNode.cpp
@@ -376,18 +376,18 @@ public:
 };
 
 OscillatorNode::OscillatorNode(AudioContext* aContext)
   : AudioNode(aContext,
               2,
               ChannelCountMode::Max,
               ChannelInterpretation::Speakers)
   , mType(OscillatorType::Sine)
-  , mFrequency(new AudioParam(this, SendFrequencyToStream, 440.0f))
-  , mDetune(new AudioParam(this, SendDetuneToStream, 0.0f))
+  , mFrequency(new AudioParam(this, SendFrequencyToStream, 440.0f, "frequency"))
+  , mDetune(new AudioParam(this, SendDetuneToStream, 0.0f, "detune"))
   , mStartCalled(false)
   , mStopped(false)
 {
   OscillatorNodeEngine* engine = new OscillatorNodeEngine(this, aContext->Destination());
   mStream = aContext->Graph()->CreateAudioNodeStream(engine, MediaStreamGraph::SOURCE_STREAM);
   engine->SetSourceStream(static_cast<AudioNodeStream*> (mStream.get()));
   mStream->AddMainThreadListener(this);
 }
--- a/dom/media/webaudio/StereoPannerNode.cpp
+++ b/dom/media/webaudio/StereoPannerNode.cpp
@@ -171,17 +171,17 @@ public:
   AudioParamTimeline mPan;
 };
 
 StereoPannerNode::StereoPannerNode(AudioContext* aContext)
   : AudioNode(aContext,
               2,
               ChannelCountMode::Clamped_max,
               ChannelInterpretation::Speakers)
-  , mPan(new AudioParam(this, SendPanToStream, 0.f))
+  , mPan(new AudioParam(this, SendPanToStream, 0.f, "pan"))
 {
   StereoPannerNodeEngine* engine = new StereoPannerNodeEngine(this, aContext->Destination());
   mStream = aContext->Graph()->CreateAudioNodeStream(engine,
                                                      MediaStreamGraph::INTERNAL_STREAM);
   engine->SetSourceStream(static_cast<AudioNodeStream*>(mStream.get()));
 }
 
 StereoPannerNode::~StereoPannerNode()
--- a/dom/media/webaudio/moz.build
+++ b/dom/media/webaudio/moz.build
@@ -1,14 +1,17 @@
 # -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
 # vim: set filetype=python:
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
+with Files('*'):
+    BUG_COMPONENT = ('Core', 'Web Audio')
+
 DIRS += ['blink']
 
 TEST_DIRS += ['compiledtest']
 
 MOCHITEST_MANIFESTS += [
     'test/blink/mochitest.ini',
     'test/mochitest.ini',
 ]
--- a/dom/media/webaudio/test/chrome.ini
+++ b/dom/media/webaudio/test/chrome.ini
@@ -1,4 +1,5 @@
 [DEFAULT]
 skip-if = buildapp == 'b2g'
 
 [test_AudioNodeDevtoolsAPI.html]
+[test_AudioParamDevtoolsAPI.html]
--- a/dom/media/webaudio/test/mochitest.ini
+++ b/dom/media/webaudio/test/mochitest.ini
@@ -38,16 +38,17 @@ support-files =
 [test_audioBufferSourceNodeLoopStartEndSame.html]
 [test_audioBufferSourceNodeNeutered.html]
 skip-if = (toolkit == 'android' && (processor == 'x86' || debug)) || os == 'win' # bug 1127845, bug 1138468
 [test_audioBufferSourceNodeNoStart.html]
 [test_audioBufferSourceNodeNullBuffer.html]
 [test_audioBufferSourceNodeOffset.html]
 skip-if = (toolkit == 'gonk') || (toolkit == 'android') || debug #bug 906752
 [test_audioBufferSourceNodePassThrough.html]
+[test_audioBufferSourceNodeRate.html]
 [test_AudioContext.html]
 [test_audioDestinationNode.html]
 [test_AudioListener.html]
 [test_audioParamExponentialRamp.html]
 [test_audioParamGain.html]
 [test_audioParamLinearRamp.html]
 [test_audioParamSetCurveAtTime.html]
 [test_audioParamSetCurveAtTimeZeroDuration.html]
new file mode 100644
--- /dev/null
+++ b/dom/media/webaudio/test/test_AudioParamDevtoolsAPI.html
@@ -0,0 +1,48 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <title>Test the devtool AudioParam API</title>
+  <script type="text/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css" />
+</head>
+<body>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+  Components.utils.import('resource://gre/modules/Services.jsm');
+
+  function checkIdAndName(node, name) {
+    is(node.id, node[name].parentNodeId, "The parent id should be correct");
+    is(node[name].name, name, "The name of the AudioParam should be correct.");
+  }
+
+  var ac = new AudioContext(),
+      gain = ac.createGain(),
+      osc = ac.createOscillator(),
+      del = ac.createDelay(),
+      source = ac.createBufferSource(),
+      stereoPanner = ac.createStereoPanner(),
+      comp = ac.createDynamicsCompressor(),
+      biquad = ac.createBiquadFilter();
+
+  checkIdAndName(gain, "gain");
+  checkIdAndName(osc, "frequency");
+  checkIdAndName(osc, "detune");
+  checkIdAndName(del, "delayTime");
+  checkIdAndName(source, "playbackRate");
+  checkIdAndName(source, "detune");
+  checkIdAndName(stereoPanner, "pan");
+  checkIdAndName(comp, "threshold");
+  checkIdAndName(comp, "knee");
+  checkIdAndName(comp, "ratio");
+  checkIdAndName(comp, "attack");
+  checkIdAndName(comp, "release");
+  checkIdAndName(biquad, "frequency");
+  checkIdAndName(biquad, "detune");
+  checkIdAndName(biquad, "Q");
+  checkIdAndName(biquad, "gain");
+
+</script>
+</pre>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/media/webaudio/test/test_audioBufferSourceNodeRate.html
@@ -0,0 +1,66 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <title>Test AudioBufferSourceNode</title>
+  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <script type="text/javascript" src="webaudio.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+
+SimpleTest.waitForExplicitFinish();
+
+var rate = 44100;
+var off = new OfflineAudioContext(1, rate, rate);
+var off2 = new OfflineAudioContext(1, rate, rate);
+
+var source = off.createBufferSource();
+var source2 = off2.createBufferSource();
+
+// a buffer of a 440Hz at half the length. If we detune by -1200 or set the
+// playbackRate to 0.5, we should get 44100 samples back with a sine at 220Hz.
+var buf = off.createBuffer(1, rate / 2, rate);
+var bufarray = buf.getChannelData(0);
+for (var i = 0; i < bufarray.length; i++) {
+  bufarray[i] = Math.sin(i * 440 * 2 * Math.PI / rate);
+}
+
+source.buffer = buf;
+source.playbackRate.value = 0.5; // 50% slowdown
+source.connect(off.destination);
+source.start(0);
+
+source2.buffer = buf;
+source2.detune.value = -1200; // one octave -> 50% slowdown
+source2.connect(off2.destination);
+source2.start(0);
+
+var finished = 0;
+function finish() {
+  finished++;
+  if (finished == 2) {
+    SimpleTest.finish();
+  }
+}
+
+off.startRendering().then((renderedPlaybackRate) => {
+  // we don't care about comparing the value here, we just want to know whether
+  // the second part is noisy.
+  var rmsValue = rms(renderedPlaybackRate, 0, 22050);
+  ok(rmsValue != 0, "Resampling happened (rms of the second part " + rmsValue + ")");
+
+  off2.startRendering().then((renderedDetune) => {
+    var rmsValue = rms(renderedDetune, 0, 22050);
+    ok(rmsValue != 0, "Resampling happened (rms of the second part " + rmsValue + ")");
+    // The two buffers should be the same: detune of -1200 is a 50% slowdown
+    compareBuffers(renderedPlaybackRate, renderedDetune);
+    SimpleTest.finish();
+  });
+});
+
+</script>
+</pre>
+</body>
+</html>
--- a/dom/media/webaudio/test/webaudio.js
+++ b/dom/media/webaudio/test/webaudio.js
@@ -88,16 +88,35 @@ function compareBuffers(got, expected) {
   }
 
   for (var i = 0; i < got.numberOfChannels; ++i) {
     compareChannels(got.getChannelData(i), expected.getChannelData(i),
                     got.length, 0, 0, true);
   }
 }
 
+/**
+ * Compute the root mean square (RMS,
+ * <http://en.wikipedia.org/wiki/Root_mean_square>) of a channel of a slice
+ * (defined by `start` and `end`) of an AudioBuffer.
+ *
+ * This is useful to detect that a buffer is noisy or silent.
+ */
+function rms(audiobuffer, channel = 0, start = 0, end = audiobuffer.length) {
+  var buffer= audiobuffer.getChannelData(channel);
+  var rms = 0;
+  for (var i = start; i < end; i++) {
+    rms += buffer[i] * buffer[i];
+  }
+
+  rms /= buffer.length;
+  rms = Math.sqrt(rms);
+  return rms;
+}
+
 function getEmptyBuffer(context, length) {
   return context.createBuffer(gTest.numberOfChannels, length, context.sampleRate);
 }
 
 /**
  * This function assumes that the test file defines a single gTest variable with
  * the following properties and methods:
  *
--- a/dom/media/webrtc/moz.build
+++ b/dom/media/webrtc/moz.build
@@ -1,14 +1,20 @@
 # -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
 # vim: set filetype=python:
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
+with Files('*'):
+    BUG_COMPONENT = ('Core', 'WebRTC: Audio/Video')
+
+with Files('PeerIdentity.*'):
+    BUG_COMPONENT = ('Core', 'WebRTC: Signaling')
+
 XPIDL_MODULE = 'content_webrtc'
 
 EXPORTS += [
     'MediaEngine.h',
     'MediaEngineCameraVideoSource.h',
     'MediaEngineDefault.h',
     'MediaTrackConstraints.h',
 ]
--- a/dom/notification/DesktopNotification.cpp
+++ b/dom/notification/DesktopNotification.cpp
@@ -25,22 +25,26 @@ namespace dom {
  */
 class DesktopNotificationRequest : public nsIContentPermissionRequest
                                  , public nsRunnable
 {
   virtual ~DesktopNotificationRequest()
   {
   }
 
+  nsCOMPtr<nsIContentPermissionRequester> mRequester;
 public:
   NS_DECL_ISUPPORTS_INHERITED
   NS_DECL_NSICONTENTPERMISSIONREQUEST
 
   explicit DesktopNotificationRequest(DesktopNotification* aNotification)
-    : mDesktopNotification(aNotification) {}
+    : mDesktopNotification(aNotification)
+  {
+    mRequester = new nsContentPermissionRequester(mDesktopNotification->GetOwner());
+  }
 
   NS_IMETHOD Run() override
   {
     nsCOMPtr<nsPIDOMWindow> window = mDesktopNotification->GetOwner();
     nsContentPermissionUtils::AskPermission(this, window);
     return NS_OK;
   }
 
@@ -309,16 +313,26 @@ DesktopNotificationRequest::Allow(JS::Ha
 {
   MOZ_ASSERT(aChoices.isUndefined());
   nsresult rv = mDesktopNotification->SetAllow(true);
   mDesktopNotification = nullptr;
   return rv;
 }
 
 NS_IMETHODIMP
+DesktopNotificationRequest::GetRequester(nsIContentPermissionRequester** aRequester)
+{
+  NS_ENSURE_ARG_POINTER(aRequester);
+
+  nsCOMPtr<nsIContentPermissionRequester> requester = mRequester;
+  requester.forget(aRequester);
+  return NS_OK;
+}
+
+NS_IMETHODIMP
 DesktopNotificationRequest::GetTypes(nsIArray** aTypes)
 {
   nsTArray<nsString> emptyOptions;
   return nsContentPermissionUtils::CreatePermissionArray(NS_LITERAL_CSTRING("desktop-notification"),
                                                          NS_LITERAL_CSTRING("unused"),
                                                          emptyOptions,
                                                          aTypes);
 }
--- a/dom/notification/Notification.cpp
+++ b/dom/notification/Notification.cpp
@@ -167,27 +167,31 @@ public:
   NS_DECL_NSIRUNNABLE
   NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(NotificationPermissionRequest,
                                            nsIContentPermissionRequest)
 
   NotificationPermissionRequest(nsIPrincipal* aPrincipal, nsPIDOMWindow* aWindow,
                                 NotificationPermissionCallback* aCallback)
     : mPrincipal(aPrincipal), mWindow(aWindow),
       mPermission(NotificationPermission::Default),
-      mCallback(aCallback) {}
+      mCallback(aCallback)
+  {
+    mRequester = new nsContentPermissionRequester(mWindow);
+  }
 
 protected:
   virtual ~NotificationPermissionRequest() {}
 
   nsresult CallCallback();
   nsresult DispatchCallback();
   nsCOMPtr<nsIPrincipal> mPrincipal;
   nsCOMPtr<nsPIDOMWindow> mWindow;
   NotificationPermission mPermission;
   nsRefPtr<NotificationPermissionCallback> mCallback;
+  nsCOMPtr<nsIContentPermissionRequester> mRequester;
 };
 
 class NotificationObserver : public nsIObserver
 {
 public:
   NS_DECL_ISUPPORTS
   NS_DECL_NSIOBSERVER
 
@@ -302,16 +306,26 @@ NS_IMETHODIMP
 NotificationPermissionRequest::Allow(JS::HandleValue aChoices)
 {
   MOZ_ASSERT(aChoices.isUndefined());
 
   mPermission = NotificationPermission::Granted;
   return DispatchCallback();
 }
 
+NS_IMETHODIMP
+NotificationPermissionRequest::GetRequester(nsIContentPermissionRequester** aRequester)
+{
+  NS_ENSURE_ARG_POINTER(aRequester);
+
+  nsCOMPtr<nsIContentPermissionRequester> requester = mRequester;
+  requester.forget(aRequester);
+  return NS_OK;
+}
+
 inline nsresult
 NotificationPermissionRequest::DispatchCallback()
 {
   if (!mCallback) {
     return NS_OK;
   }
 
   nsCOMPtr<nsIRunnable> callbackRunnable = NS_NewRunnableMethod(this,
--- a/dom/plugins/base/nsPluginDirServiceProvider.cpp
+++ b/dom/plugins/base/nsPluginDirServiceProvider.cpp
@@ -111,17 +111,17 @@ TranslateVersionStr(const WCHAR* szVersi
   }
 
   // Java may be using an underscore instead of a dot for the build ID
   szJavaBuild = wcschr(strVer, '_');
   if (szJavaBuild) {
     szJavaBuild[0] = '.';
   }
 
-#if defined(_MSC_VER) && _MSC_VER < 1900
+#if (defined(_MSC_VER) && _MSC_VER < 1900) || defined(__MINGW32__)
   // MSVC 2013 and earlier provided only a non-standard two-argument variant of
   // wcstok that is generally not thread-safe. For our purposes here, it works
   // fine, though.
   auto wcstok = [](wchar_t* strToken, const wchar_t* strDelimit,
                    wchar_t** /*ctx*/) {
     return ::std::wcstok(strToken, strDelimit);
   };
 #endif
--- a/dom/plugins/test/mochitest/cocoa_focus.html
+++ b/dom/plugins/test/mochitest/cocoa_focus.html
@@ -9,17 +9,29 @@
     function is(aLeft, aRight, aMessage) {
       window.opener.SimpleTest.is(aLeft, aRight, aMessage);
     }
 
     function ok(aValue, aMessage) {
       window.opener.SimpleTest.ok(aValue, aMessage);
     }
 
-    function runTests() {
+    function synthesizeNativeMouseEvent(aX, aY, aNativeMsg, aModifiers, aElement, aCallback) {
+      var observer = {
+        observe: function(aSubject, aTopic, aData) {
+          if (aCallback && aTopic == "mouseevent") {
+            aCallback(aData);
+          }
+        }
+      };
+      SpecialPowers.DOMWindowUtils.sendNativeMouseEvent(aX, aY, aNativeMsg, aModifiers, aElement, observer);
+      return true;
+    }
+
+    function* runTests() {
       var utils = SpecialPowers.DOMWindowUtils;
       var scale = utils.screenPixelsPerCSSPixel;
 
       var plugin1 = document.getElementById("plugin1"); // What we're testing.
       var plugin2 = document.getElementById("plugin2"); // Dummy.
 
       var plugin1Bounds = plugin1.getBoundingClientRect();
       var plugin2Bounds = plugin2.getBoundingClientRect();
@@ -29,17 +41,16 @@
       var plugin2X = (window.mozInnerScreenX + plugin2Bounds.left + 10);
       var plugin2Y = (window.mozInnerScreenY + plugin2Bounds.top + 10);
 
       const NSLeftMouseDown          = 1,
             NSLeftMouseUp            = 2;
 
       if (plugin1.getEventModel() != 1) {
         window.opener.todo(false, "Skipping this test when not testing the Cocoa event model");
-        window.opener.testsFinished();
         return;
       }
 
       // Initialize to 0 since there is no initial state event,
       // plugins should assume they do not initially have focus.
       var expectedEventCount = 0;
 
       // Make sure initial event count is correct.
@@ -50,18 +61,18 @@
       try {
         plugin1.getFocusState();
       } catch (e) {
         initialStateUnknown = true;
       }
       is(initialStateUnknown, true, "Initial state should be unknown, assumed false.");
 
       // Give the plugin focus (the window is already focused).
-      utils.sendNativeMouseEvent(plugin1X * scale, plugin1Y * scale, NSLeftMouseDown, 0, plugin1);
-      utils.sendNativeMouseEvent(plugin1X * scale, plugin1Y * scale, NSLeftMouseUp, 0, plugin1);
+      yield synthesizeNativeMouseEvent(plugin1X * scale, plugin1Y * scale, NSLeftMouseDown, 0, plugin1, continueTest);
+      yield synthesizeNativeMouseEvent(plugin1X * scale, plugin1Y * scale, NSLeftMouseUp, 0, plugin1, continueTest);
       expectedEventCount++;
 
       is(plugin1.getFocusState(), true, "(1) Plugin should have focus.");
       is(plugin1.getFocusEventCount(), expectedEventCount, "Focus event count should be " + expectedEventCount);
 
       // Make sure window activation state changes don't spontaneously
       // change plugin focus.
 
@@ -73,29 +84,29 @@
 
       // Focus the window.
       SpecialPowers.focus(window);
 
       is(plugin1.getFocusState(), true, "(3) Plugin should still have focus.");
       is(plugin1.getFocusEventCount(), expectedEventCount, "Focus event count should be " + expectedEventCount);
 
       // Take focus from the plugin.
-      utils.sendNativeMouseEvent(plugin2X * scale, plugin2Y * scale, NSLeftMouseDown, 0, plugin2);
-      utils.sendNativeMouseEvent(plugin2X * scale, plugin2Y * scale, NSLeftMouseUp, 0, plugin2);
+      yield synthesizeNativeMouseEvent(plugin2X * scale, plugin2Y * scale, NSLeftMouseDown, 0, plugin2, continueTest);
+      yield synthesizeNativeMouseEvent(plugin2X * scale, plugin2Y * scale, NSLeftMouseUp, 0, plugin2, continueTest);
       expectedEventCount++;
 
       is(plugin1.getFocusState(), false, "(4) Plugin should not have focus.");
       is(plugin1.getFocusEventCount(), expectedEventCount, "Focus event count should be " + expectedEventCount);
 
       // Make sure window activation causes the plugin to be informed of focus
       // changes that took place while the window was inactive.
 
       // Give the plugin focus (the window is already focused).
-      utils.sendNativeMouseEvent(plugin1X * scale, plugin1Y * scale, NSLeftMouseDown, 0, plugin1);
-      utils.sendNativeMouseEvent(plugin1X * scale, plugin1Y * scale, NSLeftMouseUp, 0, plugin1);
+      yield synthesizeNativeMouseEvent(plugin1X * scale, plugin1Y * scale, NSLeftMouseDown, 0, plugin1, continueTest);
+      yield synthesizeNativeMouseEvent(plugin1X * scale, plugin1Y * scale, NSLeftMouseUp, 0, plugin1, continueTest);
       expectedEventCount++;
 
       // Blur the window.
       SpecialPowers.focus(opener);
 
       // Take focus from the plugin while the window is blurred.
       plugin2.focus();
 
@@ -103,18 +114,29 @@
       is(plugin1.getFocusEventCount(), expectedEventCount, "Focus event count should be " + expectedEventCount);
 
       // Focus the window.
       SpecialPowers.focus(window);
       expectedEventCount++;
 
       is(plugin1.getFocusState(), false, "(6) Plugin should not have focus.");
       is(plugin1.getFocusEventCount(), expectedEventCount, "Focus event count should be " + expectedEventCount);
+    }
 
-      window.opener.testsFinished();
+    var gTestContinuation = null;
+    function continueTest() {
+      if (!gTestContinuation) {
+        gTestContinuation = runTests();
+      }
+      var ret = gTestContinuation.next();
+      if (ret.done) {
+        window.opener.testsFinished();
+      } else {
+        is(ret.value, true, "Mouse event successfully synthesized");
+      }
     }
 
     // Onload hander doesn't work for these tests -- no events arrive at the plugin.
-    window.opener.SimpleTest.waitForFocus(runTests, window);
+    window.opener.SimpleTest.waitForFocus(continueTest, window);
 
   </script>
 </body>
 </html>
--- a/dom/plugins/test/mochitest/test_npruntime_identifiers.html
+++ b/dom/plugins/test/mochitest/test_npruntime_identifiers.html
@@ -28,17 +28,23 @@
     var i, prop, randomnumber;
 
     for (i = 0; i < 20; ++i) {
       randomnumber=Math.floor(Math.random()*1001);
       prop = "prop" + randomnumber;
       is(reflector[prop], prop, "Property " + prop);
     }
 
-    for (i = -10; i < 10; ++i) {
+    for (i = -10; i < 0; ++i) {
+      is(reflector[i], String(i), "Property " + i);
+      prop = "prop" + i;
+      is(reflector[prop], prop, "Property " + prop);
+    }
+
+    for (i = 0; i < 10; ++i) {
       is(reflector[i], i, "Property " + i);
       prop = "prop" + i;
       is(reflector[prop], prop, "Property " + prop);
     }
 
     is(reflector.a, 'a', "Property .a");
     is(reflector['a'], 'a', "Property ['a']");
     reflector = null;
--- a/dom/plugins/test/mochitest/test_propertyAndMethod.html
+++ b/dom/plugins/test/mochitest/test_propertyAndMethod.html
@@ -29,17 +29,17 @@
       function run() {
         var plugin = document.getElementById("plugin");
         var pluginProto = Object.getPrototypeOf(plugin);
 
         delete pluginProto.propertyAndMethod;
         ok(isNaN(plugin.propertyAndMethod + 0), "Shouldn't be set yet!");
 
         plugin.propertyAndMethod = 5;
-        is(plugin.propertyAndMethod, 5, "Should be set to 5!");
+        is(+plugin.propertyAndMethod, 5, "Should be set to 5!");
 
         delete pluginProto.propertyAndMethod;
         ok(isNaN(plugin.propertyAndMethod + 0), "Shouldn't be set any more!");
 
         var res = plugin.propertyAndMethod();
         is(res, 5, "Method invocation should return 5!");
 
         SimpleTest.finish();
--- a/dom/promise/Promise.cpp
+++ b/dom/promise/Promise.cpp
@@ -9,35 +9,42 @@
 #include "jsfriendapi.h"
 #include "js/Debug.h"
 #include "mozilla/dom/BindingUtils.h"
 #include "mozilla/dom/DOMError.h"
 #include "mozilla/dom/OwningNonNull.h"
 #include "mozilla/dom/PromiseBinding.h"
 #include "mozilla/dom/ScriptSettings.h"
 #include "mozilla/dom/MediaStreamError.h"
+#include "mozilla/Atomics.h"
 #include "mozilla/CycleCollectedJSRuntime.h"
 #include "mozilla/Preferences.h"
 #include "PromiseCallback.h"
+#include "PromiseDebugging.h"
 #include "PromiseNativeHandler.h"
 #include "PromiseWorkerProxy.h"
 #include "nsContentUtils.h"
 #include "WorkerPrivate.h"
 #include "WorkerRunnable.h"
 #include "nsJSPrincipals.h"
 #include "nsJSUtils.h"
 #include "nsPIDOMWindow.h"
 #include "nsJSEnvironment.h"
 #include "nsIScriptObjectPrincipal.h"
 #include "xpcpublic.h"
 #include "nsGlobalWindow.h"
 
 namespace mozilla {
 namespace dom {
 
+namespace {
+// Generator used by Promise::GetID.
+Atomic<uintptr_t> gIDGenerator(0);
+}
+
 using namespace workers;
 
 NS_IMPL_ISUPPORTS0(PromiseNativeHandler)
 
 // This class processes the promise's callbacks with promise's result.
 class PromiseCallbackTask final : public nsRunnable
 {
 public:
@@ -264,17 +271,21 @@ private:
   NS_DECL_OWNINGTHREAD;
 };
 
 // Promise
 
 NS_IMPL_CYCLE_COLLECTION_CLASS(Promise)
 
 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(Promise)
+#if defined(DOM_PROMISE_DEPRECATED_REPORTING)
   tmp->MaybeReportRejectedOnce();
+#else
+  tmp->mResult = JS::UndefinedValue();
+#endif // defined(DOM_PROMISE_DEPRECATED_REPORTING)
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mGlobal)
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mResolveCallbacks)
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mRejectCallbacks)
   NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER
 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
 
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(Promise)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mGlobal)
@@ -328,29 +339,37 @@ NS_INTERFACE_MAP_END
 
 Promise::Promise(nsIGlobalObject* aGlobal)
   : mGlobal(aGlobal)
   , mResult(JS::UndefinedValue())
   , mAllocationStack(nullptr)
   , mRejectionStack(nullptr)
   , mFullfillmentStack(nullptr)
   , mState(Pending)
+#if defined(DOM_PROMISE_DEPRECATED_REPORTING)
   , mHadRejectCallback(false)
+#endif // defined(DOM_PROMISE_DEPRECATED_REPORTING)
+  , mTaskPending(false)
   , mResolvePending(false)
+  , mIsLastInChain(true)
+  , mWasNotifiedAsUncaught(false)
+  , mID(0)
 {
   MOZ_ASSERT(mGlobal);
 
   mozilla::HoldJSObjects(this);
 
   mCreationTimestamp = TimeStamp::Now();
 }
 
 Promise::~Promise()
 {
+#if defined(DOM_PROMISE_DEPRECATED_REPORTING)
   MaybeReportRejectedOnce();
+#endif // defined(DOM_PROMISE_DEPRECATED_REPORTING)
   mozilla::DropJSObjects(this);
 }
 
 JSObject*
 Promise::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
 {
   return PromiseBinding::Wrap(aCx, this, aGivenProto);
 }
@@ -1003,49 +1022,58 @@ Promise::Compartment() const
 {
   return js::GetObjectCompartment(GlobalJSObject());
 }
 
 void
 Promise::AppendCallbacks(PromiseCallback* aResolveCallback,
                          PromiseCallback* aRejectCallback)
 {
-  if (aResolveCallback) {
-    mResolveCallbacks.AppendElement(aResolveCallback);
-  }
+  MOZ_ASSERT(aResolveCallback);
+  MOZ_ASSERT(aRejectCallback);
 
-  if (aRejectCallback) {
-    mHadRejectCallback = true;
-    mRejectCallbacks.AppendElement(aRejectCallback);
+  if (mIsLastInChain && mState == PromiseState::Rejected) {
+    // This rejection is now consumed.
+    PromiseDebugging::AddConsumedRejection(*this);
+    // Note that we may not have had the opportunity to call
+    // RunResolveTask() yet, so we may never have called
+    // `PromiseDebugging:AddUncaughtRejection`.
+  }
+  mIsLastInChain = false;
 
-    // Now that there is a callback, we don't need to report anymore.
-    RemoveFeature();
-  }
+#if defined(DOM_PROMISE_DEPRECATED_REPORTING)
+  // Now that there is a callback, we don't need to report anymore.
+  mHadRejectCallback = true;
+  RemoveFeature();
+#endif // defined(DOM_PROMISE_DEPRECATED_REPORTING)
+
+  mResolveCallbacks.AppendElement(aResolveCallback);
+  mRejectCallbacks.AppendElement(aRejectCallback);
 
   // If promise's state is fulfilled, queue a task to process our fulfill
   // callbacks with promise's result. If promise's state is rejected, queue a
   // task to process our reject callbacks with promise's result.
   if (mState != Pending) {
     EnqueueCallbackTasks();
   }
 }
 
 class WrappedWorkerRunnable final : public WorkerSameThreadRunnable
 {
 public:
-  WrappedWorkerRunnable(WorkerPrivate* aWorkerPrivate, nsIRunnable* aRunnable)
+  WrappedWorkerRunnable(workers::WorkerPrivate* aWorkerPrivate, nsIRunnable* aRunnable)
     : WorkerSameThreadRunnable(aWorkerPrivate)
     , mRunnable(aRunnable)
   {
     MOZ_ASSERT(aRunnable);
     MOZ_COUNT_CTOR(WrappedWorkerRunnable);
   }
 
   bool
-  WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override
+  WorkerRun(JSContext* aCx, workers::WorkerPrivate* aWorkerPrivate) override
   {
     NS_ASSERT_OWNINGTHREAD(WrappedWorkerRunnable);
     mRunnable->Run();
     return true;
   }
 
 private:
   virtual
@@ -1066,16 +1094,17 @@ Promise::DispatchToMicroTask(nsIRunnable
 
   CycleCollectedJSRuntime* runtime = CycleCollectedJSRuntime::Get();
   nsTArray<nsCOMPtr<nsIRunnable>>& microtaskQueue =
     runtime->GetPromiseMicroTaskQueue();
 
   microtaskQueue.AppendElement(aRunnable);
 }
 
+#if defined(DOM_PROMISE_DEPRECATED_REPORTING)
 void
 Promise::MaybeReportRejected()
 {
   if (mState != Rejected || mHadRejectCallback || mResult.isUndefined()) {
     return;
   }
 
   AutoJSAPI jsapi;
@@ -1110,16 +1139,17 @@ Promise::MaybeReportRejected()
   // Now post an event to do the real reporting async
   // Since Promises preserve their wrapper, it is essential to nsRefPtr<> the
   // AsyncErrorReporter, otherwise if the call to DispatchToMainThread fails, it
   // will leak. See Bug 958684.
   nsRefPtr<AsyncErrorReporter> r =
     new AsyncErrorReporter(CycleCollectedJSRuntime::Get()->Runtime(), xpcReport);
   NS_DispatchToMainThread(r);
 }
+#endif // defined(DOM_PROMISE_DEPRECATED_REPORTING)
 
 void
 Promise::MaybeResolveInternal(JSContext* aCx,
                               JS::Handle<JS::Value> aValue)
 {
   if (mResolvePending) {
     return;
   }
@@ -1199,34 +1229,45 @@ Promise::Settle(JS::Handle<JS::Value> aV
   AutoJSAPI jsapi;
   jsapi.Init();
   JSContext* cx = jsapi.cx();
   JS::RootedObject wrapper(cx, GetWrapper());
   MOZ_ASSERT(wrapper); // We preserved it
   JSAutoCompartment ac(cx, wrapper);
   JS::dbg::onPromiseSettled(cx, wrapper);
 
+  if (aState == PromiseState::Rejected &&
+      mIsLastInChain) {
+    // The Promise has just been rejected, and it is last in chain.
+    // We need to inform PromiseDebugging.
+    // If the Promise is eventually not the last in chain anymore,
+    // we will need to inform PromiseDebugging again.
+    PromiseDebugging::AddUncaughtRejection(*this);
+  }
+
+#if defined(DOM_PROMISE_DEPRECATED_REPORTING)
   // If the Promise was rejected, and there is no reject handler already setup,
   // watch for thread shutdown.
   if (aState == PromiseState::Rejected &&
       !mHadRejectCallback &&
       !NS_IsMainThread()) {
-    WorkerPrivate* worker = GetCurrentThreadWorkerPrivate();
+    workers::WorkerPrivate* worker = GetCurrentThreadWorkerPrivate();
     MOZ_ASSERT(worker);
     worker->AssertIsOnWorkerThread();
 
     mFeature = new PromiseReportRejectFeature(this);
     if (NS_WARN_IF(!worker->AddFeature(worker->GetJSContext(), mFeature))) {
       // To avoid a false RemoveFeature().
       mFeature = nullptr;
       // Worker is shutting down, report rejection immediately since it is
       // unlikely that reject callbacks will be added after this point.
       MaybeReportRejectedOnce();
     }
   }
+#endif // defined(DOM_PROMISE_DEPRECATED_REPORTING)
 
   EnqueueCallbackTasks();
 }
 
 void
 Promise::MaybeSettle(JS::Handle<JS::Value> aValue,
                      PromiseState aState)
 {
@@ -1251,35 +1292,37 @@ Promise::EnqueueCallbackTasks()
 
   for (uint32_t i = 0; i < callbacks.Length(); ++i) {
     nsRefPtr<PromiseCallbackTask> task =
       new PromiseCallbackTask(this, callbacks[i], mResult);
     DispatchToMicroTask(task);
   }
 }
 
+#if defined(DOM_PROMISE_DEPRECATED_REPORTING)
 void
 Promise::RemoveFeature()
 {
   if (mFeature) {
-    WorkerPrivate* worker = GetCurrentThreadWorkerPrivate();
+    workers::WorkerPrivate* worker = GetCurrentThreadWorkerPrivate();
     MOZ_ASSERT(worker);
     worker->RemoveFeature(worker->GetJSContext(), mFeature);
     mFeature = nullptr;
   }
 }
 
 bool
 PromiseReportRejectFeature::Notify(JSContext* aCx, workers::Status aStatus)
 {
   MOZ_ASSERT(aStatus > workers::Running);
   mPromise->MaybeReportRejectedOnce();
   // After this point, `this` has been deleted by RemoveFeature!
   return true;
 }
+#endif // defined(DOM_PROMISE_DEPRECATED_REPORTING)
 
 bool
 Promise::CaptureStack(JSContext* aCx, JS::Heap<JSObject*>& aTarget)
 {
   JS::Rooted<JSObject*> stack(aCx);
   if (!JS::CaptureCurrentStack(aCx, &stack)) {
     return false;
   }
@@ -1369,17 +1412,17 @@ private:
   JSAutoStructuredCloneBuffer mBuffer;
 
   // Function pointer for calling Promise::{ResolveInternal,RejectInternal}.
   PromiseWorkerProxy::RunCallbackFunc mFunc;
 };
 
 /* static */
 already_AddRefed<PromiseWorkerProxy>
-PromiseWorkerProxy::Create(WorkerPrivate* aWorkerPrivate,
+PromiseWorkerProxy::Create(workers::WorkerPrivate* aWorkerPrivate,
                            Promise* aWorkerPromise,
                            const JSStructuredCloneCallbacks* aCb)
 {
   MOZ_ASSERT(aWorkerPrivate);
   aWorkerPrivate->AssertIsOnWorkerThread();
   MOZ_ASSERT(aWorkerPromise);
 
   nsRefPtr<PromiseWorkerProxy> proxy =
@@ -1393,34 +1436,34 @@ PromiseWorkerProxy::Create(WorkerPrivate
     proxy->mCleanedUp = true;
     proxy->mWorkerPromise = nullptr;
     return nullptr;
   }
 
   return proxy.forget();
 }
 
-PromiseWorkerProxy::PromiseWorkerProxy(WorkerPrivate* aWorkerPrivate,
+PromiseWorkerProxy::PromiseWorkerProxy(workers::WorkerPrivate* aWorkerPrivate,
                                        Promise* aWorkerPromise,
                                        const JSStructuredCloneCallbacks* aCallbacks)
   : mWorkerPrivate(aWorkerPrivate)
   , mWorkerPromise(aWorkerPromise)
   , mCleanedUp(false)
   , mCallbacks(aCallbacks)
   , mCleanUpLock("cleanUpLock")
 {
 }
 
 PromiseWorkerProxy::~PromiseWorkerProxy()
 {
   MOZ_ASSERT(mCleanedUp);
   MOZ_ASSERT(!mWorkerPromise);
 }
 
-WorkerPrivate*
+workers::WorkerPrivate*
 PromiseWorkerProxy::GetWorkerPrivate() const
 {
   // It's ok to race on |mCleanedUp|, because it will never cause us to fire
   // the assertion when we should not.
   MOZ_ASSERT(!mCleanedUp);
 
   return mWorkerPrivate;
 }
@@ -1438,17 +1481,17 @@ PromiseWorkerProxy::StoreISupports(nsISu
 
   nsMainThreadPtrHandle<nsISupports> supports(
     new nsMainThreadPtrHolder<nsISupports>(aSupports));
   mSupportsArray.AppendElement(supports);
 }
 
 bool
 PromiseWorkerProxyControlRunnable::WorkerRun(JSContext* aCx,
-                                             WorkerPrivate* aWorkerPrivate)
+                                             workers::WorkerPrivate* aWorkerPrivate)
 {
   mProxy->CleanUp(aCx);
   return true;
 }
 
 void
 PromiseWorkerProxy::RunCallback(JSContext* aCx,
                                 JS::Handle<JS::Value> aValue,
@@ -1540,10 +1583,18 @@ template<>
 void Promise::MaybeRejectBrokenly(const nsRefPtr<DOMError>& aArg) {
   MaybeSomething(aArg, &Promise::MaybeReject);
 }
 template<>
 void Promise::MaybeRejectBrokenly(const nsAString& aArg) {
   MaybeSomething(aArg, &Promise::MaybeReject);
 }
 
+uint64_t
+Promise::GetID() {
+  if (mID != 0) {
+    return mID;
+  }
+  return mID = ++gIDGenerator;
+}
+
 } // namespace dom
 } // namespace mozilla
--- a/dom/promise/Promise.h
+++ b/dom/promise/Promise.h
@@ -16,61 +16,75 @@
 #include "mozilla/dom/PromiseBinding.h"
 #include "mozilla/dom/ToJSValue.h"
 #include "mozilla/WeakPtr.h"
 #include "nsWrapperCache.h"
 #include "nsAutoPtr.h"
 #include "js/TypeDecls.h"
 #include "jspubtd.h"
 
+// Bug 1083361 introduces a new mechanism for tracking uncaught
+// rejections. This #define serves to track down the parts of code
+// that need to be removed once clients have been put together
+// to take advantage of the new mechanism. New code should not
+// depend on code #ifdefed to this #define.
+#define DOM_PROMISE_DEPRECATED_REPORTING 1
+
+#if defined(DOM_PROMISE_DEPRECATED_REPORTING)
 #include "mozilla/dom/workers/bindings/WorkerFeature.h"
+#endif // defined(DOM_PROMISE_DEPRECATED_REPORTING)
 
 class nsIGlobalObject;
 
 namespace mozilla {
 namespace dom {
 
 class AnyCallback;
 class DOMError;
 class MediaStreamError;
 class PromiseCallback;
 class PromiseInit;
 class PromiseNativeHandler;
 class PromiseDebugging;
 
 class Promise;
+
+#if defined(DOM_PROMISE_DEPRECATED_REPORTING)
 class PromiseReportRejectFeature : public workers::WorkerFeature
 {
   // The Promise that owns this feature.
   Promise* mPromise;
 
 public:
   explicit PromiseReportRejectFeature(Promise* aPromise)
     : mPromise(aPromise)
   {
     MOZ_ASSERT(mPromise);
   }
 
   virtual bool
   Notify(JSContext* aCx, workers::Status aStatus) override;
 };
+#endif // defined(DOM_PROMISE_DEPRECATED_REPORTING)
 
 #define NS_PROMISE_IID \
   { 0x1b8d6215, 0x3e67, 0x43ba, \
     { 0x8a, 0xf9, 0x31, 0x5e, 0x8f, 0xce, 0x75, 0x65 } }
 
 class Promise : public nsISupports,
                 public nsWrapperCache,
                 public SupportsWeakPtr<Promise>
 {
   friend class NativePromiseCallback;
   friend class PromiseCallbackTask;
   friend class PromiseResolverTask;
   friend class PromiseTask;
+#if defined(DOM_PROMISE_DEPRECATED_REPORTING)
   friend class PromiseReportRejectFeature;
+#endif // defined(DOM_PROMISE_DEPRECATED_REPORTING)
   friend class PromiseWorkerProxy;
   friend class PromiseWorkerProxyRunnable;
   friend class RejectPromiseCallback;
   friend class ResolvePromiseCallback;
   friend class ThenableResolverTask;
   friend class WrapperPromiseCallback;
 
 public:
@@ -177,16 +191,19 @@ public:
        const Sequence<JS::Value>& aIterable, ErrorResult& aRv);
 
   void AppendNativeHandler(PromiseNativeHandler* aRunnable);
 
   JSObject* GlobalJSObject() const;
 
   JSCompartment* Compartment() const;
 
+  // Return a unique-to-the-process identifier for this Promise.
+  uint64_t GetID();
+
 protected:
   // Do NOT call this unless you're Promise::Create.  I wish we could enforce
   // that from inside this class too, somehow.
   explicit Promise(nsIGlobalObject* aGlobal);
 
   virtual ~Promise();
 
   // Queue an async microtask to current main or worker thread.
@@ -204,16 +221,31 @@ protected:
 
   bool IsPending()
   {
     return mResolvePending;
   }
 
   void GetDependentPromises(nsTArray<nsRefPtr<Promise>>& aPromises);
 
+  bool IsLastInChain() const
+  {
+    return mIsLastInChain;
+  }
+
+  void SetNotifiedAsUncaught()
+  {
+    mWasNotifiedAsUncaught = true;
+  }
+
+  bool WasNotifiedAsUncaught() const
+  {
+    return mWasNotifiedAsUncaught;
+  }
+
 private:
   friend class PromiseDebugging;
 
   enum PromiseState {
     Pending,
     Resolved,
     Rejected
   };
@@ -237,26 +269,28 @@ private:
   void EnqueueCallbackTasks();
 
   void Settle(JS::Handle<JS::Value> aValue, Promise::PromiseState aState);
   void MaybeSettle(JS::Handle<JS::Value> aValue, Promise::PromiseState aState);
 
   void AppendCallbacks(PromiseCallback* aResolveCallback,
                        PromiseCallback* aRejectCallback);
 
+#if defined(DOM_PROMISE_DEPRECATED_REPORTING)
   // If we have been rejected and our mResult is a JS exception,
   // report it to the error console.
   // Use MaybeReportRejectedOnce() for actual calls.
   void MaybeReportRejected();
 
   void MaybeReportRejectedOnce() {
     MaybeReportRejected();
     RemoveFeature();
     mResult.setUndefined();
   }
+#endif // defined(DOM_PROMISE_DEPRECATED_REPORTING)
 
   void MaybeResolveInternal(JSContext* aCx,
                             JS::Handle<JS::Value> aValue);
   void MaybeRejectInternal(JSContext* aCx,
                            JS::Handle<JS::Value> aValue);
 
   void ResolveInternal(JSContext* aCx,
                        JS::Handle<JS::Value> aValue);
@@ -294,17 +328,19 @@ private:
   static JSObject*
   CreateFunction(JSContext* aCx, Promise* aPromise, int32_t aTask);
 
   static JSObject*
   CreateThenableFunction(JSContext* aCx, Promise* aPromise, uint32_t aTask);
 
   void HandleException(JSContext* aCx);
 
+#if defined(DOM_PROMISE_DEPRECATED_REPORTING)
   void RemoveFeature();
+#endif // defined(DOM_PROMISE_DEPRECATED_REPORTING)
 
   // Capture the current stack and store it in aTarget.  If false is
   // returned, an exception is presumably pending on aCx.
   bool CaptureStack(JSContext* aCx, JS::Heap<JSObject*>& aTarget);
 
   nsRefPtr<nsIGlobalObject> mGlobal;
 
   nsTArray<nsRefPtr<PromiseCallback> > mResolveCallbacks;
@@ -320,31 +356,48 @@ private:
   // have a rejection stack.
   JS::Heap<JSObject*> mRejectionStack;
   // mFullfillmentStack is only set when the promise is fulfilled directly from
   // script, by calling Promise.resolve() or the fulfillment callback we pass to
   // the PromiseInit function.  Promises that are fulfilled internally do not
   // have a fulfillment stack.
   JS::Heap<JSObject*> mFullfillmentStack;
   PromiseState mState;
+
+#if defined(DOM_PROMISE_DEPRECATED_REPORTING)
   bool mHadRejectCallback;
 
-  bool mResolvePending;
-
   // If a rejected promise on a worker has no reject callbacks attached, it
   // needs to know when the worker is shutting down, to report the error on the
   // console before the worker's context is deleted. This feature is used for
   // that purpose.
   nsAutoPtr<PromiseReportRejectFeature> mFeature;
+#endif // defined(DOM_PROMISE_DEPRECATED_REPORTING)
+
+  bool mTaskPending;
+  bool mResolvePending;
+
+  // `true` if this Promise is the last in the chain, or `false` if
+  // another Promise has been created from this one by a call to
+  // `then`, `all`, `race`, etc.
+  bool mIsLastInChain;
+
+  // `true` if PromiseDebugging has already notified at least one observer that
+  // this promise was left uncaught, `false` otherwise.
+  bool mWasNotifiedAsUncaught;
 
   // The time when this promise was created.
   TimeStamp mCreationTimestamp;
 
   // The time when this promise transitioned out of the pending state.
   TimeStamp mSettlementTimestamp;
+
+  // Once `GetID()` has been called, a unique-to-the-process identifier for this
+  // promise. Until then, `0`.
+  uint64_t mID;
 };
 
 NS_DEFINE_STATIC_IID_ACCESSOR(Promise, NS_PROMISE_IID)
 
 } // namespace dom
 } // namespace mozilla
 
 #endif // mozilla_dom_Promise_h
--- a/dom/promise/PromiseDebugging.cpp
+++ b/dom/promise/PromiseDebugging.cpp
@@ -1,26 +1,75 @@
 /* -*- Mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 40 -*- */
 /* vim: set ts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
-#include "mozilla/dom/PromiseDebugging.h"
-
 #include "js/Value.h"
+#include "nsThreadUtils.h"
 
+#include "mozilla/CycleCollectedJSRuntime.h"
+#include "mozilla/ThreadLocal.h"
 #include "mozilla/TimeStamp.h"
+
 #include "mozilla/dom/BindingDeclarations.h"
+#include "mozilla/dom/ContentChild.h"
 #include "mozilla/dom/Promise.h"
+#include "mozilla/dom/PromiseDebugging.h"
 #include "mozilla/dom/PromiseDebuggingBinding.h"
 
 namespace mozilla {
 namespace dom {
 
+class FlushRejections: public nsCancelableRunnable
+{
+public:
+  static void Init() {
+    if (!sDispatched.init()) {
+      MOZ_CRASH("Could not initialize FlushRejections::sDispatched");
+    }
+    sDispatched.set(false);
+  }
+
+  static void DispatchNeeded() {
+    if (sDispatched.get()) {
+      // An instance of `FlushRejections` has already been dispatched
+      // and not run yet. No need to dispatch another one.
+      return;
+    }
+    sDispatched.set(true);
+    NS_DispatchToCurrentThread(new FlushRejections());
+  }
+
+  static void FlushSync() {
+    sDispatched.set(false);
+
+    // Call the callbacks if necessary.
+    // Note that these callbacks may in turn cause Promise to turn
+    // uncaught or consumed. Since `sDispatched` is `false`,
+    // `FlushRejections` will be called once again, on an ulterior
+    // tick.
+    PromiseDebugging::FlushUncaughtRejectionsInternal();
+  }
+
+  NS_IMETHOD Run() {
+    FlushSync();
+    return NS_OK;
+  }
+
+private:
+  // `true` if an instance of `FlushRejections` is currently dispatched
+  // and has not been executed yet.
+  static ThreadLocal<bool> sDispatched;
+};
+
+/* static */ ThreadLocal<bool>
+FlushRejections::sDispatched;
+
 /* static */ void
 PromiseDebugging::GetState(GlobalObject&, Promise& aPromise,
                            PromiseDebuggingStateHolder& aState)
 {
   switch (aPromise.mState) {
   case Promise::Pending:
     aState.mState = PromiseDebuggingState::Pending;
     break;
@@ -32,16 +81,47 @@ PromiseDebugging::GetState(GlobalObject&
   case Promise::Rejected:
     aState.mState = PromiseDebuggingState::Rejected;
     JS::ExposeValueToActiveJS(aPromise.mResult);
     aState.mReason = aPromise.mResult;
     break;
   }
 }
 
+/*static */ nsString
+PromiseDebugging::sIDPrefix;
+
+/* static */ void
+PromiseDebugging::Init()
+{
+  FlushRejections::Init();
+
+  // Generate a prefix for identifiers: "PromiseDebugging.$processid."
+  sIDPrefix = NS_LITERAL_STRING("PromiseDebugging.");
+  if (XRE_GetProcessType() == GeckoProcessType_Content) {
+    sIDPrefix.AppendInt(ContentChild::GetSingleton()->GetID());
+    sIDPrefix.Append('.');
+  } else {
+    sIDPrefix.AppendLiteral("0.");
+  }
+}
+
+/* static */ void
+PromiseDebugging::Shutdown()
+{
+  sIDPrefix.SetIsVoid(true);
+}
+
+/* static */ void
+PromiseDebugging::FlushUncaughtRejections()
+{
+  MOZ_ASSERT(!NS_IsMainThread());
+  FlushRejections::FlushSync();
+}
+
 /* static */ void
 PromiseDebugging::GetAllocationStack(GlobalObject&, Promise& aPromise,
                                      JS::MutableHandle<JSObject*> aStack)
 {
   aStack.set(aPromise.mAllocationStack);
 }
 
 /* static */ void
@@ -78,10 +158,124 @@ PromiseDebugging::GetTimeToSettle(Global
   if (aPromise.mState == Promise::Pending) {
     aRv.Throw(NS_ERROR_UNEXPECTED);
     return 0;
   }
   return (aPromise.mSettlementTimestamp -
           aPromise.mCreationTimestamp).ToMilliseconds();
 }
 
+/* static */ void
+PromiseDebugging::AddUncaughtRejectionObserver(GlobalObject&,
+                                               UncaughtRejectionObserver& aObserver)
+{
+  CycleCollectedJSRuntime* storage = CycleCollectedJSRuntime::Get();
+  nsTArray<nsCOMPtr<nsISupports>>& observers = storage->mUncaughtRejectionObservers;
+  observers.AppendElement(&aObserver);
+}
+
+/* static */ bool
+PromiseDebugging::RemoveUncaughtRejectionObserver(GlobalObject&,
+                                                  UncaughtRejectionObserver& aObserver)
+{
+  CycleCollectedJSRuntime* storage = CycleCollectedJSRuntime::Get();
+  nsTArray<nsCOMPtr<nsISupports>>& observers = storage->mUncaughtRejectionObservers;
+  for (size_t i = 0; i < observers.Length(); ++i) {
+    UncaughtRejectionObserver* observer = static_cast<UncaughtRejectionObserver*>(observers[i].get());
+    if (*observer == aObserver) {
+      observers.RemoveElementAt(i);
+      return true;
+    }
+  }
+  return false;
+}
+
+/* static */ void
+PromiseDebugging::AddUncaughtRejection(Promise& aPromise)
+{
+  CycleCollectedJSRuntime::Get()->mUncaughtRejections.AppendElement(&aPromise);
+  FlushRejections::DispatchNeeded();
+}
+
+/* void */ void
+PromiseDebugging::AddConsumedRejection(Promise& aPromise)
+{
+  CycleCollectedJSRuntime::Get()->mConsumedRejections.AppendElement(&aPromise);
+  FlushRejections::DispatchNeeded();
+}
+
+/* static */ void
+PromiseDebugging::GetPromiseID(GlobalObject&,
+                               Promise& aPromise,
+                               nsString& aID)
+{
+  uint64_t promiseID = aPromise.GetID();
+  aID = sIDPrefix;
+  aID.AppendInt(promiseID);
+}
+
+/* static */ void
+PromiseDebugging::FlushUncaughtRejectionsInternal()
+{
+  CycleCollectedJSRuntime* storage = CycleCollectedJSRuntime::Get();
+
+  // The Promise that have been left uncaught (rejected and last in
+  // their chain) since the last call to this function.
+  nsTArray<nsCOMPtr<nsISupports>> uncaught;
+  storage->mUncaughtRejections.SwapElements(uncaught);
+
+  // The Promise that have been left uncaught at some point, but that
+  // have eventually had their `then` method called.
+  nsTArray<nsCOMPtr<nsISupports>> consumed;
+  storage->mConsumedRejections.SwapElements(consumed);
+
+  nsTArray<nsCOMPtr<nsISupports>>& observers = storage->mUncaughtRejectionObservers;
+
+  nsresult rv;
+  // Notify observers of uncaught Promise.
+
+  for (size_t i = 0; i < uncaught.Length(); ++i) {
+    nsCOMPtr<Promise> promise = do_QueryInterface(uncaught[i], &rv);
+    MOZ_ASSERT(NS_SUCCEEDED(rv));
+
+    if (!promise->IsLastInChain()) {
+      // This promise is not the last in the chain anymore,
+      // so the error has been caught at some point.
+      continue;
+    }
+
+    // For the moment, the Promise is still at the end of the
+    // chain. Let's inform observers, so that they may decide whether
+    // to report it.
+    for (size_t j = 0; j < observers.Length(); ++j) {
+      ErrorResult err;
+      nsRefPtr<UncaughtRejectionObserver> obs =
+        static_cast<UncaughtRejectionObserver*>(observers[j].get());
+
+      obs->OnLeftUncaught(*promise, err); // Ignore errors
+    }
+
+    promise->SetNotifiedAsUncaught();
+  }
+
+  // Notify observers of consumed Promise.
+
+  for (size_t i = 0; i < consumed.Length(); ++i) {
+    nsCOMPtr<Promise> promise = do_QueryInterface(consumed[i], &rv);
+    MOZ_ASSERT(NS_SUCCEEDED(rv));
+
+    if (!promise->WasNotifiedAsUncaught()) {
+      continue;
+    }
+
+    MOZ_ASSERT(!promise->IsLastInChain());
+    for (size_t j = 0; j < observers.Length(); ++j) {
+      ErrorResult err;
+      nsRefPtr<UncaughtRejectionObserver> obs =
+        static_cast<UncaughtRejectionObserver*>(observers[j].get());
+
+      obs->OnConsumed(*promise, err); // Ignore errors
+    }
+  }
+}
+
 } // namespace dom
 } // namespace mozilla
--- a/dom/promise/PromiseDebugging.h
+++ b/dom/promise/PromiseDebugging.h
@@ -11,36 +11,75 @@
 #include "nsTArray.h"
 #include "nsRefPtr.h"
 
 namespace mozilla {
 
 class ErrorResult;
 
 namespace dom {
+namespace workers {
+class WorkerPrivate;
+}
 
 class Promise;
 struct PromiseDebuggingStateHolder;
 class GlobalObject;
+class UncaughtRejectionObserver;
+class FlushRejections;
 
 class PromiseDebugging
 {
 public:
+  static void Init();
+  static void Shutdown();
+
   static void GetState(GlobalObject&, Promise& aPromise,
                        PromiseDebuggingStateHolder& aState);
 
   static void GetAllocationStack(GlobalObject&, Promise& aPromise,
                                  JS::MutableHandle<JSObject*> aStack);
   static void GetRejectionStack(GlobalObject&, Promise& aPromise,
                                 JS::MutableHandle<JSObject*> aStack);
   static void GetFullfillmentStack(GlobalObject&, Promise& aPromise,
                                    JS::MutableHandle<JSObject*> aStack);
   static void GetDependentPromises(GlobalObject&, Promise& aPromise,
                                    nsTArray<nsRefPtr<Promise>>& aPromises);
   static double GetPromiseLifetime(GlobalObject&, Promise& aPromise);
   static double GetTimeToSettle(GlobalObject&, Promise& aPromise,
                                 ErrorResult& aRv);
+
+  static void GetPromiseID(GlobalObject&, Promise&, nsString&);
+
+  // Mechanism for watching uncaught instances of Promise.
+  static void AddUncaughtRejectionObserver(GlobalObject&,
+                                           UncaughtRejectionObserver& aObserver);
+  static bool RemoveUncaughtRejectionObserver(GlobalObject&,
+                                              UncaughtRejectionObserver& aObserver);
+
+  // Mark a Promise as having been left uncaught at script completion.
+  static void AddUncaughtRejection(Promise&);
+  // Mark a Promise previously added with `AddUncaughtRejection` as
+  // eventually consumed.
+  static void AddConsumedRejection(Promise&);
+  // Propagate the informations from AddUncaughtRejection
+  // and AddConsumedRejection to observers.
+  static void FlushUncaughtRejections();
+
+protected:
+  static void FlushUncaughtRejectionsInternal();
+  friend class FlushRejections;
+  friend class WorkerPrivate;
+private:
+  // Identity of the process.
+  // This property is:
+  // - set during initialization of the layout module,
+  // prior to any Worker using it;
+  // - read by both the main thread and the Workers;
+  // - unset during shutdown of the layout module,
+  // after any Worker has been shutdown.
+  static nsString sIDPrefix;
 };
 
 } // namespace dom
 } // namespace mozilla
 
 #endif // mozilla_dom_PromiseDebugging_h
--- a/dom/promise/moz.build
+++ b/dom/promise/moz.build
@@ -19,16 +19,20 @@ UNIFIED_SOURCES += [
     'PromiseCallback.cpp',
     'PromiseDebugging.cpp'
 ]
 
 FAIL_ON_WARNINGS = True
 
 LOCAL_INCLUDES += [
     '../base',
+    '../ipc',
     '../workers',
 ]
 
+include('/ipc/chromium/chromium-config.mozbuild')
+
 FINAL_LIBRARY = 'xul'
 
 MOCHITEST_MANIFESTS += ['tests/mochitest.ini']
 
 MOCHITEST_CHROME_MANIFESTS += ['tests/chrome.ini']
+BROWSER_CHROME_MANIFESTS += ['tests/browser.ini']
new file mode 100644
--- /dev/null
+++ b/dom/promise/tests/browser.ini
@@ -0,0 +1,7 @@
+# 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/.
+
+[DEFAULT]
+
+[browser_monitorUncaught.js]
\ No newline at end of file
new file mode 100644
--- /dev/null
+++ b/dom/promise/tests/browser_monitorUncaught.js
@@ -0,0 +1,266 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+Cu.import("resource://gre/modules/Timer.jsm", this);
+
+add_task(function* test_globals() {
+  Assert.equal(Promise.defer || undefined, undefined, "We are testing DOM Promise.");
+  Assert.notEqual(PromiseDebugging, undefined, "PromiseDebugging is available.");
+});
+
+add_task(function* test_promiseID() {
+  let p1 = new Promise(resolve => {});
+  let p2 = new Promise(resolve => {});
+  let p3 = p2.then(null, null);
+  let promise = [p1, p2, p3];
+
+  let identifiers = promise.map(PromiseDebugging.getPromiseID);
+  info("Identifiers: " + JSON.stringify(identifiers));
+  let idSet = new Set(identifiers);
+  Assert.equal(idSet.size, identifiers.length,
+    "PromiseDebugging.getPromiseID returns a distinct id per promise");
+
+  let identifiers2 = promise.map(PromiseDebugging.getPromiseID);
+  Assert.equal(JSON.stringify(identifiers),
+               JSON.stringify(identifiers2),
+               "Successive calls to PromiseDebugging.getPromiseID return the same id for the same promise");
+});
+
+add_task(function* test_observe_uncaught() {
+  // The names of Promise instances
+  let names = new Map();
+
+  // The results for UncaughtPromiseObserver callbacks.
+  let CallbackResults = function(name) {
+    this.name = name;
+    this.expected = new Set();
+    this.observed = new Set();
+    this.blocker = new Promise(resolve => this.resolve = resolve);
+  };
+  CallbackResults.prototype = {
+    observe: function(promise) {
+      info(this.name + " observing Promise " + names.get(promise));
+      Assert.equal(PromiseDebugging.getState(promise).state, "rejected",
+                   this.name + " observed a rejected Promise");
+      if (!this.expected.has(promise)) {
+        Assert.ok(false,
+            this.name + " observed a Promise that it expected to observe, " +
+            names.get(promise) +
+            " (" + PromiseDebugging.getPromiseID(promise) +
+            ", " + PromiseDebugging.getAllocationStack(promise) + ")");
+
+      }
+      Assert.ok(this.expected.delete(promise),
+                this.name + " observed a Promise that it expected to observe, " +
+                names.get(promise)  + " (" + PromiseDebugging.getPromiseID(promise) + ")");
+      Assert.ok(!this.observed.has(promise),
+                this.name + " observed a Promise that it has not observed yet");
+      this.observed.add(promise);
+      if (this.expected.size == 0) {
+        this.resolve();
+      } else {
+        info(this.name + " is still waiting for " + this.expected.size + " observations:");
+        info(JSON.stringify([names.get(x) for (x of this.expected.values())]));
+      }
+    },
+  };
+
+  let onLeftUncaught = new CallbackResults("onLeftUncaught");
+  let onConsumed = new CallbackResults("onConsumed");
+
+  let observer = {
+    onLeftUncaught: function(promise, data) {
+      onLeftUncaught.observe(promise);
+    },
+    onConsumed: function(promise) {
+      onConsumed.observe(promise);
+    },
+  };
+
+  let resolveLater = function(delay = 20) {
+    return new Promise((resolve, reject) => setTimeout(resolve, delay));
+  };
+  let rejectLater = function(delay = 20) {
+    return new Promise((resolve, reject) => setTimeout(reject, delay));
+  };
+  let makeSamples = function*() {
+    yield {
+      promise: Promise.resolve(0),
+      name: "Promise.resolve",
+    };
+    yield {
+      promise: Promise.resolve(resolve => resolve(0)),
+      name: "Resolution callback",
+    };
+    yield {
+      promise: Promise.resolve(0).then(null, null),
+      name: "`then(null, null)`"
+    };
+    yield {
+      promise: Promise.reject(0).then(null, () => {}),
+      name: "Reject and catch immediately",
+    };
+    yield {
+      promise: resolveLater(),
+      name: "Resolve later",
+    };
+    yield {
+      promise: Promise.reject("Simple rejection"),
+      leftUncaught: true,
+      consumed: false,
+      name: "Promise.reject",
+    };
+
+    // Reject a promise now, consume it later.
+    let p = Promise.reject("Reject now, consume later");
+    setTimeout(() => p.then(null, () => {
+      info("Consumed promise");
+    }), 200);
+    yield {
+      promise: p,
+      leftUncaught: true,
+      consumed: true,
+      name: "Reject now, consume later",
+    };
+
+    yield {
+      promise: Promise.all([
+        Promise.resolve("Promise.all"),
+        rejectLater()
+      ]),
+      leftUncaught: true,
+      name: "Rejecting through Promise.all"
+    };
+    yield {
+      promise: Promise.race([
+        resolveLater(500),
+        Promise.reject(),
+      ]),
+      leftUncaught: true, // The rejection wins the race.
+      name: "Rejecting through Promise.race",
+    };
+    yield {
+      promise: Promise.race([
+        Promise.resolve(),
+        rejectLater(500)
+      ]),
+      leftUncaught: false, // The resolution wins the race.
+      name: "Resolving through Promise.race",
+    };
+
+    let boom = new Error("`throw` in the constructor");
+    yield {
+      promise: new Promise(() => { throw boom; }),
+      leftUncaught: true,
+      name: "Throwing in the constructor",
+    };
+
+    let rejection = Promise.reject("`reject` during resolution");
+    yield {
+      promise: rejection,
+      leftUncaught: false,
+      consumed: false, // `rejection` is consumed immediately (see below)
+      name: "Promise.reject, again",
+    };
+
+    yield {
+      promise: new Promise(resolve => resolve(rejection)),
+      leftUncaught: true,
+      consumed: false,
+      name: "Resolving with a rejected promise",
+    };
+
+    yield {
+      promise: Promise.resolve(0).then(() => rejection),
+      leftUncaught: true,
+      consumed: false,
+      name: "Returning a rejected promise from success handler",
+    };
+
+    yield {
+      promise: Promise.resolve(0).then(() => { throw new Error(); }),
+      leftUncaught: true,
+      consumed: false,
+      name: "Throwing during the call to the success callback",
+    };
+  };
+  let samples = [];
+  for (let s of makeSamples()) {
+    samples.push(s);
+    info("Promise '" + s.name + "' has id " + PromiseDebugging.getPromiseID(s.promise));
+  }
+
+  PromiseDebugging.addUncaughtRejectionObserver(observer);
+
+  for (let s of samples) {
+    names.set(s.promise, s.name);
+    if (s.leftUncaught || false) {
+      onLeftUncaught.expected.add(s.promise);
+    }
+    if (s.consumed || false) {
+      onConsumed.expected.add(s.promise);
+    }
+  }
+
+  info("Test setup, waiting for callbacks.");
+  yield onLeftUncaught.blocker;
+
+  info("All calls to onLeftUncaught are complete.");
+  if (onConsumed.expected.size != 0) {
+    info("onConsumed is still waiting for the following Promise:");
+    info(JSON.stringify([names.get(x) for (x of onConsumed.expected.values())]));
+    yield onConsumed.blocker;
+  }
+
+  info("All calls to onConsumed are complete.");
+  let removed = PromiseDebugging.removeUncaughtRejectionObserver(observer);
+  Assert.ok(removed, "removeUncaughtRejectionObserver succeeded");
+  removed = PromiseDebugging.removeUncaughtRejectionObserver(observer);
+  Assert.ok(!removed, "second call to removeUncaughtRejectionObserver didn't remove anything");
+});
+
+
+add_task(function* test_uninstall_observer() {
+  let Observer = function() {
+    this.blocker = new Promise(resolve => this.resolve = resolve);
+    this.active = true;
+  };
+  Observer.prototype = {
+    set active(x) {
+      this._active = x;
+      if (x) {
+        PromiseDebugging.addUncaughtRejectionObserver(this);
+      } else {
+        PromiseDebugging.removeUncaughtRejectionObserver(this);
+      }
+    },
+    onLeftUncaught: function() {
+      Assert.ok(this._active, "This observer is active.");
+      this.resolve();
+    },
+    onConsumed: function() {
+      Assert.ok(false, "We should not consume any Promise.");
+    },
+  };
+
+  info("Adding an observer.");
+  let deactivate = new Observer();
+  Promise.reject("I am an uncaught rejection.");
+  yield deactivate.blocker;
+  Assert.ok(true, "The observer has observed an uncaught Promise.");
+  deactivate.active = false;
+  info("Removing the observer, it should not observe any further uncaught Promise.");
+
+  info("Rejecting a Promise and waiting a little to give a chance to observers.");
+  let wait = new Observer();
+  Promise.reject("I am another uncaught rejection.");
+  yield wait.blocker;
+  yield new Promise(resolve => setTimeout(resolve, 100));
+  // Normally, `deactivate` should not be notified of the uncaught rejection.
+  wait.active = false;
+
+});
+
--- a/dom/quota/Client.h
+++ b/dom/quota/Client.h
@@ -107,28 +107,22 @@ public:
   virtual void
   OnOriginClearCompleted(PersistenceType aPersistenceType,
                          const nsACString& aOrigin) = 0;
 
   virtual void
   ReleaseIOThreadObjects() = 0;
 
   // Methods which are called on the main thred.
-  virtual bool
-  IsFileServiceUtilized() = 0;
-
-  virtual bool
-  IsTransactionServiceActivated() = 0;
-
   virtual void
   WaitForStoragesToComplete(nsTArray<nsIOfflineStorage*>& aStorages,
                             nsIRunnable* aCallback) = 0;
 
   virtual void
-  ShutdownTransactionService() = 0;
+  ShutdownWorkThreads() = 0;
 
 protected:
   virtual ~Client()
   { }
 };
 
 END_QUOTA_NAMESPACE
 
--- a/dom/quota/QuotaManager.cpp
+++ b/dom/quota/QuotaManager.cpp
@@ -24,17 +24,16 @@
 #include "nsIUsageCallback.h"
 #include "nsPIDOMWindow.h"
 
 #include <algorithm>
 #include "GeckoProfiler.h"
 #include "mozilla/Atomics.h"
 #include "mozilla/CondVar.h"
 #include "mozilla/dom/asmjscache/AsmJSCache.h"
-#include "mozilla/dom/FileService.h"
 #include "mozilla/dom/cache/QuotaClient.h"
 #include "mozilla/dom/indexedDB/ActorsParent.h"
 #include "mozilla/Mutex.h"
 #include "mozilla/LazyIdleThread.h"
 #include "mozilla/Preferences.h"
 #include "mozilla/Services.h"
 #include "nsAppDirectoryServiceDefs.h"
 #include "nsComponentManagerUtils.h"
@@ -85,17 +84,16 @@
 
 #define KB * 1024ULL
 #define MB * 1024ULL KB
 #define GB * 1024ULL MB
 
 USING_QUOTA_NAMESPACE
 using namespace mozilla;
 using namespace mozilla::dom;
-using mozilla::dom::FileService;
 
 static_assert(
   static_cast<uint32_t>(StorageType::Persistent) ==
   static_cast<uint32_t>(PERSISTENCE_TYPE_PERSISTENT),
   "Enum values should match.");
 
 static_assert(
   static_cast<uint32_t>(StorageType::Temporary) ==
@@ -541,36 +539,16 @@ public:
   }
 
 private:
   // The QuotaManager holds this alive.
   SynchronizedOp* mOp;
   uint32_t mCountdown;
 };
 
-class WaitForFileHandlesToFinishRunnable final : public nsRunnable
-{
-public:
-  WaitForFileHandlesToFinishRunnable()
-  : mBusy(true)
-  { }
-
-  NS_IMETHOD
-  Run();
-
-  bool
-  IsBusy() const
-  {
-    return mBusy;
-  }
-
-private:
-  bool mBusy;
-};
-
 class SaveOriginAccessTimeRunnable final : public nsRunnable
 {
 public:
   SaveOriginAccessTimeRunnable(PersistenceType aPersistenceType,
                                const nsACString& aOrigin,
                                int64_t aTimestamp)
   : mPersistenceType(aPersistenceType), mOrigin(aOrigin), mTimestamp(aTimestamp)
   { }
@@ -1705,37 +1683,31 @@ QuotaManager::UnregisterStorage(nsIOffli
 }
 
 void
 QuotaManager::AbortCloseStoragesForProcess(ContentParent* aContentParent)
 {
   MOZ_ASSERT(NS_IsMainThread());
   MOZ_ASSERT(aContentParent);
 
-  FileService* service = FileService::Get();
+  // FileHandle API is not yet supported in child processes, so we don't have
+  // to worry about aborting file handles for given child process.
 
   StorageMatcher<ArrayCluster<nsIOfflineStorage*>> liveStorages;
   liveStorages.Find(mLiveStorages);
 
   for (uint32_t i = 0; i < Client::TYPE_MAX; i++) {
-    nsRefPtr<Client>& client = mClients[i];
-    bool utilized = service && client->IsFileServiceUtilized();
-
     nsTArray<nsIOfflineStorage*>& array = liveStorages[i];
     for (uint32_t j = 0; j < array.Length(); j++) {
       nsCOMPtr<nsIOfflineStorage> storage = array[j];
 
       if (storage->IsOwnedByProcess(aContentParent)) {
         if (NS_FAILED(storage->Close())) {
           NS_WARNING("Failed to close storage for dying process!");
         }
-
-        if (utilized) {