merge mozilla-inbound to mozilla-central a=merge
authorCarsten "Tomcat" Book <cbook@mozilla.com>
Mon, 25 Jul 2016 15:50:41 +0200
changeset 346507 e23f2ec25e96ad6cdf64af446e81a0f0dfeb4b5c
parent 346370 e730a1dca1a41bd6b0824353305abc25284e2d52 (current diff)
parent 346506 1870dfec5cb00b6bcdec167eb44ae628ae7812f2 (diff)
child 346508 e48f912b76fb2415d8971f4e3b8176ffd7fc4139
child 346588 887712e8ba10bf1b9df7b0f9b5d5293bd35fdfd8
push id6389
push userraliiev@mozilla.com
push dateMon, 19 Sep 2016 13:38:22 +0000
treeherdermozilla-beta@01d67bfe6c81 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone50.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-inbound to mozilla-central a=merge
browser/app/profile/firefox.js
browser/base/content/browser.js
browser/components/customizableui/CustomizableWidgets.jsm
devtools/client/themes/toolbox.css
dom/base/nsDocument.cpp
dom/base/nsGlobalWindow.cpp
dom/ipc/ContentChild.cpp
dom/ipc/ContentParent.cpp
dom/ipc/ContentParent.h
dom/ipc/PContent.ipdl
dom/media/eme/MediaKeySystemAccessManager.cpp
media/libcubeb/src/cubeb_audiounit.c
mfbt/NumericLimits.h
mobile/android/tests/browser/robocop/src/org/mozilla/gecko/tests/testAwesomebar.java
netwerk/test/unit/test_cache2-29e-non-206-response.js
netwerk/test/unit/xpcshell.ini
security/manager/ssl/tests/unit/test_fallback_cipher.js
widget/android/android/StrongPointer.h
widget/windows/TSFTextStore.h
--- a/CLOBBER
+++ b/CLOBBER
@@ -17,9 +17,9 @@
 #
 # Modifying this file will now automatically clobber the buildbot machines \o/
 #
 
 # Are you updating CLOBBER because you think it's needed for your WebIDL
 # changes to stick? As of bug 928195, this shouldn't be necessary! Please
 # don't change CLOBBER for WebIDL changes any more.
 
-Bug 1287946 - clobber due to generated SDK headers changing (bug 1182840)
+Bug 1285541 - Clobber needed because this patch renames a file.
--- a/addon-sdk/source/lib/sdk/tab/events.js
+++ b/addon-sdk/source/lib/sdk/tab/events.js
@@ -34,37 +34,41 @@ const TYPES = ["TabOpen","TabClose","Tab
 function tabEventsFor(window) {
   // Map supported event types to a streams of those events on the given
   // `window` and than merge these streams into single form stream off
   // all events.
   let channels = TYPES.map(type => open(window, type));
   return merge(channels);
 }
 
-// Filter DOMContentLoaded events from all the browser events.
-var readyEvents = filter(events, e => e.type === "DOMContentLoaded");
-// Map DOMContentLoaded events to it's target browser windows.
-var futureWindows = map(readyEvents, e => e.target);
-// Expand all browsers that will become interactive to supported tab events
-// on these windows. Result will be a tab events from all tabs of all windows
-// that will become interactive.
-var eventsFromFuture = expand(futureWindows, tabEventsFor);
+// Create our event channels.  We do this in a separate function to
+// minimize the chance of leaking intermediate objects on the global.
+function makeEvents() {
+  // Filter DOMContentLoaded events from all the browser events.
+  var readyEvents = filter(events, e => e.type === "DOMContentLoaded");
+  // Map DOMContentLoaded events to it's target browser windows.
+  var futureWindows = map(readyEvents, e => e.target);
+  // Expand all browsers that will become interactive to supported tab events
+  // on these windows. Result will be a tab events from all tabs of all windows
+  // that will become interactive.
+  var eventsFromFuture = expand(futureWindows, tabEventsFor);
 
-// Above covers only windows that will become interactive in a future, but some
-// windows may already be interactive so we pick those and expand to supported
-// tab events for them too.
-var interactiveWindows = windows("navigator:browser", { includePrivate: true }).
-                         filter(isInteractive);
-var eventsFromInteractive = merge(interactiveWindows.map(tabEventsFor));
+  // Above covers only windows that will become interactive in a future, but some
+  // windows may already be interactive so we pick those and expand to supported
+  // tab events for them too.
+  var interactiveWindows = windows("navigator:browser", { includePrivate: true }).
+                           filter(isInteractive);
+  var eventsFromInteractive = merge(interactiveWindows.map(tabEventsFor));
 
 
-// Finally merge stream of tab events from future windows and current windows
-// to cover all tab events on all windows that will open.
-var allEvents = merge([eventsFromInteractive, eventsFromFuture]);
+  // Finally merge stream of tab events from future windows and current windows
+  // to cover all tab events on all windows that will open.
+  return merge([eventsFromInteractive, eventsFromFuture]);
+}
 
 // Map events to Fennec format if necessary
-exports.events = map(allEvents, function (event) {
+exports.events = map(makeEvents(), function (event) {
   return !isFennec ? event : {
     type: event.type,
     target: event.target.ownerDocument.defaultView.BrowserApp
             .getTabForBrowser(event.target)
   };
 });
--- a/addon-sdk/source/lib/sdk/window/events.js
+++ b/addon-sdk/source/lib/sdk/window/events.js
@@ -38,27 +38,31 @@ function eventsFor(window) {
   //       completely closed.
   let interactive = open(window, "DOMContentLoaded", { capture: true });
   let complete = open(window, "load", { capture: true });
   let states = merge([interactive, complete]);
   let changes = filter(states, makeStrictDocumentFilter(window));
   return map(changes, toEventWithDefaultViewTarget);
 }
 
-// In addition to observing windows that are open we also observe windows
-// that are already already opened in case they're in process of loading.
-var opened = windows(null, { includePrivate: true });
-var currentEvents = merge(opened.map(eventsFor));
+// Create our event channels.  We do this in a separate function to
+// minimize the chance of leaking intermediate objects on the global.
+function makeEvents() {
+  // In addition to observing windows that are open we also observe windows
+  // that are already already opened in case they're in process of loading.
+  var opened = windows(null, { includePrivate: true });
+  var currentEvents = merge(opened.map(eventsFor));
 
-// Register system event listeners for top level window open / close.
-function rename({type, target, data}) {
-  return { type: rename[type], target: target, data: data }
+  // Register system event listeners for top level window open / close.
+  function rename({type, target, data}) {
+    return { type: rename[type], target: target, data: data }
+  }
+  rename.domwindowopened = "open";
+  rename.domwindowclosed = "close";
+
+  var openEvents = map(observe("domwindowopened"), rename);
+  var closeEvents = map(observe("domwindowclosed"), rename);
+  var futureEvents = expand(openEvents, ({target}) => eventsFor(target));
+
+  return merge([currentEvents, futureEvents, openEvents, closeEvents]);
 }
-rename.domwindowopened = "open";
-rename.domwindowclosed = "close";
 
-var openEvents = map(observe("domwindowopened"), rename);
-var closeEvents = map(observe("domwindowclosed"), rename);
-var futureEvents = expand(openEvents, ({target}) => eventsFor(target));
-
-var channel = merge([currentEvents, futureEvents,
-                     openEvents, closeEvents]);
-exports.events = channel;
+exports.events = makeEvents();
--- a/addon-sdk/source/test/leak/jetpack-package.ini
+++ b/addon-sdk/source/test/leak/jetpack-package.ini
@@ -1,7 +1,8 @@
 [DEFAULT]
 support-files =
   leak-utils.js
 
 [test-leak-window-events.js]
 [test-leak-event-dom-closed-window.js]
+[test-leak-tab-events.js]
 [test-leak-event-chrome.js]
new file mode 100644
--- /dev/null
+++ b/addon-sdk/source/test/leak/test-leak-tab-events.js
@@ -0,0 +1,46 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+'use strict';
+
+const { asyncWindowLeakTest } = require("./leak-utils");
+const { Loader } = require('sdk/test/loader');
+const openWindow = require("sdk/window/utils").open;
+
+exports["test sdk/tab/events does not leak new window"] = function*(assert) {
+  yield asyncWindowLeakTest(assert, _ => {
+    return new Promise(resolve => {
+      let loader = Loader(module);
+      let { events } = loader.require('sdk/tab/events');
+      let w = openWindow();
+      w.addEventListener("load", function windowLoaded(evt) {
+        w.removeEventListener("load", windowLoaded);
+        w.addEventListener("DOMWindowClose", function windowClosed(evt) {
+          w.removeEventListener("DOMWindowClose", windowClosed);
+          resolve(loader);
+        });
+        w.close();
+      });
+    });
+  });
+}
+
+exports["test sdk/tab/events does not leak when attached to existing window"] = function*(assert) {
+  yield asyncWindowLeakTest(assert, _ => {
+    return new Promise(resolve => {
+      let loader = Loader(module);
+      let w = openWindow();
+      w.addEventListener("load", function windowLoaded(evt) {
+        w.removeEventListener("load", windowLoaded);
+        let { events } = loader.require('sdk/tab/events');
+        w.addEventListener("DOMWindowClose", function windowClosed(evt) {
+          w.removeEventListener("DOMWindowClose", windowClosed);
+          resolve(loader);
+        });
+        w.close();
+      });
+    });
+  });
+}
+
+require("sdk/test").run(exports);
--- a/addon-sdk/source/test/leak/test-leak-window-events.js
+++ b/addon-sdk/source/test/leak/test-leak-window-events.js
@@ -39,9 +39,27 @@ exports["test window/events for leaks"] 
       });
 
       // Open a window.  This will trigger our data events.
       open();
     });
   });
 };
 
+exports["test window/events for leaks with existing window"] = function*(assert) {
+  yield asyncWindowLeakTest(assert, _ => {
+    return new Promise((resolve, reject) => {
+      let loader = Loader(module);
+      let w = open();
+      w.addEventListener("load", function windowLoaded(evt) {
+        w.removeEventListener("load", windowLoaded);
+        let { events } = loader.require("sdk/window/events");
+        w.addEventListener("DOMWindowClose", function windowClosed(evt) {
+          w.removeEventListener("DOMWindowClose", windowClosed);
+          resolve(loader);
+        });
+        w.close();
+      });
+    });
+  });
+};
+
 require("sdk/test").run(exports);
--- a/browser/app/moz.build
+++ b/browser/app/moz.build
@@ -75,11 +75,14 @@ if CONFIG['OS_ARCH'] == 'WINNT' and not 
 DISABLE_STL_WRAPPING = True
 
 if CONFIG['MOZ_LINKER']:
     OS_LIBS += CONFIG['MOZ_ZLIB_LIBS']
 
 if CONFIG['HAVE_CLOCK_MONOTONIC']:
     OS_LIBS += CONFIG['REALTIME_LIBS']
 
+if CONFIG['MOZ_GPSD']:
+    DEFINES['MOZ_GPSD'] = True
+
 for icon in ('firefox', 'document', 'newwindow', 'newtab', 'pbmode'):
     DEFINES[icon.upper() + '_ICO'] = '"%s/dist/branding/%s.ico"' % (
         TOPOBJDIR, icon)
--- a/browser/app/profile/firefox.js
+++ b/browser/app/profile/firefox.js
@@ -1235,16 +1235,26 @@ pref("geo.provider.use_corelocation", fa
 pref("geo.provider.use_corelocation", true);
 #endif
 #endif
 
 #ifdef XP_WIN
 pref("geo.provider.ms-windows-location", false);
 #endif
 
+#ifdef MOZ_WIDGET_GTK
+#ifdef MOZ_GPSD
+#ifdef RELEASE_BUILD
+pref("geo.provider.use_gpsd", false);
+#else
+pref("geo.provider.use_gpsd", true);
+#endif
+#endif
+#endif
+
 // Necko IPC security checks only needed for app isolation for cookies/cache/etc:
 // currently irrelevant for desktop e10s
 pref("network.disable.ipc.security", true);
 
 // CustomizableUI debug logging.
 pref("browser.uiCustomization.debug", false);
 
 // CustomizableUI state of the browser's user interface
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -3,16 +3,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/. */
 
 var Ci = Components.interfaces;
 var Cu = Components.utils;
 var Cc = Components.classes;
 
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/ContextualIdentityService.jsm");
 Cu.import("resource://gre/modules/NotificationDB.jsm");
 Cu.import("resource:///modules/RecentWindow.jsm");
 
 
 XPCOMUtils.defineLazyModuleGetter(this, "Preferences",
                                   "resource://gre/modules/Preferences.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "Deprecated",
                                   "resource://gre/modules/Deprecated.jsm");
@@ -51,18 +52,16 @@ XPCOMUtils.defineLazyServiceGetter(this,
                                    "mozIAsyncFavicons");
 XPCOMUtils.defineLazyServiceGetter(this, "gDNSService",
                                    "@mozilla.org/network/dns-service;1",
                                    "nsIDNSService");
 XPCOMUtils.defineLazyServiceGetter(this, "WindowsUIUtils",
                                    "@mozilla.org/windows-ui-utils;1", "nsIWindowsUIUtils");
 XPCOMUtils.defineLazyModuleGetter(this, "LightweightThemeManager",
                                   "resource://gre/modules/LightweightThemeManager.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "ContextualIdentityService",
-                                  "resource://gre/modules/ContextualIdentityService.jsm");
 XPCOMUtils.defineLazyServiceGetter(this, "gAboutNewTabService",
                                    "@mozilla.org/browser/aboutnewtab-service;1",
                                    "nsIAboutNewTabService");
 XPCOMUtils.defineLazyGetter(this, "gBrowserBundle", function() {
   return Services.strings.createBundle('chrome://browser/locale/browser.properties');
 });
 XPCOMUtils.defineLazyModuleGetter(this, "AddonWatcher",
                                   "resource://gre/modules/AddonWatcher.jsm");
--- a/browser/base/content/nsContextMenu.js
+++ b/browser/base/content/nsContextMenu.js
@@ -1,23 +1,22 @@
 /* -*- tab-width: 2; indent-tabs-mode: nil; js-indent-level: 2 -*- */
 /* vim: set ts=2 sw=2 sts=2 et tw=80: */
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
+Components.utils.import("resource://gre/modules/ContextualIdentityService.jsm");
 Components.utils.import("resource://gre/modules/PrivateBrowsingUtils.jsm");
 Components.utils.import("resource://gre/modules/InlineSpellChecker.jsm");
 Components.utils.import("resource://gre/modules/LoginManagerContextMenu.jsm");
 Components.utils.import("resource://gre/modules/BrowserUtils.jsm");
 Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
 Components.utils.import("resource://gre/modules/Services.jsm");
 
-XPCOMUtils.defineLazyModuleGetter(this, "ContextualIdentityService",
-                                  "resource://gre/modules/ContextualIdentityService.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "LoginHelper",
   "resource://gre/modules/LoginHelper.jsm");
 
 var gContextMenuContentData = null;
 
 function nsContextMenu(aXulMenu, aIsShift) {
   this.shouldDisplay = true;
--- a/browser/base/content/utilityOverlay.js
+++ b/browser/base/content/utilityOverlay.js
@@ -1,26 +1,24 @@
 /* -*- indent-tabs-mode: nil; js-indent-level: 2 -*-
  * This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 // Services = object with smart getters for common XPCOM services
 Components.utils.import("resource://gre/modules/AppConstants.jsm");
+Components.utils.import("resource://gre/modules/ContextualIdentityService.jsm");
 Components.utils.import("resource://gre/modules/Services.jsm");
 Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
 Components.utils.import("resource://gre/modules/PrivateBrowsingUtils.jsm");
 Components.utils.import("resource:///modules/RecentWindow.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "ShellService",
                                   "resource:///modules/ShellService.jsm");
 
-XPCOMUtils.defineLazyModuleGetter(this, "ContextualIdentityService",
-                                  "resource://gre/modules/ContextualIdentityService.jsm");
-
 XPCOMUtils.defineLazyServiceGetter(this, "aboutNewTabService",
                                    "@mozilla.org/browser/aboutnewtab-service;1",
                                    "nsIAboutNewTabService");
 
 this.__defineGetter__("BROWSER_NEW_TAB_URL", () => {
   if (PrivateBrowsingUtils.isWindowPrivate(window) &&
       !PrivateBrowsingUtils.permanentPrivateBrowsing &&
       !aboutNewTabService.overridden) {
@@ -442,18 +440,22 @@ function createUserContextMenu(event, ad
 
   ContextualIdentityService.getIdentities().forEach(identity => {
     if (identity.userContextId == excludeUserContextId) {
       return;
     }
 
     let menuitem = document.createElement("menuitem");
     menuitem.setAttribute("usercontextid", identity.userContextId);
-    menuitem.setAttribute("label", bundle.getString(identity.label));
-    menuitem.setAttribute("accesskey", bundle.getString(identity.accessKey));
+    menuitem.setAttribute("label", ContextualIdentityService.getUserContextLabel(identity.userContextId));
+
+    if (identity.accessKey) {
+      menuitem.setAttribute("accesskey", bundle.getString(identity.accessKey));
+    }
+
     menuitem.classList.add("menuitem-iconic");
 
     if (addCommandAttribute) {
       menuitem.setAttribute("command", "Browser:NewUserContextTab");
     }
 
     menuitem.setAttribute("image", identity.icon);
 
--- a/browser/components/customizableui/CustomizableWidgets.jsm
+++ b/browser/components/customizableui/CustomizableWidgets.jsm
@@ -1143,17 +1143,17 @@ const CustomizableWidgets = [
       while (items.firstChild) {
         items.firstChild.remove();
       }
 
       let fragment = doc.createDocumentFragment();
 
       ContextualIdentityService.getIdentities().forEach(identity => {
         let bundle = doc.getElementById("bundle_browser");
-        let label = bundle.getString(identity.label);
+        let label = ContextualIdentityService.getUserContextLabel(identity.userContextId);
 
         let item = doc.createElementNS(kNSXUL, "toolbarbutton");
         item.setAttribute("label", label);
         item.setAttribute("usercontextid", identity.userContextId);
         item.setAttribute("class", "subviewbutton");
         item.setAttribute("image", identity.icon);
 
         fragment.appendChild(item);
--- a/browser/config/tooltool-manifests/linux32/releng.manifest
+++ b/browser/config/tooltool-manifests/linux32/releng.manifest
@@ -19,15 +19,23 @@
 "version": "gecko rustc 1.10.0 (cfcb716cf 2016-07-03)",
 "size": 102276708,
 "digest": "8cc9ea8347fc7e6e6fdb15a8fd1faae977f1235a426b879b3f9128ec91d8f2b6268297ce80bf4eceb47738bd40bfeda13f143dc3fe85f1434b13adfbc095ab90",
 "algorithm": "sha512",
 "filename": "rustc.tar.xz",
 "unpack": true
 },
 {
+"version": "cargo 0.13.0-nightly (664125b 2016-07-19)",
+"size": 3123796,
+"digest": "4b9d2bcb8488b6649ba6c748e19d33bfceb25c7566e882fc7e00322392e424a5a9c5878c11c61d57cdaecf67bcc110842c6eff95e49736e8f3c83d9ce1677122",
+"algorithm": "sha512",
+"filename": "cargo.tar.xz",
+"unpack": true
+},
+{
 "size": 167175,
 "digest": "0b71a936edf5bd70cf274aaa5d7abc8f77fe8e7b5593a208f805cc9436fac646b9c4f0b43c2b10de63ff3da671497d35536077ecbc72dba7f8159a38b580f831",
 "algorithm": "sha512",
 "filename": "sccache.tar.bz2",
 "unpack": true
 }
 ]
--- a/browser/config/tooltool-manifests/linux64/releng.manifest
+++ b/browser/config/tooltool-manifests/linux64/releng.manifest
@@ -19,15 +19,23 @@
 "version": "gecko rustc 1.10.0 (cfcb716cf 2016-07-03)",
 "size": 102276708,
 "digest": "8cc9ea8347fc7e6e6fdb15a8fd1faae977f1235a426b879b3f9128ec91d8f2b6268297ce80bf4eceb47738bd40bfeda13f143dc3fe85f1434b13adfbc095ab90",
 "algorithm": "sha512",
 "filename": "rustc.tar.xz",
 "unpack": true
 },
 {
+"version": "cargo 0.13.0-nightly (664125b 2016-07-19)",
+"size": 3123796,
+"digest": "4b9d2bcb8488b6649ba6c748e19d33bfceb25c7566e882fc7e00322392e424a5a9c5878c11c61d57cdaecf67bcc110842c6eff95e49736e8f3c83d9ce1677122",
+"algorithm": "sha512",
+"filename": "cargo.tar.xz",
+"unpack": true
+},
+{
 "size": 167175,
 "digest": "0b71a936edf5bd70cf274aaa5d7abc8f77fe8e7b5593a208f805cc9436fac646b9c4f0b43c2b10de63ff3da671497d35536077ecbc72dba7f8159a38b580f831",
 "algorithm": "sha512",
 "filename": "sccache.tar.bz2",
 "unpack": true
 }
 ]
--- a/browser/config/tooltool-manifests/macosx64/cross-releng.manifest
+++ b/browser/config/tooltool-manifests/macosx64/cross-releng.manifest
@@ -19,16 +19,24 @@
 "size": 35215976, 
 "visibility": "internal", 
 "digest": "8be736545ddab25ebded188458ce974d5c9a7e29f3c50d2ebfbcb878f6aff853dd2ff5a3528bdefc64396a10101a1b50fd2fe52000140df33643cebe1ea759da", 
 "algorithm": "sha512", 
 "unpack": true,
 "filename": "MacOSX10.7.sdk.tar.bz2"
 },
 {
+"version": "cargo 0.13.0-nightly (664125b 2016-07-19)",
+"size": 2571167,
+"digest": "b2616459fbf15c75b54628a6bfe8cf89c0841ea08431f5096e72be4fac4c685785dfc7a2f18a03a5f7bd377e78d3c108e5029b12616842cbbd0497ff7363fdaf",
+"algorithm": "sha512",
+"filename": "cargo.tar.bz2",
+"unpack": true
+},
+{
 "size": 167175,
 "digest": "0b71a936edf5bd70cf274aaa5d7abc8f77fe8e7b5593a208f805cc9436fac646b9c4f0b43c2b10de63ff3da671497d35536077ecbc72dba7f8159a38b580f831",
 "algorithm": "sha512",
 "unpack": true,
 "filename": "sccache.tar.bz2"
 },
 {
 "size": 57060, 
--- a/browser/config/tooltool-manifests/macosx64/releng.manifest
+++ b/browser/config/tooltool-manifests/macosx64/releng.manifest
@@ -10,15 +10,23 @@
 {
 "size": 93295855,
 "digest": "2b8fd0c1ba337a7035090c420305a7892e663ce6781569b100b36fa21cc26146e67f44a34c7715f0004f48bbe46c232bbbf2928c9d0595243d2584530770b504",
 "algorithm": "sha512",
 "filename": "rustc.tar.bz2",
 "unpack": true
 },
 {
+"version": "cargo 0.13.0-nightly (664125b 2016-07-19)",
+"size": 2571167,
+"digest": "b2616459fbf15c75b54628a6bfe8cf89c0841ea08431f5096e72be4fac4c685785dfc7a2f18a03a5f7bd377e78d3c108e5029b12616842cbbd0497ff7363fdaf",
+"algorithm": "sha512",
+"filename": "cargo.tar.bz2",
+"unpack": true
+},
+{
 "size": 167175,
 "digest": "0b71a936edf5bd70cf274aaa5d7abc8f77fe8e7b5593a208f805cc9436fac646b9c4f0b43c2b10de63ff3da671497d35536077ecbc72dba7f8159a38b580f831",
 "algorithm": "sha512",
 "filename": "sccache.tar.bz2",
 "unpack": true
 }
 ]
--- a/browser/config/tooltool-manifests/win32/releng.manifest
+++ b/browser/config/tooltool-manifests/win32/releng.manifest
@@ -9,16 +9,24 @@
 "version": "rustc 1.10.0 (cfcb716cf 2016-07-03)",
 "size": 88820579,
 "digest": "3bc772d951bf90b01cdba9dcd0e1d131a98519dff0710bb219784ea43d4d001dbce191071a4b3824933386bb9613f173760c438939eb396b0e0dfdad9a42e4f0",
 "algorithm": "sha512",
 "filename": "rustc.tar.bz2",
 "unpack": true
 },
 {
+"version": "cargo 0.13.0-nightly (664125b 2016-07-19)",
+"size": 2298848,
+"digest": "d3d1f7b6d195248550f98eb8ce87aa314d36a8a667c110ff2058777fe5a97b7007a41dc1c8a4605c4230e9105972768918222352d5e0fdebbc49639671de38ca",
+"algorithm": "sha512",
+"filename": "cargo.tar.bz2",
+"unpack": true
+},
+{
 "size": 167175,
 "digest": "0b71a936edf5bd70cf274aaa5d7abc8f77fe8e7b5593a208f805cc9436fac646b9c4f0b43c2b10de63ff3da671497d35536077ecbc72dba7f8159a38b580f831",
 "algorithm": "sha512",
 "filename": "sccache.tar.bz2",
 "unpack": true
 },
 {
 "version": "Visual Studio 2015 Update 2 / SDK 10.0.10586.0/212",
--- a/browser/config/tooltool-manifests/win64/releng.manifest
+++ b/browser/config/tooltool-manifests/win64/releng.manifest
@@ -10,16 +10,24 @@
 "size": 94067220,
 "digest": "05cabda2a28ce6674f062aab589b4b3758e0cd4a4af364bb9a2e736254baa10d668936b2b7ed0df530c7f5ba8ea1e7f51ff3affc84a6551c46188b2f67f10e05",
 "algorithm": "sha512",
 "visibility": "public",
 "filename": "rustc.tar.bz2",
 "unpack": true
 },
 {
+"version": "cargo 0.13.0-nightly (664125b 2016-07-19)",
+"size": 2561498,
+"digest": "d300fd06b16efe49bdb1a238d516c8797d2de0edca7efadd55249401e1dd1d775fb84649630e273f95d9e8b956d87d1f75726c0a68294d25fafe078c3b2b9ba9",
+"algorithm": "sha512",
+"filename": "cargo.tar.bz2",
+"unpack": true
+},
+{
 "size": 167175,
 "digest": "0b71a936edf5bd70cf274aaa5d7abc8f77fe8e7b5593a208f805cc9436fac646b9c4f0b43c2b10de63ff3da671497d35536077ecbc72dba7f8159a38b580f831",
 "algorithm": "sha512",
 "filename": "sccache.tar.bz2",
 "unpack": true
 },
 {
 "version": "Visual Studio 2015 Update 2 / SDK 10.0.10586.0/212",
--- a/browser/modules/PluginContent.jsm
+++ b/browser/modules/PluginContent.jsm
@@ -41,16 +41,17 @@ PluginContent.prototype = {
     global.addEventListener("PluginBindingAttached", this, true, true);
     global.addEventListener("PluginCrashed",         this, true);
     global.addEventListener("PluginOutdated",        this, true);
     global.addEventListener("PluginInstantiated",    this, true);
     global.addEventListener("PluginRemoved",         this, true);
     global.addEventListener("pagehide",              this, true);
     global.addEventListener("pageshow",              this, true);
     global.addEventListener("unload",                this);
+    global.addEventListener("HiddenPlugin",          this, true);
 
     global.addMessageListener("BrowserPlugins:ActivatePlugins", this);
     global.addMessageListener("BrowserPlugins:NotificationShown", this);
     global.addMessageListener("BrowserPlugins:ContextMenuCommand", this);
     global.addMessageListener("BrowserPlugins:NPAPIPluginProcessCrashed", this);
     global.addMessageListener("BrowserPlugins:CrashReportSubmitted", this);
     global.addMessageListener("BrowserPlugins:Test:ClearCrashData", this);
   },
@@ -61,16 +62,17 @@ PluginContent.prototype = {
     global.removeEventListener("PluginBindingAttached", this, true);
     global.removeEventListener("PluginCrashed",         this, true);
     global.removeEventListener("PluginOutdated",        this, true);
     global.removeEventListener("PluginInstantiated",    this, true);
     global.removeEventListener("PluginRemoved",         this, true);
     global.removeEventListener("pagehide",              this, true);
     global.removeEventListener("pageshow",              this, true);
     global.removeEventListener("unload",                this);
+    global.removeEventListener("HiddenPlugin",          this, true);
 
     global.removeMessageListener("BrowserPlugins:ActivatePlugins", this);
     global.removeMessageListener("BrowserPlugins:NotificationShown", this);
     global.removeMessageListener("BrowserPlugins:ContextMenuCommand", this);
     global.removeMessageListener("BrowserPlugins:NPAPIPluginProcessCrashed", this);
     global.removeMessageListener("BrowserPlugins:CrashReportSubmitted", this);
     global.removeMessageListener("BrowserPlugins:Test:ClearCrashData", this);
     delete this.global;
@@ -189,16 +191,55 @@ PluginContent.prototype = {
              pluginName: pluginName,
              pluginTag: pluginTag,
              permissionString: permissionString,
              fallbackType: fallbackType,
              blocklistState: blocklistState,
            };
   },
 
+  _getPluginInfoForTag: function (pluginTag, tagMimetype) {
+    let pluginHost = Cc["@mozilla.org/plugin/host;1"].getService(Ci.nsIPluginHost);
+
+    let pluginName = gNavigatorBundle.GetStringFromName("pluginInfo.unknownPlugin");
+    let permissionString = null;
+    let blocklistState = null;
+
+    if (pluginTag) {
+      pluginName = BrowserUtils.makeNicePluginName(pluginTag.name);
+
+      permissionString = pluginHost.getPermissionStringForTag(pluginTag);
+      blocklistState = pluginTag.blocklistState;
+
+      // Convert this from nsIPluginTag so it can be serialized.
+      let properties = ["name", "description", "filename", "version", "enabledState", "niceName"];
+      let pluginTagCopy = {};
+      for (let prop of properties) {
+        pluginTagCopy[prop] = pluginTag[prop];
+      }
+      pluginTag = pluginTagCopy;
+
+      // Make state-softblocked == state-notblocked for our purposes,
+      // they have the same UI. STATE_OUTDATED should not exist for plugin
+      // items, but let's alias it anyway, just in case.
+      if (blocklistState == Ci.nsIBlocklistService.STATE_SOFTBLOCKED ||
+          blocklistState == Ci.nsIBlocklistService.STATE_OUTDATED) {
+        blocklistState = Ci.nsIBlocklistService.STATE_NOT_BLOCKED;
+      }
+    }
+
+    return { mimetype: tagMimetype,
+             pluginName: pluginName,
+             pluginTag: pluginTag,
+             permissionString: permissionString,
+             fallbackType: null,
+             blocklistState: blocklistState,
+           };
+  },
+
   /**
    * Update the visibility of the plugin overlay.
    */
   setVisibility : function (plugin, overlay, shouldShow) {
     overlay.classList.toggle("visible", shouldShow);
     if (shouldShow) {
       overlay.removeAttribute("dismissed");
     }
@@ -348,16 +389,24 @@ PluginContent.prototype = {
     if (eventType == "PluginCrashed" &&
         !(event.target instanceof Ci.nsIObjectLoadingContent)) {
       // If the event target is not a plugin object (i.e., an <object> or
       // <embed> element), this call is for a window-global plugin.
       this.onPluginCrashed(event.target, event);
       return;
     }
 
+    if (eventType == "HiddenPlugin") {
+      let pluginTag = event.tag.QueryInterface(Ci.nsIPluginTag);
+      if (event.target.defaultView.top.document != this.content.document) {
+	return;
+      }
+      this._showClickToPlayNotification(pluginTag, true);
+    }
+
     let plugin = event.target;
     let doc = plugin.ownerDocument;
 
     if (!(plugin instanceof Ci.nsIObjectLoadingContent))
       return;
 
     if (eventType == "PluginBindingAttached") {
       // The plugin binding fires this event when it is created.
@@ -708,17 +757,23 @@ PluginContent.prototype = {
     }
 
     let pluginData = this.pluginData;
 
     let principal = this.content.document.nodePrincipal;
     let location = this.content.document.location.href;
 
     for (let p of plugins) {
-      let pluginInfo = this._getPluginInfo(p);
+      let pluginInfo;
+      if (p instanceof Ci.nsIPluginTag) {
+        let mimeType = p.getMimeTypes() > 0 ? p.getMimeTypes()[0] : null;
+        pluginInfo = this._getPluginInfoForTag(p, mimeType);
+      } else {
+        pluginInfo = this._getPluginInfo(p);
+      }
       if (pluginInfo.permissionString === null) {
         Cu.reportError("No permission string for active plugin.");
         continue;
       }
       if (pluginData.has(pluginInfo.permissionString)) {
         continue;
       }
 
--- a/build/mozconfig.rust
+++ b/build/mozconfig.rust
@@ -1,9 +1,10 @@
 # Options to enable rust in automation builds.
 
 # Tell configure to use the tooltool rustc.
 # Assume this is compiled with --enable-rpath so we don't
 # have to set LD_LIBRARY_PATH.
 RUSTC="$topsrcdir/rustc/bin/rustc"
+CARGO="$topsrcdir/cargo/bin/cargo"
 
 # Enable rust in the build.
 ac_add_options --enable-rust
--- a/config/system-headers
+++ b/config/system-headers
@@ -493,16 +493,17 @@ gdk-pixbuf/gdk-pixbuf.h
 Gestalt.h
 getopt.h
 glibconfig.h
 glib.h
 glib-object.h
 gmodule.h
 gnome.h
 gnu/libc-version.h
+gps.h
 grp.h
 gssapi_generic.h
 gssapi/gssapi_generic.h
 gssapi/gssapi.h
 gssapi.h
 gtk/gtk.h
 gtk/gtkx.h
 gtk/gtkunixprint.h
--- a/devtools/client/netmonitor/netmonitor.css
+++ b/devtools/client/netmonitor/netmonitor.css
@@ -2,16 +2,24 @@
 /* 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/. */
 
 #toolbar-labels {
   overflow: hidden;
 }
 
+/**
+ * Collapsed details pane needs to be truly hidden to prevent both accessibility
+ * tools and keyboard from accessing its contents.
+ */
+#details-pane.pane-collapsed {
+  visibility: hidden;
+}
+
 #details-pane-toggle[disabled] {
   display: none;
 }
 
 #custom-pane {
   overflow: auto;
 }
 
@@ -31,14 +39,13 @@
   /* workaround for textbox not supporting the @crop attribute */
   text-overflow: ellipsis;
 }
 
 /* Responsive sidebar */
 @media (max-width: 700px) {
   #toolbar-spacer,
   #details-pane-toggle,
-  #details-pane.pane-collapsed,
   .requests-menu-waterfall,
   #requests-menu-network-summary-button > .toolbarbutton-text {
     display: none;
   }
 }
--- a/devtools/client/shared/components/reps/grip.js
+++ b/devtools/client/shared/components/reps/grip.js
@@ -50,40 +50,34 @@ define(function (require, exports, modul
     },
 
     propIterator: function (object, max) {
       // Property filter. Show only interesting properties to the user.
       let isInterestingProp = (type, value) => {
         return (
           type == "boolean" ||
           type == "number" ||
-          type == "string" ||
-          type == "object"
+          (type == "string" && value.length != 0)
         );
       };
 
-      // Object members with non-empty values are preferred since it gives the
-      // user a better overview of the object.
-      let props = this.getProps(object, max, isInterestingProp);
-
-      if (props.length <= max) {
-        // There are not enough props yet (or at least, not enough props to
-        // be able to know whether we should print "more…" or not).
-        // Let's display also empty members and functions.
-        props = props.concat(this.getProps(object, max, (t, value) => {
-          return !isInterestingProp(t, value);
-        }));
+      let ownProperties = object.preview ? object.preview.ownProperties : [];
+      let indexes = this.getPropIndexes(ownProperties, max, isInterestingProp);
+      if (indexes.length < max && indexes.length < object.ownPropertyLength) {
+        // There are not enough props yet. Then add uninteresting props to display them.
+        indexes = indexes.concat(
+          this.getPropIndexes(ownProperties, max - indexes.length, (t, value) => {
+            return !isInterestingProp(t, value);
+          })
+        );
       }
 
-      // getProps() can return max+1 properties (it can't return more)
-      // to indicate that there is more props than allowed. Remove the last
-      // one and append 'more…' postfix in such case.
-      if (props.length > max) {
-        props.pop();
-
+      let props = this.getProps(ownProperties, indexes);
+      if (props.length < object.ownPropertyLength) {
+        // There are some undisplayed props. Then display "more...".
         let objectLink = this.props.objectLink || span;
 
         props.push(Caption({
           key: "more",
           object: objectLink({
             object: object
           }, "more…")
         }));
@@ -95,56 +89,83 @@ define(function (require, exports, modul
         props[last] = React.cloneElement(props[last], {
           delim: ""
         });
       }
 
       return props;
     },
 
-    getProps: function (object, max, filter) {
+    /**
+     * Get props ordered by index.
+     *
+     * @param {Object} ownProperties Props object.
+     * @param {Array} indexes Indexes of props.
+     * @return {Array} Props.
+     */
+    getProps: function (ownProperties, indexes) {
       let props = [];
 
-      max = max || 3;
-      if (!object) {
-        return props;
-      }
+      // Make indexes ordered by ascending.
+      indexes.sort(function (a, b) {
+        return a - b;
+      });
+
+      indexes.forEach((i) => {
+        let name = Object.keys(ownProperties)[i];
+        let value = ownProperties[name].value;
+        props.push(PropRep(Object.assign({}, this.props, {
+          key: name,
+          mode: "tiny",
+          name: name,
+          object: value,
+          equal: ": ",
+          delim: ", ",
+        })));
+      });
+
+      return props;
+    },
+
+    /**
+     * Get the indexes of props in the object.
+     *
+     * @param {Object} ownProperties Props object.
+     * @param {Number} max The maximum length of indexes array.
+     * @param {Function} filter Filter the props you want.
+     * @return {Array} Indexes of interesting props in the object.
+     */
+    getPropIndexes: function (ownProperties, max, filter) {
+      let indexes = [];
 
       try {
-        let ownProperties = object.preview ? object.preview.ownProperties : [];
+        let i = 0;
         for (let name in ownProperties) {
-          if (props.length > max) {
-            return props;
+          if (indexes.length >= max) {
+            return indexes;
           }
 
           let prop = ownProperties[name];
-          let value = prop.value || {};
+          let value = prop.value;
 
           // Type is specified in grip's "class" field and for primitive
           // values use typeof.
           let type = (value.class || typeof value);
           type = type.toLowerCase();
 
-          // Show only interesting properties.
           if (filter(type, value)) {
-            props.push(PropRep(Object.assign({}, this.props, {
-              key: name,
-              mode: "tiny",
-              name: name,
-              object: value,
-              equal: ": ",
-              delim: ", ",
-            })));
+            indexes.push(i);
           }
+          i++;
         }
       } catch (err) {
         console.error(err);
       }
 
-      return props;
+      return indexes;
     },
 
     render: function () {
       let object = this.props.object;
       let props = this.safePropIterator(object,
         (this.props.mode == "long") ? 100 : 3);
 
       let objectLink = this.props.objectLink || span;
--- a/devtools/client/themes/toolbox.css
+++ b/devtools/client/themes/toolbox.css
@@ -375,9 +375,28 @@
 .toolbox-panel {
   display: -moz-box;
   -moz-box-flex: 1;
   visibility: collapse;
 }
 
 .toolbox-panel[selected] {
   visibility: visible;
-}
\ No newline at end of file
+}
+
+/**
+ * When panels are collapsed or hidden, making sure that they are also
+ * inaccessible by keyboard. This is not the case by default because the are
+ * predominantly hidden using visibility: collapse; style or collapsed
+ * attribute.
+ */
+.toolbox-panel *,
+#toolbox-panel-webconsole[collapsed] * {
+  -moz-user-focus: ignore;
+}
+
+/**
+ * Enrure that selected toolbox panel's contents are keyboard accessible as they
+ * are explicitly made not to be when hidden (default).
+ */
+.toolbox-panel[selected] * {
+  -moz-user-focus: normal;
+}
--- a/devtools/shared/heapsnapshot/DeserializedNode.cpp
+++ b/devtools/shared/heapsnapshot/DeserializedNode.cpp
@@ -116,17 +116,17 @@ Concrete<DeserializedNode>::allocationSt
   MOZ_ASSERT(ptr);
   // See above comment in DeserializedNode::getEdgeReferent about why this
   // const_cast is needed and safe.
   return JS::ubi::StackFrame(const_cast<DeserializedStackFrame*>(&*ptr));
 }
 
 
 js::UniquePtr<EdgeRange>
-Concrete<DeserializedNode>::edges(JSRuntime* rt, bool) const
+Concrete<DeserializedNode>::edges(JSContext* cx, bool) const
 {
   js::UniquePtr<DeserializedEdgeRange> range(js_new<DeserializedEdgeRange>(get()));
 
   if (!range)
     return nullptr;
 
   return js::UniquePtr<EdgeRange>(range.release());
 }
--- a/devtools/shared/heapsnapshot/DeserializedNode.h
+++ b/devtools/shared/heapsnapshot/DeserializedNode.h
@@ -265,17 +265,17 @@ public:
   const char* jsObjectClassName() const override { return get().jsObjectClassName; }
   const char* scriptFilename() const final { return get().scriptFilename; }
 
   bool hasAllocationStack() const override { return get().allocationStack.isSome(); }
   StackFrame allocationStack() const override;
 
   // We ignore the `bool wantNames` parameter because we can't control whether
   // the core dump was serialized with edge names or not.
-  js::UniquePtr<EdgeRange> edges(JSRuntime* rt, bool) const override;
+  js::UniquePtr<EdgeRange> edges(JSContext* cx, bool) const override;
 
   static const char16_t concreteTypeName[];
 };
 
 template<>
 class ConcreteStackFrame<DeserializedStackFrame> : public BaseStackFrame
 {
 protected:
--- a/devtools/shared/heapsnapshot/FileDescriptorOutputStream.cpp
+++ b/devtools/shared/heapsnapshot/FileDescriptorOutputStream.cpp
@@ -10,17 +10,18 @@ namespace mozilla {
 namespace devtools {
 
 /* static */ already_AddRefed<FileDescriptorOutputStream>
 FileDescriptorOutputStream::Create(const ipc::FileDescriptor& fileDescriptor)
 {
   if (NS_WARN_IF(!fileDescriptor.IsValid()))
     return nullptr;
 
-  PRFileDesc* prfd = PR_ImportFile(PROsfd(fileDescriptor.PlatformHandle()));
+  auto rawFD = fileDescriptor.ClonePlatformHandle();
+  PRFileDesc* prfd = PR_ImportFile(PROsfd(rawFD.release()));
   if (NS_WARN_IF(!prfd))
     return nullptr;
 
   RefPtr<FileDescriptorOutputStream> stream = new FileDescriptorOutputStream(prfd);
   return stream.forget();
 }
 
 NS_IMPL_ISUPPORTS(FileDescriptorOutputStream, nsIOutputStream);
--- a/devtools/shared/heapsnapshot/HeapSnapshot.cpp
+++ b/devtools/shared/heapsnapshot/HeapSnapshot.cpp
@@ -484,17 +484,17 @@ HeapSnapshot::TakeCensus(JSContext* cx, 
     return;
   }
 
   JS::ubi::CensusHandler handler(census, rootCount, GetCurrentThreadDebuggerMallocSizeOf());
 
   {
     JS::AutoCheckCannotGC nogc;
 
-    JS::ubi::CensusTraversal traversal(JS_GetRuntime(cx), handler, nogc);
+    JS::ubi::CensusTraversal traversal(cx, handler, nogc);
     if (NS_WARN_IF(!traversal.init())) {
       rv.Throw(NS_ERROR_OUT_OF_MEMORY);
       return;
     }
 
     if (NS_WARN_IF(!traversal.addStart(getRoot()))) {
       rv.Throw(NS_ERROR_OUT_OF_MEMORY);
       return;
@@ -551,20 +551,20 @@ HeapSnapshot::DescribeNode(JSContext* cx
 
 already_AddRefed<DominatorTree>
 HeapSnapshot::ComputeDominatorTree(ErrorResult& rv)
 {
   Maybe<JS::ubi::DominatorTree> maybeTree;
   {
     auto ccrt = CycleCollectedJSRuntime::Get();
     MOZ_ASSERT(ccrt);
-    auto rt = ccrt->Runtime();
-    MOZ_ASSERT(rt);
-    JS::AutoCheckCannotGC nogc(rt);
-    maybeTree = JS::ubi::DominatorTree::Create(rt, nogc, getRoot());
+    auto cx = ccrt->Context();
+    MOZ_ASSERT(cx);
+    JS::AutoCheckCannotGC nogc(cx);
+    maybeTree = JS::ubi::DominatorTree::Create(cx, nogc, getRoot());
   }
 
   if (NS_WARN_IF(maybeTree.isNothing())) {
     rv.Throw(NS_ERROR_OUT_OF_MEMORY);
     return nullptr;
   }
 
   return MakeAndAddRef<DominatorTree>(Move(*maybeTree), this, mParent);
@@ -616,22 +616,18 @@ HeapSnapshot::ComputeShortestPaths(JSCon
       return;
     }
   }
 
   // Walk the heap graph and find the shortest paths.
 
   Maybe<ShortestPaths> maybeShortestPaths;
   {
-    auto ccrt = CycleCollectedJSRuntime::Get();
-    MOZ_ASSERT(ccrt);
-    auto rt = ccrt->Runtime();
-    MOZ_ASSERT(rt);
-    JS::AutoCheckCannotGC nogc(rt);
-    maybeShortestPaths = ShortestPaths::Create(rt, nogc, maxNumPaths, *startNode,
+    JS::AutoCheckCannotGC nogc(cx);
+    maybeShortestPaths = ShortestPaths::Create(cx, nogc, maxNumPaths, *startNode,
                                                Move(targetsSet));
   }
 
   if (NS_WARN_IF(maybeShortestPaths.isNothing())) {
     rv.Throw(NS_ERROR_OUT_OF_MEMORY);
     return;
   }
 
@@ -1233,17 +1229,17 @@ public:
       return false;
     }
 
     mozilla::MallocSizeOf mallocSizeOf = dbg::GetDebuggerMallocSizeOf(cx);
     MOZ_ASSERT(mallocSizeOf);
     protobufNode.set_size(ubiNode.size(mallocSizeOf));
 
     if (includeEdges) {
-      auto edges = ubiNode.edges(JS_GetRuntime(cx), wantNames);
+      auto edges = ubiNode.edges(cx, wantNames);
       if (NS_WARN_IF(!edges))
         return false;
 
       for ( ; !edges->empty(); edges->popFront()) {
         ubi::Edge& ubiEdge = edges->front();
 
         protobuf::Edge* protobufEdge = protobufNode.add_edges();
         if (NS_WARN_IF(!protobufEdge)) {
@@ -1375,17 +1371,17 @@ WriteHeapGraph(JSContext* cx,
   if (NS_WARN_IF(!writer.writeNode(node, CoreDumpWriter::INCLUDE_EDGES))) {
     return false;
   }
 
   // Walk the heap graph starting from the given node and serialize it into the
   // core dump.
 
   HeapSnapshotHandler handler(writer, compartments);
-  HeapSnapshotHandler::Traversal traversal(JS_GetRuntime(cx), handler, noGC);
+  HeapSnapshotHandler::Traversal traversal(cx, handler, noGC);
   if (!traversal.init())
     return false;
   traversal.wantNames = wantNames;
 
   bool ok = traversal.addStartVisited(node) &&
             traversal.traverse();
 
   if (ok) {
@@ -1540,17 +1536,17 @@ ThreadSafeChromeUtils::SaveHeapSnapshot(
   StreamWriter writer(cx, gzipStream, wantNames);
   if (NS_WARN_IF(!writer.init())) {
     rv.Throw(NS_ERROR_OUT_OF_MEMORY);
     return;
   }
 
   {
     Maybe<AutoCheckCannotGC> maybeNoGC;
-    ubi::RootList rootList(JS_GetRuntime(cx), maybeNoGC, wantNames);
+    ubi::RootList rootList(cx, maybeNoGC, wantNames);
     if (!EstablishBoundaries(cx, rv, boundaries, rootList, compartments))
       return;
 
     MOZ_ASSERT(maybeNoGC.isSome());
     ubi::Node roots(&rootList);
 
     // Serialize the initial heap snapshot metadata to the core dump.
     if (!writer.writeMetadata(PR_Now()) ||
--- a/devtools/shared/heapsnapshot/tests/gtest/DeserializedNodeUbiNodes.cpp
+++ b/devtools/shared/heapsnapshot/tests/gtest/DeserializedNodeUbiNodes.cpp
@@ -85,16 +85,16 @@ DEF_TEST(DeserializedNodeUbiNodes, {
                                                                    nullptr,
                                                                    30));
     DeserializedEdge edge3(referent3->id);
     mocked.addEdge(Move(edge3));
     EXPECT_CALL(mocked, getEdgeReferent(EdgeTo(referent3->id)))
       .Times(1)
       .WillOnce(Return(JS::ubi::Node(referent3.get())));
 
-    auto range = ubi.edges(rt);
+    auto range = ubi.edges(cx);
     ASSERT_TRUE(!!range);
 
     for ( ; !range->empty(); range->popFront()) {
       // Nothing to do here. This loop ensures that we get each edge referent
       // that we expect above.
     }
   });
--- a/devtools/shared/heapsnapshot/tests/gtest/DevTools.h
+++ b/devtools/shared/heapsnapshot/tests/gtest/DevTools.h
@@ -153,17 +153,17 @@ namespace ubi {
 
 template<>
 class Concrete<FakeNode> : public Base
 {
   const char16_t* typeName() const override {
     return concreteTypeName;
   }
 
-  js::UniquePtr<EdgeRange> edges(JSRuntime*, bool) const override {
+  js::UniquePtr<EdgeRange> edges(JSContext*, bool) const override {
     return js::UniquePtr<EdgeRange>(js_new<PreComputedEdgeRange>(get().edges));
   }
 
   Size size(mozilla::MallocSizeOf) const override {
     return get().size;
   }
 
   JS::Zone* zone() const override {
@@ -204,32 +204,32 @@ void AddEdge(FakeNode& node, FakeNode& r
 
 // Custom GMock Matchers
 
 // Use the testing namespace to avoid static analysis failures in the gmock
 // matcher classes that get generated from MATCHER_P macros.
 namespace testing {
 
 // Ensure that given node has the expected number of edges.
-MATCHER_P2(EdgesLength, rt, expectedLength, "") {
-  auto edges = arg.edges(rt);
+MATCHER_P2(EdgesLength, cx, expectedLength, "") {
+  auto edges = arg.edges(cx);
   if (!edges)
     return false;
 
   int actualLength = 0;
   for ( ; !edges->empty(); edges->popFront())
     actualLength++;
 
   return Matcher<int>(Eq(expectedLength))
     .MatchAndExplain(actualLength, result_listener);
 }
 
 // Get the nth edge and match it with the given matcher.
-MATCHER_P3(Edge, rt, n, matcher, "") {
-  auto edges = arg.edges(rt);
+MATCHER_P3(Edge, cx, n, matcher, "") {
+  auto edges = arg.edges(cx);
   if (!edges)
     return false;
 
   int i = 0;
   for ( ; !edges->empty(); edges->popFront()) {
     if (i == n) {
       return Matcher<const JS::ubi::Edge&>(matcher)
         .MatchAndExplain(edges->front(), result_listener);
--- a/devtools/shared/heapsnapshot/tests/gtest/DoesCrossCompartmentBoundaries.cpp
+++ b/devtools/shared/heapsnapshot/tests/gtest/DoesCrossCompartmentBoundaries.cpp
@@ -56,17 +56,17 @@ DEF_TEST(DoesCrossCompartmentBoundaries,
 
     // Should also serialize nodeC, which is in our target compartments, but a
     // different compartment than A.
     ExpectWriteNode(writer, nodeC);
 
     // However, should not serialize nodeD because nodeB doesn't belong to one
     // of our target compartments and so its edges are excluded from serialization.
 
-    JS::AutoCheckCannotGC noGC(rt);
+    JS::AutoCheckCannotGC noGC(cx);
 
     ASSERT_TRUE(WriteHeapGraph(cx,
                                JS::ubi::Node(&nodeA),
                                writer,
                                /* wantNames = */ false,
                                &targetCompartments,
                                noGC));
   });
--- a/devtools/shared/heapsnapshot/tests/gtest/DoesntCrossCompartmentBoundaries.cpp
+++ b/devtools/shared/heapsnapshot/tests/gtest/DoesntCrossCompartmentBoundaries.cpp
@@ -48,17 +48,17 @@ DEF_TEST(DoesntCrossCompartmentBoundarie
     ExpectWriteNode(writer, nodeA);
 
     // Should serialize nodeB, because it doesn't belong to a compartment and is
     // therefore assumed to be shared.
     ExpectWriteNode(writer, nodeB);
 
     // But we shouldn't ever serialize nodeC.
 
-    JS::AutoCheckCannotGC noGC(rt);
+    JS::AutoCheckCannotGC noGC(cx);
 
     ASSERT_TRUE(WriteHeapGraph(cx,
                                JS::ubi::Node(&nodeA),
                                writer,
                                /* wantNames = */ false,
                                &targetCompartments,
                                noGC));
   });
--- a/devtools/shared/heapsnapshot/tests/gtest/SerializesEdgeNames.cpp
+++ b/devtools/shared/heapsnapshot/tests/gtest/SerializesEdgeNames.cpp
@@ -23,31 +23,31 @@ DEF_TEST(SerializesEdgeNames, {
     AddEdge(node, referent, emptyStr);
     AddEdge(node, referent, nullptr);
 
     ::testing::NiceMock<MockWriter> writer;
 
     // Should get the node with edges once.
     EXPECT_CALL(
       writer,
-      writeNode(AllOf(EdgesLength(rt, 3),
-                      Edge(rt, 0, Field(&JS::ubi::Edge::name,
+      writeNode(AllOf(EdgesLength(cx, 3),
+                      Edge(cx, 0, Field(&JS::ubi::Edge::name,
                                         UniqueUTF16StrEq(edgeName))),
-                      Edge(rt, 1, Field(&JS::ubi::Edge::name,
+                      Edge(cx, 1, Field(&JS::ubi::Edge::name,
                                         UniqueUTF16StrEq(emptyStr))),
-                      Edge(rt, 2, Field(&JS::ubi::Edge::name,
+                      Edge(cx, 2, Field(&JS::ubi::Edge::name,
                                         UniqueIsNull()))),
                 _)
     )
       .Times(1)
       .WillOnce(Return(true));
 
     // Should get the referent node that doesn't have any edges once.
     ExpectWriteNode(writer, referent);
 
-    JS::AutoCheckCannotGC noGC(rt);
+    JS::AutoCheckCannotGC noGC(cx);
     ASSERT_TRUE(WriteHeapGraph(cx,
                                JS::ubi::Node(&node),
                                writer,
                                /* wantNames = */ true,
                                /* zones = */ nullptr,
                                noGC));
   });
--- a/devtools/shared/heapsnapshot/tests/gtest/SerializesEverythingInHeapGraphOnce.cpp
+++ b/devtools/shared/heapsnapshot/tests/gtest/SerializesEverythingInHeapGraphOnce.cpp
@@ -21,17 +21,17 @@ DEF_TEST(SerializesEverythingInHeapGraph
     ::testing::NiceMock<MockWriter> writer;
 
     // Should serialize each node once.
     ExpectWriteNode(writer, nodeA);
     ExpectWriteNode(writer, nodeB);
     ExpectWriteNode(writer, nodeC);
     ExpectWriteNode(writer, nodeD);
 
-    JS::AutoCheckCannotGC noGC(rt);
+    JS::AutoCheckCannotGC noGC(cx);
 
     ASSERT_TRUE(WriteHeapGraph(cx,
                                JS::ubi::Node(&nodeA),
                                writer,
                                /* wantNames = */ false,
                                /* zones = */ nullptr,
                                noGC));
   });
--- a/devtools/shared/heapsnapshot/tests/gtest/SerializesTypeNames.cpp
+++ b/devtools/shared/heapsnapshot/tests/gtest/SerializesTypeNames.cpp
@@ -15,16 +15,16 @@ DEF_TEST(SerializesTypeNames, {
 
     ::testing::NiceMock<MockWriter> writer;
     EXPECT_CALL(writer, writeNode(Property(&JS::ubi::Node::typeName,
                                            UTF16StrEq(u"FakeNode")),
                                   _))
       .Times(1)
       .WillOnce(Return(true));
 
-    JS::AutoCheckCannotGC noGC(rt);
+    JS::AutoCheckCannotGC noGC(cx);
     ASSERT_TRUE(WriteHeapGraph(cx,
                                JS::ubi::Node(&node),
                                writer,
                                /* wantNames = */ true,
                                /* zones = */ nullptr,
                                noGC));
   });
--- a/dom/asmjscache/AsmJSCache.cpp
+++ b/dom/asmjscache/AsmJSCache.cpp
@@ -1305,17 +1305,18 @@ private:
   RecvOnOpenCacheFile(const int64_t& aFileSize,
                       const FileDescriptor& aFileDesc) override
   {
     MOZ_ASSERT(NS_IsMainThread());
     MOZ_ASSERT(mState == eOpening);
 
     mFileSize = aFileSize;
 
-    mFileDesc = PR_ImportFile(PROsfd(aFileDesc.PlatformHandle()));
+    auto rawFD = aFileDesc.ClonePlatformHandle();
+    mFileDesc = PR_ImportFile(PROsfd(rawFD.release()));
     if (!mFileDesc) {
       return false;
     }
 
     mState = eOpened;
     Notify(JS::AsmJSCache_Success);
     return true;
   }
--- a/dom/base/BodyUtil.cpp
+++ b/dom/base/BodyUtil.cpp
@@ -319,16 +319,17 @@ private:
         File::CreateMemoryFile(mParentObject,
                                reinterpret_cast<void *>(copy), body.Length(),
                                NS_ConvertUTF8toUTF16(mFilename),
                                NS_ConvertUTF8toUTF16(mContentType), /* aLastModifiedDate */ 0);
       Optional<nsAString> dummy;
       ErrorResult rv;
       mFormData->Append(name, *file, dummy, rv);
       if (NS_WARN_IF(rv.Failed())) {
+        rv.SuppressException();
         return false;
       }
     }
 
     return true;
   }
 
 public:
--- a/dom/base/File.cpp
+++ b/dom/base/File.cpp
@@ -919,17 +919,19 @@ BlobImplFile::GetType(nsAString& aType)
         return;
       }
 
       RefPtr<GetTypeRunnable> runnable =
         new GetTypeRunnable(workerPrivate, this);
 
       ErrorResult rv;
       runnable->Dispatch(rv);
-      NS_WARN_IF(rv.Failed());
+      if (NS_WARN_IF(rv.Failed())) {
+        rv.SuppressException();
+      }
       return;
     }
 
     nsresult rv;
     nsCOMPtr<nsIMIMEService> mimeService =
       do_GetService(NS_MIMESERVICE_CONTRACTID, &rv);
     if (NS_WARN_IF(NS_FAILED(rv))) {
       return;
--- a/dom/base/Navigator.cpp
+++ b/dom/base/Navigator.cpp
@@ -2581,67 +2581,96 @@ Navigator::GetUserAgent(nsPIDOMWindowInn
 
   return siteSpecificUA->GetUserAgentForURIAndWindow(aURI, aWindow, aUserAgent);
 }
 
 #ifdef MOZ_EME
 static nsCString
 ToCString(const nsString& aString)
 {
-  return NS_ConvertUTF16toUTF8(aString);
+  nsCString str("'");
+  str.Append(NS_ConvertUTF16toUTF8(aString));
+  str.AppendLiteral("'");
+  return str;
+}
+
+static nsCString
+ToCString(const MediaKeysRequirement aValue)
+{
+  nsCString str("'");
+  str.Append(nsDependentCString(MediaKeysRequirementValues::strings[static_cast<uint32_t>(aValue)].value));
+  str.AppendLiteral("'");
+  return str;
 }
 
 static nsCString
 ToCString(const MediaKeySystemMediaCapability& aValue)
 {
   nsCString str;
-  str.AppendLiteral("{contentType='");
-  if (!aValue.mContentType.IsEmpty()) {
-    str.Append(ToCString(aValue.mContentType));
-  }
-  str.AppendLiteral("'}");
+  str.AppendLiteral("{contentType=");
+  str.Append(ToCString(aValue.mContentType));
+  str.AppendLiteral(", robustness=");
+  str.Append(ToCString(aValue.mRobustness));
+  str.AppendLiteral("}");
   return str;
 }
 
 template<class Type>
 static nsCString
 ToCString(const Sequence<Type>& aSequence)
 {
-  nsCString s;
-  s.AppendLiteral("[");
+  nsCString str;
+  str.AppendLiteral("[");
   for (size_t i = 0; i < aSequence.Length(); i++) {
     if (i != 0) {
-      s.AppendLiteral(",");
+      str.AppendLiteral(",");
     }
-    s.Append(ToCString(aSequence[i]));
+    str.Append(ToCString(aSequence[i]));
   }
-  s.AppendLiteral("]");
-  return s;
+  str.AppendLiteral("]");
+  return str;
+}
+
+template<class Type>
+static nsCString
+ToCString(const Optional<Sequence<Type>>& aOptional)
+{
+  nsCString str;
+  if (aOptional.WasPassed()) {
+    str.Append(ToCString(aOptional.Value()));
+  } else {
+    str.AppendLiteral("[]");
+  }
+  return str;
 }
 
 static nsCString
 ToCString(const MediaKeySystemConfiguration& aConfig)
 {
   nsCString str;
-  str.AppendLiteral("{");
-  str.AppendPrintf("label='%s'", NS_ConvertUTF16toUTF8(aConfig.mLabel).get());
-
-  if (aConfig.mInitDataTypes.WasPassed()) {
-    str.AppendLiteral(", initDataTypes=");
-    str.Append(ToCString(aConfig.mInitDataTypes.Value()));
-  }
-
-  if (aConfig.mAudioCapabilities.WasPassed()) {
-    str.AppendLiteral(", audioCapabilities=");
-    str.Append(ToCString(aConfig.mAudioCapabilities.Value()));
-  }
-  if (aConfig.mVideoCapabilities.WasPassed()) {
-    str.AppendLiteral(", videoCapabilities=");
-    str.Append(ToCString(aConfig.mVideoCapabilities.Value()));
-  }
+  str.AppendLiteral("{label=");
+  str.Append(ToCString(aConfig.mLabel));
+
+  str.AppendLiteral(", initDataTypes=");
+  str.Append(ToCString(aConfig.mInitDataTypes));
+
+  str.AppendLiteral(", audioCapabilities=");
+  str.Append(ToCString(aConfig.mAudioCapabilities));
+
+  str.AppendLiteral(", videoCapabilities=");
+  str.Append(ToCString(aConfig.mVideoCapabilities));
+
+  str.AppendLiteral(", distinctiveIdentifier=");
+  str.Append(ToCString(aConfig.mDistinctiveIdentifier));
+
+  str.AppendLiteral(", persistentState=");
+  str.Append(ToCString(aConfig.mPersistentState));
+
+  str.AppendLiteral(", sessionTypes=");
+  str.Append(ToCString(aConfig.mSessionTypes));
 
   str.AppendLiteral("}");
 
   return str;
 }
 
 static nsCString
 RequestKeySystemAccessLogString(const nsAString& aKeySystem,
--- a/dom/base/nsContentUtils.cpp
+++ b/dom/base/nsContentUtils.cpp
@@ -1798,19 +1798,22 @@ nsContentUtils::IsControlledByServiceWor
   }
 
   RefPtr<workers::ServiceWorkerManager> swm =
     workers::ServiceWorkerManager::GetInstance();
   MOZ_ASSERT(swm);
 
   ErrorResult rv;
   bool controlled = swm->IsControlled(aDocument, rv);
-  NS_WARN_IF(rv.Failed());
-
-  return !rv.Failed() && controlled;
+  if (NS_WARN_IF(rv.Failed())) {
+    rv.SuppressException();
+    return false;
+  }
+
+  return controlled;
 }
 
 /* static */
 void
 nsContentUtils::GetOfflineAppManifest(nsIDocument *aDocument, nsIURI **aURI)
 {
   MOZ_ASSERT(NS_IsMainThread());
   MOZ_ASSERT(aDocument);
--- a/dom/base/nsDocument.cpp
+++ b/dom/base/nsDocument.cpp
@@ -1737,20 +1737,16 @@ nsDocument::DeleteCycleCollectable()
 }
 
 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_BEGIN(nsDocument)
   if (Element::CanSkip(tmp, aRemovingAllowed)) {
     EventListenerManager* elm = tmp->GetExistingListenerManager();
     if (elm) {
       elm->MarkForCC();
     }
-    if (tmp->mExpandoAndGeneration.expando.isObject()) {
-      JS::ExposeObjectToActiveJS(
-        &(tmp->mExpandoAndGeneration.expando.toObject()));
-    }
     return true;
   }
 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_END
 
 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_IN_CC_BEGIN(nsDocument)
   return Element::CanSkipInCC(tmp);
 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_IN_CC_END
 
@@ -1926,23 +1922,17 @@ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_
       NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mDOMMediaQueryLists item");
       cb.NoteXPCOMChild(mql);
     }
   }
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
 
 NS_IMPL_CYCLE_COLLECTION_CLASS(nsDocument)
 
-NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(nsDocument)
-  if (tmp->PreservingWrapper()) {
-    NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mExpandoAndGeneration.expando)
-  }
-  NS_IMPL_CYCLE_COLLECTION_TRACE_PRESERVED_WRAPPER
-NS_IMPL_CYCLE_COLLECTION_TRACE_END
-
+NS_IMPL_CYCLE_COLLECTION_TRACE_WRAPPERCACHE(nsDocument)
 
 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsDocument)
   tmp->mInUnlinkOrDeletion = true;
 
   // Clear out our external resources
   tmp->mExternalResourceMap.Shutdown();
 
   nsAutoScriptBlocker scriptBlocker;
@@ -2005,17 +1995,17 @@ NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(ns
 
   tmp->mRadioGroups.Clear();
 
   // nsDocument has a pretty complex destructor, so we're going to
   // assume that *most* cycles you actually want to break somewhere
   // else, and not unlink an awful lot here.
 
   tmp->mIdentifierMap.Clear();
-  tmp->mExpandoAndGeneration.Unlink();
+  tmp->mExpandoAndGeneration.OwnerUnlinked();
 
   if (tmp->mAnimationController) {
     tmp->mAnimationController->Unlink();
   }
 
   tmp->mPendingTitleChangeEvent.Revoke();
 
   if (tmp->mCSSLoader) {
--- a/dom/base/nsFrameMessageManager.cpp
+++ b/dom/base/nsFrameMessageManager.cpp
@@ -787,16 +787,17 @@ nsFrameMessageManager::SendMessage(const
   NS_ENSURE_TRUE(dataArray, NS_ERROR_OUT_OF_MEMORY);
 
   for (uint32_t i = 0; i < len; ++i) {
     JS::Rooted<JS::Value> ret(aCx);
     ErrorResult rv;
     retval[i].Read(aCx, &ret, rv);
     if (rv.Failed()) {
       MOZ_ASSERT(false, "Unable to read structured clone in SendMessage");
+      rv.SuppressException();
       return NS_ERROR_UNEXPECTED;
     }
 
     NS_ENSURE_TRUE(JS_DefineElement(aCx, dataArray, i, ret, JSPROP_ENUMERATE),
                    NS_ERROR_OUT_OF_MEMORY);
   }
 
   aRetval.setObject(*dataArray);
--- a/dom/base/nsGkAtomList.h
+++ b/dom/base/nsGkAtomList.h
@@ -70,16 +70,17 @@ GK_ATOM(activateontab, "activateontab")
 GK_ATOM(actuate, "actuate")
 GK_ATOM(address, "address")
 GK_ATOM(after, "after")
 GK_ATOM(after_end, "after_end")
 GK_ATOM(after_start, "after_start")
 GK_ATOM(align, "align")
 GK_ATOM(alink, "alink")
 GK_ATOM(all, "all")
+GK_ATOM(allowdirs, "allowdirs")
 GK_ATOM(allowevents, "allowevents")
 GK_ATOM(allownegativeassertions, "allownegativeassertions")
 GK_ATOM(allowforms,"allow-forms")
 GK_ATOM(allowfullscreen, "allowfullscreen")
 GK_ATOM(allowmodals, "allow-modals")
 GK_ATOM(alloworientationlock,"allow-orientation-lock")
 GK_ATOM(allowpointerlock,"allow-pointer-lock")
 GK_ATOM(allowpopupstoescapesandbox,"allow-popups-to-escape-sandbox")
--- a/dom/base/nsGlobalWindow.cpp
+++ b/dom/base/nsGlobalWindow.cpp
@@ -2740,18 +2740,26 @@ nsGlobalWindow::SetNewDocument(nsIDocume
     JSCompartment *compartment = js::GetObjectCompartment(newInnerGlobal);
 #ifdef DEBUG
     bool sameOrigin = false;
     nsIPrincipal *existing =
       nsJSPrincipals::get(JS_GetCompartmentPrincipals(compartment));
     aDocument->NodePrincipal()->Equals(existing, &sameOrigin);
     MOZ_ASSERT(sameOrigin);
 #endif
-    JS_SetCompartmentPrincipals(compartment,
-                                nsJSPrincipals::get(aDocument->NodePrincipal()));
+    MOZ_ASSERT_IF(aDocument == oldDoc,
+                  xpc::GetCompartmentPrincipal(compartment) ==
+                  aDocument->NodePrincipal());
+    if (aDocument != oldDoc) {
+      JS_SetCompartmentPrincipals(compartment,
+                                  nsJSPrincipals::get(aDocument->NodePrincipal()));
+      // Make sure we clear out the old content XBL scope, so the new one will
+      // get created with a principal that subsumes our new principal.
+      xpc::ClearContentXBLScope(newInnerGlobal);
+    }
   } else {
     if (aState) {
       newInnerWindow = wsh->GetInnerWindow();
       newInnerGlobal = newInnerWindow->GetWrapperPreserveColor();
     } else {
       if (thisChrome) {
         newInnerWindow = nsGlobalChromeWindow::Create(this);
       } else if (mIsModalContentWindow) {
--- a/dom/base/nsJSEnvironment.cpp
+++ b/dom/base/nsJSEnvironment.cpp
@@ -2071,47 +2071,47 @@ NotifyGCEndRunnable::Run()
   const char16_t oomMsg[3] = { '{', '}', 0 };
   const char16_t *toSend = mMessage.get() ? mMessage.get() : oomMsg;
   observerService->NotifyObservers(nullptr, "garbage-collection-statistics", toSend);
 
   return NS_OK;
 }
 
 static void
-DOMGCSliceCallback(JSRuntime *aRt, JS::GCProgress aProgress, const JS::GCDescription &aDesc)
+DOMGCSliceCallback(JSContext* aCx, JS::GCProgress aProgress, const JS::GCDescription &aDesc)
 {
   NS_ASSERTION(NS_IsMainThread(), "GCs must run on the main thread");
 
   switch (aProgress) {
     case JS::GC_CYCLE_BEGIN: {
       // Prevent cycle collections and shrinking during incremental GC.
       sCCLockedOut = true;
       break;
     }
 
     case JS::GC_CYCLE_END: {
       PRTime delta = GetCollectionTimeDelta();
 
       if (sPostGCEventsToConsole) {
         NS_NAMED_LITERAL_STRING(kFmt, "GC(T+%.1f)[%s] ");
         nsString prefix, gcstats;
-        gcstats.Adopt(aDesc.formatSummaryMessage(aRt));
+        gcstats.Adopt(aDesc.formatSummaryMessage(aCx));
         prefix.Adopt(nsTextFormatter::smprintf(kFmt.get(),
                                                double(delta) / PR_USEC_PER_SEC,
                                                ProcessNameForCollectorLog()));
         nsString msg = prefix + gcstats;
         nsCOMPtr<nsIConsoleService> cs = do_GetService(NS_CONSOLESERVICE_CONTRACTID);
         if (cs) {
           cs->LogStringMessage(msg.get());
         }
       }
 
       if (sPostGCEventsToObserver) {
         nsString json;
-        json.Adopt(aDesc.formatJSON(aRt, PR_Now()));
+        json.Adopt(aDesc.formatJSON(aCx, PR_Now()));
         RefPtr<NotifyGCEndRunnable> notify = new NotifyGCEndRunnable(json);
         NS_DispatchToMainThread(notify);
       }
 
       sCCLockedOut = false;
       sIsCompactingOnUserInactive = false;
 
       // May need to kill the inter-slice GC timer
@@ -2162,31 +2162,31 @@ DOMGCSliceCallback(JSRuntime *aRt, JS::G
       }
 
       if (ShouldTriggerCC(nsCycleCollector_suspectedCount())) {
         nsCycleCollector_dispatchDeferredDeletion();
       }
 
       if (sPostGCEventsToConsole) {
         nsString gcstats;
-        gcstats.Adopt(aDesc.formatSliceMessage(aRt));
+        gcstats.Adopt(aDesc.formatSliceMessage(aCx));
         nsCOMPtr<nsIConsoleService> cs = do_GetService(NS_CONSOLESERVICE_CONTRACTID);
         if (cs) {
           cs->LogStringMessage(gcstats.get());
         }
       }
 
       break;
 
     default:
       MOZ_CRASH("Unexpected GCProgress value");
   }
 
   if (sPrevGCSliceCallback) {
-    (*sPrevGCSliceCallback)(aRt, aProgress, aDesc);
+    (*sPrevGCSliceCallback)(aCx, aProgress, aDesc);
   }
 
 }
 
 void
 nsJSContext::SetWindowProxy(JS::Handle<JSObject*> aWindowProxy)
 {
   mWindowProxy = aWindowProxy;
--- a/dom/base/nsPluginArray.cpp
+++ b/dom/base/nsPluginArray.cpp
@@ -3,28 +3,31 @@
 /* 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 "nsPluginArray.h"
 
 #include "mozilla/dom/PluginArrayBinding.h"
 #include "mozilla/dom/PluginBinding.h"
+#include "mozilla/dom/HiddenPluginEvent.h"
 
 #include "nsMimeTypeArray.h"
 #include "Navigator.h"
 #include "nsIDocShell.h"
 #include "nsIWebNavigation.h"
 #include "nsPluginHost.h"
 #include "nsPluginTags.h"
 #include "nsIObserverService.h"
 #include "nsIWeakReference.h"
 #include "mozilla/Services.h"
 #include "nsIInterfaceRequestorUtils.h"
 #include "nsContentUtils.h"
+#include "nsIPermissionManager.h"
+#include "nsIDocument.h"
 
 using namespace mozilla;
 using namespace mozilla::dom;
 
 nsPluginArray::nsPluginArray(nsPIDOMWindowInner* aWindow)
   : mWindow(aWindow)
 {
 }
@@ -68,17 +71,18 @@ NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(
   NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
   NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIObserver)
   NS_INTERFACE_MAP_ENTRY(nsIObserver)
   NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
 NS_INTERFACE_MAP_END
 
 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(nsPluginArray,
                                       mWindow,
-                                      mPlugins)
+                                      mPlugins,
+                                      mCTPPlugins)
 
 static void
 GetPluginMimeTypes(const nsTArray<RefPtr<nsPluginElement> >& aPlugins,
                    nsTArray<RefPtr<nsMimeType> >& aMimeTypes)
 {
   for (uint32_t i = 0; i < aPlugins.Length(); ++i) {
     nsPluginElement *plugin = aPlugins[i];
     aMimeTypes.AppendElements(plugin->MimeTypes());
@@ -148,16 +152,17 @@ nsPluginArray::Refresh(bool aReloadDocum
     // the both arrays contain the same plugin tags (though as
     // different types).
     if (newPluginTags.Length() == mPlugins.Length()) {
       return;
     }
   }
 
   mPlugins.Clear();
+  mCTPPlugins.Clear();
 
   nsCOMPtr<nsIDOMNavigator> navigator = mWindow->GetNavigator();
 
   if (!navigator) {
     return;
   }
 
   static_cast<mozilla::dom::Navigator*>(navigator.get())->RefreshMIMEArray();
@@ -223,16 +228,31 @@ nsPluginArray::NamedGetter(const nsAStri
   if (!AllowPlugins() || ResistFingerprinting()) {
     return nullptr;
   }
 
   EnsurePlugins();
 
   nsPluginElement* plugin = FindPlugin(mPlugins, aName);
   aFound = (plugin != nullptr);
+  if (!aFound) {
+    nsPluginElement* hiddenPlugin = FindPlugin(mCTPPlugins, aName);
+    if (hiddenPlugin) {
+      HiddenPluginEventInit init;
+      init.mTag = hiddenPlugin->PluginTag();
+      nsCOMPtr<nsIDocument> doc = hiddenPlugin->GetParentObject()->GetDoc();
+      RefPtr<HiddenPluginEvent> event =
+        HiddenPluginEvent::Constructor(doc, NS_LITERAL_STRING("HiddenPlugin"), init);
+      event->SetTarget(doc);
+      event->SetTrusted(true);
+      event->WidgetEventPtr()->mFlags.mOnlyChromeDispatch = true;
+      bool dummy;
+      doc->DispatchEvent(event, &dummy);
+    }
+  }
   return plugin;
 }
 
 uint32_t
 nsPluginArray::Length()
 {
   if (!AllowPlugins() || ResistFingerprinting()) {
     return 0;
@@ -284,34 +304,58 @@ operator<(const RefPtr<nsPluginElement>&
 {
   // Sort plugins alphabetically by name.
   return lhs->PluginTag()->Name() < rhs->PluginTag()->Name();
 }
 
 void
 nsPluginArray::EnsurePlugins()
 {
-  if (!mPlugins.IsEmpty()) {
+  if (!mPlugins.IsEmpty() || !mCTPPlugins.IsEmpty()) {
     // We already have an array of plugin elements.
     return;
   }
 
   RefPtr<nsPluginHost> pluginHost = nsPluginHost::GetInst();
   if (!pluginHost) {
     // We have no plugin host.
     return;
   }
 
   nsTArray<nsCOMPtr<nsIInternalPluginTag> > pluginTags;
   pluginHost->GetPlugins(pluginTags);
 
   // need to wrap each of these with a nsPluginElement, which is
   // scriptable.
   for (uint32_t i = 0; i < pluginTags.Length(); ++i) {
-    mPlugins.AppendElement(new nsPluginElement(mWindow, pluginTags[i]));
+    nsCOMPtr<nsPluginTag> pluginTag = do_QueryInterface(pluginTags[i]);
+    if (!pluginTag) {
+      mPlugins.AppendElement(new nsPluginElement(mWindow, pluginTags[i]));
+    } else if (pluginTag->IsActive()) {
+      uint32_t permission = nsIPermissionManager::ALLOW_ACTION;
+      if (pluginTag->IsClicktoplay()) {
+        nsCString name;
+        pluginTag->GetName(name);
+        if (NS_LITERAL_CSTRING("Shockwave Flash").Equals(name)) {
+          RefPtr<nsPluginHost> pluginHost = nsPluginHost::GetInst();
+          nsCString permString;
+          nsresult rv = pluginHost->GetPermissionStringForTag(pluginTag, 0, permString);
+          if (rv == NS_OK) {
+            nsIPrincipal* principal = mWindow->GetExtantDoc()->NodePrincipal();
+            nsCOMPtr<nsIPermissionManager> permMgr = services::GetPermissionManager();
+            permMgr->TestPermissionFromPrincipal(principal, permString.get(), &permission);
+          }
+        }
+      }
+      if (permission == nsIPermissionManager::ALLOW_ACTION) {
+        mPlugins.AppendElement(new nsPluginElement(mWindow, pluginTags[i]));
+      } else {
+        mCTPPlugins.AppendElement(new nsPluginElement(mWindow, pluginTags[i]));
+      }
+    }
   }
 
   // Alphabetize the enumeration order of non-hidden plugins to reduce
   // fingerprintable entropy based on plugins' installation file times.
   mPlugins.Sort();
 }
 
 // nsPluginElement implementation.
--- a/dom/base/nsPluginArray.h
+++ b/dom/base/nsPluginArray.h
@@ -55,16 +55,20 @@ public:
 private:
   virtual ~nsPluginArray();
 
   bool AllowPlugins() const;
   void EnsurePlugins();
 
   nsCOMPtr<nsPIDOMWindowInner> mWindow;
   nsTArray<RefPtr<nsPluginElement> > mPlugins;
+  /* A separate list of click-to-play plugins that we don't tell content
+   * about but keep track of so we can still prompt the user to click to play.
+   */
+  nsTArray<RefPtr<nsPluginElement> > mCTPPlugins;
 };
 
 class nsPluginElement final : public nsISupports,
                               public nsWrapperCache
 {
 public:
   NS_DECL_CYCLE_COLLECTING_ISUPPORTS
   NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(nsPluginElement)
--- a/dom/base/test/browser_use_counters.js
+++ b/dom/base/test/browser_use_counters.js
@@ -82,17 +82,17 @@ add_task(function* () {
       resolve();
     });
   });
 });
 
 
 function waitForDestroyedDocuments() {
   let deferred = promise.defer();
-  SpecialPowers.exactGC(window, deferred.resolve);
+  SpecialPowers.exactGC(deferred.resolve);
   return deferred.promise;
 }
 
 function waitForPageLoad(browser) {
   return ContentTask.spawn(browser, null, function*() {
     Cu.import("resource://gre/modules/PromiseUtils.jsm");
     yield new Promise(resolve => {
       let listener = () => {
--- a/dom/base/test/test_navigator_language.html
+++ b/dom/base/test/test_navigator_language.html
@@ -190,17 +190,17 @@ https://bugzilla.mozilla.org/show_bug.cg
     var frame = document.createElement('iframe');
     frame.src = 'data:text/html,<script>window.onlanguagechange=function(){}<\/script>';
     document.body.appendChild(frame);
 
     frame.contentWindow.onload = function() {
       document.body.removeChild(frame);
       frame = null;
 
-      SpecialPowers.exactGC(window, function() {
+      SpecialPowers.exactGC(function() {
         // This should not crash.
         SpecialPowers.pushPrefEnv({"set": [['intl.accept_languages', 'en-GB']]}, nextTest);
       });
     }
   });
 
   // There is one test using document.body.
   addLoadEvent(function() {
--- a/dom/bindings/Bindings.conf
+++ b/dom/bindings/Bindings.conf
@@ -840,16 +840,20 @@ DOMInterfaces = {
     'headerFile' : 'nsPluginArray.h',
     'nativeType': 'nsPluginElement',
 },
 
 'PluginArray': {
     'nativeType': 'nsPluginArray',
 },
 
+'PluginTag': {
+    'nativeType': 'nsIPluginTag',
+},
+
 'PopupBoxObject': {
     'resultNotAddRefed': ['triggerNode', 'anchorNode'],
 },
 
 'Position': {
     'headerFile': 'nsGeoPosition.h'
 },
 
--- a/dom/bindings/Codegen.py
+++ b/dom/bindings/Codegen.py
@@ -11718,18 +11718,23 @@ class CGDOMJSProxyHandler(CGClass):
              descriptor.interface.getExtendedAttribute('OverrideBuiltins'))):
             methods.append(CGDOMJSProxyHandler_setCustom(descriptor))
         if descriptor.hasNonOrdinaryGetPrototypeOf():
             methods.append(CGDOMJSProxyHandler_getPrototypeIfOrdinary())
         if descriptor.operations['LegacyCaller']:
             methods.append(CGDOMJSProxyHandler_call())
             methods.append(CGDOMJSProxyHandler_isCallable())
 
+        if descriptor.interface.getExtendedAttribute('OverrideBuiltins'):
+            parentClass = 'ShadowingDOMProxyHandler'
+        else:
+            parentClass = 'mozilla::dom::DOMProxyHandler'
+
         CGClass.__init__(self, 'DOMProxyHandler',
-                         bases=[ClassBase('mozilla::dom::DOMProxyHandler')],
+                         bases=[ClassBase(parentClass)],
                          constructors=constructors,
                          methods=methods)
 
 
 class CGDOMJSProxyHandlerDeclarer(CGThing):
     """
     A class for declaring a DOMProxyHandler.
     """
--- a/dom/bindings/DOMJSProxyHandler.cpp
+++ b/dom/bindings/DOMJSProxyHandler.cpp
@@ -292,10 +292,31 @@ DOMProxyHandler::GetExpandoObject(JSObje
   }
 
   js::ExpandoAndGeneration* expandoAndGeneration =
     static_cast<js::ExpandoAndGeneration*>(v.toPrivate());
   v = expandoAndGeneration->expando;
   return v.isUndefined() ? nullptr : &v.toObject();
 }
 
+void
+ShadowingDOMProxyHandler::trace(JSTracer* trc, JSObject* proxy) const
+{
+  DOMProxyHandler::trace(trc, proxy);
+
+  MOZ_ASSERT(IsDOMProxy(proxy), "expected a DOM proxy object");
+  JS::Value v = js::GetProxyExtra(proxy, JSPROXYSLOT_EXPANDO);
+  MOZ_ASSERT(!v.isObject(), "Should not have expando object directly!");
+
+  if (v.isUndefined()) {
+    // This can happen if we GC while creating our object, before we get a
+    // chance to set up its JSPROXYSLOT_EXPANDO slot.
+    return;
+  }
+
+  js::ExpandoAndGeneration* expandoAndGeneration =
+    static_cast<js::ExpandoAndGeneration*>(v.toPrivate());
+  JS::TraceEdge(trc, &expandoAndGeneration->expando,
+                "Shadowing DOM proxy expando");
+}
+
 } // namespace dom
 } // namespace mozilla
--- a/dom/bindings/DOMJSProxyHandler.h
+++ b/dom/bindings/DOMJSProxyHandler.h
@@ -27,17 +27,19 @@ enum {
    * The expando object is a plain JSObject whose properties correspond to
    * "expandos" (custom properties set by the script author).
    *
    * The exact value stored in the JSPROXYSLOT_EXPANDO slot depends on whether
    * the interface is annotated with the [OverrideBuiltins] extended attribute.
    *
    * If it is, the proxy is initialized with a PrivateValue, which contains a
    * pointer to a js::ExpandoAndGeneration object; this contains a pointer to
-   * the actual expando object as well as the "generation" of the object.
+   * the actual expando object as well as the "generation" of the object.  The
+   * proxy handler will trace the expando object stored in the
+   * js::ExpandoAndGeneration while the proxy itself is alive.
    *
    * If it is not, the proxy is initialized with an UndefinedValue. In
    * EnsureExpandoObject, it is set to an ObjectValue that points to the
    * expando object directly. (It is set back to an UndefinedValue only when
    * the object is about to die.)
    */
   JSPROXYSLOT_EXPANDO = 0
 };
@@ -135,16 +137,23 @@ public:
   /* GetAndClearExpandoObject does not DROP or clear the preserving wrapper flag. */
   static JSObject* GetAndClearExpandoObject(JSObject* obj);
   static JSObject* EnsureExpandoObject(JSContext* cx,
                                        JS::Handle<JSObject*> obj);
 
   static const char family;
 };
 
+// Class used by shadowing handlers (the ones that have [OverrideBuiltins].
+// This handles tracing the expando in ExpandoAndGeneration.
+class ShadowingDOMProxyHandler : public DOMProxyHandler
+{
+  virtual void trace(JSTracer* trc, JSObject* proxy) const override;
+};
+
 inline bool IsDOMProxy(JSObject *obj)
 {
     const js::Class* clasp = js::GetObjectClass(obj);
     return clasp->isProxy() &&
            js::GetProxyHandler(obj)->family() == &DOMProxyHandler::family;
 }
 
 inline const DOMProxyHandler*
--- a/dom/bindings/test/test_traceProtos.html
+++ b/dom/bindings/test/test_traceProtos.html
@@ -24,14 +24,14 @@ SimpleTest.waitForExplicitFinish();
 
 function callback() {
   new XMLHttpRequest().upload;
   ok(true, "Accessing unreferenced DOM interface objects shouldn't crash");
   SimpleTest.finish();
 }
 
 delete window.XMLHttpRequestUpload;
-SpecialPowers.exactGC(window, callback);
+SpecialPowers.exactGC(callback);
 
 </script>
 </pre>
 </body>
 </html>
--- a/dom/broadcastchannel/BroadcastChannelChild.cpp
+++ b/dom/broadcastchannel/BroadcastChannelChild.cpp
@@ -94,31 +94,32 @@ BroadcastChannelChild::RecvNotify(const 
   cloneData.UseExternalData(buffer.data, buffer.dataLength);
 
   JSContext* cx = jsapi.cx();
   JS::Rooted<JS::Value> value(cx, JS::NullValue());
   if (buffer.dataLength) {
     ErrorResult rv;
     cloneData.Read(cx, &value, rv);
     if (NS_WARN_IF(rv.Failed())) {
+      rv.SuppressException();
       return true;
     }
   }
 
   RootedDictionary<MessageEventInit> init(cx);
   init.mBubbles = false;
   init.mCancelable = false;
   init.mOrigin.Construct(mOrigin);
   init.mData = value;
 
   ErrorResult rv;
   RefPtr<MessageEvent> event =
     MessageEvent::Constructor(mBC, NS_LITERAL_STRING("message"), init, rv);
-  if (rv.Failed()) {
-    NS_WARNING("Failed to create a MessageEvent object.");
+  if (NS_WARN_IF(rv.Failed())) {
+    rv.SuppressException();
     return true;
   }
 
   event->SetTrusted(true);
 
   bool status;
   mBC->DispatchEvent(static_cast<Event*>(event.get()), &status);
 
--- a/dom/cache/test/mochitest/test_cache_orphaned_body.html
+++ b/dom/cache/test/mochitest/test_cache_orphaned_body.html
@@ -49,17 +49,17 @@ function resetStorage() {
     var request = qms.reset();
     var cb = SpecialPowers.wrapCallback(resolve);
     request.callback = cb;
   });
 }
 
 function gc() {
   return new Promise(function(resolve, reject) {
-    SpecialPowers.exactGC(window, resolve);
+    SpecialPowers.exactGC(resolve);
   });
 }
 
 SimpleTest.waitForExplicitFinish();
 SpecialPowers.pushPrefEnv({
   "set": [["dom.caches.enabled", true],
           ["dom.caches.testing.enabled", true],
           ["dom.quotaManager.testing", true]],
--- a/dom/cache/test/mochitest/test_cache_orphaned_cache.html
+++ b/dom/cache/test/mochitest/test_cache_orphaned_cache.html
@@ -49,17 +49,17 @@ function resetStorage() {
     var request = qms.reset();
     var cb = SpecialPowers.wrapCallback(resolve);
     request.callback = cb;
   });
 }
 
 function gc() {
   return new Promise(function(resolve, reject) {
-    SpecialPowers.exactGC(window, resolve);
+    SpecialPowers.exactGC(resolve);
   });
 }
 
 SimpleTest.waitForExplicitFinish();
 SpecialPowers.pushPrefEnv({
   "set": [["dom.caches.enabled", true],
           ["dom.caches.testing.enabled", true],
           ["dom.quotaManager.testing", true]],
--- a/dom/cache/test/mochitest/test_cache_shrink.html
+++ b/dom/cache/test/mochitest/test_cache_shrink.html
@@ -49,17 +49,17 @@ function resetStorage() {
     var request = qms.reset();
     var cb = SpecialPowers.wrapCallback(resolve);
     request.callback = cb;
   });
 }
 
 function gc() {
   return new Promise(function(resolve, reject) {
-    SpecialPowers.exactGC(window, resolve);
+    SpecialPowers.exactGC(resolve);
   });
 }
 
 SimpleTest.waitForExplicitFinish();
 SpecialPowers.pushPrefEnv({
   "set": [["dom.caches.enabled", true],
           ["dom.caches.testing.enabled", true],
           ["dom.quotaManager.testing", true]],
--- a/dom/camera/GonkCameraControl.cpp
+++ b/dom/camera/GonkCameraControl.cpp
@@ -1247,25 +1247,25 @@ nsGonkCameraControl::StartRecordingImpl(
   // close the file descriptor when we leave this function. Also note, that
   // since we're already off the main thread, we don't need to dispatch this.
   // We just let the CloseFileRunnable destructor do the work.
   RefPtr<CloseFileRunnable> closer;
   if (aFileDescriptor->mFileDescriptor.IsValid()) {
     closer = new CloseFileRunnable(aFileDescriptor->mFileDescriptor);
   }
   nsresult rv;
-  int fd = aFileDescriptor->mFileDescriptor.PlatformHandle();
+  auto rawFD = aFileDescriptor->mFileDescriptor.ClonePlatformHandle();
   if (aOptions) {
-    rv = SetupRecording(fd, aOptions->rotation, aOptions->maxFileSizeBytes,
+    rv = SetupRecording(rawFD.get(), aOptions->rotation, aOptions->maxFileSizeBytes,
                         aOptions->maxVideoLengthMs);
     if (NS_SUCCEEDED(rv)) {
       rv = SetupRecordingFlash(aOptions->autoEnableLowLightTorch);
     }
   } else {
-    rv = SetupRecording(fd, 0, 0, 0);
+    rv = SetupRecording(rawFD.get(), 0, 0, 0);
   }
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
 #ifdef MOZ_WIDGET_GONK
   if (mRecorder->start() != OK) {
     DOM_CAMERA_LOGE("mRecorder->start() failed\n");
--- a/dom/canvas/CanvasRenderingContext2D.cpp
+++ b/dom/canvas/CanvasRenderingContext2D.cpp
@@ -1050,19 +1050,16 @@ NS_INTERFACE_MAP_END
 // Initialize our static variables.
 uint32_t CanvasRenderingContext2D::sNumLivingContexts = 0;
 DrawTarget* CanvasRenderingContext2D::sErrorTarget = nullptr;
 
 
 
 CanvasRenderingContext2D::CanvasRenderingContext2D()
   : mRenderingMode(RenderingMode::OpenGLBackendMode)
-#ifdef USE_SKIA_GPU
-  , mVideoTexture(0)
-#endif
   // these are the default values from the Canvas spec
   , mWidth(0), mHeight(0)
   , mZero(false), mOpaque(false)
   , mResetLayer(true)
   , mIPC(false)
   , mIsSkiaGL(false)
   , mHasPendingStableStateCallback(false)
   , mDrawObserver(nullptr)
@@ -1094,25 +1091,16 @@ CanvasRenderingContext2D::~CanvasRenderi
   // Drop references from all CanvasRenderingContext2DUserData to this context
   for (uint32_t i = 0; i < mUserDatas.Length(); ++i) {
     mUserDatas[i]->Forget();
   }
   sNumLivingContexts--;
   if (!sNumLivingContexts) {
     NS_IF_RELEASE(sErrorTarget);
   }
-#ifdef USE_SKIA_GPU
-  if (mVideoTexture) {
-    SkiaGLGlue* glue = gfxPlatform::GetPlatform()->GetSkiaGLGlue();
-    MOZ_ASSERT(glue);
-    glue->GetGLContext()->MakeCurrent();
-    glue->GetGLContext()->fDeleteTextures(1, &mVideoTexture);
-  }
-#endif
-
   RemoveDemotableContext(this);
 }
 
 JSObject*
 CanvasRenderingContext2D::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
 {
   return CanvasRenderingContext2DBinding::Wrap(aCx, this, aGivenProto);
 }
@@ -1339,25 +1327,16 @@ bool CanvasRenderingContext2D::SwitchRen
   }
 
 #ifdef USE_SKIA_GPU
   // Do not attempt to switch into GL mode if the platform doesn't allow it.
   if ((aRenderingMode == RenderingMode::OpenGLBackendMode) &&
       !gfxPlatform::GetPlatform()->UseAcceleratedCanvas()) {
       return false;
   }
-
-  if (mRenderingMode == RenderingMode::OpenGLBackendMode) {
-    if (mVideoTexture) {
-      gfxPlatform::GetPlatform()->GetSkiaGLGlue()->GetGLContext()->MakeCurrent();
-      gfxPlatform::GetPlatform()->GetSkiaGLGlue()->GetGLContext()->fDeleteTextures(1, &mVideoTexture);
-    }
-    mCurrentVideoSize.width = 0;
-    mCurrentVideoSize.height = 0;
-  }
 #endif
 
   RefPtr<SourceSurface> snapshot;
   Matrix transform;
   RefPtr<PersistentBufferProvider> oldBufferProvider = mBufferProvider;
   AutoReturnSnapshot autoReturn(nullptr);
 
   if (mTarget) {
@@ -4645,56 +4624,54 @@ CanvasRenderingContext2D::DrawImage(cons
     AutoLockImage lockImage(container);
     layers::Image* srcImage = lockImage.GetImage();
     if (!srcImage) {
       aError.Throw(NS_ERROR_NOT_AVAILABLE);
       return;
     }
 
     gl->MakeCurrent();
-    if (!mVideoTexture) {
-      gl->fGenTextures(1, &mVideoTexture);
-    }
+    GLuint videoTexture = 0;
+    gl->fGenTextures(1, &videoTexture);
     // skiaGL expect upload on drawing, and uses texture 0 for texturing,
     // so we must active texture 0 and bind the texture for it.
     gl->fActiveTexture(LOCAL_GL_TEXTURE0);
-    gl->fBindTexture(LOCAL_GL_TEXTURE_2D, mVideoTexture);
-
-    bool dimensionsMatch = mCurrentVideoSize.width == srcImage->GetSize().width &&
-                           mCurrentVideoSize.height == srcImage->GetSize().height;
-    if (!dimensionsMatch) {
-      // we need to allocation
-      mCurrentVideoSize.width = srcImage->GetSize().width;
-      mCurrentVideoSize.height = srcImage->GetSize().height;
-      gl->fTexImage2D(LOCAL_GL_TEXTURE_2D, 0, LOCAL_GL_RGB, srcImage->GetSize().width, srcImage->GetSize().height, 0, LOCAL_GL_RGB, LOCAL_GL_UNSIGNED_SHORT_5_6_5, nullptr);
-      gl->fTexParameteri(LOCAL_GL_TEXTURE_2D, LOCAL_GL_TEXTURE_WRAP_S, LOCAL_GL_CLAMP_TO_EDGE);
-      gl->fTexParameteri(LOCAL_GL_TEXTURE_2D, LOCAL_GL_TEXTURE_WRAP_T, LOCAL_GL_CLAMP_TO_EDGE);
-      gl->fTexParameteri(LOCAL_GL_TEXTURE_2D, LOCAL_GL_TEXTURE_MAG_FILTER, LOCAL_GL_LINEAR);
-      gl->fTexParameteri(LOCAL_GL_TEXTURE_2D, LOCAL_GL_TEXTURE_MIN_FILTER, LOCAL_GL_LINEAR);
-    }
+    gl->fBindTexture(LOCAL_GL_TEXTURE_2D, videoTexture);
+
+    gl->fTexImage2D(LOCAL_GL_TEXTURE_2D, 0, LOCAL_GL_RGB, srcImage->GetSize().width, srcImage->GetSize().height, 0, LOCAL_GL_RGB, LOCAL_GL_UNSIGNED_SHORT_5_6_5, nullptr);
+    gl->fTexParameteri(LOCAL_GL_TEXTURE_2D, LOCAL_GL_TEXTURE_WRAP_S, LOCAL_GL_CLAMP_TO_EDGE);
+    gl->fTexParameteri(LOCAL_GL_TEXTURE_2D, LOCAL_GL_TEXTURE_WRAP_T, LOCAL_GL_CLAMP_TO_EDGE);
+    gl->fTexParameteri(LOCAL_GL_TEXTURE_2D, LOCAL_GL_TEXTURE_MAG_FILTER, LOCAL_GL_LINEAR);
+    gl->fTexParameteri(LOCAL_GL_TEXTURE_2D, LOCAL_GL_TEXTURE_MIN_FILTER, LOCAL_GL_LINEAR);
+
     const gl::OriginPos destOrigin = gl::OriginPos::TopLeft;
     bool ok = gl->BlitHelper()->BlitImageToTexture(srcImage, srcImage->GetSize(),
-                                                   mVideoTexture, LOCAL_GL_TEXTURE_2D,
+                                                   videoTexture, LOCAL_GL_TEXTURE_2D,
                                                    destOrigin);
     if (ok) {
       NativeSurface texSurf;
       texSurf.mType = NativeSurfaceType::OPENGL_TEXTURE;
       texSurf.mFormat = SurfaceFormat::R5G6B5_UINT16;
-      texSurf.mSize.width = mCurrentVideoSize.width;
-      texSurf.mSize.height = mCurrentVideoSize.height;
-      texSurf.mSurface = (void*)((uintptr_t)mVideoTexture);
+      texSurf.mSize.width = srcImage->GetSize().width;
+      texSurf.mSize.height = srcImage->GetSize().height;
+      texSurf.mSurface = (void*)((uintptr_t)videoTexture);
 
       srcSurf = mTarget->CreateSourceSurfaceFromNativeSurface(texSurf);
-      imgSize.width = mCurrentVideoSize.width;
-      imgSize.height = mCurrentVideoSize.height;
+      if (!srcSurf) {
+        gl->fDeleteTextures(1, &videoTexture);
+      }
+      imgSize.width = srcImage->GetSize().width;
+      imgSize.height = srcImage->GetSize().height;
 
       int32_t displayWidth = video->VideoWidth();
       int32_t displayHeight = video->VideoHeight();
       aSw *= (double)imgSize.width / (double)displayWidth;
       aSh *= (double)imgSize.height / (double)displayHeight;
+    } else {
+      gl->fDeleteTextures(1, &videoTexture);
     }
     srcImage = nullptr;
 
     if (mCanvasElement) {
       CanvasUtils::DoDrawImageSecurityCheck(mCanvasElement,
                                             principal, false,
                                             video->GetCORSMode() != CORS_NONE);
     }
--- a/dom/canvas/CanvasRenderingContext2D.h
+++ b/dom/canvas/CanvasRenderingContext2D.h
@@ -718,20 +718,16 @@ protected:
   static std::vector<CanvasRenderingContext2D*>& DemotableContexts();
   static void DemoteOldestContextIfNecessary();
 
   static void AddDemotableContext(CanvasRenderingContext2D* aContext);
   static void RemoveDemotableContext(CanvasRenderingContext2D* aContext);
 
   RenderingMode mRenderingMode;
 
-  // Texture informations for fast video rendering
-  unsigned int mVideoTexture;
-  nsIntSize mCurrentVideoSize;
-
   // Member vars
   int32_t mWidth, mHeight;
 
   // This is true when the canvas is valid, but of zero size, this requires
   // specific behavior on some operations.
   bool mZero;
 
   bool mOpaque;
--- a/dom/canvas/ImageBitmap.cpp
+++ b/dom/canvas/ImageBitmap.cpp
@@ -86,16 +86,17 @@ static already_AddRefed<DataSourceSurfac
 CropAndCopyDataSourceSurface(DataSourceSurface* aSurface, const IntRect& aCropRect)
 {
   MOZ_ASSERT(aSurface);
 
   // Check the aCropRect
   ErrorResult error;
   const IntRect positiveCropRect = FixUpNegativeDimension(aCropRect, error);
   if (NS_WARN_IF(error.Failed())) {
+    error.SuppressException();
     return nullptr;
   }
 
   // Calculate the size of the new SourceSurface.
   // We cannot keep using aSurface->GetFormat() to create new DataSourceSurface,
   // since it might be SurfaceFormat::B8G8R8X8 which does not handle opacity,
   // however the specification explicitly define that "If any of the pixels on
   // this rectangle are outside the area where the input bitmap was placed, then
@@ -1101,16 +1102,17 @@ AsyncFulfillImageBitmapPromise(Promise* 
 static already_AddRefed<SourceSurface>
 DecodeBlob(Blob& aBlob)
 {
   // Get the internal stream of the blob.
   nsCOMPtr<nsIInputStream> stream;
   ErrorResult error;
   aBlob.Impl()->GetInternalStream(getter_AddRefs(stream), error);
   if (NS_WARN_IF(error.Failed())) {
+    error.SuppressException();
     return nullptr;
   }
 
   // Get the MIME type string of the blob.
   // The type will be checked in the DecodeImage() method.
   nsAutoString mimeTypeUTF16;
   aBlob.GetType(mimeTypeUTF16);
 
--- a/dom/canvas/WebGL2ContextSamplers.cpp
+++ b/dom/canvas/WebGL2ContextSamplers.cpp
@@ -7,34 +7,25 @@
 #include "WebGLSampler.h"
 #include "GLContext.h"
 
 namespace mozilla {
 
 already_AddRefed<WebGLSampler>
 WebGL2Context::CreateSampler()
 {
-    const char funcName[] = "createSampler";
-
     if (IsContextLost())
         return nullptr;
 
-    /*
     GLuint sampler;
     MakeContextCurrent();
     gl->fGenSamplers(1, &sampler);
 
     RefPtr<WebGLSampler> globj = new WebGLSampler(this, sampler);
     return globj.forget();
-    */
-
-    ErrorInvalidOperation("%s: Sampler objects are still under development, and are"
-                          " currently disabled.",
-                          funcName);
-    return nullptr;
 }
 
 void
 WebGL2Context::DeleteSampler(WebGLSampler* sampler)
 {
     if (IsContextLost())
         return;
 
--- a/dom/canvas/WebGLContextGL.cpp
+++ b/dom/canvas/WebGLContextGL.cpp
@@ -771,52 +771,107 @@ WebGLContext::GetFramebufferAttachmentPa
         ErrorInvalidEnum("%s: For the default framebuffer, can only query COLOR, DEPTH,"
                          " or STENCIL.",
                          funcName);
         return JS::NullValue();
     }
 
     switch (pname) {
     case LOCAL_GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE:
+        switch (attachment) {
+        case LOCAL_GL_BACK:
+            break;
+        case LOCAL_GL_DEPTH:
+            if (!mOptions.depth) {
+              return JS::Int32Value(LOCAL_GL_NONE);
+            }
+            break;
+        case LOCAL_GL_STENCIL:
+            if (!mOptions.stencil) {
+              return JS::Int32Value(LOCAL_GL_NONE);
+            }
+            break;
+        default:
+            ErrorInvalidEnum("%s: With the default framebuffer, can only query COLOR, DEPTH,"
+                             " or STENCIL for GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE",
+                             funcName);
+            return JS::NullValue();
+        }
         return JS::Int32Value(LOCAL_GL_FRAMEBUFFER_DEFAULT);
 
     case LOCAL_GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME:
         return JS::NullValue();
 
     ////////////////
 
     case LOCAL_GL_FRAMEBUFFER_ATTACHMENT_RED_SIZE:
     case LOCAL_GL_FRAMEBUFFER_ATTACHMENT_GREEN_SIZE:
     case LOCAL_GL_FRAMEBUFFER_ATTACHMENT_BLUE_SIZE:
         if (attachment == LOCAL_GL_BACK)
             return JS::NumberValue(8);
         return JS::NumberValue(0);
 
     case LOCAL_GL_FRAMEBUFFER_ATTACHMENT_ALPHA_SIZE:
-        if (attachment == LOCAL_GL_BACK)
-            return JS::NumberValue(mOptions.alpha ? 8 : 0);
+        if (attachment == LOCAL_GL_BACK) {
+            if (mOptions.alpha) {
+                return JS::NumberValue(8);
+            }
+            ErrorInvalidOperation("The default framebuffer doesn't contain an alpha buffer");
+            return JS::NullValue();
+        }
         return JS::NumberValue(0);
 
     case LOCAL_GL_FRAMEBUFFER_ATTACHMENT_DEPTH_SIZE:
-        if (attachment == LOCAL_GL_DEPTH)
-            return JS::NumberValue(mOptions.depth ? 24 : 0);
+        if (attachment == LOCAL_GL_DEPTH) {
+            if (mOptions.depth) {
+                return JS::NumberValue(24);
+            }
+            ErrorInvalidOperation("The default framebuffer doesn't contain an depth buffer");
+            return JS::NullValue();
+        }
         return JS::NumberValue(0);
 
     case LOCAL_GL_FRAMEBUFFER_ATTACHMENT_STENCIL_SIZE:
-        if (attachment == LOCAL_GL_STENCIL)
-            return JS::NumberValue(mOptions.stencil ? 8 : 0);
+        if (attachment == LOCAL_GL_STENCIL) {
+            if (mOptions.stencil) {
+                return JS::NumberValue(8);
+            }
+            ErrorInvalidOperation("The default framebuffer doesn't contain an stencil buffer");
+            return JS::NullValue();
+        }
         return JS::NumberValue(0);
 
     case LOCAL_GL_FRAMEBUFFER_ATTACHMENT_COMPONENT_TYPE:
-        if (attachment == LOCAL_GL_STENCIL)
-            return JS::NumberValue(LOCAL_GL_UNSIGNED_INT);
-        else
+        if (attachment == LOCAL_GL_STENCIL) {
+            if (mOptions.stencil) {
+                return JS::NumberValue(LOCAL_GL_UNSIGNED_INT);
+            }
+            ErrorInvalidOperation("The default framebuffer doesn't contain an stencil buffer");
+        } else if (attachment == LOCAL_GL_DEPTH) {
+            if (mOptions.depth) {
+                return JS::NumberValue(LOCAL_GL_UNSIGNED_NORMALIZED);
+            }
+            ErrorInvalidOperation("The default framebuffer doesn't contain an depth buffer");
+        } else { // LOCAL_GL_BACK
             return JS::NumberValue(LOCAL_GL_UNSIGNED_NORMALIZED);
+        }
+        return JS::NullValue();
 
     case LOCAL_GL_FRAMEBUFFER_ATTACHMENT_COLOR_ENCODING:
+        if (attachment == LOCAL_GL_STENCIL) {
+            if (!mOptions.stencil) {
+                ErrorInvalidOperation("The default framebuffer doesn't contain an stencil buffer");
+                return JS::NullValue();
+            }
+        } else if (attachment == LOCAL_GL_DEPTH) {
+            if (!mOptions.depth) {
+                ErrorInvalidOperation("The default framebuffer doesn't contain an depth buffer");
+                return JS::NullValue();
+            }
+        }
         return JS::NumberValue(LOCAL_GL_LINEAR);
     }
 
     ErrorInvalidEnum("%s: Invalid pname: 0x%04x", funcName, pname);
     return JS::NullValue();
 }
 
 JS::Value
--- a/dom/canvas/WebGLFramebuffer.cpp
+++ b/dom/canvas/WebGLFramebuffer.cpp
@@ -523,17 +523,17 @@ WebGLFBAttachPoint::GetParameter(const c
         webgl->ErrorInvalidEnum("%s: Invalid pname: 0x%04x", funcName, pname);
         return JS::NullValue();
     }
 
     const auto usage = Format();
     if (!usage)
         return JS::NullValue();
 
-    const auto format = usage->format;
+    auto format = usage->format;
 
     GLint ret = 0;
     switch (pname) {
     case LOCAL_GL_FRAMEBUFFER_ATTACHMENT_RED_SIZE:
         ret = format->r;
         break;
     case LOCAL_GL_FRAMEBUFFER_ATTACHMENT_GREEN_SIZE:
         ret = format->g;
@@ -554,20 +554,48 @@ WebGLFBAttachPoint::GetParameter(const c
     case LOCAL_GL_FRAMEBUFFER_ATTACHMENT_COLOR_ENCODING:
         ret = (format->isSRGB ? LOCAL_GL_SRGB
                               : LOCAL_GL_LINEAR);
         break;
 
     case LOCAL_GL_FRAMEBUFFER_ATTACHMENT_COMPONENT_TYPE:
         MOZ_ASSERT(attachment != LOCAL_GL_DEPTH_STENCIL_ATTACHMENT);
 
+        if (format->componentType == webgl::ComponentType::Special) {
+            // Special format is used for DS mixed format(e.g. D24S8 and D32FS8).
+            MOZ_ASSERT(format->unsizedFormat == webgl::UnsizedFormat::DS);
+            MOZ_ASSERT(attachment == LOCAL_GL_DEPTH_ATTACHMENT ||
+                       attachment == LOCAL_GL_STENCIL_ATTACHMENT);
+
+            if (attachment == LOCAL_GL_DEPTH_ATTACHMENT) {
+                switch (format->effectiveFormat) {
+                case webgl::EffectiveFormat::DEPTH24_STENCIL8:
+                    format = webgl::GetFormat(webgl::EffectiveFormat::DEPTH_COMPONENT24);
+                    break;
+                case webgl::EffectiveFormat::DEPTH32F_STENCIL8:
+                    format = webgl::GetFormat(webgl::EffectiveFormat::DEPTH_COMPONENT32F);
+                    break;
+                default:
+                    MOZ_ASSERT(false, "no matched DS format");
+                    break;
+                }
+            } else if (attachment == LOCAL_GL_STENCIL_ATTACHMENT) {
+                switch (format->effectiveFormat) {
+                case webgl::EffectiveFormat::DEPTH24_STENCIL8:
+                case webgl::EffectiveFormat::DEPTH32F_STENCIL8:
+                    format = webgl::GetFormat(webgl::EffectiveFormat::STENCIL_INDEX8);
+                    break;
+                default:
+                    MOZ_ASSERT(false, "no matched DS format");
+                    break;
+                }
+            }
+        }
+
         switch (format->componentType) {
-        case webgl::ComponentType::Special:
-            MOZ_ASSERT(false, "Should never happen.");
-            break;
         case webgl::ComponentType::None:
             ret = LOCAL_GL_NONE;
             break;
         case webgl::ComponentType::Int:
             ret = LOCAL_GL_INT;
             break;
         case webgl::ComponentType::UInt:
             ret = LOCAL_GL_UNSIGNED_INT;
@@ -576,16 +604,19 @@ WebGLFBAttachPoint::GetParameter(const c
             ret = LOCAL_GL_SIGNED_NORMALIZED;
             break;
         case webgl::ComponentType::NormUInt:
             ret = LOCAL_GL_UNSIGNED_NORMALIZED;
             break;
         case webgl::ComponentType::Float:
             ret = LOCAL_GL_FLOAT;
             break;
+        default:
+            MOZ_ASSERT(false, "No matched component type");
+            break;
         }
         break;
 
     default:
         MOZ_ASSERT(false, "Missing case.");
         break;
     }
 
new file mode 100644
--- /dev/null
+++ b/dom/canvas/crashtests/1288872-1.html
@@ -0,0 +1,6 @@
+<canvas id='id0'></canvas>
+<script>
+var c=document.getElementById('id0').getContext('2d');
+c.transform(1,0,1,0,0,0);
+c.fillText('A',0,53);
+</script>
\ No newline at end of file
--- a/dom/canvas/crashtests/crashtests.list
+++ b/dom/canvas/crashtests/crashtests.list
@@ -25,8 +25,10 @@ load 1183363.html
 load 1190705.html
 load 1223740-1.html
 load 1225381-1.html
 skip-if(azureCairo) load 1229983-1.html
 load 1229932-1.html
 load 1244850-1.html
 load 1246775-1.html
 skip-if(d2d) load 1287515-1.html
+load 1288872-1.html
+
--- a/dom/canvas/test/webgl-conf/generated-mochitest.ini
+++ b/dom/canvas/test/webgl-conf/generated-mochitest.ini
@@ -4541,20 +4541,18 @@ skip-if = (os == 'android' || os == 'lin
 skip-if = (os == 'win' && os_version == '6.1') || (os == 'android' || os == 'linux' || (os == 'win' && os_version == '5.1') || (os == 'win' && os_version == '6.2'))
 [generated/test_2_conformance2__glsl3__texture-offset-out-of-range.html]
 skip-if = (os == 'android' || os == 'linux' || (os == 'win' && os_version == '5.1') || (os == 'win' && os_version == '6.2'))
 [generated/test_2_conformance2__glsl3__uniform-location-length-limits.html]
 skip-if = (os == 'win' && debug) || (os == 'android' || os == 'linux' || (os == 'win' && os_version == '5.1') || (os == 'win' && os_version == '6.2'))
 [generated/test_2_conformance2__glsl3__vector-dynamic-indexing.html]
 skip-if = (os == 'win') || (os == 'android' || os == 'linux' || (os == 'win' && os_version == '5.1') || (os == 'win' && os_version == '6.2'))
 [generated/test_2_conformance2__misc__expando-loss-2.html]
-fail-if = (os == 'mac') || (os == 'win')
 skip-if = (os == 'android' || os == 'linux' || (os == 'win' && os_version == '5.1') || (os == 'win' && os_version == '6.2'))
 [generated/test_2_conformance2__misc__instanceof-test.html]
-fail-if = (os == 'mac') || (os == 'win')
 skip-if = (os == 'android' || os == 'linux' || (os == 'win' && os_version == '5.1') || (os == 'win' && os_version == '6.2'))
 [generated/test_2_conformance2__misc__uninitialized-test-2.html]
 skip-if = (os == 'mac') || (os == 'win') || (os == 'android' || os == 'linux' || (os == 'win' && os_version == '5.1') || (os == 'win' && os_version == '6.2'))
 [generated/test_2_conformance2__query__occlusion-query.html]
 skip-if = (os == 'win') || (os == 'android' || os == 'linux' || (os == 'win' && os_version == '5.1') || (os == 'win' && os_version == '6.2'))
 [generated/test_2_conformance2__query__query.html]
 skip-if = (os == 'win') || (os == 'android' || os == 'linux' || (os == 'win' && os_version == '5.1') || (os == 'win' && os_version == '6.2'))
 [generated/test_2_conformance2__reading__read-pixels-from-fbo-test.html]
@@ -4581,20 +4579,18 @@ fail-if = (os == 'mac') || (os == 'win')
 skip-if = (os == 'android' || os == 'linux' || (os == 'win' && os_version == '5.1') || (os == 'win' && os_version == '6.2'))
 [generated/test_2_conformance2__rendering__element-index-uint.html]
 skip-if = (os == 'android' || os == 'linux' || (os == 'win' && os_version == '5.1') || (os == 'win' && os_version == '6.2'))
 [generated/test_2_conformance2__rendering__framebuffer-completeness-unaffected.html]
 skip-if = (os == 'android' || os == 'linux' || (os == 'win' && os_version == '5.1') || (os == 'win' && os_version == '6.2'))
 [generated/test_2_conformance2__rendering__instanced-arrays.html]
 skip-if = (os == 'android' || os == 'linux' || (os == 'win' && os_version == '5.1') || (os == 'win' && os_version == '6.2'))
 [generated/test_2_conformance2__samplers__sampler-drawing-test.html]
-fail-if = (os == 'mac') || (os == 'win')
 skip-if = (os == 'android' || os == 'linux' || (os == 'win' && os_version == '5.1') || (os == 'win' && os_version == '6.2'))
 [generated/test_2_conformance2__samplers__samplers.html]
-fail-if = (os == 'mac') || (os == 'win')
 skip-if = (os == 'android' || os == 'linux' || (os == 'win' && os_version == '5.1') || (os == 'win' && os_version == '6.2'))
 [generated/test_2_conformance2__state__gl-enum-tests.html]
 skip-if = (os == 'android' || os == 'linux' || (os == 'win' && os_version == '5.1') || (os == 'win' && os_version == '6.2'))
 [generated/test_2_conformance2__state__gl-get-calls.html]
 fail-if = (os == 'mac')
 skip-if = (os == 'android' || os == 'linux' || (os == 'win' && os_version == '5.1') || (os == 'win' && os_version == '6.2'))
 [generated/test_2_conformance2__state__gl-getstring.html]
 skip-if = (os == 'android' || os == 'linux' || (os == 'win' && os_version == '5.1') || (os == 'win' && os_version == '6.2'))
--- a/dom/canvas/test/webgl-conf/mochitest-errata.ini
+++ b/dom/canvas/test/webgl-conf/mochitest-errata.ini
@@ -128,28 +128,22 @@ fail-if = (os == 'win') || (os == 'mac')
 # application crashed [@ mozilla::gl::GLContext::AfterGLCall]
 skip-if = (os == 'android') || (os == 'win')
 
 [generated/test_2_conformance__textures__misc__cube-incomplete-fbo.html]
 fail-if = (os == 'mac')
 skip-if = (os == 'win')
 [generated/test_2_conformance__extensions__webgl-compressed-texture-s3tc.html]
 fail-if = (os == 'mac') || (os == 'win')
-[generated/test_2_conformance2__misc__instanceof-test.html]
-fail-if = (os == 'mac') || (os == 'win')
 [generated/test_2_conformance__textures__misc__tex-image-with-invalid-data.html]
 fail-if = (os == 'mac') || (os == 'win')
-[generated/test_2_conformance2__samplers__sampler-drawing-test.html]
-fail-if = (os == 'mac') || (os == 'win')
 [generated/test_2_conformance2__buffers__buffer-type-restrictions.html]
 fail-if = (os == 'mac') || (os == 'win')
 [generated/test_2_conformance2__rendering__draw-buffers.html]
 fail-if = (os == 'mac') || (os == 'win')
-[generated/test_2_conformance2__samplers__samplers.html]
-fail-if = (os == 'mac') || (os == 'win')
 [generated/test_2_conformance__textures__misc__tex-image-with-format-and-type.html]
 fail-if = (os == 'mac')
 [generated/test_2_conformance__attribs__gl-vertexattribpointer.html]
 fail-if = (os == 'mac') || (os == 'win')
 [generated/test_2_conformance2__buffers__buffer-copying-restrictions.html]
 fail-if = (os == 'mac') || (os == 'win')
 [generated/test_2_conformance2__glsl3__forbidden-operators.html]
 fail-if = (os == 'mac') || (os == 'win')
@@ -164,18 +158,16 @@ fail-if = (os == 'mac') || (os == 'win')
 [generated/test_2_conformance2__buffers__buffer-copying-contents.html]
 fail-if = (os == 'mac') || (os == 'win')
 [generated/test_2_conformance2__reading__read-pixels-pack-parameters.html]
 fail-if = (os == 'mac') || (os == 'win')
 [generated/test_conformance__attribs__gl-vertexattribpointer.html]
 fail-if = (os == 'mac') || (os == 'win') || (os == 'android') || (os == 'linux')
 [generated/test_2_conformance__textures__misc__tex-sub-image-2d-bad-args.html]
 fail-if = (os == 'mac') || (os == 'win')
-[generated/test_2_conformance2__misc__expando-loss-2.html]
-fail-if = (os == 'mac') || (os == 'win')
 [generated/test_conformance__ogles__GL__biuDepthRange__biuDepthRange_001_to_002.html]
 fail-if = (os == 'android') || (os == 'linux')
 [generated/test_conformance__ogles__GL__gl_FragCoord__gl_FragCoord_001_to_003.html]
 fail-if = (os == 'android') || (os == 'linux')
 
 [generated/test_conformance__textures__misc__texture-size-limit.html]
 fail-if = (os == 'linux') || (os == 'android')
 skip-if = (os == 'linux' && asan)
--- a/dom/devicestorage/nsDeviceStorage.cpp
+++ b/dom/devicestorage/nsDeviceStorage.cpp
@@ -1953,16 +1953,17 @@ public:
   }
 
   NS_IMETHOD Run() override
   {
     ErrorResult rv;
     nsCOMPtr<nsIInputStream> stream;
     mBlob->GetInternalStream(getter_AddRefs(stream), rv);
     if (NS_WARN_IF(rv.Failed())) {
+      rv.SuppressException();
       return Reject(POST_ERROR_EVENT_UNKNOWN);
     }
 
     bool check = false;
     mFile->mFile->Exists(&check);
     if (check) {
       return Reject(POST_ERROR_EVENT_FILE_EXISTS);
     }
@@ -2065,16 +2066,17 @@ public:
   }
 
   NS_IMETHOD Run() override
   {
     ErrorResult rv;
     nsCOMPtr<nsIInputStream> stream;
     mBlob->GetInternalStream(getter_AddRefs(stream), rv);
     if (NS_WARN_IF(rv.Failed())) {
+      rv.SuppressException();
       return Reject(POST_ERROR_EVENT_UNKNOWN);
     }
 
     bool check = false;
     mFile->mFile->Exists(&check);
     if (!check) {
       return Reject(POST_ERROR_EVENT_FILE_DOES_NOT_EXIST);
     }
--- a/dom/devicestorage/nsDeviceStorage.h
+++ b/dom/devicestorage/nsDeviceStorage.h
@@ -4,17 +4,16 @@
  * 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 nsDeviceStorage_h
 #define nsDeviceStorage_h
 
 #include "mozilla/Atomics.h"
 #include "mozilla/Attributes.h"
-#include "mozilla/Logging.h"
 #include "mozilla/dom/devicestorage/DeviceStorageRequestChild.h"
 
 #include "DOMRequest.h"
 #include "DOMCursor.h"
 #include "nsCycleCollectionParticipant.h"
 #include "nsDOMClassInfoID.h"
 #include "nsIClassInfo.h"
 #include "nsIDOMWindow.h"
--- a/dom/events/BeforeAfterKeyboardEvent.cpp
+++ b/dom/events/BeforeAfterKeyboardEvent.cpp
@@ -36,17 +36,19 @@ BeforeAfterKeyboardEvent::Constructor(
                             EventTarget* aOwner,
                             const nsAString& aType,
                             const BeforeAfterKeyboardEventInit& aParam)
 {
   RefPtr<BeforeAfterKeyboardEvent> event =
     new BeforeAfterKeyboardEvent(aOwner, nullptr, nullptr);
   ErrorResult rv;
   event->InitWithKeyboardEventInit(aOwner, aType, aParam, rv);
-  NS_WARN_IF(rv.Failed());
+  if (NS_WARN_IF(rv.Failed())) {
+    rv.SuppressException();
+  }
 
   event->mEvent->AsBeforeAfterKeyboardEvent()->mEmbeddedCancelled =
     aParam.mEmbeddedCancelled;
 
   return event.forget();
 }
 
 // static
--- a/dom/events/DataTransfer.cpp
+++ b/dom/events/DataTransfer.cpp
@@ -1281,17 +1281,19 @@ DataTransfer::SetDataWithPrincipalFromOt
   } else {
     nsAutoString format;
     GetRealFormat(aFormat, format);
 
     ErrorResult rv;
     RefPtr<DataTransferItem> item =
       mItems->SetDataWithPrincipal(format, aData, aIndex, aPrincipal,
                                    /* aInsertOnly = */ false, aHidden, rv);
-    NS_WARN_IF(rv.Failed());
+    if (NS_WARN_IF(rv.Failed())) {
+      rv.SuppressException();
+    }
   }
 }
 
 void
 DataTransfer::GetRealFormat(const nsAString& aInFormat,
                             nsAString& aOutFormat) const
 {
   // treat text/unicode as equivalent to text/plain
--- a/dom/filehandle/ActorsParent.cpp
+++ b/dom/filehandle/ActorsParent.cpp
@@ -2531,16 +2531,17 @@ WriteOp::Init(FileHandle* aFileHandle)
 
       auto blobActor = static_cast<BlobParent*>(blobData.blobParent());
 
       RefPtr<BlobImpl> blobImpl = blobActor->GetBlobImpl();
 
       ErrorResult rv;
       blobImpl->GetInternalStream(getter_AddRefs(inputStream), rv);
       if (NS_WARN_IF(rv.Failed())) {
+        rv.SuppressException();
         return false;
       }
 
       break;
     }
 
     default:
       MOZ_CRASH("Should never get here!");
--- a/dom/filesystem/FileSystemBase.cpp
+++ b/dom/filesystem/FileSystemBase.cpp
@@ -79,22 +79,24 @@ FileSystemBase::GetRealPath(BlobImpl* aF
   AssertIsOnOwningThread();
   MOZ_ASSERT(aFile, "aFile Should not be null.");
   MOZ_ASSERT(aPath);
 
   nsAutoString filePath;
   ErrorResult rv;
   aFile->GetMozFullPathInternal(filePath, rv);
   if (NS_WARN_IF(rv.Failed())) {
+    rv.SuppressException();
     return false;
   }
 
   rv = NS_NewNativeLocalFile(NS_ConvertUTF16toUTF8(filePath),
                              true, aPath);
   if (NS_WARN_IF(rv.Failed())) {
+    rv.SuppressException();
     return false;
   }
 
   return true;
 }
 
 bool
 FileSystemBase::IsSafeFile(nsIFile* aFile) const
--- a/dom/filesystem/FileSystemRequestParent.cpp
+++ b/dom/filesystem/FileSystemRequestParent.cpp
@@ -30,16 +30,17 @@ FileSystemRequestParent::~FileSystemRequ
 
 #define FILESYSTEM_REQUEST_PARENT_DISPATCH_ENTRY(name)                         \
     case FileSystemParams::TFileSystem##name##Params: {                        \
       const FileSystem##name##Params& p = aParams;                             \
       mFileSystem = FileSystemBase::DeserializeDOMPath(p.filesystem());        \
       MOZ_ASSERT(mFileSystem);                                                 \
       mTask = name##TaskParent::Create(mFileSystem, p, this, rv);              \
       if (NS_WARN_IF(rv.Failed())) {                                           \
+        rv.SuppressException();                                                \
         return false;                                                          \
       }                                                                        \
       break;                                                                   \
     }
 
 bool
 FileSystemRequestParent::Initialize(const FileSystemParams& aParams)
 {
--- a/dom/filesystem/FileSystemTaskBase.cpp
+++ b/dom/filesystem/FileSystemTaskBase.cpp
@@ -145,16 +145,17 @@ FileSystemTaskChildBase::Start()
   }
 
   nsAutoString serialization;
   mFileSystem->SerializeDOMPath(serialization);
 
   ErrorResult rv;
   FileSystemParams params = GetRequestParams(serialization, rv);
   if (NS_WARN_IF(rv.Failed())) {
+    rv.SuppressException();
     return;
   }
 
   // Retain a reference so the task object isn't deleted without IPDL's
   // knowledge. The reference will be released by
   // mozilla::ipc::BackgroundChildImpl::DeallocPFileSystemRequestChild.
   NS_ADDREF_THIS();
 
--- a/dom/filesystem/GetDirectoryListingTask.cpp
+++ b/dom/filesystem/GetDirectoryListingTask.cpp
@@ -360,20 +360,19 @@ GetDirectoryListingTaskParent::IOWork()
     nsCOMPtr<nsISupports> supp;
     if (NS_WARN_IF(NS_FAILED(entries->GetNext(getter_AddRefs(supp))))) {
       break;
     }
 
     nsCOMPtr<nsIFile> currFile = do_QueryInterface(supp);
     MOZ_ASSERT(currFile);
 
-    bool isLink, isSpecial, isFile;
-    if (NS_WARN_IF(NS_FAILED(currFile->IsSymlink(&isLink)) ||
-                   NS_FAILED(currFile->IsSpecial(&isSpecial))) ||
-        isLink || isSpecial) {
+    bool isSpecial, isFile;
+    if (NS_WARN_IF(NS_FAILED(currFile->IsSpecial(&isSpecial))) ||
+        isSpecial) {
       continue;
     }
     if (NS_WARN_IF(NS_FAILED(currFile->IsFile(&isFile)) ||
                    NS_FAILED(currFile->IsDirectory(&isDir))) ||
         !(isFile || isDir)) {
       continue;
     }
 
--- a/dom/filesystem/GetFilesHelper.cpp
+++ b/dom/filesystem/GetFilesHelper.cpp
@@ -67,18 +67,18 @@ GetFilesHelper::Create(nsIGlobalObject* 
   if (NS_WARN_IF(aRv.Failed())) {
     return nullptr;
   }
 
   return helper.forget();
 }
 
 GetFilesHelper::GetFilesHelper(nsIGlobalObject* aGlobal, bool aRecursiveFlag)
-  : mGlobal(aGlobal)
-  , mRecursiveFlag(aRecursiveFlag)
+  : GetFilesHelperBase(aRecursiveFlag)
+  , mGlobal(aGlobal)
   , mListingCompleted(false)
   , mErrorResult(NS_OK)
   , mMutex("GetFilesHelper::mMutex")
   , mCanceled(false)
 {
 }
 
 void
@@ -254,28 +254,33 @@ GetFilesHelper::RunMainThread()
       mErrorResult = NS_ERROR_OUT_OF_MEMORY;
       mFiles.Clear();
       return;
     }
   }
 }
 
 nsresult
-GetFilesHelper::ExploreDirectory(const nsAString& aDOMPath, nsIFile* aFile)
+GetFilesHelperBase::ExploreDirectory(const nsAString& aDOMPath, nsIFile* aFile)
 {
   MOZ_ASSERT(!NS_IsMainThread());
   MOZ_ASSERT(aFile);
 
   // We check if this operation has to be terminated at each recursion.
   if (IsCanceled()) {
     return NS_OK;
   }
 
+  nsresult rv = AddExploredDirectory(aFile);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
   nsCOMPtr<nsISimpleEnumerator> entries;
-  nsresult rv = aFile->GetDirectoryEntries(getter_AddRefs(entries));
+  rv = aFile->GetDirectoryEntries(getter_AddRefs(entries));
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
   for (;;) {
     bool hasMore = false;
     if (NS_WARN_IF(NS_FAILED(entries->HasMoreElements(&hasMore))) || !hasMore) {
       break;
@@ -287,26 +292,31 @@ GetFilesHelper::ExploreDirectory(const n
     }
 
     nsCOMPtr<nsIFile> currFile = do_QueryInterface(supp);
     MOZ_ASSERT(currFile);
 
     bool isLink, isSpecial, isFile, isDir;
     if (NS_WARN_IF(NS_FAILED(currFile->IsSymlink(&isLink)) ||
                    NS_FAILED(currFile->IsSpecial(&isSpecial))) ||
-        isLink || isSpecial) {
+        isSpecial) {
       continue;
     }
 
     if (NS_WARN_IF(NS_FAILED(currFile->IsFile(&isFile)) ||
                    NS_FAILED(currFile->IsDirectory(&isDir))) ||
         !(isFile || isDir)) {
       continue;
     }
 
+    // We don't want to explore loops of links.
+    if (isDir && isLink && !ShouldFollowSymLink(currFile)) {
+      continue;
+    }
+
     // The new domPath
     nsAutoString domPath;
     domPath.Assign(aDOMPath);
     if (!aDOMPath.EqualsLiteral(FILESYSTEM_DOM_PATH_SEPARATOR_LITERAL)) {
       domPath.AppendLiteral(FILESYSTEM_DOM_PATH_SEPARATOR_LITERAL);
     }
 
     nsAutoString leafName;
@@ -339,16 +349,79 @@ GetFilesHelper::ExploreDirectory(const n
     if (NS_WARN_IF(NS_FAILED(rv))) {
       return rv;
     }
   }
 
   return NS_OK;
 }
 
+nsresult
+GetFilesHelperBase::AddExploredDirectory(nsIFile* aDir)
+{
+  nsresult rv;
+
+#ifdef DEBUG
+  bool isDir;
+  rv = aDir->IsDirectory(&isDir);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  MOZ_ASSERT(isDir, "Why are we here?");
+#endif
+
+  bool isLink;
+  rv = aDir->IsSymlink(&isLink);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  nsAutoCString path;
+
+  if (!isLink) {
+    nsAutoString path16;
+    rv = aDir->GetPath(path16);
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return rv;
+    }
+
+    path = NS_ConvertUTF16toUTF8(path16);
+  } else {
+    rv = aDir->GetNativeTarget(path);
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return rv;
+    }
+  }
+
+  mExploredDirectories.PutEntry(path);
+  return NS_OK;
+}
+
+bool
+GetFilesHelperBase::ShouldFollowSymLink(nsIFile* aDir)
+{
+#ifdef DEBUG
+  bool isLink, isDir;
+  if (NS_WARN_IF(NS_FAILED(aDir->IsSymlink(&isLink)) ||
+                 NS_FAILED(aDir->IsDirectory(&isDir)))) {
+    return false;
+  }
+
+  MOZ_ASSERT(isLink && isDir, "Why are we here?");
+#endif
+
+  nsAutoCString targetPath;
+  if (NS_WARN_IF(NS_FAILED(aDir->GetNativeTarget(targetPath)))) {
+    return false;
+  }
+
+  return !mExploredDirectories.Contains(targetPath);
+}
+
 void
 GetFilesHelper::ResolveOrRejectPromise(Promise* aPromise)
 {
   MOZ_ASSERT(NS_IsMainThread());
   MOZ_ASSERT(mListingCompleted);
   MOZ_ASSERT(aPromise);
 
   // Error propagation.
--- a/dom/filesystem/GetFilesHelper.h
+++ b/dom/filesystem/GetFilesHelper.h
@@ -6,16 +6,17 @@
 
 #ifndef mozilla_dom_GetFilesHelper_h
 #define mozilla_dom_GetFilesHelper_h
 
 #include "mozilla/Mutex.h"
 #include "mozilla/RefPtr.h"
 #include "nsCycleCollectionTraversalCallback.h"
 #include "nsTArray.h"
+#include "nsTHashtable.h"
 
 class nsIGlobalObject;
 
 namespace mozilla {
 namespace dom {
 
 class BlobImpl;
 class ContentParent;
@@ -31,19 +32,56 @@ public:
 
   virtual void
   Callback(nsresult aStatus, const Sequence<RefPtr<File>>& aFiles) = 0;
 
 protected:
   virtual ~GetFilesCallback() {}
 };
 
+class GetFilesHelperBase
+{
+protected:
+  explicit GetFilesHelperBase(bool aRecursiveFlag)
+    : mRecursiveFlag(aRecursiveFlag)
+  {}
+
+  virtual ~GetFilesHelperBase() {}
+
+  virtual bool
+  IsCanceled()
+  {
+    return false;
+  }
+
+  nsresult
+  ExploreDirectory(const nsAString& aDOMPath, nsIFile* aFile);
+
+  nsresult
+  AddExploredDirectory(nsIFile* aDirectory);
+
+  bool
+  ShouldFollowSymLink(nsIFile* aDirectory);
+
+  bool mRecursiveFlag;
+
+  // We populate this array in the I/O thread with the paths of the Files that
+  // we want to send as result to the promise objects.
+  struct FileData {
+    nsString mDomPath;
+    nsString mRealPath;
+  };
+  FallibleTArray<FileData> mTargetPathArray;
+  nsTHashtable<nsCStringHashKey> mExploredDirectories;
+};
+
 // Retrieving the list of files can be very time/IO consuming. We use this
 // helper class to do it just once.
 class GetFilesHelper : public Runnable
+                     , public GetFilesHelperBase
 {
   friend class GetFilesHelperParent;
 
 public:
   static already_AddRefed<GetFilesHelper>
   Create(nsIGlobalObject* aGlobal,
          const nsTArray<OwningFileOrDirectory>& aFilesOrDirectory,
          bool aRecursiveFlag, ErrorResult& aRv);
@@ -64,18 +102,18 @@ protected:
   virtual ~GetFilesHelper() {}
 
   void
   SetDirectoryPath(const nsAString& aDirectoryPath)
   {
     mDirectoryPath = aDirectoryPath;
   }
 
-  bool
-  IsCanceled()
+  virtual bool
+  IsCanceled() override
   {
     MutexAutoLock lock(mMutex);
     return mCanceled;
   }
 
   virtual void
   Work(ErrorResult& aRv);
 
@@ -89,38 +127,27 @@ protected:
   RunIO();
 
   void
   RunMainThread();
 
   void
   OperationCompleted();
 
-  nsresult
-  ExploreDirectory(const nsAString& aDOMPath, nsIFile* aFile);
   void
   ResolveOrRejectPromise(Promise* aPromise);
 
   void
   RunCallback(GetFilesCallback* aCallback);
 
   nsCOMPtr<nsIGlobalObject> mGlobal;
 
-  bool mRecursiveFlag;
   bool mListingCompleted;
   nsString mDirectoryPath;
 
-  // We populate this array in the I/O thread with the paths of the Files that
-  // we want to send as result to the promise objects.
-  struct FileData {
-    nsString mDomPath;
-    nsString mRealPath;
-  };
-  FallibleTArray<FileData> mTargetPathArray;
-
   // This is the real File sequence that we expose via Promises.
   Sequence<RefPtr<File>> mFiles;
 
   // Error code to propagate.
   nsresult mErrorResult;
 
   nsTArray<RefPtr<Promise>> mPromises;
   nsTArray<RefPtr<GetFilesCallback>> mCallbacks;
--- a/dom/filesystem/GetFilesTask.cpp
+++ b/dom/filesystem/GetFilesTask.cpp
@@ -218,42 +218,42 @@ GetFilesTaskParent::Create(FileSystemBas
 
   return task.forget();
 }
 
 GetFilesTaskParent::GetFilesTaskParent(FileSystemBase* aFileSystem,
                                        const FileSystemGetFilesParams& aParam,
                                        FileSystemRequestParent* aParent)
   : FileSystemTaskParentBase(aFileSystem, aParam, aParent)
+  , GetFilesHelperBase(aParam.recursiveFlag())
   , mDirectoryDOMPath(aParam.domPath())
-  , mRecursiveFlag(aParam.recursiveFlag())
 {
   MOZ_ASSERT(XRE_IsParentProcess(), "Only call from parent process!");
   AssertIsOnBackgroundThread();
   MOZ_ASSERT(aFileSystem);
 }
 
 FileSystemResponseValue
 GetFilesTaskParent::GetSuccessRequestResult(ErrorResult& aRv) const
 {
   AssertIsOnBackgroundThread();
 
   InfallibleTArray<PBlobParent*> blobs;
 
   FallibleTArray<FileSystemFileResponse> inputs;
-  if (!inputs.SetLength(mTargetData.Length(), mozilla::fallible_t())) {
+  if (!inputs.SetLength(mTargetPathArray.Length(), mozilla::fallible_t())) {
     aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
     FileSystemFilesResponse response;
     return response;
   }
 
-  for (unsigned i = 0; i < mTargetData.Length(); i++) {
+  for (unsigned i = 0; i < mTargetPathArray.Length(); i++) {
     FileSystemFileResponse fileData;
-    fileData.realPath() = mTargetData[i].mRealPath;
-    fileData.domPath() = mTargetData[i].mDOMPath;
+    fileData.realPath() = mTargetPathArray[i].mRealPath;
+    fileData.domPath() = mTargetPathArray[i].mDomPath;
     inputs[i] = fileData;
   }
 
   FileSystemFilesResponse response;
   response.data().SwapElements(inputs);
   return response;
 }
 
@@ -273,117 +273,32 @@ GetFilesTaskParent::IOWork()
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
   if (!exists) {
     return NS_OK;
   }
 
-  // Get isDirectory.
-  rv = ExploreDirectory(mDirectoryDOMPath, mTargetPath);
-  if (NS_WARN_IF(NS_FAILED(rv))) {
-    return rv;
-  }
-
-  return NS_OK;
-}
-
-nsresult
-GetFilesTaskParent::ExploreDirectory(const nsAString& aDOMPath, nsIFile* aPath)
-{
-  MOZ_ASSERT(XRE_IsParentProcess(),
-             "Only call from parent process!");
-  MOZ_ASSERT(!NS_IsMainThread(), "Only call on worker thread!");
-  MOZ_ASSERT(aPath);
-
   bool isDir;
-  nsresult rv = aPath->IsDirectory(&isDir);
+  rv = mTargetPath->IsDirectory(&isDir);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
   if (!isDir) {
     return NS_ERROR_DOM_FILESYSTEM_TYPE_MISMATCH_ERR;
   }
 
-  nsCOMPtr<nsISimpleEnumerator> entries;
-  rv = aPath->GetDirectoryEntries(getter_AddRefs(entries));
+  // Get isDirectory.
+  rv = ExploreDirectory(mDirectoryDOMPath, mTargetPath);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
-  for (;;) {
-    bool hasMore = false;
-    if (NS_WARN_IF(NS_FAILED(entries->HasMoreElements(&hasMore))) || !hasMore) {
-      break;
-    }
-    nsCOMPtr<nsISupports> supp;
-    if (NS_WARN_IF(NS_FAILED(entries->GetNext(getter_AddRefs(supp))))) {
-      break;
-    }
-
-    nsCOMPtr<nsIFile> currFile = do_QueryInterface(supp);
-    MOZ_ASSERT(currFile);
-
-    bool isLink, isSpecial, isFile;
-    if (NS_WARN_IF(NS_FAILED(currFile->IsSymlink(&isLink)) ||
-                   NS_FAILED(currFile->IsSpecial(&isSpecial))) ||
-        isLink || isSpecial) {
-      continue;
-    }
-
-    if (NS_WARN_IF(NS_FAILED(currFile->IsFile(&isFile)) ||
-                   NS_FAILED(currFile->IsDirectory(&isDir))) ||
-        !(isFile || isDir)) {
-      continue;
-    }
-
-    nsAutoString domPath;
-    domPath.Assign(aDOMPath);
-
-    // This is specific for unix root filesystem.
-    if (!aDOMPath.EqualsLiteral(FILESYSTEM_DOM_PATH_SEPARATOR_LITERAL)) {
-      domPath.AppendLiteral(FILESYSTEM_DOM_PATH_SEPARATOR_LITERAL);
-    }
-
-    nsAutoString leafName;
-    if (NS_WARN_IF(NS_FAILED(currFile->GetLeafName(leafName)))) {
-      continue;
-    }
-    domPath.Append(leafName);
-
-    if (isFile) {
-      FileData data;
-      data.mDOMPath.Append(domPath);
-
-      if (NS_WARN_IF(NS_FAILED(currFile->GetPath(data.mRealPath)))) {
-        continue;
-      }
-
-      if (!mTargetData.AppendElement(data, fallible)) {
-        return NS_ERROR_OUT_OF_MEMORY;
-      }
-
-      continue;
-    }
-
-    MOZ_ASSERT(isDir);
-
-    if (!mRecursiveFlag) {
-      continue;
-    }
-
-    // Recursive.
-    rv = ExploreDirectory(domPath, currFile);
-    if (NS_WARN_IF(NS_FAILED(rv))) {
-      return rv;
-    }
-  }
-
   return NS_OK;
 }
 
 void
 GetFilesTaskParent::GetPermissionAccessType(nsCString& aAccess) const
 {
   aAccess.AssignLiteral(DIRECTORY_READ_PERMISSION);
 }
--- a/dom/filesystem/GetFilesTask.h
+++ b/dom/filesystem/GetFilesTask.h
@@ -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/. */
 
 #ifndef mozilla_dom_GetFilesTask_h
 #define mozilla_dom_GetFilesTask_h
 
 #include "mozilla/dom/Directory.h"
 #include "mozilla/dom/FileSystemTaskBase.h"
+#include "mozilla/dom/GetFilesHelper.h"
 #include "mozilla/ErrorResult.h"
 
 namespace mozilla {
 namespace dom {
 
 class BlobImpl;
 
 class GetFilesTaskChild final : public FileSystemTaskChildBase
@@ -63,16 +64,17 @@ private:
     nsString mRealPath;
     nsString mDOMPath;
   };
 
   FallibleTArray<FileData> mTargetData;
 };
 
 class GetFilesTaskParent final : public FileSystemTaskParentBase
+                               , public GetFilesHelperBase
 {
 public:
   static already_AddRefed<GetFilesTaskParent>
   Create(FileSystemBase* aFileSystem,
          const FileSystemGetFilesParams& aParam,
          FileSystemRequestParent* aParent,
          ErrorResult& aRv);
 
@@ -85,28 +87,16 @@ private:
                      FileSystemRequestParent* aParent);
 
   virtual FileSystemResponseValue
   GetSuccessRequestResult(ErrorResult& aRv) const override;
 
   virtual nsresult
   IOWork() override;
 
-  nsresult
-  ExploreDirectory(const nsAString& aDOMPath, nsIFile* aPath);
-
   nsString mDirectoryDOMPath;
   nsCOMPtr<nsIFile> mTargetPath;
-  bool mRecursiveFlag;
-
-  // We store the fullpath and the dom path of Files.
-  struct FileData {
-    nsString mRealPath;
-    nsString mDOMPath;
-  };
-
-  FallibleTArray<FileData> mTargetData;
 };
 
 } // namespace dom
 } // namespace mozilla
 
 #endif // mozilla_dom_GetFilesTask_h
--- a/dom/filesystem/tests/test_webkitdirectory.html
+++ b/dom/filesystem/tests/test_webkitdirectory.html
@@ -3,18 +3,18 @@
 <head>
   <title>Test for webkitdirectory and webkitRelativePath</title>
   <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
 </head>
 
 <body>
 <input id="inputFileWebkitDirectory" type="file" webkitdirectory></input>
-<input id="inputFileWebkitDirectoryAndDirectory" type="file" webkitdirectory directory></input>
-<input id="inputFileDirectory" type="file" directory></input>
+<input id="inputFileWebkitDirectoryAndDirectory" type="file" webkitdirectory allowdirs></input>
+<input id="inputFileDirectory" type="file" allowdirs></input>
 <input id="inputFileDirectoryChange" type="file" webkitdirectory></input>
 
 <script type="application/javascript;version=1.7">
 
 function populateInputFile(aInputFile) {
   var url = SimpleTest.getTestFileURL("script_fileList.js");
   var script = SpecialPowers.loadChromeScript(url);
 
--- a/dom/flyweb/HttpServer.cpp
+++ b/dom/flyweb/HttpServer.cpp
@@ -49,18 +49,17 @@ HttpServer::Init(int32_t aPort, bool aHt
 {
   mPort = aPort;
   mHttps = aHttps;
   mListener = aListener;
 
   if (mHttps) {
     nsCOMPtr<nsILocalCertService> lcs =
       do_CreateInstance("@mozilla.org/security/local-cert-service;1");
-    nsresult rv = lcs->GetOrCreateCert(NS_LITERAL_CSTRING("flyweb"), this,
-                                       nsILocalCertService::KEY_TYPE_EC);
+    nsresult rv = lcs->GetOrCreateCert(NS_LITERAL_CSTRING("flyweb"), this);
     if (NS_FAILED(rv)) {
       NotifyStarted(rv);
     }
   } else {
     // Make sure to always have an async step before notifying callbacks
     HandleCert(nullptr, NS_OK);
   }
 }
--- a/dom/geolocation/moz.build
+++ b/dom/geolocation/moz.build
@@ -37,8 +37,14 @@ elif CONFIG['MOZ_WIDGET_TOOLKIT'] == 'go
 elif CONFIG['MOZ_WIDGET_TOOLKIT'] == 'cocoa':
     LOCAL_INCLUDES += [
         '/dom/system/mac',
     ]
 elif CONFIG['MOZ_WIDGET_TOOLKIT'] == 'windows':
     LOCAL_INCLUDES += [
         '/dom/system/windows',
     ]
+elif CONFIG['MOZ_WIDGET_TOOLKIT'] in ('gtk2', 'gtk3'):
+    if CONFIG['MOZ_GPSD']:
+        LOCAL_INCLUDES += [
+            '/dom/system/linux',
+        ]
+        DEFINES['MOZ_GPSD'] = True
--- a/dom/geolocation/nsGeolocation.cpp
+++ b/dom/geolocation/nsGeolocation.cpp
@@ -40,16 +40,20 @@ class nsIPrincipal;
 #ifdef MOZ_WIDGET_ANDROID
 #include "AndroidLocationProvider.h"
 #endif
 
 #ifdef MOZ_WIDGET_GONK
 #include "GonkGPSGeolocationProvider.h"
 #endif
 
+#ifdef MOZ_GPSD
+#include "GpsdLocationProvider.h"
+#endif
+
 #ifdef MOZ_WIDGET_COCOA
 #include "CoreLocationLocationProvider.h"
 #endif
 
 #ifdef XP_WIN
 #include "WindowsLocationProvider.h"
 #include "mozilla/WindowsVersion.h"
 #endif
@@ -761,16 +765,24 @@ nsresult nsGeolocationService::Init()
   // GonkGPSGeolocationProvider can be started at boot up time for initialization reasons.
   // do_getService gets hold of the already initialized component and starts
   // processing location requests immediately.
   // do_Createinstance will create multiple instances of the provider which is not right.
   // bug 993041
   mProvider = do_GetService(GONK_GPS_GEOLOCATION_PROVIDER_CONTRACTID);
 #endif
 
+#ifdef MOZ_WIDGET_GTK
+#ifdef MOZ_GPSD
+  if (Preferences::GetBool("geo.provider.use_gpsd", false)) {
+    mProvider = new GpsdLocationProvider();
+  }
+#endif
+#endif
+
 #ifdef MOZ_WIDGET_COCOA
   if (Preferences::GetBool("geo.provider.use_corelocation", true)) {
     mProvider = new CoreLocationLocationProvider();
   }
 #endif
 
 #ifdef XP_WIN
   if (Preferences::GetBool("geo.provider.ms-windows-location", false) &&
--- a/dom/html/HTMLFormElement.cpp
+++ b/dom/html/HTMLFormElement.cpp
@@ -140,26 +140,19 @@ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mImageNameLookupTable)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPastNameLookupTable)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSelectedRadioButtons)
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
 
 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(HTMLFormElement,
                                                 nsGenericHTMLElement)
   tmp->Clear();
-  tmp->mExpandoAndGeneration.Unlink();
+  tmp->mExpandoAndGeneration.OwnerUnlinked();
 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
 
-NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN_INHERITED(HTMLFormElement,
-                                               nsGenericHTMLElement)
-  if (tmp->PreservingWrapper()) {
-    NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mExpandoAndGeneration.expando)
-  }
-NS_IMPL_CYCLE_COLLECTION_TRACE_END
-
 NS_IMPL_ADDREF_INHERITED(HTMLFormElement, Element)
 NS_IMPL_RELEASE_INHERITED(HTMLFormElement, Element)
 
 
 // QueryInterface implementation for HTMLFormElement
 NS_INTERFACE_TABLE_HEAD_CYCLE_COLLECTION_INHERITED(HTMLFormElement)
   NS_INTERFACE_TABLE_INHERITED(HTMLFormElement,
                                nsIDOMHTMLFormElement,
--- a/dom/html/HTMLFormElement.h
+++ b/dom/html/HTMLFormElement.h
@@ -115,18 +115,18 @@ public:
   /**
    * Forget all information about the current submission (and the fact that we
    * are currently submitting at all).
    */
   void ForgetCurrentSubmission();
 
   virtual nsresult Clone(mozilla::dom::NodeInfo *aNodeInfo, nsINode **aResult) const override;
 
-  NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_INHERITED(HTMLFormElement,
-                                                         nsGenericHTMLElement)
+  NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(HTMLFormElement,
+                                           nsGenericHTMLElement)
 
   /**
    * Remove an element from this form's list of elements
    *
    * @param aElement the element to remove
    * @param aUpdateValidity If true, updates the form validity.
    * @return NS_OK if the element was successfully removed.
    */
--- a/dom/html/HTMLInputElement.cpp
+++ b/dom/html/HTMLInputElement.cpp
@@ -503,17 +503,19 @@ GetDOMFileOrDirectoryName(const OwningFi
                           nsAString& aName)
 {
   if (aData.IsFile()) {
     aData.GetAsFile()->GetName(aName);
   } else {
     MOZ_ASSERT(aData.IsDirectory());
     ErrorResult rv;
     aData.GetAsDirectory()->GetName(aName, rv);
-    NS_WARN_IF(rv.Failed());
+    if (NS_WARN_IF(rv.Failed())) {
+      rv.SuppressException();
+    }
   }
 }
 
 void
 GetDOMFileOrDirectoryPath(const OwningFileOrDirectory& aData,
                           nsAString& aPath,
                           ErrorResult& aRv)
 {
@@ -2818,18 +2820,17 @@ HTMLInputElement::GetDisplayFileName(nsA
   if (mFilesOrDirectories.Length() == 1) {
     GetDOMFileOrDirectoryName(mFilesOrDirectories[0], aValue);
     return;
   }
 
   nsXPIDLString value;
 
   if (mFilesOrDirectories.IsEmpty()) {
-    if ((Preferences::GetBool("dom.input.dirpicker", false) &&
-         HasAttr(kNameSpaceID_None, nsGkAtoms::directory)) ||
+    if ((Preferences::GetBool("dom.input.dirpicker", false) && Allowdirs()) ||
         (Preferences::GetBool("dom.webkitBlink.dirPicker.enabled", false) &&
          HasAttr(kNameSpaceID_None, nsGkAtoms::webkitdirectory))) {
       nsContentUtils::GetLocalizedString(nsContentUtils::eFORMS_PROPERTIES,
                                          "NoDirSelected", value);
     } else if (HasAttr(kNameSpaceID_None, nsGkAtoms::multiple)) {
       nsContentUtils::GetLocalizedString(nsContentUtils::eFORMS_PROPERTIES,
                                          "NoFilesSelected", value);
     } else {
@@ -2966,18 +2967,17 @@ HTMLInputElement::FireChangeEventIfNeede
 
 FileList*
 HTMLInputElement::GetFiles()
 {
   if (mType != NS_FORM_INPUT_FILE) {
     return nullptr;
   }
 
-  if (Preferences::GetBool("dom.input.dirpicker", false) &&
-      HasAttr(kNameSpaceID_None, nsGkAtoms::directory) &&
+  if (Preferences::GetBool("dom.input.dirpicker", false) && Allowdirs() &&
       (!Preferences::GetBool("dom.webkitBlink.dirPicker.enabled", false) ||
        !HasAttr(kNameSpaceID_None, nsGkAtoms::webkitdirectory))) {
     return nullptr;
   }
 
   if (!mFileList) {
     mFileList = new FileList(static_cast<nsIContent*>(this));
     UpdateFileList();
@@ -4084,18 +4084,17 @@ HTMLInputElement::MaybeInitPickers(Event
     // If the user clicked on the "Choose folder..." button we open the
     // directory picker, else we open the file picker.
     FilePickerType type = FILE_PICKER_FILE;
     nsCOMPtr<nsIContent> target =
       do_QueryInterface(aVisitor.mEvent->mOriginalTarget);
     if (target &&
         target->GetParent() == this &&
         target->IsRootOfNativeAnonymousSubtree() &&
-        ((Preferences::GetBool("dom.input.dirpicker", false) &&
-          HasAttr(kNameSpaceID_None, nsGkAtoms::directory)) ||
+        ((Preferences::GetBool("dom.input.dirpicker", false) && Allowdirs()) ||
          (Preferences::GetBool("dom.webkitBlink.dirPicker.enabled", false) &&
           HasAttr(kNameSpaceID_None, nsGkAtoms::webkitdirectory)))) {
       type = FILE_PICKER_DIRECTORY;
     }
     return InitFilePicker(type);
   }
   if (mType == NS_FORM_INPUT_COLOR) {
     return InitColorPicker();
@@ -5381,17 +5380,17 @@ nsChangeHint
 HTMLInputElement::GetAttributeChangeHint(const nsIAtom* aAttribute,
                                          int32_t aModType) const
 {
   nsChangeHint retval =
     nsGenericHTMLFormElementWithState::GetAttributeChangeHint(aAttribute, aModType);
   if (aAttribute == nsGkAtoms::type ||
       // The presence or absence of the 'directory' attribute determines what
       // buttons we show for type=file.
-      aAttribute == nsGkAtoms::directory ||
+      aAttribute == nsGkAtoms::allowdirs ||
       aAttribute == nsGkAtoms::webkitdirectory) {
     retval |= nsChangeHint_ReconstructFrame;
   } else if (mType == NS_FORM_INPUT_IMAGE &&
              (aAttribute == nsGkAtoms::alt ||
               aAttribute == nsGkAtoms::value)) {
     // We might need to rebuild our alt text.  Just go ahead and
     // reconstruct our frame.  This should be quite rare..
     retval |= nsChangeHint_ReconstructFrame;
--- a/dom/html/HTMLInputElement.h
+++ b/dom/html/HTMLInputElement.h
@@ -703,24 +703,24 @@ public:
 
   void SetRangeText(const nsAString& aReplacement, ErrorResult& aRv);
 
   void SetRangeText(const nsAString& aReplacement, uint32_t aStart,
                     uint32_t aEnd, const SelectionMode& aSelectMode,
                     ErrorResult& aRv, int32_t aSelectionStart = -1,
                     int32_t aSelectionEnd = -1);
 
-  bool DirectoryAttr() const
+  bool Allowdirs() const
   {
-    return HasAttr(kNameSpaceID_None, nsGkAtoms::directory);
+    return HasAttr(kNameSpaceID_None, nsGkAtoms::allowdirs);
   }
 
-  void SetDirectoryAttr(bool aValue, ErrorResult& aRv)
+  void SetAllowdirs(bool aValue, ErrorResult& aRv)
   {
-    SetHTMLBoolAttr(nsGkAtoms::directory, aValue, aRv);
+    SetHTMLBoolAttr(nsGkAtoms::allowdirs, aValue, aRv);
   }
 
   bool WebkitDirectoryAttr() const
   {
     return HasAttr(kNameSpaceID_None, nsGkAtoms::webkitdirectory);
   }
 
   void SetWebkitDirectoryAttr(bool aValue, ErrorResult& aRv)
--- a/dom/html/nsDOMStringMap.cpp
+++ b/dom/html/nsDOMStringMap.cpp
@@ -27,25 +27,20 @@ NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(ns
   NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER
   // Check that mElement exists in case the unlink code is run more than once.
   if (tmp->mElement) {
     // Call back to element to null out weak reference to this object.
     tmp->mElement->ClearDataset();
     tmp->mElement->RemoveMutationObserver(tmp);
     tmp->mElement = nullptr;
   }
-  tmp->mExpandoAndGeneration.Unlink();
+  tmp->mExpandoAndGeneration.OwnerUnlinked();
 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
 
-NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(nsDOMStringMap)
-  NS_IMPL_CYCLE_COLLECTION_TRACE_PRESERVED_WRAPPER
-  if (tmp->PreservingWrapper()) {
-    NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mExpandoAndGeneration.expando)
-  }
-NS_IMPL_CYCLE_COLLECTION_TRACE_END
+NS_IMPL_CYCLE_COLLECTION_TRACE_WRAPPERCACHE(nsDOMStringMap)
 
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsDOMStringMap)
   NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
   NS_INTERFACE_MAP_ENTRY(nsIMutationObserver)
   NS_INTERFACE_MAP_ENTRY(nsISupports)
 NS_INTERFACE_MAP_END
 
 NS_IMPL_CYCLE_COLLECTING_ADDREF(nsDOMStringMap)
--- a/dom/indexedDB/ActorsParent.cpp
+++ b/dom/indexedDB/ActorsParent.cpp
@@ -3669,23 +3669,23 @@ UpgradeSchemaFrom18_0To19_0(mozIStorageC
     return rv;
   }
 
   return NS_OK;
 }
 
 #if !defined(MOZ_B2G)
 
-class NormalJSRuntime;
+class NormalJSContext;
 
 class UpgradeFileIdsFunction final
   : public mozIStorageFunction
 {
   RefPtr<FileManager> mFileManager;
-  nsAutoPtr<NormalJSRuntime> mRuntime;
+  nsAutoPtr<NormalJSContext> mContext;
 
 public:
   UpgradeFileIdsFunction()
   {
     AssertIsOnIOThread();
   }
 
   nsresult
@@ -7827,17 +7827,17 @@ private:
   DoDatabaseWork(DatabaseConnection* aConnection) override;
 };
 
 class CreateIndexOp final
   : public VersionChangeTransactionOp
 {
   friend class VersionChangeTransaction;
 
-  class ThreadLocalJSRuntime;
+  class ThreadLocalJSContext;
   class UpdateIndexDataValuesFunction;
 
   static const unsigned int kBadThreadLocalIndex =
     static_cast<unsigned int>(-1);
 
   static unsigned int sThreadLocalIndex;
 
   const IndexMetadata mMetadata;
@@ -7863,84 +7863,82 @@ private:
 
   virtual bool
   Init(TransactionBase* aTransaction) override;
 
   virtual nsresult
   DoDatabaseWork(DatabaseConnection* aConnection) override;
 };
 
-class NormalJSRuntime
-{
-  friend class nsAutoPtr<NormalJSRuntime>;
+class NormalJSContext
+{
+  friend class nsAutoPtr<NormalJSContext>;
 
   static const JSClass sGlobalClass;
-  static const uint32_t kRuntimeHeapSize = 768 * 1024;
-
-  JSRuntime* mRuntime;
+  static const uint32_t kContextHeapSize = 768 * 1024;
+
   JSContext* mContext;
   JSObject* mGlobal;
 
 public:
-  static NormalJSRuntime*
+  static NormalJSContext*
   Create();
 
   JSContext*
   Context() const
   {
     return mContext;
   }
 
   JSObject*
   Global() const
   {
     return mGlobal;
   }
 
 protected:
-  NormalJSRuntime()
-    : mRuntime(nullptr)
-    , mContext(nullptr)
+  NormalJSContext()
+    : mContext(nullptr)
     , mGlobal(nullptr)
   {
-    MOZ_COUNT_CTOR(NormalJSRuntime);
-  }
-
-  ~NormalJSRuntime()
-  {
-    MOZ_COUNT_DTOR(NormalJSRuntime);
-
-    if (mRuntime) {
-      JS_DestroyRuntime(mRuntime);
+    MOZ_COUNT_CTOR(NormalJSContext);
+  }
+
+  ~NormalJSContext()
+  {
+    MOZ_COUNT_DTOR(NormalJSContext);
+
+    if (mContext) {
+      JS_DestroyContext(mContext);
     }
   }
 
   bool
   Init();
 };
 
-class CreateIndexOp::ThreadLocalJSRuntime final
-  : public NormalJSRuntime
+class CreateIndexOp::ThreadLocalJSContext final
+  : public NormalJSContext
 {
   friend class CreateIndexOp;
-  friend class nsAutoPtr<ThreadLocalJSRuntime>;
-
-public:
-  static ThreadLocalJSRuntime*
+  friend class nsAutoPtr<ThreadLocalJSContext>;
+
+public:
+  static ThreadLocalJSContext*
   GetOrCreate();
 
 private:
-  ThreadLocalJSRuntime()
-  {
-    MOZ_COUNT_CTOR(CreateIndexOp::ThreadLocalJSRuntime);
-  }
-
-  ~ThreadLocalJSRuntime()
-  {
-    MOZ_COUNT_DTOR(CreateIndexOp::ThreadLocalJSRuntime);
+  ThreadLocalJSContext()
+  {
+    MOZ_COUNT_CTOR(CreateIndexOp::ThreadLocalJSContext);
+  }
+
+  ~ThreadLocalJSContext()
+  {
+    MOZ_COUNT_DTOR(CreateIndexOp::ThreadLocalJSContext);
   }
 };
 
 class CreateIndexOp::UpdateIndexDataValuesFunction final
   : public mozIStorageFunction
 {
   RefPtr<CreateIndexOp> mOp;
   RefPtr<DatabaseConnection> mConnection;
@@ -18746,36 +18744,36 @@ UpgradeFileIdsFunction::Init(nsIFile* aF
                     EmptyString(),
                     false);
 
   nsresult rv = fileManager->Init(aFMDirectory, aConnection);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
-  nsAutoPtr<NormalJSRuntime> runtime(NormalJSRuntime::Create());
-  if (NS_WARN_IF(!runtime)) {
+  nsAutoPtr<NormalJSContext> context(NormalJSContext::Create());
+  if (NS_WARN_IF(!context)) {
     return NS_ERROR_FAILURE;
   }
 
   mFileManager.swap(fileManager);
-  mRuntime = runtime;
+  mContext = context;
   return NS_OK;
 }
 
 NS_IMPL_ISUPPORTS(UpgradeFileIdsFunction, mozIStorageFunction)
 
 NS_IMETHODIMP
 UpgradeFileIdsFunction::OnFunctionCall(mozIStorageValueArray* aArguments,
                                        nsIVariant** aResult)
 {
   MOZ_ASSERT(aArguments);
   MOZ_ASSERT(aResult);
   MOZ_ASSERT(mFileManager);
-  MOZ_ASSERT(mRuntime);
+  MOZ_ASSERT(mContext);
 
   PROFILER_LABEL("IndexedDB",
                  "UpgradeFileIdsFunction::OnFunctionCall",
                  js::ProfileEntry::Category::STORAGE);
 
   uint32_t argc;
   nsresult rv = aArguments->GetNumEntries(&argc);
   if (NS_WARN_IF(NS_FAILED(rv))) {
@@ -18789,19 +18787,19 @@ UpgradeFileIdsFunction::OnFunctionCall(m
 
   StructuredCloneReadInfo cloneInfo;
   DatabaseOperationBase::GetStructuredCloneReadInfoFromValueArray(aArguments,
                                                                   1,
                                                                   0,
                                                                   mFileManager,
                                                                   &cloneInfo);
 
-  JSContext* cx = mRuntime->Context();
+  JSContext* cx = mContext->Context();
   JSAutoRequest ar(cx);
-  JSAutoCompartment ac(cx, mRuntime->Global());
+  JSAutoCompartment ac(cx, mContext->Global());
 
   JS::Rooted<JS::Value> clone(cx);
   if (NS_WARN_IF(!IDBObjectStore::DeserializeUpgradeValue(cx, cloneInfo,
                                                           &clone))) {
     return NS_ERROR_DOM_DATA_CLONE_ERR;
   }
 
   nsAutoString fileIds;
@@ -23821,25 +23819,25 @@ CreateIndexOp::InsertDataFromObjectStore
   PROFILER_LABEL("IndexedDB",
                  "CreateIndexOp::InsertDataFromObjectStore",
                  js::ProfileEntry::Category::STORAGE);
 
   nsCOMPtr<mozIStorageConnection> storageConnection =
     aConnection->GetStorageConnection();
   MOZ_ASSERT(storageConnection);
 
-  ThreadLocalJSRuntime* runtime = ThreadLocalJSRuntime::GetOrCreate();
-  if (NS_WARN_IF(!runtime)) {
-    IDB_REPORT_INTERNAL_ERR();
-    return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
-  }
-
-  JSContext* cx = runtime->Context();
+  ThreadLocalJSContext* context = ThreadLocalJSContext::GetOrCreate();
+  if (NS_WARN_IF(!context)) {
+    IDB_REPORT_INTERNAL_ERR();
+    return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
+  }
+
+  JSContext* cx = context->Context();
   JSAutoRequest ar(cx);
-  JSAutoCompartment ac(cx, runtime->Global());
+  JSAutoCompartment ac(cx, context->Global());
 
   RefPtr<UpdateIndexDataValuesFunction> updateFunction =
     new UpdateIndexDataValuesFunction(this, aConnection, cx);
 
   NS_NAMED_LITERAL_CSTRING(updateFunctionName, "update_index_data_values");
 
   nsresult rv =
     storageConnection->CreateFunction(updateFunctionName,
@@ -23904,17 +23902,17 @@ CreateIndexOp::Init(TransactionBase* aTr
   AssertIsOnBackgroundThread();
   MOZ_ASSERT(aTransaction);
 
   struct MOZ_STACK_CLASS Helper final
   {
     static void
     Destroy(void* aThreadLocal)
     {
-      delete static_cast<ThreadLocalJSRuntime*>(aThreadLocal);
+      delete static_cast<ThreadLocalJSContext*>(aThreadLocal);
     }
   };
 
   if (sThreadLocalIndex == kBadThreadLocalIndex) {
     if (NS_WARN_IF(PR_SUCCESS !=
                      PR_NewThreadPrivateIndex(&sThreadLocalIndex,
                                               &Helper::Destroy))) {
       return false;
@@ -24064,48 +24062,46 @@ CreateIndexOp::DoDatabaseWork(DatabaseCo
   rv = autoSave.Commit();
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
   return NS_OK;
 }
 
-static const JSClassOps sNormalJSRuntimeGlobalClassOps = {
+static const JSClassOps sNormalJSContextGlobalClassOps = {
   /* addProperty */ nullptr,
   /* delProperty */ nullptr,
   /* getProperty */ nullptr,
   /* setProperty */ nullptr,
   /* enumerate */ nullptr,
   /* resolve */ nullptr,
   /* mayResolve */ nullptr,
   /* finalize */ nullptr,
   /* call */ nullptr,
   /* hasInstance */ nullptr,
   /* construct */ nullptr,
   /* trace */ JS_GlobalObjectTraceHook
 };
 
-const JSClass NormalJSRuntime::sGlobalClass = {
+const JSClass NormalJSContext::sGlobalClass = {
   "IndexedDBTransactionThreadGlobal",
   JSCLASS_GLOBAL_FLAGS,
-  &sNormalJSRuntimeGlobalClassOps
-};
-
-bool
-NormalJSRuntime::Init()
-{
-  MOZ_ASSERT(!IsOnBackgroundThread());
-
-  mRuntime = JS_NewRuntime(kRuntimeHeapSize);
-  if (NS_WARN_IF(!mRuntime)) {
-    return false;
-  }
-
-  mContext = JS_GetContext(mRuntime);
+  &sNormalJSContextGlobalClassOps
+};
+
+bool
+NormalJSContext::Init()
+{
+  MOZ_ASSERT(!IsOnBackgroundThread());
+
+  mContext = JS_NewContext(kContextHeapSize);
+  if (NS_WARN_IF(!mContext)) {
+    return false;
+  }
 
   // Not setting this will cause JS_CHECK_RECURSION to report false positives.
   JS_SetNativeStackQuota(mContext, 128 * sizeof(size_t) * 1024);
 
   if (NS_WARN_IF(!JS::InitSelfHostedCode(mContext))) {
     return false;
   }
 
@@ -24117,56 +24113,56 @@ NormalJSRuntime::Init()
   if (NS_WARN_IF(!mGlobal)) {
     return false;
   }
 
   return true;
 }
 
 // static
-NormalJSRuntime*
-NormalJSRuntime::Create()
-{
-  MOZ_ASSERT(!IsOnBackgroundThread());
-
-  nsAutoPtr<NormalJSRuntime> newRuntime(new NormalJSRuntime());
-
-  if (NS_WARN_IF(!newRuntime->Init())) {
+NormalJSContext*
+NormalJSContext::Create()
+{
+  MOZ_ASSERT(!IsOnBackgroundThread());
+
+  nsAutoPtr<NormalJSContext> newContext(new NormalJSContext());
+
+  if (NS_WARN_IF(!newContext->Init())) {
     return nullptr;
   }
 
-  return newRuntime.forget();
+  return newContext.forget();
 }
 
 // static
 auto
 CreateIndexOp::
-ThreadLocalJSRuntime::GetOrCreate() -> ThreadLocalJSRuntime*
+ThreadLocalJSContext::GetOrCreate() -> ThreadLocalJSContext*
 {
   MOZ_ASSERT(!IsOnBackgroundThread());
   MOZ_ASSERT(CreateIndexOp::kBadThreadLocalIndex !=
              CreateIndexOp::sThreadLocalIndex);
 
-  auto* runtime = static_cast<ThreadLocalJSRuntime*>(
+  auto* context = static_cast<ThreadLocalJSContext*>(
     PR_GetThreadPrivate(CreateIndexOp::sThreadLocalIndex));
-  if (runtime) {
-    return runtime;
-  }
-
-  nsAutoPtr<ThreadLocalJSRuntime> newRuntime(new ThreadLocalJSRuntime());
-
-  if (NS_WARN_IF(!newRuntime->Init())) {
+  if (context) {
+    return context;
+  }
+
+  nsAutoPtr<ThreadLocalJSContext> newContext(new ThreadLocalJSContext());
+
+  if (NS_WARN_IF(!newContext->Init())) {
     return nullptr;
   }
 
   DebugOnly<PRStatus> status =
-    PR_SetThreadPrivate(CreateIndexOp::sThreadLocalIndex, newRuntime);
+    PR_SetThreadPrivate(CreateIndexOp::sThreadLocalIndex, newContext);
   MOZ_ASSERT(status == PR_SUCCESS);
 
-  return newRuntime.forget();
+  return newContext.forget();
 }
 
 NS_IMPL_ISUPPORTS(CreateIndexOp::UpdateIndexDataValuesFunction,
                   mozIStorageFunction);
 
 NS_IMETHODIMP
 CreateIndexOp::
 UpdateIndexDataValuesFunction::OnFunctionCall(mozIStorageValueArray* aValues,
--- a/dom/indexedDB/test/helpers.js
+++ b/dom/indexedDB/test/helpers.js
@@ -340,17 +340,17 @@ function removePermission(type, url)
 function gc()
 {
   SpecialPowers.forceGC();
   SpecialPowers.forceCC();
 }
 
 function scheduleGC()
 {
-  SpecialPowers.exactGC(window, continueToNextStep);
+  SpecialPowers.exactGC(continueToNextStep);
 }
 
 function workerScript() {
   "use strict";
 
   self.repr = function(_thing_) {
     if (typeof(_thing_) == "undefined") {
       return "undefined";
--- a/dom/indexedDB/test/unit/xpcshell-head-parent-process.js
+++ b/dom/indexedDB/test/unit/xpcshell-head-parent-process.js
@@ -212,17 +212,17 @@ function resetTesting()
 function gc()
 {
   Cu.forceGC();
   Cu.forceCC();
 }
 
 function scheduleGC()
 {
-  SpecialPowers.exactGC(null, continueToNextStep);
+  SpecialPowers.exactGC(continueToNextStep);
 }
 
 function setTimeout(fun, timeout) {
   let timer = Components.classes["@mozilla.org/timer;1"]
                         .createInstance(Components.interfaces.nsITimer);
   var event = {
     notify: function (timer) {
       fun();
@@ -487,17 +487,17 @@ var SpecialPowers = {
   },
   setIntPref: function(prefName, value) {
     this._getPrefs().setIntPref(prefName, value);
   },
   clearUserPref: function(prefName) {
     this._getPrefs().clearUserPref(prefName);
   },
   // Copied (and slightly adjusted) from specialpowersAPI.js
-  exactGC: function(win, callback) {
+  exactGC: function(callback) {
     let count = 0;
 
     function doPreciseGCandCC() {
       function scheduledGCCallback() {
         Components.utils.forceCC();
 
         if (++count < 2) {
           doPreciseGCandCC();
--- a/dom/ipc/Blob.cpp
+++ b/dom/ipc/Blob.cpp
@@ -854,16 +854,17 @@ CreateBlobImpl(const nsTArray<BlobData>&
   if (!hasRecursed && aMetadata.IsFile()) {
     blobImpl = MultipartBlobImpl::Create(blobImpls, aMetadata.mName,
                                          aMetadata.mContentType, rv);
   } else {
     blobImpl = MultipartBlobImpl::Create(blobImpls, aMetadata.mContentType, rv);
   }
 
   if (NS_WARN_IF(rv.Failed())) {
+    rv.SuppressException();
     return nullptr;
   }
 
   MOZ_ALWAYS_SUCCEEDS(blobImpl->SetMutable(false));
 
   return blobImpl.forget();
 }
 
@@ -4389,16 +4390,17 @@ BlobParent::RecvGetFilePath(nsString* aF
   MOZ_ASSERT(mOwnsBlobImpl);
 
   // In desktop e10s the file picker code sends this message.
 
   nsString filePath;
   ErrorResult rv;
   mBlobImpl->GetMozFullPathInternal(filePath, rv);
   if (NS_WARN_IF(rv.Failed())) {
+    rv.SuppressException();
     return false;
   }
 
   *aFilePath = filePath;
   return true;
 }
 
 /*******************************************************************************
--- a/dom/ipc/ContentChild.cpp
+++ b/dom/ipc/ContentChild.cpp
@@ -1417,17 +1417,18 @@ ContentChild::RecvSetProcessSandbox(cons
   // before seccomp is enabled (Bug 1259508). It also increases the startup
   // time of the content process, because cubeb is usually initialized
   // when it is actually needed. This call here is no longer required
   // once Bug 1104619 (remoting audio) is resolved.
   Unused << CubebUtils::GetCubebContext();
 #endif
   int brokerFd = -1;
   if (aBroker.type() == MaybeFileDesc::TFileDescriptor) {
-      brokerFd = aBroker.get_FileDescriptor().PlatformHandle();
+      auto fd = aBroker.get_FileDescriptor().ClonePlatformHandle();
+      brokerFd = fd.release();
       // brokerFd < 0 means to allow direct filesystem access, so
       // make absolutely sure that doesn't happen if the parent
       // didn't intend it.
       MOZ_RELEASE_ASSERT(brokerFd >= 0);
   }
   SetContentProcessSandbox(brokerFd);
 #elif defined(XP_WIN)
   mozilla::SandboxTarget::Instance()->StartSandbox();
--- a/dom/ipc/ContentParent.cpp
+++ b/dom/ipc/ContentParent.cpp
@@ -3330,16 +3330,17 @@ ContentParent::RecvGetXPCOMProcessAttrib
     nsresult result = mm->GetInitialProcessData(jsapi.cx(), &init);
     if (NS_FAILED(result)) {
       return false;
     }
 
     ErrorResult rv;
     aInitialData->Write(jsapi.cx(), init, rv);
     if (NS_WARN_IF(rv.Failed())) {
+      rv.SuppressException();
       return false;
     }
   }
 
   return true;
 }
 
 mozilla::jsipc::PJavaScriptParent *
@@ -4916,19 +4917,19 @@ ContentParent::RecvRemoveIdleObserver(co
 bool
 ContentParent::RecvBackUpXResources(const FileDescriptor& aXSocketFd)
 {
 #ifndef MOZ_X11
   NS_RUNTIMEABORT("This message only makes sense on X11 platforms");
 #else
   MOZ_ASSERT(0 > mChildXSocketFdDup.get(),
              "Already backed up X resources??");
-  mChildXSocketFdDup.forget();
   if (aXSocketFd.IsValid()) {
-    mChildXSocketFdDup.reset(aXSocketFd.PlatformHandle());
+    auto rawFD = aXSocketFd.ClonePlatformHandle();
+    mChildXSocketFdDup.reset(rawFD.release());
   }
 #endif
   return true;
 }
 
 bool
 ContentParent::RecvOpenAnonymousTemporaryFile(FileDescOrError *aFD)
 {
--- a/dom/ipc/FilePickerParent.cpp
+++ b/dom/ipc/FilePickerParent.cpp
@@ -105,21 +105,23 @@ FilePickerParent::IORunnable::Run()
       continue;
     }
 
     RefPtr<BlobImpl> blobImpl = new BlobImplFile(mFiles[i]);
 
     ErrorResult error;
     blobImpl->GetSize(error);
     if (NS_WARN_IF(error.Failed())) {
+      error.SuppressException();
       continue;
     }
 
     blobImpl->GetLastModified(error);
     if (NS_WARN_IF(error.Failed())) {
+      error.SuppressException();
       continue;
     }
 
     BlobImplOrString* data = mResults.AppendElement();
     data->mType = BlobImplOrString::eBlobImpl;
     data->mBlobImpl = blobImpl;
   }
 
--- a/dom/ipc/TabChild.cpp
+++ b/dom/ipc/TabChild.cpp
@@ -215,16 +215,17 @@ TabChildBase::DispatchMessageManagerMess
     StructuredCloneData data;
     if (JS_ParseJSON(cx,
                       static_cast<const char16_t*>(aJSONData.BeginReading()),
                       aJSONData.Length(),
                       &json)) {
         ErrorResult rv;
         data.Write(cx, json, rv);
         if (NS_WARN_IF(rv.Failed())) {
+            rv.SuppressException();
             return;
         }
     }
 
     nsCOMPtr<nsIXPConnectJSObjectHolder> kungFuDeathGrip(GetGlobal());
     // Let the BrowserElementScrolling helper (if it exists) for this
     // content manipulate the frame state.
     RefPtr<nsFrameMessageManager> mm =
--- a/dom/media/AudioStream.cpp
+++ b/dom/media/AudioStream.cpp
@@ -357,19 +357,21 @@ AudioStream::OpenCubeb(cubeb_stream_para
 {
   cubeb* cubebContext = CubebUtils::GetCubebContext();
   if (!cubebContext) {
     NS_WARNING("Can't get cubeb context!");
     return NS_ERROR_FAILURE;
   }
 
   cubeb_stream* stream = nullptr;
+  /* Convert from milliseconds to frames. */
+  uint32_t latency_frames = CubebUtils::GetCubebLatency() * aParams.rate / 1000;
   if (cubeb_stream_init(cubebContext, &stream, "AudioStream",
                         nullptr, nullptr, nullptr, &aParams,
-                        CubebUtils::GetCubebLatency(),
+                        latency_frames,
                         DataCallback_S, StateCallback_S, this) == CUBEB_OK) {
     mCubebStream.reset(stream);
     CubebUtils::ReportCubebBackendUsed();
   } else {
     NS_WARNING(nsPrintfCString("AudioStream::OpenCubeb() %p failed to init cubeb", this).get());
     CubebUtils::ReportCubebStreamInitFailure(aIsFirst);
     return NS_ERROR_FAILURE;
   }
--- a/dom/media/GraphDriver.cpp
+++ b/dom/media/GraphDriver.cpp
@@ -554,17 +554,17 @@ AudioCallbackDriver::~AudioCallbackDrive
   MOZ_ASSERT(mPromisesForOperation.IsEmpty());
 }
 
 void
 AudioCallbackDriver::Init()
 {
   cubeb_stream_params output;
   cubeb_stream_params input;
-  uint32_t latency;
+  uint32_t latency_frames;
   bool firstStream = CubebUtils::GetFirstStream();
 
   MOZ_ASSERT(!NS_IsMainThread(),
       "This is blocking and should never run on the main thread.");
 
   mSampleRate = output.rate = CubebUtils::PreferredSampleRate();
 
 #if defined(__ANDROID__)
@@ -583,17 +583,17 @@ AudioCallbackDriver::Init()
 
   output.channels = mGraphImpl->AudioChannelCount();
   if (AUDIO_OUTPUT_FORMAT == AUDIO_FORMAT_S16) {
     output.format = CUBEB_SAMPLE_S16NE;
   } else {
     output.format = CUBEB_SAMPLE_FLOAT32NE;
   }
 
-  if (cubeb_get_min_latency(CubebUtils::GetCubebContext(), output, &latency) != CUBEB_OK) {
+  if (cubeb_get_min_latency(CubebUtils::GetCubebContext(), output, &latency_frames) != CUBEB_OK) {
     NS_WARNING("Could not get minimal latency from cubeb.");
     return;
   }
 
   input = output;
   input.channels = mInputChannels; // change to support optional stereo capture
 
   cubeb_stream* stream = nullptr;
@@ -619,17 +619,17 @@ AudioCallbackDriver::Init()
         // XXX Only pass input input if we have an input listener.  Always
         // set up output because it's easier, and it will just get silence.
         // XXX Add support for adding/removing an input listener later.
         cubeb_stream_init(CubebUtils::GetCubebContext(), &stream,
                           "AudioCallbackDriver",
                           input_id,
                           mGraphImpl->mInputWanted ? &input : nullptr,
                           output_id,
-                          mGraphImpl->mOutputWanted ? &output : nullptr, latency,
+                          mGraphImpl->mOutputWanted ? &output : nullptr, latency_frames,
                           DataCallback_s, StateCallback_s, this) == CUBEB_OK) {
       mAudioStream.own(stream);
       DebugOnly<int> rv = cubeb_stream_set_volume(mAudioStream, CubebUtils::GetVolumeScale());
       NS_WARN_IF_FALSE(rv == CUBEB_OK,
           "Could not set the audio stream volume in GraphDriver.cpp");
       CubebUtils::ReportCubebBackendUsed();
     } else {
 #ifdef MOZ_WEBRTC
--- a/dom/media/VideoUtils.cpp
+++ b/dom/media/VideoUtils.cpp
@@ -402,26 +402,16 @@ LogToBrowserConsole(const nsAString& aMs
     NS_WARNING("Failed to log message to console.");
     return;
   }
   nsAutoString msg(aMsg);
   console->LogStringMessage(msg.get());
 }
 
 bool
-IsAACCodecString(const nsAString& aCodec)
-{
-  return
-    aCodec.EqualsLiteral("mp4a.40.2") || // MPEG4 AAC-LC
-    aCodec.EqualsLiteral("mp4a.40.5") || // MPEG4 HE-AAC
-    aCodec.EqualsLiteral("mp4a.67")   || // MPEG2 AAC-LC
-    aCodec.EqualsLiteral("mp4a.40.29");  // MPEG4 HE-AACv2
-}
-
-bool
 ParseCodecsString(const nsAString& aCodecs, nsTArray<nsString>& aOutCodecs)
 {
   aOutCodecs.Clear();
   bool expectMoreTokens = false;
   nsCharSeparatedTokenizer tokenizer(aCodecs, ',');
   while (tokenizer.hasMoreTokens()) {
     const nsSubstring& token = tokenizer.nextToken();
     expectMoreTokens = tokenizer.separatorAfterCurrentToken();
@@ -429,102 +419,57 @@ ParseCodecsString(const nsAString& aCode
   }
   if (expectMoreTokens) {
     // Last codec name was empty
     return false;
   }
   return true;
 }
 
-static bool
-CheckContentType(const nsAString& aContentType,
-                 mozilla::function<bool(const nsAString&)> aSubtypeFilter,
-                 mozilla::function<bool(const nsAString&)> aCodecFilter)
+bool
+ParseMIMETypeString(const nsAString& aMIMEType,
+                    nsString& aOutContainerType,
+                    nsTArray<nsString>& aOutCodecs)
 {
-  nsContentTypeParser parser(aContentType);
-  nsAutoString mimeType;
-  nsresult rv = parser.GetType(mimeType);
-  if (NS_FAILED(rv) || !aSubtypeFilter(mimeType)) {
+  nsContentTypeParser parser(aMIMEType);
+  nsresult rv = parser.GetType(aOutContainerType);
+  if (NS_FAILED(rv)) {
     return false;
   }
 
   nsString codecsStr;
   parser.GetParameter("codecs", codecsStr);
-  nsTArray<nsString> codecs;
-  if (!ParseCodecsString(codecsStr, codecs)) {
-    return false;
-  }
-  for (const nsString& codec : codecs) {
-    if (!aCodecFilter(codec)) {
-      return false;
-    }
-  }
-  return true;
+  return ParseCodecsString(codecsStr, aOutCodecs);
 }
 
 bool
-IsH264ContentType(const nsAString& aContentType)
+IsH264CodecString(const nsAString& aCodec)
 {
-  return CheckContentType(aContentType,
-    [](const nsAString& type) {
-      return type.EqualsLiteral("video/mp4");
-    },
-    [](const nsAString& codec) {
-      int16_t profile = 0;
-      int16_t level = 0;
-      return ExtractH264CodecDetails(codec, profile, level);
-    }
-  );
+  int16_t profile = 0;
+  int16_t level = 0;
+  return ExtractH264CodecDetails(aCodec, profile, level);
 }
 
 bool
-IsAACContentType(const nsAString& aContentType)
+IsAACCodecString(const nsAString& aCodec)
 {
-  return CheckContentType(aContentType,
-    [](const nsAString& type) {
-      return type.EqualsLiteral("audio/mp4") ||
-             type.EqualsLiteral("audio/x-m4a");
-    },
-    [](const nsAString& codec) {
-      return codec.EqualsLiteral("mp4a.40.2") || // MPEG4 AAC-LC
-             codec.EqualsLiteral("mp4a.40.5") || // MPEG4 HE-AAC
-             codec.EqualsLiteral("mp4a.67");     // MPEG2 AAC-LC
-    });
+  return
+    aCodec.EqualsLiteral("mp4a.40.2") || // MPEG4 AAC-LC
+    aCodec.EqualsLiteral("mp4a.40.5") || // MPEG4 HE-AAC
+    aCodec.EqualsLiteral("mp4a.67") || // MPEG2 AAC-LC
+    aCodec.EqualsLiteral("mp4a.40.29");  // MPEG4 HE-AACv2
 }
 
 bool
-IsVorbisContentType(const nsAString& aContentType)
+IsVP8CodecString(const nsAString& aCodec)
 {
-  return CheckContentType(aContentType,
-    [](const nsAString& type) {
-      return type.EqualsLiteral("audio/webm") ||
-             type.EqualsLiteral("audio/ogg");
-    },
-    [](const nsAString& codec) {
-      return codec.EqualsLiteral("vorbis");
-    });
+  return aCodec.EqualsLiteral("vp8") ||
+         aCodec.EqualsLiteral("vp8.0");
 }
 
 bool
-IsVP8ContentType(const nsAString& aContentType)
+IsVP9CodecString(const nsAString& aCodec)
 {
-  return CheckContentType(aContentType,
-    [](const nsAString& type) {
-      return type.EqualsLiteral("video/webm");
-    },
-    [](const nsAString& codec) {
-      return codec.EqualsLiteral("vp8");
-    });
-}
-
-bool
-IsVP9ContentType(const nsAString& aContentType)
-{
-  return CheckContentType(aContentType,
-    [](const nsAString& type) {
-      return type.EqualsLiteral("video/webm");
-    },
-    [](const nsAString& codec) {
-      return codec.EqualsLiteral("vp9");
-    });
+  return aCodec.EqualsLiteral("vp9") ||
+         aCodec.EqualsLiteral("vp9.0");
 }
 
 } // end namespace mozilla
--- a/dom/media/VideoUtils.h
+++ b/dom/media/VideoUtils.h
@@ -318,27 +318,35 @@ private:
   RefPtr<nsIRunnable> mTask;
   nsCOMPtr<nsITimer> mTimer;
 };
 
 void
 LogToBrowserConsole(const nsAString& aMsg);
 
 bool
+ParseMIMETypeString(const nsAString& aMIMEType,
+                    nsString& aOutContainerType,
+                    nsTArray<nsString>& aOutCodecs);
+
+bool
 ParseCodecsString(const nsAString& aCodecs, nsTArray<nsString>& aOutCodecs);
 
 bool
-IsH264ContentType(const nsAString& aContentType);
-
-bool
-IsAACContentType(const nsAString& aContentType);
+IsH264CodecString(const nsAString& aCodec);
 
 bool
 IsAACCodecString(const nsAString& aCodec);
 
+bool
+IsVP8CodecString(const nsAString& aCodec);
+
+bool
+IsVP9CodecString(const nsAString& aCodec);
+
 template <typename String>
 class StringListRange
 {
   typedef typename String::char_type CharType;
   typedef const CharType* Pointer;
 
 public:
   // Iterator into range, trims items and skips empty items.
@@ -442,20 +450,11 @@ StringListContains(const ListString& aLi
   for (const auto& listItem : MakeStringListRange(aList)) {
     if (listItem.Equals(aItem)) {
       return true;
     }
   }
   return false;
 }
 
-bool
-IsVorbisContentType(const nsAString& aContentType);
-
-bool
-IsVP8ContentType(const nsAString& aContentType);
-
-bool
-IsVP9ContentType(const nsAString& aContentType);
-
 } // end namespace mozilla
 
 #endif
--- a/dom/media/eme/CDMProxy.h
+++ b/dom/media/eme/CDMProxy.h
@@ -36,44 +36,50 @@ struct DecryptResult {
 
 // Proxies calls CDM, and proxies calls back.
 // Note: Promises are passed in via a PromiseId, so that the ID can be
 // passed via IPC to the CDM, which can then signal when to reject or
 // resolve the promise using its PromiseId.
 class CDMProxy {
 protected:
   typedef dom::PromiseId PromiseId;
-  typedef dom::SessionType SessionType;
+  typedef dom::MediaKeySessionType MediaKeySessionType;
 public:
 
   NS_IMETHOD_(MozExternalRefCountType) AddRef(void) = 0;
   NS_IMETHOD_(MozExternalRefCountType) Release(void) = 0;
 
   typedef MozPromise<DecryptResult, DecryptResult, /* IsExclusive = */ true> DecryptPromise;
 
   // Main thread only.
-  CDMProxy(dom::MediaKeys* aKeys, const nsAString& aKeySystem)
-  : mKeys(aKeys), mKeySystem(aKeySystem)
+  CDMProxy(dom::MediaKeys* aKeys,
+           const nsAString& aKeySystem,
+           bool aDistinctiveIdentifierRequired,
+           bool aPersistentStateRequired)
+    : mKeys(aKeys)
+    , mKeySystem(aKeySystem)
+    , mDistinctiveIdentifierRequired(aDistinctiveIdentifierRequired)
+    , mPersistentStateRequired(aPersistentStateRequired)
   {}
 
   // Main thread only.
   // Loads the CDM corresponding to mKeySystem.
   // Calls MediaKeys::OnCDMCreated() when the CDM is created.
   virtual void Init(PromiseId aPromiseId,
                     const nsAString& aOrigin,
                     const nsAString& aTopLevelOrigin,
                     const nsAString& aName,
                     bool aInPrivateBrowsing) = 0;
 
   // Main thread only.
   // Uses the CDM to create a key session.
   // Calls MediaKeys::OnSessionActivated() when session is created.
   // Assumes ownership of (Move()s) aInitData's contents.
   virtual void CreateSession(uint32_t aCreateSessionToken,
-                             dom::SessionType aSessionType,
+                             MediaKeySessionType aSessionType,
                              PromiseId aPromiseId,
                              const nsAString& aInitDataType,
                              nsTArray<uint8_t>& aInitData) = 0;
 
   // Main thread only.
   // Uses the CDM to load a presistent session stored on disk.
   // Calls MediaKeys::OnSessionActivated() when session is loaded.
   virtual void LoadSession(PromiseId aPromiseId,
@@ -224,14 +230,17 @@ protected:
 
   // Onwer specified thread. e.g. Gecko Media Plugin thread.
   // All interactions with the out-of-process EME plugin must come from this thread.
   RefPtr<nsIThread> mOwnerThread;
 
   nsCString mNodeId;
 
   CDMCaps mCapabilites;
+
+  const bool mDistinctiveIdentifierRequired;
+  const bool mPersistentStateRequired;
 };
 
 
 } // namespace mozilla
 
 #endif // CDMProxy_h_
--- a/dom/media/eme/MediaKeySession.cpp
+++ b/dom/media/eme/MediaKeySession.cpp
@@ -39,17 +39,17 @@ NS_IMPL_RELEASE_INHERITED(MediaKeySessio
 // unique token.
 static uint32_t sMediaKeySessionNum = 0;
 
 MediaKeySession::MediaKeySession(JSContext* aCx,
                                  nsPIDOMWindowInner* aParent,
                                  MediaKeys* aKeys,
                                  const nsAString& aKeySystem,
                                  const nsAString& aCDMVersion,
-                                 SessionType aSessionType,
+                                 MediaKeySessionType aSessionType,
                                  ErrorResult& aRv)
   : DOMEventTargetHelper(aParent)
   , mKeys(aKeys)
   , mKeySystem(aKeySystem)
   , mCDMVersion(aCDMVersion)
   , mSessionType(aSessionType)
   , mToken(sMediaKeySessionNum++)
   , mIsClosed(false)
@@ -381,17 +381,17 @@ MediaKeySession::Remove(ErrorResult& aRv
   if (!IsCallable()) {
     // If this object's callable value is false, return a promise rejected
     // with a new DOMException whose name is InvalidStateError.
     EME_LOG("MediaKeySession[%p,''] Remove() called before sessionId set by CDM", this);
     promise->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR,
       NS_LITERAL_CSTRING("MediaKeySession.Remove() called before sessionId set by CDM"));
     return promise.forget();
   }
-  if (mSessionType != SessionType::Persistent) {
+  if (mSessionType != MediaKeySessionType::Persistent_license) {
     promise->MaybeReject(NS_ERROR_DOM_INVALID_ACCESS_ERR,
                          NS_LITERAL_CSTRING("Calling MediaKeySession.remove() on non-persistent session"));
     // "The operation is not supported on session type sessions."
     EME_LOG("MediaKeySession[%p,'%s'] Remove() failed, sesion not persisrtent.",
             this, NS_ConvertUTF16toUTF8(mSessionId).get());
     return promise.forget();
   }
   if (IsClosed() || !mKeys->GetCDMProxy()) {
--- a/dom/media/eme/MediaKeySession.h
+++ b/dom/media/eme/MediaKeySession.h
@@ -37,17 +37,17 @@ public:
   NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(MediaKeySession,
                                            DOMEventTargetHelper)
 public:
   MediaKeySession(JSContext* aCx,
                   nsPIDOMWindowInner* aParent,
                   MediaKeys* aKeys,
                   const nsAString& aKeySystem,
                   const nsAString& aCDMVersion,
-                  SessionType aSessionType,
+                  MediaKeySessionType aSessionType,
                   ErrorResult& aRv);
 
   void SetSessionId(const nsAString& aSessionId);
 
   JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
 
   // Mark this as resultNotAddRefed to return raw pointers
   MediaKeyError* GetError() const;
@@ -114,17 +114,17 @@ private:
 
   RefPtr<DetailedPromise> mClosed;
 
   RefPtr<MediaKeyError> mMediaKeyError;
   RefPtr<MediaKeys> mKeys;
   const nsString mKeySystem;
   const nsString mCDMVersion;
   nsString mSessionId;
-  const SessionType mSessionType;
+  const MediaKeySessionType mSessionType;
   const uint32_t mToken;
   bool mIsClosed;
   bool mUninitialized;
   RefPtr<MediaKeyStatusMap> mKeyStatusMap;
   double mExpiration;
 };
 
 } // namespace dom
--- a/dom/media/eme/MediaKeySystemAccess.cpp
+++ b/dom/media/eme/MediaKeySystemAccess.cpp
@@ -28,16 +28,20 @@
 #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"
 #include "WebMDecoder.h"
+#include "mozilla/StaticPtr.h"
+#include "mozilla/ClearOnShutdown.h"
+#include "nsUnicharUtils.h"
+#include "mozilla/dom/MediaSource.h"
 
 namespace mozilla {
 namespace dom {
 
 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(MediaKeySystemAccess,
                                       mParent)
 NS_IMPL_CYCLE_COLLECTING_ADDREF(MediaKeySystemAccess)
 NS_IMPL_CYCLE_COLLECTING_RELEASE(MediaKeySystemAccess)
@@ -83,17 +87,21 @@ void
 MediaKeySystemAccess::GetConfiguration(MediaKeySystemConfiguration& aConfig)
 {
   aConfig = mConfig;
 }
 
 already_AddRefed<Promise>
 MediaKeySystemAccess::CreateMediaKeys(ErrorResult& aRv)
 {
-  RefPtr<MediaKeys> keys(new MediaKeys(mParent, mKeySystem, mCDMVersion));
+  RefPtr<MediaKeys> keys(new MediaKeys(mParent,
+                                       mKeySystem,
+                                       mCDMVersion,
+                                       mConfig.mDistinctiveIdentifier == MediaKeysRequirement::Required,
+                                       mConfig.mPersistentState == MediaKeysRequirement::Required));
   return keys->Init(aRv);
 }
 
 static bool
 HaveGMPFor(mozIGeckoMediaPluginService* aGMPService,
            const nsCString& aKeySystem,
            const nsCString& aAPI,
            const nsCString& aTag = EmptyCString())
@@ -308,262 +316,846 @@ MediaKeySystemAccess::GetKeySystemStatus
       }
       return EnsureMinCDMVersion(mps, aKeySystem, aMinCdmVersion, aOutMessage, aOutCdmVersion);
     }
   }
 
   return MediaKeySystemStatus::Cdm_not_supported;
 }
 
-static bool
-GMPDecryptsAndDecodesAAC(mozIGeckoMediaPluginService* aGMPS,
-                         const nsAString& aKeySystem,
-                         DecoderDoctorDiagnostics* aDiagnostics)
+typedef nsCString GMPCodecString;
+
+#define GMP_CODEC_AAC NS_LITERAL_CSTRING("aac")
+#define GMP_CODEC_OPUS NS_LITERAL_CSTRING("opus")
+#define GMP_CODEC_VORBIS NS_LITERAL_CSTRING("vorbis")
+#define GMP_CODEC_H264 NS_LITERAL_CSTRING("h264")
+#define GMP_CODEC_VP8 NS_LITERAL_CSTRING("vp8")
+#define GMP_CODEC_VP9 NS_LITERAL_CSTRING("vp9")
+
+GMPCodecString
+ToGMPAPICodecString(const nsString& aCodec)
+{
+  if (IsAACCodecString(aCodec)) {
+    return GMP_CODEC_AAC;
+  }
+  if (aCodec.EqualsLiteral("opus")) {
+    return GMP_CODEC_OPUS;
+  }
+  if (aCodec.EqualsLiteral("vorbis")) {
+    return GMP_CODEC_VORBIS;
+  }
+  if (IsH264CodecString(aCodec)) {
+    return GMP_CODEC_H264;
+  }
+  if (IsVP8CodecString(aCodec)) {
+    return GMP_CODEC_VP8;
+  }
+  if (IsVP9CodecString(aCodec)) {
+    return GMP_CODEC_VP9;
+  }
+  return EmptyCString();
+}
+
+// A codec can be decrypted-and-decoded by the CDM, or only decrypted
+// by the CDM and decoded by Gecko. Not both.
+struct KeySystemContainerSupport
+{
+  bool IsSupported() const
+  {
+    return !mCodecsDecoded.IsEmpty() || !mCodecsDecrypted.IsEmpty();
+  }
+
+  // CDM decrypts and decodes using a DRM robust decoder, and passes decoded
+  // samples back to Gecko for rendering.
+  bool DecryptsAndDecodes(GMPCodecString aCodec) const
+  {
+    return mCodecsDecoded.Contains(aCodec);
+  }
+
+  // CDM decrypts and passes the decrypted samples back to Gecko for decoding.
+  bool Decrypts(GMPCodecString aCodec) const
+  {
+    return mCodecsDecrypted.Contains(aCodec);
+  }
+
+  void SetCanDecryptAndDecode(GMPCodecString aCodec)
+  {
+    // Can't both decrypt and decrypt-and-decode a codec.
+    MOZ_ASSERT(!Decrypts(aCodec));
+    // Prevent duplicates.
+    MOZ_ASSERT(!DecryptsAndDecodes(aCodec));
+    mCodecsDecoded.AppendElement(aCodec);
+  }
+
+  void SetCanDecrypt(GMPCodecString aCodec)
+  {
+    // Prevent duplicates.
+    MOZ_ASSERT(!Decrypts(aCodec));
+    // Can't both decrypt and decrypt-and-decode a codec.
+    MOZ_ASSERT(!DecryptsAndDecodes(aCodec));
+    mCodecsDecrypted.AppendElement(aCodec);
+  }
+
+private:
+  nsTArray<GMPCodecString> mCodecsDecoded;
+  nsTArray<GMPCodecString> mCodecsDecrypted;
+};
+
+enum class KeySystemFeatureSupport
+{
+  Prohibited = 1,
+  Requestable = 2,
+  Required = 3,
+};
+
+struct KeySystemConfig
 {
-  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"));
+  nsString mKeySystem;
+  nsTArray<nsString> mInitDataTypes;
+  KeySystemFeatureSupport mPersistentState = KeySystemFeatureSupport::Prohibited;
+  KeySystemFeatureSupport mDistinctiveIdentifier = KeySystemFeatureSupport::Prohibited;
+  nsTArray<MediaKeySessionType> mSessionTypes;
+  nsTArray<nsString> mVideoRobustness;
+  nsTArray<nsString> mAudioRobustness;
+  KeySystemContainerSupport mMP4;
+  KeySystemContainerSupport mWebM;
+};
+
+StaticAutoPtr<nsTArray<KeySystemConfig>> sKeySystemConfigs;
+
+static const nsTArray<KeySystemConfig>&
+GetSupportedKeySystems()
+{
+  if (!sKeySystemConfigs) {
+    sKeySystemConfigs = new nsTArray<KeySystemConfig>();
+    ClearOnShutdown(&sKeySystemConfigs);
+
+    {
+      KeySystemConfig clearkey;
+      clearkey.mKeySystem = NS_LITERAL_STRING("org.w3.clearkey");
+      clearkey.mInitDataTypes.AppendElement(NS_LITERAL_STRING("cenc"));
+      clearkey.mInitDataTypes.AppendElement(NS_LITERAL_STRING("keyids"));
+      clearkey.mInitDataTypes.AppendElement(NS_LITERAL_STRING("webm"));
+      clearkey.mPersistentState = KeySystemFeatureSupport::Requestable;
+      clearkey.mDistinctiveIdentifier = KeySystemFeatureSupport::Prohibited;
+      clearkey.mSessionTypes.AppendElement(MediaKeySessionType::Temporary);
+      clearkey.mSessionTypes.AppendElement(MediaKeySessionType::Persistent_license);
+#if defined(XP_WIN)
+      // Clearkey CDM uses WMF decoders on Windows.
+      if (WMFDecoderModule::HasAAC()) {
+        clearkey.mMP4.SetCanDecryptAndDecode(GMP_CODEC_AAC);
+      } else {
+        clearkey.mMP4.SetCanDecrypt(GMP_CODEC_AAC);
+      }
+      if (WMFDecoderModule::HasH264()) {
+        clearkey.mMP4.SetCanDecryptAndDecode(GMP_CODEC_H264);
+      } else {
+        clearkey.mMP4.SetCanDecrypt(GMP_CODEC_H264);
+      }
+#else
+      clearkey.mMP4.SetCanDecrypt(GMP_CODEC_AAC);
+      clearkey.mMP4.SetCanDecrypt(GMP_CODEC_H264);
+#endif
+      clearkey.mWebM.SetCanDecrypt(GMP_CODEC_VORBIS);
+      clearkey.mWebM.SetCanDecrypt(GMP_CODEC_OPUS);
+      clearkey.mWebM.SetCanDecrypt(GMP_CODEC_VP8);
+      clearkey.mWebM.SetCanDecrypt(GMP_CODEC_VP9);
+      sKeySystemConfigs->AppendElement(Move(clearkey));
+    }
+    {
+      KeySystemConfig widevine;
+      widevine.mKeySystem = NS_LITERAL_STRING("com.widevine.alpha");
+      widevine.mInitDataTypes.AppendElement(NS_LITERAL_STRING("cenc"));
+      widevine.mInitDataTypes.AppendElement(NS_LITERAL_STRING("keyids"));
+      widevine.mInitDataTypes.AppendElement(NS_LITERAL_STRING("webm"));
+      widevine.mPersistentState = KeySystemFeatureSupport::Requestable;
+      widevine.mDistinctiveIdentifier = KeySystemFeatureSupport::Prohibited;
+      widevine.mSessionTypes.AppendElement(MediaKeySessionType::Temporary);
+      widevine.mAudioRobustness.AppendElement(NS_LITERAL_STRING("SW_SECURE_CRYPTO"));
+      widevine.mVideoRobustness.AppendElement(NS_LITERAL_STRING("SW_SECURE_DECODE"));
+#if 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.
+      if (WMFDecoderModule::HasAAC()) {
+        widevine.mMP4.SetCanDecrypt(GMP_CODEC_AAC);
+      }
+#else
+      widevine.mMP4.SetCanDecrypt(GMP_CODEC_AAC);
+#endif
+      widevine.mMP4.SetCanDecryptAndDecode(GMP_CODEC_H264);
+      // TODO: Enable Widevine/WebM once bug 1279077 lands.
+      //widevine.mWebM.SetCanDecrypt(GMP_CODEC_VORBIS);
+      //widevine.mWebM.SetCanDecrypt(GMP_CODEC_OPUS);
+      //widevine.mWebM.SetCanDecryptAndDecode(GMP_CODEC_VP8);
+      //widevine.mWebM.SetCanDecryptAndDecode(GMP_CODEC_VP9);
+      sKeySystemConfigs->AppendElement(Move(widevine));
+    }
+    {
+      KeySystemConfig primetime;
+      primetime.mKeySystem = NS_LITERAL_STRING("com.adobe.primetime");
+      primetime.mInitDataTypes.AppendElement(NS_LITERAL_STRING("cenc"));
+      primetime.mPersistentState = KeySystemFeatureSupport::Required;
+      primetime.mDistinctiveIdentifier = KeySystemFeatureSupport::Required;
+      primetime.mSessionTypes.AppendElement(MediaKeySessionType::Temporary);
+      primetime.mMP4.SetCanDecryptAndDecode(GMP_CODEC_AAC);
+      primetime.mMP4.SetCanDecryptAndDecode(GMP_CODEC_H264);
+      sKeySystemConfigs->AppendElement(Move(primetime));
+    }
+  }
+  return *sKeySystemConfigs;
 }
 
+static const KeySystemConfig*
+GetKeySystemConfig(const nsAString& aKeySystem)
+{
+  for (const KeySystemConfig& config : GetSupportedKeySystems()) {
+    if (config.mKeySystem.Equals(aKeySystem)) {
+      return &config;
+    }
+  }
+  return nullptr;
+}
+
+enum CodecType
+{
+  Audio,
+  Video,
+  Invalid
+};
+
 static bool
-GMPDecryptsAndDecodesH264(mozIGeckoMediaPluginService* aGMPS,
-                          const nsAString& aKeySystem,
-                          DecoderDoctorDiagnostics* aDiagnostics)
+CanDecryptAndDecode(mozIGeckoMediaPluginService* aGMPService,
+                    const nsString& aKeySystem,
+                    const nsString& aContentType,
+                    CodecType aCodecType,
+                    const KeySystemContainerSupport& aContainerSupport,
+                    const nsTArray<GMPCodecString>& aCodecs,
+                    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,
-                               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, aDiagnostics);
-}
-
-static bool
-GMPDecryptsAndGeckoDecodesAAC(mozIGeckoMediaPluginService* aGMPService,
-                              const nsAString& aKeySystem,
-                              const nsAString& aContentType,
-                              DecoderDoctorDiagnostics* aDiagnostics)
-{
+  MOZ_ASSERT(aCodecType != Invalid);
   MOZ_ASSERT(HaveGMPFor(aGMPService,
                         NS_ConvertUTF16toUTF8(aKeySystem),
                         NS_LITERAL_CSTRING(GMP_API_DECRYPTOR)));
-  MOZ_ASSERT(IsAACContentType(aContentType));
+  for (const GMPCodecString& codec : aCodecs) {
+    MOZ_ASSERT(!codec.IsEmpty());
+
+    nsCString api = (aCodecType == Audio) ? NS_LITERAL_CSTRING(GMP_API_AUDIO_DECODER)
+                                          : NS_LITERAL_CSTRING(GMP_API_VIDEO_DECODER);
+    if (aContainerSupport.DecryptsAndDecodes(codec) &&
+        HaveGMPFor(aGMPService,
+                   NS_ConvertUTF16toUTF8(aKeySystem),
+                   api,
+                   codec)) {
+      // GMP can decrypt-and-decode this codec.
+      continue;
+    }
 
-  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.
+    if (aContainerSupport.Decrypts(codec) &&
+        NS_SUCCEEDED(MediaSource::IsTypeSupported(aContentType, aDiagnostics))) {
+      // GMP can decrypt and is allowed to return compressed samples to
+      // Gecko to decode, and Gecko has a decoder.
+      continue;
+    }
+
+    // Neither the GMP nor Gecko can both decrypt and decode. We don't
+    // support this codec.
+
+#if 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.
+    if (codec == GMP_CODEC_AAC &&
+        aKeySystem.EqualsLiteral("com.widevine.alpha") &&
+        !WMFDecoderModule::HasAAC()) {
+      if (aDiagnostics) {
+        aDiagnostics->SetKeySystemIssue(
+          DecoderDoctorDiagnostics::eWidevineWithNoWMF);
+      }
+    }
+#endif
     return false;
   }
-#if 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.
-  if (aKeySystem.EqualsLiteral("com.widevine.alpha") &&
-      !WMFDecoderModule::HasAAC()) {
-    if (aDiagnostics) {
-      aDiagnostics->SetKeySystemIssue(
-        DecoderDoctorDiagnostics::eWidevineWithNoWMF);
-    }
-    return false;
-  }
-#endif
-  return MP4Decoder::CanHandleMediaType(aContentType, aDiagnostics);
-}
-
-static bool
-GMPDecryptsAndGeckoDecodesVorbis(mozIGeckoMediaPluginService* aGMPService,
-                                const nsAString& aKeySystem,
-                                const nsAString& aContentType,
-                                DecoderDoctorDiagnostics* aDiagnostics)
-{
-  MOZ_ASSERT(HaveGMPFor(aGMPService,
-             NS_ConvertUTF16toUTF8(aKeySystem),
-             NS_LITERAL_CSTRING(GMP_API_DECRYPTOR)));
-  MOZ_ASSERT(IsVorbisContentType(aContentType));
-  return !HaveGMPFor(aGMPService,
-                     NS_ConvertUTF16toUTF8(aKeySystem),
-                     NS_LITERAL_CSTRING(GMP_API_AUDIO_DECODER),
-                     NS_LITERAL_CSTRING("vorbis")) &&
-         WebMDecoder::CanHandleMediaType(aContentType);
+  return true;
 }
 
 static bool
-GMPDecryptsAndGeckoDecodesVP8(mozIGeckoMediaPluginService* aGMPService,
-                             const nsAString& aKeySystem,
-                             const nsAString& aContentType,
-                             DecoderDoctorDiagnostics* aDiagnostics)
-{
-  MOZ_ASSERT(HaveGMPFor(aGMPService,
-             NS_ConvertUTF16toUTF8(aKeySystem),
-             NS_LITERAL_CSTRING(GMP_API_DECRYPTOR)));
-  MOZ_ASSERT(IsVP8ContentType(aContentType));
-  return !HaveGMPFor(aGMPService,
-                     NS_ConvertUTF16toUTF8(aKeySystem),
-                     NS_LITERAL_CSTRING(GMP_API_VIDEO_DECODER),
-                     NS_LITERAL_CSTRING("vp8")) &&
-         WebMDecoder::CanHandleMediaType(aContentType);
-}
-
-static bool
-GMPDecryptsAndGeckoDecodesVP9(mozIGeckoMediaPluginService* aGMPService,
-                             const nsAString& aKeySystem,
-                             const nsAString& aContentType,
-                             DecoderDoctorDiagnostics* aDiagnostics)
+ToSessionType(const nsAString& aSessionType, MediaKeySessionType& aOutType)
 {
-  MOZ_ASSERT(HaveGMPFor(aGMPService,
-             NS_ConvertUTF16toUTF8(aKeySystem),
-             NS_LITERAL_CSTRING(GMP_API_DECRYPTOR)));
-  MOZ_ASSERT(IsVP9ContentType(aContentType));
-  return !HaveGMPFor(aGMPService,
-                     NS_ConvertUTF16toUTF8(aKeySystem),
-                     NS_LITERAL_CSTRING(GMP_API_VIDEO_DECODER),
-                     NS_LITERAL_CSTRING("vp9")) &&
-         WebMDecoder::CanHandleMediaType(aContentType);
-}
-
-static bool
-IsSupportedAudio(mozIGeckoMediaPluginService* aGMPService,
-                 const nsAString& aKeySystem,
-                 const nsAString& aAudioType,
-                 DecoderDoctorDiagnostics* aDiagnostics)
-{
-  if (IsAACContentType(aAudioType)) {
-    return GMPDecryptsAndDecodesAAC(aGMPService, aKeySystem, aDiagnostics) ||
-           GMPDecryptsAndGeckoDecodesAAC(aGMPService, aKeySystem, aAudioType, aDiagnostics);
+  using MediaKeySessionTypeValues::strings;
+  const char* temporary =
+    strings[static_cast<uint32_t>(MediaKeySessionType::Temporary)].value;
+  if (aSessionType.EqualsASCII(temporary)) {
+    aOutType = MediaKeySessionType::Temporary;
+    return true;
   }
-  if (IsVorbisContentType(aAudioType) && aKeySystem.EqualsLiteral("org.w3.clearkey")) {
-    // GMP does not decode Vorbis, so don't bother checking
-    return GMPDecryptsAndGeckoDecodesVorbis(aGMPService, aKeySystem, aAudioType, aDiagnostics);
+  const char* persistentLicense =
+    strings[static_cast<uint32_t>(MediaKeySessionType::Persistent_license)].value;
+  if (aSessionType.EqualsASCII(persistentLicense)) {
+    aOutType = MediaKeySessionType::Persistent_license;
+    return true;
   }
   return false;
 }
 
+// 5.2.1 Is persistent session type?
 static bool
-IsSupportedVideo(mozIGeckoMediaPluginService* aGMPService,
-                 const nsAString& aKeySystem,
-                 const nsAString& aVideoType,
-                 DecoderDoctorDiagnostics* aDiagnostics)
+IsPersistentSessionType(MediaKeySessionType aSessionType)
+{
+  return aSessionType == MediaKeySessionType::Persistent_license;
+}
+
+CodecType
+GetMajorType(const nsAString& aContentType)
 {
-  if (IsH264ContentType(aVideoType)) {
-    return GMPDecryptsAndDecodesH264(aGMPService, aKeySystem, aDiagnostics) ||
-           GMPDecryptsAndGeckoDecodesH264(aGMPService, aKeySystem, aVideoType, aDiagnostics);
+  if (CaseInsensitiveFindInReadable(NS_LITERAL_STRING("audio/"), aContentType)) {
+    return Audio;
+  }
+  if (CaseInsensitiveFindInReadable(NS_LITERAL_STRING("video/"), aContentType)) {
+    return Video;
   }
-  if (IsVP8ContentType(aVideoType) && aKeySystem.EqualsLiteral("org.w3.clearkey")) {
-    return GMPDecryptsAndGeckoDecodesVP8(aGMPService, aKeySystem, aVideoType, aDiagnostics);
+  return Invalid;
+}
+
+static CodecType
+GetCodecType(const GMPCodecString& aCodec)
+{
+  if (aCodec.Equals(GMP_CODEC_AAC) ||
+      aCodec.Equals(GMP_CODEC_OPUS) ||
+      aCodec.Equals(GMP_CODEC_VORBIS)) {
+    return Audio;
   }
-  if (IsVP9ContentType(aVideoType) && aKeySystem.EqualsLiteral("org.w3.clearkey")) {
-    return GMPDecryptsAndGeckoDecodesVP9(aGMPService, aKeySystem, aVideoType, aDiagnostics);
+  if (aCodec.Equals(GMP_CODEC_H264) ||
+      aCodec.Equals(GMP_CODEC_VP8) ||
+      aCodec.Equals(GMP_CODEC_VP9)) {
+    return Video;
   }
-  return false;
+  return Invalid;
 }
 
 static bool
-IsSupportedInitDataType(const nsString& aCandidate, const nsAString& aKeySystem)
+AllCodecsOfType(const nsTArray<GMPCodecString>& aCodecs, const CodecType aCodecType)
 {
-  // All supported keySystems can handle "cenc" initDataType.
-  // ClearKey also supports "keyids" and "webm" initDataTypes.
-  return aCandidate.EqualsLiteral("cenc") ||
-    ((aKeySystem.EqualsLiteral("org.w3.clearkey")
-    || aKeySystem.EqualsLiteral("com.widevine.alpha")) &&
-    (aCandidate.EqualsLiteral("keyids") || aCandidate.EqualsLiteral("webm")));
+  for (const GMPCodecString& codec : aCodecs) {
+    if (GetCodecType(codec) != aCodecType) {
+      return false;
+    }
+  }
+  return true;
 }
 
+// 3.1.2.3 Get Supported Capabilities for Audio/Video Type
+static Sequence<MediaKeySystemMediaCapability>
+GetSupportedCapabilities(const CodecType aCodecType,
+                         mozIGeckoMediaPluginService* aGMPService,
+                         const nsTArray<MediaKeySystemMediaCapability>& aRequestedCapabilities,
+                         const MediaKeySystemConfiguration& aPartialConfig,
+                         const KeySystemConfig& aKeySystem,
+                         DecoderDoctorDiagnostics* aDiagnostics)
+{
+  // Let local accumulated configuration be a local copy of partial configuration.
+  // (Note: It's not necessary for us to maintain a local copy, as we don't need
+  // to test whether capabilites from previous calls to this algorithm work with
+  // the capabilities currently being considered in this call. )
+
+  // Let supported media capabilities be an empty sequence of
+  // MediaKeySystemMediaCapability dictionaries.
+  Sequence<MediaKeySystemMediaCapability> supportedCapabilities;
+
+  // For each requested media capability in requested media capabilities:
+  for (const MediaKeySystemMediaCapability& capabilities : aRequestedCapabilities) {
+    // Let content type be requested media capability's contentType member.
+    const nsString& contentType = capabilities.mContentType;
+    // Let robustness be requested media capability's robustness member.
+    const nsString& robustness = capabilities.mRobustness;
+    // If content type is the empty string, return null.
+    if (contentType.IsEmpty()) {
+      EME_LOG("MediaKeySystemConfiguration (label='%s') "
+              "MediaKeySystemMediaCapability('%s','%s') rejected; "
+              "audio or video capability has empty contentType.",
+              NS_ConvertUTF16toUTF8(aPartialConfig.mLabel).get(),
+              NS_ConvertUTF16toUTF8(contentType).get(),
+              NS_ConvertUTF16toUTF8(robustness).get());
+      return Sequence<MediaKeySystemMediaCapability>();
+    }
+    // If content type is an invalid or unrecognized MIME type, continue
+    // to the next iteration.
+    nsAutoString container;
+    nsTArray<nsString> codecStrings;
+    if (!ParseMIMETypeString(contentType, container, codecStrings)) {
+      EME_LOG("MediaKeySystemConfiguration (label='%s') "
+              "MediaKeySystemMediaCapability('%s','%s') unsupported; "
+              "failed to parse contentType as MIME type.",
+              NS_ConvertUTF16toUTF8(aPartialConfig.mLabel).get(),
+              NS_ConvertUTF16toUTF8(contentType).get(),
+              NS_ConvertUTF16toUTF8(robustness).get());
+      continue;
+    }
+    bool invalid = false;
+    nsTArray<GMPCodecString> codecs;
+    for (const nsString& codecString : codecStrings) {
+      GMPCodecString gmpCodec = ToGMPAPICodecString(codecString);
+      if (gmpCodec.IsEmpty()) {
+        invalid = true;
+        EME_LOG("MediaKeySystemConfiguration (label='%s') "
+                "MediaKeySystemMediaCapability('%s','%s') unsupported; "
+                "'%s' is an invalid codec string.",
+                NS_ConvertUTF16toUTF8(aPartialConfig.mLabel).get(),
+                NS_ConvertUTF16toUTF8(contentType).get(),
+                NS_ConvertUTF16toUTF8(robustness).get(),
+                NS_ConvertUTF16toUTF8(codecString).get());
+        break;
+      }
+      codecs.AppendElement(gmpCodec);
+    }
+    if (invalid) {
+      continue;
+    }
+
+    // If the user agent does not support container, continue to the next iteration.
+    // The case-sensitivity of string comparisons is determined by the appropriate RFC.
+    // (Note: Per RFC 6838 [RFC6838], "Both top-level type and subtype names are
+    // case-insensitive."'. We're using nsContentTypeParser and that is
+    // case-insensitive and converts all its parameter outputs to lower case.)
+    NS_ConvertUTF16toUTF8 container_utf8(container);
+    const bool isMP4 = DecoderTraits::IsMP4TypeAndEnabled(container_utf8, aDiagnostics);
+    if (isMP4 && !aKeySystem.mMP4.IsSupported()) {
+      EME_LOG("MediaKeySystemConfiguration (label='%s') "
+              "MediaKeySystemMediaCapability('%s','%s') unsupported; "
+              "MP4 requested but unsupported.",
+              NS_ConvertUTF16toUTF8(aPartialConfig.mLabel).get(),
+              NS_ConvertUTF16toUTF8(contentType).get(),
+              NS_ConvertUTF16toUTF8(robustness).get());
+      continue;
+    }
+    const bool isWebM = DecoderTraits::IsWebMTypeAndEnabled(container_utf8);
+    if (isWebM && !aKeySystem.mWebM.IsSupported()) {
+      EME_LOG("MediaKeySystemConfiguration (label='%s') "
+              "MediaKeySystemMediaCapability('%s','%s') unsupported; "
+              "WebM requested but unsupported.",
+              NS_ConvertUTF16toUTF8(aPartialConfig.mLabel).get(),
+              NS_ConvertUTF16toUTF8(contentType).get(),
+              NS_ConvertUTF16toUTF8(robustness).get());
+      continue;
+    }
+    if (!isMP4 && !isWebM) {
+      EME_LOG("MediaKeySystemConfiguration (label='%s') "
+              "MediaKeySystemMediaCapability('%s','%s') unsupported; "
+              "Unsupported or unrecognized container requested.",
+              NS_ConvertUTF16toUTF8(aPartialConfig.mLabel).get(),
+              NS_ConvertUTF16toUTF8(contentType).get(),
+              NS_ConvertUTF16toUTF8(robustness).get());
+      continue;
+    }
+
+    // Let parameters be the RFC 6381[RFC6381] parameters, if any, specified by
+    // content type.
+    // If the user agent does not recognize one or more parameters, continue to
+    // the next iteration.
+    // Let media types be the set of codecs and codec constraints specified by
+    // parameters. The case-sensitivity of string comparisons is determined by
+    // the appropriate RFC or other specification.
+    // (Note: codecs array is 'parameter').
+
+    // If media types is empty:
+    if (codecs.IsEmpty()) {
+      // If container normatively implies a specific set of codecs and codec constraints:
+      // Let parameters be that set.
+      if (isMP4) {
+        if (aCodecType == Audio) {
+          codecs.AppendElement(GMP_CODEC_AAC);
+        } else if (aCodecType == Video) {
+          codecs.AppendElement(GMP_CODEC_H264);
+        }
+      } else if (isWebM) {
+        if (aCodecType == Audio) {
+          codecs.AppendElement(GMP_CODEC_VORBIS);
+        } else if (aCodecType == Video) {
+          codecs.AppendElement(GMP_CODEC_VP8);
+        }
+      }
+      // Otherwise: Continue to the next iteration.
+      // (Note: all containers we support have implied codecs, so don't continue here.)
+    }
+
+    // If content type is not strictly a audio/video type, continue to the next iteration.
+    const auto majorType = GetMajorType(container);
+    if (majorType == Invalid) {
+      EME_LOG("MediaKeySystemConfiguration (label='%s') "
+              "MediaKeySystemMediaCapability('%s','%s') unsupported; "
+              "MIME type is not an audio or video MIME type.",
+              NS_ConvertUTF16toUTF8(aPartialConfig.mLabel).get(),
+              NS_ConvertUTF16toUTF8(contentType).get(),
+              NS_ConvertUTF16toUTF8(robustness).get());
+      continue;
+    }
+    if (majorType != aCodecType || !AllCodecsOfType(codecs, aCodecType)) {
+      EME_LOG("MediaKeySystemConfiguration (label='%s') "
+              "MediaKeySystemMediaCapability('%s','%s') unsupported; "
+              "MIME type mixes audio codecs in video capabilities "
+              "or video codecs in audio capabilities.",
+              NS_ConvertUTF16toUTF8(aPartialConfig.mLabel).get(),
+              NS_ConvertUTF16toUTF8(contentType).get(),
+              NS_ConvertUTF16toUTF8(robustness).get());
+      continue;
+    }
+    // If robustness is not the empty string and contains an unrecognized
+    // value or a value not supported by implementation, continue to the
+    // next iteration. String comparison is case-sensitive.
+    if (!robustness.IsEmpty()) {
+      if (majorType == Audio && !aKeySystem.mAudioRobustness.Contains(robustness)) {
+        EME_LOG("MediaKeySystemConfiguration (label='%s') "
+                "MediaKeySystemMediaCapability('%s','%s') unsupported; "
+                "unsupported robustness string.",
+                NS_ConvertUTF16toUTF8(aPartialConfig.mLabel).get(),
+                NS_ConvertUTF16toUTF8(contentType).get(),
+                NS_ConvertUTF16toUTF8(robustness).get());
+        continue;
+      }
+      if (majorType == Video && !aKeySystem.mVideoRobustness.Contains(robustness)) {
+        EME_LOG("MediaKeySystemConfiguration (label='%s') "
+                "MediaKeySystemMediaCapability('%s','%s') unsupported; "
+                "unsupported robustness string.",
+                NS_ConvertUTF16toUTF8(aPartialConfig.mLabel).get(),
+                NS_ConvertUTF16toUTF8(contentType).get(),
+                NS_ConvertUTF16toUTF8(robustness).get());
+        continue;
+      }
+      // Note: specified robustness requirements are satisfied.
+    }
+
+    // If the user agent and implementation definitely support playback of
+    // encrypted media data for the combination of container, media types,
+    // robustness and local accumulated configuration in combination with
+    // restrictions...
+    const auto& containerSupport = isMP4 ? aKeySystem.mMP4 : aKeySystem.mWebM;
+    if (!CanDecryptAndDecode(aGMPService,
+                             aKeySystem.mKeySystem,
+                             contentType,
+                             majorType,
+                             containerSupport,
+                             codecs,
+                             aDiagnostics)) {
+        EME_LOG("MediaKeySystemConfiguration (label='%s') "
+                "MediaKeySystemMediaCapability('%s','%s') unsupported; "
+                "codec unsupported by CDM requested.",
+                NS_ConvertUTF16toUTF8(aPartialConfig.mLabel).get(),
+                NS_ConvertUTF16toUTF8(contentType).get(),
+                NS_ConvertUTF16toUTF8(robustness).get());
+        continue;
+    }
+
+    // ... add requested media capability to supported media capabilities.
+    if (!supportedCapabilities.AppendElement(capabilities, mozilla::fallible)) {
+      NS_WARNING("GetSupportedCapabilities: Malloc failure");
+      return Sequence<MediaKeySystemMediaCapability>();
+    }
+
+    // Note: omitting steps 3.13.2, our robustness is not sophisticated enough
+    // to require considering all requirements together.
+  }
+  return Move(supportedCapabilities);
+}
+
+// "Get Supported Configuration and Consent" algorithm, steps 4-7 for
+// distinctive identifier, and steps 8-11 for persistent state. The steps
+// are the same for both requirements/features, so we factor them out into
+// a single function.
+static bool
+CheckRequirement(const MediaKeysRequirement aRequirement,
+                 const KeySystemFeatureSupport aFeatureSupport,
+                 MediaKeysRequirement& aOutRequirement)
+{
+  // Let requirement be the value of candidate configuration's member.
+  MediaKeysRequirement requirement = aRequirement;
+  // If requirement is "optional" and feature is not allowed according to
+  // restrictions, set requirement to "not-allowed".
+  if (aRequirement == MediaKeysRequirement::Optional &&
+      aFeatureSupport == KeySystemFeatureSupport::Prohibited) {
+    requirement = MediaKeysRequirement::Not_allowed;
+  }
+
+  // Follow the steps for requirement from the following list:
+  switch (requirement) {
+    case MediaKeysRequirement::Required: {
+      // If the implementation does not support use of requirement in combination
+      // with accumulated configuration and restrictions, return NotSupported.
+      if (aFeatureSupport == KeySystemFeatureSupport::Prohibited) {
+        return false;
+      }
+      break;
+    }
+    case MediaKeysRequirement::Optional: {
+      // Continue with the following steps.
+      break;
+    }
+    case MediaKeysRequirement::Not_allowed: {
+      // If the implementation requires use of feature in combination with
+      // accumulated configuration and restrictions, return NotSupported.
+      if (aFeatureSupport == KeySystemFeatureSupport::Required) {
+        return false;
+      }
+      break;
+    }
+    default: {
+      return false;
+    }
+  }
+
+  // Set the requirement member of accumulated configuration to equal
+  // calculated requirement.
+  aOutRequirement = requirement;
+
+  return true;
+}
+
+// 3.1.2.2, step 12
+// Follow the steps for the first matching condition from the following list:
+// If the sessionTypes member is present in candidate configuration.
+// Let session types be candidate configuration's sessionTypes member.
+// Otherwise let session types be ["temporary"].
+// Note: This returns an empty array on malloc failure.
+static Sequence<nsString>
+UnboxSessionTypes(const Optional<Sequence<nsString>>& aSessionTypes)
+{
+  Sequence<nsString> sessionTypes;
+  if (aSessionTypes.WasPassed()) {
+    sessionTypes = aSessionTypes.Value();
+  } else {
+    using MediaKeySessionTypeValues::strings;
+    const char* temporary = strings[static_cast<uint32_t>(MediaKeySessionType::Temporary)].value;
+    // Note: fallible. Results in an empty array.
+    sessionTypes.AppendElement(NS_ConvertUTF8toUTF16(nsDependentCString(temporary)), mozilla::fallible);
+  }
+  return sessionTypes;
+}
+
+// 3.1.2.2 Get Supported Configuration and Consent
 static bool
 GetSupportedConfig(mozIGeckoMediaPluginService* aGMPService,
-                   const nsAString& aKeySystem,
+                   const KeySystemConfig& aKeySystem,
                    const MediaKeySystemConfiguration& aCandidate,
                    MediaKeySystemConfiguration& aOutConfig,
                    DecoderDoctorDiagnostics* aDiagnostics)
 {
+  // Let accumulated configuration be a new MediaKeySystemConfiguration dictionary.
   MediaKeySystemConfiguration config;
+  // Set the label member of accumulated configuration to equal the label member of
+  // candidate configuration.
   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);
+  // If the initDataTypes member of candidate configuration is non-empty, run the
+  // following steps:
+  if (!aCandidate.mInitDataTypes.IsEmpty()) {
+    // Let supported types be an empty sequence of DOMStrings.
+    nsTArray<nsString> supportedTypes;
+    // For each value in candidate configuration's initDataTypes member:
+    for (const nsString& initDataType : aCandidate.mInitDataTypes) {
+      // Let initDataType be the value.
+      // If the implementation supports generating requests based on initDataType,
+      // add initDataType to supported types. String comparison is case-sensitive.
+      // The empty string is never supported.
+      if (aKeySystem.mInitDataTypes.Contains(initDataType)) {
+        supportedTypes.AppendElement(initDataType);
       }
     }
-    if (initDataTypes.IsEmpty()) {
+    // If supported types is empty, return NotSupported.
+    if (supportedTypes.IsEmpty()) {
+      EME_LOG("MediaKeySystemConfiguration (label='%s') rejected; "
+              "no supported initDataTypes provided.",
+              NS_ConvertUTF16toUTF8(aCandidate.mLabel).get());
+      return false;
+    }
+    // Set the initDataTypes member of accumulated configuration to supported types.
+    if (!config.mInitDataTypes.Assign(supportedTypes)) {
+      return false;
+    }
+  }
+
+  if (!CheckRequirement(aCandidate.mDistinctiveIdentifier,
+                        aKeySystem.mDistinctiveIdentifier,
+                        config.mDistinctiveIdentifier)) {
+    EME_LOG("MediaKeySystemConfiguration (label='%s') rejected; "
+            "distinctiveIdentifier requirement not satisfied.",
+            NS_ConvertUTF16toUTF8(aCandidate.mLabel).get());
+    return false;
+  }
+
+  if (!CheckRequirement(aCandidate.mPersistentState,
+                        aKeySystem.mPersistentState,
+                        config.mPersistentState)) {
+    EME_LOG("MediaKeySystemConfiguration (label='%s') rejected; "
+            "persistentState requirement not satisfied.",
+            NS_ConvertUTF16toUTF8(aCandidate.mLabel).get());
+    return false;
+  }
+
+  Sequence<nsString> sessionTypes(UnboxSessionTypes(aCandidate.mSessionTypes));
+  if (sessionTypes.IsEmpty()) {
+    // Malloc failure.
+    return false;
+  }
+
+  // For each value in session types:
+  for (const auto& sessionTypeString : sessionTypes) {
+    // Let session type be the value.
+    MediaKeySessionType sessionType;
+    if (!ToSessionType(sessionTypeString, sessionType)) {
+      // (Assume invalid sessionType is unsupported as per steps below).
+      EME_LOG("MediaKeySystemConfiguration (label='%s') rejected; "
+              "invalid session type specified.",
+              NS_ConvertUTF16toUTF8(aCandidate.mLabel).get());
+      return false;
+    }
+    // If accumulated configuration's persistentState value is "not-allowed"
+    // and the Is persistent session type? algorithm returns true for session
+    // type return NotSupported.
+    if (config.mPersistentState == MediaKeysRequirement::Not_allowed &&
+        IsPersistentSessionType(sessionType)) {
+      EME_LOG("MediaKeySystemConfiguration (label='%s') rejected; "
+              "persistent session requested but keysystem doesn't"
+              "support persistent state.",
+              NS_ConvertUTF16toUTF8(aCandidate.mLabel).get());
+      return false;
+    }
+    // If the implementation does not support session type in combination
+    // with accumulated configuration and restrictions for other reasons,
+    // return NotSupported.
+    if (!aKeySystem.mSessionTypes.Contains(sessionType)) {
+      EME_LOG("MediaKeySystemConfiguration (label='%s') rejected; "
+              "session type '%s' unsupported by keySystem.",
+              NS_ConvertUTF16toUTF8(aCandidate.mLabel).get(),
+              NS_ConvertUTF16toUTF8(sessionTypeString).get());
       return false;
     }
-    config.mInitDataTypes.Construct();
-    config.mInitDataTypes.Value().Assign(initDataTypes);
+    // If accumulated configuration's persistentState value is "optional"
+    // and the result of running the Is persistent session type? algorithm
+    // on session type is true, change accumulated configuration's
+    // persistentState value to "required".
+    if (config.mPersistentState == MediaKeysRequirement::Optional &&
+        IsPersistentSessionType(sessionType)) {
+      config.mPersistentState = MediaKeysRequirement::Required;
+    }
   }
-  if (aCandidate.mAudioCapabilities.WasPassed()) {
-    nsTArray<MediaKeySystemMediaCapability> caps;
-    for (const MediaKeySystemMediaCapability& cap : aCandidate.mAudioCapabilities.Value()) {
-      if (IsSupportedAudio(aGMPService, aKeySystem, cap.mContentType, aDiagnostics)) {
-        caps.AppendElement(cap);
-      }
-    }
+  // Set the sessionTypes member of accumulated configuration to session types.
+  config.mSessionTypes.Construct(Move(sessionTypes));
+
+  // If the videoCapabilities and audioCapabilities members in candidate
+  // configuration are both empty, return NotSupported.
+  // TODO: Most sites using EME still don't pass capabilities, so we
+  // can't reject on it yet without breaking them. So add this later.
+
+  // If the videoCapabilities member in candidate configuration is non-empty:
+  if (!aCandidate.mVideoCapabilities.IsEmpty()) {
+    // Let video capabilities be the result of executing the Get Supported
+    // Capabilities for Audio/Video Type algorithm on Video, candidate
+    // configuration's videoCapabilities member, accumulated configuration,
+    // and restrictions.
+    Sequence<MediaKeySystemMediaCapability> caps =
+      GetSupportedCapabilities(Video,
+                               aGMPService,
+                               aCandidate.mVideoCapabilities,
+                               config,
+                               aKeySystem,
+                               aDiagnostics);
+    // If video capabilities is null, return NotSupported.
     if (caps.IsEmpty()) {
+      EME_LOG("MediaKeySystemConfiguration (label='%s') rejected; "
+              "no supported video capabilities.",
+              NS_ConvertUTF16toUTF8(aCandidate.mLabel).get());
       return false;
     }
-    config.mAudioCapabilities.Construct();
-    config.mAudioCapabilities.Value().Assign(caps);
+    // Set the videoCapabilities member of accumulated configuration to video capabilities.
+    config.mVideoCapabilities = Move(caps);
+  } else {
+    // Otherwise:
+    // Set the videoCapabilities member of accumulated configuration to an empty sequence.
   }
-  if (aCandidate.mVideoCapabilities.WasPassed()) {
-    nsTArray<MediaKeySystemMediaCapability> caps;
-    for (const MediaKeySystemMediaCapability& cap : aCandidate.mVideoCapabilities.Value()) {
-      if (IsSupportedVideo(aGMPService, aKeySystem, cap.mContentType, aDiagnostics)) {
-        caps.AppendElement(cap);
-      }
-    }
+
+  // If the audioCapabilities member in candidate configuration is non-empty:
+  if (!aCandidate.mAudioCapabilities.IsEmpty()) {
+    // Let audio capabilities be the result of executing the Get Supported Capabilities
+    // for Audio/Video Type algorithm on Audio, candidate configuration's audioCapabilities
+    // member, accumulated configuration, and restrictions.
+    Sequence<MediaKeySystemMediaCapability> caps =
+      GetSupportedCapabilities(Audio,
+                               aGMPService,
+                               aCandidate.mAudioCapabilities,
+                               config,
+                               aKeySystem,
+                               aDiagnostics);
+    // If audio capabilities is null, return NotSupported.
     if (caps.IsEmpty()) {
+      EME_LOG("MediaKeySystemConfiguration (label='%s') rejected; "
+              "no supported audio capabilities.",
+              NS_ConvertUTF16toUTF8(aCandidate.mLabel).get());
       return false;
     }
-    config.mVideoCapabilities.Construct();
-    config.mVideoCapabilities.Value().Assign(caps);
+    // Set the audioCapabilities member of accumulated configuration to audio capabilities.
+    config.mAudioCapabilities = Move(caps);
+  } else {
+    // Otherwise:
+    // Set the audioCapabilities member of accumulated configuration to an empty sequence.
   }
 
+  // If accumulated configuration's distinctiveIdentifier value is "optional", follow the
+  // steps for the first matching condition from the following list:
+  if (config.mDistinctiveIdentifier == MediaKeysRequirement::Optional) {
+    // If the implementation requires use Distinctive Identifier(s) or
+    // Distinctive Permanent Identifier(s) for any of the combinations
+    // in accumulated configuration
+    if (aKeySystem.mDistinctiveIdentifier == KeySystemFeatureSupport::Required) {
+      // Change accumulated configuration's distinctiveIdentifier value to "required".
+      config.mDistinctiveIdentifier = MediaKeysRequirement::Required;
+    } else {
+      // Otherwise, change accumulated configuration's distinctiveIdentifier
+      // value to "not-allowed".
+      config.mDistinctiveIdentifier = MediaKeysRequirement::Not_allowed;
+    }
+  }
+
+  // If accumulated configuration's persistentState value is "optional", follow the
+  // steps for the first matching condition from the following list:
+  if (config.mPersistentState == MediaKeysRequirement::Optional) {
+    // If the implementation requires persisting state for any of the combinations
+    // in accumulated configuration
+    if (aKeySystem.mPersistentState == KeySystemFeatureSupport::Required) {
+      // Change accumulated configuration's persistentState value to "required".
+      config.mPersistentState = MediaKeysRequirement::Required;
+    } else {
+      // Otherwise, change accumulated configuration's persistentState
+      // value to "not-allowed".
+      config.mPersistentState = MediaKeysRequirement::Not_allowed;
+    }
+  }
+
+  // Note: Omitting steps 20-22. We don't ask for consent.
+
 #if 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()) &&
+  if (aKeySystem.mKeySystem.EqualsLiteral("com.widevine.alpha") &&
+      (aCandidate.mAudioCapabilities.IsEmpty() ||
+       aCandidate.mVideoCapabilities.IsEmpty()) &&
      !WMFDecoderModule::HasAAC()) {
     if (aDiagnostics) {
       aDiagnostics->SetKeySystemIssue(
         DecoderDoctorDiagnostics::eWidevineWithNoWMF);
     }
+    EME_LOG("MediaKeySystemConfiguration (label='%s') rejected; "
+            "WMF required for Widevine decoding, but it's not available.",
+            NS_ConvertUTF16toUTF8(aCandidate.mLabel).get());
     return false;
   }
 #endif
 
+  // Return accumulated configuration.
   aOutConfig = config;
 
   return true;
 }
 
 /* static */
 bool
 MediaKeySystemAccess::GetSupportedConfig(const nsAString& aKeySystem,
@@ -571,26 +1163,29 @@ MediaKeySystemAccess::GetSupportedConfig
                                          MediaKeySystemConfiguration& aOutConfig,
                                          DecoderDoctorDiagnostics* aDiagnostics)
 {
   nsCOMPtr<mozIGeckoMediaPluginService> mps =
     do_GetService("@mozilla.org/gecko-media-plugin-service;1");
   if (NS_WARN_IF(!mps)) {
     return false;
   }
-
+  const KeySystemConfig* implementation = nullptr;
   if (!HaveGMPFor(mps,
                   NS_ConvertUTF16toUTF8(aKeySystem),
-                  NS_LITERAL_CSTRING(GMP_API_DECRYPTOR))) {
+                  NS_LITERAL_CSTRING(GMP_API_DECRYPTOR)) ||
+      !(implementation = GetKeySystemConfig(aKeySystem))) {
     return false;
   }
-
-  for (const MediaKeySystemConfiguration& config : aConfigs) {
-    if (mozilla::dom::GetSupportedConfig(
-          mps, aKeySystem, config, aOutConfig, aDiagnostics)) {
+  for (const MediaKeySystemConfiguration& candidate : aConfigs) {
+    if (mozilla::dom::GetSupportedConfig(mps,
+                                         *implementation,
+                                         candidate,
+                                         aOutConfig,
+                                         aDiagnostics)) {
       return true;
     }
   }
 
   return false;
 }
 
 
--- a/dom/media/eme/MediaKeySystemAccessManager.cpp
+++ b/dom/media/eme/MediaKeySystemAccessManager.cpp
@@ -76,16 +76,31 @@ 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());
 
+  if (aKeySystem.IsEmpty()) {
+    aPromise->MaybeReject(NS_ERROR_DOM_TYPE_ERR,
+                          NS_LITERAL_CSTRING("Key system string is empty"));
+    // Don't notify DecoderDoctor, as there's nothing we or the user can
+    // do to fix this situation; the site is using the API wrong.
+    return;
+  }
+  if (aConfigs.IsEmpty()) {
+    aPromise->MaybeReject(NS_ERROR_DOM_TYPE_ERR,
+                          NS_LITERAL_CSTRING("Candidate MediaKeySystemConfigs is empty"));
+    // Don't notify DecoderDoctor, as there's nothing we or the user can
+    // do to fix this situation; the site is using the API wrong.
+    return;
+  }
+
   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.
--- a/dom/media/eme/MediaKeys.cpp
+++ b/dom/media/eme/MediaKeys.cpp
@@ -43,21 +43,25 @@ NS_IMPL_CYCLE_COLLECTING_ADDREF(MediaKey
 NS_IMPL_CYCLE_COLLECTING_RELEASE(MediaKeys)
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(MediaKeys)
   NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
   NS_INTERFACE_MAP_ENTRY(nsISupports)
 NS_INTERFACE_MAP_END
 
 MediaKeys::MediaKeys(nsPIDOMWindowInner* aParent,
                      const nsAString& aKeySystem,
-                     const nsAString& aCDMVersion)
+                     const nsAString& aCDMVersion,
+                     bool aDistinctiveIdentifierRequired,
+                     bool aPersistentStateRequired)
   : mParent(aParent)
   , mKeySystem(aKeySystem)
   , mCDMVersion(aCDMVersion)
   , mCreatePromiseId(0)
+  , mDistinctiveIdentifierRequired(aDistinctiveIdentifierRequired)
+  , mPersistentStateRequired(aPersistentStateRequired)
 {
   EME_LOG("MediaKeys[%p] constructed keySystem=%s",
           this, NS_ConvertUTF16toUTF8(mKeySystem).get());
 }
 
 MediaKeys::~MediaKeys()
 {
   Shutdown();
@@ -308,17 +312,21 @@ already_AddRefed<DetailedPromise>
 MediaKeys::Init(ErrorResult& aRv)
 {
   RefPtr<DetailedPromise> promise(MakePromise(aRv,
     NS_LITERAL_CSTRING("MediaKeys::Init()")));
   if (aRv.Failed()) {
     return nullptr;
   }
 
-  mProxy = new GMPCDMProxy(this, mKeySystem, new MediaKeysGMPCrashHelper(this));
+  mProxy = new GMPCDMProxy(this,
+                           mKeySystem,
+                           new MediaKeysGMPCrashHelper(this),
+                           mDistinctiveIdentifierRequired,
+                           mPersistentStateRequired);
 
   // Determine principal (at creation time) of the MediaKeys object.
   nsCOMPtr<nsIScriptObjectPrincipal> sop = do_QueryInterface(GetParentObject());
   if (!sop) {
     promise->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR,
                          NS_LITERAL_CSTRING("Couldn't get script principal in MediaKeys::Init"));
     return promise.forget();
   }
@@ -409,17 +417,17 @@ MediaKeys::OnCDMCreated(PromiseId aId, c
 
   MediaKeySystemAccess::NotifyObservers(mParent,
                                         mKeySystem,
                                         MediaKeySystemStatus::Cdm_created);
 }
 
 already_AddRefed<MediaKeySession>
 MediaKeys::CreateSession(JSContext* aCx,
-                         SessionType aSessionType,
+                         MediaKeySessionType aSessionType,
                          ErrorResult& aRv)
 {
   if (!mProxy) {
     NS_WARNING("Tried to use a MediaKeys which lost its CDM");
     aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
     return nullptr;
   }
 
--- a/dom/media/eme/MediaKeys.h
+++ b/dom/media/eme/MediaKeys.h
@@ -45,33 +45,36 @@ class MediaKeys final : public nsISuppor
   ~MediaKeys();
 
 public:
   NS_DECL_CYCLE_COLLECTING_ISUPPORTS
   NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(MediaKeys)
   MOZ_DECLARE_WEAKREFERENCE_TYPENAME(MediaKeys)
 
   MediaKeys(nsPIDOMWindowInner* aParentWindow,
-            const nsAString& aKeySystem, const nsAString& aCDMVersion);
+            const nsAString& aKeySystem,
+            const nsAString& aCDMVersion,
+            bool aDistinctiveIdentifierRequired,
+            bool aPersistentStateRequired);
 
   already_AddRefed<DetailedPromise> Init(ErrorResult& aRv);
 
   nsPIDOMWindowInner* GetParentObject() const;
 
   JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
 
   nsresult Bind(HTMLMediaElement* aElement);
   void Unbind();
 
   // Javascript: readonly attribute DOMString keySystem;
   void GetKeySystem(nsString& retval) const;
 
   // JavaScript: MediaKeys.createSession()
   already_AddRefed<MediaKeySession> CreateSession(JSContext* aCx,
-                                                  SessionType aSessionType,
+                                                  MediaKeySessionType aSessionType,
                                                   ErrorResult& aRv);
 
   // JavaScript: MediaKeys.SetServerCertificate()
   already_AddRefed<DetailedPromise>
     SetServerCertificate(const ArrayBufferViewOrArrayBuffer& aServerCertificate,
                          ErrorResult& aRv);
 
   already_AddRefed<MediaKeySession> GetSession(const nsAString& aSessionId);
@@ -142,14 +145,16 @@ private:
   KeySessionHashMap mKeySessions;
   PromiseHashMap mPromises;
   PendingKeySessionsHashMap mPendingSessions;
   PromiseId mCreatePromiseId;
 
   RefPtr<nsIPrincipal> mPrincipal;
   RefPtr<nsIPrincipal> mTopLevelPrincipal;
 
+  const bool mDistinctiveIdentifierRequired;
+  const bool mPersistentStateRequired;
 };
 
 } // namespace dom
 } // namespace mozilla
 
 #endif // mozilla_dom_mediakeys_h__
--- a/dom/media/gmp-plugin/gmp-test-decryptor.h
+++ b/dom/media/gmp-plugin/gmp-test-decryptor.h
@@ -6,17 +6,17 @@
 #ifndef FAKE_DECRYPTOR_H__
 #define FAKE_DECRYPTOR_H__
 
 #include "gmp-decryption.h"
 #include "gmp-async-shutdown.h"
 #include <string>
 #include "mozilla/Attributes.h"
 
-class FakeDecryptor : public GMPDecryptor {
+class FakeDecryptor : public GMPDecryptor7 {
 public:
 
   explicit FakeDecryptor(GMPDecryptorHost* aHost);
 
   void Init(GMPDecryptorCallback* aCallback) override {
     mCallback = aCallback;
   }
 
--- a/dom/media/gmp/GMPCDMProxy.cpp
+++ b/dom/media/gmp/GMPCDMProxy.cpp
@@ -22,18 +22,23 @@
 #include "GMPService.h"
 #include "MainThreadUtils.h"
 #include "MediaData.h"
 
 namespace mozilla {
 
 GMPCDMProxy::GMPCDMProxy(dom::MediaKeys* aKeys,
                          const nsAString& aKeySystem,
-                         GMPCrashHelper* aCrashHelper)
-  : CDMProxy(aKeys, aKeySystem)
+                         GMPCrashHelper* aCrashHelper,
+                         bool aDistinctiveIdentifierRequired,
+                         bool aPersistentStateRequired)
+  : CDMProxy(aKeys,
+             aKeySystem,
+             aDistinctiveIdentifierRequired,
+             aPersistentStateRequired)
   , mCrashHelper(aCrashHelper)
   , mCDM(nullptr)
   , mDecryptionJobCount(0)
   , mShutdownCalled(false)
 {
   MOZ_ASSERT(NS_IsMainThread());
   MOZ_COUNT_CTOR(GMPCDMProxy);
 }
@@ -118,17 +123,19 @@ GMPCDMProxy::gmp_InitDone(GMPDecryptorPr
   if (!aCDM) {
     RejectPromise(aData->mPromiseId, NS_ERROR_DOM_INVALID_STATE_ERR,
                   NS_LITERAL_CSTRING("GetGMPDecryptor failed to return a CDM"));
     return;
   }
 
   mCDM = aCDM;
   mCallback = new GMPCDMCallbackProxy(this);
-  mCDM->Init(mCallback);
+  mCDM->Init(mCallback,
+             mDistinctiveIdentifierRequired,
+             mPersistentStateRequired);
   nsCOMPtr<nsIRunnable> task(
     NewRunnableMethod<uint32_t>(this,
                                 &GMPCDMProxy::OnCDMCreated,
                                 aData->mPromiseId));
   NS_DispatchToMainThread(task);
 }
 
 class gmp_InitDoneCallback : public GetGMPDecryptorCallback
@@ -232,17 +239,20 @@ GMPCDMProxy::gmp_InitGetGMPDecryptor(nsr
   nsTArray<nsCString> tags;
   tags.AppendElement(NS_ConvertUTF16toUTF8(mKeySystem));
 
   // Note: must capture helper refptr here, before the Move()
   // when we create the GetGMPDecryptorCallback below.
   RefPtr<GMPCrashHelper> crashHelper = Move(aData->mCrashHelper);
   UniquePtr<GetGMPDecryptorCallback> callback(new gmp_InitDoneCallback(this,
                                                                        Move(aData)));
-  nsresult rv = mps->GetGMPDecryptor(crashHelper, &tags, GetNodeId(), Move(callback));
+  nsresult rv = mps->GetGMPDecryptor(crashHelper,
+                                     &tags,
+                                     GetNodeId(),
+                                     Move(callback));
   if (NS_FAILED(rv)) {
     RejectPromise(promiseID, NS_ERROR_DOM_INVALID_STATE_ERR,
                   NS_LITERAL_CSTRING("Call to GetGMPDecryptor() failed early"));
   }
 }
 
 void
 GMPCDMProxy::OnCDMCreated(uint32_t aPromiseId)
@@ -258,17 +268,17 @@ GMPCDMProxy::OnCDMCreated(uint32_t aProm
     // No CDM? Just reject the promise.
     mKeys->RejectPromise(aPromiseId, NS_ERROR_DOM_INVALID_STATE_ERR,
                          NS_LITERAL_CSTRING("Null CDM in OnCDMCreated()"));
   }
 }
 
 void
 GMPCDMProxy::CreateSession(uint32_t aCreateSessionToken,
-                           dom::SessionType aSessionType,
+                           dom::MediaKeySessionType aSessionType,
                            PromiseId aPromiseId,
                            const nsAString& aInitDataType,
                            nsTArray<uint8_t>& aInitData)
 {
   MOZ_ASSERT(NS_IsMainThread());
   MOZ_ASSERT(mOwnerThread);
 
   nsAutoPtr<CreateSessionData> data(new CreateSessionData());
@@ -279,20 +289,20 @@ GMPCDMProxy::CreateSession(uint32_t aCre
   data->mInitData = Move(aInitData);
 
   nsCOMPtr<nsIRunnable> task(
     NewRunnableMethod<nsAutoPtr<CreateSessionData>>(this, &GMPCDMProxy::gmp_CreateSession, data));
   mOwnerThread->Dispatch(task, NS_DISPATCH_NORMAL);
 }
 
 GMPSessionType
-ToGMPSessionType(dom::SessionType aSessionType) {
+ToGMPSessionType(dom::MediaKeySessionType aSessionType) {
   switch (aSessionType) {
-    case dom::SessionType::Temporary: return kGMPTemporySession;
-    case dom::SessionType::Persistent: return kGMPPersistentSession;
+    case dom::MediaKeySessionType::Temporary: return kGMPTemporySession;
+    case dom::MediaKeySessionType::Persistent_license: return kGMPPersistentSession;
     default: return kGMPTemporySession;
   };
 };
 
 void
 GMPCDMProxy::gmp_CreateSession(nsAutoPtr<CreateSessionData> aData)
 {
   MOZ_ASSERT(IsOnOwnerThread());
--- a/dom/media/gmp/GMPCDMProxy.h
+++ b/dom/media/gmp/GMPCDMProxy.h
@@ -19,26 +19,28 @@ class GMPCDMProxy : public CDMProxy {
 public:
 
   NS_INLINE_DECL_THREADSAFE_REFCOUNTING(GMPCDMProxy, override)
 
   typedef MozPromise<DecryptResult, DecryptResult, /* IsExclusive = */ true> DecryptPromise;
 
   GMPCDMProxy(dom::MediaKeys* aKeys,
               const nsAString& aKeySystem,
-              GMPCrashHelper* aCrashHelper);
+              GMPCrashHelper* aCrashHelper,
+              bool aDistinctiveIdentifierRequired,
+              bool aPersistentStateRequired);
 
   void Init(PromiseId aPromiseId,
             const nsAString& aOrigin,
             const nsAString& aTopLevelOrigin,
             const nsAString& aGMPName,
             bool aInPrivateBrowsing) override;
 
   void CreateSession(uint32_t aCreateSessionToken,
-                     dom::SessionType aSessionType,
+                     dom::MediaKeySessionType aSessionType,
                      PromiseId aPromiseId,
                      const nsAString& aInitDataType,
                      nsTArray<uint8_t>& aInitData) override;
 
   void LoadSession(PromiseId aPromiseId,
                    const nsAString& aSessionId) override;
 
   void SetServerCertificate(PromiseId aPromiseId,
@@ -129,17 +131,17 @@ private:
 
   // GMP thread only.
   void gmp_Shutdown();
 
   // Main thread only.
   void OnCDMCreated(uint32_t aPromiseId);
 
   struct CreateSessionData {
-    dom::SessionType mSessionType;
+    dom::MediaKeySessionType mSessionType;
     uint32_t mCreateSessionToken;
     PromiseId mPromiseId;
     nsCString mInitDataType;
     nsTArray<uint8_t> mInitData;
   };
   // GMP thread only.
   void gmp_CreateSession(nsAutoPtr<CreateSessionData> aData);
 
--- a/dom/media/gmp/GMPContentChild.cpp
+++ b/dom/media/gmp/GMPContentChild.cpp
@@ -104,38 +104,130 @@ GMPContentChild::AllocPGMPVideoEncoderCh
 
 bool
 GMPContentChild::DeallocPGMPVideoEncoderChild(PGMPVideoEncoderChild* aActor)
 {
   static_cast<GMPVideoEncoderChild*>(aActor)->Release();
   return true;
 }
 
+// Adapts GMPDecryptor7 to the current GMPDecryptor version.
+class GMPDecryptor7BackwardsCompat : public GMPDecryptor {
+public:
+  explicit GMPDecryptor7BackwardsCompat(GMPDecryptor7* aDecryptorV7)
+    : mDecryptorV7(aDecryptorV7)
+  {
+  }
+
+  void Init(GMPDecryptorCallback* aCallback,
+            bool aDistinctiveIdentifierRequired,
+            bool aPersistentStateRequired) override
+  {
+    // Distinctive identifier and persistent state arguments not present
+    // in v7 interface.
+    mDecryptorV7->Init(aCallback);
+  }
+
+  void CreateSession(uint32_t aCreateSessionToken,
+                     uint32_t aPromiseId,
+                     const char* aInitDataType,
+                     uint32_t aInitDataTypeSize,
+                     const uint8_t* aInitData,
+                     uint32_t aInitDataSize,
+                     GMPSessionType aSessionType) override
+  {
+    mDecryptorV7->CreateSession(aCreateSessionToken,
+                                aPromiseId,
+                                aInitDataType,
+                                aInitDataTypeSize,
+                                aInitData,
+                                aInitDataSize,
+                                aSessionType);
+  }
+
+  void LoadSession(uint32_t aPromiseId,
+                   const char* aSessionId,
+                   uint32_t aSessionIdLength) override
+  {
+    mDecryptorV7->LoadSession(aPromiseId, aSessionId, aSessionIdLength);
+  }
+
+  void UpdateSession(uint32_t aPromiseId,
+                     const char* aSessionId,
+                     uint32_t aSessionIdLength,
+                     const uint8_t* aResponse,
+                     uint32_t aResponseSize) override
+  {
+    mDecryptorV7->UpdateSession(aPromiseId,
+                                aSessionId,
+                                aSessionIdLength,
+                                aResponse,
+                                aResponseSize);
+  }
+
+  void CloseSession(uint32_t aPromiseId,
+                    const char* aSessionId,
+                    uint32_t aSessionIdLength) override
+  {
+    mDecryptorV7->CloseSession(aPromiseId, aSessionId, aSessionIdLength);
+  }
+
+  void RemoveSession(uint32_t aPromiseId,
+                     const char* aSessionId,
+                     uint32_t aSessionIdLength) override
+  {
+    mDecryptorV7->RemoveSession(aPromiseId, aSessionId, aSessionIdLength);
+  }
+
+  void SetServerCertificate(uint32_t aPromiseId,
+                            const uint8_t* aServerCert,
+                            uint32_t aServerCertSize) override
+  {
+    mDecryptorV7->SetServerCertificate(aPromiseId, aServerCert, aServerCertSize);
+  }
+
+  void Decrypt(GMPBuffer* aBuffer,
+               GMPEncryptedBufferMetadata* aMetadata) override
+  {
+    mDecryptorV7->Decrypt(aBuffer, aMetadata);
+  }
+
+  void DecryptingComplete() override
+  {
+    mDecryptorV7->DecryptingComplete();
+    delete this;
+  }
+private:
+  GMPDecryptor7* mDecryptorV7;
+};
+
 bool
 GMPContentChild::RecvPGMPDecryptorConstructor(PGMPDecryptorChild* aActor)
 {
   GMPDecryptorChild* child = static_cast<GMPDecryptorChild*>(aActor);
   GMPDecryptorHost* host = static_cast<GMPDecryptorHost*>(child);
 
-  void* session = nullptr;
-  GMPErr err = mGMPChild->GetAPI(GMP_API_DECRYPTOR, host, &session);
-  if (err != GMPNoErr || !session) {
+  void* ptr = nullptr;
+  GMPErr err = mGMPChild->GetAPI(GMP_API_DECRYPTOR, host, &ptr);
+  GMPDecryptor* decryptor = nullptr;
+  if (GMP_SUCCEEDED(err) && ptr) {
+    decryptor = static_cast<GMPDecryptor*>(ptr);
+  } else if (err != GMPNoErr) {
     // We Adapt the previous GMPDecryptor version to the current, so that
-    // Gecko thinks it's only talking to the current version. Helpfully,
-    // v7 is ABI compatible with v8, it only has different enumerations.
-    // If the GMP uses a v8-only enum value in an IPDL message, the IPC
-    // layer will terminate, so we rev'd the API version to signal to the
-    // GMP that it's safe to use the new enum values.
-    err = mGMPChild->GetAPI(GMP_API_DECRYPTOR_BACKWARDS_COMPAT, host, &session);
-    if (err != GMPNoErr || !session) {
+    // Gecko thinks it's only talking to the current version. v7 differs
+    // from v9 in its Init() function arguments, and v9 has extra enumeration
+    // members at the end of the key status enumerations.
+    err = mGMPChild->GetAPI(GMP_API_DECRYPTOR_BACKWARDS_COMPAT, host, &ptr);
+    if (err != GMPNoErr || !ptr) {
       return false;
     }
+    decryptor = new GMPDecryptor7BackwardsCompat(static_cast<GMPDecryptor7*>(ptr));
   }
 
-  child->Init(static_cast<GMPDecryptor*>(session));
+  child->Init(decryptor);
 
   return true;
 }
 
 bool
 GMPContentChild::RecvPGMPAudioDecoderConstructor(PGMPAudioDecoderChild* aActor)
 {
   auto vdc = static_cast<GMPAudioDecoderChild*>(aActor);
--- a/dom/media/gmp/GMPDecryptorChild.cpp
+++ b/dom/media/gmp/GMPDecryptorChild.cpp
@@ -215,22 +215,23 @@ GMPDecryptorChild::GetPluginVoucher(cons
   if (!aVoucher || !aVoucherLength) {
     return;
   }
   *aVoucher = mPluginVoucher.Elements();
   *aVoucherLength = mPluginVoucher.Length();
 }
 
 bool
-GMPDecryptorChild::RecvInit()
+GMPDecryptorChild::RecvInit(const bool& aDistinctiveIdentifierRequired,
+                            const bool& aPersistentStateRequired)
 {
   if (!mSession) {
     return false;
   }
-  mSession->Init(this);
+  mSession->Init(this, aDistinctiveIdentifierRequired, aPersistentStateRequired);
   return true;
 }
 
 bool
 GMPDecryptorChild::RecvCreateSession(const uint32_t& aCreateSessionToken,
                                      const uint32_t& aPromiseId,
                                      const nsCString& aInitDataType,
                                      InfallibleTArray<uint8_t>&& aInitData,
--- a/dom/media/gmp/GMPDecryptorChild.h
+++ b/dom/media/gmp/GMPDecryptorChild.h
@@ -78,17 +78,18 @@ public:
                          uint32_t* aVoucherLength) override;
 
   void GetPluginVoucher(const uint8_t** aVoucher,
                         uint32_t* aVoucherLength) override;
 private:
   ~GMPDecryptorChild();
 
   // GMPDecryptorChild
-  bool RecvInit() override;
+  bool RecvInit(const bool& aDistinctiveIdentifierRequired,
+                const bool& aPersistentStateRequired) override;
 
   bool RecvCreateSession(const uint32_t& aCreateSessionToken,
                          const uint32_t& aPromiseId,
                          const nsCString& aInitDataType,
                          InfallibleTArray<uint8_t>&& aInitData,
                          const GMPSessionType& aSessionType) override;
 
   bool RecvLoadSession(const uint32_t& aPromiseId,
--- a/dom/media/gmp/GMPDecryptorParent.cpp
+++ b/dom/media/gmp/GMPDecryptorParent.cpp
@@ -36,26 +36,28 @@ GMPDecryptorParent::GMPDecryptorParent(G
   MOZ_ASSERT(mPlugin && mGMPThread);
 }
 
 GMPDecryptorParent::~GMPDecryptorParent()
 {
 }
 
 nsresult
-GMPDecryptorParent::Init(GMPDecryptorProxyCallback* aCallback)
+GMPDecryptorParent::Init(GMPDecryptorProxyCallback* aCallback,
+                         bool aDistinctiveIdentifierRequired,
+                         bool aPersistentStateRequired)
 {
   LOGD(("GMPDecryptorParent[%p]::Init()", this));
 
   if (mIsOpen) {
     NS_WARNING("Trying to re-use an in-use GMP decrypter!");
     return NS_ERROR_FAILURE;
   }
   mCallback = aCallback;
-  if (!SendInit()) {
+  if (!SendInit(aDistinctiveIdentifierRequired, aPersistentStateRequired)) {
     return NS_ERROR_FAILURE;
   }
   mIsOpen = true;
   return NS_OK;
 }
 
 void
 GMPDecryptorParent::CreateSession(uint32_t aCreateSessionToken,
--- a/dom/media/gmp/GMPDecryptorParent.h
+++ b/dom/media/gmp/GMPDecryptorParent.h
@@ -28,17 +28,19 @@ public:
   NS_INLINE_DECL_REFCOUNTING(GMPDecryptorParent)
 
   explicit GMPDecryptorParent(GMPContentParent *aPlugin);
 
   // GMPDecryptorProxy
 
   uint32_t GetPluginId() const override { return mPluginId; }
 
-  nsresult Init(GMPDecryptorProxyCallback* aCallback) override;
+  nsresult Init(GMPDecryptorProxyCallback* aCallback,
+                bool aDistinctiveIdentifierRequired,
+                bool aPersistentStateRequired) override;
 
   void CreateSession(uint32_t aCreateSessionToken,
                      uint32_t aPromiseId,
                      const nsCString& aInitDataType,
                      const nsTArray<uint8_t>& aInitData,
                      GMPSessionType aSessionType) override;
 
   void LoadSession(uint32_t aPromiseId,
--- a/dom/media/gmp/GMPDecryptorProxy.h
+++ b/dom/media/gmp/GMPDecryptorProxy.h
@@ -54,17 +54,19 @@ public:
 };
 
 class GMPDecryptorProxy {
 public:
   ~GMPDecryptorProxy() {}
 
   virtual uint32_t GetPluginId() const = 0;
 
-  virtual nsresult Init(GMPDecryptorProxyCallback* aCallback) = 0;
+  virtual nsresult Init(GMPDecryptorProxyCallback* aCallback,
+                        bool aDistinctiveIdentifierRequired,
+                        bool aPersistentStateRequired) = 0;
 
   virtual void CreateSession(uint32_t aCreateSessionToken,
                              uint32_t aPromiseId,
                              const nsCString& aInitDataType,
                              const nsTArray<uint8_t>& aInitData,
                              GMPSessionType aSessionType) = 0;
 
   virtual void LoadSession(uint32_t aPromiseId,
--- a/dom/media/gmp/PGMPDecryptor.ipdl
+++ b/dom/media/gmp/PGMPDecryptor.ipdl
@@ -15,17 +15,18 @@ using GMPErr from "gmp-errors.h";
 namespace mozilla {
 namespace gmp {
 
 async protocol PGMPDecryptor
 {
   manager PGMPContent;
 child:
 
-  async Init();
+  async Init(bool aDistinctiveIdentifierRequired,
+             bool aPersistentStateRequired);
 
   async CreateSession(uint32_t aCreateSessionToken,
                       uint32_t aPromiseId,
                       nsCString aInitDataType,
                       uint8_t[] aInitData,
                       GMPSessionType aSessionType);
 
   async LoadSession(uint32_t aPromiseId,
--- a/dom/media/gmp/gmp-api/gmp-decryption.h
+++ b/dom/media/gmp/gmp-api/gmp-decryption.h
@@ -208,30 +208,140 @@ public:
 };
 
 enum GMPSessionType {
   kGMPTemporySession = 0,
   kGMPPersistentSession = 1,
   kGMPSessionInvalid = 2 // Must always be last.
 };
 
-// Gecko supports the current GMPDecryptor version, and the previous.
-#define GMP_API_DECRYPTOR "eme-decrypt-v8"
+// Gecko supports the current GMPDecryptor version, and the obsolete
+// version that the Adobe GMP still uses.
+#define GMP_API_DECRYPTOR "eme-decrypt-v9"
 #define GMP_API_DECRYPTOR_BACKWARDS_COMPAT "eme-decrypt-v7"
 
 // API exposed by plugin library to manage decryption sessions.
 // When the Host requests this by calling GMPGetAPIFunc().
 //
 // API name macro: GMP_API_DECRYPTOR
 // Host API: GMPDecryptorHost
 class GMPDecryptor {
 public:
 
   // Sets the callback to use with the decryptor to return results
   // to Gecko.
+  virtual void Init(GMPDecryptorCallback* aCallback,
+                    bool aDistinctiveIdentifierRequired,
+                    bool aPersistentStateRequired) = 0;
+
+  // Initiates the creation of a session given |aType| and |aInitData|, and
+  // the generation of a license request message.
+  //
+  // This corresponds to a MediaKeySession.generateRequest() call in JS.
+  //
+  // The GMPDecryptor must do the following, in order, upon this method
+  // being called:
+  //
+  // 1. Generate a sessionId to expose to JS, and call
+  //    GMPDecryptorCallback::SetSessionId(aCreateSessionToken, sessionId...)
+  //    with the sessionId to be exposed to JS/EME on the MediaKeySession
+  //    object on which generateRequest() was called, and then
+  // 2. send any messages to JS/EME required to generate a license request
+  //    given the supplied initData, and then
+  // 3. generate a license request message, and send it to JS/EME, and then
+  // 4. call GMPDecryptorCallback::ResolvePromise().
+  //
+  // Note: GMPDecryptorCallback::SetSessionId(aCreateSessionToken, sessionId, ...)
+  // *must* be called before GMPDecryptorCallback::SendMessage(sessionId, ...)
+  // will work.
+  //
+  // If generating the request fails, reject aPromiseId by calling
+  // GMPDecryptorCallback::RejectPromise().
+  virtual void CreateSession(uint32_t aCreateSessionToken,
+                             uint32_t aPromiseId,
+                             const char* aInitDataType,
+                             uint32_t aInitDataTypeSize,
+                             const uint8_t* aInitData,
+                             uint32_t aInitDataSize,
+                             GMPSessionType aSessionType) = 0;
+
+  // Loads a previously loaded persistent session.
+  //
+  // This corresponds to a MediaKeySession.load() call in JS.
+  //
+  // The GMPDecryptor must do the following, in order, upon this method
+  // being called:
+  //
+  // 1. Send any messages to JS/EME, or read from storage, whatever is
+  //    required to load the session, and then
+  // 2. if there is no session with the given sessionId loadable, call
+  //    ResolveLoadSessionPromise(aPromiseId, false), otherwise
+  // 2. mark the session's keys as usable, and then
+  // 3. update the session's expiration, and then
+  // 4. call GMPDecryptorCallback::ResolveLoadSessionPromise(aPromiseId, true).
+  //
+  // If loading the session fails due to error, reject aPromiseId by calling
+  // GMPDecryptorCallback::RejectPromise().
+  virtual void LoadSession(uint32_t aPromiseId,
+                           const char* aSessionId,
+                           uint32_t aSessionIdLength) = 0;
+
+  // Updates the session with |aResponse|.
+  // This corresponds to a MediaKeySession.update() call in JS.
+  virtual void UpdateSession(uint32_t aPromiseId,
+                             const char* aSessionId,
+                             uint32_t aSessionIdLength,
+                             const uint8_t* aResponse,
+                             uint32_t aResponseSize) = 0;
+
+  // Releases the resources (keys) for the specified session.
+  // This corresponds to a MediaKeySession.close() call in JS.
+  virtual void CloseSession(uint32_t aPromiseId,
+                            const char* aSessionId,
+                            uint32_t aSessionIdLength) = 0;
+
+  // Removes the resources (keys) for the specified session.
+  // This corresponds to a MediaKeySession.remove() call in JS.
+  virtual void RemoveSession(uint32_t aPromiseId,
+                             const char* aSessionId,
+                             uint32_t aSessionIdLength) = 0;
+
+  // Resolve/reject promise on completion.
+  // This corresponds to a MediaKeySession.setServerCertificate() call in JS.
+  virtual void SetServerCertificate(uint32_t aPromiseId,
+                                    const uint8_t* aServerCert,
+                                    uint32_t aServerCertSize) = 0;
+
+  // Asynchronously decrypts aBuffer in place. When the decryption is
+  // complete, GMPDecryptor should write the decrypted data back into the
+  // same GMPBuffer object and return it to Gecko by calling Decrypted(),
+  // with the GMPNoErr successcode. If decryption fails, call Decrypted()
+  // with a failure code, and an error event will fire on the media element.
+  // Note: When Decrypted() is called and aBuffer is passed back, aBuffer
+  // is deleted. Don't forget to call Decrypted(), as otherwise aBuffer's
+  // memory will leak!
+  virtual void Decrypt(GMPBuffer* aBuffer,
+                       GMPEncryptedBufferMetadata* aMetadata) = 0;
+
+  // Called when the decryption operations are complete.
+  // Do not call the GMPDecryptorCallback's functions after this is called.
+  virtual void DecryptingComplete() = 0;
+
+  virtual ~GMPDecryptor() {}
+};
+
+// v7 is the latest decryptor version supported by the Adobe GMP.
+//
+// API name macro: GMP_API_DECRYPTOR_BACKWARDS_COMPAT
+// Host API: GMPDecryptorHost
+class GMPDecryptor7 {
+public:
+
+  // Sets the callback to use with the decryptor to return results
+  // to Gecko.
   virtual void Init(GMPDecryptorCallback* aCallback) = 0;
 
   // Initiates the creation of a session given |aType| and |aInitData|, and
   // the generation of a license request message.
   //
   // This corresponds to a MediaKeySession.generateRequest() call in JS.
   //
   // The GMPDecryptor must do the following, in order, upon this method
@@ -317,12 +427,12 @@ public:
   // memory will leak!
   virtual void Decrypt(GMPBuffer* aBuffer,
                        GMPEncryptedBufferMetadata* aMetadata) = 0;
 
   // Called when the decryption operations are complete.
   // Do not call the GMPDecryptorCallback's functions after this is called.
   virtual void DecryptingComplete() = 0;
 
-  virtual ~GMPDecryptor() {}
+  virtual ~GMPDecryptor7() {}
 };
 
 #endif // GMP_DECRYPTION_h_
--- a/dom/media/gmp/widevine-adapter/WidevineAdapter.cpp
+++ b/dom/media/gmp/widevine-adapter/WidevineAdapter.cpp
@@ -117,20 +117,16 @@ WidevineAdapter::GMPGetAPI(const char* a
     if (!cdm) {
       Log("WidevineAdapter::GMPGetAPI(%s, 0x%p, 0x%p) this=0x%p FAILED to create cdm",
           aAPIName, aHostAPI, aPluginAPI, this);
       return GMPGenericErr;
     }
     Log("cdm: 0x%x", cdm);
     sCDMWrapper = new CDMWrapper(cdm);
     decryptor->SetCDM(RefPtr<CDMWrapper>(sCDMWrapper));
-
-    cdm->Initialize(false, /* allow_distinctive_identifier */
-                    false /* allow_persistent_state */);
-
     *aPluginAPI = decryptor;
 
   } else if (!strcmp(aAPIName, GMP_API_VIDEO_DECODER)) {
     if (!sCDMWrapper) {
       Log("WidevineAdapter::GMPGetAPI(%s, 0x%p, 0x%p) this=0x%p No cdm for video decoder",
           aAPIName, aHostAPI, aPluginAPI, this);
       return GMPGenericErr;
     }
--- a/dom/media/gmp/widevine-adapter/WidevineDecryptor.cpp
+++ b/dom/media/gmp/widevine-adapter/WidevineDecryptor.cpp
@@ -2,16 +2,17 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "WidevineDecryptor.h"
 
 #include "WidevineAdapter.h"
 #include "WidevineUtils.h"
+#include "WidevineFileIO.h"
 #include <mozilla/SizePrintfMacros.h>
 #include <stdarg.h>
 
 using namespace cdm;
 using namespace std;
 
 namespace mozilla {
 
@@ -30,20 +31,31 @@ WidevineDecryptor::~WidevineDecryptor()
 
 void
 WidevineDecryptor::SetCDM(RefPtr<CDMWrapper> aCDM)
 {
   mCDM = aCDM;
 }
 
 void
-WidevineDecryptor::Init(GMPDecryptorCallback* aCallback)
+WidevineDecryptor::Init(GMPDecryptorCallback* aCallback,
+                        bool aDistinctiveIdentifierRequired,
+                        bool aPersistentStateRequired)
 {
+  Log("WidevineDecryptor::Init() this=%p distinctiveId=%d persistentState=%d",
+      this, aDistinctiveIdentifierRequired, aPersistentStateRequired);
   MOZ_ASSERT(aCallback);
   mCallback = aCallback;
+  MOZ_ASSERT(mCDM);
+  mDistinctiveIdentifierRequired = aDistinctiveIdentifierRequired;
+  mPersistentStateRequired = aPersistentStateRequired;
+  if (CDM()) {
+    CDM()->Initialize(aDistinctiveIdentifierRequired,
+                      aPersistentStateRequired);
+  }
 }
 
 static SessionType
 ToCDMSessionType(GMPSessionType aSessionType)
 {
   switch (aSessionType) {
     case kGMPTemporySession: return kTemporary;
     case kGMPPersistentSession: return kPersistentLicense;
@@ -479,14 +491,15 @@ WidevineDecryptor::OnDeferredInitializat
 {
   Log("Decryptor::OnDeferredInitializationDone()");
 }
 
 FileIO*
 WidevineDecryptor::CreateFileIO(FileIOClient* aClient)
 {
   Log("Decryptor::CreateFileIO()");
-  // Persistent storage not required or supported!
-  MOZ_ASSERT(false);
-  return nullptr;
+  if (!mPersistentStateRequired) {
+    return nullptr;
+  }
+  return new WidevineFileIO(aClient);
 }
 
 } // namespace mozilla
--- a/dom/media/gmp/widevine-adapter/WidevineDecryptor.h
+++ b/dom/media/gmp/widevine-adapter/WidevineDecryptor.h
@@ -22,17 +22,19 @@ public:
 
   NS_INLINE_DECL_THREADSAFE_REFCOUNTING(WidevineDecryptor)
 
   WidevineDecryptor();
 
   void SetCDM(RefPtr<CDMWrapper> aCDM);
 
   // GMPDecryptor
-  void Init(GMPDecryptorCallback* aCallback) override;
+  void Init(GMPDecryptorCallback* aCallback,
+            bool aDistinctiveIdentifierRequired,
+            bool aPersistentStateRequired) override;
 
   void CreateSession(uint32_t aCreateSessionToken,
                      uint32_t aPromiseId,
                      const char* aInitDataType,
                      uint32_t aInitDataTypeSize,
                      const uint8_t* aInitData,
                      uint32_t aInitDataSize,
                      GMPSessionType aSessionType) override;
@@ -115,13 +117,15 @@ public:
   RefPtr<CDMWrapper> GetCDMWrapper() const { return mCDM; }
 private:
   ~WidevineDecryptor();
   RefPtr<CDMWrapper> mCDM;
   cdm::ContentDecryptionModule_8* CDM() { return mCDM->GetCDM(); }
 
   GMPDecryptorCallback* mCallback;
   std::map<uint32_t, uint32_t> mPromiseIdToNewSessionTokens;
+  bool mDistinctiveIdentifierRequired = false;
+  bool mPersistentStateRequired = false;
 };
 
 } // namespace mozilla
 
 #endif // WidevineDecryptor_h_
new file mode 100644
--- /dev/null
+++ b/dom/media/gmp/widevine-adapter/WidevineFileIO.cpp
@@ -0,0 +1,97 @@
+#include "WidevineFileIO.h"
+#include "WidevineUtils.h"
+#include "WidevineAdapter.h"
+
+using namespace cdm;
+
+namespace mozilla {
+
+void
+WidevineFileIO::Open(const char* aFilename, uint32_t aFilenameLength)
+{
+  mName = std::string(aFilename, aFilename + aFilenameLength);
+  GMPRecord* record = nullptr;
+  GMPErr err = GMPCreateRecord(aFilename, aFilenameLength, &record, static_cast<GMPRecordClient*>(this));
+  if (GMP_FAILED(err)) {
+    Log("WidevineFileIO::Open() '%s' GMPCreateRecord failed", mName.c_str());
+    mClient->OnOpenComplete(FileIOClient::kError);
+    return;
+  }
+  if (GMP_FAILED(record->Open())) {
+    Log("WidevineFileIO::Open() '%s' record open failed", mName.c_str());
+    mClient->OnOpenComplete(FileIOClient::kError);
+    return;
+  }
+
+  Log("WidevineFileIO::Open() '%s'", mName.c_str());
+  mRecord = record;
+}
+
+void
+WidevineFileIO::Read()
+{
+  if (!mRecord) {
+    Log("WidevineFileIO::Read() '%s' used uninitialized!", mName.c_str());
+    mClient->OnReadComplete(FileIOClient::kError, nullptr, 0);
+    return;
+  }
+  Log("WidevineFileIO::Read() '%s'", mName.c_str());
+  mRecord->Read();
+}
+
+void
+WidevineFileIO::Write(const uint8_t* aData, uint32_t aDataSize)
+{
+  if (!mRecord) {
+    Log("WidevineFileIO::Write() '%s' used uninitialized!", mName.c_str());
+    mClient->OnWriteComplete(FileIOClient::kError);
+    return;
+  }
+  mRecord->Write(aData, aDataSize);
+}
+
+void
+WidevineFileIO::Close()
+{
+  Log("WidevineFileIO::Close() '%s'", mName.c_str());
+  if (mRecord) {
+    mRecord->Close();
+    mRecord = nullptr;
+  }
+  delete this;
+}
+
+static FileIOClient::Status
+GMPToWidevineFileStatus(GMPErr aStatus)
+{
+  switch (aStatus) {
+    case GMPRecordInUse: return FileIOClient::kInUse;
+    case GMPNoErr: return FileIOClient::kSuccess;
+    default: return FileIOClient::kError;
+  }
+}
+
+void
+WidevineFileIO::OpenComplete(GMPErr aStatus)
+{
+  Log("WidevineFileIO::OpenComplete() '%s' status=%d", mName.c_str(), aStatus);
+  mClient->OnOpenComplete(GMPToWidevineFileStatus(aStatus));
+}
+
+void
+WidevineFileIO::ReadComplete(GMPErr aStatus,
+                             const uint8_t* aData,
+                             uint32_t aDataSize)
+{
+  Log("WidevineFileIO::OnReadComplete() '%s' status=%d", mName.c_str(), aStatus);
+  mClient->OnReadComplete(GMPToWidevineFileStatus(aStatus), aData, aDataSize);
+}
+
+void
+WidevineFileIO::WriteComplete(GMPErr aStatus)
+{
+  Log("WidevineFileIO::WriteComplete() '%s' status=%d", mName.c_str(), aStatus);
+  mClient->OnWriteComplete(GMPToWidevineFileStatus(aStatus));
+}
+
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/dom/media/gmp/widevine-adapter/WidevineFileIO.h
@@ -0,0 +1,46 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+* License, v. 2.0. If a copy of the MPL was not distributed with this
+* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef WidevineFileIO_h_
+#define WidevineFileIO_h_
+
+#include <stddef.h>
+#include "content_decryption_module.h"
+#include "gmp-api/gmp-storage.h"
+#include <string>
+
+namespace mozilla {
+
+class WidevineFileIO : public cdm::FileIO
+                     , public GMPRecordClient
+{
+public:
+  explicit WidevineFileIO(cdm::FileIOClient* aClient)
+    : mClient(aClient)
+    , mRecord(nullptr)
+  {}
+
+  // cdm::FileIO
+  void Open(const char* aFilename, uint32_t aFilenameLength) override;
+  void Read() override;
+  void Write(const uint8_t* aData, uint32_t aDataSize) override;
+  void Close() override;
+
+  // GMPRecordClient
+  void OpenComplete(GMPErr aStatus) override;
+  void ReadComplete(GMPErr aStatus,
+                    const uint8_t* aData,
+                    uint32_t aDataSize) override;
+  void WriteComplete(GMPErr aStatus) override;
+
+private:
+  cdm::FileIOClient* mClient;
+  GMPRecord* mRecord;
+  std::string mName;
+};
+
+} // namespace mozilla
+
+#endif // WidevineFileIO_h_
\ No newline at end of file
--- a/dom/media/gmp/widevine-adapter/moz.build
+++ b/dom/media/gmp/widevine-adapter/moz.build
@@ -2,16 +2,17 @@
 # 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/.
 
 SOURCES += [
     'WidevineAdapter.cpp',
     'WidevineDecryptor.cpp',
+    'WidevineFileIO.cpp',
     'WidevineUtils.cpp',
     'WidevineVideoDecoder.cpp',
     'WidevineVideoFrame.cpp',
 ]
 
 FINAL_LIBRARY = 'xul'
 
 LOCAL_INCLUDES += [
--- a/dom/media/gtest/TestGMPCrossOrigin.cpp
+++ b/dom/media/gtest/TestGMPCrossOrigin.cpp
@@ -601,17 +601,17 @@ class GMPStorageTest : public GMPDecrypt
     }
 
     void Done(GMPDecryptorProxy* aDecryptor) override
     {
       mRunner->mDecryptor = aDecryptor;
       EXPECT_TRUE(!!mRunner->mDecryptor);
 
       if (mRunner->mDecryptor) {
-        mRunner->mDecryptor->Init(mRunner);
+        mRunner->mDecryptor->Init(mRunner, false, true);
       }
       nsCOMPtr<nsIThread> thread(GetGMPThread());
       thread->Dispatch(mContinuation, NS_DISPATCH_NORMAL);
     }
 
   private:
     RefPtr<GMPStorageTest> mRunner;
     nsCOMPtr<nsIRunnable> mContinuation;
--- a/dom/media/mediasource/MediaSource.cpp
+++ b/dom/media/mediasource/MediaSource.cpp
@@ -76,18 +76,21 @@ IsWebMForced(DecoderDoctorDiagnostics* a
 {
   bool mp4supported =
     DecoderTraits::IsMP4TypeAndEnabled(NS_LITERAL_CSTRING("video/mp4"),
                                        aDiagnostics);
   bool hwsupported = gfxPlatform::GetPlatform()->CanUseHardwareVideoDecoding();
   return !mp4supported || !hwsupported || VP9Benchmark::IsVP9DecodeFast();
 }
 
-static nsresult
-IsTypeSupported(const nsAString& aType, DecoderDoctorDiagnostics* aDiagnostics)
+namespace dom {
+
+/* static */
+nsresult
+MediaSource::IsTypeSupported(const nsAString& aType, DecoderDoctorDiagnostics* aDiagnostics)
 {
   if (aType.IsEmpty()) {
     return NS_ERROR_DOM_TYPE_ERR;
   }
   nsContentTypeParser parser(aType);
   nsAutoString mimeType;
   nsresult rv = parser.GetType(mimeType);
   if (NS_FAILED(rv)) {
@@ -127,18 +130,16 @@ IsTypeSupported(const nsAString& aType, 
         return NS_OK;
       }
     }
   }
 
   return NS_ERROR_DOM_NOT_SUPPORTED_ERR;
 }
 
-namespace dom {
-
 /* static */ already_AddRefed<MediaSource>
 MediaSource::Constructor(const GlobalObject& aGlobal,
                          ErrorResult& aRv)
 {
   nsCOMPtr<nsPIDOMWindowInner> window = do_QueryInterface(aGlobal.GetAsSupports());
   if (!window) {
     aRv.Throw(NS_ERROR_UNEXPECTED);
     return nullptr;
@@ -216,17 +217,17 @@ MediaSource::SetDuration(double aDuratio
   mDecoder->SetMediaSourceDuration(aDuration);
 }
 
 already_AddRefed<SourceBuffer>
 MediaSource::AddSourceBuffer(const nsAString& aType, ErrorResult& aRv)
 {
   MOZ_ASSERT(NS_IsMainThread());
   DecoderDoctorDiagnostics diagnostics;
-  nsresult rv = mozilla::IsTypeSupported(aType, &diagnostics);
+  nsresult rv = IsTypeSupported(aType, &diagnostics);
   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)) {
@@ -336,17 +337,17 @@ 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);
+  nsresult rv = IsTypeSupported(aType, &diagnostics);
   nsCOMPtr<nsPIDOMWindowInner> window = do_QueryInterface(aOwner.GetAsSupports());
   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);
--- a/dom/media/mediasource/MediaSource.h
+++ b/dom/media/mediasource/MediaSource.h
@@ -60,16 +60,17 @@ public:
   void RemoveSourceBuffer(SourceBuffer& aSourceBuffer, ErrorResult& aRv);
 
   void EndOfStream(const Optional<MediaSourceEndOfStreamError>& aError, ErrorResult& aRv);
 
   void SetLiveSeekableRange(double aStart, double aEnd, ErrorResult& aRv);
   void ClearLiveSeekableRange(ErrorResult& aRv);
 
   static bool IsTypeSupported(const GlobalObject&, const nsAString& aType);
+  static nsresult IsTypeSupported(const nsAString& aType, DecoderDoctorDiagnostics* aDiagnostics);
 
   static bool Enabled(JSContext* cx, JSObject* aGlobal);
 
   IMPL_EVENT_HANDLER(sourceopen);
   IMPL_EVENT_HANDLER(sourceended);
   IMPL_EVENT_HANDLER(sourceclosed);
 
   /** End WebIDL Methods. */
--- a/dom/media/test/eme.js
+++ b/dom/media/test/eme.js
@@ -1,9 +1,15 @@
-const KEYSYSTEM_TYPE = "org.w3.clearkey";
+const CLEARKEY_KEYSYSTEM = "org.w3.clearkey";
+
+const gCencMediaKeySystemConfig = [{
+  initDataTypes: ['cenc'],
+  videoCapabilities: [{ contentType: 'video/mp4' }],
+  audioCapabilities: [{ contentType: 'audio/mp4' }],
+}];
 
 function IsMacOSSnowLeopardOrEarlier() {
   var re = /Mac OS X (\d+)\.(\d+)/;
   var ver = navigator.userAgent.match(re);
   if (!ver || ver.length != 3) {
     return false;
   }
   var major = ver[1] | 0;
@@ -371,17 +377,17 @@ function SetupEME(test, token, params)
       var options = { initDataTypes: [ev.initDataType] };
       if (streamType("video")) {
         options.videoCapabilities = [{contentType: streamType("video")}];
       }
       if (streamType("audio")) {
         options.audioCapabilities = [{contentType: streamType("audio")}];
       }
 
-      var p = navigator.requestMediaKeySystemAccess(KEYSYSTEM_TYPE, [options]);
+      var p = navigator.requestMediaKeySystemAccess(CLEARKEY_KEYSYSTEM, [options]);
       var r = bail(token + " Failed to request key system access.");
       chain(p, r)
       .then(function(keySystemAccess) {
         var p = keySystemAccess.createMediaKeys();
         var r = bail(token +  " Failed to create MediaKeys object");
         return chain(p, r);
       })
 
--- a/dom/media/test/manifest.js
+++ b/dom/media/test/manifest.js
@@ -1660,17 +1660,17 @@ function mediaTestCleanup(callback) {
       removeNodeAndSource(V[i]);
       V[i] = null;
     }
     var A = document.getElementsByTagName("audio");
     for (i=0; i<A.length; i++) {
       removeNodeAndSource(A[i]);
       A[i] = null;
     }
-    SpecialPowers.exactGC(window, callback);
+    SpecialPowers.exactGC(callback);
 }
 
 function setMediaTestsPrefs(callback, extraPrefs) {
   var prefs = gTestPrefs;
   if (extraPrefs) {
     prefs = prefs.concat(extraPrefs);
   }
   SpecialPowers.pushPrefEnv({"set": prefs}, callback);
--- a/dom/media/test/test_bug1248229.html
+++ b/dom/media/test/test_bug1248229.html
@@ -15,17 +15,17 @@ SimpleTest.waitForExplicitFinish();
 function doTest() {
   window.oak = v.mozCaptureStreamUntilEnded();
   v.mozCaptureStreamUntilEnded();
   v.play();
 
   v.onended = function() {
     info("Got ended.");
     v.onended = null;
-    SpecialPowers.exactGC(window, function() {
+    SpecialPowers.exactGC(function() {
       info("GC completed.");
       v.play();
       SimpleTest.finish();
     });
   }
 }
 
 </script>
--- a/dom/media/test/test_eme_detach_media_keys.html
+++ b/dom/media/test/test_eme_detach_media_keys.html
@@ -9,28 +9,28 @@
 </head>
 <body>
 <pre id="test">
 <video id="v" controls></video>
 <script class="testbody" type="text/javascript">
 
 SimpleTest.waitForExplicitFinish();
 
-const keysystem = 'org.w3.clearkey';
-
 function createAndSet() {
   return new Promise(function(resolve, reject) {
     var m;
-    navigator.requestMediaKeySystemAccess(keysystem, [{initDataTypes: ['cenc']}])
+    navigator.requestMediaKeySystemAccess(CLEARKEY_KEYSYSTEM, gCencMediaKeySystemConfig)
     .then(function (access) {
       return access.createMediaKeys();
-    }).then(function (mediaKeys) {
+    })
+    .then(function (mediaKeys) {
       m = mediaKeys;
       return document.getElementById("v").setMediaKeys(mediaKeys);
-    }).then(function() {
+    })
+    .then(function() {
       resolve(m);
     });
   }
 )}
 
 var m1,m2;
 
 // Test that if we create and set two MediaKeys on one video element,
--- a/dom/media/test/test_eme_initDataTypes.html
+++ b/dom/media/test/test_eme_initDataTypes.html
@@ -34,19 +34,19 @@ var tests = [
     initData: '{"type":"temporary", "kids":["LwVHf8JLtPrv2GUXFW2v_A", "0DdtU9od-Bh5L3xbv0Xf_A"]}',
     expectedRequest: '{"kids":["LwVHf8JLtPrv2GUXFW2v_A","0DdtU9od-Bh5L3xbv0Xf_A"],"type":"temporary"}',
     sessionType: 'temporary',
     expectPass: true,
   },
   {
     name: "Two keyIds, persistent session, type before kids",
     initDataType: 'keyids',
-    initData: '{"type":"persistent", "kids":["LwVHf8JLtPrv2GUXFW2v_A", "0DdtU9od-Bh5L3xbv0Xf_A"]}',
-    expectedRequest: '{"kids":["LwVHf8JLtPrv2GUXFW2v_A","0DdtU9od-Bh5L3xbv0Xf_A"],"type":"persistent"}',
-    sessionType: 'persistent',
+    initData: '{"type":"persistent-license", "kids":["LwVHf8JLtPrv2GUXFW2v_A", "0DdtU9od-Bh5L3xbv0Xf_A"]}',
+    expectedRequest: '{"kids":["LwVHf8JLtPrv2GUXFW2v_A","0DdtU9od-Bh5L3xbv0Xf_A"],"type":"persistent-license"}',
+    sessionType: 'persistent-license',
     expectPass: true,
   },
   {
     name: "Invalid keyId",
     initDataType: 'keyids',
     initData: '{"kids":["0"]}',
     sessionType: 'temporary',
     expectPass: false,
@@ -57,17 +57,17 @@ var tests = [
     initData: '{"kids":[""]}',
     sessionType: 'temporary',
     expectPass: false,
   },
   {
     name: "SessionType in license doesn't match MediaKeySession's sessionType",
     initDataType: 'keyids',
     initData: '{"kids":["LwVHf8JLtPrv2GUXFW2v_A"]}',
-    sessionType: 'persistent',
+    sessionType: 'persistent-license',
     expectPass: false,
   },
   {
     name: "One valid and one invalid kid",
     initDataType: 'keyids',
     initData: '{"kids":["LwVHf8JLtPrv2GUXFW2v_A", "invalid"]}',
     expectedRequest: '{"kids":["LwVHf8JLtPrv2GUXFW2v_A"],"type":"temporary"}',
     sessionType: 'temporary',
@@ -103,20 +103,23 @@ function PrepareInitData(initDataType, i
     return new TextEncoder().encode(initData);
   } else if (initDataType == "webm") {
     return StringToArrayBuffer(atob(initData));
   }
 }
 
 function Test(test) {
   return new Promise(function(resolve, reject) {
-    navigator.requestMediaKeySystemAccess('org.w3.clearkey', [{initDataTypes: [test.initDataType]}]).then(
-      (access) => access.createMediaKeys()
-      ).then(
-        (mediaKeys) => {
+    var configs = [{
+      initDataTypes: [test.initDataType],
+      videoCapabilities: [{contentType: 'video/mp4' }],
+    }];
+    navigator.requestMediaKeySystemAccess('org.w3.clearkey', configs)
+      .then((access) => access.createMediaKeys())
+      .then((mediaKeys) => {
           var session = mediaKeys.createSession(test.sessionType);
           session.addEventListener("message", function(event) {
             is(event.messageType, "license-request", "'" + test.name + "' MediaKeyMessage type should be license-request.");
             var text = new TextDecoder().decode(event.message);
             is(text, test.expectedRequest, "'" + test.name + "' got expected response.");
             is(text == test.expectedRequest, test.expectPass,
                "'" + test.name + "' expected to " + (test.expectPass ? "pass" : "fail"));
             resolve();
--- a/dom/media/test/test_eme_non_mse_fails.html
+++ b/dom/media/test/test_eme_non_mse_fails.html
@@ -15,17 +15,17 @@ var manager = new MediaTestManager;
 function DoSetMediaKeys(v, test)
 {
   var options = [{
     initDataTypes: ["cenc"],
     audioCapabilities: [{contentType: test.audioType}],
     videoCapabilities: [{contentType: test.videoType}],
   }];
 
-  return navigator.requestMediaKeySystemAccess("org.w3.clearkey", options)
+  return navigator.requestMediaKeySystemAccess(CLEARKEY_KEYSYSTEM, options)
 
   .then(function(keySystemAccess) {
     return keySystemAccess.createMediaKeys();
   })
 
   .catch(function() {
     ok(false, token + " was not expecting failure (yet)");
   })
--- a/dom/media/test/test_eme_persistent_sessions.html
+++ b/dom/media/test/test_eme_persistent_sessions.html
@@ -81,27 +81,27 @@ function startTest(test, token)
         .then(function() {
           sessionId = session.sessionId;
           Log(token, "Closing session with id=" + sessionId);
           return session.close();
         })
 
         // Once the session is closed, reload the MediaKeys and reload the session
         .then(function() {
-          return navigator.requestMediaKeySystemAccess(KEYSYSTEM_TYPE, [{initDataTypes:["cenc"]}]);
+          return navigator.requestMediaKeySystemAccess(CLEARKEY_KEYSYSTEM, gCencMediaKeySystemConfig);
         })
 
         .then(function(requestedKeySystemAccess) {
           keySystemAccess = requestedKeySystemAccess;
           return keySystemAccess.createMediaKeys();
         })
 
         .then(function(mediaKeys) {
           Log(token, "re-created MediaKeys object ok");
-          recreatedSession = mediaKeys.createSession("persistent");
+          recreatedSession = mediaKeys.createSession("persistent-license");
           Log(token, "Created recreatedSession, loading sessionId=" + sessionId);
           return recreatedSession.load(sessionId);
         })
 
         .then(function(suceeded) {
           if (suceeded) {
             return Promise.resolve();
           } else {
@@ -122,32 +122,32 @@ function startTest(test, token)
           Log(token, "removed session, all keys unusable.");
           // Attempt to recreate the session, the attempt should fail.
           return keySystemAccess.createMediaKeys();
         })
 
         .then(function(mediaKeys) {
           Log(token, "re-re-created MediaKeys object ok");
           // Trying to load the removed persistent session should fail.
-          return mediaKeys.createSession("persistent").load(sessionId);
+          return mediaKeys.createSession("persistent-license").load(sessionId);
         })
 
         .then(function(suceeded) {
           is(suceeded, false, token + " we expect the third session creation to fail, as the session should have been removed.");
           manager.finished(token);
         })
 
         .catch(function(reason) {
           // Catch rejections if any.
           ok(false, token + " rejected, reason=" + reason);
           manager.finished(token);
         });
 
       },
-      sessionType: "persistent",
+      sessionType: "persistent-license",
     }
   );
 
   LoadTestWithManagedLoadToken(test, v, manager, token,
                                { onlyLoadFirstFragments:2, noEndOfStream:false });
 }
 
 function beginTest() {
--- a/dom/media/test/test_eme_requestKeySystemAccess.html
+++ b/dom/media/test/test_eme_requestKeySystemAccess.html
@@ -6,17 +6,16 @@
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
   <script type="text/javascript" src="manifest.js"></script>
   <script type="text/javascript" src="eme.js"></script>
 </head>
 <body>
 <pre id="test">
 <script class="testbody" type="text/javascript">
 
-const CLEARKEY_ID = 'org.w3.clearkey';
 const SUPPORTED_LABEL = "pass label";
 
 function ValidateConfig(name, expected, observed) {
   info("ValidateConfig " + name);
   info("expected cfg=" + JSON.stringify(expected));
   info("observed cfg=" + JSON.stringify(observed));
 
   is(observed.label, expected.label, name + " label should match");
@@ -31,31 +30,38 @@ function ValidateConfig(name, expected, 
   }
   if (typeof expected.videoCapabilities !== 'undefined') {
     info("expected.videoCapabilities=" + expected.videoCapabilities);
     dump("expected.videoCapabilities=" + expected.videoCapabilities + "\n");
     ok(expected.videoCapabilities.length == 1, "Test function can only handle one capability.");
     ok(observed.videoCapabilities.length == 1, "Test function can only handle one capability.");
     is(observed.videoCapabilities[0].contentType, expected.videoCapabilities[0].contentType, name + " videoCapabilities should match.");
   }
+  if (expected.sessionTypes) {
+    is(expected.sessionTypes.length, observed.sessionTypes.length, "Should have expected number of sessionTypes");
+    for (var i = 0; i < expected.sessionTypes.length; i++) {
+      is(expected[i], observed[i], "Session type " + i + " should match");
+    }
+  }
 }
 
 function Test(test) {
   var name = "'" + test.name + "'";
   return new Promise(function(resolve, reject) {
     var p;
     if (test.options) {
-      p = navigator.requestMediaKeySystemAccess(test.keySystem, test.options);
+      var keySystem = (test.keySystem !== undefined) ? test.keySystem : CLEARKEY_KEYSYSTEM;
+      p = navigator.requestMediaKeySystemAccess(keySystem, test.options);
     } else {
-      p = navigator.requestMediaKeySystemAccess(test.keySystem);
+      p = navigator.requestMediaKeySystemAccess(keySystem);
     }
     p.then(
       function(keySystemAccess) {
         ok(test.shouldPass, name + " passed and was expected to " + (test.shouldPass ? "pass" : "fail"));
-        is(keySystemAccess.keySystem, CLEARKEY_ID, "CDM keySystem should be in MediaKeySystemAccess.keySystem");
+        is(keySystemAccess.keySystem, CLEARKEY_KEYSYSTEM, "CDM keySystem should be in MediaKeySystemAccess.keySystem");
         ValidateConfig(name, test.expectedConfig, keySystemAccess.getConfiguration());
         resolve();
       },
       function(ex) {
         if (test.shouldPass) {
           info(name + " failed: " + ex);
         }
         ok(!test.shouldPass, name + " failed and was expected to " + (test.shouldPass ? "pass" : "fail"));
@@ -73,38 +79,37 @@ var tests = [
         initDataTypes: ['cenc'],
         videoCapabilities: [{contentType: 'video/mp4'}],
       }
     ],
     shouldPass: false,
   },
   {
     name: 'Empty options specified',
-    keySystem: CLEARKEY_ID,
     options: [ ],
     shouldPass: false,
   },
   {
     name: 'Undefined options',
-    keySystem: CLEARKEY_ID,
     shouldPass: false,
   },
   {
     name: 'Basic MP4 cenc',
-    keySystem: CLEARKEY_ID,
     options: [
       {
         label: SUPPORTED_LABEL,
         initDataTypes: ['cenc'],
+        audioCapabilities: [{contentType: 'audio/mp4'}],
         videoCapabilities: [{contentType: 'video/mp4'}],
       }
     ],
     expectedConfig: {
       label: SUPPORTED_LABEL,
       initDataTypes: ['cenc'],
+      audioCapabilities: [{contentType: 'audio/mp4'}],
       videoCapabilities: [{contentType: 'video/mp4'}],
     },
     shouldPass: true,
   },
   {
     name: 'Invalid keysystem failure',
     keySystem: 'bogusKeySystem',
     options: [
@@ -112,134 +117,113 @@ var tests = [
         initDataTypes: ['cenc'],
         videoCapabilities: [{contentType: 'video/mp4'}],
       }
     ],
     shouldPass: false,
   },
   {
     name: 'Invalid initDataType',
-    keySystem: CLEARKEY_ID,
     options: [
       {
         initDataTypes: ['bogus'],
+        audioCapabilities: [{contentType: 'audio/mp4'}],
       }
     ],
     shouldPass: false,
   },
   {
     name: 'Valid initDataType after invalid',
-    keySystem: CLEARKEY_ID,
     options: [
       {
         label: SUPPORTED_LABEL,
         initDataTypes: ['bogus', 'invalid', 'cenc'],
+        audioCapabilities: [{contentType: 'audio/mp4'}],
       }
     ],
     expectedConfig: {
       label: SUPPORTED_LABEL,
       initDataTypes: ['cenc'],
+      audioCapabilities: [{contentType: 'audio/mp4'}],
     },
     shouldPass: true,
   },
   {
-    name: 'Empty initDataTypes',
-    keySystem: CLEARKEY_ID,
-    options: [
-      {
-        initDataTypes: [],
-      }
-    ],
-    shouldPass: false,
-  },
-  {
     name: 'Invalid videoType',
-    keySystem: CLEARKEY_ID,
     options: [
       {
         initDataTypes: ['cenc'],
         videoCapabilities: [{contentType: 'video/bogus'}],
       }
     ],
     shouldPass: false,
   },
   {
-    name: 'distinctiveIdentifier, persistentState, and robustness ignored',
-    keySystem: CLEARKEY_ID,
+    name: 'Invalid distinctiveIdentifier fails',
+    options: [
+      {
+        initDataTypes: ['cenc'],
+        videoCapabilities: [{contentType: 'video/mp4'}],
+        distinctiveIdentifier: 'bogus',
+        persistentState: 'bogus',
+      }
+    ],
+    shouldPass: false,
+  },
+  {
+    name: 'distinctiveIdentifier is prohibited for ClearKey',
+    options: [
+      {
+        initDataTypes: ['cenc'],
+        videoCapabilities: [{contentType: 'video/mp4'}],
+        distinctiveIdentifier: 'required',
+      }
+    ],
+    shouldPass: false,
+  },
+  {
+    name: 'Invalid persistentState fails',
+    options: [
+      {
+        initDataTypes: ['cenc'],
+        videoCapabilities: [{contentType: 'video/mp4'}],
+        persistentState: 'bogus',
+      }
+    ],
+    shouldPass: false,
+  },
+  {
+    name: 'Invalid robustness unsupported',
+    options: [
+      {
+        initDataTypes: ['cenc'],
+        videoCapabilities: [{contentType: 'video/mp4', robustness: 'very much so'}],
+      }
+    ],
+    shouldPass: false,
+  },
+  {
+    name: 'Unexpected config entry should be ignored',
     options: [
       {
         label: SUPPORTED_LABEL,
         initDataTypes: ['cenc'],
-        videoCapabilities: [{contentType: 'video/mp4', robustness: 'somewhat'}],
-        distinctiveIdentifier: 'bogus',
-        persistentState: 'bogus',
+        videoCapabilities: [{contentType: 'video/mp4'}],
+        unexpectedEntry: 'this should be ignored',
       }
     ],
     expectedConfig: {
       label: SUPPORTED_LABEL,
       initDataTypes: ['cenc'],
       videoCapabilities: [{contentType: 'video/mp4'}],
     },
     shouldPass: true,
   },
   {
-    name: 'Only unrecognised dict entries specified with unrecognised video type',
-    keySystem: CLEARKEY_ID,
-    options: [
-      {
-        videoCapabilities: [{robustness: 'very much so'}],
-        distinctiveIdentifier: 'required',
-        persistentState: 'required',
-      }
-    ],
-    shouldPass: false,
-  },
-  {
-    name: 'Only unrecognised dict entries specified should be ignored',
-    keySystem: CLEARKEY_ID,
-    options: [
-      {
-        label: SUPPORTED_LABEL,
-        distinctiveIdentifier: 'required',
-        persistentState: 'required',
-      }
-    ],
-    expectedConfig: {
-      label: SUPPORTED_LABEL,
-    },
-    shouldPass: true,
-  },
-  {
-    name: 'Empty config dict',
-    keySystem: CLEARKEY_ID,
-    options: [{ label: SUPPORTED_LABEL }],
-    expectedConfig: {
-      label: SUPPORTED_LABEL
-    },
-    shouldPass: true,
-  },
-  {
-    name: 'Unexpected config entry should be ignored',
-    keySystem: CLEARKEY_ID,
-    options: [
-      {
-        label: SUPPORTED_LABEL,
-        initDataTypes: ['cenc'],
-        unexpectedEntry: 'this should be ignored',
-      }
-    ],
-    expectedConfig: {
-      label: SUPPORTED_LABEL,
-      initDataTypes: ['cenc'],
-    },
-    shouldPass: true,
-  },
-  {
     name: 'Invalid option followed by valid',
-    keySystem: CLEARKEY_ID,
     options: [
       {
         label: "this config should not be supported",
         initDataTypes: ['bogus'],
       },
       {
         label: SUPPORTED_LABEL,
         initDataTypes: ['cenc'],
@@ -249,135 +233,189 @@ var tests = [
     expectedConfig: {
       label: SUPPORTED_LABEL,
       initDataTypes: ['cenc'],
       videoCapabilities: [{contentType: 'video/mp4'}],
     },
     shouldPass: true,
   },
   {
+    name: 'Persistent sessions; persistence required',
+    options: [
+      {
+        label: SUPPORTED_LABEL,
+        initDataTypes: ['cenc'],
+        videoCapabilities: [{contentType: 'video/mp4'}],
+        sessionTypes: ['temporary','persistent-license'],
+        persistentState: 'required',
+      }
+    ],
+    expectedConfig: {
+      label: SUPPORTED_LABEL,
+      initDataTypes: ['cenc'],
+      videoCapabilities: [{contentType: 'video/mp4'}],
+      sessionTypes: ['temporary','persistent-license'],
+      persistentState: 'required',
+    },
+    shouldPass: true,
+  },
+  {
+    name: 'Persistent sessions not allowed when persistentState prohibited',
+    options: [
+      {
+        initDataTypes: ['cenc'],
+        videoCapabilities: [{contentType: 'video/mp4'}],
+        sessionTypes: ['temporary','persistent-license'],
+        persistentState: 'not-allowed',
+      }
+    ],
+    shouldPass: false,
+  },
+  {
+    name: 'Persistent sessions; should bump optional persistState to required',
+    options: [
+      {
+        label: SUPPORTED_LABEL,
+        initDataTypes: ['cenc'],
+        videoCapabilities: [{contentType: 'video/mp4'}],
+        sessionTypes: ['temporary','persistent-license'],
+        persistentState: 'optional',
+      }
+    ],
+    expectedConfig: {
+      label: SUPPORTED_LABEL,
+      initDataTypes: ['cenc'],
+      videoCapabilities: [{contentType: 'video/mp4'}],
+      sessionTypes: ['temporary','persistent-license'],
+      persistentState: 'required',
+    },
+    shouldPass: true,
+  },
+  {
+    name: 'Persistent-usage-record should not be supported by ClearKey',
+    options: [
+      {
+        initDataTypes: ['cenc'],
+        videoCapabilities: [{contentType: 'video/mp4'}],
+        sessionTypes: ['persistent-usage-record'],
+        persistentState: 'required',
+      }
+    ],
+    shouldPass: false,
+  },  
+  {
     name: 'MP4 audio container',
-    keySystem: CLEARKEY_ID,
     options: [
       {
         label: SUPPORTED_LABEL,
         initDataTypes: ['cenc'],
         audioCapabilities: [{contentType: 'audio/mp4'}],
       }
     ],
     expectedConfig: {
       label: SUPPORTED_LABEL,
       initDataTypes: ['cenc'],
       audioCapabilities: [{contentType: 'audio/mp4'}],
     },
     shouldPass: true,
   },
   {
     name: 'MP4 audio container with AAC-LC',
-    keySystem: CLEARKEY_ID,
     options: [
       {
         label: SUPPORTED_LABEL,
         initDataTypes: ['cenc'],
         audioCapabilities: [{contentType: 'audio/mp4; codecs="mp4a.40.2"'}],
       }
     ],
     expectedConfig: {
       label: SUPPORTED_LABEL,
       initDataTypes: ['cenc'],
       audioCapabilities: [{contentType: 'audio/mp4; codecs="mp4a.40.2"'}],
     },
     shouldPass: true,
   },
   {
     name: 'MP4 audio container with invalid codecs',
-    keySystem: CLEARKEY_ID,
     options: [
       {
         initDataTypes: ['cenc'],
         audioCapabilities: [{contentType: 'audio/mp4; codecs="bogus"'}],
       }
     ],
     shouldPass: false,
   },
   {
     name: 'MP4 audio container with mp3 is unsupported',
-    keySystem: CLEARKEY_ID,
     options: [
       {
         initDataTypes: ['cenc'],
         audioCapabilities: [{contentType: 'audio/mp4; codecs="mp3"'}],
       }
     ],
     shouldPass: false,
   },
   {
     name: 'MP4 video container type with an mp3 codec is unsupported',
-    keySystem: CLEARKEY_ID,
     options: [
       {
         initDataTypes: ['cenc'],
         videoCapabilities: [{contentType: 'video/mp4; codecs="mp3"'}],
       }
     ],
     shouldPass: false,
   },
   {
     name: 'MP4 audio container type with a video codec is unsupported',
-    keySystem: CLEARKEY_ID,
     options: [
       {
         initDataTypes: ['cenc'],
         audioCapabilities: [{contentType: 'audio/mp4; codecs="avc1.42E01E"'}],
       }
     ],
     shouldPass: false,
   },
   {
     name: 'MP4 video container with constrained baseline h.264',
-    keySystem: CLEARKEY_ID,
     options: [
       {
         label: SUPPORTED_LABEL,
         initDataTypes: ['cenc'],
         videoCapabilities: [{contentType: 'video/mp4; codecs="avc1.42E01E"'}],
       }
     ],
     expectedConfig: {
       label: SUPPORTED_LABEL,
       initDataTypes: ['cenc'],
       videoCapabilities: [{contentType: 'video/mp4; codecs="avc1.42E01E"'}],
     },
     shouldPass: true,
   },
   {
     name: 'MP4 video container with invalid codecs',
-    keySystem: CLEARKEY_ID,
     options: [
       {
         initDataTypes: ['cenc'],
         videoCapabilities: [{contentType: 'video/mp4; codecs="bogus"'}],
       }
     ],
     shouldPass: false,
   },
   {
     name: 'MP4 video container with both audio and video codec type in videoType',
-    keySystem: CLEARKEY_ID,
     options: [
       {
         initDataTypes: ['cenc'],
         videoCapabilities: [{contentType: 'video/mp4; codecs="avc1.42E01E,mp4a.40.2"'}],
       }
     ],
     shouldPass: false,
   },
   {
     name: 'MP4 audio and video type both specified',
-    keySystem: CLEARKEY_ID,
     options: [
       {
         label: SUPPORTED_LABEL,
         initDataTypes: ['cenc'],
         videoCapabilities: [{contentType: 'video/mp4; codecs="avc1.42E01E"'}],
         audioCapabilities: [{contentType: 'audio/mp4; codecs="mp4a.40.2"'}],
       }
     ],
@@ -386,51 +424,48 @@ var tests = [
       initDataTypes: ['cenc'],
       videoCapabilities: [{contentType: 'video/mp4; codecs="avc1.42E01E"'}],
       audioCapabilities: [{contentType: 'audio/mp4; codecs="mp4a.40.2"'}],
     },
     shouldPass: true,
   },
   {
     name: 'Basic WebM video',
-    keySystem: CLEARKEY_ID,
     options: [
       {
         label: SUPPORTED_LABEL,
         initDataTypes: ['webm'],
         videoCapabilities: [{contentType: 'video/webm'}],
       }
     ],
     expectedConfig: {
       label: SUPPORTED_LABEL,
       initDataTypes: ['webm'],
       videoCapabilities: [{contentType: 'video/webm'}],
     },
     shouldPass: true,
   },
   {
     name: 'Basic WebM audio',
-    keySystem: CLEARKEY_ID,
     options: [
       {
         label: SUPPORTED_LABEL,
         initDataTypes: ['webm'],
         audioCapabilities: [{contentType: 'audio/webm'}],
       }
     ],
     expectedConfig: {
       label: SUPPORTED_LABEL,
       initDataTypes: ['webm'],
       audioCapabilities: [{contentType: 'audio/webm'}],
     },
     shouldPass: true,
   },
   {
     name: 'Webm with Vorbis audio and VP8 video.',
-    keySystem: CLEARKEY_ID,
     options: [
       {
         label: SUPPORTED_LABEL,
         initDataTypes: ['webm'],
         videoCapabilities: [{contentType: 'video/webm;codecs="vp8"'}],
         audioCapabilities: [{contentType: 'audio/webm;codecs="vorbis"'}],
       }
     ],
@@ -439,17 +474,16 @@ var tests = [
       initDataTypes: ['webm'],
       videoCapabilities: [{contentType: 'video/webm;codecs="vp8"'}],
       audioCapabilities: [{contentType: 'audio/webm;codecs="vorbis"'}],
     },
     shouldPass: true,
   },
   {
     name: 'Webm with Vorbis audio and VP9 video.',
-    keySystem: CLEARKEY_ID,
     options: [
       {
         label: SUPPORTED_LABEL,
         initDataTypes: ['webm'],
         videoCapabilities: [{contentType: 'video/webm;codecs="vp9"'}],
         audioCapabilities: [{contentType: 'audio/webm;codecs="vorbis"'}],
       }
     ],
@@ -458,73 +492,72 @@ var tests = [
       initDataTypes: ['webm'],
       videoCapabilities: [{contentType: 'video/webm;codecs="vp9"'}],
       audioCapabilities: [{contentType: 'audio/webm;codecs="vorbis"'}],
     },
     shouldPass: true,
   },
   {
     name: 'Webm with bogus video.',
-    keySystem: CLEARKEY_ID,
     options: [
       {
         initDataTypes: ['webm'],
         videoCapabilities: [{contentType: 'video/webm;codecs="bogus"'}],
       }
     ],
     shouldPass: false,
   },
   {
     name: "CDM version less than",
-    keySystem: CLEARKEY_ID + ".0",
+    keySystem: CLEARKEY_KEYSYSTEM + ".0",
     options: [
       {
         label: SUPPORTED_LABEL,
         initDataTypes: ['cenc'],
         videoCapabilities: [{contentType: 'video/mp4'}],
       }
     ],
     expectedConfig: {
       label: SUPPORTED_LABEL,
       initDataTypes: ['cenc'],
       videoCapabilities: [{contentType: 'video/mp4'}],
     },
     shouldPass: true
   },
   {
     name: "CDM version equal to",
-    keySystem: CLEARKEY_ID + ".1",
+    keySystem: CLEARKEY_KEYSYSTEM + ".1",
     options: [
       {
         label: SUPPORTED_LABEL,
         initDataTypes: ['cenc'],
         videoCapabilities: [{contentType: 'video/mp4'}],
       }
     ],
     expectedConfig: {
       label: SUPPORTED_LABEL,
       initDataTypes: ['cenc'],
       videoCapabilities: [{contentType: 'video/mp4'}],
     },
     shouldPass: true
   },
   {
     name: "CDM version greater than",
-    keySystem: CLEARKEY_ID + ".2",
+    keySystem: CLEARKEY_KEYSYSTEM + ".2",
     options: [
       {
         initDataTypes: ['cenc'],
         videoCapabilities: [{contentType: 'video/mp4'}],
       }
     ],
     shouldPass: false
   },
   {
     name: "Non-whole number CDM version",
-    keySystem: CLEARKEY_ID + ".0.1",
+    keySystem: CLEARKEY_KEYSYSTEM + ".0.1",
     options: [
       {
         initDataTypes: ['cenc'],
         videoCapabilities: [{contentType: 'video/mp4'}],
       }
     ],
     shouldPass: false
   },
--- a/dom/media/test/test_eme_request_notifications.html
+++ b/dom/media/test/test_eme_request_notifications.html
@@ -31,17 +31,17 @@ function SetPrefs(prefs) {
 }
 
 function Test(test) {
   var p = test.prefs ? SetPrefs(test.prefs) : Promise.resolve();
   observedStatus = "nothing";
   var name = "'" + test.keySystem + "'";
   return p.then(function() {
     return new Promise(function(resolve, reject) {
-      navigator.requestMediaKeySystemAccess(test.keySystem, [{initDataTypes:["cenc"]}])
+      navigator.requestMediaKeySystemAccess(test.keySystem, gCencMediaKeySystemConfig)
       .then(
         function(keySystemAccess) {
           return keySystemAccess.createMediaKeys();
         })
       .then(
         function(mediaKeys) {
           ok(test.shouldPass, name + " passed and was expected to " + (test.shouldPass ? "pass" : "fail"));
           is(observedStatus, test.expectedStatus, name + " observer service result");
@@ -54,39 +54,37 @@ function Test(test) {
           is(observedStatus, test.expectedStatus, name + " observer service result");
           resolve();
         }
       );
     });
   });
 }
 
-const CLEARKEY_ID = 'org.w3.clearkey';
-
 var tests = [
   {
-    keySystem: CLEARKEY_ID,
+    keySystem: CLEARKEY_KEYSYSTEM,
     shouldPass: false,
     expectedStatus: 'api-disabled',
     prefs: [["media.eme.enabled", false], ["media.eme.clearkey.enabled", true]]
   },
   {
-    keySystem: CLEARKEY_ID,
+    keySystem: CLEARKEY_KEYSYSTEM,
     shouldPass: false,
     expectedStatus: 'cdm-disabled',
     prefs: [["media.eme.enabled", true], ["media.eme.clearkey.enabled", false]]
   },
   {
-    keySystem: CLEARKEY_ID + '.10000' , // A stupendously high min CDM version, presumably not installed.
+    keySystem: CLEARKEY_KEYSYSTEM + '.10000' , // A stupendously high min CDM version, presumably not installed.
     shouldPass: false,
     expectedStatus: 'cdm-insufficient-version',
     prefs: [["media.eme.enabled", true], ["media.eme.clearkey.enabled", true]]
   },
   {
-    keySystem: CLEARKEY_ID,
+    keySystem: CLEARKEY_KEYSYSTEM,
     shouldPass: true,
     expectedStatus: 'cdm-created',
     prefs: [["media.eme.enabled", true], ["media.eme.clearkey.enabled", true]]
   },
 ];
 
 SetupEMEPref(function() {
   tests.reduce(function(p,c,i,array) {
--- a/dom/media/test/test_eme_session_callable_value.html
+++ b/dom/media/test/test_eme_session_callable_value.html
@@ -7,17 +7,17 @@
   <script type="text/javascript" src="manifest.js"></script>
   <script type="text/javascript" src="eme.js"></script>
 </head>
 <body>
 <pre id="test">
 <script class="testbody" type="text/javascript">
 
 function Test() {
-  navigator.requestMediaKeySystemAccess('org.w3.clearkey', [{ initDataTypes: ['cenc'] }])
+  navigator.requestMediaKeySystemAccess(CLEARKEY_KEYSYSTEM, gCencMediaKeySystemConfig)
   .then(access => access.createMediaKeys())
   .then(mediaKeys => {
     var initData = (new TextEncoder()).encode( 'this is an invalid license, and that is ok');
     var s = mediaKeys.createSession("temporary");
     s.generateRequest("cenc", initData); // ignore result.
     // "update()" call should fail, because MediaKeySession is "not callable"
     // yet, since CDM won't have had a chance to set the sessionId on MediaKeySession.
     return s.update(initData);
--- a/dom/media/test/test_eme_setMediaKeys_before_attach_MediaSource.html
+++ b/dom/media/test/test_eme_setMediaKeys_before_attach_MediaSource.html
@@ -9,17 +9,17 @@
 </head>
 <body>
 <pre id="test">
 <script class="testbody" type="text/javascript">
 
 function beginTest() {
   var video = document.createElement("video");
 
-  navigator.requestMediaKeySystemAccess("org.w3.clearkey", [{initDataTypes: ["cenc"]}])
+  navigator.requestMediaKeySystemAccess(CLEARKEY_KEYSYSTEM, gCencMediaKeySystemConfig)
     .then(function(keySystemAccess) {
       return keySystemAccess.createMediaKeys();
     })
     .then(mediaKeys => {
       return video.setMediaKeys(mediaKeys);
     })
     .then(() => {
       var ms = new MediaSource();
--- a/dom/media/tests/mochitest/head.js
+++ b/dom/media/tests/mochitest/head.js
@@ -319,17 +319,17 @@ function setupEnvironment() {
 
   // Running as a Mochitest.
   SimpleTest.requestFlakyTimeout("WebRTC inherently depends on timeouts");
   window.finish = () => SimpleTest.finish();
   SpecialPowers.pushPrefEnv(defaultMochitestPrefs, setTestOptions);
 
   // We don't care about waiting for this to complete, we just want to ensure
   // that we don't build up a huge backlog of GC work.
-  SpecialPowers.exactGC(window);
+  SpecialPowers.exactGC();
 }
 
 // This is called by steeplechase; which provides the test configuration options
 // directly to the test through this function.  If we're not on steeplechase,
 // the test is configured directly and immediately.
 function run_test(is_initiator,timeout) {
   var options = { is_local: is_initiator,
                   is_remote: !is_initiator };
--- a/dom/media/webaudio/AudioDestinationNode.cpp
+++ b/dom/media/webaudio/AudioDestinationNode.cpp
@@ -152,16 +152,17 @@ public:
     // AudioDestinationNode::FireOfflineCompletionEvent.
 
     // Create the input buffer
     ErrorResult rv;
     RefPtr<AudioBuffer> renderedBuffer =
       AudioBuffer::Create(context, mNumberOfChannels, mLength, mSampleRate,
                           mBuffer.forget(), rv);
     if (rv.Failed()) {
+      rv.SuppressException();
       return;
     }
 
     aNode->ResolvePromise(renderedBuffer);
 
     RefPtr<OnCompleteTask> onCompleteTask =
       new OnCompleteTask(context, renderedBuffer);
     NS_DispatchToMainThread(onCompleteTask);
--- a/dom/media/webaudio/ScriptProcessorNode.cpp
+++ b/dom/media/webaudio/ScriptProcessorNode.cpp
@@ -427,16 +427,17 @@ private:
         RefPtr<AudioBuffer> inputBuffer;
         if (mInputBuffer) {
           ErrorResult rv;
           inputBuffer =
             AudioBuffer::Create(context, inputChannelCount,
                                 aNode->BufferSize(), context->SampleRate(),
                                 mInputBuffer.forget(), rv);
           if (rv.Failed()) {
+            rv.SuppressException();
             return nullptr;
           }
         }
 
         // Ask content to produce data in the output buffer
         // Note that we always avoid creating the output buffer here, and we try to
         // avoid creating the input buffer as well.  The AudioProcessingEvent class
         // knows how to lazily create them if needed once the script tries to access
--- a/dom/media/webaudio/test/browser_bug1181073.js
+++ b/dom/media/webaudio/test/browser_bug1181073.js
@@ -50,17 +50,17 @@ add_task(function*() {
         resolve(end - start);
       }, 0);
     });
   });
 
   ok(time < 1000, "Interval is not throttled with audio playing (" + time + " ms)");
 
   // Destroy the oscillator, but not the audio context
-  yield new Promise(resolve => SpecialPowers.exactGC(browser.contentWindow, resolve));
+  yield new Promise(resolve => SpecialPowers.exactGC(resolve));
   yield oscillatorDemisePromise;
 
   time = yield ContentTask.spawn(browser, null, function () {
     return new Promise(resolve => {
       let start = content.performance.now();
       let id = content.window.setInterval(function() {
         let end = content.performance.now();
         content.window.clearInterval(id);
--- a/dom/network/TCPSocketParent.cpp
+++ b/dom/network/TCPSocketParent.cpp
@@ -269,17 +269,20 @@ TCPSocketParent::RecvOpenBind(const nsCS
 }
 
 bool
 TCPSocketParent::RecvStartTLS()
 {
   NS_ENSURE_TRUE(mSocket, true);
   ErrorResult rv;
   mSocket->UpgradeToSecure(rv);
-  NS_ENSURE_FALSE(rv.Failed(), true);
+  if (NS_WARN_IF(rv.Failed())) {
+    rv.SuppressException();
+  }
+
   return true;
 }
 
 bool
 TCPSocketParent::RecvSuspend()
 {
   NS_ENSURE_TRUE(mSocket, true);
   mSocket->Suspend();
@@ -287,17 +290,20 @@ TCPSocketParent::RecvSuspend()
 }
 
 bool
 TCPSocketParent::RecvResume()
 {
   NS_ENSURE_TRUE(mSocket, true);
   ErrorResult rv;
   mSocket->Resume(rv);
-  NS_ENSURE_FALSE(rv.Failed(), true);
+  if (NS_WARN_IF(rv.Failed())) {
+    rv.SuppressException();
+  }
+
   return true;
 }
 
 bool
 TCPSocketParent::RecvData(const SendableData& aData,
                           const uint32_t& aTrackingNumber)
 {
   ErrorResult rv;
--- a/dom/performance/PerformanceObserver.cpp
+++ b/dom/performance/PerformanceObserver.cpp
@@ -111,17 +111,19 @@ PerformanceObserver::Notify()
   if (mQueuedEntries.IsEmpty()) {
     return;
   }
   RefPtr<PerformanceObserverEntryList> list =
     new PerformanceObserverEntryList(this, mQueuedEntries);
 
   ErrorResult rv;
   mCallback->Call(this, *list, *this, rv);
-  NS_WARN_IF(rv.Failed());
+  if (NS_WARN_IF(rv.Failed())) {
+    rv.SuppressException();
+  }
   mQueuedEntries.Clear();
 }
 
 void
 PerformanceObserver::QueueEntry(PerformanceEntry* aEntry)
 {
   MOZ_ASSERT(aEntry);
 
--- a/dom/plugins/base/nsIPluginHost.idl
+++ b/dom/plugins/base/nsIPluginHost.idl
@@ -95,16 +95,28 @@ interface nsIPluginHost : nsISupports
    *
    * @mimeType The MIME type we're interested in.
    * @excludeFlags Set of the EXCLUDE_* flags above, defaulting to EXCLUDE_NONE.
    */
   ACString getPermissionStringForType(in AUTF8String mimeType,
                                       [optional] in uint32_t excludeFlags);
 
   /**
+   * Get the "permission string" for the plugin.  This is a string that can be
+   * passed to the permission manager to see whether the plugin is allowed to
+   * run, for example.  This will typically be based on the plugin's "nice name"
+   * and its blocklist state.
+   *
+   * @tag The tage we're interested in
+   * @excludeFlags Set of the EXCLUDE_* flags above, defaulting to EXCLUDE_NONE.
+   */
+  ACString getPermissionStringForTag(in nsIPluginTag tag,
+                                     [optional] in uint32_t excludeFlags);
+
+  /**
    * Get the nsIPluginTag for this MIME type. This method works with both
    * enabled and disabled/blocklisted plugins, but an enabled plugin will
    * always be returned if available.
    *
    * A fake plugin tag, if one exists and is available, will be returned in
    * preference to NPAPI plugin tags unless excluded by the excludeFlags.
    *
    * @mimeType The MIME type we're interested in.
--- a/dom/plugins/base/nsPluginHost.cpp
+++ b/dom/plugins/base/nsPluginHost.cpp
@@ -1105,33 +1105,41 @@ NS_IMETHODIMP
 nsPluginHost::GetPermissionStringForType(const nsACString &aMimeType,
                                          uint32_t aExcludeFlags,
                                          nsACString &aPermissionString)
 {
   nsCOMPtr<nsIPluginTag> tag;
   nsresult rv = GetPluginTagForType(aMimeType, aExcludeFlags,
                                     getter_AddRefs(tag));
   NS_ENSURE_SUCCESS(rv, rv);
-  NS_ENSURE_TRUE(tag, NS_ERROR_FAILURE);
+  return GetPermissionStringForTag(tag, aExcludeFlags, aPermissionString);
+}
+
+NS_IMETHODIMP
+nsPluginHost::GetPermissionStringForTag(nsIPluginTag* aTag,
+                                        uint32_t aExcludeFlags,
+                                        nsACString &aPermissionString)
+{
+  NS_ENSURE_TRUE(aTag, NS_ERROR_FAILURE);
 
   aPermissionString.Truncate();
   uint32_t blocklistState;
-  rv = tag->GetBlocklistState(&blocklistState);
+  nsresult rv = aTag->GetBlocklistState(&blocklistState);
   NS_ENSURE_SUCCESS(rv, rv);
 
   if (blocklistState == nsIBlocklistService::STATE_VULNERABLE_UPDATE_AVAILABLE ||
       blocklistState == nsIBlocklistService::STATE_VULNERABLE_NO_UPDATE) {
     aPermissionString.AssignLiteral("plugin-vulnerable:");
   }
   else {
     aPermissionString.AssignLiteral("plugin:");
   }
 
   nsCString niceName;
-  rv = tag->GetNiceName(niceName);
+  rv = aTag->GetNiceName(niceName);
   NS_ENSURE_SUCCESS(rv, rv);
   NS_ENSURE_TRUE(!niceName.IsEmpty(), NS_ERROR_FAILURE);
 
   aPermissionString.Append(niceName);
 
   return NS_OK;
 }
 
--- a/dom/plugins/base/nsPluginTags.cpp
+++ b/dom/plugins/base/nsPluginTags.cpp
@@ -318,17 +318,17 @@ nsPluginTag::nsPluginTag(uint32_t aId,
 {
 }
 
 nsPluginTag::~nsPluginTag()
 {
   NS_ASSERTION(!mNext, "Risk of exhausting the stack space, bug 486349");
 }
 
-NS_IMPL_ISUPPORTS(nsPluginTag, nsIPluginTag, nsIInternalPluginTag)
+NS_IMPL_ISUPPORTS(nsPluginTag, nsPluginTag,  nsIInternalPluginTag, nsIPluginTag)
 
 void nsPluginTag::InitMime(const char* const* aMimeTypes,
                            const char* const* aMimeDescriptions,
                            const char* const* aExtensions,
                            uint32_t aVariantCount)
 {
   if (!aMimeTypes) {
     return;
--- a/dom/plugins/base/nsPluginTags.h
+++ b/dom/plugins/base/nsPluginTags.h
@@ -25,16 +25,19 @@ struct FakePluginTagInit;
 } // namespace dom
 } // namespace mozilla
 
 // An interface representing plugin tags internally.
 #define NS_IINTERNALPLUGINTAG_IID \
 { 0xe8fdd227, 0x27da, 0x46ee,     \
   { 0xbe, 0xf3, 0x1a, 0xef, 0x5a, 0x8f, 0xc5, 0xb4 } }
 
+#define NS_PLUGINTAG_IID \
+  { 0xcce2e8b9, 0x9702, 0x4d4b, \
+   { 0xbe, 0xa4, 0x7c, 0x1e, 0x13, 0x1f, 0xaf, 0x78 } }
 class nsIInternalPluginTag : public nsIPluginTag
 {
 public:
   NS_DECLARE_STATIC_IID_ACCESSOR(NS_IINTERNALPLUGINTAG_IID)
 
   nsIInternalPluginTag();
   nsIInternalPluginTag(const char* aName, const char* aDescription,
                        const char* aFileName, const char* aVersion);
@@ -85,16 +88,18 @@ protected:
 };
 NS_DEFINE_STATIC_IID_ACCESSOR(nsIInternalPluginTag, NS_IINTERNALPLUGINTAG_IID)
 
 // A linked-list of plugin information that is used for instantiating plugins
 // and reflecting plugin information into JavaScript.
 class nsPluginTag final : public nsIInternalPluginTag
 {
 public:
+  NS_DECLARE_STATIC_IID_ACCESSOR(NS_PLUGINTAG_IID)
+
   NS_DECL_ISUPPORTS
   NS_DECL_NSIPLUGINTAG
 
   // These must match the STATE_* values in nsIPluginTag.idl
   enum PluginState {
     ePluginState_Disabled = 0,
     ePluginState_Clicktoplay = 1,
     ePluginState_Enabled = 2,
@@ -187,16 +192,17 @@ private:
                 const char* const* aExtensions,
                 uint32_t aVariantCount);
   void InitSandboxLevel();
   nsresult EnsureMembersAreUTF8();
   void FixupVersion();
 
   static uint32_t sNextId;
 };
+NS_DEFINE_STATIC_IID_ACCESSOR(nsPluginTag, NS_PLUGINTAG_IID)
 
 // A class representing "fake" plugin tags; that is plugin tags not
 // corresponding to actual NPAPI plugins.  In practice these are all
 // JS-implemented plugins; maybe we want a better name for this class?
 class nsFakePluginTag : public nsIInternalPluginTag,
                         public nsIFakePluginTag
 {
 public:
--- a/dom/plugins/ipc/PluginInstanceChild.cpp
+++ b/dom/plugins/ipc/PluginInstanceChild.cpp
@@ -3524,17 +3524,17 @@ PluginInstanceChild::EnsureCurrentBuffer
     return true;
 #elif defined(XP_MACOSX)
 
     if (!mDoubleBufferCARenderer.HasCALayer()) {
         void *caLayer = nullptr;
         if (mDrawingModel == NPDrawingModelCoreGraphics) {
             if (!mCGLayer) {
                 caLayer = mozilla::plugins::PluginUtilsOSX::GetCGLayer(CallCGDraw,
-                                                                       this, false,
+                                                                       this,
                                                                        mContentsScaleFactor);
 
                 if (!caLayer) {
                     PLUGIN_LOG_DEBUG(("GetCGLayer failed."));
                     return false;
                 }
             }
             mCGLayer = caLayer;
--- a/dom/plugins/ipc/PluginModuleParent.cpp
+++ b/dom/plugins/ipc/PluginModuleParent.cpp
@@ -1909,19 +1909,19 @@ PluginModuleParent::NPP_SetValue(NPP ins
 bool
 PluginModuleParent::RecvBackUpXResources(const FileDescriptor& aXSocketFd)
 {
 #ifndef MOZ_X11
     NS_RUNTIMEABORT("This message only makes sense on X11 platforms");
 #else
     MOZ_ASSERT(0 > mPluginXSocketFdDup.get(),
                "Already backed up X resources??");
-    mPluginXSocketFdDup.forget();
     if (aXSocketFd.IsValid()) {
-      mPluginXSocketFdDup.reset(aXSocketFd.PlatformHandle());
+      auto rawFD = aXSocketFd.ClonePlatformHandle();
+      mPluginXSocketFdDup.reset(rawFD.release());
     }
 #endif
     return true;
 }
 
 void
 PluginModuleParent::NPP_URLRedirectNotify(NPP instance, const char* url,
                                           int32_t status, void* notifyData)
--- a/dom/plugins/ipc/PluginQuirks.cpp
+++ b/dom/plugins/ipc/PluginQuirks.cpp
@@ -45,17 +45,16 @@ int GetQuirksFromMimeTypeAndFilename(con
         quirks |= QUIRK_QUICKTIME_AVOID_SETWINDOW;
     }
 #endif
 
 #ifdef XP_MACOSX
     // Whitelist Flash and Quicktime to support offline renderer
     NS_NAMED_LITERAL_CSTRING(quicktime, "QuickTime Plugin.plugin");
     if (specialType == nsPluginHost::eSpecialType_Flash) {
-        quirks |= QUIRK_FLASH_AVOID_CGMODE_CRASHES;
         quirks |= QUIRK_ALLOW_OFFLINE_RENDERER;
     } else if (FindInReadable(quicktime, aPluginFilename)) {
         quirks |= QUIRK_ALLOW_OFFLINE_RENDERER;
     }
 #endif
 
 #ifdef OS_WIN
     if (specialType == nsPluginHost::eSpecialType_Unity) {
--- a/dom/plugins/ipc/PluginQuirks.h
+++ b/dom/plugins/ipc/PluginQuirks.h
@@ -42,29 +42,24 @@ enum PluginQuirks {
   QUIRK_QUICKTIME_AVOID_SETWINDOW                 = 1 << 7,
   // Win: Check to make sure the parent window has focus before calling
   // set focus on the child. Addresses a full screen dialog prompt
   // problem in Silverlight.
   QUIRK_SILVERLIGHT_FOCUS_CHECK_PARENT            = 1 << 8,
   // Mac: Allow the plugin to use offline renderer mode.
   // Use this only if the plugin is certified the support the offline renderer.
   QUIRK_ALLOW_OFFLINE_RENDERER                    = 1 << 9,
-  // Mac: Work around a Flash bug that can cause plugin process crashes
-  // in CoreGraphics mode:  The Flash plugin sometimes accesses the
-  // CGContextRef we pass to it in NPP_HandleEvent(NPCocoaEventDrawRect)
-  // outside of that call.  See bug 804606.
-  QUIRK_FLASH_AVOID_CGMODE_CRASHES                = 1 << 10,
   // Work around a Flash bug where it fails to check the error code of a
   // NPN_GetValue(NPNVdocumentOrigin) call before trying to dereference
   // its char* output.
-  QUIRK_FLASH_RETURN_EMPTY_DOCUMENT_ORIGIN        = 1 << 11,
+  QUIRK_FLASH_RETURN_EMPTY_DOCUMENT_ORIGIN        = 1 << 10,
   // Win: Addresses a Unity bug with mouse capture.
-  QUIRK_UNITY_FIXUP_MOUSE_CAPTURE                 = 1 << 12,
+  QUIRK_UNITY_FIXUP_MOUSE_CAPTURE                 = 1 << 11,
   // Win: Hook IMM32 API to handle IME event on windowless plugin
-  QUIRK_WINLESS_HOOK_IME                          = 1 << 13,
+  QUIRK_WINLESS_HOOK_IME                          = 1 << 12,
 };
 
 int GetQuirksFromMimeTypeAndFilename(const nsCString& aMimeType,
                                      const nsCString& aPluginFilename);
 
 } /* namespace plugins */
 } /* namespace mozilla */
 
--- a/dom/plugins/ipc/PluginUtilsOSX.h
+++ b/dom/plugins/ipc/PluginUtilsOSX.h
@@ -20,18 +20,17 @@ typedef void (*RemoteProcessEvents) (voi
 
 NPError ShowCocoaContextMenu(void* aMenu, int aX, int aY, void* pluginModule, RemoteProcessEvents remoteEvent);
 
 void InvokeNativeEventLoop();
 
 // Need to call back and send a cocoa draw event to the plugin.
 typedef void (*DrawPluginFunc) (CGContextRef, void*, nsIntRect aUpdateRect);
 
-void* GetCGLayer(DrawPluginFunc aFunc, void* aPluginInstance,
-                 bool aAvoidCGCrashes, double aContentsScaleFactor);
+void* GetCGLayer(DrawPluginFunc aFunc, void* aPluginInstance, double aContentsScaleFactor);
 void ReleaseCGLayer(void* cgLayer);
 void Repaint(void* cgLayer, nsIntRect aRect);
 
 bool SetProcessName(const char* aProcessName);
 
 /*
  * Provides a wrapper around nsCARenderer to manage double buffering
  * without having to unbind nsCARenderer on every surface swaps.
--- a/dom/plugins/ipc/PluginUtilsOSX.mm
+++ b/dom/plugins/ipc/PluginUtilsOSX.mm
@@ -21,24 +21,20 @@
 @end
 
 using namespace mozilla::plugins::PluginUtilsOSX;
 
 @interface CGBridgeLayer : CALayer {
   DrawPluginFunc mDrawFunc;
   void* mPluginInstance;
   nsIntRect mUpdateRect;
-  BOOL mAvoidCGCrashes;
-  CGContextRef mLastCGContext;
 }
 - (void)setDrawFunc:(DrawPluginFunc)aFunc
-     pluginInstance:(void*)aPluginInstance
-     avoidCGCrashes:(BOOL)aAvoidCGCrashes;
+     pluginInstance:(void*)aPluginInstance;
 - (void)updateRect:(nsIntRect)aRect;
-- (void)protectLastCGContext;
 
 @end
 
 // CGBitmapContextSetData() is an undocumented function present (with
 // the same signature) since at least OS X 10.5.  As the name suggests,
 // it's used to replace the "data" in a bitmap context that was
 // originally specified in a call to CGBitmapContextCreate() or
 // CGBitmapContextCreateWithData().
@@ -56,91 +52,41 @@ CGBitmapContextSetDataFunc CGBitmapConte
 @implementation CGBridgeLayer
 - (void) updateRect:(nsIntRect)aRect
 {
    mUpdateRect.UnionRect(mUpdateRect, aRect);
 }
 
 - (void) setDrawFunc:(DrawPluginFunc)aFunc
       pluginInstance:(void*)aPluginInstance
-      avoidCGCrashes:(BOOL)aAvoidCGCrashes
 {
   mDrawFunc = aFunc;
   mPluginInstance = aPluginInstance;
-  mAvoidCGCrashes = aAvoidCGCrashes;
-  mLastCGContext = nil;
-}
-
-// The Flash plugin, in very unusual circumstances, can (in CoreGraphics
-// mode) try to access the CGContextRef from -[CGBridgeLayer drawInContext:]
-// outside of any call to NPP_HandleEvent(NPCocoaEventDrawRect).  This usually
-// crashes the plugin process (probably because it tries to access deleted
-// memory).  We stop these crashes from happening by holding a reference to
-// the CGContextRef, and also by ensuring that it's data won't get deleted.
-// The CGContextRef won't "work" in this form.  But this won't cause trouble
-// for plugins that do things correctly (that don't access this CGContextRef
-// outside of the call to NPP_HandleEvent() that passes it to the plugin).
-// The OS may reuse this CGContextRef (it may get passed to other calls to
-// -[CGBridgeLayer drawInContext:]).  But before each call the OS calls
-// CGBitmapContextSetData() to replace its data, which undoes the changes
-// we make here.  See bug 804606.
-- (void)protectLastCGContext
-{
-  if (!mAvoidCGCrashes || !mLastCGContext) {
-    return;
-  }
-
-  static char ensuredData[128] = {0};
-
-  if (!CGBitmapContextSetDataPtr) {
-    CGBitmapContextSetDataPtr = (CGBitmapContextSetDataFunc)
-      dlsym(RTLD_DEFAULT, "CGBitmapContextSetData");
-  }
-
-  if (CGBitmapContextSetDataPtr && (GetContextType(mLastCGContext) == CG_CONTEXT_TYPE_BITMAP)) {
-    CGBitmapContextSetDataPtr(mLastCGContext, 0, 0, 1, 1, ensuredData, 8, 32, 64);
-  }
 }
 
 - (void)drawInContext:(CGContextRef)aCGContext
 {
   ::CGContextSaveGState(aCGContext); 
   ::CGContextTranslateCTM(aCGContext, 0, self.bounds.size.height);
   ::CGContextScaleCTM(aCGContext, (CGFloat) 1, (CGFloat) -1);
 
   mUpdateRect = nsIntRect(0, 0, self.bounds.size.width, self.bounds.size.height);
 
   mDrawFunc(aCGContext, mPluginInstance, mUpdateRect);
 
   ::CGContextRestoreGState(aCGContext);
 
-  if (mAvoidCGCrashes) {
-    if (mLastCGContext) {
-      ::CGContextRelease(mLastCGContext);
-    }
-    mLastCGContext = aCGContext;
-    ::CGContextRetain(mLastCGContext);
-  }
-
   mUpdateRect.SetEmpty();
 }
 
-- (void)dealloc
-{
-  if (mLastCGContext) {
-    ::CGContextRelease(mLastCGContext);
-  }
-  [super dealloc];
-}
-
 @end
 
-void* mozilla::plugins::PluginUtilsOSX::GetCGLayer(DrawPluginFunc aFunc, void* aPluginInstance,
-                                                   bool aAvoidCGCrashes, double aContentsScaleFactor)
-{
+void* mozilla::plugins::PluginUtilsOSX::GetCGLayer(DrawPluginFunc aFunc, 
+                                                   void* aPluginInstance, 
+                                                   double aContentsScaleFactor) {
   CGBridgeLayer *bridgeLayer = [[CGBridgeLayer alloc] init];
 
   // We need to make bridgeLayer behave properly when its superlayer changes
   // size (in nsCARenderer::SetBounds()).
   bridgeLayer.autoresizingMask = kCALayerWidthSizable | kCALayerHeightSizable;
   bridgeLayer.needsDisplayOnBoundsChange = YES;
   NSNull *nullValue = [NSNull null];
   NSDictionary *actions = [NSDictionary dictionaryWithObjectsAndKeys:
@@ -160,34 +106,32 @@ void* mozilla::plugins::PluginUtilsOSX::
 #if defined(MAC_OS_X_VERSION_10_7) && \
     MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_7
   if ([bridgeLayer respondsToSelector:@selector(setContentsScale:)]) {
     bridgeLayer.contentsScale = aContentsScaleFactor;
   }
 #endif
 
   [bridgeLayer setDrawFunc:aFunc
-            pluginInstance:aPluginInstance
-            avoidCGCrashes:aAvoidCGCrashes];
+            pluginInstance:aPluginInstance];
   return bridgeLayer;
 }
 
 void mozilla::plugins::PluginUtilsOSX::ReleaseCGLayer(void *cgLayer) {
   CGBridgeLayer *bridgeLayer = (CGBridgeLayer*)cgLayer;
   [bridgeLayer release];
 }
 
 void mozilla::plugins::PluginUtilsOSX::Repaint(void *caLayer, nsIntRect aRect) {
   CGBridgeLayer *bridgeLayer = (CGBridgeLayer*)caLayer;
   [CATransaction begin];
   [bridgeLayer updateRect:aRect];
   [bridgeLayer setNeedsDisplay];
   [bridgeLayer displayIfNeeded];
   [CATransaction commit];
-  [bridgeLayer protectLastCGContext];
 }
 
 @interface EventProcessor : NSObject {
   RemoteProcessEvents   aRemoteEvents;
   void                 *aPluginModule;
 }
 - (void)setRemoteEvents:(RemoteProcessEvents) remoteEvents pluginModule:(void*) pluginModule;
 - (void)onTick;
--- a/dom/security/nsCSPUtils.cpp
+++ b/dom/security/nsCSPUtils.cpp
@@ -13,16 +13,18 @@
 #include "nsICryptoHash.h"
 #include "nsIScriptError.h"
 #include "nsIServiceManager.h"
 #include "nsIStringBundle.h"
 #include "nsIURL.h"
 #include "nsReadableUtils.h"
 #include "nsSandboxFlags.h"
 
+#define DEFAULT_PORT -1
+
 static mozilla::LogModule*
 GetCspUtilsLog()
 {
   static mozilla::LazyLogModule gCspUtilsPRLog("CSPUtils");
   return gCspUtilsPRLog;
 }
 
 #define CSPUTILSLOG(args) MOZ_LOG(GetCspUtilsLog(), mozilla::LogLevel::Debug, args)
@@ -432,16 +434,99 @@ nsCSPHostSrc::nsCSPHostSrc(const nsAStri
 {
   ToLowerCase(mHost);
 }
 
 nsCSPHostSrc::~nsCSPHostSrc()
 {
 }
 
+/*
+ * Checks whether the current directive permits a specific port.
+ * @param aEnforcementScheme
+ *        The scheme that this directive allows
+ *        (used to query the default port for that scheme)
+ * @param aEnforcementPort
+ *        The port that this directive allows
+ * @param aResourceURI
+ *        The uri of the subresource load
+ */
+bool
+permitsPort(const nsAString& aEnforcementScheme,
+            const nsAString& aEnforcementPort,
+            nsIURI* aResourceURI)
+{
+  // If enforcement port is the wildcard, don't block the load.
+  if (aEnforcementPort.EqualsASCII("*")) {
+    return true;
+  }
+
+  int32_t resourcePort;
+  nsresult rv = aResourceURI->GetPort(&resourcePort);
+  NS_ENSURE_SUCCESS(rv, false);
+
+  // Avoid unnecessary string creation/manipulation and don't block the
+  // load if the resource to be loaded uses the default port for that
+  // scheme and there is no port to be enforced.
+  // Note, this optimization relies on scheme checks within permitsScheme().
+  if (resourcePort == DEFAULT_PORT && aEnforcementPort.IsEmpty()) {
+    return true;
+  }
+
+  // By now we know at that either the resourcePort does not use the default
+  // port or there is a port restriction to be enforced. A port value of -1
+  // corresponds to the protocol's default port (eg. -1 implies port 80 for
+  // http URIs), in such a case we have to query the default port of the
+  // resource to be loaded.
+  if (resourcePort == DEFAULT_PORT) {
+    nsAutoCString resourceScheme;
+    rv = aResourceURI->GetScheme(resourceScheme);
+    NS_ENSURE_SUCCESS(rv, false);
+    resourcePort = NS_GetDefaultPort(resourceScheme.get());
+  }
+
+  // If there is a port to be enforced and the ports match, then
+  // don't block the load.
+  nsString resourcePortStr;
+  resourcePortStr.AppendInt(resourcePort);
+  if (aEnforcementPort.Equals(resourcePortStr)) {
+    return true;
+  }
+
+  // If there is no port to be enforced, query the default port for the load.
+  nsString enforcementPort(aEnforcementPort);
+  if (enforcementPort.IsEmpty()) {
+    // For scheme less sources, our parser always generates a scheme
+    // which is the scheme of the protected resource.
+    MOZ_ASSERT(!aEnforcementScheme.IsEmpty(),
+               "need a scheme to query default port");
+    int32_t defaultEnforcementPort =
+      NS_GetDefaultPort(NS_ConvertUTF16toUTF8(aEnforcementScheme).get());
+    enforcementPort.Truncate();
+    enforcementPort.AppendInt(defaultEnforcementPort);
+  }
+
+  // If default ports match, don't block the load
+  if (enforcementPort.Equals(resourcePortStr)) {
+    return true;
+  }
+
+  // Additional port matching where the regular URL matching algorithm
+  // treats insecure ports as matching their secure variants.
+  // default port for http is  :80
+  // default port for https is :443
+  if (enforcementPort.EqualsLiteral("80") &&
+      resourcePortStr.EqualsLiteral("443")) {
+    return true;
+  }
+
+  // ports do not match, block the load.
+  return false;
+}
+
 bool
 nsCSPHostSrc::permits(nsIURI* aUri, const nsAString& aNonce, bool aWasRedirected,
                       bool aReportOnly, bool aUpgradeInsecure) const
 {
   if (CSPUTILSLOGENABLED()) {
     nsAutoCString spec;
     aUri->GetSpec(spec);
     CSPUTILSLOG(("nsCSPHostSrc::permits, aUri: %s", spec.get()));
@@ -497,16 +582,21 @@ nsCSPHostSrc::permits(nsIURI* aUri, cons
       return false;
     }
   }
   // 4.6) host matching: Check if hosts match.
   else if (!mHost.Equals(NS_ConvertUTF8toUTF16(uriHost))) {
     return false;
   }
 
+  // Port matching: Check if the ports match.
+  if (!permitsPort(mScheme, mPort, aUri)) {
+    return false;
+  }
+
   // 4.9) Path matching: If there is a path, we have to enforce
   // path-level matching, unless the channel got redirected, see:
   // http://www.w3.org/TR/CSP11/#source-list-paths-and-redirects
   if (!aWasRedirected && !mPath.IsEmpty()) {
     // converting aUri into nsIURL so we can strip query and ref
     // example.com/test#foo     -> example.com/test
     // example.com/test?val=foo -> example.com/test
     nsCOMPtr<nsIURL> url = do_QueryInterface(aUri);
@@ -529,56 +619,17 @@ nsCSPHostSrc::permits(nsIURI* aUri, cons
     // check if the loading resource matches that whitelisted file.
     else {
       if (!mPath.Equals(NS_ConvertUTF8toUTF16(uriPath))) {
         return false;
       }
     }
   }
 
-  // 4.8) Port matching: If port uses wildcard, allow the load.
-  if (mPort.EqualsASCII("*")) {
-    return true;
-  }
-
-  // Before we can check if the port matches, we have to
-  // query the port from aUri.
-  int32_t uriPort;
-  rv = aUri->GetPort(&uriPort);
-  NS_ENSURE_SUCCESS(rv, false);
-
-  nsAutoCString scheme;
-  rv = aUri->GetScheme(scheme);
-  NS_ENSURE_SUCCESS(rv, false);
-
-  uriPort = (uriPort > 0) ? uriPort : NS_GetDefaultPort(scheme.get());
-
-  // 4.7) Default port matching: If mPort is empty, we have to compare default ports.
-  if (mPort.IsEmpty()) {
-    int32_t port = NS_GetDefaultPort(NS_ConvertUTF16toUTF8(mScheme).get());
-    if (port != uriPort) {
-      // We should not return false for scheme-less sources where the protected resource
-      // is http and the load is https, see: http://www.w3.org/TR/CSP2/#match-source-expression
-      // BUT, we only allow scheme-less sources to be upgraded from http to https if CSP
-      // does not explicitly define a port.
-      if (!(uriPort == NS_GetDefaultPort("https"))) {
-        return false;
-      }
-    }
-  }
-  // 4.7) Port matching: Compare the ports.
-  else {
-    nsString portStr;
-    portStr.AppendInt(uriPort);
-    if (!mPort.Equals(portStr)) {
-      return false;
-    }
-  }
-
-  // At the end: scheme, host, path, and port match -> allow the load.
+  // At the end: scheme, host, port and path match -> allow the load.
   return true;
 }
 
 bool
 nsCSPHostSrc::visit(nsCSPSrcVisitor* aVisitor) const
 {
   return aVisitor->visitHostSrc(*this);
 }
--- a/dom/security/test/csp/test_allow_https_schemes.html
+++ b/dom/security/test/csp/test_allow_https_schemes.html
@@ -23,17 +23,22 @@ SimpleTest.waitForExplicitFinish();
  *
  * Since the file is served over http:, the upgrade to https should be
  * permitted by CSP in case no port is specified.
  */
 
 var policies = [
   ["allowed", "example.com"],
   ["allowed", "example.com:443"],
-  ["blocked", "example.com:80"]
+  ["allowed", "example.com:80"],
+  ["allowed", "http://*:80"],
+  ["allowed", "https://*:443"],
+  // our testing framework only supports :80 and :443, but
+  // using :8000 in a policy does the trick for the test.
+  ["blocked", "example.com:8000"],
 ]
 
 var counter = 0;
 var policy;
 
 function loadNextTest() {
   if (counter == policies.length) {
     SimpleTest.finish();
--- a/dom/svg/nsSVGLength2.cpp
+++ b/dom/svg/nsSVGLength2.cpp
@@ -157,16 +157,20 @@ SVGElementMetrics::GetAxisLength(uint8_t
   return FixAxisLength(mCtx->GetLength(aCtxType));
 }
 
 bool
 SVGElementMetrics::EnsureCtx() const
 {
   if (!mCtx && mSVGElement) {
     mCtx = mSVGElement->GetCtx();
+    if (!mCtx && mSVGElement->IsSVGElement(nsGkAtoms::svg)) {
+      // mSVGElement must be the outer svg element
+      mCtx = static_cast<SVGSVGElement*>(mSVGElement);
+    }
   }
   return mCtx != nullptr;
 }
 
 NonSVGFrameUserSpaceMetrics::NonSVGFrameUserSpaceMetrics(nsIFrame* aFrame)
   : mFrame(aFrame)
 {
 }
--- a/dom/svg/test/mochitest.ini
+++ b/dom/svg/test/mochitest.ini
@@ -42,17 +42,16 @@ support-files =
 [test_fragments.html]
 [test_getCTM.html]
 [test_getElementById.xhtml]
 [test_getSubStringLength.xhtml]
 [test_hasFeature.xhtml]
 [test_lang.xhtml]
 skip-if = true # disabled-for-intermittent-failures--bug-701060
 [test_length.xhtml]
-skip-if = true
 [test_lengthParsing.html]
 [test_markerOrient.xhtml]
 [test_nonAnimStrings.xhtml]
 [test_non-scaling-stroke.html]
 [test_object-delayed-intrinsic-size.html]
 [test_onerror.xhtml]
 [test_pathAnimInterpolation.xhtml]
 [test_pathLength.html]
new file mode 100644
--- /dev/null
+++ b/dom/system/linux/GpsdLocationProvider.cpp
@@ -0,0 +1,468 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "GpsdLocationProvider.h"
+#include <errno.h>
+#include <gps.h>
+#include "MLSFallback.h"
+#include "mozilla/Atomics.h"
+#include "mozilla/FloatingPoint.h"
+#include "mozilla/LazyIdleThread.h"
+#include "nsGeoPosition.h"
+#include "nsIDOMGeoPositionError.h"
+#include "nsProxyRelease.h"
+#include "nsThreadUtils.h"
+
+namespace mozilla {
+namespace dom {
+
+//
+// MLSGeolocationUpdate
+//
+
+/**
+ * |MLSGeolocationUpdate| provides a fallback if gpsd is not supported.
+ */
+class GpsdLocationProvider::MLSGeolocationUpdate final
+  : public nsIGeolocationUpdate
+{
+public:
+  NS_DECL_ISUPPORTS
+  NS_DECL_NSIGEOLOCATIONUPDATE
+
+  explicit MLSGeolocationUpdate(nsIGeolocationUpdate* aCallback);
+
+protected:
+  ~MLSGeolocationUpdate() = default;
+
+private:
+  nsCOMPtr<nsIGeolocationUpdate> mCallback;
+};
+
+GpsdLocationProvider::MLSGeolocationUpdate::MLSGeolocationUpdate(
+  nsIGeolocationUpdate* aCallback)
+  : mCallback(aCallback)
+{
+  MOZ_ASSERT(mCallback);
+}
+
+// nsISupports
+//
+
+NS_IMPL_ISUPPORTS(GpsdLocationProvider::MLSGeolocationUpdate, nsIGeolocationUpdate);
+
+// nsIGeolocationUpdate
+//
+
+NS_IMETHODIMP
+GpsdLocationProvider::MLSGeolocationUpdate::Update(nsIDOMGeoPosition* aPosition)
+{
+  nsCOMPtr<nsIDOMGeoPositionCoords> coords;
+  aPosition->GetCoords(getter_AddRefs(coords));
+  if (!coords) {
+    return NS_ERROR_FAILURE;
+  }
+
+  return mCallback->Update(aPosition);
+}
+
+NS_IMETHODIMP
+GpsdLocationProvider::MLSGeolocationUpdate::NotifyError(uint16_t aError)
+{
+  return mCallback->NotifyError(aError);
+}
+
+//
+// UpdateRunnable
+//
+
+class GpsdLocationProvider::UpdateRunnable final : public Runnable
+{
+public:
+  UpdateRunnable(
+    const nsMainThreadPtrHandle<GpsdLocationProvider>& aLocationProvider,
+    nsIDOMGeoPosition* aPosition)
+    : mLocationProvider(aLocationProvider)
+    , mPosition(aPosition)
+  {
+    MOZ_ASSERT(mLocationProvider);
+    MOZ_ASSERT(mPosition);
+  }
+
+  // nsIRunnable
+  //
+
+  NS_IMETHOD Run() override
+  {
+    mLocationProvider->Update(mPosition);
+    return NS_OK;
+  }
+
+private:
+  nsMainThreadPtrHandle<GpsdLocationProvider> mLocationProvider;
+  RefPtr<nsIDOMGeoPosition> mPosition;
+};
+
+//
+// NotifyErrorRunnable
+//
+
+class GpsdLocationProvider::NotifyErrorRunnable final : public Runnable
+{
+public:
+  NotifyErrorRunnable(
+    const nsMainThreadPtrHandle<GpsdLocationProvider>& aLocationProvider,
+    int aError)
+    : mLocationProvider(aLocationProvider)
+    , mError(aError)
+  {
+    MOZ_ASSERT(mLocationProvider);
+  }
+
+  // nsIRunnable
+  //
+
+  NS_IMETHOD Run() override
+  {
+    mLocationProvider->NotifyError(mError);
+    return NS_OK;
+  }
+
+private:
+  nsMainThreadPtrHandle<GpsdLocationProvider> mLocationProvider;
+  int mError;
+};
+
+//
+// PollRunnable
+//
+
+/**
+ * |PollRunnable| does the main work of processing GPS data received
+ * from gpsd. libgps blocks while polling, so this runnable has to be
+ * executed on it's own thread. To cancel the poll runnable, invoke
+ * |StopRunning| and |PollRunnable| will stop within a reasonable time
+ * frame.
+ */
+class GpsdLocationProvider::PollRunnable final : public Runnable
+{
+public:
+  PollRunnable(
+    const nsMainThreadPtrHandle<GpsdLocationProvider>& aLocationProvider)
+    : mLocationProvider(aLocationProvider)
+    , mRunning(true)
+  {
+    MOZ_ASSERT(mLocationProvider);
+  }
+
+  static bool IsSupported()
+  {
+    return GPSD_API_MAJOR_VERSION == 5;
+  }
+
+  bool IsRunning() const
+  {
+    return mRunning;
+  }
+
+  void StopRunning()
+  {
+    mRunning = false;
+  }
+
+  // nsIRunnable
+  //
+
+  NS_IMETHOD Run() override
+  {
+    int err;
+
+    switch (GPSD_API_MAJOR_VERSION) {
+      case 5:
+        err = PollLoop5();
+        break;
+      default:
+        err = nsIDOMGeoPositionError::POSITION_UNAVAILABLE;
+        break;
+    }
+
+    if (err) {
+      NS_DispatchToMainThread(
+        MakeAndAddRef<NotifyErrorRunnable>(mLocationProvider, err));
+    }
+
+    mLocationProvider = nullptr;
+
+    return NS_OK;
+  }
+
+protected:
+  int PollLoop5()
+  {
+#if GPSD_API_MAJOR_VERSION == 5
+    static const int GPSD_WAIT_TIMEOUT_US = 1000000; /* us to wait for GPS data */
+
+    struct gps_data_t gpsData;
+
+    auto res = gps_open(nullptr, nullptr, &gpsData);
+
+    if (res < 0) {
+      return ErrnoToError(errno);
+    }
+
+    gps_stream(&gpsData, WATCH_ENABLE | WATCH_JSON, NULL);
+
+    int err = 0;
+
+    double lat = -1;
+    double lon = -1;
+    double alt = -1;
+    double hError = -1;
+    double vError = -1;
+    double heading = -1;
+    double speed = -1;
+    long long timestamp = 0;
+
+    while (IsRunning()) {
+
+      errno = 0;
+      auto hasGpsData = gps_waiting(&gpsData, GPSD_WAIT_TIMEOUT_US);
+
+      if (errno) {
+        err = ErrnoToError(errno);
+        break;
+      }
+      if (!hasGpsData) {
+        continue; /* woke up from timeout */
+      }
+
+      res = gps_read(&gpsData);
+
+      if (res < 0) {
+        err = ErrnoToError(errno);
+        break;
+      } else if (!res) {
+        continue; /* no data available */
+      }
+
+      if (gpsData.status == STATUS_NO_FIX) {
+        continue;
+      }
+
+      switch (gpsData.fix.mode) {
+        case MODE_3D:
+          if (!IsNaN(gpsData.fix.altitude)) {
+            alt = gpsData.fix.altitude;
+          }
+          MOZ_FALLTHROUGH;
+        case MODE_2D:
+          if (!IsNaN(gpsData.fix.latitude)) {
+            lat = gpsData.fix.latitude;
+          }
+          if (!IsNaN(gpsData.fix.longitude)) {
+            lon = gpsData.fix.longitude;
+          }
+          if (!IsNaN(gpsData.fix.epx) && !IsNaN(gpsData.fix.epy)) {
+            hError = std::max(gpsData.fix.epx, gpsData.fix.epy);
+          } else if (!IsNaN(gpsData.fix.epx)) {
+            hError = gpsData.fix.epx;
+          } else if (!IsNaN(gpsData.fix.epy)) {
+            hError = gpsData.fix.epy;
+          }
+          if (!IsNaN(gpsData.fix.altitude)) {
+            alt = gpsData.fix.altitude;
+          }
+          if (!IsNaN(gpsData.fix.epv)) {
+            vError = gpsData.fix.epv;
+          }
+          if (!IsNaN(gpsData.fix.track)) {
+            heading = gpsData.fix.track;
+          }
+          if (!IsNaN(gpsData.fix.speed)) {
+            speed = gpsData.fix.speed;
+          }
+          break;
+        default:
+          continue; // There's no useful data in this fix; continue.
+      }
+
+      timestamp = PR_Now() / PR_USEC_PER_MSEC; // convert to milliseconds
+
+      NS_DispatchToMainThread(
+        MakeAndAddRef<UpdateRunnable>(mLocationProvider,
+                                      new nsGeoPosition(lat, lon, alt,
+                                                        hError, vError,
+                                                        heading, speed,
+                                                        timestamp)));
+    }
+
+    gps_stream(&gpsData, WATCH_DISABLE, NULL);
+    gps_close(&gpsData);
+
+    return err;
+#else
+    return nsIDOMGeoPositionError::POSITION_UNAVAILABLE;
+#endif // GPSD_MAJOR_API_VERSION
+  }
+
+  static int ErrnoToError(int aErrno)
+  {
+    switch (aErrno) {
+      case EACCES:
+          MOZ_FALLTHROUGH;
+      case EPERM:
+          MOZ_FALLTHROUGH;
+      case EROFS:
+        return nsIDOMGeoPositionError::PERMISSION_DENIED;
+      case ETIME:
+          MOZ_FALLTHROUGH;
+      case ETIMEDOUT:
+        return nsIDOMGeoPositionError::TIMEOUT;
+      default:
+        return nsIDOMGeoPositionError::POSITION_UNAVAILABLE;
+    }
+  }
+
+private:
+  nsMainThreadPtrHandle<GpsdLocationProvider> mLocationProvider;
+  Atomic<bool> mRunning;
+};
+
+//
+// GpsdLocationProvider
+//
+
+const uint32_t GpsdLocationProvider::GPSD_POLL_THREAD_TIMEOUT_MS = 5000;
+
+GpsdLocationProvider::GpsdLocationProvider()
+{ }
+
+GpsdLocationProvider::~GpsdLocationProvider()
+{ }
+
+void
+GpsdLocationProvider::Update(nsIDOMGeoPosition* aPosition)
+{
+  if (!mCallback || !mPollRunnable) {
+    return; // not initialized or already shut down
+  }
+
+  if (mMLSProvider) {
+    /* We got a location from gpsd, so let's cancel our MLS fallback. */
+    mMLSProvider->Shutdown();
+    mMLSProvider = nullptr;
+  }
+
+  mCallback->Update(aPosition);
+}
+
+void
+GpsdLocationProvider::NotifyError(int aError)
+{
+  if (!mCallback) {
+    return; // not initialized or already shut down
+  }
+
+  if (!mMLSProvider) {
+    /* With gpsd failed, we restart MLS. It will be canceled once we
+     * get another location from gpsd.
+     */
+    mMLSProvider = MakeAndAddRef<MLSFallback>();
+    mMLSProvider->Startup(new MLSGeolocationUpdate(mCallback));
+  }
+
+  mCallback->NotifyError(aError);
+}
+
+// nsISupports
+//
+
+NS_IMPL_ISUPPORTS(GpsdLocationProvider, nsIGeolocationProvider)
+
+// nsIGeolocationProvider
+//
+
+NS_IMETHODIMP
+GpsdLocationProvider::Startup()
+{
+  if (!PollRunnable::IsSupported()) {
+    return NS_OK; // We'll fall back to MLS.
+  }
+
+  if (mPollRunnable) {
+    return NS_OK; // already running
+  }
+
+  RefPtr<PollRunnable> pollRunnable =
+    MakeAndAddRef<PollRunnable>(
+      nsMainThreadPtrHandle<GpsdLocationProvider>(
+        new nsMainThreadPtrHolder<GpsdLocationProvider>(this)));
+
+  // Use existing poll thread...
+  RefPtr<LazyIdleThread> pollThread = mPollThread;
+
+  // ... or create a new one.
+  if (!pollThread) {
+    pollThread = MakeAndAddRef<LazyIdleThread>(
+      GPSD_POLL_THREAD_TIMEOUT_MS,
+      NS_LITERAL_CSTRING("Gpsd poll thread"),
+      LazyIdleThread::ManualShutdown);
+  }
+
+  auto rv = pollThread->Dispatch(pollRunnable, NS_DISPATCH_NORMAL);
+
+  if (NS_FAILED(rv)) {
+    return rv;
+  }
+
+  mPollRunnable = pollRunnable.forget();
+  mPollThread = pollThread.forget();
+
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+GpsdLocationProvider::Watch(nsIGeolocationUpdate* aCallback)
+{
+  mCallback = aCallback;
+
+  /* The MLS fallback will kick in after a few seconds if gpsd
+   * doesn't provide location information within time. Once we
+   * see the first message from gpsd, the fallback will be
+   * disabled in |Update|.
+   */
+  mMLSProvider = MakeAndAddRef<MLSFallback>();
+  mMLSProvider->Startup(new MLSGeolocationUpdate(aCallback));
+
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+GpsdLocationProvider::Shutdown()
+{
+  if (mMLSProvider) {
+    mMLSProvider->Shutdown();
+    mMLSProvider = nullptr;
+  }
+
+  if (!mPollRunnable) {
+    return NS_OK; // not running
+  }
+
+  mPollRunnable->StopRunning();
+  mPollRunnable = nullptr;
+
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+GpsdLocationProvider::SetHighAccuracy(bool aHigh)
+{
+  return NS_OK;
+}
+
+} // namespace dom
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/dom/system/linux/GpsdLocationProvider.h
@@ -0,0 +1,52 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef GpsdLocationProvider_h
+#define GpsdLocationProvider_h
+
+#include "nsCOMPtr.h"
+#include "nsGeolocation.h"
+#include "nsIGeolocationProvider.h"
+
+class MLSFallback;
+
+namespace mozilla {
+
+class LazyIdleThread;
+
+namespace dom {
+
+class GpsdLocationProvider final : public nsIGeolocationProvider
+{
+  class MLSGeolocationUpdate;
+  class NotifyErrorRunnable;
+  class PollRunnable;
+  class UpdateRunnable;
+
+public:
+  NS_DECL_ISUPPORTS
+  NS_DECL_NSIGEOLOCATIONPROVIDER
+
+  GpsdLocationProvider();
+
+private:
+  ~GpsdLocationProvider();
+
+  void Update(nsIDOMGeoPosition* aPosition);
+  void NotifyError(int aError);
+
+  static const uint32_t GPSD_POLL_THREAD_TIMEOUT_MS;
+
+  nsCOMPtr<nsIGeolocationUpdate> mCallback;
+  RefPtr<LazyIdleThread> mPollThread;
+  RefPtr<PollRunnable> mPollRunnable;
+  RefPtr<MLSFallback> mMLSProvider;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif /* GpsLocationProvider_h */
new file mode 100644
--- /dev/null
+++ b/dom/system/linux/moz.build
@@ -0,0 +1,20 @@
+# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+if CONFIG['MOZ_GPSD']:
+    SOURCES += [
+        'GpsdLocationProvider.cpp'
+    ]
+
+    CXXFLAGS += CONFIG['MOZ_GPSD_CFLAGS']
+
+    OS_LIBS += CONFIG['MOZ_GPSD_LIBS']
+
+    LOCAL_INCLUDES += [
+        '/dom/geolocation'
+    ]
+
+FINAL_LIBRARY = 'xul'
--- a/dom/system/moz.build
+++ b/dom/system/moz.build
@@ -9,16 +9,18 @@ toolkit = CONFIG['MOZ_WIDGET_TOOLKIT']
 if toolkit == 'windows':
     DIRS += ['windows']
 elif toolkit == 'cocoa':
     DIRS += ['mac']
 elif toolkit == 'android':
     DIRS += ['android']
 elif toolkit == 'gonk':
     DIRS += ['gonk']
+elif toolkit in ('gtk2', 'gtk3'):
+    DIRS += ['linux']
 
 XPIDL_SOURCES += [
     'nsIOSFileConstantsService.idl',
     'nsISystemUpdateProvider.idl',
 ]
 
 XPIDL_MODULE = 'dom_system'
 
--- a/dom/telephony/MMICall.cpp
+++ b/dom/telephony/MMICall.cpp
@@ -26,16 +26,17 @@ MMICall::MMICall(nsPIDOMWindowInner* aWi
   nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(mWindow);
   if (!global) {
     return;
   }
 
   ErrorResult rv;
   RefPtr<Promise> promise = Promise::Create(global, rv);
   if (rv.Failed()) {
+    rv.SuppressException();
     return;
   }
 
   mPromise = promise;
 }
 
 MMICall::~MMICall()
 {
--- a/dom/tests/browser/browser_bug396843.js
+++ b/dom/tests/browser/browser_bug396843.js
@@ -262,21 +262,25 @@ function testInDocument(doc, documentID)
     runTestProps();
     runTestUnwrapped();
 }
 
 add_task(function* test1() {
     testInDocument(document, "browser window");
 });
 
-function newTabTest(location) {
-    let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, location);
-    let doc = content.document;
-    testInDocument(doc, location);
-    yield BrowserTestUtils.removeTab(tab);
+function* newTabTest(location) {
+    yield BrowserTestUtils.withNewTab({ gBrowser, url: location },
+        function* (browser) {
+          yield ContentTask.spawn(browser, { location, testInDocument_: testInDocument.toSource() },
+            function* ({ location, testInDocument_ }) {
+              let testInDocument = eval(`(() => (${testInDocument_}))()`);
+              testInDocument(content.document, location);
+            });
+        });
 }
 
 add_task(function* test2() {
     yield newTabTest("about:blank");
 });
 
 add_task(function* test3() {
     yield newTabTest("about:config");
--- a/dom/tv/TVTuner.cpp
+++ b/dom/tv/TVTuner.cpp
@@ -233,36 +233,39 @@ TVTuner::CreateSimulatedMediaStream()
   if (NS_WARN_IF(!doc)) {
     return nullptr;
   }
 
   ErrorResult error;
   ElementCreationOptions options;
   RefPtr<Element> element = doc->CreateElement(VIDEO_TAG, options, error);
   if (NS_WARN_IF(error.Failed())) {
+    error.SuppressException();
     return nullptr;
   }
 
   nsCOMPtr<nsIContent> content(do_QueryInterface(element));
   if (NS_WARN_IF(!content)) {
     return nullptr;
   }
 
   HTMLMediaElement* mediaElement = static_cast<HTMLMediaElement*>(content.get());
   if (NS_WARN_IF(!mediaElement)) {
     return nullptr;
   }
 
   mediaElement->SetAutoplay(true, error);
   if (NS_WARN_IF(error.Failed())) {
+    error.SuppressException();
     return nullptr;
   }
 
   mediaElement->SetLoop(true, error);
   if (NS_WARN_IF(error.Failed())) {
+    error.SuppressException();
     return nullptr;
   }
 
   nsCOMPtr<nsITVSimulatorService> simService(do_QueryInterface(mTVService));
   if (NS_WARN_IF(!simService)) {
     return nullptr;
   }
 
@@ -288,23 +291,25 @@ TVTuner::CreateSimulatedMediaStream()
                                                      domWin,
                                                      currentVideoBlobUrl);
   if (NS_WARN_IF(NS_FAILED(rv) || currentVideoBlobUrl.IsEmpty())) {
     return nullptr;
   }
 
   mediaElement->SetSrc(currentVideoBlobUrl, error);
   if (NS_WARN_IF(error.Failed())) {
+    error.SuppressException();
     return nullptr;
   }
 
   // See Media Capture from DOM Elements spec.
   // http://www.w3.org/TR/mediacapture-fromelement/
   RefPtr<DOMMediaStream> stream = mediaElement->MozCaptureStream(error);
   if (NS_WARN_IF(error.Failed())) {
+    error.SuppressException();
     return nullptr;
   }
 
   return stream.forget();
 }
 
 nsresult
 TVTuner::DispatchCurrentSourceChangedEvent(TVSource* aSource)
--- a/dom/webidl/HTMLInputElement.webidl
+++ b/dom/webidl/HTMLInputElement.webidl
@@ -194,18 +194,18 @@ partial interface HTMLInputElement {
   // This is similar to set .value on nsIDOMInput/TextAreaElements, but handling
   // of the value change is closer to the normal user input, so 'change' event
   // for example will be dispatched when focusing out the element.
   [ChromeOnly]
   void setUserInput(DOMString input);
 };
 
 partial interface HTMLInputElement {
-  [Pref="dom.input.dirpicker", BinaryName="DirectoryAttr", SetterThrows]
-  attribute boolean directory;
+  [Pref="dom.input.dirpicker", SetterThrows]
+  attribute boolean allowdirs;
 
   [Pref="dom.input.dirpicker"]
   readonly attribute boolean isFilesAndDirectoriesSupported;
 
   [Throws, Pref="dom.input.dirpicker"]
   Promise<sequence<(File or Directory)>> getFilesAndDirectories();
 
   [Throws, Pref="dom.input.dirpicker"]
new file mode 100644
--- /dev/null
+++ b/dom/webidl/HiddenPluginEvent.webidl
@@ -0,0 +1,12 @@
+interface PluginTag;
+
+[Constructor(DOMString type, optional HiddenPluginEventInit eventInit), ChromeOnly]
+interface HiddenPluginEvent : Event
+{
+  readonly attribute PluginTag? tag;
+};
+
+dictionary HiddenPluginEventInit : EventInit
+{
+  PluginTag? tag = null;
+};
--- a/dom/webidl/MediaKeySystemAccess.webidl
+++ b/dom/webidl/MediaKeySystemAccess.webidl
@@ -5,28 +5,35 @@
  *
  * The origin of this IDL file is
  * https://dvcs.w3.org/hg/html-media/raw-file/default/encrypted-media/encrypted-media.html
  *
  * Copyright © 2014 W3C® (MIT, ERCIM, Keio, Beihang), All Rights Reserved.
  * W3C liability, trademark and document use rules apply.
  */
 
+enum MediaKeysRequirement {
+  "required",
+  "optional",
+  "not-allowed"
+};
+
 dictionary MediaKeySystemMediaCapability {
    DOMString contentType = "";
-   // TODO: robustness
+   DOMString robustness = "";
 };
 
 dictionary MediaKeySystemConfiguration {
   DOMString                               label = "";
-  sequence<DOMString>                     initDataTypes;
-  sequence<MediaKeySystemMediaCapability> audioCapabilities;
-  sequence<MediaKeySystemMediaCapability> videoCapabilities;
-
-   // TODO: distinctiveIdentifier, persistentState, sessionTypes  
+  sequence<DOMString>                     initDataTypes = [];
+  sequence<MediaKeySystemMediaCapability> audioCapabilities = [];
+  sequence<MediaKeySystemMediaCapability> videoCapabilities = [];
+  MediaKeysRequirement                    distinctiveIdentifier = "optional";
+  MediaKeysRequirement                    persistentState = "optional";
+  sequence<DOMString>                     sessionTypes;
 };
 
 [Pref="media.eme.apiVisible"]
 interface MediaKeySystemAccess {
   readonly    attribute DOMString keySystem;
   [NewObject]
   MediaKeySystemConfiguration getConfiguration();
   [NewObject]
--- a/dom/webidl/MediaKeys.webidl
+++ b/dom/webidl/MediaKeys.webidl
@@ -5,20 +5,26 @@
  *
  * The origin of this IDL file is
  * https://dvcs.w3.org/hg/html-media/raw-file/default/encrypted-media/encrypted-media.html
  *
  * Copyright © 2014 W3C® (MIT, ERCIM, Keio, Beihang), All Rights Reserved.
  * W3C liability, trademark and document use rules apply.
  */
 
-enum SessionType { "temporary", "persistent" };
+// Note: "persistent-usage-record" session type is unsupported yet, as
+// it's marked as "at risk" in the spec, and Chrome doesn't support it. 
+enum MediaKeySessionType {
+  "temporary",
+  "persistent-license",
+  // persistent-usage-record,
+};
 
 [Pref="media.eme.apiVisible"]
 interface MediaKeys {
   readonly attribute DOMString keySystem;
 
   [NewObject, Throws]
-  MediaKeySession createSession(optional SessionType sessionType = "temporary");
+  MediaKeySession createSession(optional MediaKeySessionType sessionType = "temporary");
 
   [NewObject]
   Promise<void> setServerCertificate((ArrayBufferView or ArrayBuffer) serverCertificate);
 };
--- a/dom/webidl/moz.build
+++ b/dom/webidl/moz.build
@@ -785,16 +785,17 @@ GENERATED_EVENTS_WEBIDL_FILES = [
     'DeviceProximityEvent.webidl',
     'DeviceStorageAreaChangedEvent.webidl',
     'DeviceStorageChangeEvent.webidl',
     'DOMTransactionEvent.webidl',
     'DownloadEvent.webidl',
     'ErrorEvent.webidl',
     'FontFaceSetLoadEvent.webidl',
     'HashChangeEvent.webidl',
+    'HiddenPluginEvent.webidl',
     'IccChangeEvent.webidl',
     'ImageCaptureErrorEvent.webidl',
     'MediaStreamEvent.webidl',
     'MozCellBroadcastEvent.webidl',
     'MozClirModeEvent.webidl',
     'MozContactChangeEvent.webidl',
     'MozEmergencyCbModeEvent.webidl',
     'MozMessageDeletedEvent.webidl',
--- a/dom/workers/RuntimeService.cpp
+++ b/dom/workers/RuntimeService.cpp
@@ -870,30 +870,27 @@ public:
     // in cycles that were broken during CC shutdown.
     nsCycleCollector_shutdown();
 
     // The CC is shut down, and the superclass destructor will GC, so make sure
     // we don't try to CC again.
     mWorkerPrivate = nullptr;
   }
 
-  nsresult Initialize(JSRuntime* aParentRuntime)
+  nsresult Initialize(JSContext* aParentContext)
   {
     nsresult rv =
-      CycleCollectedJSRuntime::Initialize(aParentRuntime,
+      CycleCollectedJSRuntime::Initialize(aParentContext,
                                           WORKER_DEFAULT_RUNTIME_HEAPSIZE,
                                           WORKER_DEFAULT_NURSERY_SIZE);
      if (NS_WARN_IF(NS_FAILED(rv))) {
        return rv;
      }
 
-    JSRuntime* rt = Runtime();
-    MOZ_ASSERT(rt);
-
-    JSContext* cx = JS_GetContext(rt);
+    JSContext* cx = Context();
 
     JS_SetContextPrivate(cx, new WorkerThreadContextPrivate(mWorkerPrivate));
 
     js::SetPreserveWrapperCallback(cx, PreserveWrapper);
     JS_InitDestroyPrincipalsCallback(cx, DestroyWorkerPrincipals);
     JS_SetWrapObjectCallbacks(cx, &WrapObjectCallbacks);
     if (mWorkerPrivate->IsDedicatedWorker()) {
       JS_SetFutexCanWait(cx);
@@ -982,17 +979,17 @@ public:
 private:
   WorkerPrivate* mWorkerPrivate;
 };
 
 class WorkerThreadPrimaryRunnable final : public Runnable
 {
   WorkerPrivate* mWorkerPrivate;
   RefPtr<WorkerThread> mThread;
-  JSRuntime* mParentRuntime;
+  JSContext* mParentContext;
 
   class FinishedRunnable final : public Runnable
   {
     RefPtr<WorkerThread> mThread;
 
   public:
     explicit FinishedRunnable(already_AddRefed<WorkerThread> aThread)
     : mThread(aThread)
@@ -1007,18 +1004,18 @@ class WorkerThreadPrimaryRunnable final 
     { }
 
     NS_DECL_NSIRUNNABLE
   };
 
 public:
   WorkerThreadPrimaryRunnable(WorkerPrivate* aWorkerPrivate,
                               WorkerThread* aThread,
-                              JSRuntime* aParentRuntime)
-  : mWorkerPrivate(aWorkerPrivate), mThread(aThread), mParentRuntime(aParentRuntime)
+                              JSContext* aParentContext)
+  : mWorkerPrivate(aWorkerPrivate), mThread(aThread), mParentContext(aParentContext)
   {
     MOZ_ASSERT(aWorkerPrivate);
     MOZ_ASSERT(aThread);
   }
 
   NS_DECL_ISUPPORTS_INHERITED
 
 private:
@@ -1618,20 +1615,20 @@ RuntimeService::ScheduleWorker(WorkerPri
   int32_t priority = aWorkerPrivate->IsChromeWorker() ?
                      nsISupportsPriority::PRIORITY_NORMAL :
                      nsISupportsPriority::PRIORITY_LOW;
 
   if (NS_FAILED(thread->SetPriority(priority))) {
     NS_WARNING("Could not set the thread's priority!");
   }
 
-  JSRuntime* rt = CycleCollectedJSRuntime::Get()->Runtime();
+  JSContext* cx = CycleCollectedJSRuntime::Get()->Context();
   nsCOMPtr<nsIRunnable> runnable =
     new WorkerThreadPrimaryRunnable(aWorkerPrivate, thread,
-                                    JS_GetParentRuntime(rt));
+                                    JS_GetParentContext(cx));
   if (NS_FAILED(thread->DispatchPrimaryRunnable(friendKey, runnable.forget()))) {
     UnregisterWorker(aWorkerPrivate);
     return false;
   }
 
   return true;
 }
 
@@ -2556,17 +2553,17 @@ WorkerThreadPrimaryRunnable::Run()
   SetThreadHelper threadHelper(mWorkerPrivate, mThread);
 
   mWorkerPrivate->AssertIsOnWorkerThread();
 
   {
     nsCycleCollector_startup();
 
     WorkerJSRuntime runtime(mWorkerPrivate);
-    nsresult rv = runtime.Initialize(mParentRuntime);
+    nsresult rv = runtime.Initialize(mParentContext);
     if (NS_WARN_IF(NS_FAILED(rv))) {
       return rv;
     }
 
     JSRuntime* rt = runtime.Runtime();
 
     JSContext* cx = InitJSContextForWorker(mWorkerPrivate, rt);
     if (!cx) {
--- a/dom/workers/ScriptLoader.cpp
+++ b/dom/workers/ScriptLoader.cpp
@@ -1560,16 +1560,17 @@ CacheCreator::DeleteCache()
   AssertIsOnMainThread();
 
   ErrorResult rv;
 
   // It's safe to do this while Cache::Match() and Cache::Put() calls are
   // running.
   RefPtr<Promise> promise = mCacheStorage->Delete(mCacheName, rv);
   if (NS_WARN_IF(rv.Failed())) {
+    rv.SuppressException();
     return;
   }
 
   // We don't care to know the result of the promise object.
   FailLoaders(NS_ERROR_FAILURE);
 }
 
 void
--- a/dom/workers/WorkerPrivate.cpp
+++ b/dom/workers/WorkerPrivate.cpp
@@ -755,16 +755,17 @@ public:
       init.mData = messageData;
       init.mPorts.Construct();
       init.mPorts.Value().SetNull();
 
       ErrorResult rv;
       RefPtr<ExtendableMessageEvent> event = ExtendableMessageEvent::Constructor(
         aTarget, NS_LITERAL_STRING("message"), init, rv);
       if (NS_WARN_IF(rv.Failed())) {
+        rv.SuppressException();
         return false;
       }
       event->SetSource(client);
       event->SetPorts(new MessagePortList(static_cast<dom::Event*>(event.get()),
                                           ports));
       domEvent = do_QueryObject(event);
     } else {
       RefPtr<MessageEvent> event = new MessageEvent(aTarget, nullptr, nullptr);
@@ -2493,21 +2494,17 @@ WorkerPrivateParent<Derived>::DispatchCo
       return NS_ERROR_UNEXPECTED;
     }
 
     // Transfer ownership to the control queue.
     self->mControlQueue.Push(runnable.forget().take());
 
     if (JSContext* cx = self->mJSContext) {
       MOZ_ASSERT(self->mThread);
-
-      JSRuntime* rt = JS_GetRuntime(cx);
-      MOZ_ASSERT(rt);
-
-      JS_RequestInterruptCallback(rt);
+      JS_RequestInterruptCallback(cx);
     }
 
     mCondVar.Notify();
   }
 
   return NS_OK;
 }
 
@@ -4902,23 +4899,22 @@ WorkerPrivate::BlockAndCollectRuntimeSta
   NS_ASSERTION(aRtStats, "Null RuntimeStats!");
 
   NS_ASSERTION(!mMemoryReporterRunning, "How can we get reentered here?!");
 
   // This signals the worker that it should block itself as soon as possible.
   mMemoryReporterRunning = true;
 
   NS_ASSERTION(mJSContext, "This must never be null!");
-  JSRuntime* rt = JS_GetRuntime(mJSContext);
 
   // If the worker is not already blocked (e.g. waiting for a worker event or
   // currently in a ctypes call) then we need to trigger the interrupt
   // callback to trap the worker.
   if (!mBlockedForMemoryReporter) {
-    JS_RequestInterruptCallback(rt);
+    JS_RequestInterruptCallback(mJSContext);
 
     // Wait until the worker actually blocks.
     while (!mBlockedForMemoryReporter) {
       mMemoryReportCondVar.Wait();
     }
   }
 
   bool succeeded = false;
@@ -6566,16 +6562,17 @@ WorkerPrivate::ConnectMessagePort(JSCont
   JS::Rooted<JSObject*> jsGlobal(aCx, globalScope->GetWrapper());
   MOZ_ASSERT(jsGlobal);
 
   // This MessagePortIdentifier is used to create a new port, still connected
   // with the other one, but in the worker thread.
   ErrorResult rv;
   RefPtr<MessagePort> port = MessagePort::Create(globalScope, aIdentifier, rv);
   if (NS_WARN_IF(rv.Failed())) {
+    rv.SuppressException();
     return false;
   }
 
   GlobalObject globalObject(aCx, jsGlobal);
   if (globalObject.Failed()) {
     return false;
   }
 
--- a/dom/workers/test/serviceworkers/test_bug1151916.html
+++ b/dom/workers/test/serviceworkers/test_bug1151916.html
@@ -54,17 +54,17 @@
   function unloadFrame() {
     frame.src = "about:blank";
     frame.parentNode.removeChild(frame);
     frame = null;
   }
 
   function gc() {
    return new Promise(function(resolve) {
-     SpecialPowers.exactGC(window, resolve);
+     SpecialPowers.exactGC(resolve);
    });
   }
 
   function testCaches() {
     var p = listenForMessage();
 
     frame = document.createElement("iframe");
     document.body.appendChild(frame);
--- a/dom/workers/test/serviceworkers/test_https_synth_fetch_from_cached_sw.html
+++ b/dom/workers/test/serviceworkers/test_https_synth_fetch_from_cached_sw.html
@@ -33,17 +33,17 @@
         // In order to load synth.html from a cached service worker, we first
         // remove the existing window that is keeping the service worker alive,
         // and do a GC to ensure that the SW is destroyed.  This way, when we
         // load synth.html for the second time, we will first recreate the
         // service worker from the cache.  This is intended to test that we
         // properly store and retrieve the security info from the cache.
         iframe.parentNode.removeChild(iframe);
         iframe = null;
-        SpecialPowers.exactGC(window, function() {
+        SpecialPowers.exactGC(function() {
           iframe = document.createElement("iframe");
           iframe.src = "https://example.com/tests/dom/workers/test/serviceworkers/fetch/https/synth.html";
           document.body.appendChild(iframe);
         });
       } else if (e.data.status == "done-synth") {
         ios.offline = false;
         iframe.src = "https://example.com/tests/dom/workers/test/serviceworkers/fetch/https/unregister.html";
       } else if (e.data.status == "unregistrationdone") {
--- a/dom/workers/test/serviceworkers/test_notification_get.html
+++ b/dom/workers/test/serviceworkers/test_notification_get.html
@@ -18,17 +18,17 @@
   function testFrame(src) {
     return new Promise(function(resolve, reject) {
       var iframe = document.createElement("iframe");
       iframe.src = src;
       window.callback = function(result) {
         iframe.src = "about:blank";
         document.body.removeChild(iframe);
         iframe = null;
-        SpecialPowers.exactGC(window, function() {
+        SpecialPowers.exactGC(function() {
           resolve(result);
         });
       };
       document.body.appendChild(iframe);
     });
   }
 
   function registerSW() {
--- a/dom/workers/test/serviceworkers/test_sanitize.html
+++ b/dom/workers/test/serviceworkers/test_sanitize.html
@@ -54,17 +54,17 @@
     return new Promise(function(resolve, reject) {
       var iframe = document.createElement("iframe");
       iframe.src = src;
       window.onmessage = function(message) {
         window.onmessage = null;
         iframe.src = "about:blank";
         document.body.removeChild(iframe);
         iframe = null;
-        SpecialPowers.exactGC(window, function() {
+        SpecialPowers.exactGC(function() {
           resolve(message.data);
         });
       };
       document.body.appendChild(iframe);
     });
   }
 
   function registerSW() {
--- a/dom/workers/test/serviceworkers/test_sanitize_domain.html
+++ b/dom/workers/test/serviceworkers/test_sanitize_domain.html
@@ -53,17 +53,17 @@
     return new Promise(function(resolve, reject) {
       var iframe = document.createElement("iframe");
       iframe.src = src;
       window.onmessage = function(message) {
         window.onmessage = null;
         iframe.src = "about:blank";
         document.body.removeChild(iframe);
         iframe = null;
-        SpecialPowers.exactGC(window, function() {
+        SpecialPowers.exactGC(function() {
           resolve(message.data);
         });
       };
       document.body.appendChild(iframe);
     });
   }
 
   function registerSW() {
--- a/dom/workers/test/test_multi_sharedWorker_lifetimes.html
+++ b/dom/workers/test/test_multi_sharedWorker_lifetimes.html
@@ -108,17 +108,17 @@
           info("Navigating to about:blank");
           frame.src = "about:blank";
           frame.contentWindow.document.body.offsetTop;
 
           yield undefined;
 
           for (let i = 0; i < 3; i++) {
             info("Running GC");
-            SpecialPowers.exactGC(window, sendToGenerator);
+            SpecialPowers.exactGC(sendToGenerator);
             yield undefined;
 
             info("Waiting the event queue to clear");
             SpecialPowers.executeSoon(sendToGenerator);
             yield undefined;
           }
 
           info("Navigating to " + frameRelativeURL);
--- a/dom/xul/templates/nsXULTemplateQueryProcessorXML.cpp
+++ b/dom/xul/templates/nsXULTemplateQueryProcessorXML.cpp
@@ -40,17 +40,23 @@ NS_IMPL_ISUPPORTS(nsXULTemplateResultSet
 
 NS_IMETHODIMP
 nsXULTemplateResultSetXML::HasMoreElements(bool *aResult)
 {
     // if GetSnapshotLength failed, then the return type was not a set of
     // nodes, so just return false in this case.
     ErrorResult rv;
     uint32_t length = mResults->GetSnapshotLength(rv);
-    *aResult = !rv.Failed() && mPosition < length;
+    if (NS_WARN_IF(rv.Failed())) {
+      rv.SuppressException();
+      *aResult = false;
+      return NS_OK;
+    }
+
+    *aResult = mPosition < length;
     return NS_OK;
 }
 
 NS_IMETHODIMP
 nsXULTemplateResultSetXML::GetNext(nsISupports **aResult)
 {
     ErrorResult rv;
     nsINode* node = mResults->SnapshotItem(mPosition, rv);
--- a/embedding/components/windowwatcher/nsAutoWindowStateHelper.cpp
+++ b/embedding/components/windowwatcher/nsAutoWindowStateHelper.cpp
@@ -54,16 +54,17 @@ nsAutoWindowStateHelper::DispatchEventTo
   nsIDocument* doc = mWindow->GetExtantDoc();
   if (!doc) {
     return true;
   }
 
   ErrorResult rv;
   RefPtr<Event> event = doc->CreateEvent(NS_LITERAL_STRING("Events"), rv);
   if (rv.Failed()) {
+    rv.SuppressException();
     return false;
   }
   event->InitEvent(NS_ConvertASCIItoUTF16(aEventName), true, true);
   event->SetTrusted(true);
   event->WidgetEventPtr()->mFlags.mOnlyChromeDispatch = true;
 
   nsCOMPtr<EventTarget> target = do_QueryInterface(mWindow);
   bool defaultActionEnabled;
--- a/gfx/2d/2D.h
+++ b/gfx/2d/2D.h
@@ -1036,17 +1036,17 @@ public:
    * arbitrary SourceSurface type supported by this backend. This may return
    * aSourceSurface or some other existing surface.
    */
   virtual already_AddRefed<SourceSurface> OptimizeSourceSurface(SourceSurface *aSurface) const = 0;
 
   /**
    * Create a SourceSurface for a type of NativeSurface. This may fail if the
    * draw target does not know how to deal with the type of NativeSurface passed
-   * in.
+   * in. If this succeeds, the SourceSurface takes the ownersip of the NativeSurface.
    */
   virtual already_AddRefed<SourceSurface>
     CreateSourceSurfaceFromNativeSurface(const NativeSurface &aSurface) const = 0;
 
   /**
    * Create a DrawTarget whose snapshot is optimized for use with this DrawTarget.
    */
   virtual already_AddRefed<DrawTarget>
--- a/gfx/2d/DrawTargetSkia.cpp
+++ b/gfx/2d/DrawTargetSkia.cpp
@@ -1349,17 +1349,17 @@ DrawTargetSkia::CreateSourceSurfaceFromN
     texDesc.fOrigin = kTopLeft_GrSurfaceOrigin;
     texDesc.fConfig = GfxFormatToGrConfig(aSurface.mFormat);
 
     GrGLTextureInfo texInfo;
     texInfo.fTarget = LOCAL_GL_TEXTURE_2D;
     texInfo.fID = (GrGLuint)(uintptr_t)aSurface.mSurface;
     texDesc.fTextureHandle = reinterpret_cast<GrBackendObject>(&texInfo);
 
-    SkAutoTUnref<GrTexture> texture(mGrContext->textureProvider()->wrapBackendTexture(texDesc));
+    SkAutoTUnref<GrTexture> texture(mGrContext->textureProvider()->wrapBackendTexture(texDesc, kAdopt_GrWrapOwnership));
 
     RefPtr<SourceSurfaceSkia> newSurf = new SourceSurfaceSkia();
     if (newSurf->InitFromGrTexture(texture, aSurface.mSize, aSurface.mFormat)) {
       return newSurf.forget();
     }
     return nullptr;
   }
 #endif
--- a/gfx/gl/GLContext.cpp
+++ b/gfx/gl/GLContext.cpp
@@ -7,17 +7,16 @@
 #include "GLContext.h"
 
 #include <algorithm>
 #include <stdio.h>
 #include <string.h>
 #include <ctype.h>
 #include <vector>
 #ifdef MOZ_WIDGET_ANDROID
-#include <fcntl.h>
 #include <sys/mman.h>
 #endif
 
 #include "GLBlitHelper.h"
 #include "GLReadTexImageHelper.h"
 #include "GLScreenBuffer.h"
 
 #include "gfxCrashReporterUtils.h"
@@ -2881,26 +2880,22 @@ GLContext::fDeleteFramebuffers(GLsizei n
 static bool
 WillTextureMapSucceed(GLsizei width, GLsizei height, GLenum format, GLenum type)
 {
     bool willSucceed = false;
     // Some drivers leave large gaps between textures, so require
     // there to be double the actual size of the texture available.
     size_t size = width * height * GetBytesPerTexel(format, type) * 2;
 
-    int fd = open("/dev/zero", O_RDONLY);
-
-    void *p = mmap(nullptr, size, PROT_NONE, MAP_SHARED, fd, 0);
+    void *p = mmap(nullptr, size, PROT_NONE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
     if (p != MAP_FAILED) {
         willSucceed = true;
         munmap(p, size);
     }
 
-    close(fd);
-
     return willSucceed;
 }
 #endif // MOZ_WIDGET_ANDROID
 
 void
 GLContext::fTexImage2D(GLenum target, GLint level, GLint internalformat,
                        GLsizei width, GLsizei height, GLint border,
                        GLenum format, GLenum type, const GLvoid* pixels) {
--- a/gfx/layers/client/TextureClientPool.cpp
+++ b/gfx/layers/client/TextureClientPool.cpp
@@ -30,16 +30,17 @@ TextureClientPool::TextureClientPool(Lay
   : mBackend(aLayersBackend)
   , mFormat(aFormat)
   , mSize(aSize)
   , mFlags(aFlags)
   , mInitialPoolSize(aInitialPoolSize)
   , mPoolIncrementSize(aPoolIncrementSize)
   , mOutstandingClients(0)
   , mSurfaceAllocator(aAllocator)
+  , mDestroyed(false)
 {
   TCP_LOG("TexturePool %p created with maximum unused texture clients %u\n",
       this, mInitialPoolSize);
   mTimer = do_CreateInstance("@mozilla.org/timer;1");
   if (aFormat == gfx::SurfaceFormat::UNKNOWN) {
     gfxWarning() << "Creating texture pool for SurfaceFormat::UNKNOWN format";
   }
 }
@@ -144,17 +145,17 @@ TextureClientPool::AllocateTextureClient
       mTextureClients.push(newClient);
     }
   }
 }
 
 void
 TextureClientPool::ReturnTextureClient(TextureClient *aClient)
 {
-  if (!aClient) {
+  if (!aClient || mDestroyed) {
     return;
   }
 #ifdef GFX_DEBUG_TRACK_CLIENTS_IN_POOL
   DebugOnly<bool> ok = TestClientPool("return", aClient, this);
   MOZ_ASSERT(ok);
 #endif
   // Add the client to the pool:
   MOZ_ASSERT(mOutstandingClients > mTextureClientsDeferred.size());
@@ -165,17 +166,17 @@ TextureClientPool::ReturnTextureClient(T
 
   // Shrink down if we're beyond our maximum size
   ShrinkToMaximumSize();
 }
 
 void
 TextureClientPool::ReturnTextureClientDeferred(TextureClient* aClient)
 {
-  if (!aClient) {
+  if (!aClient || mDestroyed) {
     return;
   }
   MOZ_ASSERT(aClient->GetReadLock());
 #ifdef GFX_DEBUG_TRACK_CLIENTS_IN_POOL
   DebugOnly<bool> ok = TestClientPool("defer", aClient, this);
   MOZ_ASSERT(ok);
 #endif
   mTextureClientsDeferred.push_back(aClient);
@@ -280,14 +281,15 @@ TextureClientPool::Clear()
         this, mTextureClientsDeferred.front().get());
     mTextureClientsDeferred.pop_front();
   }
 }
 
 void TextureClientPool::Destroy()
 {
   Clear();
+  mDestroyed = true;
   mInitialPoolSize = 0;
   mPoolIncrementSize = 0;
 }
 
 } // namespace layers
 } // namespace mozilla
--- a/gfx/layers/client/TextureClientPool.h
+++ b/gfx/layers/client/TextureC