Merge mozilla-central to fx-team
authorCarsten "Tomcat" Book <cbook@mozilla.com>
Fri, 22 Apr 2016 15:58:35 +0200
changeset 332353 0da9dcd298a62e744a069c336797b24135f277a0
parent 332352 4ca15e44f0b59c06d35b776320cb149e2422a2fc (current diff)
parent 332342 fc15477ce628599519cb0055f52cc195d640dc94 (diff)
child 332354 df4e550128b976a664216e57f78f80c36d78f275
push id6048
push userkmoir@mozilla.com
push dateMon, 06 Jun 2016 19:02:08 +0000
treeherdermozilla-beta@46d72a56c57d [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
milestone48.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge mozilla-central to fx-team
dom/plugins/test/testplugin/Makefile.in
dom/plugins/test/testplugin/flashplugin/Makefile.in
dom/plugins/test/testplugin/javaplugin/Makefile.in
dom/plugins/test/testplugin/secondplugin/Makefile.in
dom/plugins/test/testplugin/silverlightplugin/Makefile.in
dom/plugins/test/testplugin/testplugin.mk
dom/plugins/test/testplugin/thirdplugin/Makefile.in
testing/talos/talos/compare.py
testing/taskcluster/tasks/tests/fx_desktop_unittest.yml
--- a/accessible/atk/AccessibleWrap.cpp
+++ b/accessible/atk/AccessibleWrap.cpp
@@ -1349,26 +1349,43 @@ AccessibleWrap::HandleAccEvent(AccEvent*
         g_signal_emit_by_name(atkObj, "column_reordered");
         break;
 
     case nsIAccessibleEvent::EVENT_SECTION_CHANGED:
         g_signal_emit_by_name(atkObj, "visible_data_changed");
         break;
 
     case nsIAccessibleEvent::EVENT_SHOW:
-        return FireAtkShowHideEvent(aEvent, atkObj, true);
+      {
+        AccMutationEvent* event = downcast_accEvent(aEvent);
+        Accessible* parentAcc = event ? event->Parent() : accessible->Parent();
+        AtkObject* parent = AccessibleWrap::GetAtkObject(parentAcc);
+        NS_ENSURE_STATE(parent);
+        auto obj = reinterpret_cast<MaiAtkObject*>(atkObj);
+        obj->FireAtkShowHideEvent(parent, true, aEvent->IsFromUserInput());
+        return NS_OK;
+      }
 
     case nsIAccessibleEvent::EVENT_HIDE:
+      {
         // XXX - Handle native dialog accessibles.
         if (!accessible->IsRoot() && accessible->HasARIARole() &&
             accessible->ARIARole() == roles::DIALOG) {
           guint id = g_signal_lookup("deactivate", MAI_TYPE_ATK_OBJECT);
           g_signal_emit(atkObj, id, 0);
         }
-        return FireAtkShowHideEvent(aEvent, atkObj, false);
+
+        AccMutationEvent* event = downcast_accEvent(aEvent);
+        Accessible* parentAcc = event ? event->Parent() : accessible->Parent();
+        AtkObject* parent = AccessibleWrap::GetAtkObject(parentAcc);
+        NS_ENSURE_STATE(parent);
+        auto obj = reinterpret_cast<MaiAtkObject*>(atkObj);
+        obj->FireAtkShowHideEvent(parent, false, aEvent->IsFromUserInput());
+        return NS_OK;
+      }
 
         /*
          * Because dealing with menu is very different between nsIAccessible
          * and ATK, and the menu activity is important, specially transfer the
          * following two event.
          * Need more verification by AT test.
          */
     case nsIAccessibleEvent::EVENT_MENU_START:
@@ -1563,37 +1580,39 @@ MaiAtkObject::FireTextChangeEvent(const 
   } else {
     const char* signal_name =
       textChangedStrings[aFromUser][aIsInsert];
     g_signal_emit_by_name(this, signal_name, aStart, aLen,
                           NS_ConvertUTF16toUTF8(aStr).get());
   }
 }
 
+void
+a11y::ProxyShowHideEvent(ProxyAccessible* aTarget, ProxyAccessible* aParent,
+                         bool aInsert, bool aFromUser)
+{
+  MaiAtkObject* obj = MAI_ATK_OBJECT(GetWrapperFor(aTarget));
+  obj->FireAtkShowHideEvent(GetWrapperFor(aParent), aInsert, aFromUser);
+}
+
 #define ADD_EVENT "children_changed::add"
 #define HIDE_EVENT "children_changed::remove"
 
 static const char *kMutationStrings[2][2] = {
   { HIDE_EVENT NON_USER_EVENT, ADD_EVENT NON_USER_EVENT },
   { HIDE_EVENT, ADD_EVENT },
 };
 
-nsresult
-AccessibleWrap::FireAtkShowHideEvent(AccEvent* aEvent,
-                                     AtkObject* aObject, bool aIsAdded)
+void
+MaiAtkObject::FireAtkShowHideEvent(AtkObject* aParent, bool aIsAdded,
+                                   bool aFromUser)
 {
-    int32_t indexInParent = getIndexInParentCB(aObject);
-    AtkObject *parentObject = getParentCB(aObject);
-    NS_ENSURE_STATE(parentObject);
-
-    bool isFromUserInput = aEvent->IsFromUserInput();
-    const char *signal_name = kMutationStrings[isFromUserInput][aIsAdded];
-    g_signal_emit_by_name(parentObject, signal_name, indexInParent, aObject, nullptr);
-
-    return NS_OK;
+    int32_t indexInParent = getIndexInParentCB(&this->parent);
+    const char *signal_name = kMutationStrings[aFromUser][aIsAdded];
+    g_signal_emit_by_name(aParent, signal_name, indexInParent, this, nullptr);
 }
 
 // static
 void
 AccessibleWrap::GetKeyBinding(Accessible* aAccessible, nsAString& aResult)
 {
   // Return all key bindings including access key and keyboard shortcut.
 
--- a/accessible/atk/AccessibleWrap.h
+++ b/accessible/atk/AccessibleWrap.h
@@ -74,18 +74,16 @@ public:
   static Accessible* GetColumnHeader(TableAccessible* aAccessible,
                                      int32_t aColIdx);
   static Accessible* GetRowHeader(TableAccessible* aAccessible,
                                   int32_t aRowIdx);
 protected:
 
   nsresult FireAtkStateChangeEvent(AccEvent* aEvent, AtkObject *aObject);
   nsresult FireAtkTextChangedEvent(AccEvent* aEvent, AtkObject *aObject);
-  nsresult FireAtkShowHideEvent(AccEvent* aEvent, AtkObject *aObject,
-                                bool aIsAdded);
 
   AtkObject *mAtkObject;
 
 private:
   uint16_t CreateMaiInterfaces();
 };
 
 } // namespace a11y
--- a/accessible/atk/nsMai.h
+++ b/accessible/atk/nsMai.h
@@ -116,16 +116,22 @@ struct MaiAtkObject
   void FireStateChangeEvent(uint64_t aState, bool aEnabled);
 
   /*
    * Notify ATK of a text change within this ATK object.
    */
   void FireTextChangeEvent(const nsString& aStr, int32_t aStart, uint32_t aLen,
                            bool aIsInsert, bool aIsFromUser);
 
+  /**
+   * Notify ATK of a shown or hidden subtree rooted at aObject whose parent is
+   * aParent
+   */
+  void FireAtkShowHideEvent(AtkObject* aParent, bool aIsAdded, bool aFromUser);
+
 private:
   /*
    * do we have text-remove and text-insert signals if not we need to use
    * text-changed see AccessibleWrap::FireAtkTextChangedEvent() and
    * bug 619002
    */
   enum EAvailableAtkSignals {
     eUnknown,
--- a/accessible/base/Platform.h
+++ b/accessible/base/Platform.h
@@ -70,11 +70,13 @@ void ProxyDestroyed(ProxyAccessible*);
  */
 void ProxyEvent(ProxyAccessible* aTarget, uint32_t aEventType);
 void ProxyStateChangeEvent(ProxyAccessible* aTarget, uint64_t aState,
                            bool aEnabled);
 void ProxyCaretMoveEvent(ProxyAccessible* aTarget, int32_t aOffset);
 void ProxyTextChangeEvent(ProxyAccessible* aTarget, const nsString& aStr,
                           int32_t aStart, uint32_t aLen, bool aIsInsert,
                           bool aFromUser);
+void ProxyShowHideEvent(ProxyAccessible* aTarget, ProxyAccessible* aParent,
+                        bool aInsert, bool aFromUser);
 } // namespace a11y
 } // namespace mozilla
 
--- a/accessible/generic/Accessible.cpp
+++ b/accessible/generic/Accessible.cpp
@@ -849,17 +849,17 @@ Accessible::HandleAccEvent(AccEvent* aEv
         reinterpret_cast<uintptr_t>(aEvent->GetAccessible());
 
       switch(aEvent->GetEventType()) {
         case nsIAccessibleEvent::EVENT_SHOW:
           ipcDoc->ShowEvent(downcast_accEvent(aEvent));
           break;
 
         case nsIAccessibleEvent::EVENT_HIDE:
-          ipcDoc->SendHideEvent(id);
+          ipcDoc->SendHideEvent(id, aEvent->IsFromUserInput());
           break;
 
         case nsIAccessibleEvent::EVENT_REORDER:
           // reorder events on the application acc aren't necessary to tell the parent
           // about new top level documents.
           if (!aEvent->GetAccessible()->IsApplication())
             ipcDoc->SendEvent(id, aEvent->GetEventType());
           break;
--- a/accessible/ipc/DocAccessibleChild.cpp
+++ b/accessible/ipc/DocAccessibleChild.cpp
@@ -146,17 +146,17 @@ void
 DocAccessibleChild::ShowEvent(AccShowEvent* aShowEvent)
 {
   Accessible* parent = aShowEvent->Parent();
   uint64_t parentID = parent->IsDoc() ? 0 : reinterpret_cast<uint64_t>(parent->UniqueID());
   uint32_t idxInParent = aShowEvent->InsertionIndex();
   nsTArray<AccessibleData> shownTree;
   ShowEventData data(parentID, idxInParent, shownTree);
   SerializeTree(aShowEvent->GetAccessible(), data.NewTree());
-  SendShowEvent(data);
+  SendShowEvent(data, aShowEvent->IsFromUserInput());
 }
 
 bool
 DocAccessibleChild::RecvState(const uint64_t& aID, uint64_t* aState)
 {
   Accessible* acc = IdToAccessible(aID);
   if (!acc) {
     *aState = states::DEFUNCT;
--- a/accessible/ipc/DocAccessibleParent.cpp
+++ b/accessible/ipc/DocAccessibleParent.cpp
@@ -13,17 +13,18 @@
 #include "xpcAccEvents.h"
 #include "nsAccUtils.h"
 #include "nsCoreUtils.h"
 
 namespace mozilla {
 namespace a11y {
 
 bool
-DocAccessibleParent::RecvShowEvent(const ShowEventData& aData)
+DocAccessibleParent::RecvShowEvent(const ShowEventData& aData,
+                                   const bool& aFromUser)
 {
   if (mShutdown)
     return true;
 
   MOZ_DIAGNOSTIC_ASSERT(CheckDocTree());
 
   if (aData.NewTree().IsEmpty()) {
     NS_ERROR("no children being added");
@@ -51,16 +52,17 @@ DocAccessibleParent::RecvShowEvent(const
   for (uint32_t i = 0; i < consumed; i++) {
     uint64_t id = aData.NewTree()[i].ID();
     MOZ_ASSERT(mAccessibles.GetEntry(id));
   }
 #endif
 
   MOZ_DIAGNOSTIC_ASSERT(CheckDocTree());
 
+  ProxyShowHideEvent(parent->ChildAt(newChildIdx), parent, true, aFromUser);
   return true;
 }
 
 uint32_t
 DocAccessibleParent::AddSubtree(ProxyAccessible* aParent,
                                 const nsTArray<a11y::AccessibleData>& aNewTree,
                                 uint32_t aIdx, uint32_t aIdxInParent)
 {
@@ -98,17 +100,18 @@ DocAccessibleParent::AddSubtree(ProxyAcc
   }
 
   MOZ_ASSERT(newProxy->ChildrenCount() == kids);
 
   return accessibles;
 }
 
 bool
-DocAccessibleParent::RecvHideEvent(const uint64_t& aRootID)
+DocAccessibleParent::RecvHideEvent(const uint64_t& aRootID,
+                                   const bool& aFromUser)
 {
   if (mShutdown)
     return true;
 
   MOZ_DIAGNOSTIC_ASSERT(CheckDocTree());
 
   // We shouldn't actually need this because mAccessibles shouldn't have an
   // entry for the document itself, but it doesn't hurt to be explicit.
@@ -125,16 +128,17 @@ DocAccessibleParent::RecvHideEvent(const
 
   ProxyAccessible* root = rootEntry->mProxy;
   if (!root) {
     NS_ERROR("invalid root being removed!");
     return true;
   }
 
   ProxyAccessible* parent = root->Parent();
+  ProxyShowHideEvent(root, parent, false, aFromUser);
   parent->RemoveChild(root);
   root->Shutdown();
 
   MOZ_DIAGNOSTIC_ASSERT(CheckDocTree());
 
   return true;
 }
 
--- a/accessible/ipc/DocAccessibleParent.h
+++ b/accessible/ipc/DocAccessibleParent.h
@@ -43,18 +43,20 @@ public:
 
   /*
    * Called when a message from a document in a child process notifies the main
    * process it is firing an event.
    */
   virtual bool RecvEvent(const uint64_t& aID, const uint32_t& aType)
     override;
 
-  virtual bool RecvShowEvent(const ShowEventData& aData) override;
-  virtual bool RecvHideEvent(const uint64_t& aRootID) override;
+  virtual bool RecvShowEvent(const ShowEventData& aData, const bool& aFromUser)
+    override;
+  virtual bool RecvHideEvent(const uint64_t& aRootID, const bool& aFromUser)
+    override;
   virtual bool RecvStateChangeEvent(const uint64_t& aID,
                                     const uint64_t& aState,
                                     const bool& aEnabled) override final;
 
   virtual bool RecvCaretMoveEvent(const uint64_t& aID, const int32_t& aOffset)
     override final;
 
   virtual bool RecvTextChangeEvent(const uint64_t& aID, const nsString& aStr,
--- a/accessible/ipc/PDocAccessible.ipdl
+++ b/accessible/ipc/PDocAccessible.ipdl
@@ -50,18 +50,18 @@ prio(normal upto high) sync protocol PDo
 parent:
   async Shutdown();
 
   /*
    * Notify the parent process the document in the child process is firing an
    * event.
    */
   async Event(uint64_t aID, uint32_t type);
-  async ShowEvent(ShowEventData data);
-  async HideEvent(uint64_t aRootID);
+  async ShowEvent(ShowEventData data, bool aFromuser);
+  async HideEvent(uint64_t aRootID, bool aFromUser);
   async StateChangeEvent(uint64_t aID, uint64_t aState, bool aEnabled);
   async CaretMoveEvent(uint64_t aID, int32_t aOffset);
   async TextChangeEvent(uint64_t aID, nsString aStr, int32_t aStart, uint32_t aLen,
                         bool aIsInsert, bool aFromUser);
 
   /*
    * Tell the parent document to bind the existing document as a new child
    * document.
--- a/accessible/mac/Platform.mm
+++ b/accessible/mac/Platform.mm
@@ -96,16 +96,21 @@ ProxyCaretMoveEvent(ProxyAccessible* aTa
     [wrapper selectedTextDidChange];
 }
 
 void
 ProxyTextChangeEvent(ProxyAccessible*, const nsString&, int32_t, uint32_t,
                      bool, bool)
 {
 }
+
+void
+ProxyShowHideEvent(ProxyAccessible*, ProxyAccessible*, bool, bool)
+{
+}
 } // namespace a11y
 } // namespace mozilla
 
 @interface GeckoNSApplication(a11y)
 -(void)accessibilitySetValue:(id)value forAttribute:(NSString*)attribute;
 @end
 
 @implementation GeckoNSApplication(a11y)
--- a/accessible/other/Platform.cpp
+++ b/accessible/other/Platform.cpp
@@ -44,8 +44,13 @@ a11y::ProxyCaretMoveEvent(ProxyAccessibl
 {
 }
 
 void
 a11y::ProxyTextChangeEvent(ProxyAccessible*, const nsString&, int32_t, uint32_t,
                      bool, bool)
 {
 }
+
+void
+a11y::ProxyShowHideEvent(ProxyAccessible*, ProxyAccessible*, bool, bool)
+{
+}
--- a/accessible/windows/msaa/Platform.cpp
+++ b/accessible/windows/msaa/Platform.cpp
@@ -113,8 +113,17 @@ a11y::ProxyTextChangeEvent(ProxyAccessib
   if (text) {
     ia2AccessibleText::UpdateTextChangeData(text, aInsert, aStr, aStart, aLen);
   }
 
   uint32_t eventType = aInsert ? nsIAccessibleEvent::EVENT_TEXT_INSERTED :
     nsIAccessibleEvent::EVENT_TEXT_REMOVED;
   AccessibleWrap::FireWinEvent(wrapper, eventType);
 }
+
+void
+a11y::ProxyShowHideEvent(ProxyAccessible* aTarget, ProxyAccessible*, bool aInsert, bool)
+{
+  uint32_t event = aInsert ? nsIAccessibleEvent::EVENT_SHOW :
+    nsIAccessibleEvent::EVENT_HIDE;
+  AccessibleWrap* wrapper = WrapperFor(aTarget);
+  AccessibleWrap::FireWinEvent(wrapper, event);
+}
--- a/browser/config/mozconfigs/linux32/beta
+++ b/browser/config/mozconfigs/linux32/beta
@@ -6,9 +6,10 @@ fi
 
 . "$topsrcdir/browser/config/mozconfigs/linux32/common-opt"
 
 ac_add_options --enable-official-branding
 ac_add_options --enable-verify-mar
 
 mk_add_options MOZ_PGO=1
 
+. "$topsrcdir/build/mozconfig.rust"
 . "$topsrcdir/build/mozconfig.common.override"
--- a/browser/config/mozconfigs/linux32/debug
+++ b/browser/config/mozconfigs/linux32/debug
@@ -18,10 +18,11 @@ export MOZ_TELEMETRY_REPORTING=1
 # Treat warnings as errors (modulo ALLOW_COMPILER_WARNINGS).
 ac_add_options --enable-warnings-as-errors
 
 # Package js shell.
 export MOZ_PACKAGE_JSSHELL=1
 
 ac_add_options --with-branding=browser/branding/nightly
 
+. "$topsrcdir/build/mozconfig.rust"
 . "$topsrcdir/build/mozconfig.common.override"
 . "$topsrcdir/build/mozconfig.cache"
--- a/browser/config/mozconfigs/linux32/nightly
+++ b/browser/config/mozconfigs/linux32/nightly
@@ -5,10 +5,11 @@ ac_add_options --enable-verify-mar
 
 # This will overwrite the default of stripping everything and keep the symbol table.
 # This is useful for profiling and debugging and only increases the package size
 # by 2 MBs.
 STRIP_FLAGS="--strip-debug"
 
 ac_add_options --with-branding=browser/branding/nightly
 
+. "$topsrcdir/build/mozconfig.rust"
 . "$topsrcdir/build/mozconfig.common.override"
 . "$topsrcdir/build/mozconfig.cache"
--- a/browser/config/mozconfigs/linux32/release
+++ b/browser/config/mozconfigs/linux32/release
@@ -13,9 +13,10 @@ ac_add_options --enable-official-brandin
 ac_add_options --enable-verify-mar
 
 mk_add_options MOZ_PGO=1
 
 # safeguard against someone forgetting to re-set EARLY_BETA_OR_EARLIER in
 # defines.sh during the beta cycle
 export BUILDING_RELEASE=1
 
+. "$topsrcdir/build/mozconfig.rust"
 . "$topsrcdir/build/mozconfig.common.override"
--- a/browser/config/tooltool-manifests/linux32/releng.manifest
+++ b/browser/config/tooltool-manifests/linux32/releng.manifest
@@ -11,15 +11,23 @@
 "size": 11189216,
 "digest": "18bc52b0599b1308b667e282abb45f47597bfc98a5140cfcab8da71dacf89dd76d0dee22a04ce26fe7ad1f04e2d6596991f9e5b01fd2aaaab5542965f596b0e6",
 "algorithm": "sha512",
 "filename": "gtk3.tar.xz",
 "setup": "setup.sh",
 "unpack": true
 },
 {
+"version": "rustc 1.8.0 (db2939409 2016-04-11)",
+"size": 123218320,
+"digest": "7afcd7b39c4d5277db6b28951602aff4c698102ba45d3d811b353ca7446074beceebf03a2a529e323af19d73db4acbe96ec2bdad44def2e218ed36f55e82cab2",
+"algorithm": "sha512",
+"filename": "rustc.tar.xz",
+"unpack": true
+},
+{
 "size": 167175,
 "digest": "0b71a936edf5bd70cf274aaa5d7abc8f77fe8e7b5593a208f805cc9436fac646b9c4f0b43c2b10de63ff3da671497d35536077ecbc72dba7f8159a38b580f831",
 "algorithm": "sha512",
 "filename": "sccache.tar.bz2",
 "unpack": true
 }
 ]
--- a/build/moz.configure/init.configure
+++ b/build/moz.configure/init.configure
@@ -286,22 +286,26 @@ def mozconfig_options(mozconfig, help):
             add(key, value)
 
 
 # Mozilla-Build
 # ==============================================================
 option(env='MOZILLABUILD', nargs=1,
        help='Path to Mozilla Build (Windows-only)')
 
+option(env='CONFIG_SHELL', nargs=1, help='Path to a POSIX shell')
+
 # 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')
+@depends('CONFIG_SHELL', 'MOZILLABUILD')
 @checking('for a shell')
 @imports('sys')
-def shell(mozillabuild):
+def shell(value, mozillabuild):
+    if value:
+        return find_program(value[0])
     shell = 'sh'
     if mozillabuild:
         shell = mozillabuild[0] + '/msys/bin/sh'
     if sys.platform == 'win32':
         shell = shell + '.exe'
     return find_program(shell)
 
 
--- a/build/moz.configure/toolchain.configure
+++ b/build/moz.configure/toolchain.configure
@@ -543,29 +543,30 @@ def compiler(language, host_or_target, c
 
     @depends(valid_compiler)
     @checking('%s version' % what)
     def compiler_version(compiler):
         return compiler.version
 
     if language == 'C++':
         @depends(valid_compiler, c_compiler)
-        def compiler_suite_consistency(compiler, c_compiler):
+        def valid_compiler(compiler, c_compiler):
             if compiler.type != c_compiler.type:
                 die('The %s C compiler is %s, while the %s C++ compiler is '
                     '%s. Need to use the same compiler suite.',
                     host_or_target_str, c_compiler.type,
                     host_or_target_str, compiler.type)
 
             if compiler.version != c_compiler.version:
                 die('The %s C compiler is version %s, while the %s C++ '
                     'compiler is version %s. Need to use the same compiler '
                     'version.',
                     host_or_target_str, c_compiler.version,
                     host_or_target_str, compiler.version)
+            return compiler
 
     # Set CC/CXX/HOST_CC/HOST_CXX for old-configure, which needs the wrapper
     # and the flags that were part of the user input for those variables to
     # be provided.
     add_old_configure_assignment(var, depends_if(valid_compiler)(
         lambda x: list(x.wrapper) + [x.compiler] + list(x.flags)))
 
     # Set CC_TYPE/CC_VERSION/HOST_CC_TYPE/HOST_CC_VERSION to allow
--- a/config/external/nss/nss.symbols
+++ b/config/external/nss/nss.symbols
@@ -263,16 +263,17 @@ NSS_Get_SECOID_AlgorithmIDTemplate_Util
 NSS_Get_SEC_SignedCertificateTemplate
 NSS_Get_SEC_UTF8StringTemplate
 NSS_Get_SEC_UTF8StringTemplate_Util
 NSS_GetVersion
 NSS_Init
 NSS_Initialize
 NSS_InitWithMerge
 NSS_IsInitialized
+NSS_OptionSet
 NSS_NoDB_Init
 NSS_SecureMemcmp
 NSS_SetAlgorithmPolicy
 NSS_SetDomesticPolicy
 NSS_Shutdown
 NSSSMIME_GetVersion
 NSS_SMIMESignerInfo_SaveSMIMEProfile
 NSS_SMIMEUtil_FindBulkAlgForRecipients
--- a/devtools/server/tests/browser/browser_storage_listings.js
+++ b/devtools/server/tests/browser/browser_storage_listings.js
@@ -356,19 +356,17 @@ var testCookiesObjects = Task.async(func
           is(item.expires, toMatch.expires, "The expiry time matches.");
           is(item.path, toMatch.path, "The path matches.");
           is(item.host, toMatch.host, "The host matches.");
           is(item.isSecure, toMatch.isSecure, "The isSecure value matches.");
           is(item.isDomain, toMatch.isDomain, "The isDomain value matches.");
           break;
         }
       }
-      if (!found) {
-        ok(false, "cookie " + item.name + " should not exist in response;");
-      }
+      ok(found, "cookie " + item.name + " should exist in response");
     }
   };
 
   ok(!!storeMap.cookies[host], "Host is present in the list : " + host);
   matchItems(yield cookiesActor.getStoreObjects(host));
   if (index == Object.keys(hosts).length - 1) {
     return;
   }
@@ -391,20 +389,17 @@ var testLocalStorageObjects = Task.async
       for (let toMatch of storeMap.localStorage[host]) {
         if (item.name == toMatch.name) {
           found = true;
           ok(true, "Found local storage item " + item.name + " in response");
           is(item.value.str, toMatch.value, "The value matches.");
           break;
         }
       }
-      if (!found) {
-        ok(false, "local storage item " + item.name +
-                  " should not exist in response;");
-      }
+      ok(found, "local storage item " + item.name + " should exist in response");
     }
   };
 
   ok(!!storeMap.localStorage[host], "Host is present in the list : " + host);
   matchItems(yield localStorageActor.getStoreObjects(host));
   if (index == Object.keys(hosts).length - 1) {
     return;
   }
@@ -428,20 +423,17 @@ var testSessionStorageObjects = Task.asy
       for (let toMatch of storeMap.sessionStorage[host]) {
         if (item.name == toMatch.name) {
           found = true;
           ok(true, "Found session storage item " + item.name + " in response");
           is(item.value.str, toMatch.value, "The value matches.");
           break;
         }
       }
-      if (!found) {
-        ok(false, "session storage item " + item.name +
-                  " should not exist in response;");
-      }
+      ok(found, "session storage item " + item.name + " should exist in response");
     }
   };
 
   ok(!!storeMap.sessionStorage[host], "Host is present in the list : " + host);
   matchItems(yield sessionStorageActor.getStoreObjects(host));
   if (index == Object.keys(hosts).length - 1) {
     return;
   }
@@ -457,22 +449,17 @@ var testIndexedDB = Task.async(function*
       let parsedItem = JSON.parse(item);
       let found = false;
       for (let toMatch of IDBValues.listStoresResponse[host]) {
         if (toMatch[0] == parsedItem[0] && toMatch[1] == parsedItem[1]) {
           found = true;
           break;
         }
       }
-      if (!found) {
-        ok (false, item + " should not be present in list stores response");
-      }
-      else {
-        ok (true, item + " found from indexedDB list stores response");
-      }
+      ok(found, item + " should exist in list stores response");
     }
   }
 
   yield testIndexedDBs(0, indexedDBActor.hosts, indexedDBActor);
   yield  testObjectStores(0, indexedDBActor.hosts, indexedDBActor);
   yield  testIDBEntries(0, indexedDBActor.hosts, indexedDBActor);
 });
 
@@ -489,19 +476,17 @@ var testIndexedDBs = Task.async(function
           ok(true, "Found indexed db " + item.db + " in response");
           is(item.origin, toMatch.origin, "The origin matches.");
           is(item.version, toMatch.version, "The version matches.");
           is(item.objectStores, toMatch.objectStores,
              "The numebr of object stores matches.");
           break;
         }
       }
-      if (!found) {
-        ok(false, "indexed db " + item.name + " should not exist in response");
-      }
+      ok(found, "indexed db " + item.name + " should exist in response");
     }
   };
 
   ok(!!IDBValues.dbDetails[host], "Host is present in the list : " + host);
   matchItems(yield indexedDBActor.getStoreObjects(host));
   if (index == Object.keys(hosts).length - 1) {
     return;
   }
@@ -532,26 +517,22 @@ var testObjectStores = Task.async(functi
                 is(index.keyPath, toMatchIndex.keyPath,
                    "The keyPath of index matches.");
                 is(index.unique, toMatchIndex.unique, "The unique matches");
                 is(index.multiEntry, toMatchIndex.multiEntry,
                    "The multiEntry matches");
                 break;
               }
             }
-            if (!indexFound) {
-              ok(false, "Index " + index + " should not be present in response");
-            }
+            ok(indexFound, "Index " + index + " should exist in response");
           }
           break;
         }
       }
-      if (!found) {
-        ok(false, "indexed db " + item.name + " should not exist in response");
-      }
+      ok(found, "indexed db " + item.name + " should exist in response");
     }
   };
 
   ok(!!IDBValues.objectStoreDetails[host], "Host is present in the list : " + host);
   for (let name of hosts[host]) {
     let objName = JSON.parse(name).slice(0, 1);
     matchItems((
       yield indexedDBActor.getStoreObjects(host, [JSON.stringify(objName)])
@@ -579,19 +560,17 @@ var testIDBEntries = Task.async(function
              "Number of entries in the value matches");
           for (let key in value) {
             is(value[key], toMatch.value[key],
                "value of " + key + " value key matches");
           }
           break;
         }
       }
-      if (!found) {
-        ok(false, "indexed db item " + item.name + " should not exist in response");
-      }
+      ok(found, "indexed db item " + item.name + " should exist in response");
     }
   };
 
   ok(!!IDBValues.entries[host], "Host is present in the list : " + host);
   for (let name of hosts[host]) {
     let parsed = JSON.parse(name);
     matchItems((
       yield indexedDBActor.getStoreObjects(host, [name])
--- a/dom/animation/Animation.cpp
+++ b/dom/animation/Animation.cpp
@@ -89,24 +89,24 @@ namespace {
 Animation::Constructor(const GlobalObject& aGlobal,
                        KeyframeEffectReadOnly* aEffect,
                        AnimationTimeline* aTimeline,
                        ErrorResult& aRv)
 {
   nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aGlobal.GetAsSupports());
   RefPtr<Animation> animation = new Animation(global);
 
+  if (!aEffect) {
+    // Bug 1049975: We do not support null effect yet.
+    aRv.Throw(NS_ERROR_DOM_ANIM_NO_EFFECT_ERR);
+    return nullptr;
+  }
   if (!aTimeline) {
     // Bug 1096776: We do not support null timeline yet.
-    aRv.Throw(NS_ERROR_FAILURE);
-    return nullptr;
-  }
-  if (!aEffect) {
-    // Bug 1049975: We do not support null effect yet.
-    aRv.Throw(NS_ERROR_FAILURE);
+    aRv.Throw(NS_ERROR_DOM_ANIM_NO_TIMELINE_ERR);
     return nullptr;
   }
 
   animation->SetTimeline(aTimeline);
   animation->SetEffect(aEffect);
 
   return animation.forget();
 }
--- a/dom/animation/KeyframeEffect.cpp
+++ b/dom/animation/KeyframeEffect.cpp
@@ -433,16 +433,47 @@ KeyframesEqualIgnoringComputedOffsets(co
         a.mTimingFunction != b.mTimingFunction ||
         a.mPropertyValues != b.mPropertyValues) {
       return false;
     }
   }
   return true;
 }
 
+// https://w3c.github.io/web-animations/#dom-keyframeeffect-setframes
+void
+KeyframeEffectReadOnly::SetFrames(JSContext* aContext,
+                                  JS::Handle<JSObject*> aFrames,
+                                  ErrorResult& aRv)
+{
+  nsIDocument* doc = AnimationUtils::GetCurrentRealmDocument(aContext);
+  if (!doc) {
+    aRv.Throw(NS_ERROR_FAILURE);
+    return;
+  }
+
+  nsTArray<Keyframe> keyframes =
+    KeyframeUtils::GetKeyframesFromObject(aContext, aFrames, aRv);
+  if (aRv.Failed()) {
+    return;
+  }
+
+  RefPtr<nsStyleContext> styleContext;
+  nsIPresShell* shell = doc->GetShell();
+  if (shell && mTarget) {
+    nsIAtom* pseudo =
+      mPseudoType < CSSPseudoElementType::Count ?
+      nsCSSPseudoElements::GetPseudoAtom(mPseudoType) : nullptr;
+    styleContext =
+      nsComputedDOMStyle::GetStyleContextForElement(mTarget, pseudo, shell);
+  }
+
+  SetFrames(Move(keyframes), styleContext);
+}
+
 void
 KeyframeEffectReadOnly::SetFrames(nsTArray<Keyframe>&& aFrames,
                                   nsStyleContext* aStyleContext)
 {
   if (KeyframesEqualIgnoringComputedOffsets(aFrames, mFrames)) {
     return;
   }
 
@@ -720,37 +751,23 @@ KeyframeEffectReadOnly::ConstructKeyfram
   CSSPseudoElementType pseudoType = CSSPseudoElementType::NotPseudo;
   if (target.IsElement()) {
     targetElement = &target.GetAsElement();
   } else {
     targetElement = target.GetAsCSSPseudoElement().ParentElement();
     pseudoType = target.GetAsCSSPseudoElement().GetType();
   }
 
-  nsTArray<Keyframe> keyframes =
-    KeyframeUtils::GetKeyframesFromObject(aGlobal.Context(), aFrames, aRv);
-  if (aRv.Failed()) {
-    return nullptr;
-  }
-
   RefPtr<KeyframeEffectType> effect =
     new KeyframeEffectType(targetElement->OwnerDoc(), targetElement,
                            pseudoType, timingParams);
-
-  RefPtr<nsStyleContext> styleContext;
-  nsIPresShell* shell = doc->GetShell();
-  if (shell && targetElement) {
-    nsIAtom* pseudo =
-      pseudoType < CSSPseudoElementType::Count ?
-      nsCSSPseudoElements::GetPseudoAtom(pseudoType) : nullptr;
-    styleContext =
-      nsComputedDOMStyle::GetStyleContextForElement(targetElement, pseudo,
-                                                    shell);
+  effect->SetFrames(aGlobal.Context(), aFrames, aRv);
+  if (aRv.Failed()) {
+    return nullptr;
   }
-  effect->SetFrames(Move(keyframes), styleContext);
 
   return effect.forget();
 }
 
 void
 KeyframeEffectReadOnly::ResetIsRunningOnCompositor()
 {
   for (AnimationProperty& property : mProperties) {
--- a/dom/animation/KeyframeEffect.h
+++ b/dom/animation/KeyframeEffect.h
@@ -276,16 +276,18 @@ public:
 
   bool IsInPlay() const;
   bool IsCurrent() const;
   bool IsInEffect() const;
 
   void SetAnimation(Animation* aAnimation);
   Animation* GetAnimation() const { return mAnimation; }
 
+  void SetFrames(JSContext* aContext, JS::Handle<JSObject*> aFrames,
+                 ErrorResult& aRv);
   void SetFrames(nsTArray<Keyframe>&& aFrames, nsStyleContext* aStyleContext);
   const AnimationProperty*
   GetAnimationOfProperty(nsCSSProperty aProperty) const;
   bool HasAnimationOfProperty(nsCSSProperty aProperty) const {
     return GetAnimationOfProperty(aProperty) != nullptr;
   }
   bool HasAnimationOfProperties(const nsCSSProperty* aProperties,
                                 size_t aPropertyCount) const;
--- a/dom/apps/Webapps.jsm
+++ b/dom/apps/Webapps.jsm
@@ -4422,20 +4422,22 @@ this.DOMApplicationRegistry = {
         jwtData = receiptParts[0];
       }
 
       let segments = jwtData.split('.');
       if (segments.length != 3) {
         return "INVALID_SEGMENTS_NUMBER";
       }
 
-      // We need to translate the base64 alphabet used in JWT to our base64 alphabet
-      // before calling atob.
-      let decodedReceipt = JSON.parse(atob(segments[1].replace(/-/g, '+')
-                                                      .replace(/_/g, '/')));
+      let jwtBuffer = ChromeUtils.base64URLDecode(segments[1], {
+        // JWT/JWS prohibits padding per RFC 7515, section 2.
+        padding: "reject",
+      });
+      let textDecoder = new TextDecoder("utf-8");
+      let decodedReceipt = JSON.parse(textDecoder.decode(jwtBuffer));
       if (!decodedReceipt) {
         return "INVALID_RECEIPT_ENCODING";
       }
 
       // Required values for a receipt
       if (!decodedReceipt.typ) {
         return "RECEIPT_TYPE_REQUIRED";
       }
--- a/dom/base/ChromeUtils.cpp
+++ b/dom/base/ChromeUtils.cpp
@@ -1,15 +1,16 @@
 /* -*-  Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2; -*- */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "ChromeUtils.h"
 
+#include "mozilla/Base64.h"
 #include "mozilla/BasePrincipal.h"
 
 namespace mozilla {
 namespace dom {
 
 /* static */ void
 ThreadSafeChromeUtils::NondeterministicGetWeakMapKeys(GlobalObject& aGlobal,
                                                       JS::Handle<JS::Value> aMap,
@@ -46,16 +47,71 @@ ThreadSafeChromeUtils::NondeterministicG
       aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
     } else {
       aRetval.set(objRet ? JS::ObjectValue(*objRet) : JS::UndefinedValue());
     }
   }
 }
 
 /* static */ void
+ThreadSafeChromeUtils::Base64URLEncode(GlobalObject& aGlobal,
+                                       const ArrayBufferViewOrArrayBuffer& aSource,
+                                       const Base64URLEncodeOptions& aOptions,
+                                       nsACString& aResult,
+                                       ErrorResult& aRv)
+{
+  size_t length = 0;
+  uint8_t* data = nullptr;
+  if (aSource.IsArrayBuffer()) {
+    const ArrayBuffer& buffer = aSource.GetAsArrayBuffer();
+    buffer.ComputeLengthAndData();
+    length = buffer.Length();
+    data = buffer.Data();
+  } else if (aSource.IsArrayBufferView()) {
+    const ArrayBufferView& view = aSource.GetAsArrayBufferView();
+    view.ComputeLengthAndData();
+    length = view.Length();
+    data = view.Data();
+  } else {
+    MOZ_CRASH("Uninitialized union: expected buffer or view");
+  }
+
+  nsresult rv = mozilla::Base64URLEncode(length, data, aOptions, aResult);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    aResult.Truncate();
+    aRv.Throw(rv);
+  }
+}
+
+/* static */ void
+ThreadSafeChromeUtils::Base64URLDecode(GlobalObject& aGlobal,
+                                       const nsACString& aString,
+                                       const Base64URLDecodeOptions& aOptions,
+                                       JS::MutableHandle<JSObject*> aRetval,
+                                       ErrorResult& aRv)
+{
+  FallibleTArray<uint8_t> data;
+  nsresult rv = mozilla::Base64URLDecode(aString, aOptions, data);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    aRv.Throw(rv);
+    return;
+  }
+
+  JS::Rooted<JSObject*> buffer(aGlobal.Context(),
+                               ArrayBuffer::Create(aGlobal.Context(),
+                                                   data.Length(),
+                                                   data.Elements()));
+  if (NS_WARN_IF(!buffer)) {
+    aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
+    return;
+  }
+  aRetval.set(buffer);
+}
+
+/* static */ void
 ChromeUtils::OriginAttributesToSuffix(dom::GlobalObject& aGlobal,
                                       const dom::OriginAttributesDictionary& aAttrs,
                                       nsCString& aSuffix)
 
 {
   GenericOriginAttributes attrs(aAttrs);
   attrs.CreateSuffix(aSuffix);
 }
--- a/dom/base/ChromeUtils.h
+++ b/dom/base/ChromeUtils.h
@@ -15,16 +15,18 @@
 namespace mozilla {
 
 namespace devtools {
 class HeapSnapshot;
 } // namespace devtools
 
 namespace dom {
 
+class ArrayBufferViewOrArrayBuffer;
+
 class ThreadSafeChromeUtils
 {
 public:
   // Implemented in devtools/shared/heapsnapshot/HeapSnapshot.cpp
   static void SaveHeapSnapshot(GlobalObject& global,
                                const HeapSnapshotBoundaries& boundaries,
                                nsAString& filePath,
                                ErrorResult& rv);
@@ -38,16 +40,28 @@ public:
                                              JS::Handle<JS::Value> aMap,
                                              JS::MutableHandle<JS::Value> aRetval,
                                              ErrorResult& aRv);
 
   static void NondeterministicGetWeakSetKeys(GlobalObject& aGlobal,
                                              JS::Handle<JS::Value> aSet,
                                              JS::MutableHandle<JS::Value> aRetval,
                                              ErrorResult& aRv);
+
+  static void Base64URLEncode(GlobalObject& aGlobal,
+                              const ArrayBufferViewOrArrayBuffer& aSource,
+                              const Base64URLEncodeOptions& aOptions,
+                              nsACString& aResult,
+                              ErrorResult& aRv);
+
+  static void Base64URLDecode(GlobalObject& aGlobal,
+                              const nsACString& aString,
+                              const Base64URLDecodeOptions& aOptions,
+                              JS::MutableHandle<JSObject*> aRetval,
+                              ErrorResult& aRv);
 };
 
 class ChromeUtils : public ThreadSafeChromeUtils
 {
 public:
   static void
   OriginAttributesToSuffix(GlobalObject& aGlobal,
                            const dom::OriginAttributesDictionary& aAttrs,
--- a/dom/base/IndexedDBHelper.jsm
+++ b/dom/base/IndexedDBHelper.jsm
@@ -19,16 +19,20 @@ const Ci = Components.interfaces;
 this.EXPORTED_SYMBOLS = ["IndexedDBHelper"];
 
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.importGlobalProperties(["indexedDB"]);
 
 XPCOMUtils.defineLazyModuleGetter(this, 'Services',
   'resource://gre/modules/Services.jsm');
 
+function getErrorName(err) {
+  return err && err.name || "UnknownError";
+}
+
 this.IndexedDBHelper = function IndexedDBHelper() {
 }
 
 IndexedDBHelper.prototype = {
   // Close the database
   close: function close() {
     if (this._db) {
       this._db.close();
@@ -57,17 +61,25 @@ IndexedDBHelper.prototype = {
     let invokeCallbacks = err => {
       for (let callback of self._waitForOpenCallbacks) {
         callback(err);
       }
       self._waitForOpenCallbacks.clear();
     };
 
     if (DEBUG) debug("Try to open database:" + self.dbName + " " + self.dbVersion);
-    let req = indexedDB.open(this.dbName, this.dbVersion);
+    let req;
+    try {
+      req = indexedDB.open(this.dbName, this.dbVersion);
+    } catch (e) {
+      if (DEBUG) debug("Error opening database: " + self.dbName);
+      Services.tm.currentThread.dispatch(() => invokeCallbacks(getErrorName(e)),
+                                         Ci.nsIThread.DISPATCH_NORMAL);
+      return;
+    }
     req.onsuccess = function (event) {
       if (DEBUG) debug("Opened database:" + self.dbName + " " + self.dbVersion);
       self._db = event.target.result;
       self._db.onversionchange = function(event) {
         if (DEBUG) debug("WARNING: DB modified from a different window.");
       }
       invokeCallbacks();
     };
@@ -78,17 +90,17 @@ IndexedDBHelper.prototype = {
         debug("Correct new database version:" + (aEvent.newVersion == this.dbVersion));
       }
 
       let _db = aEvent.target.result;
       self.upgradeSchema(req.transaction, _db, aEvent.oldVersion, aEvent.newVersion);
     };
     req.onerror = function (aEvent) {
       if (DEBUG) debug("Failed to open database: " + self.dbName);
-      invokeCallbacks(aEvent.target.error.name);
+      invokeCallbacks(getErrorName(aEvent.target.error));
     };
     req.onblocked = function (aEvent) {
       if (DEBUG) debug("Opening database request is blocked.");
     };
   },
 
   /**
    * Use the cached DB or open a new one.
@@ -130,18 +142,25 @@ IndexedDBHelper.prototype = {
    *        Success callback to call on a successful transaction commit.
    *        The result is stored in txn.result.
    * @param failureCb
    *        Error callback to call when an error is encountered.
    */
   newTxn: function newTxn(txn_type, store_name, callback, successCb, failureCb) {
     this.ensureDB(function () {
       if (DEBUG) debug("Starting new transaction" + txn_type);
-      let txn = this._db.transaction(Array.isArray(store_name) ? store_name : this.dbStoreNames, txn_type);
-      if (DEBUG) debug("Retrieving object store", this.dbName);
+      let txn;
+      try {
+        txn = this._db.transaction(Array.isArray(store_name) ? store_name : this.dbStoreNames, txn_type);
+      } catch (e) {
+        if (DEBUG) debug("Error starting transaction: " + this.dbName);
+        failureCb(getErrorName(e));
+        return;
+      }
+      if (DEBUG) debug("Retrieving object store: " + this.dbName);
       let stores;
       if (Array.isArray(store_name)) {
         stores = [];
         for (let i = 0; i < store_name.length; ++i) {
           stores.push(txn.objectStore(store_name[i]));
         }
       } else {
         stores = txn.objectStore(store_name);
@@ -156,21 +175,17 @@ IndexedDBHelper.prototype = {
 
       txn.onabort = function (event) {
         if (DEBUG) debug("Caught error on transaction");
         /*
          * event.target.error may be null
          * if txn was aborted by calling txn.abort()
          */
         if (failureCb) {
-          if (event.target.error) {
-            failureCb(event.target.error.name);
-          } else {
-            failureCb("UnknownError");
-          }
+          failureCb(getErrorName(event.target.error));
         }
       };
       callback(txn, stores);
     }.bind(this), failureCb);
   },
 
   /**
    * Initialize the DB. Does not call open.
--- a/dom/base/domerr.msg
+++ b/dom/base/domerr.msg
@@ -114,16 +114,18 @@ DOM4_MSG_DEF(BtUnhandledError,    "Unhan
 DOM4_MSG_DEF(BtAuthFailureError,  "Authentication failure",  NS_ERROR_DOM_BLUETOOTH_AUTH_FAILURE)
 DOM4_MSG_DEF(BtRmtDevDownError,   "Remote device down",  NS_ERROR_DOM_BLUETOOTH_RMT_DEV_DOWN)
 DOM4_MSG_DEF(BtAuthRejectedError, "Authentication rejected",  NS_ERROR_DOM_BLUETOOTH_AUTH_REJECTED)
 
 /* Web Animations errors */
 
 DOM4_MSG_DEF(NotSupportedError, "Animation to or from an underlying value is not yet supported.", NS_ERROR_DOM_ANIM_MISSING_PROPS_ERR)
 DOM4_MSG_DEF(NotSupportedError, "Animation with no target is not yet supported.", NS_ERROR_DOM_ANIM_NO_TARGET_ERR)
+DOM4_MSG_DEF(NotSupportedError, "Animation with no timeline is not yet supported.", NS_ERROR_DOM_ANIM_NO_TIMELINE_ERR)
+DOM4_MSG_DEF(NotSupportedError, "Animation with no effect is not yet supported.", NS_ERROR_DOM_ANIM_NO_EFFECT_ERR)
 
 /* common global codes (from nsError.h) */
 
 DOM_MSG_DEF(NS_OK                                  , "Success")
 DOM_MSG_DEF(NS_ERROR_NOT_INITIALIZED               , "Component not initialized")
 DOM_MSG_DEF(NS_ERROR_ALREADY_INITIALIZED           , "Component already initialized")
 DOM_MSG_DEF(NS_ERROR_NOT_IMPLEMENTED               , "Method not implemented")
 DOM_MSG_DEF(NS_NOINTERFACE                         , "Component does not have requested interface")
@@ -151,11 +153,13 @@ DOM4_MSG_DEF(InvalidStateError, "A mutat
 DOM4_MSG_DEF(AbortError, "A request was aborted, for example through a call to FileHandle.abort.", NS_ERROR_DOM_FILEHANDLE_ABORT_ERR)
 DOM4_MSG_DEF(QuotaExceededError, "The current file handle exceeded its quota limitations.", NS_ERROR_DOM_FILEHANDLE_QUOTA_ERR)
 
 /* Push API errors. */
 DOM4_MSG_DEF(InvalidStateError, "Invalid service worker registration.", NS_ERROR_DOM_PUSH_INVALID_REGISTRATION_ERR)
 DOM4_MSG_DEF(PermissionDeniedError, "User denied permission to use the Push API.", NS_ERROR_DOM_PUSH_DENIED_ERR)
 DOM4_MSG_DEF(AbortError, "Error retrieving push subscription.", NS_ERROR_DOM_PUSH_ABORT_ERR)
 DOM4_MSG_DEF(NetworkError, "Push service unreachable.", NS_ERROR_DOM_PUSH_SERVICE_UNREACHABLE)
+DOM4_MSG_DEF(InvalidAccessError, "Invalid raw ECDSA P-256 public key.", NS_ERROR_DOM_PUSH_INVALID_KEY_ERR)
+DOM4_MSG_DEF(InvalidStateError, "A subscription with a different application server key already exists.", NS_ERROR_DOM_PUSH_MISMATCHED_KEY_ERR)
 
 DOM_MSG_DEF(NS_ERROR_DOM_JS_EXCEPTION, "A callback threw an exception")
 DOM_MSG_DEF(NS_ERROR_DOM_DOMEXCEPTION, "A DOMException was thrown")
new file mode 100644
--- /dev/null
+++ b/dom/base/test/unit/test_chromeutils_base64.js
@@ -0,0 +1,103 @@
+"use strict";
+
+function run_test() {
+  test_base64URLEncode();
+  test_base64URLDecode();
+}
+
+// Test vectors from RFC 4648, section 10.
+let textTests = {
+  "": "",
+  "f": "Zg",
+  "fo": "Zm8",
+  "foo": "Zm9v",
+  "foob": "Zm9vYg",
+  "fooba": "Zm9vYmE",
+  "foobar": "Zm9vYmFy",
+}
+
+// Examples from RFC 4648, section 9.
+let binaryTests = [{
+  decoded: new Uint8Array([0x14, 0xfb, 0x9c, 0x03, 0xd9, 0x7e]),
+  encoded: "FPucA9l-",
+}, {
+  decoded: new Uint8Array([0x14, 0xfb, 0x9c, 0x03, 0xd9]),
+  encoded: "FPucA9k",
+}, {
+  decoded: new Uint8Array([0x14, 0xfb, 0x9c, 0x03]),
+  encoded: "FPucAw",
+}];
+
+function padEncodedValue(value) {
+  switch (value.length % 4) {
+    case 0:
+      return value;
+    case 2:
+      return value + "==";
+    case 3:
+      return value + "=";
+    default:
+      throw new TypeError("Invalid encoded value");
+  }
+}
+
+function testEncode(input, encoded) {
+  equal(ChromeUtils.base64URLEncode(input, { pad: false }),
+        encoded, encoded + " without padding");
+  equal(ChromeUtils.base64URLEncode(input, { pad: true }),
+        padEncodedValue(encoded), encoded + " with padding");
+}
+
+function test_base64URLEncode() {
+  throws(_ => ChromeUtils.base64URLEncode(new Uint8Array(0)), /TypeError/,
+         "Should require encoding options");
+  throws(_ => ChromeUtils.base64URLEncode(new Uint8Array(0), {}), /TypeError/,
+         "Encoding should require the padding option");
+
+  for (let {decoded, encoded} of binaryTests) {
+    testEncode(decoded, encoded);
+  }
+
+  let textEncoder = new TextEncoder("utf-8");
+  for (let decoded of Object.keys(textTests)) {
+    let input = textEncoder.encode(decoded);
+    testEncode(input, textTests[decoded]);
+  }
+}
+
+function testDecode(input, decoded) {
+  let buffer = ChromeUtils.base64URLDecode(input, { padding: "reject" });
+  deepEqual(new Uint8Array(buffer), decoded, input + " with padding rejected");
+
+  let paddedValue = padEncodedValue(input);
+  buffer = ChromeUtils.base64URLDecode(paddedValue, { padding: "ignore" });
+  deepEqual(new Uint8Array(buffer), decoded, input + " with padding ignored");
+
+  if (paddedValue.length > input.length) {
+    throws(_ => ChromeUtils.base64URLDecode(paddedValue, { padding: "reject" }),
+           paddedValue + " with padding rejected should throw");
+
+    throws(_ => ChromeUtils.base64URLDecode(input, { padding: "require" }),
+           input + " with padding required should throw");
+
+    buffer = ChromeUtils.base64URLDecode(paddedValue, { padding: "require" });
+    deepEqual(new Uint8Array(buffer), decoded, paddedValue + " with padding required");
+  }
+}
+
+function test_base64URLDecode() {
+  throws(_ => ChromeUtils.base64URLDecode(""), /TypeError/,
+         "Should require decoding options");
+  throws(_ => ChromeUtils.base64URLEncode("", {}), /TypeError/,
+         "Decoding should require the padding option");
+
+  for (let {decoded, encoded} of binaryTests) {
+    testDecode(encoded, decoded);
+  }
+
+  let textEncoder = new TextEncoder("utf-8");
+  for (let decoded of Object.keys(textTests)) {
+    let expectedBuffer = textEncoder.encode(decoded);
+    testDecode(textTests[decoded], expectedBuffer);
+  }
+}
--- a/dom/base/test/unit/xpcshell.ini
+++ b/dom/base/test/unit/xpcshell.ini
@@ -46,8 +46,9 @@ head = head_xml.js
 [test_xhr_document.js]
 [test_xhr_standalone.js]
 [test_xml_parser.js]
 head = head_xml.js
 [test_xml_serializer.js]
 head = head_xml.js
 [test_xmlserializer.js]
 [test_cancelPrefetch.js]
+[test_chromeutils_base64.js]
--- a/dom/canvas/WebGLContext.cpp
+++ b/dom/canvas/WebGLContext.cpp
@@ -1465,17 +1465,17 @@ static bool
 CheckContextLost(GLContext* gl, bool* const out_isGuilty)
 {
     MOZ_ASSERT(gl);
     MOZ_ASSERT(out_isGuilty);
 
     bool isEGL = gl->GetContextType() == gl::GLContextType::EGL;
 
     GLenum resetStatus = LOCAL_GL_NO_ERROR;
-    if (gl->HasRobustness()) {
+    if (gl->IsSupported(GLFeature::robustness)) {
         gl->MakeCurrent();
         resetStatus = gl->fGetGraphicsResetStatus();
     } else if (isEGL) {
         // Simulate a ARB_robustness guilty context loss for when we
         // get an EGL_CONTEXT_LOST error. It may not actually be guilty,
         // but we can't make any distinction.
         if (!gl->MakeCurrent(true) && gl->IsContextLost()) {
             resetStatus = LOCAL_GL_UNKNOWN_CONTEXT_RESET_ARB;
--- a/dom/crypto/CryptoBuffer.cpp
+++ b/dom/crypto/CryptoBuffer.cpp
@@ -110,68 +110,40 @@ CryptoBuffer::AppendSECItem(const SECIte
 // * No padding
 // * URL-safe character set
 nsresult
 CryptoBuffer::FromJwkBase64(const nsString& aBase64)
 {
   NS_ConvertUTF16toUTF8 temp(aBase64);
   temp.StripWhitespace();
 
-  // Re-add padding
-  if (temp.Length() % 4 == 3) {
-    temp.AppendLiteral("=");
-  } else if (temp.Length() % 4 == 2) {
-    temp.AppendLiteral("==");
-  } if (temp.Length() % 4 == 1) {
-    return NS_ERROR_FAILURE; // bad Base64
-  }
-
-  // Translate from URL-safe character set to normal
-  temp.ReplaceChar('-', '+');
-  temp.ReplaceChar('_', '/');
-
-  // Perform the actual base64 decode
-  nsCString binaryData;
-  nsresult rv = Base64Decode(temp, binaryData);
+  Base64URLDecodeOptions options;
+  // JWK prohibits padding per RFC 7515, section 2.
+  options.mPadding = Base64URLDecodePadding::Reject;
+  nsresult rv = Base64URLDecode(temp, options, *this);
   NS_ENSURE_SUCCESS(rv, rv);
 
-  if (!Assign((const uint8_t*) binaryData.BeginReading(),
-              binaryData.Length())) {
-    return NS_ERROR_FAILURE;
-  }
-
   return NS_OK;
 }
 
 nsresult
 CryptoBuffer::ToJwkBase64(nsString& aBase64)
 {
   // Shortcut for the empty octet string
   if (Length() == 0) {
     aBase64.Truncate();
     return NS_OK;
   }
 
-  // Perform the actual base64 encode
-  nsCString base64;
-  nsDependentCSubstring binaryData((const char*) Elements(),
-                                   (const char*) (Elements() + Length()));
-  nsresult rv = Base64Encode(binaryData, base64);
+  nsAutoCString base64;
+  Base64URLEncodeOptions options;
+  options.mPad = false;
+  nsresult rv = Base64URLEncode(Length(), Elements(), options, base64);
   NS_ENSURE_SUCCESS(rv, rv);
 
-  // Strip padding
-  base64.Trim("=");
-
-  // Translate to the URL-safe charset
-  base64.ReplaceChar('+', '-');
-  base64.ReplaceChar('/', '_');
-  if (base64.FindCharInSet("+/", 0) != kNotFound) {
-    return NS_ERROR_FAILURE;
-  }
-
   CopyASCIItoUTF16(base64, aBase64);
   return NS_OK;
 }
 
 bool
 CryptoBuffer::ToSECItem(PLArenaPool *aArena, SECItem* aItem) const
 {
   aItem->type = siBuffer;
--- a/dom/crypto/test/test-vectors.js
+++ b/dom/crypto/test/test-vectors.js
@@ -718,17 +718,17 @@ tv = {
       x: "YoV6fhCph4kyt7sUkqiZOtbRs0rF6etPqlnrn1nzSB95NElaw4uTK7Pn2nlFFqqH",
       y: "bf3tRz6icq3-W6hhmoqDTBKjdOQUJ5xHr5kX4X-h5MZk_P_nCrG3IUVl1SAbhWDw"
     },
 
     jwk_priv: {
       kty: "EC",
       crv: "P-384",
       d: "RT8f0pRw4CL1Tgk4rwuNnNbFoQBNTTBkr7WVLLm4fDA3boYZpNB_t-rbMVLx0CRp",
-      x: "_XwhXRnOzEfCsWIRCz3QLClaDkigQFvXmqYNdh/7vJdADykPbfGi1VgAu3XJdXoD",
+      x: "_XwhXRnOzEfCsWIRCz3QLClaDkigQFvXmqYNdh_7vJdADykPbfGi1VgAu3XJdXoD",
       y: "S1P_FBCXYGE-5VPvTCRnFT7bPIPmUPV9qKTM24TQFYEUgIDfzCLsyGCWK-rhP6jU"
     },
 
     secret: util.hex2abv(
       "a3d28aa18f905a48a5f166b4ddbf5f6b499e43858ccdd80b869946aba2c5d461" +
       "db6a1e5b1137687801878ff0f8d9a7b3"
     )
   },
--- a/dom/events/DataTransfer.cpp
+++ b/dom/events/DataTransfer.cpp
@@ -13,16 +13,20 @@
 #include "nsISupportsPrimitives.h"
 #include "nsIScriptSecurityManager.h"
 #include "mozilla/dom/DOMStringList.h"
 #include "nsError.h"
 #include "nsIDragService.h"
 #include "nsIClipboard.h"
 #include "nsContentUtils.h"
 #include "nsIContent.h"
+#include "nsIBinaryInputStream.h"
+#include "nsIBinaryOutputStream.h"
+#include "nsIStorageStream.h"
+#include "nsStringStream.h"
 #include "nsCRT.h"
 #include "nsIScriptObjectPrincipal.h"
 #include "nsIScriptContext.h"
 #include "nsIDocument.h"
 #include "nsIScriptGlobalObject.h"
 #include "nsVariant.h"
 #include "mozilla/dom/ContentChild.h"
 #include "mozilla/dom/DataTransferBinding.h"
@@ -75,16 +79,22 @@ NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(
   NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIDOMDataTransfer)
 NS_INTERFACE_MAP_END
 
 // the size of the array
 const char DataTransfer::sEffects[8][9] = {
   "none", "copy", "move", "copyMove", "link", "copyLink", "linkMove", "all"
 };
 
+// Used for custom clipboard types.
+enum CustomClipboardTypeId {
+  eCustomClipboardTypeId_None,
+  eCustomClipboardTypeId_String
+};
+
 DataTransfer::DataTransfer(nsISupports* aParent, EventMessage aEventMessage,
                            bool aIsExternal, int32_t aClipboardType)
   : mParent(aParent)
   , mDropEffect(nsIDragService::DRAGDROP_ACTION_NONE)
   , mEffectAllowed(nsIDragService::DRAGDROP_ACTION_UNINITIALIZED)
   , mEventMessage(aEventMessage)
   , mCursorState(false)
   , mReadOnly(true)
@@ -705,22 +715,35 @@ DataTransfer::SetDataAtInternal(const ns
 
   // Only the first item is valid for clipboard events
   if (aIndex > 0 &&
       (mEventMessage == eCut || mEventMessage == eCopy ||
        mEventMessage == ePaste)) {
     return NS_ERROR_DOM_INDEX_SIZE_ERR;
   }
 
-  // don't allow non-chrome to add file data
-  // XXX perhaps this should also limit any non-string type as well
-  if ((aFormat.EqualsLiteral("application/x-moz-file-promise") ||
-       aFormat.EqualsLiteral("application/x-moz-file")) &&
-       !nsContentUtils::IsSystemPrincipal(aSubjectPrincipal)) {
-    return NS_ERROR_DOM_SECURITY_ERR;
+  // Don't allow the custom type to be assigned.
+  if (aFormat.EqualsLiteral(kCustomTypesMime)) {
+    return NS_ERROR_TYPE_ERR;
+  }
+
+  // Don't allow non-chrome to add non-string or file data. We'll block file
+  // promises as well which are used internally for drags to the desktop.
+  if (!nsContentUtils::IsSystemPrincipal(aSubjectPrincipal)) {
+    if (aFormat.EqualsLiteral("application/x-moz-file-promise") ||
+        aFormat.EqualsLiteral("application/x-moz-file")) {
+      return NS_ERROR_DOM_SECURITY_ERR;
+    }
+
+    uint16_t type;
+    aData->GetDataType(&type);
+    if (type == nsIDataType::VTYPE_INTERFACE ||
+        type == nsIDataType::VTYPE_INTERFACE_IS) {
+      return NS_ERROR_DOM_SECURITY_ERR;
+    }
   }
 
   return SetDataWithPrincipal(aFormat, aData, aIndex, aSubjectPrincipal);
 }
 
 void
 DataTransfer::MozSetDataAt(JSContext* aCx, const nsAString& aFormat,
                            JS::Handle<JS::Value> aData,
@@ -971,54 +994,176 @@ DataTransfer::GetTransferable(uint32_t a
 
   nsCOMPtr<nsITransferable> transferable =
     do_CreateInstance("@mozilla.org/widget/transferable;1");
   if (!transferable) {
     return nullptr;
   }
   transferable->Init(aLoadContext);
 
+  nsCOMPtr<nsIStorageStream> storageStream;
+  nsCOMPtr<nsIBinaryOutputStream> stream;
+
   bool added = false;
-  for (uint32_t f = 0; f < count; f++) {
-    const TransferItem& formatitem = item[f];
-    if (!formatitem.mData) { // skip empty items
-      continue;
-    }
+  bool handlingCustomFormats = true;
+  uint32_t totalCustomLength = 0;
+
+  const char* knownFormats[] = { kTextMime, kHTMLMime, kNativeHTMLMime, kRTFMime,
+                                 kURLMime, kURLDataMime, kURLDescriptionMime, kURLPrivateMime,
+                                 kPNGImageMime, kJPEGImageMime, kGIFImageMime, kNativeImageMime,
+                                 kFileMime, kFilePromiseMime, kFilePromiseDirectoryMime,
+                                 kMozTextInternal, kHTMLContext, kHTMLInfo };
+
+  /*
+   * Two passes are made here to iterate over all of the types. First, look for
+   * any types that are not in the list of known types. For this pass, handlingCustomFormats
+   * will be true. Data that corresponds to unknown types will be pulled out and
+   * inserted into a single type (kCustomTypesMime) by writing the data into a stream.
+   *
+   * The second pass will iterate over the formats looking for known types. These are
+   * added as is. The unknown types are all then inserted as a single type (kCustomTypesMime)
+   * in the same position of the first custom type. This model is used to maintain the
+   * format order as best as possible.
+   *
+   * The format of the kCustomTypesMime type is one or more of the following stored sequentially:
+   *   <32-bit> type (only none or string is supported)
+   *   <32-bit> length of format
+   *   <wide string> format
+   *   <32-bit> length of data
+   *   <wide string> data
+   * A type of eCustomClipboardTypeId_None ends the list, without any following data.
+   */
+  do {
+    for (uint32_t f = 0; f < count; f++) {
+      const TransferItem& formatitem = item[f];
+      if (!formatitem.mData) { // skip empty items
+        continue;
+      }
+
+      // If the data is of one of the well-known formats, use it directly.
+      bool isCustomFormat = true;
+      for (uint32_t f = 0; f < ArrayLength(knownFormats); f++) {
+        if (formatitem.mFormat.EqualsASCII(knownFormats[f])) {
+          isCustomFormat = false;
+          break;
+        }
+      }
+
+      uint32_t lengthInBytes;
+      nsCOMPtr<nsISupports> convertedData;
+
+      if (handlingCustomFormats) {
+        if (!ConvertFromVariant(formatitem.mData, getter_AddRefs(convertedData), &lengthInBytes)) {
+          continue;
+        }
+
+        // When handling custom types, add the data to the stream if this is a
+        // custom type.
+        if (isCustomFormat) {
+          // If it isn't a string, just ignore it. The dataTransfer is cached in the
+          // drag sesion during drag-and-drop, so non-strings will be available when
+          // dragging locally.
+          nsCOMPtr<nsISupportsString> str(do_QueryInterface(convertedData));
+          if (str) {
+            nsAutoString data;
+            str->GetData(data);
+
+            if (!stream) {
+              // Create a storage stream to write to.
+              NS_NewStorageStream(1024, UINT32_MAX, getter_AddRefs(storageStream));
+
+              nsCOMPtr<nsIOutputStream> outputStream;
+              storageStream->GetOutputStream(0, getter_AddRefs(outputStream));
+
+              stream = do_CreateInstance("@mozilla.org/binaryoutputstream;1");
+              stream->SetOutputStream(outputStream);
+            }
+
+            int32_t formatLength = formatitem.mFormat.Length() * sizeof(nsString::char_type);
 
-    uint32_t length;
-    nsCOMPtr<nsISupports> convertedData;
-    if (!ConvertFromVariant(formatitem.mData, getter_AddRefs(convertedData), &length)) {
-      continue;
+            stream->Write32(eCustomClipboardTypeId_String);
+            stream->Write32(formatLength);
+            stream->WriteBytes((const char *)formatitem.mFormat.get(), formatLength);
+            stream->Write32(lengthInBytes);
+            stream->WriteBytes((const char *)data.get(), lengthInBytes);
+
+            // The total size of the stream is the format length, the data length,
+            // two integers to hold the lengths and one integer for the string flag.
+            totalCustomLength += formatLength + lengthInBytes + (sizeof(uint32_t) * 3);
+          }
+        }
+      } else if (isCustomFormat && stream) {
+        // This is the second pass of the loop (handlingCustomFormats is false).
+        // When encountering the first custom format, append all of the stream
+        // at this position.
+
+        // Write out a terminator.
+        totalCustomLength += sizeof(uint32_t);
+        stream->Write32(eCustomClipboardTypeId_None);
+
+        nsCOMPtr<nsIInputStream> inputStream;
+        storageStream->NewInputStream(0, getter_AddRefs(inputStream));
+
+        RefPtr<nsStringBuffer> stringBuffer = nsStringBuffer::Alloc(totalCustomLength + 1);
+
+        // Read the data from the string and add a null-terminator as ToString needs it.
+        uint32_t amountRead;
+        inputStream->Read(static_cast<char*>(stringBuffer->Data()), totalCustomLength, &amountRead);
+        static_cast<char*>(stringBuffer->Data())[amountRead] = 0;
+
+        nsCString str;
+        stringBuffer->ToString(totalCustomLength, str);
+        nsCOMPtr<nsISupportsCString> strSupports(do_CreateInstance(NS_SUPPORTS_CSTRING_CONTRACTID));
+        strSupports->SetData(str);
+
+        nsresult rv = transferable->SetTransferData(kCustomTypesMime, strSupports, totalCustomLength);
+        if (NS_FAILED(rv)) {
+          return nullptr;
+        }
+
+        added = true;
+
+        // Clear the stream so it doesn't get used again.
+        stream = nullptr;
+      } else {
+        // This is the second pass of the loop and a known type is encountered.
+        // Add it as is.
+        if (!ConvertFromVariant(formatitem.mData, getter_AddRefs(convertedData), &lengthInBytes)) {
+          continue;
+        }
+
+        // The underlying drag code uses text/unicode, so use that instead of text/plain
+        const char* format;
+        NS_ConvertUTF16toUTF8 utf8format(formatitem.mFormat);
+        if (utf8format.EqualsLiteral(kTextMime)) {
+          format = kUnicodeMime;
+        } else {
+          format = utf8format.get();
+        }
+
+        // If a converter is set for a format, set the converter for the
+        // transferable and don't add the item
+        nsCOMPtr<nsIFormatConverter> converter = do_QueryInterface(convertedData);
+        if (converter) {
+          transferable->AddDataFlavor(format);
+          transferable->SetConverter(converter);
+          continue;
+        }
+
+        nsresult rv = transferable->SetTransferData(format, convertedData, lengthInBytes);
+        if (NS_FAILED(rv)) {
+          return nullptr;
+        }
+
+        added = true;
+      }
     }
 
-    // the underlying drag code uses text/unicode, so use that instead of text/plain
-    const char* format;
-    NS_ConvertUTF16toUTF8 utf8format(formatitem.mFormat);
-    if (utf8format.EqualsLiteral("text/plain")) {
-      format = kUnicodeMime;
-    } else {
-      format = utf8format.get();
-    }
-
-    // if a converter is set for a format, set the converter for the
-    // transferable and don't add the item
-    nsCOMPtr<nsIFormatConverter> converter = do_QueryInterface(convertedData);
-    if (converter) {
-      transferable->AddDataFlavor(format);
-      transferable->SetConverter(converter);
-      continue;
-    }
-
-    nsresult rv = transferable->SetTransferData(format, convertedData, length);
-    if (NS_FAILED(rv)) {
-      return nullptr;
-    }
-
-    added = true;
-  }
+    handlingCustomFormats = !handlingCustomFormats;
+  } while (!handlingCustomFormats);
 
   // only return the transferable if data was successfully added to it
   if (added) {
     return transferable.forget();
   }
 
   return nullptr;
 }
@@ -1140,16 +1285,29 @@ DataTransfer::SetDataWithPrincipal(const
   formatitem->mFormat = format;
   formatitem->mPrincipal = aPrincipal;
   formatitem->mData = aData;
 
   return NS_OK;
 }
 
 void
+DataTransfer::SetDataWithPrincipalFromOtherProcess(const nsAString& aFormat,
+                                                   nsIVariant* aData,
+                                                   uint32_t aIndex,
+                                                   nsIPrincipal* aPrincipal)
+{
+  if (aFormat.EqualsLiteral(kCustomTypesMime)) {
+    FillInExternalCustomTypes(aData, aIndex, aPrincipal);
+  } else {
+    SetDataWithPrincipal(aFormat, aData, aIndex, aPrincipal);
+  }
+}
+
+void
 DataTransfer::GetRealFormat(const nsAString& aInFormat, nsAString& aOutFormat)
 {
   // treat text/unicode as equivalent to text/plain
   nsAutoString lowercaseFormat;
   nsContentUtils::ASCIIToLower(aInFormat, lowercaseFormat);
   if (lowercaseFormat.EqualsLiteral("text") || lowercaseFormat.EqualsLiteral("text/unicode"))
     aOutFormat.AssignLiteral("text/plain");
   else if (lowercaseFormat.EqualsLiteral("url"))
@@ -1187,21 +1345,29 @@ DataTransfer::CacheExternalDragFormats()
   // make sure that the system principal is used for external drags
   nsIScriptSecurityManager* ssm = nsContentUtils::GetSecurityManager();
   nsCOMPtr<nsIPrincipal> sysPrincipal;
   ssm->GetSystemPrincipal(getter_AddRefs(sysPrincipal));
 
   // there isn't a way to get a list of the formats that might be available on
   // all platforms, so just check for the types that can actually be imported
   // XXXndeakin there are some other formats but those are platform specific.
-  const char* formats[] = { kFileMime, kHTMLMime, kRTFMime, kURLMime, kURLDataMime, kUnicodeMime };
+  const char* formats[] = { kFileMime, kHTMLMime, kRTFMime,
+                            kURLMime, kURLDataMime, kUnicodeMime };
 
   uint32_t count;
   dragSession->GetNumDropItems(&count);
   for (uint32_t c = 0; c < count; c++) {
+    // First, check for the special format that holds custom types.
+    bool supported;
+    dragSession->IsDataFlavorSupported(kCustomTypesMime, &supported);
+    if (supported) {
+      FillInExternalCustomTypes(c, sysPrincipal);
+    }
+
     for (uint32_t f = 0; f < ArrayLength(formats); f++) {
       // IsDataFlavorSupported doesn't take an index as an argument and just
       // checks if any of the items support a particular flavor, even though
       // the GetData method does take an index. Here, we just assume that
       // every item being dragged has the same set of flavors.
       bool supported;
       dragSession->IsDataFlavorSupported(formats[f], &supported);
       // if the format is supported, add an item to the array with null as
@@ -1228,27 +1394,33 @@ DataTransfer::CacheExternalClipboardForm
     return;
   }
 
   nsIScriptSecurityManager* ssm = nsContentUtils::GetSecurityManager();
   nsCOMPtr<nsIPrincipal> sysPrincipal;
   ssm->GetSystemPrincipal(getter_AddRefs(sysPrincipal));
 
   // there isn't a way to get a list of the formats that might be available on
-  // all platforms, so just check for the types that can actually be imported
-  const char* formats[] = { kFileMime, kHTMLMime, kRTFMime, kURLMime, kURLDataMime, kUnicodeMime };
+  // all platforms, so just check for the types that can actually be imported.
+  // Note that the loop below assumes that kCustomTypesMime will be first.
+  const char* formats[] = { kCustomTypesMime, kFileMime, kHTMLMime, kRTFMime,
+                            kURLMime, kURLDataMime, kUnicodeMime };
 
   for (uint32_t f = 0; f < mozilla::ArrayLength(formats); ++f) {
     // check each format one at a time
     bool supported;
     clipboard->HasDataMatchingFlavors(&(formats[f]), 1, mClipboardType, &supported);
     // if the format is supported, add an item to the array with null as
     // the data. When retrieved, GetRealData will read the data.
     if (supported) {
-      CacheExternalData(formats[f], 0, sysPrincipal);
+      if (f == 0) {
+        FillInExternalCustomTypes(0, sysPrincipal);
+      } else {
+        CacheExternalData(formats[f], 0, sysPrincipal);
+      }
     }
   }
 }
 
 void
 DataTransfer::FillInExternalData(TransferItem& aItem, uint32_t aIndex)
 {
   NS_PRECONDITION(mIsExternal, "Not an external data transfer");
@@ -1256,27 +1428,27 @@ DataTransfer::FillInExternalData(Transfe
   if (aItem.mData) {
     return;
   }
 
   // only drag and paste events should be calling FillInExternalData
   NS_ASSERTION(mEventMessage != eCut && mEventMessage != eCopy,
                "clipboard event with empty data");
 
-    NS_ConvertUTF16toUTF8 utf8format(aItem.mFormat);
-    const char* format = utf8format.get();
-    if (strcmp(format, "text/plain") == 0)
-      format = kUnicodeMime;
-    else if (strcmp(format, "text/uri-list") == 0)
-      format = kURLDataMime;
+  NS_ConvertUTF16toUTF8 utf8format(aItem.mFormat);
+  const char* format = utf8format.get();
+  if (strcmp(format, "text/plain") == 0)
+    format = kUnicodeMime;
+  else if (strcmp(format, "text/uri-list") == 0)
+    format = kURLDataMime;
 
-    nsCOMPtr<nsITransferable> trans =
-      do_CreateInstance("@mozilla.org/widget/transferable;1");
-    if (!trans)
-      return;
+  nsCOMPtr<nsITransferable> trans =
+    do_CreateInstance("@mozilla.org/widget/transferable;1");
+  if (!trans)
+    return;
 
   trans->Init(nullptr);
   trans->AddDataFlavor(format);
 
   if (mEventMessage == ePaste) {
     MOZ_ASSERT(aIndex == 0, "index in clipboard must be 0");
 
     nsCOMPtr<nsIClipboard> clipboard = do_GetService("@mozilla.org/widget/clipboard;1");
@@ -1296,53 +1468,115 @@ DataTransfer::FillInExternalData(Transfe
     nsCOMPtr<nsIDOMDocument> domDoc;
     dragSession->GetSourceDocument(getter_AddRefs(domDoc));
     MOZ_ASSERT(!domDoc);
 #endif
 
     dragSession->GetData(trans, aIndex);
   }
 
-    uint32_t length = 0;
-    nsCOMPtr<nsISupports> data;
-    trans->GetTransferData(format, getter_AddRefs(data), &length);
-    if (!data)
-      return;
+  uint32_t length = 0;
+  nsCOMPtr<nsISupports> data;
+  trans->GetTransferData(format, getter_AddRefs(data), &length);
+  if (!data)
+    return;
+
+  RefPtr<nsVariantCC> variant = new nsVariantCC();
 
-    RefPtr<nsVariantCC> variant = new nsVariantCC();
-
-    nsCOMPtr<nsISupportsString> supportsstr = do_QueryInterface(data);
-    if (supportsstr) {
-      nsAutoString str;
-      supportsstr->GetData(str);
-      variant->SetAsAString(str);
+  nsCOMPtr<nsISupportsString> supportsstr = do_QueryInterface(data);
+  if (supportsstr) {
+    nsAutoString str;
+    supportsstr->GetData(str);
+    variant->SetAsAString(str);
+  }
+  else {
+    nsCOMPtr<nsISupportsCString> supportscstr = do_QueryInterface(data);
+    if (supportscstr) {
+      nsAutoCString str;
+      supportscstr->GetData(str);
+      variant->SetAsACString(str);
+    } else {
+      variant->SetAsISupports(data);
     }
-    else {
-      nsCOMPtr<nsISupportsCString> supportscstr = do_QueryInterface(data);
-      if (supportscstr) {
-        nsAutoCString str;
-        supportscstr->GetData(str);
-        variant->SetAsACString(str);
-      } else {
-        variant->SetAsISupports(data);
-      }
-    }
+  }
 
-    aItem.mData = variant;
-  }
+  aItem.mData = variant;
+}
 
 void
 DataTransfer::FillAllExternalData()
 {
   if (mIsExternal) {
     for (uint32_t i = 0; i < mItems.Length(); ++i) {
       nsTArray<TransferItem>& itemArray = mItems[i];
       for (uint32_t j = 0; j < itemArray.Length(); ++j) {
         if (!itemArray[j].mData) {
           FillInExternalData(itemArray[j], i);
         }
       }
     }
   }
 }
 
+void
+DataTransfer::FillInExternalCustomTypes(uint32_t aIndex, nsIPrincipal* aPrincipal)
+{
+  TransferItem item;
+  item.mFormat.AssignLiteral(kCustomTypesMime);
+
+  FillInExternalData(item, aIndex);
+  if (!item.mData) {
+    return;
+  }
+
+  FillInExternalCustomTypes(item.mData, aIndex, aPrincipal);
+}
+
+void
+DataTransfer::FillInExternalCustomTypes(nsIVariant* aData, uint32_t aIndex, nsIPrincipal* aPrincipal)
+{
+  char* chrs;
+  uint32_t len = 0;
+  nsresult rv = aData->GetAsStringWithSize(&len, &chrs);
+  if (NS_FAILED(rv)) {
+    return;
+  }
+
+  nsAutoCString str;
+  str.Adopt(chrs, len);
+
+  nsCOMPtr<nsIInputStream> stringStream;
+  NS_NewCStringInputStream(getter_AddRefs(stringStream), str);
+
+  nsCOMPtr<nsIBinaryInputStream> stream = do_CreateInstance("@mozilla.org/binaryinputstream;1");
+  stream->SetInputStream(stringStream);
+  if (!stream) {
+    return;
+  }
+
+  uint32_t type;
+  do {
+    stream->Read32(&type);
+    if (type == eCustomClipboardTypeId_String) {
+      uint32_t formatLength;
+      stream->Read32(&formatLength);
+      char* formatBytes;
+      stream->ReadBytes(formatLength, &formatBytes);
+      nsAutoString format;
+      format.Adopt(reinterpret_cast<char16_t*>(formatBytes), formatLength / sizeof(char16_t));
+
+      uint32_t dataLength;
+      stream->Read32(&dataLength);
+      char* dataBytes;
+      stream->ReadBytes(dataLength, &dataBytes);
+      nsAutoString data;
+      data.Adopt(reinterpret_cast<char16_t*>(dataBytes), dataLength / sizeof(char16_t));
+
+      RefPtr<nsVariantCC> variant = new nsVariantCC();
+      variant->SetAsAString(data);
+
+      SetDataWithPrincipal(format, variant, aIndex, aPrincipal);
+    }
+  } while (type != eCustomClipboardTypeId_None);
+}
+
 } // namespace dom
 } // namespace mozilla
--- a/dom/events/DataTransfer.h
+++ b/dom/events/DataTransfer.h
@@ -213,16 +213,23 @@ public:
   // Similar to SetData except also specifies the principal to store.
   // aData may be null when called from CacheExternalDragFormats or
   // CacheExternalClipboardFormats.
   nsresult SetDataWithPrincipal(const nsAString& aFormat,
                                 nsIVariant* aData,
                                 uint32_t aIndex,
                                 nsIPrincipal* aPrincipal);
 
+  // Variation of SetDataWithPrincipal with handles extracting
+  // kCustomTypesMime data into separate types.
+  void SetDataWithPrincipalFromOtherProcess(const nsAString& aFormat,
+                                            nsIVariant* aData,
+                                            uint32_t aIndex,
+                                            nsIPrincipal* aPrincipal);
+
   // returns a weak reference to the drag image
   Element* GetDragImage(int32_t* aX, int32_t* aY)
   {
     *aX = mDragImageX;
     *aY = mDragImageY;
     return mDragImage;
   }
 
@@ -256,16 +263,19 @@ protected:
   nsresult GetDataAtInternal(const nsAString& aFormat, uint32_t aIndex,
                              nsIPrincipal* aSubjectPrincipal, nsIVariant** aData);
   nsresult SetDataAtInternal(const nsAString& aFormat, nsIVariant* aData, uint32_t aIndex,
                              nsIPrincipal* aSubjectPrincipal);
 
   friend class ContentParent;
   void FillAllExternalData();
 
+  void FillInExternalCustomTypes(uint32_t aIndex, nsIPrincipal* aPrincipal);
+  void FillInExternalCustomTypes(nsIVariant* aData, uint32_t aIndex, nsIPrincipal* aPrincipal);
+
   void MozClearDataAtHelper(const nsAString& aFormat, uint32_t aIndex,
                             mozilla::ErrorResult& aRv);
 
   nsCOMPtr<nsISupports> mParent;
 
 
   // the drop effect and effect allowed
   uint32_t mDropEffect;
--- a/dom/events/test/test_dragstart.html
+++ b/dom/events/test/test_dragstart.html
@@ -308,39 +308,39 @@ function test_DataTransfer(dt)
 
   dt.setData("text/html", "Changed with setData");
   is(dt.mozItemCount, 2, "changed with setData");
   checkOneDataItem(dt, ["text/plain", "text/html"],
                    ["First Item", "Changed with setData"], 0, "changed with setData item at index 0");
   checkOneDataItem(dt, ["text/plain", "text/html"],
                    ["Changed Second Item", "<em>Second Item</em>"], 1, "changed with setData item at index 1");
 
-  dt.mozSetDataAt("application/-moz-node", draggable, 2);
+  dt.mozSetDataAt("application/-moz-node", "draggable", 2);
   is(dt.mozItemCount, 3, "setDataAt node itemCount");
-  checkOneDataItem(dt, ["application/-moz-node"], [draggable], 2, "setDataAt node item at index 2");
+  checkOneDataItem(dt, ["application/-moz-node"], ["draggable"], 2, "setDataAt node item at index 2");
 
   dt.mozClearDataAt("text/html", 1);
   is(dt.mozItemCount, 3, "clearDataAt itemCount");
   checkOneDataItem(dt, ["text/plain", "text/html"],
                    ["First Item", "Changed with setData"], 0, "clearDataAt item at index 0");
   checkOneDataItem(dt, ["text/plain"], ["Changed Second Item"], 1, "clearDataAt item at index 1");
 
   dt.mozClearDataAt("text/plain", 1);
   is(dt.mozItemCount, 2, "clearDataAt last type itemCount");
   checkOneDataItem(dt, ["text/plain", "text/html"],
                    ["First Item", "Changed with setData"], 0, "clearDataAt last type at index 0");
-  checkOneDataItem(dt, ["application/-moz-node"], [draggable], 1, "clearDataAt last type item at index 2");
+  checkOneDataItem(dt, ["application/-moz-node"], ["draggable"], 1, "clearDataAt last type item at index 2");
   expectError(() => dt.mozGetDataAt("text/plain", 2),
               "IndexSizeError", "getDataAt after item removed index too high");
 
   dt.mozSetDataAt("text/unknown", "Unknown type", 2);
   dt.mozSetDataAt("text/unknown", "Unknown type", 1);
   is(dt.mozItemCount, 3, "add unknown type");
   checkOneDataItem(dt, ["application/-moz-node", "text/unknown"],
-                   [draggable, "Unknown type"], 1, "add unknown type item at index 1");
+                   ["draggable", "Unknown type"], 1, "add unknown type item at index 1");
   checkOneDataItem(dt, ["text/unknown"], ["Unknown type"], 2, "add unknown type item at index 2");
 
   dt.mozClearDataAt("", 1);
   is(dt.mozItemCount, 2, "clearDataAt empty string");
   checkOneDataItem(dt, ["text/plain", "text/html"],
                    ["First Item", "Changed with setData"], 0, "clearDataAt empty string item at index 0");
   checkOneDataItem(dt, ["text/unknown"],
                    ["Unknown type"], 1, "clearDataAt empty string item at index 1");
--- a/dom/events/test/test_legacy_event.html
+++ b/dom/events/test/test_legacy_event.html
@@ -5,18 +5,18 @@ https://bugzilla.mozilla.org/show_bug.cg
 -->
 <head>
   <meta charset="utf-8">
   <title>Test for Bug 1236979 (events that have legacy alternative versions)</title>
   <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
   <style>
     @keyframes anim1 {
-      0%   { margin-left: 200px }
-      100% { margin-left: 300px }
+      0%   { margin-left: 0px }
+      100% { margin-left: 100px }
     }
   </style>
 </head>
 <body>
 <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1236979">Mozilla Bug 1236979</a>
 <p id="display"></p>
 <div id="content" style="display: none">
 
@@ -27,16 +27,31 @@ https://bugzilla.mozilla.org/show_bug.cg
 /** Test for Bug 1236979 **/
 
 'use strict';
 SimpleTest.waitForExplicitFinish();
 
 // Array of info-bundles about each legacy event to be tested:
 var gLegacyEventInfo = [
   {
+    legacy_name: "webkitTransitionEnd",
+    modern_name: "transitionend",
+    trigger_event: triggerShortTransition,
+  },
+  {
+    legacy_name: "webkitAnimationStart",
+    modern_name: "animationstart",
+    trigger_event: triggerShortAnimation,
+  },
+  {
+    legacy_name: "webkitAnimationEnd",
+    modern_name: "animationend",
+    trigger_event: triggerShortAnimation,
+  },
+  {
     legacy_name: "webkitAnimationIteration",
     modern_name: "animationiteration",
     trigger_event: triggerAnimationIteration,
   }
 ];
 
 // EVENT-TRIGGERING FUNCTIONS
 // --------------------------
@@ -68,18 +83,17 @@ function triggerShortAnimation(node) {
 // any animationiteration events -- the CSS Animations spec says this event
 // must not be fired "...when an animationend event would fire at the same time"
 // (which would be the case in this example with a 1ms duration). So, to make
 // sure our event does fire, we use a long duration and a nearly-as-long
 // negative delay. This ensures we hit the end of the first iteration right
 // away, and that we don't risk hitting the end of the second iteration at the
 // same time.
 function triggerAnimationIteration(node) {
-  dump("************ Add Animation! **********");
-  node.style.animation = "anim1 1s -0.999s linear 2";
+  node.style.animation = "anim1 300s -299.999s linear 2";
 }
 
 // GENERAL UTILITY FUNCTIONS
 // -------------------------
 // Creates a new div and appends it as a child of the specified parentNode, or
 // (if no parent is specified) as a child of the element with ID 'display'.
 function createChildDiv(parentNode) {
   if (!parentNode) {
@@ -93,17 +107,16 @@ function createChildDiv(parentNode) {
   return div;
 }
 
 // Returns an event-handler function, which (when invoked) simply checks that
 // the event's type matches what's expected. If a callback is passed in, then
 // the event-handler will invoke that callback as well.
 function createHandlerWithTypeCheck(expectedEventType, extraHandlerLogic) {
   var handler = function(e) {
-    dump("********** event fired [" + e.type + "] ******\n");
     is(e.type, expectedEventType,
        "When an event handler for '" + expectedEventType + "' is invoked, " +
        "the event's type field should be '" + expectedEventType + "'.");
     if (extraHandlerLogic) {
       extraHandlerLogic(e);
     }
   }
   return handler;
@@ -119,29 +132,26 @@ function createHandlerWithTypeCheck(expe
 
 // Tests that the legacy event type is sent, when only a legacy handler is
 // registered.
 function mpTestLegacyEventSent(eventInfo) {
   return new Promise(
     function(resolve, reject) {
       // Create a node & register an event-handler for the legacy event:
       var div = createChildDiv();
-      div.innerHTML = "<h2>Clobber</h2>";
 
       var handler = createHandlerWithTypeCheck(eventInfo.legacy_name,
                                                function() {
         // When event-handler is done, clean up & resolve:
         div.parentNode.removeChild(div);
-        dump("************ Resolved **********\n");
         resolve();
       });
       div.addEventListener(eventInfo.legacy_name, handler);
 
       // Trigger the event:
-      dump("************ trigger[" + eventInfo.legacy_name + "] **********\n");
       eventInfo.trigger_event(div);
     }
   );
 }
 
 // Test that the modern event type (and only the modern event type) is fired,
 // when listeners of both modern & legacy types are registered. The legacy
 // listener should not be invoked.
@@ -159,17 +169,16 @@ function mpTestModernBeatsLegacy(eventIn
       var modernHandler = createHandlerWithTypeCheck(eventInfo.modern_name,
                                                      function() {
         // Indicate that the test has passed (we invoked the modern handler):
         ok(true, "Handler for modern event '" + eventInfo.modern_name +
            "' should be invoked when there's a handler registered for " +
            "both modern & legacy event type on the same node");
         // When event-handler is done, clean up & resolve:
         div.parentNode.removeChild(div);
-        dump("************ Resolved **********\n");
         resolve();
       });
 
       div.addEventListener(eventInfo.legacy_name, legacyHandler);
       div.addEventListener(eventInfo.modern_name, modernHandler);
       eventInfo.trigger_event(div);
     }
   );
@@ -207,17 +216,16 @@ function mpTestDiffListenersEventBubblin
         ok(didEventFireOnTarget,
            "Event should have fired on child");
         ok(didEventFireOnParent,
            "Event should have fired on parent");
         is(e, eventSentToTarget,
            "Same event object should bubble, despite difference in type");
         // Clean up.
         grandparent.parentNode.removeChild(grandparent);
-        dump("************ Resolved **********\n");
         resolve();
       }));
 
       eventInfo.trigger_event(target);
     }
   );
 }
 
@@ -257,41 +265,35 @@ function mpTestDiffListenersEventCapturi
           is(e.eventPhase, Event.AT_TARGET,
              "event should be at target phase");
           is(e, eventSentToGrandparent,
              "Same event object should capture, despite difference in type");
           ok(didEventFireOnParent,
              "Event should have fired on parent");
           // Clean up.
           grandparent.parentNode.removeChild(grandparent);
-          dump("************ Resolved **********\n");
           resolve();
       }), true);
 
       eventInfo.trigger_event(target);
     }
   );
 }
 
 // MAIN FUNCTION: Kick off the tests.
 function main() {
   Promise.resolve().then(function() {
-    dump("part1 clear\n");
     return Promise.all(gLegacyEventInfo.map(mpTestLegacyEventSent))
   }).then(function() {
-    dump("part2 clear\n");
     return Promise.all(gLegacyEventInfo.map(mpTestModernBeatsLegacy));
   }).then(function() {
-    dump("part3 clear");
     return Promise.all(gLegacyEventInfo.map(mpTestDiffListenersEventCapturing));
   }).then(function() {
-    dump("part4 clear");
     return Promise.all(gLegacyEventInfo.map(mpTestDiffListenersEventBubbling));
   }).then(function() {
-    dump("part5 clear");
     SimpleTest.finish();
   }).catch(function(reason) {
     ok(false, "Test failed: " + reason);
     SimpleTest.finish();
   });
 }
 
 main();
--- a/dom/html/HTMLAnchorElement.h
+++ b/dom/html/HTMLAnchorElement.h
@@ -122,17 +122,17 @@ public:
     SetHTMLAttr(nsGkAtoms::rel, aValue, rv);
   }
   void SetReferrerPolicy(const nsAString& aValue, mozilla::ErrorResult& rv)
   {
     SetHTMLAttr(nsGkAtoms::referrerpolicy, aValue, rv);
   }
   void GetReferrerPolicy(nsAString& aReferrer)
   {
-    GetHTMLAttr(nsGkAtoms::referrerpolicy, aReferrer);
+    GetEnumAttr(nsGkAtoms::referrerpolicy, EmptyCString().get(), aReferrer);
   }
   nsDOMTokenList* RelList();
   void GetHreflang(DOMString& aValue)
   {
     GetHTMLAttr(nsGkAtoms::hreflang, aValue);
   }
   void SetHreflang(const nsAString& aValue, mozilla::ErrorResult& rv)
   {
--- a/dom/html/HTMLAreaElement.h
+++ b/dom/html/HTMLAreaElement.h
@@ -126,17 +126,17 @@ public:
   nsDOMTokenList* RelList();
 
   void SetReferrerPolicy(const nsAString& aValue, mozilla::ErrorResult& rv)
   {
     SetHTMLAttr(nsGkAtoms::referrerpolicy, aValue, rv);
   }
   void GetReferrerPolicy(nsAString& aReferrer)
   {
-    GetHTMLAttr(nsGkAtoms::referrerpolicy, aReferrer);
+    GetEnumAttr(nsGkAtoms::referrerpolicy, EmptyCString().get(), aReferrer);
   }
 
   // The Link::GetOrigin is OK for us
 
   // Link::Link::GetProtocol is OK for us
   // Link::Link::SetProtocol is OK for us
 
   // The Link::GetUsername is OK for us
--- a/dom/html/HTMLIFrameElement.h
+++ b/dom/html/HTMLIFrameElement.h
@@ -160,19 +160,18 @@ public:
     SetHTMLAttr(nsGkAtoms::marginheight, aMarginHeight, aError);
   }
   void SetReferrerPolicy(const nsAString& aReferrer, ErrorResult& aError)
   {
     SetHTMLAttr(nsGkAtoms::referrerpolicy, aReferrer, aError);
   }
   void GetReferrerPolicy(nsAString& aReferrer)
   {
-    GetHTMLAttr(nsGkAtoms::referrerpolicy, aReferrer);
+    GetEnumAttr(nsGkAtoms::referrerpolicy, EmptyCString().get(), aReferrer);
   }
-
   nsIDocument* GetSVGDocument()
   {
     return GetContentDocument();
   }
   bool Mozbrowser() const
   {
     return GetBoolAttr(nsGkAtoms::mozbrowser);
   }
--- a/dom/html/HTMLImageElement.h
+++ b/dom/html/HTMLImageElement.h
@@ -192,17 +192,17 @@ public:
     SetHTMLAttr(nsGkAtoms::border, aBorder, aError);
   }
   void SetReferrerPolicy(const nsAString& aReferrer, ErrorResult& aError)
   {
     SetHTMLAttr(nsGkAtoms::referrerpolicy, aReferrer, aError);
   }
   void GetReferrerPolicy(nsAString& aReferrer)
   {
-    GetHTMLAttr(nsGkAtoms::referrerpolicy, aReferrer);
+    GetEnumAttr(nsGkAtoms::referrerpolicy, EmptyCString().get(), aReferrer);
   }
 
   net::ReferrerPolicy
   GetImageReferrerPolicy() override
   {
     return GetReferrerPolicyAsEnum();
   }
 
--- a/dom/html/HTMLMediaElement.cpp
+++ b/dom/html/HTMLMediaElement.cpp
@@ -559,16 +559,24 @@ HTMLMediaElement::GetMozMediaSourceObjec
 void
 HTMLMediaElement::GetMozDebugReaderData(nsAString& aString)
 {
   if (mDecoder && !mSrcStream) {
     mDecoder->GetMozDebugReaderData(aString);
   }
 }
 
+void
+HTMLMediaElement::MozDumpDebugInfo()
+{
+  if (mDecoder) {
+    mDecoder->DumpDebugInfo();
+  }
+}
+
 already_AddRefed<DOMMediaStream>
 HTMLMediaElement::GetSrcObject() const
 {
   NS_ASSERTION(!mSrcAttrStream || mSrcAttrStream->GetPlaybackStream(),
                "MediaStream should have been set up properly");
   RefPtr<DOMMediaStream> stream = mSrcAttrStream;
   return stream.forget();
 }
@@ -1060,20 +1068,18 @@ void HTMLMediaElement::LoadFromSourceChi
       continue;
     }
 
     // If we have a type attribute, it must be a supported type.
     nsAutoString type;
     if (child->GetAttr(kNameSpaceID_None, nsGkAtoms::type, type)) {
       DecoderDoctorDiagnostics diagnostics;
       CanPlayStatus canPlay = GetCanPlay(type, &diagnostics);
-      if (canPlay != CANPLAY_NO) {
-        diagnostics.SetCanPlay();
-      }
-      diagnostics.StoreDiagnostics(OwnerDoc(), type, __func__);
+      diagnostics.StoreFormatDiagnostics(
+        OwnerDoc(), type, canPlay != CANPLAY_NO, __func__);
       if (canPlay == CANPLAY_NO) {
         DispatchAsyncSourceError(child);
         const char16_t* params[] = { type.get(), src.get() };
         ReportLoadError("MediaLoadUnsupportedTypeAttribute", params, ArrayLength(params));
         continue;
       }
     }
     nsAutoString media;
@@ -2911,20 +2917,18 @@ HTMLMediaElement::GetCanPlay(const nsASt
                                            aDiagnostics);
 }
 
 NS_IMETHODIMP
 HTMLMediaElement::CanPlayType(const nsAString& aType, nsAString& aResult)
 {
   DecoderDoctorDiagnostics diagnostics;
   CanPlayStatus canPlay = GetCanPlay(aType, &diagnostics);
-  if (canPlay != CANPLAY_NO) {
-    diagnostics.SetCanPlay();
-  }
-  diagnostics.StoreDiagnostics(OwnerDoc(), aType, __func__);
+  diagnostics.StoreFormatDiagnostics(
+    OwnerDoc(), aType, canPlay != CANPLAY_NO, __func__);
   switch (canPlay) {
   case CANPLAY_NO:
     aResult.Truncate();
     break;
   case CANPLAY_YES:
     aResult.AssignLiteral("probably");
     break;
   default:
@@ -2978,22 +2982,20 @@ nsresult HTMLMediaElement::InitializeDec
   nsAutoCString mimeType;
 
   aChannel->GetContentType(mimeType);
   NS_ASSERTION(!mimeType.IsEmpty(), "We should have the Content-Type.");
 
   DecoderDoctorDiagnostics diagnostics;
   RefPtr<MediaDecoder> decoder =
     DecoderTraits::CreateDecoder(mimeType, this, &diagnostics);
-  if (decoder) {
-    diagnostics.SetCanPlay();
-  }
-  diagnostics.StoreDiagnostics(OwnerDoc(),
-                               NS_ConvertASCIItoUTF16(mimeType),
-                               __func__);
+  diagnostics.StoreFormatDiagnostics(OwnerDoc(),
+                                     NS_ConvertASCIItoUTF16(mimeType),
+                                     decoder != nullptr,
+                                     __func__);
   if (!decoder) {
     nsAutoString src;
     GetCurrentSrc(src);
     NS_ConvertUTF8toUTF16 mimeUTF16(mimeType);
     const char16_t* params[] = { mimeUTF16.get(), src.get() };
     ReportLoadError("MediaLoadUnsupportedMimeType", params, ArrayLength(params));
     return NS_ERROR_FAILURE;
   }
@@ -5142,16 +5144,19 @@ HTMLMediaElement::ContainsRestrictedCont
 {
   return GetMediaKeys() != nullptr;
 }
 
 already_AddRefed<Promise>
 HTMLMediaElement::SetMediaKeys(mozilla::dom::MediaKeys* aMediaKeys,
                                ErrorResult& aRv)
 {
+  LOG(LogLevel::Debug, ("%p SetMediaKeys(%p) mMediaKeys=%p mDecoder=%p",
+    this, aMediaKeys, mMediaKeys.get(), mDecoder.get()));
+
   if (MozAudioCaptured()) {
     aRv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
     return nullptr;
   }
 
   nsCOMPtr<nsIGlobalObject> global =
     do_QueryInterface(OwnerDoc()->GetInnerWindow());
   if (!global) {
--- a/dom/html/HTMLMediaElement.h
+++ b/dom/html/HTMLMediaElement.h
@@ -605,16 +605,18 @@ public:
     mIsCasting = aShow;
   }
 
   already_AddRefed<MediaSource> GetMozMediaSourceObject() const;
   // Returns a string describing the state of the media player internal
   // data. Used for debugging purposes.
   void GetMozDebugReaderData(nsAString& aString);
 
+  void MozDumpDebugInfo();
+
   already_AddRefed<DOMMediaStream> GetSrcObject() const;
   void SetSrcObject(DOMMediaStream& aValue);
   void SetSrcObject(DOMMediaStream* aValue);
 
   // TODO: remove prefixed versions soon (1183495).
   already_AddRefed<DOMMediaStream> GetMozSrcObject() const;
   void SetMozSrcObject(DOMMediaStream& aValue);
   void SetMozSrcObject(DOMMediaStream* aValue);
--- a/dom/html/test/mochitest.ini
+++ b/dom/html/test/mochitest.ini
@@ -602,9 +602,10 @@ skip-if = buildapp == 'b2g' # bug 112901
 [test_image_clone_load.html]
 [test_bug1203668.html]
 [test_bug1166138.html]
 [test_bug1230665.html]
 [test_filepicker_default_directory.html]
 skip-if = buildapp == 'mulet' || buildapp == 'b2g' || toolkit == 'android'
 [test_bug1233598.html]
 [test_bug1250401.html]
+[test_bug1260664.html]
 [test_allowMedia.html]
new file mode 100644
--- /dev/null
+++ b/dom/html/test/test_bug1260664.html
@@ -0,0 +1,55 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1260664
+-->
+<head>
+  <meta charset="utf-8">
+  <title>Test for Bug 1260664</title>
+  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <script type="application/javascript" src="reflect.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=1260664">Mozilla Bug 1260664</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+/** Test for Bug 1260664 **/
+SpecialPowers.setBoolPref("network.http.enablePerElementReferrer", true);
+
+SimpleTest.waitForExplicitFinish();
+SimpleTest.waitForFocus(runTests);
+
+function runTests() {
+  var elements = [ "iframe", "img", "a", "area" ];
+
+  for (var i = 0; i < elements.length; ++i) {
+    reflectLimitedEnumerated({
+      element: document.createElement(elements[i]),
+      attribute: { content: "referrerpolicy", idl: "referrerPolicy" },
+      validValues: [ "no-referrer",
+                     "origin",
+                     /** These 2 below values are still invalid, please see
+                       Bug 1178337 - Valid referrer attribute values **/
+                     /** "no-referrer-when-downgrade",
+                     "origin-when-cross-origin", **/
+                     "unsafe-url" ],
+      invalidValues: [
+        "", "  orIgin   ", "  unsafe-uRl  ", "  No-RefeRRer  ", " fOoBaR  "
+      ],
+      defaultValue: "",
+    });
+  }
+
+  SpecialPowers.clearUserPref("network.http.enablePerElementReferrer");
+  SimpleTest.finish();
+}
+
+</script>
+</pre>
+</body>
+</html>
--- a/dom/interfaces/push/nsIPushService.idl
+++ b/dom/interfaces/push/nsIPushService.idl
@@ -92,16 +92,26 @@ interface nsIPushService : nsISupports
    * will be fired, with the subject set to `null` and the data set to |scope|.
    * Servers may drop subscriptions at any time, so callers should recreate
    * subscriptions if desired.
    */
   void subscribe(in DOMString scope, in nsIPrincipal principal,
                  in nsIPushSubscriptionCallback callback);
 
   /**
+   * Creates a restricted push subscription with the given public |key|. The
+   * application server must use the corresponding private key to authenticate
+   * message delivery requests, as described in draft-thomson-webpush-vapid.
+   */
+  void subscribeWithKey(in DOMString scope, in nsIPrincipal principal,
+                        in uint32_t keyLength,
+                        [const, array, size_is(keyLength)] in uint8_t key,
+                        in nsIPushSubscriptionCallback callback);
+
+  /**
    * Removes a push subscription for the given |scope|.
    */
   void unsubscribe(in DOMString scope, in nsIPrincipal principal,
                    in nsIUnsubscribeResultCallback callback);
 
   /**
    * Retrieves the subscription record associated with the given
    * |(scope, principal)| pair. If the subscription does not exist, the
--- a/dom/ipc/ContentChild.cpp
+++ b/dom/ipc/ContentChild.cpp
@@ -3217,26 +3217,29 @@ ContentChild::RecvInvokeDragSession(nsTA
       for (uint32_t i = 0; i < aTransfers.Length(); ++i) {
         auto& items = aTransfers[i].items();
         for (uint32_t j = 0; j < items.Length(); ++j) {
           const IPCDataTransferItem& item = items[j];
           RefPtr<nsVariantCC> variant = new nsVariantCC();
           if (item.data().type() == IPCDataTransferData::TnsString) {
             const nsString& data = item.data().get_nsString();
             variant->SetAsAString(data);
+          } else if (item.data().type() == IPCDataTransferData::TnsCString) {
+            const nsCString& data = item.data().get_nsCString();
+            variant->SetAsACString(data);
           } else if (item.data().type() == IPCDataTransferData::TPBlobChild) {
             BlobChild* blob = static_cast<BlobChild*>(item.data().get_PBlobChild());
             RefPtr<BlobImpl> blobImpl = blob->GetBlobImpl();
             variant->SetAsISupports(blobImpl);
           } else {
             continue;
           }
-          dataTransfer->SetDataWithPrincipal(NS_ConvertUTF8toUTF16(item.flavor()),
-                                             variant, i,
-                                             nsContentUtils::GetSystemPrincipal());
+          dataTransfer->SetDataWithPrincipalFromOtherProcess(
+            NS_ConvertUTF8toUTF16(item.flavor()), variant, i,
+            nsContentUtils::GetSystemPrincipal());
         }
       }
       session->SetDataTransfer(dataTransfer);
     }
   }
   return true;
 }
 
--- a/dom/ipc/TabContext.cpp
+++ b/dom/ipc/TabContext.cpp
@@ -245,16 +245,21 @@ MaybeInvalidTabContext::MaybeInvalidTabC
 
   switch(aParams.type()) {
     case IPCTabContext::TPopupIPCTabContext: {
       const PopupIPCTabContext &ipcContext = aParams.get_PopupIPCTabContext();
 
       TabContext *context;
       if (ipcContext.opener().type() == PBrowserOrId::TPBrowserParent) {
         context = TabParent::GetFrom(ipcContext.opener().get_PBrowserParent());
+        if (!context) {
+          mInvalidReason = "Child is-browser process tried to "
+                           "open a null tab.";
+          return;
+        }
         if (context->IsMozBrowserElement() &&
             !ipcContext.isMozBrowserElement()) {
           // If the TabParent corresponds to a browser element, then it can only
           // open other browser elements, for security reasons.  We should have
           // checked this before calling the TabContext constructor, so this is
           // a fatal error.
           mInvalidReason = "Child is-browser process tried to "
                            "open a non-browser tab.";
--- a/dom/ipc/TabParent.cpp
+++ b/dom/ipc/TabParent.cpp
@@ -3137,34 +3137,37 @@ TabParent::AddInitialDnDDataTo(DataTrans
           new nsContentAreaDragDropDataProvider();
         variant->SetAsISupports(flavorDataProvider);
       } else if (item.data().type() == IPCDataTransferData::TnsString) {
         variant->SetAsAString(item.data().get_nsString());
       } else if (item.data().type() == IPCDataTransferData::TPBlobParent) {
         auto* parent = static_cast<BlobParent*>(item.data().get_PBlobParent());
         RefPtr<BlobImpl> impl = parent->GetBlobImpl();
         variant->SetAsISupports(impl);
-      } else if (item.data().type() == IPCDataTransferData::TnsCString &&
-                 nsContentUtils::IsFlavorImage(item.flavor())) {
-        // An image! Get the imgIContainer for it and set it in the variant.
-        nsCOMPtr<imgIContainer> imageContainer;
-        nsresult rv =
-          nsContentUtils::DataTransferItemToImage(item,
-                                                  getter_AddRefs(imageContainer));
-        if (NS_FAILED(rv)) {
-          continue;
+      } else if (item.data().type() == IPCDataTransferData::TnsCString) {
+        if (nsContentUtils::IsFlavorImage(item.flavor())) {
+          // An image! Get the imgIContainer for it and set it in the variant.
+          nsCOMPtr<imgIContainer> imageContainer;
+          nsresult rv =
+            nsContentUtils::DataTransferItemToImage(item,
+                                                    getter_AddRefs(imageContainer));
+          if (NS_FAILED(rv)) {
+            continue;
+          }
+          variant->SetAsISupports(imageContainer);
+        } else {
+          variant->SetAsACString(item.data().get_nsCString());
         }
-        variant->SetAsISupports(imageContainer);
       }
 
       // Using system principal here, since once the data is on parent process
       // side, it can be handled as being from browser chrome or OS.
-      aDataTransfer->SetDataWithPrincipal(NS_ConvertUTF8toUTF16(item.flavor()),
-                                          variant, i,
-                                          nsContentUtils::GetSystemPrincipal());
+      aDataTransfer->SetDataWithPrincipalFromOtherProcess(NS_ConvertUTF8toUTF16(item.flavor()),
+                                                          variant, i,
+                                                          nsContentUtils::GetSystemPrincipal());
     }
   }
   mInitialDataTransferItems.Clear();
 }
 
 void
 TabParent::TakeDragVisualization(RefPtr<mozilla::gfx::SourceSurface>& aSurface,
                                  int32_t& aDragAreaX, int32_t& aDragAreaY)
--- a/dom/locales/en-US/chrome/dom/dom.properties
+++ b/dom/locales/en-US/chrome/dom/dom.properties
@@ -102,21 +102,24 @@ MediaLoadInvalidURI=Invalid URI. Load of
 # LOCALIZATION NOTE: %1$S is the media resource's format/codec type (basically equivalent to the file type, e.g. MP4,AVI,WMV,MOV etc), %2$S is the URL of the media resource which failed to load.
 MediaLoadUnsupportedTypeAttribute=Specified "type" attribute of "%1$S" is not supported. Load of media resource %2$S failed.
 # LOCALIZATION NOTE: %1$S is the "media" attribute value of the <source> element. It is a media query. %2$S is the URL of the media resource which failed to load.
 MediaLoadSourceMediaNotMatched=Specified "media" attribute of "%1$S" does not match the environment. Load of media resource %2$S failed.
 # LOCALIZATION NOTE: %1$S is the MIME type HTTP header being sent by the web server, %2$S is the URL of the media resource which failed to load.
 MediaLoadUnsupportedMimeType=HTTP "Content-Type" of "%1$S" is not supported. Load of media resource %2$S failed.
 # LOCALIZATION NOTE: %S is the URL of the media resource which failed to load because of error in decoding.
 MediaLoadDecodeError=Media resource %S could not be decoded.
+MediaWidevineNoWMFNoSilverlight=Trying to play Widevine with no Windows Media Foundation (nor Silverlight fallback), see https://support.mozilla.org/kb/fix-video-audio-problems-firefox-windows
+# LOCALIZATION NOTE: %S is a comma-separated list of codecs (e.g. 'video/mp4, video/webm')
+MediaWMFNeeded=To play video formats %S, you need to install extra Microsoft software, see https://support.mozilla.org/kb/fix-video-audio-problems-firefox-windows
+# LOCALIZATION NOTE: %S is a comma-separated list of codecs (e.g. 'video/mp4, video/webm')
+MediaPlatformDecoderNotFound=The video on this page can't be played. Your system may not have the required video codecs for: %S
 # LOCALIZATION NOTE: %S is a comma-separated list of codecs (e.g. 'video/mp4, video/webm')
 MediaCannotPlayNoDecoders=Cannot play media. No decoders for requested formats: %S
 # LOCALIZATION NOTE: %S is a comma-separated list of codecs (e.g. 'video/mp4, video/webm')
-MediaPlatformDecoderNotFound=The video on this page can't be played. Your system may not have the required video codecs for: %S
-# LOCALIZATION NOTE: %S is a comma-separated list of codecs (e.g. 'video/mp4, video/webm')
 MediaNoDecoders=No decoders for some of the requested formats: %S
 # LOCALIZATION NOTE: Do not translate "MediaRecorder".
 MediaRecorderMultiTracksNotSupported=MediaRecorder does not support recording multiple tracks of the same type at this time.
 # LOCALIZATION NOTE: %S is the ID of the MediaStreamTrack passed to MediaStream.addTrack(). Do not translate "MediaStreamTrack" and "AudioChannel".
 MediaStreamAddTrackDifferentAudioChannel=MediaStreamTrack %S could not be added since it belongs to a different AudioChannel.
 # LOCALIZATION NOTE: Do not translate "MediaStream", "stop()" and "MediaStreamTrack"
 MediaStreamStopDeprecatedWarning=MediaStream.stop() is deprecated and will soon be removed. Use MediaStreamTrack.stop() instead.
 # LOCALIZATION NOTE: Do not translate "DOMException", "code" and "name"
--- a/dom/media/DecoderDoctorDiagnostics.cpp
+++ b/dom/media/DecoderDoctorDiagnostics.cpp
@@ -3,21 +3,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/. */
 
 #include "DecoderDoctorDiagnostics.h"
 
 #include "mozilla/dom/DecoderDoctorNotificationBinding.h"
 #include "mozilla/Logging.h"
+#include "mozilla/Preferences.h"
 #include "nsGkAtoms.h"
 #include "nsIDocument.h"
 #include "nsIObserverService.h"
 #include "nsITimer.h"
 #include "nsIWeakReference.h"
+#include "nsPluginHost.h"
 
 static mozilla::LazyLogModule sDecoderDoctorLog("DecoderDoctor");
 #define DD_LOG(level, arg, ...) MOZ_LOG(sDecoderDoctorLog, level, (arg, ##__VA_ARGS__))
 #define DD_DEBUG(arg, ...) DD_LOG(mozilla::LogLevel::Debug, arg, ##__VA_ARGS__)
 #define DD_INFO(arg, ...) DD_LOG(mozilla::LogLevel::Info, arg, ##__VA_ARGS__)
 #define DD_WARN(arg, ...) DD_LOG(mozilla::LogLevel::Warning, arg, ##__VA_ARGS__)
 
 namespace mozilla
@@ -30,25 +32,24 @@ namespace mozilla
 // inter-task captures.
 // When notified that the document is dead, or when the timer expires but
 // nothing new happened, StopWatching() will remove the document property and
 // timer (if present), so no more work will happen and the watcher will be
 // destroyed once all references are gone.
 class DecoderDoctorDocumentWatcher : public nsITimerCallback
 {
 public:
-  static RefPtr<DecoderDoctorDocumentWatcher>
+  static already_AddRefed<DecoderDoctorDocumentWatcher>
   RetrieveOrCreate(nsIDocument* aDocument);
 
   NS_DECL_ISUPPORTS
   NS_DECL_NSITIMERCALLBACK
 
-  void AddDiagnostics(const nsAString& aFormat,
-                      const char* aCallSite,
-                      DecoderDoctorDiagnostics&& aDiagnostics);
+  void AddDiagnostics(DecoderDoctorDiagnostics&& aDiagnostics,
+                      const char* aCallSite);
 
 private:
   explicit DecoderDoctorDocumentWatcher(nsIDocument* aDocument);
   virtual ~DecoderDoctorDocumentWatcher();
 
   // This will prevent further work from happening, watcher will deregister
   // itself from document (if requested) and cancel any timer, and soon die.
   void StopWatching(bool aRemoveProperty);
@@ -80,45 +81,41 @@ private:
   //    period, so we just stop watching.
   // Once nulled, no more actual work will happen, and the watcher will be
   // destroyed soon.
   nsIDocument* mDocument;
 
   struct Diagnostics
   {
     Diagnostics(DecoderDoctorDiagnostics&& aDiagnostics,
-                const nsAString& aFormat,
                 const char* aCallSite)
       : mDecoderDoctorDiagnostics(Move(aDiagnostics))
-      , mFormat(aFormat)
       , mCallSite(aCallSite)
     {}
     Diagnostics(const Diagnostics&) = delete;
     Diagnostics(Diagnostics&& aOther)
       : mDecoderDoctorDiagnostics(Move(aOther.mDecoderDoctorDiagnostics))
-      , mFormat(Move(aOther.mFormat))
       , mCallSite(Move(aOther.mCallSite))
     {}
 
     const DecoderDoctorDiagnostics mDecoderDoctorDiagnostics;
-    const nsString mFormat;
     const nsCString mCallSite;
   };
   typedef nsTArray<Diagnostics> DiagnosticsSequence;
   DiagnosticsSequence mDiagnosticsSequence;
 
   nsCOMPtr<nsITimer> mTimer; // Keep timer alive until we run.
   DiagnosticsSequence::size_type mDiagnosticsHandled = 0;
 };
 
 
 NS_IMPL_ISUPPORTS(DecoderDoctorDocumentWatcher, nsITimerCallback)
 
 // static
-RefPtr<DecoderDoctorDocumentWatcher>
+already_AddRefed<DecoderDoctorDocumentWatcher>
 DecoderDoctorDocumentWatcher::RetrieveOrCreate(nsIDocument* aDocument)
 {
   MOZ_ASSERT(NS_IsMainThread());
   MOZ_ASSERT(aDocument);
   RefPtr<DecoderDoctorDocumentWatcher> watcher =
     static_cast<DecoderDoctorDocumentWatcher*>(
       aDocument->GetProperty(nsGkAtoms::decoderDoctor));
   if (!watcher) {
@@ -131,17 +128,17 @@ DecoderDoctorDocumentWatcher::RetrieveOr
       DD_WARN("DecoderDoctorDocumentWatcher::RetrieveOrCreate(doc=%p) - Could not set property in document, will destroy new watcher[%p]",
               aDocument, watcher.get());
       return nullptr;
     }
     // Document owns watcher through this property.
     // Released in DestroyPropertyCallback().
     NS_ADDREF(watcher.get());
   }
-  return watcher;
+  return watcher.forget();
 }
 
 DecoderDoctorDocumentWatcher::DecoderDoctorDocumentWatcher(nsIDocument* aDocument)
   : mDocument(aDocument)
 {
   MOZ_ASSERT(NS_IsMainThread());
   MOZ_ASSERT(mDocument);
   DD_DEBUG("DecoderDoctorDocumentWatcher[%p]::DecoderDoctorDocumentWatcher(doc=%p)",
@@ -265,114 +262,237 @@ DispatchNotification(nsISupports* aSubje
     obs->NotifyObservers(aSubject, "decoder-doctor-notification", json.get());
   }
 }
 
 void
 DecoderDoctorDocumentWatcher::ReportAnalysis(
   dom::DecoderDoctorNotificationType aNotificationType,
   const char* aReportStringId,
-  const nsAString& aFormats)
+  const nsAString& aParams)
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   if (!mDocument) {
     return;
   }
 
-  const char16_t* params[] = { aFormats.Data() };
+  // 'params' will only be forwarded for non-empty strings.
+  const char16_t* params[1] = { aParams.Data() };
   DD_DEBUG("DecoderDoctorDocumentWatcher[%p, doc=%p]::ReportAnalysis() ReportToConsole - aMsg='%s' params[0]='%s'",
            this, mDocument, aReportStringId,
-           NS_ConvertUTF16toUTF8(params[0]).get());
+           aParams.IsEmpty() ? "<no params>" : NS_ConvertUTF16toUTF8(params[0]).get());
   nsContentUtils::ReportToConsole(nsIScriptError::warningFlag,
                                   NS_LITERAL_CSTRING("Media"),
                                   mDocument,
                                   nsContentUtils::eDOM_PROPERTIES,
                                   aReportStringId,
-                                  params,
-                                  ArrayLength(params));
+                                  aParams.IsEmpty() ? nullptr : params,
+                                  aParams.IsEmpty() ? 0 : 1);
+
+  // "media.decoder-doctor.notifications-allowed" controls which notifications
+  // may be dispatched to the front-end. It either contains:
+  // - '*' -> Allow everything.
+  // - Comma-separater list of ids -> Allow if aReportStringId (from
+  //                                  dom.properties) is one of them.
+  // - Nothing (missing or empty) -> Disable everything.
+  nsAdoptingCString filter =
+    Preferences::GetCString("media.decoder-doctor.notifications-allowed");
+  filter.StripWhitespace();
+  bool allowed = false;
+  if (!filter || filter.IsEmpty()) {
+    // Allow nothing.
+  } else if (filter.EqualsLiteral("*")) {
+    allowed = true;
+  } else for (uint32_t start = 0; start < filter.Length(); ) {
+    int32_t comma = filter.FindChar(',', start);
+    uint32_t end = (comma >= 0) ? uint32_t(comma) : filter.Length();
+    if (strncmp(aReportStringId, filter.Data() + start, end - start) == 0) {
+      allowed = true;
+      break;
+    }
+    // Skip comma. End of line will be caught in for 'while' clause.
+    start = end + 1;
+  }
+  if (allowed) {
+    DispatchNotification(
+      mDocument->GetInnerWindow(), aNotificationType, aParams);
+  }
+}
 
-  // For now, disable all front-end notifications by default.
-  // TODO: Future bugs will use finer-grained filtering instead.
-  if (Preferences::GetBool("media.decoderdoctor.enable-notification-bar", false)) {
-    DispatchNotification(
-      mDocument->GetInnerWindow(), aNotificationType, aFormats);
+enum SilverlightPresence {
+  eNoSilverlight,
+  eSilverlightDisabled,
+  eSilverlightEnabled
+};
+static SilverlightPresence
+CheckSilverlight()
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  RefPtr<nsPluginHost> pluginHost = nsPluginHost::GetInst();
+  if (!pluginHost) {
+    return eNoSilverlight;
   }
+  nsTArray<nsCOMPtr<nsIInternalPluginTag>> plugins;
+  pluginHost->GetPlugins(plugins, /*aIncludeDisabled*/ true);
+  for (const auto& plugin : plugins) {
+    for (const auto& mime : plugin->MimeTypes()) {
+      if (mime.LowerCaseEqualsLiteral("application/x-silverlight")
+          || mime.LowerCaseEqualsLiteral("application/x-silverlight-2")) {
+        return plugin->IsEnabled() ? eSilverlightEnabled : eSilverlightDisabled;
+      }
+    }
+  }
+
+  return eNoSilverlight;
+}
+
+static void AppendToStringList(nsAString& list, const nsAString& item)
+{
+  if (!list.IsEmpty()) {
+    list += NS_LITERAL_STRING(", ");
+  }
+  list += item;
 }
 
 void
 DecoderDoctorDocumentWatcher::SynthesizeAnalysis()
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   bool canPlay = false;
+#if defined(XP_WIN)
+  bool WMFNeeded = false;
+#endif
 #if defined(MOZ_FFMPEG)
-  bool PlatformDecoderNeeded = false;
+  bool FFMpegNeeded = false;
 #endif
-  nsAutoString formats;
+  nsAutoString playableFormats;
+  nsAutoString unplayableFormats;
+  nsAutoString supportedKeySystems;
+  nsAutoString unsupportedKeySystems;
+  DecoderDoctorDiagnostics::KeySystemIssue lastKeySystemIssue =
+    DecoderDoctorDiagnostics::eUnset;
+
   for (auto& diag : mDiagnosticsSequence) {
-    if (diag.mDecoderDoctorDiagnostics.CanPlay()) {
-      canPlay = true;
-    } else {
+    switch (diag.mDecoderDoctorDiagnostics.Type()) {
+    case DecoderDoctorDiagnostics::eFormatSupportCheck:
+      if (diag.mDecoderDoctorDiagnostics.CanPlay()) {
+        canPlay = true;
+        AppendToStringList(playableFormats,
+                           diag.mDecoderDoctorDiagnostics.Format());
+      } else {
+#if defined(XP_WIN)
+        if (diag.mDecoderDoctorDiagnostics.DidWMFFailToLoad()) {
+          WMFNeeded = true;
+        }
+#endif
 #if defined(MOZ_FFMPEG)
-      if (diag.mDecoderDoctorDiagnostics.DidFFmpegFailToLoad()) {
-        PlatformDecoderNeeded = true;
-      }
+        if (diag.mDecoderDoctorDiagnostics.DidFFmpegFailToLoad()) {
+          FFMpegNeeded = true;
+        }
 #endif
-      if (!formats.IsEmpty()) {
-        formats += NS_LITERAL_STRING(", ");
+        AppendToStringList(unplayableFormats,
+                           diag.mDecoderDoctorDiagnostics.Format());
       }
-      formats += diag.mFormat;
+      break;
+    case DecoderDoctorDiagnostics::eMediaKeySystemAccessRequest:
+      if (diag.mDecoderDoctorDiagnostics.IsKeySystemSupported()) {
+        AppendToStringList(supportedKeySystems,
+                           diag.mDecoderDoctorDiagnostics.KeySystem());
+      } else {
+        AppendToStringList(unsupportedKeySystems,
+                           diag.mDecoderDoctorDiagnostics.KeySystem());
+        DecoderDoctorDiagnostics::KeySystemIssue issue =
+          diag.mDecoderDoctorDiagnostics.GetKeySystemIssue();
+        if (issue != DecoderDoctorDiagnostics::eUnset) {
+          lastKeySystemIssue = issue;
+        }
+      }
+      break;
+    default:
+      MOZ_ASSERT(diag.mDecoderDoctorDiagnostics.Type()
+                   == DecoderDoctorDiagnostics::eFormatSupportCheck
+                 || diag.mDecoderDoctorDiagnostics.Type()
+                      == DecoderDoctorDiagnostics::eMediaKeySystemAccessRequest);
+      break;
     }
   }
-  if (!canPlay) {
-#if defined(MOZ_FFMPEG)
-    if (PlatformDecoderNeeded) {
-      DD_DEBUG("DecoderDoctorDocumentWatcher[%p, doc=%p]::Notify() - formats: %s -> Cannot play media because platform decoder was not found",
-               this, mDocument, NS_ConvertUTF16toUTF8(formats).get());
+
+  // Look at Key System issues first, as they may influence format checks.
+  if (!unsupportedKeySystems.IsEmpty() && supportedKeySystems.IsEmpty()) {
+    // No supported key systems!
+    switch (lastKeySystemIssue) {
+    case DecoderDoctorDiagnostics::eWidevineWithNoWMF:
+      if (CheckSilverlight() != eSilverlightEnabled) {
+        DD_DEBUG("DecoderDoctorDocumentWatcher[%p, doc=%p]::SynthesizeAnalysis() - unsupported key systems: %s, widevine without WMF nor Silverlight",
+                 this, mDocument, NS_ConvertUTF16toUTF8(unplayableFormats).get());
+        ReportAnalysis(dom::DecoderDoctorNotificationType::Platform_decoder_not_found,
+                       "MediaWidevineNoWMFNoSilverlight", NS_LITERAL_STRING(""));
+        return;
+      }
+      break;
+    default:
+      break;
+    }
+  }
+
+  if (!canPlay && !unplayableFormats.IsEmpty()) {
+#if defined(XP_WIN)
+    if (WMFNeeded) {
+      DD_DEBUG("DecoderDoctorDocumentWatcher[%p, doc=%p]::SynthesizeAnalysis() - formats: %s -> Cannot play media because WMF was not found",
+               this, mDocument, NS_ConvertUTF16toUTF8(unplayableFormats).get());
       ReportAnalysis(dom::DecoderDoctorNotificationType::Platform_decoder_not_found,
-                     "MediaPlatformDecoderNotFound", formats);
-    } else
+                     "MediaWMFNeeded", unplayableFormats);
+      return;
+    }
 #endif
-    {
-      DD_WARN("DecoderDoctorDocumentWatcher[%p, doc=%p]::Notify() - Cannot play media, formats: %s",
-              this, mDocument, NS_ConvertUTF16toUTF8(formats).get());
-      ReportAnalysis(dom::DecoderDoctorNotificationType::Cannot_play,
-                     "MediaCannotPlayNoDecoders", formats);
+#if defined(MOZ_FFMPEG)
+    if (FFMpegNeeded) {
+      DD_DEBUG("DecoderDoctorDocumentWatcher[%p, doc=%p]::SynthesizeAnalysis() - unplayable formats: %s -> Cannot play media because platform decoder was not found",
+               this, mDocument, NS_ConvertUTF16toUTF8(unplayableFormats).get());
+      ReportAnalysis(dom::DecoderDoctorNotificationType::Platform_decoder_not_found,
+                     "MediaPlatformDecoderNotFound", unplayableFormats);
+      return;
     }
-  } else if (!formats.IsEmpty()) {
-    DD_INFO("DecoderDoctorDocumentWatcher[%p, doc=%p]::Notify() - Can play media, but no decoders for some requested formats: %s",
-            this, mDocument, NS_ConvertUTF16toUTF8(formats).get());
-    if (Preferences::GetBool("media.decoderdoctor.verbose", false)) {
+#endif
+    DD_WARN("DecoderDoctorDocumentWatcher[%p, doc=%p]::SynthesizeAnalysis() - Cannot play media, unplayable formats: %s",
+            this, mDocument, NS_ConvertUTF16toUTF8(unplayableFormats).get());
+    ReportAnalysis(dom::DecoderDoctorNotificationType::Cannot_play,
+                   "MediaCannotPlayNoDecoders", unplayableFormats);
+    return;
+  }
+  if (!unplayableFormats.IsEmpty()) {
+    DD_INFO("DecoderDoctorDocumentWatcher[%p, doc=%p]::SynthesizeAnalysis() - Can play media, but no decoders for some requested formats: %s",
+            this, mDocument, NS_ConvertUTF16toUTF8(unplayableFormats).get());
+    if (Preferences::GetBool("media.decoder-doctor.verbose", false)) {
       ReportAnalysis(
         dom::DecoderDoctorNotificationType::Can_play_but_some_missing_decoders,
-        "MediaNoDecoders", formats);
+        "MediaNoDecoders", unplayableFormats);
     }
-  } else {
-    DD_DEBUG("DecoderDoctorDocumentWatcher[%p, doc=%p]::Notify() - Can play media, decoders available for all requested formats",
-             this, mDocument);
+    return;
   }
+  DD_DEBUG("DecoderDoctorDocumentWatcher[%p, doc=%p]::SynthesizeAnalysis() - Can play media, decoders available for all requested formats",
+           this, mDocument);
 }
 
 void
-DecoderDoctorDocumentWatcher::AddDiagnostics(const nsAString& aFormat,
-                                            const char* aCallSite,
-                                            DecoderDoctorDiagnostics&& aDiagnostics)
+DecoderDoctorDocumentWatcher::AddDiagnostics(DecoderDoctorDiagnostics&& aDiagnostics,
+                                             const char* aCallSite)
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   if (!mDocument) {
     return;
   }
 
-  DD_DEBUG("DecoderDoctorDocumentWatcher[%p, doc=%p]::AddDiagnostics(format='%s', call site '%s', can play=%d, platform lib failed to load=%d)",
-           this, mDocument, NS_ConvertUTF16toUTF8(aFormat).get(),
-           aCallSite, aDiagnostics.CanPlay(), aDiagnostics.DidFFmpegFailToLoad());
+  DD_DEBUG("DecoderDoctorDocumentWatcher[%p, doc=%p]::AddDiagnostics(DecoderDoctorDiagnostics{%s}, call site '%s')",
+           this, mDocument, aDiagnostics.GetDescription().Data(), aCallSite);
   mDiagnosticsSequence.AppendElement(
-    Diagnostics(Move(aDiagnostics), aFormat, aCallSite));
+    Diagnostics(Move(aDiagnostics), aCallSite));
   EnsureTimerIsStarted();
 }
 
 NS_IMETHODIMP
 DecoderDoctorDocumentWatcher::Notify(nsITimer* timer)
 {
   MOZ_ASSERT(NS_IsMainThread());
   MOZ_ASSERT(timer == mTimer);
@@ -402,34 +522,139 @@ DecoderDoctorDocumentWatcher::Notify(nsI
     StopWatching(true);
   }
 
   return NS_OK;
 }
 
 
 void
-DecoderDoctorDiagnostics::StoreDiagnostics(nsIDocument* aDocument,
-                                           const nsAString& aFormat,
-                                           const char* aCallSite)
+DecoderDoctorDiagnostics::StoreFormatDiagnostics(nsIDocument* aDocument,
+                                                 const nsAString& aFormat,
+                                                 bool aCanPlay,
+                                                 const char* aCallSite)
 {
   MOZ_ASSERT(NS_IsMainThread());
+  // Make sure we only store once.
+  MOZ_ASSERT(mDiagnosticsType == eUnsaved);
+  mDiagnosticsType = eFormatSupportCheck;
+
   if (NS_WARN_IF(!aDocument)) {
-    DD_WARN("DecoderDoctorDiagnostics[%p]::StoreDiagnostics(nsIDocument* aDocument=nullptr, format='%s', call site '%s')",
-            this, NS_ConvertUTF16toUTF8(aFormat).get(), aCallSite);
+    DD_WARN("DecoderDoctorDiagnostics[%p]::StoreFormatDiagnostics(nsIDocument* aDocument=nullptr, format='%s', can-play=%d, call site '%s')",
+            this, NS_ConvertUTF16toUTF8(aFormat).get(), aCanPlay, aCallSite);
+    return;
+  }
+  if (NS_WARN_IF(aFormat.IsEmpty())) {
+    DD_WARN("DecoderDoctorDiagnostics[%p]::StoreFormatDiagnostics(nsIDocument* aDocument=%p, format=<empty>, can-play=%d, call site '%s')",
+            this, aDocument, aCanPlay, aCallSite);
     return;
   }
 
   RefPtr<DecoderDoctorDocumentWatcher> watcher =
     DecoderDoctorDocumentWatcher::RetrieveOrCreate(aDocument);
 
   if (NS_WARN_IF(!watcher)) {
-    DD_WARN("DecoderDoctorDiagnostics[%p]::StoreDiagnostics(nsIDocument* aDocument=nullptr, format='%s', call site '%s') - Could not create document watcher",
-            this, NS_ConvertUTF16toUTF8(aFormat).get(), aCallSite);
+    DD_WARN("DecoderDoctorDiagnostics[%p]::StoreFormatDiagnostics(nsIDocument* aDocument=%p, format='%s', can-play=%d, call site '%s') - Could not create document watcher",
+            this, aDocument, NS_ConvertUTF16toUTF8(aFormat).get(), aCanPlay, aCallSite);
+    return;
+  }
+
+  mFormat = aFormat;
+  mCanPlay = aCanPlay;
+
+  // StoreDiagnostics should only be called once, after all data is available,
+  // so it is safe to Move() from this object.
+  watcher->AddDiagnostics(Move(*this), aCallSite);
+  // Even though it's moved-from, the type should stay set
+  // (Only used to ensure that we do store only once.)
+  MOZ_ASSERT(mDiagnosticsType == eFormatSupportCheck);
+}
+
+void
+DecoderDoctorDiagnostics::StoreMediaKeySystemAccess(nsIDocument* aDocument,
+                                                    const nsAString& aKeySystem,
+                                                    bool aIsSupported,
+                                                    const char* aCallSite)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  // Make sure we only store once.
+  MOZ_ASSERT(mDiagnosticsType == eUnsaved);
+  mDiagnosticsType = eMediaKeySystemAccessRequest;
+
+  if (NS_WARN_IF(!aDocument)) {
+    DD_WARN("DecoderDoctorDiagnostics[%p]::StoreMediaKeySystemAccess(nsIDocument* aDocument=nullptr, keysystem='%s', supported=%d, call site '%s')",
+            this, NS_ConvertUTF16toUTF8(aKeySystem).get(), aIsSupported, aCallSite);
+    return;
+  }
+  if (NS_WARN_IF(aKeySystem.IsEmpty())) {
+    DD_WARN("DecoderDoctorDiagnostics[%p]::StoreMediaKeySystemAccess(nsIDocument* aDocument=%p, keysystem=<empty>, supported=%d, call site '%s')",
+            this, aDocument, aIsSupported, aCallSite);
+    return;
+  }
+
+  RefPtr<DecoderDoctorDocumentWatcher> watcher =
+    DecoderDoctorDocumentWatcher::RetrieveOrCreate(aDocument);
+
+  if (NS_WARN_IF(!watcher)) {
+    DD_WARN("DecoderDoctorDiagnostics[%p]::StoreMediaKeySystemAccess(nsIDocument* aDocument=%p, keysystem='%s', supported=%d, call site '%s') - Could not create document watcher",
+            this, NS_ConvertUTF16toUTF8(aKeySystem).get(), aIsSupported, aCallSite);
     return;
   }
 
+  mKeySystem = aKeySystem;
+  mIsKeySystemSupported = aIsSupported;
+
   // StoreDiagnostics should only be called once, after all data is available,
   // so it is safe to Move() from this object.
-  watcher->AddDiagnostics(aFormat, aCallSite, Move(*this));
+  watcher->AddDiagnostics(Move(*this), aCallSite);
+  // Even though it's moved-from, the type should stay set
+  // (Only used to ensure that we do store only once.)
+  MOZ_ASSERT(mDiagnosticsType == eMediaKeySystemAccessRequest);
+}
+
+nsCString
+DecoderDoctorDiagnostics::GetDescription() const
+{
+  MOZ_ASSERT(mDiagnosticsType == eFormatSupportCheck
+             || mDiagnosticsType == eMediaKeySystemAccessRequest);
+  nsCString s;
+  switch (mDiagnosticsType) {
+  case eUnsaved:
+    s = "Unsaved diagnostics, cannot get accurate description";
+    break;
+  case eFormatSupportCheck:
+    s = "format='";
+    s += NS_ConvertUTF16toUTF8(mFormat).get();
+    s += mCanPlay ? "', can play" : "', cannot play";
+    if (mWMFFailedToLoad) {
+      s += ", Windows platform decoder failed to load";
+    }
+    if (mFFmpegFailedToLoad) {
+      s += ", Linux platform decoder failed to load";
+    }
+    if (mGMPPDMFailedToStartup) {
+      s += ", GMP PDM failed to startup";
+    } else if (!mGMP.IsEmpty()) {
+      s += ", Using GMP '";
+      s += mGMP;
+      s += "'";
+    }
+    break;
+  case eMediaKeySystemAccessRequest:
+    s = "key system='";
+    s += NS_ConvertUTF16toUTF8(mKeySystem).get();
+    s += mIsKeySystemSupported ? "', supported" : "', not supported";
+    switch (mKeySystemIssue) {
+    case eUnset:
+      break;
+    case eWidevineWithNoWMF:
+      s += ", Widevine with no WMF";
+      break;
+    }
+    break;
+    default:
+      s = "?";
+      break;
+  }
+  return s;
 }
 
 } // namespace mozilla
--- a/dom/media/DecoderDoctorDiagnostics.h
+++ b/dom/media/DecoderDoctorDiagnostics.h
@@ -2,18 +2,19 @@
 /* vim:set ts=2 sw=2 sts=2 et cindent: */
 /* 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 DecoderDoctorDiagnostics_h_
 #define DecoderDoctorDiagnostics_h_
 
+#include "nsString.h"
+
 class nsIDocument;
-class nsAString;
 
 namespace mozilla {
 
 // DecoderDoctorDiagnostics class, used to gather data from PDMs/DecoderTraits,
 // and then notify the user about issues preventing (or worsening) playback.
 //
 // The expected usage is:
 // 1. Instantiate a DecoderDoctorDiagnostics in a function (close to the point
@@ -30,30 +31,82 @@ namespace mozilla {
 
 class DecoderDoctorDiagnostics
 {
 public:
   // Store the diagnostic information collected so far on a document for a
   // given format. All diagnostics for a document will be analyzed together
   // within a short timeframe.
   // Should only be called once.
-  void StoreDiagnostics(nsIDocument* aDocument,
-                        const nsAString& aFormat,
-                        const char* aCallSite);
+  void StoreFormatDiagnostics(nsIDocument* aDocument,
+                              const nsAString& aFormat,
+                              bool aCanPlay,
+                              const char* aCallSite);
+
+  void StoreMediaKeySystemAccess(nsIDocument* aDocument,
+                                 const nsAString& aKeySystem,
+                                 bool aIsSupported,
+                                 const char* aCallSite);
+
+  enum DiagnosticsType {
+    eUnsaved,
+    eFormatSupportCheck,
+    eMediaKeySystemAccessRequest
+  };
+  DiagnosticsType Type() const { return mDiagnosticsType; }
+
+  // Description string, for logging purposes; only call on stored diags.
+  nsCString GetDescription() const;
 
   // Methods to record diagnostic information:
 
-  void SetCanPlay() { mCanPlay = true; }
+  const nsAString& Format() const { return mFormat; }
   bool CanPlay() const { return mCanPlay; }
 
+  void SetWMFFailedToLoad() { mWMFFailedToLoad = true; }
+  bool DidWMFFailToLoad() const { return mWMFFailedToLoad; }
+
   void SetFFmpegFailedToLoad() { mFFmpegFailedToLoad = true; }
   bool DidFFmpegFailToLoad() const { return mFFmpegFailedToLoad; }
 
+  void SetGMPPDMFailedToStartup() { mGMPPDMFailedToStartup = true; }
+  bool DidGMPPDMFailToStartup() const { return mGMPPDMFailedToStartup; }
+  void SetGMP(const nsACString& aGMP) { mGMP = aGMP; }
+  const nsACString& GMP() const { return mGMP; }
+
+  const nsAString& KeySystem() const { return mKeySystem; }
+  bool IsKeySystemSupported() const { return mIsKeySystemSupported; }
+  enum KeySystemIssue {
+    eUnset,
+    eWidevineWithNoWMF
+  };
+  void SetKeySystemIssue(KeySystemIssue aKeySystemIssue)
+  {
+    mKeySystemIssue = aKeySystemIssue;
+  }
+  KeySystemIssue GetKeySystemIssue() const
+  {
+    return mKeySystemIssue;
+  }
+
 private:
-  // True if there is at least one decoder that can play the media.
+  // Currently-known type of diagnostics. Set from one of the 'Store...' methods.
+  // This helps ensure diagnostics are only stored once,
+  // and makes it easy to know what information they contain.
+  DiagnosticsType mDiagnosticsType = eUnsaved;
+
+  nsString mFormat;
+  // True if there is at least one decoder that can play that format.
   bool mCanPlay = false;
 
+  bool mWMFFailedToLoad = false;
   bool mFFmpegFailedToLoad = false;
+  bool mGMPPDMFailedToStartup = false;
+  nsCString mGMP;
+
+  nsString mKeySystem;
+  bool mIsKeySystemSupported = false;
+  KeySystemIssue mKeySystemIssue = eUnset;
 };
 
 } // namespace mozilla
 
 #endif
--- a/dom/media/DecoderTraits.cpp
+++ b/dom/media/DecoderTraits.cpp
@@ -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/. */
 
 #include "DecoderTraits.h"
 #include "MediaDecoder.h"
 #include "nsCharSeparatedTokenizer.h"
 #include "nsMimeTypes.h"
 #include "mozilla/Preferences.h"
+#include "mozilla/Telemetry.h"
 
 #include "OggDecoder.h"
 #include "OggReader.h"
 
 #include "WebMDecoder.h"
 #include "WebMDemuxer.h"
 
 #ifdef MOZ_RAW
@@ -167,16 +168,31 @@ DecoderTraits::IsWebMTypeAndEnabled(cons
 }
 
 /* static */ bool
 DecoderTraits::IsWebMAudioType(const nsACString& aType)
 {
   return aType.EqualsASCII("audio/webm");
 }
 
+static char const *const gHttpLiveStreamingTypes[] = {
+  // For m3u8.
+  // https://tools.ietf.org/html/draft-pantos-http-live-streaming-19#section-10
+  "application/vnd.apple.mpegurl",
+  // Some sites serve these as the informal m3u type.
+  "audio/x-mpegurl",
+  nullptr
+};
+
+static bool
+IsHttpLiveStreamingType(const nsACString& aType)
+{
+  return CodecListContains(gHttpLiveStreamingTypes, aType);
+}
+
 #ifdef MOZ_OMX_DECODER
 static const char* const gOmxTypes[] = {
   "audio/mpeg",
   "audio/mp4",
   "audio/amr",
   "audio/3gpp",
   "audio/flac",
   "video/mp4",
@@ -472,16 +488,20 @@ DecoderTraits::CanHandleCodecsType(const
 CanPlayStatus
 DecoderTraits::CanHandleMediaType(const char* aMIMEType,
                                   bool aHaveRequestedCodecs,
                                   const nsAString& aRequestedCodecs,
                                   DecoderDoctorDiagnostics* aDiagnostics)
 {
   MOZ_ASSERT(NS_IsMainThread());
 
+  if (IsHttpLiveStreamingType(nsDependentCString(aMIMEType))) {
+    Telemetry::Accumulate(Telemetry::MEDIA_HLS_CANPLAY_REQUESTED, true);
+  }
+
   if (aHaveRequestedCodecs) {
     CanPlayStatus result = CanHandleCodecsType(aMIMEType,
                                                aRequestedCodecs,
                                                aDiagnostics);
     if (result == CANPLAY_NO || result == CANPLAY_YES) {
       return result;
     }
   }
@@ -616,16 +636,21 @@ InstantiateDecoder(const nsACString& aTy
   // Note: DirectShow should come before WMF, so that we prefer DirectShow's
   // MP3 support over WMF's.
   if (IsDirectShowSupportedType(aType)) {
     decoder = new DirectShowDecoder(aOwner);
     return decoder.forget();
   }
 #endif
 
+  if (IsHttpLiveStreamingType(aType)) {
+    // We don't have an HLS decoder.
+    Telemetry::Accumulate(Telemetry::MEDIA_HLS_DECODER_SUCCESS, false);
+  }
+
   return nullptr;
 }
 
 /* static */
 already_AddRefed<MediaDecoder>
 DecoderTraits::CreateDecoder(const nsACString& aType,
                              MediaDecoderOwner* aOwner,
                              DecoderDoctorDiagnostics* aDiagnostics)
--- a/dom/media/MediaDecoder.cpp
+++ b/dom/media/MediaDecoder.cpp
@@ -46,21 +46,25 @@ namespace mozilla {
 // The amount of instability we tollerate in calls to
 // MediaDecoder::UpdateEstimatedMediaDuration(); changes of duration
 // less than this are ignored, as they're assumed to be the result of
 // instability in the duration estimation.
 static const uint64_t ESTIMATED_DURATION_FUZZ_FACTOR_USECS = USECS_PER_S / 2;
 
 // avoid redefined macro in unified build
 #undef DECODER_LOG
+#undef DUMP_LOG
 
 LazyLogModule gMediaDecoderLog("MediaDecoder");
 #define DECODER_LOG(x, ...) \
   MOZ_LOG(gMediaDecoderLog, LogLevel::Debug, ("Decoder=%p " x, this, ##__VA_ARGS__))
 
+#define DUMP_LOG(x, ...) \
+  NS_DebugBreak(NS_DEBUG_WARNING, nsPrintfCString("Decoder=%p " x, this, ##__VA_ARGS__).get(), nullptr, nullptr, -1)
+
 static const char*
 ToPlayStateStr(MediaDecoder::PlayState aState)
 {
   switch (aState) {
     case MediaDecoder::PLAY_STATE_START:    return "START";
     case MediaDecoder::PLAY_STATE_LOADING:  return "LOADING";
     case MediaDecoder::PLAY_STATE_PAUSED:   return "PAUSED";
     case MediaDecoder::PLAY_STATE_PLAYING:  return "PLAYING";
@@ -1868,16 +1872,33 @@ MediaDecoder::NextFrameBufferedStatus()
   media::TimeInterval interval(currentPosition,
                                currentPosition + media::TimeUnit::FromMicroseconds(DEFAULT_NEXT_FRAME_AVAILABLE_BUFFERED));
   return GetBuffered().Contains(interval)
     ? MediaDecoderOwner::NEXT_FRAME_AVAILABLE
     : MediaDecoderOwner::NEXT_FRAME_UNAVAILABLE;
 }
 
 void
+MediaDecoder::DumpDebugInfo()
+{
+  DUMP_LOG("metadata: channels=%u rate=%u hasAudio=%d hasVideo=%d, "
+           "state: mPlayState=%s mIsDormant=%d, mShuttingDown=%d",
+           mInfo->mAudio.mChannels, mInfo->mAudio.mRate, mInfo->HasAudio(), mInfo->HasVideo(),
+           PlayStateStr(), mIsDormant, mShuttingDown);
+
+  nsString str;
+  GetMozDebugReaderData(str);
+  DUMP_LOG("reader data:\n%s", NS_ConvertUTF16toUTF8(str).get());
+
+  if (!mShuttingDown && GetStateMachine()) {
+    GetStateMachine()->DumpDebugInfo();
+  }
+}
+
+void
 MediaDecoder::NotifyAudibleStateChanged()
 {
   MOZ_ASSERT(!mShuttingDown);
   mOwner->NotifyAudibleStateChanged(mIsAudioDataAudible);
 }
 
 MediaMemoryTracker::MediaMemoryTracker()
 {
--- a/dom/media/MediaDecoder.h
+++ b/dom/media/MediaDecoder.h
@@ -494,16 +494,18 @@ private:
 
   virtual MediaDecoderOwner::NextFrameStatus NextFrameStatus() { return mNextFrameStatus; }
   virtual MediaDecoderOwner::NextFrameStatus NextFrameBufferedStatus();
 
   // Returns a string describing the state of the media player internal
   // data. Used for debugging purposes.
   virtual void GetMozDebugReaderData(nsAString& aString) {}
 
+  virtual void DumpDebugInfo();
+
 protected:
   virtual ~MediaDecoder();
 
   // Called when the first audio and/or video from the media file has been loaded
   // by the state machine. Call on the main thread only.
   virtual void FirstFrameLoaded(nsAutoPtr<MediaInfo> aInfo,
                                 MediaDecoderEventVisibility aEventVisibility);
 
--- a/dom/media/MediaDecoderReader.cpp
+++ b/dom/media/MediaDecoderReader.cpp
@@ -142,21 +142,21 @@ nsresult MediaDecoderReader::ResetDecode
   mVideoDiscontinuity = true;
 
   mBaseAudioPromise.RejectIfExists(CANCELED, __func__);
   mBaseVideoPromise.RejectIfExists(CANCELED, __func__);
 
   return NS_OK;
 }
 
-RefPtr<MediaDecoderReader::VideoDataPromise>
+RefPtr<MediaDecoderReader::MediaDataPromise>
 MediaDecoderReader::DecodeToFirstVideoData()
 {
   MOZ_ASSERT(OnTaskQueue());
-  typedef MediaDecoderReader::VideoDataPromise PromiseType;
+  typedef MediaDecoderReader::MediaDataPromise PromiseType;
   RefPtr<PromiseType::Private> p = new PromiseType::Private(__func__);
   RefPtr<MediaDecoderReader> self = this;
   InvokeUntil([self] () -> bool {
     MOZ_ASSERT(self->OnTaskQueue());
     NS_ENSURE_TRUE(!self->mShutdown, false);
     bool skip = false;
     if (!self->DecodeVideoFrame(skip, 0)) {
       self->VideoQueue().Finish();
@@ -270,21 +270,21 @@ public:
 
     return NS_OK;
   }
 
 private:
   RefPtr<MediaDecoderReader> mReader;
 };
 
-RefPtr<MediaDecoderReader::VideoDataPromise>
+RefPtr<MediaDecoderReader::MediaDataPromise>
 MediaDecoderReader::RequestVideoData(bool aSkipToNextKeyframe,
                                      int64_t aTimeThreshold)
 {
-  RefPtr<VideoDataPromise> p = mBaseVideoPromise.Ensure(__func__);
+  RefPtr<MediaDataPromise> p = mBaseVideoPromise.Ensure(__func__);
   bool skip = aSkipToNextKeyframe;
   while (VideoQueue().GetSize() == 0 &&
          !VideoQueue().IsFinished()) {
     if (!DecodeVideoFrame(skip, aTimeThreshold)) {
       VideoQueue().Finish();
     } else if (skip) {
       // We still need to decode more data in order to skip to the next
       // keyframe. Post another task to the decode task queue to decode
@@ -306,20 +306,20 @@ MediaDecoderReader::RequestVideoData(boo
     mBaseVideoPromise.Reject(END_OF_STREAM, __func__);
   } else {
     MOZ_ASSERT(false, "Dropping this promise on the floor");
   }
 
   return p;
 }
 
-RefPtr<MediaDecoderReader::AudioDataPromise>
+RefPtr<MediaDecoderReader::MediaDataPromise>
 MediaDecoderReader::RequestAudioData()
 {
-  RefPtr<AudioDataPromise> p = mBaseAudioPromise.Ensure(__func__);
+  RefPtr<MediaDataPromise> p = mBaseAudioPromise.Ensure(__func__);
   while (AudioQueue().GetSize() == 0 &&
          !AudioQueue().IsFinished()) {
     if (!DecodeAudioData()) {
       AudioQueue().Finish();
       break;
     }
     // AudioQueue size is still zero, post a task to try again. Don't spin
     // waiting in this while loop since it somehow prevents audio EOS from
--- a/dom/media/MediaDecoderReader.h
+++ b/dom/media/MediaDecoderReader.h
@@ -70,19 +70,17 @@ public:
     END_OF_STREAM,
     DECODE_ERROR,
     WAITING_FOR_DATA,
     CANCELED
   };
 
   using MetadataPromise =
     MozPromise<RefPtr<MetadataHolder>, ReadMetadataFailureReason, IsExclusive>;
-  using AudioDataPromise =
-    MozPromise<RefPtr<MediaData>, NotDecodedReason, IsExclusive>;
-  using VideoDataPromise =
+  using MediaDataPromise =
     MozPromise<RefPtr<MediaData>, NotDecodedReason, IsExclusive>;
   using SeekPromise = MozPromise<media::TimeUnit, nsresult, IsExclusive>;
 
   // Note that, conceptually, WaitForData makes sense in a non-exclusive sense.
   // But in the current architecture it's only ever used exclusively (by MDSM),
   // so we mark it that way to verify our assumptions. If you have a use-case
   // for multiple WaitForData consumers, feel free to flip the exclusivity here.
   using WaitForDataPromise =
@@ -130,25 +128,25 @@ public:
   virtual nsresult ResetDecode();
 
   // Requests one audio sample from the reader.
   //
   // The decode should be performed asynchronously, and the promise should
   // be resolved when it is complete. Don't hold the decoder
   // monitor while calling this, as the implementation may try to wait
   // on something that needs the monitor and deadlock.
-  virtual RefPtr<AudioDataPromise> RequestAudioData();
+  virtual RefPtr<MediaDataPromise> RequestAudioData();
 
   // Requests one video sample from the reader.
   //
   // Don't hold the decoder monitor while calling this, as the implementation
   // may try to wait on something that needs the monitor and deadlock.
   // If aSkipToKeyframe is true, the decode should skip ahead to the
   // the next keyframe at or after aTimeThreshold microseconds.
-  virtual RefPtr<VideoDataPromise>
+  virtual RefPtr<MediaDataPromise>
   RequestVideoData(bool aSkipToNextKeyframe, int64_t aTimeThreshold);
 
   // By default, the state machine polls the reader once per second when it's
   // in buffering mode. Some readers support a promise-based mechanism by which
   // they notify the state machine when the data arrives.
   virtual bool IsWaitForDataSupported() const { return false; }
 
   virtual RefPtr<WaitForDataPromise> WaitForData(MediaData::Type aType)
@@ -301,17 +299,17 @@ protected:
   // The primary advantage of this implementation in the reader base class
   // is that it's a fast approximation, which does not perform any I/O.
   //
   // The OggReader relies on this base implementation not performing I/O,
   // since in FirefoxOS we can't do I/O on the main thread, where this is
   // called.
   virtual media::TimeIntervals GetBuffered();
 
-  RefPtr<VideoDataPromise> DecodeToFirstVideoData();
+  RefPtr<MediaDataPromise> DecodeToFirstVideoData();
 
   bool HaveStartTime()
   {
     MOZ_ASSERT(OnTaskQueue());
     return mStartTime.isSome();
   }
 
   int64_t StartTime() { MOZ_ASSERT(HaveStartTime()); return mStartTime.ref(); }
@@ -416,18 +414,18 @@ private:
   // is complete.
   virtual bool DecodeVideoFrame(bool &aKeyframeSkip, int64_t aTimeThreshold)
   {
     return false;
   }
 
   // Promises used only for the base-class (sync->async adapter) implementation
   // of Request{Audio,Video}Data.
-  MozPromiseHolder<AudioDataPromise> mBaseAudioPromise;
-  MozPromiseHolder<VideoDataPromise> mBaseVideoPromise;
+  MozPromiseHolder<MediaDataPromise> mBaseAudioPromise;
+  MozPromiseHolder<MediaDataPromise> mBaseVideoPromise;
 
   // Flags whether a the next audio/video sample comes after a "gap" or
   // "discontinuity" in the stream. For example after a seek.
   bool mAudioDiscontinuity;
   bool mVideoDiscontinuity;
 
   MediaEventListener mDataArrivedListener;
 };
--- a/dom/media/MediaDecoderReaderWrapper.cpp
+++ b/dom/media/MediaDecoderReaderWrapper.cpp
@@ -14,19 +14,18 @@ extern LazyLogModule gMediaDecoderLog;
 #undef LOG
 #define LOG(...) \
   MOZ_LOG(gMediaDecoderLog, mozilla::LogLevel::Debug, (__VA_ARGS__))
 
 // StartTimeRendezvous is a helper class that quarantines the first sample
 // until it gets a sample from both channels, such that we can be guaranteed
 // to know the start time by the time On{Audio,Video}Decoded is called on MDSM.
 class StartTimeRendezvous {
+  typedef MediaDecoderReader::MediaDataPromise MediaDataPromise;
   NS_INLINE_DECL_THREADSAFE_REFCOUNTING(StartTimeRendezvous);
-  typedef MediaDecoderReader::AudioDataPromise AudioDataPromise;
-  typedef MediaDecoderReader::VideoDataPromise VideoDataPromise;
 
 public:
   StartTimeRendezvous(AbstractThread* aOwnerThread,
                       bool aHasAudio,
                       bool aHasVideo,
                       bool aForceZeroStartTime)
     : mOwnerThread(aOwnerThread)
   {
@@ -53,33 +52,27 @@ public:
   RefPtr<HaveStartTimePromise> AwaitStartTime()
   {
     if (HaveStartTime()) {
       return HaveStartTimePromise::CreateAndResolve(true, __func__);
     }
     return mHaveStartTimePromise.Ensure(__func__);
   }
 
-  template<typename PromiseType>
-  struct PromiseSampleType {
-    typedef typename PromiseType::ResolveValueType::element_type Type;
-  };
-
-  template<typename PromiseType, MediaData::Type SampleType>
-  RefPtr<PromiseType>
-  ProcessFirstSample(typename PromiseSampleType<PromiseType>::Type* aData)
+  template<MediaData::Type SampleType>
+  RefPtr<MediaDataPromise>
+  ProcessFirstSample(MediaData* aData)
   {
-    typedef typename PromiseSampleType<PromiseType>::Type DataType;
-    typedef typename PromiseType::Private PromisePrivate;
+    typedef typename MediaDataPromise::Private PromisePrivate;
     MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn());
 
     MaybeSetChannelStartTime<SampleType>(aData->mTime);
 
     RefPtr<PromisePrivate> p = new PromisePrivate(__func__);
-    RefPtr<DataType> data = aData;
+    RefPtr<MediaData> data = aData;
     RefPtr<StartTimeRendezvous> self = this;
     AwaitStartTime()->Then(
       mOwnerThread, __func__,
       [p, data, self] () {
         MOZ_ASSERT(self->mOwnerThread->IsCurrentThreadIn());
         p->Resolve(data, __func__);
       },
       [p] () {
@@ -180,39 +173,39 @@ MediaDecoderReaderWrapper::ReadMetadata(
 RefPtr<HaveStartTimePromise>
 MediaDecoderReaderWrapper::AwaitStartTime()
 {
   MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn());
   MOZ_ASSERT(!mShutdown);
   return mStartTimeRendezvous->AwaitStartTime();
 }
 
-RefPtr<MediaDecoderReaderWrapper::AudioDataPromise>
+RefPtr<MediaDecoderReaderWrapper::MediaDataPromise>
 MediaDecoderReaderWrapper::RequestAudioData()
 {
   MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn());
   MOZ_ASSERT(!mShutdown);
 
   auto p = InvokeAsync(mReader->OwnerThread(), mReader.get(), __func__,
                        &MediaDecoderReader::RequestAudioData);
 
   if (!mStartTimeRendezvous->HaveStartTime()) {
     p = p->Then(mOwnerThread, __func__, mStartTimeRendezvous.get(),
-                &StartTimeRendezvous::ProcessFirstSample<AudioDataPromise, MediaData::AUDIO_DATA>,
+                &StartTimeRendezvous::ProcessFirstSample<MediaData::AUDIO_DATA>,
                 &StartTimeRendezvous::FirstSampleRejected<MediaData::AUDIO_DATA>)
          ->CompletionPromise();
   }
 
   return p->Then(mOwnerThread, __func__, this,
                  &MediaDecoderReaderWrapper::OnSampleDecoded,
                  &MediaDecoderReaderWrapper::OnNotDecoded)
           ->CompletionPromise();
 }
 
-RefPtr<MediaDecoderReaderWrapper::VideoDataPromise>
+RefPtr<MediaDecoderReaderWrapper::MediaDataPromise>
 MediaDecoderReaderWrapper::RequestVideoData(bool aSkipToNextKeyframe,
                                             media::TimeUnit aTimeThreshold)
 {
   MOZ_ASSERT(mOwnerThread->IsCurrentThreadIn());
   MOZ_ASSERT(!mShutdown);
 
   if (aTimeThreshold.ToMicroseconds() > 0 &&
       mStartTimeRendezvous->HaveStartTime()) {
@@ -220,17 +213,17 @@ MediaDecoderReaderWrapper::RequestVideoD
   }
 
   auto p = InvokeAsync(mReader->OwnerThread(), mReader.get(), __func__,
                        &MediaDecoderReader::RequestVideoData,
                        aSkipToNextKeyframe, aTimeThreshold.ToMicroseconds());
 
   if (!mStartTimeRendezvous->HaveStartTime()) {
     p = p->Then(mOwnerThread, __func__, mStartTimeRendezvous.get(),
-                &StartTimeRendezvous::ProcessFirstSample<VideoDataPromise, MediaData::VIDEO_DATA>,
+                &StartTimeRendezvous::ProcessFirstSample<MediaData::VIDEO_DATA>,
                 &StartTimeRendezvous::FirstSampleRejected<MediaData::VIDEO_DATA>)
          ->CompletionPromise();
   }
 
   return p->Then(mOwnerThread, __func__, this,
                  &MediaDecoderReaderWrapper::OnSampleDecoded,
                  &MediaDecoderReaderWrapper::OnNotDecoded)
           ->CompletionPromise();
--- a/dom/media/MediaDecoderReaderWrapper.h
+++ b/dom/media/MediaDecoderReaderWrapper.h
@@ -22,33 +22,32 @@ typedef MozPromise<bool, bool, /* isExcl
 /**
  * A wrapper around MediaDecoderReader to offset the timestamps of Audio/Video
  * samples by the start time to ensure MDSM can always assume zero start time.
  * It also adjusts the seek target passed to Seek() to ensure correct seek time
  * is passed to the underlying reader.
  */
 class MediaDecoderReaderWrapper {
   typedef MediaDecoderReader::MetadataPromise MetadataPromise;
-  typedef MediaDecoderReader::AudioDataPromise AudioDataPromise;
-  typedef MediaDecoderReader::VideoDataPromise VideoDataPromise;
+  typedef MediaDecoderReader::MediaDataPromise MediaDataPromise;
   typedef MediaDecoderReader::SeekPromise SeekPromise;
   typedef MediaDecoderReader::WaitForDataPromise WaitForDataPromise;
   typedef MediaDecoderReader::BufferedUpdatePromise BufferedUpdatePromise;
   NS_INLINE_DECL_THREADSAFE_REFCOUNTING(MediaDecoderReaderWrapper);
 
 public:
   MediaDecoderReaderWrapper(bool aIsRealTime,
                             AbstractThread* aOwnerThread,
                             MediaDecoderReader* aReader);
 
   media::TimeUnit StartTime() const;
   RefPtr<MetadataPromise> ReadMetadata();
   RefPtr<HaveStartTimePromise> AwaitStartTime();
-  RefPtr<AudioDataPromise> RequestAudioData();
-  RefPtr<VideoDataPromise> RequestVideoData(bool aSkipToNextKeyframe,
+  RefPtr<MediaDataPromise> RequestAudioData();
+  RefPtr<MediaDataPromise> RequestVideoData(bool aSkipToNextKeyframe,
                                             media::TimeUnit aTimeThreshold);
   RefPtr<SeekPromise> Seek(SeekTarget aTarget, media::TimeUnit aEndTime);
   RefPtr<WaitForDataPromise> WaitForData(MediaData::Type aType);
   RefPtr<BufferedUpdatePromise> UpdateBufferedWithPromise();
   RefPtr<ShutdownPromise> Shutdown();
 
   void ReleaseMediaResources();
   void SetIdle();
--- a/dom/media/MediaDecoderStateMachine.cpp
+++ b/dom/media/MediaDecoderStateMachine.cpp
@@ -58,32 +58,36 @@ using namespace mozilla::layers;
 using namespace mozilla::media;
 
 #define NS_DispatchToMainThread(...) CompileError_UseAbstractThreadDispatchInstead
 
 // avoid redefined macro in unified build
 #undef LOG
 #undef DECODER_LOG
 #undef VERBOSE_LOG
+#undef DUMP_LOG
 
 #define LOG(m, l, x, ...) \
   MOZ_LOG(m, l, ("Decoder=%p " x, mDecoderID, ##__VA_ARGS__))
 #define DECODER_LOG(x, ...) \
   LOG(gMediaDecoderLog, LogLevel::Debug, x, ##__VA_ARGS__)
 #define VERBOSE_LOG(x, ...) \
   LOG(gMediaDecoderLog, LogLevel::Verbose, x, ##__VA_ARGS__)
 #define SAMPLE_LOG(x, ...) \
   LOG(gMediaSampleLog, LogLevel::Debug, x, ##__VA_ARGS__)
 
 // Somehow MSVC doesn't correctly delete the comma before ##__VA_ARGS__
 // when __VA_ARGS__ expands to nothing. This is a workaround for it.
 #define DECODER_WARN_HELPER(a, b) NS_WARNING b
 #define DECODER_WARN(x, ...) \
   DECODER_WARN_HELPER(0, (nsPrintfCString("Decoder=%p " x, mDecoderID, ##__VA_ARGS__).get()))
 
+#define DUMP_LOG(x, ...) \
+  NS_DebugBreak(NS_DEBUG_WARNING, nsPrintfCString("Decoder=%p " x, mDecoderID, ##__VA_ARGS__).get(), nullptr, nullptr, -1)
+
 // Certain constants get stored as member variables and then adjusted by various
 // scale factors on a per-decoder basis. We want to make sure to avoid using these
 // constants directly, so we put them in a namespace.
 namespace detail {
 
 // If audio queue has less than this many usecs of decoded audio, we won't risk
 // trying to decode the video, we'll skip decoding video up to the next
 // keyframe. We may increase this value for an individual decoder if we
@@ -2659,16 +2663,38 @@ MediaDecoderStateMachine::SetAudioCaptur
 uint32_t MediaDecoderStateMachine::GetAmpleVideoFrames() const
 {
   MOZ_ASSERT(OnTaskQueue());
   return (mReader->IsAsync() && mReader->VideoIsHardwareAccelerated())
     ? std::max<uint32_t>(sVideoQueueHWAccelSize, MIN_VIDEO_QUEUE_SIZE)
     : std::max<uint32_t>(sVideoQueueDefaultSize, MIN_VIDEO_QUEUE_SIZE);
 }
 
+void
+MediaDecoderStateMachine::DumpDebugInfo()
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  // It is fine to capture a raw pointer here because MediaDecoder only call
+  // this function before shutdown begins.
+  nsCOMPtr<nsIRunnable> r = NS_NewRunnableFunction([this] () {
+    DUMP_LOG(
+      "GetMediaTime=%lld GetClock=%lld "
+      "mState=%s mPlayState=%d mDecodingFirstFrame=%d IsPlaying=%d "
+      "mAudioStatus=%s mVideoStatus=%s mDecodedAudioEndTime=%lld mDecodedVideoEndTime=%lld "
+      "mIsAudioPrerolling=%d mIsVideoPrerolling=%d",
+      GetMediaTime(), mMediaSink->IsStarted() ? GetClock() : -1,
+      gMachineStateStr[mState], mPlayState.Ref(), mDecodingFirstFrame, IsPlaying(),
+      AudioRequestStatus(), VideoRequestStatus(), mDecodedAudioEndTime, mDecodedVideoEndTime,
+      mIsAudioPrerolling, mIsVideoPrerolling);
+  });
+
+  OwnerThread()->DispatchStateChange(r.forget());
+}
+
 void MediaDecoderStateMachine::AddOutputStream(ProcessedMediaStream* aStream,
                                                bool aFinishWhenEnded)
 {
   MOZ_ASSERT(NS_IsMainThread());
   DECODER_LOG("AddOutputStream aStream=%p!", aStream);
   mOutputStreamManager->Add(aStream, aFinishWhenEnded);
   nsCOMPtr<nsIRunnable> r = NS_NewRunnableMethodWithArg<bool>(
     this, &MediaDecoderStateMachine::SetAudioCaptured, true);
--- a/dom/media/MediaDecoderStateMachine.h
+++ b/dom/media/MediaDecoderStateMachine.h
@@ -130,18 +130,16 @@ enum class MediaEventType : int8_t {
   shared state machine thread.
 
   See MediaDecoder.h for more details.
 */
 class MediaDecoderStateMachine
 {
   NS_INLINE_DECL_THREADSAFE_REFCOUNTING(MediaDecoderStateMachine)
 public:
-  typedef MediaDecoderReader::AudioDataPromise AudioDataPromise;
-  typedef MediaDecoderReader::VideoDataPromise VideoDataPromise;
   typedef MediaDecoderOwner::NextFrameStatus NextFrameStatus;
   typedef mozilla::layers::ImageContainer::FrameID FrameID;
   MediaDecoderStateMachine(MediaDecoder* aDecoder,
                            MediaDecoderReader* aReader,
                            bool aRealTime = false);
 
   nsresult Init(MediaDecoder* aDecoder);
 
@@ -153,16 +151,18 @@ public:
     DECODER_STATE_DECODING,
     DECODER_STATE_SEEKING,
     DECODER_STATE_BUFFERING,
     DECODER_STATE_COMPLETED,
     DECODER_STATE_SHUTDOWN,
     DECODER_STATE_ERROR
   };
 
+  void DumpDebugInfo();
+
   void AddOutputStream(ProcessedMediaStream* aStream, bool aFinishWhenEnded);
   // Remove an output stream added with AddOutputStream.
   void RemoveOutputStream(MediaStream* aStream);
 
   // Seeks to the decoder to aTarget asynchronously.
   RefPtr<MediaDecoder::SeekPromise> InvokeSeek(SeekTarget aTarget);
 
   // Set/Unset dormant state.
@@ -805,32 +805,32 @@ private:
   // playback. The flags below are true when the corresponding stream is
   // being "prerolled".
   bool mIsAudioPrerolling;
   bool mIsVideoPrerolling;
 
   // Only one of a given pair of ({Audio,Video}DataPromise, WaitForDataPromise)
   // should exist at any given moment.
 
-  MozPromiseRequestHolder<MediaDecoderReader::AudioDataPromise> mAudioDataRequest;
+  MozPromiseRequestHolder<MediaDecoderReader::MediaDataPromise> mAudioDataRequest;
   MozPromiseRequestHolder<MediaDecoderReader::WaitForDataPromise> mAudioWaitRequest;
   const char* AudioRequestStatus()
   {
     MOZ_ASSERT(OnTaskQueue());
     if (mAudioDataRequest.Exists()) {
       MOZ_DIAGNOSTIC_ASSERT(!mAudioWaitRequest.Exists());
       return "pending";
     } else if (mAudioWaitRequest.Exists()) {
       return "waiting";
     }
     return "idle";
   }
 
   MozPromiseRequestHolder<MediaDecoderReader::WaitForDataPromise> mVideoWaitRequest;
-  MozPromiseRequestHolder<MediaDecoderReader::VideoDataPromise> mVideoDataRequest;
+  MozPromiseRequestHolder<MediaDecoderReader::MediaDataPromise> mVideoDataRequest;
   const char* VideoRequestStatus()
   {
     MOZ_ASSERT(OnTaskQueue());
     if (mVideoDataRequest.Exists()) {
       MOZ_DIAGNOSTIC_ASSERT(!mVideoWaitRequest.Exists());
       return "pending";
     } else if (mVideoWaitRequest.Exists()) {
       return "waiting";
--- a/dom/media/MediaFormatReader.cpp
+++ b/dom/media/MediaFormatReader.cpp
@@ -479,63 +479,63 @@ MediaFormatReader::ShouldSkip(bool aSkip
   media::TimeUnit nextKeyframe;
   nsresult rv = mVideo.mTrackDemuxer->GetNextRandomAccessPoint(&nextKeyframe);
   if (NS_FAILED(rv)) {
     return aSkipToNextKeyframe;
   }
   return nextKeyframe < aTimeThreshold && nextKeyframe.ToMicroseconds() >= 0;
 }
 
-RefPtr<MediaDecoderReader::VideoDataPromise>
+RefPtr<MediaDecoderReader::MediaDataPromise>
 MediaFormatReader::RequestVideoData(bool aSkipToNextKeyframe,
                                     int64_t aTimeThreshold)
 {
   MOZ_ASSERT(OnTaskQueue());
   MOZ_DIAGNOSTIC_ASSERT(mSeekPromise.IsEmpty(), "No sample requests allowed while seeking");
   MOZ_DIAGNOSTIC_ASSERT(!mVideo.HasPromise(), "No duplicate sample requests");
   MOZ_DIAGNOSTIC_ASSERT(!mVideo.mSeekRequest.Exists() ||
                         mVideo.mTimeThreshold.isSome());
   MOZ_DIAGNOSTIC_ASSERT(!mSkipRequest.Exists(), "called mid-skipping");
   MOZ_DIAGNOSTIC_ASSERT(!IsSeeking(), "called mid-seek");
   LOGV("RequestVideoData(%d, %lld)", aSkipToNextKeyframe, aTimeThreshold);
 
   if (!HasVideo()) {
     LOG("called with no video track");
-    return VideoDataPromise::CreateAndReject(DECODE_ERROR, __func__);
+    return MediaDataPromise::CreateAndReject(DECODE_ERROR, __func__);
   }
 
   if (IsSeeking()) {
     LOG("called mid-seek. Rejecting.");
-    return VideoDataPromise::CreateAndReject(CANCELED, __func__);
+    return MediaDataPromise::CreateAndReject(CANCELED, __func__);
   }
 
   if (mShutdown) {
     NS_WARNING("RequestVideoData on shutdown MediaFormatReader!");
-    return VideoDataPromise::CreateAndReject(CANCELED, __func__);
+    return MediaDataPromise::CreateAndReject(CANCELED, __func__);
   }
 
   media::TimeUnit timeThreshold{media::TimeUnit::FromMicroseconds(aTimeThreshold)};
   if (ShouldSkip(aSkipToNextKeyframe, timeThreshold)) {
     // Cancel any pending demux request.
     mVideo.mDemuxRequest.DisconnectIfExists();
 
     // I think it's still possible for an output to have been sent from the decoder
     // and is currently sitting in our event queue waiting to be processed. The following
     // flush won't clear it, and when we return to the event loop it'll be added to our
     // output queue and be used.
     // This code will count that as dropped, which was the intent, but not quite true.
     mDecoder->NotifyDecodedFrames(0, 0, SizeOfVideoQueueInFrames());
 
     Flush(TrackInfo::kVideoTrack);
-    RefPtr<VideoDataPromise> p = mVideo.mPromise.Ensure(__func__);
+    RefPtr<MediaDataPromise> p = mVideo.mPromise.Ensure(__func__);
     SkipVideoDemuxToNextKeyFrame(timeThreshold);
     return p;
   }
 
-  RefPtr<VideoDataPromise> p = mVideo.mPromise.Ensure(__func__);
+  RefPtr<MediaDataPromise> p = mVideo.mPromise.Ensure(__func__);
   NotifyDecodingRequested(TrackInfo::kVideoTrack);
 
   return p;
 }
 
 void
 MediaFormatReader::OnDemuxFailed(TrackType aTrack, DemuxerFailureReason aFailure)
 {
@@ -585,43 +585,43 @@ MediaFormatReader::OnVideoDemuxCompleted
   LOGV("%d video samples demuxed (sid:%d)",
        aSamples->mSamples.Length(),
        aSamples->mSamples[0]->mTrackInfo ? aSamples->mSamples[0]->mTrackInfo->GetID() : 0);
   mVideo.mDemuxRequest.Complete();
   mVideo.mQueuedSamples.AppendElements(aSamples->mSamples);
   ScheduleUpdate(TrackInfo::kVideoTrack);
 }
 
-RefPtr<MediaDecoderReader::AudioDataPromise>
+RefPtr<MediaDecoderReader::MediaDataPromise>
 MediaFormatReader::RequestAudioData()
 {
   MOZ_ASSERT(OnTaskQueue());
   MOZ_DIAGNOSTIC_ASSERT(mSeekPromise.IsEmpty(), "No sample requests allowed while seeking");
   MOZ_DIAGNOSTIC_ASSERT(!mAudio.mSeekRequest.Exists() ||
                         mAudio.mTimeThreshold.isSome());
   MOZ_DIAGNOSTIC_ASSERT(!mAudio.HasPromise(), "No duplicate sample requests");
   MOZ_DIAGNOSTIC_ASSERT(!IsSeeking(), "called mid-seek");
   LOGV("");
 
   if (!HasAudio()) {
     LOG("called with no audio track");
-    return AudioDataPromise::CreateAndReject(DECODE_ERROR, __func__);
+    return MediaDataPromise::CreateAndReject(DECODE_ERROR, __func__);
   }
 
   if (IsSeeking()) {
     LOG("called mid-seek. Rejecting.");
-    return AudioDataPromise::CreateAndReject(CANCELED, __func__);
+    return MediaDataPromise::CreateAndReject(CANCELED, __func__);
   }
 
   if (mShutdown) {
     NS_WARNING("RequestAudioData on shutdown MediaFormatReader!");
-    return AudioDataPromise::CreateAndReject(CANCELED, __func__);
+    return MediaDataPromise::CreateAndReject(CANCELED, __func__);
   }
 
-  RefPtr<AudioDataPromise> p = mAudio.mPromise.Ensure(__func__);
+  RefPtr<MediaDataPromise> p = mAudio.mPromise.Ensure(__func__);
   NotifyDecodingRequested(TrackInfo::kAudioTrack);
 
   return p;
 }
 
 void
 MediaFormatReader::DoDemuxAudio()
 {
--- a/dom/media/MediaFormatReader.h
+++ b/dom/media/MediaFormatReader.h
@@ -32,20 +32,20 @@ public:
 
   virtual ~MediaFormatReader();
 
   nsresult Init() override;
 
   size_t SizeOfVideoQueueInFrames() override;
   size_t SizeOfAudioQueueInFrames() override;
 
-  RefPtr<VideoDataPromise>
+  RefPtr<MediaDataPromise>
   RequestVideoData(bool aSkipToNextKeyframe, int64_t aTimeThreshold) override;
 
-  RefPtr<AudioDataPromise> RequestAudioData() override;
+  RefPtr<MediaDataPromise> RequestAudioData() override;
 
   RefPtr<MetadataPromise> AsyncReadMetadata() override;
 
   void ReadUpdatedMetadata(MediaInfo* aInfo) override;
 
   RefPtr<SeekPromise>
   Seek(SeekTarget aTarget, int64_t aUnused) override;
 
@@ -361,43 +361,42 @@ private:
     // Sample format monitoring.
     uint32_t mLastStreamSourceID;
     Maybe<uint32_t> mNextStreamSourceID;
     media::TimeIntervals mTimeRanges;
     Maybe<media::TimeUnit> mLastTimeRangesEnd;
     RefPtr<SharedTrackInfo> mInfo;
   };
 
-  template<typename PromiseType>
   struct DecoderDataWithPromise : public DecoderData {
     DecoderDataWithPromise(MediaFormatReader* aOwner,
                            MediaData::Type aType,
                            uint32_t aDecodeAhead) :
       DecoderData(aOwner, aType, aDecodeAhead)
     {}
 
-    MozPromiseHolder<PromiseType> mPromise;
+    MozPromiseHolder<MediaDataPromise> mPromise;
 
     bool HasPromise() override
     {
       MOZ_ASSERT(mOwner->OnTaskQueue());
       return !mPromise.IsEmpty();
     }
 
     void RejectPromise(MediaDecoderReader::NotDecodedReason aReason,
                        const char* aMethodName) override
     {
       MOZ_ASSERT(mOwner->OnTaskQueue());
       mPromise.Reject(aReason, aMethodName);
       mDecodingRequested = false;
     }
   };
 
-  DecoderDataWithPromise<AudioDataPromise> mAudio;
-  DecoderDataWithPromise<VideoDataPromise> mVideo;
+  DecoderDataWithPromise mAudio;
+  DecoderDataWithPromise mVideo;
 
   // Returns true when the decoder for this track needs input.
   bool NeedInput(DecoderData& aDecoder);
 
   DecoderData& GetDecoderData(TrackType aTrack);
 
   // Demuxer objects.
   RefPtr<MediaDataDemuxer> mDemuxer;
--- a/dom/media/SeekTask.h
+++ b/dom/media/SeekTask.h
@@ -152,18 +152,18 @@ protected:
   // the seek target, we will still have a frame that we can display as the
   // last frame in the media.
   RefPtr<MediaData> mFirstVideoFrameAfterSeek;
 
   /*
    * Track the current seek promise made by the reader.
    */
   MozPromiseRequestHolder<MediaDecoderReader::SeekPromise> mSeekRequest;
-  MozPromiseRequestHolder<MediaDecoderReader::AudioDataPromise> mAudioDataRequest;
-  MozPromiseRequestHolder<MediaDecoderReader::VideoDataPromise> mVideoDataRequest;
+  MozPromiseRequestHolder<MediaDecoderReader::MediaDataPromise> mAudioDataRequest;
+  MozPromiseRequestHolder<MediaDecoderReader::MediaDataPromise> mVideoDataRequest;
   MozPromiseRequestHolder<MediaDecoderReader::WaitForDataPromise> mAudioWaitRequest;
   MozPromiseRequestHolder<MediaDecoderReader::WaitForDataPromise> mVideoWaitRequest;
 
   /*
    * Information which are going to be returned to MDSM.
    */
   RefPtr<MediaData> mSeekedAudioData;
   RefPtr<MediaData> mSeekedVideoData;
--- a/dom/media/android/AndroidMediaReader.h
+++ b/dom/media/android/AndroidMediaReader.h
@@ -32,17 +32,17 @@ class AndroidMediaReader : public MediaD
   bool mHasAudio;
   bool mHasVideo;
   nsIntRect mPicture;
   nsIntSize mInitialFrame;
   int64_t mVideoSeekTimeUs;
   int64_t mAudioSeekTimeUs;
   RefPtr<VideoData> mLastVideoFrame;
   MozPromiseHolder<MediaDecoderReader::SeekPromise> mSeekPromise;
-  MozPromiseRequestHolder<MediaDecoderReader::VideoDataPromise> mSeekRequest;
+  MozPromiseRequestHolder<MediaDecoderReader::MediaDataPromise> mSeekRequest;
 public:
   AndroidMediaReader(AbstractMediaDecoder* aDecoder,
                      const nsACString& aContentType);
 
   nsresult ResetDecode() override;
 
   bool DecodeAudioData() override;
   bool DecodeVideoFrame(bool &aKeyframeSkip, int64_t aTimeThreshold) override;
--- a/dom/media/eme/CDMCallbackProxy.cpp
+++ b/dom/media/eme/CDMCallbackProxy.cpp
@@ -210,16 +210,29 @@ CDMCallbackProxy::ExpirationChange(const
   NS_DispatchToMainThread(task);
 }
 
 void
 CDMCallbackProxy::SessionClosed(const nsCString& aSessionId)
 {
   MOZ_ASSERT(mProxy->IsOnGMPThread());
 
+  bool keyStatusesChange = false;
+  {
+    CDMCaps::AutoLock caps(mProxy->Capabilites());
+    keyStatusesChange = caps.RemoveKeysForSession(NS_ConvertUTF8toUTF16(aSessionId));
+  }
+  if (keyStatusesChange) {
+    nsCOMPtr<nsIRunnable> task;
+    task = NS_NewRunnableMethodWithArg<nsString>(mProxy,
+      &CDMProxy::OnKeyStatusesChange,
+      NS_ConvertUTF8toUTF16(aSessionId));
+    NS_DispatchToMainThread(task);
+  }
+
   nsCOMPtr<nsIRunnable> task;
   task = NS_NewRunnableMethodWithArg<nsString>(mProxy,
                                                &CDMProxy::OnSessionClosed,
                                                NS_ConvertUTF8toUTF16(aSessionId));
   NS_DispatchToMainThread(task);
 }
 
 class SessionErrorTask : public nsRunnable {
--- a/dom/media/eme/CDMCaps.cpp
+++ b/dom/media/eme/CDMCaps.cpp
@@ -238,9 +238,21 @@ CDMCaps::AutoLock::GetSessionIdsForKeyId
 {
   for (const auto& keyStatus : mData.mKeyStatuses) {
     if (keyStatus.mId == aKeyId) {
       aOutSessionIds.AppendElement(NS_ConvertUTF16toUTF8(keyStatus.mSessionId));
     }
   }
 }
 
+bool
+CDMCaps::AutoLock::RemoveKeysForSession(const nsString& aSessionId)
+{
+  bool changed = false;
+  nsTArray<KeyStatus> statuses;
+  GetKeyStatusesForSession(aSessionId, statuses);
+  for (const KeyStatus& status : statuses) {
+    changed |= SetKeyStatus(status.mId, aSessionId, kGMPUnknown);
+  }
+  return changed;
+}
+
 } // namespace mozilla
--- a/dom/media/eme/CDMCaps.h
+++ b/dom/media/eme/CDMCaps.h
@@ -66,16 +66,20 @@ public:
     bool SetKeyStatus(const CencKeyId& aKeyId, const nsString& aSessionId, GMPMediaKeyStatus aStatus);
 
     void GetKeyStatusesForSession(const nsAString& aSessionId,
                                   nsTArray<KeyStatus>& aOutKeyStatuses);
 
     void GetSessionIdsForKeyId(const CencKeyId& aKeyId,
                                nsTArray<nsCString>& aOutSessionIds);
 
+    // Ensures all keys for a session are marked as 'unknown', i.e. removed.
+    // Returns true if a key status was changed.
+    bool RemoveKeysForSession(const nsString& aSessionId);
+
     // Sets the capabilities of the CDM. aCaps is the logical OR of the
     // GMP_EME_CAP_* flags from gmp-decryption.h.
     void SetCaps(uint64_t aCaps);
 
     bool CanRenderAudio();
     bool CanRenderVideo();
 
     bool CanDecryptAndDecodeAudio();
--- a/dom/media/eme/MediaKeySystemAccess.cpp
+++ b/dom/media/eme/MediaKeySystemAccess.cpp
@@ -28,16 +28,17 @@
 #include "mozilla/EMEUtils.h"
 #include "GMPUtils.h"
 #include "nsAppDirectoryServiceDefs.h"
 #include "nsDirectoryServiceUtils.h"
 #include "nsDirectoryServiceDefs.h"
 #include "nsXULAppAPI.h"
 #include "gmp-audio-decode.h"
 #include "gmp-video-decode.h"
+#include "DecoderDoctorDiagnostics.h"
 
 namespace mozilla {
 namespace dom {
 
 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(MediaKeySystemAccess,
                                       mParent)
 NS_IMPL_CYCLE_COLLECTING_ADDREF(MediaKeySystemAccess)
 NS_IMPL_CYCLE_COLLECTING_RELEASE(MediaKeySystemAccess)
@@ -316,129 +317,144 @@ MediaKeySystemAccess::GetKeySystemStatus
   }
 #endif
 
   return MediaKeySystemStatus::Cdm_not_supported;
 }
 
 static bool
 GMPDecryptsAndDecodesAAC(mozIGeckoMediaPluginService* aGMPS,
-                         const nsAString& aKeySystem)
+                         const nsAString& aKeySystem,
+                         DecoderDoctorDiagnostics* aDiagnostics)
 {
   MOZ_ASSERT(HaveGMPFor(aGMPS,
                         NS_ConvertUTF16toUTF8(aKeySystem),
                         NS_LITERAL_CSTRING(GMP_API_DECRYPTOR)));
   return HaveGMPFor(aGMPS,
                     NS_ConvertUTF16toUTF8(aKeySystem),
                     NS_LITERAL_CSTRING(GMP_API_AUDIO_DECODER),
                     NS_LITERAL_CSTRING("aac"));
 }
 
 static bool
 GMPDecryptsAndDecodesH264(mozIGeckoMediaPluginService* aGMPS,
-                          const nsAString& aKeySystem)
+                          const nsAString& aKeySystem,
+                          DecoderDoctorDiagnostics* aDiagnostics)
 {
   MOZ_ASSERT(HaveGMPFor(aGMPS,
                         NS_ConvertUTF16toUTF8(aKeySystem),
                         NS_LITERAL_CSTRING(GMP_API_DECRYPTOR)));
   return HaveGMPFor(aGMPS,
                     NS_ConvertUTF16toUTF8(aKeySystem),
                     NS_LITERAL_CSTRING(GMP_API_VIDEO_DECODER),
                     NS_LITERAL_CSTRING("h264"));
 }
 
 // If this keysystem's CDM explicitly says it doesn't support decoding,
 // that means it's OK with passing the decrypted samples back to Gecko
 // for decoding.
 static bool
 GMPDecryptsAndGeckoDecodesH264(mozIGeckoMediaPluginService* aGMPService,
                                const nsAString& aKeySystem,
-                               const nsAString& aContentType)
+                               const nsAString& aContentType,
+                               DecoderDoctorDiagnostics* aDiagnostics)
 {
   MOZ_ASSERT(HaveGMPFor(aGMPService,
                         NS_ConvertUTF16toUTF8(aKeySystem),
                         NS_LITERAL_CSTRING(GMP_API_DECRYPTOR)));
   MOZ_ASSERT(IsH264ContentType(aContentType));
   return !HaveGMPFor(aGMPService,
                      NS_ConvertUTF16toUTF8(aKeySystem),
                      NS_LITERAL_CSTRING(GMP_API_VIDEO_DECODER),
                      NS_LITERAL_CSTRING("h264")) &&
-         MP4Decoder::CanHandleMediaType(aContentType,
-                                        /* DecoderDoctorDiagnostics* */ nullptr);
+         MP4Decoder::CanHandleMediaType(aContentType, aDiagnostics);
 }
 
 static bool
 GMPDecryptsAndGeckoDecodesAAC(mozIGeckoMediaPluginService* aGMPService,
                               const nsAString& aKeySystem,
-                              const nsAString& aContentType)
+                              const nsAString& aContentType,
+                              DecoderDoctorDiagnostics* aDiagnostics)
 {
   MOZ_ASSERT(HaveGMPFor(aGMPService,
                         NS_ConvertUTF16toUTF8(aKeySystem),
                         NS_LITERAL_CSTRING(GMP_API_DECRYPTOR)));
   MOZ_ASSERT(IsAACContentType(aContentType));
 
-  return !HaveGMPFor(aGMPService,
-                     NS_ConvertUTF16toUTF8(aKeySystem),
-                     NS_LITERAL_CSTRING(GMP_API_AUDIO_DECODER),
-                     NS_LITERAL_CSTRING("aac")) &&
+  if (HaveGMPFor(aGMPService,
+    NS_ConvertUTF16toUTF8(aKeySystem),
+    NS_LITERAL_CSTRING(GMP_API_AUDIO_DECODER),
+    NS_LITERAL_CSTRING("aac"))) {
+    // We do have a GMP for AAC -> Gecko itself does *not* decode AAC.
+    return false;
+  }
 #if defined(MOZ_WIDEVINE_EME) && defined(XP_WIN)
-         // Widevine CDM doesn't include an AAC decoder. So if WMF can't
-         // decode AAC, and a codec wasn't specified, be conservative
-         // and reject the MediaKeys request, since our policy is to prevent
-         //  the Adobe GMP's unencrypted AAC decoding path being used to
-         // decode content decrypted by the Widevine CDM.
-        (!aKeySystem.EqualsLiteral("com.widevine.alpha") || WMFDecoderModule::HasAAC()) &&
+  // Widevine CDM doesn't include an AAC decoder. So if WMF can't
+  // decode AAC, and a codec wasn't specified, be conservative
+  // and reject the MediaKeys request, since our policy is to prevent
+  //  the Adobe GMP's unencrypted AAC decoding path being used to
+  // decode content decrypted by the Widevine CDM.
+  if (aKeySystem.EqualsLiteral("com.widevine.alpha") &&
+      !WMFDecoderModule::HasAAC()) {
+    if (aDiagnostics) {
+      aDiagnostics->SetKeySystemIssue(
+        DecoderDoctorDiagnostics::eWidevineWithNoWMF);
+    }
+    return false;
+  }
 #endif
-    MP4Decoder::CanHandleMediaType(aContentType,
-                                   /* DecoderDoctorDiagnostics* */ nullptr);
+  return MP4Decoder::CanHandleMediaType(aContentType, aDiagnostics);
 }
 
 static bool
 IsSupportedAudio(mozIGeckoMediaPluginService* aGMPService,
                  const nsAString& aKeySystem,
-                 const nsAString& aAudioType)
+                 const nsAString& aAudioType,
+                 DecoderDoctorDiagnostics* aDiagnostics)
 {
   return IsAACContentType(aAudioType) &&
-         (GMPDecryptsAndDecodesAAC(aGMPService, aKeySystem) ||
-          GMPDecryptsAndGeckoDecodesAAC(aGMPService, aKeySystem, aAudioType));
+         (GMPDecryptsAndDecodesAAC(aGMPService, aKeySystem, aDiagnostics) ||
+          GMPDecryptsAndGeckoDecodesAAC(aGMPService, aKeySystem, aAudioType, aDiagnostics));
 }
 
 static bool
 IsSupportedVideo(mozIGeckoMediaPluginService* aGMPService,
                  const nsAString& aKeySystem,
-                 const nsAString& aVideoType)
+                 const nsAString& aVideoType,
+                 DecoderDoctorDiagnostics* aDiagnostics)
 {
   return IsH264ContentType(aVideoType) &&
-         (GMPDecryptsAndDecodesH264(aGMPService, aKeySystem) ||
-          GMPDecryptsAndGeckoDecodesH264(aGMPService, aKeySystem, aVideoType));
+         (GMPDecryptsAndDecodesH264(aGMPService, aKeySystem, aDiagnostics) ||
+          GMPDecryptsAndGeckoDecodesH264(aGMPService, aKeySystem, aVideoType, aDiagnostics));
 }
 
 static bool
 IsSupported(mozIGeckoMediaPluginService* aGMPService,
             const nsAString& aKeySystem,
-            const MediaKeySystemConfiguration& aConfig)
+            const MediaKeySystemConfiguration& aConfig,
+            DecoderDoctorDiagnostics* aDiagnostics)
 {
   if (aConfig.mInitDataType.IsEmpty() &&
       aConfig.mAudioType.IsEmpty() &&
       aConfig.mVideoType.IsEmpty()) {
     // Not an old-style request.
     return false;
   }
 
   // Backwards compatibility with legacy MediaKeySystemConfiguration method.
   if (!aConfig.mInitDataType.IsEmpty() &&
       !aConfig.mInitDataType.EqualsLiteral("cenc")) {
     return false;
   }
   if (!aConfig.mAudioType.IsEmpty() &&
-      !IsSupportedAudio(aGMPService, aKeySystem, aConfig.mAudioType)) {
+      !IsSupportedAudio(aGMPService, aKeySystem, aConfig.mAudioType, aDiagnostics)) {
     return false;
   }
   if (!aConfig.mVideoType.IsEmpty() &&
-      !IsSupportedVideo(aGMPService, aKeySystem, aConfig.mVideoType)) {
+      !IsSupportedVideo(aGMPService, aKeySystem, aConfig.mVideoType, aDiagnostics)) {
     return false;
   }
 
   return true;
 }
 
 static bool
 IsSupportedInitDataType(const nsString& aCandidate, const nsAString& aKeySystem)
@@ -453,17 +469,18 @@ IsSupportedInitDataType(const nsString& 
     ) &&
     (aCandidate.EqualsLiteral("keyids") || aCandidate.EqualsLiteral("webm)")));
 }
 
 static bool
 GetSupportedConfig(mozIGeckoMediaPluginService* aGMPService,
                    const nsAString& aKeySystem,
                    const MediaKeySystemConfiguration& aCandidate,
-                   MediaKeySystemConfiguration& aOutConfig)
+                   MediaKeySystemConfiguration& aOutConfig,
+                   DecoderDoctorDiagnostics* aDiagnostics)
 {
   MediaKeySystemConfiguration config;
   config.mLabel = aCandidate.mLabel;
   if (aCandidate.mInitDataTypes.WasPassed()) {
     nsTArray<nsString> initDataTypes;
     for (const nsString& candidate : aCandidate.mInitDataTypes.Value()) {
       if (IsSupportedInitDataType(candidate, aKeySystem)) {
         initDataTypes.AppendElement(candidate);
@@ -473,30 +490,30 @@ GetSupportedConfig(mozIGeckoMediaPluginS
       return false;
     }
     config.mInitDataTypes.Construct();
     config.mInitDataTypes.Value().Assign(initDataTypes);
   }
   if (aCandidate.mAudioCapabilities.WasPassed()) {
     nsTArray<MediaKeySystemMediaCapability> caps;
     for (const MediaKeySystemMediaCapability& cap : aCandidate.mAudioCapabilities.Value()) {
-      if (IsSupportedAudio(aGMPService, aKeySystem, cap.mContentType)) {
+      if (IsSupportedAudio(aGMPService, aKeySystem, cap.mContentType, aDiagnostics)) {
         caps.AppendElement(cap);
       }
     }
     if (caps.IsEmpty()) {
       return false;
     }
     config.mAudioCapabilities.Construct();
     config.mAudioCapabilities.Value().Assign(caps);
   }
   if (aCandidate.mVideoCapabilities.WasPassed()) {
     nsTArray<MediaKeySystemMediaCapability> caps;
     for (const MediaKeySystemMediaCapability& cap : aCandidate.mVideoCapabilities.Value()) {
-      if (IsSupportedVideo(aGMPService, aKeySystem, cap.mContentType)) {
+      if (IsSupportedVideo(aGMPService, aKeySystem, cap.mContentType, aDiagnostics)) {
         caps.AppendElement(cap);
       }
     }
     if (caps.IsEmpty()) {
       return false;
     }
     config.mVideoCapabilities.Construct();
     config.mVideoCapabilities.Value().Assign(caps);
@@ -504,72 +521,79 @@ GetSupportedConfig(mozIGeckoMediaPluginS
 
 #if defined(MOZ_WIDEVINE_EME) && defined(XP_WIN)
   // Widevine CDM doesn't include an AAC decoder. So if WMF can't decode AAC,
   // and a codec wasn't specified, be conservative and reject the MediaKeys request.
   if (aKeySystem.EqualsLiteral("com.widevine.alpha") &&
       (!aCandidate.mAudioCapabilities.WasPassed() ||
        !aCandidate.mVideoCapabilities.WasPassed()) &&
      !WMFDecoderModule::HasAAC()) {
+    if (aDiagnostics) {
+      aDiagnostics->SetKeySystemIssue(
+        DecoderDoctorDiagnostics::eWidevineWithNoWMF);
+    }
     return false;
   }
 #endif
 
   aOutConfig = config;
 
   return true;
 }
 
 // Backwards compatibility with legacy requestMediaKeySystemAccess with fields
 // from old MediaKeySystemOptions dictionary.
 /* static */
 bool
 MediaKeySystemAccess::IsSupported(const nsAString& aKeySystem,
-                                  const Sequence<MediaKeySystemConfiguration>& aConfigs)
+                                  const Sequence<MediaKeySystemConfiguration>& aConfigs,
+                                  DecoderDoctorDiagnostics* aDiagnostics)
 {
   nsCOMPtr<mozIGeckoMediaPluginService> mps =
     do_GetService("@mozilla.org/gecko-media-plugin-service;1");
   if (NS_WARN_IF(!mps)) {
     return false;
   }
 
   if (!HaveGMPFor(mps,
                   NS_ConvertUTF16toUTF8(aKeySystem),
                   NS_LITERAL_CSTRING(GMP_API_DECRYPTOR))) {
     return false;
   }
 
   for (const MediaKeySystemConfiguration& config : aConfigs) {
-    if (mozilla::dom::IsSupported(mps, aKeySystem, config)) {
+    if (mozilla::dom::IsSupported(mps, aKeySystem, config, aDiagnostics)) {
       return true;
     }
   }
   return false;
 }
 
 /* static */
 bool
 MediaKeySystemAccess::GetSupportedConfig(const nsAString& aKeySystem,
                                          const Sequence<MediaKeySystemConfiguration>& aConfigs,
-                                         MediaKeySystemConfiguration& aOutConfig)
+                                         MediaKeySystemConfiguration& aOutConfig,
+                                         DecoderDoctorDiagnostics* aDiagnostics)
 {
   nsCOMPtr<mozIGeckoMediaPluginService> mps =
     do_GetService("@mozilla.org/gecko-media-plugin-service;1");
   if (NS_WARN_IF(!mps)) {
     return false;
   }
 
   if (!HaveGMPFor(mps,
                   NS_ConvertUTF16toUTF8(aKeySystem),
                   NS_LITERAL_CSTRING(GMP_API_DECRYPTOR))) {
     return false;
   }
 
   for (const MediaKeySystemConfiguration& config : aConfigs) {
-    if (mozilla::dom::GetSupportedConfig(mps, aKeySystem, config, aOutConfig)) {
+    if (mozilla::dom::GetSupportedConfig(
+          mps, aKeySystem, config, aOutConfig, aDiagnostics)) {
       return true;
     }
   }
 
   return false;
 }
 
 
--- a/dom/media/eme/MediaKeySystemAccess.h
+++ b/dom/media/eme/MediaKeySystemAccess.h
@@ -14,16 +14,19 @@
 
 #include "mozilla/dom/Promise.h"
 #include "mozilla/dom/MediaKeySystemAccessBinding.h"
 #include "mozilla/dom/MediaKeysRequestStatusBinding.h"
 
 #include "js/TypeDecls.h"
 
 namespace mozilla {
+
+class DecoderDoctorDiagnostics;
+
 namespace dom {
 
 class MediaKeySystemAccess final : public nsISupports,
                                    public nsWrapperCache
 {
 public:
   NS_DECL_CYCLE_COLLECTING_ISUPPORTS
   NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(MediaKeySystemAccess)
@@ -51,29 +54,31 @@ public:
 
 
   static MediaKeySystemStatus GetKeySystemStatus(const nsAString& aKeySystem,
                                                  int32_t aMinCdmVersion,
                                                  nsACString& aOutExceptionMessage,
                                                  nsACString& aOutCdmVersion);
 
   static bool IsSupported(const nsAString& aKeySystem,
-                          const Sequence<MediaKeySystemConfiguration>& aConfigs);
+                          const Sequence<MediaKeySystemConfiguration>& aConfigs,
+                          DecoderDoctorDiagnostics* aDiagnostics);
 
   static void NotifyObservers(nsPIDOMWindowInner* aWindow,
                               const nsAString& aKeySystem,
                               MediaKeySystemStatus aStatus);
 
   static bool IsGMPPresentOnDisk(const nsAString& aKeySystem,
                                  const nsACString& aVersion,
                                  nsACString& aOutMessage);
 
   static bool GetSupportedConfig(const nsAString& aKeySystem,
                                  const Sequence<MediaKeySystemConfiguration>& aConfigs,
-                                 MediaKeySystemConfiguration& aOutConfig);
+                                 MediaKeySystemConfiguration& aOutConfig,
+                                 DecoderDoctorDiagnostics* aDiagnostics);
 
 private:
   nsCOMPtr<nsPIDOMWindowInner> mParent;
   const nsString mKeySystem;
   const nsString mCDMVersion;
   const MediaKeySystemConfiguration mConfig;
 };
 
--- a/dom/media/eme/MediaKeySystemAccessManager.cpp
+++ b/dom/media/eme/MediaKeySystemAccessManager.cpp
@@ -1,13 +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/. */
 
 #include "MediaKeySystemAccessManager.h"
+#include "DecoderDoctorDiagnostics.h"
 #include "mozilla/Preferences.h"
 #include "mozilla/EMEUtils.h"
 #include "nsServiceManagerUtils.h"
 #include "nsComponentManagerUtils.h"
 #include "nsIObserverService.h"
 #include "mozilla/Services.h"
 #include "mozilla/DetailedPromise.h"
 #ifdef XP_WIN
@@ -75,35 +76,41 @@ MediaKeySystemAccessManager::Request(Det
 void
 MediaKeySystemAccessManager::Request(DetailedPromise* aPromise,
                                      const nsAString& aKeySystem,
                                      const Sequence<MediaKeySystemConfiguration>& aConfigs,
                                      RequestType aType)
 {
   EME_LOG("MediaKeySystemAccessManager::Request %s", NS_ConvertUTF16toUTF8(aKeySystem).get());
 
+  DecoderDoctorDiagnostics diagnostics;
+
   // Parse keysystem, split it out into keySystem prefix, and version suffix.
   nsAutoString keySystem;
   int32_t minCdmVersion = NO_CDM_VERSION;
   if (!ParseKeySystem(aKeySystem, keySystem, minCdmVersion)) {
     // Not to inform user, because nothing to do if the keySystem is not
     // supported.
     aPromise->MaybeReject(NS_ERROR_DOM_NOT_SUPPORTED_ERR,
                           NS_LITERAL_CSTRING("Key system string is invalid,"
                                              " or key system is unsupported"));
+    diagnostics.StoreMediaKeySystemAccess(mWindow->GetExtantDoc(),
+                                          aKeySystem, false, __func__);
     return;
   }
 
   if (!Preferences::GetBool("media.eme.enabled", false)) {
     // EME disabled by user, send notification to chrome so UI can inform user.
     MediaKeySystemAccess::NotifyObservers(mWindow,
                                           aKeySystem,
                                           MediaKeySystemStatus::Api_disabled);
     aPromise->MaybeReject(NS_ERROR_DOM_NOT_SUPPORTED_ERR,
                           NS_LITERAL_CSTRING("EME has been preffed off"));
+    diagnostics.StoreMediaKeySystemAccess(mWindow->GetExtantDoc(),
+                                          aKeySystem, false, __func__);
     return;
   }
 
   nsAutoCString message;
   nsAutoCString cdmVersion;
   MediaKeySystemStatus status =
     MediaKeySystemAccess::GetKeySystemStatus(keySystem, minCdmVersion, message, cdmVersion);
 
@@ -136,46 +143,54 @@ MediaKeySystemAccessManager::Request(Det
       MediaKeySystemAccess::NotifyObservers(mWindow, keySystem, status);
     } else {
       // We waited or can't wait for an update and we still can't service
       // the request. Give up. Chrome will still be showing a "I can't play,
       // updating" notification.
       aPromise->MaybeReject(NS_ERROR_DOM_NOT_SUPPORTED_ERR,
                             NS_LITERAL_CSTRING("Gave up while waiting for a CDM update"));
     }
+    diagnostics.StoreMediaKeySystemAccess(mWindow->GetExtantDoc(),
+                                          aKeySystem, false, __func__);
     return;
   }
   if (status != MediaKeySystemStatus::Available) {
     if (status != MediaKeySystemStatus::Error) {
       // Failed due to user disabling something, send a notification to
       // chrome, so we can show some UI to explain how the user can rectify
       // the situation.
       MediaKeySystemAccess::NotifyObservers(mWindow, keySystem, status);
       aPromise->MaybeReject(NS_ERROR_DOM_NOT_SUPPORTED_ERR, message);
       return;
     }
     aPromise->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR,
                           NS_LITERAL_CSTRING("GetKeySystemAccess failed"));
+    diagnostics.StoreMediaKeySystemAccess(mWindow->GetExtantDoc(),
+                                          aKeySystem, false, __func__);
     return;
   }
 
   MediaKeySystemConfiguration config;
   // TODO: Remove IsSupported() check here once we remove backwards
   // compatibility with initial implementation...
-  if (MediaKeySystemAccess::GetSupportedConfig(keySystem, aConfigs, config) ||
-      MediaKeySystemAccess::IsSupported(keySystem, aConfigs)) {
+  if (MediaKeySystemAccess::GetSupportedConfig(keySystem, aConfigs, config, &diagnostics) ||
+      MediaKeySystemAccess::IsSupported(keySystem, aConfigs, &diagnostics)) {
     RefPtr<MediaKeySystemAccess> access(
       new MediaKeySystemAccess(mWindow, keySystem, NS_ConvertUTF8toUTF16(cdmVersion), config));
     aPromise->MaybeResolve(access);
+    diagnostics.StoreMediaKeySystemAccess(mWindow->GetExtantDoc(),
+                                          aKeySystem, true, __func__);
     return;
   }
   // Not to inform user, because nothing to do if the corresponding keySystem
   // configuration is not supported.
   aPromise->MaybeReject(NS_ERROR_DOM_NOT_SUPPORTED_ERR,
                         NS_LITERAL_CSTRING("Key system configuration is not supported"));
+  diagnostics.StoreMediaKeySystemAccess(mWindow->GetExtantDoc(),
+                                        aKeySystem, false, __func__);
 }
 
 MediaKeySystemAccessManager::PendingRequest::PendingRequest(DetailedPromise* aPromise,
                                                             const nsAString& aKeySystem,
                                                             const Sequence<MediaKeySystemConfiguration>& aConfigs,
                                                             nsITimer* aTimer)
   : mPromise(aPromise)
   , mKeySystem(aKeySystem)
--- a/dom/media/mediasource/MediaSource.cpp
+++ b/dom/media/mediasource/MediaSource.cpp
@@ -109,36 +109,30 @@ IsTypeSupported(const nsAString& aType, 
           return NS_ERROR_DOM_NOT_SUPPORTED_ERR;
         }
         if (hasCodecs &&
             DecoderTraits::CanHandleCodecsType(mimeTypeUTF8.get(),
                                                codecs,
                                                aDiagnostics) == CANPLAY_NO) {
           return NS_ERROR_DOM_NOT_SUPPORTED_ERR;
         }
-        if (aDiagnostics) {
-          aDiagnostics->SetCanPlay();
-        }
         return NS_OK;
       } else if (DecoderTraits::IsWebMTypeAndEnabled(mimeTypeUTF8)) {
         if (!(Preferences::GetBool("media.mediasource.webm.enabled", false) ||
               (Preferences::GetBool("media.mediasource.webm.audio.enabled", true) &&
                DecoderTraits::IsWebMAudioType(mimeTypeUTF8)) ||
               IsWebMForced(aDiagnostics))) {
           return NS_ERROR_DOM_NOT_SUPPORTED_ERR;
         }
         if (hasCodecs &&
             DecoderTraits::CanHandleCodecsType(mimeTypeUTF8.get(),
                                                codecs,
                                                aDiagnostics) == CANPLAY_NO) {
           return NS_ERROR_DOM_NOT_SUPPORTED_ERR;
         }
-        if (aDiagnostics) {
-          aDiagnostics->SetCanPlay();
-        }
         return NS_OK;
       }
     }
   }
 
   return NS_ERROR_DOM_NOT_SUPPORTED_ERR;
 }
 
@@ -227,26 +221,20 @@ MediaSource::SetDuration(double aDuratio
 }
 
 already_AddRefed<SourceBuffer>
 MediaSource::AddSourceBuffer(const nsAString& aType, ErrorResult& aRv)
 {
   MOZ_ASSERT(NS_IsMainThread());
   DecoderDoctorDiagnostics diagnostics;
   nsresult rv = mozilla::IsTypeSupported(aType, &diagnostics);
-  if (NS_SUCCEEDED(rv)) {
-    diagnostics.SetCanPlay();
-  }
-  if (GetOwner()) {
-    diagnostics.StoreDiagnostics(GetOwner()->GetExtantDoc(), aType,
-                                 "AddSourceBuffer with owner window's doc");
-  } else {
-    diagnostics.StoreDiagnostics(nullptr, aType,
-                                 "AddSourceBuffer with nothing");
-  }
+  diagnostics.StoreFormatDiagnostics(GetOwner()
+                                     ? GetOwner()->GetExtantDoc()
+                                     : nullptr,
+                                     aType, NS_SUCCEEDED(rv), __func__);
   MSE_API("AddSourceBuffer(aType=%s)%s",
           NS_ConvertUTF16toUTF8(aType).get(),
           rv == NS_OK ? "" : " [not supported]");
   if (NS_FAILED(rv)) {
     aRv.Throw(rv);
     return nullptr;
   }
   if (mSourceBuffers->Length() >= MAX_SOURCE_BUFFERS) {
@@ -357,27 +345,19 @@ MediaSource::EndOfStream(const Optional<
 }
 
 /* static */ bool
 MediaSource::IsTypeSupported(const GlobalObject& aOwner, const nsAString& aType)
 {
   MOZ_ASSERT(NS_IsMainThread());
   DecoderDoctorDiagnostics diagnostics;
   nsresult rv = mozilla::IsTypeSupported(aType, &diagnostics);
-  if (NS_SUCCEEDED(rv)) {
-    diagnostics.SetCanPlay();
-  }
   nsCOMPtr<nsPIDOMWindowInner> window = do_QueryInterface(aOwner.GetAsSupports());
-  if (window) {
-    diagnostics.StoreDiagnostics(window->GetExtantDoc(), aType,
-                                 "IsTypeSupported with aOwner window's doc");
-  } else {
-    diagnostics.StoreDiagnostics(nullptr, aType,
-                                 "IsTypeSupported with nothing");
-  }
+  diagnostics.StoreFormatDiagnostics(window ? window->GetExtantDoc() : nullptr,
+                                     aType, NS_SUCCEEDED(rv), __func__);
 #define this nullptr
   MSE_API("IsTypeSupported(aType=%s)%s ",
           NS_ConvertUTF16toUTF8(aType).get(), rv == NS_OK ? "OK" : "[not supported]");
 #undef this // don't ever remove this line !
   return NS_SUCCEEDED(rv);
 }
 
 /* static */ bool
--- a/dom/media/mediasource/test/mediasource.js
+++ b/dom/media/mediasource/test/mediasource.js
@@ -14,16 +14,17 @@ function runWithMSE(testFunction) {
     });
 
     testFunction(ms, el);
   }
 
   addLoadEvent(function () {
     SpecialPowers.pushPrefEnv({"set": [
       [ "media.mediasource.enabled", true ],
+      [ "media.test.dumpDebugInfo", true ],
     ]},
                               bootstrapTest);
   });
 }
 
 function fetchWithXHR(uri, onLoadFunction) {
   var p = new Promise(function(resolve, reject) {
     var xhr = new XMLHttpRequest();
@@ -100,8 +101,18 @@ function fetchAndLoad(sb, prefix, chunks
   return Promise.all(fetches).then(function() {
     var rv = Promise.resolve();
     for (var chunk of chunks) {
       rv = rv.then(loadSegment.bind(null, sb, buffers[chunk]));
     }
     return rv;
   });
 }
+
+//Register timeout function to dump debugging logs.
+SimpleTest.registerTimeoutFunction(function() {
+  for (var v of document.getElementsByTagName("video")) {
+    v.mozDumpDebugInfo();
+  }
+  for (var a of document.getElementsByTagName("audio")) {
+    a.mozDumpDebugInfo();
+  }
+});
\ No newline at end of file
--- a/dom/media/omx/MediaOmxReader.h
+++ b/dom/media/omx/MediaOmxReader.h
@@ -43,17 +43,17 @@ class MediaOmxReader : public MediaOmxCo
   int32_t mSkipCount;
   // If mIsShutdown is false, and mShutdownMutex is held, then
   // AbstractMediaDecoder::mDecoder will be non-null.
   bool mIsShutdown;
   MozPromiseHolder<MediaDecoderReader::MetadataPromise> mMetadataPromise;
   MozPromiseRequestHolder<MediaResourcePromise> mMediaResourceRequest;
 
   MozPromiseHolder<MediaDecoderReader::SeekPromise> mSeekPromise;
-  MozPromiseRequestHolder<MediaDecoderReader::VideoDataPromise> mSeekRequest;
+  MozPromiseRequestHolder<MediaDecoderReader::MediaDataPromise> mSeekRequest;
 protected:
   android::sp<android::OmxDecoder> mOmxDecoder;
   android::sp<android::MediaExtractor> mExtractor;
   MP3FrameParser mMP3FrameParser;
 
   // Called by ReadMetadata() during MediaDecoderStateMachine::DecodeMetadata()
   // on decode thread. It create and initialize the OMX decoder including
   // setting up custom extractor. The extractor provide the essential
--- a/dom/media/platforms/PDMFactory.cpp
+++ b/dom/media/platforms/PDMFactory.cpp
@@ -160,19 +160,25 @@ PDMFactory::CreateDecoder(const TrackInf
                                 aDiagnostics,
                                 aLayersBackend,
                                 aImageContainer);
   }
 
   if (aDiagnostics) {
     // If libraries failed to load, the following loop over mCurrentPDMs
     // will not even try to use them. So we record failures now.
+    if (mWMFFailedToLoad) {
+      aDiagnostics->SetWMFFailedToLoad();
+    }
     if (mFFmpegFailedToLoad) {
       aDiagnostics->SetFFmpegFailedToLoad();
     }
+    if (mGMPPDMFailedToStartup) {
+      aDiagnostics->SetGMPPDMFailedToStartup();
+    }
   }
 
   for (auto& current : mCurrentPDMs) {
     if (!current->SupportsMimeType(aConfig.mMimeType, aDiagnostics)) {
       continue;
     }
     RefPtr<MediaDataDecoder> m =
       CreateDecoderWithPDM(current,
@@ -285,17 +291,19 @@ PDMFactory::CreatePDMs()
   if(sAndroidMCDecoderPreferred && sAndroidMCDecoderEnabled) {
     m = new AndroidDecoderModule();
     StartupPDM(m);
   }
 #endif
 #ifdef XP_WIN
   if (sWMFDecoderEnabled) {
     m = new WMFDecoderModule();
-    StartupPDM(m);
+    if (!StartupPDM(m)) {
+      mWMFFailedToLoad = true;
+    }
   }
 #endif
 #ifdef MOZ_FFVPX
   if (sFFVPXDecoderEnabled) {
     m = FFVPXRuntimeLinker::CreateDecoderModule();
     StartupPDM(m);
   }
 #endif
@@ -324,18 +332,20 @@ PDMFactory::CreatePDMs()
   }
 #endif
 
   m = new AgnosticDecoderModule();
   StartupPDM(m);
 
   if (sGMPDecoderEnabled) {
     m = new GMPDecoderModule();
-    StartupPDM(m);
-  }  
+    if (!StartupPDM(m)) {
+      mGMPPDMFailedToStartup = true;
+    }
+  }
 }
 
 bool
 PDMFactory::StartupPDM(PlatformDecoderModule* aPDM)
 {
   if (aPDM && NS_SUCCEEDED(aPDM->Startup())) {
     mCurrentPDMs.AppendElement(aPDM);
     return true;
@@ -345,19 +355,25 @@ PDMFactory::StartupPDM(PlatformDecoderMo
 
 already_AddRefed<PlatformDecoderModule>
 PDMFactory::GetDecoder(const nsACString& aMimeType,
                        DecoderDoctorDiagnostics* aDiagnostics) const
 {
   if (aDiagnostics) {
     // If libraries failed to load, the following loop over mCurrentPDMs
     // will not even try to use them. So we record failures now.
+    if (mWMFFailedToLoad) {
+      aDiagnostics->SetWMFFailedToLoad();
+    }
     if (mFFmpegFailedToLoad) {
       aDiagnostics->SetFFmpegFailedToLoad();
     }
+    if (mGMPPDMFailedToStartup) {
+      aDiagnostics->SetGMPPDMFailedToStartup();
+    }
   }
 
   RefPtr<PlatformDecoderModule> pdm;
   for (auto& current : mCurrentPDMs) {
     if (current->SupportsMimeType(aMimeType, aDiagnostics)) {
       pdm = current;
       break;
     }
--- a/dom/media/platforms/PDMFactory.h
+++ b/dom/media/platforms/PDMFactory.h
@@ -90,14 +90,16 @@ private:
 #endif
   static bool sEnableFuzzingWrapper;
   static uint32_t sVideoOutputMinimumInterval_ms;
   static bool sDontDelayInputExhausted;
 
   nsTArray<RefPtr<PlatformDecoderModule>> mCurrentPDMs;
   RefPtr<PlatformDecoderModule> mEMEPDM;
 
+  bool mWMFFailedToLoad = false;
   bool mFFmpegFailedToLoad = false;
+  bool mGMPPDMFailedToStartup = false;
 };
 
 } // namespace mozilla
 
 #endif /* PDMFactory_h_ */
--- a/dom/media/platforms/agnostic/gmp/GMPDecoderModule.cpp
+++ b/dom/media/platforms/agnostic/gmp/GMPDecoderModule.cpp
@@ -1,15 +1,16 @@
 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim:set ts=2 sw=2 sts=2 et cindent: */
 /* 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 "GMPDecoderModule.h"
+#include "DecoderDoctorDiagnostics.h"
 #include "GMPAudioDecoder.h"
 #include "GMPVideoDecoder.h"
 #include "MediaDataDecoderProxy.h"
 #include "mozIGeckoMediaPluginService.h"
 #include "nsServiceManagerUtils.h"
 #include "mozilla/Preferences.h"
 #include "mozilla/StaticMutex.h"
 #include "gmp-audio-decode.h"
@@ -50,16 +51,23 @@ GMPDecoderModule::CreateVideoDecoder(con
                                      FlushableTaskQueue* aVideoTaskQueue,
                                      MediaDataDecoderCallback* aCallback,
                                      DecoderDoctorDiagnostics* aDiagnostics)
 {
   if (!aConfig.mMimeType.EqualsLiteral("video/avc")) {
     return nullptr;
   }
 
+  if (aDiagnostics) {
+    const Maybe<nsCString> preferredGMP = PreferredGMP(aConfig.mMimeType);
+    if (preferredGMP.isSome()) {
+      aDiagnostics->SetGMP(preferredGMP.value());
+    }
+  }
+
   RefPtr<MediaDataDecoderProxy> wrapper = CreateDecoderWrapper(aCallback);
   wrapper->SetProxyTarget(new GMPVideoDecoder(aConfig,
                                               aLayersBackend,
                                               aImageContainer,
                                               aVideoTaskQueue,
                                               wrapper->Callback()));
   return wrapper.forget();
 }
@@ -69,16 +77,23 @@ GMPDecoderModule::CreateAudioDecoder(con
                                      FlushableTaskQueue* aAudioTaskQueue,
                                      MediaDataDecoderCallback* aCallback,
                                      DecoderDoctorDiagnostics* aDiagnostics)
 {
   if (!aConfig.mMimeType.EqualsLiteral("audio/mp4a-latm")) {
     return nullptr;
   }
 
+  if (aDiagnostics) {
+    const Maybe<nsCString> preferredGMP = PreferredGMP(aConfig.mMimeType);
+    if (preferredGMP.isSome()) {
+      aDiagnostics->SetGMP(preferredGMP.value());
+    }
+  }
+
   RefPtr<MediaDataDecoderProxy> wrapper = CreateDecoderWrapper(aCallback);
   wrapper->SetProxyTarget(new GMPAudioDecoder(aConfig,
                                               aAudioTaskQueue,
                                               wrapper->Callback()));
   return wrapper.forget();
 }
 
 PlatformDecoderModule::ConversionRequired
@@ -227,12 +242,17 @@ GMPDecoderModule::SupportsMimeType(const
 
   return false;
 }
 
 bool
 GMPDecoderModule::SupportsMimeType(const nsACString& aMimeType,
                                    DecoderDoctorDiagnostics* aDiagnostics) const
 {
-  return SupportsMimeType(aMimeType, PreferredGMP(aMimeType));
+  const Maybe<nsCString> preferredGMP = PreferredGMP(aMimeType);
+  bool rv = SupportsMimeType(aMimeType, preferredGMP);
+  if (rv && aDiagnostics && preferredGMP.isSome()) {
+    aDiagnostics->SetGMP(preferredGMP.value());
+  }
+  return rv;
 }
 
 } // namespace mozilla
--- a/dom/media/test/manifest.js
+++ b/dom/media/test/manifest.js
@@ -1410,17 +1410,18 @@ function Log(token, msg) {
 // Number of tests to run in parallel.
 var PARALLEL_TESTS = 2;
 
 // Prefs to set before running tests.  Use this to improve coverage of
 // conditions that might not otherwise be encountered on the test data.
 var gTestPrefs = [
   ['media.recorder.max_memory', 1024],
   ["media.preload.default", 2], // default preload = metadata
-  ["media.preload.auto", 3] // auto preload = enough
+  ["media.preload.auto", 3], // auto preload = enough
+  ["media.test.dumpDebugInfo", true],
 ];
 
 // When true, we'll loop forever on whatever test we run. Use this to debug
 // intermittent test failures.
 const DEBUG_TEST_LOOP_FOREVER = false;
 
 // Manages a run of media tests. Runs them in chunks in order to limit
 // the number of media elements/threads running in parallel. This limits peak
@@ -1582,9 +1583,19 @@ function setMediaTestsPrefs(callback, ex
 function isSlowPlatform() {
   return SpecialPowers.Services.appinfo.name == "B2G" || getAndroidVersion() == 10;
 }
 
 // Could be undefined in a page opened by the parent test page
 // like file_access_controls.html.
 if ("SimpleTest" in window) {
   SimpleTest.requestFlakyTimeout("untriaged");
+
+  // Register timeout function to dump debugging logs.
+  SimpleTest.registerTimeoutFunction(function() {
+    for (var v of document.getElementsByTagName("video")) {
+      v.mozDumpDebugInfo();
+    }
+    for (var a of document.getElementsByTagName("audio")) {
+      a.mozDumpDebugInfo();
+    }
+  });
 }
--- a/dom/media/webaudio/AudioBuffer.h
+++ b/dom/media/webaudio/AudioBuffer.h
@@ -65,17 +65,17 @@ public:
 
   JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
 
   float SampleRate() const
   {
     return mSampleRate;
   }
 
-  int32_t Length() const
+  uint32_t Length() const
   {
     return mLength;
   }
 
   double Duration() const
   {
     return mLength / static_cast<double> (mSampleRate);
   }
--- a/dom/media/webaudio/AudioBufferSourceNode.cpp
+++ b/dom/media/webaudio/AudioBufferSourceNode.cpp
@@ -5,16 +5,17 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "AudioBufferSourceNode.h"
 #include "mozilla/dom/AudioBufferSourceNodeBinding.h"
 #include "mozilla/dom/AudioParam.h"
 #include "mozilla/FloatingPoint.h"
 #include "nsContentUtils.h"
 #include "nsMathUtils.h"
+#include "AlignmentUtils.h"
 #include "AudioNodeEngine.h"
 #include "AudioNodeStream.h"
 #include "AudioDestinationNode.h"
 #include "AudioParamTimeline.h"
 #include <limits>
 #include <algorithm>
 
 namespace mozilla {
@@ -405,17 +406,25 @@ public:
       TrackTicks end = (*aCurrentPosition + availableInOutput) *
         mBufferSampleRate / mResamplerOutRate;
       mBufferPosition += end - start;
       return;
     }
 
     uint32_t numFrames = std::min(aBufferMax - mBufferPosition,
                                   availableInOutput);
-    if (numFrames == WEBAUDIO_BLOCK_SIZE) {
+
+    bool inputBufferAligned = true;
+    for (uint32_t i = 0; i < aChannels; ++i) {
+      if (!IS_ALIGNED16(mBuffer->GetData(i) + mBufferPosition)) {
+        inputBufferAligned = false;
+      }
+    }
+
+    if (numFrames == WEBAUDIO_BLOCK_SIZE && inputBufferAligned) {
       MOZ_ASSERT(mBufferPosition < aBufferMax);
       BorrowFromInputBuffer(aOutput, aChannels);
     } else {
       if (*aOffsetWithinBlock == 0) {
         aOutput->AllocateChannels(aChannels);
       }
       MOZ_ASSERT(mBufferPosition < aBufferMax);
       CopyFromInputBuffer(aOutput, aChannels, *aOffsetWithinBlock, numFrames);
deleted file mode 100644
--- a/dom/plugins/test/testplugin/Makefile.in
+++ /dev/null
@@ -1,8 +0,0 @@
-#
-# This Source Code Form is subject to the terms of the Mozilla Public
-# License, v. 2.0. If a copy of the MPL was not distributed with this
-# file, You can obtain one at http://mozilla.org/MPL/2.0/.
-
-RELATIVE_PATH=.
-COCOA_NAME=Test
-include @srcdir@/testplugin.mk
deleted file mode 100644
--- a/dom/plugins/test/testplugin/flashplugin/Makefile.in
+++ /dev/null
@@ -1,9 +0,0 @@
-#
-# This Source Code Form is subject to the terms of the Mozilla Public
-# License, v. 2.0. If a copy of the MPL was not distributed with this file,
-# You can obtain one at http://mozilla.org/MPL/2.0/.
-
-RELATIVE_PATH=..
-COCOA_NAME=npswftest
-include @srcdir@/../testplugin.mk
-
--- a/dom/plugins/test/testplugin/flashplugin/moz.build
+++ b/dom/plugins/test/testplugin/flashplugin/moz.build
@@ -2,9 +2,10 @@
 # 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/.
 
 SharedLibrary('npswftest')
 
 relative_path = 'flashplugin'
+cocoa_name = 'npswftest'
 include('../testplugin.mozbuild')
deleted file mode 100644
--- a/dom/plugins/test/testplugin/javaplugin/Makefile.in
+++ /dev/null
@@ -1,8 +0,0 @@
-#
-# This Source Code Form is subject to the terms of the Mozilla Public
-# License, v. 2.0. If a copy of the MPL was not distributed with this file,
-# You can obtain one at http://mozilla.org/MPL/2.0/.
-
-RELATIVE_PATH=..
-COCOA_NAME=JavaTest
-include @srcdir@/../testplugin.mk
--- a/dom/plugins/test/testplugin/javaplugin/moz.build
+++ b/dom/plugins/test/testplugin/javaplugin/moz.build
@@ -2,9 +2,10 @@
 # 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/.
 
 SharedLibrary('nptestjava')
 
 relative_path = 'javaplugin'
+cocoa_name = 'JavaTest'
 include('../testplugin.mozbuild')
--- a/dom/plugins/test/testplugin/moz.build
+++ b/dom/plugins/test/testplugin/moz.build
@@ -4,9 +4,10 @@
 # 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/.
 
 DIRS += ['secondplugin', 'javaplugin', 'thirdplugin', 'flashplugin', 'silverlightplugin']
 
 SharedLibrary('nptest')
 
 relative_path = '.'
+cocoa_name = 'Test'
 include('testplugin.mozbuild')
deleted file mode 100644
--- a/dom/plugins/test/testplugin/secondplugin/Makefile.in
+++ /dev/null
@@ -1,8 +0,0 @@
-#
-# This Source Code Form is subject to the terms of the Mozilla Public
-# License, v. 2.0. If a copy of the MPL was not distributed with this file,
-# You can obtain one at http://mozilla.org/MPL/2.0/.
-
-RELATIVE_PATH=..
-COCOA_NAME=SecondTest
-include @srcdir@/../testplugin.mk
--- a/dom/plugins/test/testplugin/secondplugin/moz.build
+++ b/dom/plugins/test/testplugin/secondplugin/moz.build
@@ -2,9 +2,10 @@
 # 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/.
 
 SharedLibrary('npsecondtest')
 
 relative_path = 'secondplugin'
+cocoa_name = 'SecondTest'
 include('../testplugin.mozbuild')
deleted file mode 100644
--- a/dom/plugins/test/testplugin/silverlightplugin/Makefile.in
+++ /dev/null
@@ -1,9 +0,0 @@
-#
-# This Source Code Form is subject to the terms of the Mozilla Public
-# License, v. 2.0. If a copy of the MPL was not distributed with this file,
-# You can obtain one at http://mozilla.org/MPL/2.0/.
-
-RELATIVE_PATH=..
-COCOA_NAME=npctrltest
-include @srcdir@/../testplugin.mk
-
--- a/dom/plugins/test/testplugin/silverlightplugin/moz.build
+++ b/dom/plugins/test/testplugin/silverlightplugin/moz.build
@@ -2,9 +2,10 @@
 # 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/.
 
 SharedLibrary('npctrltest')
 
 relative_path = 'silverlightplugin'
+cocoa_name = 'npctrltest'
 include('../testplugin.mozbuild')
deleted file mode 100644
--- a/dom/plugins/test/testplugin/testplugin.mk
+++ /dev/null
@@ -1,19 +0,0 @@
-#
-# This Source Code Form is subject to the terms of the Mozilla Public
-# License, v. 2.0. If a copy of the MPL was not distributed with this file,
-# You can obtain one at http://mozilla.org/MPL/2.0/.
-
-TEST_PLUGIN_FILES = $(SHARED_LIBRARY)
-
-ifeq ($(MOZ_WIDGET_TOOLKIT),cocoa)
-MAC_PLIST_FILES += $(srcdir)/Info.plist
-MAC_PLIST_DEST   = $(DIST)/plugins/$(COCOA_NAME).plugin/Contents
-TEST_PLUGIN_DEST = $(DIST)/plugins/$(COCOA_NAME).plugin/Contents/MacOS
-INSTALL_TARGETS += \
-	TEST_PLUGIN \
-	MAC_PLIST \
-	$(NULL)
-else
-TEST_PLUGIN_DEST = $(DIST)/plugins
-INSTALL_TARGETS += TEST_PLUGIN
-endif
--- a/dom/plugins/test/testplugin/testplugin.mozbuild
+++ b/dom/plugins/test/testplugin/testplugin.mozbuild
@@ -1,16 +1,14 @@
 # -*- 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/.
 
-DIST_INSTALL = False
-
 UNIFIED_SOURCES += [
     'nptest.cpp',
     'nptest_utils.cpp',
 ]
 
 UNIFIED_SOURCES += [
     '%s/nptest_name.cpp' % relative_path,
 ]
@@ -69,8 +67,14 @@ if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'qt':
     OS_LIBS += CONFIG['MOZ_QT_LIBS']
     OS_LIBS += CONFIG['XLDFLAGS']
     OS_LIBS += CONFIG['XLIBS']
 
 if CONFIG['_MSC_VER']:
     # This is intended as a temporary hack to support building with VS2015.
     # conversion from 'X' to 'Y' requires a narrowing conversion
     CXXFLAGS += ['-wd4838']
+
+if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'cocoa':
+    FINAL_TARGET = 'dist/plugins/%s.plugin/Contents/MacOS' % cocoa_name
+    OBJDIR_FILES.dist.plugins['%s.plugin' % cocoa_name].Contents += ['%s/Info.plist' % relative_path]
+else:
+    FINAL_TARGET = 'dist/plugins'
deleted file mode 100644
--- a/dom/plugins/test/testplugin/thirdplugin/Makefile.in
+++ /dev/null
@@ -1,8 +0,0 @@
-#
-# This Source Code Form is subject to the terms of the Mozilla Public
-# License, v. 2.0. If a copy of the MPL was not distributed with this file,
-# You can obtain one at http://mozilla.org/MPL/2.0/.
-
-RELATIVE_PATH=..
-COCOA_NAME=ThirdTest
-include @srcdir@/../testplugin.mk
--- a/dom/plugins/test/testplugin/thirdplugin/moz.build
+++ b/dom/plugins/test/testplugin/thirdplugin/moz.build
@@ -2,9 +2,10 @@
 # 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/.
 
 SharedLibrary('npthirdtest')
 
 relative_path = 'thirdplugin'
+cocoa_name = 'ThirdTest'
 include('../testplugin.mozbuild')
--- a/dom/push/Push.js
+++ b/dom/push/Push.js
@@ -41,37 +41,37 @@ Push.prototype = {
   contractID: "@mozilla.org/push/PushManager;1",
 
   classID : PUSH_CID,
 
   QueryInterface : XPCOMUtils.generateQI([Ci.nsIDOMGlobalPropertyInitializer,
                                           Ci.nsISupportsWeakReference,
                                           Ci.nsIObserver]),
 
-  init: function(aWindow) {
+  init: function(win) {
     console.debug("init()");
 
-    this._window = aWindow;
+    this._window = win;
 
-    this.initDOMRequestHelper(aWindow);
+    this.initDOMRequestHelper(win);
 
-    this._principal = aWindow.document.nodePrincipal;
+    this._principal = win.document.nodePrincipal;
   },
 
   __init: function(scope) {
     this._scope = scope;
   },
 
-  askPermission: function (aAllowCallback, aCancelCallback) {
+  askPermission: function () {
     console.debug("askPermission()");
 
     return this.createPromise((resolve, reject) => {
       let permissionDenied = () => {
         reject(new this._window.DOMException(
-          "User denied permission to use the Push API",
+          "User denied permission to use the Push API.",
           "PermissionDeniedError"
         ));
       };
 
       let permission = Ci.nsIPermissionManager.UNKNOWN_ACTION;
       try {
         permission = this._testPermission();
       } catch (e) {
@@ -84,25 +84,40 @@ Push.prototype = {
       } else if (permission == Ci.nsIPermissionManager.DENY_ACTION) {
         permissionDenied();
       } else {
         this._requestPermission(resolve, permissionDenied);
       }
     });
   },
 
-  subscribe: function() {
+  subscribe: function(options) {
     console.debug("subscribe()", this._scope);
 
     let histogram = Services.telemetry.getHistogramById("PUSH_API_USED");
     histogram.add(true);
     return this.askPermission().then(() =>
       this.createPromise((resolve, reject) => {
         let callback = new PushSubscriptionCallback(this, resolve, reject);
-        PushService.subscribe(this._scope, this._principal, callback);
+
+        if (!options || !options.applicationServerKey) {
+          PushService.subscribe(this._scope, this._principal, callback);
+          return;
+        }
+
+        let appServerKey = options.applicationServerKey;
+        let keyView = new Uint8Array(ArrayBuffer.isView(appServerKey) ?
+                                     appServerKey.buffer : appServerKey);
+        if (keyView.byteLength === 0) {
+          callback._rejectWithError(Cr.NS_ERROR_DOM_PUSH_INVALID_KEY_ERR);
+          return;
+        }
+        PushService.subscribeWithKey(this._scope, this._principal,
+                                     appServerKey.length, appServerKey,
+                                     callback);
       })
     );
   },
 
   getSubscription: function() {
     console.debug("getSubscription()", this._scope);
 
     return this.createPromise((resolve, reject) => {
@@ -185,43 +200,74 @@ function PushSubscriptionCallback(pushMa
 }
 
 PushSubscriptionCallback.prototype = {
   QueryInterface: XPCOMUtils.generateQI([Ci.nsIPushSubscriptionCallback]),
 
   onPushSubscription: function(ok, subscription) {
     let {pushManager} = this;
     if (!Components.isSuccessCode(ok)) {
-      this.reject(new pushManager._window.DOMException(
-        "Error retrieving push subscription",
-        "AbortError"
-      ));
+      this._rejectWithError(ok);
       return;
     }
 
     if (!subscription) {
       this.resolve(null);
       return;
     }
 
-    let publicKey = this._getKey(subscription, "p256dh");
+    let p256dhKey = this._getKey(subscription, "p256dh");
     let authSecret = this._getKey(subscription, "auth");
-    let sub = new pushManager._window.PushSubscription(subscription.endpoint,
-                                                       pushManager._scope,
-                                                       publicKey,
-                                                       authSecret);
+    let options = {
+      endpoint: subscription.endpoint,
+      scope: pushManager._scope,
+      p256dhKey: p256dhKey,
+      authSecret: authSecret,
+    };
+    let appServerKey = this._getKey(subscription, "appServer");
+    if (appServerKey) {
+      // Avoid passing null keys to work around bug 1256449.
+      options.appServerKey = appServerKey;
+    }
+    let sub = new pushManager._window.PushSubscription(options);
     this.resolve(sub);
   },
 
   _getKey: function(subscription, name) {
     let outKeyLen = {};
     let rawKey = subscription.getKey(name, outKeyLen);
     if (!outKeyLen.value) {
       return null;
     }
     let key = new ArrayBuffer(outKeyLen.value);
     let keyView = new Uint8Array(key);
     keyView.set(rawKey);
     return key;
   },
+
+  _rejectWithError: function(result) {
+    let error;
+    switch (result) {
+      case Cr.NS_ERROR_DOM_PUSH_INVALID_KEY_ERR:
+        error = new this.pushManager._window.DOMException(
+          "Invalid raw ECDSA P-256 public key.",
+          "InvalidAccessError"
+        );
+        break;
+
+      case Cr.NS_ERROR_DOM_PUSH_MISMATCHED_KEY_ERR:
+        error = new this.pushManager._window.DOMException(
+          "A subscription with a different application server key already exists.",
+          "InvalidStateError"
+        );
+        break;
+
+      default:
+        error = new this.pushManager._window.DOMException(
+          "Error retrieving push subscription.",
+          "AbortError"
+        );
+    }
+    this.reject(error);
+  },
 };
 
 this.NSGetFactory = XPCOMUtils.generateNSGetFactory([Push]);
--- a/dom/push/PushComponents.js
+++ b/dom/push/PushComponents.js
@@ -92,16 +92,22 @@ PushServiceBase.prototype = {
 
   _deliverSubscription(request, props) {
     if (!props) {
       request.onPushSubscription(Cr.NS_OK, null);
       return;
     }
     request.onPushSubscription(Cr.NS_OK, new PushSubscription(props));
   },
+
+  _deliverSubscriptionError(request, error) {
+    let result = typeof error.result == "number" ?
+                 error.result : Cr.NS_ERROR_FAILURE;
+    request.onPushSubscription(result, null);
+  },
 };
 
 /**
  * The parent process implementation of `nsIPushService`. This version loads
  * `PushService.jsm` at startup and calls its methods directly. It also
  * receives and responds to requests from the content process.
  */
 function PushServiceParent() {
@@ -124,22 +130,27 @@ Object.assign(PushServiceParent.prototyp
     "Push:NotificationForOriginShown",
     "Push:NotificationForOriginClosed",
     "Push:ReportError",
   ],
 
   // nsIPushService methods
 
   subscribe(scope, principal, callback) {
-    return this._handleRequest("Push:Register", principal, {
+    this.subscribeWithKey(scope, principal, 0, null, callback);
+  },
+
+  subscribeWithKey(scope, principal, keyLen, key, callback) {
+    this._handleRequest("Push:Register", principal, {
       scope: scope,
+      appServerKey: key,
     }).then(result => {
       this._deliverSubscription(callback, result);
     }, error => {
-      callback.onPushSubscription(Cr.NS_ERROR_FAILURE, null);
+      this._deliverSubscriptionError(callback, error);
     }).catch(Cu.reportError);
   },
 
   unsubscribe(scope, principal, callback) {
     this._handleRequest("Push:Unregister", principal, {
       scope: scope,
     }).then(result => {
       callback.onUnsubscribe(Cr.NS_OK, result);
@@ -149,17 +160,17 @@ Object.assign(PushServiceParent.prototyp
   },
 
   getSubscription(scope, principal, callback) {
     return this._handleRequest("Push:Registration", principal, {
       scope: scope,
     }).then(result => {
       this._deliverSubscription(callback, result);
     }, error => {
-      callback.onPushSubscription(Cr.NS_ERROR_FAILURE, null);
+      this._deliverSubscriptionError(callback, error);
     }).catch(Cu.reportError);
   },
 
   clearForDomain(domain, callback) {
     return this._handleRequest("Push:Clear", null, {
       domain: domain,
     }).then(result => {
       callback.onClear(Cr.NS_OK);
@@ -208,16 +219,17 @@ Object.assign(PushServiceParent.prototyp
     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"), {
         requestID: data.requestID,
+        result: error.result,
       });
     }).catch(Cu.reportError);
   },
 
   _handleReady() {
     this.service.init();
   },
 
@@ -325,19 +337,24 @@ Object.assign(PushServiceContent.prototy
     "PushService:Unregister:KO",
     "PushService:Clear:OK",
     "PushService:Clear:KO",
   ],
 
   // nsIPushService methods
 
   subscribe(scope, principal, callback) {
+    this.subscribeWithKey(scope, principal, 0, null, callback);
+  },
+
+  subscribeWithKey(scope, principal, keyLen, key, callback) {
     let requestId = this._addRequest(callback);
     this._mm.sendAsyncMessage("Push:Register", {
       scope: scope,
+      appServerKey: key,
       requestID: requestId,
     }, null, principal);
   },
 
   unsubscribe(scope, principal, callback) {
     let requestId = this._addRequest(callback);
     this._mm.sendAsyncMessage("Push:Unregister", {
       scope: scope,
@@ -406,17 +423,17 @@ Object.assign(PushServiceContent.prototy
     switch (name) {
       case "PushService:Register:OK":
       case "PushService:Registration:OK":
         this._deliverSubscription(request, data.result);
         break;
 
       case "PushService:Register:KO":
       case "PushService:Registration:KO":
-        request.onPushSubscription(Cr.NS_ERROR_FAILURE, null);
+        this._deliverSubscriptionError(request, data);
         break;
 
       case "PushService:Unregister:OK":
         if (typeof data.result === "boolean") {
           request.onUnsubscribe(Cr.NS_OK, data.result);
         } else {
           request.onUnsubscribe(Cr.NS_ERROR_FAILURE, false);
         }
@@ -488,21 +505,25 @@ PushSubscription.prototype = {
   },
 
   /**
    * Returns a key for encrypting messages sent to this subscription. JS
    * callers receive the key buffer as a return value, while C++ callers
    * receive the key size and buffer as out parameters.
    */
   getKey(name, outKeyLen) {
-    if (name === "p256dh") {
-      return this._getRawKey(this._props.p256dhKey, outKeyLen);
-    }
-    if (name === "auth") {
-      return this._getRawKey(this._props.authenticationSecret, outKeyLen);
+    switch (name) {
+      case "p256dh":
+        return this._getRawKey(this._props.p256dhKey, outKeyLen);
+
+      case "auth":
+        return this._getRawKey(this._props.authenticationSecret, outKeyLen);
+
+      case "appServer":
+        return this._getRawKey(this._props.appServerKey, outKeyLen);
     }
     return null;
   },
 
   _getRawKey(key, outKeyLen) {
     if (!key) {
       return null;
     }
--- a/dom/push/PushCrypto.jsm
+++ b/dom/push/PushCrypto.jsm
@@ -5,33 +5,33 @@
 
 'use strict';
 
 const Cu = Components.utils;
 
 Cu.importGlobalProperties(['crypto']);
 
 this.EXPORTED_SYMBOLS = ['PushCrypto', 'concatArray',
-                         'getCryptoParams',
-                         'base64UrlDecode'];
+                         'getCryptoParams'];
 
 var UTF8 = new TextEncoder('utf-8');
 
 // Legacy encryption scheme (draft-thomson-http-encryption-02).
 var AESGCM128_ENCODING = 'aesgcm128';
 var AESGCM128_ENCRYPT_INFO = UTF8.encode('Content-Encoding: aesgcm128');
 
 // New encryption scheme (draft-ietf-httpbis-encryption-encoding-01).
 var AESGCM_ENCODING = 'aesgcm';
 var AESGCM_ENCRYPT_INFO = UTF8.encode('Content-Encoding: aesgcm');
 
 var NONCE_INFO = UTF8.encode('Content-Encoding: nonce');
 var AUTH_INFO = UTF8.encode('Content-Encoding: auth\0'); // note nul-terminus
 var P256DH_INFO = UTF8.encode('P-256\0');
 var ECDH_KEY = { name: 'ECDH', namedCurve: 'P-256' };
+var ECDSA_KEY =  { name: 'ECDSA', namedCurve: 'P-256' };
 // A default keyid with a name that won't conflict with a real keyid.
 var DEFAULT_KEYID = '';
 
 function getEncryptionKeyParams(encryptKeyField) {
   if (!encryptKeyField) {
     return null;
   }
   var params = encryptKeyField.split(',');
@@ -113,44 +113,16 @@ function chunkArray(array, size) {
     index += size;
   }
   if (index < array.byteLength) {
     result.push(new Uint8Array(array, start + index));
   }
   return result;
 }
 
-this.base64UrlDecode = function(s) {
-  s = s.replace(/-/g, '+').replace(/_/g, '/');
-
-  // Replace padding if it was stripped by the sender.
-  // See http://tools.ietf.org/html/rfc4648#section-4
-  switch (s.length % 4) {
-    case 0:
-      break; // No pad chars in this case
-    case 2:
-      s += '==';
-      break; // Two pad chars
-    case 3:
-      s += '=';
-      break; // One pad char
-    default:
-      throw new Error('Illegal base64url string!');
-  }
-
-  // With correct padding restored, apply the standard base64 decoder
-  var decoded = atob(s);
-
-  var array = new Uint8Array(new ArrayBuffer(decoded.length));
-  for (var i = 0; i < decoded.length; i++) {
-    array[i] = decoded.charCodeAt(i);
-  }
-  return array;
-};
-
 this.concatArray = function(arrays) {
   var size = arrays.reduce((total, a) => total + a.byteLength, 0);
   var index = 0;
   return arrays.reduce((result, a) => {
     result.set(new Uint8Array(a), index);
     index += a.byteLength;
     return result;
   }, new Uint8Array(size));
@@ -198,16 +170,22 @@ function generateNonce(base, index) {
 }
 
 this.PushCrypto = {
 
   generateAuthenticationSecret() {
     return crypto.getRandomValues(new Uint8Array(16));
   },
 
+  validateAppServerKey(key) {
+    return crypto.subtle.importKey('raw', key, ECDSA_KEY,
+                                   true, ['verify'])
+      .then(_ => key);
+  },
+
   generateKeys() {
     return crypto.subtle.generateKey(ECDH_KEY, true, ['deriveBits'])
       .then(cryptoKey =>
          Promise.all([
            crypto.subtle.exportKey('raw', cryptoKey.publicKey),
            crypto.subtle.exportKey('jwk', cryptoKey.privateKey)
          ]));
   },
@@ -221,29 +199,34 @@ this.PushCrypto = {
     }
 
     // The last chunk of data must be less than aRs, if it is not return an
     // error.
     if (aData.byteLength % (aRs + 16) === 0) {
       return Promise.reject(new Error('Data truncated'));
     }
 
-    let senderKey = base64UrlDecode(aSenderPublicKey)
+    let senderKey = ChromeUtils.base64URLDecode(aSenderPublicKey, {
+      // draft-ietf-httpbis-encryption-encoding-01 prohibits padding.
+      padding: "reject",
+    });
+
     return Promise.all([
       crypto.subtle.importKey('raw', senderKey, ECDH_KEY,
                               false, ['deriveBits']),
       crypto.subtle.importKey('jwk', aPrivateKey, ECDH_KEY,
                               false, ['deriveBits'])
     ])
     .then(([appServerKey, subscriptionPrivateKey]) =>
           crypto.subtle.deriveBits({ name: 'ECDH', public: appServerKey },
                                    subscriptionPrivateKey, 256))
     .then(ikm => this._deriveKeyAndNonce(aPadSize,
                                          new Uint8Array(ikm),
-                                         base64UrlDecode(aSalt),
+                                         ChromeUtils.base64URLDecode(aSalt,
+                                                    { padding: "reject" }),
                                          aPublicKey,
                                          senderKey,
                                          aAuthenticationSecret))
     .then(r =>
       // AEAD_AES_128_GCM expands ciphertext to be 16 octets longer.
       Promise.all(chunkArray(aData, aRs + 16).map((slice, index) =>
         this._decodeChunk(aPadSize, slice, index, r[1], r[0]))))
     .then(r => concatArray(r));
--- a/dom/push/PushManager.cpp
+++ b/dom/push/PushManager.cpp
@@ -5,16 +5,18 @@
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "mozilla/dom/PushManager.h"
 
 #include "mozilla/Services.h"
 #include "mozilla/unused.h"
 #include "mozilla/dom/PushManagerBinding.h"
 #include "mozilla/dom/PushSubscription.h"
+#include "mozilla/dom/PushSubscriptionOptionsBinding.h"
+#include "mozilla/dom/PushUtil.h"
 
 #include "mozilla/dom/Promise.h"
 #include "mozilla/dom/PromiseWorkerProxy.h"
 
 #include "nsIGlobalObject.h"
 #include "nsIPermissionManager.h"
 #include "nsIPrincipal.h"
 #include "nsIPushService.h"
@@ -58,46 +60,124 @@ GetPermissionState(nsIPrincipal* aPrinci
     aState = PushPermissionState::Denied;
   } else {
     aState = PushPermissionState::Prompt;
   }
 
   return NS_OK;
 }
 
+// A helper class that frees an `nsIPushSubscription` key buffer when it
+// goes out of scope.
+class MOZ_RAII AutoFreeKeyBuffer final
+{
+  uint8_t** mKeyBuffer;
+
+public:
+  explicit AutoFreeKeyBuffer(uint8_t** aKeyBuffer)
+    : mKeyBuffer(aKeyBuffer)
+  {
+    MOZ_ASSERT(mKeyBuffer);
+  }
+
+  ~AutoFreeKeyBuffer()
+  {
+    NS_Free(*mKeyBuffer);
+  }
+};
+
+// Copies a subscription key buffer into an array.
+nsresult
+CopySubscriptionKeyToArray(nsIPushSubscription* aSubscription,
+                           const nsAString& aKeyName,
+                           nsTArray<uint8_t>& aKey)
+{
+  uint8_t* keyBuffer = nullptr;
+  AutoFreeKeyBuffer autoFree(&keyBuffer);
+
+  uint32_t keyLen;
+  nsresult rv = aSubscription->GetKey(aKeyName, &keyLen, &keyBuffer);
+  if (NS_FAILED(rv)) {
+    return rv;
+  }
+  if (!aKey.SetLength(keyLen, fallible) ||
+      !aKey.ReplaceElementsAt(0, keyLen, keyBuffer, keyLen, fallible)) {
+    return NS_ERROR_OUT_OF_MEMORY;
+  }
+  return NS_OK;
+}
+
+nsresult
+GetSubscriptionParams(nsIPushSubscription* aSubscription,
+                      nsAString& aEndpoint,
+                      nsTArray<uint8_t>& aRawP256dhKey,
+                      nsTArray<uint8_t>& aAuthSecret,
+                      nsTArray<uint8_t>& aAppServerKey)
+{
+  if (!aSubscription) {
+    return NS_OK;
+  }
+
+  nsresult rv = aSubscription->GetEndpoint(aEndpoint);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  rv = CopySubscriptionKeyToArray(aSubscription, NS_LITERAL_STRING("p256dh"),
+                                  aRawP256dhKey);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+  rv = CopySubscriptionKeyToArray(aSubscription, NS_LITERAL_STRING("auth"),
+                                  aAuthSecret);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+  rv = CopySubscriptionKeyToArray(aSubscription, NS_LITERAL_STRING("appServer"),
+                                  aAppServerKey);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  return NS_OK;
+}
+
 class GetSubscriptionResultRunnable final : public WorkerRunnable
 {
 public:
   GetSubscriptionResultRunnable(WorkerPrivate* aWorkerPrivate,
                                 already_AddRefed<PromiseWorkerProxy>&& aProxy,
                                 nsresult aStatus,
                                 const nsAString& aEndpoint,
                                 const nsAString& aScope,
                                 nsTArray<uint8_t>&& aRawP256dhKey,
-                                nsTArray<uint8_t>&& aAuthSecret)
+                                nsTArray<uint8_t>&& aAuthSecret,
+                                nsTArray<uint8_t>&& aAppServerKey)
     : WorkerRunnable(aWorkerPrivate, WorkerThreadModifyBusyCount)
     , mProxy(Move(aProxy))
     , mStatus(aStatus)
     , mEndpoint(aEndpoint)
     , mScope(aScope)
     , mRawP256dhKey(Move(aRawP256dhKey))
     , mAuthSecret(Move(aAuthSecret))
+    , mAppServerKey(Move(aAppServerKey))
   { }
 
   bool
   WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override
   {
     RefPtr<Promise> promise = mProxy->WorkerPromise();
     if (NS_SUCCEEDED(mStatus)) {
       if (mEndpoint.IsEmpty()) {
         promise->MaybeResolve(JS::NullHandleValue);
       } else {
         RefPtr<PushSubscription> sub =
             new PushSubscription(nullptr, mEndpoint, mScope,
-                                 Move(mRawP256dhKey), Move(mAuthSecret));
+                                 Move(mRawP256dhKey), Move(mAuthSecret),
+                                 Move(mAppServerKey));
         promise->MaybeResolve(sub);
       }
     } else if (NS_ERROR_GET_MODULE(mStatus) == NS_ERROR_MODULE_DOM_PUSH ) {
       promise->MaybeReject(mStatus);
     } else {
       promise->MaybeReject(NS_ERROR_DOM_PUSH_ABORT_ERR);
     }
 
@@ -110,16 +190,17 @@ private:
   {}
 
   RefPtr<PromiseWorkerProxy> mProxy;
   nsresult mStatus;
   nsString mEndpoint;
   nsString mScope;
   nsTArray<uint8_t> mRawP256dhKey;
   nsTArray<uint8_t> mAuthSecret;
+  nsTArray<uint8_t> mAppServerKey;
 };
 
 class GetSubscriptionCallback final : public nsIPushSubscriptionCallback
 {
 public:
   NS_DECL_ISUPPORTS
 
   explicit GetSubscriptionCallback(PromiseWorkerProxy* aProxy,
@@ -136,31 +217,32 @@ public:
     MOZ_ASSERT(mProxy, "OnPushSubscription() called twice?");
 
     MutexAutoLock lock(mProxy->Lock());
     if (mProxy->CleanedUp()) {
       return NS_OK;
     }
 
     nsAutoString endpoint;
-    nsTArray<uint8_t> rawP256dhKey, authSecret;
+    nsTArray<uint8_t> rawP256dhKey, authSecret, appServerKey;
     if (NS_SUCCEEDED(aStatus)) {
       aStatus = GetSubscriptionParams(aSubscription, endpoint, rawP256dhKey,
-                                      authSecret);
+                                      authSecret, appServerKey);
     }
 
     WorkerPrivate* worker = mProxy->GetWorkerPrivate();
     RefPtr<GetSubscriptionResultRunnable> r =
       new GetSubscriptionResultRunnable(worker,
                                         mProxy.forget(),
                                         aStatus,
                                         endpoint,
                                         mScope,
                                         Move(rawP256dhKey),
-                                        Move(authSecret));
+                                        Move(authSecret),
+                                        Move(appServerKey));
     MOZ_ALWAYS_TRUE(r->Dispatch());
 
     return NS_OK;
   }
 
   // Convenience method for use in this file.
   void
   OnPushSubscriptionError(nsresult aStatus)
@@ -169,82 +251,33 @@ public:
         OnPushSubscription(aStatus, nullptr)));
   }
 
 protected:
   ~GetSubscriptionCallback()
   {}
 
 private:
-  inline nsresult
-  FreeKeys(nsresult aStatus, uint8_t* aKey, uint8_t* aAuthSecret)
-  {
-    NS_Free(aKey);
-    NS_Free(aAuthSecret);
-
-    return aStatus;
-  }
-
-  nsresult
-  GetSubscriptionParams(nsIPushSubscription* aSubscription,
-                        nsAString& aEndpoint,
-                        nsTArray<uint8_t>& aRawP256dhKey,
-                        nsTArray<uint8_t>& aAuthSecret)
-  {
-    if (!aSubscription) {
-      return NS_OK;
-    }
-
-    nsresult rv = aSubscription->GetEndpoint(aEndpoint);
-    if (NS_WARN_IF(NS_FAILED(rv))) {
-      return rv;
-    }
-
-    uint8_t* key = nullptr;
-    uint8_t* authSecret = nullptr;
-
-    uint32_t keyLen;
-    rv = aSubscription->GetKey(NS_LITERAL_STRING("p256dh"), &keyLen, &key);
-    if (NS_WARN_IF(NS_FAILED(rv))) {
-      return FreeKeys(rv, key, authSecret);
-    }
-
-    uint32_t authSecretLen;
-    rv = aSubscription->GetKey(NS_LITERAL_STRING("auth"), &authSecretLen,
-                               &authSecret);
-    if (NS_WARN_IF(NS_FAILED(rv))) {
-      return FreeKeys(rv, key, authSecret);
-    }
-
-    if (!aRawP256dhKey.SetLength(keyLen, fallible) ||
-        !aRawP256dhKey.ReplaceElementsAt(0, keyLen, key, keyLen, fallible) ||
-        !aAuthSecret.SetLength(authSecretLen, fallible) ||
-        !aAuthSecret.ReplaceElementsAt(0, authSecretLen, authSecret,
-                                       authSecretLen, fallible)) {
-
-      return FreeKeys(NS_ERROR_OUT_OF_MEMORY, key, authSecret);
-    }
-
-    return FreeKeys(NS_OK, key, authSecret);
-  }
-
   RefPtr<PromiseWorkerProxy> mProxy;
   nsString mScope;
 };
 
 NS_IMPL_ISUPPORTS(GetSubscriptionCallback, nsIPushSubscriptionCallback)
 
 class GetSubscriptionRunnable final : public nsRunnable
 {
 public:
   GetSubscriptionRunnable(PromiseWorkerProxy* aProxy,
                           const nsAString& aScope,
-                          PushManager::SubscriptionAction aAction)
+                          PushManager::SubscriptionAction aAction,
+                          nsTArray<uint8_t>&& aAppServerKey)
     : mProxy(aProxy)
-    , mScope(aScope), mAction(aAction)
+    , mScope(aScope)
+    , mAction(aAction)
+    , mAppServerKey(Move(aAppServerKey))
   {}
 
   NS_IMETHOD
   Run() override
   {
     AssertIsOnMainThread();
 
     nsCOMPtr<nsIPrincipal> principal;
@@ -284,17 +317,23 @@ public:
     nsCOMPtr<nsIPushService> service =
       do_GetService("@mozilla.org/push/Service;1");
     if (NS_WARN_IF(!service)) {
       callback->OnPushSubscriptionError(NS_ERROR_FAILURE);
       return NS_OK;
     }
 
     if (mAction == PushManager::SubscribeAction) {
-      rv = service->Subscribe(mScope, principal, callback);
+      if (mAppServerKey.IsEmpty()) {
+        rv = service->Subscribe(mScope, principal, callback);
+      } else {
+        rv = service->SubscribeWithKey(mScope, principal,
+                                       mAppServerKey.Length(),
+                                       mAppServerKey.Elements(), callback);
+      }
     } else {
       MOZ_ASSERT(mAction == PushManager::GetSubscriptionAction);
       rv = service->GetSubscription(mScope, principal, callback);
     }
 
     if (NS_WARN_IF(NS_FAILED(rv))) {
       callback->OnPushSubscriptionError(NS_ERROR_FAILURE);
       return NS_OK;
@@ -305,16 +344,17 @@ public:
 
 private:
   ~GetSubscriptionRunnable()
   {}
 
   RefPtr<PromiseWorkerProxy> mProxy;
   nsString mScope;
   PushManager::SubscriptionAction mAction;
+  nsTArray<uint8_t> mAppServerKey;
 };
 
 class PermissionResultRunnable final : public WorkerRunnable
 {
 public:
   PermissionResultRunnable(PromiseWorkerProxy *aProxy,
                            nsresult aStatus,
                            PushPermissionState aState)
@@ -448,43 +488,45 @@ PushManager::Constructor(GlobalObject& a
 
   nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aGlobal.GetAsSupports());
   RefPtr<PushManager> ret = new PushManager(global, impl);
 
   return ret.forget();
 }
 
 already_AddRefed<Promise>
-PushManager::Subscribe(ErrorResult& aRv)
+PushManager::Subscribe(const PushSubscriptionOptionsInit& aOptions,
+                       ErrorResult& aRv)
 {
   if (mImpl) {
     MOZ_ASSERT(NS_IsMainThread());
-    return mImpl->Subscribe(aRv);
+    return mImpl->Subscribe(aOptions, aRv);
   }
 
-  return PerformSubscriptionActionFromWorker(SubscribeAction, aRv);
+  return PerformSubscriptionActionFromWorker(SubscribeAction, aOptions, aRv);
 }
 
 already_AddRefed<Promise>
 PushManager::GetSubscription(ErrorResult& aRv)
 {
   if (mImpl) {
     MOZ_ASSERT(NS_IsMainThread());
     return mImpl->GetSubscription(aRv);
   }
 
   return PerformSubscriptionActionFromWorker(GetSubscriptionAction, aRv);
 }
 
 already_AddRefed<Promise>
-PushManager::PermissionState(ErrorResult& aRv)
+PushManager::PermissionState(const PushSubscriptionOptionsInit& aOptions,
+                             ErrorResult& aRv)
 {
   if (mImpl) {
     MOZ_ASSERT(NS_IsMainThread());
-    return mImpl->PermissionState(aRv);
+    return mImpl->PermissionState(aOptions, aRv);
   }
 
   WorkerPrivate* worker = GetCurrentThreadWorkerPrivate();
   MOZ_ASSERT(worker);
   worker->AssertIsOnWorkerThread();
 
   nsCOMPtr<nsIGlobalObject> global = worker->GlobalScope();
   RefPtr<Promise> p = Promise::Create(global, aRv);
@@ -501,18 +543,27 @@ PushManager::PermissionState(ErrorResult
   RefPtr<PermissionStateRunnable> r =
     new PermissionStateRunnable(proxy);
   NS_DispatchToMainThread(r);
 
   return p.forget();
 }
 
 already_AddRefed<Promise>
-PushManager::PerformSubscriptionActionFromWorker(
-  SubscriptionAction aAction, ErrorResult& aRv)
+PushManager::PerformSubscriptionActionFromWorker(SubscriptionAction aAction,
+                                                 ErrorResult& aRv)
+{
+  PushSubscriptionOptionsInit options;
+  return PerformSubscriptionActionFromWorker(aAction, options, aRv);
+}
+
+already_AddRefed<Promise>
+PushManager::PerformSubscriptionActionFromWorker(SubscriptionAction aAction,
+                                                 const PushSubscriptionOptionsInit& aOptions,
+                                                 ErrorResult& aRv)
 {
   WorkerPrivate* worker = GetCurrentThreadWorkerPrivate();
   MOZ_ASSERT(worker);
   worker->AssertIsOnWorkerThread();
 
   nsCOMPtr<nsIGlobalObject> global = worker->GlobalScope();
   RefPtr<Promise> p = Promise::Create(global, aRv);
   if (NS_WARN_IF(aRv.Failed())) {
@@ -520,17 +571,28 @@ PushManager::PerformSubscriptionActionFr
   }
 
   RefPtr<PromiseWorkerProxy> proxy = PromiseWorkerProxy::Create(worker, p);
   if (!proxy) {
     p->MaybeReject(NS_ERROR_DOM_PUSH_ABORT_ERR);
     return p.forget();
   }
 
+  nsTArray<uint8_t> appServerKey;
+  if (!aOptions.mApplicationServerKey.IsNull()) {
+    const OwningArrayBufferViewOrArrayBuffer& bufferSource =
+      aOptions.mApplicationServerKey.Value();
+    if (!PushUtil::CopyBufferSourceToArray(bufferSource, appServerKey) ||
+        appServerKey.IsEmpty()) {
+      p->MaybeReject(NS_ERROR_DOM_PUSH_INVALID_KEY_ERR);
+      return p.forget();
+    }
+  }
+
   RefPtr<GetSubscriptionRunnable> r =
-    new GetSubscriptionRunnable(proxy, mScope, aAction);
+    new GetSubscriptionRunnable(proxy, mScope, aAction, Move(appServerKey));
   MOZ_ALWAYS_SUCCEEDS(NS_DispatchToMainThread(r));
 
   return p.forget();
 }
 
 } // namespace dom
 } // namespace mozilla
--- a/dom/push/PushManager.h
+++ b/dom/push/PushManager.h
@@ -43,16 +43,17 @@ namespace mozilla {
 namespace dom {
 
 namespace workers {
 class WorkerPrivate;
 }
 
 class Promise;
 class PushManagerImpl;
+struct PushSubscriptionOptionsInit;
 
 class PushManager final : public nsISupports
                         , public nsWrapperCache
 {
 public:
   NS_DECL_CYCLE_COLLECTING_ISUPPORTS
   NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(PushManager)
 
@@ -80,28 +81,33 @@ public:
   Constructor(GlobalObject& aGlobal, const nsAString& aScope,
               ErrorResult& aRv);
 
   already_AddRefed<Promise>
   PerformSubscriptionActionFromWorker(SubscriptionAction aAction,
                                       ErrorResult& aRv);
 
   already_AddRefed<Promise>
-  Subscribe(ErrorResult& aRv);
+  PerformSubscriptionActionFromWorker(SubscriptionAction aAction,
+                                      const PushSubscriptionOptionsInit& aOptions,
+                                      ErrorResult& aRv);
+
+  already_AddRefed<Promise>
+  Subscribe(const PushSubscriptionOptionsInit& aOptions, ErrorResult& aRv);
 
   already_AddRefed<Promise>
   GetSubscription(ErrorResult& aRv);
 
   already_AddRefed<Promise>
-  PermissionState(ErrorResult& aRv);
+  PermissionState(const PushSubscriptionOptionsInit& aOptions,
+                  ErrorResult& aRv);
 
-protected:
+private:
   ~PushManager();
 
-private:
   // The following are only set and accessed on the main thread.
   nsCOMPtr<nsIGlobalObject> mGlobal;
   RefPtr<PushManagerImpl> mImpl;
 
   // Only used on the worker thread.
   nsString mScope;
 };
 } // namespace dom
--- a/dom/push/PushRecord.jsm
+++ b/dom/push/PushRecord.jsm
@@ -37,16 +37,17 @@ function PushRecord(props) {
   this.scope = props.scope;
   this.originAttributes = props.originAttributes;
   this.pushCount = props.pushCount || 0;
   this.lastPush = props.lastPush || 0;
   this.p256dhPublicKey = props.p256dhPublicKey;
   this.p256dhPrivateKey = props.p256dhPrivateKey;
   this.authenticationSecret = props.authenticationSecret;
   this.systemRecord = !!props.systemRecord;
+  this.appServerKey = props.appServerKey;
   this.setQuota(props.quota);
   this.ctime = (typeof props.ctime === "number") ? props.ctime : 0;
 }
 
 PushRecord.prototype = {
   setQuota(suggestedQuota) {
     if (this.quotaApplies() && !isNaN(suggestedQuota) && suggestedQuota >= 0) {
       this.quota = suggestedQuota;
@@ -226,23 +227,35 @@ PushRecord.prototype = {
       this.principal.originAttributes, pattern);
   },
 
   hasAuthenticationSecret() {
     return !!this.authenticationSecret &&
            this.authenticationSecret.byteLength == 16;
   },
 
+  matchesAppServerKey(key) {
+    if (!this.appServerKey) {
+      return !key;
+    }
+    if (!key) {
+      return false;
+    }
+    return this.appServerKey.length === key.length &&
+           this.appServerKey.every((value, index) => value === key[index]);
+  },
+
   toSubscription() {
     return {
       endpoint: this.pushEndpoint,
       lastPush: this.lastPush,
       pushCount: this.pushCount,
       p256dhKey: this.p256dhPublicKey,
       authenticationSecret: this.authenticationSecret,
+      appServerKey: this.appServerKey,
       quota: this.quotaApplies() ? this.quota : -1,
     };
   },
 };
 
 // Define lazy getters for the principal and scope URI. IndexedDB can't store
 // `nsIPrincipal` objects, so we keep them in a private weak map.
 var principals = new WeakMap();
--- a/dom/push/PushService.jsm
+++ b/dom/push/PushService.jsm
@@ -89,16 +89,27 @@ const kDROP_NOTIFICATION_REASON_EXPIRED 
 
 // This is for starting and stopping service.
 const STARTING_SERVICE_EVENT = 0;
 const CHANGING_SERVICE_EVENT = 1;
 const STOPPING_SERVICE_EVENT = 2;
 const UNINIT_EVENT = 3;
 
 /**
+ * Annotates an error with an XPCOM result code. We use this helper
+ * instead of `Components.Exception` because the latter can assert in
+ * `nsXPCComponents_Exception::HasInstance` when inspected at shutdown.
+ */
+function errorWithResult(message, result = Cr.NS_ERROR_FAILURE) {
+  let error = new Error(message);
+  error.result = result;
+  return error;
+}
+
+/**
  * The implementation of the push system. It uses WebSockets
  * (PushServiceWebSocket) to communicate with the server and PushDB (IndexedDB)
  * for persistence.
  */
 this.PushService = {
   _service: null,
   _state: PUSH_SERVICE_UNINIT,
   _db: null,
@@ -1089,33 +1100,55 @@ this.PushService = {
     return this._checkActivated().then(_ =>
       this._db.getByIdentifiers(pageRecord)
     );
   },
 
   register: function(aPageRecord) {
     console.debug("register()", aPageRecord);
 
-    return this._getByPageRecord(aPageRecord)
-      .then(record => {
-        if (!record) {
-          return this._lookupOrPutPendingRequest(aPageRecord);
-        }
-        if (record.isExpired()) {
-          return record.quotaChanged().then(isChanged => {
-            if (isChanged) {
-              // If the user revisited the site, drop the expired push
-              // registration and re-register.
-              return this.dropRegistrationAndNotifyApp(record.keyID);
-            }
-            throw new Error("Push subscription expired");
-          }).then(_ => this._lookupOrPutPendingRequest(aPageRecord));
-        }
-        return record.toSubscription();
-      });
+    let keyPromise;
+    if (aPageRecord.appServerKey) {
+      let keyView = new Uint8Array(aPageRecord.appServerKey);
+      keyPromise = PushCrypto.validateAppServerKey(keyView)
+        .catch(error => {
+          // Normalize Web Crypto exceptions. `nsIPushService` will forward the
+          // error result to the DOM API implementation in `PushManager.cpp` or
+          // `Push.js`, which will convert it to the correct `DOMException`.
+          throw errorWithResult("Invalid app server key",
+                                Cr.NS_ERROR_DOM_PUSH_INVALID_KEY_ERR);
+        });
+    } else {
+      keyPromise = Promise.resolve(null);
+    }
+
+    return Promise.all([
+      keyPromise,
+      this._getByPageRecord(aPageRecord),
+    ]).then(([appServerKey, record]) => {
+      aPageRecord.appServerKey = appServerKey;
+      if (!record) {
+        return this._lookupOrPutPendingRequest(aPageRecord);
+      }
+      if (!record.matchesAppServerKey(appServerKey)) {
+        throw errorWithResult("Mismatched app server key",
+                              Cr.NS_ERROR_DOM_PUSH_MISMATCHED_KEY_ERR);
+      }
+      if (record.isExpired()) {
+        return record.quotaChanged().then(isChanged => {
+          if (isChanged) {
+            // If the user revisited the site, drop the expired push
+            // registration and re-register.
+            return this.dropRegistrationAndNotifyApp(record.keyID);
+          }
+          throw new Error("Push subscription expired");
+        }).then(_ => this._lookupOrPutPendingRequest(aPageRecord));
+      }
+      return record.toSubscription();
+    });
   },
 
   /**
    * Called on message from the child process.
    *
    * Why is the record being deleted from the local database before the server
    * is told?
    *
--- a/dom/push/PushServiceAndroidGCM.jsm
+++ b/dom/push/PushServiceAndroidGCM.jsm
@@ -17,17 +17,16 @@ Cu.import("resource://gre/modules/Servic
 Cu.import("resource://gre/modules/Preferences.jsm"); /*global: Preferences */
 Cu.import("resource://gre/modules/Promise.jsm"); /*global: Promise */
 Cu.import("resource://gre/modules/XPCOMUtils.jsm"); /*global: XPCOMUtils */
 
 const Log = Cu.import("resource://gre/modules/AndroidLog.jsm", {}).AndroidLog.bind("Push");
 
 const {
   PushCrypto,
-  base64UrlDecode,
   concatArray,
   getCryptoParams,
 } = Cu.import("resource://gre/modules/PushCrypto.jsm");
 
 this.EXPORTED_SYMBOLS = ["PushServiceAndroidGCM"];
 
 XPCOMUtils.defineLazyGetter(this, "console", () => {
   let {ConsoleAPI} = Cu.import("resource://gre/modules/Console.jsm", {});
@@ -113,22 +112,25 @@ this.PushServiceAndroidGCM = {
         let headers = {
           encryption_key: data.enckey,
           crypto_key: data.cryptokey,
           encryption: data.enc,
           encoding: data.con,
         };
         cryptoParams = getCryptoParams(headers);
         // Ciphertext is (urlsafe) Base 64 encoded.
-        message = base64UrlDecode(data.message);
+        message = ChromeUtils.base64URLDecode(data.message, {
+          // The Push server may append padding.
+          padding: "ignore",
+        });
       }
 
       console.debug("Delivering message to main PushService:", message, cryptoParams);
       this._mainPushService.receivedPushMessage(
-        data.channelID, message, cryptoParams, (record) => {
+        data.channelID, "", message, cryptoParams, (record) => {
           // Always update the stored record.
           return record;
         });
       return;
     }
   },
 
   _configure: function(serverURL, debug) {
@@ -232,16 +234,21 @@ this.PushServiceAndroidGCM = {
 
   unregister: function(record) {
     console.debug("unregister: ", record);
     return Messaging.sendRequestForResult({
       type: "PushServiceAndroidGCM:UnsubscribeChannel",
       channelID: record.keyID,
     });
   },
+
+  reportDeliveryError: function(messageID, reason) {
+    console.warn("reportDeliveryError: Ignoring message delivery error",
+      messageID, reason);
+  },
 };
 
 function PushRecordAndroidGCM(record) {
   PushRecord.call(this, record);
   this.channelID = record.channelID;
 }
 
 PushRecordAndroidGCM.prototype = Object.create(PushRecord.prototype, {
--- a/dom/push/PushServiceHttp2.jsm
+++ b/dom/push/PushServiceHttp2.jsm
@@ -325,16 +325,17 @@ SubscriptionListener.prototype = {
 
     let reply = new PushRecordHttp2({
       subscriptionUri: subscriptionUri,
       pushEndpoint: linkParserResult.pushEndpoint,
       pushReceiptEndpoint: linkParserResult.pushReceiptEndpoint,
       scope: this._subInfo.record.scope,
       originAttributes: this._subInfo.record.originAttributes,
       systemRecord: this._subInfo.record.systemRecord,
+      appServerKey: this._subInfo.record.appServerKey,
       ctime: Date.now(),
     });
 
     Services.telemetry.getHistogramById("PUSH_API_SUBSCRIBE_HTTP2_TIME").add(Date.now() - this._ctime);
     this._resolve(reply);
   },
 
   abortRetry: function() {
@@ -707,16 +708,21 @@ this.PushServiceHttp2 = {
     this._listenersPendingRetry.clear();
   },
 
   unregister: function(aRecord) {
     this._shutdownSubscription(aRecord.subscriptionUri);
     return this._unsubscribeResource(aRecord.subscriptionUri);
   },
 
+  reportDeliveryError: function(messageID, reason) {
+    console.warn("reportDeliveryError: Ignoring message delivery error",
+      messageID, reason);
+  },
+
   /** 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)
--- a/dom/push/PushServiceWebSocket.jsm
+++ b/dom/push/PushServiceWebSocket.jsm
@@ -16,17 +16,16 @@ Cu.import("resource://gre/modules/Promis
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/Timer.jsm");
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 
 const {PushDB} = Cu.import("resource://gre/modules/PushDB.jsm");
 const {PushRecord} = Cu.import("resource://gre/modules/PushRecord.jsm");
 const {
   PushCrypto,
-  base64UrlDecode,
   getCryptoParams,
 } = Cu.import("resource://gre/modules/PushCrypto.jsm");
 
 XPCOMUtils.defineLazyServiceGetter(this, "gDNSService",
                                    "@mozilla.org/network/dns-service;1",
                                    "nsIDNSService");
 
 if (AppConstants.MOZ_B2G) {
@@ -902,16 +901,17 @@ this.PushServiceWebSocket = {
 
       let record = new PushRecordWebSocket({
         channelID: reply.channelID,
         pushEndpoint: reply.pushEndpoint,
         scope: tmp.record.scope,
         originAttributes: tmp.record.originAttributes,
         version: null,
         systemRecord: tmp.record.systemRecord,
+        appServerKey: tmp.record.appServerKey,
         ctime: Date.now(),
       });
       Services.telemetry.getHistogramById("PUSH_API_SUBSCRIBE_WS_TIME").add(Date.now() - tmp.ctime);
       tmp.resolve(record);
     } else {
       console.error("handleRegisterReply: Unexpected server response", reply);
       tmp.reject(new Error("Wrong status code for register reply: " +
         reply.status));
@@ -931,17 +931,20 @@ this.PushServiceWebSocket = {
         update.version,
         null,
         null,
         record => record
       );
     } else {
       let params = getCryptoParams(update.headers);
       if (params) {
-        let message = base64UrlDecode(update.data);
+        let message = ChromeUtils.base64URLDecode(update.data, {
+          // The Push server may append padding.
+          padding: "ignore",
+        });
         promise = this._mainPushService.receivedPushMessage(
           update.channelID,
           update.version,
           message,
           params,
           record => record
         );
       } else {
@@ -1040,16 +1043,23 @@ this.PushServiceWebSocket = {
     console.debug("register() ", record);
 
     // start the timer since we now have at least one request
     this._startRequestTimeoutTimer();
 
     let data = {channelID: this._generateID(),
                 messageType: "register"};
 
+    if (record.appServerKey) {
+      data.key = ChromeUtils.base64URLEncode(record.appServerKey, {
+        // The Push server requires padding.
+        pad: true,
+      });
+    }
+
     return new Promise((resolve, reject) => {
       this._registerRequests.set(data.channelID, {
         record: record,
         resolve: resolve,
         reject: reject,
         ctime: Date.now(),
       });
       this._queueRequest(data);
--- a/dom/push/PushSubscription.cpp
+++ b/dom/push/PushSubscription.cpp
@@ -7,16 +7,18 @@
 #include "nsIPushService.h"
 #include "nsIScriptObjectPrincipal.h"
 
 #include "mozilla/Base64.h"
 #include "mozilla/unused.h"
 
 #include "mozilla/dom/Promise.h"
 #include "mozilla/dom/PromiseWorkerProxy.h"
+#include "mozilla/dom/PushSubscriptionOptions.h"
+#include "mozilla/dom/PushUtil.h"
 #include "mozilla/dom/WorkerPrivate.h"
 #include "mozilla/dom/WorkerScope.h"
 #include "mozilla/dom/workers/Workers.h"
 
 namespace mozilla {
 namespace dom {
 
 using namespace workers;
@@ -186,97 +188,103 @@ public:
 private:
   ~UnsubscribeRunnable()
   {}
 
   RefPtr<PromiseWorkerProxy> mProxy;
   nsString mScope;
 };
 
-bool
-CopyArrayBufferToArray(const ArrayBuffer& aBuffer,
-                       nsTArray<uint8_t>& aArray)
-{
-  aBuffer.ComputeLengthAndData();
-  if (!aArray.SetLength(aBuffer.Length(), fallible) ||
-      !aArray.ReplaceElementsAt(0, aBuffer.Length(), aBuffer.Data(),
-                                aBuffer.Length(), fallible)) {
-    return false;
-  }
-  return true;
-}
-
 } // anonymous namespace
 
 PushSubscription::PushSubscription(nsIGlobalObject* aGlobal,
                                    const nsAString& aEndpoint,
                                    const nsAString& aScope,
                                    nsTArray<uint8_t>&& aRawP256dhKey,
-                                   nsTArray<uint8_t>&& aAuthSecret)
+                                   nsTArray<uint8_t>&& aAuthSecret,
+                                   nsTArray<uint8_t>&& aAppServerKey)
   : mEndpoint(aEndpoint)
   , mScope(aScope)
   , mRawP256dhKey(Move(aRawP256dhKey))
   , mAuthSecret(Move(aAuthSecret))
 {
   if (NS_IsMainThread()) {
     mGlobal = aGlobal;
   } else {
 #ifdef DEBUG
     // There's only one global on a worker, so we don't need to pass a global
     // object to the constructor.
     WorkerPrivate* worker = GetCurrentThreadWorkerPrivate();
     MOZ_ASSERT(worker);
     worker->AssertIsOnWorkerThread();
 #endif
   }
+  mOptions = new PushSubscriptionOptions(mGlobal, Move(aAppServerKey));
 }
 
 PushSubscription::~PushSubscription()
 {}
 
-NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(PushSubscription, mGlobal)
-
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(PushSubscription, mGlobal, mOptions)
 NS_IMPL_CYCLE_COLLECTING_ADDREF(PushSubscription)
 NS_IMPL_CYCLE_COLLECTING_RELEASE(PushSubscription)
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(PushSubscription)
   NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
   NS_INTERFACE_MAP_ENTRY(nsISupports)
 NS_INTERFACE_MAP_END
 
 JSObject*
 PushSubscription::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
 {
   return PushSubscriptionBinding::Wrap(aCx, this, aGivenProto);
 }
 
 // static
 already_AddRefed<PushSubscription>
 PushSubscription::Constructor(GlobalObject& aGlobal,
-                              const nsAString& aEndpoint,
-                              const nsAString& aScope,
-                              const Nullable<ArrayBuffer>& aP256dhKey,
-                              const Nullable<ArrayBuffer>& aAuthSecret,
+                              const PushSubscriptionInit& aInitDict,
                               ErrorResult& aRv)
 {
   nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aGlobal.GetAsSupports());
 
-  nsTArray<uint8_t> rawKey, authSecret;
-  if ((!aP256dhKey.IsNull() && !CopyArrayBufferToArray(aP256dhKey.Value(),
-                                                       rawKey)) ||
-      (!aAuthSecret.IsNull() && !CopyArrayBufferToArray(aAuthSecret.Value(),
-                                                        authSecret))) {
+  nsTArray<uint8_t> rawKey;
+  if (aInitDict.mP256dhKey.WasPassed() &&
+      !aInitDict.mP256dhKey.Value().IsNull() &&
+      !PushUtil::CopyArrayBufferToArray(aInitDict.mP256dhKey.Value().Value(),
+                                        rawKey)) {
+    aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
+    return nullptr;
+  }
+
+  nsTArray<uint8_t> authSecret;
+  if (aInitDict.mAuthSecret.WasPassed() &&
+      !aInitDict.mAuthSecret.Value().IsNull() &&
+      !PushUtil::CopyArrayBufferToArray(aInitDict.mAuthSecret.Value().Value(),
+                                        authSecret)) {
     aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
     return nullptr;
   }
 
+  nsTArray<uint8_t> appServerKey;
+  if (aInitDict.mAppServerKey.WasPassed() &&
+      !aInitDict.mAppServerKey.Value().IsNull()) {
+    const OwningArrayBufferViewOrArrayBuffer& bufferSource =
+      aInitDict.mAppServerKey.Value().Value();
+    if (!PushUtil::CopyBufferSourceToArray(bufferSource, appServerKey)) {
+      aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
+      return nullptr;
+    }
+  }
+
   RefPtr<PushSubscription> sub = new PushSubscription(global,
-                                                      aEndpoint,
-                                                      aScope,
+                                                      aInitDict.mEndpoint,
+                                                      aInitDict.mScope,
                                                       Move(rawKey),
-                                                      Move(authSecret));
+                                                      Move(authSecret),
+                                                      Move(appServerKey));
 
   return sub.forget();
 }
 
 already_AddRefed<Promise>
 PushSubscription::Unsubscribe(ErrorResult& aRv)
 {
   if (!NS_IsMainThread()) {
@@ -310,47 +318,61 @@ PushSubscription::Unsubscribe(ErrorResul
     service->Unsubscribe(mScope, sop->GetPrincipal(), callback)));
 
   return p.forget();
 }
 
 void
 PushSubscription::GetKey(JSContext* aCx,
                          PushEncryptionKeyName aType,
-                         JS::MutableHandle<JSObject*> aKey)
+                         JS::MutableHandle<JSObject*> aKey,
+                         ErrorResult& aRv)
 {
-  if (aType == PushEncryptionKeyName::P256dh && !mRawP256dhKey.IsEmpty()) {
-    aKey.set(ArrayBuffer::Create(aCx,
-                                 mRawP256dhKey.Length(),
-                                 mRawP256dhKey.Elements()));
-  } else if (aType == PushEncryptionKeyName::Auth && !mAuthSecret.IsEmpty()) {
-    aKey.set(ArrayBuffer::Create(aCx,
-                                 mAuthSecret.Length(),
-                                 mAuthSecret.Elements()));
+  if (aType == PushEncryptionKeyName::P256dh) {
+    PushUtil::CopyArrayToArrayBuffer(aCx, mRawP256dhKey, aKey, aRv);
+  } else if (aType == PushEncryptionKeyName::Auth) {
+    PushUtil::CopyArrayToArrayBuffer(aCx, mAuthSecret, aKey, aRv);
   } else {
     aKey.set(nullptr);
   }
 }
 
 void
-PushSubscription::ToJSON(PushSubscriptionJSON& aJSON)
+PushSubscription::ToJSON(PushSubscriptionJSON& aJSON, ErrorResult& aRv)
 {
   aJSON.mEndpoint.Construct();
   aJSON.mEndpoint.Value() = mEndpoint;
 
+  Base64URLEncodeOptions encodeOptions;
+  encodeOptions.mPad = false;
+
   aJSON.mKeys.mP256dh.Construct();
   nsresult rv = Base64URLEncode(mRawP256dhKey.Length(),
                                 mRawP256dhKey.Elements(),
+                                encodeOptions,
                                 aJSON.mKeys.mP256dh.Value());
-  Unused << NS_WARN_IF(NS_FAILED(rv));
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    aRv.Throw(rv);
+    return;
+  }
 
   aJSON.mKeys.mAuth.Construct();
   rv = Base64URLEncode(mAuthSecret.Length(), mAuthSecret.Elements(),
-                       aJSON.mKeys.mAuth.Value());
-  Unused << NS_WARN_IF(NS_FAILED(rv));
+                       encodeOptions, aJSON.mKeys.mAuth.Value());
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    aRv.Throw(rv);
+    return;
+  }
+}
+
+already_AddRefed<PushSubscriptionOptions>
+PushSubscription::Options()
+{
+  RefPtr<PushSubscriptionOptions> options = mOptions;
+  return options.forget();
 }
 
 already_AddRefed<Promise>
 PushSubscription::UnsubscribeFromWorker(ErrorResult& aRv)
 {
   WorkerPrivate* worker = GetCurrentThreadWorkerPrivate();
   MOZ_ASSERT(worker);
   worker->AssertIsOnWorkerThread();
--- a/dom/push/PushSubscription.h
+++ b/dom/push/PushSubscription.h
@@ -10,16 +10,17 @@
 #include "nsWrapperCache.h"
 
 #include "mozilla/AlreadyAddRefed.h"
 #include "mozilla/ErrorResult.h"
 #include "mozilla/RefPtr.h"
 
 #include "mozilla/dom/BindingDeclarations.h"
 #include "mozilla/dom/PushSubscriptionBinding.h"
+#include "mozilla/dom/PushSubscriptionOptionsBinding.h"
 #include "mozilla/dom/TypedArray.h"
 
 class nsIGlobalObject;
 
 namespace mozilla {
 namespace dom {
 
 namespace workers {
@@ -34,17 +35,18 @@ class PushSubscription final : public ns
 public:
   NS_DECL_CYCLE_COLLECTING_ISUPPORTS
   NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(PushSubscription)
 
   PushSubscription(nsIGlobalObject* aGlobal,
                    const nsAString& aEndpoint,
                    const nsAString& aScope,
                    nsTArray<uint8_t>&& aP256dhKey,
-                   nsTArray<uint8_t>&& aAuthSecret);
+                   nsTArray<uint8_t>&& aAuthSecret,
+                   nsTArray<uint8_t>&& aAppServerKey);
 
   JSObject*
   WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
 
   nsIGlobalObject*
   GetParentObject() const
   {
     return mGlobal;
@@ -54,42 +56,43 @@ public:
   GetEndpoint(nsAString& aEndpoint) const
   {
     aEndpoint = mEndpoint;
   }
 
   void
   GetKey(JSContext* cx,
          PushEncryptionKeyName aType,
-         JS::MutableHandle<JSObject*> aKey);
+         JS::MutableHandle<JSObject*> aKey,
+         ErrorResult& aRv);
 
   static already_AddRefed<PushSubscription>
   Constructor(GlobalObject& aGlobal,
-              const nsAString& aEndpoint,
-              const nsAString& aScope,
-              const Nullable<ArrayBuffer>& aP256dhKey,
-              const Nullable<ArrayBuffer>& aAuthSecret,
+              const PushSubscriptionInit& aInitDict,
               ErrorResult& aRv);
 
   already_AddRefed<Promise>
   Unsubscribe(ErrorResult& aRv);
 
   void
-  ToJSON(PushSubscriptionJSON& aJSON);
+  ToJSON(PushSubscriptionJSON& aJSON, ErrorResult& aRv);
 
-protected:
+  already_AddRefed<PushSubscriptionOptions>
+  Options();
+
+private:
   ~PushSubscription();
 
-private:
   already_AddRefed<Promise>
   UnsubscribeFromWorker(ErrorResult& aRv);
 
   nsString mEndpoint;
   nsString mScope;
   nsTArray<uint8_t> mRawP256dhKey;
   nsTArray<uint8_t> mAuthSecret;
   nsCOMPtr<nsIGlobalObject> mGlobal;
+  RefPtr<PushSubscriptionOptions> mOptions;
 };
 
 } // namespace dom
 } // namespace mozilla
 
 #endif // mozilla_dom_PushSubscription_h
new file mode 100644
--- /dev/null
+++ b/dom/push/PushSubscriptionOptions.cpp
@@ -0,0 +1,48 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/dom/PushSubscriptionOptions.h"
+
+#include "mozilla/dom/PushSubscriptionOptionsBinding.h"
+
+namespace mozilla {
+namespace dom {
+
+PushSubscriptionOptions::PushSubscriptionOptions(nsIGlobalObject* aGlobal,
+                                                 nsTArray<uint8_t>&& aAppServerKey)
+  : mGlobal(aGlobal)
+  , mAppServerKey(Move(aAppServerKey))
+{
+  // There's only one global on a worker, so we don't need to pass a global
+  // object to the constructor.
+  MOZ_ASSERT_IF(NS_IsMainThread(), mGlobal);
+}
+
+PushSubscriptionOptions::~PushSubscriptionOptions() {}
+
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(PushSubscriptionOptions, mGlobal)
+NS_IMPL_CYCLE_COLLECTING_ADDREF(PushSubscriptionOptions)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(PushSubscriptionOptions)
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(PushSubscriptionOptions)
+  NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
+  NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+JSObject*
+PushSubscriptionOptions::WrapObject(JSContext* aCx,
+                                    JS::Handle<JSObject*> aGivenProto)
+{
+  return PushSubscriptionOptionsBinding::Wrap(aCx, this, aGivenProto);
+}
+
+void
+PushSubscriptionOptions::GetApplicationServerKey(JSContext* aCx,
+                                                 JS::MutableHandle<JSObject*> aKey,
+                                                 ErrorResult& aRv)
+{
+  PushUtil::CopyArrayToArrayBuffer(aCx, mAppServerKey, aKey, aRv);
+}
+
+} // namespace dom
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/dom/push/PushSubscriptionOptions.h
@@ -0,0 +1,45 @@
+/* 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_dom_PushSubscriptionOptions_h
+#define mozilla_dom_PushSubscriptionOptions_h
+
+namespace mozilla {
+namespace dom {
+
+class PushSubscriptionOptions final : public nsISupports
+                                    , public nsWrapperCache
+{
+public:
+  NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+  NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(PushSubscriptionOptions)
+
+  PushSubscriptionOptions(nsIGlobalObject* aGlobal,
+                          nsTArray<uint8_t>&& aAppServerKey);
+
+  nsIGlobalObject*
+  GetParentObject() const
+  {
+    return mGlobal;
+  }
+
+  JSObject*
+  WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
+
+  void
+  GetApplicationServerKey(JSContext* aCx,
+                          JS::MutableHandle<JSObject*> aKey,
+                          ErrorResult& aRv);
+
+private:
+  ~PushSubscriptionOptions();
+
+  nsCOMPtr<nsIGlobalObject> mGlobal;
+  nsTArray<uint8_t> mAppServerKey;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_PushSubscriptionOptions_h
new file mode 100644
--- /dev/null
+++ b/dom/push/PushUtil.cpp
@@ -0,0 +1,58 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/dom/PushUtil.h"
+
+namespace mozilla {
+namespace dom {
+
+/* static */ bool
+PushUtil::CopyArrayBufferToArray(const ArrayBuffer& aBuffer,
+                                 nsTArray<uint8_t>& aArray)
+{
+  aBuffer.ComputeLengthAndData();
+  return aArray.SetLength(aBuffer.Length(), fallible) &&
+         aArray.ReplaceElementsAt(0, aBuffer.Length(), aBuffer.Data(),
+                                  aBuffer.Length(), fallible);
+}
+
+/* static */ bool
+PushUtil::CopyBufferSourceToArray(
+  const OwningArrayBufferViewOrArrayBuffer& aSource, nsTArray<uint8_t>& aArray)
+{
+  if (aSource.IsArrayBuffer()) {
+    return CopyArrayBufferToArray(aSource.GetAsArrayBuffer(), aArray);
+  }
+  if (aSource.IsArrayBufferView()) {
+    const ArrayBufferView& view = aSource.GetAsArrayBufferView();
+    view.ComputeLengthAndData();
+    return aArray.SetLength(view.Length(), fallible) &&
+           aArray.ReplaceElementsAt(0, view.Length(), view.Data(),
+                                    view.Length(), fallible);
+  }
+  MOZ_CRASH("Uninitialized union: expected buffer or view");
+}
+
+/* static */ void
+PushUtil::CopyArrayToArrayBuffer(JSContext* aCx,
+                                 const nsTArray<uint8_t>& aArray,
+                                 JS::MutableHandle<JSObject*> aValue,
+                                 ErrorResult& aRv)
+{
+  if (aArray.IsEmpty()) {
+    aValue.set(nullptr);
+    return;
+  }
+  JS::Rooted<JSObject*> buffer(aCx, ArrayBuffer::Create(aCx,
+                                                        aArray.Length(),
+                                                        aArray.Elements()));
+  if (NS_WARN_IF(!buffer)) {
+    aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
+    return;
+  }
+  aValue.set(buffer);
+}
+
+} // namespace dom
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/dom/push/PushUtil.h
@@ -0,0 +1,35 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_PushUtil_h
+#define mozilla_dom_PushUtil_h
+
+namespace mozilla {
+namespace dom {
+
+class OwningArrayBufferViewOrArrayBuffer;
+
+class PushUtil final
+{
+private:
+  PushUtil() = delete;
+
+public:
+  static bool
+  CopyArrayBufferToArray(const ArrayBuffer& aBuffer,
+                         nsTArray<uint8_t>& aArray);
+
+  static bool
+  CopyBufferSourceToArray(const OwningArrayBufferViewOrArrayBuffer& aSource,
+                          nsTArray<uint8_t>& aArray);
+
+  static void
+  CopyArrayToArrayBuffer(JSContext* aCx, const nsTArray<uint8_t>& aArray,
+                         JS::MutableHandle<JSObject*> aValue, ErrorResult& aRv);
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_PushUtil_h
--- a/dom/push/moz.build
+++ b/dom/push/moz.build
@@ -35,22 +35,26 @@ MOCHITEST_MANIFESTS += [
 XPCSHELL_TESTS_MANIFESTS += [
     'test/xpcshell/xpcshell.ini',
 ]
 
 EXPORTS.mozilla.dom += [
     'PushManager.h',
     'PushNotifier.h',
     'PushSubscription.h',
+    'PushSubscriptionOptions.h',
+    'PushUtil.h',
 ]
 
 UNIFIED_SOURCES += [
     'PushManager.cpp',
     'PushNotifier.cpp',
     'PushSubscription.cpp',
+    'PushSubscriptionOptions.cpp',
+    'PushUtil.cpp',
 ]
 
 TEST_DIRS += ['test/xpcshell']
 
 include('/ipc/chromium/chromium-config.mozbuild')
 
 if CONFIG['GNU_CXX']:
     CXXFLAGS += ['-Wshadow']
--- a/dom/push/test/mochitest.ini
+++ b/dom/push/test/mochitest.ini
@@ -7,16 +7,17 @@ support-files =
   lifetime_worker.js
   test_utils.js
   mockpushserviceparent.js
   error_worker.js
 
 [test_has_permissions.html]
 [test_permissions.html]
 [test_register.html]
+[test_register_key.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]
new file mode 100644
--- /dev/null
+++ b/dom/push/test/test_register_key.html
@@ -0,0 +1,206 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+Bug 1247685: Implement `applicationServerKey` for subscription association.
+
+Any copyright is dedicated to the Public Domain.
+http://creativecommons.org/licenses/publicdomain/
+
+-->
+<head>
+  <title>Test for Bug 1247685</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=1247685">Mozilla Bug 1247685</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+
+<script class="testbody" type="text/javascript">
+
+  var isTestingMismatchedKey = false;
+  var subscriptions = 0;
+  var testKey; // Generated in `start`.
+
+  function generateKey() {
+    return crypto.subtle.generateKey({
+      name: "ECDSA",
+      namedCurve: "P-256",
+    }, true, ["sign", "verify"]).then(cryptoKey =>
+      crypto.subtle.exportKey("raw", cryptoKey.publicKey)
+    ).then(publicKey => new Uint8Array(publicKey));
+  }
+
+  var registration;
+  add_task(function* start() {
+    yield setupPrefsAndReplaceService({
+      register(pageRecord) {
+        ok(pageRecord.appServerKey.length > 0,
+          "App server key should not be empty");
+        if (pageRecord.appServerKey.length != 65) {
+          throw { result:
+                  SpecialPowers.Cr.NS_ERROR_DOM_PUSH_INVALID_KEY_ERR };
+        }
+        if (isTestingMismatchedKey) {
+          throw { result:
+                  SpecialPowers.Cr.NS_ERROR_DOM_PUSH_MISMATCHED_KEY_ERR };
+        }
+        return {
+          endpoint: "https://example.com/push/" + (++subscriptions),
+          appServerKey: pageRecord.appServerKey,
+        };
+      },
+
+      registration(pageRecord) {
+        return {
+          endpoint: "https://example.com/push/subWithKey",
+          appServerKey: testKey,
+        };
+      },
+    });
+    yield setPushPermission(true);
+    testKey = yield generateKey();
+
+    var url = "worker.js" + "?" + (Math.random());
+    registration = yield navigator.serviceWorker.register(url, {scope: "."});
+  });
+
+  var controlledFrame;
+  add_task(function* createControlledIFrame() {
+    controlledFrame = yield injectControlledFrame();
+  });
+
+  add_task(function* emptyKey() {
+    try {
+      yield registration.pushManager.subscribe({
+        applicationServerKey: new ArrayBuffer(0),
+      });
+      ok(false, "Should reject for empty app server keys");
+    } catch (error) {
+      ok(error instanceof DOMException,
+        "Wrong exception type for empty key");
+      is(error.name, "InvalidAccessError",
+        "Wrong exception name for empty key");
+    }
+  });
+
+  add_task(function* invalidKey() {
+    try {
+      yield registration.pushManager.subscribe({
+        applicationServerKey: new Uint8Array([0]),
+      });
+      ok(false, "Should reject for invalid app server keys");
+    } catch (error) {
+      ok(error instanceof DOMException,
+        "Wrong exception type for invalid key");
+      is(error.name, "InvalidAccessError",
+        "Wrong exception name for invalid key");
+    }
+  });
+
+  add_task(function* validKey() {
+    var pushSubscription = yield registration.pushManager.subscribe({
+      applicationServerKey: yield generateKey(),
+    });
+    is(pushSubscription.endpoint, "https://example.com/push/1",
+      "Wrong endpoint for subscription with key");
+  });
+
+  add_task(function* retrieveKey() {
+    var pushSubscription = yield registration.pushManager.getSubscription();
+    is(pushSubscription.endpoint, "https://example.com/push/subWithKey",
+      "Got wrong endpoint for subscription with key");
+    isDeeply(
+      new Uint8Array(pushSubscription.options.applicationServerKey),
+      testKey,
+      "Got wrong app server key"
+    );
+  });
+
+  add_task(function* mismatchedKey() {
+    isTestingMismatchedKey = true;
+    try {
+      yield registration.pushManager.subscribe({
+        applicationServerKey: yield generateKey(),
+      });
+      ok(false, "Should reject for mismatched app server keys");
+    } catch (error) {
+      ok(error instanceof DOMException,
+        "Wrong exception type for mismatched key");
+      is(error.name, "InvalidStateError",
+        "Wrong exception name for mismatched key");
+    } finally {
+      isTestingMismatchedKey = false;
+    }
+  });
+
+  add_task(function* emptyKeyInWorker() {
+    var errorInfo = yield sendRequestToWorker({
+      type: "subscribeWithKey",
+      key: new ArrayBuffer(0),
+    });
+    ok(errorInfo.isDOMException,
+      "Wrong exception type in worker for empty key");
+    is(errorInfo.name, "InvalidAccessError",
+      "Wrong exception name in worker for empty key");
+  });
+
+  add_task(function* invalidKeyInWorker() {
+    var errorInfo = yield sendRequestToWorker({
+      type: "subscribeWithKey",
+      key: new Uint8Array([1]),
+    });
+    ok(errorInfo.isDOMException,
+      "Wrong exception type in worker for invalid key");
+    is(errorInfo.name, "InvalidAccessError",
+      "Wrong exception name in worker for invalid key");
+  });
+
+  add_task(function* validKeyInWorker() {
+    var key = yield generateKey();
+    var data = yield sendRequestToWorker({
+      type: "subscribeWithKey",
+      key: key,
+    });
+    is(data.endpoint, "https://example.com/push/2",
+      "Wrong endpoint for subscription with key created in worker");
+    isDeeply(new Uint8Array(data.key), key,
+      "Wrong app server key for subscription created in worker");
+  });
+
+  add_task(function* mismatchedKeyInWorker() {
+    isTestingMismatchedKey = true;
+    try {
+      var errorInfo = yield sendRequestToWorker({
+        type: "subscribeWithKey",
+        key: yield generateKey(),
+      });
+      ok(errorInfo.isDOMException,
+        "Wrong exception type in worker for mismatched key");
+      is(errorInfo.name, "InvalidStateError",
+        "Wrong exception name in worker for mismatched key");
+    } finally {
+      isTestingMismatchedKey = false;
+    }
+  });
+
+  add_task(function* unsubscribe() {
+    is(subscriptions, 2, "Wrong subscription count");
+    controlledFrame.remove();
+  });
+
+  add_task(function* unregister() {
+    yield registration.unregister();
+  });
+
+</script>
+</body>
+</html>
+
--- a/dom/push/test/worker.js
+++ b/dom/push/test/worker.js
@@ -68,62 +68,85 @@ function handlePush(event) {
       };
     }
     broadcast(event, message);
     return;
   }
   broadcast(event, {type: "finished", okay: "no"});
 }
 
-function handleMessage(event) {
-  if (event.data.type == "publicKey") {
-    reply(event, self.registration.pushManager.getSubscription().then(
+var testHandlers = {
+  publicKey(data) {
+    return self.registration.pushManager.getSubscription().then(
       subscription => ({
         p256dh: subscription.getKey("p256dh"),
         auth: subscription.getKey("auth"),
       })
-    ));
-    return;
-  }
-  if (event.data.type == "resubscribe") {
-    reply(event, self.registration.pushManager.getSubscription().then(
+    );
+  },
+
+  resubscribe(data) {
+    return self.registration.pushManager.getSubscription().then(
       subscription => {
-        assert(subscription.endpoint == event.data.endpoint,
+        assert(subscription.endpoint == data.endpoint,
           "Wrong push endpoint in worker");
         return subscription.unsubscribe();
       }
     ).then(result => {
       assert(result, "Error unsubscribing in worker");
       return self.registration.pushManager.getSubscription();
     }).then(subscription => {
       assert(!subscription, "Subscription not removed in worker");
       return self.registration.pushManager.subscribe();
     }).then(subscription => {
       return {
         endpoint: subscription.endpoint,
       };
-    }));
-    return;
-  }
-  if (event.data.type == "denySubscribe") {
-    reply(event, self.registration.pushManager.getSubscription().then(
+    });
+  },
+
+  denySubscribe(data) {
+    return self.registration.pushManager.getSubscription().then(
       subscription => {
         assert(!subscription,
           "Should not return worker subscription with revoked permission");
         return self.registration.pushManager.subscribe().then(_ => {
           assert(false, "Expected error subscribing with revoked permission");
         }, error => {
           return {
             isDOMException: error instanceof DOMException,
             name: error.name,
           };
         });
       }
-    ));
-    return;
+    );
+  },
+
+  subscribeWithKey(data) {
+    return self.registration.pushManager.subscribe({
+      applicationServerKey: data.key,
+    }).then(subscription => {
+      return {
+        endpoint: subscription.endpoint,
+        key: subscription.options.applicationServerKey,
+      };
+    }, error => {
+      return {
+        isDOMException: error instanceof DOMException,
+        name: error.name,
+      };
+    });
+  },
+};
+
+function handleMessage(event) {
+  var handler = testHandlers[event.data.type];
+  if (handler) {
+    reply(event, handler(event.data));
+  } else {
+    reply(event, Promise.reject(
+      "Invalid message type: " + event.data.type));
   }
-  reply(event, Promise.reject(
-    "Invalid message type: " + event.data.type));
 }
 
 function handlePushSubscriptionChange(event) {
   broadcast(event, {type: "changed", okay: "yes"});
 }
--- a/dom/push/test/xpcshell/head.js
+++ b/dom/push/test/xpcshell/head.js
@@ -450,16 +450,25 @@ var setUpServiceInParent = Task.async(fu
         onHello(request) {
           this.serverSendMsg(JSON.stringify({
             messageType: 'hello',
             uaid: userAgentID,
             status: 200,
           }));
         },
         onRegister(request) {
+          if (request.key) {
+            let appServerKey = new Uint8Array(
+              ChromeUtils.base64URLDecode(request.key, {
+                padding: "require",
+              })
+            );
+            equal(appServerKey.length, 65, 'Wrong app server key length');
+            equal(appServerKey[0], 4, 'Wrong app server key format');
+          }
           this.serverSendMsg(JSON.stringify({
             messageType: 'register',
             uaid: userAgentID,
             channelID: request.channelID,
             status: 200,
             pushEndpoint: 'https://example.org/push/' + request.channelID,
           }));
         },
--- a/dom/push/test/xpcshell/test_crypto.js
+++ b/dom/push/test/xpcshell/test_crypto.js
@@ -1,12 +1,11 @@
 'use strict';
 
 const {
-  base64UrlDecode,
   getCryptoParams,
   PushCrypto,
 } = Cu.import('resource://gre/modules/PushCrypto.jsm', {});
 
 function run_test() {
   run_next_test();
 }
 
@@ -133,17 +132,19 @@ add_task(function* test_crypto_decodeMsg
     crv: 'P-256',
     d: '4h23G_KkXC9TvBSK2v0Q7ImpS2YAuRd8hQyN0rFAwBg',
     ext: true,
     key_ops: ['deriveBits'],
     kty: 'EC',
     x: 'sd85ZCbEG6dEkGMCmDyGBIt454Qy-Yo-1xhbaT2Jlk4',
     y: 'vr3cKpQ-Sp1kpZ9HipNjUCwSA55yy0uM8N9byE8dmLs',
   };
-  let publicKey = base64UrlDecode('BLHfOWQmxBunRJBjApg8hgSLeOeEMvmKPtcYW2k9iZZOvr3cKpQ-Sp1kpZ9HipNjUCwSA55yy0uM8N9byE8dmLs');
+  let publicKey = ChromeUtils.base64URLDecode('BLHfOWQmxBunRJBjApg8hgSLeOeEMvmKPtcYW2k9iZZOvr3cKpQ-Sp1kpZ9HipNjUCwSA55yy0uM8N9byE8dmLs', {
+    padding: "reject",
+  });
 
   let expectedSuccesses = [{
     desc: 'padSize = 2, rs = 24, pad = 0',
     result: 'Some message',
     data: 'Oo34w2F9VVnTMFfKtdx48AZWQ9Li9M6DauWJVgXU',
     senderPublicKey: 'BCHFVrflyxibGLlgztLwKelsRZp4gqX3tNfAKFaxAcBhpvYeN1yIUMrxaDKiLh4LNKPtj0BOXGdr-IQ-QP82Wjo',
     salt: 'zCU18Rw3A5aB_Xi-vfixmA',
     rs: 24,
@@ -172,18 +173,23 @@ add_task(function* test_crypto_decodeMsg
     data: 'oY4e5eDatDVt2fpQylxbPJM-3vrfhDasfPc8Q1PWt4tPfMVbz_sDNL_cvr0DXXkdFzS1lxsJsj550USx4MMl01ihjImXCjrw9R5xFgFrCAqJD3GwXA1vzS4T5yvGVbUp3SndMDdT1OCcEofTn7VC6xZ-zP8rzSQfDCBBxmPU7OISzr8Z4HyzFCGJeBfqiZ7yUfNlKF1x5UaZ4X6iU_TXx5KlQy_toV1dXZ2eEAMHJUcSdArvB6zRpFdEIxdcHcJyo1BIYgAYTDdAIy__IJVCPY_b2CE5W_6ohlYKB7xDyH8giNuWWXAgBozUfScLUVjPC38yJTpAUi6w6pXgXUWffende5FreQpnMFL1L4G-38wsI_-ISIOzdO8QIrXHxmtc1S5xzYu8bMqSgCinvCEwdeGFCmighRjj8t1zRWo0D14rHbQLPR_b1P5SvEeJTtS9Nm3iibM',
     senderPublicKey: 'BCg6ZIGuE2ZNm2ti6Arf4CDVD_8--aLXAGLYhpghwjl1xxVjTLLpb7zihuEOGGbyt8Qj0_fYHBP4ObxwJNl56bk',
     salt: '5LIDBXbvkBvvb7ZdD-T4PQ',
     rs: 3,
     authSecret: 'g2rWVHUCpUxgcL9Tz7vyeQ',
     padSize: 2,
   }];
   for (let test of expectedSuccesses) {
-    let authSecret = test.authSecret ? base64UrlDecode(test.authSecret) : null;
-    let result = yield PushCrypto.decodeMsg(base64UrlDecode(test.data),
+    let authSecret = test.authSecret ? ChromeUtils.base64URLDecode(test.authSecret, {
+      padding: "reject",
+    }) : null;
+    let data = ChromeUtils.base64URLDecode(test.data, {
+      padding: "reject",
+    });
+    let result = yield PushCrypto.decodeMsg(data,
                                             privateKey, publicKey,
                                             test.senderPublicKey, test.salt,
                                             test.rs, authSecret, test.padSize);
     let decoder = new TextDecoder('utf-8');
     equal(decoder.decode(new Uint8Array(result)), test.result, test.desc);
   }
 
   let expectedFailures = [{
@@ -218,17 +224,22 @@ add_task(function* test_crypto_decodeMsg
     authSecret: 'BhbpNTWyO5wVJmVKTV6XaA',
     padSize: 2,
   }, {
     desc: 'Truncated input',
     data: 'AlDjj6NvT5HGyrHbT8M5D6XBFSra6xrWS9B2ROaCIjwSu3RyZ1iyuv0',
     rs: 25,
   }];
   for (let test of expectedFailures) {
-    let authSecret = test.authSecret ? base64UrlDecode(test.authSecret) : null;
+    let authSecret = test.authSecret ? ChromeUtils.base64URLDecode(test.authSecret, {
+      padding: "reject",
+    }) : null;
+    let data = ChromeUtils.base64URLDecode(test.data, {
+      padding: "reject",
+    });
     yield rejects(
-      PushCrypto.decodeMsg(base64UrlDecode(test.data), privateKey, publicKey,
+      PushCrypto.decodeMsg(data, privateKey, publicKey,
                            test.senderPublicKey, test.salt, test.rs,
                            authSecret, test.padSize),
       test.desc
     );
   }
 });
--- a/dom/push/test/xpcshell/test_notification_data.js
+++ b/dom/push/test/xpcshell/test_notification_data.js
@@ -1,15 +1,14 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 'use strict';
 
 const {PushDB, PushService, PushServiceWebSocket} = serviceExports;
-const {base64UrlDecode} = Cu.import('resource://gre/modules/PushCrypto.jsm', {});
 
 let db;
 let userAgentID = 'f5b47f8d-771f-4ea3-b999-91c135f8766d';
 
 function run_test() {
   do_get_profile();
   setPrefs({
     userAgentID: userAgentID,
@@ -22,19 +21,23 @@ function putRecord(channelID, scope, pub
     channelID: channelID,
     pushEndpoint: 'https://example.org/push/' + channelID,
     scope: scope,
     pushCount: 0,
     lastPush: 0,
     originAttributes: '',
     quota: Infinity,
     systemRecord: true,
-    p256dhPublicKey: base64UrlDecode(publicKey),
+    p256dhPublicKey: ChromeUtils.base64URLDecode(publicKey, {
+      padding: "reject",
+    }),
     p256dhPrivateKey: privateKey,
-    authenticationSecret: base64UrlDecode(authSecret),
+    authenticationSecret: ChromeUtils.base64URLDecode(authSecret, {
+      padding: "reject",
+    }),
   });
 }
 
 let ackDone;
 let server;
 add_task(function* test_notification_ack_data_setup() {
   db = PushServiceWebSocket.newPushDB();
   do_register_cleanup(() => {return db.drop().then(_ => db.close());});
--- a/dom/push/test/xpcshell/test_notification_http2.js
+++ b/dom/push/test/xpcshell/test_notification_http2.js
@@ -1,17 +1,16 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 'use strict';
 
 Cu.import("resource://gre/modules/Services.jsm");
 
 const {PushDB, PushService, PushServiceHttp2} = serviceExports;
-const {base64UrlDecode} = Cu.import('resource://gre/modules/PushCrypto.jsm', {});
 
 var prefs;
 var tlsProfile;
 var pushEnabled;
 var pushConnectionEnabled;
 
 var serverPort = -1;
 
@@ -124,27 +123,31 @@ add_task(function* test_pushNotification
       { appId: Ci.nsIScriptSecurityManager.NO_APP_ID, inIsolatedMozBrowser: false }),
     quota: Infinity,
     systemRecord: true,
   }, {
     subscriptionUri: serverURL + '/pushNotifications/subscription4',
     pushEndpoint: serverURL + '/pushEndpoint4',
     pushReceiptEndpoint: serverURL + '/pushReceiptEndpoint4',
     scope: 'https://example.com/page/4',
-    p256dhPublicKey: base64UrlDecode('BEcvDzkWCrUtjU_wygL98sbQCQrW1lY9irtgGnlCc4B0JJXLCHB9MTM73qD6GZYfL0YOvKo8XLOflh-J4dMGklU'),
+    p256dhPublicKey: ChromeUtils.base64URLDecode('BEcvDzkWCrUtjU_wygL98sbQCQrW1lY9irtgGnlCc4B0JJXLCHB9MTM73qD6GZYfL0YOvKo8XLOflh-J4dMGklU', {
+      padding: "reject",
+    }),
     p256dhPrivateKey: {
       crv: 'P-256',
       d: 'fWi7tZaX0Pk6WnLrjQ3kiRq_g5XStL5pdH4pllNCqXw',
       ext: true,
       key_ops: ["deriveBits"],
       kty: 'EC',
       x: 'Ry8PORYKtS2NT_DKAv3yxtAJCtbWVj2Ku2AaeUJzgHQ',
       y: 'JJXLCHB9MTM73qD6GZYfL0YOvKo8XLOflh-J4dMGklU'
     },
-    authenticationSecret: base64UrlDecode('cwDVC1iwAn8E37mkR3tMSg'),
+    authenticationSecret: ChromeUtils.base64URLDecode('cwDVC1iwAn8E37mkR3tMSg', {
+      padding: "reject",
+    }),
     originAttributes: ChromeUtils.originAttributesToSuffix(
       { appId: Ci.nsIScriptSecurityManager.NO_APP_ID, inIsolatedMozBrowser: false }),
     quota: Infinity,
     systemRecord: true,
   }];
 
   for (let record of records) {
     yield db.put(record);
--- a/dom/push/test/xpcshell/test_service_child.js
+++ b/dom/push/test/xpcshell/test_service_child.js
@@ -1,17 +1,33 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 'use strict';
 
+Cu.importGlobalProperties(["crypto"]);
+
 const {PushDB, PushService, PushServiceWebSocket} = serviceExports;
 
 var db;
 
+function done() {
+  do_test_finished();
+  run_next_test();
+}
+
+function generateKey() {
+  return crypto.subtle.generateKey({
+    name: "ECDSA",
+    namedCurve: "P-256",
+  }, true, ["sign", "verify"]).then(cryptoKey =>
+    crypto.subtle.exportKey("raw", cryptoKey.publicKey)
+  ).then(publicKey => new Uint8Array(publicKey));
+}
+
 function run_test() {
   if (isParent) {
     do_get_profile();
   }
   run_next_test();
 }
 
 if (isParent) {
@@ -35,16 +51,79 @@ add_test(function test_subscribe_success
       equal(subscription.quota, -1, 'Wrong quota for system subscription');
 
       do_test_finished();
       run_next_test();
     }
   );
 });
 
+add_test(function test_subscribeWithKey_error() {
+  do_test_pending();
+
+  let invalidKey = [0, 1];
+  PushServiceComponent.subscribeWithKey(
+    'https://example.com/sub-key/invalid',
+    Services.scriptSecurityManager.getSystemPrincipal(),
+    invalidKey.length,
+    invalidKey,
+    (result, subscription) => {
+      ok(!Components.isSuccessCode(result), 'Expected error creating subscription with invalid key');
+      equal(result, Cr.NS_ERROR_DOM_PUSH_INVALID_KEY_ERR, 'Wrong error code for invalid key');
+      strictEqual(subscription, null, 'Unexpected subscription');
+
+      do_test_finished();
+      run_next_test();
+    }
+  );
+});
+
+add_test(function test_subscribeWithKey_success() {
+  do_test_pending();
+
+  generateKey().then(key => {
+    PushServiceComponent.subscribeWithKey(
+      'https://example.com/sub-key/ok',
+      Services.scriptSecurityManager.getSystemPrincipal(),
+      key.length,
+      key,
+      (result, subscription) => {
+        ok(Components.isSuccessCode(result), 'Error creating subscription with key');
+        notStrictEqual(subscription, null, 'Expected subscription');
+        done();
+      }
+    );
+  }, error => {
+    ok(false, "Error generating app server key");
+    done();
+  });
+});
+
+add_test(function test_subscribeWithKey_conflict() {
+  do_test_pending();
+
+  generateKey().then(differentKey => {
+    PushServiceComponent.subscribeWithKey(
+      'https://example.com/sub-key/ok',
+      Services.scriptSecurityManager.getSystemPrincipal(),
+      differentKey.length,
+      differentKey,
+      (result, subscription) => {
+        ok(!Components.isSuccessCode(result), 'Expected error creating subscription with conflicting key');
+        equal(result, Cr.NS_ERROR_DOM_PUSH_MISMATCHED_KEY_ERR, 'Wrong error code for mismatched key');
+        strictEqual(subscription, null, 'Unexpected subscription');
+        done();
+      }
+    );
+  }, error => {
+    ok(false, "Error generating different app server key");
+    done();
+  });
+});
+
 add_test(function test_subscribe_error() {
   do_test_pending();
   PushServiceComponent.subscribe(
     'https://example.com/sub/fail',
     Services.scriptSecurityManager.getSystemPrincipal(),
     (result, subscription) => {
       ok(!Components.isSuccessCode(result), 'Expected error creating subscription');
       strictEqual(subscription, null, 'Unexpected subscription');
--- a/dom/tests/browser/browser.ini
+++ b/dom/tests/browser/browser.ini
@@ -22,19 +22,17 @@ skip-if = e10s
 [browser_ConsoleStorageAPITests.js]
 skip-if = e10s
 [browser_ConsoleStoragePBTest_perwindowpb.js]
 skip-if = e10s
 [browser_autofocus_background.js]
 skip-if= buildapp == 'mulet'
 [browser_autofocus_preference.js]
 [browser_bug1238427.js]
-skip-if = e10s
 [browser_bug396843.js]
-skip-if = e10s
 [browser_focus_steal_from_chrome.js]
 skip-if = e10s
 [browser_focus_steal_from_chrome_during_mousedown.js]
 skip-if = e10s
 [browser_frame_elements.js]
 [browser_localStorage_privatestorageevent.js]
 skip-if = e10s
 [browser_test_new_window_from_content.js]
--- a/dom/tests/browser/browser_bug396843.js
+++ b/dom/tests/browser/browser_bug396843.js
@@ -1,294 +1,283 @@
-function test() {
-    /** Test for Bug 396843 **/
-    waitForExplicitFinish();
+/** Test for Bug 396843 **/
+
+function testInDocument(doc, documentID) {
+
+    var allNodes = [];
+    var XMLNodes = [];
+
+    // HTML
+    function HTML_TAG(name) {
+        allNodes.push(doc.createElement(name));
+    }
 
-    function testInDocument(doc, documentID) {
-        
-        var allNodes = [];
-        var XMLNodes = [];
+    /* List copy/pasted from nsHTMLTagList.h */
+    HTML_TAG("a", "Anchor")
+    HTML_TAG("abbr", "Span")
+    HTML_TAG("acronym", "Span")
+    HTML_TAG("address", "Span")
+    HTML_TAG("applet", "SharedObject")
+    HTML_TAG("area", "Area")
+    HTML_TAG("b", "Span")
+    HTML_TAG("base", "Shared")
+    HTML_TAG("basefont", "Span")
+    HTML_TAG("bdo", "Span")
+    HTML_TAG("bgsound", "Span")
+    HTML_TAG("big", "Span")
+    HTML_TAG("blockquote", "Shared")
+    HTML_TAG("body", "Body")
+    HTML_TAG("br", "BR")
+    HTML_TAG("button", "Button")
+    HTML_TAG("canvas", "Canvas")
+    HTML_TAG("caption", "TableCaption")
+    HTML_TAG("center", "Span")
+    HTML_TAG("cite", "Span")
+    HTML_TAG("code", "Span")
+    HTML_TAG("col", "TableCol")
+    HTML_TAG("colgroup", "TableCol")
+    HTML_TAG("dd", "Span")
+    HTML_TAG("del", "Mod")
+    HTML_TAG("dfn", "Span")
+    HTML_TAG("dir", "Shared")
+    HTML_TAG("div", "Div")
+    HTML_TAG("dl", "SharedList")
+    HTML_TAG("dt", "Span")
+    HTML_TAG("em", "Span")
+    HTML_TAG("embed", "SharedObject")
+    HTML_TAG("fieldset", "FieldSet")
+    HTML_TAG("font", "Font")
+    HTML_TAG("form", "Form")
+    HTML_TAG("frame", "Frame")
+    HTML_TAG("frameset", "FrameSet")
+    HTML_TAG("h1", "Heading")
+    HTML_TAG("h2", "Heading")
+    HTML_TAG("h3", "Heading")
+    HTML_TAG("h4", "Heading")
+    HTML_TAG("h5", "Heading")
+    HTML_TAG("h6", "Heading")
+    HTML_TAG("head", "Head")
+    HTML_TAG("hr", "HR")
+    HTML_TAG("html", "Html")
+    HTML_TAG("i", "Span")
+    HTML_TAG("iframe", "IFrame")
+    HTML_TAG("image", "")
+    HTML_TAG("img", "Image")
+    HTML_TAG("input", "Input")
+    HTML_TAG("ins", "Mod")
+    HTML_TAG("isindex", "Unknown")
+    HTML_TAG("kbd", "Span")
+    HTML_TAG("keygen", "Span")
+    HTML_TAG("label", "Label")
+    HTML_TAG("legend", "Legend")
+    HTML_TAG("li", "LI")
+    HTML_TAG("link", "Link")
+    HTML_TAG("listing", "Span")
+    HTML_TAG("map", "Map")
+    HTML_TAG("marquee", "Div")
+    HTML_TAG("menu", "Shared")
+    HTML_TAG("meta", "Meta")
+    HTML_TAG("multicol", "Unknown")
+    HTML_TAG("nobr", "Span")
+    HTML_TAG("noembed", "Div")
+    HTML_TAG("noframes", "Div")
+    HTML_TAG("noscript", "Div")
+    HTML_TAG("object", "Object")
+    HTML_TAG("ol", "SharedList")
+    HTML_TAG("optgroup", "OptGroup")
+    HTML_TAG("option", "Option")
+    HTML_TAG("p", "Paragraph")
+    HTML_TAG("param", "Shared")
+    HTML_TAG("plaintext", "Span")
+    HTML_TAG("pre", "Pre")
+    HTML_TAG("q", "Shared")
+    HTML_TAG("s", "Span")
+    HTML_TAG("samp", "Span")
+    HTML_TAG("script", "Script")
+    HTML_TAG("select", "Select")
+    HTML_TAG("small", "Span")
+    HTML_TAG("spacer", "Unknown")
+    HTML_TAG("span", "Span")
+    HTML_TAG("strike", "Span")
+    HTML_TAG("strong", "Span")
+    HTML_TAG("style", "Style")
+    HTML_TAG("sub", "Span")
+    HTML_TAG("sup", "Span")
+    HTML_TAG("table", "Table")
+    HTML_TAG("tbody", "TableSection")
+    HTML_TAG("td", "TableCell")
+    HTML_TAG("textarea", "TextArea")
+    HTML_TAG("tfoot", "TableSection")
+    HTML_TAG("th", "TableCell")
+    HTML_TAG("thead", "TableSection")
+    HTML_TAG("template", "Template")
+    HTML_TAG("title", "Title")
+    HTML_TAG("tr", "TableRow")
+    HTML_TAG("tt", "Span")
+    HTML_TAG("u", "Span")
+    HTML_TAG("ul", "SharedList")
+    HTML_TAG("var", "Span")
+    HTML_TAG("wbr", "Shared")
+    HTML_TAG("xmp", "Span")
 
-        // HTML
-        function HTML_TAG(name) {
-            allNodes.push(doc.createElement(name));
-        }
+    function SVG_TAG(name) {
+        allNodes.push(doc.createElementNS("http://www.w3.org/2000/svg", name));
+    }
 
-        /* List copy/pasted from nsHTMLTagList.h */
-        HTML_TAG("a", "Anchor")
-        HTML_TAG("abbr", "Span")
-        HTML_TAG("acronym", "Span")
-        HTML_TAG("address", "Span")
-        HTML_TAG("applet", "SharedObject")
-        HTML_TAG("area", "Area")
-        HTML_TAG("b", "Span")
-        HTML_TAG("base", "Shared")
-        HTML_TAG("basefont", "Span")
-        HTML_TAG("bdo", "Span")
-        HTML_TAG("bgsound", "Span")
-        HTML_TAG("big", "Span")
-        HTML_TAG("blockquote", "Shared")
-        HTML_TAG("body", "Body")
-        HTML_TAG("br", "BR")
-        HTML_TAG("button", "Button")
-        HTML_TAG("canvas", "Canvas")
-        HTML_TAG("caption", "TableCaption")
-        HTML_TAG("center", "Span")
-        HTML_TAG("cite", "Span")
-        HTML_TAG("code", "Span")
-        HTML_TAG("col", "TableCol")
-        HTML_TAG("colgroup", "TableCol")
-        HTML_TAG("dd", "Span")
-        HTML_TAG("del", "Mod")
-        HTML_TAG("dfn", "Span")
-        HTML_TAG("dir", "Shared")
-        HTML_TAG("div", "Div")
-        HTML_TAG("dl", "SharedList")
-        HTML_TAG("dt", "Span")
-        HTML_TAG("em", "Span")
-        HTML_TAG("embed", "SharedObject")
-        HTML_TAG("fieldset", "FieldSet")
-        HTML_TAG("font", "Font")
-        HTML_TAG("form", "Form")
-        HTML_TAG("frame", "Frame")
-        HTML_TAG("frameset", "FrameSet")
-        HTML_TAG("h1", "Heading")
-        HTML_TAG("h2", "Heading")
-        HTML_TAG("h3", "Heading")
-        HTML_TAG("h4", "Heading")
-        HTML_TAG("h5", "Heading")
-        HTML_TAG("h6", "Heading")
-        HTML_TAG("head", "Head")
-        HTML_TAG("hr", "HR")
-        HTML_TAG("html", "Html")
-        HTML_TAG("i", "Span")
-        HTML_TAG("iframe", "IFrame")
-        HTML_TAG("image", "")
-        HTML_TAG("img", "Image")
-        HTML_TAG("input", "Input")
-        HTML_TAG("ins", "Mod")
-        HTML_TAG("isindex", "Unknown")
-        HTML_TAG("kbd", "Span")
-        HTML_TAG("keygen", "Span")
-        HTML_TAG("label", "Label")
-        HTML_TAG("legend", "Legend")
-        HTML_TAG("li", "LI")
-        HTML_TAG("link", "Link")
-        HTML_TAG("listing", "Span")
-        HTML_TAG("map", "Map")
-        HTML_TAG("marquee", "Div")
-        HTML_TAG("menu", "Shared")
-        HTML_TAG("meta", "Meta")
-        HTML_TAG("multicol", "Unknown")
-        HTML_TAG("nobr", "Span")
-        HTML_TAG("noembed", "Div")
-        HTML_TAG("noframes", "Div")
-        HTML_TAG("noscript", "Div")
-        HTML_TAG("object", "Object")
-        HTML_TAG("ol", "SharedList")
-        HTML_TAG("optgroup", "OptGroup")
-        HTML_TAG("option", "Option")
-        HTML_TAG("p", "Paragraph")
-        HTML_TAG("param", "Shared")
-        HTML_TAG("plaintext", "Span")
-        HTML_TAG("pre", "Pre")
-        HTML_TAG("q", "Shared")
-        HTML_TAG("s", "Span")
-        HTML_TAG("samp", "Span")
-        HTML_TAG("script", "Script")
-        HTML_TAG("select", "Select")
-        HTML_TAG("small", "Span")
-        HTML_TAG("spacer", "Unknown")
-        HTML_TAG("span", "Span")
-        HTML_TAG("strike", "Span")
-        HTML_TAG("strong", "Span")
-        HTML_TAG("style", "Style")
-        HTML_TAG("sub", "Span")
-        HTML_TAG("sup", "Span")
-        HTML_TAG("table", "Table")
-        HTML_TAG("tbody", "TableSection")
-        HTML_TAG("td", "TableCell")
-        HTML_TAG("textarea", "TextArea")
-        HTML_TAG("tfoot", "TableSection")
-        HTML_TAG("th", "TableCell")
-        HTML_TAG("thead", "TableSection")
-        HTML_TAG("template", "Template")
-        HTML_TAG("title", "Title")
-        HTML_TAG("tr", "TableRow")
-        HTML_TAG("tt", "Span")
-        HTML_TAG("u", "Span")
-        HTML_TAG("ul", "SharedList")
-        HTML_TAG("var", "Span")
-        HTML_TAG("wbr", "Shared")
-        HTML_TAG("xmp", "Span")
+    // List sorta stolen from SVG element factory.
+    SVG_TAG("a")
+    SVG_TAG("polyline")
+    SVG_TAG("polygon")
+    SVG_TAG("circle")
+    SVG_TAG("ellipse")
+    SVG_TAG("line")
+    SVG_TAG("rect")
+    SVG_TAG("svg")
+    SVG_TAG("g")
+    SVG_TAG("foreignObject")
+    SVG_TAG("path")
+    SVG_TAG("text")
+    SVG_TAG("tspan")
+    SVG_TAG("image")
+    SVG_TAG("style")
+    SVG_TAG("linearGradient")
+    SVG_TAG("metadata")
+    SVG_TAG("radialGradient")
+    SVG_TAG("stop")
+    SVG_TAG("defs")
+    SVG_TAG("desc")
+    SVG_TAG("script")
+    SVG_TAG("use")
+    SVG_TAG("symbol")
+    SVG_TAG("marker")
+    SVG_TAG("title")
+    SVG_TAG("clipPath")
+    SVG_TAG("textPath")
+    SVG_TAG("filter")
+    SVG_TAG("feBlend")
+    SVG_TAG("feColorMatrix")
+    SVG_TAG("feComponentTransfer")
+    SVG_TAG("feComposite")
+    SVG_TAG("feFuncR")
+    SVG_TAG("feFuncG")
+    SVG_TAG("feFuncB")
+    SVG_TAG("feFuncA")
+    SVG_TAG("feGaussianBlur")
+    SVG_TAG("feMerge")
+    SVG_TAG("feMergeNode")
+    SVG_TAG("feMorphology")
+    SVG_TAG("feOffset")
+    SVG_TAG("feFlood")
+    SVG_TAG("feTile")
+    SVG_TAG("feTurbulence")
+    SVG_TAG("feConvolveMatrix")
+    SVG_TAG("feDistantLight")
+    SVG_TAG("fePointLight")
+    SVG_TAG("feSpotLight")
+    SVG_TAG("feDiffuseLighting")
+    SVG_TAG("feSpecularLighting")
+    SVG_TAG("feDisplacementMap")
+    SVG_TAG("feImage")
+    SVG_TAG("pattern")
+    SVG_TAG("mask")
+    SVG_TAG("svgSwitch")
 
-        function SVG_TAG(name) {
-            allNodes.push(doc.createElementNS("http://www.w3.org/2000/svg", name));
+    // Toss in some other namespaced stuff too, for good measure
+    // XUL stuff might not be creatable in content documents
+    try {
+        allNodes.push(doc.createElementNS(
+            "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul",
+            "window"));
+    } catch (e) {}
+    allNodes.push(doc.createElementNS("http://www.w3.org/1998/Math/MathML",
+                                        "math"));
+    allNodes.push(doc.createElementNS("http://www.w3.org/2001/xml-events",
+                                        "testname"));
+    allNodes.push(doc.createElementNS("bogus.namespace", "testname"));
+
+    var XMLDoc = doc.implementation.createDocument("", "", null);
+
+    // And non-elements
+    allNodes.push(doc.createTextNode("some text"));
+    allNodes.push(doc.createComment("some text"));
+    allNodes.push(doc.createDocumentFragment());
+    XMLNodes.push(XMLDoc.createCDATASection("some text"));
+    XMLNodes.push(XMLDoc.createProcessingInstruction("PI", "data"));
+        
+    function runTestUnwrapped() {
+        if (!("wrappedJSObject" in doc)) {
+            return;
+        }
+        ok(doc.wrappedJSObject.nodePrincipal === undefined,
+            "Must not have document principal for " + documentID);
+        ok(doc.wrappedJSObject.baseURIObject === undefined,
+            "Must not have document base URI for " + documentID);
+        ok(doc.wrappedJSObject.documentURIObject === undefined,
+            "Must not have document URI for " + documentID);
+
+        for (var i = 0; i < allNodes.length; ++i) {
+            ok(allNodes[i].wrappedJSObject.nodePrincipal === undefined,
+                "Unexpected principal appears for " + allNodes[i].nodeName +
+                " in " + documentID);
+            ok(allNodes[i].wrappedJSObject.baseURIObject === undefined,
+                "Unexpected base URI appears for " + allNodes[i].nodeName +
+                " in " + documentID);
+        }
+    }
+
+    function runTestProps() {
+        isnot(doc.nodePrincipal, null,
+                "Must have document principal in " + documentID);
+        is(doc.nodePrincipal instanceof Components.interfaces.nsIPrincipal,
+            true, "document principal must be a principal in " + documentID);
+        isnot(doc.baseURIObject, null,
+                "Must have document base URI in" + documentID);
+        is(doc.baseURIObject instanceof Components.interfaces.nsIURI,
+            true, "document base URI must be a URI in " + documentID);
+        isnot(doc.documentURIObject, null,
+                "Must have document URI " + documentID);
+        is(doc.documentURIObject instanceof Components.interfaces.nsIURI,
+            true, "document URI must be a URI in " + documentID);
+        is(doc.documentURIObject.spec, doc.documentURI,
+               "document URI must be the right URI in " + documentID);
+
+        for (var i = 0; i < allNodes.length; ++i) {
+            is(allNodes[i].nodePrincipal, doc.nodePrincipal,
+                "Unexpected principal for " + allNodes[i].nodeName +
+                " in " + documentID);
+            is(allNodes[i].baseURIObject, doc.baseURIObject,
+                "Unexpected base URI for " + allNodes[i].nodeName +
+                " in " + documentID);
         }
 
-        // List sorta stolen from SVG element factory.
-        SVG_TAG("a")
-        SVG_TAG("polyline")
-        SVG_TAG("polygon")
-        SVG_TAG("circle")
-        SVG_TAG("ellipse")
-        SVG_TAG("line")
-        SVG_TAG("rect")
-        SVG_TAG("svg")
-        SVG_TAG("g")
-        SVG_TAG("foreignObject")
-        SVG_TAG("path")
-        SVG_TAG("text")
-        SVG_TAG("tspan")
-        SVG_TAG("image")
-        SVG_TAG("style")
-        SVG_TAG("linearGradient")
-        SVG_TAG("metadata")
-        SVG_TAG("radialGradient")
-        SVG_TAG("stop")
-        SVG_TAG("defs")
-        SVG_TAG("desc")
-        SVG_TAG("script")
-        SVG_TAG("use")
-        SVG_TAG("symbol")
-        SVG_TAG("marker")
-        SVG_TAG("title")
-        SVG_TAG("clipPath")
-        SVG_TAG("textPath")
-        SVG_TAG("filter")
-        SVG_TAG("feBlend")
-        SVG_TAG("feColorMatrix")
-        SVG_TAG("feComponentTransfer")
-        SVG_TAG("feComposite")
-        SVG_TAG("feFuncR")
-        SVG_TAG("feFuncG")
-        SVG_TAG("feFuncB")
-        SVG_TAG("feFuncA")
-        SVG_TAG("feGaussianBlur")
-        SVG_TAG("feMerge")
-        SVG_TAG("feMergeNode")
-        SVG_TAG("feMorphology")
-        SVG_TAG("feOffset")
-        SVG_TAG("feFlood")
-        SVG_TAG("feTile")
-        SVG_TAG("feTurbulence")
-        SVG_TAG("feConvolveMatrix")
-        SVG_TAG("feDistantLight")
-        SVG_TAG("fePointLight")
-        SVG_TAG("feSpotLight")
-        SVG_TAG("feDiffuseLighting")
-        SVG_TAG("feSpecularLighting")
-        SVG_TAG("feDisplacementMap")
-        SVG_TAG("feImage")
-        SVG_TAG("pattern")
-        SVG_TAG("mask")
-        SVG_TAG("svgSwitch")
-
-        // Toss in some other namespaced stuff too, for good measure
-        // XUL stuff might not be creatable in content documents
-        try {
-            allNodes.push(doc.createElementNS(
-                "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul",
-                "window"));
-        } catch (e) {}
-        allNodes.push(doc.createElementNS("http://www.w3.org/1998/Math/MathML",
-                                          "math"));
-        allNodes.push(doc.createElementNS("http://www.w3.org/2001/xml-events",
-                                          "testname"));
-        allNodes.push(doc.createElementNS("bogus.namespace", "testname"));
-
-        var XMLDoc = doc.implementation.createDocument("", "", null);
-
-        // And non-elements
-        allNodes.push(doc.createTextNode("some text"));
-        allNodes.push(doc.createComment("some text"));
-        allNodes.push(doc.createDocumentFragment());
-        XMLNodes.push(XMLDoc.createCDATASection("some text"));
-        XMLNodes.push(XMLDoc.createProcessingInstruction("PI", "data"));
-        
-        function runTestUnwrapped() {
-            if (!("wrappedJSObject" in doc)) {
-                return;
-            }
-            ok(doc.wrappedJSObject.nodePrincipal === undefined,
-               "Must not have document principal for " + documentID);
-            ok(doc.wrappedJSObject.baseURIObject === undefined,
-               "Must not have document base URI for " + documentID);
-            ok(doc.wrappedJSObject.documentURIObject === undefined,
-               "Must not have document URI for " + documentID);
-
-            for (var i = 0; i < allNodes.length; ++i) {
-                ok(allNodes[i].wrappedJSObject.nodePrincipal === undefined,
-                   "Unexpected principal appears for " + allNodes[i].nodeName +
-                   " in " + documentID);
-                ok(allNodes[i].wrappedJSObject.baseURIObject === undefined,
-                   "Unexpected base URI appears for " + allNodes[i].nodeName +
-                   " in " + documentID);
-            }
+        for (i = 0; i < XMLNodes.length; ++i) {
+            is(XMLNodes[i].nodePrincipal, doc.nodePrincipal,
+                "Unexpected principal for " + XMLNodes[i].nodeName +
+                " in " + documentID);
+            is(XMLNodes[i].baseURIObject.spec, "about:blank",
+                "Unexpected base URI for " + XMLNodes[i].nodeName +
+                " in " + documentID);
         }
-        
-        function runTestProps() {
-            isnot(doc.nodePrincipal, null,
-                  "Must have document principal in " + documentID);
-            is(doc.nodePrincipal instanceof Components.interfaces.nsIPrincipal,
-               true, "document principal must be a principal in " + documentID);
-            isnot(doc.baseURIObject, null,
-                  "Must have document base URI in" + documentID);
-            is(doc.baseURIObject instanceof Components.interfaces.nsIURI,
-               true, "document base URI must be a URI in " + documentID);
-            isnot(doc.documentURIObject, null,
-                  "Must have document URI " + documentID);
-            is(doc.documentURIObject instanceof Components.interfaces.nsIURI,
-               true, "document URI must be a URI in " + documentID);
-            is(doc.documentURIObject.spec, doc.documentURI,
-               "document URI must be the right URI in " + documentID);
-     
-            for (var i = 0; i < allNodes.length; ++i) {
-                is(allNodes[i].nodePrincipal, doc.nodePrincipal,
-                   "Unexpected principal for " + allNodes[i].nodeName +
-                   " in " + documentID);
-                is(allNodes[i].baseURIObject, doc.baseURIObject,
-                   "Unexpected base URI for " + allNodes[i].nodeName +
-                   " in " + documentID);
-            }
-
-            for (i = 0; i < XMLNodes.length; ++i) {
-                is(XMLNodes[i].nodePrincipal, doc.nodePrincipal,
-                   "Unexpected principal for " + XMLNodes[i].nodeName +
-                   " in " + documentID);
-                is(XMLNodes[i].baseURIObject.spec, "about:blank",
-                   "Unexpected base URI for " + XMLNodes[i].nodeName +
-                   " in " + documentID);
-            }
-        }
-
-        runTestUnwrapped();
-        runTestProps();
-        runTestUnwrapped();
     }
 
-    var testsRunning = 2;
-    
-    testInDocument(document, "browser window");
+    runTestUnwrapped();
+    runTestProps();
+    runTestUnwrapped();
+}
 
-    function newTabTest(url) {
-        var newTab = gBrowser.addTab();
-        var newBrowser = gBrowser.getBrowserForTab(newTab);
-        newBrowser.contentWindow.location.href = url;
-        function testBrowser(event) {
-            newBrowser.removeEventListener("load", testBrowser, true);
-            is(event.target, newBrowser.contentDocument,
-               "Unexpected target in " + url);
-            testInDocument(newBrowser.contentDocument, url);
+add_task(function* test1() {
+    testInDocument(document, "browser window");
+});
 
-            gBrowser.removeTab(newTab);
+function newTabTest(location) {
+    let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, location);
+    let doc = content.document;
+    testInDocument(doc, location);
+    yield BrowserTestUtils.removeTab(tab);
+}
 
-            --testsRunning;
-            if (testsRunning == 0) {
-                finish();
-            }
-        }
-        newBrowser.addEventListener("load", testBrowser, true);
-    }
+add_task(function* test2() {
+    yield newTabTest("about:blank");
+});
 
-    newTabTest("about:blank");
-    newTabTest("about:config");
-}
+add_task(function* test3() {
+    yield newTabTest("about:config");
+});
--- a/dom/tests/mochitest/general/mochitest.ini
+++ b/dom/tests/mochitest/general/mochitest.ini
@@ -59,16 +59,17 @@ skip-if = ((buildapp == 'mulet' || build
 [test_bug628069_1.html]
 skip-if = (buildapp == 'b2g' && toolkit != 'gonk') #Bug 931116, b2g desktop specific, initial triage
 [test_bug628069_2.html]
 [test_bug631440.html]
 skip-if = (buildapp == 'b2g' && toolkit != 'gonk') #Bug 931116, b2g desktop specific, initial triage
 [test_bug653364.html]
 [test_bug861217.html]
 [test_clientRects.html]
+[test_clipboard_disallowed.html]
 [test_clipboard_events.html]
 skip-if = buildapp == 'b2g' # b2g(clipboard undefined) b2g-debug(clipboard undefined) b2g-desktop(clipboard undefined)
 [test_consoleAPI.html]
 [test_DOMMatrix.html]
 [test_domWindowUtils.html]
 [test_domWindowUtils_scrollXY.html]
 [test_domWindowUtils_scrollbarSize.html]
 [test_donottrack.html]
new file mode 100644
--- /dev/null
+++ b/dom/tests/mochitest/general/test_clipboard_disallowed.html
@@ -0,0 +1,61 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <title>Test for Clipboard Events</title>
+  <script type="text/javascript" src="/MochiKit/MochiKit.js"></script>
+  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <script type="application/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<p id="display"></p>
+<input id="input" value="INPUT TEXT" oncopy="checkAllowed(event)">
+
+<script>
+function doTest()
+{
+  document.getElementById("input").focus();
+  synthesizeKey("c", {accelKey: 1});
+}
+
+function checkAllowed(event)
+{
+  let clipboardData = event.clipboardData;
+
+  let exception;
+  try {
+    clipboardData.mozSetDataAt("text/customdata", document.getElementById("input"), 0);
+  } catch(ex) {
+    exception = ex;
+  }
+  is(String(exception).indexOf("SecurityError"), 0, "Cannot set non-string");
+
+  exception = null;
+  try {
+    clipboardData.mozSetDataAt("application/x-moz-file", "Test", 0);
+  } catch(ex) {
+    exception = ex;
+  }
+  is(String(exception).indexOf("SecurityError"), 0, "Cannot set file");
+
+  exception = null;
+  try {
+    clipboardData.mozSetDataAt("application/x-moz-file-promise", "Test", 0);
+  } catch(ex) {
+    exception = ex;
+  }
+  is(String(exception).indexOf("SecurityError"), 0, "Cannot set file promise");
+
+  exception = null;
+  try {
+    clipboardData.mozSetDataAt("application/something", "This is data", 0);
+  } catch(ex) {
+    exception = ex;
+  }
+  is(exception, null, "Can set custom data to a string");
+  SimpleTest.finish();
+}
+
+SimpleTest.waitForExplicitFinish();
+SimpleTest.waitForFocus(doTest);
+</script>
--- a/dom/tests/mochitest/general/test_clipboard_events.html
+++ b/dom/tests/mochitest/general/test_clipboard_events.html
@@ -448,17 +448,17 @@ function test_input_copypaste_dataTransf
     exh = false;
     try { cd.mozGetDataAt("text/plain", 1); } catch (ex) { exh = true; }
     ok(exh, "exception occured mozGetDataAt 1");
     exh = false;
     try { cd.mozClearDataAt("text/plain", 1); } catch (ex) { exh = true; }
     ok(exh, "exception occured mozClearDataAt 1");
 
     cd.setData("text/x-moz-url", "http://www.mozilla.org");
-    cd.mozSetDataAt("text/x-custom", "Custom Text", 0);
+    cd.mozSetDataAt("text/x-custom", "Custom Text with \u0000 null", 0);
     is(cd.mozItemCount, 1, "mozItemCount after set multiple types");
     return false;
   };
 
   try {
     selectContentInput();
     synthesizeKey("c", {accelKey: 1});
   }
@@ -474,19 +474,26 @@ function test_input_copypaste_dataTransf
     var cd = event.clipboardData;
     is(cd.mozItemCount, 1, "paste after copy multiple types mozItemCount");
     is(cd.getData("text/plain"), "would be a phrase", "paste text/plain multiple types");
 
     // Firefox for Android's clipboard code doesn't handle x-moz-url. Therefore
     // disabling the following test. Enable this once bug #840101 is fixed.
     if (navigator.appVersion.indexOf("Android") == -1) {
       is(cd.getData("text/x-moz-url"), "http://www.mozilla.org", "paste text/x-moz-url multiple types");
+      is(cd.getData("text/x-custom"), "Custom Text with \u0000 null", "paste text/custom multiple types");
+    } else {
+      is(cd.getData("text/x-custom"), "", "paste text/custom multiple types");
     }
-    // this is empty because only the built-in types are supported at the moment
-    is(cd.getData("text/x-custom"), "", "paste text/custom multiple types");
+
+    is(cd.getData("application/x-moz-custom-clipdata"), "", "application/x-moz-custom-clipdata is not present");
+
+    exh = false;
+    try { cd.setData("application/x-moz-custom-clipdata", "Some Data"); } catch (ex) { exh = true; }
+    ok(exh, "exception occured setData with application/x-moz-custom-clipdata");
 
     exh = false;
     try { cd.setData("text/plain", "Text on Paste"); } catch (ex) { exh = true; }
     ok(exh, "exception occured setData on paste");
 
     is(cd.getData("text/plain"), "would be a phrase", "text/plain data unchanged");
   };
   try {
--- a/dom/tests/mochitest/general/test_interfaces.html
+++ b/dom/tests/mochitest/general/test_interfaces.html
@@ -963,16 +963,18 @@ var interfaceNamesInGlobalScope =
     "Promise",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "PropertyNodeList",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     {name: "PushManager", b2g: false, nightlyAndroid: true, android: false},
 // IMPORTANT: Do not change this list without review from a DOM peer!
     {name: "PushSubscription", b2g: false, nightlyAndroid: true, android: false},
 // IMPORTANT: Do not change this list without review from a DOM peer!
+    {name: "PushSubscriptionOptions", b2g: false, nightlyAndroid: true, android: false},
+// IMPORTANT: Do not change this list without review from a DOM peer!
     "RadioNodeList",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "Range",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "RecordErrorEvent",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "Rect",
 // IMPORTANT: Do not change this list without review from a DOM peer!
--- a/dom/webidl/Animation.webidl
+++ b/dom/webidl/Animation.webidl
@@ -7,17 +7,17 @@
  * http://w3c.github.io/web-animations/#the-animation-interface
  *
  * Copyright © 2015 W3C® (MIT, ERCIM, Keio), All Rights Reserved. W3C
  * liability, trademark and document use rules apply.
  */
 
 enum AnimationPlayState { "idle", "pending", "running", "paused", "finished" };
 
-[Func="nsDocument::IsWebAnimationsEnabled",
+[Func="nsDocument::IsElementAnimateEnabled",
  Constructor (optional KeyframeEffectReadOnly? effect = null,
               optional AnimationTimeline? timeline = null)]
 interface Animation : EventTarget {
   attribute DOMString id;
   // Bug 1049975: Make 'effect' writeable
   [Func="nsDocument::IsWebAnimationsEnabled", Pure]
   readonly attribute AnimationEffectReadOnly? effect;
   [Func="nsDocument::IsWebAnimationsEnabled"]
@@ -25,17 +25,17 @@ interface Animation : EventTarget {
   [BinaryName="startTimeAsDouble"]
   attribute double? startTime;
   [SetterThrows, BinaryName="currentTimeAsDouble"]
   attribute double? currentTime;
 
            attribute double             playbackRate;
   [BinaryName="playStateFromJS"]
   readonly attribute AnimationPlayState playState;
-  [Throws]
+  [Func="nsDocument::IsWebAnimationsEnabled", Throws]
   readonly attribute Promise<Animation> ready;
   [Func="nsDocument::IsWebAnimationsEnabled", Throws]
   readonly attribute Promise<Animation> finished;
            attribute EventHandler       onfinish;
            attribute EventHandler       oncancel;
   void cancel ();
   [Throws]
   void finish ();
--- a/dom/webidl/AudioBuffer.webidl
+++ b/dom/webidl/AudioBuffer.webidl
@@ -8,22 +8,22 @@
  *
  * Copyright © 2012 W3C® (MIT, ERCIM, Keio), All Rights Reserved. W3C
  * liability, trademark and document use rules apply.
  */
 
 interface AudioBuffer {
 
     readonly attribute float sampleRate;
-    readonly attribute long length;
+    readonly attribute unsigned long length;
 
     // in seconds 
     readonly attribute double duration;
 
-    readonly attribute long numberOfChannels;
+    readonly attribute unsigned long numberOfChannels;
 
     [Throws]
     Float32Array getChannelData(unsigned long channel);
 
     [Throws]
     void copyFromChannel(Float32Array destination, long channelNumber, optional unsigned long startInChannel = 0);
     [Throws]
     void copyToChannel(Float32Array source, long channelNumber, optional unsigned long startInChannel = 0);
--- a/dom/webidl/HTMLMediaElement.webidl
+++ b/dom/webidl/HTMLMediaElement.webidl
@@ -102,16 +102,19 @@ interface HTMLMediaElement : HTMLElement
 
 // Mozilla extensions:
 partial interface HTMLMediaElement {
   [ChromeOnly]
   readonly attribute MediaSource? mozMediaSourceObject;
   [ChromeOnly]
   readonly attribute DOMString mozDebugReaderData;
 
+  [Pref="media.test.dumpDebugInfo"]
+  void mozDumpDebugInfo();
+
   attribute MediaStream? srcObject;
   // TODO: remove prefixed version soon (1183495).
   attribute MediaStream? mozSrcObject;
 
   attribute boolean mozPreservesPitch;
   readonly attribute boolean mozAutoplayEnabled;
 
   // NB: for internal use with the video controls:
--- a/dom/webidl/KeyframeEffect.webidl
+++ b/dom/webidl/KeyframeEffect.webidl
@@ -72,11 +72,11 @@ interface KeyframeEffect : KeyframeEffec
   // Bug 1067769 - Allow setting KeyframeEffect.target
   // inherit attribute Animatable?                 target;
   // Bug 1216843 - implement animation composition
   // inherit attribute IterationCompositeOperation iterationComposite;
   // Bug 1216844 - implement additive animation
   // inherit attribute CompositeOperation          composite;
   // Bug 1244590 - implement spacing modes
   // inherit attribute DOMString                   spacing;
-  // Bug 1244591 - implement setFrames
-  // void setFrames (object? frames);
+  [Throws]
+  void setFrames (object? frames);
 };
--- a/dom/webidl/PushManager.webidl
+++ b/dom/webidl/PushManager.webidl
@@ -2,35 +2,40 @@
 /* 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/.
 *
 * The origin of this IDL file is
 * https://w3c.github.io/push-api/
 */
 
+dictionary PushSubscriptionOptionsInit {
+  // boolean userVisibleOnly = false;
+  BufferSource? applicationServerKey = null;
+};
+
 // The main thread JS implementation. Please see comments in
 // dom/push/PushManager.h for the split between PushManagerImpl and PushManager.
 [JSImplementation="@mozilla.org/push/PushManager;1",
  ChromeOnly, Constructor(DOMString scope)]
 interface PushManagerImpl {
-  Promise<PushSubscription>    subscribe();
+  Promise<PushSubscription>    subscribe(optional PushSubscriptionOptionsInit options);
   Promise<PushSubscription?>   getSubscription();
-  Promise<PushPermissionState> permissionState();
+  Promise<PushPermissionState> permissionState(optional PushSubscriptionOptionsInit options);
 };
 
 [Exposed=(Window,Worker), Func="nsContentUtils::PushEnabled",
  ChromeConstructor(DOMString scope)]
 interface PushManager {
   [Throws, UseCounter]
-  Promise<PushSubscription>    subscribe();
+  Promise<PushSubscription>    subscribe(optional PushSubscriptionOptionsInit options);
   [Throws]
   Promise<PushSubscription?>   getSubscription();
   [Throws]
-  Promise<PushPermissionState> permissionState();
+  Promise<PushPermissionState> permissionState(optional PushSubscriptionOptionsInit options);
 };
 
 enum PushPermissionState
 {
     "granted",
     "denied",
     "prompt"
 };
--- a/dom/webidl/PushSubscription.webidl
+++ b/dom/webidl/PushSubscription.webidl
@@ -22,21 +22,32 @@ dictionary PushSubscriptionKeys
 };
 
 dictionary PushSubscriptionJSON
 {
   USVString endpoint;
   PushSubscriptionKeys keys;
 };
 
+dictionary PushSubscriptionInit
+{
+  required USVString endpoint;
+  required USVString scope;
+  ArrayBuffer? p256dhKey;
+  ArrayBuffer? authSecret;
+  BufferSource? appServerKey;
+};
+
 [Exposed=(Window,Worker), Func="nsContentUtils::PushEnabled",
- ChromeConstructor(DOMString pushEndpoint, DOMString scope,
-                   ArrayBuffer? key, ArrayBuffer? authSecret)]
+ ChromeConstructor(PushSubscriptionInit initDict)]
 interface PushSubscription
 {
-    readonly attribute USVString endpoint;
-    ArrayBuffer? getKey(PushEncryptionKeyName name);
-    [Throws, UseCounter]
-    Promise<boolean> unsubscribe();
+  readonly attribute USVString endpoint;
+  readonly attribute PushSubscriptionOptions options;
+  [Throws]
+  ArrayBuffer? getKey(PushEncryptionKeyName name);
+  [Throws, UseCounter]
+  Promise<boolean> unsubscribe();
 
-    // Implements the custom serializer specified in Push API, section 9.
-    PushSubscriptionJSON toJSON();
+  // Implements the custom serializer specified in Push API, section 9.
+  [Throws]
+  PushSubscriptionJSON toJSON();
 };
new file mode 100644
--- /dev/null
+++ b/dom/webidl/PushSubscriptionOptions.webidl
@@ -0,0 +1,15 @@
+/* -*- 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/.
+*
+* The origin of this IDL file is
+* https://w3c.github.io/push-api/
+*/
+
+[Exposed=(Window,Worker), Func="nsContentUtils::PushEnabled"]
+interface PushSubscriptionOptions
+{
+  [Throws]
+  readonly attribute ArrayBuffer? applicationServerKey;
+};
--- a/dom/webidl/ThreadSafeChromeUtils.webidl
+++ b/dom/webidl/ThreadSafeChromeUtils.webidl
@@ -50,16 +50,38 @@ interface ThreadSafeChromeUtils {
    * garbage collector and the cycle collector.
    *
    * @param aSet weak set or other JavaScript value
    * @returns If aSet is a weak set object, return the keys of the weak
    *          set as an array.  Otherwise, return undefined.
    */
   [Throws, NewObject]
   static any nondeterministicGetWeakSetKeys(any aSet);
+
+  /**
+   * Converts a buffer to a Base64 URL-encoded string per RFC 4648.
+   *
+   * @param source The buffer to encode.
+   * @param options Additional encoding options.
+   * @returns The encoded string.
+   */
+  [Throws]
+  static ByteString base64URLEncode(BufferSource source,
+                                    Base64URLEncodeOptions options);
+
+  /**
+   * Decodes a Base64 URL-encoded string per RFC 4648.
+   *
+   * @param string The string to decode.
+   * @param options Additional decoding options.
+   * @returns The decoded buffer.
+   */
+  [Throws, NewObject]
+  static ArrayBuffer base64URLDecode(ByteString string,
+                                     Base64URLDecodeOptions options);
 };
 
 /**
  * A JS object whose properties specify what portion of the heap graph to
  * write. The recognized properties are:
  *
  * * globals: [ global, ... ]
  *   Dump only nodes that either:
@@ -83,8 +105,36 @@ interface ThreadSafeChromeUtils {
  * set of globals, the root has an edge to each global, and an edge for each
  * incoming JS reference to the selected Zones.
  */
 dictionary HeapSnapshotBoundaries {
   sequence<object> globals;
   object           debugger;
   boolean          runtime;
 };
+
+dictionary Base64URLEncodeOptions {
+  /** Specifies whether the output should be padded with "=" characters. */
+  required boolean pad;
+};
+
+enum Base64URLDecodePadding {
+  /**
+   * Fails decoding if the input is unpadded. RFC 4648, section 3.2 requires
+   * padding, unless the referring specification prohibits it.
+   */
+  "require",
+
+  /** Tolerates padded and unpadded input. */
+  "ignore",
+
+  /**
+   * Fails decoding if the input is padded. This follows the strict base64url
+   * variant used in JWS (RFC 7515, Appendix C) and HTTP Encrypted
+   * Content-Encoding (draft-ietf-httpbis-encryption-encoding-01).
+   */
+  "reject"
+};
+
+dictionary Base64URLDecodeOptions {
+  /** Specifies the padding mode for decoding the input. */
+  required Base64URLDecodePadding padding;
+};
--- a/dom/webidl/moz.build
+++ b/dom/webidl/moz.build
@@ -722,16 +722,17 @@ if CONFIG['MOZ_SIMPLEPUSH']:
     ]
 else:
     WEBIDL_FILES += [
         'PushEvent.webidl',
         'PushManager.webidl',
         'PushManager.webidl',
         'PushMessageData.webidl',
         'PushSubscription.webidl',
+        'PushSubscriptionOptions.webidl',
     ]
 
 if CONFIG['MOZ_NFC']:
     WEBIDL_FILES += [
          'MozIsoDepTech.webidl',
          'MozNDEFRecord.webidl',
          'MozNFC.webidl',
          'MozNfcATech.webidl',
--- a/dom/workers/ServiceWorkerClient.cpp
+++ b/dom/workers/ServiceWorkerClient.cpp
@@ -30,16 +30,17 @@ NS_IMPL_CYCLE_COLLECTING_RELEASE(Service
 
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(ServiceWorkerClient)
   NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
   NS_INTERFACE_MAP_ENTRY(nsISupports)
 NS_INTERFACE_MAP_END
 
 ServiceWorkerClientInfo::ServiceWorkerClientInfo(nsIDocument* aDoc)
   : mWindowId(0)
+  , mFrameType(FrameType::None)
 {
   MOZ_ASSERT(aDoc);
   nsresult rv = aDoc->GetOrCreateId(mClientId);
   if (NS_FAILED(rv)) {
     NS_WARNING("Failed to get the UUID of the document.");
   }
 
   RefPtr<nsGlobalWindow> innerWindow = nsGlobalWindow::Cast(aDoc->GetInnerWindow());
@@ -59,18 +60,19 @@ ServiceWorkerClientInfo::ServiceWorkerCl
 
   ErrorResult result;
   mFocused = aDoc->HasFocus(result);
   if (result.Failed()) {
     NS_WARNING("Failed to get focus information.");
   }
 
   RefPtr<nsGlobalWindow> outerWindow = nsGlobalWindow::Cast(aDoc->GetWindow());
-  MOZ_ASSERT(outerWindow);
-  if (!outerWindow->IsTopLevelWindow()) {
+  if (!outerWindow) {
+    MOZ_ASSERT(mFrameType == FrameType::None);
+  } else if (!outerWindow->IsTopLevelWindow()) {
     mFrameType = FrameType::Nested;
   } else if (outerWindow->HadOriginalOpener()) {
     mFrameType = FrameType::Auxiliary;
   } else {
     mFrameType = FrameType::Top_level;
   }
 }
 
--- a/dom/workers/test/serviceworkers/test_serviceworker_interfaces.js
+++ b/dom/workers/test/serviceworkers/test_serviceworker_interfaces.js
@@ -182,16 +182,18 @@ var interfaceNamesInGlobalScope =
     { name: "PushEvent", b2g: false, android: false, nightlyAndroid: true },
 // IMPORTANT: Do not change this list without review from a DOM peer!
     { name: "PushManager", b2g: false, android: false, nightlyAndroid: true },
 // IMPORTANT: Do not change this list without review from a DOM peer!
     { name: "PushMessageData", b2g: false, android: false, nightlyAndroid: true },
 // IMPORTANT: Do not change this list without review from a DOM peer!
     { name: "PushSubscription", b2g: false, android: false, nightlyAndroid: true },
 // IMPORTANT: Do not change this list without review from a DOM peer!
+    { name: "PushSubscriptionOptions", b2g: false, android: false, nightlyAndroid: true },
+// IMPORTANT: Do not change this list without review from a DOM peer!
     "Request",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "Response",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "ServiceWorker",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "ServiceWorkerGlobalScope",
 // IMPORTANT: Do not change this list without review from a DOM peer!
--- a/dom/workers/test/test_worker_interfaces.js
+++ b/dom/workers/test/test_worker_interfaces.js
@@ -168,16 +168,18 @@ var interfaceNamesInGlobalScope =
     "PerformanceMeasure",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "Promise",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     { name: "PushManager", b2g: false, nightlyAndroid: true, android: false },
 // IMPORTANT: Do not change this list without review from a DOM peer!
     { name: "PushSubscription", b2g: false, nightlyAndroid: true, android: false },
 // IMPORTANT: Do not change this list without review from a DOM peer!
+    { name: "PushSubscriptionOptions", b2g: false, nightlyAndroid: true, android: false },
+// IMPORTANT: Do not change this list without review from a DOM peer!
     "Request",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "Response",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     { name: "ServiceWorkerRegistration", b2g: false },
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "SubtleCrypto",
 // IMPORTANT: Do not change this list without review from a DOM peer!
--- a/extensions/cookie/test/unit/test_eviction.js
+++ b/extensions/cookie/test/unit/test_eviction.js
@@ -1,11 +1,13 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
+"use strict";
+
 var test_generator = do_run_test();
 
 function run_test()
 {
   do_test_pending();
   do_run_generator(test_generator);
 }
 
@@ -226,17 +228,17 @@ function get_creationTime(i)
 // Test that 'aNumberToExpect' cookies remain after purging is complete, and
 // that the cookies that remain consist of the set expected given the number of
 // of older and newer cookies -- eviction should occur by order of lastAccessed
 // time, if both the limit on total cookies (maxNumber + 10%) and the purge age
 // + 10% are exceeded.
 function check_remaining_cookies(aNumberTotal, aNumberOld, aNumberToExpect) {
   var enumerator = Services.cookiemgr.enumerator;
 
-  i = 0;
+  let i = 0;
   while (enumerator.hasMoreElements()) {
     var cookie = enumerator.getNext().QueryInterface(Ci.nsICookie2);
     ++i;
 
     if (aNumberTotal != aNumberToExpect) {
       // make sure the cookie is one of the batch we expect was purged.
       var hostNumber = new Number(cookie.rawHost.split(".")[1]);
       if (hostNumber < (aNumberOld - aNumberToExpect)) break;
--- a/gfx/2d/DrawTargetD2D1.cpp
+++ b/gfx/2d/DrawTargetD2D1.cpp
@@ -1196,20 +1196,26 @@ DrawTargetD2D1::PrepareForDrawing(Compos
     FlushTransformToDC();
 
     if (aOp != CompositionOp::OP_OVER)
       mDC->SetPrimitiveBlend(D2DPrimitiveBlendMode(aOp));
 
     return;
   }
 
-  mDC->CreateCommandList(getter_AddRefs(mCommandList));
+  HRESULT result = mDC->CreateCommandList(getter_AddRefs(mCommandList));
   mDC->SetTarget(mCommandList);
   mUsedCommandListsSincePurge++;
 
+  // This is where we should have a valid command list.  If we don't, something is
+  // wrong, and it's likely an OOM.
+  if (!mCommandList) {
+    gfxDevCrash(LogReason::InvalidCommandList) << "Invalid D2D1.1 command list on creation " << mUsedCommandListsSincePurge << ", " << gfx::hexa(result);
+  }
+
   D2D1_RECT_F rect;
   bool isAligned;
   bool clipIsComplex = CurrentLayer().mPushedClips.size() && !GetDeviceSpaceClipRect(rect, isAligned);
 
   if (ShouldClipTemporarySurfaceDrawing(aOp, aPattern, clipIsComplex)) {
     PushClipsToDC(mDC);
   }
 
@@ -1231,16 +1237,20 @@ DrawTargetD2D1::FinalizeDrawing(Composit
   bool isAligned;
   bool clipIsComplex = CurrentLayer().mPushedClips.size() && !GetDeviceSpaceClipRect(rect, isAligned);
 
   if (ShouldClipTemporarySurfaceDrawing(aOp, aPattern, clipIsComplex)) {
     PopClipsFromDC(mDC);
   }
 
   mDC->SetTarget(CurrentTarget());
+  if (!mCommandList) {
+    gfxDevCrash(LogReason::InvalidCommandList) << "Invalid D21.1 command list on finalize";
+    return;
+  }
   mCommandList->Close();
 
   RefPtr<ID2D1CommandList> source = mCommandList;
   mCommandList = nullptr;
 
   mDC->SetTransform(D2D1::IdentityMatrix());
   mTransformDirty = true;
 
--- a/gfx/2d/DrawTargetSkia.cpp
+++ b/gfx/2d/DrawTargetSkia.cpp
@@ -631,18 +631,20 @@ DrawTargetSkia::FillGlyphs(ScaledFont *a
 
     if (cairoOptions->GetAntialiasMode() == AntialiasMode::NONE) {
       paint.mPaint.setAntiAlias(false);
     }
   } else {
     // SkFontHost_cairo does not support subpixel text, so only enable it for other font hosts.
     paint.mPaint.setSubpixelText(true);
 
-    if (aFont->GetType() == FontType::MAC && shouldLCDRenderText) {
+    if (aFont->GetType() == FontType::MAC) {
       // SkFontHost_mac only supports subpixel antialiasing when hinting is turned off.
+      // For grayscale AA, we want to disable font smoothing as the only time we should
+      // use grayscale AA is with explicit -moz-osx-font-smoothing
       paint.mPaint.setHinting(SkPaint::kNo_Hinting);
     } else {
       paint.mPaint.setHinting(SkPaint::kNormal_Hinting);
     }
   }
 
   std::vector<uint16_t> indices;
   std::vector<SkPoint> offsets;
--- a/gfx/2d/Factory.cpp
+++ b/gfx/2d/Factory.cpp
@@ -82,19 +82,18 @@ HasCPUIDBit(unsigned int level, CPUIDReg
   return __get_cpuid(level, &regs[0], &regs[1], &regs[2], &regs[3]) &&
          (regs[reg] & bit);
 }
 #endif
 
 #define HAVE_CPU_DETECTION
 #else
 
-#if defined(_MSC_VER) && _MSC_VER >= 1600 && (defined(_M_IX86) || defined(_M_AMD64))
+#if defined(_MSC_VER) && (defined(_M_IX86) || defined(_M_AMD64))
 // MSVC 2005 or later supports __cpuid by intrin.h
-// But it does't work on MSVC 2005 with SDK 7.1 (Bug 753772)
 #include <intrin.h>
 
 #define HAVE_CPU_DETECTION
 #elif defined(__SUNPRO_CC) && (defined(__i386) || defined(__x86_64__))
 
 // Define a function identical to MSVC function.
 #ifdef __i386
 static void
--- a/gfx/2d/Logging.h
+++ b/gfx/2d/Logging.h
@@ -132,16 +132,17 @@ enum class LogReason : int {
   CannotDraw3D, // 20
   IncompatibleBasicTexturedEffect,
   InvalidFont,
   PAllocTextureBackendMismatch,
   GetFontFileDataFailed,
   MessageChannelCloseFailure,
   TextureAliveAfterShutdown,
   InvalidContext,
+  InvalidCommandList,
   // End
   MustBeLessThanThis = 101,
 };
 
 struct BasicLogger
 {
   // For efficiency, this method exists and copies the logic of the
   // OutputMessage below.  If making any changes here, also make it
--- a/gfx/2d/Tools.h
+++ b/gfx/2d/Tools.h
@@ -7,19 +7,16 @@
 #define MOZILLA_GFX_TOOLS_H_
 
 #include "mozilla/CheckedInt.h"
 #include "mozilla/Move.h"
 #include "mozilla/TypeTraits.h"
 #include "Types.h"
 #include "Point.h"
 #include <math.h>
-#if defined(_MSC_VER) && (_MSC_VER < 1600)
-#define hypotf _hypotf
-#endif
 
 namespace mozilla {
 namespace gfx {
 
 static inline bool
 IsOperatorBoundByMask(CompositionOp aOp) {
   switch (aOp) {
   case CompositionOp::OP_IN:
--- a/gfx/cairo/cairo/src/cairo-atomic-private.h
+++ b/gfx/cairo/cairo/src/cairo-atomic-private.h
@@ -74,16 +74,28 @@ CAIRO_BEGIN_DECLS
 typedef int cairo_atomic_int_t;
 
 static cairo_always_inline cairo_atomic_int_t
 _cairo_atomic_int_get (cairo_atomic_int_t *x)
 {
     return __atomic_load_n(x, __ATOMIC_SEQ_CST);
 }
 
+static cairo_always_inline cairo_atomic_int_t
+_cairo_atomic_int_get_relaxed (cairo_atomic_int_t *x)
+{
+    return __atomic_load_n(x, __ATOMIC_RELAXED);
+}
+
+static cairo_always_inline void
+_cairo_atomic_int_set_relaxed (cairo_atomic_int_t *x, cairo_atomic_int_t val)
+{
+    __atomic_store_n(x, val, __ATOMIC_RELAXED);
+}
+
 static cairo_always_inline void *
 _cairo_atomic_ptr_get (void **x)
 {
     return __atomic_load_n(x, __ATOMIC_SEQ_CST);
 }
 
 # define _cairo_atomic_int_inc(x) ((void) __atomic_fetch_add(x, 1, __ATOMIC_SEQ_CST))
 # define _cairo_atomic_int_dec(x) ((void) __atomic_fetch_sub(x, 1, __ATOMIC_SEQ_CST))
@@ -149,16 +161,18 @@ static cairo_always_inline void *
 
 #if HAVE_WIN32_ATOMIC_PRIMITIVES
 
 #define HAS_ATOMIC_OPS 1
 
 typedef volatile long cairo_atomic_int_t;
 
 # define _cairo_atomic_int_get(x) ((int)*x)
+# define _cairo_atomic_int_get_relaxed(x) ((int)*(x))
+# define _cairo_atomic_int_set_relaxed(x, val) (*(x) = (val))
 # define _cairo_atomic_ptr_get(x) ((void*)*x)
 
 # define _cairo_atomic_int_inc(x) ((void) InterlockedIncrement(x))
 # define _cairo_atomic_int_dec(x) ((void) InterlockedDecrement(x))
 # define _cairo_atomic_int_dec_and_test(x) (InterlockedDecrement(x) == 0)
 # define _cairo_atomic_int_cmpxchg(x, oldv, newv) (InterlockedCompareExchange(x, newv, oldv) == oldv)
 # define _cairo_atomic_int_cmpxchg_return_old(x, oldv, newv) InterlockedCompareExchange(x, newv, oldv)
 
@@ -178,24 +192,38 @@ typedef int cairo_atomic_int_t;
 #ifdef ATOMIC_OP_NEEDS_MEMORY_BARRIER
 static cairo_always_inline cairo_atomic_int_t
 _cairo_atomic_int_get (cairo_atomic_int_t *x)
 {
     __sync_synchronize ();
     return *x;
 }
 
+static cairo_always_inline cairo_atomic_int_t
+_cairo_atomic_int_get_relaxed (cairo_atomic_int_t *x)
+{
+    return *x;
+}
+
+static cairo_always_inline void
+_cairo_atomic_int_set_relaxed (cairo_atomic_int_t *x, cairo_atomic_int_t val)
+{
+    *x = val;
+}
+
 static cairo_always_inline void *
 _cairo_atomic_ptr_get (void **x)
 {
     __sync_synchronize ();
     return *x;
 }
 #else
 # define _cairo_atomic_int_get(x) (*x)
+# define _cairo_atomic_int_get_relaxed(x) (*(x))
+# define _cairo_atomic_int_set_relaxed(x, val) (*(x) = (val))
 # define _cairo_atomic_ptr_get(x) (*x)
 #endif
 
 # define _cairo_atomic_int_inc(x) ((void) __sync_fetch_and_add(x, 1))
 # define _cairo_atomic_int_dec_and_test(x) (__sync_fetch_and_add(x, -1) == 1)
 # define _cairo_atomic_int_cmpxchg(x, oldv, newv) __sync_bool_compare_and_swap (x, oldv, newv)
 # define _cairo_atomic_int_cmpxchg_return_old(x, oldv, newv) __sync_val_compare_and_swap (x, oldv, newv)
 
@@ -220,16 +248,18 @@ typedef long long cairo_atomic_intptr_t;
 #if HAVE_LIB_ATOMIC_OPS
 #include <atomic_ops.h>
 
 #define HAS_ATOMIC_OPS 1
 
 typedef  AO_t cairo_atomic_int_t;
 
 # define _cairo_atomic_int_get(x) (AO_load_full (x))
+# define _cairo_atomic_int_get_relaxed(x) (AO_load_full (x))
+# define _cairo_atomic_int_set_relaxed(x, val) (AO_store_full ((x), (val)))
 
 # define _cairo_atomic_int_inc(x) ((void) AO_fetch_and_add1_full(x))
 # define _cairo_atomic_int_dec_and_test(x) (AO_fetch_and_sub1_full(x) == 1)
 # define _cairo_atomic_int_cmpxchg(x, oldv, newv) AO_compare_and_swap_full(x, oldv, newv)
 
 #if SIZEOF_VOID_P==SIZEOF_INT
 typedef unsigned int cairo_atomic_intptr_t;
 #elif SIZEOF_VOID_P==SIZEOF_LONG
@@ -249,16 +279,18 @@ typedef unsigned long long cairo_atomic_
 #if HAVE_OS_ATOMIC_OPS
 #include <libkern/OSAtomic.h>
 
 #define HAS_ATOMIC_OPS 1
 
 typedef int32_t cairo_atomic_int_t;
 
 # define _cairo_atomic_int_get(x) (OSMemoryBarrier(), *(x))
+# define _cairo_atomic_int_get_relaxed(x) (*(x))
+# define _cairo_atomic_int_set_relaxed(x, val) (*(x) = (val))
 
 # define _cairo_atomic_int_inc(x) ((void) OSAtomicIncrement32Barrier (x))
 # define _cairo_atomic_int_dec_and_test(x) (OSAtomicDecrement32Barrier (x) == 0)
 # define _cairo_atomic_int_cmpxchg(x, oldv, newv) OSAtomicCompareAndSwap32Barrier(oldv, newv, x)
 
 #if SIZEOF_VOID_P==4
 typedef int32_t cairo_atomic_intptr_t;
 # define _cairo_atomic_ptr_cmpxchg(x, oldv, newv) \
@@ -304,19 +336,25 @@ cairo_private void *
 _cairo_atomic_ptr_cmpxchg_return_old_impl (void **x, void *oldv, void *newv);
 
 #define _cairo_atomic_int_cmpxchg_return_old(x, oldv, newv) _cairo_atomic_int_cmpxchg_return_old_impl (x, oldv, newv)
 #define _cairo_atomic_ptr_cmpxchg_return_old(x, oldv, newv) _cairo_atomic_ptr_cmpxchg_return_old_impl (x, oldv, newv)
 
 #ifdef ATOMIC_OP_NEEDS_MEMORY_BARRIER
 cairo_private cairo_atomic_int_t
 _cairo_atomic_int_get (cairo_atomic_int_t *x);
+cairo_private cairo_atomic_int_t
+_cairo_atomic_int_get_relaxed (cairo_atomic_int_t *x);
+void
+_cairo_atomic_int_set_relaxed (cairo_atomic_int_t *x, cairo_atomic_int_t val);
 # define _cairo_atomic_ptr_get(x) (void *) _cairo_atomic_int_get((cairo_atomic_int_t *) x)
 #else
 # define _cairo_atomic_int_get(x) (*x)
+# define _cairo_atomic_int_get_relaxed(x) (*(x))
+# define _cairo_atomic_int_set_relaxed(x, val) (*(x) = (val))
 # define _cairo_atomic_ptr_get(x) (*x)
 #endif
 
 #else
 
 /* Workaround GCC complaining about casts */
 static cairo_always_inline void *
 _cairo_atomic_intptr_to_voidptr (cairo_atomic_intptr_t x)
--- a/gfx/cairo/cairo/src/cairo-atomic.c
+++ b/gfx/cairo/cairo/src/cairo-atomic.c
@@ -96,11 +96,25 @@ cairo_atomic_intptr_t
     cairo_atomic_intptr_t ret;
 
     CAIRO_MUTEX_LOCK (_cairo_atomic_mutex);
     ret = *x;
     CAIRO_MUTEX_UNLOCK (_cairo_atomic_mutex);
 
     return ret;
 }
+
+cairo_atomic_intptr_t
+_cairo_atomic_int_get_relaxed (cairo_atomic_intptr_t *x)
+{
+    return _cairo_atomic_int_get (x);
+}
+
+void
+_cairo_atomic_int_set_relaxed (cairo_atomic_intptr_t *x, cairo_atomic_intptr_t val)
+{
+    CAIRO_MUTEX_LOCK (_cairo_atomic_mutex);
+    *x = val;
+    CAIRO_MUTEX_UNLOCK (_cairo_atomic_mutex);
+}
 #endif
 
 #endif
--- a/gfx/cairo/cairo/src/cairo-freed-pool-private.h
+++ b/gfx/cairo/cairo/src/cairo-freed-pool-private.h
@@ -42,17 +42,17 @@
 
 #if HAS_ATOMIC_OPS
 /* Keep a stash of recently freed clip_paths, since we need to
  * reallocate them frequently.
  */
 #define MAX_FREED_POOL_SIZE 4
 typedef struct {
     void *pool[MAX_FREED_POOL_SIZE];
-    int top;
+    cairo_atomic_int_t top;
 } freed_pool_t;
 
 static cairo_always_inline void *
 _atomic_fetch (void **slot)
 {
     void *ptr;
 
     do {
@@ -72,43 +72,43 @@ cairo_private void *
 _freed_pool_get_search (freed_pool_t *pool);
 
 static inline void *
 _freed_pool_get (freed_pool_t *pool)
 {
     void *ptr;
     int i;
 
-    i = pool->top - 1;
+    i = _cairo_atomic_int_get_relaxed (&pool->top) - 1;
     if (i < 0)
 	i = 0;
 
     ptr = _atomic_fetch (&pool->pool[i]);
     if (likely (ptr != NULL)) {
-	pool->top = i;
+	_cairo_atomic_int_set_relaxed (&pool->top, i);
 	return ptr;
     }
 
     /* either empty or contended */
     return _freed_pool_get_search (pool);
 }
 
 cairo_private void
 _freed_pool_put_search (freed_pool_t *pool, void *ptr);
 
 static inline void
 _freed_pool_put (freed_pool_t *pool, void *ptr)
 {
     int i;
 
-    i = pool->top;
+    i = _cairo_atomic_int_get_relaxed (&pool->top);
     if (likely (i < ARRAY_LENGTH (pool->pool) &&
 		_atomic_store (&pool->pool[i], ptr)))
     {
-	pool->top = i + 1;
+	_cairo_atomic_int_set_relaxed (&pool->top, i + 1);
 	return;
     }
 
     /* either full or contended */
     _freed_pool_put_search (pool, ptr);
 }
 
 cairo_private void
--- a/gfx/cairo/cairo/src/cairo-freed-pool.c
+++ b/gfx/cairo/cairo/src/cairo-freed-pool.c
@@ -45,49 +45,49 @@ void *
 _freed_pool_get_search (freed_pool_t *pool)
 {
     void *ptr;
     int i;
 
     for (i = ARRAY_LENGTH (pool->pool); i--;) {
 	ptr = _atomic_fetch (&pool->pool[i]);
 	if (ptr != NULL) {
-	    pool->top = i;
+	    _cairo_atomic_int_set_relaxed (&pool->top, i);
 	    return ptr;
 	}
     }
 
     /* empty */
-    pool->top = 0;
+    _cairo_atomic_int_set_relaxed (&pool->top, 0);
     return NULL;
 }
 
 void
 _freed_pool_put_search (freed_pool_t *pool, void *ptr)
 {
     int i;
 
     for (i = 0; i < ARRAY_LENGTH (pool->pool); i++) {
 	if (_atomic_store (&pool->pool[i], ptr)) {
-	    pool->top = i + 1;
+	    _cairo_atomic_int_set_relaxed (&pool->top, i + 1);
 	    return;
 	}
     }
 
     /* full */
-    pool->top = i;
+    _cairo_atomic_int_set_relaxed (&pool->top, i);
     free (ptr);
 }
 
 void
 _freed_pool_reset (freed_pool_t *pool)
 {
     int i;
 
     for (i = 0; i < ARRAY_LENGTH (pool->pool); i++) {
 	free (pool->pool[i]);
 	pool->pool[i] = NULL;
     }
 
-    pool->top = 0;
+    _cairo_atomic_int_set_relaxed (&pool->top, 0);
 }
 
 #endif
--- a/gfx/gl/GLContext.cpp
+++ b/gfx/gl/GLContext.cpp
@@ -60,17 +60,17 @@ uint32_t GLContext::sDebugMode = 0;
 // If adding defines, don't forget to undefine symbols. See #undef block below.
 #define CORE_SYMBOL(x) { (PRFuncPtr*) &mSymbols.f##x, { #x, nullptr } }
 #define CORE_EXT_SYMBOL2(x,y,z) { (PRFuncPtr*) &mSymbols.f##x, { #x, #x #y, #x #z, nullptr } }
 #define EXT_SYMBOL2(x,y,z) { (PRFuncPtr*) &mSymbols.f##x, { #x #y, #x #z, nullptr } }
 #define EXT_SYMBOL3(x,y,z,w) { (PRFuncPtr*) &mSymbols.f##x, { #x #y, #x #z, #x #w, nullptr } }
 #define END_SYMBOLS { nullptr, { nullptr } }
 
 // should match the order of GLExtensions, and be null-terminated.
-static const char *const sExtensionNames[] = {
+static const char* const sExtensionNames[] = {
     "NO_EXTENSION",
     "GL_AMD_compressed_ATC_texture",
     "GL_ANGLE_depth_texture",
     "GL_ANGLE_framebuffer_blit",
     "GL_ANGLE_framebuffer_multisample",
     "GL_ANGLE_instanced_arrays",
     "GL_ANGLE_texture_compression_dxt3",
     "GL_ANGLE_texture_compression_dxt5",
@@ -394,27 +394,25 @@ ParseGLVersion(GLContext* gl, uint32_t* 
         return false;
     }
 
     *out_version = (uint32_t)majorVersion * 100 + (uint32_t)minorVersion * 10;
     return true;
 }
 
 GLContext::GLContext(const SurfaceCaps& caps,
-          GLContext* sharedContext,
-          bool isOffscreen)
-  : mInitialized(false),
-    mIsOffscreen(isOffscreen),
+                     GLContext* sharedContext,
+                     bool isOffscreen)
+  : mIsOffscreen(isOffscreen),
     mContextLost(false),
     mVersion(0),
     mProfile(ContextProfile::Unknown),
     mShadingLanguageVersion(0),
     mVendor(GLVendor::Other),
     mRenderer(GLRenderer::Other),
-    mHasRobustness(false),
     mTopError(LOCAL_GL_NO_ERROR),
     mSharedContext(sharedContext),
     mCaps(caps),
     mScreen(nullptr),
     mLockedSurface(nullptr),
     mMaxTextureSize(0),
     mMaxCubeMapTextureSize(0),
     mMaxTextureImageSize(0),
@@ -454,37 +452,103 @@ GLContext::StaticDebugCallback(GLenum so
                                const GLchar* message,
                                const GLvoid* userParam)
 {
     GLContext* gl = (GLContext*)userParam;
     gl->DebugCallback(source, type, id, severity, length, message);
 }
 
 static void
-ClearSymbols(GLLibraryLoader::SymLoadStruct *symbols)
+ClearSymbols(const GLLibraryLoader::SymLoadStruct* symbols)
 {
     while (symbols->symPointer) {
         *symbols->symPointer = nullptr;
         symbols++;
     }
 }
 
 bool
-GLContext::InitWithPrefix(const char *prefix, bool trygl)
+GLContext::InitWithPrefix(const char* prefix, bool trygl)
 {
+    MOZ_RELEASE_ASSERT(!mSymbols.fBindFramebuffer,
+                       "InitWithPrefix should only be called once.");
+
     ScopedGfxFeatureReporter reporter("GL Context");
 
-    if (mInitialized) {
-        reporter.SetSuccessful();
+    if (!InitWithPrefixImpl(prefix, trygl)) {
+        // If initialization fails, zero the symbols to avoid hard-to-understand bugs.
+        mSymbols.Zero();
+        NS_WARNING("GLContext::InitWithPrefix failed!");
+        return false;
+    }
+
+    reporter.SetSuccessful();
+    return true;
+}
+
+static bool
+LoadGLSymbols(GLContext* gl, const char* prefix, bool trygl,
+              const GLLibraryLoader::SymLoadStruct* list, const char* desc)
+{
+    if (gl->LoadSymbols(list, trygl, prefix))
         return true;
+
+    ClearSymbols(list);
+
+    if (desc) {
+        const nsPrintfCString err("Failed to load symbols for %s.", desc);
+        NS_ERROR(err.BeginReading());
+    }
+    return false;
+}
+
+bool
+GLContext::LoadExtSymbols(const char* prefix, bool trygl, const SymLoadStruct* list,
+                          GLExtensions ext)
+{
+    const char* extName = sExtensionNames[size_t(ext)];
+    if (!LoadGLSymbols(this, prefix, trygl, list, extName)) {
+        MarkExtensionUnsupported(ext);
+        return false;
     }
-
+    return true;
+};
+
+bool
+GLContext::LoadFeatureSymbols(const char* prefix, bool trygl, const SymLoadStruct* list,
+                              GLFeature feature)
+{
+    const char* featureName = GetFeatureName(feature);
+    if (!LoadGLSymbols(this, prefix, trygl, list, featureName)) {
+        MarkUnsupported(feature);
+        return false;
+    }
+    return true;
+};
+
+bool
+GLContext::InitWithPrefixImpl(const char* prefix, bool trygl)
+{
     mWorkAroundDriverBugs = gfxPrefs::WorkAroundDriverBugs();
 
-    SymLoadStruct symbols[] = {
+#ifdef MOZ_GL_DEBUG
+    if (gfxEnv::GlDebug())
+        sDebugMode |= DebugEnabled;
+
+    // enables extra verbose output, informing of the start and finish of every GL call.
+    // useful e.g. to record information to investigate graphics system crashes/lockups
+    if (gfxEnv::GlDebugVerbose())
+        sDebugMode |= DebugTrace;
+
+    // aborts on GL error. Can be useful to debug quicker code that is known not to generate any GL error in principle.
+    if (gfxEnv::GlDebugAbortOnError())
+        sDebugMode |= DebugAbortOnError;
+#endif
+
+    const SymLoadStruct coreSymbols[] = {
         { (PRFuncPtr*) &mSymbols.fActiveTexture, { "ActiveTexture", "ActiveTextureARB", nullptr } },
         { (PRFuncPtr*) &mSymbols.fAttachShader, { "AttachShader", "AttachShaderARB", nullptr } },
         { (PRFuncPtr*) &mSymbols.fBindAttribLocation, { "BindAttribLocation", "BindAttribLocationARB", nullptr } },
         { (PRFuncPtr*) &mSymbols.fBindBuffer, { "BindBuffer", "BindBufferARB", nullptr } },
         { (PRFuncPtr*) &mSymbols.fBindTexture, { "BindTexture", "BindTextureARB", nullptr } },
         { (PRFuncPtr*) &mSymbols.fBlendColor, { "BlendColor", nullptr } },
         { (PRFuncPtr*) &mSymbols.fBlendEquation, { "BlendEquation", nullptr } },
         { (PRFuncPtr*) &mSymbols.fBlendEquationSeparate, { "BlendEquationSeparate", "BlendEquationSeparateEXT", nullptr } },
@@ -601,1198 +665,932 @@ GLContext::InitWithPrefix(const char *pr
         { (PRFuncPtr*) &mSymbols.fCreateShader, { "CreateShader", "CreateShaderARB", nullptr } },
 
         { (PRFuncPtr*) &mSymbols.fDeleteBuffers, { "DeleteBuffers", "DeleteBuffersARB", nullptr } },
         { (PRFuncPtr*) &mSymbols.fDeleteTextures, { "DeleteTextures", "DeleteTexturesARB", nullptr } },
         { (PRFuncPtr*) &mSymbols.fDeleteProgram, { "DeleteProgram", "DeleteProgramARB", nullptr } },
         { (PRFuncPtr*) &mSymbols.fDeleteShader, { "DeleteShader", "DeleteShaderARB", nullptr } },
 
         END_SYMBOLS
-
     };
 
-    mInitialized = LoadSymbols(&symbols[0], trygl, prefix);
+    if (!LoadGLSymbols(this, prefix, trygl, coreSymbols, "GL"))
+        return false;
+
+    ////////////////
+
     MakeCurrent();
-    if (mInitialized) {
-        MOZ_ASSERT(mProfile != ContextProfile::Unknown);
-
-        uint32_t version = 0;
-        ParseGLVersion(this, &version);
-
-        mShadingLanguageVersion = 100;
-        ParseGLSLVersion(this, &mShadingLanguageVersion);
-
-        if (ShouldSpew()) {
-            printf_stderr("OpenGL version detected: %u\n", version);
-            printf_stderr("OpenGL shading language version detected: %u\n", mShadingLanguageVersion);
-            printf_stderr("OpenGL vendor: %s\n", fGetString(LOCAL_GL_VENDOR));
-            printf_stderr("OpenGL renderer: %s\n", fGetString(LOCAL_GL_RENDERER));
-        }
-
-        if (version >= mVersion) {
-            mVersion = version;
-        }
-        // Don't fail if version < mVersion, see bug 999445,
-        // Mac OSX 10.6/10.7 machines with Intel GPUs claim only OpenGL 1.4 but
-        // have all the GL2+ extensions that we need.
+    MOZ_ASSERT(mProfile != ContextProfile::Unknown);
+
+    uint32_t version = 0;
+    ParseGLVersion(this, &version);
+
+    mShadingLanguageVersion = 100;
+    ParseGLSLVersion(this, &mShadingLanguageVersion);
+
+    if (ShouldSpew()) {
+        printf_stderr("OpenGL version detected: %u\n", version);
+        printf_stderr("OpenGL shading language version detected: %u\n", mShadingLanguageVersion);
+        printf_stderr("OpenGL vendor: %s\n", fGetString(LOCAL_GL_VENDOR));
+        printf_stderr("OpenGL renderer: %s\n", fGetString(LOCAL_GL_RENDERER));
     }
 
+    if (version >= mVersion) {
+        mVersion = version;
+    }
+    // Don't fail if version < mVersion, see bug 999445,
+    // Mac OSX 10.6/10.7 machines with Intel GPUs claim only OpenGL 1.4 but
+    // have all the GL2+ extensions that we need.
+
+    ////////////////
+
     // Load OpenGL ES 2.0 symbols, or desktop if we aren't using ES 2.
-    if (mInitialized) {
-        if (IsGLES()) {
-            SymLoadStruct symbols_ES2[] = {
-                { (PRFuncPtr*) &mSymbols.fGetShaderPrecisionFormat, { "GetShaderPrecisionFormat", nullptr } },
-                { (PRFuncPtr*) &mSymbols.fClearDepthf, { "ClearDepthf", nullptr } },
-                { (PRFuncPtr*) &mSymbols.fDepthRangef, { "DepthRangef", nullptr } },
+    if (IsGLES()) {
+        const SymLoadStruct symbols[] = {
+            { (PRFuncPtr*) &mSymbols.fGetShaderPrecisionFormat, { "GetShaderPrecisionFormat", nullptr } },
+            { (PRFuncPtr*) &mSymbols.fClearDepthf, { "ClearDepthf", nullptr } },
+            { (PRFuncPtr*) &mSymbols.fDepthRangef, { "DepthRangef", nullptr } },
+            END_SYMBOLS
+        };
+
+        if (!LoadGLSymbols(this, prefix, trygl, symbols, "OpenGL ES"))
+            return false;
+    } else {
+        const SymLoadStruct symbols[] = {
+            { (PRFuncPtr*) &mSymbols.fClearDepth, { "ClearDepth", nullptr } },
+            { (PRFuncPtr*) &mSymbols.fDepthRange, { "DepthRange", nullptr } },
+            { (PRFuncPtr*) &mSymbols.fReadBuffer, { "ReadBuffer", nullptr } },
+            { (PRFuncPtr*) &mSymbols.fMapBuffer, { "MapBuffer", nullptr } },
+            { (PRFuncPtr*) &mSymbols.fUnmapBuffer, { "UnmapBuffer", nullptr } },
+            { (PRFuncPtr*) &mSymbols.fPointParameterf, { "PointParameterf", nullptr } },
+            { (PRFuncPtr*) &mSymbols.fDrawBuffer, { "DrawBuffer", nullptr } },
+            // The following functions are only used by Skia/GL in desktop mode.
+            // Other parts of Gecko should avoid using these
+            { (PRFuncPtr*) &mSymbols.fDrawBuffers, { "DrawBuffers", nullptr } },
+            { (PRFuncPtr*) &mSymbols.fClientActiveTexture, { "ClientActiveTexture", nullptr } },
+            { (PRFuncPtr*) &mSymbols.fDisableClientState, { "DisableClientState", nullptr } },
+            { (PRFuncPtr*) &mSymbols.fEnableClientState, { "EnableClientState", nullptr } },
+            { (PRFuncPtr*) &mSymbols.fLoadIdentity, { "LoadIdentity", nullptr } },
+            { (PRFuncPtr*) &mSymbols.fLoadMatrixf, { "LoadMatrixf", nullptr } },
+            { (PRFuncPtr*) &mSymbols.fMatrixMode, { "MatrixMode", nullptr } },
+            { (PRFuncPtr*) &mSymbols.fTexGeni, { "TexGeni", nullptr } },
+            { (PRFuncPtr*) &mSymbols.fTexGenf, { "TexGenf", nullptr } },
+            { (PRFuncPtr*) &mSymbols.fTexGenfv, { "TexGenfv", nullptr } },
+            { (PRFuncPtr*) &mSymbols.fVertexPointer, { "VertexPointer", nullptr } },
+            END_SYMBOLS
+        };
+
+        if (!LoadGLSymbols(this, prefix, trygl, symbols, "Desktop OpenGL"))
+            return false;
+    }
+
+    ////////////////
+
+    const char* glVendorString = (const char*)fGetString(LOCAL_GL_VENDOR);
+    const char* glRendererString = (const char*)fGetString(LOCAL_GL_RENDERER);
+    if (!glVendorString || !glRendererString)
+        return false;
+
+    // The order of these strings must match up with the order of the enum
+    // defined in GLContext.h for vendor IDs.
+    const char* vendorMatchStrings[size_t(GLVendor::Other)] = {
+        "Intel",
+        "NVIDIA",
+        "ATI",
+        "Qualcomm",
+        "Imagination",
+        "nouveau",
+        "Vivante",
+        "VMware, Inc.",
+        "ARM"
+    };
+
+    mVendor = GLVendor::Other;
+    for (size_t i = 0; i < size_t(GLVendor::Other); ++i) {
+        if (DoesStringMatch(glVendorString, vendorMatchStrings[i])) {
+            mVendor = GLVendor(i);
+            break;
+        }
+    }
+
+    // The order of these strings must match up with the order of the enum
+    // defined in GLContext.h for renderer IDs.
+    const char* rendererMatchStrings[size_t(GLRenderer::Other)] = {
+        "Adreno 200",
+        "Adreno 205",
+        "Adreno (TM) 200",
+        "Adreno (TM) 205",
+        "Adreno (TM) 320",
+        "Adreno (TM) 420",
+        "PowerVR SGX 530",
+        "PowerVR SGX 540",
+        "NVIDIA Tegra",
+        "Android Emulator",
+        "Gallium 0.4 on llvmpipe",
+        "Intel HD Graphics 3000 OpenGL Engine",
+        "Microsoft Basic Render Driver"
+    };
+
+    mRenderer = GLRenderer::Other;
+    for (size_t i = 0; i < size_t(GLRenderer::Other); ++i) {
+        if (DoesStringMatch(glRendererString, rendererMatchStrings[i])) {
+            mRenderer = GLRenderer(i);
+            break;
+        }
+    }
+
+    if (ShouldSpew()) {
+        const char* vendors[size_t(GLVendor::Other)] = {
+            "Intel",
+            "NVIDIA",
+            "ATI",
+            "Qualcomm"
+        };
+
+        MOZ_ASSERT(glVendorString);
+        if (mVendor < GLVendor::Other) {
+            printf_stderr("OpenGL vendor ('%s') recognized as: %s\n",
+                          glVendorString, vendors[size_t(mVendor)]);
+        } else {
+            printf_stderr("OpenGL vendor ('%s') not recognized.\n", glVendorString);
+        }
+    }
+
+    ////////////////
+
+    // We need this for retrieving the list of extensions on Core profiles.
+    if (IsFeatureProvidedByCoreSymbols(GLFeature::get_string_indexed)) {
+        const SymLoadStruct symbols[] = {
+            { (PRFuncPtr*) &mSymbols.fGetStringi, { "GetStringi", nullptr } },
+            END_SYMBOLS
+        };
+
+        if (!LoadGLSymbols(this, prefix, trygl, symbols, "get_string_indexed")) {
+            MOZ_RELEASE_ASSERT(false, "get_string_indexed is required!");
+            return false;
+        }
+    }
+
+    InitExtensions();
+    InitFeatures();
+
+    // Disable extensions with partial or incorrect support.
+    if (WorkAroundDriverBugs()) {
+        if (Renderer() == GLRenderer::AdrenoTM320) {
+            MarkUnsupported(GLFeature::standard_derivatives);
+        }
+
+        if (Vendor() == GLVendor::Vivante) {
+            // bug 958256
+            MarkUnsupported(GLFeature::standard_derivatives);
+        }
+
+        if (Renderer() == GLRenderer::MicrosoftBasicRenderDriver) {
+            // Bug 978966: on Microsoft's "Basic Render Driver" (software renderer)
+            // multisampling hardcodes blending with the default blendfunc, which breaks WebGL.
+            MarkUnsupported(GLFeature::framebuffer_multisample);
+        }
+
+#ifdef XP_MACOSX
+        // The Mac Nvidia driver, for versions up to and including 10.8,
+        // don't seem to properly support this.  See 814839
+        // this has been fixed in Mac OS X 10.9. See 907946
+        // and it also works in 10.8.3 and higher.  See 1094338.
+        if (Vendor() == gl::GLVendor::NVIDIA &&
+            !nsCocoaFeatures::IsAtLeastVersion(10,8,3))
+        {
+            MarkUnsupported(GLFeature::depth_texture);
+        }
+#endif
+    }
+
+    if (IsExtensionSupported(GLContext::ARB_pixel_buffer_object)) {
+        MOZ_ASSERT((mSymbols.fMapBuffer && mSymbols.fUnmapBuffer),
+                   "ARB_pixel_buffer_object supported without glMapBuffer/UnmapBuffer"
+                   " being available!");
+    }
+
+    ////////////////////////////////////////////////////////////////////////////
+
+    const auto fnLoadForFeature = [this, prefix, trygl](const SymLoadStruct* list,
+                                                        GLFeature feature)
+    {
+        return this->LoadFeatureSymbols(prefix, trygl, list, feature);
+    };
+
+    // Check for ARB_framebuffer_objects
+    if (IsSupported(GLFeature::framebuffer_object)) {
+        // https://www.opengl.org/registry/specs/ARB/framebuffer_object.txt
+        const SymLoadStruct symbols[] = {
+            CORE_SYMBOL(IsRenderbuffer),
+            CORE_SYMBOL(BindRenderbuffer),
+            CORE_SYMBOL(DeleteRenderbuffers),
+            CORE_SYMBOL(GenRenderbuffers),
+            CORE_SYMBOL(RenderbufferStorage),
+            CORE_SYMBOL(RenderbufferStorageMultisample),
+            CORE_SYMBOL(GetRenderbufferParameteriv),
+            CORE_SYMBOL(IsFramebuffer),
+            CORE_SYMBOL(BindFramebuffer),
+            CORE_SYMBOL(DeleteFramebuffers),
+            CORE_SYMBOL(GenFramebuffers),
+            CORE_SYMBOL(CheckFramebufferStatus),
+            CORE_SYMBOL(FramebufferTexture2D),
+            CORE_SYMBOL(FramebufferTextureLayer),
+            CORE_SYMBOL(FramebufferRenderbuffer),
+            CORE_SYMBOL(GetFramebufferAttachmentParameteriv),
+            CORE_SYMBOL(BlitFramebuffer),
+            CORE_SYMBOL(GenerateMipmap),
+            END_SYMBOLS
+        };
+        fnLoadForFeature(symbols, GLFeature::framebuffer_object);
+    }
+
+    if (!IsSupported(GLFeature::framebuffer_object)) {
+        // Check for aux symbols based on extensions
+        if (IsSupported(GLFeature::framebuffer_object_EXT_OES)) {
+            const SymLoadStruct symbols[] = {
+                CORE_EXT_SYMBOL2(IsRenderbuffer, EXT, OES),
+                CORE_EXT_SYMBOL2(BindRenderbuffer, EXT, OES),
+                CORE_EXT_SYMBOL2(DeleteRenderbuffers, EXT, OES),
+                CORE_EXT_SYMBOL2(GenRenderbuffers, EXT, OES),
+                CORE_EXT_SYMBOL2(RenderbufferStorage, EXT, OES),
+                CORE_EXT_SYMBOL2(GetRenderbufferParameteriv, EXT, OES),
+                CORE_EXT_SYMBOL2(IsFramebuffer, EXT, OES),
+                CORE_EXT_SYMBOL2(BindFramebuffer, EXT, OES),
+                CORE_EXT_SYMBOL2(DeleteFramebuffers, EXT, OES),
+                CORE_EXT_SYMBOL2(GenFramebuffers, EXT, OES),
+                CORE_EXT_SYMBOL2(CheckFramebufferStatus, EXT, OES),
+                CORE_EXT_SYMBOL2(FramebufferTexture2D, EXT, OES),
+                CORE_EXT_SYMBOL2(FramebufferRenderbuffer, EXT, OES),
+                CORE_EXT_SYMBOL2(GetFramebufferAttachmentParameteriv, EXT, OES),
+                CORE_EXT_SYMBOL2(GenerateMipmap, EXT, OES),
                 END_SYMBOLS
             };
-
-            if (!LoadSymbols(&symbols_ES2[0], trygl, prefix)) {
-                NS_ERROR("OpenGL ES 2.0 supported, but symbols could not be loaded.");
-                mInitialized = false;
-            }
-        } else {
-            SymLoadStruct symbols_desktop[] = {
-                { (PRFuncPtr*) &mSymbols.fClearDepth, { "ClearDepth", nullptr } },
-                { (PRFuncPtr*) &mSymbols.fDepthRange, { "DepthRange", nullptr } },
-                { (PRFuncPtr*) &mSymbols.fReadBuffer, { "ReadBuffer", nullptr } },
-                { (PRFuncPtr*) &mSymbols.fMapBuffer, { "MapBuffer", nullptr } },
-                { (PRFuncPtr*) &mSymbols.fUnmapBuffer, { "UnmapBuffer", nullptr } },
-                { (PRFuncPtr*) &mSymbols.fPointParameterf, { "PointParameterf", nullptr } },
-                { (PRFuncPtr*) &mSymbols.fDrawBuffer, { "DrawBuffer", nullptr } },
-                    // These functions are only used by Skia/GL in desktop mode.
-                    // Other parts of Gecko should avoid using these
-                    { (PRFuncPtr*) &mSymbols.fDrawBuffers, { "DrawBuffers", nullptr } },
-                    { (PRFuncPtr*) &mSymbols.fClientActiveTexture, { "ClientActiveTexture", nullptr } },
-                    { (PRFuncPtr*) &mSymbols.fDisableClientState, { "DisableClientState", nullptr } },
-                    { (PRFuncPtr*) &mSymbols.fEnableClientState, { "EnableClientState", nullptr } },
-                    { (PRFuncPtr*) &mSymbols.fLoadIdentity, { "LoadIdentity", nullptr } },
-                    { (PRFuncPtr*) &mSymbols.fLoadMatrixf, { "LoadMatrixf", nullptr } },
-                    { (PRFuncPtr*) &mSymbols.fMatrixMode, { "MatrixMode", nullptr } },
-                    { (PRFuncPtr*) &mSymbols.fTexGeni, { "TexGeni", nullptr } },
-                    { (PRFuncPtr*) &mSymbols.fTexGenf, { "TexGenf", nullptr } },
-                    { (PRFuncPtr*) &mSymbols.fTexGenfv, { "TexGenfv", nullptr } },
-                    { (PRFuncPtr*) &mSymbols.fVertexPointer, { "VertexPointer", nullptr } },
+            fnLoadForFeature(symbols, GLFeature::framebuffer_object_EXT_OES);
+        }
+
+        if (IsSupported(GLFeature::framebuffer_blit)) {
+            const SymLoadStruct symbols[] = {
+                EXT_SYMBOL3(BlitFramebuffer, ANGLE, EXT, NV),
+                END_SYMBOLS
+            };
+            fnLoadForFeature(symbols, GLFeature::framebuffer_blit);
+        }
+
+        if (IsSupported(GLFeature::framebuffer_multisample)) {
+            const SymLoadStruct symbols[] = {
+                EXT_SYMBOL3(RenderbufferStorageMultisample, ANGLE, APPLE, EXT),
                 END_SYMBOLS
             };
-
-            if (!LoadSymbols(&symbols_desktop[0], trygl, prefix)) {
-                NS_ERROR("Desktop symbols failed to load.");
-                mInitialized = false;
+            fnLoadForFeature(symbols, GLFeature::framebuffer_multisample);
+        }
+
+        if (IsExtensionSupported(GLContext::ARB_geometry_shader4) ||
+            IsExtensionSupported(GLContext::NV_geometry_program4))
+        {
+            const SymLoadStruct symbols[] = {
+                EXT_SYMBOL2(FramebufferTextureLayer, ARB, EXT),
+                END_SYMBOLS
+            };
+            if (!LoadGLSymbols(this, prefix, trygl, symbols,
+                               "ARB_geometry_shader4/NV_geometry_program4"))
+            {
+                MarkExtensionUnsupported(GLContext::ARB_geometry_shader4);
+                MarkExtensionUnsupported(GLContext::NV_geometry_program4);
             }
         }
     }
 
-    const char *glVendorString = nullptr;
-    const char *glRendererString = nullptr;
-
-    if (mInitialized) {
-        // The order of these strings must match up with the order of the enum
-        // defined in GLContext.h for vendor IDs
-        glVendorString = (const char *)fGetString(LOCAL_GL_VENDOR);
-        if (!glVendorString)
-            mInitialized = false;
-
-        const char *vendorMatchStrings[size_t(GLVendor::Other)] = {
-                "Intel",
-                "NVIDIA",
-                "ATI",
-                "Qualcomm",
-                "Imagination",
-                "nouveau",
-                "Vivante",
-                "VMware, Inc.",
-                "ARM"
-        };
-
-        mVendor = GLVendor::Other;
-        for (size_t i = 0; i < size_t(GLVendor::Other); ++i) {
-            if (DoesStringMatch(glVendorString, vendorMatchStrings[i])) {
-                mVendor = GLVendor(i);
-                break;
+    if (!IsSupported(GLFeature::framebuffer_object) &&
+        !IsSupported(GLFeature::framebuffer_object_EXT_OES))
+    {
+        NS_ERROR("GLContext requires support for framebuffer objects.");
+        return false;
+    }
+    MOZ_RELEASE_ASSERT(mSymbols.fBindFramebuffer);
+
+    ////////////////
+
+    LoadMoreSymbols(prefix, trygl);
+
+    ////////////////////////////////////////////////////////////////////////////
+
+    raw_fGetIntegerv(LOCAL_GL_VIEWPORT, mViewportRect);
+    raw_fGetIntegerv(LOCAL_GL_SCISSOR_BOX, mScissorRect);
+    raw_fGetIntegerv(LOCAL_GL_MAX_TEXTURE_SIZE, &mMaxTextureSize);
+    raw_fGetIntegerv(LOCAL_GL_MAX_CUBE_MAP_TEXTURE_SIZE, &mMaxCubeMapTextureSize);
+    raw_fGetIntegerv(LOCAL_GL_MAX_RENDERBUFFER_SIZE, &mMaxRenderbufferSize);
+    raw_fGetIntegerv(LOCAL_GL_MAX_VIEWPORT_DIMS, mMaxViewportDims);
+
+#ifdef XP_MACOSX
+    if (mWorkAroundDriverBugs) {
+        if (mVendor == GLVendor::Intel) {
+            // see bug 737182 for 2D textures, bug 684882 for cube map textures.
+            mMaxTextureSize        = std::min(mMaxTextureSize,        4096);
+            mMaxCubeMapTextureSize = std::min(mMaxCubeMapTextureSize, 512);
+            // for good measure, we align renderbuffers on what we do for 2D textures
+            mMaxRenderbufferSize   = std::min(mMaxRenderbufferSize,   4096);
+            mNeedsTextureSizeChecks = true;
+        } else if (mVendor == GLVendor::NVIDIA) {
+            if (nsCocoaFeatures::OnMountainLionOrLater()) {
+                // See bug 879656.  8192 fails, 8191 works.
+                mMaxTextureSize = std::min(mMaxTextureSize, 8191);
+                mMaxRenderbufferSize = std::min(mMaxRenderbufferSize, 8191);
+            } else {
+                // See bug 877949.
+                mMaxTextureSize = std::min(mMaxTextureSize, 4096);
+                mMaxRenderbufferSize = std::min(mMaxRenderbufferSize, 4096);
+            }
+
+            // Part of the bug 879656, but it also doesn't hurt the 877949
+            mNeedsTextureSizeChecks = true;
+        }
+    }
+#endif
+#ifdef MOZ_X11
+    if (mWorkAroundDriverBugs) {
+        if (mVendor == GLVendor::Nouveau) {
+            // see bug 814716. Clamp MaxCubeMapTextureSize at 2K for Nouveau.
+            mMaxCubeMapTextureSize = std::min(mMaxCubeMapTextureSize, 2048);
+            mNeedsTextureSizeChecks = true;
+        } else if (mVendor == GLVendor::Intel) {
+            // Bug 1199923. Driver seems to report a larger max size than
+            // actually supported.
+            mMaxTextureSize /= 2;
+            mMaxRenderbufferSize /= 2;
+            mNeedsTextureSizeChecks = true;
+        }
+    }
+#endif
+    if (mWorkAroundDriverBugs &&
+        Renderer() == GLRenderer::AdrenoTM420) {
+        // see bug 1194923. Calling glFlush before glDeleteFramebuffers
+        // prevents occasional driver crash.
+        mNeedsFlushBeforeDeleteFB = true;
+    }
+
+    mMaxTextureImageSize = mMaxTextureSize;
+
+    if (IsSupported(GLFeature::framebuffer_multisample)) {
+        fGetIntegerv(LOCAL_GL_MAX_SAMPLES, (GLint*)&mMaxSamples);
+    }
+
+    ////////////////////////////////////////////////////////////////////////////
+
+    // We're ready for final setup.
+    fBindFramebuffer(LOCAL_GL_FRAMEBUFFER, 0);
+
+    // TODO: Remove SurfaceCaps::any.
+    if (mCaps.any) {
+        mCaps.any = false;
+        mCaps.color = true;
+        mCaps.alpha = false;
+    }
+
+    UpdateGLFormats(mCaps);
+
+    mTexGarbageBin = new TextureGarbageBin(this);
+
+    MOZ_ASSERT(IsCurrent());
+
+    if (DebugMode() && IsExtensionSupported(KHR_debug)) {
+        fEnable(LOCAL_GL_DEBUG_OUTPUT);
+        fDisable(LOCAL_GL_DEBUG_OUTPUT_SYNCHRONOUS);
+        fDebugMessageCallback(&StaticDebugCallback, (void*)this);
+        fDebugMessageControl(LOCAL_GL_DONT_CARE,
+                             LOCAL_GL_DONT_CARE,
+                             LOCAL_GL_DONT_CARE,
+                             0, nullptr,
+                             true);
+    }
+
+    mVersionString = nsPrintfCString("%u.%u.%u", mVersion / 100, (mVersion / 10) % 10,
+                                     mVersion % 10);
+    return true;
+}
+
+void
+GLContext::LoadMoreSymbols(const char* prefix, bool trygl)
+{
+    const auto fnLoadForExt = [this, prefix, trygl](const SymLoadStruct* list,
+                                                    GLExtensions ext)
+    {
+        return this->LoadExtSymbols(prefix, trygl, list, ext);
+    };
+
+    const auto fnLoadForFeature = [this, prefix, trygl](const SymLoadStruct* list,
+                                                        GLFeature feature)
+    {
+        return this->LoadFeatureSymbols(prefix, trygl, list, feature);
+    };
+
+    const auto fnLoadFeatureByCore = [this, fnLoadForFeature](const SymLoadStruct* coreList,
+                                                              const SymLoadStruct* extList,
+                                                              GLFeature feature)
+    {
+        const bool useCore = this->IsFeatureProvidedByCoreSymbols(feature);
+        const auto list = useCore ? coreList : extList;
+        return fnLoadForFeature(list, feature);
+    };
+
+    bool hasRobustness = false;
+    if (SupportsRobustness()) {
+        if (IsExtensionSupported(ARB_robustness)) {
+            const SymLoadStruct symbols[] = {
+                { (PRFuncPtr*) &mSymbols.fGetGraphicsResetStatus, { "GetGraphicsResetStatusARB", nullptr } },
+                END_SYMBOLS
+            };
+            if (fnLoadForExt(symbols, ARB_robustness)) {
+                hasRobustness = true;
             }
         }
 
-        // The order of these strings must match up with the order of the enum
-        // defined in GLContext.h for renderer IDs
-        glRendererString = (const char *)fGetString(LOCAL_GL_RENDERER);
-        if (!glRendererString)
-            mInitialized = false;
-
-        const char *rendererMatchStrings[size_t(GLRenderer::Other)] = {
-                "Adreno 200",
-                "Adreno 205",
-                "Adreno (TM) 200",
-                "Adreno (TM) 205",
-                "Adreno (TM) 320",
-                "Adreno (TM) 420",
-                "PowerVR SGX 530",
-                "PowerVR SGX 540",
-                "NVIDIA Tegra",
-                "Android Emulator",
-                "Gallium 0.4 on llvmpipe",
-                "Intel HD Graphics 3000 OpenGL Engine",
-                "Microsoft Basic Render Driver"
-        };
-
-        mRenderer = GLRenderer::Other;
-        for (size_t i = 0; i < size_t(GLRenderer::Other); ++i) {
-            if (DoesStringMatch(glRendererString, rendererMatchStrings[i])) {
-                mRenderer = GLRenderer(i);