merge mozilla-inbound to mozilla-central a=merge
authorCarsten "Tomcat" Book <cbook@mozilla.com>
Mon, 25 Jul 2016 15:50:41 +0200
changeset 331541 e23f2ec25e96ad6cdf64af446e81a0f0dfeb4b5c
parent 331404 e730a1dca1a41bd6b0824353305abc25284e2d52 (current diff)
parent 331540 1870dfec5cb00b6bcdec167eb44ae628ae7812f2 (diff)
child 331542 e48f912b76fb2415d8971f4e3b8176ffd7fc4139
child 331622 887712e8ba10bf1b9df7b0f9b5d5293bd35fdfd8
push id9858
push userjlund@mozilla.com
push dateMon, 01 Aug 2016 14:37:10 +0000
treeherdermozilla-aurora@203106ef6cb6 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone50.0a1
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/TextureClientPool.h
@@ -146,14 +146,19 @@ private:
   // On ICS, fence wait happens implicitly before drawing.
   // Since JB, fence wait happens explicitly when fetching a client from the pool.
   std::stack<RefPtr<TextureClient> > mTextureClients;
 
   std::list<RefPtr<TextureClient>> mTextureClientsDeferred;
   RefPtr<nsITimer> mTimer;
   // This mSurfaceAllocator owns us, so no need to hold a ref to it
   TextureForwarder* mSurfaceAllocator;
+
+  // Keep track of whether this pool has been destroyed or not. If it has,
+  // we won't accept returns of TextureClients anymore, and the refcounting
+  // should take care of their destruction.
+  bool mDestroyed;
 };
 
 } // namespace layers
 } // namespace mozilla
 
 #endif /* MOZILLA_GFX_TEXTURECLIENTPOOL_H */
--- a/gfx/layers/ipc/CompositorBridgeChild.cpp
+++ b/gfx/layers/ipc/CompositorBridgeChild.cpp
@@ -286,16 +286,19 @@ CompositorBridgeChild::RecvCompositorUpd
 {
   if (mLayerManager) {
     // This case is handled directly by nsBaseWidget.
     MOZ_ASSERT(aLayersId == 0);
   } else if (aLayersId != 0) {
     if (dom::TabChild* child = dom::TabChild::GetFrom(aLayersId)) {
       child->CompositorUpdated(aNewIdentifier);
     }
+    if (!mCanSend) {
+      return true;
+    }
     SendAcknowledgeCompositorUpdate(aLayersId);
   }
   return true;
 }
 
 #if defined(XP_WIN) || defined(MOZ_WIDGET_GTK)
 static void CalculatePluginClip(const LayoutDeviceIntRect& aBounds,
                                 const nsTArray<LayoutDeviceIntRect>& aPluginClipRects,
@@ -411,16 +414,19 @@ CompositorBridgeChild::RecvUpdatePluginC
 #endif
         visiblePluginIds.AppendElement(aPlugins[pluginsIdx].windowId());
       }
     }
   }
   // Any plugins we didn't update need to be hidden