Merge inbound to central, a=merge
authorWes Kocher <wkocher@mozilla.com>
Thu, 31 Mar 2016 13:04:19 -0700
changeset 291096 bccb11375f2af838cda714d42fd8cef78f5c7bf1
parent 290901 09f42355aa061bada9ab92056215d9cdae86a220 (current diff)
parent 291095 3b74da083e58b7464159789e20431979713842bd (diff)
child 291097 165538409c04d972fd977d2ef26dcb1daa466983
child 291292 50c354c8516f0cf1ea64b216ca09f5de69c5968c
push id19656
push usergwagner@mozilla.com
push dateMon, 04 Apr 2016 13:43:23 +0000
treeherderb2g-inbound@e99061fde28a [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone48.0a1
Merge inbound to central, a=merge MozReview-Commit-ID: FSnrOgtIFer
browser/base/content/test/general/test_feed_discovery.html
devtools/client/inspector/rules/test/browser_rules_completion-new-property_02.js
docshell/base/nsIDocShell.idl
dom/animation/test/chrome/test_animation_property_state.html
ipc/chromium/src/chrome/common/ipc_channel_proxy.cc
ipc/chromium/src/chrome/common/ipc_channel_proxy.h
ipc/chromium/src/chrome/common/ipc_sync_channel.cc
ipc/chromium/src/chrome/common/ipc_sync_channel.h
ipc/chromium/src/chrome/common/ipc_sync_message.cc
ipc/chromium/src/chrome/common/ipc_sync_message.h
ipc/chromium/src/chrome/common/message_router.cc
ipc/chromium/src/chrome/common/message_router.h
js/src/frontend/BytecodeEmitter.cpp
js/src/frontend/BytecodeEmitter.h
mobile/android/tests/browser/robocop/libs/robotium-solo-4.3.1.jar
mobile/android/tests/browser/robocop/src/org/mozilla/gecko/tests/BaseRobocopTest.java
mobile/android/tests/browser/robocop/src/org/mozilla/gecko/tests/BaseTest.java
testing/mozharness/configs/releases/postrelease_release.py
testing/mozharness/configs/releases/updates_release.py
testing/web-platform/meta/web-animations/animation-node/animation-node-after.html.ini
testing/web-platform/meta/web-animations/animation-node/animation-node-before.html.ini
testing/web-platform/meta/web-animations/animation-node/animation-node-next-sibling.html.ini
testing/web-platform/meta/web-animations/animation-node/animation-node-parent.html.ini
testing/web-platform/meta/web-animations/animation-node/animation-node-previous-sibling.html.ini
testing/web-platform/meta/web-animations/animation-node/animation-node-remove.html.ini
testing/web-platform/meta/web-animations/animation-node/animation-node-replace.html.ini
testing/web-platform/meta/web-animations/animation-node/idlharness.html.ini
testing/web-platform/tests/web-animations/animation-node/animation-node-after.html
testing/web-platform/tests/web-animations/animation-node/animation-node-before.html
testing/web-platform/tests/web-animations/animation-node/animation-node-next-sibling.html
testing/web-platform/tests/web-animations/animation-node/animation-node-parent.html
testing/web-platform/tests/web-animations/animation-node/animation-node-previous-sibling.html
testing/web-platform/tests/web-animations/animation-node/animation-node-remove.html
testing/web-platform/tests/web-animations/animation-node/animation-node-replace.html
testing/web-platform/tests/web-animations/animation-node/idlharness.html
--- a/accessible/generic/DocAccessible.cpp
+++ b/accessible/generic/DocAccessible.cpp
@@ -1385,18 +1385,31 @@ DocAccessible::ProcessInvalidationList()
 {
   // Invalidate children of container accessible for each element in
   // invalidation list. Allow invalidation list insertions while container
   // children are recached.
   for (uint32_t idx = 0; idx < mInvalidationList.Length(); idx++) {
     nsIContent* content = mInvalidationList[idx];
     if (!HasAccessible(content)) {
       Accessible* container = GetContainerAccessible(content);
-      if (container)
-        UpdateTreeOnInsertion(container);
+      if (container) {
+        TreeWalker walker(container);
+        if (container->IsAcceptableChild(content) && walker.Seek(content)) {
+          Accessible* child =
+            GetAccService()->GetOrCreateAccessible(content, container);
+          if (child) {
+            AutoTreeMutation mMut(container);
+            RefPtr<AccReorderEvent> reorderEvent =
+              new AccReorderEvent(container);
+            container->InsertAfter(child, walker.Prev());
+            uint32_t flags = UpdateTreeInternal(child, true, reorderEvent);
+            FireEventsOnInsertion(container, reorderEvent, flags);
+          }
+        }
+      }
     }
   }
 
   mInvalidationList.Clear();
 }
 
 Accessible*
 DocAccessible::GetAccessibleEvenIfNotInMap(nsINode* aNode) const
@@ -1829,37 +1842,45 @@ DocAccessible::ProcessContentInserted(Ac
     MOZ_ASSERT_UNREACHABLE("accessible was rejected");
   }
 
 #ifdef A11Y_LOG
   logging::TreeInfo("children after insertion", logging::eVerbose,
                     aContainer);
 #endif
 
+  FireEventsOnInsertion(aContainer, reorderEvent, updateFlags);
+}
+
+void
+DocAccessible::FireEventsOnInsertion(Accessible* aContainer,
+                                     AccReorderEvent* aReorderEvent,
+                                     uint32_t aUpdateFlags)
+{
   // Content insertion did not cause an accessible tree change.
-  if (updateFlags == eNoAccessible) {
+  if (aUpdateFlags == eNoAccessible) {
     return;
   }
 
   // Check to see if change occurred inside an alert, and fire an EVENT_ALERT
   // if it did.
-  if (!(updateFlags & eAlertAccessible) &&
+  if (!(aUpdateFlags & eAlertAccessible) &&
       (aContainer->IsAlert() || aContainer->IsInsideAlert())) {
     Accessible* ancestor = aContainer;
     do {
       if (ancestor->IsAlert()) {
         FireDelayedEvent(nsIAccessibleEvent::EVENT_ALERT, ancestor);
         break;
       }
     }
     while ((ancestor = ancestor->Parent()));
   }
 
   MaybeNotifyOfValueChange(aContainer);
-  FireDelayedEvent(reorderEvent);
+  FireDelayedEvent(aReorderEvent);
 }
 
 void
 DocAccessible::UpdateTreeOnInsertion(Accessible* aContainer)
 {
   for (uint32_t idx = 0; idx < aContainer->ContentChildCount(); idx++) {
     Accessible* child = aContainer->ContentChildAt(idx);
     child->SetSurvivingInUpdate(true);
@@ -2256,55 +2277,78 @@ DocAccessible::MoveChild(Accessible* aCh
 
 void
 DocAccessible::PutChildrenBack(nsTArray<RefPtr<Accessible> >* aChildren,
                                uint32_t aStartIdx)
 {
   nsTArray<RefPtr<Accessible> > containers;
   for (auto idx = aStartIdx; idx < aChildren->Length(); idx++) {
     Accessible* child = aChildren->ElementAt(idx);
+    if (!child->IsInDocument()) {
+      continue;
+    }
 
-    // If the child is in the tree then remove it from the owner.
-    if (child->IsInDocument()) {
-      Accessible* owner = child->Parent();
-      if (!owner) {
-        NS_ERROR("Cannot put the child back. No parent, a broken tree.");
-        continue;
-      }
-      RefPtr<AccReorderEvent> reorderEvent = new AccReorderEvent(owner);
-      RefPtr<AccMutationEvent> hideEvent = new AccHideEvent(child, false);
-      reorderEvent->AddSubMutationEvent(hideEvent);
-      FireDelayedEvent(hideEvent);
+    // Remove the child from the owner
+    Accessible* owner = child->Parent();
+    if (!owner) {
+      NS_ERROR("Cannot put the child back. No parent, a broken tree.");
+      continue;
+    }
 
-      {
-        AutoTreeMutation mut(owner);
-        owner->RemoveChild(child);
-        child->SetRelocated(false);
-      }
+#ifdef A11Y_LOG
+    logging::TreeInfo("aria owns put child back", 0,
+                      "old parent", owner, "child", child, nullptr);
+#endif
 
-      MaybeNotifyOfValueChange(owner);
-      FireDelayedEvent(reorderEvent);
+    RefPtr<AccReorderEvent> reorderEvent = new AccReorderEvent(owner);
+    RefPtr<AccMutationEvent> hideEvent = new AccHideEvent(child, false);
+    reorderEvent->AddSubMutationEvent(hideEvent);
+    FireDelayedEvent(hideEvent);
+
+    {
+      AutoTreeMutation mut(owner);
+      owner->RemoveChild(child);
+      child->SetRelocated(false);
     }
 
-    Accessible* container = GetContainerAccessible(child->GetContent());
-    if (container &&
-        containers.IndexOf(container) == nsTArray<Accessible*>::NoIndex) {
-      containers.AppendElement(container);
+    MaybeNotifyOfValueChange(owner);
+    FireDelayedEvent(reorderEvent);
+
+#ifdef A11Y_LOG
+    logging::TreeInfo("aria owns put child back: old parent tree after",
+                      logging::eVerbose, owner);
+#endif
+
+    // and put it back where it belongs to.
+    Accessible* origContainer = GetContainerAccessible(child->GetContent());
+    if (origContainer) {
+      TreeWalker walker(origContainer);
+      if (walker.Seek(child->GetContent())) {
+        Accessible* prevChild = walker.Prev();
+        {
+          AutoTreeMutation mut(origContainer);
+          origContainer->InsertAfter(child, prevChild);
+        }
+
+        RefPtr<AccReorderEvent> reorderEvent = new AccReorderEvent(origContainer);
+        RefPtr<AccMutationEvent> showEvent = new AccShowEvent(child);
+        reorderEvent->AddSubMutationEvent(showEvent);
+        FireDelayedEvent(showEvent);
+        MaybeNotifyOfValueChange(origContainer);
+        FireDelayedEvent(reorderEvent);
+
+#ifdef A11Y_LOG
+        logging::TreeInfo("aria owns put child back: new parent tree after",
+                          logging::eVerbose, origContainer);
+#endif
+      }
     }
   }
 
-  // And put it back where it belongs to.
   aChildren->RemoveElementsAt(aStartIdx, aChildren->Length() - aStartIdx);
-  for (uint32_t idx = 0; idx < containers.Length(); idx++) {
-    NS_ASSERTION(containers[idx]->IsInDocument(),
-                 "A container has been destroyed.");
-    if (containers[idx]->IsInDocument()) {
-      UpdateTreeOnInsertion(containers[idx]);
-    }
-  }
 }
 
 void
 DocAccessible::CacheChildrenInSubtree(Accessible* aRoot,
                                       Accessible** aFocusedAcc)
 {
   // If the accessible is focused then report a focus event after all related
   // mutation events.
--- a/accessible/generic/DocAccessible.h
+++ b/accessible/generic/DocAccessible.h
@@ -182,16 +182,19 @@ public:
   DocAccessible* GetChildDocumentAt(uint32_t aIndex) const
     { return mChildDocuments.SafeElementAt(aIndex, nullptr); }
 
   /**
    * Fire accessible event asynchronously.
    */
   void FireDelayedEvent(AccEvent* aEvent);
   void FireDelayedEvent(uint32_t aEventType, Accessible* aTarget);
+  void FireEventsOnInsertion(Accessible* aContainer,
+                             AccReorderEvent* aReorderEvent,
+                             uint32_t aUpdateFlags);
 
   /**
    * Fire value change event on the given accessible if applicable.
    */
   void MaybeNotifyOfValueChange(Accessible* aAccessible);
 
   /**
    * Get/set the anchor jump.
--- a/accessible/jsat/Gestures.jsm
+++ b/accessible/jsat/Gestures.jsm
@@ -8,20 +8,18 @@
 /******************************************************************************
   All gestures have the following pathways when being resolved(v)/rejected(x):
                Tap -> DoubleTap        (x)
                    -> Dwell            (x)
                    -> Swipe            (x)
 
          DoubleTap -> TripleTap        (x)
                    -> TapHold          (x)
-                   -> Explore          (x)
 
          TripleTap -> DoubleTapHold    (x)
-                   -> Explore          (x)
 
              Dwell -> DwellEnd         (v)
 
              Swipe -> Explore          (x)
 
            TapHold -> TapHoldEnd       (v)
 
      DoubleTapHold -> DoubleTapHoldEnd (v)
@@ -34,17 +32,16 @@
 
         ExploreEnd -> Explore          (x)
 
            Explore -> ExploreEnd       (v)
 ******************************************************************************/
 
 'use strict';
 
-const Ci = Components.interfaces;
 const Cu = Components.utils;
 
 this.EXPORTED_SYMBOLS = ['GestureSettings', 'GestureTracker']; // jshint ignore:line
 
 Cu.import('resource://gre/modules/XPCOMUtils.jsm');
 
 XPCOMUtils.defineLazyModuleGetter(this, 'Utils', // jshint ignore:line
   'resource://gre/modules/accessibility/Utils.jsm');
@@ -68,18 +65,16 @@ const MAX_CONSECUTIVE_GESTURE_DELAY = 20
 const DWELL_THRESHOLD = 250;
 // Minimal swipe distance in inches
 const SWIPE_MIN_DISTANCE = 0.4;
 // Maximum distance the pointer could move during a tap in inches
 const TAP_MAX_RADIUS = 0.2;
 // Directness coefficient. It is based on the maximum 15 degree angle between
 // consequent pointer move lines.
 const DIRECTNESS_COEFF = 1.44;
-// The virtual touch ID generated by a mouse event.
-const MOUSE_ID = 'mouse';
 // Amount in inches from the edges of the screen for it to be an edge swipe
 const EDGE = 0.1;
 // Multiply timeouts by this constant, x2 works great too for slower users.
 const TIMEOUT_MULTIPLIER = 1;
 // A single pointer down/up sequence periodically precedes the tripple swipe
 // gesture on Android. This delay acounts for that.
 const IS_ANDROID = Utils.MozBuildApp === 'mobile/android' &&
   Utils.AndroidSdkVersion >= 14;
@@ -206,17 +201,16 @@ this.GestureTracker = { // jshint ignore
    * @param  {Number} aTimeStamp A new pointer event timeStamp.
    * @param  {Function} aGesture A gesture constructor (default: Tap).
    */
   _init: function GestureTracker__init(aDetail, aTimeStamp, aGesture) {
     // Only create a new gesture on |pointerdown| event.
     if (aDetail.type !== 'pointerdown') {
       return;
     }
-    let points = aDetail.points;
     let GestureConstructor = aGesture || (IS_ANDROID ? DoubleTap : Tap);
     this._create(GestureConstructor);
     this._update(aDetail, aTimeStamp);
   },
 
   /**
    * Handle the incoming pointer event with the existing gesture object(if
    * present) or with the newly created one.
@@ -265,16 +259,17 @@ this.GestureTracker = { // jshint ignore
     if (!current || current.id !== id) {
       return;
     }
     // Only create a gesture if we got a constructor.
     if (gestureType) {
       this._create(gestureType, current.startTime, current.points,
         current.lastEvent);
     } else {
+      this.current.clearTimer();
       delete this.current;
     }
   }
 };
 
 /**
  * Compile a mozAccessFuGesture detail structure.
  * @param  {String} aType A gesture type.
@@ -603,16 +598,19 @@ function TravelGesture(aTimeStamp, aPoin
 
 TravelGesture.prototype = Object.create(Gesture.prototype);
 
 /**
  * Test the gesture points for travel. The gesture will be rejected to
  * this._travelTo gesture iff at least one point crosses this._threshold.
  */
 TravelGesture.prototype.test = function TravelGesture_test() {
+  if (!this._travelTo) {
+    return;
+  }
   for (let identifier in this.points) {
     let point = this.points[identifier];
     if (point.totalDistanceTraveled / Utils.dpi > this._threshold) {
       this._deferred.reject(this._travelTo);
       return;
     }
   }
 };
@@ -675,20 +673,20 @@ DoubleTapHoldEnd.prototype.type = 'doubl
  * A common tap gesture object.
  * @param {Number} aTimeStamp An original pointer event's timeStamp that started
  * the gesture resolution sequence.
  * @param {Object} aPoints An existing set of points (from previous events).
  * @param {?String} aLastEvent Last pointer event type.
  * @param {Function} aRejectToOnWait A constructor for the next gesture to
  * reject to in case no pointermove or pointerup happens within the
  * GestureSettings.dwellThreshold.
+ * @param {Function} aTravelTo An optional constuctor for the next gesture to
+ * reject to in case the the TravelGesture test fails.
  * @param {Function} aRejectToOnPointerDown A constructor for the gesture to
  * reject to if a finger comes down immediately after the tap.
- * @param {Function} aTravelTo An optional constuctor for the next gesture to
- * reject to in case the the TravelGesture test fails.
  */
 function TapGesture(aTimeStamp, aPoints, aLastEvent, aRejectToOnWait, aTravelTo, aRejectToOnPointerDown) {
   this._rejectToOnWait = aRejectToOnWait;
   this._rejectToOnPointerDown = aRejectToOnPointerDown;
   // If the pointer travels, reject to aTravelTo.
   TravelGesture.call(this, aTimeStamp, aPoints, aLastEvent, aTravelTo,
     TAP_MAX_RADIUS);
 }
@@ -717,21 +715,22 @@ TapGesture.prototype.pointerup = functio
         }
       }
     } else {
       TravelGesture.prototype.pointerup.call(this, aPoints);
     }
 };
 
 TapGesture.prototype.pointerdown = function TapGesture_pointerdown(aPoints, aTimeStamp) {
-  TravelGesture.prototype.pointerdown.call(this, aPoints, aTimeStamp);
   if (this._pointerUpTimer) {
     clearTimeout(this._pointerUpTimer);
     delete this._pointerUpTimer;
     this._deferred.reject(this._rejectToOnPointerDown);
+  } else {
+    TravelGesture.prototype.pointerdown.call(this, aPoints, aTimeStamp);
   }
 };
 
 
 /**
  * Tap gesture.
  * @param {Number} aTimeStamp An original pointer event's timeStamp that started
  * the gesture resolution sequence.
@@ -766,17 +765,17 @@ DoubleTap.prototype.type = 'doubletap';
  * Triple Tap gesture.
  * @param {Number} aTimeStamp An original pointer event's timeStamp that started
  * the gesture resolution sequence.
  * @param {Object} aPoints An existing set of points (from previous events).
  * @param {?String} aLastEvent Last pointer event type.
  */
 function TripleTap(aTimeStamp, aPoints, aLastEvent) {
   this._inProgress = true;
-  TapGesture.call(this, aTimeStamp, aPoints, aLastEvent, DoubleTapHold);
+  TapGesture.call(this, aTimeStamp, aPoints, aLastEvent, DoubleTapHold, null, null);
 }
 
 TripleTap.prototype = Object.create(TapGesture.prototype);
 TripleTap.prototype.type = 'tripletap';
 
 /**
  * Common base object for gestures that are created as resolved.
  * @param {Number} aTimeStamp An original pointer event's timeStamp that started
--- a/accessible/tests/mochitest/treeupdate/test_ariaowns.html
+++ b/accessible/tests/mochitest/treeupdate/test_ariaowns.html
@@ -69,18 +69,18 @@
         return "Change @aria-owns attribute";
       }
     }
 
     function removeARIAOwns()
     {
       this.eventSeq = [
         new invokerChecker(EVENT_HIDE, getNode("t1_button")),
+        new invokerChecker(EVENT_SHOW, getNode("t1_button")),
         new invokerChecker(EVENT_HIDE, getNode("t1_subdiv")),
-        new invokerChecker(EVENT_SHOW, getNode("t1_button")),
         new invokerChecker(EVENT_SHOW, getNode("t1_subdiv")),
         new invokerChecker(EVENT_REORDER, getNode("t1_container"))
       ];
 
       this.invoke = function removeARIAOwns_invoke()
       {
         getNode("t1_container").removeAttribute("aria-owns");
       }
--- a/b2g/common.configure
+++ b/b2g/common.configure
@@ -8,17 +8,17 @@
 # ==============================================================
 option(env='MOZTTDIR', nargs=1, help='Path to truetype fonts for B2G')
 
 @depends('MOZTTDIR')
 def mozttdir(value):
     if value:
         path = value[0]
         if not os.path.isdir(path):
-            error('MOZTTDIR "%s" is not a valid directory' % path)
+            die('MOZTTDIR "%s" is not a valid directory', path)
         return path
 
 set_config('MOZTTDIR', mozttdir)
 
 @depends('MOZTTDIR')
 def package_moztt(value):
     if value:
         return True
--- a/b2g/config/aries/config.json
+++ b/b2g/config/aries/config.json
@@ -37,17 +37,16 @@
     "env": {
         "VARIANT": "user",
         "MOZILLA_OFFICIAL": "1",
         "MOZ_TELEMETRY_REPORTING": "1",
         "GAIA_KEYBOARD_LAYOUTS": "en,pt-BR,es,de,fr,pl,zh-Hans-Pinyin,zh-Hant-Zhuyin,en-Dvorak"
     },
     "b2g_manifest": "aries.xml",
     "b2g_manifest_intree": true,
-    "additional_source_tarballs": ["backup-aries.tar.xz"],
     "gecko_l10n_root": "https://hg.mozilla.org/l10n-central",
     "gaia": {
         "l10n": {
             "vcs": "hgtool",
             "root": "https://hg.mozilla.org/gaia-l10n"
         }
     }
 }
--- a/b2g/config/aries/releng-aries.tt
+++ b/b2g/config/aries/releng-aries.tt
@@ -1,17 +1,10 @@
 [
 {
-"size": 135359412,
-"digest": "45e677c9606cc4eec44ef4761df47ff431df1ffad17a5c6d21ce700a1c47f79e87a4aa9f30ae47ff060bd64f5b775d995780d88211f9a759ffa0d076beb4816b",
-"algorithm": "sha512",
-"filename": "backup-aries.tar.xz",
-"comment": "v18D"
-},
-{
 "version": "gcc 4.8.5",
 "size": 81065660,
 "digest": "db26f498ab56a3b5c65d7cda290cbb74174af9f2d021ca9c158f53b0382924ccf5ed9638d41eef449434aa9383a9113994d9729d9dd910321d1f35f9411eae38",
 "algorithm": "sha512",
 "filename": "gcc.tar.xz",
 "unpack": "True"
 }
 ]
--- a/b2g/installer/package-manifest.in
+++ b/b2g/installer/package-manifest.in
@@ -669,16 +669,19 @@
 @RESPATH@/components/Payment.manifest
 
 @RESPATH@/components/DownloadsAPI.js
 @RESPATH@/components/DownloadsAPI.manifest
 
 ; InputMethod API
 @RESPATH@/components/MozKeyboard.js
 @RESPATH@/components/InputMethod.manifest
+#ifdef MOZ_B2G
+@RESPATH@/components/inputmethod.xpt
+#endif
 
 @RESPATH@/components/EngineeringMode.manifest
 @RESPATH@/components/EngineeringModeAPI.js
 @RESPATH@/components/EngineeringModeService.js
 
 @RESPATH@/components/SystemUpdate.manifest
 @RESPATH@/components/SystemUpdateManager.js
 
--- a/browser/base/content/content.js
+++ b/browser/base/content/content.js
@@ -11,16 +11,18 @@ var {classes: Cc, interfaces: Ci, utils:
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource:///modules/ContentWebRTC.jsm");
 Cu.import("resource:///modules/ContentObservers.jsm");
 Cu.import("resource://gre/modules/InlineSpellChecker.jsm");
 Cu.import("resource://gre/modules/InlineSpellCheckerContent.jsm");
 Cu.import("resource://gre/modules/Task.jsm");
 
+XPCOMUtils.defineLazyModuleGetter(this, "E10SUtils",
+  "resource:///modules/E10SUtils.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "BrowserUtils",
   "resource://gre/modules/BrowserUtils.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "ContentLinkHandler",
   "resource:///modules/ContentLinkHandler.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "LoginManagerContent",
   "resource://gre/modules/LoginManagerContent.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "InsecurePasswordUtils",
   "resource://gre/modules/InsecurePasswordUtils.jsm");
@@ -44,17 +46,19 @@ XPCOMUtils.defineLazyModuleGetter(this, 
 
 // TabChildGlobal
 var global = this;
 
 // Load the form validation popup handler
 var formSubmitObserver = new FormSubmitObserver(content, this);
 
 addMessageListener("ContextMenu:DoCustomCommand", function(message) {
-  PageMenuChild.executeMenu(message.data);
+  E10SUtils.wrapHandlingUserInput(
+    content, message.data.handlingUserInput,
+    () => PageMenuChild.executeMenu(message.data.generatedItemId));
 });
 
 addMessageListener("RemoteLogins:fillForm", function(message) {
   LoginManagerContent.receiveMessage(message, content);
 });
 addEventListener("DOMFormHasPassword", function(event) {
   LoginManagerContent.onDOMFormHasPassword(event, content);
   InsecurePasswordUtils.checkForInsecurePasswords(event.target);
--- a/browser/base/content/tab-content.js
+++ b/browser/base/content/tab-content.js
@@ -63,25 +63,20 @@ addMessageListener("Browser:Reload", fun
   try {
     let sh = webNav.sessionHistory;
     if (sh)
       webNav = sh.QueryInterface(Ci.nsIWebNavigation);
   } catch (e) {
   }
 
   let reloadFlags = message.data.flags;
-  let handlingUserInput;
   try {
-    handlingUserInput = content.QueryInterface(Ci.nsIInterfaceRequestor)
-                               .getInterface(Ci.nsIDOMWindowUtils)
-                               .setHandlingUserInput(message.data.handlingUserInput);
-    webNav.reload(reloadFlags);
+    E10SUtils.wrapHandlingUserInput(content, message.data.handlingUserInput,
+                                    () => webNav.reload(reloadFlags));
   } catch (e) {
-  } finally {
-    handlingUserInput.destruct();
   }
 });
 
 addMessageListener("MixedContent:ReenableProtection", function() {
   docShell.mixedContentChannel = null;
 });
 
 addMessageListener("SecondScreen:tab-mirror", function(message) {
--- a/browser/base/content/test/general/browser.ini
+++ b/browser/base/content/test/general/browser.ini
@@ -346,16 +346,18 @@ skip-if = os == "linux" # Linux: Intermi
 [browser_menuButtonFitts.js]
 skip-if = os != "win" # The Fitts Law menu button is only supported on Windows (bug 969376)
 [browser_middleMouse_noJSPaste.js]
 [browser_minimize.js]
 [browser_mixedcontent_securityflags.js]
 tags = mcb
 [browser_offlineQuotaNotification.js]
 skip-if = buildapp == 'mulet'
+[browser_feed_discovery.js]
+support-files = feed_discovery.html
 [browser_gZipOfflineChild.js]
 skip-if = buildapp == 'mulet' # Bug 1066070 - I don't think either popup notifications nor addon install stuff works?
 support-files = test_offline_gzip.html gZipOfflineChild.cacheManifest gZipOfflineChild.cacheManifest^headers^ gZipOfflineChild.html gZipOfflineChild.html^headers^
 [browser_openPromptInBackgroundTab.js]
 support-files = openPromptOffTimeout.html
 [browser_overflowScroll.js]
 [browser_pageInfo.js]
 skip-if = buildapp == 'mulet'
new file mode 100644
--- /dev/null
+++ b/browser/base/content/test/general/browser_feed_discovery.js
@@ -0,0 +1,33 @@
+const URL = "http://mochi.test:8888/browser/browser/base/content/test/general/feed_discovery.html"
+
+/** Test for Bug 377611 **/
+
+add_task(function* () {
+  // Open a new tab.
+  gBrowser.selectedTab = gBrowser.addTab(URL);
+  registerCleanupFunction(() => gBrowser.removeCurrentTab());
+
+  let browser = gBrowser.selectedBrowser;
+  yield BrowserTestUtils.browserLoaded(browser);
+
+  let discovered = browser.feeds;
+  ok(discovered.length > 0, "some feeds should be discovered");
+
+  let feeds = {};
+  for (let aFeed of discovered) {
+    feeds[aFeed.href] = true;
+  }
+
+  yield ContentTask.spawn(browser, feeds, function* (feeds) {
+    for (let aLink of content.document.getElementsByTagName("link")) {
+      // ignore real stylesheets, and anything without an href property
+      if (aLink.type != "text/css" && aLink.href) {
+        if (/bogus/i.test(aLink.title)) {
+          ok(!feeds[aLink.href], "don't discover " + aLink.href);
+        } else {
+          ok(feeds[aLink.href], "should discover " + aLink.href);
+        }
+      }
+    }
+  });
+})
--- a/browser/base/content/test/general/feed_discovery.html
+++ b/browser/base/content/test/general/feed_discovery.html
@@ -1,15 +1,16 @@
 <!DOCTYPE HTML>
 <html>
 <!--
 https://bugzilla.mozilla.org/show_bug.cgi?id=377611
 -->
   <head>
     <title>Test for feed discovery</title>
+    <meta charset="utf-8">
 
     <!-- Straight up standard -->
     <link rel="alternate" type="application/atom+xml" title="1" href="/1.atom" />
     <link rel="alternate" type="application/rss+xml" title="2" href="/2.rss" />
     <link rel="feed" title="3" href="/3.xml" />
 
     <!-- rel is a space-separated list -->
     <link rel=" alternate " type="application/atom+xml" title="4" href="/4.atom" />
@@ -62,51 +63,11 @@ https://bugzilla.mozilla.org/show_bug.cg
 
     <!-- don't find text/xml by title -->
     <link rel="alternate" type="text/xml" title="Bogus12 RSS and Atom" href="/Bogus12" />
 
     <!-- alternate and stylesheet isn't a feed -->
     <link rel="alternate stylesheet" type="application/rss+xml" title="Bogus13 RSS" href="/Bogus13" />
   </head>
   <body>
-    <script type="text/javascript">
-      window.onload = function() {
-
-        var tests = new Array();
-
-        var currentWindow =
-        SpecialPowers.wrap(window).QueryInterface(SpecialPowers.Ci.nsIInterfaceRequestor)
-                              .getInterface(SpecialPowers.Ci.nsIWebNavigation)
-                              .QueryInterface(SpecialPowers.Ci.nsIDocShellTreeItem)
-                              .rootTreeItem
-                              .QueryInterface(SpecialPowers.Ci.nsIInterfaceRequestor)
-                              .getInterface(SpecialPowers.Ci.nsIDOMWindow);
-        var browser = currentWindow.gBrowser.selectedBrowser;
-
-        var discovered = browser.feeds;
-        tests.push({ check: discovered.length > 0,
-                     message: "some feeds should be discovered" });
-
-        var feeds = [];
-
-        for (var aFeed of discovered) {
-          feeds[aFeed.href] = true;
-        }
-
-        for (var aLink of document.getElementsByTagName("link")) {
-          // ignore real stylesheets, and anything without an href property
-          if (aLink.type != "text/css" && aLink.href) {
-            if (/bogus/i.test(aLink.title)) {
-              tests.push({ check: !feeds[aLink.href],
-                           message: "don't discover " + aLink.href });
-            } else {
-              tests.push({ check: feeds[aLink.href],
-                           message: "should discover " + aLink.href });
-            }
-          }
-        }
-        window.arguments[0].tests = tests;
-        window.close();
-      }
-    </script>
   </body>
 </html>
 
--- a/browser/base/content/test/general/mochitest.ini
+++ b/browser/base/content/test/general/mochitest.ini
@@ -2,29 +2,26 @@
 skip-if = buildapp == 'b2g'
 support-files =
   audio.ogg
   bug364677-data.xml
   bug364677-data.xml^headers^
   bug395533-data.txt
   contextmenu_common.js
   ctxmenu-image.png
-  feed_discovery.html
   head_plain.js
   offlineByDefault.js
   offlineChild.cacheManifest
   offlineChild.cacheManifest^headers^
   offlineChild.html
   offlineChild2.cacheManifest
   offlineChild2.cacheManifest^headers^
   offlineChild2.html
   offlineEvent.cacheManifest
   offlineEvent.cacheManifest^headers^
   offlineEvent.html
   subtst_contextmenu.html
   video.ogg
 
 [test_bug364677.html]
 [test_bug395533.html]
-[test_feed_discovery.html]
-skip-if = e10s
 [test_offlineNotification.html]
 skip-if = buildapp == 'mulet' || e10s # Bug 1066070 - I don't think either popup notifications nor addon install stuff works?
deleted file mode 100644
--- a/browser/base/content/test/general/test_feed_discovery.html
+++ /dev/null
@@ -1,54 +0,0 @@
-<!DOCTYPE HTML>
-<html>
-<!--
-https://bugzilla.mozilla.org/show_bug.cgi?id=377611
--->
-<head>
-  <title>Test for feed discovery</title>
-  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>        
-  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
-</head>
-<body>
-<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=377611">Mozilla Bug 377611</a>
-<p id="display"></p>
-<div id="content" style="display: none">
-  
-</div>
-<pre id="test">
-<script class="testbody" type="text/javascript">
-
-/** Test for Bug 377611 **/
-
-var rv = { tests: null };
-var testCheckInterval = null;
-
-function startTest() {
-  var url = window.location.href.replace(/test_feed_discovery\.html/,
-                                         'feed_discovery.html');
-  SpecialPowers.openDialog(window, [url, '', 'dialog=no,width=10,height=10', rv]);
-  testCheckInterval = window.setInterval(tryIfTestIsFinished, 500);
-}
-
-function tryIfTestIsFinished() {
-  if (rv.tests) {
-    window.clearInterval(testCheckInterval);
-    checkTest();
-  }
-}
-
-function checkTest() {
-  for (var i = 0; i < rv.tests.length; ++ i) {
-    var test = rv.tests[i];
-    ok(test.check, test.message);
-  }
-  SimpleTest.finish();
-}
-
-window.onload = startTest;
-
-SimpleTest.waitForExplicitFinish();
-</script>
-</pre>
-</body>
-</html>
-
--- a/browser/components/safebrowsing/content/test/head.js
+++ b/browser/components/safebrowsing/content/test/head.js
@@ -48,10 +48,11 @@ function promiseTabLoadEvent(tab, url, e
     tab.linkedBrowser.loadURI(url);
   }
   return deferred.promise;
 }
 
 Services.prefs.setCharPref("urlclassifier.forbiddenTable", "test-forbid-simple");
 Services.prefs.setCharPref("urlclassifier.malwareTable", "test-malware-simple,test-unwanted-simple");
 Services.prefs.setCharPref("urlclassifier.phishTable", "test-phish-simple");
+Services.prefs.setCharPref("urlclassifier.blockedTable", "test-block-simple");
 Services.prefs.setBoolPref("browser.safebrowsing.forbiddenURIs.enabled", true);
 SafeBrowsing.init();
--- a/browser/components/sessionstore/SessionStore.jsm
+++ b/browser/components/sessionstore/SessionStore.jsm
@@ -3441,33 +3441,41 @@ var SessionStoreInternal = {
     var _this = this;
     function win_(aName) { return _this._getWindowDimension(win, aName); }
 
     // find available space on the screen where this window is being placed
     let screen = gScreenManager.screenForRect(aLeft, aTop, aWidth, aHeight);
     if (screen) {
       let screenLeft = {}, screenTop = {}, screenWidth = {}, screenHeight = {};
       screen.GetAvailRectDisplayPix(screenLeft, screenTop, screenWidth, screenHeight);
+      // screenX/Y are based on the origin of the screen's desktop-pixel coordinate space
+      let screenLeftCss = screenLeft.value;
+      let screenTopCss = screenTop.value;
+      // convert screen's device pixel dimensions to CSS px dimensions
+      screen.GetAvailRect(screenLeft, screenTop, screenWidth, screenHeight);
+      let cssToDevScale = screen.defaultCSSScaleFactor;
+      let screenWidthCss = screenWidth.value / cssToDevScale;
+      let screenHeightCss = screenHeight.value / cssToDevScale;
       // constrain the dimensions to the actual space available
-      if (aWidth > screenWidth.value) {
-        aWidth = screenWidth.value;
+      if (aWidth > screenWidthCss) {
+        aWidth = screenWidthCss;
       }
-      if (aHeight > screenHeight.value) {
-        aHeight = screenHeight.value;
+      if (aHeight > screenHeightCss) {
+        aHeight = screenHeightCss;
       }
       // and then pull the window within the screen's bounds
-      if (aLeft < screenLeft.value) {
-        aLeft = screenLeft.value;
-      } else if (aLeft + aWidth > screenLeft.value + screenWidth.value) {
-        aLeft = screenLeft.value + screenWidth.value - aWidth;
+      if (aLeft < screenLeftCss) {
+        aLeft = screenLeftCss;
+      } else if (aLeft + aWidth > screenLeftCss + screenWidthCss) {
+        aLeft = screenLeftCss + screenWidthCss - aWidth;
       }
-      if (aTop < screenTop.value) {
-        aTop = screenTop.value;
-      } else if (aTop + aHeight > screenTop.value + screenHeight.value) {
-        aTop = screenTop.value + screenHeight.value - aHeight;
+      if (aTop < screenTopCss) {
+        aTop = screenTopCss;
+      } else if (aTop + aHeight > screenTopCss + screenHeightCss) {
+        aTop = screenTopCss + screenHeightCss - aHeight;
       }
     }
 
     // only modify those aspects which aren't correct yet
     if (!isNaN(aLeft) && !isNaN(aTop) && (aLeft != win_("screenX") || aTop != win_("screenY"))) {
       aWindow.moveTo(aLeft, aTop);
     }
     if (aWidth && aHeight && (aWidth != win_("width") || aHeight != win_("height"))) {
--- a/browser/config/mozconfigs/linux64/artifact
+++ b/browser/config/mozconfigs/linux64/artifact
@@ -1,6 +1,8 @@
 MOZ_AUTOMATION_BUILD_SYMBOLS=0
 MOZ_AUTOMATION_L10N_CHECK=0
 
+NO_CACHE=1
+
 . "$topsrcdir/browser/config/mozconfigs/linux64/nightly"
 
 ac_add_options --enable-artifact-builds
--- a/browser/modules/E10SUtils.jsm
+++ b/browser/modules/E10SUtils.jsm
@@ -97,9 +97,21 @@ this.E10SUtils = {
         uri: aURI.spec,
         flags: Ci.nsIWebNavigation.LOAD_FLAGS_NONE,
         referrer: aReferrer ? aReferrer.spec : null,
       },
       historyIndex: sessionHistory.requestedIndex,
     });
     return false;
   },
+
+  wrapHandlingUserInput: function(aWindow, aIsHandling, aCallback) {
+    var handlingUserInput;
+    try {
+      handlingUserInput = aWindow.QueryInterface(Ci.nsIInterfaceRequestor)
+                                 .getInterface(Ci.nsIDOMWindowUtils)
+                                 .setHandlingUserInput(aIsHandling);
+      aCallback();
+    } finally {
+      handlingUserInput.destruct();
+    }
+  },
 };
--- a/browser/modules/PluginContent.jsm
+++ b/browser/modules/PluginContent.jsm
@@ -417,18 +417,23 @@ PluginContent.prototype = {
 
       case "PluginDisabled":
         let manageLink = this.getPluginUI(plugin, "managePluginsLink");
         this.addLinkClickCallback(manageLink, "forwardCallback", "managePlugins");
         shouldShowNotification = true;
         break;
 
       case "PluginInstantiated":
-        Services.telemetry.getKeyedHistogramById('PLUGIN_ACTIVATION_COUNT').add(this._getPluginInfo(plugin).pluginTag.niceName);
+        let key = this._getPluginInfo(plugin).pluginTag.niceName;
+        Services.telemetry.getKeyedHistogramById('PLUGIN_ACTIVATION_COUNT').add(key);
         shouldShowNotification = true;
+        let pluginRect = plugin.getBoundingClientRect();
+        if (pluginRect.width <= 5 && pluginRect.height <= 5) {
+          Services.telemetry.getKeyedHistogramById('PLUGIN_TINY_CONTENT').add(key);
+        }
         break;
     }
 
     if (this._getPluginInfo(plugin).mimetype === "application/x-shockwave-flash") {
       this._recordFlashPluginTelemetry(eventType, plugin);
     }
 
     // Show the in-content UI if it's not too big. The crashed plugin handler already did this.
--- a/browser/themes/linux/searchbar.css
+++ b/browser/themes/linux/searchbar.css
@@ -158,16 +158,18 @@ menuitem[cmd="cmd_clearhistory"][disable
   display: -moz-box;
   -moz-margin-end: 0;
   width: 16px;
   height: 16px;
 }
 
 .addengine-item {
   -moz-appearance: none;
+  background-color: Menu;
+  color: MenuText;
   border: none;
   height: 32px;
   margin: 0;
   padding: 0 10px;
 }
 
 .addengine-item > .button-box {
   -moz-box-pack: start;
--- a/build/autoconf/hooks.m4
+++ b/build/autoconf/hooks.m4
@@ -1,25 +1,16 @@
 dnl This Source Code Form is subject to the terms of the Mozilla Public
 dnl License, v. 2.0. If a copy of the MPL was not distributed with this
 dnl file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
-dnl Output the contents of config.log when configure exits with an
-dnl error code.
-define([MOZ_CONFIG_LOG_TRAP],
-[changequote(<<<, >>>)dnl
-trap '[ "$?" != 0 ] && echo "------ config.log ------" && tail -n 25 config.log' EXIT
-changequote([, ])dnl
-])
-
 dnl Wrap AC_INIT_PREPARE to add the above trap.
 define([_MOZ_AC_INIT_PREPARE], defn([AC_INIT_PREPARE]))
 define([AC_INIT_PREPARE],
 [_MOZ_AC_INIT_PREPARE($1)
-MOZ_CONFIG_LOG_TRAP
 
 test "x$prefix" = xNONE && prefix=$ac_default_prefix
 # Let make expand exec_prefix.
 test "x$exec_prefix" = xNONE && exec_prefix='${prefix}'
 
 > subconfigures
 > skip_subconfigures
 ])
@@ -74,17 +65,16 @@ done
 
 define([MOZ_RUN_SUBCONFIGURES],
 [dnl Execute subconfigure, unless --no-recursion was passed to configure.
 if test "$no_recursion" != yes; then
   trap '' EXIT
   if ! $PYTHON $_topsrcdir/build/subconfigure.py $1; then
       exit 1
   fi
-  MOZ_CONFIG_LOG_TRAP
 fi
 ])
 
 define([MOZ_RUN_ALL_SUBCONFIGURES],[
 MOZ_RUN_SUBCONFIGURES([--list subconfigures --skip skip_subconfigures])
 ])
 
 dnl Print error messages in config.log as well as stderr
--- a/build/autoconf/jemalloc.m4
+++ b/build/autoconf/jemalloc.m4
@@ -29,16 +29,21 @@ if test "$MOZ_BUILD_APP" != js -o -n "$J
           # See memory/build/mozmemory_wrap.h for details.
           ac_configure_args="$ac_configure_args --without-export"
           ;;
       esac
       if test "${OS_ARCH}" = WINNT; then
         # Lazy lock initialization doesn't play well with lazy linking of
         # mozglue.dll on Windows XP (leads to startup crash), so disable it.
         ac_configure_args="$ac_configure_args --disable-lazy-lock"
+
+        # 64-bit Windows builds require a minimum 16-byte alignment.
+        if test -n "$HAVE_64BIT_BUILD"; then
+          ac_configure_args="$ac_configure_args --with-lg-tiny-min=4"
+        fi
       fi
     elif test "${OS_ARCH}" = Darwin; then
       # When building as a replace-malloc lib, disabling the zone allocator
       # forces to use pthread_atfork.
       ac_configure_args="$ac_configure_args --disable-zone-allocator"
     fi
     _MANGLE="malloc posix_memalign aligned_alloc calloc realloc free memalign valloc malloc_usable_size"
     JEMALLOC_WRAPPER=
--- a/build/autoconf/wrapper.m4
+++ b/build/autoconf/wrapper.m4
@@ -2,74 +2,24 @@ dnl This Source Code Form is subject to 
 dnl License, v. 2.0. If a copy of the MPL was not distributed with this
 dnl file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 dnl =======================================================================
 dnl = Enable compiling with various compiler wrappers (distcc, ccache, etc)
 dnl =======================================================================
 AC_DEFUN([MOZ_CHECK_COMPILER_WRAPPER],
 [
-MOZ_ARG_WITH_STRING(compiler_wrapper,
-[  --with-compiler-wrapper[=path/to/wrapper]
-    Enable compiling with wrappers such as distcc and ccache],
-    COMPILER_WRAPPER=$withval, COMPILER_WRAPPER="no")
-
-MOZ_ARG_WITH_STRING(ccache,
-[  --with-ccache[=path/to/ccache]
-                          Enable compiling with ccache],
-    CCACHE=$withval, CCACHE="no")
-
-if test "$CCACHE" != "no"; then
-    if test -z "$CCACHE" -o "$CCACHE" = "yes"; then
-        CCACHE=
-    else
-        if test ! -e "$CCACHE"; then
-            AC_MSG_ERROR([$CCACHE not found])
-        fi
-    fi
-    MOZ_PATH_PROGS(CCACHE, $CCACHE ccache)
-    if test -z "$CCACHE" -o "$CCACHE" = ":"; then
-        AC_MSG_ERROR([ccache not found])
-    elif test -x "$CCACHE"; then
-        if test "$COMPILER_WRAPPER" != "no"; then
-            COMPILER_WRAPPER="$CCACHE $COMPILER_WRAPPER"
-        else
-            COMPILER_WRAPPER="$CCACHE"
-        fi
-        MOZ_USING_CCACHE=1
-    else
-        AC_MSG_ERROR([$CCACHE is not executable])
-    fi
-fi
-
-AC_SUBST(MOZ_USING_CCACHE)
-
-if test "$COMPILER_WRAPPER" != "no"; then
-    case "$target" in
-    *-mingw*)
-        dnl When giving a windows path with backslashes, js/src/configure
-        dnl fails because of double wrapping because the test further below
-        dnl doesn't work with backslashes. While fixing that test to work
-        dnl might seem better, a lot of the make build backend actually
-        dnl doesn't like backslashes, so normalize windows paths to use
-        dnl forward slashes.
-        COMPILER_WRAPPER=`echo "$COMPILER_WRAPPER" | tr '\\' '/'`
-        ;;
-    esac
-
+if test -n "$COMPILER_WRAPPER"; then
     case "$CC" in
     $COMPILER_WRAPPER\ *)
         :
         ;;
     *)
         CC="$COMPILER_WRAPPER $CC"
         CXX="$COMPILER_WRAPPER $CXX"
         _SUBDIR_CC="$CC"
         _SUBDIR_CXX="$CXX"
         ac_cv_prog_CC="$CC"
         ac_cv_prog_CXX="$CXX"
         ;;
     esac
-    MOZ_USING_COMPILER_WRAPPER=1
 fi
-
-AC_SUBST(MOZ_USING_COMPILER_WRAPPER)
 ])
--- a/build/moz.configure/checks.configure
+++ b/build/moz.configure/checks.configure
@@ -1,86 +1,142 @@
 # -*- 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/.
 
 # Templates implementing some generic checks.
+# ==============================================================
+
+# Declare some exceptions. This is cumbersome, but since we shouldn't need a
+# lot of them, let's stack them all here. When adding a new one, put it in the
+# _declare_exceptions template, and add it to the return statement. Then
+# destructure in the assignment below the function declaration.
+@template
+@imports(_from='__builtin__', _import='Exception')
+@imports(_from='__builtin__', _import='__name__')
+def _declare_exceptions():
+    class FatalCheckError(Exception):
+        '''An exception to throw from a function decorated with @checking.
+        It will result in calling die() with the given message.
+        Debugging messages emitted from the decorated function will also be
+        printed out.'''
+    return (FatalCheckError,)
+
+(FatalCheckError,) = _declare_exceptions()
+
+del _declare_exceptions
 
 # Helper to display "checking" messages
 #   @checking('for foo')
 #   def foo():
 #       return 'foo'
 # is equivalent to:
 #   def foo():
-#       sys.stdout.write('checking for foo... ')
+#       log.info('checking for foo... ')
 #       ret = foo
-#       sys.stdout.write(ret + '\n')
+#       log.info(ret)
 #       return ret
 # This can be combined with e.g. @depends:
 #   @depends(some_option)
 #   @checking('for something')
 #   def check(value):
 #       ...
 # An optional callback can be given, that will be used to format the returned
 # value when displaying it.
 @template
 def checking(what, callback=None):
     def decorator(func):
-        @advanced
         def wrapped(*args, **kwargs):
-            import sys
-            print('checking', what, end='... ')
-            sys.stdout.flush()
-            ret = func(*args, **kwargs)
-            if callback:
-                print(callback(ret))
-            elif ret is True:
-                print('yes')
-            elif ret is False:
-                print('no')
-            else:
-                print(ret)
-            sys.stdout.flush()
+            log.info('checking %s... ', what)
+            with log.queue_debug():
+                error, ret = None, None
+                try:
+                    ret = func(*args, **kwargs)
+                except FatalCheckError as e:
+                    error = e.message
+                if callback:
+                    log.info(callback(ret))
+                elif ret is True:
+                    log.info('yes')
+                elif ret is False:
+                    log.info('no')
+                else:
+                    log.info(ret)
+                if error:
+                    die(error)
             return ret
         return wrapped
     return decorator
 
 
 # Template to check for programs in $PATH.
-#   check('PROG', ('a', 'b'))
-# will look for 'a' or 'b' in $PATH, and set_config PROG to the one
+# - `var` is the name of the variable that will be set with `set_config` when
+#   the program is found.
+# - `progs` is a list (or tuple) of program names that will be searched for.
+#   It can also be a reference to a @depends function that returns such a
+#   list. If the list is empty and there is no input, the check is skipped.
+# - `what` is a human readable description of what is being looked for. It
+#   defaults to the lowercase version of `var`.
+# - `input` is a string reference to an existing option or a reference to a
+#   @depends function resolving to explicit input for the program check.
+#   The default is to create an option for the environment variable `var`.
+#   This argument allows to use a different kind of option (possibly using a
+#   configure flag), or doing some pre-processing with a @depends function.
+# - `allow_missing` indicates whether not finding the program is an error.
+#
+# The simplest form is:
+#   check_prog('PROG', ('a', 'b'))
+# This will look for 'a' or 'b' in $PATH, and set_config PROG to the one
 # it can find. If PROG is already set from the environment or command line,
 # use that value instead.
 @template
-def check_prog(var, progs, allow_missing=False):
-    option(env=var, nargs=1, help='Path to the %s program' % var.lower())
-
-    if not (isinstance(progs, tuple) or isinstance(progs, list)):
-        configure_error('progs should be a list or tuple!')
-    progs = list(progs)
+@imports(_from='mozbuild.shellutil', _import='quote')
+@imports(_from='mozbuild.configure', _import='DependsFunction')
+def check_prog(var, progs, what=None, input=None, allow_missing=False):
+    if input:
+        # Wrap input with type checking and normalization.
+        @depends(input)
+        def input(value):
+            if not value:
+                return
+            if isinstance(value, str):
+                return (value,)
+            if isinstance(value, (tuple, list)) and len(value) == 1:
+                return value
+            configure_error('input must resolve to a tuple or a list with a '
+                            'single element, or a string')
+    else:
+        option(env=var, nargs=1,
+               help='Path to %s' % (what or 'the %s program' % var.lower()))
+        input = var
+    what = what or var.lower()
 
-    @depends(var)
-    @checking('for %s' % var.lower(), lambda x: x or 'not found')
-    def check(value):
-        if value:
-            progs[:] = value
-        for prog in progs:
+    if not isinstance(progs, DependsFunction):
+        # Trick to make a @depends function out of an immediate value.
+        progs = depends('--help')(lambda h: progs)
+
+    @depends_if(input, progs)
+    @checking('for %s' % what, lambda x: quote(x) if x else 'not found')
+    def check(value, progs):
+        if progs is None:
+            progs = ()
+
+        if not isinstance(progs, (tuple, list)):
+            configure_error('progs must resolve to a list or tuple!')
+
+        for prog in value or progs:
+            log.debug('%s: Trying %s', var.lower(), quote(prog))
             result = find_program(prog)
             if result:
                 return result
 
-    @depends(check, var)
-    @advanced
-    def postcheck(value, raw_value):
-        if value is None and (not allow_missing or raw_value):
-            from mozbuild.shellutil import quote
-            error('Cannot find %s (tried: %s)'
-                  % (var.lower(), ', '.join(quote(p) for p in progs)))
+        if not allow_missing or value:
+            raise FatalCheckError('Cannot find %s' % what)
 
-    @depends(check)
-    def normalized_for_config(value):
+    @depends_if(check, progs)
+    def normalized_for_config(value, progs):
         return ':' if value is None else value
 
     set_config(var, normalized_for_config)
 
     return check
--- a/build/moz.configure/init.configure
+++ b/build/moz.configure/init.configure
@@ -27,36 +27,34 @@ def check_build_environment(help, dist):
         topobjdir=topobjdir,
         dist=dist,
     )
 
     if help:
         return result
 
     if topsrcdir == topobjdir:
-        error(
-            '  ***\n'
+        die('  ***\n'
             '  * Building directly in the main source directory is not allowed.\n'
             '  *\n'
             '  * To build, you must run configure from a separate directory\n'
             '  * (referred to as an object directory).\n'
             '  *\n'
             '  * If you are building with a mozconfig, you will need to change your\n'
             '  * mozconfig to point to a different object directory.\n'
             '  ***'
         )
 
     # Check for a couple representative files in the source tree
     conflict_files = [
         '*         %s' % f for f in ('Makefile', 'config/autoconf.mk')
         if os.path.exists(os.path.join(topsrcdir, f))
     ]
     if conflict_files:
-        error(
-            '  ***\n'
+        die('  ***\n'
             '  *   Your source tree contains these files:\n'
             '  %s\n'
             '  *   This indicates that you previously built in the source tree.\n'
             '  *   A source tree build can confuse the separate objdir build.\n'
             '  *\n'
             '  *   To clean up the source tree:\n'
             '  *     1. cd %s\n'
             '  *     2. gmake distclean\n'
@@ -80,22 +78,20 @@ option(env='MOZCONFIG', nargs=1, help='M
 
 # Read user mozconfig
 # ==============================================================
 # Note: the dependency on --help is only there to always read the mozconfig,
 # even when --help is passed. Without this dependency, the function wouldn't
 # be called when --help is passed, and the mozconfig wouldn't be read.
 @depends('MOZ_CURRENT_PROJECT', 'MOZCONFIG', 'OLD_CONFIGURE',
          check_build_environment, '--help')
-@advanced
+@imports(_from='mozbuild.mozconfig', _import='MozconfigLoader')
 def mozconfig(current_project, mozconfig, old_configure, build_env, help):
-    from mozbuild.mozconfig import MozconfigLoader
-
     if not old_configure:
-        error('The OLD_CONFIGURE environment variable must be set')
+        die('The OLD_CONFIGURE environment variable must be set')
 
     # Don't read the mozconfig for the js configure (yay backwards
     # compatibility)
     # While the long term goal is that js and top-level use the same configure
     # and the same overall setup, including the possibility to use mozconfigs,
     # figuring out what we want to do wrt mozconfig vs. command line and
     # environment variable is not a clear-cut case, and it's more important to
     # fix the immediate problem mozconfig causes to js developers by
@@ -127,139 +123,130 @@ def mozconfig(current_project, mozconfig
 def old_configure_assignments(help):
     return []
 
 @depends('--help')
 def extra_old_configure_args(help):
     return []
 
 @template
-@advanced
+@imports(_from='mozbuild.configure', _import='DependsFunction')
 def add_old_configure_assignment(var, value_func):
-    from mozbuild.configure import DummyFunction
-    assert isinstance(value_func, DummyFunction)
+    assert isinstance(value_func, DependsFunction)
 
     @depends(old_configure_assignments, value_func)
-    @advanced
+    @imports(_from='mozbuild.shellutil', _import='quote')
     def add_assignment(assignments, value):
         if value is None:
             return
         if value is True:
             assignments.append('%s=1' % var)
         elif value is False:
             assignments.append('%s=' % var)
         else:
-            from mozbuild.shellutil import quote
+            if isinstance(value, (list, tuple)):
+                value = ' '.join(quote(v) for v in value)
             assignments.append('%s=%s' % (var, quote(value)))
 
 @template
 def add_old_configure_arg(arg):
-    @depends(extra_old_configure_args)
-    def add_arg(args):
-        args.append(arg)
+    @depends(extra_old_configure_args, arg)
+    def add_arg(args, arg):
+        if arg:
+            args.append(arg)
 
 
 option(env='PYTHON', nargs=1, help='Python interpreter')
 
 # Setup python virtualenv
 # ==============================================================
 @depends('PYTHON', check_build_environment, mozconfig)
-@advanced
+@imports('os')
+@imports('sys')
+@imports('subprocess')
+@imports(_from='mozbuild.configure.util', _import='LineIO')
+@imports(_from='mozbuild.virtualenv', _import='VirtualenvManager')
+@imports(_from='mozbuild.virtualenv', _import='verify_python_version')
+@imports('distutils.sysconfig')
 def virtualenv_python(env_python, build_env, mozconfig):
-    import os
-    import sys
-    import subprocess
-    from mozbuild.virtualenv import (
-        VirtualenvManager,
-        verify_python_version,
-    )
-
     python = env_python[0] if env_python else None
 
     # Ideally we'd rely on the mozconfig injection from mozconfig_options,
     # but we'd rather avoid the verbosity when we need to reexecute with
     # a different python.
     if mozconfig['path']:
         if 'PYTHON' in mozconfig['env']['added']:
             python = mozconfig['env']['added']['PYTHON']
         elif 'PYTHON' in mozconfig['env']['modified']:
             python = mozconfig['env']['modified']['PYTHON'][1]
         elif 'PYTHON' in mozconfig['vars']['added']:
             python = mozconfig['vars']['added']['PYTHON']
         elif 'PYTHON' in mozconfig['vars']['modified']:
             python = mozconfig['vars']['modified']['PYTHON'][1]
 
-    verify_python_version(sys.stderr)
+    with LineIO(lambda l: log.error(l)) as out:
+        verify_python_version(out)
     topsrcdir, topobjdir = build_env.topsrcdir, build_env.topobjdir
     if topobjdir.endswith('/js/src'):
         topobjdir = topobjdir[:-7]
 
-    manager = VirtualenvManager(
-        topsrcdir, topobjdir,
-        os.path.join(topobjdir, '_virtualenv'), sys.stdout,
-        os.path.join(topsrcdir, 'build', 'virtualenv_packages.txt'))
+    with LineIO(lambda l: log.info(l)) as out:
+        manager = VirtualenvManager(
+            topsrcdir, topobjdir,
+            os.path.join(topobjdir, '_virtualenv'), out,
+            os.path.join(topsrcdir, 'build', 'virtualenv_packages.txt'))
 
     if python:
         # If we're not in the virtualenv, we need the which module for
         # find_program.
         if normsep(sys.executable) != normsep(manager.python_path):
             sys.path.append(os.path.join(topsrcdir, 'python', 'which'))
         found_python = find_program(python)
         if not found_python:
-            error('The PYTHON environment variable does not contain '
-                  'a valid path. Cannot find %s' % python)
+            die('The PYTHON environment variable does not contain '
+                'a valid path. Cannot find %s', python)
         python = found_python
     else:
         python = sys.executable
 
     if not manager.up_to_date(python):
-        warn('Creating Python environment')
+        log.info('Creating Python environment')
         manager.build(python)
 
     python = normsep(manager.python_path)
 
     if python != normsep(sys.executable):
-        warn('Reexecuting in the virtualenv')
+        log.info('Reexecuting in the virtualenv')
         if env_python:
             del os.environ['PYTHON']
         # One would prefer to use os.execl, but that's completely borked on
         # Windows.
         sys.exit(subprocess.call([python] + sys.argv))
 
     # We are now in the virtualenv
-    import distutils.sysconfig
     if not distutils.sysconfig.get_python_lib():
-        error('Could not determine python site packages directory')
+        die('Could not determine python site packages directory')
 
     return python
 
 set_config('PYTHON', virtualenv_python)
 add_old_configure_assignment('PYTHON', virtualenv_python)
 
 # Inject mozconfig options
 # ==============================================================
-@template
-@advanced
-def command_line_helper():
-    # This escapes the sandbox. Don't copy this. This is only here because
-    # it is a one off and because the required functionality doesn't need
-    # to be exposed for other usecases.
-    return depends.__self__._helper
-
-
 # All options defined above this point can't be injected in mozconfig_options
 # below, so collect them.
 @template
 def early_options():
     @depends('--help')
-    @advanced
+    @imports('__sandbox__')
     def early_options(help):
         return set(
             option.env
-            for option in depends.__self__._options.itervalues()
+            for option in __sandbox__._options.itervalues()
             if option.env
         )
     return early_options
 
 early_options = early_options()
 
 # At the moment, moz.configure doesn't have complete knowledge of all the
 # supported options in mozconfig because of all that is still in old.configure.
@@ -267,16 +254,18 @@ early_options = early_options()
 # moz.configure files are executed, so we keep a manual list here, that is
 # checked in old.configure (we'll assume it's the last moz.configure file
 # processed). This is tedious but necessary for now.
 @depends('--help')
 def wanted_mozconfig_variables(help):
      return set([
          'AUTOCONF',
          'AWK',
+         'CCACHE',
+         'COMPILER_WRAPPER',
          'DISABLE_EXPORT_JS',
          'DISABLE_SHARED_JS',
          'DOXYGEN',
          'DSYMUTIL',
          'EXTERNAL_SOURCE_DIR',
          'GENISOIMAGE',
          'GRADLE',
          'GRADLE_FLAGS',
@@ -305,76 +294,72 @@ def wanted_mozconfig_variables(help):
          'WITHOUT_X',
          'XARGS',
          'YASM',
          'ZIP',
      ])
 
 
 @depends(mozconfig, wanted_mozconfig_variables, '--help')
+# This gives access to the sandbox. Don't copy this blindly.
+@imports('__sandbox__')
 def mozconfig_options(mozconfig, wanted_mozconfig_variables, help):
     if mozconfig['path']:
-        helper = command_line_helper()
-        warn('Adding configure options from %s' % mozconfig['path'])
+        helper = __sandbox__._helper
+        log.info('Adding configure options from %s' % mozconfig['path'])
         for arg in mozconfig['configure_args']:
-            warn('  %s' % arg)
+            log.info('  %s' % arg)
             # We could be using imply_option() here, but it has other
             # contraints that don't really apply to the command-line
             # emulation that mozconfig provides.
             helper.add(arg, origin='mozconfig', args=helper._args)
 
         def add(key, value):
             # See comment above wanted_mozconfig_variables
             if key in wanted_mozconfig_variables:
                 arg = '%s=%s' % (key, value)
-                warn('  %s' % arg)
+                log.info('  %s' % arg)
                 helper.add(arg, origin='mozconfig', args=helper._args)
 
         for key, value in mozconfig['env']['added'].iteritems():
             add(key, value)
         for key, (_, value) in mozconfig['env']['modified'].iteritems():
             add(key, value)
         for key, value in mozconfig['vars']['added'].iteritems():
             add(key, value)
         for key, (_, value) in mozconfig['vars']['modified'].iteritems():
             add(key, value)
 
 
-del command_line_helper
-
-
 # Mozilla-Build
 # ==============================================================
 option(env='MOZILLABUILD', nargs=1,
        help='Path to Mozilla Build (Windows-only)')
 
 # It feels dirty replicating this from python/mozbuild/mozbuild/mozconfig.py,
 # but the end goal being that the configure script would go away...
 @depends('MOZILLABUILD')
-@advanced
+@imports('sys')
 def shell(mozillabuild):
-    import sys
-
     shell = 'sh'
     if mozillabuild:
         shell = mozillabuild[0] + '/msys/bin/sh'
     if sys.platform == 'win32':
         shell = shell + '.exe'
     return shell
 
 
 # Host and target systems
 # ==============================================================
 option('--host', nargs=1, help='Define the system type performing the build')
 
 option('--target', nargs=1,
        help='Define the system type where the resulting executables will be '
             'used')
 
-@template
 def split_triplet(triplet):
     # The standard triplet is defined as
     #   CPU_TYPE-MANUFACTURER-OPERATING_SYSTEM
     # There is also a quartet form:
     #   CPU_TYPE-MANUFACTURER-KERNEL-OPERATING_SYSTEM
     # But we can consider the "KERNEL-OPERATING_SYSTEM" as one.
     cpu, manufacturer, os = triplet.split('-', 2)
 
@@ -411,17 +396,17 @@ def split_triplet(triplet):
         canonical_os = canonical_kernel = 'DragonFly'
     elif os.startswith('freebsd'):
         canonical_os = canonical_kernel = 'FreeBSD'
     elif os.startswith('netbsd'):
         canonical_os = canonical_kernel = 'NetBSD'
     elif os.startswith('openbsd'):
         canonical_os = canonical_kernel = 'OpenBSD'
     else:
-        error('Unknown OS: %s' % os)
+        die('Unknown OS: %s' % os)
 
     # The CPU granularity is probably not enough. Moving more things from
     # old-configure will tell us if we need more
     if cpu.endswith('86') or (cpu.startswith('i') and '86' in cpu):
         canonical_cpu = 'x86'
     elif cpu in ('s390', 's390x', 'x86_64', 'ia64'):
         canonical_cpu = cpu
     elif cpu in ('powerpc64', 'ppc64', 'powerpc64le', 'ppc64le'):
@@ -450,31 +435,28 @@ def split_triplet(triplet):
         cpu=canonical_cpu,
         kernel=canonical_kernel,
         os=canonical_os,
         raw_cpu=cpu,
         raw_os=os,
     )
 
 
-@template
-@advanced
+@imports('subprocess')
 def config_sub(shell, triplet):
-    import subprocess
     config_sub = os.path.join(os.path.dirname(__file__), '..',
                               'autoconf', 'config.sub')
     return subprocess.check_output([shell, config_sub, triplet]).strip()
 
 
 @depends('--host', shell)
 @checking('for host system type', lambda h: h.alias)
-@advanced
+@imports('subprocess')
 def host(value, shell):
     if not value:
-        import subprocess
         config_guess = os.path.join(os.path.dirname(__file__), '..',
                                     'autoconf', 'config.guess')
         host = subprocess.check_output([shell, config_guess]).strip()
     else:
         host = value[0]
 
     return split_triplet(config_sub(shell, host))
 
@@ -482,27 +464,33 @@ def host(value, shell):
 @depends('--target', host, shell)
 @checking('for target system type', lambda t: t.alias)
 def target(value, host, shell):
     if not value:
         return host
     return split_triplet(config_sub(shell, value[0]))
 
 
+# Autoconf needs these set
+@depends(host)
+def host_for_old_configure(host):
+    return '--host=%s' % host.alias
+
+add_old_configure_arg(host_for_old_configure)
+
 @depends(host, target)
-def host_and_target_for_old_configure(host, target):
-    # Autoconf needs these set
-    add_old_configure_arg('--host=%s' % host.alias)
-
+def target_for_old_configure(host, target):
     target_alias = target.alias
     # old-configure does plenty of tests against $target and $target_os
     # and expects darwin for iOS, so make it happy.
     if target.os == 'iOS':
         target_alias = target_alias.replace('-ios', '-darwin')
-    add_old_configure_arg('--target=%s' % target_alias)
+    return '--target=%s' % target_alias
+
+add_old_configure_arg(target_for_old_configure)
 
 
 # These variables are for compatibility with the current moz.builds and
 # old-configure. Eventually, we'll want to canonicalize better.
 @depends(target)
 def target_variables(target):
     if target.kernel == 'kFreeBSD':
         os_target = 'GNU/kFreeBSD'
@@ -627,25 +615,25 @@ option('--enable-project', nargs=1, defa
 
 option('--with-external-source-dir', env='EXTERNAL_SOURCE_DIR', nargs=1,
        help='External directory containing additional build files')
 
 @depends('--enable-project', '--with-external-source-dir',
          check_build_environment, '--help')
 def include_project_configure(project, external_source_dir, build_env, help):
     if not project:
-        error('--enable-project is required.')
+        die('--enable-project is required.')
 
     base_dir = build_env.topsrcdir
     if external_source_dir:
         base_dir = os.path.join(base_dir, external_source_dir[0])
 
     path = os.path.join(base_dir, project[0], 'moz.configure')
     if not os.path.exists(path):
-        error('Cannot find project %s' % project[0])
+        die('Cannot find project %s', project[0])
     return path
 
 @depends('--with-external-source-dir')
 def external_source_dir(value):
     if value:
         return value[0]
 
 set_config('EXTERNAL_SOURCE_DIR', external_source_dir)
@@ -664,17 +652,17 @@ add_old_configure_assignment('MOZ_BUILD_
 
 
 # set RELEASE_BUILD and NIGHTLY_BUILD variables depending on the cycle we're in
 # The logic works like this:
 # - if we have "a1" in GRE_MILESTONE, we're building Nightly (define NIGHTLY_BUILD)
 # - otherwise, if we have "a" in GRE_MILESTONE, we're building Nightly or Aurora
 # - otherwise, we're building Release/Beta (define RELEASE_BUILD)
 @depends(check_build_environment)
-@advanced
+@imports(_from='__builtin__', _import='open')
 def milestone(build_env):
     milestone_path = os.path.join(build_env.topsrcdir,
                                   'config',
                                   'milestone.txt')
     with open(milestone_path, 'r') as fh:
         milestone = fh.read().splitlines()[-1]
 
     is_nightly = is_release = None
@@ -703,17 +691,19 @@ add_old_configure_assignment('RELEASE_BU
 # Use instead of option() in js/moz.configure
 @template
 def js_option(*args, **kwargs):
     opt = option(*args, **kwargs)
 
     @depends(opt.option, build_project)
     def js_option(value, build_project):
         if build_project != 'js':
-            add_old_configure_arg(value.format(opt.option))
+            return value.format(opt.option)
+
+    add_old_configure_arg(js_option)
 
 
 # This is overridden in b2g/moz.configure with an option. No other project
 # needs the option directly, but it's used to influence some other things in
 # toolkit/moz.configure (and possibly top-level moz.configure later on), so
 # define a dummy default here.
 @depends('--help')
 def gonkdir(help):
--- a/build/moz.configure/old.configure
+++ b/build/moz.configure/old.configure
@@ -1,30 +1,26 @@
 # -*- 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/.
 
-@template
-@advanced
+@imports('codecs')
+@imports('sys')
 def encoded_open(path, mode):
-    import codecs
-    import sys
     encoding = 'mbcs' if sys.platform == 'win32' else 'utf-8'
     return codecs.open(path, mode, encoding)
 
 
 option(env='AUTOCONF', nargs=1, help='Path to autoconf 2.13')
 
 @depends(mozconfig, 'AUTOCONF')
-@advanced
+@imports('re')
 def autoconf(mozconfig, autoconf):
-    import re
-
     mozconfig_autoconf = None
     if mozconfig['path']:
         make_extra = mozconfig['make_extra']
         if make_extra:
             for assignment in make_extra:
                 m = re.match('(?:export\s+)?AUTOCONF\s*:?=\s*(.+)$',
                              assignment)
                 if m:
@@ -46,59 +42,58 @@ def autoconf(mozconfig, autoconf):
         else:
             brew = find_program('brew')
             if brew:
                 autoconf = os.path.normpath(os.path.join(
                     brew, '..', '..', 'Cellar', 'autoconf213', '2.13', 'bin',
                     'autoconf213'))
 
     if not autoconf:
-        error('Could not find autoconf 2.13')
+        die('Could not find autoconf 2.13')
 
     if not os.path.exists(autoconf):
-        error('Could not find autoconf 2.13 at %s' % (autoconf,))
+        die('Could not find autoconf 2.13 at %s', autoconf)
 
     return autoconf
 
 set_config('AUTOCONF', autoconf)
 
 
 # See comment in mozconfig_options() from build/moz.configure/init.configure
 @template
-@advanced
+# This gives access to the sandbox. Don't copy this blindly.
+@imports('__sandbox__')
 def check_mozconfig_variables():
     # This escapes the sandbox. Don't copy this. This is only here because it
     # is a one off until old-configure is gone.
-    all_options = depends.__self__._options.itervalues()
+    all_options = __sandbox__._options.itervalues()
 
     @depends(early_options, wanted_mozconfig_variables)
     def check_mozconfig_variables(early_options, wanted_mozconfig_variables):
         for option in all_options:
             if (option.env and option.env not in early_options and
                     option.env not in wanted_mozconfig_variables):
-                error(
-                    'You need to add `%s` to the `wanted_mozconfig_variables` '
-                    'list in build/moz.configure/init.configure.' % option.env)
+                die('You need to add `%s` to the `wanted_mozconfig_variables` '
+                    'list in build/moz.configure/init.configure.', option.env)
 
 check_mozconfig_variables()
 
 
 @depends('OLD_CONFIGURE', mozconfig, autoconf, check_build_environment, shell,
          old_configure_assignments, build_project)
-@advanced
+@imports(_from='__builtin__', _import='open')
+@imports(_from='__builtin__', _import='print')
+@imports('glob')
+@imports('itertools')
+@imports('subprocess')
+# Import getmtime without overwriting the sandbox os.path.
+@imports(_from='os.path', _import='getmtime')
+@imports(_from='mozbuild.shellutil', _import='quote')
 def prepare_configure(old_configure, mozconfig, autoconf, build_env, shell,
                       old_configure_assignments, build_project):
-    import glob
-    import itertools
-    import subprocess
-    # Import getmtime without overwriting the sandbox os.path.
-    from os.path import getmtime
-
-    from mozbuild.shellutil import quote
-
     # os.path.abspath in the sandbox will ensure forward slashes on Windows,
     # which is actually necessary because this path actually ends up literally
     # as $0, and backslashes there breaks autoconf's detection of the source
     # directory.
     old_configure = os.path.abspath(old_configure[0])
     if build_project == 'js':
         old_configure_dir = os.path.dirname(old_configure)
         if not old_configure_dir.endswith('/js/src'):
@@ -116,22 +111,28 @@ def prepare_configure(old_configure, moz
             glob.iglob(aclocal),
         ):
             if getmtime(input) > mtime:
                 break
         else:
             refresh = False
 
     if refresh:
-        warn('Refreshing %s with %s' % (old_configure, autoconf))
+        log.info('Refreshing %s with %s', old_configure, autoconf)
+        script = subprocess.check_output([
+            shell, autoconf,
+            '--localdir=%s' % os.path.dirname(old_configure),
+            old_configure + '.in'])
+
+        # Make old-configure append to config.log, where we put our own log.
+        # This could be done with a m4 macro, but it's way easier this way
+        script = script.replace('>./config.log', '>>./config.log')
+
         with open(old_configure, 'wb') as fh:
-            subprocess.check_call([
-                shell, autoconf,
-                '--localdir=%s' % os.path.dirname(old_configure),
-                old_configure + '.in'], stdout=fh)
+            fh.write(script)
 
     cmd = [shell, old_configure]
     with encoded_open('old-configure.vars', 'w') as out:
         if mozconfig['path']:
             for key, value in mozconfig['env']['added'].items():
                 print("export %s=%s" % (key, quote(value)), file=out)
             for key, (old, value) in mozconfig['env']['modified'].items():
                 print("export %s=%s" % (key, quote(value)), file=out)
@@ -304,18 +305,16 @@ def old_configure_options(*options):
     '--with-android-toolchain',
     '--with-android-version',
     '--with-app-basename',
     '--with-app-name',
     '--with-arch',
     '--with-arm-kuser',
     '--with-bing-api-keyfile',
     '--with-branding',
-    '--with-ccache',
-    '--with-compiler-wrapper',
     '--with-crashreporter-enable-percent',
     '--with-cross-lib',
     '--with-debug-label',
     '--with-default-mozilla-five-home',
     '--with-distribution-id',
     '--with-doc-include-dirs',
     '--with-doc-input-dirs',
     '--with-doc-output-dir',
@@ -361,24 +360,26 @@ def old_configure_options(*options):
     '--x-libraries',
 
     # Below are the configure flags used by comm-central.
     '--enable-ldap',
     '--enable-mapi',
     '--enable-calendar',
     '--enable-incomplete-external-linkage',
 )
-@advanced
+@imports(_from='__builtin__', _import='compile')
+@imports(_from='__builtin__', _import='open')
+@imports(_from='__builtin__', _import='zip')
+@imports('logging')
+@imports('os')
+@imports('subprocess')
+@imports('sys')
+@imports(_from='mozbuild.shellutil', _import='quote')
 def old_configure(prepare_configure, extra_old_configure_args, all_options,
                   *options):
-    import os
-    import subprocess
-    import sys
-    from mozbuild.shellutil import quote
-
     cmd = prepare_configure
 
     # old-configure only supports the options listed in @old_configure_options
     # so we don't need to pass it every single option we've been passed. Only
     # the ones that are not supported by python configure need to.
     cmd += [
         value.format(name)
         for name, value in zip(all_options, options)
@@ -388,61 +389,77 @@ def old_configure(prepare_configure, ext
     # We also pass it the options from js/moz.configure so that it can pass
     # them down to js/src/configure. Note this list is empty when running
     # js/src/configure, in which case we don't need to pass those options
     # to old-configure since old-configure doesn't handle them anyways.
     if extra_old_configure_args:
         cmd += extra_old_configure_args
 
     # For debugging purpose, in case it's not what we'd expect.
-    warn('running %s' % ' '.join(quote(a) for a in cmd))
+    log.info('running %s', ' '.join(quote(a) for a in cmd))
+
+    # Our logging goes to config.log, the same file old.configure uses.
+    # We can't share the handle on the file, so close it. We assume nothing
+    # beyond this point is going to be interesting to log to config.log from
+    # our end, so we don't make the effort to recreate a logging.FileHandler.
+    logger = logging.getLogger('moz.configure')
+    for handler in logger.handlers:
+        if isinstance(handler, logging.FileHandler):
+            handler.close()
+
+    log_size = os.path.getsize('config.log')
+
     ret = subprocess.call(cmd)
     if ret:
+        with log.queue_debug():
+            with open('config.log') as fh:
+                fh.seek(log_size)
+                for line in fh:
+                    log.debug(line.rstrip())
+            log.error('old-configure failed')
         sys.exit(ret)
 
     raw_config = {}
     with encoded_open('config.data', 'r') as fh:
         code = compile(fh.read(), 'config.data', 'exec')
         # Every variation of the exec() function I tried led to:
         # SyntaxError: unqualified exec is not allowed in function 'main' it
         # contains a nested function with free variables
         exec code in raw_config
 
     # Ensure all the flags known to old-configure appear in the
     # @old_configure_options above.
     all_options = set(all_options)
     for flag in raw_config['flags']:
         if flag not in all_options:
-            error('Missing option in `@old_configure_options` in %s: %s'
-                  % (__file__, flag))
+            die('Missing option in `@old_configure_options` in %s: %s',
+                __file__, flag)
 
     # If the code execution above fails, we want to keep the file around for
     # debugging.
     os.remove('config.data')
     return raw_config
 
 
 # set_config is only available in the global namespace, not directly in
 # @depends functions, but we do need to enumerate the result of
 # old_configure, so we cheat.
-@template
+@imports('__sandbox__')
 def set_old_configure_config(name, value):
-    set_config(name, value)
+    __sandbox__.set_config_impl(name, value)
 
 # Same as set_old_configure_config, but for set_define.
-@template
+@imports('__sandbox__')
 def set_old_configure_define(name, value):
-    set_define(name, value)
+    __sandbox__.set_define_impl(name, value)
 
 
 @depends(old_configure)
-@advanced
+@imports('types')
 def post_old_configure(raw_config):
-    import types
-
     for k, v in raw_config['substs']:
         set_old_configure_config(
             k[1:-1], v[1:-1] if isinstance(v, types.StringTypes) else v)
 
     for k, v in dict(raw_config['defines']).iteritems():
         set_old_configure_define(k[1:-1], v[1:-1])
 
     set_old_configure_config('non_global_defines',
--- a/build/moz.configure/toolchain.configure
+++ b/build/moz.configure/toolchain.configure
@@ -5,26 +5,25 @@
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 # yasm detection
 # ==============================================================
 yasm = check_prog('YASM', ['yasm'], allow_missing=True)
 
 @depends_if(yasm)
 @checking('yasm version')
-@advanced
+@imports('subprocess')
 def yasm_version(yasm):
-    import subprocess
     try:
         version = Version(subprocess.check_output(
             [yasm, '--version']
         ).splitlines()[0].split()[1])
         return version
     except subprocess.CalledProcessError as e:
-        error('Failed to get yasm version: %s' % e.message)
+        die('Failed to get yasm version: %s', e.message)
 
 # Until we move all the yasm consumers out of old-configure.
 # bug 1257904
 add_old_configure_assignment('_YASM_MAJOR_VERSION',
                              delayed_getattr(yasm_version, 'major'))
 add_old_configure_assignment('_YASM_MINOR_VERSION',
                              delayed_getattr(yasm_version, 'minor'))
 
@@ -53,8 +52,52 @@ set_config('YASM_ASFLAGS', yasm_asflags)
 @depends(yasm_asflags)
 def have_yasm(value):
     if value:
         return True
 
 set_config('HAVE_YASM', have_yasm)
 # Until the YASM variable is not necessary in old-configure.
 add_old_configure_assignment('YASM', have_yasm)
+
+
+# Compiler wrappers
+# ==============================================================
+js_option('--with-compiler-wrapper', env='COMPILER_WRAPPER', nargs=1,
+          help='Enable compiling with wrappers such as distcc and ccache')
+
+js_option('--with-ccache', env='CCACHE', nargs='?',
+          help='Enable compiling with ccache')
+
+@depends_if('--with-ccache')
+def ccache(value):
+    if len(value):
+        return value
+    # If --with-ccache was given without an explicit value, we default to
+    # 'ccache'.
+    return 'ccache'
+
+ccache = check_prog('CCACHE', progs=(), input=ccache)
+
+@depends_if(ccache)
+def using_ccache(ccache):
+    return True
+
+set_config('MOZ_USING_CCACHE', using_ccache)
+
+@depends('--with-compiler-wrapper', ccache)
+@imports(_from='mozbuild.shellutil', _import='split', _as='shell_split')
+def compiler_wrapper(wrapper, ccache):
+    if ccache:
+        if wrapper:
+            return tuple([ccache] + shell_split(wrapper[0]))
+        else:
+            return (ccache,)
+    elif wrapper:
+        return tuple(shell_split(wrapper[0]))
+
+add_old_configure_assignment('COMPILER_WRAPPER', compiler_wrapper)
+
+@depends_if(compiler_wrapper)
+def using_compiler_wrapper(compiler_wrapper):
+    return True
+
+set_config('MOZ_USING_COMPILER_WRAPPER', using_compiler_wrapper)
--- a/build/moz.configure/util.configure
+++ b/build/moz.configure/util.configure
@@ -1,84 +1,66 @@
 # -*- 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/.
 
-@template
-@advanced
-def warn(*args):
-    'Print a warning.'
-    import sys
-    print(*args, file=sys.stderr)
-    sys.stderr.flush()
-
-
-@template
-@advanced
-def error(*args):
+@imports('sys')
+def die(*args):
     'Print an error and terminate configure.'
-    import sys
-    print(*args, file=sys.stderr)
-    sys.stderr.flush()
+    log.error(*args)
     sys.exit(1)
 
 
-@template
-@advanced
+@imports(_from='mozbuild.configure', _import='ConfigureError')
 def configure_error(message):
     '''Raise a programming error and terminate configure.
     Primarily for use in moz.configure templates to sanity check
     their inputs from moz.configure usage.'''
-    from mozbuild.configure import ConfigureError
     raise ConfigureError(message)
 
 
-@template
-@advanced
+@imports('os')
 def is_absolute_or_relative(path):
-    import os
     if os.altsep and os.altsep in path:
         return True
     return os.sep in path
 
 
-@template
-@advanced
+@imports(_import='mozpack.path', _as='mozpath')
 def normsep(path):
-    import mozpack.path as mozpath
     return mozpath.normsep(path)
 
 
-@template
-@advanced
+# This unlocks the sandbox. Do not copy blindly.
+@imports(_import='__builtin__', _as='__builtins__')
 def find_program(file):
     if is_absolute_or_relative(file):
         return os.path.abspath(file) if os.path.isfile(file) else None
+    # We can't use @imports here because it imports at declaration time,
+    # and the declaration of find_program happens before we ensure the
+    # which module is available in sys.path somehow.
     from which import which, WhichError
     try:
         return normsep(which(file))
     except WhichError:
         return None
 
 
-@template
 def unique_list(l):
     result = []
     for i in l:
         if l not in result:
             result.append(i)
     return result
 
-@template
-@advanced
+@imports(_from='mozbuild.configure.util', _import='Version', _as='_Version')
 def Version(v):
     'A version number that can be compared usefully.'
-    from mozbuild.configure.util import Version as _Version
     return _Version(v)
 
 # Denotes a deprecated option. Combines option() and @depends:
 # @deprecated_option('--option')
 # def option(value):
 #     ...
 # @deprecated_option() takes the same arguments as option(), except `help`.
 # The function may handle the option like a typical @depends function would,
@@ -96,44 +78,39 @@ def deprecated_option(*args, **kwargs):
             if value.origin != 'default':
                 return func(value)
         return deprecated
 
     return decorator
 
 
 # from mozbuild.util import ReadOnlyNamespace as namespace
-@template
-@advanced
+@imports(_from='mozbuild.util', _import='ReadOnlyNamespace')
 def namespace(**kwargs):
-    from mozbuild.util import ReadOnlyNamespace
     return ReadOnlyNamespace(**kwargs)
 
 
 # Some @depends function return namespaces, and one could want to use one
 # specific attribute from such a namespace as a "value" given to functions
 # such as `set_config`. But those functions do not take immediate values.
 # The `delayed_getattr` function allows access to attributes from the result
 # of a @depends function in a non-immediate manner.
 #   @depends('--option')
 #   def option(value)
 #       return namespace(foo=value)
 #   set_config('FOO', delayed_getattr(option, 'foo')
 @template
 def delayed_getattr(func, key):
     @depends(func)
-    @advanced
+    @imports(_from='__builtin__', _import='getattr')
     def result(value):
-        try:
-            return getattr(value, key)
-        except AttributeError:
-            # The @depends function we're being passed may have returned
-            # None, or an object that simply doesn't have the wanted key.
-            # In that case, just return None.
-            return None
+        # The @depends function we're being passed may have returned
+        # None, or an object that simply doesn't have the wanted key.
+        # In that case, just return None.
+        return getattr(value, key, None)
     return result
 
 
 # Like @depends, but the decorated function is only called if one of the
 # arguments it would be called with has a positive value (bool(value) is True)
 @template
 def depends_if(*args):
     def decorator(func):
--- a/build/mozconfig.cache
+++ b/build/mozconfig.cache
@@ -1,16 +1,16 @@
 # 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/.
 
 # Setup for build cache
 
 # Avoid duplication if the file happens to be included twice.
-if test -z "$bucket"; then
+if test -z "$bucket" -a -z "$NO_CACHE"; then
 
 read branch platform master <<EOF
 $(python2.7 -c 'import json; p = json.loads(open("'"$topsrcdir"'/../buildprops.json").read())["properties"]; print p["branch"], p["platform"], p["master"]' 2> /dev/null)
 EOF
 
 bucket=
 if test -z "$SCCACHE_DISABLE" -a -z "$no_sccache" -a -z "$MOZ_PGO_IS_SET" -a -z "$MOZ_PGO"; then
     case "${branch}" in
--- a/caps/tests/mochitest/mochitest.ini
+++ b/caps/tests/mochitest/mochitest.ini
@@ -6,9 +6,9 @@ support-files =
 [test_app_principal_equality.html]
 [test_bug246699.html]
 [test_bug292789.html]
 skip-if = (buildapp == 'b2g' && toolkit != 'gonk') #Bug 931116, b2g desktop specific, initial triage
 [test_bug423375.html]
 [test_bug470804.html]
 [test_disallowInheritPrincipal.html]
 [test_extensionURL.html]
-skip-if = (os == 'android' || buildapp == 'b2g') # Bug 1185773 for android, nonsensical on b2g
+skip-if = (buildapp == 'b2g') # nonsensical on b2g
--- a/devtools/client/netmonitor/har/test/browser_net_har_copy_all_as_har.js
+++ b/devtools/client/netmonitor/har/test/browser_net_har_copy_all_as_har.js
@@ -31,17 +31,17 @@ add_task(function*() {
   is(har.log.creator.name, "Firefox", "The creator field must be set");
   is(har.log.browser.name, "Firefox", "The browser field must be set");
   is(har.log.pages.length, 1, "There must be one page");
   is(har.log.entries.length, 1, "There must be one request");
 
   let entry = har.log.entries[0];
   is(entry.request.method, "GET", "Check the method");
   is(entry.request.url, SIMPLE_URL, "Check the URL");
-  is(entry.request.headers.length, 8, "Check number of request headers");
+  is(entry.request.headers.length, 9, "Check number of request headers");
   is(entry.response.status, 200, "Check response status");
   is(entry.response.statusText, "OK", "Check response status text");
   is(entry.response.headers.length, 6, "Check number of response headers");
   is(entry.response.content.mimeType, // eslint-disable-line
     "text/html", "Check response content type"); // eslint-disable-line
   isnot(entry.response.content.text, undefined, // eslint-disable-line
     "Check response body");
   isnot(entry.timings, undefined, "Check timings");
--- a/devtools/client/netmonitor/test/browser.ini
+++ b/devtools/client/netmonitor/test/browser.ini
@@ -107,17 +107,19 @@ skip-if = e10s # Bug 1091612
 [browser_net_security-state.js]
 [browser_net_security-tab-deselect.js]
 [browser_net_security-tab-visibility.js]
 [browser_net_security-warnings.js]
 [browser_net_send-beacon.js]
 [browser_net_send-beacon-other-tab.js]
 [browser_net_simple-init.js]
 [browser_net_simple-request-data.js]
+skip-if = true # Bug 1258809
 [browser_net_simple-request-details.js]
+skip-if = true # Bug 1258809
 [browser_net_simple-request.js]
 [browser_net_sort-01.js]
 skip-if = (e10s && debug && os == 'mac') # Bug 1253037
 [browser_net_sort-02.js]
 [browser_net_sort-03.js]
 [browser_net_statistics-01.js]
 [browser_net_statistics-02.js]
 [browser_net_statistics-03.js]
--- a/devtools/client/netmonitor/test/browser_net_copy_headers.js
+++ b/devtools/client/netmonitor/test/browser_net_copy_headers.js
@@ -26,16 +26,17 @@ add_task(function*() {
   const EXPECTED_REQUEST_HEADERS = [
     requestItem.attachment.method + " " + SIMPLE_URL + " " + requestItem.attachment.httpVersion,
     "Host: example.com",
     "User-Agent: " + navigator.userAgent + "",
     "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
     "Accept-Language: " + navigator.languages.join(",") + ";q=0.5",
     "Accept-Encoding: gzip, deflate",
     "Connection: keep-alive",
+    "Upgrade-Insecure-Requests: 1",
     "Pragma: no-cache",
     "Cache-Control: no-cache"
   ].join("\n");
 
   RequestsMenu.copyRequestHeaders();
   clipboard = SpecialPowers.getClipboardData("text/unicode");
   // Sometimes, a "Cookie" header is left over from other tests. Remove it:
   clipboard = clipboard.replace(/Cookie: [^\n]+\n/, "");
--- a/docshell/base/nsDocShell.cpp
+++ b/docshell/base/nsDocShell.cpp
@@ -14103,23 +14103,24 @@ nsDocShell::SetOpener(nsITabParent* aOpe
 
 nsITabParent*
 nsDocShell::GetOpener()
 {
   nsCOMPtr<nsITabParent> opener(do_QueryReferent(mOpener));
   return opener;
 }
 
+// The caller owns |aAsyncCause| here.
 void
 nsDocShell::NotifyJSRunToCompletionStart(const char* aReason,
                                          const char16_t* aFunctionName,
                                          const char16_t* aFilename,
                                          const uint32_t aLineNumber,
                                          JS::Handle<JS::Value> aAsyncStack,
-                                         JS::Handle<JS::Value> aAsyncCause)
+                                         const char* aAsyncCause)
 {
   // If first start, mark interval start.
   if (mJSRunToCompletionDepth == 0) {
     RefPtr<TimelineConsumers> timelines = TimelineConsumers::Get();
     if (timelines && timelines->HasConsumer(this)) {
       timelines->AddMarkerForDocShell(this, Move(
         mozilla::MakeUnique<JavascriptTimelineMarker>(
           aReason, aFunctionName, aFilename, aLineNumber, MarkerTracingType::START,
--- a/docshell/base/nsIDocShell.idl
+++ b/docshell/base/nsIDocShell.idl
@@ -1054,17 +1054,17 @@ interface nsIDocShell : nsIDocShellTreeI
    * that execution has stopped.  This only occurs when the Timeline devtool
    * is collecting information.
    */
   [noscript,notxpcom,nostdcall] void notifyJSRunToCompletionStart(in string aReason,
                                                                   in wstring functionName,
                                                                   in wstring fileName,
                                                                   in unsigned long lineNumber,
                                                                   in jsval asyncStack,
-                                                                  in jsval asyncCause);
+                                                                  in string asyncCause);
   [noscript,notxpcom,nostdcall] void notifyJSRunToCompletionStop();
 
   /**
    * This attribute determines whether a document which is not about:blank has
    * already be loaded by this docShell.
    */
   [infallible] readonly attribute boolean hasLoadedNonBlankURI;
 
--- a/docshell/base/timeline/JavascriptTimelineMarker.h
+++ b/docshell/base/timeline/JavascriptTimelineMarker.h
@@ -12,53 +12,61 @@
 #include "mozilla/dom/RootedDictionary.h"
 #include "mozilla/dom/ToJSValue.h"
 
 namespace mozilla {
 
 class JavascriptTimelineMarker : public TimelineMarker
 {
 public:
+  // The caller owns |aAsyncCause| here, so we must copy it into a separate
+  // string for use later on.
   JavascriptTimelineMarker(const char* aReason,
                            const char16_t* aFunctionName,
                            const char16_t* aFileName,
                            uint32_t aLineNumber,
                            MarkerTracingType aTracingType,
                            JS::Handle<JS::Value> aAsyncStack,
-                           JS::Handle<JS::Value> aAsyncCause)
+                           const char* aAsyncCause)
     : TimelineMarker("Javascript", aTracingType, MarkerStackRequest::NO_STACK)
     , mCause(NS_ConvertUTF8toUTF16(aReason))
     , mFunctionName(aFunctionName)
     , mFileName(aFileName)
     , mLineNumber(aLineNumber)
+    , mAsyncCause(aAsyncCause)
   {
     JSContext* ctx = nsContentUtils::GetCurrentJSContext();
     if (ctx) {
       mAsyncStack.init(ctx, aAsyncStack);
-      mAsyncCause.init(ctx, aAsyncCause);
     }
   }
 
   virtual void AddDetails(JSContext* aCx, dom::ProfileTimelineMarker& aMarker) override
   {
     TimelineMarker::AddDetails(aCx, aMarker);
 
     aMarker.mCauseName.Construct(mCause);
 
     if (!mFunctionName.IsEmpty() || !mFileName.IsEmpty()) {
       dom::RootedDictionary<dom::ProfileTimelineStackFrame> stackFrame(aCx);
       stackFrame.mLine.Construct(mLineNumber);
       stackFrame.mSource.Construct(mFileName);
       stackFrame.mFunctionDisplayName.Construct(mFunctionName);
 
       if (mAsyncStack.isObject() && !mAsyncStack.isNullOrUndefined() &&
-          mAsyncCause.isString()) {
+          !mAsyncCause.IsEmpty()) {
         JS::Rooted<JSObject*> asyncStack(aCx, mAsyncStack.toObjectOrNull());
-        JS::Rooted<JSString*> asyncCause(aCx, mAsyncCause.toString());
         JS::Rooted<JSObject*> parentFrame(aCx);
+        JS::Rooted<JSString*> asyncCause(aCx, JS_NewUCStringCopyN(aCx, mAsyncCause.BeginReading(),
+                                                                  mAsyncCause.Length()));
+        if (!asyncCause) {
+          JS_ClearPendingException(aCx);
+          return;
+        }
+
         if (!JS::CopyAsyncStack(aCx, asyncStack, asyncCause, &parentFrame, 0)) {
           JS_ClearPendingException(aCx);
         } else {
           stackFrame.mAsyncParent = parentFrame;
         }
       }
 
       JS::Rooted<JS::Value> newStack(aCx);
@@ -73,14 +81,14 @@ public:
   }
 
 private:
   nsString mCause;
   nsString mFunctionName;
   nsString mFileName;
   uint32_t mLineNumber;
   JS::PersistentRooted<JS::Value> mAsyncStack;
-  JS::PersistentRooted<JS::Value> mAsyncCause;
+  NS_ConvertUTF8toUTF16 mAsyncCause;
 };
 
 } // namespace mozilla
 
 #endif // mozilla_JavascriptTimelineMarker_h_
--- a/dom/animation/AnimValuesStyleRule.h
+++ b/dom/animation/AnimValuesStyleRule.h
@@ -54,23 +54,16 @@ public:
     return &p->mValue;
   }
 
   struct PropertyValuePair {
     nsCSSProperty mProperty;
     StyleAnimationValue mValue;
   };
 
-  void AddPropertiesToSet(nsCSSPropertySet& aSet) const
-  {
-    for (const PropertyValuePair& cv : mPropertyValuePairs) {
-      aSet.AddProperty(cv.mProperty);
-    }
-  }
-
 private:
   ~AnimValuesStyleRule() {}
 
   InfallibleTArray<PropertyValuePair> mPropertyValuePairs;
   uint32_t mStyleBits;
 };
 
 } // namespace mozilla
--- a/dom/animation/test/chrome.ini
+++ b/dom/animation/test/chrome.ini
@@ -2,14 +2,14 @@
 support-files =
   testcommon.js
   ../../imptests/testharness.js
   ../../imptests/testharnessreport.js
 [chrome/test_animate_xrays.html]
 # file_animate_xrays.html needs to go in mochitest.ini since it is served
 # over HTTP
 [chrome/test_animation_observers.html]
+[chrome/test_animation_performance_warning.html]
 [chrome/test_animation_properties.html]
-[chrome/test_animation_property_state.html]
 [chrome/test_generated_content_getAnimations.html]
 [chrome/test_restyles.html]
 [chrome/test_running_on_compositor.html]
 skip-if = buildapp == 'b2g'
rename from dom/animation/test/chrome/test_animation_property_state.html
rename to dom/animation/test/chrome/test_animation_performance_warning.html
--- a/dom/animation/test/chrome/test_animation_property_state.html
+++ b/dom/animation/test/chrome/test_animation_performance_warning.html
@@ -212,21 +212,21 @@ var gAnimationsTests = [
     ]
   },
 ];
 
 gAnimationsTests.forEach(function(subtest) {
   promise_test(function(t) {
     var div = addDiv(t, { class: 'compositable' });
     var animation = div.animate(subtest.frames, 100000);
-    return animation.ready.then(t.step_func(function() {
+    return animation.ready.then(function() {
       assert_animation_property_state_equals(
         animation.effect.getProperties(),
         subtest.expected);
-    }));
+    });
   }, subtest.desc);
 });
 
 var gPerformanceWarningTests = [
   {
     desc: 'preserve-3d transform',
     frames: {
       transform: ['translate(0px)', 'translate(100px)']
@@ -498,190 +498,180 @@ function start() {
     .getService(SpecialPowers.Ci.nsIStringBundleService);
   gStringBundle = bundleService
     .createBundle("chrome://global/locale/layout_errors.properties");
 
   gAnimationsTests.forEach(function(subtest) {
     promise_test(function(t) {
       var div = addDiv(t, { class: 'compositable' });
       var animation = div.animate(subtest.frames, 100000);
-      return animation.ready.then(t.step_func(function() {
+      return animation.ready.then(function() {
         assert_animation_property_state_equals(
           animation.effect.getProperties(),
           subtest.expected);
-      }));
+      });
     }, subtest.desc);
   });
 
   gPerformanceWarningTests.forEach(function(subtest) {
-    // FIXME: Bug 1255710: 'preserve-3d' frame breaks other tests,
-    // we should skip all 'preserve-3d' tests here.
-    if (subtest.desc.includes('preserve-3d')) {
-      return;
-    }
     promise_test(function(t) {
       var div = addDiv(t, { class: 'compositable' });
       var animation = div.animate(subtest.frames, 100000);
-      return animation.ready.then(t.step_func(function() {
+      return animation.ready.then(function() {
         assert_property_state_on_compositor(
           animation.effect.getProperties(),
           subtest.expected);
         div.style = subtest.style;
         return waitForFrame();
-      })).then(t.step_func(function() {
+      }).then(function() {
         assert_animation_property_state_equals(
           animation.effect.getProperties(),
           subtest.expected);
         div.style = '';
         return waitForFrame();
-      })).then(t.step_func(function() {
+      }).then(function() {
         assert_property_state_on_compositor(
           animation.effect.getProperties(),
           subtest.expected);
-      }));
+      });
     }, subtest.desc);
   });
 
   gMultipleAsyncAnimationsTests.forEach(function(subtest) {
-    // FIXME: Bug 1255710: 'preserve-3d' frame breaks other tests,
-    // we should skip all 'preserve-3d' tests here.
-    if (subtest.desc.includes('preserve-3d')) {
-      return;
-    }
     promise_test(function(t) {
       var div = addDiv(t, { class: 'compositable' });
       var animations = subtest.animations.map(function(anim) {
         var animation = div.animate(anim.frames, 100000);
 
         // Bind expected values to animation object.
         animation.expected = anim.expected;
         return animation;
       });
-      return waitForAllAnimations(animations).then(t.step_func(function() {
-        animations.forEach(t.step_func(function(anim) {
+      return waitForAllAnimations(animations).then(function() {
+        animations.forEach(function(anim) {
           assert_property_state_on_compositor(
             anim.effect.getProperties(),
             anim.expected);
-        }));
+        });
         div.style = subtest.style;
         return waitForFrame();
-      })).then(t.step_func(function() {
-        animations.forEach(t.step_func(function(anim) {
+      }).then(function() {
+        animations.forEach(function(anim) {
           assert_animation_property_state_equals(
             anim.effect.getProperties(),
             anim.expected);
-        }));
+        });
         div.style = '';
         return waitForFrame();
-      })).then(t.step_func(function() {
-        animations.forEach(t.step_func(function(anim) {
+      }).then(function() {
+        animations.forEach(function(anim) {
           assert_property_state_on_compositor(
             anim.effect.getProperties(),
             anim.expected);
-        }));
-      }));
+        });
+      });
     }, 'Multiple animations: ' + subtest.desc);
   });
 
   gMultipleAsyncAnimationsWithGeometricKeyframeTests.forEach(function(subtest) {
     promise_test(function(t) {
       var div = addDiv(t, { class: 'compositable' });
       var animations = subtest.animations.map(function(anim) {
         var animation = div.animate(anim.frames, 100000);
 
         // Bind expected values to animation object.
         animation.expected = anim.expected;
         return animation;
       });
-      return waitForAllAnimations(animations).then(t.step_func(function() {
-        animations.forEach(t.step_func(function(anim) {
+      return waitForAllAnimations(animations).then(function() {
+        animations.forEach(function(anim) {
           assert_animation_property_state_equals(
             anim.effect.getProperties(),
             anim.expected);
-        }));
-      }));
+        });
+      });
     }, 'Multiple animations with geometric property: ' + subtest.desc);
   });
 
   gMultipleAsyncAnimationsWithGeometricAnimationTests.forEach(function(subtest) {
     promise_test(function(t) {
       var div = addDiv(t, { class: 'compositable' });
       var animations = subtest.animations.map(function(anim) {
         var animation = div.animate(anim.frames, 100000);
 
         // Bind expected values to animation object.
         animation.expected = anim.expected;
         return animation;
       });
 
       var widthAnimation;
 
-      return waitForAllAnimations(animations).then(t.step_func(function() {
-        animations.forEach(t.step_func(function(anim) {
+      return waitForAllAnimations(animations).then(function() {
+        animations.forEach(function(anim) {
           assert_property_state_on_compositor(
             anim.effect.getProperties(),
             anim.expected);
-        }));
-      })).then(t.step_func(function() {
+        });
+      }).then(function() {
         // Append 'width' animation on the same element.
         widthAnimation = div.animate({ width: ['100px', '200px'] }, 100000);
         return waitForFrame();
-      })).then(t.step_func(function() {
+      }).then(function() {
         // Now transform animations are not running on compositor because of
         // the 'width' animation.
-        animations.forEach(t.step_func(function(anim) {
+        animations.forEach(function(anim) {
           assert_animation_property_state_equals(
             anim.effect.getProperties(),
             anim.expected);
-        }));
+        });
         // Remove the 'width' animation.
         widthAnimation.cancel();
         return waitForFrame();
-      })).then(t.step_func(function() {
+      }).then(function() {
         // Now all animations are running on compositor.
-        animations.forEach(t.step_func(function(anim) {
+        animations.forEach(function(anim) {
           assert_property_state_on_compositor(
             anim.effect.getProperties(),
             anim.expected);
-        }));
-      }));
+        });
+      });
     }, 'Multiple async animations and geometric animation: ' + subtest.desc);
   });
 
   promise_test(function(t) {
     var div = addDiv(t, { class: 'compositable' });
     var animation = div.animate(
       { transform: ['translate(0px)', 'translate(100px)'] }, 100000);
-    return animation.ready.then(t.step_func(function() {
+    return animation.ready.then(function() {
       assert_animation_property_state_equals(
         animation.effect.getProperties(),
         [ { property: 'transform', runningOnCompositor: true } ]);
       div.style = 'width: 10000px; height: 10000px';
       return waitForFrame();
-    })).then(t.step_func(function() {
+    }).then(function() {
       // viewport depends on test environment.
       var expectedWarning = new RegExp(
         "Async animation disabled because frame size \\(10000, 10000\\) is " +
         "bigger than the viewport \\(\\d+, \\d+\\) or the visual rectangle " +
         "\\(10000, 10000\\) is larger than the max allowed value \\(\\d+\\)");
       assert_animation_property_state_equals(
         animation.effect.getProperties(),
         [ {
           property: 'transform',
           runningOnCompositor: false,
           warning: expectedWarning
         } ]);
       div.style = 'width: 100px; height: 100px';
       return waitForFrame();
-    })).then(t.step_func(function() {
+    }).then(function() {
       // FIXME: Bug 1253164: the animation should get back on compositor.
       assert_animation_property_state_equals(
         animation.effect.getProperties(),
         [ { property: 'transform', runningOnCompositor: false } ]);
-    }));
+    });
   }, 'transform on too big element');
 
   promise_test(function(t) {
     var svg  = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
     svg.setAttribute('width', '100');
     svg.setAttribute('height', '100');
     var rect = document.createElementNS('http://www.w3.org/2000/svg', 'rect');
     rect.setAttribute('width', '100');
@@ -690,37 +680,37 @@ function start() {
     svg.appendChild(rect);
     document.body.appendChild(svg);
     t.add_cleanup(function() {
       svg.remove();
     });
 
     var animation = svg.animate(
       { transform: ['translate(0px)', 'translate(100px)'] }, 100000);
-    return animation.ready.then(t.step_func(function() {
+    return animation.ready.then(function() {
       assert_animation_property_state_equals(
         animation.effect.getProperties(),
         [ { property: 'transform', runningOnCompositor: true } ]);
       svg.setAttribute('transform', 'translate(10, 20)');
       return waitForFrame();
-    })).then(t.step_func(function() {
+    }).then(function() {
       assert_animation_property_state_equals(
         animation.effect.getProperties(),
         [ {
           property: 'transform',
           runningOnCompositor: false,
           warning: 'AnimationWarningTransformSVG'
         } ]);
       svg.removeAttribute('transform');
       return waitForFrame();
-    })).then(t.step_func(function() {
+    }).then(function() {
       assert_animation_property_state_equals(
         animation.effect.getProperties(),
         [ { property: 'transform', runningOnCompositor: true } ]);
-    }));
+    });
   }, 'transform of nsIFrame with SVG transform');
 
   promise_test(function(t) {
     var div = addDiv(t, { class: 'compositable',
                           style: 'animation: fade 100s' });
     var cssAnimation = div.getAnimations()[0];
     var scriptAnimation = div.animate({ opacity: [ 1, 0 ] }, 100000);
     return scriptAnimation.ready.then(function() {
--- a/dom/base/Console.cpp
+++ b/dom/base/Console.cpp
@@ -2317,24 +2317,24 @@ Console::RetrieveConsoleEvents(JSContext
       return;
     }
 
     aEvents.AppendElement(value);
   }
 }
 
 void
-Console::SetConsoleEventHandler(AnyCallback& aHandler)
+Console::SetConsoleEventHandler(AnyCallback* aHandler)
 {
   AssertIsOnOwningThread();
 
   // We don't want to expose this functionality to main-thread yet.
   MOZ_ASSERT(!NS_IsMainThread());
 
-  mConsoleEventNotifier = &aHandler;
+  mConsoleEventNotifier = aHandler;
 }
 
 void
 Console::AssertIsOnOwningThread() const
 {
   MOZ_ASSERT(mOwningThread);
   MOZ_ASSERT(PR_GetCurrentThread() == mOwningThread);
 }
--- a/dom/base/Console.h
+++ b/dom/base/Console.h
@@ -121,17 +121,17 @@ public:
   void
   ClearStorage();
 
   void
   RetrieveConsoleEvents(JSContext* aCx, nsTArray<JS::Value>& aEvents,
                         ErrorResult& aRv);
 
   void
-  SetConsoleEventHandler(AnyCallback& aHandler);
+  SetConsoleEventHandler(AnyCallback* aHandler);
 
 private:
   explicit Console(nsPIDOMWindowInner* aWindow);
 
   void
   Initialize(ErrorResult& aRv);
 
   void
--- a/dom/base/ScriptSettings.cpp
+++ b/dom/base/ScriptSettings.cpp
@@ -678,17 +678,17 @@ AutoEntryScript::DocshellEntryMonitor::D
   : JS::dbg::AutoEntryMonitor(aCx)
   , mReason(aReason)
 {
 }
 
 void
 AutoEntryScript::DocshellEntryMonitor::Entry(JSContext* aCx, JSFunction* aFunction,
                                              JSScript* aScript, JS::Handle<JS::Value> aAsyncStack,
-                                             JS::Handle<JSString*> aAsyncCause)
+                                             const char* aAsyncCause)
 {
   JS::Rooted<JSFunction*> rootedFunction(aCx);
   if (aFunction) {
     rootedFunction = aFunction;
   }
   JS::Rooted<JSScript*> rootedScript(aCx);
   if (aScript) {
     rootedScript = aScript;
@@ -723,23 +723,21 @@ AutoEntryScript::DocshellEntryMonitor::E
     filename = NS_ConvertUTF8toUTF16(JS_GetScriptFilename(rootedScript));
     lineNumber = JS_GetScriptBaseLineNumber(aCx, rootedScript);
   }
 
   if (!filename.IsEmpty() || functionName.isTwoByte()) {
     const char16_t* functionNameChars = functionName.isTwoByte() ?
       functionName.twoByteChars() : nullptr;
 
-    JS::Rooted<JS::Value> asyncCauseValue(aCx, aAsyncCause ? StringValue(aAsyncCause) :
-                                          JS::NullValue());
     docShellForJSRunToCompletion->NotifyJSRunToCompletionStart(mReason,
                                                                functionNameChars,
                                                                filename.BeginReading(),
                                                                lineNumber, aAsyncStack,
-                                                               asyncCauseValue);
+                                                               aAsyncCause);
   }
 }
 
 void
 AutoEntryScript::DocshellEntryMonitor::Exit(JSContext* aCx)
 {
   nsCOMPtr<nsPIDOMWindowInner> window =
     do_QueryInterface(xpc::NativeGlobal(JS::CurrentGlobalOrNull(aCx)));
--- a/dom/base/ScriptSettings.h
+++ b/dom/base/ScriptSettings.h
@@ -351,36 +351,41 @@ public:
 
 private:
   // A subclass of AutoEntryMonitor that notifies the docshell.
   class DocshellEntryMonitor : public JS::dbg::AutoEntryMonitor
   {
   public:
     DocshellEntryMonitor(JSContext* aCx, const char* aReason);
 
+    // Please note that |aAsyncCause| here is owned by the caller, and its
+    // lifetime must outlive the lifetime of the DocshellEntryMonitor object.
+    // In practice, |aAsyncCause| is identical to |aReason| passed into
+    // the AutoEntryScript constructor, so the lifetime requirements are
+    // trivially satisfied by |aReason| being a statically allocated string.
     void Entry(JSContext* aCx, JSFunction* aFunction,
                JS::Handle<JS::Value> aAsyncStack,
-               JS::Handle<JSString*> aAsyncCause) override
+               const char* aAsyncCause) override
     {
       Entry(aCx, aFunction, nullptr, aAsyncStack, aAsyncCause);
     }
 
     void Entry(JSContext* aCx, JSScript* aScript,
                JS::Handle<JS::Value> aAsyncStack,
-               JS::Handle<JSString*> aAsyncCause) override
+               const char* aAsyncCause) override
     {
       Entry(aCx, nullptr, aScript, aAsyncStack, aAsyncCause);
     }
 
     void Exit(JSContext* aCx) override;
 
   private:
     void Entry(JSContext* aCx, JSFunction* aFunction, JSScript* aScript,
                JS::Handle<JS::Value> aAsyncStack,
-               JS::Handle<JSString*> aAsyncCause);
+               const char* aAsyncCause);
 
     const char* mReason;
   };
 
   // It's safe to make this a weak pointer, since it's the subject principal
   // when we go on the stack, so can't go away until after we're gone.  In
   // particular, this is only used from the CallSetup constructor, and only in
   // the aIsJSImplementedWebIDL case.  And in that case, the subject principal
--- a/dom/base/TextInputProcessor.cpp
+++ b/dom/base/TextInputProcessor.cpp
@@ -761,16 +761,18 @@ TextInputProcessor::PrepareKeyboardEvent
     // If KeyboardEvent.keyCode is 0, it may be uninitialized.  If so, we may
     // be able to decide a good .keyCode value if the .key value is a
     // non-printable key.
     aKeyboardEvent.keyCode =
       WidgetKeyboardEvent::ComputeKeyCodeFromKeyNameIndex(
         aKeyboardEvent.mKeyNameIndex);
   }
 
+  aKeyboardEvent.mIsSynthesizedByTIP = (mForTests)? false : true;
+
   return NS_OK;
 }
 
 NS_IMETHODIMP
 TextInputProcessor::Keydown(nsIDOMKeyEvent* aDOMKeyEvent,
                             uint32_t aKeyFlags,
                             uint8_t aOptionalArgc,
                             uint32_t* aConsumedFlags)
--- a/dom/base/WebSocket.cpp
+++ b/dom/base/WebSocket.cpp
@@ -2626,18 +2626,16 @@ WebSocketImpl::CancelInternal()
     return NS_OK;
   }
 
   int64_t readyState = mWebSocket->ReadyState();
   if (readyState == WebSocket::CLOSING || readyState == WebSocket::CLOSED) {
     return NS_OK;
   }
 
-  ConsoleError();
-
   return CloseConnection(nsIWebSocketChannel::CLOSE_GOING_AWAY);
 }
 
 NS_IMETHODIMP
 WebSocketImpl::Suspend()
 {
   AssertIsOnMainThread();
   return NS_ERROR_NOT_IMPLEMENTED;
--- a/dom/base/nsContentUtils.cpp
+++ b/dom/base/nsContentUtils.cpp
@@ -3472,41 +3472,43 @@ nsContentUtils::ReportToConsole(uint32_t
 /* static */ nsresult
 nsContentUtils::ReportToConsoleNonLocalized(const nsAString& aErrorText,
                                             uint32_t aErrorFlags,
                                             const nsACString& aCategory,
                                             const nsIDocument* aDocument,
                                             nsIURI* aURI,
                                             const nsAFlatString& aSourceLine,
                                             uint32_t aLineNumber,
-                                            uint32_t aColumnNumber)
+                                            uint32_t aColumnNumber,
+                                            MissingErrorLocationMode aLocationMode)
 {
   uint64_t innerWindowID = 0;
   if (aDocument) {
     if (!aURI) {
       aURI = aDocument->GetDocumentURI();
     }
     innerWindowID = aDocument->InnerWindowID();
   }
 
   nsresult rv;
   if (!sConsoleService) { // only need to bother null-checking here
     rv = CallGetService(NS_CONSOLESERVICE_CONTRACTID, &sConsoleService);
     NS_ENSURE_SUCCESS(rv, rv);
   }
 
   nsAutoCString spec;
-  if (!aLineNumber) {
+  if (!aLineNumber && aLocationMode == eUSE_CALLING_LOCATION) {
     JSContext *cx = GetCurrentJSContext();
     if (cx) {
       nsJSUtils::GetCallingLocation(cx, spec, &aLineNumber, &aColumnNumber);
     }
   }
-  if (spec.IsEmpty() && aURI)
+  if (spec.IsEmpty() && aURI) {
     aURI->GetSpec(spec);
+  }
 
   nsCOMPtr<nsIScriptError> errorObject =
       do_CreateInstance(NS_SCRIPTERROR_CONTRACTID, &rv);
   NS_ENSURE_SUCCESS(rv, rv);
 
   rv = errorObject->InitWithWindowID(aErrorText,
                                      NS_ConvertUTF8toUTF16(spec), // file name
                                      aSourceLine,
--- a/dom/base/nsContentUtils.h
+++ b/dom/base/nsContentUtils.h
@@ -820,26 +820,36 @@ public:
    *   @param [aURI=nullptr] (Optional) URI of resource containing error.
    *   @param [aSourceLine=EmptyString()] (Optional) The text of the line that
               contains the error (may be empty).
    *   @param [aLineNumber=0] (Optional) Line number within resource
               containing error.
    *   @param [aColumnNumber=0] (Optional) Column number within resource
               containing error.
               If aURI is null, then aDocument->GetDocumentURI() is used.
+   *   @param [aLocationMode] (Optional) Specifies the behavior if
+              error location information is omitted.
    */
+  enum MissingErrorLocationMode {
+    // Don't show location information in the error console.
+    eOMIT_LOCATION,
+    // Get location information from the currently executing script.
+    eUSE_CALLING_LOCATION
+  };
   static nsresult ReportToConsoleNonLocalized(const nsAString& aErrorText,
                                               uint32_t aErrorFlags,
                                               const nsACString& aCategory,
                                               const nsIDocument* aDocument,
                                               nsIURI* aURI = nullptr,
                                               const nsAFlatString& aSourceLine
                                                 = EmptyString(),
                                               uint32_t aLineNumber = 0,
-                                              uint32_t aColumnNumber = 0);
+                                              uint32_t aColumnNumber = 0,
+                                              MissingErrorLocationMode aLocationMode
+                                                = eUSE_CALLING_LOCATION);
 
   /**
    * Report a localized error message to the error console.
    *   @param aErrorFlags See nsIScriptError.
    *   @param aCategory Name of module reporting error.
    *   @param aDocument Reference to the document which triggered the message.
    *   @param aFile Properties file containing localized message.
    *   @param aMessageName Name of localized message.
--- a/dom/base/nsObjectLoadingContent.cpp
+++ b/dom/base/nsObjectLoadingContent.cpp
@@ -1148,16 +1148,30 @@ nsObjectLoadingContent::OnStopRequest(ns
   // We make a note of this object node by including it in a dedicated
   // array of blocked tracking nodes under its parent document.
   if (aStatusCode == NS_ERROR_TRACKING_URI) {
     nsCOMPtr<nsIContent> thisNode =
       do_QueryInterface(static_cast<nsIObjectLoadingContent*>(this));
     if (thisNode && thisNode->IsInComposedDoc()) {
       thisNode->GetComposedDoc()->AddBlockedTrackingNode(thisNode);
     }
+  } else if (aStatusCode == NS_ERROR_BLOCKED_URI) {
+    // Logging is temporarily disabled until after experiment phase.
+    //
+    // nsAutoCString uri;
+    // mURI->GetSpec(uri);
+    // nsCOMPtr<nsIConsoleService> console(
+    //   do_GetService("@mozilla.org/consoleservice;1"));
+    // if (console) {
+    //   nsString message = NS_LITERAL_STRING("Blocking ") +
+    //     NS_ConvertASCIItoUTF16(uri) +
+    //     NS_LITERAL_STRING(" since it was found on an internal Firefox blocklist.");
+    //   console->LogStringMessage(message.get());
+    // }
+    Telemetry::Accumulate(Telemetry::PLUGIN_BLOCKED_FOR_STABILITY, 1);
   }
 
   NS_ENSURE_TRUE(nsContentUtils::LegacyIsCallerChromeOrNativeCode(), NS_ERROR_NOT_AVAILABLE);
 
   if (aRequest != mChannel) {
     return NS_BINDING_ABORTED;
   }
 
--- a/dom/base/test/test_websocket5.html
+++ b/dom/base/test/test_websocket5.html
@@ -14,16 +14,18 @@
 var tests = [
   test41, // HSTS
   test42, // non-char utf-8 sequences
   test43, // Test setting binaryType attribute
   test44, // Test sending/receving binary ArrayBuffer
   test45, // Test sending/receving binary Blob
   test46, // Test that we don't dispatch incoming msgs once in CLOSING state
   test47, // Make sure onerror/onclose aren't called during close()
+  test48, // see bug 1227136 - client calls close() from onopen() and waits
+          // until WebSocketChannel::mSocketIn is nulled out on socket thread
 ];
 
 function testWebSocket() {
   doTest();
 }
 
 SimpleTest.requestFlakyTimeout("The web socket tests are really fragile, but avoiding timeouts might be hard, since it's testing stuff on the network. " +
                                "Expect all sorts of flakiness in this test...");
--- a/dom/base/test/websocket_tests.js
+++ b/dom/base/test/websocket_tests.js
@@ -1208,8 +1208,37 @@ function test47() {
     // Make sure we call onerror/onclose asynchronously
     ws._withinClose = 1;
     ws.close(3333, "Closed before we were open: error");
     ws._withinClose = 0;
     is(ws.readyState, 2, "test-47: readyState should be CLOSING(2) after close(): got "
        + ws.readyState);
   });
 }
+
+// test48: see bug 1227136 - client calls close() from onopen() and waits until
+// WebSocketChannel::mSocketIn is nulled out on socket thread.
+function test48() {
+  return new Promise(function(resolve, reject) {
+    const pref_close = "network.websocket.timeout.close";
+    SpecialPowers.setIntPref(pref_close, 1);
+
+    var ws = CreateTestWS("ws://mochi.test:8888/tests/dom/base/test/file_websocket", "test-48");
+
+    ws.onopen = function() {
+      ws.close();
+
+      var date = new Date();
+      var curDate = null;
+      do {
+        curDate = new Date();
+      } while(curDate-date < 1500);
+
+    }
+
+    ws.onclose = function(e) {
+      ok(true, "ws close in test 48");
+      resolve();
+    }
+
+    SpecialPowers.clearUserPref(pref_close);
+  });
+}
--- a/dom/bindings/BindingUtils.cpp
+++ b/dom/bindings/BindingUtils.cpp
@@ -761,25 +761,48 @@ CreateInterfaceObject(JSContext* cx, JS:
   return constructor;
 }
 
 static JSObject*
 CreateInterfacePrototypeObject(JSContext* cx, JS::Handle<JSObject*> global,
                                JS::Handle<JSObject*> parentProto,
                                const js::Class* protoClass,
                                const NativeProperties* properties,
-                               const NativeProperties* chromeOnlyProperties)
+                               const NativeProperties* chromeOnlyProperties,
+                               const char* const* unscopableNames)
 {
   JS::Rooted<JSObject*> ourProto(cx,
     JS_NewObjectWithUniqueType(cx, Jsvalify(protoClass), parentProto));
   if (!ourProto ||
       !DefineProperties(cx, ourProto, properties, chromeOnlyProperties)) {
     return nullptr;
   }
 
+  if (unscopableNames) {
+    JS::Rooted<JSObject*> unscopableObj(cx, JS_NewPlainObject(cx));
+    if (!unscopableObj) {
+      return nullptr;
+    }
+
+    for (; *unscopableNames; ++unscopableNames) {
+      if (!JS_DefineProperty(cx, unscopableObj, *unscopableNames,
+                             JS::TrueHandleValue, JSPROP_ENUMERATE)) {
+        return nullptr;
+      }
+    }
+
+    JS::Rooted<jsid> unscopableId(cx,
+      SYMBOL_TO_JSID(JS::GetWellKnownSymbol(cx, JS::SymbolCode::unscopables)));
+    // Readonly and non-enumerable to match Array.prototype.
+    if (!JS_DefinePropertyById(cx, ourProto, unscopableId, unscopableObj,
+                               JSPROP_READONLY)) {
+      return nullptr;
+    }
+  }
+
   return ourProto;
 }
 
 bool
 DefineProperties(JSContext* cx, JS::Handle<JSObject*> obj,
                  const NativeProperties* properties,
                  const NativeProperties* chromeOnlyProperties)
 {
@@ -825,17 +848,18 @@ CreateInterfaceObjects(JSContext* cx, JS
                        JS::Handle<JSObject*> protoProto,
                        const js::Class* protoClass, JS::Heap<JSObject*>* protoCache,
                        JS::Handle<JSObject*> constructorProto,
                        const js::Class* constructorClass, const JSNativeHolder* constructor,
                        unsigned ctorNargs, const NamedConstructor* namedConstructors,
                        JS::Heap<JSObject*>* constructorCache,
                        const NativeProperties* properties,
                        const NativeProperties* chromeOnlyProperties,
-                       const char* name, bool defineOnGlobal)
+                       const char* name, bool defineOnGlobal,
+                       const char* const* unscopableNames)
 {
   MOZ_ASSERT(protoClass || constructorClass || constructor,
              "Need at least one class or a constructor!");
   MOZ_ASSERT(!((properties &&
                 (properties->methods || properties->attributes)) ||
                (chromeOnlyProperties &&
                 (chromeOnlyProperties->methods ||
                  chromeOnlyProperties->attributes))) || protoClass,
@@ -856,17 +880,18 @@ CreateInterfaceObjects(JSContext* cx, JS
   MOZ_ASSERT(!(constructorClass || constructor) == !constructorCache,
              "If, and only if, there is an interface object we need to cache "
              "it");
 
   JS::Rooted<JSObject*> proto(cx);
   if (protoClass) {
     proto =
       CreateInterfacePrototypeObject(cx, global, protoProto, protoClass,
-                                     properties, chromeOnlyProperties);
+                                     properties, chromeOnlyProperties,
+                                     unscopableNames);
     if (!proto) {
       return;
     }
 
     *protoCache = proto;
   }
   else {
     MOZ_ASSERT(!proto);
@@ -1767,16 +1792,31 @@ NativePropertyHooks sEmptyNativeProperty
     nullptr,
     nullptr
   },
   prototypes::id::_ID_Count,
   constructors::id::_ID_Count,
   nullptr
 };
 
+const js::ObjectOps sInterfaceObjectClassObjectOps = {
+  nullptr, /* lookupProperty */
+  nullptr, /* defineProperty */
+  nullptr, /* hasProperty */
+  nullptr, /* getProperty */
+  nullptr, /* setProperty */
+  nullptr, /* getOwnPropertyDescriptor */
+  nullptr, /* deleteProperty */
+  nullptr, /* watch */
+  nullptr, /* unwatch */
+  nullptr, /* getElements */
+  nullptr, /* enumerate */
+  InterfaceObjectToString, /* funToString */
+};
+
 bool
 GetPropertyOnPrototype(JSContext* cx, JS::Handle<JSObject*> proxy,
                        JS::Handle<JS::Value> receiver, JS::Handle<jsid> id,
                        bool* found, JS::MutableHandle<JS::Value> vp)
 {
   JS::Rooted<JSObject*> proto(cx);
   if (!js::GetObjectProto(cx, proxy, &proto)) {
     return false;
--- a/dom/bindings/BindingUtils.h
+++ b/dom/bindings/BindingUtils.h
@@ -589,33 +589,36 @@ struct NamedConstructor
  *                  interface doesn't have any ChromeOnly properties or if the
  *                  object is being created in non-chrome compartment.
  * defineOnGlobal controls whether properties should be defined on the given
  *                global for the interface object (if any) and named
  *                constructors (if any) for this interface.  This can be
  *                false in situations where we want the properties to only
  *                appear on privileged Xrays but not on the unprivileged
  *                underlying global.
+ * unscopableNames if not null it points to a null-terminated list of const
+ *                 char* names of the unscopable properties for this interface.
  *
  * At least one of protoClass, constructorClass or constructor should be
  * non-null. If constructorClass or constructor are non-null, the resulting
  * interface object will be defined on the given global with property name
  * |name|, which must also be non-null.
  */
 void
 CreateInterfaceObjects(JSContext* cx, JS::Handle<JSObject*> global,
                        JS::Handle<JSObject*> protoProto,
                        const js::Class* protoClass, JS::Heap<JSObject*>* protoCache,
                        JS::Handle<JSObject*> interfaceProto,
                        const js::Class* constructorClass, const JSNativeHolder* constructor,
                        unsigned ctorNargs, const NamedConstructor* namedConstructors,
                        JS::Heap<JSObject*>* constructorCache,
                        const NativeProperties* regularProperties,
                        const NativeProperties* chromeOnlyProperties,
-                       const char* name, bool defineOnGlobal);
+                       const char* name, bool defineOnGlobal,
+                       const char* const* unscopableNames);
 
 /**
  * Define the properties (regular and chrome-only) on obj.
  *
  * obj the object to instal the properties on. This should be the interface
  *     prototype object for regular interfaces and the instance object for
  *     interfaces marked with Global.
  * properties contains the methods, attributes and constants to be defined on
@@ -2500,16 +2503,18 @@ XrayGetNativeProto(JSContext* cx, JS::Ha
     }
   }
 
   return JS_WrapObject(cx, protop);
 }
 
 extern NativePropertyHooks sEmptyNativePropertyHooks;
 
+extern const js::ObjectOps sInterfaceObjectClassObjectOps;
+
 // We use one constructor JSNative to represent all DOM interface objects (so
 // we can easily detect when we need to wrap them in an Xray wrapper). We store
 // the real JSNative in the mNative member of a JSNativeHolder in the
 // CONSTRUCTOR_NATIVE_HOLDER_RESERVED_SLOT slot of the JSFunction object for a
 // specific interface object. We also store the NativeProperties in the
 // JSNativeHolder.
 // Note that some interface objects are not yet a JSFunction but a normal
 // JSObject with a DOMJSClass, those do not use these slots.
--- a/dom/bindings/CallbackObject.cpp
+++ b/dom/bindings/CallbackObject.cpp
@@ -167,22 +167,17 @@ CallbackObject::CallSetup::CallSetup(Cal
 
     if (!allowed) {
       return;
     }
   }
 
   mAsyncStack.emplace(cx, aCallback->GetCreationStack());
   if (*mAsyncStack) {
-    mAsyncCause.emplace(cx, JS_NewStringCopyZ(cx, aExecutionReason));
-    if (*mAsyncCause) {
-      mAsyncStackSetter.emplace(cx, *mAsyncStack, *mAsyncCause);
-    } else {
-      JS_ClearPendingException(cx);
-    }
+    mAsyncStackSetter.emplace(cx, *mAsyncStack, aExecutionReason);
   }
 
   // Enter the compartment of our callback, so we can actually work with it.
   //
   // Note that if the callback is a wrapper, this will not be the same
   // compartment that we ended up in with mAutoEntryScript above, because the
   // entry point is based off of the unwrapped callback (realCallback).
   mAc.emplace(cx, *mRootedCallable);
--- a/dom/bindings/CallbackObject.h
+++ b/dom/bindings/CallbackObject.h
@@ -252,17 +252,16 @@ protected:
     Maybe<AutoIncumbentScript> mAutoIncumbentScript;
 
     // Constructed the rooter within the scope of mCxPusher above, so that it's
     // always within a request during its lifetime.
     Maybe<JS::Rooted<JSObject*> > mRootedCallable;
 
     // Members which are used to set the async stack.
     Maybe<JS::Rooted<JSObject*>> mAsyncStack;
-    Maybe<JS::Rooted<JSString*>> mAsyncCause;
     Maybe<JS::AutoSetAsyncStackForNewCalls> mAsyncStackSetter;
 
     // Can't construct a JSAutoCompartment without a JSContext either.  Also,
     // Put mAc after mAutoEntryScript so that we exit the compartment before
     // we pop the JSContext. Though in practice we'll often manually order
     // those two things.
     Maybe<JSAutoCompartment> mAc;
 
--- a/dom/bindings/Codegen.py
+++ b/dom/bindings/Codegen.py
@@ -695,30 +695,17 @@ class CGInterfaceObjectJSClass(CGThing):
                 nullptr,               /* mayResolve */
                 nullptr,               /* finalize */
                 ${ctorname}, /* call */
                 ${hasInstance}, /* hasInstance */
                 ${ctorname}, /* construct */
                 nullptr,               /* trace */
                 JS_NULL_CLASS_SPEC,
                 JS_NULL_CLASS_EXT,
-                {
-                  nullptr, /* lookupProperty */
-                  nullptr, /* defineProperty */
-                  nullptr, /* hasProperty */
-                  nullptr, /* getProperty */
-                  nullptr, /* setProperty */
-                  nullptr, /* getOwnPropertyDescriptor */
-                  nullptr, /* deleteProperty */
-                  nullptr, /* watch */
-                  nullptr, /* unwatch */
-                  nullptr, /* getElements */
-                  nullptr, /* enumerate */
-                  InterfaceObjectToString, /* funToString */
-                }
+                &sInterfaceObjectClassObjectOps
               },
               eInterface,
               ${prototypeID},
               ${depth},
               ${hooks},
               "function ${name}() {\\n    [native code]\\n}",
               ${protoGetter}
             };
@@ -2740,23 +2727,24 @@ class CGJsonifyAttributesMethod(CGAbstra
 
 
 class CGCreateInterfaceObjectsMethod(CGAbstractMethod):
     """
     Generate the CreateInterfaceObjects method for an interface descriptor.
 
     properties should be a PropertyArrays instance.
     """
-    def __init__(self, descriptor, properties):
+    def __init__(self, descriptor, properties, haveUnscopables):
         args = [Argument('JSContext*', 'aCx'),
                 Argument('JS::Handle<JSObject*>', 'aGlobal'),
                 Argument('ProtoAndIfaceCache&', 'aProtoAndIfaceCache'),
                 Argument('bool', 'aDefineOnGlobal')]
         CGAbstractMethod.__init__(self, descriptor, 'CreateInterfaceObjects', 'void', args)
         self.properties = properties
+        self.haveUnscopables = haveUnscopables
 
     def definition_body(self):
         (protoGetter, protoHandleGetter) = InterfacePrototypeObjectProtoGetter(self.descriptor)
         if protoHandleGetter is None:
             parentProtoType = "Rooted"
             getParentProto = "aCx, " + protoGetter
         else:
             parentProtoType = "Handle"
@@ -2886,29 +2874,31 @@ class CGCreateInterfaceObjectsMethod(CGA
             JS::Heap<JSObject*>* protoCache = ${protoCache};
             JS::Heap<JSObject*>* interfaceCache = ${interfaceCache};
             dom::CreateInterfaceObjects(aCx, aGlobal, ${parentProto},
                                         ${protoClass}, protoCache,
                                         constructorProto, ${interfaceClass}, ${constructHookHolder}, ${constructArgs}, ${namedConstructors},
                                         interfaceCache,
                                         ${properties},
                                         ${chromeProperties},
-                                        ${name}, aDefineOnGlobal);
+                                        ${name}, aDefineOnGlobal,
+                                        ${unscopableNames});
             """,
             protoClass=protoClass,
             parentProto=parentProto,
             protoCache=protoCache,
             interfaceClass=interfaceClass,
             constructHookHolder=constructHookHolder,
             constructArgs=constructArgs,
             namedConstructors=namedConstructors,
             interfaceCache=interfaceCache,
             properties=properties,
             chromeProperties=chromeProperties,
-            name='"' + self.descriptor.interface.identifier.name + '"' if needInterfaceObject else "nullptr")
+            name='"' + self.descriptor.interface.identifier.name + '"' if needInterfaceObject else "nullptr",
+            unscopableNames="unscopableNames" if self.haveUnscopables else "nullptr")
 
         # If we fail after here, we must clear interface and prototype caches
         # using this code: intermediate failure must not expose the interface in
         # partially-constructed state.  Note that every case after here needs an
         # interface prototype object.
         failureCode = dedent(
             """
             *protoCache = nullptr;
@@ -11830,28 +11820,32 @@ class CGDescriptor(CGThing):
                                       toBindingNamespace(descriptor.parentPrototypeName)))
 
         # These are set to true if at least one non-static
         # method/getter/setter or jsonifier exist on the interface.
         (hasMethod, hasGetter, hasLenientGetter, hasSetter, hasLenientSetter,
             hasPromiseReturningMethod) = False, False, False, False, False, False
         jsonifierMethod = None
         crossOriginMethods, crossOriginGetters, crossOriginSetters = set(), set(), set()
+        unscopableNames = list()
         for n in descriptor.interface.namedConstructors:
             cgThings.append(CGClassConstructor(descriptor, n,
                                                NamedConstructorName(n)))
         for m in descriptor.interface.members:
             if m.isMethod() and m.identifier.name == 'queryInterface':
                 continue
             if not isMaybeExposedIn(m, descriptor):
                 continue
 
             props = memberProperties(m, descriptor)
 
             if m.isMethod():
+                if m.getExtendedAttribute("Unscopable"):
+                    assert not m.isStatic()
+                    unscopableNames.append(m.identifier.name)
                 if props.isJsonifier:
                     jsonifierMethod = m
                 elif not m.isIdentifierLess() or m == descriptor.operations['Stringifier']:
                     if m.isStatic():
                         assert descriptor.interface.hasInterfaceObject()
                         cgThings.append(CGStaticMethod(descriptor, m))
                         if m.returnsPromise():
                             cgThings.append(CGStaticMethodJitinfo(m))
@@ -11867,16 +11861,19 @@ class CGDescriptor(CGThing):
             # generate its convenience functions.
             elif m.isMaplikeOrSetlike():
                 cgThings.append(CGMaplikeOrSetlikeHelperGenerator(descriptor, m))
             elif m.isAttr():
                 if m.stringifier:
                     raise TypeError("Stringifier attributes not supported yet. "
                                     "See bug 824857.\n"
                                     "%s" % m.location)
+                if m.getExtendedAttribute("Unscopable"):
+                    assert not m.isStatic()
+                    unscopableNames.append(m.identifier.name)
                 if m.isStatic():
                     assert descriptor.interface.hasInterfaceObject()
                     cgThings.append(CGStaticGetter(descriptor, m))
                 elif descriptor.interface.hasInterfacePrototypeObject():
                     cgThings.append(CGSpecializedGetter(descriptor, m))
                     if props.isCrossOriginGetter:
                         crossOriginGetters.add(m.identifier.name)
                 if not m.readonly:
@@ -12054,19 +12051,30 @@ class CGDescriptor(CGThing):
                                                             properties))
 
         # If we're not wrappercached, we don't know how to clear our
         # cached values, since we can't get at the JSObject.
         if descriptor.wrapperCache:
             cgThings.extend(CGClearCachedValueMethod(descriptor, m) for
                             m in clearableCachedAttrs(descriptor))
 
+        haveUnscopables = (len(unscopableNames) != 0 and
+                           descriptor.interface.hasInterfacePrototypeObject())
+        if haveUnscopables:
+            cgThings.append(
+                CGList([CGGeneric("static const char* const unscopableNames[] = {"),
+                        CGIndenter(CGList([CGGeneric('"%s"' % name) for
+                                           name in unscopableNames] +
+                                          [CGGeneric("nullptr")], ",\n")),
+                        CGGeneric("};\n")], "\n"))
+
         # CGCreateInterfaceObjectsMethod needs to come after our
-        # CGDOMJSClass, if any.
-        cgThings.append(CGCreateInterfaceObjectsMethod(descriptor, properties))
+        # CGDOMJSClass and unscopables, if any.
+        cgThings.append(CGCreateInterfaceObjectsMethod(descriptor, properties,
+                                                       haveUnscopables))
 
         # CGGetProtoObjectMethod and CGGetConstructorObjectMethod need
         # to come after CGCreateInterfaceObjectsMethod.
         if descriptor.interface.hasInterfacePrototypeObject():
             cgThings.append(CGGetProtoObjectHandleMethod(descriptor))
             if descriptor.interface.hasChildInterfaces():
                 cgThings.append(CGGetProtoObjectMethod(descriptor))
         if descriptor.interface.hasInterfaceObject():
--- a/dom/bindings/parser/WebIDL.py
+++ b/dom/bindings/parser/WebIDL.py
@@ -4103,16 +4103,24 @@ class IDLAttribute(IDLInterfaceMember):
                                   "readonly attributes" % attr.value(),
                                   [attr.location, self.location])
             self._setDependsOn(attr.value())
         elif identifier == "UseCounter":
             if self.stringifier:
                 raise WebIDLError("[UseCounter] must not be used on a "
                                   "stringifier attribute",
                                   [attr.location, self.location])
+        elif identifier == "Unscopable":
+            if not attr.noArguments():
+                raise WebIDLError("[Unscopable] must take no arguments",
+                                  [attr.location])
+            if self.isStatic():
+                raise WebIDLError("[Unscopable] is only allowed on non-static "
+                                  "attributes and operations",
+                                  [attr.location, self.location])
         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
@@ -4803,16 +4811,24 @@ class IDLMethod(IDLInterfaceMember, IDLS
                 raise WebIDLError("[Alias] takes an identifier or string",
                                   [attr.location])
             self._addAlias(attr.value())
         elif identifier == "UseCounter":
             if self.isSpecial():
                 raise WebIDLError("[UseCounter] must not be used on a special "
                                   "operation",
                                   [attr.location, self.location])
+        elif identifier == "Unscopable":
+            if not attr.noArguments():
+                raise WebIDLError("[Unscopable] must take no arguments",
+                                  [attr.location])
+            if self.isStatic():
+                raise WebIDLError("[Unscopable] is only allowed on non-static "
+                                  "attributes and operations",
+                                  [attr.location, self.location])
         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
--- a/dom/canvas/WebGLExtensionDisjointTimerQuery.cpp
+++ b/dom/canvas/WebGLExtensionDisjointTimerQuery.cpp
@@ -237,15 +237,18 @@ WebGLExtensionDisjointTimerQuery::GetQue
 
 bool
 WebGLExtensionDisjointTimerQuery::IsSupported(const WebGLContext* webgl)
 {
   webgl->MakeContextCurrent();
   gl::GLContext* gl = webgl->GL();
   return gl->IsSupported(gl::GLFeature::query_objects) &&
          gl->IsSupported(gl::GLFeature::get_query_object_i64v) &&
-         gl->IsSupported(gl::GLFeature::query_counter); // provides GL_TIMESTAMP
+         gl->IsSupported(gl::GLFeature::query_counter) && // provides GL_TIMESTAMP
+         gl->IsSupported(gl::GLFeature::sync); // provides glGetInteger64v
+  // 'sync' provides glGetInteger64v either by supporting ARB_sync, GL3+, or GLES3+.
+  // Since there are no differences between support for glGetInteger64v and support for
+  // 'sync', we just piggy-back off of 'sync'.
 }
 
-
 IMPL_WEBGL_EXTENSION_GOOP(WebGLExtensionDisjointTimerQuery, EXT_disjoint_timer_query)
 
 } // namespace mozilla
--- a/dom/canvas/test/webgl-mochitest.ini
+++ b/dom/canvas/test/webgl-mochitest.ini
@@ -10,17 +10,17 @@ support-files =
 
 [webgl-mochitest/ensure-exts/test_ANGLE_instanced_arrays.html]
 fail-if = (os == 'android') || (os == 'mac' && os_version == '10.6')
 [webgl-mochitest/ensure-exts/test_EXT_blend_minmax.html]
 fail-if = (os == 'android')
 [webgl-mochitest/ensure-exts/test_EXT_color_buffer_half_float.html]
 fail-if = (os == 'android')
 [webgl-mochitest/ensure-exts/test_EXT_disjoint_timer_query.html]
-fail-if = (os == 'android') || (os == 'mac') || (os == 'win' && (os_version == '5.1' || os_version == '6.1'))
+fail-if = (os == 'android') || (os == 'mac') || (os == 'win')
 [webgl-mochitest/ensure-exts/test_EXT_frag_depth.html]
 fail-if = (os == 'android')
 [webgl-mochitest/ensure-exts/test_EXT_sRGB.html]
 fail-if = (os == 'android') || (os == 'mac' && os_version == '10.6') || (os == 'win')
 [webgl-mochitest/ensure-exts/test_EXT_shader_texture_lod.html]
 fail-if = (os == 'android') || (os == 'linux') || (os == 'mac')
 [webgl-mochitest/ensure-exts/test_EXT_texture_filter_anisotropic.html]
 fail-if = (os == 'android') || (os == 'linux')
--- a/dom/events/DataTransfer.cpp
+++ b/dom/events/DataTransfer.cpp
@@ -315,23 +315,29 @@ DataTransfer::GetFileListInternal(ErrorR
 
       if (NS_FAILED(rv))
         continue;
 
       nsCOMPtr<nsIFile> file = do_QueryInterface(supports);
 
       RefPtr<File> domFile;
       if (file) {
-#ifdef DEBUG
-        if (XRE_GetProcessType() == GeckoProcessType_Default) {
-          bool isDir;
-          file->IsDirectory(&isDir);
-          MOZ_ASSERT(!isDir, "How did we get here?");
+        MOZ_ASSERT(XRE_GetProcessType() == GeckoProcessType_Default,
+                   "nsIFile objects are not expected on the content process");
+
+        bool isDir;
+        aRv = file->IsDirectory(&isDir);
+        if (NS_WARN_IF(aRv.Failed())) {
+          return nullptr;
         }
-#endif
+
+        if (isDir) {
+          continue;
+        }
+
         domFile = File::CreateFromFile(GetParentObject(), file);
       } else {
         nsCOMPtr<BlobImpl> blobImpl = do_QueryInterface(supports);
         if (!blobImpl) {
           continue;
         }
 
         MOZ_ASSERT(blobImpl->IsFile());
@@ -915,17 +921,17 @@ already_AddRefed<nsISupportsArray>
 DataTransfer::GetTransferables(nsIDOMNode* aDragTarget)
 {
   MOZ_ASSERT(aDragTarget);
 
   nsCOMPtr<nsINode> dragNode = do_QueryInterface(aDragTarget);
   if (!dragNode) {
     return nullptr;
   }
-    
+
   nsIDocument* doc = dragNode->GetCurrentDoc();
   if (!doc) {
     return nullptr;
   }
 
   return GetTransferables(doc->GetLoadContext());
 }
 
@@ -1027,17 +1033,17 @@ DataTransfer::ConvertFromVariant(nsIVari
 
   uint16_t type;
   aVariant->GetDataType(&type);
   if (type == nsIDataType::VTYPE_INTERFACE ||
       type == nsIDataType::VTYPE_INTERFACE_IS) {
     nsCOMPtr<nsISupports> data;
     if (NS_FAILED(aVariant->GetAsISupports(getter_AddRefs(data))))
        return false;
- 
+
     nsCOMPtr<nsIFlavorDataProvider> fdp = do_QueryInterface(data);
     if (fdp) {
       // for flavour data providers, use kFlavorHasDataProvider (which has the
       // value 0) as the length.
       fdp.forget(aSupports);
       *aLength = nsITransferable::kFlavorHasDataProvider;
     }
     else {
--- a/dom/events/Event.h
+++ b/dom/events/Event.h
@@ -185,16 +185,26 @@ public:
   // See comments in its implementation for the detail.
   bool DefaultPrevented(JSContext* aCx) const;
 
   bool DefaultPrevented() const
   {
     return mEvent->DefaultPrevented();
   }
 
+  bool DefaultPreventedByChrome() const
+  {
+    return mEvent->mFlags.mDefaultPreventedByChrome;
+  }
+
+  bool DefaultPreventedByContent() const
+  {
+    return mEvent->mFlags.mDefaultPreventedByContent;
+  }
+
   bool MultipleActionsPrevented() const
   {
     return mEvent->mFlags.mMultipleActionsPrevented;
   }
 
   bool IsTrusted() const
   {
     return mEvent->IsTrusted();
--- a/dom/events/EventStateManager.cpp
+++ b/dom/events/EventStateManager.cpp
@@ -2809,17 +2809,18 @@ NodeAllowsClickThrough(nsINode* aNode)
 }
 #endif
 
 void
 EventStateManager::PostHandleKeyboardEvent(WidgetKeyboardEvent* aKeyboardEvent,
                                            nsEventStatus& aStatus,
                                            bool dispatchedToContentProcess)
 {
-  if (aStatus == nsEventStatus_eConsumeNoDefault) {
+  if (aStatus == nsEventStatus_eConsumeNoDefault ||
+      aKeyboardEvent->mInputMethodAppState == WidgetKeyboardEvent::eHandling) {
     return;
   }
 
   // XXX Currently, our automated tests don't support mKeyNameIndex.
   //     Therefore, we still need to handle this with keyCode.
   switch(aKeyboardEvent->keyCode) {
     case NS_VK_TAB:
     case NS_VK_F6:
--- a/dom/events/EventStateManager.h
+++ b/dom/events/EventStateManager.h
@@ -103,16 +103,20 @@ public:
    * also contain any centralized event processing which must occur after
    * DOM and frame processing.
    */
   nsresult PostHandleEvent(nsPresContext* aPresContext,
                            WidgetEvent* aEvent,
                            nsIFrame* aTargetFrame,
                            nsEventStatus* aStatus);
 
+  void PostHandleKeyboardEvent(WidgetKeyboardEvent* aKeyboardEvent,
+                               nsEventStatus& aStatus,
+                               bool dispatchedToContentProcess);
+
   /**
    * DispatchLegacyMouseScrollEvents() dispatches eLegacyMouseLineOrPageScroll
    * event and eLegacyMousePixelScroll event for compatibility with old Gecko.
    */
   void DispatchLegacyMouseScrollEvents(nsIFrame* aTargetFrame,
                                        WidgetWheelEvent* aEvent,
                                        nsEventStatus* aStatus);
 
@@ -894,19 +898,16 @@ private:
                                    bool aAddState);
   static void UpdateAncestorState(nsIContent* aStartNode,
                                   nsIContent* aStopBefore,
                                   EventStates aState,
                                   bool aAddState);
   static void ResetLastOverForContent(const uint32_t& aIdx,
                                       RefPtr<OverOutElementsWrapper>& aChunk,
                                       nsIContent* aClosure);
-  void PostHandleKeyboardEvent(WidgetKeyboardEvent* aKeyboardEvent,
-                               nsEventStatus& aStatus,
-                               bool dispatchedToContentProcess);
 
   int32_t     mLockCursor;
   bool mLastFrameConsumedSetCursor;
 
   // Last mouse event refPoint (the offset from the widget's origin in
   // device pixels) when mouse was locked, used to restore mouse position
   // after unlocking.
   LayoutDeviceIntPoint mPreLockPoint;
--- a/dom/events/KeyboardEvent.cpp
+++ b/dom/events/KeyboardEvent.cpp
@@ -131,16 +131,51 @@ KeyboardEvent::GetKey(nsAString& aKeyNam
 }
 
 void
 KeyboardEvent::GetCode(nsAString& aCodeName)
 {
   mEvent->AsKeyboardEvent()->GetDOMCodeName(aCodeName);
 }
 
+void KeyboardEvent::GetInitDict(KeyboardEventInit& aParam)
+{
+  GetKey(aParam.mKey);
+  GetCode(aParam.mCode);
+  aParam.mLocation = Location();
+  aParam.mRepeat = Repeat();
+  aParam.mIsComposing = IsComposing();
+
+  // legacy attributes
+  aParam.mKeyCode = KeyCode();
+  aParam.mCharCode = CharCode();
+  aParam.mWhich = Which();
+
+  // modifiers from EventModifierInit
+  aParam.mCtrlKey = CtrlKey();
+  aParam.mShiftKey = ShiftKey();
+  aParam.mAltKey = AltKey();
+  aParam.mMetaKey = MetaKey();
+
+  WidgetKeyboardEvent* internalEvent = mEvent->AsKeyboardEvent();
+  aParam.mModifierAltGraph = internalEvent->IsAltGraph();
+  aParam.mModifierCapsLock = internalEvent->IsCapsLocked();
+  aParam.mModifierFn = internalEvent->IsFn();
+  aParam.mModifierFnLock = internalEvent->IsFnLocked();
+  aParam.mModifierNumLock = internalEvent->IsNumLocked();
+  aParam.mModifierOS = internalEvent->IsOS();
+  aParam.mModifierScrollLock = internalEvent->IsScrollLocked();
+  aParam.mModifierSymbol = internalEvent->IsSymbol();
+  aParam.mModifierSymbolLock = internalEvent->IsSymbolLocked();
+
+  // EventInit
+  aParam.mBubbles =  internalEvent->mFlags.mBubbles;
+  aParam.mCancelable = internalEvent->mFlags.mCancelable;
+}
+
 NS_IMETHODIMP
 KeyboardEvent::GetCharCode(uint32_t* aCharCode)
 {
   NS_ENSURE_ARG_POINTER(aCharCode);
   *aCharCode = CharCode();
   return NS_OK;
 }
 
--- a/dom/events/KeyboardEvent.h
+++ b/dom/events/KeyboardEvent.h
@@ -55,16 +55,17 @@ public:
   bool Repeat();
   bool IsComposing();
   uint32_t CharCode();
   uint32_t KeyCode();
   virtual uint32_t Which() override;
   uint32_t Location();
 
   void GetCode(nsAString& aCode);
+  void GetInitDict(KeyboardEventInit& aParam);
 
   void InitKeyEvent(const nsAString& aType, bool aCanBubble, bool aCancelable,
                     nsGlobalWindow* aView, bool aCtrlKey, bool aAltKey,
                     bool aShiftKey, bool aMetaKey,
                     uint32_t aKeyCode, uint32_t aCharCode)
   {
     auto* view = aView ? aView->AsInner() : nullptr;
     InitKeyEvent(aType, aCanBubble, aCancelable, view, aCtrlKey, aAltKey,
--- a/dom/filesystem/DeviceStorageFileSystem.cpp
+++ b/dom/filesystem/DeviceStorageFileSystem.cpp
@@ -39,17 +39,17 @@ DeviceStorageFileSystem::DeviceStorageFi
   NS_WARN_IF(NS_FAILED(rv));
 
   // Get the local path of the file system root.
   nsCOMPtr<nsIFile> rootFile;
   DeviceStorageFile::GetRootDirectoryForType(aStorageType,
                                              aStorageName,
                                              getter_AddRefs(rootFile));
 
-  NS_WARN_IF(!rootFile || NS_FAILED(rootFile->GetPath(mLocalRootPath)));
+  NS_WARN_IF(!rootFile || NS_FAILED(rootFile->GetPath(mLocalOrDeviceStorageRootPath)));
 
   if (!XRE_IsParentProcess()) {
     return;
   }
 
   // DeviceStorageTypeChecker is a singleton object and must be initialized on
   // the main thread. We initialize it here so that we can use it on the worker
   // thread.
@@ -108,17 +108,17 @@ DeviceStorageFileSystem::GetRootName(nsA
 bool
 DeviceStorageFileSystem::IsSafeFile(nsIFile* aFile) const
 {
   MOZ_ASSERT(XRE_IsParentProcess(),
              "Should be on parent process!");
   MOZ_ASSERT(aFile);
 
   nsCOMPtr<nsIFile> rootPath;
-  nsresult rv = NS_NewLocalFile(GetLocalRootPath(), false,
+  nsresult rv = NS_NewLocalFile(LocalOrDeviceStorageRootPath(), false,
                                 getter_AddRefs(rootPath));
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return false;
   }
 
   // Check if this file belongs to this storage.
   if (NS_WARN_IF(!FileSystemUtils::IsDescendantPath(rootPath, aFile))) {
     return false;
--- a/dom/filesystem/Directory.cpp
+++ b/dom/filesystem/Directory.cpp
@@ -113,17 +113,17 @@ NS_INTERFACE_MAP_END
 
 // static
 already_AddRefed<Promise>
 Directory::GetRoot(FileSystemBase* aFileSystem, ErrorResult& aRv)
 {
   MOZ_ASSERT(aFileSystem);
 
   nsCOMPtr<nsIFile> path;
-  aRv = NS_NewNativeLocalFile(NS_ConvertUTF16toUTF8(aFileSystem->GetLocalRootPath()),
+  aRv = NS_NewNativeLocalFile(NS_ConvertUTF16toUTF8(aFileSystem->LocalOrDeviceStorageRootPath()),
                               true, getter_AddRefs(path));
   if (NS_WARN_IF(aRv.Failed())) {
     return nullptr;
   }
 
   RefPtr<GetFileOrDirectoryTask> task =
     GetFileOrDirectoryTask::Create(aFileSystem, path, eDOMRootDirectory, true, aRv);
   if (NS_WARN_IF(aRv.Failed())) {
@@ -350,22 +350,30 @@ Directory::RemoveInternal(const StringOr
   task->SetError(error);
   FileSystemPermissionRequest::RequestForTask(task);
   return task->GetPromise();
 }
 
 void
 Directory::GetPath(nsAString& aRetval, ErrorResult& aRv)
 {
-  if (mType == eDOMRootDirectory) {
-    aRetval.AssignLiteral(FILESYSTEM_DOM_PATH_SEPARATOR_LITERAL);
-  } else {
-    // TODO: this should be a bit different...
-    GetName(aRetval, aRv);
+  // This operation is expensive. Better to cache the result.
+  if (mPath.IsEmpty()) {
+    RefPtr<FileSystemBase> fs = GetFileSystem(aRv);
+    if (NS_WARN_IF(aRv.Failed())) {
+      return;
+    }
+
+    fs->GetDOMPath(mFile, mType, mPath, aRv);
+    if (NS_WARN_IF(aRv.Failed())) {
+      return;
+    }
   }
+
+  aRetval = mPath;
 }
 
 nsresult
 Directory::GetFullRealPath(nsAString& aPath)
 {
   nsresult rv = mFile->GetPath(aPath);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
@@ -397,29 +405,22 @@ Directory::SetContentFilters(const nsASt
 {
   mFilters = aFilters;
 }
 
 FileSystemBase*
 Directory::GetFileSystem(ErrorResult& aRv)
 {
   if (!mFileSystem) {
-    nsCOMPtr<nsIFile> parent;
-    aRv = mFile->GetParent(getter_AddRefs(parent));
-    if (NS_WARN_IF(aRv.Failed())) {
-      return nullptr;
-    }
-
-    // Parent can be null if mFile is pointing to the top directory.
-    if (!parent) {
-      parent = mFile;
-    }
+    // Any subdir inherits the FileSystem of the parent Directory. If we are
+    // here it's because we are dealing with the DOM root.
+    MOZ_ASSERT(mType == eDOMRootDirectory);
 
     nsAutoString path;
-    aRv = parent->GetPath(path);
+    aRv = mFile->GetPath(path);
     if (NS_WARN_IF(aRv.Failed())) {
       return nullptr;
     }
 
     RefPtr<OSFileSystem> fs = new OSFileSystem(path);
     fs->Init(mParent);
 
     mFileSystem = fs;
--- a/dom/filesystem/Directory.h
+++ b/dom/filesystem/Directory.h
@@ -162,14 +162,15 @@ private:
                  ErrorResult& aRv);
 
   nsCOMPtr<nsISupports> mParent;
   RefPtr<FileSystemBase> mFileSystem;
   nsCOMPtr<nsIFile> mFile;
   DirectoryType mType;
 
   nsString mFilters;
+  nsString mPath;
 };
 
 } // namespace dom
 } // namespace mozilla
 
 #endif // mozilla_dom_Directory_h
--- a/dom/filesystem/FileSystemBase.cpp
+++ b/dom/filesystem/FileSystemBase.cpp
@@ -94,10 +94,83 @@ FileSystemBase::IsSafeFile(nsIFile* aFil
 }
 
 bool
 FileSystemBase::IsSafeDirectory(Directory* aDir) const
 {
   return false;
 }
 
+void
+FileSystemBase::GetDOMPath(nsIFile* aFile,
+                           Directory::DirectoryType aType,
+                           nsAString& aRetval,
+                           ErrorResult& aRv) const
+{
+  MOZ_ASSERT(aFile);
+
+  if (aType == Directory::eDOMRootDirectory) {
+    aRetval.AssignLiteral(FILESYSTEM_DOM_PATH_SEPARATOR_LITERAL);
+    return;
+  }
+
+  nsCOMPtr<nsIFile> fileSystemPath;
+  aRv = NS_NewNativeLocalFile(NS_ConvertUTF16toUTF8(LocalOrDeviceStorageRootPath()),
+                              true, getter_AddRefs(fileSystemPath));
+  if (NS_WARN_IF(aRv.Failed())) {
+    return;
+  }
+
+  MOZ_ASSERT(FileSystemUtils::IsDescendantPath(fileSystemPath, aFile));
+
+  nsCOMPtr<nsIFile> path;
+  aRv = aFile->Clone(getter_AddRefs(path));
+  if (NS_WARN_IF(aRv.Failed())) {
+    return;
+  }
+
+  nsTArray<nsString> parts;
+
+  while (true) {
+    bool equal = false;
+    aRv = fileSystemPath->Equals(path, &equal);
+    if (NS_WARN_IF(aRv.Failed())) {
+      return;
+    }
+
+    if (equal) {
+      break;
+    }
+
+    nsAutoString leafName;
+    aRv = path->GetLeafName(leafName);
+    if (NS_WARN_IF(aRv.Failed())) {
+      return;
+    }
+
+    parts.AppendElement(leafName);
+
+    nsCOMPtr<nsIFile> parentPath;
+    aRv = path->GetParent(getter_AddRefs(parentPath));
+    if (NS_WARN_IF(aRv.Failed())) {
+      return;
+    }
+
+    MOZ_ASSERT(parentPath);
+
+    aRv = parentPath->Clone(getter_AddRefs(path));
+    if (NS_WARN_IF(aRv.Failed())) {
+      return;
+    }
+  }
+
+  MOZ_ASSERT(!parts.IsEmpty());
+
+  aRetval.Truncate();
+
+  for (int32_t i = parts.Length() - 1; i >= 0; --i) {
+    aRetval.AppendLiteral(FILESYSTEM_DOM_PATH_SEPARATOR_LITERAL);
+    aRetval.Append(parts[i]);
+  }
+}
+
 } // namespace dom
 } // namespace mozilla
--- a/dom/filesystem/FileSystemBase.h
+++ b/dom/filesystem/FileSystemBase.h
@@ -4,22 +4,22 @@
  * 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_FileSystemBase_h
 #define mozilla_dom_FileSystemBase_h
 
 #include "nsAutoPtr.h"
 #include "nsString.h"
+#include "Directory.h"
 
 namespace mozilla {
 namespace dom {
 
 class BlobImpl;
-class Directory;
 
 class FileSystemBase
 {
   NS_INLINE_DECL_REFCOUNTING(FileSystemBase)
 public:
 
   // Create file system object from its string representation.
   static already_AddRefed<FileSystemBase>
@@ -42,20 +42,30 @@ public:
 
   /*
    * Get the virtual name of the root directory. This name will be exposed to
    * the content page.
    */
   virtual void
   GetRootName(nsAString& aRetval) const = 0;
 
+  void
+  GetDOMPath(nsIFile* aFile, Directory::DirectoryType aType,
+             nsAString& aRetval, ErrorResult& aRv) const;
+
+  /*
+   * Return the local root path of the FileSystem implementation.
+   * For OSFileSystem, this is equal to the path of the root Directory;
+   * For DeviceStorageFileSystem, this is the path of the SDCard, parent
+   * directory of the exposed root Directory (per type).
+   */
   const nsAString&
-  GetLocalRootPath() const
+  LocalOrDeviceStorageRootPath() const
   {
-    return mLocalRootPath;
+    return mLocalOrDeviceStorageRootPath;
   }
 
   bool
   IsShutdown() const
   {
     return mShutdown;
   }
 
@@ -87,19 +97,27 @@ public:
   virtual void Unlink() {}
   virtual void Traverse(nsCycleCollectionTraversalCallback &cb) {}
 
 protected:
   virtual ~FileSystemBase();
 
   // The local path of the root (i.e. the OS path, with OS path separators, of
   // the OS directory that acts as the root of this OSFileSystem).
-  // Only available in the parent process.
-  // In the child process, we don't use it and its value should be empty.
-  nsString mLocalRootPath;
+  // This path must be set by the FileSystem implementation immediately
+  // because it will be used for the validation of any FileSystemTaskBase.
+  // The concept of this path is that, any task will never go out of it and this
+  // must be considered the OS 'root' of the current FileSystem. Different
+  // Directory object can have different OS 'root' path.
+  // To be more clear, any path managed by this FileSystem implementation must
+  // be discendant of this local root path.
+  // The reason why it's not just called 'localRootPath' is because for
+  // DeviceStorage this contains the path of the device storage SDCard, that is
+  // the parent directory of the exposed root path.
+  nsString mLocalOrDeviceStorageRootPath;
 
   bool mShutdown;
 
   // The permission name required to access the file system.
   nsCString mPermission;
 
   bool mRequiresPermissionChecks;
 };
--- a/dom/filesystem/GetDirectoryListingTask.cpp
+++ b/dom/filesystem/GetDirectoryListingTask.cpp
@@ -167,28 +167,33 @@ GetDirectoryListingTask::SetSuccessReque
   MOZ_ASSERT(NS_IsMainThread(), "Only call on main thread!");
   MOZ_ASSERT(aValue.type() ==
                FileSystemResponseValue::TFileSystemDirectoryListingResponse);
 
   FileSystemDirectoryListingResponse r = aValue;
   for (uint32_t i = 0; i < r.data().Length(); ++i) {
     const FileSystemDirectoryListingResponseData& data = r.data()[i];
 
+    Directory::BlobImplOrDirectoryPath element;
+
     if (data.type() == FileSystemDirectoryListingResponseData::TFileSystemDirectoryListingResponseBlob) {
       PBlobChild* blob = data.get_FileSystemDirectoryListingResponseBlob().blobChild();
 
-      Directory::BlobImplOrDirectoryPath* element = mTargetData.AppendElement();
-      element->mType = Directory::BlobImplOrDirectoryPath::eBlobImpl;
-      element->mBlobImpl = static_cast<BlobChild*>(blob)->GetBlobImpl();
+      element.mType = Directory::BlobImplOrDirectoryPath::eBlobImpl;
+      element.mBlobImpl = static_cast<BlobChild*>(blob)->GetBlobImpl();
     } else {
       MOZ_ASSERT(data.type() == FileSystemDirectoryListingResponseData::TFileSystemDirectoryListingResponseDirectory);
 
-      Directory::BlobImplOrDirectoryPath* element = mTargetData.AppendElement();
-      element->mType = Directory::BlobImplOrDirectoryPath::eDirectoryPath;
-      element->mDirectoryPath = data.get_FileSystemDirectoryListingResponseDirectory().directoryRealPath();
+      element.mType = Directory::BlobImplOrDirectoryPath::eDirectoryPath;
+      element.mDirectoryPath = data.get_FileSystemDirectoryListingResponseDirectory().directoryRealPath();
+    }
+
+    if (!mTargetData.AppendElement(element, fallible)) {
+      aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
+      return;
     }
   }
 }
 
 nsresult
 GetDirectoryListingTask::Work()
 {
   MOZ_ASSERT(XRE_IsParentProcess(),
@@ -282,31 +287,34 @@ GetDirectoryListingTask::Work()
       if (NS_WARN_IF(NS_FAILED(currFile->GetLeafName(leafName)))) {
         continue;
       }
       if (leafName[0] == char16_t('.')) {
         continue;
       }
     }
 
+    Directory::BlobImplOrDirectoryPath element;
     if (isDir) {
       nsAutoString path;
       if (NS_WARN_IF(NS_FAILED(currFile->GetPath(path)))) {
         continue;
       }
 
-      Directory::BlobImplOrDirectoryPath* element = mTargetData.AppendElement();
-      element->mType = Directory::BlobImplOrDirectoryPath::eDirectoryPath;
-      element->mDirectoryPath = path;
+      element.mType = Directory::BlobImplOrDirectoryPath::eDirectoryPath;
+      element.mDirectoryPath = path;
     } else {
       BlobImplFile* impl = new BlobImplFile(currFile);
 
-      Directory::BlobImplOrDirectoryPath* element = mTargetData.AppendElement();
-      element->mType = Directory::BlobImplOrDirectoryPath::eBlobImpl;
-      element->mBlobImpl = impl;
+      element.mType = Directory::BlobImplOrDirectoryPath::eBlobImpl;
+      element.mBlobImpl = impl;
+    }
+
+    if (!mTargetData.AppendElement(element, fallible)) {
+      return NS_ERROR_OUT_OF_MEMORY;
     }
   }
   return NS_OK;
 }
 
 void
 GetDirectoryListingTask::HandlerCallback()
 {
@@ -341,17 +349,17 @@ GetDirectoryListingTask::HandlerCallback
       if (NS_WARN_IF(NS_FAILED(rv))) {
         mPromise->MaybeReject(rv);
         mPromise = nullptr;
         return;
       }
 
 #ifdef DEBUG
       nsCOMPtr<nsIFile> rootPath;
-      rv = NS_NewLocalFile(mFileSystem->GetLocalRootPath(), false,
+      rv = NS_NewLocalFile(mFileSystem->LocalOrDeviceStorageRootPath(), false,
                            getter_AddRefs(rootPath));
       if (NS_WARN_IF(NS_FAILED(rv))) {
         mPromise->MaybeReject(rv);
         mPromise = nullptr;
         return;
       }
 
       MOZ_ASSERT(FileSystemUtils::IsDescendantPath(rootPath, directoryPath));
--- a/dom/filesystem/GetDirectoryListingTask.h
+++ b/dom/filesystem/GetDirectoryListingTask.h
@@ -72,15 +72,15 @@ private:
 
   RefPtr<Promise> mPromise;
   nsCOMPtr<nsIFile> mTargetPath;
   nsString mFilters;
   Directory::DirectoryType mType;
 
   // We cannot store File or Directory objects bacause this object is created
   // on a different thread and File and Directory are not thread-safe.
-  nsTArray<Directory::BlobImplOrDirectoryPath> mTargetData;
+  FallibleTArray<Directory::BlobImplOrDirectoryPath> mTargetData;
 };
 
 } // namespace dom
 } // namespace mozilla
 
 #endif // mozilla_dom_GetDirectoryListing_h
--- a/dom/filesystem/OSFileSystem.cpp
+++ b/dom/filesystem/OSFileSystem.cpp
@@ -14,31 +14,31 @@
 #include "nsDebug.h"
 #include "nsIFile.h"
 
 namespace mozilla {
 namespace dom {
 
 OSFileSystem::OSFileSystem(const nsAString& aRootDir)
 {
-  mLocalRootPath = aRootDir;
+  mLocalOrDeviceStorageRootPath = aRootDir;
 
   // Non-mobile devices don't have the concept of separate permissions to
   // access different parts of devices storage like Pictures, or Videos, etc.
   mRequiresPermissionChecks = false;
 
 #ifdef DEBUG
   mPermission.AssignLiteral("never-used");
 #endif
 }
 
 already_AddRefed<FileSystemBase>
 OSFileSystem::Clone()
 {
-  RefPtr<OSFileSystem> fs = new OSFileSystem(mLocalRootPath);
+  RefPtr<OSFileSystem> fs = new OSFileSystem(mLocalOrDeviceStorageRootPath);
   if (mParent) {
     fs->Init(mParent);
   }
 
   return fs.forget();
 }
 
 void
@@ -99,13 +99,13 @@ OSFileSystem::Traverse(nsCycleCollection
 {
   OSFileSystem* tmp = this;
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mParent);
 }
 
 void
 OSFileSystem::SerializeDOMPath(nsAString& aOutput) const
 {
-  aOutput = mLocalRootPath;
+  aOutput = mLocalOrDeviceStorageRootPath;
 }
 
 } // namespace dom
 } // namespace mozilla
--- a/dom/filesystem/tests/script_fileList.js
+++ b/dom/filesystem/tests/script_fileList.js
@@ -1,13 +1,25 @@
 var { classes: Cc, interfaces: Ci, utils: Cu } = Components;
 Cu.importGlobalProperties(["File"]);
 
-addMessageListener("dir.open", function () {
+addMessageListener("dir.open", function (e) {
   var testFile = Cc["@mozilla.org/file/directory_service;1"]
                    .getService(Ci.nsIDirectoryService)
                    .QueryInterface(Ci.nsIProperties)
-                   .get("ProfD", Ci.nsIFile);
+                   .get(e.path == 'root' ? 'ProfD' : e.path, Ci.nsIFile);
+
+  // Let's go back to the root of the FileSystem
+  if (e.path == 'root') {
+    while (true) {
+      var parent = testFile.parent;
+      if (!parent) {
+        break;
+      }
+
+      testFile = parent;
+    }
+  }
 
   sendAsyncMessage("dir.opened", {
     dir: testFile.path
   });
 });
--- a/dom/filesystem/tests/test_basic.html
+++ b/dom/filesystem/tests/test_basic.html
@@ -7,17 +7,17 @@
 </head>
 
 <body>
 <input id="fileList" type="file"></input>
 <script type="application/javascript;version=1.7">
 
 var directory;
 
-function create_fileList() {
+function create_fileList(aPath) {
   var url = SimpleTest.getTestFileURL("script_fileList.js");
   var script = SpecialPowers.loadChromeScript(url);
 
   function onOpened(message) {
     var fileList = document.getElementById('fileList');
     SpecialPowers.wrap(fileList).mozSetDirectory(message.dir);
 
     // Just a simple test
@@ -26,69 +26,76 @@ function create_fileList() {
 
     directory = fileList.files[0];
 
     script.destroy();
     next();
   }
 
   script.addMessageListener("dir.opened", onOpened);
-  script.sendAsyncMessage("dir.open");
+  script.sendAsyncMessage("dir.open", { path: aPath });
 }
 
 function test_basic() {
   ok(directory, "Directory exists.");
   ok(directory instanceof Directory, "We have a directory.");
   is(directory.name, '/', "directory.name must be '/'");
   is(directory.path, '/', "directory.path must be '/'");
   next();
 }
 
 function checkSubDir(dir) {
   return dir.getFilesAndDirectories().then(
     function(data) {
       for (var i = 0; i < data.length; ++i) {
         ok (data[i] instanceof File || data[i] instanceof Directory, "Just Files or Directories");
         if (data[i] instanceof Directory) {
-          isnot(data[i].name, '/', "Subdirectory should be called with the leafname");
-          isnot(data[i].path, '/', "Subdirectory path should be called with the leafname");
+          isnot(data[i].name, '/', "Subdirectory should be called with the leafname: " + data[i].name);
+          isnot(data[i].path, '/', "Subdirectory path should be called with the leafname:" + data[i].path);
+          isnot(data[i].path, dir.path, "Subdirectory path should contain the parent path.");
+          is(data[i].path,dir.path + '/' + data[i].name, "Subdirectory path should be called parentdir.path + '/' + leafname");
         }
       }
     }
   );
 }
 
-function getFilesAndDirectories() {
+function getFilesAndDirectories(aRecursive) {
   directory.getFilesAndDirectories().then(
     function(data) {
       ok(data.length, "We should have some data.");
       var promises = [];
       for (var i = 0; i < data.length; ++i) {
         ok (data[i] instanceof File || data[i] instanceof Directory, "Just Files or Directories");
         if (data[i] instanceof Directory) {
           isnot(data[i].name, '/', "Subdirectory should be called with the leafname");
-          isnot(data[i].path, '/', "Subdirectory path should be called with the leafname");
-          promises.push(checkSubDir(data[i]));
+          is(data[i].path, '/' + data[i].name, "Subdirectory path should be called '/' + leafname");
+
+          if (aRecursive) {
+            promises.push(checkSubDir(data[i]));
+          }
         }
       }
 
       return Promise.all(promises);
     },
     function() {
       ok(false, "Something when wrong");
     }
   ).then(next);
 }
 
 var tests = [
-  create_fileList,
-
+  function() { create_fileList('ProfD') },
   test_basic,
+  function() { getFilesAndDirectories(true) },
 
-  getFilesAndDirectories,
+  function() { create_fileList('root') },
+  test_basic,
+  function() { getFilesAndDirectories(false) },
 ];
 
 function next() {
   if (!tests.length) {
     SimpleTest.finish();
     return;
   }
 
--- a/dom/html/HTMLImageElement.cpp
+++ b/dom/html/HTMLImageElement.cpp
@@ -93,16 +93,20 @@ public:
     if (mElement->mPendingImageLoadTask == this) {
       mElement->mPendingImageLoadTask = nullptr;
       mElement->LoadSelectedImage(true, true, mAlwaysLoad);
     }
     mDocument->UnblockOnload(false);
     return NS_OK;
   }
 
+  bool AlwaysLoad() {
+    return mAlwaysLoad;
+  }
+
 private:
   ~ImageLoadTask() {}
   RefPtr<HTMLImageElement> mElement;
   nsCOMPtr<nsIDocument> mDocument;
   bool mAlwaysLoad;
 };
 
 HTMLImageElement::HTMLImageElement(already_AddRefed<mozilla::dom::NodeInfo>& aNodeInfo)
@@ -894,17 +898,23 @@ void
 HTMLImageElement::QueueImageLoadTask(bool aAlwaysLoad)
 {
   // If loading is temporarily disabled, we don't want to queue tasks
   // that may then run when loading is re-enabled.
   if (!LoadingEnabled() || !this->OwnerDoc()->IsCurrentActiveDocument()) {
     return;
   }
 
-  nsCOMPtr<nsIRunnable> task = new ImageLoadTask(this, aAlwaysLoad);
+  // Ensure that we don't overwrite a previous load request that requires
+  // a complete load to occur.
+  bool alwaysLoad = aAlwaysLoad;
+  if (mPendingImageLoadTask) {
+    alwaysLoad = alwaysLoad || mPendingImageLoadTask->AlwaysLoad();
+  }
+  RefPtr<ImageLoadTask> task = new ImageLoadTask(this, alwaysLoad);
   // The task checks this to determine if it was the last
   // queued event, and so earlier tasks are implicitly canceled.
   mPendingImageLoadTask = task;
   nsContentUtils::RunInStableState(task.forget());
 }
 
 bool
 HTMLImageElement::HaveSrcsetOrInPicture()
--- a/dom/html/HTMLImageElement.h
+++ b/dom/html/HTMLImageElement.h
@@ -17,16 +17,18 @@
 
 // Only needed for IsPictureEnabled()
 #include "mozilla/dom/HTMLPictureElement.h"
 
 namespace mozilla {
 class EventChainPreVisitor;
 namespace dom {
 
+class ImageLoadTask;
+
 class ResponsiveImageSelector;
 class HTMLImageElement final : public nsGenericHTMLElement,
                                public nsImageLoadingContent,
                                public nsIDOMHTMLImageElement
 {
   friend class HTMLSourceElement;
   friend class HTMLPictureElement;
   friend class ImageLoadTask;
@@ -357,15 +359,15 @@ protected:
 
 private:
   bool SourceElementMatches(nsIContent* aSourceNode);
 
   static void MapAttributesIntoRule(const nsMappedAttributes* aAttributes,
                                     nsRuleData* aData);
 
   bool mInDocResponsiveContent;
-  nsCOMPtr<nsIRunnable> mPendingImageLoadTask;
+  RefPtr<ImageLoadTask> mPendingImageLoadTask;
 };
 
 } // namespace dom
 } // namespace mozilla
 
 #endif /* mozilla_dom_HTMLImageElement_h */
--- a/dom/html/reftests/reftest.list
+++ b/dom/html/reftests/reftest.list
@@ -29,16 +29,18 @@ skip-if(B2G) == 41464-1b.html 41464-1-re
 skip-if(Android||B2G) == 649134-2.html 649134-2-ref.html
 
 == bug448564-1_malformed.html bug448564-1_well-formed.html
 == bug448564-1_malformed.html bug448564-1_ideal.html
 
 == bug448564-4a.html          bug448564-4b.html
 == bug502168-1_malformed.html bug502168-1_well-formed.html
 
+== responsive-image-load-shortcircuit.html responsive-image-load-shortcircuit-ref.html
+
 # Test that image documents taken into account CSS properties like
 # image-orientation when determining the size of the image.
 # (Fuzzy necessary due to pixel-wise comparison of different JPEGs.
 # The vast majority of the fuzziness comes from Linux and WinXP.)
 fuzzy(1,149) == bug917595-iframe-1.html    bug917595-1-ref.html
 skip-if(B2G||Mulet) fuzzy-if((!B2G&&!Mulet),3,640) == bug917595-exif-rotated.jpg bug917595-pixel-rotated.jpg # bug 1060869 # Bug 1150490 disabling on Mulet as on B2G
 
 # Test support for SVG-as-image in <picture> elements.
new file mode 100644
--- /dev/null
+++ b/dom/html/reftests/responsive-image-load-shortcircuit-ref.html
@@ -0,0 +1,1 @@
+<iframe srcdoc="<img src=pass.png>" width="300px"></iframe>
new file mode 100644
--- /dev/null
+++ b/dom/html/reftests/responsive-image-load-shortcircuit.html
@@ -0,0 +1,15 @@
+<html class="reftest-wait">
+<iframe srcdoc="<img srcset=red.png>" width="150px"></iframe>
+<script>
+  var iframe = document.querySelector('iframe');
+  iframe.onload = function() {
+    var doc = iframe.contentDocument;
+    var img = doc.querySelector('img');
+    img.srcset = "pass.png";
+    iframe.width = "300px";
+    img.onload = function() {
+      document.documentElement.classList.remove('reftest-wait');
+    };
+  }
+</script>
+</html>
--- a/dom/html/test/browser.ini
+++ b/dom/html/test/browser.ini
@@ -1,23 +1,25 @@
 [DEFAULT]
 support-files =
   bug592641_img.jpg
   dummy_page.html
   file_bug649778.html
   file_bug649778.html^headers^
   file_fullscreen-api-keys.html
+  file_content_contextmenu.html
   head.js
 
 [browser_bug592641.js]
 [browser_bug649778.js]
 skip-if = e10s # Bug ?????? - leaked until shutdown [nsGlobalWindow #16 about:blank]
 [browser_bug1081537.js]
 [browser_bug1108547.js]
 support-files =
   file_bug1108547-1.html
   file_bug1108547-2.html
   file_bug1108547-3.html
 [browser_DOMDocElementInserted.js]
 [browser_fullscreen-api-keys.js]
 tags = fullscreen
 [browser_fullscreen-contextmenu-esc.js]
 tags = fullscreen
+[browser_content_contextmenu_userinput.js]
new file mode 100644
--- /dev/null
+++ b/dom/html/test/browser_content_contextmenu_userinput.js
@@ -0,0 +1,61 @@
+"use strict";
+
+function frameScript() {
+  let Ci = Components.interfaces;
+  let windowUtils = content.QueryInterface(Ci.nsIInterfaceRequestor)
+                           .getInterface(Ci.nsIDOMWindowUtils);
+  let menuitem = content.document.getElementById("menuitem");
+  menuitem.addEventListener("click", function() {
+    sendAsyncMessage("Test:ContextMenuClick", windowUtils.isHandlingUserInput);
+  });
+}
+
+var gMessageManager;
+
+function listenOneMessage(aMsg, aListener) {
+  function listener({ data }) {
+    gMessageManager.removeMessageListener(aMsg, listener);
+    aListener(data);
+  }
+  gMessageManager.addMessageListener(aMsg, listener);
+}
+
+function promiseOneMessage(aMsg) {
+  return new Promise(resolve => listenOneMessage(aMsg, resolve));
+}
+
+const kPage = "http://example.org/browser/" +
+              "dom/html/test/file_content_contextmenu.html";
+
+add_task(function* () {
+  yield BrowserTestUtils.withNewTab({
+    gBrowser,
+    url: kPage
+  }, function*(aBrowser) {
+    gMessageManager = aBrowser.messageManager;
+    ContentTask.spawn(aBrowser, null, frameScript);
+
+    let contextMenu = document.getElementById("contentAreaContextMenu");
+    ok(contextMenu, "Got context menu");
+
+    info("Open context menu");
+    is(contextMenu.state, "closed", "Should not have opened context menu");
+    let popupShownPromise = promiseWaitForEvent(window, "popupshown");
+    EventUtils.synthesizeMouse(aBrowser, window.innerWidth / 3,
+                               window.innerHeight / 3,
+                               {type: "contextmenu", button: 2}, window);
+    yield popupShownPromise;
+    is(contextMenu.state, "open", "Should have opened context menu");
+
+    let pageMenuSep = document.getElementById("page-menu-separator");
+    ok(pageMenuSep && !pageMenuSep.hidden,
+       "Page menu separator should be shown");
+    let testMenuItem = pageMenuSep.previousSibling;
+    is(testMenuItem.label, "Test Context Menu Click", "Got context menu item");
+
+    let promiseContextMenuClick = promiseOneMessage("Test:ContextMenuClick");
+    EventUtils.synthesizeMouseAtCenter(testMenuItem, {}, window);
+    let isUserInput = yield promiseContextMenuClick;
+    ok(isUserInput, "Content menu click should be a user input");
+  });
+});
new file mode 100644
--- /dev/null
+++ b/dom/html/test/file_content_contextmenu.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+  <meta charset="UTF-8">
+  <title></title>
+  <style>
+    body {
+      margin: 0;
+      width: 100vw;
+      height: 100vh;
+    }
+  </style>
+</head>
+<body contextmenu="testmenu">
+  <menu type="context" id="testmenu">
+    <menuitem label="Test Context Menu Click" id="menuitem">
+  </menu>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/inputmethod/HardwareKeyHandler.cpp
@@ -0,0 +1,566 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "HardwareKeyHandler.h"
+#include "mozilla/BasicEvents.h"
+#include "mozilla/ClearOnShutdown.h"
+#include "mozilla/dom/KeyboardEvent.h"
+#include "mozilla/dom/TabParent.h"
+#include "mozilla/EventDispatcher.h"
+#include "mozilla/EventStateManager.h"
+#include "mozilla/TextEvents.h"
+#include "nsDeque.h"
+#include "nsFocusManager.h"
+#include "nsFrameLoader.h"
+#include "nsIContent.h"
+#include "nsIDOMHTMLDocument.h"
+#include "nsIDOMHTMLElement.h"
+#include "nsPIDOMWindow.h"
+#include "nsPresContext.h"
+#include "nsPresShell.h"
+
+namespace mozilla {
+
+using namespace dom;
+
+NS_IMPL_ISUPPORTS(HardwareKeyHandler, nsIHardwareKeyHandler)
+
+StaticRefPtr<HardwareKeyHandler> HardwareKeyHandler::sInstance;
+
+HardwareKeyHandler::HardwareKeyHandler()
+  : mInputMethodAppConnected(false)
+{
+}
+
+HardwareKeyHandler::~HardwareKeyHandler()
+{
+}
+
+NS_IMETHODIMP
+HardwareKeyHandler::OnInputMethodAppConnected()
+{
+  if (NS_WARN_IF(mInputMethodAppConnected)) {
+    return NS_ERROR_UNEXPECTED;
+  }
+
+  mInputMethodAppConnected = true;
+
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+HardwareKeyHandler::OnInputMethodAppDisconnected()
+{
+  if (NS_WARN_IF(!mInputMethodAppConnected)) {
+    return NS_ERROR_UNEXPECTED;
+  }
+
+  mInputMethodAppConnected = false;
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+HardwareKeyHandler::RegisterListener(nsIHardwareKeyEventListener* aListener)
+{
+  // Make sure the listener is not nullptr and there is no available
+  // hardwareKeyEventListener now
+  if (NS_WARN_IF(!aListener)) {
+    return NS_ERROR_NULL_POINTER;
+  }
+
+  if (NS_WARN_IF(mHardwareKeyEventListener)) {
+    return NS_ERROR_ALREADY_INITIALIZED;
+  }
+
+  mHardwareKeyEventListener = do_GetWeakReference(aListener);
+
+  if (NS_WARN_IF(!mHardwareKeyEventListener)) {
+    return NS_ERROR_NULL_POINTER;
+  }
+
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+HardwareKeyHandler::UnregisterListener()
+{
+  // Clear the HardwareKeyEventListener
+  mHardwareKeyEventListener = nullptr;
+  return NS_OK;
+}
+
+bool
+HardwareKeyHandler::ForwardKeyToInputMethodApp(nsINode* aTarget,
+                                               WidgetKeyboardEvent* aEvent,
+                                               nsEventStatus* aEventStatus)
+{
+  MOZ_ASSERT(aTarget, "No target provided");
+  MOZ_ASSERT(aEvent, "No event provided");
+
+  // No need to forward hardware key event to IME
+  // if key's defaultPrevented is true
+  if (aEvent->mFlags.mDefaultPrevented) {
+    return false;
+  }
+
+  // No need to forward hardware key event to IME if IME is disabled
+  if (!mInputMethodAppConnected) {
+    return false;
+  }
+
+  // No need to forward hardware key event to IME
+  // if this key event is generated by IME itself(from nsITextInputProcessor)
+  if (aEvent->mIsSynthesizedByTIP) {
+    return false;
+  }
+
+  // No need to forward hardware key event to IME
+  // if the key event is handling or already handled
+  if (aEvent->mInputMethodAppState != WidgetKeyboardEvent::eNotHandled) {
+    return false;
+  }
+
+  // No need to forward hardware key event to IME
+  // if there is no nsIHardwareKeyEventListener in use
+  nsCOMPtr<nsIHardwareKeyEventListener>
+    keyHandler(do_QueryReferent(mHardwareKeyEventListener));
+  if (!keyHandler) {
+    return false;
+  }
+
+  // Set the flags to specify the keyboard event is in forwarding phase.
+  aEvent->mInputMethodAppState = WidgetKeyboardEvent::eHandling;
+
+  // For those keypress events coming after their heading keydown's reply
+  // already arrives, they should be dispatched directly instead of
+  // being stored into the event queue. Otherwise, without the heading keydown
+  // in the event queue, the stored keypress will never be withdrawn to be fired.
+  if (aEvent->mMessage == eKeyPress && mEventQueue.IsEmpty()) {
+    DispatchKeyPress(aTarget, *aEvent, *aEventStatus);
+    return true;
+  }
+
+  // Push the key event into queue for reuse when its reply arrives.
+  KeyboardInfo* copiedInfo =
+    new KeyboardInfo(aTarget,
+                     *aEvent,
+                     aEventStatus ? *aEventStatus : nsEventStatus_eIgnore);
+
+  // No need to forward hardware key event to IME if the event queue is full
+  if (!mEventQueue.Push(copiedInfo)) {
+    delete copiedInfo;
+    return false;
+  }
+
+  // We only forward keydown and keyup event to input-method-app
+  // because input-method-app will generate keypress by itself.
+  if (aEvent->mMessage == eKeyPress) {
+    return true;
+  }
+
+  // Create a keyboard event to pass into
+  // nsIHardwareKeyEventListener.onHardwareKey
+  nsCOMPtr<EventTarget> eventTarget = do_QueryInterface(aTarget);
+  nsPresContext* presContext = GetPresContext(aTarget);
+  RefPtr<KeyboardEvent> keyboardEvent =
+    NS_NewDOMKeyboardEvent(eventTarget, presContext, aEvent->AsKeyboardEvent());
+  // Duplicate the internal event data in the heap for the keyboardEvent,
+  // or the internal data from |aEvent| in the stack may be destroyed by others.
+  keyboardEvent->DuplicatePrivateData();
+
+  // Forward the created keyboard event to input-method-app
+  bool isSent = false;
+  keyHandler->OnHardwareKey(keyboardEvent, &isSent);
+
+  // Pop the pending key event if it can't be forwarded
+  if (!isSent) {
+    mEventQueue.RemoveFront();
+  }
+
+  return isSent;
+}
+
+NS_IMETHODIMP
+HardwareKeyHandler::OnHandledByInputMethodApp(const nsAString& aType,
+                                              uint16_t aDefaultPrevented)
+{
+  // We can not handle this reply because the pending events had been already
+  // removed from the forwarding queue before this reply arrives.
+  if (mEventQueue.IsEmpty()) {
+    return NS_OK;
+  }
+
+  RefPtr<KeyboardInfo> keyInfo = mEventQueue.PopFront();
+
+  // Only allow keydown and keyup to call this method
+  if (NS_WARN_IF(aType.EqualsLiteral("keydown") &&
+                 keyInfo->mEvent.mMessage != eKeyDown) ||
+      NS_WARN_IF(aType.EqualsLiteral("keyup") &&
+                 keyInfo->mEvent.mMessage != eKeyUp)) {
+    return NS_ERROR_INVALID_ARG;
+  }
+
+  // The value of defaultPrevented depends on whether or not
+  // the key is consumed by input-method-app
+  SetDefaultPrevented(keyInfo->mEvent, aDefaultPrevented);
+
+  // Set the flag to specify the reply phase
+  keyInfo->mEvent.mInputMethodAppState = WidgetKeyboardEvent::eHandled;
+
+  // Check whether the event is still valid to be fired
+  if (CanDispatchEvent(keyInfo->mTarget, keyInfo->mEvent)) {
+    // If the key's defaultPrevented is true, it means that the
+    // input-method-app has already consumed this key,
+    // so we can dispatch |mozbrowserafterkey*| directly if
+    // preference "dom.beforeAfterKeyboardEvent.enabled" is enabled.
+    if (keyInfo->mEvent.mFlags.mDefaultPrevented) {
+      DispatchAfterKeyEvent(keyInfo->mTarget, keyInfo->mEvent);
+    // Otherwise, it means that input-method-app doesn't handle this key,
+    // so we need to dispatch it to its current event target.
+    } else {
+      DispatchToTargetApp(keyInfo->mTarget,
+                          keyInfo->mEvent,
+                          keyInfo->mStatus);
+    }
+  }
+
+  // No need to do further processing if the event is not keydown
+  if (keyInfo->mEvent.mMessage != eKeyDown) {
+    return NS_OK;
+  }
+
+  // Update the latest keydown data:
+  //   Release the holding reference to the previous keydown's data and
+  //   add a reference count to the current keydown's data.
+  mLatestKeyDownInfo = keyInfo;
+
+  // Handle the pending keypress event once keydown's reply arrives:
+  // It may have many keypress events per keydown on some platforms,
+  // so we use loop to dispatch keypress events.
+  // (But Gonk dispatch only one keypress per keydown)
+  // However, if there is no keypress after this keydown,
+  // then those following keypress will be handled in
+  // ForwardKeyToInputMethodApp directly.
+  for (KeyboardInfo* keypressInfo;
+       !mEventQueue.IsEmpty() &&
+       (keypressInfo = mEventQueue.PeekFront()) &&
+       keypressInfo->mEvent.mMessage == eKeyPress;
+       mEventQueue.RemoveFront()) {
+    DispatchKeyPress(keypressInfo->mTarget,
+                     keypressInfo->mEvent,
+                     keypressInfo->mStatus);
+  }
+
+  return NS_OK;
+}
+
+bool
+HardwareKeyHandler::DispatchKeyPress(nsINode* aTarget,
+                                     WidgetKeyboardEvent& aEvent,
+                                     nsEventStatus& aStatus)
+{
+  MOZ_ASSERT(aTarget, "No target provided");
+  MOZ_ASSERT(aEvent, "No event provided");
+  MOZ_ASSERT(aEvent.mMessage == eKeyPress, "Event is not keypress");
+
+  // No need to dispatch keypress to the event target
+  // if the keydown event is consumed by the input-method-app.
+  if (mLatestKeyDownInfo &&
+      mLatestKeyDownInfo->mEvent.mFlags.mDefaultPrevented) {
+    return false;
+  }
+
+  // No need to dispatch keypress to the event target
+  // if the previous keydown event is modifier key's
+  if (mLatestKeyDownInfo &&
+      mLatestKeyDownInfo->mEvent.IsModifierKeyEvent()) {
+    return false;
+  }
+
+  // No need to dispatch keypress to the event target
+  // if it's invalid to be dispatched
+  if (!CanDispatchEvent(aTarget, aEvent)) {
+    return false;
+  }
+
+  // Set the flag to specify the reply phase.
+  aEvent.mInputMethodAppState = WidgetKeyboardEvent::eHandled;
+
+  // Dispatch the pending keypress event
+  bool ret = DispatchToTargetApp(aTarget, aEvent, aStatus);
+
+  // Re-trigger EventStateManager::PostHandleKeyboardEvent for keypress
+  PostHandleKeyboardEvent(aTarget, aEvent, aStatus);
+
+  return ret;
+}
+
+void
+HardwareKeyHandler::DispatchAfterKeyEvent(nsINode* aTarget,
+                                          WidgetKeyboardEvent& aEvent)
+{
+  MOZ_ASSERT(aTarget, "No target provided");
+  MOZ_ASSERT(aEvent, "No event provided");
+
+  if (!PresShell::BeforeAfterKeyboardEventEnabled() ||
+      aEvent.mMessage == eKeyPress) {
+    return;
+  }
+
+  nsCOMPtr<nsIPresShell> presShell = GetPresShell(aTarget);
+  if (NS_WARN_IF(presShell)) {
+    presShell->DispatchAfterKeyboardEvent(aTarget,
+                                          aEvent,
+                                          aEvent.mFlags.mDefaultPrevented);
+  }
+}
+
+bool
+HardwareKeyHandler::DispatchToTargetApp(nsINode* aTarget,
+                                        WidgetKeyboardEvent& aEvent,
+                                        nsEventStatus& aStatus)
+{
+  MOZ_ASSERT(aTarget, "No target provided");
+  MOZ_ASSERT(aEvent, "No event provided");
+
+  // Get current focused element as the event target
+  nsCOMPtr<nsIContent> currentTarget = GetCurrentTarget();
+  if (NS_WARN_IF(!currentTarget)) {
+    return false;
+  }
+
+  // The event target should be set to the current focused element.
+  // However, it might have security issue if the event is dispatched to
+  // the unexpected application, and it might cause unexpected operation
+  // in the new app.
+  nsCOMPtr<nsPIDOMWindowOuter> originalRootWindow = GetRootWindow(aTarget);
+  nsCOMPtr<nsPIDOMWindowOuter> currentRootWindow = GetRootWindow(currentTarget);
+  if (currentRootWindow != originalRootWindow) {
+    NS_WARNING("The root window is changed during the event is dispatching");
+    return false;
+  }
+
+  // If the current focused element is still in the same app,
+  // then we can use it as the current target to dispatch event.
+  nsCOMPtr<nsIPresShell> presShell = GetPresShell(currentTarget);
+  if (!presShell) {
+    return false;
+  }
+
+  if (!presShell->CanDispatchEvent(&aEvent)) {
+    return false;
+  }
+
+  // In-process case: the event target is in the current process
+  if (!PresShell::IsTargetIframe(currentTarget)) {
+    DispatchToCurrentProcess(presShell, currentTarget, aEvent, aStatus);
+
+    if (presShell->CanDispatchEvent(&aEvent)) {
+      DispatchAfterKeyEvent(aTarget, aEvent);
+    }
+
+    return true;
+  }
+
+  // OOP case: the event target is in the child process
+  return DispatchToCrossProcess(aTarget, aEvent);
+
+  // After the oop target receives the event from TabChild::RecvRealKeyEvent
+  // and return the result through TabChild::SendDispatchAfterKeyboardEvent,
+  // the |mozbrowserafterkey*| will be fired from
+  // TabParent::RecvDispatchAfterKeyboardEvent, so we don't need to dispatch
+  // |mozbrowserafterkey*| by ourselves in this module.
+}
+
+void
+HardwareKeyHandler::DispatchToCurrentProcess(nsIPresShell* presShell,
+                                             nsIContent* aTarget,
+                                             WidgetKeyboardEvent& aEvent,
+                                             nsEventStatus& aStatus)
+{
+  EventDispatcher::Dispatch(aTarget, presShell->GetPresContext(),
+                            &aEvent, nullptr, &aStatus, nullptr);
+}
+
+bool
+HardwareKeyHandler::DispatchToCrossProcess(nsINode* aTarget,
+                                           WidgetKeyboardEvent& aEvent)
+{
+  nsCOMPtr<nsIFrameLoaderOwner> remoteLoaderOwner = do_QueryInterface(aTarget);
+  if (NS_WARN_IF(!remoteLoaderOwner)) {
+    return false;
+  }
+
+  RefPtr<nsFrameLoader> remoteFrameLoader =
+    remoteLoaderOwner->GetFrameLoader();
+  if (NS_WARN_IF(!remoteFrameLoader)) {
+    return false;
+  }
+
+  uint32_t eventMode;
+  remoteFrameLoader->GetEventMode(&eventMode);
+  if (eventMode == nsIFrameLoader::EVENT_MODE_DONT_FORWARD_TO_CHILD) {
+    return false;
+  }
+
+  PBrowserParent* remoteBrowser = remoteFrameLoader->GetRemoteBrowser();
+  TabParent* remote = static_cast<TabParent*>(remoteBrowser);
+  if (NS_WARN_IF(!remote)) {
+    return false;
+  }
+
+  return remote->SendRealKeyEvent(aEvent);
+}
+
+void
+HardwareKeyHandler::PostHandleKeyboardEvent(nsINode* aTarget,
+                                            WidgetKeyboardEvent& aEvent,
+                                            nsEventStatus& aStatus)
+{
+  MOZ_ASSERT(aTarget, "No target provided");
+  MOZ_ASSERT(aEvent, "No event provided");
+
+  nsPresContext* presContext = GetPresContext(aTarget);
+
+  RefPtr<mozilla::EventStateManager> esm = presContext->EventStateManager();
+  bool dispatchedToChildProcess = PresShell::IsTargetIframe(aTarget);
+  esm->PostHandleKeyboardEvent(&aEvent, aStatus, dispatchedToChildProcess);
+}
+
+void
+HardwareKeyHandler::SetDefaultPrevented(WidgetKeyboardEvent& aEvent,
+                                        uint16_t aDefaultPrevented) {
+  if (aDefaultPrevented & DEFAULT_PREVENTED) {
+    aEvent.mFlags.mDefaultPrevented = true;
+  }
+
+  if (aDefaultPrevented & DEFAULT_PREVENTED_BY_CHROME) {
+    aEvent.mFlags.mDefaultPreventedByChrome = true;
+  }
+
+  if (aDefaultPrevented & DEFAULT_PREVENTED_BY_CONTENT) {
+    aEvent.mFlags.mDefaultPreventedByContent = true;
+  }
+}
+
+bool
+HardwareKeyHandler::CanDispatchEvent(nsINode* aTarget,
+                                     WidgetKeyboardEvent& aEvent)
+{
+  nsCOMPtr<nsIPresShell> presShell = GetPresShell(aTarget);
+  if (NS_WARN_IF(!presShell)) {
+    return false;
+  }
+  return presShell->CanDispatchEvent(&aEvent);
+}
+
+already_AddRefed<nsPIDOMWindowOuter>
+HardwareKeyHandler::GetRootWindow(nsINode* aNode)
+{
+  // Get nsIPresShell's pointer first
+  nsCOMPtr<nsIPresShell> presShell = GetPresShell(aNode);
+  if (NS_WARN_IF(!presShell)) {
+    return nullptr;
+  }
+  nsCOMPtr<nsPIDOMWindowOuter> rootWindow = presShell->GetRootWindow();
+  return rootWindow.forget();
+}
+
+already_AddRefed<nsIContent>
+HardwareKeyHandler::GetCurrentTarget()
+{
+  nsFocusManager* fm = nsFocusManager::GetFocusManager();
+  if (NS_WARN_IF(!fm)) {
+    return nullptr;
+  }
+
+  nsCOMPtr<mozIDOMWindowProxy> focusedWindow;
+  fm->GetFocusedWindow(getter_AddRefs(focusedWindow));
+  if (NS_WARN_IF(!focusedWindow)) {
+    return nullptr;
+  }
+
+  auto* ourWindow = nsPIDOMWindowOuter::From(focusedWindow);
+
+  nsCOMPtr<nsPIDOMWindowOuter> rootWindow = ourWindow->GetPrivateRoot();
+  if (NS_WARN_IF(!rootWindow)) {
+    return nullptr;
+  }
+
+  nsCOMPtr<nsPIDOMWindowOuter> focusedFrame;
+  nsCOMPtr<nsIContent> focusedContent =
+    fm->GetFocusedDescendant(rootWindow, true, getter_AddRefs(focusedFrame));
+
+  // If there is no focus, then we use document body instead
+  if (NS_WARN_IF(!focusedContent || !focusedContent->GetPrimaryFrame())) {
+    nsIDocument* document = ourWindow->GetExtantDoc();
+    if (NS_WARN_IF(!document)) {
+      return nullptr;
+    }
+
+    focusedContent = document->GetRootElement();
+
+    nsCOMPtr<nsIDOMHTMLDocument> htmlDocument = do_QueryInterface(document);
+    if (htmlDocument) {
+      nsCOMPtr<nsIDOMHTMLElement> body;
+      htmlDocument->GetBody(getter_AddRefs(body));
+      nsCOMPtr<nsIContent> bodyContent = do_QueryInterface(body);
+      if (bodyContent) {
+        focusedContent = bodyContent;
+      }
+    }
+  }
+
+  return focusedContent ? focusedContent.forget() : nullptr;
+}
+
+nsPresContext*
+HardwareKeyHandler::GetPresContext(nsINode* aNode)
+{
+  // Get nsIPresShell's pointer first
+  nsCOMPtr<nsIPresShell> presShell = GetPresShell(aNode);
+  if (NS_WARN_IF(!presShell)) {
+    return nullptr;
+  }
+
+  // then use nsIPresShell to get nsPresContext's pointer
+  return presShell->GetPresContext();
+}
+
+already_AddRefed<nsIPresShell>
+HardwareKeyHandler::GetPresShell(nsINode* aNode)
+{
+  nsIDocument* doc = aNode->OwnerDoc();
+  if (NS_WARN_IF(!doc)) {
+    return nullptr;
+  }
+
+  nsCOMPtr<nsIPresShell> presShell = doc->GetShell();
+  if (NS_WARN_IF(!presShell)) {
+    return nullptr;
+  }
+
+  return presShell.forget();
+}
+
+/* static */
+already_AddRefed<HardwareKeyHandler>
+HardwareKeyHandler::GetInstance()
+{
+  if (!XRE_IsParentProcess()) {
+    return nullptr;
+  }
+
+  if (!sInstance) {
+    sInstance = new HardwareKeyHandler();
+    ClearOnShutdown(&sInstance);
+  }
+
+  RefPtr<HardwareKeyHandler> service = sInstance.get();
+  return service.forget();
+}
+
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/dom/inputmethod/HardwareKeyHandler.h
@@ -0,0 +1,222 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_HardwareKeyHandler_h_
+#define mozilla_HardwareKeyHandler_h_
+
+#include "mozilla/EventForwards.h"          // for nsEventStatus
+#include "mozilla/StaticPtr.h"
+#include "mozilla/TextEvents.h"
+#include "nsCOMPtr.h"
+#include "nsDeque.h"
+#include "nsIHardwareKeyHandler.h"
+#include "nsIWeakReferenceUtils.h"          // for nsWeakPtr
+
+class nsIContent;
+class nsINode;
+class nsIPresShell;
+class nsPIDOMWindowOuter;
+class nsPresContext;
+
+namespace mozilla {
+
+// This module will copy the events' data into its event queue for reuse
+// after receiving input-method-app's reply, so we use the following struct
+// for storing these information.
+// RefCounted<T> is a helper class for adding reference counting mechanism.
+struct KeyboardInfo : public RefCounted<KeyboardInfo>
+{
+  nsINode* mTarget;
+  WidgetKeyboardEvent mEvent;
+  nsEventStatus mStatus;
+
+  KeyboardInfo(nsINode* aTarget,
+               WidgetKeyboardEvent& aEvent,
+               nsEventStatus aStatus)
+    : mTarget(aTarget)
+    , mEvent(aEvent)
+    , mStatus(aStatus)
+  {
+  }
+};
+
+// The following is the type-safe wrapper around nsDeque
+// for storing events' data.
+// The T must be one class that supports reference counting mechanism.
+// The EventQueueDeallocator will be called in nsDeque::~nsDeque() or
+// nsDeque::Erase() to deallocate the objects. nsDeque::Erase() will remove
+// and delete all items in the queue. See more from nsDeque.h.
+template <class T>
+class EventQueueDeallocator : public nsDequeFunctor
+{
+  virtual void* operator() (void* aObject)
+  {
+    RefPtr<T> releaseMe = dont_AddRef(static_cast<T*>(aObject));
+    return nullptr;
+  }
+};
+
+// The type-safe queue to be used to store the KeyboardInfo data
+template <class T>
+class EventQueue : private nsDeque
+{
+public:
+  EventQueue()
+    : nsDeque(new EventQueueDeallocator<T>())
+  {
+  };
+
+  ~EventQueue()
+  {
+    Clear();
+  }
+
+  inline size_t GetSize()
+  {
+    return nsDeque::GetSize();
+  }
+
+  bool IsEmpty()
+  {
+    return !nsDeque::GetSize();
+  }
+
+  inline bool Push(T* aItem)
+  {
+    MOZ_ASSERT(aItem);
+    NS_ADDREF(aItem);
+    size_t sizeBefore = GetSize();
+    nsDeque::Push(aItem);
+    if (GetSize() != sizeBefore + 1) {
+      NS_RELEASE(aItem);
+      return false;
+    }
+    return true;
+  }
+
+  inline already_AddRefed<T> PopFront()
+  {
+    RefPtr<T> rv = dont_AddRef(static_cast<T*>(nsDeque::PopFront()));
+    return rv.forget();
+  }
+
+  inline void RemoveFront()
+  {
+    RefPtr<T> releaseMe = PopFront();
+  }
+
+  inline T* PeekFront()
+  {
+    return static_cast<T*>(nsDeque::PeekFront());
+  }
+
+  void Clear()
+  {
+    while (GetSize() > 0) {
+      RemoveFront();
+    }
+  }
+};
+
+class HardwareKeyHandler : public nsIHardwareKeyHandler
+{
+public:
+  HardwareKeyHandler();
+
+  NS_DECL_ISUPPORTS
+  NS_DECL_NSIHARDWAREKEYHANDLER
+
+  static already_AddRefed<HardwareKeyHandler> GetInstance();
+
+  virtual bool ForwardKeyToInputMethodApp(nsINode* aTarget,
+                                          WidgetKeyboardEvent* aEvent,
+                                          nsEventStatus* aEventStatus) override;
+
+private:
+  virtual ~HardwareKeyHandler();
+
+  // Return true if the keypress is successfully dispatched.
+  // Otherwise, return false.
+  bool DispatchKeyPress(nsINode* aTarget,
+                        WidgetKeyboardEvent& aEvent,
+                        nsEventStatus& aStatus);
+
+  void DispatchAfterKeyEvent(nsINode* aTarget, WidgetKeyboardEvent& aEvent);
+
+  void DispatchToCurrentProcess(nsIPresShell* aPresShell,
+                                nsIContent* aTarget,
+                                WidgetKeyboardEvent& aEvent,
+                                nsEventStatus& aStatus);
+
+  bool DispatchToCrossProcess(nsINode* aTarget, WidgetKeyboardEvent& aEvent);
+
+  // This method will dispatch not only key* event to its event target,
+  // no mather it's in the current process or in its child process,
+  // but also mozbrowserafterkey* to the corresponding target if it needs.
+  // Return true if the key is successfully dispatched.
+  // Otherwise, return false.
+  bool DispatchToTargetApp(nsINode* aTarget,
+                           WidgetKeyboardEvent& aEvent,
+                           nsEventStatus& aStatus);
+
+  // This method will be called after dispatching keypress to its target,
+  // if the input-method-app doesn't handle the key.
+  // In normal dispatching path, EventStateManager::PostHandleKeyboardEvent
+  // will be called when event is keypress.
+  // However, the ::PostHandleKeyboardEvent mentioned above will be aborted
+  // when we try to forward key event to the input-method-app.
+  // If the input-method-app consumes the key, then we don't need to do anything
+  // because the input-method-app will generate a new key event by itself.
+  // On the other hand, if the input-method-app doesn't consume the key,
+  // then we need to dispatch the key event by ourselves
+  // and call ::PostHandleKeyboardEvent again after the event is forwarded.
+  // Note that the EventStateManager::PreHandleEvent is already called before
+  // forwarding, so we don't need to call it in this module.
+  void PostHandleKeyboardEvent(nsINode* aTarget,
+                               WidgetKeyboardEvent& aEvent,
+                               nsEventStatus& aStatus);
+
+  void SetDefaultPrevented(WidgetKeyboardEvent& aEvent,
+                           uint16_t aDefaultPrevented);
+
+  // Check whether the event is valid to be fired.
+  // This method should be called every time before dispatching next event.
+  bool CanDispatchEvent(nsINode* aTarget,
+                        WidgetKeyboardEvent& aEvent);
+
+  already_AddRefed<nsPIDOMWindowOuter> GetRootWindow(nsINode* aNode);
+
+  already_AddRefed<nsIContent> GetCurrentTarget();
+
+  nsPresContext* GetPresContext(nsINode* aNode);
+
+  already_AddRefed<nsIPresShell> GetPresShell(nsINode* aNode);
+
+  static StaticRefPtr<HardwareKeyHandler> sInstance;
+
+  // The event queue is used to store the forwarded keyboard events.
+  // Those stored events will be dispatched if input-method-app doesn't
+  // consume them.
+  EventQueue<KeyboardInfo> mEventQueue;
+
+  // Hold the pointer to the latest keydown's data
+  RefPtr<KeyboardInfo> mLatestKeyDownInfo;
+
+  // input-method-app needs to register a listener by
+  // |nsIHardwareKeyHandler.registerListener| to receive
+  // the hardware keyboard event, and |nsIHardwareKeyHandler.registerListener|
+  // will set an nsIHardwareKeyEventListener to mHardwareKeyEventListener.
+  // Then, mHardwareKeyEventListener is used to forward the event
+  // to the input-method-app.
+  nsWeakPtr mHardwareKeyEventListener;
+
+  // To keep tracking the input-method-app is active or disabled.
+  bool mInputMethodAppConnected;
+};
+
+} // namespace mozilla
+
+#endif // #ifndef mozilla_HardwareKeyHandler_h_
--- a/dom/inputmethod/Keyboard.jsm
+++ b/dom/inputmethod/Keyboard.jsm
@@ -18,16 +18,25 @@ XPCOMUtils.defineLazyServiceGetter(this,
 
 XPCOMUtils.defineLazyModuleGetter(this, "SystemAppProxy",
                                   "resource://gre/modules/SystemAppProxy.jsm");
 
 XPCOMUtils.defineLazyGetter(this, "appsService", function() {
   return Cc["@mozilla.org/AppsService;1"].getService(Ci.nsIAppsService);
 });
 
+XPCOMUtils.defineLazyGetter(this, "hardwareKeyHandler", function() {
+#ifdef MOZ_B2G
+  return Cc["@mozilla.org/HardwareKeyHandler;1"]
+         .getService(Ci.nsIHardwareKeyHandler);
+#else
+  return null;
+#endif
+});
+
 var Utils = {
   getMMFromMessage: function u_getMMFromMessage(msg) {
     let mm;
     try {
       mm = msg.target.QueryInterface(Ci.nsIFrameLoaderOwner)
                      .frameLoader.messageManager;
     } catch(e) {
       mm = msg.target;
@@ -36,16 +45,28 @@ var Utils = {
     return mm;
   },
   checkPermissionForMM: function u_checkPermissionForMM(mm, permName) {
     return mm.assertPermission(permName);
   }
 };
 
 this.Keyboard = {
+#ifdef MOZ_B2G
+  // For receving keyboard event fired from hardware before it's dispatched,
+  // |this| object is used to be the listener to get the forwarded event.
+  // As the listener, |this| object must implement nsIHardwareKeyEventListener
+  // and nsSupportsWeakReference.
+  // Please see nsIHardwareKeyHandler.idl to get more information.
+  QueryInterface: XPCOMUtils.generateQI([
+    Ci.nsIHardwareKeyEventListener,
+    Ci.nsISupportsWeakReference
+  ]),
+#endif
+  _isConnectedToHardwareKeyHandler: false,
   _formMM: null,      // The current web page message manager.
   _keyboardMM: null,  // The keyboard app message manager.
   _keyboardID: -1,    // The keyboard app's ID number. -1 = invalid
   _nextKeyboardID: 0, // The ID number counter.
   _systemMMs: [],     // The message managers registered to handle system async
                       // messages.
   _supportsSwitchingTypes: [],
   _systemMessageNames: [
@@ -54,17 +75,18 @@ this.Keyboard = {
   ],
 
   _messageNames: [
     'RemoveFocus',
     'SetSelectionRange', 'ReplaceSurroundingText', 'ShowInputMethodPicker',
     'SwitchToNextInputMethod', 'HideInputMethod',
     'SendKey', 'GetContext',
     'SetComposition', 'EndComposition',
-    'RegisterSync', 'Unregister'
+    'RegisterSync', 'Unregister',
+    'ReplyHardwareKeyEvent'
   ],
 
   get formMM() {
     if (this._formMM && !Cu.isDeadWrapper(this._formMM))
       return this._formMM;
 
     return null;
   },
@@ -83,17 +105,20 @@ this.Keyboard = {
     try {
       this.formMM.sendAsyncMessage(name, data);
     } catch(e) { }
   },
 
   sendToKeyboard: function(name, data) {
     try {
       this._keyboardMM.sendAsyncMessage(name, data);
-    } catch(e) { }
+    } catch(e) {
+      return false;
+    }
+    return true;
   },
 
   sendToSystem: function(name, data) {
     if (!this._systemMMs.length) {
       dump("Keyboard.jsm: Attempt to send message " + name +
         " to system but no message manager registered.\n");
 
       return;
@@ -106,27 +131,42 @@ this.Keyboard = {
   },
 
   init: function keyboardInit() {
     Services.obs.addObserver(this, 'inprocess-browser-shown', false);
     Services.obs.addObserver(this, 'remote-browser-shown', false);
     Services.obs.addObserver(this, 'oop-frameloader-crashed', false);
     Services.obs.addObserver(this, 'message-manager-close', false);
 
+    // For receiving the native hardware keyboard event
+    if (hardwareKeyHandler) {
+      hardwareKeyHandler.registerListener(this);
+    }
+
     for (let name of this._messageNames) {
       ppmm.addMessageListener('Keyboard:' + name, this);
     }
 
     for (let name of this._systemMessageNames) {
       ppmm.addMessageListener('System:' + name, this);
     }
 
     this.inputRegistryGlue = new InputRegistryGlue();
   },
 
+  // This method will be registered into nsIHardwareKeyHandler:
+  // Send the initialized dictionary retrieved from the native keyboard event
+  // to input-method-app for generating a new event.
+  onHardwareKey: function onHardwareKeyReceived(evt) {
+    return this.sendToKeyboard('Keyboard:ReceiveHardwareKeyEvent', {
+      type: evt.type,
+      keyDict: evt.initDict
+    });
+  },
+
   observe: function keyboardObserve(subject, topic, data) {
     let frameLoader = null;
     let mm = null;
 
     if (topic == 'message-manager-close') {
       mm = subject;
     } else {
       frameLoader = subject.QueryInterface(Ci.nsIFrameLoader);
@@ -310,25 +350,38 @@ this.Keyboard = {
           // and we want to return the id back to inputmethod
           return this._keyboardID;
         }
         break;
       case 'Keyboard:Unregister':
         this._keyboardMM = null;
         this._keyboardID = -1;
         break;
+      case 'Keyboard:ReplyHardwareKeyEvent':
+        if (hardwareKeyHandler) {
+          let reply = msg.data;
+          hardwareKeyHandler.onHandledByInputMethodApp(reply.type,
+                                                       reply.defaultPrevented);
+        }
+        break;
     }
   },
 
   handleFocus: function keyboardHandleFocus(msg) {
     // Set the formMM to the new message manager received.
     let mm = msg.target.QueryInterface(Ci.nsIFrameLoaderOwner)
                 .frameLoader.messageManager;
     this.formMM = mm;
 
+    // Notify the nsIHardwareKeyHandler that the input-method-app is active now.
+    if (hardwareKeyHandler && !this._isConnectedToHardwareKeyHandler) {
+      this._isConnectedToHardwareKeyHandler = true;
+      hardwareKeyHandler.onInputMethodAppConnected();
+    }
+
     // Notify the current active input app to gain focus.
     this.forwardEvent('Keyboard:Focus', msg);
 
     // Notify System app, used also to render value selectors for now;
     // that's why we need the info about choices / min / max here as well...
     this.sendToSystem('System:Focus', msg.data);
 
     // XXX: To be removed when content migrate away from mozChromeEvents.
@@ -351,16 +404,23 @@ this.Keyboard = {
     // ipc messages from two processes.
     if (mm !== this.formMM) {
       return;
     }
 
     // unset formMM
     this.formMM = null;
 
+    // Notify the nsIHardwareKeyHandler that
+    // the input-method-app is disabled now.
+    if (hardwareKeyHandler && this._isConnectedToHardwareKeyHandler) {
+      this._isConnectedToHardwareKeyHandler = false;
+      hardwareKeyHandler.onInputMethodAppDisconnected();
+    }
+
     this.forwardEvent('Keyboard:Blur', msg);
     this.sendToSystem('System:Blur', {});
 
     // XXX: To be removed when content migrate away from mozChromeEvents.
     SystemAppProxy.dispatchEvent({
       type: 'inputmethod-contextchange',
       inputType: 'blur'
     });
--- a/dom/inputmethod/MozKeyboard.js
+++ b/dom/inputmethod/MozKeyboard.js
@@ -426,16 +426,17 @@ MozInputMethod.prototype = {
 
     Services.obs.addObserver(this, "inner-window-destroyed", false);
 
     cpmm.addWeakMessageListener('Keyboard:Focus', this);
     cpmm.addWeakMessageListener('Keyboard:Blur', this);
     cpmm.addWeakMessageListener('Keyboard:SelectionChange', this);
     cpmm.addWeakMessageListener('Keyboard:GetContext:Result:OK', this);
     cpmm.addWeakMessageListener('Keyboard:SupportsSwitchingTypesChange', this);
+    cpmm.addWeakMessageListener('Keyboard:ReceiveHardwareKeyEvent', this);
     cpmm.addWeakMessageListener('InputRegistry:Result:OK', this);
     cpmm.addWeakMessageListener('InputRegistry:Result:Error', this);
 
     if (this._hasInputManagePerm(win)) {
       this._inputManageId = cpmm.sendSyncMessage('System:RegisterSync', {})[0];
       cpmm.addWeakMessageListener('System:Focus', this);
       cpmm.addWeakMessageListener('System:Blur', this);
       cpmm.addWeakMessageListener('System:ShowAll', this);
@@ -450,16 +451,17 @@ MozInputMethod.prototype = {
     this._mgmt = null;
     this._wrappedMgmt = null;
 
     cpmm.removeWeakMessageListener('Keyboard:Focus', this);
     cpmm.removeWeakMessageListener('Keyboard:Blur', this);
     cpmm.removeWeakMessageListener('Keyboard:SelectionChange', this);
     cpmm.removeWeakMessageListener('Keyboard:GetContext:Result:OK', this);
     cpmm.removeWeakMessageListener('Keyboard:SupportsSwitchingTypesChange', this);
+    cpmm.removeWeakMessageListener('Keyboard:ReceiveHardwareKeyEvent', this);
     cpmm.removeWeakMessageListener('InputRegistry:Result:OK', this);
     cpmm.removeWeakMessageListener('InputRegistry:Result:Error', this);
     this.setActive(false);
 
     if (typeof this._inputManageId === 'number') {
       cpmm.sendAsyncMessage('System:Unregister', {
         'id': this._inputManageId
       });
@@ -503,17 +505,34 @@ MozInputMethod.prototype = {
         }
         break;
       case 'Keyboard:GetContext:Result:OK':
         this.setInputContext(data);
         break;
       case 'Keyboard:SupportsSwitchingTypesChange':
         this._supportsSwitchingTypes = data.types;
         break;
+      case 'Keyboard:ReceiveHardwareKeyEvent':
+        if (!Ci.nsIHardwareKeyHandler) {
+          break;
+        }
 
+        let defaultPrevented = Ci.nsIHardwareKeyHandler.NO_DEFAULT_PREVENTED;
+
+        // |event.preventDefault()| is allowed to be called only when
+        // |event.cancelable| is true
+        if (this._inputcontext && data.keyDict.cancelable) {
+          defaultPrevented |= this._inputcontext.forwardHardwareKeyEvent(data);
+        }
+
+        cpmmSendAsyncMessageWithKbID(this, 'Keyboard:ReplyHardwareKeyEvent', {
+                                       type: data.type,
+                                       defaultPrevented: defaultPrevented
+                                     });
+        break;
       case 'InputRegistry:Result:OK':
         resolver.resolve();
 
         break;
 
       case 'InputRegistry:Result:Error':
         resolver.reject(data.error);
 
@@ -679,17 +698,17 @@ MozInputMethod.prototype = {
   _hasInputManagePerm: function(win) {
     let principal = win.document.nodePrincipal;
     let perm = Services.perms.testExactPermissionFromPrincipal(principal,
                                                                "input-manage");
     return (perm === Ci.nsIPermissionManager.ALLOW_ACTION);
   }
 };
 
- /**
+/**
  * ==============================================
  * InputContextDOMRequestIpcHelper
  * ==============================================
  */
 function InputContextDOMRequestIpcHelper(win) {
   this.initDOMRequestHelper(win,
     ["Keyboard:GetText:Result:OK",
      "Keyboard:GetText:Result:Error",
@@ -778,17 +797,30 @@ MozInputContextSurroundingTextChangeEven
     return this._ctx.textBeforeCursor;
   },
 
   get textAfterCursor() {
     return this._ctx.textAfterCursor;
   }
 };
 
- /**
+/**
+ * ==============================================
+ * HardwareInput
+ * ==============================================
+ */
+function MozHardwareInput() {
+}
+
+MozHardwareInput.prototype = {
+  classID: Components.ID("{1e38633d-d08b-4867-9944-afa5c648adb6}"),
+  QueryInterface: XPCOMUtils.generateQI([]),
+};
+
+/**
  * ==============================================
  * InputContext
  * ==============================================
  */
 function MozInputContext(data) {
   this._context = {
     type: data.type,
     inputType: data.inputType,
@@ -802,29 +834,34 @@ function MozInputContext(data) {
   this._contextId = data.contextId;
 }
 
 MozInputContext.prototype = {
   _window: null,
   _context: null,
   _contextId: -1,
   _ipcHelper: null,
+  _hardwareinput: null,
+  _wrappedhardwareinput: null,
 
   classID: Components.ID("{1e38633d-d08b-4867-9944-afa5c648adb6}"),
 
   QueryInterface: XPCOMUtils.generateQI([
     Ci.nsIObserver,
     Ci.nsISupportsWeakReference
   ]),
 
   init: function ic_init(win) {
     this._window = win;
 
     this._ipcHelper = WindowMap.getInputContextIpcHelper(win);
     this._ipcHelper.attachInputContext(this);
+    this._hardwareinput = new MozHardwareInput();
+    this._wrappedhardwareinput =
+      this._window.MozHardwareInput._create(this._window, this._hardwareinput);
   },
 
   destroy: function ic_destroy() {
     // A consuming application might still hold a cached version of
     // this object. After destroying all methods will throw because we
     // cannot create new promises anymore, but we still hold
     // (outdated) information in the context. So let's clear that out.
     for (var k in this._context) {
@@ -832,16 +869,18 @@ MozInputContext.prototype = {
         this._context[k] = null;
       }
     }
 
     this._ipcHelper.detachInputContext();
     this._ipcHelper = null;
 
     this._window = null;
+    this._hardwareinput = null;
+    this._wrappedhardwareinput = null;
   },
 
   receiveMessage: function ic_receiveMessage(msg) {
     if (!msg || !msg.json) {
       dump('InputContext received message without data\n');
       return;
     }
 
@@ -984,16 +1023,20 @@ MozInputContext.prototype = {
 
   get textAfterCursor() {
     let text = this._context.text;
     let start = this._context.selectionStart;
     let end = this._context.selectionEnd;
     return text.substr(start, end - start + 100);
   },
 
+  get hardwareinput() {
+    return this._wrappedhardwareinput;
+  },
+
   setSelectionRange: function ic_setSelectionRange(start, length) {
     let self = this;
     return this._sendPromise(function(resolverId) {
       cpmmSendAsyncMessageWithKbID(self, 'Keyboard:SetSelectionRange', {
         contextId: self._contextId,
         requestId: resolverId,
         selectionStart: start,
         selectionEnd: start + length
@@ -1106,16 +1149,55 @@ MozInputContext.prototype = {
         contextId: self._contextId,
         requestId: resolverId,
         text: text || '',
         keyboardEventDict: this._getkeyboardEventDict(dict)
       });
     });
   },
 
+  // Generate a new keyboard event by the received keyboard dictionary
+  // and return defaultPrevented's result of the event after dispatching.
+  forwardHardwareKeyEvent: function ic_forwardHardwareKeyEvent(data) {
+    if (!Ci.nsIHardwareKeyHandler) {
+      return;
+    }
+
+    if (!this._context) {
+      return Ci.nsIHardwareKeyHandler.NO_DEFAULT_PREVENTED;
+    }
+    let evt = new this._window.KeyboardEvent(data.type,
+                                             Cu.cloneInto(data.keyDict,
+                                                          this._window));
+    this._hardwareinput.__DOM_IMPL__.dispatchEvent(evt);
+    return this._getDefaultPreventedValue(evt);
+  },
+
+  _getDefaultPreventedValue: function(evt) {
+    if (!Ci.nsIHardwareKeyHandler) {
+      return;
+    }
+
+    let flags = Ci.nsIHardwareKeyHandler.NO_DEFAULT_PREVENTED;
+
+    if (evt.defaultPrevented) {
+      flags |= Ci.nsIHardwareKeyHandler.DEFAULT_PREVENTED;
+    }
+
+    if (evt.defaultPreventedByChrome) {
+      flags |= Ci.nsIHardwareKeyHandler.DEFAULT_PREVENTED_BY_CHROME;
+    }
+
+    if (evt.defaultPreventedByContent) {
+      flags |= Ci.nsIHardwareKeyHandler.DEFAULT_PREVENTED_BY_CONTENT;
+    }
+
+    return flags;
+  },
+
   _sendPromise: function(callback) {
     let self = this;
     return this._ipcHelper.createPromiseWithId(function(aResolverId) {
       if (!WindowMap.isActive(self._window)) {
         self._ipcHelper.removePromiseResolver(aResolverId);
         reject('Input method is not active.');
         return;
       }
--- a/dom/inputmethod/moz.build
+++ b/dom/inputmethod/moz.build
@@ -1,18 +1,41 @@
 # -*- 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/.
 
+if CONFIG['MOZ_B2G']:
+    XPIDL_SOURCES += [
+        'nsIHardwareKeyHandler.idl',
+    ]
+
+    XPIDL_MODULE = 'inputmethod'
+
+    EXPORTS.mozilla += [
+        'HardwareKeyHandler.h',
+    ]
+
+    SOURCES += [
+        'HardwareKeyHandler.cpp'
+    ]
+
+    include('/ipc/chromium/chromium-config.mozbuild')
+
+    FINAL_LIBRARY = 'xul'
+    LOCAL_INCLUDES += [
+        '/dom/base',
+        '/layout/base',
+    ]
+
 EXTRA_COMPONENTS += [
     'InputMethod.manifest',
     'MozKeyboard.js',
 ]
 
-EXTRA_JS_MODULES += [
+EXTRA_PP_JS_MODULES += [
     'Keyboard.jsm',
 ]
 
 JAR_MANIFESTS += ['jar.mn']
 
 MOCHITEST_MANIFESTS += ['mochitest/mochitest.ini']
new file mode 100644
--- /dev/null
+++ b/dom/inputmethod/nsIHardwareKeyHandler.idl
@@ -0,0 +1,142 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIDOMKeyEvent;
+
+%{C++
+#define NS_HARDWARE_KEY_HANDLER_CID \
+  { 0xfb45921b, 0xe0a5, 0x45c6, \
+    { 0x90, 0xd0, 0xa6, 0x97, 0xa7, 0x72, 0xc4, 0x2a } }
+#define NS_HARDWARE_KEY_HANDLER_CONTRACTID \
+  "@mozilla.org/HardwareKeyHandler;1"
+
+#include "mozilla/EventForwards.h" /* For nsEventStatus */
+
+namespace mozilla {
+class WidgetKeyboardEvent;
+}
+
+using mozilla::WidgetKeyboardEvent;
+
+class nsINode;
+%}
+
+/**
+ * This interface is used to be registered to the nsIHardwareKeyHandler through
+ * |nsIHardwareKeyHandler.registerListener|.
+ */
+[scriptable, function, uuid(cd5aeee3-b4b9-459d-85e7-c0671c7a8a2e)]
+interface nsIHardwareKeyEventListener : nsISupports
+{
+  /**
+   * This method will be invoked by nsIHardwareKeyHandler to forward the native
+   * keyboard event to the active input method
+   */
+  bool onHardwareKey(in nsIDOMKeyEvent aEvent);
+};
+
+/**
+ * This interface has two main roles. One is to send a hardware keyboard event
+ * to the active input method app and the other is to receive its reply result.
+ * If a keyboard event is triggered from a hardware keyboard when an editor has
+ * focus, the event target should be the editor. However, the text input
+ * processor algorithm is implemented in an input method app and it should
+ * handle the event earlier than the real event target to do the mapping such
+ * as character conversion according to the language setting or the type of a
+ * hardware keyboard.
+ */
+[scriptable, builtinclass, uuid(25b34270-caad-4d18-a910-860351690639)]
+interface nsIHardwareKeyHandler : nsISupports
+{
+  /**
+   * Flags used to set the defaultPrevented's result. The default result
+   * from input-method-app should be set to NO_DEFAULT_PREVENTED.
+   * (It means the forwarded event isn't consumed by input-method-app.)
+   * If the input-method-app consumes the forwarded event,
+   * then the result should be set by DEFAULT_PREVENTED* before reply.
+   */
+  const unsigned short NO_DEFAULT_PREVENTED           = 0x0000;
+  const unsigned short DEFAULT_PREVENTED              = 0x0001;
+  const unsigned short DEFAULT_PREVENTED_BY_CHROME    = 0x0002;
+  const unsigned short DEFAULT_PREVENTED_BY_CONTENT   = 0x0004;
+
+  /**
+   * Registers a listener in input-method-app to receive
+   * the forwarded hardware keyboard events
+   *
+   * @param aListener             Listener object to be notified for receiving
+   *                              the keyboard event fired from hardware
+   * @note                        A listener object must implement
+   *                              nsIHardwareKeyEventListener and
+   *                              nsSupportsWeakReference
+   * @see nsIHardwareKeyEventListener
+   * @see nsSupportsWeakReference
+   */
+  void registerListener(in nsIHardwareKeyEventListener aListener);
+
+  /**
+   * Unregisters the current listener from input-method-app
+   */
+  void unregisterListener();
+
+  /**
+   * Notifies nsIHardwareKeyHandler that input-method-app is active.
+   */
+  void onInputMethodAppConnected();
+
+  /**
+   * Notifies nsIHardwareKeyHandler that input-method-app is disabled.
+   */
+  void onInputMethodAppDisconnected();
+
+  /**
+   * Input-method-app will pass the processing result that the forwarded
+   * event is handled or not through this method, and the nsIHardwareKeyHandler
+   * can use this to receive the reply of |forwardKeyToInputMethodApp|
+   * from the active input method.
+   *
+   * The result should contain the original event type and the info whether
+   * the default is prevented, also, it is prevented by chrome or content.
+   *
+   * @param aEventType            The type of an original event.
+   * @param aDefaultPrevented     State that |evt.preventDefault|
+   *                              is called by content, chrome or not.
+   */
+  void onHandledByInputMethodApp(in DOMString aType,
+                                 in unsigned short aDefaultPrevented);
+
+  /**
+   * Sends the native keyboard events triggered from hardware to the
+   * active input method before dispatching to its event target.
+   * This method only forwards keydown and keyup events.
+   * If the event isn't allowed to be forwarded, we should continue the
+   * normal event processing. For those forwarded keydown and keyup events
+   * We will pause the further event processing to wait for the completion
+   * of the event handling in the active input method app.
+   * Once |onHandledByInputMethodApp| is called by the input method app,
+   * the pending event processing can be resumed according to its reply.
+   * On the other hand, the keypress will never be sent to the input-method-app.
+   * Depending on whether the keydown's reply arrives before the keypress event
+   * comes, the keypress event will be handled directly or pushed into
+   * the event queue to wait for its heading keydown's reply.
+   *
+   * This implementation will call |nsIHardwareKeyEventListener.onHardwareKey|,
+   * which is registered through |nsIHardwareKeyEventListener.registerListener|,
+   * to forward the events.
+   *
+   * Returns true, if the event is handled in this module.
+   * Returns false, otherwise.
+   *
+   * If it returns false, we should continue the normal event processing.
+   */
+  %{C++
+  virtual bool ForwardKeyToInputMethodApp(nsINode* aTarget,
+                                          WidgetKeyboardEvent* aEvent,
+                                          nsEventStatus* aEventStatus) = 0;
+  %}
+};
--- a/dom/interfaces/push/moz.build
+++ b/dom/interfaces/push/moz.build
@@ -1,12 +1,13 @@
 # -*- 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/.
 
 XPIDL_SOURCES += [
+    'nsIPushErrorReporter.idl',
     'nsIPushNotifier.idl',
     'nsIPushService.idl',
 ]
 
 XPIDL_MODULE = 'dom_push'
new file mode 100644
--- /dev/null
+++ b/dom/interfaces/push/nsIPushErrorReporter.idl
@@ -0,0 +1,46 @@
+/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+[scriptable, uuid(b58249f9-1a04-48cc-bc20-2c992d64c73e)]
+interface nsIPushErrorReporter : nsISupports
+{
+  /**
+   * Ack status codes, reported when the Push service acknowledges an incoming
+   * message.
+   *
+   * Acks are sent before the message is dispatched to the service worker,
+   * since the server delays new messages until all outstanding ones have been
+   * acked. |reportDeliveryError| will be called if an error occurs in the
+   * worker's `push` event handler after acking the message.
+  */
+  const uint16_t ACK_DELIVERED = 0;
+  const uint16_t ACK_DECRYPTION_ERROR = 1;
+  const uint16_t ACK_NOT_DELIVERED = 2;
+
+  /**
+   * Unsubscribe reasons, reported when the service drops a subscription.
+   */
+  const uint16_t UNSUBSCRIBE_MANUAL = 3;
+  const uint16_t UNSUBSCRIBE_QUOTA_EXCEEDED = 4;
+  const uint16_t UNSUBSCRIBE_PERMISSION_REVOKED = 5;
+
+  /**
+   * Delivery error reasons, reported when a service worker fails to handle
+   * an incoming push message in its `push` event handler.
+   */
+  const uint16_t DELIVERY_UNCAUGHT_EXCEPTION = 6;
+  const uint16_t DELIVERY_UNHANDLED_REJECTION = 7;
+  const uint16_t DELIVERY_INTERNAL_ERROR = 8;
+
+  /**
+   * Reports a `push` event handler error to the Push service. |messageId| is
+   * an opaque string passed to `nsIPushNotifier.notifyPush{WithData}`.
+   * |status| is a delivery error reason.
+   */
+  void reportDeliveryError(in DOMString messageId,
+                           [optional] in uint16_t reason);
+};
--- a/dom/interfaces/push/nsIPushNotifier.idl
+++ b/dom/interfaces/push/nsIPushNotifier.idl
@@ -1,37 +1,47 @@
+/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
 #include "nsISupports.idl"
 
 interface nsIPrincipal;
 
 /**
  * Fires service worker events for push messages sent to content subscriptions,
  * and XPCOM observer notifications for system subscriptions. This service
  * can only be used from the parent process.
  */
-[scriptable, uuid(b00dfdeb-14e5-425b-adc7-b531442e3216)]
+[scriptable, builtinclass, uuid(b00dfdeb-14e5-425b-adc7-b531442e3216)]
 interface nsIPushNotifier : nsISupports
 {
-  void notifyPush(in ACString scope, in nsIPrincipal principal);
+  void notifyPush(in ACString scope, in nsIPrincipal principal,
+                  in DOMString messageId);
 
   void notifyPushWithData(in ACString scope, in nsIPrincipal principal,
+                          in DOMString messageId,
                           [optional] in uint32_t dataLen,
                           [array, size_is(dataLen)] in uint8_t data);
 
   void notifySubscriptionChange(in ACString scope, in nsIPrincipal principal);
+
+  void notifyError(in ACString scope, in nsIPrincipal principal,
+                   in DOMString message, in uint32_t flags);
 };
 
 /**
  * A push message sent to a system subscription, used as the subject of a
  * `push-message` observer notification. System subscriptions are created by
  * the system principal, and do not use worker events.
  *
  * This interface resembles the `PushMessageData` WebIDL interface.
  */
-[scriptable, uuid(136dc8fd-8c56-4176-9170-eaa86b6ba99e)]
+[scriptable, builtinclass, uuid(136dc8fd-8c56-4176-9170-eaa86b6ba99e)]
 interface nsIPushMessage : nsISupports
 {
   /** Extracts the data as a UTF-8 text string. */
   DOMString text();
 
   /** Extracts the data as a JSON value. */
   [implicit_jscontext] jsval json();
 
--- a/dom/ipc/ContentChild.cpp
+++ b/dom/ipc/ContentChild.cpp
@@ -3268,47 +3268,50 @@ ContentChild::RecvEndDragSession(const b
     }
     dragService->EndDragSession(aDoneDrag);
   }
   return true;
 }
 
 bool
 ContentChild::RecvPush(const nsCString& aScope,
-                       const IPC::Principal& aPrincipal)
+                       const IPC::Principal& aPrincipal,
+                       const nsString& aMessageId)
 {
 #ifndef MOZ_SIMPLEPUSH
   nsCOMPtr<nsIPushNotifier> pushNotifierIface =
       do_GetService("@mozilla.org/push/Notifier;1");
   if (NS_WARN_IF(!pushNotifierIface)) {
       return true;
   }
   PushNotifier* pushNotifier =
     static_cast<PushNotifier*>(pushNotifierIface.get());
-  nsresult rv = pushNotifier->NotifyPushWorkers(aScope, aPrincipal, Nothing());
+  nsresult rv = pushNotifier->NotifyPushWorkers(aScope, aPrincipal,
+                                                aMessageId, Nothing());
   Unused << NS_WARN_IF(NS_FAILED(rv));
 #endif
   return true;
 }
 
 bool
 ContentChild::RecvPushWithData(const nsCString& aScope,
                                const IPC::Principal& aPrincipal,
+                               const nsString& aMessageId,
                                InfallibleTArray<uint8_t>&& aData)
 {
 #ifndef MOZ_SIMPLEPUSH
   nsCOMPtr<nsIPushNotifier> pushNotifierIface =
       do_GetService("@mozilla.org/push/Notifier;1");
   if (NS_WARN_IF(!pushNotifierIface)) {
       return true;
   }
   PushNotifier* pushNotifier =
     static_cast<PushNotifier*>(pushNotifierIface.get());
   nsresult rv = pushNotifier->NotifyPushWorkers(aScope, aPrincipal,
-                                                Some(aData));
+                                                aMessageId, Some(aData));
   Unused << NS_WARN_IF(NS_FAILED(rv));
 #endif
   return true;
 }
 
 bool
 ContentChild::RecvPushSubscriptionChange(const nsCString& aScope,
                                          const IPC::Principal& aPrincipal)
@@ -3323,10 +3326,27 @@ ContentChild::RecvPushSubscriptionChange
     static_cast<PushNotifier*>(pushNotifierIface.get());
   nsresult rv = pushNotifier->NotifySubscriptionChangeWorkers(aScope,
                                                               aPrincipal);
   Unused << NS_WARN_IF(NS_FAILED(rv));
 #endif
   return true;
 }
 
+bool
+ContentChild::RecvPushError(const nsCString& aScope, const nsString& aMessage,
+                            const uint32_t& aFlags)
+{
+#ifndef MOZ_SIMPLEPUSH
+  nsCOMPtr<nsIPushNotifier> pushNotifierIface =
+      do_GetService("@mozilla.org/push/Notifier;1");
+  if (NS_WARN_IF(!pushNotifierIface)) {
+      return true;
+  }
+  PushNotifier* pushNotifier =
+    static_cast<PushNotifier*>(pushNotifierIface.get());
+  pushNotifier->NotifyErrorWorkers(aScope, aMessage, aFlags);
+#endif
+  return true;
+}
+
 } // namespace dom
 } // namespace mozilla
--- a/dom/ipc/ContentChild.h
+++ b/dom/ipc/ContentChild.h
@@ -526,27 +526,33 @@ public:
   RecvInvokeDragSession(nsTArray<IPCDataTransfer>&& aTransfers,
                         const uint32_t& aAction) override;
 
   virtual bool RecvEndDragSession(const bool& aDoneDrag,
                                   const bool& aUserCancelled) override;
 
   virtual bool
   RecvPush(const nsCString& aScope,
-           const IPC::Principal& aPrincipal) override;
+           const IPC::Principal& aPrincipal,
+           const nsString& aMessageId) override;
 
   virtual bool
   RecvPushWithData(const nsCString& aScope,
                    const IPC::Principal& aPrincipal,
+                   const nsString& aMessageId,
                    InfallibleTArray<uint8_t>&& aData) override;
 
   virtual bool
   RecvPushSubscriptionChange(const nsCString& aScope,
                              const IPC::Principal& aPrincipal) override;
 
+  virtual bool
+  RecvPushError(const nsCString& aScope, const nsString& aMessage,
+                const uint32_t& aFlags) override;
+
   // Get the directory for IndexedDB files. We query the parent for this and
   // cache the value
   nsString &GetIndexedDBPath();
 
   ContentParentId GetID() const { return mID; }
 
   bool IsForApp() const { return mIsForApp; }
   bool IsForBrowser() const { return mIsForBrowser; }
--- a/dom/ipc/PContent.ipdl
+++ b/dom/ipc/PContent.ipdl
@@ -725,29 +725,35 @@ child:
     /**
      * Notify the child that the Gecko Media Plugins installed changed.
      */
     async NotifyGMPsChanged();
 
     /**
      * Send a `push` event without data to a service worker in the child.
      */
-    async Push(nsCString scope, Principal principal);
+    async Push(nsCString scope, Principal principal, nsString messageId);
 
     /**
      * Send a `push` event with data to a service worker in the child.
      */
-    async PushWithData(nsCString scope, Principal principal, uint8_t[] data);
+    async PushWithData(nsCString scope, Principal principal,
+                       nsString messageId, uint8_t[] data);
 
     /**
      * Send a `pushsubscriptionchange` event to a service worker in the child.
      */
     async PushSubscriptionChange(nsCString scope, Principal principal);
 
     /**
+     * Send a Push error message to all service worker clients in the child.
+     */
+    async PushError(nsCString scope, nsString message, uint32_t flags);
+
+    /**
      * Windows specific: associate this content process with the browsers
      * audio session.
      */
     async SetAudioSessionData(nsID aID,
                               nsString aDisplayName,
                               nsString aIconPath);
 parent:
     /**
--- a/dom/locales/en-US/chrome/dom/dom.properties
+++ b/dom/locales/en-US/chrome/dom/dom.properties
@@ -200,8 +200,10 @@ ManifestInvalidType=Expected the %1$S's 
 ManifestInvalidCSSColor=%1$S: %2$S is not a valid CSS color.
 PatternAttributeCompileFailure=Unable to check <input pattern='%S'> because the pattern is not a valid regexp: %S
 # LOCALIZATION NOTE: Do not translate "postMessage" or DOMWindow. %S values are origins, like https://domain.com:port
 TargetPrincipalDoesNotMatch=Failed to execute 'postMessage' on 'DOMWindow': The target origin provided ('%S') does not match the recipient window's origin ('%S').
 # LOCALIZATION NOTE: Do not translate 'youtube'. %S values are origins, like https://domain.com:port
 RewriteYoutubeEmbed=Rewriting old-style Youtube Flash embed (%S) to iframe embed (%S). Please update page to use iframe instead of embed/object, if possible.
 # LOCALIZATION NOTE: Do not translate 'youtube'. %S values are origins, like https://domain.com:port
 RewriteYoutubeEmbedInvalidQuery=Rewriting old-style Youtube Flash embed (%S) to iframe embed (%S). Query was invalid and removed from URL. Please update page to use iframe instead of embed/object, if possible.
+# LOCALIZATION NOTE: Do not translate "ServiceWorker". %1$S is the ServiceWorker scope URL. %2$S is an error string.
+PushMessageDecryptionFailure=The ServiceWorker for scope '%1$S' encountered an error decrypting a push message: '%2$S'. For help with encryption, please see https://developer.mozilla.org/en-US/docs/Web/API/Push_API/Using_the_Push_API#Encryption
--- a/dom/media/MediaRecorder.cpp
+++ b/dom/media/MediaRecorder.cpp
@@ -638,17 +638,17 @@ private:
     if (!mTrackUnionStream) {
       LOG(LogLevel::Debug, ("Session.InitEncoder !mTrackUnionStream %p", this));
       DoSessionEndTask(NS_OK);
       return;
     }
     mTrackUnionStream->AddListener(mEncoder);
     // Try to use direct listeners if possible
     DOMMediaStream* domStream = mRecorder->Stream();
-    if (domStream) {
+    if (domStream && domStream->GetInputStream()) {
       mInputStream = domStream->GetInputStream()->AsSourceStream();
       if (mInputStream) {
         mInputStream->AddDirectListener(mEncoder);
         mEncoder->SetDirectConnect(true);
       }
     }
 
     // Create a thread to read encode media data from MediaEncoder.
--- a/dom/media/PeerConnection.js
+++ b/dom/media/PeerConnection.js
@@ -617,18 +617,18 @@ RTCPeerConnection.prototype = {
     if (this._closed) {
       throw new this._win.DOMException("Peer connection is closed",
                                        "InvalidStateError");
     }
   },
 
   dispatchEvent: function(event) {
     // PC can close while events are firing if there is an async dispatch
-    // in c++ land
-    if (!this._closed) {
+    // in c++ land. But let through "closed" signaling and ice connection events.
+    if (!this._closed || this._inClose) {
       this.__DOM_IMPL__.dispatchEvent(event);
     }
   },
 
   // Log error message to web console and window.onerror, if present.
   logErrorAndCallOnError: function(e) {
     this.logMsg(e.message, e.fileName, e.lineNumber, Ci.nsIScriptError.exceptionFlag);
 
@@ -1134,21 +1134,23 @@ RTCPeerConnection.prototype = {
     }
     return this._impl.getParameters(sender.track);
   },
 
   close: function() {
     if (this._closed) {
       return;
     }
+    this._closed = true;
+    this._inClose = true;
     this.changeIceConnectionState("closed");
     this._localIdp.close();
     this._remoteIdp.close();
     this._impl.close();
-    this._closed = true;
+    this._inClose = false;
   },
 
   getLocalStreams: function() {
     this._checkClosed();
     return this._impl.getLocalStreams();
   },
 
   getRemoteStreams: function() {
--- a/dom/media/eme/CDMProxy.cpp
+++ b/dom/media/eme/CDMProxy.cpp
@@ -329,17 +329,17 @@ CDMProxy::gmp_LoadSession(nsAutoPtr<Sess
 
 void
 CDMProxy::SetServerCertificate(PromiseId aPromiseId,
                                nsTArray<uint8_t>& aCert)
 {
   MOZ_ASSERT(NS_IsMainThread());
   MOZ_ASSERT(mGMPThread);
 
-  nsAutoPtr<SetServerCertificateData> data;
+  nsAutoPtr<SetServerCertificateData> data(new SetServerCertificateData());
   data->mPromiseId = aPromiseId;
   data->mCert = Move(aCert);
   nsCOMPtr<nsIRunnable> task(
     NS_NewRunnableMethodWithArg<nsAutoPtr<SetServerCertificateData>>(this, &CDMProxy::gmp_SetServerCertificate, data));
   mGMPThread->Dispatch(task, NS_DISPATCH_NORMAL);
 }
 
 void
new file mode 100644
--- /dev/null
+++ b/dom/media/platforms/MediaTelemetryConstants.h
@@ -0,0 +1,22 @@
+/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+#ifndef dom_media_platforms_MediaTelemetryConstants_h___
+#define dom_media_platforms_MediaTelemetryConstants_h___
+
+namespace mozilla {
+namespace media {
+
+enum class MediaDecoderBackend : uint32_t
+{
+  WMFSoftware = 0,
+  WMFDXVA2D3D9 = 1,
+  WMFDXVA2D3D11 = 2
+};
+
+} // namespace media
+} // namespace mozilla
+
+#endif // dom_media_platforms_MediaTelemetryConstants_h___
--- a/dom/media/platforms/agnostic/VPXDecoder.cpp
+++ b/dom/media/platforms/agnostic/VPXDecoder.cpp
@@ -209,16 +209,18 @@ VPXDecoder::Drain()
     NS_NewRunnableMethod(this, &VPXDecoder::DoDrain));
   mTaskQueue->Dispatch(runnable.forget());
 
   return NS_OK;
 }
 
 /* static */
 bool
-VPXDecoder::IsVPX(const nsACString& aMimeType)
+VPXDecoder::IsVPX(const nsACString& aMimeType, uint8_t aCodecMask)
 {
-  return aMimeType.EqualsLiteral("video/webm; codecs=vp8") ||
-    aMimeType.EqualsLiteral("video/webm; codecs=vp9");
+  return ((aCodecMask & VPXDecoder::VP8) &&
+          aMimeType.EqualsLiteral("video/webm; codecs=vp8")) ||
+         ((aCodecMask & VPXDecoder::VP9) &&
+          aMimeType.EqualsLiteral("video/webm; codecs=vp9"));
 }
 
 } // namespace mozilla
 #undef LOG
--- a/dom/media/platforms/agnostic/VPXDecoder.h
+++ b/dom/media/platforms/agnostic/VPXDecoder.h
@@ -33,23 +33,23 @@ public:
   nsresult Flush() override;
   nsresult Drain() override;
   nsresult Shutdown() override;
   const char* GetDescriptionName() const override
   {
     return "libvpx video decoder";
   }
 
-  // Return true if mimetype is a VPX codec
-  static bool IsVPX(const nsACString& aMimeType);
+  enum Codec: uint8_t {
+    VP8 = 1 << 0,
+    VP9 = 1 << 1
+  };
 
-  enum Codec {
-    VP8,
-    VP9
-  };
+  // Return true if mimetype is a VPX codec of given types.
+  static bool IsVPX(const nsACString& aMimeType, uint8_t aCodecMask=VP8|VP9);
 
 private:
   void DecodeFrame (MediaRawData* aSample);
   int DoDecodeFrame (MediaRawData* aSample);
   void DoDrain ();
   void OutputDelayedFrames ();
 
   RefPtr<ImageContainer> mImageContainer;
--- a/dom/media/platforms/android/AndroidDecoderModule.cpp
+++ b/dom/media/platforms/android/AndroidDecoderModule.cpp
@@ -4,20 +4,22 @@
 
 #include "AndroidDecoderModule.h"
 #include "AndroidBridge.h"
 #include "AndroidSurfaceTexture.h"
 #include "GLImages.h"
 
 #include "MediaData.h"
 #include "MediaInfo.h"
+#include "VPXDecoder.h"
 
 #include "nsThreadUtils.h"
 #include "nsAutoPtr.h"
 #include "nsPromiseFlatString.h"
+#include "nsIGfxInfo.h"
 
 #include "prlog.h"
 
 #include <jni.h>
 
 static PRLogModuleInfo* AndroidDecoderModuleLog()
 {
   static PRLogModuleInfo* sLogModule = nullptr;
@@ -44,33 +46,44 @@ namespace mozilla {
     mCallback->Func(__VA_ARGS__); \
   } else { \
     NS_WARNING("Callback not set"); \
   }
 
 static const char*
 TranslateMimeType(const nsACString& aMimeType)
 {
-  if (aMimeType.EqualsLiteral("video/webm; codecs=vp8")) {
+  if (VPXDecoder::IsVPX(aMimeType, VPXDecoder::VP8)) {
     return "video/x-vnd.on2.vp8";
-  } else if (aMimeType.EqualsLiteral("video/webm; codecs=vp9")) {
+  } else if (VPXDecoder::IsVPX(aMimeType, VPXDecoder::VP9)) {
     return "video/x-vnd.on2.vp9";
   }
   return PromiseFlatCString(aMimeType).get();
 }
 
 static MediaCodec::LocalRef
 CreateDecoder(const nsACString& aMimeType)
 {
   MediaCodec::LocalRef codec;
   NS_ENSURE_SUCCESS(MediaCodec::CreateDecoderByType(TranslateMimeType(aMimeType),
                     &codec), nullptr);
   return codec;
 }
 
+static bool
+GetFeatureStatus(int32_t aFeature)
+{
+  nsCOMPtr<nsIGfxInfo> gfxInfo = services::GetGfxInfo();
+  int32_t status = nsIGfxInfo::FEATURE_STATUS_UNKNOWN;
+  if (!gfxInfo || NS_FAILED(gfxInfo->GetFeatureStatus(aFeature, &status))) {
+    return false;
+  }
+  return status == nsIGfxInfo::FEATURE_STATUS_OK;
+};
+
 class VideoDataDecoder : public MediaCodecDataDecoder
 {
 public:
   VideoDataDecoder(const VideoInfo& aConfig,
                    MediaFormat::Param aFormat,
                    MediaDataDecoderCallback* aCallback,
                    layers::ImageContainer* aImageContainer)
     : MediaCodecDataDecoder(MediaData::Type::VIDEO_DATA, aConfig.mMimeType,
@@ -244,16 +257,23 @@ AndroidDecoderModule::SupportsMimeType(c
   if (aMimeType.EqualsLiteral("audio/x-wav") ||
       aMimeType.EqualsLiteral("audio/wave; codecs=1") ||
       aMimeType.EqualsLiteral("audio/wave; codecs=6") ||
       aMimeType.EqualsLiteral("audio/wave; codecs=7") ||
       aMimeType.EqualsLiteral("audio/wave; codecs=65534")) {
     return false;
   }  
 
+  if ((VPXDecoder::IsVPX(aMimeType, VPXDecoder::VP8) &&
+       !GetFeatureStatus(nsIGfxInfo::FEATURE_VP8_HW_DECODE)) ||
+      (VPXDecoder::IsVPX(aMimeType, VPXDecoder::VP9) &&
+       !GetFeatureStatus(nsIGfxInfo::FEATURE_VP9_HW_DECODE))) {
+    return false;
+  }
+
   return widget::HardwareCodecCapabilityUtils::FindDecoderCodecInfoForMimeType(
       nsCString(TranslateMimeType(aMimeType)));
 }
 
 already_AddRefed<MediaDataDecoder>
 AndroidDecoderModule::CreateVideoDecoder(
     const VideoInfo& aConfig, layers::LayersBackend aLayersBackend,
     layers::ImageContainer* aImageContainer, FlushableTaskQueue* aVideoTaskQueue,
--- a/dom/media/platforms/moz.build
+++ b/dom/media/platforms/moz.build
@@ -4,16 +4,17 @@
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 EXPORTS += [
     'agnostic/AgnosticDecoderModule.h',
     'agnostic/OpusDecoder.h',
     'agnostic/VorbisDecoder.h',
     'agnostic/VPXDecoder.h',
+    'MediaTelemetryConstants.h',
     'PDMFactory.h',
     'PlatformDecoderModule.h',
     'wrappers/FuzzingWrapper.h',
     'wrappers/H264Converter.h'
 ]
 
 UNIFIED_SOURCES += [
     'agnostic/AgnosticDecoderModule.cpp',
--- a/dom/media/platforms/wmf/DXVA2Manager.cpp
+++ b/dom/media/platforms/wmf/DXVA2Manager.cpp
@@ -8,16 +8,18 @@
 #include <d3d11.h>
 #include "nsThreadUtils.h"
 #include "ImageContainer.h"
 #include "gfxWindowsPlatform.h"
 #include "D3D9SurfaceImage.h"
 #include "mozilla/layers/D3D11ShareHandleImage.h"
 #include "mozilla/layers/ImageBridgeChild.h"
 #include "mozilla/Preferences.h"
+#include "mozilla/Telemetry.h"
+#include "MediaTelemetryConstants.h"
 #include "mfapi.h"
 #include "MFTDecoder.h"
 #include "DriverCrashGuard.h"
 #include "nsPrintfCString.h"
 
 const CLSID CLSID_VideoProcessorMFT =
 {
   0x88753b26,
@@ -411,16 +413,18 @@ D3D9DXVA2Manager::Init(nsACString& aFail
   mDevice = device;
   mDeviceManager = deviceManager;
   mSyncSurface = syncSurf;
 
   mTextureClientAllocator = new D3D9RecycleAllocator(layers::ImageBridgeChild::GetSingleton(),
                                                      mDevice);
   mTextureClientAllocator->SetMaxPoolSize(5);
 
+  Telemetry::Accumulate(Telemetry::MEDIA_DECODER_BACKEND_USED,
+                        uint32_t(media::MediaDecoderBackend::WMFDXVA2D3D9));
   return S_OK;
 }
 
 HRESULT
 D3D9DXVA2Manager::CopyToImage(IMFSample* aSample,
                               const nsIntRect& aRegion,
                               ImageContainer* aImageContainer,
                               Image** aOutImage)
@@ -725,16 +729,18 @@ D3D11DXVA2Manager::Init(nsACString& aFai
 
   hr = mDevice->CreateTexture2D(&desc, NULL, getter_AddRefs(mSyncSurface));
   NS_ENSURE_TRUE(SUCCEEDED(hr), hr);
 
   mTextureClientAllocator = new D3D11RecycleAllocator(layers::ImageBridgeChild::GetSingleton(),
                                                       mDevice);
   mTextureClientAllocator->SetMaxPoolSize(5);
 
+  Telemetry::Accumulate(Telemetry::MEDIA_DECODER_BACKEND_USED,
+                        uint32_t(media::MediaDecoderBackend::WMFDXVA2D3D11));
   return S_OK;
 }
 
 HRESULT
 D3D11DXVA2Manager::CreateOutputSample(RefPtr<IMFSample>& aSample, ID3D11Texture2D* aTexture)
 {
   RefPtr<IMFSample> sample;
   HRESULT hr = wmf::MFCreateSample(getter_AddRefs(sample));
--- a/dom/media/platforms/wmf/WMFVideoMFTManager.cpp
+++ b/dom/media/platforms/wmf/WMFVideoMFTManager.cpp
@@ -18,16 +18,17 @@
 #include "mozilla/Logging.h"
 #include "gfx2DGlue.h"
 #include "gfxWindowsPlatform.h"
 #include "IMFYCbCrImage.h"
 #include "mozilla/WindowsVersion.h"
 #include "mozilla/Preferences.h"
 #include "mozilla/Telemetry.h"
 #include "nsPrintfCString.h"
+#include "MediaTelemetryConstants.h"
 
 extern mozilla::LogModule* GetPDMLog();
 #define LOG(...) MOZ_LOG(GetPDMLog(), mozilla::LogLevel::Debug, (__VA_ARGS__))
 
 using mozilla::layers::Image;
 using mozilla::layers::IMFYCbCrImage;
 using mozilla::layers::LayerManager;
 using mozilla::layers::LayersBackend;
@@ -271,16 +272,21 @@ WMFVideoMFTManager::InitInternal(bool aF
         mDXVAFailureReason = nsPrintfCString("MFT_MESSAGE_SET_D3D_MANAGER failed with code %X", hr);
       }
     }
     else {
       mDXVAFailureReason.AssignLiteral("Decoder returned false for MF_SA_D3D_AWARE");
     }
   }
 
+  if (!mUseHwAccel) {
+    Telemetry::Accumulate(Telemetry::MEDIA_DECODER_BACKEND_USED,
+                          uint32_t(media::MediaDecoderBackend::WMFSoftware));
+  }
+
   mDecoder = decoder;
   hr = SetDecoderMediaTypes();
   NS_ENSURE_TRUE(SUCCEEDED(hr), false);
 
   LOG("Video Decoder initialized, Using DXVA: %s", (mUseHwAccel ? "Yes" : "No"));
 
   return true;
 }
--- a/dom/media/test/external/external_media_harness/testcase.py
+++ b/dom/media/test/external/external_media_harness/testcase.py
@@ -1,12 +1,13 @@
 # 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/.
 
+import re
 import os
 
 from marionette import BrowserMobProxyTestCaseMixin, MarionetteTestCase
 from marionette_driver import Wait
 from marionette_driver.errors import TimeoutException
 from marionette.marionette_test import SkipTest
 
 from firefox_puppeteer.testcases import BaseFirefoxTestCase
@@ -131,17 +132,18 @@ class NetworkBandwidthTestCase(MediaTest
                 video = VP(self.marionette, url,
                                        stall_wait_time=60,
                                        set_duration=60)
                 self.run_playback(video)
 
 
 class VideoPlaybackTestsMixin(object):
 
-    """ Test MSE playback in HTML5 video element.
+    """
+    Test MSE playback in HTML5 video element.
 
     These tests should pass on any site where a single video element plays
     upon loading and is uninterrupted (by ads, for example).
 
     This tests both starting videos and performing partial playback at one
     minute each, and is the test that should be run frequently in automation.
     """
 
@@ -171,8 +173,133 @@ class VideoPlaybackTestsMixin(object):
         Test to make sure that playback of 60 seconds works for each video.
         """
         with self.marionette.using_context('content'):
             for url in self.video_urls:
                 video = VP(self.marionette, url,
                            stall_wait_time=10,
                            set_duration=60)
                 self.run_playback(video)
+
+
+class NetworkBandwidthTestsMixin(object):
+
+    """
+        Test video urls with various bandwidth settings.
+    """
+
+    def test_playback_limiting_bandwidth_250(self):
+        self.proxy.limits({'downstream_kbps': 250})
+        self.run_videos()
+
+    def test_playback_limiting_bandwidth_500(self):
+        self.proxy.limits({'downstream_kbps': 500})
+        self.run_videos()
+
+    def test_playback_limiting_bandwidth_1000(self):
+        self.proxy.limits({'downstream_kbps': 1000})
+        self.run_videos()
+
+
+reset_adobe_gmp_script = """
+navigator.requestMediaKeySystemAccess('com.adobe.primetime',
+[{initDataType: 'cenc'}]).then(
+    function(access) {
+        marionetteScriptFinished('success');
+    },
+    function(ex) {
+        marionetteScriptFinished(ex);
+    }
+);
+"""
+
+
+class EMESetupMixin(object):
+
+    """
+    An object that needs to use the Adobe GMP system must inherit from this
+    class, and then call check_eme_system() to insure that everything is
+    setup correctly.
+    """
+
+    version_needs_reset = True
+
+    def check_eme_system(self):
+        """
+        Download the most current version of the Adobe GMP Plugin. Verify
+        that all MSE and EME prefs are set correctly. Raises if things
+        are not OK.
+        """
+        self.set_eme_prefs()
+        self.reset_GMP_version()
+        assert(self.check_eme_prefs())
+
+    def set_eme_prefs(self):
+        with self.marionette.using_context('chrome'):
+            # https://bugzilla.mozilla.org/show_bug.cgi?id=1187471#c28
+            # 2015-09-28 cpearce says this is no longer necessary, but in case
+            # we are working with older firefoxes...
+            self.prefs.set_pref('media.gmp.trial-create.enabled', False)
+
+    def reset_GMP_version(self):
+        if EMESetupMixin.version_needs_reset:
+            with self.marionette.using_context('chrome'):
+                if self.prefs.get_pref('media.gm-eme-adobe.version'):
+                    self.prefs.set_pref('media.gm-eme-adobe.version', None)
+                result = self.marionette.execute_async_script(
+                    reset_adobe_gmp_script,
+                    script_timeout=60000)
+                if not result == 'success':
+                    raise VideoException(
+                        'ERROR: Resetting Adobe GMP failed % s' % result)
+
+            EMESetupMixin.version_needs_reset = False
+
+    def check_and_log_boolean_pref(self, pref_name, expected_value):
+        with self.marionette.using_context('chrome'):
+            pref_value = self.prefs.get_pref(pref_name)
+
+            if pref_value is None:
+                self.logger.info('Pref %s has no value.' % pref_name)
+                return False
+            else:
+                self.logger.info('Pref %s = %s' % (pref_name, pref_value))
+                if pref_value != expected_value:
+                    self.logger.info('Pref %s has unexpected value.'
+                                     % pref_name)
+                    return False
+
+        return True
+
+    def check_and_log_integer_pref(self, pref_name, minimum_value=0):
+        with self.marionette.using_context('chrome'):
+            pref_value = self.prefs.get_pref(pref_name)
+
+            if pref_value is None:
+                self.logger.info('Pref %s has no value.' % pref_name)
+                return False
+            else:
+                self.logger.info('Pref %s = %s' % (pref_name, pref_value))
+
+                match = re.search('^\d+$', pref_value)
+                if not match:
+                    self.logger.info('Pref %s is not an integer' % pref_name)
+                    return False
+
+            return pref_value >= minimum_value
+
+    def check_eme_prefs(self):
+        with self.marionette.using_context('chrome'):
+            prefs_ok = self.check_and_log_boolean_pref(
+                'media.mediasource.enabled', True)
+            prefs_ok = self.check_and_log_boolean_pref(
+                'media.eme.enabled', True) and prefs_ok
+            prefs_ok = self.check_and_log_boolean_pref(
+                'media.mediasource.mp4.enabled', True) and prefs_ok
+            prefs_ok = self.check_and_log_boolean_pref(
+                'media.gmp-eme-adobe.enabled', True) and prefs_ok
+            prefs_ok = self.check_and_log_integer_pref(
+                'media.gmp-eme-adobe.version', 1) and prefs_ok
+
+        return prefs_ok
+
+
+
--- a/dom/media/test/external/external_media_tests/playback/netflix_limiting_bandwidth.ini
+++ b/dom/media/test/external/external_media_tests/playback/netflix_limiting_bandwidth.ini
@@ -1,1 +1,1 @@
-[test_playback_limiting_bandwidth.py]
+[test_eme_playback_limiting_bandwidth.py]
--- a/dom/media/test/external/external_media_tests/playback/test_eme_playback.py
+++ b/dom/media/test/external/external_media_tests/playback/test_eme_playback.py
@@ -1,100 +1,14 @@
 # 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/.
 
-import re
-
-from external_media_harness.testcase import MediaTestCase, VideoPlaybackTestsMixin
-from external_media_tests.media_utils.video_puppeteer import VideoException
+from external_media_harness.testcase import MediaTestCase, VideoPlaybackTestsMixin, EMESetupMixin
 
-reset_adobe_gmp_script = """
-navigator.requestMediaKeySystemAccess('com.adobe.primetime',
-[{initDataType: 'cenc'}]).then(
-    function(access) {
-        marionetteScriptFinished('success');
-    },
-    function(ex) {
-        marionetteScriptFinished(ex);
-    }
-);
-"""
 
-class TestEMEPlayback(MediaTestCase, VideoPlaybackTestsMixin):
-
-    # Class variable. We only need to reset the adobe GMP version once, not
-    # every time we instantiate the class.
-    version_needs_reset = True
+class TestEMEPlayback(MediaTestCase, VideoPlaybackTestsMixin, EMESetupMixin):
 
     def setUp(self):
         super(TestEMEPlayback, self).setUp()
-        self.set_eme_prefs()
-        self.reset_GMP_version()
-        assert(self.check_eme_prefs())
-
-    def set_eme_prefs(self):
-        with self.marionette.using_context('chrome'):
-
-            # https://bugzilla.mozilla.org/show_bug.cgi?id=1187471#c28
-            # 2015-09-28 cpearce says this is no longer necessary, but in case
-            # we are working with older firefoxes...
-            self.prefs.set_pref('media.gmp.trial-create.enabled', False)
-
-    def reset_GMP_version(self):
-        if TestEMEPlayback.version_needs_reset:
-            with self.marionette.using_context('chrome'):
-                if self.prefs.get_pref('media.gm-eme-adobe.version'):
-                    self.prefs.set_pref('media.gm-eme-adobe.version', None)
-                result = self.marionette.execute_async_script(reset_adobe_gmp_script,
-                                                              script_timeout=60000)
-                if not result == 'success':
-                    raise VideoException('ERROR: Resetting Adobe GMP failed % s' % result)
-
-            TestEMEPlayback.version_needs_reset = False
-
-    def check_and_log_boolean_pref(self, pref_name, expected_value):
-        with self.marionette.using_context('chrome'):
-            pref_value = self.prefs.get_pref(pref_name)
+        self.check_eme_system()
 
-            if pref_value is None:
-                self.logger.info('Pref %s has no value.' % pref_name)
-                return False
-            else:
-                self.logger.info('Pref %s = %s' % (pref_name, pref_value))
-                if pref_value != expected_value:
-                    self.logger.info('Pref %s has unexpected value.'
-                                     % pref_name)
-                    return False
-
-        return True
-
-    def check_and_log_integer_pref(self, pref_name, minimum_value=0):
-        with self.marionette.using_context('chrome'):
-            pref_value = self.prefs.get_pref(pref_name)
-
-            if pref_value is None:
-                self.logger.info('Pref %s has no value.' % pref_name)
-                return False
-            else:
-                self.logger.info('Pref %s = %s' % (pref_name, pref_value))
-
-                match = re.search('^\d+$', pref_value)
-                if not match:
-                    self.logger.info('Pref %s is not an integer' % pref_name)
-                    return False
-
-            return pref_value >= minimum_value
-
-    def check_eme_prefs(self):
-        with self.marionette.using_context('chrome'):
-            prefs_ok = self.check_and_log_boolean_pref(
-                'media.mediasource.enabled', True)
-            prefs_ok = self.check_and_log_boolean_pref(
-                'media.eme.enabled', True) and prefs_ok
-            prefs_ok = self.check_and_log_boolean_pref(
-                'media.mediasource.mp4.enabled', True) and prefs_ok
-            prefs_ok = self.check_and_log_boolean_pref(
-                'media.gmp-eme-adobe.enabled', True) and prefs_ok
-            prefs_ok = self.check_and_log_integer_pref(
-                'media.gmp-eme-adobe.version', 1) and prefs_ok
-
-        return prefs_ok
+    # Tests are implemented in VideoPlaybackTestsMixin
new file mode 100644
--- /dev/null
+++ b/dom/media/test/external/external_media_tests/playback/test_eme_playback_limiting_bandwidth.py
@@ -0,0 +1,23 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+from marionette import BrowserMobProxyTestCaseMixin
+
+from external_media_harness.testcase import (
+    EMESetupMixin,
+    NetworkBandwidthTestCase,
+    NetworkBandwidthTestsMixin,
+)
+
+class TestEMEPlaybackLimitingBandwidth(NetworkBandwidthTestCase,
+                                       BrowserMobProxyTestCaseMixin,
+                                       NetworkBandwidthTestsMixin,
+                                       EMESetupMixin):
+
+
+    def setUp(self):
+        super(TestEMEPlaybackLimitingBandwidth, self).setUp()
+        self.check_eme_system()
+
+    # Tests in NetworkBandwidthTestsMixin
--- a/dom/media/test/external/external_media_tests/playback/test_playback_limiting_bandwidth.py
+++ b/dom/media/test/external/external_media_tests/playback/test_playback_limiting_bandwidth.py
@@ -1,23 +1,17 @@
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 from marionette import BrowserMobProxyTestCaseMixin
 
-from external_media_harness.testcase import NetworkBandwidthTestCase
+from external_media_harness.testcase import (
+    NetworkBandwidthTestCase, NetworkBandwidthTestsMixin
+)
 
 
 class TestPlaybackLimitingBandwidth(NetworkBandwidthTestCase,
+                                    NetworkBandwidthTestsMixin,
                                     BrowserMobProxyTestCaseMixin):
 
-    def test_playback_limiting_bandwidth_250(self):
-        self.proxy.limits({'downstream_kbps': 250})
-        self.run_videos()
-
-    def test_playback_limiting_bandwidth_500(self):
-        self.proxy.limits({'downstream_kbps': 500})
-        self.run_videos()
-
-    def test_playback_limiting_bandwidth_1000(self):
-        self.proxy.limits({'downstream_kbps': 1000})
-        self.run_videos()
+    # Tests are in NetworkBandwidthTestsMixin
+    pass
--- a/dom/media/tests/mochitest/test_peerConnection_close.html
+++ b/dom/media/tests/mochitest/test_peerConnection_close.html
@@ -24,16 +24,26 @@
     var finish;
     var finished = new Promise(resolve => finish = resolve);
 
     pc.onsignalingstatechange = function(e) {
       clearTimeout(eTimeout);
       is(pc.signalingState, "closed", "signalingState is 'closed'");
       is(pc.iceConnectionState, "closed", "iceConnectionState is 'closed'");
 
+      // test that pc is really closed (and doesn't crash, bug 1259728)
+      try {
+        pc.getLocalStreams();
+      } catch (e) {
+        exception = e;
+      }
+      is(exception && exception.name, "InvalidStateError",
+         "pc.getLocalStreams should throw when closed");
+      exception = null;
+
       try {
         pc.close();
       } catch (e) {
         exception = e;
       }
       is(exception, null, "A second close() should not raise an exception");
       is(pc.signalingState, "closed", "Final signalingState stays at 'closed'");
       is(pc.iceConnectionState, "closed", "Final iceConnectionState stays at 'closed'");
--- a/dom/plugins/base/nsJSNPRuntime.cpp
+++ b/dom/plugins/base/nsJSNPRuntime.cpp
@@ -213,16 +213,30 @@ NPObjWrapper_Construct(JSContext *cx, un
 static bool
 NPObjWrapper_toPrimitive(JSContext *cx, unsigned argc, JS::Value *vp);
 
 static bool
 CreateNPObjectMember(NPP npp, JSContext *cx, JSObject *obj, NPObject* npobj,
                      JS::Handle<jsid> id,  NPVariant* getPropertyResult,
                      JS::MutableHandle<JS::Value> vp);
 
+const static js::ObjectOps sNPObjectJSWrapperObjectOps = {
+    nullptr, // lookupProperty
+    nullptr, // defineProperty
+    nullptr, // hasProperty
+    nullptr, // getProperty
+    nullptr, // setProperty
+    nullptr, // getOwnPropertyDescriptor
+    nullptr, // deleteProperty
+    nullptr, nullptr, // watch/unwatch
+    nullptr, // getElements
+    NPObjWrapper_Enumerate,
+    nullptr,
+};
+
 const static js::Class sNPObjectJSWrapperClass =
   {
     NPRUNTIME_JSCLASS_NAME,
     JSCLASS_HAS_PRIVATE,
     NPObjWrapper_AddProperty,
     NPObjWrapper_DelProperty,
     NPObjWrapper_GetProperty,
     NPObjWrapper_SetProperty,
@@ -235,29 +249,17 @@ const static js::Class sNPObjectJSWrappe
     NPObjWrapper_Construct,
     nullptr,                                                /* trace */
     JS_NULL_CLASS_SPEC,
     {
       false,                                                /* isWrappedNative */
       nullptr,                                              /* weakmapKeyDelegateOp */
       NPObjWrapper_ObjectMoved
     },
-    {
-        nullptr, // lookupProperty
-        nullptr, // defineProperty
-        nullptr, // hasProperty
-        nullptr, // getProperty
-        nullptr, // setProperty
-        nullptr, // getOwnPropertyDescriptor
-        nullptr, // deleteProperty
-        nullptr, nullptr, // watch/unwatch
-        nullptr, // getElements
-        NPObjWrapper_Enumerate,
-        nullptr,
-    }
+    &sNPObjectJSWrapperObjectOps
   };
 
 typedef struct NPObjectMemberPrivate {
     JS::Heap<JSObject *> npobjWrapper;
     JS::Heap<JS::Value> fieldValue;
     JS::Heap<jsid> methodName;
     NPP   npp;
 } NPObjectMemberPrivate;
--- a/dom/promise/Promise.cpp
+++ b/dom/promise/Promise.cpp
@@ -91,26 +91,21 @@ protected:
     JS::Rooted<JS::Value> value(cx, mValue);
     if (!MaybeWrapValue(cx, &value)) {
       NS_WARNING("Failed to wrap value into the right compartment.");
       JS_ClearPendingException(cx);
       return NS_OK;
     }
 
     JS::Rooted<JSObject*> asyncStack(cx, mPromise->mAllocationStack);
-    JS::Rooted<JSString*> asyncCause(cx, JS_NewStringCopyZ(cx, "Promise"));
-    if (!asyncCause) {
-      JS_ClearPendingException(cx);
-      return NS_ERROR_OUT_OF_MEMORY;
-    }
 
     {
       Maybe<JS::AutoSetAsyncStackForNewCalls> sas;
       if (asyncStack) {
-        sas.emplace(cx, asyncStack, asyncCause);
+        sas.emplace(cx, asyncStack, "Promise");
       }
       mCallback->Call(cx, value);
     }
 
     return NS_OK;
   }
 
 private:
--- a/dom/push/PushComponents.js
+++ b/dom/push/PushComponents.js
@@ -50,16 +50,17 @@ function PushServiceBase() {
 PushServiceBase.prototype = {
   classID: Components.ID("{daaa8d73-677e-4233-8acd-2c404bd01658}"),
   contractID: "@mozilla.org/push/Service;1",
   QueryInterface: XPCOMUtils.generateQI([
     Ci.nsIObserver,
     Ci.nsISupportsWeakReference,
     Ci.nsIPushService,
     Ci.nsIPushQuotaManager,
+    Ci.nsIPushErrorReporter,
   ]),
 
   pushTopic: OBSERVER_TOPIC_PUSH,
   subscriptionChangeTopic: OBSERVER_TOPIC_SUBSCRIPTION_CHANGE,
 
   _handleReady() {},
 
   _addListeners() {
@@ -112,16 +113,17 @@ Object.assign(PushServiceParent.prototyp
 
   _messages: [
     "Push:Register",
     "Push:Registration",
     "Push:Unregister",
     "Push:Clear",
     "Push:NotificationForOriginShown",
     "Push:NotificationForOriginClosed",
+    "Push:ReportError",
   ],
 
   // nsIPushService methods
 
   subscribe(scope, principal, callback) {
     return this._handleRequest("Push:Register", principal, {
       scope: scope,
     }).then(result => {
@@ -166,32 +168,42 @@ Object.assign(PushServiceParent.prototyp
   notificationForOriginShown(origin) {
     this.service.notificationForOriginShown(origin);
   },
 
   notificationForOriginClosed(origin) {
     this.service.notificationForOriginClosed(origin);
   },
 
+  // nsIPushErrorReporter methods
+
+  reportDeliveryError(messageId, reason) {
+    this.service.reportDeliveryError(messageId, reason);
+  },
+
   receiveMessage(message) {
     if (!this._isValidMessage(message)) {
       return;
     }
     let {name, principal, target, data} = message;
     if (name === "Push:NotificationForOriginShown") {
       this.notificationForOriginShown(data);
       return;
     }
     if (name === "Push:NotificationForOriginClosed") {
       this.notificationForOriginClosed(data);
       return;
     }
     if (!target.assertPermission("push")) {
       return;
     }
+    if (name === "Push:ReportError") {
+      this.reportDeliveryError(data.messageId, data.reason);
+      return;
+    }
     let sender = target.QueryInterface(Ci.nsIMessageSender);
     return this._handleRequest(name, principal, data).then(result => {
       sender.sendAsyncMessage(this._getResponseName(name, "OK"), {
         requestID: data.requestID,
         result: result
       });
     }, error => {
       sender.sendAsyncMessage(this._getResponseName(name, "KO"), {
@@ -349,16 +361,25 @@ Object.assign(PushServiceContent.prototy
   notificationForOriginShown(origin) {
     this._mm.sendAsyncMessage("Push:NotificationForOriginShown", origin);
   },
 
   notificationForOriginClosed(origin) {
     this._mm.sendAsyncMessage("Push:NotificationForOriginClosed", origin);
   },
 
+  // nsIPushErrorReporter methods
+
+  reportDeliveryError(messageId, reason) {
+    this._mm.sendAsyncMessage("Push:ReportError", {
+      messageId: messageId,
+      reason: reason,
+    });
+  },
+
   _addRequest(data) {
     let id = ++this._requestId;
     this._requests.set(id, data);
     return id;
   },
 
   _takeRequest(requestId) {
     let d = this._requests.get(requestId);
--- a/dom/push/PushNotifier.cpp
+++ b/dom/push/PushNotifier.cpp
@@ -1,30 +1,32 @@
 /* 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 "PushNotifier.h"
 
 #include "nsContentUtils.h"
 #include "nsCOMPtr.h"
-#include "nsXPCOM.h"
+#include "nsICategoryManager.h"
 #include "nsIXULRuntime.h"
+#include "nsNetUtil.h"
+#include "nsXPCOM.h"
 #include "ServiceWorkerManager.h"
-#include "nsICategoryManager.h"
 
 #include "mozilla/Services.h"
 #include "mozilla/unused.h"
 
 #include "mozilla/dom/BodyUtil.h"
 #include "mozilla/dom/ContentParent.h"
 
 namespace mozilla {
 namespace dom {
 
+using workers::AssertIsOnMainThread;
 using workers::ServiceWorkerManager;
 
 PushNotifier::PushNotifier()
 {}
 
 PushNotifier::~PushNotifier()
 {}
 
@@ -36,85 +38,105 @@ NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(
 NS_INTERFACE_MAP_END
 
 NS_IMPL_CYCLE_COLLECTING_ADDREF(PushNotifier)
 NS_IMPL_CYCLE_COLLECTING_RELEASE(PushNotifier)
 
 NS_IMETHODIMP
 PushNotifier::NotifyPushWithData(const nsACString& aScope,
                                  nsIPrincipal* aPrincipal,
+                                 const nsAString& aMessageId,
                                  uint32_t aDataLen, uint8_t* aData)
 {
   nsTArray<uint8_t> data;
   if (!data.SetCapacity(aDataLen, fallible)) {
     return NS_ERROR_OUT_OF_MEMORY;
   }
   if (!data.InsertElementsAt(0, aData, aDataLen, fallible)) {
     return NS_ERROR_OUT_OF_MEMORY;
   }
-  return NotifyPush(aScope, aPrincipal, Some(data));
+  return NotifyPush(aScope, aPrincipal, aMessageId, Some(data));
 }
 
 NS_IMETHODIMP
-PushNotifier::NotifyPush(const nsACString& aScope, nsIPrincipal* aPrincipal)
+PushNotifier::NotifyPush(const nsACString& aScope, nsIPrincipal* aPrincipal,
+                         const nsAString& aMessageId)
 {
-  return NotifyPush(aScope, aPrincipal, Nothing());
+  return NotifyPush(aScope, aPrincipal, aMessageId, Nothing());
 }
 
 NS_IMETHODIMP
 PushNotifier::NotifySubscriptionChange(const nsACString& aScope,
                                        nsIPrincipal* aPrincipal)
 {
-  if (XRE_IsContentProcess()) {
-    return NS_ERROR_NOT_IMPLEMENTED;
-  }
   nsresult rv;
   if (ShouldNotifyObservers(aPrincipal)) {
     rv = NotifySubscriptionChangeObservers(aScope);
     if (NS_WARN_IF(NS_FAILED(rv))) {
       return rv;
     }
   }
   if (ShouldNotifyWorkers(aPrincipal)) {
     rv = NotifySubscriptionChangeWorkers(aScope, aPrincipal);
     if (NS_WARN_IF(NS_FAILED(rv))) {
       return rv;
     }
   }
   return NS_OK;
 }
 
+NS_IMETHODIMP
+PushNotifier::NotifyError(const nsACString& aScope, nsIPrincipal* aPrincipal,
+                          const nsAString& aMessage, uint32_t aFlags)
+{
+  if (ShouldNotifyWorkers(aPrincipal)) {
+    // For service worker subscriptions, report the error to all clients.
+    NotifyErrorWorkers(aScope, aMessage, aFlags);
+    return NS_OK;
+  }
+  // For system subscriptions, log the error directly to the browser console.
+  return nsContentUtils::ReportToConsoleNonLocalized(aMessage,
+                                                     aFlags,
+                                                     NS_LITERAL_CSTRING("Push"),
+                                                     nullptr, /* aDocument */
+                                                     nullptr, /* aURI */
+                                                     EmptyString(), /* aLine */
+                                                     0, /* aLineNumber */
+                                                     0, /* aColumnNumber */
+                                                     nsContentUtils::eOMIT_LOCATION);
+}
+
 nsresult
 PushNotifier::NotifyPush(const nsACString& aScope, nsIPrincipal* aPrincipal,
-                         Maybe<nsTArray<uint8_t>> aData)
+                         const nsAString& aMessageId,
+                         const Maybe<nsTArray<uint8_t>>& aData)
 {
-  if (XRE_IsContentProcess()) {
-    return NS_ERROR_NOT_IMPLEMENTED;
-  }
   nsresult rv;
   if (ShouldNotifyObservers(aPrincipal)) {
     rv = NotifyPushObservers(aScope, aData);
     if (NS_WARN_IF(NS_FAILED(rv))) {
       return rv;
     }
   }
   if (ShouldNotifyWorkers(aPrincipal)) {
-    rv = NotifyPushWorkers(aScope, aPrincipal, aData);
+    rv = NotifyPushWorkers(aScope, aPrincipal, aMessageId, aData);
     if (NS_WARN_IF(NS_FAILED(rv))) {
       return rv;
     }
   }
   return NS_OK;
 }
 
 nsresult
 PushNotifier::NotifyPushWorkers(const nsACString& aScope,
                                 nsIPrincipal* aPrincipal,
-                                Maybe<nsTArray<uint8_t>> aData)
+                                const nsAString& aMessageId,
+                                const Maybe<nsTArray<uint8_t>>& aData)
 {
+  AssertIsOnMainThread();
   if (!aPrincipal) {
     return NS_ERROR_INVALID_ARG;
   }
 
   if (XRE_IsContentProcess() || !BrowserTabsRemoteAutostart()) {
     // Notify the worker from the current process. Either we're running in
     // the content process and received a message from the parent, or e10s
     // is disabled.
@@ -122,40 +144,41 @@ PushNotifier::NotifyPushWorkers(const ns
     if (!swm) {
       return NS_ERROR_FAILURE;
     }
     nsAutoCString originSuffix;
     nsresult rv = aPrincipal->GetOriginSuffix(originSuffix);
     if (NS_WARN_IF(NS_FAILED(rv))) {
       return rv;
     }
-    return swm->SendPushEvent(originSuffix, aScope, aData);
+    return swm->SendPushEvent(originSuffix, aScope, aMessageId, aData);
   }
 
   // Otherwise, we're in the parent and e10s is enabled. Broadcast the event
   // to all content processes.
   bool ok = true;
   nsTArray<ContentParent*> contentActors;
   ContentParent::GetAll(contentActors);
   for (uint32_t i = 0; i < contentActors.Length(); ++i) {
     if (aData) {
       ok &= contentActors[i]->SendPushWithData(PromiseFlatCString(aScope),
-        IPC::Principal(aPrincipal), aData.ref());
+        IPC::Principal(aPrincipal), PromiseFlatString(aMessageId), aData.ref());
     } else {
       ok &= contentActors[i]->SendPush(PromiseFlatCString(aScope),
-        IPC::Principal(aPrincipal));
+        IPC::Principal(aPrincipal), PromiseFlatString(aMessageId));
     }
   }
   return ok ? NS_OK : NS_ERROR_FAILURE;
 }
 
 nsresult
 PushNotifier::NotifySubscriptionChangeWorkers(const nsACString& aScope,
                                               nsIPrincipal* aPrincipal)
 {
+  AssertIsOnMainThread();
   if (!aPrincipal) {
     return NS_ERROR_INVALID_ARG;
   }
 
   if (XRE_IsContentProcess() || !BrowserTabsRemoteAutostart()) {
     // Content process or e10s disabled.
     RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance();
     if (!swm) {
@@ -175,19 +198,71 @@ PushNotifier::NotifySubscriptionChangeWo
   ContentParent::GetAll(contentActors);
   for (uint32_t i = 0; i < contentActors.Length(); ++i) {
     ok &= contentActors[i]->SendPushSubscriptionChange(
       PromiseFlatCString(aScope), IPC::Principal(aPrincipal));
   }
   return ok ? NS_OK : NS_ERROR_FAILURE;
 }
 
+void
+PushNotifier::NotifyErrorWorkers(const nsACString& aScope,
+                                 const nsAString& aMessage,
+                                 uint32_t aFlags)
+{
+  AssertIsOnMainThread();
+
+  if (XRE_IsContentProcess() || !BrowserTabsRemoteAutostart()) {
+    // Content process or e10s disabled.
+    RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance();
+    if (swm) {
+      swm->ReportToAllClients(PromiseFlatCString(aScope),
+                              PromiseFlatString(aMessage),
+                              NS_ConvertUTF8toUTF16(aScope), /* aFilename */
+                              EmptyString(), /* aLine */
+                              0, /* aLineNumber */
+                              0, /* aColumnNumber */
+                              aFlags);
+    }
+    return;
+  }
+
+  // Parent process, e10s enabled.
+  nsTArray<ContentParent*> contentActors;
+  ContentParent::GetAll(contentActors);
+  if (!contentActors.IsEmpty()) {
+    // At least one content process active.
+    for (uint32_t i = 0; i < contentActors.Length(); ++i) {
+      Unused << NS_WARN_IF(
+        !contentActors[i]->SendPushError(PromiseFlatCString(aScope),
+          PromiseFlatString(aMessage), aFlags));
+    }
+    return;
+  }
+  // Report to the console if no content processes are active.
+  nsCOMPtr<nsIURI> scopeURI;
+  nsresult rv = NS_NewURI(getter_AddRefs(scopeURI), aScope);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return;
+  }
+  Unused << NS_WARN_IF(NS_FAILED(
+    nsContentUtils::ReportToConsoleNonLocalized(aMessage,
+                                                aFlags,
+                                                NS_LITERAL_CSTRING("Push"),
+                                                nullptr, /* aDocument */
+                                                scopeURI, /* aURI */
+                                                EmptyString(), /* aLine */
+                                                0, /* aLineNumber */
+                                                0, /* aColumnNumber */
+                                                nsContentUtils::eOMIT_LOCATION)));
+}
+
 nsresult
 PushNotifier::NotifyPushObservers(const nsACString& aScope,
-                                  Maybe<nsTArray<uint8_t>> aData)
+                                  const Maybe<nsTArray<uint8_t>>& aData)
 {
   nsCOMPtr<nsIPushMessage> message = nullptr;
   if (aData) {
     message = new PushMessage(aData.ref());
   }
   return DoNotifyObservers(message, OBSERVER_TOPIC_PUSH, aScope);
 }
 
--- a/dom/push/PushNotifier.h
+++ b/dom/push/PushNotifier.h
@@ -24,43 +24,46 @@
 namespace mozilla {
 namespace dom {
 
 /**
  * `PushNotifier` implements the `nsIPushNotifier` interface. This service
  * forwards incoming push messages to service workers running in the content
  * process, and emits XPCOM observer notifications for system subscriptions.
  *
- * The XPCOM service can only be used from the main process. Callers running
- * in the content process should use
- * `ServiceWorkerManager::SendPush{SubscriptionChange}Event` directly.
+ * This service exists solely to support `PushService.jsm`. Other callers
+ * should use `ServiceWorkerManager` directly.
  */
 class PushNotifier final : public nsIPushNotifier
 {
 public:
   PushNotifier();
 
   NS_DECL_CYCLE_COLLECTING_ISUPPORTS
   NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(PushNotifier, nsIPushNotifier)
   NS_DECL_NSIPUSHNOTIFIER
 
   nsresult NotifyPush(const nsACString& aScope, nsIPrincipal* aPrincipal,
-                      Maybe<nsTArray<uint8_t>> aData);
+                      const nsAString& aMessageId,
+                      const Maybe<nsTArray<uint8_t>>& aData);
   nsresult NotifyPushWorkers(const nsACString& aScope,
                              nsIPrincipal* aPrincipal,
-                             Maybe<nsTArray<uint8_t>> aData);
+                             const nsAString& aMessageId,
+                             const Maybe<nsTArray<uint8_t>>& aData);
   nsresult NotifySubscriptionChangeWorkers(const nsACString& aScope,
                                            nsIPrincipal* aPrincipal);
+  void NotifyErrorWorkers(const nsACString& aScope, const nsAString& aMessage,
+                          uint32_t aFlags);
 
 protected:
   virtual ~PushNotifier();
 
 private:
   nsresult NotifyPushObservers(const nsACString& aScope,
-                               Maybe<nsTArray<uint8_t>> aData);
+                               const Maybe<nsTArray<uint8_t>>& aData);
   nsresult NotifySubscriptionChangeObservers(const nsACString& aScope);
   nsresult DoNotifyObservers(nsISupports *aSubject, const char *aTopic,
                              const nsACString& aScope);
   bool ShouldNotifyObservers(nsIPrincipal* aPrincipal);
   bool ShouldNotifyWorkers(nsIPrincipal* aPrincipal);
 };
 
 /**
--- a/dom/push/PushService.jsm
+++ b/dom/push/PushService.jsm
@@ -34,16 +34,19 @@ const CONNECTION_PROTOCOLS = (function()
 XPCOMUtils.defineLazyServiceGetter(this, "gContentSecurityManager",
                                    "@mozilla.org/contentsecuritymanager;1",
                                    "nsIContentSecurityManager");
 
 XPCOMUtils.defineLazyServiceGetter(this, "gPushNotifier",
                                    "@mozilla.org/push/Notifier;1",
                                    "nsIPushNotifier");
 
+XPCOMUtils.defineLazyGetter(this, "gDOMBundle", () =>
+  Services.strings.createBundle("chrome://global/locale/dom/dom.properties"));
+
 this.EXPORTED_SYMBOLS = ["PushService"];
 
 XPCOMUtils.defineLazyGetter(this, "console", () => {
   let {ConsoleAPI} = Cu.import("resource://gre/modules/Console.jsm", {});
   return new ConsoleAPI({
     maxLogLevelPref: "dom.push.loglevel",
     prefix: "PushService",
   });
@@ -308,36 +311,39 @@ this.PushService = {
       return Promise.resolve();
     }
 
     let pattern = JSON.parse(data);
     return this._db.clearIf(record => {
       if (!record.matchesOriginAttributes(pattern)) {
         return false;
       }
-      this._backgroundUnregister(record);
+      this._backgroundUnregister(record,
+                                 Ci.nsIPushErrorReporter.UNSUBSCRIBE_MANUAL);
       return true;
     });
   },
 
   /**
    * Sends an unregister request to the server in the background. If the
    * service is not connected, this function is a no-op.
    *
    * @param {PushRecord} record The record to unregister.
+   * @param {Number} reason An `nsIPushErrorReporter` unsubscribe reason,
+   *  indicating why this record was removed.
    */
-  _backgroundUnregister: function(record) {
+  _backgroundUnregister(record, reason) {
     console.debug("backgroundUnregister()");
 
     if (!this._service.isConnected() || !record) {
       return;
     }
 
     console.debug("backgroundUnregister: Notifying server", record);
-    this._sendUnregister(record).catch(e => {
+    this._sendUnregister(record, reason).catch(e => {
       console.error("backgroundUnregister: Error notifying server", e);
     });
   },
 
   // utility function used to add/remove observers in startObservers() and
   // stopObservers()
   getNetworkStateChangeEventName: function() {
     try {
@@ -748,100 +754,127 @@ this.PushService = {
 
   /**
    * Dispatches an incoming message to a service worker, recalculating the
    * quota for the associated push registration. If the quota is exceeded,
    * the registration and message will be dropped, and the worker will not
    * be notified.
    *
    * @param {String} keyID The push registration ID.
-   * @param {String} message The message contents.
+   * @param {String} messageID The message ID, used to report service worker
+   *  delivery failures. For Web Push messages, this is the version. If empty,
+   *  failures will not be reported.
+   * @param {ArrayBuffer|Uint8Array} data The encrypted message data.
    * @param {Object} cryptoParams The message encryption settings.
    * @param {Function} updateFunc A function that receives the existing
    *  registration record as its argument, and returns a new record. If the
    *  function returns `null` or `undefined`, the record will not be updated.
    *  `PushServiceWebSocket` uses this to drop incoming updates with older
    *  versions.
+   * @returns {Promise} Resolves with an `nsIPushErrorReporter` ack status
+   *  code, indicating whether the message was delivered successfully.
    */
-  receivedPushMessage: function(keyID, message, cryptoParams, updateFunc) {
+  receivedPushMessage(keyID, messageID, data, cryptoParams, updateFunc) {
     console.debug("receivedPushMessage()");
     Services.telemetry.getHistogramById("PUSH_API_NOTIFICATION_RECEIVED").add();
 
-    let shouldNotify = false;
+    return this._updateRecordAfterPush(keyID, updateFunc).then(record => {
+      if (!record) {
+        throw new Error("Ignoring update for key ID " + keyID);
+      }
+      // Update quota after the delay, at which point
+      // we check for visible notifications.
+      let timeoutID = setTimeout(_ =>
+        {
+          this._updateQuota(keyID);
+          if (!this._updateQuotaTimeouts.delete(timeoutID)) {
+            console.debug("receivedPushMessage: quota update timeout missing?");
+          }
+        }, prefs.get("quotaUpdateDelay"));
+      this._updateQuotaTimeouts.add(timeoutID);
+      return this._decryptAndNotifyApp(record, messageID, data, cryptoParams);
+    }).catch(error => {
+      console.error("receivedPushMessage: Error notifying app", error);
+      return Ci.nsIPushErrorReporter.ACK_NOT_DELIVERED;
+    });
+  },
+
+  /**
+   * Updates a registration record after receiving a push message.
+   *
+   * @param {String} keyID The push registration ID.
+   * @param {Function} updateFunc The function passed to `receivedPushMessage`.
+   * @returns {Promise} Resolves with the updated record, or `null` if the
+   *  record was not updated.
+   */
+  _updateRecordAfterPush(keyID, updateFunc) {
     return this.getByKeyID(keyID).then(record => {
       if (!record) {
         this._recordDidNotNotify(kDROP_NOTIFICATION_REASON_KEY_NOT_FOUND);
         throw new Error("No record for key ID " + keyID);
       }
-      return record.getLastVisit();
-    }).then(lastVisit => {
-      // As a special case, don't notify the service worker if the user
-      // cleared their history.
-      shouldNotify = isFinite(lastVisit);
-      if (!shouldNotify) {
+      return record.getLastVisit().then(lastVisit => {
+        // As a special case, don't notify the service worker if the user
+        // cleared their history.
+        if (!isFinite(lastVisit)) {
           this._recordDidNotNotify(kDROP_NOTIFICATION_REASON_NO_HISTORY);
-      }
-      return this._db.update(keyID, record => {
-        let newRecord = updateFunc(record);
-        if (!newRecord) {
-          this._recordDidNotNotify(kDROP_NOTIFICATION_REASON_NO_VERSION_INCREMENT);
-          return null;
+          throw new Error("Ignoring message sent to unvisited origin");
         }
-        // Because `unregister` is advisory only, we can still receive messages
-        // for stale Simple Push registrations from the server. To work around
-        // this, we check if the record has expired before *and* after updating
-        // the quota.
-        if (newRecord.isExpired()) {
-          console.error("receivedPushMessage: Ignoring update for expired key ID",
-            keyID);
-          return null;
-        }
-        newRecord.receivedPush(lastVisit);
-        return newRecord;
+        return lastVisit;
+      }).then(lastVisit => {
+        // Update the record, resetting the quota if the user has visited the
+        // site since the last push.
+        return this._db.update(keyID, record => {
+          let newRecord = updateFunc(record);
+          if (!newRecord) {
+            this._recordDidNotNotify(kDROP_NOTIFICATION_REASON_NO_VERSION_INCREMENT);
+            return null;
+          }
+          // Because `unregister` is advisory only, we can still receive messages
+          // for stale Simple Push registrations from the server. To work around
+          // this, we check if the record has expired before *and* after updating
+          // the quota.
+          if (newRecord.isExpired()) {
+            return null;
+          }
+          newRecord.receivedPush(lastVisit);
+          return newRecord;
+        });
       });
-    }).then(record => {
-      var notified = false;
-      if (!record) {
-        return notified;
-      }
-      let decodedPromise;
-      if (cryptoParams) {
-        decodedPromise = PushCrypto.decodeMsg(
-          message,
-          record.p256dhPrivateKey,
-          record.p256dhPublicKey,
-          cryptoParams.dh,
-          cryptoParams.salt,
-          cryptoParams.rs,
-          cryptoParams.auth ? record.authenticationSecret : null,
-          cryptoParams.padSize
-        );
-      } else {
-        decodedPromise = Promise.resolve(null);
-      }
-      return decodedPromise.then(message => {
-        if (shouldNotify) {
-          notified = this._notifyApp(record, message);
-        }
-        // Update quota after the delay, at which point
-        // we check for visible notifications.
-        let timeoutID = setTimeout(_ =>
-          {
-            this._updateQuota(keyID);
-            if (!this._updateQuotaTimeouts.delete(timeoutID)) {
-              console.debug("receivedPushMessage: quota update timeout missing?");
-            }
-          }, prefs.get("quotaUpdateDelay"));
-        this._updateQuotaTimeouts.add(timeoutID);
-        return notified;
-      }, error => {
-        console.error("receivedPushMessage: Error decrypting message", error);
-      });
-    }).catch(error => {
-      console.error("receivedPushMessage: Error notifying app", error);
+    });
+  },
+
+  /**
+   * Decrypts an incoming message and notifies the associated service worker.
+   *
+   * @param {PushRecord} record The receiving registration.
+   * @param {String} messageID The message ID.
+   * @param {ArrayBuffer|Uint8Array} data The encrypted message data.
+   * @param {Object} cryptoParams The message encryption settings.
+   * @returns {Promise} Resolves with an ack status code.
+   */
+  _decryptAndNotifyApp(record, messageID, data, cryptoParams) {
+    if (!cryptoParams) {
+      return this._notifyApp(record, messageID, null);
+    }
+    return PushCrypto.decodeMsg(
+      data,
+      record.p256dhPrivateKey,
+      record.p256dhPublicKey,
+      cryptoParams.dh,
+      cryptoParams.salt,
+      cryptoParams.rs,
+      cryptoParams.auth ? record.authenticationSecret : null,
+      cryptoParams.padSize
+    ).then(message => this._notifyApp(record, messageID, message), error => {
+      let message = gDOMBundle.formatStringFromName(
+        "PushMessageDecryptionFailure", [record.scope, String(error)], 2);
+      gPushNotifier.notifyError(record.scope, record.principal, message,
+                                Ci.nsIScriptError.errorFlag);
+      return Ci.nsIPushErrorReporter.ACK_DECRYPTION_ERROR;
     });
   },
 
   _updateQuota: function(keyID) {
     console.debug("updateQuota()");
 
     this._db.update(keyID, record => {
       // Record may have expired from an earlier quota update.
@@ -857,17 +890,18 @@ this.PushService = {
       }
       return record;
     }).then(record => {
       if (record && record.isExpired()) {
         this._recordDidNotNotify(kDROP_NOTIFICATION_REASON_EXPIRED);
         // Drop the registration in the background. If the user returns to the
         // site, the service worker will be notified on the next `idle-daily`
         // event.
-        this._backgroundUnregister(record);
+        this._backgroundUnregister(record,
+          Ci.nsIPushErrorReporter.UNSUBSCRIBE_QUOTA_EXCEEDED);
       }
       if (this._updateQuotaTestCallback) {
         // Callback so that test may be notified when the quota update is complete.
         this._updateQuotaTestCallback();
       }
     }).catch(error => {
       console.debug("updateQuota: Error while trying to update quota", error);
     });
@@ -895,68 +929,85 @@ this.PushService = {
     }
     if (count > 1) {
       this._visibleNotifications.set(origin, count - 1);
     } else {
       this._visibleNotifications.delete(origin);
     }
   },
 
-  _notifyApp: function(aPushRecord, message) {
+  reportDeliveryError(messageID, reason) {
+    console.debug("reportDeliveryError()", messageID, reason);
+    if (this._state == PUSH_SERVICE_RUNNING &&
+        this._service.isConnected()) {
+
+      // Only report errors if we're initialized and connected.
+      this._service.reportDeliveryError(messageID, reason);
+    }
+  },
+
+  _notifyApp(aPushRecord, messageID, message) {
     if (!aPushRecord || !aPushRecord.scope ||
         aPushRecord.originAttributes === undefined) {
       console.error("notifyApp: Invalid record", aPushRecord);
-      return false;
+      return Ci.nsIPushErrorReporter.ACK_NOT_DELIVERED;
     }
 
     console.debug("notifyApp()", aPushRecord.scope);
 
     // If permission has been revoked, trash the message.
     if (!aPushRecord.hasPermission()) {
       console.warn("notifyApp: Missing push permission", aPushRecord);
-      return false;
+      return Ci.nsIPushErrorReporter.ACK_NOT_DELIVERED;
     }
 
     let payload = ArrayBuffer.isView(message) ?
                   new Uint8Array(message.buffer) : message;
 
     if (aPushRecord.quotaApplies()) {
       // Don't record telemetry for chrome push messages.
       Services.telemetry.getHistogramById("PUSH_API_NOTIFY").add();
     }
 
     if (payload) {
       gPushNotifier.notifyPushWithData(aPushRecord.scope,
                                        aPushRecord.principal,
-                                       payload.length, payload);
+                                       messageID, payload.length, payload);
     } else {
-      gPushNotifier.notifyPush(aPushRecord.scope, aPushRecord.principal);
+      gPushNotifier.notifyPush(aPushRecord.scope, aPushRecord.principal,
+                               messageID);
     }
 
-    return true;
+    return Ci.nsIPushErrorReporter.ACK_DELIVERED;
   },
 
   getByKeyID: function(aKeyID) {
     return this._db.getByKeyID(aKeyID);
   },
 
   getAllUnexpired: function() {
     return this._db.getAllUnexpired();
   },
 
-  _sendRequest: function(action, aRecord) {
+  _sendRequest(action, ...params) {
     if (this._state == PUSH_SERVICE_CONNECTION_DISABLE) {
       return Promise.reject(new Error("Push service disabled"));
     } else if (this._state == PUSH_SERVICE_ACTIVE_OFFLINE) {
       if (this._service.serviceType() == "WebSocket" && action == "unregister") {
         return Promise.resolve();
       }
       return Promise.reject(new Error("Push service offline"));
     }
-    return this._service.request(action, aRecord);
+    switch (action) {
+      case "register":
+        return this._service.register(...params);
+      case "unregister":
+        return this._service.unregister(...params);
+    }
+    return Promise.reject(new Error("Unknown request type: " + action));
   },
 
   /**
    * Called on message from the child process. aPageRecord is an object sent by
    * navigator.push, identifying the sending page and other fields.
    */
   _registerWithServer: function(aPageRecord) {
     console.debug("registerWithServer()", aPageRecord);
@@ -969,19 +1020,19 @@ this.PushService = {
         this._deletePendingRequest(aPageRecord);
         return record.toSubscription();
       }, err => {
         this._deletePendingRequest(aPageRecord);
         throw err;
      });
   },
 
-  _sendUnregister: function(aRecord) {
+  _sendUnregister(aRecord, aReason) {
     Services.telemetry.getHistogramById("PUSH_API_UNSUBSCRIBE_ATTEMPT").add();
-    return this._sendRequest("unregister", aRecord).then(function(v) {
+    return this._sendRequest("unregister", aRecord, aReason).then(function(v) {
       Services.telemetry.getHistogramById("PUSH_API_UNSUBSCRIBE_SUCCEEDED").add();
       return v;
     }).catch(function(e) {
       Services.telemetry.getHistogramById("PUSH_API_UNSUBSCRIBE_FAILED").add();
       return Promise.reject(e);
     });
   },
 
@@ -995,17 +1046,18 @@ this.PushService = {
     return this._db.put(aRecord)
       .then(record => {
         Services.telemetry.getHistogramById("PUSH_API_SUBSCRIBE_SUCCEEDED").add();
         return record;
       })
       .catch(error => {
         Services.telemetry.getHistogramById("PUSH_API_SUBSCRIBE_FAILED").add()
         // Unable to save. Destroy the subscription in the background.
-        this._backgroundUnregister(aRecord);
+        this._backgroundUnregister(aRecord,
+                                   Ci.nsIPushErrorReporter.UNSUBSCRIBE_MANUAL);
         throw error;
       });
   },
 
   /**
    * Exceptions thrown in _onRegisterError are caught by the promise obtained
    * from _service.request, causing the promise to be rejected instead.
    */
@@ -1082,17 +1134,18 @@ this.PushService = {
 
     return this._getByPageRecord(aPageRecord)
       .then(record => {
         if (record === undefined) {
           return false;
         }
 
         return Promise.all([
-          this._sendUnregister(record),
+          this._sendUnregister(record,
+                               Ci.nsIPushErrorReporter.UNSUBSCRIBE_MANUAL),
           this._db.delete(record.keyID),
         ]).then(() => true);
       });
   },
 
   clear: function(info) {
     if (info.domain == "*") {
       return this._clearAll();
@@ -1192,17 +1245,18 @@ this.PushService = {
 
     if (data == "cleared") {
       // If the permission list was cleared, drop all registrations
       // that are subject to quota.
       return this._db.clearIf(record => {
         if (record.quotaApplies()) {
           if (!record.isExpired()) {
             // Drop the registration in the background.
-            this._backgroundUnregister(record);
+            this._backgroundUnregister(record,
+              Ci.nsIPushErrorReporter.UNSUBSCRIBE_PERMISSION_REVOKED);
           }
           return true;
         }
         return false;
       });
     }
 
     let permission = subject.QueryInterface(Ci.nsIPermission);
@@ -1268,17 +1322,18 @@ this.PushService = {
   _permissionDenied: function(record, cursor) {
     console.debug("permissionDenied()");
 
     if (!record.quotaApplies() || record.isExpired()) {
       // Ignore already-expired records.
       return;
     }
     // Drop the registration in the background.
-    this._backgroundUnregister(record);
+    this._backgroundUnregister(record,
+      Ci.nsIPushErrorReporter.UNSUBSCRIBE_PERMISSION_REVOKED);
     record.setQuota(0);
     cursor.update(record);
   },
 
   /**
    * The update function called for each registration record if the push
    * permission is granted. If the record has expired, it will be dropped;
    * otherwise, its quota will be reset to the default value.
--- a/dom/push/PushServiceAndroidGCM.jsm
+++ b/dom/push/PushServiceAndroidGCM.jsm
@@ -190,30 +190,18 @@ this.PushServiceAndroidGCM = {
   isConnected: function() {
     return this._mainPushService != null;
   },
 
   disconnect: function() {
     console.debug("disconnect");
   },
 
-  request: function(action, record) {
-    switch (action) {
-    case "register":
-      console.debug("register:", record);
-      return this._register(record);
-    case "unregister":
-      console.debug("unregister: ", record);
-      return this._unregister(record);
-    default:
-      console.debug("Ignoring unrecognized request action:", action);
-    }
-  },
-
-  _register: function(record) {
+  register: function(record) {
+    console.debug("register:", record);
     let ctime = Date.now();
     // Caller handles errors.
     return Messaging.sendRequestForResult({
       type: "PushServiceAndroidGCM:SubscribeChannel",
     }).then(data => {
       console.debug("Got data:", data);
       return PushCrypto.generateKeys()
         .then(exportedKeys =>
@@ -229,17 +217,18 @@ this.PushServiceAndroidGCM = {
             p256dhPublicKey: exportedKeys[0],
             p256dhPrivateKey: exportedKeys[1],
             authenticationSecret: PushCrypto.generateAuthenticationSecret(),
           })
       );
     });
   },
 
-  _unregister: function(record) {
+  unregister: function(record) {
+    console.debug("unregister: ", record);
     return Messaging.sendRequestForResult({
       type: "PushServiceAndroidGCM:UnsubscribeChannel",
       channelID: record.keyID,
     });
   },
 };
 
 function PushRecordAndroidGCM(record) {
--- a/dom/push/PushServiceHttp2.jsm
+++ b/dom/push/PushServiceHttp2.jsm
@@ -461,17 +461,17 @@ this.PushServiceHttp2 = {
                       .createInstance(Ci.nsILoadGroup);
     chan.loadGroup = loadGroup;
     return chan;
   },
 
   /**
    * Subscribe new resource.
    */
-  _subscribeResource: function(aRecord) {
+  register: function(aRecord) {
     console.debug("subscribeResource()");
 
     return this._subscribeResourceInternal({
       record: aRecord,
       retries: 0
     })
     .then(result =>
       PushCrypto.generateKeys()
@@ -702,37 +702,30 @@ this.PushServiceHttp2 = {
     this._mainPushService = null;
   },
 
   _abortPendingSubscriptionRetries: function() {
     this._listenersPendingRetry.forEach((listener) => listener.abortRetry());
     this._listenersPendingRetry.clear();
   },
 
-  request: function(action, aRecord) {
-    switch (action) {
-      case "register":
-        return this._subscribeResource(aRecord);
-     case "unregister":
-        this._shutdownSubscription(aRecord.subscriptionUri);
-        return this._unsubscribeResource(aRecord.subscriptionUri);
-      default:
-        return Promise.reject(new Error("Unknown request type: " + action));
-    }
+  unregister: function(aRecord) {
+    this._shutdownSubscription(aRecord.subscriptionUri);
+    return this._unsubscribeResource(aRecord.subscriptionUri);
   },
 
   /** Push server has deleted subscription.
    *  Re-subscribe - if it succeeds send update db record and send
    *                 pushsubscriptionchange,
    *               - on error delete record and send pushsubscriptionchange
    *  TODO: maybe pushsubscriptionerror will be included.
    */
   _resubscribe: function(aSubscriptionUri) {
     this._mainPushService.getByKeyID(aSubscriptionUri)
-      .then(record => this._subscribeResource(record)
+      .then(record => this.register(record)
         .then(recordNew => {
           if (this._mainPushService) {
             this._mainPushService
                 .updateRegistrationAndNotifyApp(aSubscriptionUri, recordNew)
                 .catch(Cu.reportError);
           }
         }, error => {
           if (this._mainPushService) {
@@ -785,17 +778,17 @@ this.PushServiceHttp2 = {
       console.debug("removeListenerPendingRetry: listener not in list?");
     }
   },
 
   _pushChannelOnStop: function(aUri, aAckUri, aMessage, cryptoParams) {
     console.debug("pushChannelOnStop()");
 
     this._mainPushService.receivedPushMessage(
-      aUri, aMessage, cryptoParams, record => {
+      aUri, "", aMessage, cryptoParams, record => {
         // Always update the stored record.
         return record;
       }
     )
     .then(_ => this._ackMsgRecv(aAckUri))
     .catch(err => {
       console.error("pushChannelOnStop: Error receiving message",
         err);
--- a/dom/push/PushServiceWebSocket.jsm
+++ b/dom/push/PushServiceWebSocket.jsm
@@ -41,16 +41,36 @@ var threadManager = Cc["@mozilla.org/thr
 const kPUSHWSDB_DB_NAME = "pushapi";
 const kPUSHWSDB_DB_VERSION = 5; // Change this if the IndexedDB format changes
 const kPUSHWSDB_STORE_NAME = "pushapi";
 
 const kUDP_WAKEUP_WS_STATUS_CODE = 4774;  // WebSocket Close status code sent
                                           // by server to signal that it can
                                           // wake client up using UDP.
 
+// Maps ack statuses, unsubscribe reasons, and delivery error reasons to codes
+// included in request payloads.
+const kACK_STATUS_TO_CODE = {
+  [Ci.nsIPushErrorReporter.ACK_DELIVERED]: 100,
+  [Ci.nsIPushErrorReporter.ACK_DECRYPTION_ERROR]: 101,
+  [Ci.nsIPushErrorReporter.ACK_NOT_DELIVERED]: 102,
+};
+
+const kUNREGISTER_REASON_TO_CODE = {
+  [Ci.nsIPushErrorReporter.UNSUBSCRIBE_MANUAL]: 200,
+  [Ci.nsIPushErrorReporter.UNSUBSCRIBE_QUOTA_EXCEEDED]: 201,
+  [Ci.nsIPushErrorReporter.UNSUBSCRIBE_PERMISSION_REVOKED]: 202,
+};
+
+const kDELIVERY_REASON_TO_CODE = {
+  [Ci.nsIPushErrorReporter.DELIVERY_UNCAUGHT_EXCEPTION]: 301,
+  [Ci.nsIPushErrorReporter.DELIVERY_UNHANDLED_REJECTION]: 302,
+  [Ci.nsIPushErrorReporter.DELIVERY_INTERNAL_ERROR]: 303,
+};
+
 const prefs = new Preferences("dom.push.");
 
 this.EXPORTED_SYMBOLS = ["PushServiceWebSocket"];
 
 XPCOMUtils.defineLazyGetter(this, "console", () => {
   let {ConsoleAPI} = Cu.import("resource://gre/modules/Console.jsm", {});
   return new ConsoleAPI({
     maxLogLevelPref: "dom.push.loglevel",
@@ -900,49 +920,48 @@ this.PushServiceWebSocket = {
 
   _handleDataUpdate: function(update) {
     let promise;
     if (typeof update.channelID != "string") {
       console.warn("handleDataUpdate: Discarding update without channel ID",
         update);
       return;
     }
-    // Unconditionally ack the update. This is important because the Push
-    // server requires the client to ack all outstanding updates before
-    // resuming delivery. However, the server doesn't verify the encryption
-    // params, and can't ensure that an update is encrypted correctly because
-    // it doesn't have the private key. Thus, if we only acked valid updates,
-    // it would be possible for a single invalid one to block delivery of all
-    // subsequent updates. A nack would be more appropriate for this case, but
-    // the protocol doesn't currently support them.
-    this._sendAck(update.channelID, update.version);
     if (typeof update.data != "string") {
       promise = this._mainPushService.receivedPushMessage(
         update.channelID,
+        update.version,
         null,
         null,
         record => record
       );
     } else {
       let params = getCryptoParams(update.headers);
-      if (!params) {
-        console.warn("handleDataUpdate: Discarding invalid encrypted message",
-          update);
-        return;
+      if (params) {
+        let message = base64UrlDecode(update.data);
+        promise = this._mainPushService.receivedPushMessage(
+          update.channelID,
+          update.version,
+          message,
+          params,
+          record => record
+        );
+      } else {
+        promise = Promise.reject(new Error("Invalid crypto headers"));
       }
-      let message = base64UrlDecode(update.data);
-      promise = this._mainPushService.receivedPushMessage(
-        update.channelID,
-        message,
-        params,
-        record => record
-      );
     }
-    promise.catch(err => {
-      console.error("handleDataUpdate: Error delivering message", err);
+    promise.then(status => {
+      this._sendAck(update.channelID, update.version, status);
+    }, err => {
+      console.error("handleDataUpdate: Error delivering message", update, err);
+      this._sendAck(update.channelID, update.version,
+        Ci.nsIPushErrorReporter.ACK_DECRYPTION_ERROR);
+    }).catch(err => {
+      console.error("handleDataUpdate: Error acknowledging message", update,
+        err);
     });
   },
 
   /**
    * Protocol handler invoked by server message.
    */
   _handleNotificationReply: function(reply) {
     console.debug("handleNotificationReply()");
@@ -976,72 +995,94 @@ this.PushServiceWebSocket = {
       if (typeof version === "string") {
         version = parseInt(version, 10);
       }
 
       if (typeof version === "number" && version >= 0) {
         // FIXME(nsm): this relies on app update notification being infallible!
         // eventually fix this
         this._receivedUpdate(update.channelID, version);
-        this._sendAck(update.channelID, version);
       }
     }
   },
 
-  // FIXME(nsm): batch acks for efficiency reasons.
-  _sendAck: function(channelID, version) {
+  reportDeliveryError(messageID, reason) {
+    console.debug("reportDeliveryError()");
+    let code = kDELIVERY_REASON_TO_CODE[reason];
+    if (!code) {
+      throw new Error('Invalid delivery error reason');
+    }
+    let data = {messageType: 'nack',
+                version: messageID,
+                code: code};
+    this._queueRequest(data);
+  },
+
+  _sendAck(channelID, version, status) {
     console.debug("sendAck()");
-    var data = {messageType: 'ack',
+    let code = kACK_STATUS_TO_CODE[status];
+    if (!code) {
+      throw new Error('Invalid ack status');
+    }
+    let data = {messageType: 'ack',
                 updates: [{channelID: channelID,
-                           version: version}]
-               };
+                           version: version,
+                           code: code}]};
     this._queueRequest(data);
   },
 
   _generateID: function() {
     let uuidGenerator = Cc["@mozilla.org/uuid-generator;1"]
                           .getService(Ci.nsIUUIDGenerator);
     // generateUUID() gives a UUID surrounded by {...}, slice them off.
     return uuidGenerator.generateUUID().toString().slice(1, -1);
   },
 
-  request: function(action, record) {
-    console.debug("request() ", action);
+  register(record) {
+    console.debug("register() ", record);
 
     // start the timer since we now have at least one request
     this._startRequestTimeoutTimer();
 
-    if (action == "register") {
-      let data = {channelID: this._generateID(),
-                  messageType: action};
+    let data = {channelID: this._generateID(),
+                messageType: "register"};
 
-      return new Promise((resolve, reject) => {
-        this._registerRequests.set(data.channelID, {
-          record: record,
-          resolve: resolve,
-          reject: reject,
-          ctime: Date.now(),
+    return new Promise((resolve, reject) => {
+      this._registerRequests.set(data.channelID, {
+        record: record,
+        resolve: resolve,
+        reject: reject,
+        ctime: Date.now(),
+      });
+      this._queueRequest(data);
+    }).then(record => {
+      if (!this._dataEnabled) {
+        return record;
+      }
+      return PushCrypto.generateKeys()
+        .then(([publicKey, privateKey]) => {
+          record.p256dhPublicKey = publicKey;
+          record.p256dhPrivateKey = privateKey;
+          record.authenticationSecret = PushCrypto.generateAuthenticationSecret();
+          return record;
         });
-        this._queueRequest(data);
-      }).then(record => {
-        if (!this._dataEnabled) {
-          return record;
-        }
-        return PushCrypto.generateKeys()
-          .then(([publicKey, privateKey]) => {
-            record.p256dhPublicKey = publicKey;
-            record.p256dhPrivateKey = privateKey;
-            record.authenticationSecret = PushCrypto.generateAuthenticationSecret();
-            return record;
-          });
-      });
+    });
+  },
+
+  unregister(record, reason) {
+    console.debug("unregister() ", record, reason);
+
+    let code = kUNREGISTER_REASON_TO_CODE[reason];
+    if (!code) {
+      return Promise.reject(new Error('Invalid unregister reason'));
     }
-
-    this._queueRequest({channelID: record.channelID,
-                        messageType: action});
+    let data = {channelID: record.channelID,
+                messageType: "unregister",
+                code: code};
+    this._queueRequest(data);
     return Promise.resolve();
   },
 
   _queueStart: Promise.resolve(),
   _notifyRequestQueue: null,
   _queue: null,
   _enqueue: function(op) {
     console.debug("enqueue()");
@@ -1099,27 +1140,32 @@ this.PushServiceWebSocket = {
         this._notifyRequestQueue = null;
       }
     }
   },
 
   _receivedUpdate: function(aChannelID, aLatestVersion) {
     console.debug("receivedUpdate: Updating", aChannelID, "->", aLatestVersion);
 
-    this._mainPushService.receivedPushMessage(aChannelID, null, null, record => {
+    this._mainPushService.receivedPushMessage(aChannelID, "", null, null, record => {
       if (record.version === null ||
           record.version < aLatestVersion) {
         console.debug("receivedUpdate: Version changed for", aChannelID,
           aLatestVersion);
         record.version = aLatestVersion;
         return record;
       }
       console.debug("receivedUpdate: No significant version change for",
         aChannelID, aLatestVersion);
       return null;
+    }).then(status => {
+      this._sendAck(aChannelID, aLatestVersion, status);
+    }).catch(err => {
+      console.error("receivedUpdate: Error acknowledging message", aChannelID,
+        aLatestVersion, err);
     });
   },
 
   // begin Push protocol handshake
   _wsOnStart: function(context) {
     console.debug("wsOnStart()");
     this._releaseWakeLock();
 
new file mode 100644
--- /dev/null
+++ b/dom/push/test/error_worker.js
@@ -0,0 +1,10 @@
+this.onpush = function(event) {
+  var request = event.data.json();
+  if (request.type == "exception") {
+    throw new Error("Uncaught exception");
+  }
+  if (request.type == "rejection") {
+    event.waitUntil(Promise.reject(
+      new Error("Unhandled rejection")));
+  }
+};
--- a/dom/push/test/mochitest.ini
+++ b/dom/push/test/mochitest.ini
@@ -2,20 +2,22 @@
 skip-if = os == "android" || toolkit == "gonk"
 support-files =
   worker.js
   frame.html
   webpush.js
   lifetime_worker.js
   test_utils.js
   mockpushserviceparent.js
+  error_worker.js
 
 [test_has_permissions.html]
 [test_permissions.html]
 [test_register.html]
 [test_multiple_register.html]
 [test_multiple_register_during_service_activation.html]
 [test_unregister.html]
 [test_multiple_register_different_scope.html]
 [test_subscription_change.html]
 [test_data.html]
 [test_try_registering_offline_disabled.html]
 [test_serviceworker_lifetime.html]
+[test_error_reporting.html]
--- a/dom/push/test/mockpushserviceparent.js
+++ b/dom/push/test/mockpushserviceparent.js
@@ -152,16 +152,23 @@ var MockService = {
 
   registration(pageRecord) {
     return this.sendRequest("registration", pageRecord);
   },
 
   unregister(pageRecord) {
     return this.sendRequest("unregister", pageRecord);
   },
+
+  reportDeliveryError(messageId, reason) {
+    sendAsyncMessage("service-delivery-error", {
+      messageId: messageId,
+      reason: reason,
+    });
+  },
 };
 
 addMessageListener("service-replace", function () {
   pushService.service = MockService;
 });
 
 addMessageListener("service-restore", function () {
   pushService.service = null;
new file mode 100644
--- /dev/null
+++ b/dom/push/test/test_error_reporting.html
@@ -0,0 +1,129 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+Bug 1246341: Report message delivery failures to the Push server.
+
+Any copyright is dedicated to the Public Domain.
+http://creativecommons.org/licenses/publicdomain/
+
+-->
+<head>
+  <title>Test for Bug 1246341</title>
+  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <script type="text/javascript" src="/tests/SimpleTest/SpawnTask.js"></script>
+  <script type="text/javascript" src="/tests/dom/push/test/test_utils.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+  <meta http-equiv="Content-type" content="text/html;charset=UTF-8">
+</head>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1246341">Mozilla Bug 1246341</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+
+<script class="testbody" type="text/javascript">
+
+  var pushNotifier = SpecialPowers.Cc["@mozilla.org/push/Notifier;1"]
+                                  .getService(SpecialPowers.Ci.nsIPushNotifier);
+
+  var reporters = new Map();
+
+  var registration;
+  add_task(function* start() {
+    yield setupPrefsAndReplaceService({
+      reportDeliveryError(messageId, reason) {
+        ok(reporters.has(messageId),
+          'Unexpected error reported for message ' + messageId);
+        var resolve = reporters.get(messageId);
+        reporters.delete(messageId);
+        resolve(reason);
+      },
+    });
+    yield setPushPermission(true);
+
+    var url = "error_worker.js" + "?" + (Math.random());
+    registration = yield navigator.serviceWorker.register(url, {scope: "."});
+  });
+
+  var controlledFrame;
+  add_task(function* createControlledIFrame() {
+    controlledFrame = yield injectControlledFrame();
+  });
+
+  var idCounter = 1;
+  function waitForDeliveryError(request) {
+    return new Promise(resolve => {
+      var data = new TextEncoder("utf-8").encode(JSON.stringify(request));
+      var principal = SpecialPowers.wrap(document).nodePrincipal;
+
+      let messageId = "message-" + (idCounter++);
+      reporters.set(messageId, resolve);
+      pushNotifier.notifyPushWithData(registration.scope, principal, messageId,
+                                      data.length, data);
+    });
+  }
+
+  add_task(function* reportDeliveryErrors() {
+    var reason = yield waitForDeliveryError({ type: "exception" });
+    is(reason, SpecialPowers.Ci.nsIPushErrorReporter.DELIVERY_UNCAUGHT_EXCEPTION,
+      "Should report uncaught exceptions");
+
+    reason = yield waitForDeliveryError({ type: "rejection" });
+    is(reason, SpecialPowers.Ci.nsIPushErrorReporter.DELIVERY_UNHANDLED_REJECTION,
+      "Should report unhandled rejections");
+  });
+
+  add_task(function* reportDecryptionError() {
+    var message = yield new Promise(resolve => {
+      var consoleService = SpecialPowers.Cc["@mozilla.org/consoleservice;1"]
+        .getService(SpecialPowers.Ci.nsIConsoleService);
+
+      var listener = SpecialPowers.wrapCallbackObject({
+        QueryInterface(iid) {
+          if (!SpecialPowers.Ci.nsISupports.equals(iid) &&
+              !SpecialPowers.Ci.nsIConsoleListener.equals(iid)) {
+            throw SpecialPowers.Cr.NS_ERROR_NO_INTERFACE;
+          }
+          return this;
+        },
+
+        observe(message) {
+          let error = message;
+          try {
+            error.QueryInterface(SpecialPowers.Ci.nsIScriptError);
+          } catch (error) {
+            return;
+          }
+          if (message.innerWindowID == controlledFrame.innerWindowId()) {
+            consoleService.unregisterListener(listener);
+            resolve(error);
+          }
+        },
+      });
+      consoleService.registerListener(listener);
+
+      var principal = SpecialPowers.wrap(document).nodePrincipal;
+      pushNotifier.notifyError(registration.scope, principal, "Push error",
+        SpecialPowers.Ci.nsIScriptError.errorFlag);
+    });
+
+    is(message.sourceName, registration.scope,
+      "Should use the qualified scope URL as the source");
+    is(message.errorMessage, "Push error",
+      "Should report the given error string");
+  });
+
+  add_task(function* unsubscribe() {
+    controlledFrame.remove();
+  });
+
+  add_task(function* unregister() {
+    yield registration.unregister();
+  });
+
+</script>
+</body>
+</html>
+
--- a/dom/push/test/test_utils.js
+++ b/dom/push/test/test_utils.js
@@ -6,16 +6,19 @@
 
   /**
    * Replaces `PushService.jsm` with a mock implementation that handles requests
    * from the DOM API. This allows tests to simulate local errors and error
    * reporting, bypassing the `PushService.jsm` machinery.
    */
   function replacePushService(mockService) {
     chromeScript.sendSyncMessage("service-replace");
+    chromeScript.addMessageListener("service-delivery-error", function(msg) {
+      mockService.reportDeliveryError(msg.messageId, msg.reason);
+    });
     chromeScript.addMessageListener("service-request", function(msg) {
       let promise;
       try {
         let handler = mockService[msg.name];
         promise = Promise.resolve(handler(msg.params));
       } catch (error) {
         promise = Promise.reject(error);
       }
@@ -192,16 +195,20 @@ function injectControlledFrame(target = 
       remove() {
         target.removeChild(iframe);
         iframe = null;
       },
       waitOnWorkerMessage(type) {
         return iframe ? iframe.contentWindow.waitOnWorkerMessage(type) :
                Promise.reject(new Error("Frame removed from document"));
       },
+      innerWindowId() {
+        var utils = SpecialPowers.getDOMWindowUtils(iframe.contentWindow);
+        return utils.currentInnerWindowID;
+      },
     };
 
     iframe.onload = () => res(controlledFrame);
     target.appendChild(iframe);
   });
 }
 
 function sendRequestToWorker(request) {
--- a/dom/push/test/xpcshell/test_clear_origin_data.js
+++ b/dom/push/test/xpcshell/test_clear_origin_data.js
@@ -103,16 +103,17 @@ add_task(function* test_webapps_cleardat
             messageType: 'register',
             status: 200,
             channelID: data.channelID,
             uaid: userAgentID,
             pushEndpoint: 'https://example.com/update/' + Math.random(),
           }));
         },
         onUnregister(data) {
+          equal(data.code, 200, 'Expected manual unregister reason');
           unregisterDone();
         },
       });
     }
   });
 
   yield Promise.all(testRecords.map(test =>
     PushService.register({
--- a/dom/push/test/xpcshell/test_handler_service.js
+++ b/dom/push/test/xpcshell/test_handler_service.js
@@ -10,17 +10,17 @@ const kServiceContractID = "@mozilla.org
 let pushService = Cc["@mozilla.org/push/Service;1"].getService(Ci.nsIPushService);
 
 add_test(function test_service_instantiation() {
   do_load_manifest("PushServiceHandler.manifest");
 
   let scope = "chrome://test-scope";
   let pushNotifier = Cc["@mozilla.org/push/Notifier;1"].getService(Ci.nsIPushNotifier);
   let principal = Services.scriptSecurityManager.getSystemPrincipal();
-  pushNotifier.notifyPush(scope, principal);
+  pushNotifier.notifyPush(scope, principal, "");
 
   // Now get a handle to our service and check it received the notification.
   let handlerService = Cc[kServiceContractID]
                        .getService(Ci.nsISupports)
                        .wrappedJSObject;
 
   equal(handlerService.observed.length, 1);
   equal(handlerService.observed[0].topic, pushService.pushTopic);
--- a/dom/push/test/xpcshell/test_notification_ack.js
+++ b/dom/push/test/xpcshell/test_notification_ack.js
@@ -72,45 +72,48 @@ add_task(function* test_notification_ack
               channelID: '21668e05-6da8-42c9-b8ab-9cc3f4d5630c',
               version: 2
             }]
           }));
         },
         onACK(request) {
           equal(request.messageType, 'ack', 'Should send acknowledgements');
           let updates = request.updates;
-          ok(Array.isArray(updates),
-            'Should send an array of acknowledged updates');
-          equal(updates.length, 1,
-            'Should send one acknowledged update per packet');
           switch (++acks) {
           case 1:
+            deepEqual([{
+              channelID: '21668e05-6da8-42c9-b8ab-9cc3f4d5630c',
+              version: 2,
+              code: 100,
+            }], updates, 'Wrong updates for acknowledgement 1');
             this.serverSendMsg(JSON.stringify({
               messageType: 'notification',
               updates: [{
                 channelID: '9a5ff87f-47c9-4215-b2b8-0bdd38b4b305',
                 version: 4
               }, {
                 channelID: '5477bfda-22db-45d4-9614-fee369630260',
                 version: 6
               }]
             }));
             break;
 
           case 2:
             deepEqual([{
               channelID: '9a5ff87f-47c9-4215-b2b8-0bdd38b4b305',
-              version: 4
+              version: 4,
+              code: 100,
             }], updates, 'Wrong updates for acknowledgement 2');
             break;
 
           case 3:
             deepEqual([{
               channelID: '5477bfda-22db-45d4-9614-fee369630260',
-              version: 6
+              version: 6,
+              code: 100,
             }], updates, 'Wrong updates for acknowledgement 3');
             ackDone();
             break;
 
           default:
             ok(false, 'Unexpected acknowledgement ' + acks);
           }
         }
--- a/dom/push/test/xpcshell/test_notification_data.js
+++ b/dom/push/test/xpcshell/test_notification_data.js
@@ -103,17 +103,17 @@ add_task(function* test_notification_ack
             status: 200,
             use_webpush: true,
           }));
           server = this;
           setupDone();
         },
         onACK(request) {
           if (ackDone) {
-            ackDone(request.updates);
+            ackDone(request);
           }
         }
       });
     }
   });
   yield setupDonePromise;
 });
 
@@ -126,48 +126,51 @@ add_task(function* test_notification_ack
         headers: {
           encryption_key: 'keyid="notification1"; dh="BO_tgGm-yvYAGLeRe16AvhzaUcpYRiqgsGOlXpt0DRWDRGGdzVLGlEVJMygqAUECarLnxCiAOHTP_znkedrlWoU"',
           encryption: 'keyid="notification1";salt="uAZaiXpOSfOLJxtOCZ09dA"',
           encoding: 'aesgcm128',
         },
         data: 'NwrrOWPxLE8Sv5Rr0Kep7n0-r_j3rsYrUw_CXPo',
         version: 'v1',
       },
+      ackCode: 100,
       receive: {
         scope: 'https://example.com/page/1',
         data: 'Some message'
       }
     },
     {
       channelID: 'subscription2',
       version: 'v2',
       send: {
         headers: {
           encryption_key: 'keyid="notification2"; dh="BKVdQcgfncpNyNWsGrbecX0zq3eHIlHu5XbCGmVcxPnRSbhjrA6GyBIeGdqsUL69j5Z2CvbZd-9z1UBH0akUnGQ"',
           encryption: 'keyid="notification2";salt="vFn3t3M_k42zHBdpch3VRw"',
           encoding: 'aesgcm128',
         },
         data: 'Zt9dEdqgHlyAL_l83385aEtb98ZBilz5tgnGgmwEsl5AOCNgesUUJ4p9qUU',
       },
+      ackCode: 100,
       receive: {
         scope: 'https://example.com/page/2',
         data: 'Some message'
       }
     },
     {
       channelID: 'subscription3',
       version: 'v3',
       send: {
         headers: {
           encryption_key: 'keyid="notification3";dh="BD3xV_ACT8r6hdIYES3BJj1qhz9wyv7MBrG9vM2UCnjPzwE_YFVpkD-SGqE-BR2--0M-Yf31wctwNsO1qjBUeMg"',
           encryption: 'keyid="notification3"; salt="DFq188piWU7osPBgqn4Nlg"; rs=24',
           encoding: 'aesgcm128',
         },
         data: 'LKru3ZzxBZuAxYtsaCfaj_fehkrIvqbVd1iSwnwAUgnL-cTeDD-83blxHXTq7r0z9ydTdMtC3UjAcWi8LMnfY-BFzi0qJAjGYIikDA',
       },
+      ackCode: 100,
       receive: {
         scope: 'https://example.com/page/3',
         data: 'Some message'
       }
     },
     // This message uses the newer, authenticated form based on the crypto-key
     // header field.  No padding or record size changes.
     {
@@ -176,16 +179,17 @@ add_task(function* test_notification_ack
       send: {
         headers: {
           crypto_key: 'keyid=v4;dh="BHqG01j7rOfp12BEDzxWXxlCaU4cdOx2DZAwCt3QuzEsnXN9lCna9QmZCkVpXsx7sAlaEmtl_VfF1lHlFS7XWcA"',
           encryption: 'keyid="v4";salt="X5-iy5rzhm4naNmMHdSYJw"',
           encoding: 'aesgcm128',
         },
         data: '7YlxyNlZsNX4UNknHxzTqFrcrzz58W95uXBa0iY',
       },
+      ackCode: 100,
       receive: {
         scope: 'https://example.com/page/1',
         data: 'Some message'
       }
     },
     // A message encoded with `aesgcm` (2 bytes of padding).
     {
       channelID: 'subscription1',
@@ -193,16 +197,17 @@ add_task(function* test_notification_ack
       send: {
         headers: {
           crypto_key: 'dh="BMh_vsnqu79ZZkMTYkxl4gWDLdPSGE72Lr4w2hksSFW398xCMJszjzdblAWXyhSwakRNEU_GopAm4UGzyMVR83w"',
           encryption: 'salt="C14Wb7rQTlXzrgcPHtaUzw"',
           encoding: 'aesgcm',
         },
         data: 'pus4kUaBWzraH34M-d_oN8e0LPpF_X6acx695AMXovDe',
       },
+      ackCode: 100,
       receive: {
         scope: 'https://example.com/page/1',
         data: 'Another message'
       }
     },
     // A message with 17 bytes of padding and rs of 24
     {
       channelID: 'subscription2',
@@ -210,16 +215,17 @@ add_task(function* test_notification_ack
       send: {
         headers: {
           crypto_key: 'keyid="v5"; dh="BJhyKIH5P30YUKn1bolj_LMnael1-KZT_aGXgD2CRspBfv9gcUhVAmpxToZrw7QQEKl9K83b3zcqNY6G_dFhEsI"',
           encryption: 'keyid=v5;salt="bLmqCy550eK1Ao41tD7orA";rs=24',
           encoding: 'aesgcm128',
         },
         data: 'SQDlDg1ftLkM_ruZlmyB2bk9L78HYtkcbA-y4-uAxwL-G4KtOA-J-A_rJ007Vi6NUkQe9K4kSZeIBrIUpmGv',
       },
+      ackCode: 100,
       receive: {
         scope: 'https://example.com/page/2',
         data: 'Some message'
       }
     },
     // A message without key identifiers.
     {
       channelID: 'subscription3',
@@ -227,39 +233,58 @@ add_task(function* test_notification_ack
       send: {
         headers: {
           crypto_key: 'dh="BEgnDmVw9Gcn1fWA5t53Jtpsgfewk_pzsjSc_PBPpPmROWGQA2v8ESrSsQgosNXx0o-uMMhi9tDAUeks3380kd8"',
           encryption: 'salt=T9DM8bNxuMHRVTn4LzkJDQ',
           encoding: 'aesgcm128',
         },
         data: '7KUCi0dBBJbWmsYTqEqhFrgTv4ZOo_BmQRQ_2kY',
       },
+      ackCode: 100,
       receive: {
         scope: 'https://example.com/page/3',
         data: 'Some message'
       }
     },
+    // A malformed encrypted message.
+    {
+      channelID: 'subscription3',
+      version: 'v7',
+      send: {
+        headers: {
+          crypto_key: 'dh=AAAAAAAA',
+          encryption: 'salt=AAAAAAAA',
+        },
+        data: 'AAAAAAAA',
+      },
+      ackCode: 101,
+      receive: null,
+    },
   ];
 
   let sendAndReceive = testData => {
-    let messageReceived = promiseObserverNotification(PushServiceComponent.pushTopic, (subject, data) => {
+    let messageReceived = testData.receive ? promiseObserverNotification(PushServiceComponent.pushTopic, (subject, data) => {
       let notification = subject.QueryInterface(Ci.nsIPushMessage);
       equal(notification.text(), testData.receive.data,
             'Check data for notification ' + testData.version);
       equal(data, testData.receive.scope,
             'Check scope for notification ' + testData.version);
       return true;
-    });
+    }) : Promise.resolve();
 
     let ackReceived = new Promise(resolve => ackDone = resolve)
         .then(ackData => {
-          deepEqual([{
-            channelID: testData.channelID,
-            version: testData.version
-          }], ackData, 'Check updates for acknowledgment ' + testData.version);
+          deepEqual({
+            messageType: 'ack',
+            updates: [{
+              channelID: testData.channelID,
+              version: testData.version,
+              code: testData.ackCode,
+            }],
+          }, ackData, 'Check updates for acknowledgment ' + testData.version);
         });
 
     let msg = JSON.parse(JSON.stringify(testData.send));
     msg.messageType = 'notification';
     msg.channelID = testData.channelID;
     msg.version = testData.version;
     server.serverSendMsg(JSON.stringify(msg));
 
--- a/dom/push/test/xpcshell/test_permissions.js
+++ b/dom/push/test/xpcshell/test_permissions.js
@@ -113,16 +113,18 @@ add_task(function* setUp() {
           }));
           handshakeDone();
         },
         onUnregister(request) {
           let resolve = unregisterDefers[request.channelID];
           equal(typeof resolve, 'function',
             'Dropped unexpected channel ID ' + request.channelID);
           delete unregisterDefers[request.channelID];
+          equal(request.code, 202,
+            'Expected permission revoked unregister reason');
           resolve();
         },
         onACK(request) {},
       });
     }
   });
   yield handshakePromise;
 });
--- a/dom/push/test/xpcshell/test_quota_exceeded.js
+++ b/dom/push/test/xpcshell/test_quota_exceeded.js
@@ -118,16 +118,17 @@ add_task(function* test_expiration_origi
             updates: [{
               channelID: '46cc6f6a-c106-4ffa-bb7c-55c60bd50c41',
               version: 1,
             }],
           }));
         },
         onUnregister(request) {
           equal(request.channelID, 'eb33fc90-c883-4267-b5cb-613969e8e349', 'Unregistered wrong channel ID');
+          equal(request.code, 201, 'Expected quota exceeded unregister reason');
           unregisterDone();
         },
         // We expect to receive acks, but don't care about their
         // contents.
         onACK(request) {},
       });
     },
   });
--- a/dom/push/test/xpcshell/test_quota_observer.js
+++ b/dom/push/test/xpcshell/test_quota_observer.js
@@ -86,16 +86,17 @@ add_task(function* test_expiration_histo
             updates: [{
               channelID: '379c0668-8323-44d2-a315-4ee83f1a9ee9',
               version: 2,
             }],
           }));
         },
         onUnregister(request) {
           equal(request.channelID, '379c0668-8323-44d2-a315-4ee83f1a9ee9', 'Dropped wrong channel ID');
+          equal(request.code, 201, 'Expected quota exceeded unregister reason');
           unregisterDone();
         },
         onACK(request) {},
       });
     }
   });
 
   yield subChangePromise;
--- a/dom/push/test/xpcshell/test_register_rollback.js
+++ b/dom/push/test/xpcshell/test_register_rollback.js
@@ -54,16 +54,17 @@ add_task(function* test_register_rollbac
             status: 200,
             uaid: userAgentID,
             channelID,
             pushEndpoint: 'https://example.com/update/rollback'
           }));
         },
         onUnregister(request) {
           equal(request.channelID, channelID, 'Unregister: wrong channel ID');
+          equal(request.code, 200, 'Expected manual unregister reason');
           this.serverSendMsg(JSON.stringify({
             messageType: 'unregister',
             status: 200,
             channelID
           }));
           unregisterDone();
         }
       });
--- a/dom/push/test/xpcshell/test_unregister_success.js
+++ b/dom/push/test/xpcshell/test_unregister_success.js
@@ -37,16 +37,17 @@ add_task(function* test_unregister_succe
           this.serverSendMsg(JSON.stringify({
             messageType: 'hello',
             status: 200,
             uaid: 'fbe865a6-aeb8-446f-873c-aeebdb8d493c'
           }));
         },
         onUnregister(request) {
           equal(request.channelID, channelID, 'Should include the channel ID');
+          equal(request.code, 200, 'Expected manual unregister reason');
           this.serverSendMsg(JSON.stringify({
             messageType: 'unregister',
             status: 200,
             channelID
           }));
           unregisterDone();
         }
       });
--- a/dom/security/nsMixedContentBlocker.cpp
+++ b/dom/security/nsMixedContentBlocker.cpp
@@ -495,16 +495,27 @@ nsMixedContentBlocker::ShouldLoad(bool a
 
 
     // This content policy works as a whitelist.
     default:
       MOZ_ASSERT(false, "Mixed content of unknown type");
       break;
   }
 
+  // Make sure to get the URI the load started with. No need to check
+  // outer schemes because all the wrapping pseudo protocols inherit the
+  // security properties of the actual network request represented
+  // by the innerMost URL.
+  nsCOMPtr<nsIURI> innerContentLocation = NS_GetInnermostURI(aContentLocation);
+  if (!innerContentLocation) {
+    NS_ERROR("Can't get innerURI from aContentLocation");
+    *aDecision = REJECT_REQUEST;
+    return NS_OK;
+  }
+
  /* Get the scheme of the sub-document resource to be requested. If it is
   * a safe to load in an https context then mixed content doesn't apply.
   *
   * Check Protocol Flags to determine if scheme is safe to load:
   * URI_DOES_NOT_RETURN_DATA - e.g.
   *   "mailto"
   * URI_IS_LOCAL_RESOURCE - e.g.
   *   "data",
@@ -516,26 +527,26 @@ nsMixedContentBlocker::ShouldLoad(bool a
   *   "https",
   *   "moz-safe-about"
   *
   */
   bool schemeLocal = false;
   bool schemeNoReturnData = false;
   bool schemeInherits = false;
   bool schemeSecure = false;
-  if (NS_FAILED(NS_URIChainHasFlags(aContentLocation, nsIProtocolHandler::URI_IS_LOCAL_RESOURCE , &schemeLocal))  ||
-      NS_FAILED(NS_URIChainHasFlags(aContentLocation, nsIProtocolHandler::URI_DOES_NOT_RETURN_DATA, &schemeNoReturnData)) ||
-      NS_FAILED(NS_URIChainHasFlags(aContentLocation, nsIProtocolHandler::URI_INHERITS_SECURITY_CONTEXT, &schemeInherits)) ||
-      NS_FAILED(NS_URIChainHasFlags(aContentLocation, nsIProtocolHandler::URI_SAFE_TO_LOAD_IN_SECURE_CONTEXT, &schemeSecure))) {
+  if (NS_FAILED(NS_URIChainHasFlags(innerContentLocation, nsIProtocolHandler::URI_IS_LOCAL_RESOURCE , &schemeLocal))  ||
+      NS_FAILED(NS_URIChainHasFlags(innerContentLocation, nsIProtocolHandler::URI_DOES_NOT_RETURN_DATA, &schemeNoReturnData)) ||
+      NS_FAILED(NS_URIChainHasFlags(innerContentLocation, nsIProtocolHandler::URI_INHERITS_SECURITY_CONTEXT, &schemeInherits)) ||
+      NS_FAILED(NS_URIChainHasFlags(innerContentLocation, nsIProtocolHandler::URI_SAFE_TO_LOAD_IN_SECURE_CONTEXT, &schemeSecure))) {
     *aDecision = REJECT_REQUEST;
     return NS_ERROR_FAILURE;
   }
   // TYPE_IMAGE redirects are cached based on the original URI, not the final
   // destination and hence cache hits for images may not have the correct
-  // aContentLocation.  Check if the cached hit went through an http redirect,
+  // innerContentLocation.  Check if the cached hit went through an http redirect,
   // and if it did, we can't treat this as a secure subresource.
   if (!aHadInsecureImageRedirect &&
       (schemeLocal || schemeNoReturnData || schemeInherits || schemeSecure)) {
     *aDecision = ACCEPT;
      return NS_OK;
   }
 
   // Since there are cases where aRequestingLocation and aRequestPrincipal are
@@ -609,24 +620,24 @@ nsMixedContentBlocker::ShouldLoad(bool a
   if (!requestingLocation) {
     *aDecision = REJECT_REQUEST;
     return NS_OK;
   }
 
   // Check the parent scheme. If it is not an HTTPS page then mixed content
   // restrictions do not apply.
   bool parentIsHttps;
-  nsCOMPtr<nsIURI> innerURI = NS_GetInnermostURI(requestingLocation);
-  if (!innerURI) {
+  nsCOMPtr<nsIURI> innerRequestingLocation = NS_GetInnermostURI(requestingLocation);
+  if (!innerRequestingLocation) {
     NS_ERROR("Can't get innerURI from requestingLocation");
     *aDecision = REJECT_REQUEST;
     return NS_OK;
   }
 
-  nsresult rv = innerURI->SchemeIs("https", &parentIsHttps);
+  nsresult rv = innerRequestingLocation->SchemeIs("https", &parentIsHttps);
   if (NS_FAILED(rv)) {
     NS_ERROR("requestingLocation->SchemeIs failed");
     *aDecision = REJECT_REQUEST;
     return NS_OK;
   }
   if (!parentIsHttps) {
     *aDecision = ACCEPT;
     return NS_OK;
@@ -660,22 +671,22 @@ nsMixedContentBlocker::ShouldLoad(bool a
     *aDecision = REJECT_REQUEST;
     return NS_OK;
   }
 
   // Disallow mixed content loads for workers, shared workers and service
   // workers.
   if (isWorkerType) {
     // For workers, we can assume that we're mixed content at this point, since
-    // the parent is https, and the protocol associated with aContentLocation
+    // the parent is https, and the protocol associated with innerContentLocation
     // doesn't map to the secure URI flags checked above.  Assert this for
     // sanity's sake
 #ifdef DEBUG
     bool isHttpsScheme = false;
-    rv = aContentLocation->SchemeIs("https", &isHttpsScheme);
+    rv = innerContentLocation->SchemeIs("https", &isHttpsScheme);
     NS_ENSURE_SUCCESS(rv, rv);
     MOZ_ASSERT(!isHttpsScheme);
 #endif
     *aDecision = REJECT_REQUEST;
     return NS_OK;
   }
 
   // The page might have set the CSP directive 'upgrade-insecure-requests'. In such
@@ -685,17 +696,17 @@ nsMixedContentBlocker::ShouldLoad(bool a
   //
   // Please note that the CSP directive 'upgrade-insecure-requests' only applies to
   // http: and ws: (for websockets). Websockets are not subject to mixed content
   // blocking since insecure websockets are not allowed within secure pages. Hence,
   // we only have to check against http: here. Skip mixed content blocking if the
   // subresource load uses http: and the CSP directive 'upgrade-insecure-requests'
   // is present on the page.
   bool isHttpScheme = false;
-  rv = aContentLocation->SchemeIs("http", &isHttpScheme);
+  rv = innerContentLocation->SchemeIs("http", &isHttpScheme);
   NS_ENSURE_SUCCESS(rv, rv);
   if (isHttpScheme && docShell->GetDocument()->GetUpgradeInsecureRequests(isPreload)) {
     *aDecision = ACCEPT;
     return NS_OK;
   }
 
   // Determine if the rootDoc is https and if the user decided to allow Mixed Content
   bool rootHasSecureConnection = false;
@@ -782,23 +793,23 @@ nsMixedContentBlocker::ShouldLoad(bool a
   // is not blocked (e.g., for images).  For more detail, see:
   //   https://bugzilla.mozilla.org/show_bug.cgi?id=1198572#c19
   //
   // We do not count requests aHadInsecureImageRedirect=true, since these are
   // just an artifact of the image caching system.
   bool active = (classification == eMixedScript);
   if (!aHadInsecureImageRedirect) {
     if (XRE_IsParentProcess()) {
-      AccumulateMixedContentHSTS(aContentLocation, active);
+      AccumulateMixedContentHSTS(innerContentLocation, active);
     } else {
       // Ask the parent process to do the same call
       mozilla::dom::ContentChild* cc = mozilla::dom::ContentChild::GetSingleton();
       if (cc) {
         mozilla::ipc::URIParams uri;
-        SerializeURI(aContentLocation, uri);
+        SerializeURI(innerContentLocation, uri);
         cc->SendAccumulateMixedContentHSTS(uri, active);
       }
     }
   }
 
   // set hasMixedContentObjectSubrequest on this object if necessary
   if (aContentType == TYPE_OBJECT_SUBREQUEST) {
     rootDoc->SetHasMixedContentObjectSubrequest(true);
@@ -905,19 +916,16 @@ nsMixedContentBlocker::ShouldLoad(bool a
 
     // Fire the event from a script runner as it is unsafe to run script
     // from within ShouldLoad
     nsContentUtils::AddScriptRunner(
       new nsMixedContentEvent(aRequestingContext, classification, rootHasSecureConnection));
     *aDecision = ACCEPT;
     return NS_OK;
   }
-
-  *aDecision = REJECT_REQUEST;
-  return NS_OK;
 }
 
 NS_IMETHODIMP
 nsMixedContentBlocker::ShouldProcess(uint32_t aContentType,
                                      nsIURI* aContentLocation,
                                      nsIURI* aRequestingLocation,
                                      nsISupports* aRequestingContext,
                                      const nsACString& aMimeGuess,
new file mode 100644
--- /dev/null
+++ b/dom/security/test/unit/test_csp_upgrade_insecure_request_header.js
@@ -0,0 +1,98 @@
+var Cu = Components.utils;
+var Ci = Components.interfaces;
+
+Cu.import("resource://testing-common/httpd.js");
+Cu.import("resource://gre/modules/NetUtil.jsm");
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+
+XPCOMUtils.defineLazyGetter(this, "URL", function() {
+  return "http://localhost:" + httpserver.identity.primaryPort;
+});
+
+var httpserver =  null;
+var channel = null;
+var curTest = null;
+var testpath = "/footpath";
+
+var tests = [
+  {
+    description: "should not set request header for TYPE_OTHER",
+    expectingHeader: false,
+    contentType: Ci.nsIContentPolicy.TYPE_OTHER
+  },
+  {
+    description: "should set request header for TYPE_DOCUMENT",
+    expectingHeader: true,
+    contentType: Ci.nsIContentPolicy.TYPE_DOCUMENT
+  },
+  {
+    description: "should set request header for TYPE_SUBDOCUMENT",
+    expectingHeader: true,
+    contentType: Ci.nsIContentPolicy.TYPE_SUBDOCUMENT
+  },
+  {
+    description: "should not set request header for TYPE_IMG",
+    expectingHeader: false,
+    contentType: Ci.nsIContentPolicy.TYPE_IMG
+  },
+];
+
+function ChannelListener() {
+}
+
+ChannelListener.prototype = {
+  onStartRequest: function(request, context) { },
+  onDataAvailable: function(request, context, stream, offset, count) {
+    do_throw("Should not get any data!");
+  },
+  onStopRequest: function(request, context, status) {
+    var upgrade_insecure_header = false;
+    try {
+      if (request.getRequestHeader("Upgrade-Insecure-Requests")) {
+        upgrade_insecure_header = true;
+      }
+    }
+    catch (e) {
+      // exception is thrown if header is not available on the request
+    }
+    // debug
+    // dump("executing test: " + curTest.description);
+    do_check_eq(upgrade_insecure_header, curTest.expectingHeader)
+    run_next_test();
+  },
+};
+
+function setupChannel(aContentType) {
+  var chan = NetUtil.newChannel({
+    uri: URL + testpath,
+    loadUsingSystemPrincipal: true,
+    contentPolicyType: aContentType
+  });
+  chan.QueryInterface(Ci.nsIHttpChannel);
+  chan.requestMethod = "GET";
+  return chan;
+}
+
+function serverHandler(metadata, response) {
+  // no need to perform anything here
+}
+
+function run_next_test() {
+  curTest = tests.shift();
+  if (!curTest) {
+    httpserver.stop(do_test_finished);
+    return;
+  }
+  channel = setupChannel(curTest.contentType);
+  channel.asyncOpen(new ChannelListener(), null);
+}
+
+function run_test() {
+  // set up the test environment
+  httpserver = new HttpServer();
+  httpserver.registerPathHandler(testpath, serverHandler);
+  httpserver.start(-1);
+
+  run_next_test();
+  do_test_pending();
+}
--- a/dom/security/test/unit/xpcshell.ini
+++ b/dom/security/test/unit/xpcshell.ini
@@ -1,8 +1,9 @@
 [DEFAULT]
 head =
 tail =
 skip-if = toolkit == 'gonk'
 
 [test_csp_reports.js]
 skip-if = buildapp == 'mulet'
 [test_isURIPotentiallyTrustworthy.js]
+[test_csp_upgrade_insecure_request_header.js]
--- a/dom/webidl/ChildNode.webidl
+++ b/dom/webidl/ChildNode.webidl
@@ -5,19 +5,23 @@
  *
  * The origin of this IDL file is
  * http://dom.spec.whatwg.org/#interface-childnode
  */
 
 [NoInterfaceObject]
 interface ChildNode {
 // Not implemented yet:
+//  [Unscopable]
 //  void before((Node or DOMString)... nodes);
+//  [Unscopable]
 //  void after((Node or DOMString)... nodes);
+//  [Unscopable]
 //  void replace((Node or DOMString)... nodes);
+  [Unscopable]
   void remove();
 };
 
 [NoInterfaceObject]
 interface NonDocumentTypeChildNode {
   [Pure]
   readonly attribute Element? previousElementSibling;
   [Pure]
--- a/dom/webidl/Event.webidl
+++ b/dom/webidl/Event.webidl
@@ -32,16 +32,20 @@ interface Event {
 
   [Pure]
   readonly attribute boolean bubbles;
   [Pure]
   readonly attribute boolean cancelable;
   void preventDefault();
   [Pure]
   readonly attribute boolean defaultPrevented;
+  [ChromeOnly, Pure]
+  readonly attribute boolean defaultPreventedByChrome;
+  [ChromeOnly, Pure]
+  readonly attribute boolean defaultPreventedByContent;
 
   [Unforgeable, Pure]
   readonly attribute boolean isTrusted;
   [Pure]
   readonly attribute DOMHighResTimeStamp timeStamp;
 
   void initEvent(DOMString type, boolean bubbles, boolean cancelable);
 };
--- a/dom/webidl/InputMethod.webidl
+++ b/dom/webidl/InputMethod.webidl
@@ -520,27 +520,45 @@ interface MozInputContext: EventTarget {
    * Note that composition always ends automatically with nothing to commit if
    * the composition does not explicitly end by calling |endComposition|, but
    * is interrupted by |sendKey|, |setSelectionRange|,
    * |replaceSurroundingText|, |deleteSurroundingText|, user moving the
    * cursor, changing the focus, etc.
    */
   Promise<boolean> endComposition(optional DOMString text,
                                   optional MozInputMethodKeyboardEventDict dict);
+
+  /**
+   * The interface used to receive the native events from hardware keyboard
+   */
+  readonly attribute MozHardwareInput? hardwareinput;
+};
+
+/*
+ * This interface will be added into inputcontext and used to receive the
+ * events from the hardware keyboard.
+ * Example:
+ *   mozInputMethod.inputcontext.hardwareinput.addEventListener('keyup', this);
+ *   mozInputMethod.inputcontext.hardwareinput.removeEventListener('keyup', this);
+ */
+[JSImplementation="@mozilla.org/b2g-hardwareinput;1",
+ Pref="dom.mozInputMethod.enabled",
+ CheckAnyPermissions="input"]
+interface MozHardwareInput: EventTarget {
 };
 
 /**
  * Detail of the selectionchange event.
  */
 [JSImplementation="@mozilla.org/b2g-imm-selectionchange;1",
  Pref="dom.mozInputMethod.enabled",
  CheckAnyPermissions="input"]
 interface MozInputContextSelectionChangeEventDetail {
   /**
-   * Indicate whether or not the change is due to our own action from, 
+   * Indicate whether or not the change is due to our own action from,
    * for example, sendKey() call.
    *
    * Note: this property is untrustworthy because it would still be true even
    * if script in the page changed the text synchronously upon responding to
    * events trigger by the call.
    */
   readonly attribute boolean ownAction;
 
@@ -554,17 +572,17 @@ interface MozInputContextSelectionChange
 /**
  * Detail of the surroundingtextchange event.
  */
 [JSImplementation="@mozilla.org/b2g-imm-surroundingtextchange;1",
  Pref="dom.mozInputMethod.enabled",
  CheckAnyPermissions="input"]
 interface MozInputContextSurroundingTextChangeEventDetail {
   /**
-   * Indicate whether or not the change is due to our own action from, 
+   * Indicate whether or not the change is due to our own action from,
    * for example, sendKey() call.
    *
    * Note: this property is untrustworthy because it would still be true even
    * if script in the page changed the text synchronously upon responding to
    * events trigger by the call.
    */
   readonly attribute boolean ownAction;
 
--- a/dom/webidl/KeyboardEvent.webidl
+++ b/dom/webidl/KeyboardEvent.webidl
@@ -24,16 +24,21 @@ interface KeyboardEvent : UIEvent
 
   readonly attribute unsigned long location;
   readonly attribute boolean       repeat;
   readonly attribute boolean       isComposing;
 
   readonly attribute DOMString key;
   [Pref="dom.keyboardevent.code.enabled"]
   readonly attribute DOMString code;
+
+  // This returns the initialized dictionary for generating a
+  // same-type keyboard event
+  [Cached, ChromeOnly, Constant]
+  readonly attribute KeyboardEventInit initDict;
 };
 
 dictionary KeyboardEventInit : EventModifierInit
 {
   DOMString      key           = "";
   DOMString      code          = "";
   unsigned long  location      = 0;
   boolean        repeat        = false;
--- a/dom/webidl/Promise.webidl
+++ b/dom/webidl/Promise.webidl
@@ -58,17 +58,17 @@ interface _Promise {
   // return value of PromiseSubclass.race to be a Promise object.  As a result,
   // we also have to do our argument conversion manually, because we want to
   // convert its exceptions into rejections.
   [NewObject, Throws]
   static any race(optional any iterable);
 };
 #else // SPIDERMONKEY_PROMISE
 [NoInterfaceObject,
- Exposed=(Window,Worker,System)]
+ Exposed=(Window,Worker,WorkerDebugger,System)]
 // Need to escape "Promise" so it's treated as an identifier.
 interface _Promise {
 };
 
 // Hack to allow us to have JS owning and properly tracing/CCing/etc a
 // PromiseNativeHandler.
 [NoInterfaceObject,
  Exposed=(Window,Worker,System)]
--- a/dom/webidl/WorkerDebuggerGlobalScope.webidl
+++ b/dom/webidl/WorkerDebuggerGlobalScope.webidl
@@ -26,17 +26,17 @@ interface WorkerDebuggerGlobalScope : Ev
   void setImmediate(Function handler);
 
   void reportError(DOMString message);
 
   [Throws]
   sequence<any> retrieveConsoleEvents();
 
   [Throws]
-  void setConsoleEventHandler(AnyCallback handler);
+  void setConsoleEventHandler(AnyCallback? handler);
 };
 
 // So you can debug while you debug
 partial interface WorkerDebuggerGlobalScope {
   void dump(optional DOMString string);
 
   [Throws, Replaceable]
   readonly attribute Console console;
--- a/dom/workers/ServiceWorkerManager.cpp
+++ b/dom/workers/ServiceWorkerManager.cpp
@@ -2221,26 +2221,27 @@ ServiceWorkerManager::SendPushEvent(cons
                                     uint8_t* aDataBytes,
                                     uint8_t optional_argc)
 {
   if (optional_argc == 2) {
     nsTArray<uint8_t> data;
     if (!data.InsertElementsAt(0, aDataBytes, aDataLength, fallible)) {
       return NS_ERROR_OUT_OF_MEMORY;
     }
-    return SendPushEvent(aOriginAttributes, aScope, Some(data));
+    return SendPushEvent(aOriginAttributes, aScope, EmptyString(), Some(data));
   }
   MOZ_ASSERT(optional_argc == 0);
-  return SendPushEvent(aOriginAttributes, aScope, Nothing());
+  return SendPushEvent(aOriginAttributes, aScope, EmptyString(), Nothing());
 }
 
 nsresult
 ServiceWorkerManager::SendPushEvent(const nsACString& aOriginAttributes,
                                     const nsACString& aScope,
-                                    Maybe<nsTArray<uint8_t>> aData)
+                                    const nsAString& aMessageId,
+                                    const Maybe<nsTArray<uint8_t>>& aData)
 {
 #ifdef MOZ_SIMPLEPUSH
   return NS_ERROR_NOT_AVAILABLE;
 #else
   PrincipalOriginAttributes attrs;
   if (!attrs.PopulateFromSuffix(aOriginAttributes)) {
     return NS_ERROR_INVALID_ARG;
   }
@@ -2248,17 +2249,18 @@ ServiceWorkerManager::SendPushEvent(cons
   ServiceWorkerInfo* serviceWorker = GetActiveWorkerInfoForScope(attrs, aScope);
   if (NS_WARN_IF(!serviceWorker)) {
     return NS_ERROR_FAILURE;
   }
 
   RefPtr<ServiceWorkerRegistrationInfo> registration =
     GetRegistration(serviceWorker->GetPrincipal(), aScope);
 
-  return serviceWorker->WorkerPrivate()->SendPushEvent(aData, registration);
+  return serviceWorker->WorkerPrivate()->SendPushEvent(aMessageId, aData,
+                                                       registration);
 #endif // MOZ_SIMPLEPUSH
 }
 
 NS_IMETHODIMP
 ServiceWorkerManager::SendPushSubscriptionChangeEvent(const nsACString& aOriginAttributes,
                                                       const nsACString& aScope)
 {
 #ifdef MOZ_SIMPLEPUSH
@@ -2733,17 +2735,18 @@ ServiceWorkerManager::ReportToAllClients
 
     nsContentUtils::ReportToConsoleNonLocalized(aMessage,
                                                 aFlags,
                                                 NS_LITERAL_CSTRING("Service Workers"),
                                                 doc,
                                                 uri,
                                                 aLine,
                                                 aLineNumber,
-                                                aColumnNumber);
+                                                aColumnNumber,
+                                                nsContentUtils::eOMIT_LOCATION);
   }
 
   // Report to any documents that have called .register() for this scope.  They
   // may not be controlled, but will still want to see error reports.
   WeakDocumentList* regList = mRegisteringDocuments.Get(aScope);
   if (regList) {
     for (int32_t i = regList->Length() - 1; i >= 0; --i) {
       nsCOMPtr<nsIDocument> doc = do_QueryReferent(regList->ElementAt(i));
@@ -2765,17 +2768,18 @@ ServiceWorkerManager::ReportToAllClients
 
       nsContentUtils::ReportToConsoleNonLocalized(aMessage,
                                                   aFlags,
                                                   NS_LITERAL_CSTRING("Service Workers"),
                                                   doc,
                                                   uri,
                                                   aLine,
                                                   aLineNumber,
-                                                  aColumnNumber);
+                                                  aColumnNumber,
+                                                  nsContentUtils::eOMIT_LOCATION);
     }
 
     if (regList->IsEmpty()) {
       regList = nullptr;
       nsAutoPtr<WeakDocumentList> doomed;
       mRegisteringDocuments.RemoveAndForget(aScope, doomed);
     }
   }
@@ -2836,17 +2840,18 @@ ServiceWorkerManager::ReportToAllClients
   if (windows.IsEmpty()) {
     nsContentUtils::ReportToConsoleNonLocalized(aMessage,
                                                 aFlags,
                                                 NS_LITERAL_CSTRING("Service Workers"),
                                                 nullptr,  // document
                                                 uri,
                                                 aLine,
                                                 aLineNumber,
-                                                aColumnNumber);
+                                                aColumnNumber,
+                                                nsContentUtils::eOMIT_LOCATION);
     return;
   }
 }
 
 void
 ServiceWorkerManager::HandleError(JSContext* aCx,
                                   nsIPrincipal* aPrincipal,
                                   const nsCString& aScope,
--- a/dom/workers/ServiceWorkerManager.h
+++ b/dom/workers/ServiceWorkerManager.h
@@ -494,17 +494,18 @@ public:
                                   ServiceWorkerRegistrationListener* aListener);
 
   void
   MaybeCheckNavigationUpdate(nsIDocument* aDoc);
 
   nsresult
   SendPushEvent(const nsACString& aOriginAttributes,
                 const nsACString& aScope,
-                Maybe<nsTArray<uint8_t>> aData);
+                const nsAString& aMessageId,
+                const Maybe<nsTArray<uint8_t>>& aData);
 
   nsresult
   NotifyUnregister(nsIPrincipal* aPrincipal, const nsAString& aScope);
 
 private:
   ServiceWorkerManager();
   ~ServiceWorkerManager();
 
--- a/dom/workers/ServiceWorkerPrivate.cpp
+++ b/dom/workers/ServiceWorkerPrivate.cpp
@@ -21,20 +21,23 @@
 #include "WorkerRunnable.h"
 #include "WorkerScope.h"
 #include "mozilla/Assertions.h"
 #include "mozilla/dom/FetchUtil.h"
 #include "mozilla/dom/IndexedDatabaseManager.h"
 #include "mozilla/dom/InternalHeaders.h"
 #include "mozilla/dom/NotificationEvent.h"
 #include "mozilla/dom/PromiseNativeHandler.h"
+#include "mozilla/dom/RequestBinding.h"
+#include "mozilla/unused.h"
+
 #ifndef MOZ_SIMPLEPUSH
+#include "nsIPushErrorReporter.h"
 #include "mozilla/dom/PushEventBinding.h"
 #endif
-#include "mozilla/dom/RequestBinding.h"
 
 using namespace mozilla;
 using namespace mozilla::dom;
 
 BEGIN_WORKERS_NAMESPACE
 
 NS_IMPL_CYCLE_COLLECTING_ADDREF(ServiceWorkerPrivate)
 NS_IMPL_CYCLE_COLLECTING_RELEASE(ServiceWorkerPrivate)
@@ -572,63 +575,139 @@ ServiceWorkerPrivate::SendLifeCycleEvent
   }
 
   return NS_OK;
 }
 
 #ifndef MOZ_SIMPLEPUSH
 namespace {
 
+class PushErrorReporter final : public PromiseNativeHandler
+{
+  WorkerPrivate* mWorkerPrivate;
+  nsString mMessageId;
+
+  ~PushErrorReporter()
+  {
+  }
+
+public:
+  NS_DECL_THREADSAFE_ISUPPORTS
+
+  PushErrorReporter(WorkerPrivate* aWorkerPrivate,
+                    const nsAString& aMessageId)
+    : mWorkerPrivate(aWorkerPrivate)
+    , mMessageId(aMessageId)
+  {
+    mWorkerPrivate->AssertIsOnWorkerThread();
+  }
+
+  void ResolvedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue) override
+  {
+    mWorkerPrivate->AssertIsOnWorkerThread();
+    mWorkerPrivate = nullptr;
+    // Do nothing; we only use this to report errors to the Push service.
+  }
+
+  void RejectedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue) override
+  {
+    Report(nsIPushErrorReporter::DELIVERY_UNHANDLED_REJECTION);
+  }
+
+  void Report(uint16_t aReason = nsIPushErrorReporter::DELIVERY_INTERNAL_ERROR)
+  {
+    mWorkerPrivate->AssertIsOnWorkerThread();
+    mWorkerPrivate = nullptr;
+
+    if (NS_WARN_IF(aReason > nsIPushErrorReporter::DELIVERY_INTERNAL_ERROR) ||
+        mMessageId.IsEmpty()) {
+      return;
+    }
+    nsCOMPtr<nsIRunnable> runnable =
+      NS_NewRunnableMethodWithArg<uint16_t>(this,
+        &PushErrorReporter::ReportOnMainThread, aReason);
+    MOZ_ALWAYS_TRUE(NS_SUCCEEDED(
+      NS_DispatchToMainThread(runnable.forget())));
+  }
+
+  void ReportOnMainThread(uint16_t aReason)
+  {
+    AssertIsOnMainThread();
+    nsCOMPtr<nsIPushErrorReporter> reporter =
+      do_GetService("@mozilla.org/push/Service;1");
+    if (reporter) {
+      nsresult rv = reporter->ReportDeliveryError(mMessageId, aReason);
+      Unused << NS_WARN_IF(NS_FAILED(rv));
+    }
+  }
+};
+
+NS_IMPL_ISUPPORTS0(PushErrorReporter)
+
 class SendPushEventRunnable final : public ExtendableFunctionalEventWorkerRunnable
 {
+  nsString mMessageId;
   Maybe<nsTArray<uint8_t>> mData;
 
 public:
   SendPushEventRunnable(WorkerPrivate* aWorkerPrivate,
                         KeepAliveToken* aKeepAliveToken,
+                        const nsAString& aMessageId,
                         const Maybe<nsTArray<uint8_t>>& aData,
                         nsMainThreadPtrHandle<ServiceWorkerRegistrationInfo> aRegistration)
       : ExtendableFunctionalEventWorkerRunnable(
           aWorkerPrivate, aKeepAliveToken, aRegistration)
+      , mMessageId(aMessageId)
       , mData(aData)
   {
     AssertIsOnMainThread();
     MOZ_ASSERT(aWorkerPrivate);
     MOZ_ASSERT(aWorkerPrivate->IsServiceWorker());
   }
 
   bool
   WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override
   {
     MOZ_ASSERT(aWorkerPrivate);
     GlobalObject globalObj(aCx, aWorkerPrivate->GlobalScope()->GetWrapper());
 
+    RefPtr<PushErrorReporter> errorReporter =
+      new PushErrorReporter(aWorkerPrivate, mMessageId);
+
     PushEventInit pei;
     if (mData) {
       const nsTArray<uint8_t>& bytes = mData.ref();
       JSObject* data = Uint8Array::Create(aCx, bytes.Length(), bytes.Elements());
       if (!data) {
+        errorReporter->Report();
         return false;
       }
       pei.mData.Construct().SetAsArrayBufferView().Init(data);
     }
     pei.mBubbles = false;
     pei.mCancelable = false;
 
     ErrorResult result;
     RefPtr<PushEvent> event =
       PushEvent::Constructor(globalObj, NS_LITERAL_STRING("push"), pei, result);
     if (NS_WARN_IF(result.Failed())) {
       result.SuppressException();
+      errorReporter->Report();
       return false;
     }
     event->SetTrusted(true);
 
+    RefPtr<Promise> waitUntil;
     DispatchExtendableEventOnWorkerScope(aCx, aWorkerPrivate->GlobalScope(),
-                                         event, nullptr);
+                                         event, getter_AddRefs(waitUntil));
+    if (waitUntil) {
+      waitUntil->AppendNativeHandler(errorReporter);
+    } else {
+      errorReporter->Report(nsIPushErrorReporter::DELIVERY_UNCAUGHT_EXCEPTION);
+    }
 
     return true;
   }
 };
 
 class SendPushSubscriptionChangeEventRunnable final : public ExtendableEventWorkerRunnable
 {
 
@@ -666,34 +745,36 @@ public:
     return true;
   }
 };
 
 } // anonymous namespace
 #endif // !MOZ_SIMPLEPUSH
 
 nsresult
-ServiceWorkerPrivate::SendPushEvent(const Maybe<nsTArray<uint8_t>>& aData,
+ServiceWorkerPrivate::SendPushEvent(const nsAString& aMessageId,
+                                    const Maybe<nsTArray<uint8_t>>& aData,
                                     ServiceWorkerRegistrationInfo* aRegistration)
 {
 #ifdef MOZ_SIMPLEPUSH
   return NS_ERROR_NOT_AVAILABLE;
 #else
   nsresult rv = SpawnWorkerIfNeeded(PushEvent, nullptr);
   NS_ENSURE_SUCCESS(rv, rv);
 
   MOZ_ASSERT(mKeepAliveToken);
 
   nsMainThreadPtrHandle<ServiceWorkerRegistrationInfo> regInfo(
     new nsMainThreadPtrHolder<ServiceWorkerRegistrationInfo>(aRegistration, false));
 
   RefPtr<WorkerRunnable> r = new SendPushEventRunnable(mWorkerPrivate,
-                                                         mKeepAliveToken,
-                                                         aData,
-                                                         regInfo);
+                                                       mKeepAliveToken,
+                                                       aMessageId,
+                                                       aData,
+                                                       regInfo);
 
   if (mInfo->State() == ServiceWorkerState::Activating) {
     mPendingFunctionalEvents.AppendElement(r.forget());
     return NS_OK;
   }
 
   MOZ_ASSERT(mInfo->State() == ServiceWorkerState::Activated);
 
--- a/dom/workers/ServiceWorkerPrivate.h
+++ b/dom/workers/ServiceWorkerPrivate.h
@@ -80,17 +80,18 @@ public:
   CheckScriptEvaluation(LifeCycleEventCallback* aCallback);
 
   nsresult
   SendLifeCycleEvent(const nsAString& aEventType,
                      LifeCycleEventCallback* aCallback,
                      nsIRunnable* aLoadFailure);
 
   nsresult
-  SendPushEvent(const Maybe<nsTArray<uint8_t>>& aData,
+  SendPushEvent(const nsAString& aMessageId,
+                const Maybe<nsTArray<uint8_t>>& aData,
                 ServiceWorkerRegistrationInfo* aRegistration);
 
   nsresult
   SendPushSubscriptionChangeEvent();
 
   nsresult
   SendNotificationClickEvent(const nsAString& aID,
                              const nsAString& aTitle,
--- a/dom/workers/ServiceWorkerRegistrar.cpp
+++ b/dom/workers/ServiceWorkerRegistrar.cpp
@@ -175,29 +175,17 @@ ServiceWorkerRegistrar::RegisterServiceW
   if (mShuttingDown) {
     NS_WARNING("Failed to register a serviceWorker during shutting down.");
     return;
   }
 
   {
     MonitorAutoLock lock(mMonitor);
     MOZ_ASSERT(mDataLoaded);
-
-    bool found = false;
-    for (uint32_t i = 0, len = mData.Length(); i < len; ++i) {
-      if (Equivalent(aData, mData[i])) {
-        mData[i] = aData;
-        found = true;
-        break;
-      }
-    }
-
-    if (!found) {
-      mData.AppendElement(aData);
-    }
+    RegisterServiceWorkerInternal(aData);
   }
 
   ScheduleSaveData();
 }
 
 void
 ServiceWorkerRegistrar::UnregisterServiceWorker(
                                             const PrincipalInfo& aPrincipalInfo,
@@ -511,16 +499,33 @@ ServiceWorkerRegistrar::DeleteData()
     return;
   }
 
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return;
   }
 }
 
+void
+ServiceWorkerRegistrar::RegisterServiceWorkerInternal(const ServiceWorkerRegistrationData& aData)
+{
+  bool found = false;
+  for (uint32_t i = 0, len = mData.Length(); i < len; ++i) {
+    if (Equivalent(aData, mData[i])) {
+      mData[i] = aData;
+      found = true;
+      break;
+    }
+  }
+
+  if (!found) {
+    mData.AppendElement(aData);
+  }
+}
+
 class ServiceWorkerRegistrarSaveDataRunnable final : public nsRunnable
 {
 public:
   ServiceWorkerRegistrarSaveDataRunnable()
     : mThread(do_GetCurrentThread())
   {
     AssertIsOnBackgroundThread();
   }
--- a/dom/workers/ServiceWorkerRegistrar.h
+++ b/dom/workers/ServiceWorkerRegistrar.h
@@ -61,16 +61,18 @@ protected:
   // subclassing it.
   void LoadData();
   void SaveData();
 
   nsresult ReadData();
   nsresult WriteData();
   void DeleteData();
 
+  void RegisterServiceWorkerInternal(const ServiceWorkerRegistrationData& aData);
+
   ServiceWorkerRegistrar();
   virtual ~ServiceWorkerRegistrar();
 
 private:
   void ProfileStarted();
   void ProfileStopped();
 
   void ScheduleSaveData();
--- a/dom/workers/WorkerScope.cpp
+++ b/dom/workers/WorkerScope.cpp
@@ -938,17 +938,17 @@ WorkerDebuggerGlobalScope::RetrieveConso
     return;
   }
 
   console->RetrieveConsoleEvents(aCx, aEvents, aRv);
 }
 
 void
 WorkerDebuggerGlobalScope::SetConsoleEventHandler(JSContext* aCx,
-                                                  AnyCallback& aHandler,
+                                                  AnyCallback* aHandler,
                                                   ErrorResult& aRv)
 {
   WorkerGlobalScope* scope = mWorkerPrivate->GetOrCreateGlobalScope(aCx);
   if (!scope) {
     aRv.Throw(NS_ERROR_FAILURE);
     return;
   }
 
--- a/dom/workers/WorkerScope.h
+++ b/dom/workers/WorkerScope.h
@@ -335,17 +335,17 @@ public:
   void
   ReportError(JSContext* aCx, const nsAString& aMessage);
 
   void
   RetrieveConsoleEvents(JSContext* aCx, nsTArray<JS::Value>& aEvents,
                         ErrorResult& aRv);
 
   void
-  SetConsoleEventHandler(JSContext* aCx, AnyCallback& aHandler,
+  SetConsoleEventHandler(JSContext* aCx, AnyCallback* aHandler,
                          ErrorResult& aRv);
 
   Console*
   GetConsole(ErrorResult& aRv);
 
   Console*
   GetConsoleIfExists() const
   {
--- a/dom/workers/test/WorkerDebugger.console_debugger.js
+++ b/dom/workers/test/WorkerDebugger.console_debugger.js
@@ -19,16 +19,20 @@ function magic() {
   ok(Array.isArray(foo), "We received an array.");
   ok(foo.length >= 2, "At least 2 messages.");
 
   is(foo[0].arguments[0], "Can you see this console message?", "First message ok.");
   is(foo[1].arguments[0], "Can you see this second console message?", "Second message ok.");
 
   setConsoleEventHandler(function(consoleData) {
     is(consoleData.arguments[0], "Random message.", "Random message ok!");
+
+    // The consoleEventHandler can be null.
+    setConsoleEventHandler(null);
+
     finish();
   });
 }
 
 this.onmessage = function (event) {
   switch (event.data) {
   case "do magic":
     magic();
--- a/dom/workers/test/gtest/TestReadWrite.cpp
+++ b/dom/workers/test/gtest/TestReadWrite.cpp
@@ -9,16 +9,17 @@
 #include "mozilla/dom/ServiceWorkerRegistrar.h"
 #include "mozilla/dom/ServiceWorkerRegistrarTypes.h"
 #include "mozilla/ipc/PBackgroundSharedTypes.h"
 
 #include "nsAppDirectoryServiceDefs.h"
 #include "nsIFile.h"
 #include "nsIOutputStream.h"
 #include "nsNetUtil.h"
+#include "nsPrintfCString.h"
 
 using namespace mozilla::dom;
 using namespace mozilla::ipc;
 
 class ServiceWorkerRegistrarTest : public ServiceWorkerRegistrar
 {
 public:
   ServiceWorkerRegistrarTest()
@@ -29,16 +30,21 @@ public:
       return;
     }
   }
 
   nsresult TestReadData() { return ReadData(); }
   nsresult TestWriteData() { return WriteData(); }
   void TestDeleteData() { DeleteData(); }
 
+  void TestRegisterServiceWorker(const ServiceWorkerRegistrationData& aData)
+  {
+    RegisterServiceWorkerInternal(aData);
+  }
+
   nsTArray<ServiceWorkerRegistrationData>& TestGetData() { return mData; }
 };
 
 already_AddRefed<nsIFile>
 GetFile()
 {
   nsCOMPtr<nsIFile> file;
   nsresult rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR,
@@ -203,27 +209,30 @@ TEST(ServiceWorkerRegistrar, TestDeleteD
   ASSERT_FALSE(exists) << "The file should not exist after a DeleteData().";
 }
 
 TEST(ServiceWorkerRegistrar, TestWriteData)
 {
   {
     RefPtr<ServiceWorkerRegistrarTest> swr = new ServiceWorkerRegistrarTest;
 
-    nsTArray<ServiceWorkerRegistrationData>& data = swr->TestGetData();
+    for (int i = 0; i < 10; ++i) {
+      ServiceWorkerRegistrationData reg;
 
-    for (int i = 0; i < 10; ++i) {
-      ServiceWorkerRegistrationData* d = data.AppendElement();
+      reg.scope() = nsPrintfCString("scope write %d", i);
+      reg.currentWorkerURL() = nsPrintfCString("currentWorkerURL write %d", i);
+      reg.cacheName() =
+        NS_ConvertUTF8toUTF16(nsPrintfCString("cacheName write %d", i));
 
       nsAutoCString spec;
       spec.AppendPrintf("spec write %d", i);
-      d->principal() = mozilla::ipc::ContentPrincipalInfo(mozilla::PrincipalOriginAttributes(i, i % 2), spec);
-      d->scope().AppendPrintf("scope write %d", i);
-      d->currentWorkerURL().AppendPrintf("currentWorkerURL write %d", i);
-      d->cacheName().AppendPrintf("cacheName write %d", i);
+      reg.principal() =
+        mozilla::ipc::ContentPrincipalInfo(mozilla::PrincipalOriginAttributes(i, i % 2), spec);
+
+      swr->TestRegisterServiceWorker(reg);
     }
 
     nsresult rv = swr->TestWriteData();
     ASSERT_EQ(NS_OK, rv) << "WriteData() should not fail";
   }
 
   RefPtr<ServiceWorkerRegistrarTest> swr = new ServiceWorkerRegistrarTest;
 
@@ -356,17 +365,17 @@ TEST(ServiceWorkerRegistrar, TestVersion
 
   ASSERT_STREQ("", suffix1.get());
   ASSERT_STREQ("scope 1", cInfo1.spec().get());
   ASSERT_STREQ("scope 1", data[1].scope().get());
   ASSERT_STREQ("currentWorkerURL 1", data[1].currentWorkerURL().get());
   ASSERT_STREQ("cacheName 1", NS_ConvertUTF16toUTF8(data[1].cacheName()).get());
 }
 
-TEST(ServiceWorkerRegistrar, TestDedupe)
+TEST(ServiceWorkerRegistrar, TestDedupeRead)
 {
   nsAutoCString buffer("3" "\n");
 
   // unique entries
   buffer.Append("^appId=123&inBrowser=1\n");
   buffer.Append("spec 0\nscope 0\ncurrentWorkerURL 0\ncacheName 0\n");
   buffer.Append(SERVICEWORKERREGISTRAR_TERMINATOR "\n");
 
@@ -419,14 +428,66 @@ TEST(ServiceWorkerRegistrar, TestDedupe)
 
   ASSERT_STREQ("", suffix1.get());
   ASSERT_STREQ("scope 1", cInfo1.spec().get());
   ASSERT_STREQ("scope 1", data[1].scope().get());
   ASSERT_STREQ("currentWorkerURL 1", data[1].currentWorkerURL().get());
   ASSERT_STREQ("cacheName 1", NS_ConvertUTF16toUTF8(data[1].cacheName()).get());
 }
 
+TEST(ServiceWorkerRegistrar, TestDedupeWrite)