merge mozilla-inbound to mozilla-central a=merge
authorCarsten "Tomcat" Book <cbook@mozilla.com>
Fri, 24 Mar 2017 14:24:21 +0100
changeset 349471 4c987b7ed54a630a7de76adcc2eb00dab49d5dfd
parent 349376 473e0b20176139cff952b04e3eeff3ee155d0d3d (current diff)
parent 349470 4f4ae3322be2d7c5f9bdda5e0985151b26edea3c (diff)
child 349483 b9f319edc8506efd99fcc417c94b4ed87c68e8d6
child 349551 08a18b232c7cd746802de0c14cbbd6d753440d68
child 349772 21025b310431b4e6088f10684acd04e751bac605
push id31551
push usercbook@mozilla.com
push dateFri, 24 Mar 2017 13:24:56 +0000
treeherdermozilla-central@4c987b7ed54a [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone55.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
merge mozilla-inbound to mozilla-central a=merge
browser/base/content/test/general/browser_tab_dragdrop2_frame1.xul
browser/base/content/test/static/browser_parsable_css.js
devtools/client/responsive.html/index.css
devtools/client/sourceeditor/test/browser_css_getInfo.js
devtools/client/sourceeditor/test/css_statemachine_testcases.css
devtools/client/sourceeditor/test/css_statemachine_tests.json
devtools/client/themes/animationinspector.css
devtools/client/themes/commandline.inc.css
devtools/client/themes/common.css
devtools/client/themes/debugger.css
devtools/client/themes/devtools-browser.css
devtools/client/themes/floating-scrollbars-dark-theme.css
devtools/client/themes/floating-scrollbars-responsive-design.css
devtools/client/themes/netmonitor.css
devtools/client/themes/responsivedesign.inc.css
devtools/client/themes/shadereditor.css
devtools/client/themes/splitters.css
devtools/client/themes/toolbars.css
devtools/client/themes/toolbox.css
devtools/client/themes/tooltips.css
devtools/client/themes/webaudioeditor.css
devtools/client/themes/webconsole.css
devtools/client/themes/widgets.css
devtools/shared/css/generated/properties-db.js
dom/animation/test/mozilla/file_discrete-animations.html
dom/base/test/test_caretPositionFromPoint.html
dom/events/test/bug656379-1.html
dom/events/test/test_bug864040.html
dom/html/reftests/autofocus/input-number-ref.html
dom/html/reftests/autofocus/input-number.html
dom/html/reftests/autofocus/input-time-ref.html
dom/html/reftests/autofocus/input-time.html
dom/html/test/forms/test_input_textarea_set_value_no_scroll.html
dom/tests/mochitest/general/test_focusrings.xul
dom/tests/mochitest/general/test_offsets.xul
editor/libeditor/tests/test_bug1053048.html
editor/reftests/xul/input.css
gfx/src/nsThemeConstants.h
gfx/tests/crashtests/crashtests.list
intl/locale/mac/nsCollationMacUC.cpp
intl/locale/mac/nsCollationMacUC.h
intl/locale/nsCollation.cpp
intl/locale/nsCollation.h
intl/locale/tests/unit/test_collation_mac_icu.js
intl/locale/unix/nsCollationUnix.cpp
intl/locale/unix/nsCollationUnix.h
intl/locale/windows/nsCollationWin.cpp
intl/locale/windows/nsCollationWin.h
layout/base/GeckoRestyleManager.cpp
layout/base/RestyleManager.cpp
layout/base/crashtests/crashtests.list
layout/base/nsCSSFrameConstructor.cpp
layout/base/nsLayoutUtils.cpp
layout/base/tests/bug106855-1-ref.html
layout/base/tests/bug106855-1.html
layout/base/tests/bug106855-2.html
layout/base/tests/bug1237236-1-ref.html
layout/base/tests/bug1237236-1.html
layout/base/tests/bug1258308-1-ref.html
layout/base/tests/bug1258308-1.html
layout/base/tests/bug1259949-1-ref.html
layout/base/tests/bug1259949-1.html
layout/base/tests/bug240933-1-ref.html
layout/base/tests/bug240933-1.html
layout/base/tests/bug240933-2.html
layout/base/tests/bug585922-ref.html
layout/base/tests/bug585922.html
layout/base/tests/bug597519-1-ref.html
layout/base/tests/bug597519-1.html
layout/base/tests/bug612271-1.html
layout/base/tests/bug612271-2.html
layout/base/tests/bug612271-3.html
layout/base/tests/bug612271-ref.html
layout/base/tests/bug613807-1-ref.html
layout/base/tests/bug613807-1.html
layout/base/tests/bug634406-1-ref.html
layout/base/tests/bug634406-1.html
layout/base/tests/bug646382-1-ref.html
layout/base/tests/bug646382-1.html
layout/base/tests/bug646382-2-ref.html
layout/base/tests/bug646382-2.html
layout/base/tests/bug664087-1-ref.html
layout/base/tests/bug664087-1.html
layout/base/tests/bug664087-2-ref.html
layout/base/tests/bug664087-2.html
layout/base/tests/chrome/chrome_content_integration_window.xul
layout/base/tests/test_bug332655-1.html
layout/base/tests/test_bug332655-2.html
layout/base/tests/test_bug499538-1.html
layout/base/tests/test_bug644768.html
layout/forms/crashtests/1102791.html
layout/forms/crashtests/crashtests.list
layout/forms/nsButtonFrameRenderer.cpp
layout/forms/nsComboboxControlFrame.cpp
layout/forms/nsFormControlFrame.cpp
layout/forms/nsMeterFrame.cpp
layout/forms/nsNumberControlFrame.cpp
layout/forms/nsProgressFrame.cpp
layout/forms/nsRangeFrame.cpp
layout/forms/test/bug665540_window.xul
layout/forms/test/test_bug476308.html
layout/forms/test/test_textarea_resize.html
layout/generic/ReflowInput.cpp
layout/generic/crashtests/crashtests.list
layout/generic/nsFlexContainerFrame.cpp
layout/generic/nsFrame.cpp
layout/generic/nsIFrame.h
layout/generic/test/test_selection_preventDefault.html
layout/painting/nsCSSRendering.cpp
layout/painting/nsDisplayList.cpp
layout/reftests/abs-pos/button-1.html
layout/reftests/abs-pos/button-2.html
layout/reftests/box-shadow/boxshadow-button-ref.html
layout/reftests/box-shadow/boxshadow-button.html
layout/reftests/bugs/180085-1-ref.html
layout/reftests/bugs/180085-1.html
layout/reftests/bugs/180085-2-ref.html
layout/reftests/bugs/180085-2.html
layout/reftests/bugs/363858-2-ref.html
layout/reftests/bugs/363858-2.html
layout/reftests/bugs/363858-4-ref.html
layout/reftests/bugs/363858-4.html
layout/reftests/bugs/363858-6-ref.html
layout/reftests/bugs/363858-6a.html
layout/reftests/bugs/363858-6b.html
layout/reftests/bugs/373381-2-ref.html
layout/reftests/bugs/373381-2.html
layout/reftests/bugs/373381-4-ref.html
layout/reftests/bugs/373381-4.html
layout/reftests/bugs/423599-1-ref.html
layout/reftests/bugs/423599-1.html
layout/reftests/bugs/468473-1-ref.xul
layout/reftests/bugs/468473-1.xul
layout/reftests/bugs/474336-1-ref.xul
layout/reftests/bugs/474336-1.xul
layout/reftests/bugs/491180-1-ref.html
layout/reftests/bugs/491180-1.html
layout/reftests/bugs/491180-2-ref.html
layout/reftests/bugs/491180-2.html
layout/reftests/bugs/491323-1-ref.xul
layout/reftests/bugs/491323-1.xul
layout/reftests/bugs/542116-1-ref.html
layout/reftests/bugs/542116-1.html
layout/reftests/bugs/542116-2-ref.html
layout/reftests/bugs/542116-2.html
layout/reftests/bugs/664127-1.css
layout/reftests/bugs/668319-1.xul
layout/reftests/bugs/966992-1-ref.html
layout/reftests/bugs/966992-1.html
layout/reftests/css-enabled/select/style.css
layout/reftests/css-invalid/input/style.css
layout/reftests/css-placeholder/input/style-shown.css
layout/reftests/css-placeholder/textarea/style-shown.css
layout/reftests/flexbox/reftest.list
layout/reftests/forms/button/percent-height-child.html
layout/reftests/forms/button/percent-width-child.html
layout/reftests/forms/button/reftest.list
layout/reftests/forms/button/vertical-centering.html
layout/reftests/forms/button/width-auto-size-em-ltr.html
layout/reftests/forms/button/width-auto-size-em-rtl.html
layout/reftests/forms/button/width-auto-size-ltr.html
layout/reftests/forms/button/width-auto-size-rtl.html
layout/reftests/forms/button/width-erode-all-focuspadding-rtl.html
layout/reftests/forms/button/width-exact-fit-ltr.html
layout/reftests/forms/button/width-exact-fit-rtl.html
layout/reftests/forms/input/checkbox/checkbox-baseline.html
layout/reftests/forms/input/checkbox/checked-appearance-none.html
layout/reftests/forms/input/checkbox/indeterminate-checked-notref.html
layout/reftests/forms/input/checkbox/indeterminate-checked.html
layout/reftests/forms/input/checkbox/indeterminate-unchecked-notref.html
layout/reftests/forms/input/checkbox/indeterminate-unchecked.html
layout/reftests/forms/input/checkbox/unchecked-appearance-none.html
layout/reftests/forms/input/color/reftest.list
layout/reftests/forms/input/datetime/from-time-to-other-type-unthemed-ref.html
layout/reftests/forms/input/datetime/from-time-to-other-type-unthemed.html
layout/reftests/forms/input/datetime/time-simple-unthemed-ref.html
layout/reftests/forms/input/datetime/time-simple-unthemed.html
layout/reftests/forms/input/datetime/to-time-from-other-type-unthemed.html
layout/reftests/forms/input/file/style.css
layout/reftests/forms/input/number/focus-handling-ref.html
layout/reftests/forms/input/number/focus-handling.html
layout/reftests/forms/input/number/from-number-to-other-type-unthemed-1-ref.html
layout/reftests/forms/input/number/from-number-to-other-type-unthemed-1.html
layout/reftests/forms/input/number/not-other-type-unthemed-1.html
layout/reftests/forms/input/number/not-other-type-unthemed-1a-notref.html
layout/reftests/forms/input/number/not-other-type-unthemed-1b-notref.html
layout/reftests/forms/input/number/number-auto-width-1-ref.html
layout/reftests/forms/input/number/number-auto-width-1.html
layout/reftests/forms/input/number/number-disabled-ref.html
layout/reftests/forms/input/number/number-disabled.html
layout/reftests/forms/input/number/number-pseudo-elements-ref.html
layout/reftests/forms/input/number/number-pseudo-elements.html
layout/reftests/forms/input/number/number-same-as-text-unthemed-ref.html
layout/reftests/forms/input/number/number-same-as-text-unthemed.html
layout/reftests/forms/input/number/number-similar-to-text-unthemed-ref.html
layout/reftests/forms/input/number/number-similar-to-text-unthemed-rtl-ref.html
layout/reftests/forms/input/number/number-similar-to-text-unthemed-rtl.html
layout/reftests/forms/input/number/number-similar-to-text-unthemed-vertical-lr-ref.html
layout/reftests/forms/input/number/number-similar-to-text-unthemed-vertical-lr.html
layout/reftests/forms/input/number/number-similar-to-text-unthemed-vertical-rl-ref.html
layout/reftests/forms/input/number/number-similar-to-text-unthemed-vertical-rl.html
layout/reftests/forms/input/number/number-similar-to-text-unthemed.html
layout/reftests/forms/input/number/reftest.list
layout/reftests/forms/input/number/show-value-ref.html
layout/reftests/forms/input/number/show-value.html
layout/reftests/forms/input/number/to-number-from-other-type-unthemed-1-ref.html
layout/reftests/forms/input/number/to-number-from-other-type-unthemed-1.html
layout/reftests/forms/input/radio/checked-appearance-none.html
layout/reftests/forms/input/radio/unchecked-appearance-none.html
layout/reftests/forms/input/range/75pct-unthemed-common-ref.html
layout/reftests/forms/input/range/different-fraction-of-range-unthemed-1-notref.html
layout/reftests/forms/input/range/different-fraction-of-range-unthemed-1.html
layout/reftests/forms/input/range/direction-unthemed-1-ref.html
layout/reftests/forms/input/range/direction-unthemed-1.html
layout/reftests/forms/input/range/from-range-to-other-type-unthemed-1-ref.html
layout/reftests/forms/input/range/from-range-to-other-type-unthemed-1.html
layout/reftests/forms/input/range/not-other-type-unthemed-1.html
layout/reftests/forms/input/range/not-other-type-unthemed-1a-notref.html
layout/reftests/forms/input/range/not-other-type-unthemed-1b-notref.html
layout/reftests/forms/input/range/not-other-type-unthemed-1c-notref.html
layout/reftests/forms/input/range/same-fraction-of-range-unthemed-1-ref.html
layout/reftests/forms/input/range/same-fraction-of-range-unthemed-1.html
layout/reftests/forms/input/range/stepDown-unthemed.html
layout/reftests/forms/input/range/stepUp-unthemed.html
layout/reftests/forms/input/range/to-range-from-other-type-unthemed-1-ref.html
layout/reftests/forms/input/range/to-range-from-other-type-unthemed-1.html
layout/reftests/forms/input/range/value-prop-unthemed.html
layout/reftests/forms/input/range/valueAsNumber-prop-unthemed.html
layout/reftests/forms/meter/default-style/style.css
layout/reftests/forms/meter/style.css
layout/reftests/forms/progress/reftest.list
layout/reftests/forms/progress/style.css
layout/reftests/forms/select/focusring-1-ref.html
layout/reftests/forms/select/focusring-1.html
layout/reftests/forms/select/focusring-2-ref.html
layout/reftests/forms/select/focusring-2.html
layout/reftests/forms/select/focusring-3-ref.html
layout/reftests/forms/select/focusring-3.html
layout/reftests/forms/select/vertical-centering-ref.html
layout/reftests/forms/select/vertical-centering.html
layout/reftests/forms/textarea/resize-ref.html
layout/reftests/forms/textarea/resize.html
layout/reftests/forms/textarea/setvalue-framereconstruction-1.html
layout/reftests/forms/textarea/setvalue-framereconstruction-ref.html
layout/reftests/forms/textbox/accesskey-1-notref.xul
layout/reftests/forms/textbox/accesskey-1.xul
layout/reftests/forms/textbox/accesskey-2-ref.xul
layout/reftests/forms/textbox/accesskey-2.xul
layout/reftests/forms/textbox/accesskey-3-notref.xul
layout/reftests/forms/textbox/accesskey-3-ref.xul
layout/reftests/forms/textbox/accesskey-3.xul
layout/reftests/forms/textbox/accesskey-4-notref.xul
layout/reftests/forms/textbox/accesskey-4-ref.xul
layout/reftests/forms/textbox/accesskey-4.xul
layout/reftests/forms/textbox/align-baseline-1-ref.xul
layout/reftests/forms/textbox/align-baseline-1.xul
layout/reftests/forms/textbox/reftest.list
layout/reftests/native-theme/button-nonnative-when-styled-ref.html
layout/reftests/native-theme/button-nonnative.html
layout/reftests/native-theme/checkbox-nonnative.html
layout/reftests/native-theme/combobox-nonnative-when-styled-ref.html
layout/reftests/native-theme/combobox-nonnative.html
layout/reftests/native-theme/listbox-nonnative-when-styled-ref.html
layout/reftests/native-theme/listbox-nonnative.html
layout/reftests/native-theme/native-theme-disabled-cascade-levels-ref.html
layout/reftests/native-theme/radio-nonnative.html
layout/reftests/native-theme/reftest.list
layout/reftests/native-theme/text-input-nonnative-when-styled-ref.html
layout/reftests/native-theme/text-input-nonnative.html
layout/reftests/native-theme/textarea-nonnative-when-styled-ref.html
layout/reftests/native-theme/textarea-nonnative.html
layout/reftests/svg/foreignObject-form-no-theme.svg
layout/reftests/svg/reftest.list
layout/reftests/text-decoration/underline-block-propagation-2-quirks-ref.html
layout/reftests/text-decoration/underline-block-propagation-2-quirks.html
layout/reftests/text-decoration/underline-block-propagation-2-standards-ref.html
layout/reftests/text-decoration/underline-block-propagation-2-standards.html
layout/reftests/text-overflow/combobox-zoom-ref.html
layout/reftests/text-overflow/combobox-zoom.html
layout/reftests/text/control-chars-01-notref.html
layout/reftests/text/control-chars-01a.html
layout/reftests/text/control-chars-01b.html
layout/reftests/text/control-chars-01c.html
layout/reftests/text/control-chars-01d.html
layout/reftests/unicode/unicode-attribute-selector.html
layout/reftests/unicode/unicode-element-selector.html
layout/reftests/unicode/unicode-lang.html
layout/reftests/unicode/unicode-pseudo-selector.html
layout/reftests/unicode/unicode-ref.html
layout/reftests/writing-mode/1138356-1-button-contents-alignment-ref.html
layout/reftests/writing-mode/1138356-1-button-contents-alignment.html
layout/reftests/writing-mode/1138356-2-button-contents-alignment-notref.html
layout/reftests/writing-mode/1138356-2-button-contents-alignment.html
layout/reftests/writing-mode/reftest.list
layout/reftests/writing-mode/ua-style-sheet-button-1.html
layout/reftests/writing-mode/ua-style-sheet-button-1a-ref.html
layout/reftests/writing-mode/ua-style-sheet-button-1b-ref.html
layout/reftests/writing-mode/ua-style-sheet-input-color-1-ref.html
layout/reftests/writing-mode/ua-style-sheet-input-color-1.html
layout/style/nsCSSPropAliasList.h
layout/style/nsCSSPropList.h
layout/style/nsCSSProps.cpp
layout/style/nsCSSProps.h
layout/style/nsComputedDOMStyle.cpp
layout/style/nsComputedDOMStyle.h
layout/style/nsComputedDOMStylePropertyList.h
layout/style/nsRuleNode.cpp
layout/style/nsStyleStruct.cpp
layout/style/nsStyleStruct.h
layout/style/test/property_database.js
layout/style/test/test_pseudoelement_state.html
layout/svg/crashtests/crashtests.list
layout/tables/nsTableCellFrame.cpp
layout/tables/nsTableFrame.cpp
layout/xul/crashtests/189814-1.xul
layout/xul/nsBox.cpp
layout/xul/nsBoxFrame.cpp
layout/xul/nsImageBoxFrame.cpp
layout/xul/nsMenuPopupFrame.cpp
layout/xul/nsScrollbarFrame.cpp
layout/xul/reftest/reftest.list
layout/xul/test/test_bug563416.html
layout/xul/test/test_windowminmaxsize.xul
layout/xul/tree/nsTreeBodyFrame.cpp
modules/libpref/init/all.js
toolkit/content/tests/chrome/test_contextmenu_list.xul
toolkit/content/tests/chrome/test_position.xul
toolkit/content/tests/chrome/window_panel.xul
toolkit/content/tests/chrome/window_popup_anchor.xul
toolkit/content/tests/chrome/window_tooltip.xul
toolkit/content/tests/reftests/reftest.list
toolkit/themes/osx/reftests/baseline.xul
toolkit/themes/osx/reftests/reftest.list
view/crashtests/crashtests.list
widget/cocoa/crashtests/crashtests.list
widget/cocoa/nsNativeThemeCocoa.mm
widget/crashtests/crashtests.list
widget/gtk/crashtests/crashtests.list
widget/nsNativeTheme.cpp
widget/reftests/meter-fallback-default-style-ref.html
widget/reftests/meter-fallback-default-style.html
widget/reftests/progressbar-fallback-default-style-ref.html
widget/reftests/progressbar-fallback-default-style.html
widget/reftests/reftest.list
widget/windows/nsNativeThemeWin.cpp
--- a/browser/components/extensions/ExtensionPopups.jsm
+++ b/browser/components/extensions/ExtensionPopups.jsm
@@ -69,24 +69,25 @@ XPCOMUtils.defineLazyGetter(this, "stand
   }
   if (AppConstants.platform === "win") {
     stylesheets.push("chrome://browser/content/extension-win-panel.css");
   }
   return stylesheets;
 });
 
 class BasePopup {
-  constructor(extension, viewNode, popupURL, browserStyle, fixedWidth = false) {
+  constructor(extension, viewNode, popupURL, browserStyle, fixedWidth = false, blockParser = false) {
     this.extension = extension;
     this.popupURL = popupURL;
     this.viewNode = viewNode;
     this.browserStyle = browserStyle;
     this.window = viewNode.ownerGlobal;
     this.destroyed = false;
     this.fixedWidth = fixedWidth;
+    this.blockParser = blockParser;
 
     extension.callOnClose(this);
 
     this.contentReady = new Promise(resolve => {
       this._resolveContentReady = resolve;
     });
 
     this.viewNode.addEventListener(this.DESTROY_EVENT, this);
@@ -112,32 +113,35 @@ class BasePopup {
     this.closePopup();
   }
 
   destroy() {
     this.extension.forgetOnClose(this);
 
     this.destroyed = true;
     this.browserLoadedDeferred.reject(new Error("Popup destroyed"));
+
+    BasePopup.instances.get(this.window).delete(this.extension);
+
     return this.browserReady.then(() => {
-      this.destroyBrowser(this.browser, true);
-      this.browser.remove();
+      if (this.browser) {
+        this.destroyBrowser(this.browser, true);
+        this.browser.remove();
+      }
 
       if (this.viewNode) {
         this.viewNode.removeEventListener(this.DESTROY_EVENT, this);
         this.viewNode.style.maxHeight = "";
       }
 
       if (this.panel) {
         this.panel.style.removeProperty("--arrowpanel-background");
         this.panel.style.removeProperty("--panel-arrow-image-vertical");
       }
 
-      BasePopup.instances.get(this.window).delete(this.extension);
-
       this.browser = null;
       this.viewNode = null;
     });
   }
 
   destroyBrowser(browser, finalize = false) {
     let mm = browser.messageManager;
     // If the browser has already been removed from the document, because the
@@ -282,26 +286,33 @@ class BasePopup {
       setupBrowser(browser);
       let mm = browser.messageManager;
 
       mm.loadFrameScript(
         "chrome://extensions/content/ext-browser-content.js", false);
 
       mm.sendAsyncMessage("Extension:InitBrowser", {
         allowScriptsToClose: true,
+        blockParser: this.blockParser,
         fixedWidth: this.fixedWidth,
         maxWidth: 800,
         maxHeight: 600,
         stylesheets: this.STYLESHEETS,
       });
 
       browser.loadURI(popupURL);
     });
   }
 
+  unblockParser() {
+    this.browserReady.then(browser => {
+      this.browser.messageManager.sendAsyncMessage("Extension:UnblockParser");
+    });
+  }
+
   resizeBrowser({width, height, detail}) {
     if (this.fixedWidth) {
       // Figure out how much extra space we have on the side of the panel
       // opposite the arrow.
       let side = this.panel.getAttribute("side") == "top" ? "bottom" : "top";
       let maxHeight = this.viewHeight + this.extraHeight[side];
 
       height = Math.min(height, maxHeight);
@@ -396,27 +407,27 @@ class PanelPopup extends BasePopup {
       if (this.viewNode && this.viewNode.hidePopup) {
         this.viewNode.hidePopup();
       }
     });
   }
 }
 
 class ViewPopup extends BasePopup {
-  constructor(extension, window, popupURL, browserStyle, fixedWidth) {
+  constructor(extension, window, popupURL, browserStyle, fixedWidth, blockParser) {
     let document = window.document;
 
     // Create a temporary panel to hold the browser while it pre-loads its
     // content. This panel will never be shown, but the browser's docShell will
     // be swapped with the browser in the real panel when it's ready.
     let panel = document.createElement("panel");
     panel.setAttribute("type", "arrow");
     document.getElementById("mainPopupSet").appendChild(panel);
 
-    super(extension, panel, popupURL, browserStyle, fixedWidth);
+    super(extension, panel, popupURL, browserStyle, fixedWidth, blockParser);
 
     this.ignoreResizes = true;
 
     this.attached = false;
     this.shown = false;
     this.tempPanel = panel;
 
     this.browser.classList.add("webextension-preload-browser");
--- a/browser/components/extensions/ext-browserAction.js
+++ b/browser/components/extensions/ext-browserAction.js
@@ -117,16 +117,18 @@ BrowserAction.prototype = {
       },
 
       onCreated: node => {
         node.classList.add("badged-button");
         node.classList.add("webextension-browser-action");
         node.setAttribute("constrain-size", "true");
 
         node.onmousedown = event => this.handleEvent(event);
+        node.onmouseover = event => this.handleEvent(event);
+        node.onmouseout = event => this.handleEvent(event);
 
         this.updateButton(node, this.defaults);
       },
 
       onViewShowing: event => {
         let document = event.target.ownerDocument;
         let tabbrowser = document.defaultView.gBrowser;
 
@@ -204,20 +206,20 @@ BrowserAction.prototype = {
       case "mousedown":
         if (event.button == 0) {
           // Begin pre-loading the browser for the popup, so it's more likely to
           // be ready by the time we get a complete click.
           let tab = window.gBrowser.selectedTab;
           let popupURL = this.getProperty(tab, "popup");
           let enabled = this.getProperty(tab, "enabled");
 
-          if (popupURL && enabled) {
+          if (popupURL && enabled && (this.pendingPopup || !ViewPopup.for(this.extension, window))) {
             // Add permission for the active tab so it will exist for the popup.
             // Store the tab to revoke the permission during clearPopup.
-            if (!this.pendingPopup && !this.tabManager.hasActiveTabPermission(tab)) {
+            if (!this.tabManager.hasActiveTabPermission(tab)) {
               this.tabManager.addActiveTabPermission(tab);
               this.tabToRevokeDuringClearPopup = tab;
             }
 
             this.pendingPopup = this.getPopup(window, popupURL);
             window.addEventListener("mouseup", this, true);
           } else {
             this.clearPopup();
@@ -237,16 +239,36 @@ BrowserAction.prototype = {
                                                     POPUP_PRELOAD_TIMEOUT_MS);
             } else {
               this.clearPopup();
             }
           }
         }
         break;
 
+      case "mouseover": {
+        // Begin pre-loading the browser for the popup, so it's more likely to
+        // be ready by the time we get a complete click.
+        let tab = window.gBrowser.selectedTab;
+        let popupURL = this.getProperty(tab, "popup");
+        let enabled = this.getProperty(tab, "enabled");
+
+        if (popupURL && enabled && (this.pendingPopup || !ViewPopup.for(this.extension, window))) {
+          this.pendingPopup = this.getPopup(window, popupURL, true);
+        }
+        break;
+      }
+
+      case "mouseout":
+        if (this.pendingPopup) {
+          this.clearPopup();
+        }
+        break;
+
+
       case "popupshowing":
         const menu = event.target;
         const trigger = menu.triggerNode;
         const node = window.document.getElementById(this.id);
         const contexts = ["toolbar-context-menu", "customizationPanelItemContextMenu"];
 
         if (contexts.includes(menu.id) && node && isAncestorOrSelf(node, trigger)) {
           global.actionContextMenu({
@@ -266,32 +288,38 @@ BrowserAction.prototype = {
    *
    * If a pre-load popup exists which does not match, it is destroyed before a
    * new one is created.
    *
    * @param {Window} window
    *        The browser window in which to create the popup.
    * @param {string} popupURL
    *        The URL to load into the popup.
+   * @param {boolean} [blockParser = false]
+   *        True if the HTML parser should initially be blocked.
    * @returns {ViewPopup}
    */
-  getPopup(window, popupURL) {
+  getPopup(window, popupURL, blockParser = false) {
     this.clearPopupTimeout();
     let {pendingPopup} = this;
     this.pendingPopup = null;
 
     if (pendingPopup) {
       if (pendingPopup.window === window && pendingPopup.popupURL === popupURL) {
+        if (!this.blockParser) {
+          pendingPopup.unblockParser();
+        }
+
         return pendingPopup;
       }
       pendingPopup.destroy();
     }
 
     let fixedWidth = this.widget.areaType == CustomizableUI.TYPE_MENU_PANEL;
-    return new ViewPopup(this.extension, window, popupURL, this.browserStyle, fixedWidth);
+    return new ViewPopup(this.extension, window, popupURL, this.browserStyle, fixedWidth, blockParser);
   },
 
   /**
    * Clears any pending pre-loaded popup and related timeouts.
    */
   clearPopup() {
     this.clearPopupTimeout();
     if (this.pendingPopup) {
--- a/browser/components/extensions/test/browser/browser_ext_browserAction_popup_preload.js
+++ b/browser/components/extensions/test/browser/browser_ext_browserAction_popup_preload.js
@@ -2,16 +2,19 @@
 /* vim: set sts=2 sw=2 et tw=80: */
 "use strict";
 
 let scriptPage = url => `<html><head><meta charset="utf-8"><script src="${url}"></script></head><body>${url}</body></html>`;
 
 add_task(function* testBrowserActionClickCanceled() {
   let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, "http://example.com/");
 
+  // Make sure the mouse isn't hovering over the browserAction widget.
+  EventUtils.synthesizeMouseAtCenter(gURLBar, {type: "mouseover"}, window);
+
   let extension = ExtensionTestUtils.loadExtension({
     manifest: {
       "browser_action": {
         "default_popup": "popup.html",
         "browser_style": true,
       },
       "permissions": ["activeTab"],
     },
@@ -77,16 +80,19 @@ add_task(function* testBrowserActionClic
   yield closeBrowserAction(extension);
 
   yield extension.unload();
 
   yield BrowserTestUtils.removeTab(tab);
 });
 
 add_task(function* testBrowserActionDisabled() {
+  // Make sure the mouse isn't hovering over the browserAction widget.
+  EventUtils.synthesizeMouseAtCenter(gURLBar, {type: "mouseover"}, window);
+
   let extension = ExtensionTestUtils.loadExtension({
     manifest: {
       "browser_action": {
         "default_popup": "popup.html",
         "browser_style": true,
       },
     },
 
@@ -181,16 +187,19 @@ add_task(function* testBrowserActionTabP
       },
     },
   });
 
   let win = yield BrowserTestUtils.openNewBrowserWindow();
   yield BrowserTestUtils.loadURI(win.gBrowser.selectedBrowser, "http://example.com/");
   yield BrowserTestUtils.browserLoaded(win.gBrowser.selectedBrowser);
 
+  // Make sure the mouse isn't hovering over the browserAction widget.
+  EventUtils.synthesizeMouseAtCenter(win.gURLBar, {type: "mouseover"}, win);
+
   yield extension.startup();
 
   let widget = getBrowserActionWidget(extension).forWindow(win);
   EventUtils.synthesizeMouseAtCenter(widget.node, {type: "mousedown", button: 0}, win);
 
   yield extension.awaitMessage("tabTitle");
 
   EventUtils.synthesizeMouseAtCenter(widget.node, {type: "mouseup", button: 0}, win);
--- a/browser/components/extensions/test/browser/browser_ext_commands_execute_browser_action.js
+++ b/browser/components/extensions/test/browser/browser_ext_commands_execute_browser_action.js
@@ -1,13 +1,16 @@
 /* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
 /* vim: set sts=2 sw=2 et tw=80: */
 "use strict";
 
 function* testExecuteBrowserActionWithOptions(options = {}) {
+  // Make sure the mouse isn't hovering over the browserAction widget.
+  EventUtils.synthesizeMouseAtCenter(gURLBar, {type: "mouseover"}, window);
+
   let extensionOptions = {};
 
   extensionOptions.manifest = {
     "commands": {
       "_execute_browser_action": {
         "suggested_key": {
           "default": "Alt+Shift+J",
         },
--- a/browser/components/extensions/test/browser/browser_ext_incognito_views.js
+++ b/browser/components/extensions/test/browser/browser_ext_incognito_views.js
@@ -1,13 +1,16 @@
 /* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
 /* vim: set sts=2 sw=2 et tw=80: */
 "use strict";
 
 add_task(function* testIncognitoViews() {
+  // Make sure the mouse isn't hovering over the browserAction widget.
+  EventUtils.synthesizeMouseAtCenter(gURLBar, {type: "mouseover"}, window);
+
   let extension = ExtensionTestUtils.loadExtension({
     manifest: {
       "permissions": ["tabs"],
       "browser_action": {
         "default_popup": "popup.html",
       },
     },
 
--- a/caps/ContentPrincipal.cpp
+++ b/caps/ContentPrincipal.cpp
@@ -224,17 +224,19 @@ ContentPrincipal::GetOriginInternal(nsAC
   // If we reached this branch, we can only create an origin if we have a
   // nsIStandardURL.  So, we query to a nsIStandardURL, and fail if we aren't
   // an instance of an nsIStandardURL nsIStandardURLs have the good property
   // of escaping the '^' character in their specs, which means that we can be
   // sure that the caret character (which is reserved for delimiting the end
   // of the spec, and the beginning of the origin attributes) is not present
   // in the origin string
   nsCOMPtr<nsIStandardURL> standardURL = do_QueryInterface(origin);
-  NS_ENSURE_TRUE(standardURL, NS_ERROR_FAILURE);
+  if (!standardURL) {
+    return NS_ERROR_FAILURE;
+  }
 
   rv = origin->GetAsciiSpec(aOrigin);
   NS_ENSURE_SUCCESS(rv, rv);
 
   // The origin, when taken from the spec, should not contain the ref part of
   // the URL.
 
   int32_t pos = aOrigin.FindChar('?');
--- a/devtools/client/inspector/grids/components/GridOutline.js
+++ b/devtools/client/inspector/grids/components/GridOutline.js
@@ -1,19 +1,23 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
 const { addons, createClass, DOM: dom, PropTypes } =
   require("devtools/client/shared/vendor/react");
+const { throttle } = require("devtools/client/inspector/shared/utils");
 
 const Types = require("../types");
 
+// The delay prior to executing the grid cell highlighting.
+const GRID_CELL_MOUSEOVER_TIMEOUT = 150;
+
 // Move SVG grid to the right 100 units, so that it is not flushed against the edge of
 // layout border
 const TRANSLATE_X = -100;
 const TRANSLATE_Y = 0;
 
 const VIEWPORT_HEIGHT = 100;
 const VIEWPORT_WIDTH = 450;
 
@@ -30,16 +34,23 @@ module.exports = createClass({
   mixins: [ addons.PureRenderMixin ],
 
   getInitialState() {
     return {
       selectedGrids: [],
     };
   },
 
+  componentWillMount() {
+    // Throttle the grid highlighting of grid cells. It makes the UX smoother by not
+    // lagging the grid cell highlighting if a lot of grid cells are mouseover in a
+    // quick succession.
+    this.highlightCell = throttle(this.highlightCell, GRID_CELL_MOUSEOVER_TIMEOUT);
+  },
+
   componentWillReceiveProps({ grids }) {
     this.setState({
       selectedGrids: grids.filter(grid => grid.highlighted),
     });
   },
 
   /**
    * Returns the grid area name if the given grid cell is part of a grid area, otherwise
@@ -61,16 +72,41 @@ module.exports = createClass({
 
     if (!gridArea) {
       return null;
     }
 
     return gridArea.name;
   },
 
+  highlightCell({ target }) {
+    const {
+      grids,
+      onShowGridAreaHighlight,
+      onShowGridCellHighlight,
+    } = this.props;
+    const name = target.getAttribute("data-grid-area-name");
+    const id = target.getAttribute("data-grid-id");
+    const fragmentIndex = target.getAttribute("data-grid-fragment-index");
+    const color = target.getAttribute("stroke");
+    const rowNumber = target.getAttribute("data-grid-row");
+    const columnNumber = target.getAttribute("data-grid-column");
+
+    target.setAttribute("fill", color);
+
+    if (name) {
+      onShowGridAreaHighlight(grids[id].nodeFront, name, color);
+    }
+
+    if (fragmentIndex && rowNumber && columnNumber) {
+      onShowGridCellHighlight(grids[id].nodeFront, fragmentIndex,
+        rowNumber, columnNumber);
+    }
+  },
+
   /**
    * Renders the grid outline for the given grid container object.
    *
    * @param  {Object} grid
    *         A single grid container in the document.
    */
   renderGrid(grid) {
     const { id, color, gridFragments } = grid;
@@ -181,39 +217,19 @@ module.exports = createClass({
     const color = target.getAttribute("stroke");
 
     target.setAttribute("fill", "none");
 
     onShowGridAreaHighlight(grids[id].nodeFront, null, color);
     onShowGridCellHighlight(grids[id].nodeFront);
   },
 
-  onMouseOverCell({ target }) {
-    const {
-      grids,
-      onShowGridAreaHighlight,
-      onShowGridCellHighlight,
-    } = this.props;
-    const name = target.getAttribute("data-grid-area-name");
-    const id = target.getAttribute("data-grid-id");
-    const fragmentIndex = target.getAttribute("data-grid-fragment-index");
-    const color = target.getAttribute("stroke");
-    const rowNumber = target.getAttribute("data-grid-row");
-    const columnNumber = target.getAttribute("data-grid-column");
-
-    target.setAttribute("fill", color);
-
-    if (name) {
-      onShowGridAreaHighlight(grids[id].nodeFront, name, color);
-    }
-
-    if (fragmentIndex && rowNumber && columnNumber) {
-      onShowGridCellHighlight(grids[id].nodeFront, fragmentIndex,
-        rowNumber, columnNumber);
-    }
+  onMouseOverCell(event) {
+    event.persist();
+    this.highlightCell(event);
   },
 
   render() {
     return this.state.selectedGrids.length ?
       dom.svg(
         {
           className: "grid-outline",
           width: "100%",
--- a/dom/ipc/ContentPrefs.cpp
+++ b/dom/ipc/ContentPrefs.cpp
@@ -117,16 +117,17 @@ const char* mozilla::dom::ContentPrefs::
   "media.decoder-doctor.wmf-disabled-is-failure",
   "media.decoder.fuzzing.dont-delay-inputexhausted",
   "media.decoder.fuzzing.enabled",
   "media.decoder.fuzzing.video-output-minimum-interval-ms",
   "media.decoder.limit",
   "media.decoder.recycle.enabled",
   "media.dormant-on-pause-timeout-ms",
   "media.eme.audio.blank",
+  "media.eme.chromium-api.enabled",
   "media.eme.enabled",
   "media.eme.video.blank",
   "media.ffmpeg.enabled",
   "media.ffvpx.enabled",
   "media.ffvpx.low-latency.enabled",
   "media.flac.enabled",
   "media.forcestereo.enabled",
   "media.gmp.async-shutdown-timeout",
--- a/dom/media/MediaPrefs.h
+++ b/dom/media/MediaPrefs.h
@@ -137,16 +137,17 @@ private:
   DECL_MEDIA_PREF("media.decoder.fuzzing.video-output-minimum-interval-ms", PDMFuzzingInterval, uint32_t, 0);
   DECL_MEDIA_PREF("media.decoder.fuzzing.dont-delay-inputexhausted", PDMFuzzingDelayInputExhausted, bool, true);
   DECL_MEDIA_PREF("media.decoder.recycle.enabled",            MediaDecoderCheckRecycling, bool, false);
   DECL_MEDIA_PREF("media.gmp.decoder.enabled",                PDMGMPEnabled, bool, true);
   DECL_MEDIA_PREF("media.gmp.decoder.aac",                    GMPAACPreferred, uint32_t, 0);
   DECL_MEDIA_PREF("media.gmp.decoder.h264",                   GMPH264Preferred, uint32_t, 0);
   DECL_MEDIA_PREF("media.eme.audio.blank",                    EMEBlankAudio, bool, false);
   DECL_MEDIA_PREF("media.eme.video.blank",                    EMEBlankVideo, bool, false);
+  DECL_MEDIA_PREF("media.eme.chromium-api.enabled",           EMEChromiumAPIEnabled, bool, false);
 
   // MediaDecoderStateMachine
   DECL_MEDIA_PREF("media.suspend-bkgnd-video.enabled",        MDSMSuspendBackgroundVideoEnabled, bool, false);
   DECL_MEDIA_PREF("media.suspend-bkgnd-video.delay-ms",       MDSMSuspendBackgroundVideoDelay, AtomicUint32, SUSPEND_BACKGROUND_VIDEO_DELAY_MS);
   DECL_MEDIA_PREF("media.dormant-on-pause-timeout-ms",        DormantOnPauseTimeout, int32_t, 5000);
 
   // WebSpeech
   DECL_MEDIA_PREF("media.webspeech.synth.force_global_queue", WebSpeechForceGlobal, bool, false);
--- a/dom/media/eme/CDMProxy.h
+++ b/dom/media/eme/CDMProxy.h
@@ -12,16 +12,17 @@
 
 #include "mozilla/dom/MediaKeyMessageEvent.h"
 #include "mozilla/dom/MediaKeys.h"
 
 #include "nsIThread.h"
 
 namespace mozilla {
 class MediaRawData;
+class ChromiumCDMProxy;
 
 enum DecryptStatus {
   Ok = 0,
   GenericErr = 1,
   NoKeyErr = 2,
   AbortedErr = 3,
 };
 
@@ -109,16 +110,17 @@ public:
                              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,
+                           dom::MediaKeySessionType aSessionType,
                            const nsAString& aSessionId) = 0;
 
   // Main thread only.
   // Sends a new certificate to the CDM.
   // Calls MediaKeys->ResolvePromise(aPromiseId) after the CDM has
   // processed the request.
   // Assumes ownership of (Move()s) aCert's contents.
   virtual void SetServerCertificate(PromiseId aPromiseId,
@@ -218,16 +220,18 @@ public:
                                      nsTArray<nsCString>& aSessionIds) = 0;
 
 #ifdef DEBUG
   virtual bool IsOnOwnerThread() = 0;
 #endif
 
   virtual uint32_t GetDecryptorId() { return 0; }
 
+  virtual ChromiumCDMProxy* AsChromiumCDMProxy() { return nullptr; }
+
 protected:
   virtual ~CDMProxy() {}
 
   // Helper to enforce that a raw pointer is only accessed on the main thread.
   template<class Type>
   class MainThreadOnlyRawPtr {
   public:
     explicit MainThreadOnlyRawPtr(Type* aPtr)
--- a/dom/media/eme/DetailedPromise.cpp
+++ b/dom/media/eme/DetailedPromise.cpp
@@ -31,27 +31,27 @@ DetailedPromise::DetailedPromise(nsIGlob
 }
 
 DetailedPromise::~DetailedPromise()
 {
   // It would be nice to assert that mResponded is identical to
   // GetPromiseState() == PromiseState::Rejected.  But by now we've been
   // unlinked, so don't have a reference to our actual JS Promise object
   // anymore.
-  MaybeReportTelemetry(Failed);
+  MaybeReportTelemetry(kFailed);
 }
 
 void
 DetailedPromise::MaybeReject(nsresult aArg, const nsACString& aReason)
 {
   nsPrintfCString msg("%s promise rejected 0x%" PRIx32 " '%s'", mName.get(),
                       static_cast<uint32_t>(aArg), PromiseFlatCString(aReason).get());
   EME_LOG("%s", msg.get());
 
-  MaybeReportTelemetry(Failed);
+  MaybeReportTelemetry(kFailed);
 
   LogToBrowserConsole(NS_ConvertUTF8toUTF16(msg));
 
   ErrorResult rv;
   rv.ThrowDOMException(aArg, aReason);
   Promise::MaybeReject(rv);
 }
 
@@ -79,27 +79,27 @@ DetailedPromise::Create(nsIGlobalObject*
                         Telemetry::HistogramID aFailureLatencyProbe)
 {
   RefPtr<DetailedPromise> promise = new DetailedPromise(aGlobal, aName, aSuccessLatencyProbe, aFailureLatencyProbe);
   promise->CreateWrapper(nullptr, aRv);
   return aRv.Failed() ? nullptr : promise.forget();
 }
 
 void
-DetailedPromise::MaybeReportTelemetry(Status aStatus)
+DetailedPromise::MaybeReportTelemetry(eStatus aStatus)
 {
   if (mResponded) {
     return;
   }
   mResponded = true;
   if (!mSuccessLatencyProbe.WasPassed() || !mFailureLatencyProbe.WasPassed()) {
     return;
   }
   uint32_t latency = (TimeStamp::Now() - mStartTime).ToMilliseconds();
   EME_LOG("%s %s latency %ums reported via telemetry", mName.get(),
-          ((aStatus == Succeeded) ? "succcess" : "failure"), latency);
-  Telemetry::HistogramID tid = (aStatus == Succeeded) ? mSuccessLatencyProbe.Value()
+          ((aStatus == kSucceeded) ? "succcess" : "failure"), latency);
+  Telemetry::HistogramID tid = (aStatus == kSucceeded) ? mSuccessLatencyProbe.Value()
                                                       : mFailureLatencyProbe.Value();
   Telemetry::Accumulate(tid, latency);
 }
 
 } // namespace dom
 } // namespace mozilla
--- a/dom/media/eme/DetailedPromise.h
+++ b/dom/media/eme/DetailedPromise.h
@@ -32,17 +32,17 @@ public:
          const nsACString& aName,
          Telemetry::HistogramID aSuccessLatencyProbe,
          Telemetry::HistogramID aFailureLatencyProbe);
 
   template <typename T>
   void MaybeResolve(const T& aArg)
   {
     EME_LOG("%s promise resolved", mName.get());
-    MaybeReportTelemetry(Succeeded);
+    MaybeReportTelemetry(eStatus::kSucceeded);
     Promise::MaybeResolve<T>(aArg);
   }
 
   void MaybeReject(nsresult aArg) = delete;
   void MaybeReject(nsresult aArg, const nsACString& aReason);
 
   void MaybeReject(ErrorResult& aArg) = delete;
   void MaybeReject(ErrorResult&, const nsACString& aReason);
@@ -52,18 +52,18 @@ private:
                            const nsACString& aName);
 
   explicit DetailedPromise(nsIGlobalObject* aGlobal,
                            const nsACString& aName,
                            Telemetry::HistogramID aSuccessLatencyProbe,
                            Telemetry::HistogramID aFailureLatencyProbe);
   virtual ~DetailedPromise();
 
-  enum Status { Succeeded, Failed };
-  void MaybeReportTelemetry(Status aStatus);
+  enum eStatus { kSucceeded, kFailed };
+  void MaybeReportTelemetry(eStatus aStatus);
 
   nsCString mName;
   bool mResponded;
   TimeStamp mStartTime;
   Optional<Telemetry::HistogramID> mSuccessLatencyProbe;
   Optional<Telemetry::HistogramID> mFailureLatencyProbe;
 };
 
--- a/dom/media/eme/MediaKeySession.cpp
+++ b/dom/media/eme/MediaKeySession.cpp
@@ -391,17 +391,17 @@ MediaKeySession::Load(const nsAString& a
   // session from its owning MediaKey's set of sessions awaiting a sessionId.
   RefPtr<MediaKeySession> session(mKeys->GetPendingSession(Token()));
   MOZ_ASSERT(session == this, "Session should be awaiting id on its own token");
 
   // Associate with the known sessionId.
   SetSessionId(aSessionId);
 
   PromiseId pid = mKeys->StorePromise(promise);
-  mKeys->GetCDMProxy()->LoadSession(pid, aSessionId);
+  mKeys->GetCDMProxy()->LoadSession(pid, mSessionType, aSessionId);
 
   EME_LOG("MediaKeySession[%p,'%s'] Load() sent to CDM, promiseId=%d",
     this, NS_ConvertUTF16toUTF8(mSessionId).get(), pid);
 
   return promise.forget();
 }
 
 already_AddRefed<Promise>
--- a/dom/media/eme/MediaKeySystemAccess.cpp
+++ b/dom/media/eme/MediaKeySystemAccess.cpp
@@ -94,18 +94,21 @@ MediaKeySystemAccess::CreateMediaKeys(Er
                                        mKeySystem,
                                        mConfig));
   return keys->Init(aRv);
 }
 
 static bool
 HavePluginForKeySystem(const nsCString& aKeySystem)
 {
-  bool havePlugin = HaveGMPFor(NS_LITERAL_CSTRING(GMP_API_DECRYPTOR),
-                               { aKeySystem });
+  nsCString api = MediaPrefs::EMEChromiumAPIEnabled()
+                    ? NS_LITERAL_CSTRING(CHROMIUM_CDM_API)
+                    : NS_LITERAL_CSTRING(GMP_API_DECRYPTOR);
+
+  bool havePlugin = HaveGMPFor(api, { aKeySystem });
 #ifdef MOZ_WIDGET_ANDROID
   // Check if we can use MediaDrm for this keysystem.
   if (!havePlugin) {
     havePlugin = mozilla::java::MediaDrmProxy::IsSchemeSupported(aKeySystem);
   }
 #endif
   return havePlugin;
 }
--- a/dom/media/eme/MediaKeys.cpp
+++ b/dom/media/eme/MediaKeys.cpp
@@ -27,16 +27,17 @@
 #endif
 #ifdef XP_WIN
 #include "mozilla/WindowsVersion.h"
 #endif
 #include "nsContentCID.h"
 #include "nsServiceManagerUtils.h"
 #include "mozilla/dom/MediaKeySystemAccess.h"
 #include "nsPrintfCString.h"
+#include "ChromiumCDMProxy.h"
 
 namespace mozilla {
 
 namespace dom {
 
 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(MediaKeys,
                                       mElement,
                                       mParent,
@@ -338,22 +339,33 @@ MediaKeys::CreateCDMProxy(nsIEventTarget
     proxy = new MediaDrmCDMProxy(this,
                                  mKeySystem,
                                  mConfig.mDistinctiveIdentifier == MediaKeysRequirement::Required,
                                  mConfig.mPersistentState == MediaKeysRequirement::Required,
                                  aMainThread);
   } else
 #endif
   {
-    proxy = new GMPCDMProxy(this,
-                            mKeySystem,
-                            new MediaKeysGMPCrashHelper(this),
-                            mConfig.mDistinctiveIdentifier == MediaKeysRequirement::Required,
-                            mConfig.mPersistentState == MediaKeysRequirement::Required,
-                            aMainThread);
+    if (MediaPrefs::EMEChromiumAPIEnabled()) {
+      proxy = new ChromiumCDMProxy(
+        this,
+        mKeySystem,
+        new MediaKeysGMPCrashHelper(this),
+        mConfig.mDistinctiveIdentifier == MediaKeysRequirement::Required,
+        mConfig.mPersistentState == MediaKeysRequirement::Required,
+        aMainThread);
+    } else {
+      proxy = new GMPCDMProxy(
+        this,
+        mKeySystem,
+        new MediaKeysGMPCrashHelper(this),
+        mConfig.mDistinctiveIdentifier == MediaKeysRequirement::Required,
+        mConfig.mPersistentState == MediaKeysRequirement::Required,
+        aMainThread);
+    }
   }
   return proxy.forget();
 }
 
 already_AddRefed<DetailedPromise>
 MediaKeys::Init(ErrorResult& aRv)
 {
   RefPtr<DetailedPromise> promise(MakePromise(aRv,
--- a/dom/media/eme/mediadrm/MediaDrmCDMProxy.cpp
+++ b/dom/media/eme/mediadrm/MediaDrmCDMProxy.cpp
@@ -96,16 +96,17 @@ MediaDrmCDMProxy::CreateSession(uint32_t
     NewRunnableMethod<UniquePtr<CreateSessionData>&&>(this,
                                                       &MediaDrmCDMProxy::md_CreateSession,
                                                       Move(data)));
   mOwnerThread->Dispatch(task, NS_DISPATCH_NORMAL);
 }
 
 void
 MediaDrmCDMProxy::LoadSession(PromiseId aPromiseId,
+                              dom::MediaKeySessionType aSessionType,
                               const nsAString& aSessionId)
 {
   // TODO: Implement LoadSession.
   RejectPromise(aPromiseId, NS_ERROR_DOM_INVALID_STATE_ERR,
                 NS_LITERAL_CSTRING("Currently Fennec did not support LoadSession"));
 }
 
 void
--- a/dom/media/eme/mediadrm/MediaDrmCDMProxy.h
+++ b/dom/media/eme/mediadrm/MediaDrmCDMProxy.h
@@ -8,16 +8,17 @@
 #define MediaDrmCDMProxy_h_
 
 #include <jni.h>
 #include "mozilla/jni/Types.h"
 #include "GeneratedJNINatives.h"
 #include "mozilla/CDMProxy.h"
 #include "mozilla/CDMCaps.h"
 #include "mozilla/dom/MediaKeys.h"
+#include "mozilla/dom/MediaKeySession.h"
 #include "mozilla/MediaDrmProxySupport.h"
 #include "mozilla/UniquePtr.h"
 
 #include "MediaCodec.h"
 #include "nsString.h"
 
 using namespace mozilla::java;
 
@@ -42,16 +43,17 @@ public:
 
   void CreateSession(uint32_t aCreateSessionToken,
                      MediaKeySessionType aSessionType,
                      PromiseId aPromiseId,
                      const nsAString& aInitDataType,
                      nsTArray<uint8_t>& aInitData) override;
 
   void LoadSession(PromiseId aPromiseId,
+                   dom::MediaKeySessionType aSessionType,
                    const nsAString& aSessionId) override;
 
   void SetServerCertificate(PromiseId aPromiseId,
                             nsTArray<uint8_t>& aCert) override;
 
   void UpdateSession(const nsAString& aSessionId,
                      PromiseId aPromiseId,
                      nsTArray<uint8_t>& aResponse) override;
copy from dom/media/gmp/widevine-adapter/WidevineAdapter.cpp
copy to dom/media/gmp/ChromiumCDMAdapter.cpp
--- a/dom/media/gmp/widevine-adapter/WidevineAdapter.cpp
+++ b/dom/media/gmp/ChromiumCDMAdapter.cpp
@@ -1,66 +1,51 @@
 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
-#include "WidevineAdapter.h"
+#include "ChromiumCDMAdapter.h"
 #include "content_decryption_module.h"
 #include "VideoUtils.h"
-#include "WidevineDecryptor.h"
-#include "WidevineDummyDecoder.h"
-#include "WidevineUtils.h"
-#include "WidevineVideoDecoder.h"
 #include "gmp-api/gmp-entrypoints.h"
 #include "gmp-api/gmp-decryption.h"
 #include "gmp-api/gmp-video-codec.h"
 #include "gmp-api/gmp-platform.h"
+#include "WidevineUtils.h"
+#include "GMPLog.h"
 
-static const GMPPlatformAPI* sPlatform = nullptr;
+// Declared in WidevineAdapter.cpp.
+extern const GMPPlatformAPI* sPlatform;
 
 namespace mozilla {
 
-GMPErr GMPGetCurrentTime(GMPTimestamp* aOutTime) {
-  return sPlatform->getcurrenttime(aOutTime);
-}
-
-// Call on main thread only.
-GMPErr GMPSetTimerOnMainThread(GMPTask* aTask, int64_t aTimeoutMS) {
-  return sPlatform->settimer(aTask, aTimeoutMS);
-}
-
-GMPErr GMPCreateRecord(const char* aRecordName,
-                       uint32_t aRecordNameSize,
-                       GMPRecord** aOutRecord,
-                       GMPRecordClient* aClient)
-{
-  return sPlatform->createrecord(aRecordName, aRecordNameSize, aOutRecord, aClient);
-}
-
 void
-WidevineAdapter::SetAdaptee(PRLibrary* aLib)
+ChromiumCDMAdapter::SetAdaptee(PRLibrary* aLib)
 {
   mLib = aLib;
 }
 
-void* GetCdmHost(int aHostInterfaceVersion, void* aUserData)
+void*
+ChromiumCdmHost(int aHostInterfaceVersion, void* aUserData)
 {
-  CDM_LOG("GetCdmHostFunc(%d, %p)", aHostInterfaceVersion, aUserData);
-  WidevineDecryptor* decryptor = reinterpret_cast<WidevineDecryptor*>(aUserData);
-  MOZ_ASSERT(decryptor);
-  return static_cast<cdm::Host_8*>(decryptor);
+  CDM_LOG("ChromiumCdmHostFunc(%d, %p)", aHostInterfaceVersion, aUserData);
+  if (aHostInterfaceVersion != cdm::Host_8::kVersion) {
+    return nullptr;
+  }
+  return static_cast<cdm::Host_8*>(aUserData);
 }
 
 #define STRINGIFY(s) _STRINGIFY(s)
 #define _STRINGIFY(s) #s
 
 GMPErr
-WidevineAdapter::GMPInit(const GMPPlatformAPI* aPlatformAPI)
+ChromiumCDMAdapter::GMPInit(const GMPPlatformAPI* aPlatformAPI)
 {
+  CDM_LOG("ChromiumCDMAdapter::GMPInit");
   sPlatform = aPlatformAPI;
   if (!mLib) {
     return GMPGenericErr;
   }
 
   auto init = reinterpret_cast<decltype(::INITIALIZE_CDM_MODULE)*>(
     PR_FindFunctionSymbol(mLib, STRINGIFY(INITIALIZE_CDM_MODULE)));
   if (!init) {
@@ -69,91 +54,80 @@ WidevineAdapter::GMPInit(const GMPPlatfo
 
   CDM_LOG(STRINGIFY(INITIALIZE_CDM_MODULE)"()");
   init();
 
   return GMPNoErr;
 }
 
 GMPErr
-WidevineAdapter::GMPGetAPI(const char* aAPIName,
-                           void* aHostAPI,
-                           void** aPluginAPI,
-                           uint32_t aDecryptorId)
+ChromiumCDMAdapter::GMPGetAPI(const char* aAPIName,
+                              void* aHostAPI,
+                              void** aPluginAPI,
+                              uint32_t aDecryptorId)
 {
-  CDM_LOG("WidevineAdapter::GMPGetAPI(%s, 0x%p, 0x%p, %u) this=0x%p",
-          aAPIName, aHostAPI, aPluginAPI, aDecryptorId, this);
-  if (!strcmp(aAPIName, GMP_API_DECRYPTOR)) {
-    if (WidevineDecryptor::GetInstance(aDecryptorId)) {
-      // We only support one CDM instance per PGMPDecryptor. Fail!
-      CDM_LOG("WidevineAdapter::GMPGetAPI() Tried to create more than once CDM per IPDL actor! FAIL!");
-      return GMPQuotaExceededErr;
-    }
+  CDM_LOG("ChromiumCDMAdapter::GMPGetAPI(%s, 0x%p, 0x%p, %u) this=0x%p",
+          aAPIName,
+          aHostAPI,
+          aPluginAPI,
+          aDecryptorId,
+          this);
+  if (!strcmp(aAPIName, CHROMIUM_CDM_API)) {
     auto create = reinterpret_cast<decltype(::CreateCdmInstance)*>(
       PR_FindFunctionSymbol(mLib, "CreateCdmInstance"));
     if (!create) {
-      CDM_LOG("WidevineAdapter::GMPGetAPI(%s, 0x%p, 0x%p, %u) this=0x%p FAILED to find CreateCdmInstance",
-              aAPIName, aHostAPI, aPluginAPI, aDecryptorId, this);
+      CDM_LOG("ChromiumCDMAdapter::GMPGetAPI(%s, 0x%p, 0x%p, %u) this=0x%p "
+              "FAILED to find CreateCdmInstance",
+              aAPIName,
+              aHostAPI,
+              aPluginAPI,
+              aDecryptorId,
+              this);
       return GMPGenericErr;
     }
 
-    auto* decryptor = new WidevineDecryptor();
-
     auto cdm = reinterpret_cast<cdm::ContentDecryptionModule*>(
       create(cdm::ContentDecryptionModule::kVersion,
              kEMEKeySystemWidevine.get(),
              kEMEKeySystemWidevine.Length(),
-             &GetCdmHost,
-             decryptor));
+             &ChromiumCdmHost,
+             aHostAPI));
     if (!cdm) {
-      CDM_LOG("WidevineAdapter::GMPGetAPI(%s, 0x%p, 0x%p, %u) this=0x%p FAILED to create cdm",
-              aAPIName, aHostAPI, aPluginAPI, aDecryptorId, this);
+      CDM_LOG("ChromiumCDMAdapter::GMPGetAPI(%s, 0x%p, 0x%p, %u) this=0x%p "
+              "FAILED to create cdm",
+              aAPIName,
+              aHostAPI,
+              aPluginAPI,
+              aDecryptorId,
+              this);
       return GMPGenericErr;
     }
     CDM_LOG("cdm: 0x%p", cdm);
-    RefPtr<CDMWrapper> wrapper(new CDMWrapper(cdm, decryptor));
-    decryptor->SetCDM(wrapper, aDecryptorId);
-    *aPluginAPI = decryptor;
-
-  } else if (!strcmp(aAPIName, GMP_API_VIDEO_DECODER)) {
-    RefPtr<CDMWrapper> wrapper = WidevineDecryptor::GetInstance(aDecryptorId);
-
-    // There is a possible race condition, where the decryptor will be destroyed
-    // before we are able to create the video decoder, so we create a dummy
-    // decoder to avoid crashing.
-    if (!wrapper) {
-      CDM_LOG("WidevineAdapter::GMPGetAPI(%s, 0x%p, 0x%p, %u) this=0x%p No cdm for video decoder. Using a DummyDecoder",
-              aAPIName, aHostAPI, aPluginAPI, aDecryptorId, this);
-
-      *aPluginAPI = new WidevineDummyDecoder();
-    } else {
-      *aPluginAPI = new WidevineVideoDecoder(static_cast<GMPVideoHost*>(aHostAPI),
-                                             wrapper);
-    }
+    *aPluginAPI = cdm;
   }
   return *aPluginAPI ? GMPNoErr : GMPNotImplementedErr;
 }
 
 void
-WidevineAdapter::GMPShutdown()
+ChromiumCDMAdapter::GMPShutdown()
 {
-  CDM_LOG("WidevineAdapter::GMPShutdown()");
+  CDM_LOG("ChromiumCDMAdapter::GMPShutdown()");
 
   decltype(::DeinitializeCdmModule)* deinit;
   deinit = (decltype(deinit))(PR_FindFunctionSymbol(mLib, "DeinitializeCdmModule"));
   if (deinit) {
     CDM_LOG("DeinitializeCdmModule()");
     deinit();
   }
 }
 
 /* static */
 bool
-WidevineAdapter::Supports(int32_t aModuleVersion,
-                          int32_t aInterfaceVersion,
-                          int32_t aHostVersion)
+ChromiumCDMAdapter::Supports(int32_t aModuleVersion,
+                             int32_t aInterfaceVersion,
+                             int32_t aHostVersion)
 {
   return aModuleVersion == CDM_MODULE_VERSION &&
          aInterfaceVersion == cdm::ContentDecryptionModule::kVersion &&
          aHostVersion == cdm::Host_8::kVersion;
 }
 
 } // namespace mozilla
copy from dom/media/gmp/widevine-adapter/WidevineAdapter.h
copy to dom/media/gmp/ChromiumCDMAdapter.h
--- a/dom/media/gmp/widevine-adapter/WidevineAdapter.h
+++ b/dom/media/gmp/ChromiumCDMAdapter.h
@@ -1,25 +1,26 @@
 /* -*- 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 WidevineAdapter_h_
-#define WidevineAdapter_h_
+#ifndef ChromiumAdapter_h_
+#define ChromiumAdapter_h_
 
 #include "GMPLoader.h"
 #include "prlink.h"
 #include "GMPUtils.h"
 
 struct GMPPlatformAPI;
 
 namespace mozilla {
 
-class WidevineAdapter : public gmp::GMPAdapter {
+class ChromiumCDMAdapter : public gmp::GMPAdapter
+{
 public:
 
   void SetAdaptee(PRLibrary* aLib) override;
 
   // These are called in place of the corresponding GMP API functions.
   GMPErr GMPInit(const GMPPlatformAPI* aPlatformAPI) override;
   GMPErr GMPGetAPI(const char* aAPIName,
                    void* aHostAPI,
@@ -30,26 +31,11 @@ public:
   static bool Supports(int32_t aModuleVersion,
                        int32_t aInterfaceVersion,
                        int32_t aHostVersion);
 
 private:
   PRLibrary* mLib = nullptr;
 };
 
-GMPErr GMPCreateThread(GMPThread** aThread);
-GMPErr GMPRunOnMainThread(GMPTask* aTask);
-GMPErr GMPCreateMutex(GMPMutex** aMutex);
-
-// Call on main thread only.
-GMPErr GMPCreateRecord(const char* aRecordName,
-                       uint32_t aRecordNameSize,
-                       GMPRecord** aOutRecord,
-                       GMPRecordClient* aClient);
-
-// Call on main thread only.
-GMPErr GMPSetTimerOnMainThread(GMPTask* aTask, int64_t aTimeoutMS);
-
-GMPErr GMPGetCurrentTime(GMPTimestamp* aOutTime);
-
 } // namespace mozilla
 
-#endif // WidevineAdapter_h_
+#endif // ChromiumAdapter_h_
new file mode 100644
--- /dev/null
+++ b/dom/media/gmp/ChromiumCDMChild.cpp
@@ -0,0 +1,620 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "ChromiumCDMChild.h"
+#include "GMPContentChild.h"
+#include "WidevineUtils.h"
+#include "WidevineFileIO.h"
+#include "WidevineVideoFrame.h"
+#include "GMPLog.h"
+#include "GMPPlatform.h"
+#include "mozilla/Unused.h"
+#include "nsPrintfCString.h"
+#include "base/time.h"
+
+namespace mozilla {
+namespace gmp {
+
+ChromiumCDMChild::ChromiumCDMChild(GMPContentChild* aPlugin)
+  : mPlugin(aPlugin)
+{
+  MOZ_ASSERT(IsOnMessageLoopThread());
+}
+
+void
+ChromiumCDMChild::Init(cdm::ContentDecryptionModule_8* aCDM)
+{
+  MOZ_ASSERT(IsOnMessageLoopThread());
+  mCDM = aCDM;
+  MOZ_ASSERT(mCDM);
+}
+
+void
+ChromiumCDMChild::TimerExpired(void* aContext)
+{
+  MOZ_ASSERT(IsOnMessageLoopThread());
+  GMP_LOG("ChromiumCDMChild::TimerExpired(context=0x%p)", aContext);
+  if (mCDM) {
+    mCDM->TimerExpired(aContext);
+  }
+}
+
+cdm::Buffer*
+ChromiumCDMChild::Allocate(uint32_t aCapacity)
+{
+  MOZ_ASSERT(IsOnMessageLoopThread());
+  GMP_LOG("ChromiumCDMChild::Allocate(capacity=%" PRIu32 ")", aCapacity);
+  return new WidevineBuffer(aCapacity);
+}
+
+void
+ChromiumCDMChild::SetTimer(int64_t aDelayMs, void* aContext)
+{
+  MOZ_ASSERT(IsOnMessageLoopThread());
+  GMP_LOG("ChromiumCDMChild::SetTimer(delay=%" PRId64 ", context=0x%p)",
+          aDelayMs,
+          aContext);
+  RefPtr<ChromiumCDMChild> self(this);
+  SetTimerOnMainThread(NewGMPTask([self, aContext]() {
+    self->TimerExpired(aContext);
+  }), aDelayMs);
+}
+
+cdm::Time
+ChromiumCDMChild::GetCurrentWallTime()
+{
+  MOZ_ASSERT(IsOnMessageLoopThread());
+  return base::Time::Now().ToDoubleT();
+}
+
+void
+ChromiumCDMChild::OnResolveNewSessionPromise(uint32_t aPromiseId,
+                                             const char* aSessionId,
+                                             uint32_t aSessionIdSize)
+{
+  MOZ_ASSERT(IsOnMessageLoopThread());
+  GMP_LOG("ChromiumCDMChild::OnResolveNewSessionPromise(pid=%" PRIu32
+          ", sid=%s)",
+          aPromiseId,
+          aSessionId);
+
+  if (mLoadSessionPromiseIds.Contains(aPromiseId)) {
+    // As laid out in the Chromium CDM API, if the CDM fails to load
+    // a session it calls OnResolveNewSessionPromise with nullptr as the sessionId.
+    // We can safely assume this means that we have failed to load a session
+    // as the other methods specify calling 'OnRejectPromise' when they fail.
+    bool loadSuccessful = aSessionId != nullptr;
+    GMP_LOG("ChromiumCDMChild::OnResolveNewSessionPromise(pid=%u, sid=%s) "
+            "resolving %s load session ",
+            aPromiseId,
+            aSessionId,
+            (loadSuccessful ? "successful" : "failed"));
+    Unused << SendResolveLoadSessionPromise(aPromiseId, loadSuccessful);
+    mLoadSessionPromiseIds.RemoveElement(aPromiseId);
+    return;
+  }
+
+  Unused << SendOnResolveNewSessionPromise(aPromiseId,
+                                           nsCString(aSessionId, aSessionIdSize));
+}
+
+void ChromiumCDMChild::OnResolvePromise(uint32_t aPromiseId)
+{
+  MOZ_ASSERT(IsOnMessageLoopThread());
+  GMP_LOG("ChromiumCDMChild::OnResolvePromise(pid=%" PRIu32 ")", aPromiseId);
+  Unused << SendOnResolvePromise(aPromiseId);
+}
+
+void
+ChromiumCDMChild::OnRejectPromise(uint32_t aPromiseId,
+                                  cdm::Error aError,
+                                  uint32_t aSystemCode,
+                                  const char* aErrorMessage,
+                                  uint32_t aErrorMessageSize)
+{
+  MOZ_ASSERT(IsOnMessageLoopThread());
+  GMP_LOG("ChromiumCDMChild::OnRejectPromise(pid=%" PRIu32 ", err=%" PRIu32
+          " code=%" PRIu32 ", msg='%s')",
+          aPromiseId,
+          aError,
+          aSystemCode,
+          aErrorMessage);
+  Unused << SendOnRejectPromise(aPromiseId,
+                                static_cast<uint32_t>(aError),
+                                aSystemCode,
+                                nsCString(aErrorMessage, aErrorMessageSize));
+}
+
+void
+ChromiumCDMChild::OnSessionMessage(const char* aSessionId,
+                                   uint32_t aSessionIdSize,
+                                   cdm::MessageType aMessageType,
+                                   const char* aMessage,
+                                   uint32_t aMessageSize,
+                                   const char* aLegacyDestinationUrl,
+                                   uint32_t aLegacyDestinationUrlLength)
+{
+  MOZ_ASSERT(IsOnMessageLoopThread());
+  GMP_LOG("ChromiumCDMChild::OnSessionMessage(sid=%s, type=%" PRIu32
+          " size=%" PRIu32 ")",
+          aSessionId,
+          aMessageType,
+          aMessageSize);
+  nsTArray<uint8_t> message;
+  message.AppendElements(aMessage, aMessageSize);
+  Unused << SendOnSessionMessage(nsCString(aSessionId, aSessionIdSize),
+                                 static_cast<uint32_t>(aMessageType),
+                                 message);
+}
+
+static nsCString
+ToString(const cdm::KeyInformation* aKeysInfo, uint32_t aKeysInfoCount)
+{
+  nsCString str;
+  for (uint32_t i = 0; i < aKeysInfoCount; i++) {
+    nsCString keyId;
+    const cdm::KeyInformation& key = aKeysInfo[i];
+    for (size_t k = 0; k < key.key_id_size; k++) {
+      keyId.Append(nsPrintfCString("%hhX", key.key_id[k]));
+    }
+    if (!str.IsEmpty()) {
+      str.AppendLiteral(",");
+    }
+    str.Append(keyId);
+    str.AppendLiteral("=");
+    str.AppendInt(key.status);
+  }
+  return str;
+}
+
+void
+ChromiumCDMChild::OnSessionKeysChange(const char *aSessionId,
+                                      uint32_t aSessionIdSize,
+                                      bool aHasAdditionalUsableKey,
+                                      const cdm::KeyInformation* aKeysInfo,
+                                      uint32_t aKeysInfoCount)
+{
+  MOZ_ASSERT(IsOnMessageLoopThread());
+  GMP_LOG("ChromiumCDMChild::OnSessionKeysChange(sid=%s) keys={%s}",
+          aSessionId,
+          ToString(aKeysInfo, aKeysInfoCount).get());
+
+  nsTArray<CDMKeyInformation> keys;
+  keys.SetCapacity(aKeysInfoCount);
+  for (uint32_t i = 0; i < aKeysInfoCount; i++) {
+    const cdm::KeyInformation& key = aKeysInfo[i];
+    nsTArray<uint8_t> kid;
+    kid.AppendElements(key.key_id, key.key_id_size);
+    keys.AppendElement(CDMKeyInformation(kid, key.status, key.system_code));
+  }
+  Unused << SendOnSessionKeysChange(nsCString(aSessionId, aSessionIdSize),
+                                    keys);
+}
+
+void
+ChromiumCDMChild::OnExpirationChange(const char* aSessionId,
+                                     uint32_t aSessionIdSize,
+                                     cdm::Time aNewExpiryTime)
+{
+  MOZ_ASSERT(IsOnMessageLoopThread());
+  GMP_LOG("ChromiumCDMChild::OnExpirationChange(sid=%s, time=%lf)",
+          aSessionId,
+          aNewExpiryTime);
+  Unused << SendOnExpirationChange(nsCString(aSessionId, aSessionIdSize),
+                                   aNewExpiryTime);
+}
+
+void
+ChromiumCDMChild::OnSessionClosed(const char* aSessionId,
+                                  uint32_t aSessionIdSize)
+{
+  MOZ_ASSERT(IsOnMessageLoopThread());
+  GMP_LOG("ChromiumCDMChild::OnSessionClosed(sid=%s)", aSessionId);
+  Unused << SendOnSessionClosed(nsCString(aSessionId, aSessionIdSize));
+}
+
+void
+ChromiumCDMChild::OnLegacySessionError(const char* aSessionId,
+                                       uint32_t aSessionIdLength,
+                                       cdm::Error aError,
+                                       uint32_t aSystemCode,
+                                       const char* aErrorMessage,
+                                       uint32_t aErrorMessageLength)
+{
+  MOZ_ASSERT(IsOnMessageLoopThread());
+  GMP_LOG("ChromiumCDMChild::OnLegacySessionError(sid=%s, error=%" PRIu32
+          " msg='%s')",
+          aSessionId,
+          aError,
+          aErrorMessage);
+  Unused << SendOnLegacySessionError(
+    nsCString(aSessionId, aSessionIdLength),
+    static_cast<uint32_t>(aError),
+    aSystemCode,
+    nsCString(aErrorMessage, aErrorMessageLength));
+}
+
+cdm::FileIO*
+ChromiumCDMChild::CreateFileIO(cdm::FileIOClient * aClient)
+{
+  MOZ_ASSERT(IsOnMessageLoopThread());
+  GMP_LOG("ChromiumCDMChild::CreateFileIO()");
+  if (!mPersistentStateAllowed) {
+    return nullptr;
+  }
+  return new WidevineFileIO(aClient);
+}
+
+bool
+ChromiumCDMChild::IsOnMessageLoopThread()
+{
+  return mPlugin && mPlugin->GMPMessageLoop() == MessageLoop::current();
+}
+
+mozilla::ipc::IPCResult
+ChromiumCDMChild::RecvInit(const bool& aAllowDistinctiveIdentifier,
+                           const bool& aAllowPersistentState)
+{
+  MOZ_ASSERT(IsOnMessageLoopThread());
+  GMP_LOG("ChromiumCDMChild::RecvInit(distinctiveId=%d, persistentState=%d)",
+          aAllowDistinctiveIdentifier,
+          aAllowPersistentState);
+  mPersistentStateAllowed = aAllowPersistentState;
+  if (mCDM) {
+    mCDM->Initialize(aAllowDistinctiveIdentifier, aAllowPersistentState);
+  }
+  return IPC_OK();
+}
+
+mozilla::ipc::IPCResult
+ChromiumCDMChild::RecvSetServerCertificate(const uint32_t& aPromiseId,
+                                           nsTArray<uint8_t>&& aServerCert)
+
+{
+  MOZ_ASSERT(IsOnMessageLoopThread());
+  GMP_LOG("ChromiumCDMChild::RecvSetServerCertificate() certlen=%zu",
+          aServerCert.Length());
+  if (mCDM) {
+    mCDM->SetServerCertificate(aPromiseId,
+                               aServerCert.Elements(),
+                               aServerCert.Length());
+  }
+  return IPC_OK();
+}
+
+mozilla::ipc::IPCResult
+ChromiumCDMChild::RecvCreateSessionAndGenerateRequest(
+  const uint32_t& aPromiseId,
+  const uint32_t& aSessionType,
+  const uint32_t& aInitDataType,
+  nsTArray<uint8_t>&& aInitData)
+{
+  MOZ_ASSERT(IsOnMessageLoopThread());
+  GMP_LOG("ChromiumCDMChild::RecvCreateSessionAndGenerateRequest("
+          "pid=%" PRIu32 ", sessionType=%" PRIu32 ", initDataType=%" PRIu32
+          ") initDataLen=%zu",
+          aPromiseId,
+          aSessionType,
+          aInitDataType,
+          aInitData.Length());
+  MOZ_ASSERT(aSessionType <= cdm::SessionType::kPersistentKeyRelease);
+  MOZ_ASSERT(aInitDataType <= cdm::InitDataType::kWebM);
+  if (mCDM) {
+    mCDM->CreateSessionAndGenerateRequest(aPromiseId,
+                                          static_cast<cdm::SessionType>(aSessionType),
+                                          static_cast<cdm::InitDataType>(aInitDataType),
+                                          aInitData.Elements(),
+                                          aInitData.Length());
+  }
+  return IPC_OK();
+}
+
+mozilla::ipc::IPCResult
+ChromiumCDMChild::RecvLoadSession(const uint32_t& aPromiseId,
+                                  const uint32_t& aSessionType,
+                                  const nsCString& aSessionId)
+{
+  GMP_LOG("ChromiumCDMChild::RecvLoadSession(pid=%u, type=%u, sessionId=%s)",
+          aPromiseId,
+          aSessionType,
+          aSessionId.get());
+  if (mCDM) {
+    mLoadSessionPromiseIds.AppendElement(aPromiseId);
+    mCDM->LoadSession(aPromiseId,
+                      static_cast<cdm::SessionType>(aSessionType),
+                      aSessionId.get(),
+                      aSessionId.Length());
+  }
+  return IPC_OK();
+}
+
+mozilla::ipc::IPCResult
+ChromiumCDMChild::RecvUpdateSession(const uint32_t& aPromiseId,
+                                    const nsCString& aSessionId,
+                                    nsTArray<uint8_t>&& aResponse)
+{
+  MOZ_ASSERT(IsOnMessageLoopThread());
+  GMP_LOG("ChromiumCDMChild::RecvUpdateSession(pid=%" PRIu32
+          ", sid=%s) responseLen=%zu",
+          aPromiseId,
+          aSessionId.get(),
+          aResponse.Length());
+  if (mCDM) {
+    mCDM->UpdateSession(aPromiseId,
+                        aSessionId.get(),
+                        aSessionId.Length(),
+                        aResponse.Elements(),
+                        aResponse.Length());
+  }
+  return IPC_OK();
+}
+
+mozilla::ipc::IPCResult
+ChromiumCDMChild::RecvCloseSession(const uint32_t& aPromiseId,
+                                   const nsCString& aSessionId)
+{
+  MOZ_ASSERT(IsOnMessageLoopThread());
+  GMP_LOG("ChromiumCDMChild::RecvCloseSession(pid=%" PRIu32 ", sid=%s)",
+          aPromiseId,
+          aSessionId.get());
+  if (mCDM) {
+    mCDM->CloseSession(aPromiseId, aSessionId.get(), aSessionId.Length());
+  }
+  return IPC_OK();
+}
+
+mozilla::ipc::IPCResult
+ChromiumCDMChild::RecvRemoveSession(const uint32_t& aPromiseId,
+                                    const nsCString& aSessionId)
+{
+  MOZ_ASSERT(IsOnMessageLoopThread());
+  GMP_LOG("ChromiumCDMChild::RecvRemoveSession(pid=%" PRIu32 ", sid=%s)",
+          aPromiseId,
+          aSessionId.get());
+  if (mCDM) {
+    mCDM->RemoveSession(aPromiseId, aSessionId.get(), aSessionId.Length());
+  }
+  return IPC_OK();
+}
+
+void
+ChromiumCDMChild::DecryptFailed(uint32_t aId, cdm::Status aStatus)
+{
+  MOZ_ASSERT(IsOnMessageLoopThread());
+  Unused << SendDecrypted(aId, aStatus, nsTArray<uint8_t>());
+}
+
+static void
+InitInputBuffer(const CDMInputBuffer& aBuffer,
+                nsTArray<cdm::SubsampleEntry>& aSubSamples,
+                cdm::InputBuffer& aInputBuffer)
+{
+  aInputBuffer.data = aBuffer.mData().Elements();
+  aInputBuffer.data_size = aBuffer.mData().Length();
+
+  if (aBuffer.mIsEncrypted()) {
+    aInputBuffer.key_id = aBuffer.mKeyId().Elements();
+    aInputBuffer.key_id_size = aBuffer.mKeyId().Length();
+
+    aInputBuffer.iv = aBuffer.mIV().Elements();
+    aInputBuffer.iv_size = aBuffer.mIV().Length();
+
+    aSubSamples.SetCapacity(aBuffer.mClearBytes().Length());
+    for (size_t i = 0; i < aBuffer.mCipherBytes().Length(); i++) {
+      aSubSamples.AppendElement(cdm::SubsampleEntry(aBuffer.mClearBytes()[i],
+                                                    aBuffer.mCipherBytes()[i]));
+    }
+    aInputBuffer.subsamples = aSubSamples.Elements();
+    aInputBuffer.num_subsamples = aSubSamples.Length();
+  }
+  aInputBuffer.timestamp = aBuffer.mTimestamp();
+}
+
+mozilla::ipc::IPCResult
+ChromiumCDMChild::RecvDecrypt(const uint32_t& aId,
+                              const CDMInputBuffer& aBuffer)
+{
+  MOZ_ASSERT(IsOnMessageLoopThread());
+  GMP_LOG("ChromiumCDMChild::RecvDecrypt()");
+  if (!mCDM) {
+    GMP_LOG("ChromiumCDMChild::RecvDecrypt() no CDM");
+    DecryptFailed(aId, cdm::kDecryptError);
+    return IPC_OK();
+  }
+  if (aBuffer.mClearBytes().Length() != aBuffer.mCipherBytes().Length()) {
+    GMP_LOG("ChromiumCDMChild::RecvDecrypt() clear/cipher bytes length doesn't "
+            "match");
+    DecryptFailed(aId, cdm::kDecryptError);
+    return IPC_OK();
+  }
+
+  cdm::InputBuffer input;
+  nsTArray<cdm::SubsampleEntry> subsamples;
+  InitInputBuffer(aBuffer, subsamples, input);
+
+  WidevineDecryptedBlock output;
+  cdm::Status status = mCDM->Decrypt(input, &output);
+
+  if (status != cdm::kSuccess) {
+    DecryptFailed(aId, status);
+    return IPC_OK();
+  }
+
+  if (!output.DecryptedBuffer() ||
+      output.DecryptedBuffer()->Size() != aBuffer.mData().Length()) {
+    // The sizes of the input and output should exactly match.
+    DecryptFailed(aId, cdm::kDecryptError);
+    return IPC_OK();
+  }
+
+  nsTArray<uint8_t> buf =
+    static_cast<WidevineBuffer*>(output.DecryptedBuffer())->ExtractBuffer();
+  Unused << SendDecrypted(aId, cdm::kSuccess, buf);
+
+  return IPC_OK();
+}
+
+mozilla::ipc::IPCResult
+ChromiumCDMChild::RecvInitializeVideoDecoder(
+  const CDMVideoDecoderConfig& aConfig)
+{
+  MOZ_ASSERT(IsOnMessageLoopThread());
+  MOZ_ASSERT(!mDecoderInitialized);
+  cdm::VideoDecoderConfig config;
+  config.codec =
+    static_cast<cdm::VideoDecoderConfig::VideoCodec>(aConfig.mCodec());
+  config.profile =
+    static_cast<cdm::VideoDecoderConfig::VideoCodecProfile>(aConfig.mProfile());
+  config.format = static_cast<cdm::VideoFormat>(aConfig.mFormat());
+  config.coded_size =
+    mCodedSize = { aConfig.mImageWidth(), aConfig.mImageHeight() };
+  nsTArray<uint8_t> extraData(aConfig.mExtraData());
+  config.extra_data = extraData.Elements();
+  config.extra_data_size = extraData.Length();
+  cdm::Status status = mCDM->InitializeVideoDecoder(config);
+  GMP_LOG("ChromiumCDMChild::RecvInitializeVideoDecoder() status=%u", status);
+  Unused << SendOnDecoderInitDone(status);
+  mDecoderInitialized = status == cdm::kSuccess;
+  return IPC_OK();
+}
+
+mozilla::ipc::IPCResult
+ChromiumCDMChild::RecvDeinitializeVideoDecoder()
+{
+  MOZ_ASSERT(IsOnMessageLoopThread());
+  GMP_LOG("ChromiumCDMChild::RecvDeinitializeVideoDecoder()");
+  MOZ_ASSERT(mDecoderInitialized);
+  if (mDecoderInitialized) {
+    mDecoderInitialized = false;
+    mCDM->DeinitializeDecoder(cdm::kStreamTypeVideo);
+  }
+  return IPC_OK();
+}
+
+mozilla::ipc::IPCResult
+ChromiumCDMChild::RecvResetVideoDecoder()
+{
+  MOZ_ASSERT(IsOnMessageLoopThread());
+  GMP_LOG("ChromiumCDMChild::RecvResetVideoDecoder()");
+  if (mDecoderInitialized) {
+    mCDM->ResetDecoder(cdm::kStreamTypeVideo);
+  }
+  Unused << SendResetVideoDecoderComplete();
+  return IPC_OK();
+}
+
+mozilla::ipc::IPCResult
+ChromiumCDMChild::RecvDecryptAndDecodeFrame(const CDMInputBuffer& aBuffer)
+{
+  MOZ_ASSERT(IsOnMessageLoopThread());
+  GMP_LOG("ChromiumCDMChild::RecvDecryptAndDecodeFrame()");
+  MOZ_ASSERT(mDecoderInitialized);
+
+  // The output frame may not have the same timestamp as the frame we put in.
+  // We may need to input a number of frames before we receive output. The
+  // CDM's decoder reorders to ensure frames output are in presentation order.
+  // So we need to store the durations of the frames input, and retrieve them
+  // on output.
+  mFrameDurations.Insert(aBuffer.mTimestamp(), aBuffer.mDuration());
+
+  cdm::InputBuffer input;
+  nsTArray<cdm::SubsampleEntry> subsamples;
+  InitInputBuffer(aBuffer, subsamples, input);
+
+  WidevineVideoFrame frame;
+  cdm::Status rv = mCDM->DecryptAndDecodeFrame(input, &frame);
+  GMP_LOG("WidevineVideoDecoder::Decode(timestamp=%" PRId64 ") rv=%d",
+          input.timestamp,
+          rv);
+
+  switch (rv) {
+    case cdm::kNoKey:
+      GMP_LOG("NoKey for sample at time=%" PRId64 "!", input.timestamp);
+      // Somehow our key became unusable. Typically this would happen when
+      // a stream requires output protection, and the configuration changed
+      // such that output protection is no longer available. For example, a
+      // non-compliant monitor was attached. The JS player should notice the
+      // key status changing to "output-restricted", and is supposed to switch
+      // to a stream that doesn't require OP. In order to keep the playback
+      // pipeline rolling, just output a black frame. See bug 1343140.
+      frame.InitToBlack(mCodedSize.width, mCodedSize.height, input.timestamp);
+      MOZ_FALLTHROUGH;
+    case cdm::kSuccess:
+      ReturnOutput(frame);
+      break;
+    case cdm::kNeedMoreData:
+      Unused << SendDecoded(gmp::CDMVideoFrame());
+      break;
+    default:
+      Unused << SendDecodeFailed(rv);
+      break;
+  }
+
+  return IPC_OK();
+}
+
+void
+ChromiumCDMChild::ReturnOutput(WidevineVideoFrame& aFrame)
+{
+  // TODO: WidevineBuffers should hold a shmem instead of a array, and we can
+  // send the handle instead of copying the array here.
+  gmp::CDMVideoFrame output;
+  output.mFormat() = static_cast<cdm::VideoFormat>(aFrame.Format());
+  output.mImageWidth() = aFrame.Size().width;
+  output.mImageHeight() = aFrame.Size().height;
+  output.mData() = Move(
+    reinterpret_cast<WidevineBuffer*>(aFrame.FrameBuffer())->ExtractBuffer());
+  output.mYPlane() = { aFrame.PlaneOffset(cdm::VideoFrame::kYPlane),
+                       aFrame.Stride(cdm::VideoFrame::kYPlane) };
+  output.mUPlane() = { aFrame.PlaneOffset(cdm::VideoFrame::kUPlane),
+                       aFrame.Stride(cdm::VideoFrame::kUPlane) };
+  output.mVPlane() = { aFrame.PlaneOffset(cdm::VideoFrame::kVPlane),
+                       aFrame.Stride(cdm::VideoFrame::kVPlane) };
+  output.mTimestamp() = aFrame.Timestamp();
+
+  uint64_t duration = 0;
+  if (mFrameDurations.Find(aFrame.Timestamp(), duration)) {
+    output.mDuration() = duration;
+  }
+
+  Unused << SendDecoded(output);
+}
+
+mozilla::ipc::IPCResult
+ChromiumCDMChild::RecvDrain()
+{
+  WidevineVideoFrame frame;
+  cdm::InputBuffer sample;
+  cdm::Status rv = mCDM->DecryptAndDecodeFrame(sample, &frame);
+  CDM_LOG("ChromiumCDMChild::RecvDrain();  DecryptAndDecodeFrame() rv=%d", rv);
+  if (rv == cdm::kSuccess) {
+    MOZ_ASSERT(frame.Format() != cdm::kUnknownVideoFormat);
+    ReturnOutput(frame);
+  } else {
+    Unused << SendDrainComplete();
+  }
+  return IPC_OK();
+}
+
+mozilla::ipc::IPCResult
+ChromiumCDMChild::RecvDestroy()
+{
+  MOZ_ASSERT(IsOnMessageLoopThread());
+  GMP_LOG("ChromiumCDMChild::RecvDestroy()");
+
+  MOZ_ASSERT(!mDecoderInitialized);
+
+  if (mCDM) {
+    mCDM->Destroy();
+    mCDM = nullptr;
+  }
+
+  Unused << Send__delete__(this);
+
+  return IPC_OK();
+}
+
+} // namespace gmp
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/dom/media/gmp/ChromiumCDMChild.h
@@ -0,0 +1,131 @@
+/* -*- 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 ChromiumCDMChild_h_
+#define ChromiumCDMChild_h_
+
+#include "content_decryption_module.h"
+#include "mozilla/gmp/PChromiumCDMChild.h"
+#include "SimpleMap.h"
+#include "WidevineVideoFrame.h"
+
+namespace mozilla {
+namespace gmp {
+
+class GMPContentChild;
+
+class ChromiumCDMChild : public PChromiumCDMChild
+                       , public cdm::Host_8
+{
+public:
+  NS_INLINE_DECL_THREADSAFE_REFCOUNTING(ChromiumCDMChild);
+
+  explicit ChromiumCDMChild(GMPContentChild* aPlugin);
+
+  void Init(cdm::ContentDecryptionModule_8* aCDM);
+
+  void TimerExpired(void* aContext);
+
+  // cdm::Host_8
+  cdm::Buffer* Allocate(uint32_t aCapacity) override;
+  void SetTimer(int64_t aDelayMs, void* aContext) override;
+  cdm::Time GetCurrentWallTime() override;
+  void OnResolveNewSessionPromise(uint32_t aPromiseId,
+                                  const char* aSessionId,
+                                  uint32_t aSessionIdSize) override;
+  void OnResolvePromise(uint32_t aPromiseId) override;
+  void OnRejectPromise(uint32_t aPromiseId,
+                       cdm::Error aError,
+                       uint32_t aSystemCode,
+                       const char* aErrorMessage,
+                       uint32_t aErrorMessageSize) override;
+  void OnSessionMessage(const char* aSessionId,
+                        uint32_t aSessionIdSize,
+                        cdm::MessageType aMessageType,
+                        const char* aMessage,
+                        uint32_t aMessageSize,
+                        const char* aLegacyDestinationUrl,
+                        uint32_t aLegacyDestinationUrlLength) override;
+  void OnSessionKeysChange(const char* aSessionId,
+                           uint32_t aSessionIdSize,
+                           bool aHasAdditionalUsableKey,
+                           const cdm::KeyInformation* aKeysInfo,
+                           uint32_t aKeysInfoCount) override;
+  void OnExpirationChange(const char* aSessionId,
+                          uint32_t aSessionIdSize,
+                          cdm::Time aNewExpiryTime) override;
+  void OnSessionClosed(const char* aSessionId,
+                       uint32_t aSessionIdSize) override;
+  void OnLegacySessionError(const char* aSessionId,
+                            uint32_t aSessionIdLength,
+                            cdm::Error aError,
+                            uint32_t aSystemCode,
+                            const char* aErrorMessage,
+                            uint32_t aErrorMessageLength) override;
+  void SendPlatformChallenge(const char* aServiceId,
+                             uint32_t aServiceIdSize,
+                             const char* aChallenge,
+                             uint32_t aChallengeSize) override {}
+  void EnableOutputProtection(uint32_t aDesiredProtectionMask) override {}
+  void QueryOutputProtectionStatus() override {}
+  void OnDeferredInitializationDone(cdm::StreamType aStreamType,
+                                    cdm::Status aDecoderStatus) override {}
+  cdm::FileIO* CreateFileIO(cdm::FileIOClient* aClient) override;
+protected:
+  ~ChromiumCDMChild() {}
+
+  bool IsOnMessageLoopThread();
+
+  ipc::IPCResult RecvInit(const bool& aAllowDistinctiveIdentifier,
+                          const bool& aAllowPersistentState) override;
+  ipc::IPCResult RecvSetServerCertificate(
+    const uint32_t& aPromiseId,
+    nsTArray<uint8_t>&& aServerCert) override;
+  ipc::IPCResult RecvCreateSessionAndGenerateRequest(
+    const uint32_t& aPromiseId,
+    const uint32_t& aSessionType,
+    const uint32_t& aInitDataType,
+    nsTArray<uint8_t>&& aInitData) override;
+  ipc::IPCResult RecvLoadSession(const uint32_t& aPromiseId,
+                                 const uint32_t& aSessionType,
+                                 const nsCString& aSessionId) override;
+  ipc::IPCResult RecvUpdateSession(const uint32_t& aPromiseId,
+                                   const nsCString& aSessionId,
+                                   nsTArray<uint8_t>&& aResponse) override;
+  ipc::IPCResult RecvCloseSession(const uint32_t& aPromiseId,
+                                  const nsCString& aSessionId) override;
+  ipc::IPCResult RecvRemoveSession(const uint32_t& aPromiseId,
+                                   const nsCString& aSessionId) override;
+  ipc::IPCResult RecvDecrypt(const uint32_t& aId,
+                             const CDMInputBuffer& aBuffer) override;
+  ipc::IPCResult RecvInitializeVideoDecoder(
+    const CDMVideoDecoderConfig& aConfig) override;
+  ipc::IPCResult RecvDeinitializeVideoDecoder() override;
+  ipc::IPCResult RecvResetVideoDecoder() override;
+  ipc::IPCResult RecvDecryptAndDecodeFrame(
+    const CDMInputBuffer& aBuffer) override;
+  ipc::IPCResult RecvDrain() override;
+  ipc::IPCResult RecvDestroy() override;
+
+  void DecryptFailed(uint32_t aId, cdm::Status aStatus);
+  void ReturnOutput(WidevineVideoFrame& aFrame);
+
+  GMPContentChild* mPlugin = nullptr;
+  cdm::ContentDecryptionModule_8* mCDM = nullptr;
+
+  typedef SimpleMap<uint64_t> DurationMap;
+  DurationMap mFrameDurations;
+  nsTArray<uint32_t> mLoadSessionPromiseIds;
+
+  cdm::Size mCodedSize;
+
+  bool mDecoderInitialized = false;
+  bool mPersistentStateAllowed = false;
+};
+
+} // namespace gmp
+} // namespace mozilla
+
+#endif // ChromiumCDMChild_h_
new file mode 100644
--- /dev/null
+++ b/dom/media/gmp/ChromiumCDMParent.cpp
@@ -0,0 +1,850 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "ChromiumCDMParent.h"
+#include "mozilla/gmp/GMPTypes.h"
+#include "GMPContentChild.h"
+#include "GMPContentParent.h"
+#include "mozilla/Unused.h"
+#include "ChromiumCDMProxy.h"
+#include "mozilla/dom/MediaKeyMessageEventBinding.h"
+#include "content_decryption_module.h"
+#include "GMPLog.h"
+
+namespace mozilla {
+namespace gmp {
+
+ChromiumCDMParent::ChromiumCDMParent(GMPContentParent* aContentParent,
+                                     uint32_t aPluginId)
+  : mPluginId(aPluginId)
+  , mContentParent(aContentParent)
+{
+  GMP_LOG(
+    "ChromiumCDMParent::ChromiumCDMParent(this=%p, contentParent=%p, id=%u)",
+    this,
+    aContentParent,
+    aPluginId);
+}
+
+bool
+ChromiumCDMParent::Init(ChromiumCDMProxy* aProxy,
+                        bool aAllowDistinctiveIdentifier,
+                        bool aAllowPersistentState)
+{
+  GMP_LOG("ChromiumCDMParent::Init(this=%p)", this);
+  if (!aProxy) {
+    return false;
+  }
+  mProxy = aProxy;
+  return SendInit(aAllowDistinctiveIdentifier, aAllowPersistentState);
+}
+
+void
+ChromiumCDMParent::CreateSession(uint32_t aCreateSessionToken,
+                                 uint32_t aSessionType,
+                                 uint32_t aInitDataType,
+                                 uint32_t aPromiseId,
+                                 const nsTArray<uint8_t>& aInitData)
+{
+  GMP_LOG("ChromiumCDMParent::CreateSession(this=%p)", this);
+  if (mIsShutdown) {
+    RejectPromise(aPromiseId,
+                  NS_ERROR_DOM_INVALID_STATE_ERR,
+                  NS_LITERAL_CSTRING("CDM is shutdown."));
+    return;
+  }
+  if (!SendCreateSessionAndGenerateRequest(
+        aPromiseId, aSessionType, aInitDataType, aInitData)) {
+    RejectPromise(
+      aPromiseId,
+      NS_ERROR_DOM_INVALID_STATE_ERR,
+      NS_LITERAL_CSTRING("Failed to send generateRequest to CDM process."));
+    return;
+  }
+  mPromiseToCreateSessionToken.Put(aPromiseId, aCreateSessionToken);
+}
+
+void
+ChromiumCDMParent::LoadSession(uint32_t aPromiseId,
+                               uint32_t aSessionType,
+                               nsString aSessionId)
+{
+  GMP_LOG("ChromiumCDMParent::LoadSession(this=%p, pid=%u, type=%u, sid=%s)",
+          this,
+          aPromiseId,
+          aSessionType,
+          NS_ConvertUTF16toUTF8(aSessionId).get());
+  if (mIsShutdown) {
+    RejectPromise(aPromiseId,
+                  NS_ERROR_DOM_INVALID_STATE_ERR,
+                  NS_LITERAL_CSTRING("CDM is shutdown."));
+    return;
+  }
+  if (!SendLoadSession(
+        aPromiseId, aSessionType, NS_ConvertUTF16toUTF8(aSessionId))) {
+    RejectPromise(
+      aPromiseId,
+      NS_ERROR_DOM_INVALID_STATE_ERR,
+      NS_LITERAL_CSTRING("Failed to send loadSession to CDM process."));
+    return;
+  }
+}
+
+void
+ChromiumCDMParent::SetServerCertificate(uint32_t aPromiseId,
+                                        const nsTArray<uint8_t>& aCert)
+{
+  GMP_LOG("ChromiumCDMParent::SetServerCertificate(this=%p)", this);
+  if (mIsShutdown) {
+    RejectPromise(aPromiseId,
+                  NS_ERROR_DOM_INVALID_STATE_ERR,
+                  NS_LITERAL_CSTRING("CDM is shutdown."));
+    return;
+  }
+  if (!SendSetServerCertificate(aPromiseId, aCert)) {
+    RejectPromise(
+      aPromiseId,
+      NS_ERROR_DOM_INVALID_STATE_ERR,
+      NS_LITERAL_CSTRING("Failed to send setServerCertificate to CDM process"));
+  }
+}
+
+void
+ChromiumCDMParent::UpdateSession(const nsCString& aSessionId,
+                                 uint32_t aPromiseId,
+                                 const nsTArray<uint8_t>& aResponse)
+{
+  GMP_LOG("ChromiumCDMParent::UpdateSession(this=%p)", this);
+  if (mIsShutdown) {
+    RejectPromise(aPromiseId,
+                  NS_ERROR_DOM_INVALID_STATE_ERR,
+                  NS_LITERAL_CSTRING("CDM is shutdown."));
+    return;
+  }
+  if (!SendUpdateSession(aPromiseId, aSessionId, aResponse)) {
+    RejectPromise(
+      aPromiseId,
+      NS_ERROR_DOM_INVALID_STATE_ERR,
+      NS_LITERAL_CSTRING("Failed to send updateSession to CDM process"));
+  }
+}
+
+void
+ChromiumCDMParent::CloseSession(const nsCString& aSessionId,
+                                uint32_t aPromiseId)
+{
+  GMP_LOG("ChromiumCDMParent::CloseSession(this=%p)", this);
+  if (mIsShutdown) {
+    RejectPromise(aPromiseId,
+                  NS_ERROR_DOM_INVALID_STATE_ERR,
+                  NS_LITERAL_CSTRING("CDM is shutdown."));
+    return;
+  }
+  if (!SendCloseSession(aPromiseId, aSessionId)) {
+    RejectPromise(
+      aPromiseId,
+      NS_ERROR_DOM_INVALID_STATE_ERR,
+      NS_LITERAL_CSTRING("Failed to send closeSession to CDM process"));
+  }
+}
+
+void
+ChromiumCDMParent::RemoveSession(const nsCString& aSessionId,
+                                 uint32_t aPromiseId)
+{
+  GMP_LOG("ChromiumCDMParent::RemoveSession(this=%p)", this);
+  if (mIsShutdown) {
+    RejectPromise(aPromiseId,
+                  NS_ERROR_DOM_INVALID_STATE_ERR,
+                  NS_LITERAL_CSTRING("CDM is shutdown."));
+    return;
+  }
+  if (!SendRemoveSession(aPromiseId, aSessionId)) {
+    RejectPromise(
+      aPromiseId,
+      NS_ERROR_DOM_INVALID_STATE_ERR,
+      NS_LITERAL_CSTRING("Failed to send removeSession to CDM process"));
+  }
+}
+
+static bool
+InitCDMInputBuffer(gmp::CDMInputBuffer& aBuffer, MediaRawData* aSample)
+{
+  const CryptoSample& crypto = aSample->mCrypto;
+  if (crypto.mEncryptedSizes.Length() != crypto.mPlainSizes.Length()) {
+    GMP_LOG("InitCDMInputBuffer clear/cipher subsamples don't match");
+    return false;
+  }
+
+  nsTArray<uint8_t> data;
+  data.AppendElements(aSample->Data(), aSample->Size());
+
+  aBuffer = gmp::CDMInputBuffer(data,
+                                crypto.mKeyId,
+                                crypto.mIV,
+                                aSample->mTime,
+                                aSample->mDuration,
+                                crypto.mPlainSizes,
+                                crypto.mEncryptedSizes,
+                                crypto.mValid);
+  return true;
+}
+
+RefPtr<DecryptPromise>
+ChromiumCDMParent::Decrypt(MediaRawData* aSample)
+{
+  if (mIsShutdown) {
+    return DecryptPromise::CreateAndReject(DecryptResult(GenericErr, aSample),
+                                           __func__);
+  }
+  CDMInputBuffer buffer;
+  if (!InitCDMInputBuffer(buffer, aSample)) {
+    return DecryptPromise::CreateAndReject(DecryptResult(GenericErr, aSample),
+                                           __func__);
+  }
+  RefPtr<DecryptJob> job = new DecryptJob(aSample);
+  if (!SendDecrypt(job->mId, buffer)) {
+    GMP_LOG(
+      "ChromiumCDMParent::Decrypt(this=%p) failed to send decrypt message",
+      this);
+    return DecryptPromise::CreateAndReject(DecryptResult(GenericErr, aSample),
+                                           __func__);
+  }
+  RefPtr<DecryptPromise> promise = job->Ensure();
+  mDecrypts.AppendElement(job);
+  return promise;
+}
+
+ipc::IPCResult
+ChromiumCDMParent::Recv__delete__()
+{
+  MOZ_ASSERT(mIsShutdown);
+  GMP_LOG("ChromiumCDMParent::Recv__delete__(this=%p)", this);
+  if (mContentParent) {
+    mContentParent->ChromiumCDMDestroyed(this);
+    mContentParent = nullptr;
+  }
+  return IPC_OK();
+}
+
+ipc::IPCResult
+ChromiumCDMParent::RecvOnResolveNewSessionPromise(const uint32_t& aPromiseId,
+                                                  const nsCString& aSessionId)
+{
+  GMP_LOG("ChromiumCDMParent::RecvOnResolveNewSessionPromise(this=%p, pid=%u, "
+          "sid=%s)",
+          this,
+          aPromiseId,
+          aSessionId.get());
+  if (!mProxy || mIsShutdown) {
+    return IPC_OK();
+  }
+
+  Maybe<uint32_t> token = mPromiseToCreateSessionToken.GetAndRemove(aPromiseId);
+  if (token.isNothing()) {
+    RejectPromise(aPromiseId,
+                  NS_ERROR_DOM_INVALID_STATE_ERR,
+                  NS_LITERAL_CSTRING("Lost session token for new session."));
+    return IPC_OK();
+  }
+
+  RefPtr<Runnable> task =
+    NewRunnableMethod<uint32_t, nsString>(mProxy,
+                                          &ChromiumCDMProxy::OnSetSessionId,
+                                          token.value(),
+                                          NS_ConvertUTF8toUTF16(aSessionId));
+  NS_DispatchToMainThread(task);
+
+  ResolvePromise(aPromiseId);
+
+  return IPC_OK();
+}
+
+ipc::IPCResult
+ChromiumCDMParent::RecvResolveLoadSessionPromise(const uint32_t& aPromiseId,
+                                                 const bool& aSuccessful)
+{
+  GMP_LOG("ChromiumCDMParent::RecvResolveLoadSessionPromise(this=%p, pid=%u, "
+          "successful=%d)",
+          this,
+          aPromiseId,
+          aSuccessful);
+  if (!mProxy || mIsShutdown) {
+    return IPC_OK();
+  }
+
+  NS_DispatchToMainThread(NewRunnableMethod<uint32_t, bool>(
+    mProxy,
+    &ChromiumCDMProxy::OnResolveLoadSessionPromise,
+    aPromiseId,
+    aSuccessful));
+  return IPC_OK();
+}
+void
+ChromiumCDMParent::ResolvePromise(uint32_t aPromiseId)
+{
+  GMP_LOG(
+    "ChromiumCDMParent::ResolvePromise(this=%p, pid=%u)", this, aPromiseId);
+
+  // Note: The MediaKeys rejects all pending DOM promises when it
+  // initiates shutdown.
+  if (!mProxy || mIsShutdown) {
+    return;
+  }
+  NS_DispatchToMainThread(NewRunnableMethod<uint32_t>(
+    mProxy, &ChromiumCDMProxy::ResolvePromise, aPromiseId));
+}
+
+ipc::IPCResult
+ChromiumCDMParent::RecvOnResolvePromise(const uint32_t& aPromiseId)
+{
+  ResolvePromise(aPromiseId);
+  return IPC_OK();
+}
+
+static nsresult
+ToNsresult(uint32_t aError)
+{
+  switch (static_cast<cdm::Error>(aError)) {
+    case cdm::kNotSupportedError:
+      return NS_ERROR_DOM_NOT_SUPPORTED_ERR;
+    case cdm::kInvalidStateError:
+      return NS_ERROR_DOM_INVALID_STATE_ERR;
+    case cdm::kInvalidAccessError:
+      // Note: Chrome converts kInvalidAccessError to TypeError, since the
+      // Chromium CDM API doesn't have a type error enum value. The EME spec
+      // requires TypeError in some places, so we do the same conversion.
+      // See bug 1313202.
+      return NS_ERROR_DOM_TYPE_ERR;
+    case cdm::kQuotaExceededError:
+      return NS_ERROR_DOM_QUOTA_EXCEEDED_ERR;
+    case cdm::kUnknownError:
+      return NS_ERROR_DOM_UNKNOWN_ERR; // Note: Unique placeholder.
+    case cdm::kClientError:
+      return NS_ERROR_DOM_ABORT_ERR; // Note: Unique placeholder.
+    case cdm::kOutputError:
+      return NS_ERROR_DOM_SECURITY_ERR; // Note: Unique placeholder.
+  };
+  MOZ_ASSERT_UNREACHABLE("Invalid cdm::Error enum value.");
+  return NS_ERROR_DOM_TIMEOUT_ERR; // Note: Unique placeholder.
+}
+
+void
+ChromiumCDMParent::RejectPromise(uint32_t aPromiseId,
+                                 nsresult aError,
+                                 const nsCString& aErrorMessage)
+{
+  GMP_LOG(
+    "ChromiumCDMParent::RejectPromise(this=%p, pid=%u)", this, aPromiseId);
+  // Note: The MediaKeys rejects all pending DOM promises when it
+  // initiates shutdown.
+  if (!mProxy || mIsShutdown) {
+    return;
+  }
+  NS_DispatchToMainThread(NewRunnableMethod<uint32_t, nsresult, nsCString>(
+    mProxy,
+    &ChromiumCDMProxy::RejectPromise,
+    aPromiseId,
+    aError,
+    aErrorMessage));
+}
+
+ipc::IPCResult
+ChromiumCDMParent::RecvOnRejectPromise(const uint32_t& aPromiseId,
+                                       const uint32_t& aError,
+                                       const uint32_t& aSystemCode,
+                                       const nsCString& aErrorMessage)
+{
+  RejectPromise(aPromiseId, ToNsresult(aError), aErrorMessage);
+  return IPC_OK();
+}
+
+static dom::MediaKeyMessageType
+ToDOMMessageType(uint32_t aMessageType)
+{
+  switch (static_cast<cdm::MessageType>(aMessageType)) {
+    case cdm::kLicenseRequest:
+      return dom::MediaKeyMessageType::License_request;
+    case cdm::kLicenseRenewal:
+      return dom::MediaKeyMessageType::License_renewal;
+    case cdm::kLicenseRelease:
+      return dom::MediaKeyMessageType::License_release;
+  }
+  MOZ_ASSERT_UNREACHABLE("Invalid cdm::MessageType enum value.");
+  return dom::MediaKeyMessageType::License_request;
+}
+
+ipc::IPCResult
+ChromiumCDMParent::RecvOnSessionMessage(const nsCString& aSessionId,
+                                        const uint32_t& aMessageType,
+                                        nsTArray<uint8_t>&& aMessage)
+{
+  GMP_LOG("ChromiumCDMParent::RecvOnSessionMessage(this=%p, sid=%s)",
+          this,
+          aSessionId.get());
+  if (!mProxy || mIsShutdown) {
+    return IPC_OK();
+  }
+  RefPtr<CDMProxy> proxy = mProxy;
+  nsString sid = NS_ConvertUTF8toUTF16(aSessionId);
+  dom::MediaKeyMessageType messageType = ToDOMMessageType(aMessageType);
+  nsTArray<uint8_t> msg(Move(aMessage));
+  NS_DispatchToMainThread(
+    NS_NewRunnableFunction([proxy, sid, messageType, msg]() mutable {
+      proxy->OnSessionMessage(sid, messageType, msg);
+    }));
+  return IPC_OK();
+}
+
+static dom::MediaKeyStatus
+ToDOMMediaKeyStatus(uint32_t aStatus)
+{
+  switch (static_cast<cdm::KeyStatus>(aStatus)) {
+    case cdm::kUsable:
+      return dom::MediaKeyStatus::Usable;
+    case cdm::kInternalError:
+      return dom::MediaKeyStatus::Internal_error;
+    case cdm::kExpired:
+      return dom::MediaKeyStatus::Expired;
+    case cdm::kOutputRestricted:
+      return dom::MediaKeyStatus::Output_restricted;
+    case cdm::kOutputDownscaled:
+      return dom::MediaKeyStatus::Output_downscaled;
+    case cdm::kStatusPending:
+      return dom::MediaKeyStatus::Status_pending;
+    case cdm::kReleased:
+      return dom::MediaKeyStatus::Released;
+  }
+  MOZ_ASSERT_UNREACHABLE("Invalid cdm::KeyStatus enum value.");
+  return dom::MediaKeyStatus::Internal_error;
+}
+
+ipc::IPCResult
+ChromiumCDMParent::RecvOnSessionKeysChange(
+  const nsCString& aSessionId,
+  nsTArray<CDMKeyInformation>&& aKeysInfo)
+{
+  GMP_LOG("ChromiumCDMParent::RecvOnSessionKeysChange(this=%p)", this);
+  if (!mProxy || mIsShutdown) {
+    return IPC_OK();
+  }
+  bool keyStatusesChange = false;
+  {
+    CDMCaps::AutoLock caps(mProxy->Capabilites());
+    for (size_t i = 0; i < aKeysInfo.Length(); i++) {
+      keyStatusesChange |=
+        caps.SetKeyStatus(aKeysInfo[i].mKeyId(),
+                          NS_ConvertUTF8toUTF16(aSessionId),
+                          dom::Optional<dom::MediaKeyStatus>(
+                            ToDOMMediaKeyStatus(aKeysInfo[i].mStatus())));
+    }
+  }
+  if (keyStatusesChange) {
+    NS_DispatchToMainThread(
+      NewRunnableMethod<nsString>(mProxy,
+                                  &ChromiumCDMProxy::OnKeyStatusesChange,
+                                  NS_ConvertUTF8toUTF16(aSessionId)));
+  }
+  return IPC_OK();
+}
+
+ipc::IPCResult
+ChromiumCDMParent::RecvOnExpirationChange(const nsCString& aSessionId,
+                                          const double& aSecondsSinceEpoch)
+{
+  GMP_LOG("ChromiumCDMParent::RecvOnExpirationChange(this=%p) time=%lf",
+          this,
+          aSecondsSinceEpoch);
+  if (!mProxy || mIsShutdown) {
+    return IPC_OK();
+  }
+  NS_DispatchToMainThread(NewRunnableMethod<nsString, UnixTime>(
+    mProxy,
+    &ChromiumCDMProxy::OnExpirationChange,
+    NS_ConvertUTF8toUTF16(aSessionId),
+    GMPTimestamp(aSecondsSinceEpoch * 1000)));
+  return IPC_OK();
+}
+
+ipc::IPCResult
+ChromiumCDMParent::RecvOnSessionClosed(const nsCString& aSessionId)
+{
+  GMP_LOG("ChromiumCDMParent::RecvOnSessionClosed(this=%p)", this);
+  if (!mProxy || mIsShutdown) {
+    return IPC_OK();
+  }
+  NS_DispatchToMainThread(
+    NewRunnableMethod<nsString>(mProxy,
+                                &ChromiumCDMProxy::OnSessionClosed,
+                                NS_ConvertUTF8toUTF16(aSessionId)));
+  return IPC_OK();
+}
+
+ipc::IPCResult
+ChromiumCDMParent::RecvOnLegacySessionError(const nsCString& aSessionId,
+                                            const uint32_t& aError,
+                                            const uint32_t& aSystemCode,
+                                            const nsCString& aMessage)
+{
+  GMP_LOG("ChromiumCDMParent::RecvOnLegacySessionError(this=%p)", this);
+  if (!mProxy || mIsShutdown) {
+    return IPC_OK();
+  }
+  NS_DispatchToMainThread(
+    NewRunnableMethod<nsString, nsresult, uint32_t, nsString>(
+      mProxy,
+      &ChromiumCDMProxy::OnSessionError,
+      NS_ConvertUTF8toUTF16(aSessionId),
+      ToNsresult(aError),
+      aSystemCode,
+      NS_ConvertUTF8toUTF16(aMessage)));
+  return IPC_OK();
+}
+
+DecryptStatus
+ToDecryptStatus(uint32_t aError)
+{
+  switch (static_cast<cdm::Status>(aError)) {
+    case cdm::kSuccess:
+      return DecryptStatus::Ok;
+    case cdm::kNoKey:
+      return DecryptStatus::NoKeyErr;
+    default:
+      return DecryptStatus::GenericErr;
+  }
+}
+
+ipc::IPCResult
+ChromiumCDMParent::RecvDecrypted(const uint32_t& aId,
+                                 const uint32_t& aStatus,
+                                 nsTArray<uint8_t>&& aData)
+{
+  GMP_LOG("ChromiumCDMParent::RecvDecrypted(this=%p, id=%u, status=%u)",
+          this,
+          aId,
+          aStatus);
+  if (mIsShutdown) {
+    MOZ_ASSERT(mDecrypts.IsEmpty());
+    return IPC_OK();
+  }
+  for (size_t i = 0; i < mDecrypts.Length(); i++) {
+    if (mDecrypts[i]->mId == aId) {
+      mDecrypts[i]->PostResult(ToDecryptStatus(aStatus), aData);
+      mDecrypts.RemoveElementAt(i);
+      break;
+    }
+  }
+  return IPC_OK();
+}
+
+ipc::IPCResult
+ChromiumCDMParent::RecvDecoded(const CDMVideoFrame& aFrame)
+{
+  if (mIsShutdown || mDecodePromise.IsEmpty()) {
+    return IPC_OK();
+  }
+  VideoData::YCbCrBuffer b;
+  nsTArray<uint8_t> data;
+  data = aFrame.mData();
+
+  if (data.IsEmpty()) {
+    mDecodePromise.ResolveIfExists(nsTArray<RefPtr<MediaData>>(), __func__);
+    return IPC_OK();
+  }
+
+  b.mPlanes[0].mData = data.Elements();
+  b.mPlanes[0].mWidth = aFrame.mImageWidth();
+  b.mPlanes[0].mHeight = aFrame.mImageHeight();
+  b.mPlanes[0].mStride = aFrame.mYPlane().mStride();
+  b.mPlanes[0].mOffset = aFrame.mYPlane().mPlaneOffset();
+  b.mPlanes[0].mSkip = 0;
+
+  b.mPlanes[1].mData = data.Elements();
+  b.mPlanes[1].mWidth = (aFrame.mImageWidth() + 1) / 2;
+  b.mPlanes[1].mHeight = (aFrame.mImageHeight() + 1) / 2;
+  b.mPlanes[1].mStride = aFrame.mUPlane().mStride();
+  b.mPlanes[1].mOffset = aFrame.mUPlane().mPlaneOffset();
+  b.mPlanes[1].mSkip = 0;
+
+  b.mPlanes[2].mData = data.Elements();
+  b.mPlanes[2].mWidth = (aFrame.mImageWidth() + 1) / 2;
+  b.mPlanes[2].mHeight = (aFrame.mImageHeight() + 1) / 2;
+  b.mPlanes[2].mStride = aFrame.mVPlane().mStride();
+  b.mPlanes[2].mOffset = aFrame.mVPlane().mPlaneOffset();
+  b.mPlanes[2].mSkip = 0;
+
+  gfx::IntRect pictureRegion(0, 0, aFrame.mImageWidth(), aFrame.mImageHeight());
+  RefPtr<VideoData> v = VideoData::CreateAndCopyData(mVideoInfo,
+                                                     mImageContainer,
+                                                     mLastStreamOffset,
+                                                     aFrame.mTimestamp(),
+                                                     aFrame.mDuration(),
+                                                     b,
+                                                     false,
+                                                     -1,
+                                                     pictureRegion);
+
+  RefPtr<ChromiumCDMParent> self = this;
+  if (v) {
+    mDecodePromise.ResolveIfExists({ Move(v) }, __func__);
+  } else {
+    mDecodePromise.RejectIfExists(
+      MediaResult(NS_ERROR_OUT_OF_MEMORY,
+                  RESULT_DETAIL("CallBack::CreateAndCopyData")),
+      __func__);
+  }
+
+  return IPC_OK();
+}
+
+ipc::IPCResult
+ChromiumCDMParent::RecvDecodeFailed(const uint32_t& aStatus)
+{
+  if (mIsShutdown) {
+    MOZ_ASSERT(mDecodePromise.IsEmpty());
+    return IPC_OK();
+  }
+  mDecodePromise.RejectIfExists(
+    MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR,
+                RESULT_DETAIL("ChromiumCDMParent::RecvDecodeFailed")),
+    __func__);
+  return IPC_OK();
+}
+
+ipc::IPCResult
+ChromiumCDMParent::RecvShutdown()
+{
+  GMP_LOG("ChromiumCDMParent::RecvShutdown(this=%p)", this);
+  Shutdown();
+  return IPC_OK();
+}
+
+void
+ChromiumCDMParent::ActorDestroy(ActorDestroyReason aWhy)
+{
+  GMP_LOG("ChromiumCDMParent::ActorDestroy(this=%p, reason=%d)", this, aWhy);
+  MOZ_ASSERT(!mActorDestroyed);
+  mActorDestroyed = true;
+  if (!mIsShutdown) {
+    // Plugin crash.
+    MOZ_ASSERT(aWhy == AbnormalShutdown);
+    Shutdown();
+  }
+  MOZ_ASSERT(mIsShutdown);
+  RefPtr<ChromiumCDMParent> kungFuDeathGrip(this);
+  if (mContentParent) {
+    mContentParent->ChromiumCDMDestroyed(this);
+    mContentParent = nullptr;
+  }
+  bool abnormalShutdown = (aWhy == AbnormalShutdown);
+  if (abnormalShutdown && mProxy) {
+    RefPtr<Runnable> task =
+      NewRunnableMethod(mProxy, &ChromiumCDMProxy::Terminated);
+    NS_DispatchToMainThread(task);
+  }
+  MaybeDisconnect(abnormalShutdown);
+}
+
+RefPtr<MediaDataDecoder::InitPromise>
+ChromiumCDMParent::InitializeVideoDecoder(
+  const gmp::CDMVideoDecoderConfig& aConfig,
+  const VideoInfo& aInfo,
+  RefPtr<layers::ImageContainer> aImageContainer)
+{
+  if (mIsShutdown) {
+    return MediaDataDecoder::InitPromise::CreateAndReject(
+      MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR,
+                  RESULT_DETAIL("ChromiumCDMParent is shutdown")),
+      __func__);
+  }
+
+  if (!SendInitializeVideoDecoder(aConfig)) {
+    return MediaDataDecoder::InitPromise::CreateAndReject(
+      MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR,
+                  RESULT_DETAIL("Failed to send init video decoder to CDM")),
+      __func__);
+  }
+
+  mVideoDecoderInitialized = true;
+  mImageContainer = aImageContainer;
+  mVideoInfo = aInfo;
+
+  return mInitVideoDecoderPromise.Ensure(__func__);
+}
+
+ipc::IPCResult
+ChromiumCDMParent::RecvOnDecoderInitDone(const uint32_t& aStatus)
+{
+  GMP_LOG("ChromiumCDMParent::RecvOnDecoderInitDone(this=%p, status=%u)",
+          this,
+          aStatus);
+  if (mIsShutdown) {
+    MOZ_ASSERT(mInitVideoDecoderPromise.IsEmpty());
+    return IPC_OK();
+  }
+  if (aStatus == static_cast<uint32_t>(cdm::kSuccess)) {
+    mInitVideoDecoderPromise.ResolveIfExists(TrackInfo::kVideoTrack, __func__);
+  } else {
+    mVideoDecoderInitialized = false;
+    mInitVideoDecoderPromise.RejectIfExists(
+      MediaResult(
+        NS_ERROR_DOM_MEDIA_FATAL_ERR,
+        RESULT_DETAIL("CDM init decode failed with %" PRIu32, aStatus)),
+      __func__);
+  }
+  return IPC_OK();
+}
+
+RefPtr<MediaDataDecoder::DecodePromise>
+ChromiumCDMParent::DecryptAndDecodeFrame(MediaRawData* aSample)
+{
+  if (mIsShutdown) {
+    return MediaDataDecoder::DecodePromise::CreateAndReject(
+      MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR,
+                  RESULT_DETAIL("ChromiumCDMParent is shutdown")),
+      __func__);
+  }
+
+  CDMInputBuffer buffer;
+
+  if (!InitCDMInputBuffer(buffer, aSample)) {
+    return MediaDataDecoder::DecodePromise::CreateAndReject(
+      MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR, "Failed to init CDM buffer."),
+      __func__);
+  }
+
+  mLastStreamOffset = aSample->mOffset;
+
+  if (!SendDecryptAndDecodeFrame(buffer)) {
+    GMP_LOG(
+      "ChromiumCDMParent::Decrypt(this=%p) failed to send decrypt message.",
+      this);
+    return MediaDataDecoder::DecodePromise::CreateAndReject(
+      MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR,
+                  "Failed to send decrypt to CDM process."),
+      __func__);
+  }
+
+  return mDecodePromise.Ensure(__func__);
+}
+
+RefPtr<MediaDataDecoder::FlushPromise>
+ChromiumCDMParent::FlushVideoDecoder()
+{
+  if (mIsShutdown) {
+    return MediaDataDecoder::FlushPromise::CreateAndReject(
+      MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR,
+                  RESULT_DETAIL("ChromiumCDMParent is shutdown")),
+      __func__);
+  }
+
+  mDecodePromise.RejectIfExists(NS_ERROR_DOM_MEDIA_CANCELED, __func__);
+  if (!SendResetVideoDecoder()) {
+    return MediaDataDecoder::FlushPromise::CreateAndReject(
+      MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR, "Failed to send flush to CDM."),
+      __func__);
+  }
+  return mFlushDecoderPromise.Ensure(__func__);
+}
+
+ipc::IPCResult
+ChromiumCDMParent::RecvResetVideoDecoderComplete()
+{
+  if (mIsShutdown) {
+    MOZ_ASSERT(mFlushDecoderPromise.IsEmpty());
+    return IPC_OK();
+  }
+  mFlushDecoderPromise.ResolveIfExists(true, __func__);
+  return IPC_OK();
+}
+
+RefPtr<MediaDataDecoder::DecodePromise>
+ChromiumCDMParent::Drain()
+{
+  MOZ_ASSERT(mDecodePromise.IsEmpty(), "Must wait for decoding to complete");
+  if (mIsShutdown) {
+    return MediaDataDecoder::DecodePromise::CreateAndReject(
+      MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR,
+                  RESULT_DETAIL("ChromiumCDMParent is shutdown")),
+      __func__);
+  }
+
+  RefPtr<MediaDataDecoder::DecodePromise> p = mDecodePromise.Ensure(__func__);
+  if (!SendDrain()) {
+    mDecodePromise.Resolve(MediaDataDecoder::DecodedData(), __func__);
+  }
+  return p;
+}
+
+ipc::IPCResult
+ChromiumCDMParent::RecvDrainComplete()
+{
+  if (mIsShutdown) {
+    MOZ_ASSERT(mDecodePromise.IsEmpty());
+    return IPC_OK();
+  }
+  mDecodePromise.ResolveIfExists(MediaDataDecoder::DecodedData(), __func__);
+  return IPC_OK();
+}
+RefPtr<ShutdownPromise>
+ChromiumCDMParent::ShutdownVideoDecoder()
+{
+  if (mIsShutdown || !mVideoDecoderInitialized) {
+    return ShutdownPromise::CreateAndResolve(true, __func__);
+  }
+  mInitVideoDecoderPromise.RejectIfExists(NS_ERROR_DOM_MEDIA_CANCELED,
+                                          __func__);
+  mDecodePromise.RejectIfExists(NS_ERROR_DOM_MEDIA_CANCELED, __func__);
+  MOZ_ASSERT(mFlushDecoderPromise.IsEmpty());
+  if (!SendDeinitializeVideoDecoder()) {
+    return ShutdownPromise::CreateAndResolve(true, __func__);
+  }
+  mVideoDecoderInitialized = false;
+  return ShutdownPromise::CreateAndResolve(true, __func__);
+}
+
+void
+ChromiumCDMParent::Shutdown()
+{
+  GMP_LOG("ChromiumCDMParent::Shutdown(this=%p)", this);
+
+  if (mIsShutdown) {
+    return;
+  }
+  mIsShutdown = true;
+
+  for (RefPtr<DecryptJob>& decrypt : mDecrypts) {
+    decrypt->PostResult(AbortedErr);
+  }
+  mDecrypts.Clear();
+
+  if (mVideoDecoderInitialized && !mActorDestroyed) {
+    Unused << SendDeinitializeVideoDecoder();
+    mVideoDecoderInitialized = false;
+  }
+
+  // Note: MediaKeys rejects all outstanding promises when it initiates shutdown.
+  mPromiseToCreateSessionToken.Clear();
+
+  mInitVideoDecoderPromise.RejectIfExists(
+    MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR,
+                RESULT_DETAIL("ChromiumCDMParent is shutdown")),
+    __func__);
+  mDecodePromise.RejectIfExists(
+    MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR,
+                RESULT_DETAIL("ChromiumCDMParent is shutdown")),
+    __func__);
+  mFlushDecoderPromise.RejectIfExists(
+    MediaResult(NS_ERROR_DOM_MEDIA_FATAL_ERR,
+                RESULT_DETAIL("ChromiumCDMParent is shutdown")),
+    __func__);
+
+  if (!mActorDestroyed) {
+    Unused << SendDestroy();
+  }
+}
+
+} // namespace gmp
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/dom/media/gmp/ChromiumCDMParent.h
@@ -0,0 +1,156 @@
+/* -*- 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 ChromiumCDMParent_h_
+#define ChromiumCDMParent_h_
+
+#include "DecryptJob.h"
+#include "GMPCrashHelper.h"
+#include "GMPCrashHelperHolder.h"
+#include "GMPMessageUtils.h"
+#include "mozilla/gmp/PChromiumCDMParent.h"
+#include "mozilla/RefPtr.h"
+#include "nsDataHashtable.h"
+#include "PlatformDecoderModule.h"
+#include "ImageContainer.h"
+
+namespace mozilla {
+
+class MediaRawData;
+class ChromiumCDMProxy;
+
+namespace gmp {
+
+class GMPContentParent;
+
+class ChromiumCDMParent final
+  : public PChromiumCDMParent
+  , public GMPCrashHelperHolder
+{
+public:
+  NS_INLINE_DECL_THREADSAFE_REFCOUNTING(ChromiumCDMParent)
+
+  ChromiumCDMParent(GMPContentParent* aContentParent, uint32_t aPluginId);
+
+  uint32_t PluginId() const { return mPluginId; }
+
+  bool Init(ChromiumCDMProxy* aProxy,
+            bool aAllowDistinctiveIdentifier,
+            bool aAllowPersistentState);
+
+  void CreateSession(uint32_t aCreateSessionToken,
+                     uint32_t aSessionType,
+                     uint32_t aInitDataType,
+                     uint32_t aPromiseId,
+                     const nsTArray<uint8_t>& aInitData);
+
+  void LoadSession(uint32_t aPromiseId,
+                   uint32_t aSessionType,
+                   nsString aSessionId);
+
+  void SetServerCertificate(uint32_t aPromiseId,
+                            const nsTArray<uint8_t>& aCert);
+
+  void UpdateSession(const nsCString& aSessionId,
+                     uint32_t aPromiseId,
+                     const nsTArray<uint8_t>& aResponse);
+
+  void CloseSession(const nsCString& aSessionId, uint32_t aPromiseId);
+
+  void RemoveSession(const nsCString& aSessionId, uint32_t aPromiseId);
+
+  RefPtr<DecryptPromise> Decrypt(MediaRawData* aSample);
+
+  // TODO: Add functions for clients to send data to CDM, and
+  // a Close() function.
+  RefPtr<MediaDataDecoder::InitPromise> InitializeVideoDecoder(
+    const gmp::CDMVideoDecoderConfig& aConfig,
+    const VideoInfo& aInfo,
+    RefPtr<layers::ImageContainer> aImageContainer);
+
+  RefPtr<MediaDataDecoder::DecodePromise> DecryptAndDecodeFrame(
+    MediaRawData* aSample);
+
+  RefPtr<MediaDataDecoder::FlushPromise> FlushVideoDecoder();
+
+  RefPtr<MediaDataDecoder::DecodePromise> Drain();
+
+  RefPtr<ShutdownPromise> ShutdownVideoDecoder();
+
+  void Shutdown();
+
+protected:
+  ~ChromiumCDMParent() {}
+
+  ipc::IPCResult Recv__delete__() override;
+  ipc::IPCResult RecvOnResolveNewSessionPromise(
+    const uint32_t& aPromiseId,
+    const nsCString& aSessionId) override;
+  ipc::IPCResult RecvResolveLoadSessionPromise(
+    const uint32_t& aPromiseId,
+    const bool& aSuccessful) override;
+  ipc::IPCResult RecvOnResolvePromise(const uint32_t& aPromiseId) override;
+  ipc::IPCResult RecvOnRejectPromise(const uint32_t& aPromiseId,
+                                     const uint32_t& aError,
+                                     const uint32_t& aSystemCode,
+                                     const nsCString& aErrorMessage) override;
+  ipc::IPCResult RecvOnSessionMessage(const nsCString& aSessionId,
+                                      const uint32_t& aMessageType,
+                                      nsTArray<uint8_t>&& aMessage) override;
+  ipc::IPCResult RecvOnSessionKeysChange(
+    const nsCString& aSessionId,
+    nsTArray<CDMKeyInformation>&& aKeysInfo) override;
+  ipc::IPCResult RecvOnExpirationChange(
+    const nsCString& aSessionId,
+    const double& aSecondsSinceEpoch) override;
+  ipc::IPCResult RecvOnSessionClosed(const nsCString& aSessionId) override;
+  ipc::IPCResult RecvOnLegacySessionError(const nsCString& aSessionId,
+                                          const uint32_t& aError,
+                                          const uint32_t& aSystemCode,
+                                          const nsCString& aMessage) override;
+  ipc::IPCResult RecvDecrypted(const uint32_t& aId,
+                               const uint32_t& aStatus,
+                               nsTArray<uint8_t>&& aData) override;
+  ipc::IPCResult RecvOnDecoderInitDone(const uint32_t& aStatus) override;
+  ipc::IPCResult RecvDecoded(const CDMVideoFrame& aFrame) override;
+  ipc::IPCResult RecvDecodeFailed(const uint32_t& aStatus) override;
+  ipc::IPCResult RecvShutdown() override;
+  ipc::IPCResult RecvResetVideoDecoderComplete() override;
+  ipc::IPCResult RecvDrainComplete() override;
+  void ActorDestroy(ActorDestroyReason aWhy) override;
+
+  void RejectPromise(uint32_t aPromiseId,
+                     nsresult aError,
+                     const nsCString& aErrorMessage);
+
+  void ResolvePromise(uint32_t aPromiseId);
+
+  const uint32_t mPluginId;
+  GMPContentParent* mContentParent;
+  // Note: this pointer is a weak reference because otherwise it would cause
+  // a cycle, as ChromiumCDMProxy has a strong reference to the
+  // ChromiumCDMParent.
+  ChromiumCDMProxy* mProxy = nullptr;
+  nsDataHashtable<nsUint32HashKey, uint32_t> mPromiseToCreateSessionToken;
+  nsTArray<RefPtr<DecryptJob>> mDecrypts;
+
+  MozPromiseHolder<MediaDataDecoder::InitPromise> mInitVideoDecoderPromise;
+  MozPromiseHolder<MediaDataDecoder::DecodePromise> mDecodePromise;
+
+  RefPtr<layers::ImageContainer> mImageContainer;
+  VideoInfo mVideoInfo;
+  uint64_t mLastStreamOffset = 0;
+
+  MozPromiseHolder<MediaDataDecoder::FlushPromise> mFlushDecoderPromise;
+
+  bool mIsShutdown = false;
+  bool mVideoDecoderInitialized = false;
+  bool mActorDestroyed = false;
+};
+
+} // namespace gmp
+} // namespace mozilla
+
+#endif // ChromiumCDMParent_h_
new file mode 100644
--- /dev/null
+++ b/dom/media/gmp/ChromiumCDMProxy.cpp
@@ -0,0 +1,589 @@
+/* -*- 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 "ChromiumCDMProxy.h"
+#include "mozilla/dom/MediaKeySession.h"
+#include "GMPUtils.h"
+#include "nsPrintfCString.h"
+#include "GMPService.h"
+
+namespace mozilla {
+
+ChromiumCDMProxy::ChromiumCDMProxy(dom::MediaKeys* aKeys,
+                                   const nsAString& aKeySystem,
+                                   GMPCrashHelper* aCrashHelper,
+                                   bool aDistinctiveIdentifierRequired,
+                                   bool aPersistentStateRequired,
+                                   nsIEventTarget* aMainThread)
+  : CDMProxy(aKeys,
+             aKeySystem,
+             aDistinctiveIdentifierRequired,
+             aPersistentStateRequired,
+             aMainThread)
+  , mCrashHelper(aCrashHelper)
+  , mCDMMutex("ChromiumCDMProxy")
+  , mGMPThread(GetGMPAbstractThread())
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  MOZ_COUNT_CTOR(ChromiumCDMProxy);
+}
+
+ChromiumCDMProxy::~ChromiumCDMProxy()
+{
+  MOZ_COUNT_DTOR(ChromiumCDMProxy);
+}
+
+void
+ChromiumCDMProxy::Init(PromiseId aPromiseId,
+                       const nsAString& aOrigin,
+                       const nsAString& aTopLevelOrigin,
+                       const nsAString& aGMPName)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  NS_ENSURE_TRUE_VOID(!mKeys.IsNull());
+
+  EME_LOG(
+    "ChromiumCDMProxy::Init (pid=%u, origin=%s, topLevelOrigin=%s, gmp=%s)",
+    aPromiseId,
+    NS_ConvertUTF16toUTF8(aOrigin).get(),
+    NS_ConvertUTF16toUTF8(aTopLevelOrigin).get(),
+    NS_ConvertUTF16toUTF8(aGMPName).get());
+
+  if (!mGMPThread) {
+    RejectPromise(
+      aPromiseId,
+      NS_ERROR_DOM_INVALID_STATE_ERR,
+      NS_LITERAL_CSTRING("Couldn't get GMP thread ChromiumCDMProxy::Init"));
+    return;
+  }
+
+  if (aGMPName.IsEmpty()) {
+    RejectPromise(aPromiseId,
+                  NS_ERROR_DOM_INVALID_STATE_ERR,
+                  nsPrintfCString("Unknown GMP for keysystem '%s'",
+                                  NS_ConvertUTF16toUTF8(mKeySystem).get()));
+    return;
+  }
+
+  gmp::NodeId nodeId(aOrigin, aTopLevelOrigin, aGMPName);
+  RefPtr<AbstractThread> thread = mGMPThread;
+  RefPtr<GMPCrashHelper> helper(mCrashHelper);
+  RefPtr<ChromiumCDMProxy> self(this);
+  nsCString keySystem = NS_ConvertUTF16toUTF8(mKeySystem);
+  RefPtr<Runnable> task(NS_NewRunnableFunction(
+    [self, nodeId, helper, aPromiseId, thread, keySystem]() -> void {
+      MOZ_ASSERT(self->IsOnOwnerThread());
+
+      RefPtr<gmp::GeckoMediaPluginService> service =
+        gmp::GeckoMediaPluginService::GetGeckoMediaPluginService();
+      if (!service) {
+        self->RejectPromise(
+          aPromiseId,
+          NS_ERROR_DOM_INVALID_STATE_ERR,
+          NS_LITERAL_CSTRING(
+            "Couldn't get GeckoMediaPluginService in ChromiumCDMProxy::Init"));
+        return;
+      }
+      RefPtr<gmp::GetCDMParentPromise> promise =
+        service->GetCDM(nodeId, { keySystem }, helper);
+      promise->Then(
+        thread,
+        __func__,
+        [self, aPromiseId](RefPtr<gmp::ChromiumCDMParent> cdm) {
+          if (!cdm->Init(self,
+                         self->mDistinctiveIdentifierRequired,
+                         self->mPersistentStateRequired)) {
+            self->RejectPromise(aPromiseId,
+                                NS_ERROR_FAILURE,
+                                NS_LITERAL_CSTRING("GetCDM failed."));
+            return;
+          }
+          {
+            MutexAutoLock lock(self->mCDMMutex);
+            self->mCDM = cdm;
+          }
+          self->OnCDMCreated(aPromiseId);
+        },
+        [self, aPromiseId](nsresult rv) {
+          self->RejectPromise(
+            aPromiseId, NS_ERROR_FAILURE, NS_LITERAL_CSTRING("GetCDM failed."));
+        });
+    }));
+
+  mGMPThread->Dispatch(task.forget());
+}
+
+void
+ChromiumCDMProxy::OnCDMCreated(uint32_t aPromiseId)
+{
+  EME_LOG("ChromiumCDMProxy::OnCDMCreated(pid=%u) isMainThread=%d this=%p",
+          aPromiseId,
+          NS_IsMainThread(),
+          this);
+
+  if (!NS_IsMainThread()) {
+    mMainThread->Dispatch(NewRunnableMethod<PromiseId>(
+                            this, &ChromiumCDMProxy::OnCDMCreated, aPromiseId),
+                          NS_DISPATCH_NORMAL);
+    return;
+  }
+  MOZ_ASSERT(NS_IsMainThread());
+  if (mKeys.IsNull()) {
+    return;
+  }
+  RefPtr<gmp::ChromiumCDMParent> cdm = GetCDMParent();
+  // This should only be called once the CDM has been created.
+  MOZ_ASSERT(cdm);
+  if (cdm) {
+    mKeys->OnCDMCreated(aPromiseId, cdm->PluginId());
+  } else {
+    // No CDM? Shouldn't be possible, but reject the promise anyway...
+    mKeys->RejectPromise(aPromiseId,
+                         NS_ERROR_DOM_INVALID_STATE_ERR,
+                         NS_LITERAL_CSTRING("Null CDM in OnCDMCreated()"));
+  }
+}
+
+#ifdef DEBUG
+bool
+ChromiumCDMProxy::IsOnOwnerThread()
+{
+  return mGMPThread->IsCurrentThreadIn();
+}
+#endif
+
+static uint32_t
+ToCDMSessionType(dom::MediaKeySessionType aSessionType)
+{
+  switch (aSessionType) {
+    case dom::MediaKeySessionType::Temporary:
+      return static_cast<uint32_t>(cdm::kTemporary);
+    case dom::MediaKeySessionType::Persistent_license:
+      return static_cast<uint32_t>(cdm::kPersistentLicense);
+    default:
+      return static_cast<uint32_t>(cdm::kTemporary);
+  };
+};
+
+static uint32_t
+ToCDMInitDataType(const nsAString& aInitDataType)
+{
+  if (aInitDataType.EqualsLiteral("cenc")) {
+    return static_cast<uint32_t>(cdm::kCenc);
+  }
+  if (aInitDataType.EqualsLiteral("webm")) {
+    return static_cast<uint32_t>(cdm::kWebM);
+  }
+  if (aInitDataType.EqualsLiteral("keyids")) {
+    return static_cast<uint32_t>(cdm::kKeyIds);
+  }
+  return static_cast<uint32_t>(cdm::kCenc);
+}
+
+void
+ChromiumCDMProxy::CreateSession(uint32_t aCreateSessionToken,
+                                dom::MediaKeySessionType aSessionType,
+                                PromiseId aPromiseId,
+                                const nsAString& aInitDataType,
+                                nsTArray<uint8_t>& aInitData)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  EME_LOG("ChromiumCDMProxy::CreateSession(token=%u, type=%d, pid=%u) "
+          "initDataLen=%zu",
+          aCreateSessionToken,
+          (int)aSessionType,
+          aPromiseId,
+          aInitData.Length());
+
+  uint32_t sessionType = ToCDMSessionType(aSessionType);
+  uint32_t initDataType = ToCDMInitDataType(aInitDataType);
+
+  RefPtr<gmp::ChromiumCDMParent> cdm = GetCDMParent();
+  if (!cdm) {
+    RejectPromise(aPromiseId,
+                  NS_ERROR_DOM_INVALID_STATE_ERR,
+                  NS_LITERAL_CSTRING("Null CDM in CreateSession"));
+    return;
+  }
+
+  mGMPThread->Dispatch(
+    NewRunnableMethod<uint32_t,
+                      uint32_t,
+                      uint32_t,
+                      uint32_t,
+                      nsTArray<uint8_t>>(cdm,
+                                         &gmp::ChromiumCDMParent::CreateSession,
+                                         aCreateSessionToken,
+                                         sessionType,
+                                         initDataType,
+                                         aPromiseId,
+                                         Move(aInitData)));
+}
+
+void
+ChromiumCDMProxy::LoadSession(PromiseId aPromiseId,
+                              dom::MediaKeySessionType aSessionType,
+                              const nsAString& aSessionId)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  RefPtr<gmp::ChromiumCDMParent> cdm = GetCDMParent();
+  if (!cdm) {
+    RejectPromise(aPromiseId,
+                  NS_ERROR_DOM_INVALID_STATE_ERR,
+                  NS_LITERAL_CSTRING("Null CDM in LoadSession"));
+    return;
+  }
+
+  mGMPThread->Dispatch(NewRunnableMethod<uint32_t, uint32_t, nsString>(
+    cdm,
+    &gmp::ChromiumCDMParent::LoadSession,
+    aPromiseId,
+    ToCDMSessionType(aSessionType),
+    aSessionId));
+}
+
+void
+ChromiumCDMProxy::SetServerCertificate(PromiseId aPromiseId,
+                                       nsTArray<uint8_t>& aCert)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  EME_LOG("ChromiumCDMProxy::SetServerCertificate(pid=%u) certLen=%zu",
+          aPromiseId,
+          aCert.Length());
+
+  RefPtr<gmp::ChromiumCDMParent> cdm = GetCDMParent();
+  if (!cdm) {
+    RejectPromise(aPromiseId,
+                  NS_ERROR_DOM_INVALID_STATE_ERR,
+                  NS_LITERAL_CSTRING("Null CDM in SetServerCertificate"));
+    return;
+  }
+
+  mGMPThread->Dispatch(NewRunnableMethod<uint32_t, nsTArray<uint8_t>>(
+    cdm,
+    &gmp::ChromiumCDMParent::SetServerCertificate,
+    aPromiseId,
+    Move(aCert)));
+}
+
+void
+ChromiumCDMProxy::UpdateSession(const nsAString& aSessionId,
+                                PromiseId aPromiseId,
+                                nsTArray<uint8_t>& aResponse)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  EME_LOG("ChromiumCDMProxy::UpdateSession(sid='%s', pid=%u) responseLen=%zu",
+          NS_ConvertUTF16toUTF8(aSessionId).get(),
+          aPromiseId,
+          aResponse.Length());
+
+  RefPtr<gmp::ChromiumCDMParent> cdm = GetCDMParent();
+  if (!cdm) {
+    RejectPromise(aPromiseId,
+                  NS_ERROR_DOM_INVALID_STATE_ERR,
+                  NS_LITERAL_CSTRING("Null CDM in UpdateSession"));
+    return;
+  }
+  mGMPThread->Dispatch(
+    NewRunnableMethod<nsCString, uint32_t, nsTArray<uint8_t>>(
+      cdm,
+      &gmp::ChromiumCDMParent::UpdateSession,
+      NS_ConvertUTF16toUTF8(aSessionId),
+      aPromiseId,
+      Move(aResponse)));
+}
+
+void
+ChromiumCDMProxy::CloseSession(const nsAString& aSessionId,
+                               PromiseId aPromiseId)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  EME_LOG("ChromiumCDMProxy::CloseSession(sid='%s', pid=%u)",
+          NS_ConvertUTF16toUTF8(aSessionId).get(),
+          aPromiseId);
+
+  RefPtr<gmp::ChromiumCDMParent> cdm = GetCDMParent();
+  if (!cdm) {
+    RejectPromise(aPromiseId,
+                  NS_ERROR_DOM_INVALID_STATE_ERR,
+                  NS_LITERAL_CSTRING("Null CDM in CloseSession"));
+    return;
+  }
+  mGMPThread->Dispatch(NewRunnableMethod<nsCString, uint32_t>(
+    cdm,
+    &gmp::ChromiumCDMParent::CloseSession,
+    NS_ConvertUTF16toUTF8(aSessionId),
+    aPromiseId));
+}
+
+void
+ChromiumCDMProxy::RemoveSession(const nsAString& aSessionId,
+                                PromiseId aPromiseId)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  EME_LOG("ChromiumCDMProxy::RemoveSession(sid='%s', pid=%u)",
+          NS_ConvertUTF16toUTF8(aSessionId).get(),
+          aPromiseId);
+
+  RefPtr<gmp::ChromiumCDMParent> cdm = GetCDMParent();
+  if (!cdm) {
+    RejectPromise(aPromiseId,
+                  NS_ERROR_DOM_INVALID_STATE_ERR,
+                  NS_LITERAL_CSTRING("Null CDM in RemoveSession"));
+    return;
+  }
+  mGMPThread->Dispatch(NewRunnableMethod<nsCString, uint32_t>(
+    cdm,
+    &gmp::ChromiumCDMParent::RemoveSession,
+    NS_ConvertUTF16toUTF8(aSessionId),
+    aPromiseId));
+}
+
+void
+ChromiumCDMProxy::Shutdown()
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  EME_LOG("ChromiumCDMProxy::Shutdown()");
+  mKeys.Clear();
+  RefPtr<gmp::ChromiumCDMParent> cdm;
+  {
+    MutexAutoLock lock(mCDMMutex);
+    cdm.swap(mCDM);
+  }
+  if (cdm) {
+    nsCOMPtr<nsIRunnable> task =
+      NewRunnableMethod(mCDM, &gmp::ChromiumCDMParent::Shutdown);
+    mGMPThread->Dispatch(task.forget());
+  }
+}
+
+void
+ChromiumCDMProxy::RejectPromise(PromiseId aId,
+                                nsresult aCode,
+                                const nsCString& aReason)
+{
+  if (!NS_IsMainThread()) {
+    nsCOMPtr<nsIRunnable> task;
+    task = NewRunnableMethod<PromiseId, nsresult, nsCString>(
+      this, &ChromiumCDMProxy::RejectPromise, aId, aCode, aReason);
+    NS_DispatchToMainThread(task);
+    return;
+  }
+  EME_LOG("ChromiumCDMProxy::RejectPromise(pid=%u, code=0x%x, reason='%s')",
+          aId,
+          static_cast<uint32_t>(aCode),
+          aReason.get());
+  if (!mKeys.IsNull()) {
+    mKeys->RejectPromise(aId, aCode, aReason);
+  }
+}
+
+void
+ChromiumCDMProxy::ResolvePromise(PromiseId aId)
+{
+  if (!NS_IsMainThread()) {
+    nsCOMPtr<nsIRunnable> task;
+    task = NewRunnableMethod<PromiseId>(
+      this, &ChromiumCDMProxy::ResolvePromise, aId);
+    NS_DispatchToMainThread(task);
+    return;
+  }
+
+  EME_LOG("ChromiumCDMProxy::ResolvePromise(pid=%u)", aId);
+  if (!mKeys.IsNull()) {
+    mKeys->ResolvePromise(aId);
+  } else {
+    NS_WARNING("ChromiumCDMProxy unable to resolve promise!");
+  }
+}
+
+const nsCString&
+ChromiumCDMProxy::GetNodeId() const
+{
+  return mNodeId;
+}
+
+void
+ChromiumCDMProxy::OnSetSessionId(uint32_t aCreateSessionToken,
+                                 const nsAString& aSessionId)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  EME_LOG("ChromiumCDMProxy::OnSetSessionId(token=%u, sid='%s')",
+          aCreateSessionToken,
+          NS_ConvertUTF16toUTF8(aSessionId).get());
+
+  if (mKeys.IsNull()) {
+    return;
+  }
+  RefPtr<dom::MediaKeySession> session(
+    mKeys->GetPendingSession(aCreateSessionToken));
+  if (session) {
+    session->SetSessionId(aSessionId);
+  }
+}
+
+void
+ChromiumCDMProxy::OnResolveLoadSessionPromise(uint32_t aPromiseId,
+                                              bool aSuccess)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  if (mKeys.IsNull()) {
+    return;
+  }
+  mKeys->OnSessionLoaded(aPromiseId, aSuccess);
+}
+
+void
+ChromiumCDMProxy::OnSessionMessage(const nsAString& aSessionId,
+                                   dom::MediaKeyMessageType aMessageType,
+                                   nsTArray<uint8_t>& aMessage)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  if (mKeys.IsNull()) {
+    return;
+  }
+  RefPtr<dom::MediaKeySession> session(mKeys->GetSession(aSessionId));
+  if (session) {
+    session->DispatchKeyMessage(aMessageType, aMessage);
+  }
+}
+
+void
+ChromiumCDMProxy::OnKeyStatusesChange(const nsAString& aSessionId)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  if (mKeys.IsNull()) {
+    return;
+  }
+  RefPtr<dom::MediaKeySession> session(mKeys->GetSession(aSessionId));
+  if (session) {
+    session->DispatchKeyStatusesChange();
+  }
+}
+
+void
+ChromiumCDMProxy::OnExpirationChange(const nsAString& aSessionId,
+                                     GMPTimestamp aExpiryTime)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  if (mKeys.IsNull()) {
+    return;
+  }
+  RefPtr<dom::MediaKeySession> session(mKeys->GetSession(aSessionId));
+  if (session) {
+    // Expiry of 0 is interpreted as "never expire". See bug 1345341.
+    double t = (aExpiryTime == 0) ? std::numeric_limits<double>::quiet_NaN()
+                                  : static_cast<double>(aExpiryTime);
+    session->SetExpiration(t);
+  }
+}
+
+void
+ChromiumCDMProxy::OnSessionClosed(const nsAString& aSessionId)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  bool keyStatusesChange = false;
+  {
+    CDMCaps::AutoLock caps(Capabilites());
+    keyStatusesChange = caps.RemoveKeysForSession(nsString(aSessionId));
+  }
+  if (keyStatusesChange) {
+    OnKeyStatusesChange(aSessionId);
+  }
+  if (mKeys.IsNull()) {
+    return;
+  }
+  RefPtr<dom::MediaKeySession> session(mKeys->GetSession(aSessionId));
+  if (session) {
+    session->OnClosed();
+  }
+}
+
+void
+ChromiumCDMProxy::OnDecrypted(uint32_t aId,
+                              DecryptStatus aResult,
+                              const nsTArray<uint8_t>& aDecryptedData)
+{
+}
+
+void
+ChromiumCDMProxy::OnSessionError(const nsAString& aSessionId,
+                                 nsresult aException,
+                                 uint32_t aSystemCode,
+                                 const nsAString& aMsg)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  if (mKeys.IsNull()) {
+    return;
+  }
+  RefPtr<dom::MediaKeySession> session(mKeys->GetSession(aSessionId));
+  if (session) {
+    session->DispatchKeyError(aSystemCode);
+  }
+  LogToConsole(aMsg);
+}
+
+void
+ChromiumCDMProxy::OnRejectPromise(uint32_t aPromiseId,
+                                  nsresult aDOMException,
+                                  const nsCString& aMsg)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  RejectPromise(aPromiseId, aDOMException, aMsg);
+}
+
+const nsString&
+ChromiumCDMProxy::KeySystem() const
+{
+  return mKeySystem;
+}
+
+CDMCaps&
+ChromiumCDMProxy::Capabilites()
+{
+  return mCapabilites;
+}
+
+RefPtr<DecryptPromise>
+ChromiumCDMProxy::Decrypt(MediaRawData* aSample)
+{
+  RefPtr<gmp::ChromiumCDMParent> cdm = GetCDMParent();
+  if (!cdm) {
+    return DecryptPromise::CreateAndReject(DecryptResult(AbortedErr, aSample),
+                                           __func__);
+  }
+  RefPtr<MediaRawData> sample = aSample;
+  return InvokeAsync(
+    mGMPThread, __func__, [cdm, sample]() { return cdm->Decrypt(sample); });
+}
+
+void
+ChromiumCDMProxy::GetSessionIdsForKeyId(const nsTArray<uint8_t>& aKeyId,
+                                        nsTArray<nsCString>& aSessionIds)
+{
+  CDMCaps::AutoLock caps(Capabilites());
+  caps.GetSessionIdsForKeyId(aKeyId, aSessionIds);
+}
+
+void
+ChromiumCDMProxy::Terminated()
+{
+  if (!mKeys.IsNull()) {
+    mKeys->Terminated();
+  }
+}
+
+already_AddRefed<gmp::ChromiumCDMParent>
+ChromiumCDMProxy::GetCDMParent()
+{
+  MutexAutoLock lock(mCDMMutex);
+  RefPtr<gmp::ChromiumCDMParent> cdm = mCDM;
+  return cdm.forget();
+}
+
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/dom/media/gmp/ChromiumCDMProxy.h
@@ -0,0 +1,132 @@
+/* -*- 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 ChromiumCDMProxy_h_
+#define ChromiumCDMProxy_h_
+
+#include "mozilla/CDMProxy.h"
+#include "mozilla/AbstractThread.h"
+#include "ChromiumCDMParent.h"
+
+namespace mozilla {
+
+class MediaRawData;
+class DecryptJob;
+
+class ChromiumCDMProxy : public CDMProxy
+{
+public:
+  NS_INLINE_DECL_THREADSAFE_REFCOUNTING(ChromiumCDMProxy, override)
+
+  ChromiumCDMProxy(dom::MediaKeys* aKeys,
+                   const nsAString& aKeySystem,
+                   GMPCrashHelper* aCrashHelper,
+                   bool aAllowDistinctiveIdentifier,
+                   bool aAllowPersistentState,
+                   nsIEventTarget* aMainThread);
+
+  void Init(PromiseId aPromiseId,
+            const nsAString& aOrigin,
+            const nsAString& aTopLevelOrigin,
+            const nsAString& aGMPName) override;
+
+  void CreateSession(uint32_t aCreateSessionToken,
+                     dom::MediaKeySessionType aSessionType,
+                     PromiseId aPromiseId,
+                     const nsAString& aInitDataType,
+                     nsTArray<uint8_t>& aInitData) override;
+
+  void LoadSession(PromiseId aPromiseId,
+                   dom::MediaKeySessionType aSessionType,
+                   const nsAString& aSessionId) override;
+
+  void SetServerCertificate(PromiseId aPromiseId,
+                            nsTArray<uint8_t>& aCert) override;
+
+  void UpdateSession(const nsAString& aSessionId,
+                     PromiseId aPromiseId,
+                     nsTArray<uint8_t>& aResponse) override;
+
+  void CloseSession(const nsAString& aSessionId, PromiseId aPromiseId) override;
+
+  void RemoveSession(const nsAString& aSessionId,
+                     PromiseId aPromiseId) override;
+
+  void Shutdown() override;
+
+  void Terminated() override;
+
+  const nsCString& GetNodeId() const override;
+
+  void OnSetSessionId(uint32_t aCreateSessionToken,
+                      const nsAString& aSessionId) override;
+
+  void OnResolveLoadSessionPromise(uint32_t aPromiseId, bool aSuccess) override;
+
+  void OnSessionMessage(const nsAString& aSessionId,
+                        dom::MediaKeyMessageType aMessageType,
+                        nsTArray<uint8_t>& aMessage) override;
+
+  void OnExpirationChange(const nsAString& aSessionId,
+                          GMPTimestamp aExpiryTime) override;
+
+  void OnSessionClosed(const nsAString& aSessionId) override;
+
+  void OnSessionError(const nsAString& aSessionId,
+                      nsresult aException,
+                      uint32_t aSystemCode,
+                      const nsAString& aMsg) override;
+
+  void OnRejectPromise(uint32_t aPromiseId,
+                       nsresult aDOMException,
+                       const nsCString& aMsg) override;
+
+  RefPtr<DecryptPromise> Decrypt(MediaRawData* aSample) override;
+
+  void OnDecrypted(uint32_t aId,
+                   DecryptStatus aResult,
+                   const nsTArray<uint8_t>& aDecryptedData) override;
+
+  void RejectPromise(PromiseId aId,
+                     nsresult aExceptionCode,
+                     const nsCString& aReason) override;
+
+  void ResolvePromise(PromiseId aId) override;
+
+  const nsString& KeySystem() const override;
+
+  CDMCaps& Capabilites() override;
+
+  void OnKeyStatusesChange(const nsAString& aSessionId) override;
+
+  void GetSessionIdsForKeyId(const nsTArray<uint8_t>& aKeyId,
+                             nsTArray<nsCString>& aSessionIds) override;
+
+#ifdef DEBUG
+  bool IsOnOwnerThread() override;
+#endif
+
+  ChromiumCDMProxy* AsChromiumCDMProxy() override { return this; }
+
+  // Threadsafe. Note this may return a reference to a shutdown
+  // CDM, which will fail on all operations.
+  already_AddRefed<gmp::ChromiumCDMParent> GetCDMParent();
+
+private:
+  void OnCDMCreated(uint32_t aPromiseId);
+
+  ~ChromiumCDMProxy();
+
+  GMPCrashHelper* mCrashHelper;
+
+  Mutex mCDMMutex;
+  RefPtr<gmp::ChromiumCDMParent> mCDM;
+  RefPtr<AbstractThread> mGMPThread;
+};
+
+} // namespace mozilla
+
+#endif // GMPCDMProxy_h_
--- a/dom/media/gmp/GMPCDMProxy.cpp
+++ b/dom/media/gmp/GMPCDMProxy.cpp
@@ -323,16 +323,17 @@ GMPCDMProxy::gmp_CreateSession(UniquePtr
                       aData->mPromiseId,
                       aData->mInitDataType,
                       aData->mInitData,
                       ToGMPSessionType(aData->mSessionType));
 }
 
 void
 GMPCDMProxy::LoadSession(PromiseId aPromiseId,
+                         dom::MediaKeySessionType aSessionType,
                          const nsAString& aSessionId)
 {
   MOZ_ASSERT(NS_IsMainThread());
   MOZ_ASSERT(mOwnerThread);
 
   UniquePtr<SessionOpData> data(new SessionOpData());
   data->mPromiseId = aPromiseId;
   data->mSessionId = NS_ConvertUTF16toUTF8(aSessionId);
--- a/dom/media/gmp/GMPCDMProxy.h
+++ b/dom/media/gmp/GMPCDMProxy.h
@@ -38,16 +38,17 @@ public:
 
   void CreateSession(uint32_t aCreateSessionToken,
                      dom::MediaKeySessionType aSessionType,
                      PromiseId aPromiseId,
                      const nsAString& aInitDataType,
                      nsTArray<uint8_t>& aInitData) override;
 
   void LoadSession(PromiseId aPromiseId,
+                   dom::MediaKeySessionType aSessionType,
                    const nsAString& aSessionId) override;
 
   void SetServerCertificate(PromiseId aPromiseId,
                             nsTArray<uint8_t>& aCert) override;
 
   void UpdateSession(const nsAString& aSessionId,
                      PromiseId aPromiseId,
                      nsTArray<uint8_t>& aResponse) override;
--- a/dom/media/gmp/GMPChild.cpp
+++ b/dom/media/gmp/GMPChild.cpp
@@ -18,16 +18,17 @@
 #include "gmp-video-encode.h"
 #include "GMPPlatform.h"
 #include "mozilla/ipc/CrashReporterClient.h"
 #include "mozilla/ipc/ProcessChild.h"
 #include "GMPUtils.h"
 #include "prio.h"
 #include "base/task.h"
 #include "widevine-adapter/WidevineAdapter.h"
+#include "ChromiumCDMAdapter.h"
 
 using namespace mozilla::ipc;
 
 #ifdef XP_WIN
 #include <stdlib.h> // for _exit()
 #else
 #include <unistd.h> // for _exit()
 #endif
@@ -341,29 +342,36 @@ GMPChild::AnswerStartPlugin(const nsStri
   if (!mGMPLoader->CanSandbox()) {
     LOGD("%s Can't sandbox GMP, failing", __FUNCTION__);
     delete platformAPI;
     return IPC_FAIL_NO_REASON(this);
   }
 #endif
 
   bool isWidevine = aAdapter.EqualsLiteral("widevine");
+  bool isChromium = aAdapter.EqualsLiteral("chromium");
 #if defined(MOZ_GMP_SANDBOX) && defined(XP_MACOSX)
   MacSandboxPluginType pluginType = MacSandboxPluginType_GMPlugin_Default;
-  if (isWidevine) {
+  if (isWidevine || isChromium) {
     pluginType = MacSandboxPluginType_GMPlugin_EME_Widevine;
   }
   if (!SetMacSandboxInfo(pluginType)) {
     NS_WARNING("Failed to set Mac GMP sandbox info");
     delete platformAPI;
     return IPC_FAIL_NO_REASON(this);
   }
 #endif
 
-  GMPAdapter* adapter = (isWidevine) ? new WidevineAdapter() : nullptr;
+  GMPAdapter* adapter = nullptr;
+  if (isWidevine) {
+    adapter = new WidevineAdapter();
+  } else if (isChromium) {
+    adapter = new ChromiumCDMAdapter();
+  }
+
   if (!mGMPLoader->Load(libPath.get(),
                         libPath.Length(),
                         platformAPI,
                         adapter)) {
     NS_WARNING("Failed to load GMP");
     delete platformAPI;
     return IPC_FAIL_NO_REASON(this);
   }
--- a/dom/media/gmp/GMPContentChild.cpp
+++ b/dom/media/gmp/GMPContentChild.cpp
@@ -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/. */
 
 #include "GMPContentChild.h"
 #include "GMPChild.h"
 #include "GMPDecryptorChild.h"
 #include "GMPVideoDecoderChild.h"
 #include "GMPVideoEncoderChild.h"
+#include "ChromiumCDMChild.h"
 #include "base/task.h"
 
 namespace mozilla {
 namespace gmp {
 
 GMPContentChild::GMPContentChild(GMPChild* aChild)
   : mGMPChild(aChild)
 {
@@ -88,16 +89,31 @@ GMPContentChild::AllocPGMPVideoEncoderCh
 
 bool
 GMPContentChild::DeallocPGMPVideoEncoderChild(PGMPVideoEncoderChild* aActor)
 {
   static_cast<GMPVideoEncoderChild*>(aActor)->Release();
   return true;
 }
 
+PChromiumCDMChild*
+GMPContentChild::AllocPChromiumCDMChild()
+{
+  ChromiumCDMChild* actor = new ChromiumCDMChild(this);
+  actor->AddRef();
+  return actor;
+}
+
+bool
+GMPContentChild::DeallocPChromiumCDMChild(PChromiumCDMChild* aActor)
+{
+  static_cast<ChromiumCDMChild*>(aActor)->Release();
+  return true;
+}
+
 mozilla::ipc::IPCResult
 GMPContentChild::RecvPGMPDecryptorConstructor(PGMPDecryptorChild* aActor)
 {
   GMPDecryptorChild* child = static_cast<GMPDecryptorChild*>(aActor);
 
   void* ptr = nullptr;
   GMPErr err = mGMPChild->GetAPI(GMP_API_DECRYPTOR, nullptr, &ptr, child->DecryptorId());
   if (err != GMPNoErr || !ptr) {
@@ -139,16 +155,34 @@ GMPContentChild::RecvPGMPVideoEncoderCon
     return IPC_FAIL_NO_REASON(this);
   }
 
   vec->Init(static_cast<GMPVideoEncoder*>(ve));
 
   return IPC_OK();
 }
 
+mozilla::ipc::IPCResult
+GMPContentChild::RecvPChromiumCDMConstructor(PChromiumCDMChild* aActor)
+{
+  ChromiumCDMChild* child = static_cast<ChromiumCDMChild*>(aActor);
+  cdm::Host_8* host = child;
+
+  void* cdm = nullptr;
+  GMPErr err = mGMPChild->GetAPI(CHROMIUM_CDM_API, host, &cdm);
+  if (err != GMPNoErr || !cdm) {
+    NS_WARNING("GMPGetAPI call failed trying to get CDM.");
+    return IPC_FAIL_NO_REASON(this);
+  }
+
+  child->Init(static_cast<cdm::ContentDecryptionModule_8*>(cdm));
+
+  return IPC_OK();
+}
+
 void
 GMPContentChild::CloseActive()
 {
   // Invalidate and remove any remaining API objects.
   const ManagedContainer<PGMPDecryptorChild>& decryptors =
     ManagedPGMPDecryptorChild();
   for (auto iter = decryptors.ConstIter(); !iter.Done(); iter.Next()) {
     iter.Get()->GetKey()->SendShutdown();
@@ -160,20 +194,26 @@ GMPContentChild::CloseActive()
     iter.Get()->GetKey()->SendShutdown();
   }
 
   const ManagedContainer<PGMPVideoEncoderChild>& videoEncoders =
     ManagedPGMPVideoEncoderChild();
   for (auto iter = videoEncoders.ConstIter(); !iter.Done(); iter.Next()) {
     iter.Get()->GetKey()->SendShutdown();
   }
+
+  const ManagedContainer<PChromiumCDMChild>& cdms = ManagedPChromiumCDMChild();
+  for (auto iter = cdms.ConstIter(); !iter.Done(); iter.Next()) {
+    iter.Get()->GetKey()->SendShutdown();
+  }
 }
 
 bool
 GMPContentChild::IsUsed()
 {
   return !ManagedPGMPDecryptorChild().IsEmpty() ||
          !ManagedPGMPVideoDecoderChild().IsEmpty() ||
-         !ManagedPGMPVideoEncoderChild().IsEmpty();
+         !ManagedPGMPVideoEncoderChild().IsEmpty() ||
+         !ManagedPChromiumCDMChild().IsEmpty();
 }
 
 } // namespace gmp
 } // namespace mozilla
--- a/dom/media/gmp/GMPContentChild.h
+++ b/dom/media/gmp/GMPContentChild.h
@@ -21,26 +21,31 @@ public:
   explicit GMPContentChild(GMPChild* aChild);
   virtual ~GMPContentChild();
 
   MessageLoop* GMPMessageLoop();
 
   mozilla::ipc::IPCResult RecvPGMPDecryptorConstructor(PGMPDecryptorChild* aActor) override;
   mozilla::ipc::IPCResult RecvPGMPVideoDecoderConstructor(PGMPVideoDecoderChild* aActor, const uint32_t& aDecryptorId) override;
   mozilla::ipc::IPCResult RecvPGMPVideoEncoderConstructor(PGMPVideoEncoderChild* aActor) override;
+  mozilla::ipc::IPCResult RecvPChromiumCDMConstructor(
+    PChromiumCDMChild* aActor) override;
 
   PGMPDecryptorChild* AllocPGMPDecryptorChild() override;
   bool DeallocPGMPDecryptorChild(PGMPDecryptorChild* aActor) override;
 
   PGMPVideoDecoderChild* AllocPGMPVideoDecoderChild(const uint32_t& aDecryptorId) override;
   bool DeallocPGMPVideoDecoderChild(PGMPVideoDecoderChild* aActor) override;
 
   PGMPVideoEncoderChild* AllocPGMPVideoEncoderChild() override;
   bool DeallocPGMPVideoEncoderChild(PGMPVideoEncoderChild* aActor) override;
 
+  PChromiumCDMChild* AllocPChromiumCDMChild() override;
+  bool DeallocPChromiumCDMChild(PChromiumCDMChild* aActor) override;
+
   void ActorDestroy(ActorDestroyReason aWhy) override;
   void ProcessingError(Result aCode, const char* aReason) override;
 
   // GMPSharedMem
   void CheckThread() override;
 
   void CloseActive();
   bool IsUsed();
--- a/dom/media/gmp/GMPContentParent.cpp
+++ b/dom/media/gmp/GMPContentParent.cpp
@@ -4,16 +4,17 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "GMPContentParent.h"
 #include "GMPDecryptorParent.h"
 #include "GMPParent.h"
 #include "GMPServiceChild.h"
 #include "GMPVideoDecoderParent.h"
 #include "GMPVideoEncoderParent.h"
+#include "ChromiumCDMParent.h"
 #include "mozIGeckoMediaPluginService.h"
 #include "mozilla/Logging.h"
 #include "mozilla/Unused.h"
 #include "base/task.h"
 
 namespace mozilla {
 
 #ifdef LOG
@@ -60,29 +61,37 @@ public:
 
 private:
   RefPtr<GMPContentParent> mToRelease;
 };
 
 void
 GMPContentParent::ActorDestroy(ActorDestroyReason aWhy)
 {
-  MOZ_ASSERT(mDecryptors.IsEmpty() &&
-             mVideoDecoders.IsEmpty() &&
-             mVideoEncoders.IsEmpty());
+  MOZ_ASSERT(mDecryptors.IsEmpty() && mVideoDecoders.IsEmpty() &&
+             mVideoEncoders.IsEmpty() && mChromiumCDMs.IsEmpty());
   NS_DispatchToCurrentThread(new ReleaseGMPContentParent(this));
 }
 
 void
 GMPContentParent::CheckThread()
 {
   MOZ_ASSERT(mGMPThread == NS_GetCurrentThread());
 }
 
 void
+GMPContentParent::ChromiumCDMDestroyed(ChromiumCDMParent* aDecoder)
+{
+  MOZ_ASSERT(GMPThread() == NS_GetCurrentThread());
+
+  MOZ_ALWAYS_TRUE(mChromiumCDMs.RemoveElement(aDecoder));
+  CloseIfUnused();
+}
+
+void
 GMPContentParent::VideoDecoderDestroyed(GMPVideoDecoderParent* aDecoder)
 {
   MOZ_ASSERT(GMPThread() == NS_GetCurrentThread());
 
   // If the constructor fails, we'll get called before it's added
   Unused << NS_WARN_IF(!mVideoDecoders.RemoveElement(aDecoder));
   CloseIfUnused();
 }
@@ -119,19 +128,18 @@ GMPContentParent::RemoveCloseBlocker()
   MOZ_ASSERT(GMPThread() == NS_GetCurrentThread());
   --mCloseBlockerCount;
   CloseIfUnused();
 }
 
 void
 GMPContentParent::CloseIfUnused()
 {
-  if (mDecryptors.IsEmpty() &&
-      mVideoDecoders.IsEmpty() &&
-      mVideoEncoders.IsEmpty() &&
+  if (mDecryptors.IsEmpty() && mVideoDecoders.IsEmpty() &&
+      mVideoEncoders.IsEmpty() && mChromiumCDMs.IsEmpty() &&
       mCloseBlockerCount == 0) {
     RefPtr<GMPContentParent> toClose;
     if (mParent) {
       toClose = mParent->ForgetGMPContentParent();
     } else {
       toClose = this;
       RefPtr<GeckoMediaPluginServiceChild> gmp(
         GeckoMediaPluginServiceChild::GetSingleton());
@@ -154,17 +162,17 @@ GMPContentParent::GetGMPDecryptor(GMPDec
   // It's dropped by calling Close() on the interface.
   NS_ADDREF(dp);
   mDecryptors.AppendElement(dp);
   *aGMPDP = dp;
 
   return NS_OK;
 }
 
-nsIThread*
+nsCOMPtr<nsIThread>
 GMPContentParent::GMPThread()
 {
   if (!mGMPThread) {
     nsCOMPtr<mozIGeckoMediaPluginService> mps = do_GetService("@mozilla.org/gecko-media-plugin-service;1");
     MOZ_ASSERT(mps);
     if (!mps) {
       return nullptr;
     }
@@ -175,16 +183,31 @@ GMPContentParent::GMPThread()
     // to use swap() under a lock.
     mps->GetThread(getter_AddRefs(mGMPThread));
     MOZ_ASSERT(mGMPThread);
   }
 
   return mGMPThread;
 }
 
+already_AddRefed<ChromiumCDMParent>
+GMPContentParent::GetChromiumCDM()
+{
+  PChromiumCDMParent* actor = SendPChromiumCDMConstructor();
+  if (!actor) {
+    return nullptr;
+  }
+  RefPtr<ChromiumCDMParent> parent = static_cast<ChromiumCDMParent*>(actor);
+
+  // TODO: Remove parent from mChromiumCDMs in ChromiumCDMParent::Destroy().
+  mChromiumCDMs.AppendElement(parent);
+
+  return parent.forget();
+}
+
 nsresult
 GMPContentParent::GetGMPVideoDecoder(GMPVideoDecoderParent** aGMPVD,
                                      uint32_t aDecryptorId)
 {
   // returned with one anonymous AddRef that locks it until Destroy
   PGMPVideoDecoderParent* pvdp = SendPGMPVideoDecoderConstructor(aDecryptorId);
   if (!pvdp) {
     return NS_ERROR_FAILURE;
@@ -212,25 +235,41 @@ GMPContentParent::GetGMPVideoEncoder(GMP
   // It's dropped by calling Close() on the interface.
   NS_ADDREF(vep);
   *aGMPVE = vep;
   mVideoEncoders.AppendElement(vep);
 
   return NS_OK;
 }
 
+PChromiumCDMParent*
+GMPContentParent::AllocPChromiumCDMParent()
+{
+  ChromiumCDMParent* parent = new ChromiumCDMParent(this, GetPluginId());
+  NS_ADDREF(parent);
+  return parent;
+}
+
 PGMPVideoDecoderParent*
 GMPContentParent::AllocPGMPVideoDecoderParent(const uint32_t& aDecryptorId)
 {
   GMPVideoDecoderParent* vdp = new GMPVideoDecoderParent(this);
   NS_ADDREF(vdp);
   return vdp;
 }
 
 bool
+GMPContentParent::DeallocPChromiumCDMParent(PChromiumCDMParent* aActor)
+{
+  ChromiumCDMParent* parent = static_cast<ChromiumCDMParent*>(aActor);
+  NS_RELEASE(parent);
+  return true;
+}
+
+bool
 GMPContentParent::DeallocPGMPVideoDecoderParent(PGMPVideoDecoderParent* aActor)
 {
   GMPVideoDecoderParent* vdp = static_cast<GMPVideoDecoderParent*>(aActor);
   NS_RELEASE(vdp);
   return true;
 }
 
 PGMPVideoEncoderParent*
--- a/dom/media/gmp/GMPContentParent.h
+++ b/dom/media/gmp/GMPContentParent.h
@@ -12,16 +12,17 @@
 
 namespace mozilla {
 namespace gmp {
 
 class GMPDecryptorParent;
 class GMPParent;
 class GMPVideoDecoderParent;
 class GMPVideoEncoderParent;
+class ChromiumCDMParent;
 
 class GMPContentParent final : public PGMPContentParent,
                                public GMPSharedMem
 {
 public:
   NS_INLINE_DECL_THREADSAFE_REFCOUNTING(GMPContentParent)
 
   explicit GMPContentParent(GMPParent* aParent = nullptr);
@@ -31,17 +32,20 @@ public:
   void VideoDecoderDestroyed(GMPVideoDecoderParent* aDecoder);
 
   nsresult GetGMPVideoEncoder(GMPVideoEncoderParent** aGMPVE);
   void VideoEncoderDestroyed(GMPVideoEncoderParent* aEncoder);
 
   nsresult GetGMPDecryptor(GMPDecryptorParent** aGMPKS);
   void DecryptorDestroyed(GMPDecryptorParent* aSession);
 
-  nsIThread* GMPThread();
+  already_AddRefed<ChromiumCDMParent> GetChromiumCDM();
+  void ChromiumCDMDestroyed(ChromiumCDMParent* aCDM);
+
+  nsCOMPtr<nsIThread> GMPThread();
 
   // GMPSharedMem
   void CheckThread() override;
 
   void SetDisplayName(const nsCString& aDisplayName)
   {
     mDisplayName = aDisplayName;
   }
@@ -87,27 +91,31 @@ private:
   bool DeallocPGMPVideoDecoderParent(PGMPVideoDecoderParent* aActor) override;
 
   PGMPVideoEncoderParent* AllocPGMPVideoEncoderParent() override;
   bool DeallocPGMPVideoEncoderParent(PGMPVideoEncoderParent* aActor) override;
 
   PGMPDecryptorParent* AllocPGMPDecryptorParent() override;
   bool DeallocPGMPDecryptorParent(PGMPDecryptorParent* aActor) override;
 
+  PChromiumCDMParent* AllocPChromiumCDMParent() override;
+  bool DeallocPChromiumCDMParent(PChromiumCDMParent* aActor) override;
+
   void CloseIfUnused();
   // Needed because NewRunnableMethod tried to use the class that the method
   // lives on to store the receiver, but PGMPContentParent isn't refcounted.
   void Close()
   {
     PGMPContentParent::Close();
   }
 
   nsTArray<RefPtr<GMPVideoDecoderParent>> mVideoDecoders;
   nsTArray<RefPtr<GMPVideoEncoderParent>> mVideoEncoders;
   nsTArray<RefPtr<GMPDecryptorParent>> mDecryptors;
+  nsTArray<RefPtr<ChromiumCDMParent>> mChromiumCDMs;
   nsCOMPtr<nsIThread> mGMPThread;
   RefPtr<GMPParent> mParent;
   nsCString mDisplayName;
   uint32_t mPluginId;
   uint32_t mCloseBlockerCount = 0;
 };
 
 } // namespace gmp
--- a/dom/media/gmp/GMPDecryptorParent.h
+++ b/dom/media/gmp/GMPDecryptorParent.h
@@ -114,16 +114,16 @@ private:
 
   bool mIsOpen;
   bool mShuttingDown;
   bool mActorDestroyed;
   RefPtr<GMPContentParent> mPlugin;
   uint32_t mPluginId;
   GMPDecryptorProxyCallback* mCallback;
 #ifdef DEBUG
-  nsIThread* const mGMPThread;
+  nsCOMPtr<nsIThread> const mGMPThread;
 #endif
 };
 
 } // namespace gmp
 } // namespace mozilla
 
 #endif // GMPDecryptorChild_h_
new file mode 100644
--- /dev/null
+++ b/dom/media/gmp/GMPLog.h
@@ -0,0 +1,19 @@
+/* -*- 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 GMP_LOG_h_
+#define GMP_LOG_h_
+
+#include "mozilla/Logging.h"
+
+namespace mozilla {
+
+extern LogModule* GetGMPLog();
+
+#define GMP_LOG(msg, ...) MOZ_LOG(GetGMPLog(), LogLevel::Debug, (msg, ##__VA_ARGS__))
+
+} // namespace mozilla
+
+#endif // GMP_LOG_h_
--- a/dom/media/gmp/GMPMessageUtils.h
+++ b/dom/media/gmp/GMPMessageUtils.h
@@ -4,16 +4,17 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef GMPMessageUtils_h_
 #define GMPMessageUtils_h_
 
 #include "gmp-video-codec.h"
 #include "gmp-video-frame-encoded.h"
 #include "gmp-decryption.h"
+#include "IPCMessageUtils.h"
 
 namespace IPC {
 
 template <>
 struct ParamTraits<GMPErr>
 : public ContiguousEnumSerializer<GMPErr,
                                   GMPNoErr,
                                   GMPLastErr>
--- a/dom/media/gmp/GMPParent.cpp
+++ b/dom/media/gmp/GMPParent.cpp
@@ -38,16 +38,17 @@ using CrashReporter::GetIDFromMinidump;
 #include "mozilla/Telemetry.h"
 
 #ifdef XP_WIN
 #include "WMFDecoderModule.h"
 #endif
 
 #include "mozilla/dom/WidevineCDMManifestBinding.h"
 #include "widevine-adapter/WidevineAdapter.h"
+#include "ChromiumCDMAdapter.h"
 
 namespace mozilla {
 
 #undef LOG
 #undef LOGD
 
 extern LogModule* GetGMPLog();
 #define LOG(level, x, ...) MOZ_LOG(GetGMPLog(), (level), (x, ##__VA_ARGS__))
@@ -318,17 +319,17 @@ public:
   }
   nsString mNodeId;
 };
 
 void
 GMPParent::ChildTerminated()
 {
   RefPtr<GMPParent> self(this);
-  nsIThread* gmpThread = GMPThread();
+  nsCOMPtr<nsIThread> gmpThread = GMPThread();
 
   if (!gmpThread) {
     // Bug 1163239 - this can happen on shutdown.
     // PluginTerminated removes the GMP from the GMPService.
     // On shutdown we can have this case where it is already been
     // removed so there is no harm in not trying to remove it again.
     LOGD("%s::%s: GMPThread() returned nullptr.", __CLASS__, __FUNCTION__);
   } else {
@@ -367,36 +368,30 @@ GMPParent::DeleteProcess()
 }
 
 GMPState
 GMPParent::State() const
 {
   return mState;
 }
 
-// Not changing to use mService since we'll be removing it
-nsIThread*
+nsCOMPtr<nsIThread>
 GMPParent::GMPThread()
 {
-  if (!mGMPThread) {
-    nsCOMPtr<mozIGeckoMediaPluginService> mps = do_GetService("@mozilla.org/gecko-media-plugin-service;1");
-    MOZ_ASSERT(mps);
-    if (!mps) {
-      return nullptr;
-    }
-    // Not really safe if we just grab to the mGMPThread, as we don't know
-    // what thread we're running on and other threads may be trying to
-    // access this without locks!  However, debug only, and primary failure
-    // mode outside of compiler-helped TSAN is a leak.  But better would be
-    // to use swap() under a lock.
-    mps->GetThread(getter_AddRefs(mGMPThread));
-    MOZ_ASSERT(mGMPThread);
+  nsCOMPtr<mozIGeckoMediaPluginService> mps =
+    do_GetService("@mozilla.org/gecko-media-plugin-service;1");
+  MOZ_ASSERT(mps);
+  if (!mps) {
+    return nullptr;
   }
-
-  return mGMPThread;
+  // Note: GeckoMediaPluginService::GetThread() is threadsafe, and returns
+  // nullptr if the GeckoMediaPluginService has started shutdown.
+  nsCOMPtr<nsIThread> gmpThread;
+  mps->GetThread(getter_AddRefs(gmpThread));
+  return gmpThread;
 }
 
 /* static */
 bool
 GMPCapability::Supports(const nsTArray<GMPCapability>& aCapabilities,
                         const nsCString& aAPI,
                         const nsTArray<nsCString>& aTags)
 {
@@ -584,17 +579,18 @@ mozilla::ipc::IPCResult
 GMPParent::RecvPGMPTimerConstructor(PGMPTimerParent* actor)
 {
   return IPC_OK();
 }
 
 PGMPTimerParent*
 GMPParent::AllocPGMPTimerParent()
 {
-  GMPTimerParent* p = new GMPTimerParent(GMPThread());
+  nsCOMPtr<nsIThread> thread = GMPThread();
+  GMPTimerParent* p = new GMPTimerParent(thread);
   mTimers.AppendElement(p); // Released in DeallocPGMPTimerParent, or on shutdown.
   return p;
 }
 
 bool
 GMPParent::DeallocPGMPTimerParent(PGMPTimerParent* aActor)
 {
   GMPTimerParent* p = static_cast<GMPTimerParent*>(aActor);
@@ -729,31 +725,44 @@ GMPParent::ReadChromiumManifestFile(nsIF
   }
 
   // DOM JSON parsing needs to run on the main thread.
   return InvokeAsync<nsString&&>(
     mMainThread, this, __func__,
     &GMPParent::ParseChromiumManifest, NS_ConvertUTF8toUTF16(json));
 }
 
+static bool
+IsCDMAPISupported(const mozilla::dom::WidevineCDMManifest& aManifest)
+{
+  nsresult ignored; // Note: ToInteger returns 0 on failure.
+  int32_t moduleVersion = aManifest.mX_cdm_module_versions.ToInteger(&ignored);
+  int32_t interfaceVersion =
+    aManifest.mX_cdm_interface_versions.ToInteger(&ignored);
+  int32_t hostVersion = aManifest.mX_cdm_host_versions.ToInteger(&ignored);
+  if (MediaPrefs::EMEChromiumAPIEnabled()) {
+    return ChromiumCDMAdapter::Supports(
+      moduleVersion, interfaceVersion, hostVersion);
+  }
+  return WidevineAdapter::Supports(
+    moduleVersion, interfaceVersion, hostVersion);
+}
+
 RefPtr<GenericPromise>
 GMPParent::ParseChromiumManifest(const nsAString& aJSON)
 {
   LOGD("%s: for '%s'", __FUNCTION__, NS_LossyConvertUTF16toASCII(aJSON).get());
 
   MOZ_ASSERT(NS_IsMainThread());
   mozilla::dom::WidevineCDMManifest m;
   if (!m.Init(aJSON)) {
     return GenericPromise::CreateAndReject(NS_ERROR_FAILURE, __func__);
   }
 
-  nsresult ignored; // Note: ToInteger returns 0 on failure.
-  if (!WidevineAdapter::Supports(m.mX_cdm_module_versions.ToInteger(&ignored),
-                                 m.mX_cdm_interface_versions.ToInteger(&ignored),
-                                 m.mX_cdm_host_versions.ToInteger(&ignored))) {
+  if (!IsCDMAPISupported(m)) {
     return GenericPromise::CreateAndReject(NS_ERROR_FAILURE, __func__);
   }
 
   mDisplayName = NS_ConvertUTF16toUTF8(m.mName);
   mDescription = NS_ConvertUTF16toUTF8(m.mDescription);
   mVersion = NS_ConvertUTF16toUTF8(m.mVersion);
 
 #if defined(XP_LINUX) && defined(MOZ_GMP_SANDBOX)
@@ -781,17 +790,17 @@ GMPParent::ParseChromiumManifest(const n
     kEMEKeySystem = kEMEKeySystemWidevine;
 #if XP_WIN
     mLibs = NS_LITERAL_CSTRING("dxva2.dll");
 #endif
   } else {
     return GenericPromise::CreateAndReject(NS_ERROR_FAILURE, __func__);
   }
 
-  GMPCapability video(NS_LITERAL_CSTRING(GMP_API_VIDEO_DECODER));
+  GMPCapability video;
 
   nsCString codecsString = NS_ConvertUTF16toUTF8(m.mX_cdm_codecs);
   nsTArray<nsCString> codecs;
   SplitAt(",", codecsString, codecs);
 
   for (const nsCString& chromiumCodec : codecs) {
     nsCString codec;
     if (chromiumCodec.EqualsASCII("vp8")) {
@@ -803,24 +812,29 @@ GMPParent::ParseChromiumManifest(const n
     } else {
       return GenericPromise::CreateAndReject(NS_ERROR_FAILURE, __func__);
     }
 
     video.mAPITags.AppendElement(codec);
   }
 
   video.mAPITags.AppendElement(kEMEKeySystem);
-  mCapabilities.AppendElement(Move(video));
-
-  GMPCapability decrypt(NS_LITERAL_CSTRING(GMP_API_DECRYPTOR));
 
-  decrypt.mAPITags.AppendElement(kEMEKeySystem);
-  mCapabilities.AppendElement(Move(decrypt));
+  if (MediaPrefs::EMEChromiumAPIEnabled()) {
+    video.mAPIName = NS_LITERAL_CSTRING(CHROMIUM_CDM_API);
+    mAdapter = NS_LITERAL_STRING("chromium");
+  } else {
+    video.mAPIName = NS_LITERAL_CSTRING(GMP_API_VIDEO_DECODER);
+    mAdapter = NS_LITERAL_STRING("widevine");
 
-  mAdapter = NS_LITERAL_STRING("widevine");
+    GMPCapability decrypt(NS_LITERAL_CSTRING(GMP_API_DECRYPTOR));
+    decrypt.mAPITags.AppendElement(kEMEKeySystem);
+    mCapabilities.AppendElement(Move(decrypt));
+  }
+  mCapabilities.AppendElement(Move(video));
 
   return GenericPromise::CreateAndResolve(true, __func__);
 }
 
 bool
 GMPParent::CanBeSharedCrossNodeIds() const
 {
   return mNodeId.IsEmpty() &&
--- a/dom/media/gmp/GMPParent.h
+++ b/dom/media/gmp/GMPParent.h
@@ -95,17 +95,17 @@ public:
 
   // Called by the GMPService to forcibly close active de/encoders at shutdown
   void Shutdown();
 
   // This must not be called while we're in the middle of abnormal ActorDestroy
   void DeleteProcess();
 
   GMPState State() const;
-  nsIThread* GMPThread();
+  nsCOMPtr<nsIThread> GMPThread();
 
   // A GMP can either be a single instance shared across all NodeIds (like
   // in the OpenH264 case), or we can require a new plugin instance for every
   // NodeIds running the plugin (as in the EME plugin case).
   //
   // A NodeId is a hash of the ($urlBarOrigin, $ownerDocOrigin) pair.
   //
   // Plugins are associated with an NodeIds by calling SetNodeId() before
@@ -202,17 +202,16 @@ private:
   bool mDeleteProcessOnlyOnUnload;
   bool mAbnormalShutdownInProgress;
   bool mIsBlockingDeletion;
 
   bool mCanDecrypt;
 
   nsTArray<RefPtr<GMPTimerParent>> mTimers;
   nsTArray<RefPtr<GMPStorageParent>> mStorage;
-  nsCOMPtr<nsIThread> mGMPThread;
   // NodeId the plugin is assigned to, or empty if the the plugin is not
   // assigned to a NodeId.
   nsCString mNodeId;
   // This is used for GMP content in the parent, there may be more of these in
   // the content processes.
   RefPtr<GMPContentParent> mGMPContentParent;
   nsTArray<UniquePtr<MozPromiseHolder<GetGMPContentParentPromise>>> mGetContentParentPromises;
   uint32_t mGMPContentChildCount;
--- a/dom/media/gmp/GMPService.cpp
+++ b/dom/media/gmp/GMPService.cpp
@@ -224,16 +224,57 @@ GeckoMediaPluginService::Init()
   MOZ_ASSERT(obsService);
   MOZ_ALWAYS_SUCCEEDS(obsService->AddObserver(this, NS_XPCOM_SHUTDOWN_THREADS_OBSERVER_ID, false));
 
   // Kick off scanning for plugins
   nsCOMPtr<nsIThread> thread;
   return GetThread(getter_AddRefs(thread));
 }
 
+RefPtr<GetCDMParentPromise>
+GeckoMediaPluginService::GetCDM(const NodeId& aNodeId,
+                                nsTArray<nsCString> aTags,
+                                GMPCrashHelper* aHelper)
+{
+  MOZ_ASSERT(NS_GetCurrentThread() == mGMPThread);
+
+  if (mShuttingDownOnGMPThread || aTags.IsEmpty()) {
+    return GetCDMParentPromise::CreateAndReject(NS_ERROR_FAILURE, __func__);
+  }
+
+  typedef MozPromiseHolder<GetCDMParentPromise> PromiseHolder;
+  PromiseHolder* rawHolder(new PromiseHolder());
+  RefPtr<GetCDMParentPromise> promise = rawHolder->Ensure(__func__);
+  RefPtr<AbstractThread> thread(GetAbstractGMPThread());
+  RefPtr<GMPCrashHelper> helper(aHelper);
+  GetContentParent(
+    aHelper, aNodeId, NS_LITERAL_CSTRING(CHROMIUM_CDM_API), aTags)
+    ->Then(thread,
+           __func__,
+           [rawHolder, helper](RefPtr<GMPContentParent::CloseBlocker> wrapper) {
+             RefPtr<GMPContentParent> parent = wrapper->mParent;
+             UniquePtr<PromiseHolder> holder(rawHolder);
+             RefPtr<ChromiumCDMParent> cdm = parent->GetChromiumCDM();
+             if (!parent) {
+               holder->Reject(NS_ERROR_FAILURE, __func__);
+               return;
+             }
+             if (helper) {
+               cdm->SetCrashHelper(helper);
+             }
+             holder->Resolve(cdm, __func__);
+           },
+           [rawHolder] {
+             UniquePtr<PromiseHolder> holder(rawHolder);
+             holder->Reject(NS_ERROR_FAILURE, __func__);
+           });
+
+  return promise;
+}
+
 void
 GeckoMediaPluginService::ShutdownGMPThread()
 {
   LOGD(("%s::%s", __CLASS__, __FUNCTION__));
   nsCOMPtr<nsIThread> gmpThread;
   {
     MutexAutoLock lock(mMutex);
     mGMPThreadShutdown = true;
--- a/dom/media/gmp/GMPService.h
+++ b/dom/media/gmp/GMPService.h
@@ -19,39 +19,66 @@
 #include "nsIDocument.h"
 #include "nsIWeakReference.h"
 #include "mozilla/AbstractThread.h"
 #include "nsClassHashtable.h"
 #include "nsISupportsImpl.h"
 #include "mozilla/MozPromise.h"
 #include "GMPContentParent.h"
 #include "GMPCrashHelper.h"
+#include "ChromiumCDMParent.h"
 
 template <class> struct already_AddRefed;
 
 namespace mozilla {
 
 class GMPCrashHelper;
 
 extern LogModule* GetGMPLog();
 
 namespace gmp {
 
-typedef MozPromise<RefPtr<GMPContentParent::CloseBlocker>, nsresult, /* IsExclusive = */ true> GetGMPContentParentPromise;
+struct NodeId
+{
+  NodeId(const nsAString& aOrigin,
+         const nsAString& aTopLevelOrigin,
+         const nsAString& aGMPName)
+    : mOrigin(aOrigin)
+    , mTopLevelOrigin(aTopLevelOrigin)
+    , mGMPName(aGMPName)
+  {
+  }
+  nsString mOrigin;
+  nsString mTopLevelOrigin;
+  nsString mGMPName;
+};
+
+typedef MozPromise<RefPtr<GMPContentParent::CloseBlocker>,
+                   nsresult,
+                   /* IsExclusive = */ true>
+  GetGMPContentParentPromise;
+typedef MozPromise<RefPtr<ChromiumCDMParent>,
+                   nsresult,
+                   /* IsExclusive = */ true>
+  GetCDMParentPromise;
 
 class GeckoMediaPluginService : public mozIGeckoMediaPluginService
                               , public nsIObserver
 {
 public:
   static already_AddRefed<GeckoMediaPluginService> GetGeckoMediaPluginService();
 
   virtual nsresult Init();
 
   NS_DECL_THREADSAFE_ISUPPORTS
 
+  RefPtr<GetCDMParentPromise> GetCDM(const NodeId& aNodeId,
+                                     nsTArray<nsCString> aTags,
+                                     GMPCrashHelper* aHelper);
+
   // mozIGeckoMediaPluginService
   NS_IMETHOD GetThread(nsIThread** aThread) override;
   NS_IMETHOD GetDecryptingGMPVideoDecoder(GMPCrashHelper* aHelper,
                                           nsTArray<nsCString>* aTags,
                                           const nsACString& aNodeId,
                                           UniquePtr<GetGMPVideoDecoderCallback>&& aCallback,
                                           uint32_t aDecryptorId)
     override;
@@ -85,21 +112,27 @@ public:
   void DisconnectCrashHelper(GMPCrashHelper* aHelper);
 
 protected:
   GeckoMediaPluginService();
   virtual ~GeckoMediaPluginService();
 
   virtual void InitializePlugins(AbstractThread* aAbstractGMPThread) = 0;
 
-  virtual RefPtr<GetGMPContentParentPromise>
-  GetContentParent(GMPCrashHelper* aHelper,
-                   const nsACString& aNodeId,
-                   const nsCString& aAPI,
-                   const nsTArray<nsCString>& aTags) = 0;
+  virtual RefPtr<GetGMPContentParentPromise> GetContentParent(
+    GMPCrashHelper* aHelper,
+    const nsACString& aNodeIdString,
+    const nsCString& aAPI,
+    const nsTArray<nsCString>& aTags) = 0;
+
+  virtual RefPtr<GetGMPContentParentPromise> GetContentParent(
+    GMPCrashHelper* aHelper,
+    const NodeId& aNodeId,
+    const nsCString& aAPI,
+    const nsTArray<nsCString>& aTags) = 0;
 
   nsresult GMPDispatch(nsIRunnable* event, uint32_t flags = NS_DISPATCH_NORMAL);
   nsresult GMPDispatch(already_AddRefed<nsIRunnable> event, uint32_t flags = NS_DISPATCH_NORMAL);
   void ShutdownGMPThread();
 
   Mutex mMutex; // Protects mGMPThread, mAbstractGMPThread, mPluginCrashHelpers,
                 // mGMPThreadShutdown and some members in derived classes.
   nsCOMPtr<nsIThread> mGMPThread;
--- a/dom/media/gmp/GMPServiceChild.cpp
+++ b/dom/media/gmp/GMPServiceChild.cpp
@@ -49,27 +49,103 @@ GeckoMediaPluginServiceChild::GetSinglet
     MOZ_ASSERT(!chromeService);
   }
 #endif
   return service.forget().downcast<GeckoMediaPluginServiceChild>();
 }
 
 RefPtr<GetGMPContentParentPromise>
 GeckoMediaPluginServiceChild::GetContentParent(GMPCrashHelper* aHelper,
-                                               const nsACString& aNodeId,
+                                               const nsACString& aNodeIdString,
                                                const nsCString& aAPI,
                                                const nsTArray<nsCString>& aTags)
 {
   MOZ_ASSERT(NS_GetCurrentThread() == mGMPThread);
 
   MozPromiseHolder<GetGMPContentParentPromise>* rawHolder = new MozPromiseHolder<GetGMPContentParentPromise>();
   RefPtr<GetGMPContentParentPromise> promise = rawHolder->Ensure(__func__);
   RefPtr<AbstractThread> thread(GetAbstractGMPThread());
 
-  nsCString nodeId(aNodeId);
+  nsCString nodeIdString(aNodeIdString);
+  nsCString api(aAPI);
+  nsTArray<nsCString> tags(aTags);
+  RefPtr<GMPCrashHelper> helper(aHelper);
+  RefPtr<GeckoMediaPluginServiceChild> self(this);
+  GetServiceChild()->Then(
+    thread,
+    __func__,
+    [self, nodeIdString, api, tags, helper, rawHolder](GMPServiceChild* child) {
+      UniquePtr<MozPromiseHolder<GetGMPContentParentPromise>> holder(rawHolder);
+      nsresult rv;
+
+      nsTArray<base::ProcessId> alreadyBridgedTo;
+      child->GetAlreadyBridgedTo(alreadyBridgedTo);
+
+      base::ProcessId otherProcess;
+      nsCString displayName;
+      uint32_t pluginId = 0;
+      ipc::Endpoint<PGMPContentParent> endpoint;
+      bool ok = child->SendLaunchGMP(nodeIdString,
+                                     api,
+                                     tags,
+                                     alreadyBridgedTo,
+                                     &pluginId,
+                                     &otherProcess,
+                                     &displayName,
+                                     &endpoint,
+                                     &rv);
+      if (helper && pluginId) {
+        // Note: Even if the launch failed, we need to connect the crash
+        // helper so that if the launch failed due to the plugin crashing,
+        // we can report the crash via the crash reporter. The crash
+        // handling notification will arrive shortly if the launch failed
+        // due to the plugin crashing.
+        self->ConnectCrashHelper(pluginId, helper);
+      }
+
+      if (!ok || NS_FAILED(rv)) {
+        LOGD(("GeckoMediaPluginServiceChild::GetContentParent SendLaunchGMP "
+              "failed rv=0x%x",
+              static_cast<uint32_t>(rv)));
+        holder->Reject(rv, __func__);
+        return;
+      }
+
+      RefPtr<GMPContentParent> parent =
+        child->GetBridgedGMPContentParent(otherProcess, Move(endpoint));
+      if (!alreadyBridgedTo.Contains(otherProcess)) {
+        parent->SetDisplayName(displayName);
+        parent->SetPluginId(pluginId);
+      }
+      RefPtr<GMPContentParent::CloseBlocker> blocker(
+        new GMPContentParent::CloseBlocker(parent));
+      holder->Resolve(blocker, __func__);
+    },
+    [rawHolder](nsresult rv) {
+      UniquePtr<MozPromiseHolder<GetGMPContentParentPromise>> holder(rawHolder);
+      holder->Reject(rv, __func__);
+    });
+
+  return promise;
+}
+
+RefPtr<GetGMPContentParentPromise>
+GeckoMediaPluginServiceChild::GetContentParent(GMPCrashHelper* aHelper,
+                                               const NodeId& aNodeId,
+                                               const nsCString& aAPI,
+                                               const nsTArray<nsCString>& aTags)
+{
+  MOZ_ASSERT(NS_GetCurrentThread() == mGMPThread);
+
+  MozPromiseHolder<GetGMPContentParentPromise>* rawHolder =
+    new MozPromiseHolder<GetGMPContentParentPromise>();
+  RefPtr<GetGMPContentParentPromise> promise = rawHolder->Ensure(__func__);
+  RefPtr<AbstractThread> thread(GetAbstractGMPThread());
+
+  NodeIdData nodeId(aNodeId.mOrigin, aNodeId.mTopLevelOrigin, aNodeId.mGMPName);
   nsCString api(aAPI);
   nsTArray<nsCString> tags(aTags);
   RefPtr<GMPCrashHelper> helper(aHelper);
   RefPtr<GeckoMediaPluginServiceChild> self(this);
   GetServiceChild()->Then(thread, __func__,
     [self, nodeId, api, tags, helper, rawHolder](GMPServiceChild* child) {
       UniquePtr<MozPromiseHolder<GetGMPContentParentPromise>> holder(rawHolder);
       nsresult rv;
@@ -77,25 +153,25 @@ GeckoMediaPluginServiceChild::GetContent
       nsTArray<base::ProcessId> alreadyBridgedTo;
       child->GetAlreadyBridgedTo(alreadyBridgedTo);
 
       base::ProcessId otherProcess;
       nsCString displayName;
       uint32_t pluginId = 0;
       ipc::Endpoint<PGMPContentParent> endpoint;
 
-      bool ok = child->SendLaunchGMP(nodeId,
-                                     api,
-                                     tags,
-                                     alreadyBridgedTo,
-                                     &pluginId,
-                                     &otherProcess,
-                                     &displayName,
-                                     &endpoint,
-                                     &rv);
+      bool ok = child->SendLaunchGMPForNodeId(nodeId,
+                                              api,
+                                              tags,
+                                              alreadyBridgedTo,
+                                              &pluginId,
+                                              &otherProcess,
+                                              &displayName,
+                                              &endpoint,
+                                              &rv);
 
       if (helper && pluginId) {
         // Note: Even if the launch failed, we need to connect the crash
         // helper so that if the launch failed due to the plugin crashing,
         // we can report the crash via the crash reporter. The crash
         // handling notification will arrive shortly if the launch failed
         // due to the plugin crashing.
         self->ConnectCrashHelper(pluginId, helper);
--- a/dom/media/gmp/GMPServiceChild.h
+++ b/dom/media/gmp/GMPServiceChild.h
@@ -43,21 +43,27 @@ public:
   static void UpdateGMPCapabilities(nsTArray<mozilla::dom::GMPCapabilityData>&& aCapabilities);
 
 protected:
   void InitializePlugins(AbstractThread*) override
   {
     // Nothing to do here.
   }
 
-  virtual RefPtr<GetGMPContentParentPromise>
-  GetContentParent(GMPCrashHelper* aHelper,
-                   const nsACString& aNodeId,
-                   const nsCString& aAPI,
-                   const nsTArray<nsCString>& aTags) override;
+  virtual RefPtr<GetGMPContentParentPromise> GetContentParent(
+    GMPCrashHelper* aHelper,
+    const nsACString& aNodeIdString,
+    const nsCString& aAPI,
+    const nsTArray<nsCString>& aTags) override;
+
+  RefPtr<GetGMPContentParentPromise> GetContentParent(
+    GMPCrashHelper* aHelper,
+    const NodeId& aNodeId,
+    const nsCString& aAPI,
+    const nsTArray<nsCString>& aTags) override;
 
 private:
   friend class OpenPGMPServiceChild;
 
   typedef MozPromise<GMPServiceChild*, nsresult, /* IsExclusive = */ true> GetServiceChildPromise;
   RefPtr<GetServiceChildPromise> GetServiceChild();
 
   nsTArray<MozPromiseHolder<GetServiceChildPromise>> mGetServiceChildPromises;
--- a/dom/media/gmp/GMPServiceParent.cpp
+++ b/dom/media/gmp/GMPServiceParent.cpp
@@ -348,38 +348,41 @@ GeckoMediaPluginServiceParent::EnsureIni
     return GenericPromise::CreateAndResolve(true, __func__);
   }
   // We should have an init promise in flight.
   MOZ_ASSERT(!mInitPromise.IsEmpty());
   return mInitPromise.Ensure(__func__);
 }
 
 RefPtr<GetGMPContentParentPromise>
-GeckoMediaPluginServiceParent::GetContentParent(GMPCrashHelper* aHelper,
-                                               const nsACString& aNodeId,
-                                               const nsCString& aAPI,
-                                               const nsTArray<nsCString>& aTags)
+GeckoMediaPluginServiceParent::GetContentParent(
+  GMPCrashHelper* aHelper,
+  const nsACString& aNodeIdString,
+  const nsCString& aAPI,
+  const nsTArray<nsCString>& aTags)
 {
   RefPtr<AbstractThread> thread(GetAbstractGMPThread());
   if (!thread) {
     return GetGMPContentParentPromise::CreateAndReject(NS_ERROR_FAILURE, __func__);
   }
 
   typedef MozPromiseHolder<GetGMPContentParentPromise> PromiseHolder;
   PromiseHolder* rawHolder = new PromiseHolder();
   RefPtr<GeckoMediaPluginServiceParent> self(this);
   RefPtr<GetGMPContentParentPromise> promise = rawHolder->Ensure(__func__);
-  nsCString nodeId(aNodeId);
+  nsCString nodeIdString(aNodeIdString);
   nsTArray<nsCString> tags(aTags);
   nsCString api(aAPI);
   RefPtr<GMPCrashHelper> helper(aHelper);
-  EnsureInitialized()->Then(thread, __func__,
-    [self, tags, api, nodeId, helper, rawHolder]() -> void {
+  EnsureInitialized()->Then(
+    thread,
+    __func__,
+    [self, tags, api, nodeIdString, helper, rawHolder]() -> void {
       UniquePtr<PromiseHolder> holder(rawHolder);
-      RefPtr<GMPParent> gmp = self->SelectPluginForAPI(nodeId, api, tags);
+      RefPtr<GMPParent> gmp = self->SelectPluginForAPI(nodeIdString, api, tags);
       LOGD(("%s: %p returning %p for api %s", __FUNCTION__, (void *)self, (void *)gmp, api.get()));
       if (!gmp) {
         NS_WARNING("GeckoMediaPluginServiceParent::GetContentParentFrom failed");
         holder->Reject(NS_ERROR_FAILURE, __func__);
         return;
       }
       self->ConnectCrashHelper(gmp->GetPluginId(), helper);
       gmp->GetGMPContentParent(Move(holder));
@@ -388,16 +391,35 @@ GeckoMediaPluginServiceParent::GetConten
       UniquePtr<PromiseHolder> holder(rawHolder);
       NS_WARNING("GMPService::EnsureInitialized failed.");
       holder->Reject(NS_ERROR_FAILURE, __func__);
     });
 
   return promise;
 }
 
+RefPtr<GetGMPContentParentPromise>
+GeckoMediaPluginServiceParent::GetContentParent(
+  GMPCrashHelper* aHelper,
+  const NodeId& aNodeId,
+  const nsCString& aAPI,
+  const nsTArray<nsCString>& aTags)
+{
+  MOZ_ASSERT(NS_GetCurrentThread() == mGMPThread);
+
+  nsCString nodeIdString;
+  nsresult rv = GetNodeId(
+    aNodeId.mOrigin, aNodeId.mTopLevelOrigin, aNodeId.mGMPName, nodeIdString);
+  if (NS_FAILED(rv)) {
+    return GetGMPContentParentPromise::CreateAndReject(NS_ERROR_FAILURE,
+                                                       __func__);
+  }
+  return GetContentParent(aHelper, nodeIdString, aAPI, aTags);
+}
+
 void
 GeckoMediaPluginServiceParent::InitializePlugins(
   AbstractThread* aAbstractGMPThread)
 {
   MOZ_ASSERT(aAbstractGMPThread);
   MonitorAutoLock lock(mInitPromiseMonitor);
   if (mLoadPluginsFromDiskComplete) {
     return;
@@ -1732,16 +1754,46 @@ GMPServiceParent::RecvLaunchGMP(const ns
 
   gmp->IncrementGMPContentChildCount();
 
   *aOutRv = NS_OK;
   return IPC_OK();
 }
 
 mozilla::ipc::IPCResult
+GMPServiceParent::RecvLaunchGMPForNodeId(
+  const NodeIdData& aNodeId,
+  const nsCString& aApi,
+  nsTArray<nsCString>&& aTags,
+  nsTArray<ProcessId>&& aAlreadyBridgedTo,
+  uint32_t* aOutPluginId,
+  ProcessId* aOutId,
+  nsCString* aOutDisplayName,
+  Endpoint<PGMPContentParent>* aOutEndpoint,
+  nsresult* aOutRv)
+{
+  nsCString nodeId;
+  nsresult rv = mService->GetNodeId(
+    aNodeId.mOrigin(), aNodeId.mTopLevelOrigin(), aNodeId.mGMPName(), nodeId);
+  if (!NS_SUCCEEDED(rv)) {
+    *aOutRv = rv;
+    return IPC_OK();
+  }
+  return RecvLaunchGMP(nodeId,
+                       aApi,
+                       Move(aTags),
+                       Move(aAlreadyBridgedTo),
+                       aOutPluginId,
+                       aOutId,
+                       aOutDisplayName,
+                       aOutEndpoint,
+                       aOutRv);
+}
+
+mozilla::ipc::IPCResult
 GMPServiceParent::RecvGetGMPNodeId(const nsString& aOrigin,
                                    const nsString& aTopLevelOrigin,
                                    const nsString& aGMPName,
                                    nsCString* aID)
 {
   nsresult rv = mService->GetNodeId(aOrigin, aTopLevelOrigin, aGMPName, *aID);
   if (!NS_SUCCEEDED(rv)) {
     return IPC_FAIL_NO_REASON(this);
--- a/dom/media/gmp/GMPServiceParent.h
+++ b/dom/media/gmp/GMPServiceParent.h
@@ -112,21 +112,27 @@ private:
 protected:
   friend class GMPParent;
   void ReAddOnGMPThread(const RefPtr<GMPParent>& aOld);
   void PluginTerminated(const RefPtr<GMPParent>& aOld);
   void InitializePlugins(AbstractThread* aAbstractGMPThread) override;
   RefPtr<GenericPromise::AllPromiseType> LoadFromEnvironment();
   RefPtr<GenericPromise> AddOnGMPThread(nsString aDirectory);
 
-  virtual RefPtr<GetGMPContentParentPromise>
-  GetContentParent(GMPCrashHelper* aHelper,
-                   const nsACString& aNodeId,
-                   const nsCString& aAPI,
-                   const nsTArray<nsCString>& aTags) override;
+  virtual RefPtr<GetGMPContentParentPromise> GetContentParent(
+    GMPCrashHelper* aHelper,
+    const nsACString& aNodeIdString,
+    const nsCString& aAPI,
+    const nsTArray<nsCString>& aTags) override;
+
+  RefPtr<GetGMPContentParentPromise> GetContentParent(
+    GMPCrashHelper* aHelper,
+    const NodeId& aNodeId,
+    const nsCString& aAPI,
+    const nsTArray<nsCString>& aTags) override;
 
 private:
   // Creates a copy of aOriginal. Note that the caller is responsible for
   // adding this to GeckoMediaPluginServiceParent::mPlugins.
   already_AddRefed<GMPParent> ClonePlugin(const GMPParent* aOriginal);
   nsresult EnsurePluginsOnDiskScanned();
   nsresult InitStorage();
 
@@ -218,33 +224,44 @@ class GMPServiceParent final : public PG
 public:
   explicit GMPServiceParent(GeckoMediaPluginServiceParent* aService)
     : mService(aService)
   {
     mService->ServiceUserCreated();
   }
   virtual ~GMPServiceParent();
 
-  mozilla::ipc::IPCResult RecvGetGMPNodeId(const nsString& aOrigin,
-                                           const nsString& aTopLevelOrigin,
-                                           const nsString& aGMPName,
-                                           nsCString* aID) override;
+  ipc::IPCResult RecvGetGMPNodeId(const nsString& aOrigin,
+                                  const nsString& aTopLevelOrigin,
+                                  const nsString& aGMPName,
+                                  nsCString* aID) override;
   void ActorDestroy(ActorDestroyReason aWhy) override;
 
   static bool Create(Endpoint<PGMPServiceParent>&& aGMPService);
 
-  mozilla::ipc::IPCResult RecvLaunchGMP(const nsCString& aNodeId,
-                                        const nsCString& aAPI,
-                                        nsTArray<nsCString>&& aTags,
-                                        nsTArray<ProcessId>&& aAlreadyBridgedTo,
-                                        uint32_t* aOutPluginId,
-                                        ProcessId* aOutID,
-                                        nsCString* aOutDisplayName,
-                                        Endpoint<PGMPContentParent>* aOutEndpoint,
-                                        nsresult* aOutRv) override;
+  ipc::IPCResult RecvLaunchGMP(const nsCString& aNodeId,
+                               const nsCString& aAPI,
+                               nsTArray<nsCString>&& aTags,
+                               nsTArray<ProcessId>&& aAlreadyBridgedTo,
+                               uint32_t* aOutPluginId,
+                               ProcessId* aOutID,
+                               nsCString* aOutDisplayName,
+                               Endpoint<PGMPContentParent>* aOutEndpoint,
+                               nsresult* aOutRv) override;
+
+  ipc::IPCResult RecvLaunchGMPForNodeId(
+    const NodeIdData& nodeId,
+    const nsCString& aAPI,
+    nsTArray<nsCString>&& aTags,
+    nsTArray<ProcessId>&& aAlreadyBridgedTo,
+    uint32_t* aOutPluginId,
+    ProcessId* aOutID,
+    nsCString* aOutDisplayName,
+    Endpoint<PGMPContentParent>* aOutEndpoint,
+    nsresult* aOutRv) override;
 
 private:
   void CloseTransport(Monitor* aSyncMonitor, bool* aCompleted);
 
   RefPtr<GeckoMediaPluginServiceParent> mService;
 };
 
 } // namespace gmp
--- a/dom/media/gmp/GMPTypes.ipdlh
+++ b/dom/media/gmp/GMPTypes.ipdlh
@@ -4,16 +4,22 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 using GMPBufferType from "gmp-video-codec.h";
 using GMPMediaKeyStatus from "gmp-decryption.h";
 
 namespace mozilla {
 namespace gmp {
 
+struct NodeIdData {
+  nsString mOrigin;
+  nsString mTopLevelOrigin;
+  nsString mGMPName;
+};
+
 struct GMPDecryptionData {
   uint8_t[] mKeyId;
   uint8_t[] mIV;
   uint16_t[] mClearBytes;
   uint32_t[] mCipherBytes;
   nsCString[] mSessionIds;
 };
 
@@ -49,10 +55,53 @@ struct GMPVideoi420FrameData
   uint64_t mDuration; // microseconds
 };
 
 struct GMPKeyInformation {
   uint8_t[] keyId;
   GMPMediaKeyStatus status;
 };
 
+struct CDMInputBuffer {
+  uint8_t[] mData;
+  uint8_t[] mKeyId;
+  uint8_t[] mIV;
+  int64_t mTimestamp;
+  int64_t mDuration;
+  uint16_t[] mClearBytes;
+  uint32_t[] mCipherBytes;
+  bool mIsEncrypted;
+};
+
+struct CDMVideoDecoderConfig {
+  uint32_t mCodec;
+  uint32_t mProfile;
+  uint32_t mFormat;
+  int32_t mImageWidth;
+  int32_t mImageHeight;
+  uint8_t[] mExtraData;
+};
+
+struct CDMKeyInformation {
+  uint8_t[] mKeyId;
+  uint32_t mStatus;
+  uint32_t mSystemCode;
+};
+
+struct CDMVideoPlane {
+  uint32_t mPlaneOffset;
+  uint32_t mStride;
+};
+
+struct CDMVideoFrame {
+  uint32_t mFormat;
+  int32_t mImageWidth;
+  int32_t mImageHeight;
+  uint8_t[] mData;
+  CDMVideoPlane mYPlane;
+  CDMVideoPlane mUPlane;
+  CDMVideoPlane mVPlane;
+  int64_t mTimestamp;
+  int64_t mDuration;
+};
+
 }
 }
--- a/dom/media/gmp/GMPUtils.cpp
+++ b/dom/media/gmp/GMPUtils.cpp
@@ -10,16 +10,17 @@
 #include "nsCOMPtr.h"
 #include "nsLiteralString.h"
 #include "nsCRTGlue.h"
 #include "mozilla/Base64.h"
 #include "nsISimpleEnumerator.h"
 #include "prio.h"
 #include "nsIConsoleService.h"
 #include "mozIGeckoMediaPluginService.h"
+#include "GMPService.h"
 
 namespace mozilla {
 
 void
 SplitAt(const char* aDelims,
         const nsACString& aInput,
         nsTArray<nsCString>& aOutTokens)
 {
@@ -225,9 +226,17 @@ LogToConsole(const nsAString& aMsg)
   if (!console) {
     NS_WARNING("Failed to log message to console.");
     return;
   }
   nsAutoString msg(aMsg);
   console->LogStringMessage(msg.get());
 }
 
+RefPtr<AbstractThread>
+GetGMPAbstractThread()
+{
+  RefPtr<gmp::GeckoMediaPluginService> service =
+    gmp::GeckoMediaPluginService::GetGeckoMediaPluginService();
+  return service ? service->GetAbstractGMPThread() : nullptr;
+}
+
 } // namespace mozilla
--- a/dom/media/gmp/GMPUtils.h
+++ b/dom/media/gmp/GMPUtils.h
@@ -2,20 +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/. */
 
 #ifndef GMPUtils_h_
 #define GMPUtils_h_
 
 #include "mozilla/UniquePtr.h"
+#include "mozilla/RefPtr.h"
+#include "mozilla/AbstractThread.h"
 #include "nsTArray.h"
 #include "nsCOMPtr.h"
 #include "nsClassHashtable.h"
 
+#define CHROMIUM_CDM_API "chromium-cdm8-host4"
+
 class nsIFile;
 class nsCString;
 class nsISimpleEnumerator;
 
 namespace mozilla {
 
 template<typename T>
 struct DestroyPolicy
@@ -76,11 +80,14 @@ ReadIntoString(nsIFile* aFile,
 
 bool
 HaveGMPFor(const nsCString& aAPI,
            nsTArray<nsCString>&& aTags);
 
 void
 LogToConsole(const nsAString& aMsg);
 
+RefPtr<AbstractThread>
+GetGMPAbstractThread();
+
 } // namespace mozilla
 
 #endif
--- a/dom/media/gmp/GMPVideoDecoderParent.cpp
+++ b/dom/media/gmp/GMPVideoDecoderParent.cpp
@@ -204,17 +204,18 @@ GMPVideoDecoderParent::Reset()
   RefPtr<GMPVideoDecoderParent> self(this);
   nsCOMPtr<nsIRunnable> task = NS_NewRunnableFunction([self]() -> void
   {
     LOGD(("GMPVideoDecoderParent[%p]::ResetCompleteTimeout() timed out waiting for ResetComplete", self.get()));
     self->mResetCompleteTimeout = nullptr;
     LogToBrowserConsole(NS_LITERAL_STRING("GMPVideoDecoderParent timed out waiting for ResetComplete()"));
   });
   CancelResetCompleteTimeout();
-  mResetCompleteTimeout = SimpleTimer::Create(task, 5000, mPlugin->GMPThread());
+  nsCOMPtr<nsIThread> thread = mPlugin->GMPThread();
+  mResetCompleteTimeout = SimpleTimer::Create(task, 5000, thread);
 
   // Async IPC, we don't have access to a return value.
   return NS_OK;
 }
 
 void
 GMPVideoDecoderParent::CancelResetCompleteTimeout()
 {
--- a/dom/media/gmp/GMPVideoEncoderParent.cpp
+++ b/dom/media/gmp/GMPVideoEncoderParent.cpp
@@ -12,16 +12,17 @@
 #include "nsAutoRef.h"
 #include "GMPContentParent.h"
 #include "mozilla/gmp/GMPTypes.h"
 #include "nsThread.h"
 #include "nsThreadUtils.h"
 #include "runnable_utils.h"
 #include "GMPUtils.h"
 #include "mozilla/SystemGroup.h"
+#include "GMPCrashHelper.h"
 
 namespace mozilla {
 
 #ifdef LOG
 #undef LOG
 #endif
 
 extern LogModule* GetGMPLog();
new file mode 100644
--- /dev/null
+++ b/dom/media/gmp/PChromiumCDM.ipdl
@@ -0,0 +1,106 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+include protocol PGMPContent;
+include GMPTypes;
+
+namespace mozilla {
+namespace gmp {
+
+async protocol PChromiumCDM
+{
+  manager PGMPContent;
+child:
+
+  // cdm::ContentDecryptionModule8
+  async Init(bool aAllowDistinctiveIdentifier,
+             bool aAllowPersistentState);
+
+  async SetServerCertificate(uint32_t aPromiseId,
+                             uint8_t[] aServerCert);
+
+  async CreateSessionAndGenerateRequest(uint32_t aPromiseId,
+                                        uint32_t aSessionType,
+                                        uint32_t aInitDataType,
+                                        uint8_t[] aInitData);
+
+  async LoadSession(uint32_t aPromiseId,
+                    uint32_t aSessionType,
+                    nsCString aSessionId);
+
+  async UpdateSession(uint32_t aPromiseId,
+                      nsCString aSessionId,
+                      uint8_t[] aResponse);
+
+  async CloseSession(uint32_t aPromiseId,
+                     nsCString aSessionId);
+
+  async RemoveSession(uint32_t aPromiseId,
+                      nsCString aSessionId);
+
+  async Decrypt(uint32_t aId, CDMInputBuffer aBuffer);
+
+  async InitializeVideoDecoder(CDMVideoDecoderConfig aConfig);
+
+  async DeinitializeVideoDecoder();
+
+  async ResetVideoDecoder();
+
+  async DecryptAndDecodeFrame(CDMInputBuffer aBuffer);
+
+  async Drain();
+
+  async Destroy();
+
+parent:
+  async __delete__();
+
+  // cdm::Host8
+  async OnResolveNewSessionPromise(uint32_t aPromiseId, nsCString aSessionId);
+
+  async OnResolvePromise(uint32_t aPromiseId);
+
+  async OnRejectPromise(uint32_t aPromiseId,
+                        uint32_t aError,
+                        uint32_t aSystemCode,
+                        nsCString aErrorMessage);
+
+  async OnSessionMessage(nsCString aSessionId,
+                         uint32_t aMessageType,
+                         uint8_t[] aMessage);
+
+  async OnSessionKeysChange(nsCString aSessionId,
+                            CDMKeyInformation[] aKeysInfo);
+
+  async OnExpirationChange(nsCString aSessionId,
+                           double aSecondsSinceEpoch);
+
+  async OnSessionClosed(nsCString aSessionId);
+
+  async OnLegacySessionError(nsCString aSessionId,
+                             uint32_t aError,
+                             uint32_t aSystemCode,
+                             nsCString aMessage);
+
+  async ResolveLoadSessionPromise(uint32_t aPromiseId, bool aSuccessful);
+
+  // Return values of cdm::ContentDecryptionModule8::Decrypt
+  async Decrypted(uint32_t aId, uint32_t aStatus, uint8_t[] aData);
+
+  async OnDecoderInitDone(uint32_t aStatus);
+
+  // Return values of cdm::ContentDecryptionModule8::DecryptAndDecodeFrame
+  async Decoded(CDMVideoFrame aFrame);
+  async DecodeFailed(uint32_t aStatus);
+
+  async ResetVideoDecoderComplete();
+
+  async DrainComplete();
+
+  async Shutdown();
+};
+
+} // namespace gmp
+} // namespace mozilla
--- a/dom/media/gmp/PGMPContent.ipdl
+++ b/dom/media/gmp/PGMPContent.ipdl
@@ -1,26 +1,29 @@
 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 include protocol PGMPVideoDecoder;
 include protocol PGMPVideoEncoder;
 include protocol PGMPDecryptor;
+include protocol PChromiumCDM;
 
 namespace mozilla {
 namespace gmp {
 
 intr protocol PGMPContent
 {
   manages PGMPDecryptor;
   manages PGMPVideoDecoder;
   manages PGMPVideoEncoder;
+  manages PChromiumCDM;
 
 child:
   async PGMPDecryptor();
   async PGMPVideoDecoder(uint32_t aDecryptorId);
   async PGMPVideoEncoder();
+  async PChromiumCDM();
 };
 
 } // namespace gmp
 } // namespace mozilla
--- a/dom/media/gmp/PGMPService.ipdl
+++ b/dom/media/gmp/PGMPService.ipdl
@@ -1,14 +1,15 @@
 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 include protocol PGMPContent;
+include GMPTypes;
 
 using base::ProcessId from "base/process.h";
 
 namespace mozilla {
 namespace gmp {
 
 sync protocol PGMPService
 {
@@ -18,14 +19,24 @@ parent:
                  nsCString[] tags,
                  ProcessId[] alreadyBridgedTo)
     returns (uint32_t pluginId,
              ProcessId id,
              nsCString displayName,
              Endpoint<PGMPContentParent> endpoint,
              nsresult aResult);
 
+  sync LaunchGMPForNodeId(NodeIdData nodeId,
+                          nsCString api,
+                          nsCString[] tags,
+                          ProcessId[] alreadyBridgedTo)
+    returns (uint32_t pluginId,
+             ProcessId id,
+             nsCString displayName,
+             Endpoint<PGMPContentParent> endpoint,
+             nsresult aResult);
+
   sync GetGMPNodeId(nsString origin, nsString topLevelOrigin, nsString gmpName)
     returns (nsCString id);
 };
 
 } // namespace gmp
 } // namespace mozilla
--- a/dom/media/gmp/moz.build
+++ b/dom/media/gmp/moz.build
@@ -7,16 +7,18 @@
 XPIDL_MODULE = 'content_geckomediaplugins'
 
 XPIDL_SOURCES += [
     'mozIGeckoMediaPluginChromeService.idl',
     'mozIGeckoMediaPluginService.idl',
 ]
 
 EXPORTS += [
+    'ChromiumCDMParent.h',
+    'ChromiumCDMProxy.h',
     'DecryptJob.h',
     'gmp-api/gmp-decryption.h',
     'gmp-api/gmp-entrypoints.h',
     'gmp-api/gmp-errors.h',
     'gmp-api/gmp-platform.h',
     'gmp-api/gmp-storage.h',
     'gmp-api/gmp-video-codec.h',
     'gmp-api/gmp-video-decode.h',
@@ -63,16 +65,20 @@ EXPORTS += [
     'GMPVideoEncoderProxy.h',
     'GMPVideoHost.h',
     'GMPVideoi420FrameImpl.h',
     'GMPVideoPlaneImpl.h',
     'widevine-adapter/content_decryption_module.h',
 ]
 
 UNIFIED_SOURCES += [
+    'ChromiumCDMAdapter.cpp',
+    'ChromiumCDMChild.cpp',
+    'ChromiumCDMParent.cpp',
+    'ChromiumCDMProxy.cpp',
     'DecryptJob.cpp',
     'GMPCDMCallbackProxy.cpp',
     'GMPCDMProxy.cpp',
     'GMPChild.cpp',
     'GMPContentChild.cpp',
     'GMPContentParent.cpp',
     'GMPCrashHelper.cpp',
     'GMPCrashHelperHolder.cpp',
@@ -106,16 +112,17 @@ UNIFIED_SOURCES += [
 ]
 
 DIRS += [
     'widevine-adapter',
 ]
 
 IPDL_SOURCES += [
   'GMPTypes.ipdlh',
+  'PChromiumCDM.ipdl',
   'PGMP.ipdl',
   'PGMPContent.ipdl',
   'PGMPDecryptor.ipdl',
   'PGMPService.ipdl',
   'PGMPStorage.ipdl',
   'PGMPTimer.ipdl',
   'PGMPVideoDecoder.ipdl',
   'PGMPVideoEncoder.ipdl',
--- a/dom/media/gmp/widevine-adapter/WidevineAdapter.cpp
+++ b/dom/media/gmp/widevine-adapter/WidevineAdapter.cpp
@@ -10,17 +10,17 @@
 #include "WidevineDummyDecoder.h"
 #include "WidevineUtils.h"
 #include "WidevineVideoDecoder.h"
 #include "gmp-api/gmp-entrypoints.h"
 #include "gmp-api/gmp-decryption.h"
 #include "gmp-api/gmp-video-codec.h"
 #include "gmp-api/gmp-platform.h"
 
-static const GMPPlatformAPI* sPlatform = nullptr;
+const GMPPlatformAPI* sPlatform = nullptr;
 
 namespace mozilla {
 
 GMPErr GMPGetCurrentTime(GMPTimestamp* aOutTime) {
   return sPlatform->getcurrenttime(aOutTime);
 }
 
 // Call on main thread only.
--- a/dom/media/gmp/widevine-adapter/moz.build
+++ b/dom/media/gmp/widevine-adapter/moz.build
@@ -11,17 +11,19 @@ SOURCES += [
     'WidevineFileIO.cpp',
     'WidevineUtils.cpp',
     'WidevineVideoDecoder.cpp',
     'WidevineVideoFrame.cpp',
 ]
 
 EXPORTS += [
     'WidevineDecryptor.h',
-    'WidevineUtils.h'
+    'WidevineFileIO.h',
+    'WidevineUtils.h',
+    'WidevineVideoFrame.h'
 ]
 
 FINAL_LIBRARY = 'xul'
 
 LOCAL_INCLUDES += [
     '/dom/media/gmp',
 ]
 
new file mode 100644
--- /dev/null
+++ b/dom/media/platforms/agnostic/eme/ChromiumCDMVideoDecoder.cpp
@@ -0,0 +1,148 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "ChromiumCDMVideoDecoder.h"
+#include "ChromiumCDMProxy.h"
+#include "content_decryption_module.h"
+#include "GMPService.h"
+#include "GMPVideoDecoder.h"
+#include "MP4Decoder.h"
+#include "VPXDecoder.h"
+
+namespace mozilla {
+
+ChromiumCDMVideoDecoder::ChromiumCDMVideoDecoder(
+  const GMPVideoDecoderParams& aParams,
+  CDMProxy* aCDMProxy)
+  : mCDMParent(aCDMProxy->AsChromiumCDMProxy()->GetCDMParent())
+  , mConfig(aParams.mConfig)
+  , mCrashHelper(aParams.mCrashHelper)
+  , mGMPThread(GetGMPAbstractThread())
+  , mImageContainer(aParams.mImageContainer)
+{
+}
+
+ChromiumCDMVideoDecoder::~ChromiumCDMVideoDecoder()
+{
+}
+
+static uint32_t
+ToCDMH264Profile(uint8_t aProfile)
+{
+  switch (aProfile) {
+    case 66:
+      return cdm::VideoDecoderConfig::kH264ProfileBaseline;
+    case 77:
+      return cdm::VideoDecoderConfig::kH264ProfileMain;
+    case 88:
+      return cdm::VideoDecoderConfig::kH264ProfileExtended;
+    case 100:
+      return cdm::VideoDecoderConfig::kH264ProfileHigh;
+    case 110:
+      return cdm::VideoDecoderConfig::kH264ProfileHigh10;
+    case 122:
+      return cdm::VideoDecoderConfig::kH264ProfileHigh422;
+    case 144:
+      return cdm::VideoDecoderConfig::kH264ProfileHigh444Predictive;
+  }
+  return cdm::VideoDecoderConfig::kUnknownVideoCodecProfile;
+}
+
+RefPtr<MediaDataDecoder::InitPromise>
+ChromiumCDMVideoDecoder::Init()
+{
+  if (!mCDMParent) {
+    // Must have failed to get the CDMParent from the ChromiumCDMProxy
+    // in our constructor; the MediaKeys must have shut down the CDM
+    // before we had a chance to start up the decoder.
+    return InitPromise::CreateAndReject(
+      NS_ERROR_DOM_MEDIA_FATAL_ERR, __func__);
+  }
+
+  gmp::CDMVideoDecoderConfig config;
+  if (MP4Decoder::IsH264(mConfig.mMimeType)) {
+    config.mCodec() = cdm::VideoDecoderConfig::kCodecH264;
+    config.mProfile() =
+      ToCDMH264Profile(mConfig.mExtraData->SafeElementAt(1, 0));
+    config.mExtraData() = *mConfig.mExtraData;
+    mConvertToAnnexB = true;
+  } else if (VPXDecoder::IsVP8(mConfig.mMimeType)) {
+    config.mCodec() = cdm::VideoDecoderConfig::kCodecVp8;
+    config.mProfile() = cdm::VideoDecoderConfig::kProfileNotNeeded;
+  } else if (VPXDecoder::IsVP9(mConfig.mMimeType)) {
+    config.mCodec() = cdm::VideoDecoderConfig::kCodecVp9;
+    config.mProfile() = cdm::VideoDecoderConfig::kProfileNotNeeded;
+  } else {
+    return MediaDataDecoder::InitPromise::CreateAndReject(
+      NS_ERROR_DOM_MEDIA_FATAL_ERR, __func__);
+  }
+  config.mImageWidth() = mConfig.mImage.width;
+  config.mImageHeight() = mConfig.mImage.height;
+
+  RefPtr<gmp::ChromiumCDMParent> cdm = mCDMParent;
+  VideoInfo info = mConfig;
+  RefPtr<layers::ImageContainer> imageContainer = mImageContainer;
+  return InvokeAsync(
+    mGMPThread, __func__, [cdm, config, info, imageContainer]() {
+      return cdm->InitializeVideoDecoder(config, info, imageContainer);
+    });
+}
+
+const char*
+ChromiumCDMVideoDecoder::GetDescriptionName() const
+{
+  return "Chromium CDM video decoder";
+}
+
+MediaDataDecoder::ConversionRequired
+ChromiumCDMVideoDecoder::NeedsConversion() const
+{
+  return mConvertToAnnexB ? ConversionRequired::kNeedAnnexB
+                          : ConversionRequired::kNeedNone;
+}
+
+RefPtr<MediaDataDecoder::DecodePromise>
+ChromiumCDMVideoDecoder::Decode(MediaRawData* aSample)
+{
+  RefPtr<gmp::ChromiumCDMParent> cdm = mCDMParent;
+  RefPtr<MediaRawData> sample = aSample;
+  return InvokeAsync(mGMPThread, __func__, [cdm, sample]() {
+    return cdm->DecryptAndDecodeFrame(sample);
+  });
+}
+
+RefPtr<MediaDataDecoder::FlushPromise>
+ChromiumCDMVideoDecoder::Flush()
+{
+  MOZ_ASSERT(mCDMParent);
+  RefPtr<gmp::ChromiumCDMParent> cdm = mCDMParent;
+  return InvokeAsync(
+    mGMPThread, __func__, [cdm]() { return cdm->FlushVideoDecoder(); });
+}
+
+RefPtr<MediaDataDecoder::DecodePromise>
+ChromiumCDMVideoDecoder::Drain()
+{
+  MOZ_ASSERT(mCDMParent);
+  RefPtr<gmp::ChromiumCDMParent> cdm = mCDMParent;
+  return InvokeAsync(mGMPThread, __func__, [cdm]() { return cdm->Drain(); });
+}
+
+RefPtr<ShutdownPromise>
+ChromiumCDMVideoDecoder::Shutdown()
+{
+  if (!mCDMParent) {
+    // Must have failed to get the CDMParent from the ChromiumCDMProxy
+    // in our constructor; the MediaKeys must have shut down the CDM
+    // before we had a chance to start up the decoder.
+    return ShutdownPromise::CreateAndResolve(true, __func__);
+  }
+  RefPtr<gmp::ChromiumCDMParent> cdm = mCDMParent;
+  return InvokeAsync(
+    mGMPThread, __func__, [cdm]() { return cdm->ShutdownVideoDecoder(); });
+}
+
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/dom/media/platforms/agnostic/eme/ChromiumCDMVideoDecoder.h
@@ -0,0 +1,46 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef ChromiumCDMVideoDecoder_h_
+#define ChromiumCDMVideoDecoder_h_
+
+#include "PlatformDecoderModule.h"
+#include "ChromiumCDMParent.h"
+
+namespace mozilla {
+
+class CDMProxy;
+struct GMPVideoDecoderParams;
+
+class ChromiumCDMVideoDecoder : public MediaDataDecoder
+{
+public:
+  ChromiumCDMVideoDecoder(const GMPVideoDecoderParams& aParams,
+                          CDMProxy* aCDMProxy);
+
+  RefPtr<InitPromise> Init() override;
+  RefPtr<DecodePromise> Decode(MediaRawData* aSample) override;
+  RefPtr<FlushPromise> Flush() override;
+  RefPtr<DecodePromise> Drain() override;
+  RefPtr<ShutdownPromise> Shutdown() override;
+  const char* GetDescriptionName() const override;
+  ConversionRequired NeedsConversion() const override;
+
+private:
+  ~ChromiumCDMVideoDecoder();
+
+  RefPtr<gmp::ChromiumCDMParent> mCDMParent;
+  const VideoInfo mConfig;
+  RefPtr<GMPCrashHelper> mCrashHelper;
+  RefPtr<AbstractThread> mGMPThread;
+  RefPtr<layers::ImageContainer> mImageContainer;
+  MozPromiseHolder<InitPromise> mInitPromise;
+  bool mConvertToAnnexB = false;
+};
+
+} // mozilla
+
+#endif // ChromiumCDMVideoDecoder_h_
--- a/dom/media/platforms/agnostic/eme/EMEDecoderModule.cpp
+++ b/dom/media/platforms/agnostic/eme/EMEDecoderModule.cpp
@@ -348,17 +348,21 @@ EMEDecoderModule::CreateVideoDecoder(con
     return m->CreateVideoDecoder(aParams);
   }
 
   if (SupportsMimeType(aParams.mConfig.mMimeType, nullptr)) {
     // GMP decodes. Assume that means it can decrypt too.
     RefPtr<MediaDataDecoderProxy> wrapper =
       CreateDecoderWrapper(mProxy, aParams);
     auto params = GMPVideoDecoderParams(aParams);
-    wrapper->SetProxyTarget(new EMEVideoDecoder(mProxy, params));
+    if (MediaPrefs::EMEChromiumAPIEnabled()) {
+      wrapper->SetProxyTarget(new ChromiumCDMVideoDecoder(params, mProxy));
+    } else {
+      wrapper->SetProxyTarget(new EMEVideoDecoder(mProxy, params));
+    }
     return wrapper.forget();
   }
 
   MOZ_ASSERT(mPDM);
   RefPtr<MediaDataDecoder> decoder(mPDM->CreateDecoder(aParams));
   if (!decoder) {
     return nullptr;
   }
--- a/dom/media/platforms/agnostic/eme/moz.build
+++ b/dom/media/platforms/agnostic/eme/moz.build
@@ -1,22 +1,24 @@
 # -*- Mode: python; 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/.
 
 EXPORTS += [
+    'ChromiumCDMVideoDecoder.h',
     'DecryptThroughputLimit.h',
     'EMEDecoderModule.h',
     'EMEVideoDecoder.h',
     'SamplesWaitingForKey.h',
 ]
 
 UNIFIED_SOURCES += [
+    'ChromiumCDMVideoDecoder.cpp',
     'EMEDecoderModule.cpp',
     'EMEVideoDecoder.cpp',
     'SamplesWaitingForKey.cpp',
 ]
 
 include('/ipc/chromium/chromium-config.mozbuild')
 
 FINAL_LIBRARY = 'xul'
--- a/dom/media/platforms/agnostic/gmp/GMPDecoderModule.cpp
+++ b/dom/media/platforms/agnostic/gmp/GMPDecoderModule.cpp
@@ -74,29 +74,30 @@ GMPDecoderModule::CreateAudioDecoder(con
 bool
 GMPDecoderModule::SupportsMimeType(const nsACString& aMimeType,
                                    const Maybe<nsCString>& aGMP)
 {
   if (aGMP.isNothing()) {
     return false;
   }
 
+  nsCString api = MediaPrefs::EMEChromiumAPIEnabled()
+    ? NS_LITERAL_CSTRING(CHROMIUM_CDM_API)
+    : NS_LITERAL_CSTRING(GMP_API_VIDEO_DECODER);
+
   if (MP4Decoder::IsH264(aMimeType)) {
-    return HaveGMPFor(NS_LITERAL_CSTRING(GMP_API_VIDEO_DECODER),
-                      { NS_LITERAL_CSTRING("h264"), aGMP.value()});
+    return HaveGMPFor(api, { NS_LITERAL_CSTRING("h264"), aGMP.value()});
   }
 
   if (VPXDecoder::IsVP9(aMimeType)) {
-    return HaveGMPFor(NS_LITERAL_CSTRING(GMP_API_VIDEO_DECODER),
-                      { NS_LITERAL_CSTRING("vp9"), aGMP.value()});
+    return HaveGMPFor(api, { NS_LITERAL_CSTRING("vp9"), aGMP.value()});
   }
 
   if (VPXDecoder::IsVP8(aMimeType)) {
-    return HaveGMPFor(NS_LITERAL_CSTRING(GMP_API_VIDEO_DECODER),
-                      { NS_LITERAL_CSTRING("vp8"), aGMP.value()});
+    return HaveGMPFor(api, { NS_LITERAL_CSTRING("vp8"), aGMP.value()});
   }
 
   return false;
 }
 
 bool
 GMPDecoderModule::SupportsMimeType(const nsACString& aMimeType,
                                    DecoderDoctorDiagnostics* aDiagnostics) const
--- a/editor/libeditor/CompositionTransaction.cpp
+++ b/editor/libeditor/CompositionTransaction.cpp
@@ -28,46 +28,51 @@ CompositionTransaction::CompositionTrans
                           const nsAString& aStringToInsert,
                           EditorBase& aEditorBase,
                           RangeUpdater* aRangeUpdater)
   : mTextNode(&aTextNode)
   , mOffset(aOffset)
   , mReplaceLength(aReplaceLength)
   , mRanges(aTextRangeArray)
   , mStringToInsert(aStringToInsert)
-  , mEditorBase(aEditorBase)
+  , mEditorBase(&aEditorBase)
   , mRangeUpdater(aRangeUpdater)
   , mFixed(false)
 {
   MOZ_ASSERT(mTextNode->TextLength() >= mOffset);
 }
 
 CompositionTransaction::~CompositionTransaction()
 {
 }
 
 NS_IMPL_CYCLE_COLLECTION_INHERITED(CompositionTransaction, EditTransactionBase,
+                                   mEditorBase,
                                    mTextNode)
 // mRangeList can't lead to cycles
 
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(CompositionTransaction)
   if (aIID.Equals(NS_GET_IID(CompositionTransaction))) {
     foundInterface = static_cast<nsITransaction*>(this);
   } else
 NS_INTERFACE_MAP_END_INHERITING(EditTransactionBase)
 
 NS_IMPL_ADDREF_INHERITED(CompositionTransaction, EditTransactionBase)
 NS_IMPL_RELEASE_INHERITED(CompositionTransaction, EditTransactionBase)
 
 NS_IMETHODIMP
 CompositionTransaction::DoTransaction()
 {
+  if (NS_WARN_IF(!mEditorBase)) {
+    return NS_ERROR_NOT_INITIALIZED;
+  }
+
   // Fail before making any changes if there's no selection controller
   nsCOMPtr<nsISelectionController> selCon;
-  mEditorBase.GetSelectionController(getter_AddRefs(selCon));
+  mEditorBase->GetSelectionController(getter_AddRefs(selCon));
   NS_ENSURE_TRUE(selCon, NS_ERROR_NOT_INITIALIZED);
 
   // Advance caret: This requires the presentation shell to get the selection.
   if (mReplaceLength == 0) {
     nsresult rv = mTextNode->InsertData(mOffset, mStringToInsert);
     if (NS_WARN_IF(NS_FAILED(rv))) {
       return rv;
     }
@@ -103,19 +108,23 @@ CompositionTransaction::DoTransaction()
   NS_ENSURE_SUCCESS(rv, rv);
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
 CompositionTransaction::UndoTransaction()
 {
+  if (NS_WARN_IF(!mEditorBase)) {
+    return NS_ERROR_NOT_INITIALIZED;
+  }
+
   // Get the selection first so we'll fail before making any changes if we
   // can't get it
-  RefPtr<Selection> selection = mEditorBase.GetSelection();
+  RefPtr<Selection> selection = mEditorBase->GetSelection();
   NS_ENSURE_TRUE(selection, NS_ERROR_NOT_INITIALIZED);
 
   nsresult rv = mTextNode->DeleteData(mOffset, mStringToInsert.Length());
   NS_ENSURE_SUCCESS(rv, rv);
 
   // set the selection to the insertion point where the string was removed
   rv = selection->Collapse(mTextNode, mOffset);
   NS_ASSERTION(NS_SUCCEEDED(rv),
@@ -166,17 +175,20 @@ CompositionTransaction::GetTxnDescriptio
   return NS_OK;
 }
 
 /* ============ private methods ================== */
 
 nsresult
 CompositionTransaction::SetSelectionForRanges()
 {
-  return SetIMESelection(mEditorBase, mTextNode, mOffset,
+  if (NS_WARN_IF(!mEditorBase)) {
+    return NS_ERROR_NOT_INITIALIZED;
+  }
+  return SetIMESelection(*mEditorBase, mTextNode, mOffset,
                          mStringToInsert.Length(), mRanges);
 }
 
 // static
 nsresult
 CompositionTransaction::SetIMESelection(EditorBase& aEditorBase,
                                         Text* aTextNode,
                                         uint32_t aOffsetInNode,
--- a/editor/libeditor/CompositionTransaction.h
+++ b/editor/libeditor/CompositionTransaction.h
@@ -85,17 +85,17 @@ private:
 
   // The range list.
   RefPtr<TextRangeArray> mRanges;
 
   // The text to insert into mTextNode at mOffset.
   nsString mStringToInsert;
 
   // The editor, which is used to get the selection controller.
-  EditorBase& mEditorBase;
+  RefPtr<EditorBase> mEditorBase;
 
   RangeUpdater* mRangeUpdater;
 
   bool mFixed;
 };
 
 NS_DEFINE_STATIC_IID_ACCESSOR(CompositionTransaction, NS_IMETEXTTXN_IID)
 
--- a/editor/libeditor/CreateElementTransaction.cpp
+++ b/editor/libeditor/CreateElementTransaction.cpp
@@ -44,30 +44,33 @@ CreateElementTransaction::CreateElementT
 }
 
 CreateElementTransaction::~CreateElementTransaction()
 {
 }
 
 NS_IMPL_CYCLE_COLLECTION_INHERITED(CreateElementTransaction,
                                    EditTransactionBase,
+                                   mEditorBase,
                                    mParent,
                                    mNewNode,
                                    mRefNode)
 
 NS_IMPL_ADDREF_INHERITED(CreateElementTransaction, EditTransactionBase)
 NS_IMPL_RELEASE_INHERITED(CreateElementTransaction, EditTransactionBase)
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(CreateElementTransaction)
 NS_INTERFACE_MAP_END_INHERITING(EditTransactionBase)
 
 
 NS_IMETHODIMP
 CreateElementTransaction::DoTransaction()
 {
-  MOZ_ASSERT(mEditorBase && mTag && mParent);
+  if (NS_WARN_IF(!mEditorBase) || NS_WARN_IF(!mTag) || NS_WARN_IF(!mParent)) {
+    return NS_ERROR_NOT_INITIALIZED;
+  }
 
   mNewNode = mEditorBase->CreateHTMLContent(mTag);
   NS_ENSURE_STATE(mNewNode);
 
   // Try to insert formatting whitespace for the new node:
   mEditorBase->MarkNodeDirty(GetAsDOMNode(mNewNode));
 
   // Insert the new node
@@ -100,28 +103,32 @@ CreateElementTransaction::DoTransaction(
   NS_ASSERTION(!rv.Failed(),
                "selection could not be collapsed after insert");
   return NS_OK;
 }
 
 NS_IMETHODIMP
 CreateElementTransaction::UndoTransaction()
 {
-  MOZ_ASSERT(mEditorBase && mParent);
+  if (NS_WARN_IF(!mEditorBase) || NS_WARN_IF(!mParent)) {
+    return NS_ERROR_NOT_INITIALIZED;
+  }
 
   ErrorResult rv;
   mParent->RemoveChild(*mNewNode, rv);
 
   return rv.StealNSResult();
 }
 
 NS_IMETHODIMP
 CreateElementTransaction::RedoTransaction()
 {
-  MOZ_ASSERT(mEditorBase && mParent);
+  if (NS_WARN_IF(!mEditorBase) || NS_WARN_IF(!mParent)) {
+    return NS_ERROR_NOT_INITIALIZED;
+  }
 
   // First, reset mNewNode so it has no attributes or content
   // XXX We never actually did this, we only cleared mNewNode's contents if it
   // was a CharacterData node (which it's not, it's an Element)
 
   // Now, reinsert mNewNode
   ErrorResult rv;
   nsCOMPtr<nsIContent> refNode = mRefNode;
--- a/editor/libeditor/CreateElementTransaction.h
+++ b/editor/libeditor/CreateElementTransaction.h
@@ -52,17 +52,17 @@ public:
   NS_IMETHOD RedoTransaction() override;
 
   already_AddRefed<dom::Element> GetNewNode();
 
 protected:
   virtual ~CreateElementTransaction();
 
   // The document into which the new node will be inserted.
-  EditorBase* mEditorBase;
+  RefPtr<EditorBase> mEditorBase;
 
   // The tag (mapping to object type) for the new element.
   nsCOMPtr<nsIAtom> mTag;
 
   // The node into which the new node will be inserted.
   nsCOMPtr<nsINode> mParent;
 
   // The index in mParent for the new node.
--- a/editor/libeditor/DeleteNodeTransaction.cpp
+++ b/editor/libeditor/DeleteNodeTransaction.cpp
@@ -10,46 +10,47 @@
 #include "nsError.h"
 #include "nsAString.h"
 
 namespace mozilla {
 
 DeleteNodeTransaction::DeleteNodeTransaction(EditorBase& aEditorBase,
                                              nsINode& aNodeToDelete,
                                              RangeUpdater* aRangeUpdater)
-  : mEditorBase(aEditorBase)
+  : mEditorBase(&aEditorBase)
   , mNodeToDelete(&aNodeToDelete)
   , mParentNode(aNodeToDelete.GetParentNode())
   , mRangeUpdater(aRangeUpdater)
 {
   // XXX We're not sure if this is really necessary.
   if (!CanDoIt()) {
     mRangeUpdater = nullptr;
   }
 }
 
 DeleteNodeTransaction::~DeleteNodeTransaction()
 {
 }
 
 NS_IMPL_CYCLE_COLLECTION_INHERITED(DeleteNodeTransaction, EditTransactionBase,
+                                   mEditorBase,
                                    mNodeToDelete,
                                    mParentNode,
                                    mRefNode)
 
 NS_IMPL_ADDREF_INHERITED(DeleteNodeTransaction, EditTransactionBase)
 NS_IMPL_RELEASE_INHERITED(DeleteNodeTransaction, EditTransactionBase)
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(DeleteNodeTransaction)
 NS_INTERFACE_MAP_END_INHERITING(EditTransactionBase)
 
 bool
 DeleteNodeTransaction::CanDoIt() const
 {
-  if (NS_WARN_IF(!mNodeToDelete) || !mParentNode ||
-      !mEditorBase.IsModifiableNode(mParentNode)) {
+  if (NS_WARN_IF(!mNodeToDelete) || NS_WARN_IF(!mEditorBase) ||
+      !mParentNode || !mEditorBase->IsModifiableNode(mParentNode)) {
     return false;
   }
   return true;
 }
 
 NS_IMETHODIMP
 DeleteNodeTransaction::DoTransaction()
 {
--- a/editor/libeditor/DeleteNodeTransaction.h
+++ b/editor/libeditor/DeleteNodeTransaction.h
@@ -41,17 +41,17 @@ public:
   NS_DECL_EDITTRANSACTIONBASE
 
   NS_IMETHOD RedoTransaction() override;
 
 protected:
   virtual ~DeleteNodeTransaction();
 
   // The editor for this transaction.
-  EditorBase& mEditorBase;
+  RefPtr<EditorBase> mEditorBase;
 
   // The element to delete.
   nsCOMPtr<nsINode> mNodeToDelete;
 
   // Parent of node to delete.
   nsCOMPtr<nsINode> mParentNode;
 
   // Next sibling to remember for undo/redo purposes.
--- a/editor/libeditor/DeleteRangeTransaction.cpp
+++ b/editor/libeditor/DeleteRangeTransaction.cpp
@@ -22,33 +22,36 @@
 namespace mozilla {
 
 using namespace dom;
 
 // note that aEditorBase is not refcounted
 DeleteRangeTransaction::DeleteRangeTransaction(EditorBase& aEditorBase,
                                                nsRange& aRangeToDelete,
                                                RangeUpdater* aRangeUpdater)
-  : mEditorBase(aEditorBase)
+  : mEditorBase(&aEditorBase)
   , mRangeToDelete(aRangeToDelete.CloneRange())
   , mRangeUpdater(aRangeUpdater)
 {
 }
 
 NS_IMPL_CYCLE_COLLECTION_INHERITED(DeleteRangeTransaction,
                                    EditAggregateTransaction,
+                                   mEditorBase,
                                    mRangeToDelete)
 
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(DeleteRangeTransaction)
 NS_INTERFACE_MAP_END_INHERITING(EditAggregateTransaction)
 
 NS_IMETHODIMP
 DeleteRangeTransaction::DoTransaction()
 {
-  MOZ_ASSERT(mRangeToDelete);
+  if (NS_WARN_IF(!mEditorBase) || NS_WARN_IF(!mRangeToDelete)) {
+    return NS_ERROR_NOT_AVAILABLE;
+  }
 
   // build the child transactions
   nsCOMPtr<nsINode> startParent = mRangeToDelete->GetStartParent();
   int32_t startOffset = mRangeToDelete->StartOffset();
   nsCOMPtr<nsINode> endParent = mRangeToDelete->GetEndParent();
   int32_t endOffset = mRangeToDelete->EndOffset();
   MOZ_ASSERT(startParent && endParent);
 
@@ -72,19 +75,19 @@ DeleteRangeTransaction::DoTransaction()
   }
 
   // if we've successfully built this aggregate transaction, then do it.
   nsresult rv = EditAggregateTransaction::DoTransaction();
   NS_ENSURE_SUCCESS(rv, rv);
 
   // only set selection to deletion point if editor gives permission
   bool bAdjustSelection;
-  mEditorBase.ShouldTxnSetSelection(&bAdjustSelection);
+  mEditorBase->ShouldTxnSetSelection(&bAdjustSelection);
   if (bAdjustSelection) {
-    RefPtr<Selection> selection = mEditorBase.GetSelection();
+    RefPtr<Selection> selection = mEditorBase->GetSelection();
     NS_ENSURE_TRUE(selection, NS_ERROR_NULL_POINTER);
     rv = selection->Collapse(startParent, startOffset);
     NS_ENSURE_SUCCESS(rv, rv);
   }
   // else do nothing - dom range gravity will adjust selection
 
   return NS_OK;
 }
@@ -112,31 +115,35 @@ DeleteRangeTransaction::GetTxnDescriptio
   return NS_OK;
 }
 
 nsresult
 DeleteRangeTransaction::CreateTxnsToDeleteBetween(nsINode* aNode,
                                                   int32_t aStartOffset,
                                                   int32_t aEndOffset)
 {
+  if (NS_WARN_IF(!mEditorBase)) {
+    return NS_ERROR_NOT_AVAILABLE;
+  }
+
   // see what kind of node we have
   if (aNode->IsNodeOfType(nsINode::eDATA_NODE)) {
     // if the node is a chardata node, then delete chardata content
     int32_t numToDel;
     if (aStartOffset == aEndOffset) {
       numToDel = 1;
     } else {
       numToDel = aEndOffset - aStartOffset;
     }
 
     RefPtr<nsGenericDOMDataNode> charDataNode =
       static_cast<nsGenericDOMDataNode*>(aNode);
 
     RefPtr<DeleteTextTransaction> deleteTextTransaction =
-      new DeleteTextTransaction(mEditorBase, *charDataNode, aStartOffset,
+      new DeleteTextTransaction(*mEditorBase, *charDataNode, aStartOffset,
                                 numToDel, mRangeUpdater);
     // If the text node isn't editable, it should be never undone/redone.
     // So, the transaction shouldn't be recorded.
     if (NS_WARN_IF(!deleteTextTransaction->CanDoIt())) {
       return NS_ERROR_FAILURE;
     }
     AppendChild(deleteTextTransaction);
     return NS_OK;
@@ -145,17 +152,17 @@ DeleteRangeTransaction::CreateTxnsToDele
   nsCOMPtr<nsIContent> child = aNode->GetChildAt(aStartOffset);
   for (int32_t i = aStartOffset; i < aEndOffset; ++i) {
     // Even if we detect invalid range, we should ignore it for removing
     // specified range's nodes as far as possible.
     if (NS_WARN_IF(!child)) {
       break;
     }
     RefPtr<DeleteNodeTransaction> deleteNodeTransaction =
-      new DeleteNodeTransaction(mEditorBase, *child, mRangeUpdater);
+      new DeleteNodeTransaction(*mEditorBase, *child, mRangeUpdater);
     // XXX This is odd handling.  Even if some children are not editable,
     //     editor should append transactions because they could be editable
     //     at undoing/redoing.  Additionally, if the transaction needs to
     //     delete/restore all nodes, it should at undoing/redoing.
     if (deleteNodeTransaction->CanDoIt()) {
       AppendChild(deleteNodeTransaction);
     }
     child = child->GetNextSibling();
@@ -164,62 +171,70 @@ DeleteRangeTransaction::CreateTxnsToDele
   return NS_OK;
 }
 
 nsresult
 DeleteRangeTransaction::CreateTxnsToDeleteContent(nsINode* aNode,
                                                   int32_t aOffset,
                                                   nsIEditor::EDirection aAction)
 {
+  if (NS_WARN_IF(!mEditorBase)) {
+    return NS_ERROR_NOT_AVAILABLE;
+  }
+
   // see what kind of node we have
   if (aNode->IsNodeOfType(nsINode::eDATA_NODE)) {
     // if the node is a chardata node, then delete chardata content
     uint32_t start, numToDelete;
     if (nsIEditor::eNext == aAction) {
       start = aOffset;
       numToDelete = aNode->Length() - aOffset;
     } else {
       start = 0;
       numToDelete = aOffset;
     }
 
     if (numToDelete) {
       RefPtr<nsGenericDOMDataNode> dataNode =
         static_cast<nsGenericDOMDataNode*>(aNode);
       RefPtr<DeleteTextTransaction> deleteTextTransaction =
-        new DeleteTextTransaction(mEditorBase, *dataNode, start, numToDelete,
+        new DeleteTextTransaction(*mEditorBase, *dataNode, start, numToDelete,
                                   mRangeUpdater);
       // If the text node isn't editable, it should be never undone/redone.
       // So, the transaction shouldn't be recorded.
       if (NS_WARN_IF(!deleteTextTransaction->CanDoIt())) {
         return NS_ERROR_FAILURE;
       }
       AppendChild(deleteTextTransaction);
     }
   }
 
   return NS_OK;
 }
 
 nsresult
 DeleteRangeTransaction::CreateTxnsToDeleteNodesBetween()
 {
+  if (NS_WARN_IF(!mEditorBase)) {
+    return NS_ERROR_NOT_AVAILABLE;
+  }
+
   nsCOMPtr<nsIContentIterator> iter = NS_NewContentSubtreeIterator();
 
   nsresult rv = iter->Init(mRangeToDelete);
   NS_ENSURE_SUCCESS(rv, rv);
 
   while (!iter->IsDone()) {
     nsCOMPtr<nsINode> node = iter->GetCurrentNode();
     if (NS_WARN_IF(!node)) {
       return NS_ERROR_NULL_POINTER;
     }
 
     RefPtr<DeleteNodeTransaction> deleteNodeTransaction =
-      new DeleteNodeTransaction(mEditorBase, *node, mRangeUpdater);
+      new DeleteNodeTransaction(*mEditorBase, *node, mRangeUpdater);
     // XXX This is odd handling.  Even if some nodes in the range are not
     //     editable, editor should append transactions because they could
     //     at undoing/redoing.  Additionally, if the transaction needs to
     //     delete/restore all nodes, it should at undoing/redoing.
     if (NS_WARN_IF(!deleteNodeTransaction->CanDoIt())) {
       return NS_ERROR_FAILURE;
     }
     AppendChild(deleteNodeTransaction);
--- a/editor/libeditor/DeleteRangeTransaction.h
+++ b/editor/libeditor/DeleteRangeTransaction.h
@@ -56,17 +56,17 @@ protected:
 
   nsresult CreateTxnsToDeleteNodesBetween();
 
   nsresult CreateTxnsToDeleteContent(nsINode* aParent,
                                      int32_t aOffset,
                                      nsIEditor::EDirection aAction);
 
   // The editor for this transaction.
-  EditorBase& mEditorBase;
+  RefPtr<EditorBase> mEditorBase;
 
   // P1 in the range.
   RefPtr<nsRange> mRangeToDelete;
 
   // Range updater object.
   RangeUpdater* mRangeUpdater;
 };
 
--- a/editor/libeditor/DeleteTextTransaction.cpp
+++ b/editor/libeditor/DeleteTextTransaction.cpp
@@ -20,77 +20,81 @@ namespace mozilla {
 using namespace dom;
 
 DeleteTextTransaction::DeleteTextTransaction(
                          EditorBase& aEditorBase,
                          nsGenericDOMDataNode& aCharData,
                          uint32_t aOffset,
                          uint32_t aNumCharsToDelete,
                          RangeUpdater* aRangeUpdater)
-  : mEditorBase(aEditorBase)
+  : mEditorBase(&aEditorBase)
   , mCharData(&aCharData)
   , mOffset(aOffset)
   , mNumCharsToDelete(aNumCharsToDelete)
   , mRangeUpdater(aRangeUpdater)
 {
   NS_ASSERTION(mCharData->Length() >= aOffset + aNumCharsToDelete,
                "Trying to delete more characters than in node");
 }
 
 NS_IMPL_CYCLE_COLLECTION_INHERITED(DeleteTextTransaction, EditTransactionBase,
+                                   mEditorBase,
                                    mCharData)
 
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(DeleteTextTransaction)
 NS_INTERFACE_MAP_END_INHERITING(EditTransactionBase)
 
 bool
 DeleteTextTransaction::CanDoIt() const
 {
-  if (NS_WARN_IF(!mCharData)) {
+  if (NS_WARN_IF(!mCharData) || NS_WARN_IF(!mEditorBase)) {
     return false;
   }
-  return mEditorBase.IsModifiableNode(mCharData);
+  return mEditorBase->IsModifiableNode(mCharData);
 }
 
 NS_IMETHODIMP
 DeleteTextTransaction::DoTransaction()
 {
-  MOZ_ASSERT(mCharData);
+  if (NS_WARN_IF(!mEditorBase) || NS_WARN_IF(!mCharData)) {
+    return NS_ERROR_NOT_AVAILABLE;
+  }
 
   // Get the text that we're about to delete
   nsresult rv = mCharData->SubstringData(mOffset, mNumCharsToDelete,
                                          mDeletedText);
   MOZ_ASSERT(NS_SUCCEEDED(rv));
   rv = mCharData->DeleteData(mOffset, mNumCharsToDelete);
   NS_ENSURE_SUCCESS(rv, rv);
 
   if (mRangeUpdater) {
     mRangeUpdater->SelAdjDeleteText(mCharData, mOffset, mNumCharsToDelete);
   }
 
   // Only set selection to deletion point if editor gives permission
-  if (mEditorBase.GetShouldTxnSetSelection()) {
-    RefPtr<Selection> selection = mEditorBase.GetSelection();
+  if (mEditorBase->GetShouldTxnSetSelection()) {
+    RefPtr<Selection> selection = mEditorBase->GetSelection();
     NS_ENSURE_TRUE(selection, NS_ERROR_NULL_POINTER);
     rv = selection->Collapse(mCharData, mOffset);
     NS_ASSERTION(NS_SUCCEEDED(rv),
                  "Selection could not be collapsed after undo of deletetext");
     NS_ENSURE_SUCCESS(rv, rv);
   }
   // Else do nothing - DOM Range gravity will adjust selection
   return NS_OK;
 }
 
 //XXX: We may want to store the selection state and restore it properly.  Was
 //     it an insertion point or an extended selection?
 NS_IMETHODIMP
 DeleteTextTransaction::UndoTransaction()
 {
-  MOZ_ASSERT(mCharData);
-
+  if (NS_WARN_IF(!mCharData)) {
+    return NS_ERROR_NOT_INITIALIZED;
+  }
   return mCharData->InsertData(mOffset, mDeletedText);
 }
 
 NS_IMETHODIMP
 DeleteTextTransaction::GetTxnDescription(nsAString& aString)
 {
   aString.AssignLiteral("DeleteTextTransaction: ");
   aString += mDeletedText;
--- a/editor/libeditor/DeleteTextTransaction.h
+++ b/editor/libeditor/DeleteTextTransaction.h
@@ -52,17 +52,17 @@ public:
   NS_DECL_EDITTRANSACTIONBASE
 
   uint32_t GetOffset() { return mOffset; }
 
   uint32_t GetNumCharsToDelete() { return mNumCharsToDelete; }
 
 protected:
   // The provider of basic editing operations.
-  EditorBase& mEditorBase;
+  RefPtr<EditorBase> mEditorBase;
 
   // The CharacterData node to operate upon.
   RefPtr<nsGenericDOMDataNode> mCharData;
 
   // The offset into mCharData where the deletion is to take place.
   uint32_t mOffset;
 
   // The number of characters to delete.
--- a/editor/libeditor/EditorBase.cpp
+++ b/editor/libeditor/EditorBase.cpp
@@ -460,16 +460,23 @@ EditorBase::PreDestroy(bool aDestroyingF
   HideCaret(false);
   mActionListeners.Clear();
   mEditorObservers.Clear();
   mDocStateListeners.Clear();
   mInlineSpellChecker = nullptr;
   mSpellcheckCheckboxState = eTriUnset;
   mRootElement = nullptr;
 
+  // Transaction may grab this instance.  Therefore, they should be released
+  // here for stopping the circular reference with this instance.
+  if (mTxnMgr) {
+    mTxnMgr->Clear();
+    mTxnMgr = nullptr;
+  }
+
   mDidPreDestroy = true;
   return NS_OK;
 }
 
 NS_IMETHODIMP
 EditorBase::GetFlags(uint32_t* aFlags)
 {
   *aFlags = mFlags;
--- a/editor/libeditor/HTMLEditRules.cpp
+++ b/editor/libeditor/HTMLEditRules.cpp
@@ -7296,61 +7296,65 @@ HTMLEditRules::AdjustWhitespace(Selectio
 nsresult
 HTMLEditRules::PinSelectionToNewBlock(Selection* aSelection)
 {
   NS_ENSURE_TRUE(aSelection, NS_ERROR_NULL_POINTER);
   if (!aSelection->Collapsed()) {
     return NS_OK;
   }
 
+  if (NS_WARN_IF(!mNewBlock)) {
+    return NS_ERROR_NULL_POINTER;
+  }
+
   // get the (collapsed) selection location
-  nsCOMPtr<nsIDOMNode> selNode, temp;
+  nsCOMPtr<nsIDOMNode> selNode;
   int32_t selOffset;
   nsresult rv =
     EditorBase::GetStartNodeAndOffset(aSelection,
                                       getter_AddRefs(selNode), &selOffset);
   NS_ENSURE_SUCCESS(rv, rv);
-  temp = selNode;
 
   // use ranges and sRangeHelper to compare sel point to new block
   nsCOMPtr<nsINode> node = do_QueryInterface(selNode);
   NS_ENSURE_STATE(node);
   RefPtr<nsRange> range = new nsRange(node);
   rv = range->SetStart(selNode, selOffset);
   NS_ENSURE_SUCCESS(rv, rv);
   rv = range->SetEnd(selNode, selOffset);
   NS_ENSURE_SUCCESS(rv, rv);
-  nsCOMPtr<nsIContent> block = mNewBlock.get();
-  NS_ENSURE_TRUE(block, NS_ERROR_NO_INTERFACE);
   bool nodeBefore, nodeAfter;
-  rv = nsRange::CompareNodeToRange(block, range, &nodeBefore, &nodeAfter);
+  rv = nsRange::CompareNodeToRange(mNewBlock, range, &nodeBefore, &nodeAfter);
   NS_ENSURE_SUCCESS(rv, rv);
 
   if (nodeBefore && nodeAfter) {
     return NS_OK;  // selection is inside block
   } else if (nodeBefore) {
     // selection is after block.  put at end of block.
-    nsCOMPtr<nsIDOMNode> tmp = GetAsDOMNode(mNewBlock);
     NS_ENSURE_STATE(mHTMLEditor);
-    tmp = GetAsDOMNode(mHTMLEditor->GetLastEditableChild(*block));
+    nsCOMPtr<nsINode> tmp = mHTMLEditor->GetLastEditableChild(*mNewBlock);
+    if (!tmp) {
+      tmp = mNewBlock;
+    }
     uint32_t endPoint;
     if (EditorBase::IsTextNode(tmp) ||
         mHTMLEditor->IsContainer(tmp)) {
-      rv = EditorBase::GetLengthOfDOMNode(tmp, endPoint);
-      NS_ENSURE_SUCCESS(rv, rv);
+      endPoint = tmp->Length();
     } else {
       tmp = EditorBase::GetNodeLocation(tmp, (int32_t*)&endPoint);
       endPoint++;  // want to be after this node
     }
     return aSelection->Collapse(tmp, (int32_t)endPoint);
   } else {
     // selection is before block.  put at start of block.
-    nsCOMPtr<nsIDOMNode> tmp = GetAsDOMNode(mNewBlock);
     NS_ENSURE_STATE(mHTMLEditor);
-    tmp = GetAsDOMNode(mHTMLEditor->GetFirstEditableChild(*block));
+    nsCOMPtr<nsINode> tmp = mHTMLEditor->GetFirstEditableChild(*mNewBlock);
+    if (!tmp) {
+      tmp = mNewBlock;
+    }
     int32_t offset;
     if (EditorBase::IsTextNode(tmp) ||
         mHTMLEditor->IsContainer(tmp)) {
       tmp = EditorBase::GetNodeLocation(tmp, &offset);
     }
     return aSelection->Collapse(tmp, 0);
   }
 }
--- a/editor/libeditor/InsertNodeTransaction.cpp
+++ b/editor/libeditor/InsertNodeTransaction.cpp
@@ -23,70 +23,74 @@ using namespace dom;
 
 InsertNodeTransaction::InsertNodeTransaction(nsIContent& aNode,
                                              nsINode& aParent,
                                              int32_t aOffset,
                                              EditorBase& aEditorBase)
   : mNode(&aNode)
   , mParent(&aParent)
   , mOffset(aOffset)
-  , mEditorBase(aEditorBase)
+  , mEditorBase(&aEditorBase)
 {
 }
 
 InsertNodeTransaction::~InsertNodeTransaction()
 {
 }
 
 NS_IMPL_CYCLE_COLLECTION_INHERITED(InsertNodeTransaction, EditTransactionBase,
+                                   mEditorBase,
                                    mNode,
                                    mParent)
 
 NS_IMPL_ADDREF_INHERITED(InsertNodeTransaction, EditTransactionBase)
 NS_IMPL_RELEASE_INHERITED(InsertNodeTransaction, EditTransactionBase)
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(InsertNodeTransaction)
 NS_INTERFACE_MAP_END_INHERITING(EditTransactionBase)
 
 NS_IMETHODIMP
 InsertNodeTransaction::DoTransaction()
 {
-  MOZ_ASSERT(mNode && mParent);
+  if (NS_WARN_IF(!mEditorBase) || NS_WARN_IF(!mNode) || NS_WARN_IF(!mParent)) {
+    return NS_ERROR_NOT_INITIALIZED;
+  }
 
   uint32_t count = mParent->GetChildCount();
   if (mOffset > static_cast<int32_t>(count) || mOffset == -1) {
     // -1 is sentinel value meaning "append at end"
     mOffset = count;
   }
 
   // Note, it's ok for ref to be null. That means append.
   nsCOMPtr<nsIContent> ref = mParent->GetChildAt(mOffset);
 
-  mEditorBase.MarkNodeDirty(GetAsDOMNode(mNode));
+  mEditorBase->MarkNodeDirty(GetAsDOMNode(mNode));
 
   ErrorResult rv;
   mParent->InsertBefore(*mNode, ref, rv);
   NS_ENSURE_TRUE(!rv.Failed(), rv.StealNSResult());
 
   // Only set selection to insertion point if editor gives permission
-  if (mEditorBase.GetShouldTxnSetSelection()) {
-    RefPtr<Selection> selection = mEditorBase.GetSelection();
+  if (mEditorBase->GetShouldTxnSetSelection()) {
+    RefPtr<Selection> selection = mEditorBase->GetSelection();
     NS_ENSURE_TRUE(selection, NS_ERROR_NULL_POINTER);
     // Place the selection just after the inserted element
     selection->Collapse(mParent, mOffset + 1);
   } else {
     // Do nothing - DOM Range gravity will adjust selection
   }
   return NS_OK;
 }
 
 NS_IMETHODIMP
 InsertNodeTransaction::UndoTransaction()
 {
-  MOZ_ASSERT(mNode && mParent);
-
+  if (NS_WARN_IF(!mNode) || NS_WARN_IF(!mParent)) {
+    return NS_ERROR_NOT_INITIALIZED;
+  }
   ErrorResult rv;
   mParent->RemoveChild(*mNode, rv);
   return rv.StealNSResult();
 }
 
 NS_IMETHODIMP
 InsertNodeTransaction::GetTxnDescription(nsAString& aString)
 {
--- a/editor/libeditor/InsertNodeTransaction.h
+++ b/editor/libeditor/InsertNodeTransaction.h
@@ -45,14 +45,14 @@ protected:
 
   // The node into which the new node will be inserted.
   nsCOMPtr<nsINode> mParent;
 
   // The index in mParent for the new node.
   int32_t mOffset;
 
   // The editor for this transaction.
-  EditorBase& mEditorBase;
+  RefPtr<EditorBase> mEditorBase;
 };
 
 } // namespace mozilla
 
 #endif // #ifndef InsertNodeTransaction_h
--- a/editor/libeditor/InsertTextTransaction.cpp
+++ b/editor/libeditor/InsertTextTransaction.cpp
@@ -21,46 +21,51 @@ using namespace dom;
 InsertTextTransaction::InsertTextTransaction(Text& aTextNode,
                                              uint32_t aOffset,
                                              const nsAString& aStringToInsert,
                                              EditorBase& aEditorBase,
                                              RangeUpdater* aRangeUpdater)
   : mTextNode(&aTextNode)
   , mOffset(aOffset)
   , mStringToInsert(aStringToInsert)
-  , mEditorBase(aEditorBase)
+  , mEditorBase(&aEditorBase)
   , mRangeUpdater(aRangeUpdater)
 {
 }
 
 InsertTextTransaction::~InsertTextTransaction()
 {
 }
 
 NS_IMPL_CYCLE_COLLECTION_INHERITED(InsertTextTransaction, EditTransactionBase,
+                                   mEditorBase,
                                    mTextNode)
 
 NS_IMPL_ADDREF_INHERITED(InsertTextTransaction, EditTransactionBase)
 NS_IMPL_RELEASE_INHERITED(InsertTextTransaction, EditTransactionBase)
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(InsertTextTransaction)
   if (aIID.Equals(NS_GET_IID(InsertTextTransaction))) {
     foundInterface = static_cast<nsITransaction*>(this);
   } else
 NS_INTERFACE_MAP_END_INHERITING(EditTransactionBase)
 
 
 NS_IMETHODIMP
 InsertTextTransaction::DoTransaction()
 {
+  if (NS_WARN_IF(!mEditorBase) || NS_WARN_IF(!mTextNode)) {
+    return NS_ERROR_NOT_AVAILABLE;
+  }
+
   nsresult rv = mTextNode->InsertData(mOffset, mStringToInsert);
   NS_ENSURE_SUCCESS(rv, rv);
 
   // Only set selection to insertion point if editor gives permission
-  if (mEditorBase.GetShouldTxnSetSelection()) {
-    RefPtr<Selection> selection = mEditorBase.GetSelection();
+  if (mEditorBase->GetShouldTxnSetSelection()) {
+    RefPtr<Selection> selection = mEditorBase->GetSelection();
     NS_ENSURE_TRUE(selection, NS_ERROR_NULL_POINTER);
     DebugOnly<nsresult> rv =
       selection->Collapse(mTextNode, mOffset + mStringToInsert.Length());
     NS_ASSERTION(NS_SUCCEEDED(rv),
                  "Selection could not be collapsed after insert");
   } else {
     // Do nothing - DOM Range gravity will adjust selection
   }
--- a/editor/libeditor/InsertTextTransaction.h
+++ b/editor/libeditor/InsertTextTransaction.h
@@ -71,17 +71,17 @@ private:
 
   // The offset into mTextNode where the insertion is to take place.
   uint32_t mOffset;
 
   // The text to insert into mTextNode at mOffset.
   nsString mStringToInsert;
 
   // The editor, which we'll need to get the selection.
-  EditorBase& mEditorBase;
+  RefPtr<EditorBase> mEditorBase;
 
   RangeUpdater* mRangeUpdater;
 };
 
 NS_DEFINE_STATIC_IID_ACCESSOR(InsertTextTransaction, NS_INSERTTEXTTXN_IID)
 
 } // namespace mozilla
 
--- a/editor/libeditor/JoinNodeTransaction.cpp
+++ b/editor/libeditor/JoinNodeTransaction.cpp
@@ -16,71 +16,83 @@
 
 namespace mozilla {
 
 using namespace dom;
 
 JoinNodeTransaction::JoinNodeTransaction(EditorBase& aEditorBase,
                                          nsINode& aLeftNode,
                                          nsINode& aRightNode)
-  : mEditorBase(aEditorBase)
+  : mEditorBase(&aEditorBase)
   , mLeftNode(&aLeftNode)
   , mRightNode(&aRightNode)
   , mOffset(0)
 {
 }
 
 NS_IMPL_CYCLE_COLLECTION_INHERITED(JoinNodeTransaction, EditTransactionBase,
+                                   mEditorBase,
                                    mLeftNode,
                                    mRightNode,
                                    mParent)
 
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(JoinNodeTransaction)
 NS_INTERFACE_MAP_END_INHERITING(EditTransactionBase)
 
 bool
 JoinNodeTransaction::CanDoIt() const
 {
   if (NS_WARN_IF(!mLeftNode) ||
       NS_WARN_IF(!mRightNode) ||
+      NS_WARN_IF(!mEditorBase) ||
       !mLeftNode->GetParentNode()) {
     return false;
   }
-  return mEditorBase.IsModifiableNode(mLeftNode->GetParentNode());
+  return mEditorBase->IsModifiableNode(mLeftNode->GetParentNode());
 }
 
 // After DoTransaction() and RedoTransaction(), the left node is removed from
 // the content tree and right node remains.
 NS_IMETHODIMP
 JoinNodeTransaction::DoTransaction()
 {
+  if (NS_WARN_IF(!mEditorBase) ||
+      NS_WARN_IF(!mLeftNode) ||
+      NS_WARN_IF(!mRightNode)) {
+    return NS_ERROR_NOT_INITIALIZED;
+  }
+
   // Get the parent node
   nsCOMPtr<nsINode> leftParent = mLeftNode->GetParentNode();
   NS_ENSURE_TRUE(leftParent, NS_ERROR_NULL_POINTER);
 
   // Verify that mLeftNode and mRightNode have the same parent
   if (leftParent != mRightNode->GetParentNode()) {
     NS_ASSERTION(false, "Nodes do not have same parent");
     return NS_ERROR_INVALID_ARG;
   }
 
   // Set this instance's mParent.  Other methods will see a non-null mParent
   // and know all is well
   mParent = leftParent;
   mOffset = mLeftNode->Length();
 
-  return mEditorBase.JoinNodesImpl(mRightNode, mLeftNode, mParent);
+  return mEditorBase->JoinNodesImpl(mRightNode, mLeftNode, mParent);
 }
 
 //XXX: What if instead of split, we just deleted the unneeded children of
 //     mRight and re-inserted mLeft?
 NS_IMETHODIMP
 JoinNodeTransaction::UndoTransaction()
 {
-  MOZ_ASSERT(mParent);
+  if (NS_WARN_IF(!mParent) ||
+      NS_WARN_IF(!mLeftNode) ||
+      NS_WARN_IF(!mRightNode)) {
+    return NS_ERROR_NOT_INITIALIZED;
+  }
 
   // First, massage the existing node so it is in its post-split state
   ErrorResult rv;
   if (mRightNode->GetAsText()) {
     rv = mRightNode->GetAsText()->DeleteData(0, mOffset);
   } else {
     nsCOMPtr<nsIContent> child = mRightNode->GetFirstChild();
     for (uint32_t i = 0; i < mOffset; i++) {
--- a/editor/libeditor/JoinNodeTransaction.h
+++ b/editor/libeditor/JoinNodeTransaction.h
@@ -43,17 +43,17 @@ public:
 
   NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(JoinNodeTransaction,
                                            EditTransactionBase)
   NS_IMETHOD QueryInterface(REFNSIID aIID, void** aInstancePtr) override;
 
   NS_DECL_EDITTRANSACTIONBASE
 
 protected:
-  EditorBase& mEditorBase;
+  RefPtr<EditorBase> mEditorBase;
 
   // The nodes to operate upon.  After the merge, mRightNode remains and
   // mLeftNode is removed from the content tree.
   nsCOMPtr<nsINode> mLeftNode;
   nsCOMPtr<nsINode> mRightNode;
 
   // The offset into mNode where the children of mElement are split (for
   // undo). mOffset is the index of the first child in the right node.  -1
--- a/editor/libeditor/PlaceholderTransaction.cpp
+++ b/editor/libeditor/PlaceholderTransaction.cpp
@@ -20,40 +20,42 @@ PlaceholderTransaction::PlaceholderTrans
                           EditorBase& aEditorBase,
                           nsIAtom* aName,
                           UniquePtr<SelectionState> aSelState)
   : mAbsorb(true)
   , mForwarding(nullptr)
   , mCompositionTransaction(nullptr)
   , mCommitted(false)
   , mStartSel(Move(aSelState))
-  , mEditorBase(aEditorBase)
+  , mEditorBase(&aEditorBase)
 {
   mName = aName;
 }
 
 PlaceholderTransaction::~PlaceholderTransaction()
 {
 }
 
 NS_IMPL_CYCLE_COLLECTION_CLASS(PlaceholderTransaction)
 
 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(PlaceholderTransaction,
                                                 EditAggregateTransaction)
   if (tmp->mStartSel) {
     ImplCycleCollectionUnlink(*tmp->mStartSel);
   }
+  NS_IMPL_CYCLE_COLLECTION_UNLINK(mEditorBase);
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mEndSel);
 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
 
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(PlaceholderTransaction,
                                                   EditAggregateTransaction)
   if (tmp->mStartSel) {
     ImplCycleCollectionTraverse(cb, *tmp->mStartSel, "mStartSel", 0);
   }
+  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mEditorBase);
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mEndSel);
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
 
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(PlaceholderTransaction)
   NS_INTERFACE_MAP_ENTRY(nsIAbsorbingTransaction)
   NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
 NS_INTERFACE_MAP_END_INHERITING(EditAggregateTransaction)
 
@@ -64,37 +66,45 @@ NS_IMETHODIMP
 PlaceholderTransaction::DoTransaction()
 {
   return NS_OK;
 }
 
 NS_IMETHODIMP
 PlaceholderTransaction::UndoTransaction()
 {
+  if (NS_WARN_IF(!mEditorBase)) {
+    return NS_ERROR_NOT_INITIALIZED;
+  }
+
   // Undo transactions.
   nsresult rv = EditAggregateTransaction::UndoTransaction();
   NS_ENSURE_SUCCESS(rv, rv);
 
   NS_ENSURE_TRUE(mStartSel, NS_ERROR_NULL_POINTER);
 
   // now restore selection
-  RefPtr<Selection> selection = mEditorBase.GetSelection();
+  RefPtr<Selection> selection = mEditorBase->GetSelection();
   NS_ENSURE_TRUE(selection, NS_ERROR_NULL_POINTER);
   return mStartSel->RestoreSelection(selection);
 }
 
 NS_IMETHODIMP
 PlaceholderTransaction::RedoTransaction()
 {
+  if (NS_WARN_IF(!mEditorBase)) {
+    return NS_ERROR_NOT_INITIALIZED;
+  }
+
   // Redo transactions.
   nsresult rv = EditAggregateTransaction::RedoTransaction();
   NS_ENSURE_SUCCESS(rv, rv);
 
   // now restore selection
-  RefPtr<Selection> selection = mEditorBase.GetSelection();
+  RefPtr<Selection> selection = mEditorBase->GetSelection();
   NS_ENSURE_TRUE(selection, NS_ERROR_NULL_POINTER);
   return mEndSel.RestoreSelection(selection);
 }
 
 
 NS_IMETHODIMP
 PlaceholderTransaction::Merge(nsITransaction* aTransaction,
                               bool* aDidMerge)
@@ -249,15 +259,19 @@ PlaceholderTransaction::Commit()
 {
   mCommitted = true;
   return NS_OK;
 }
 
 nsresult
 PlaceholderTransaction::RememberEndingSelection()
 {
-  RefPtr<Selection> selection = mEditorBase.GetSelection();
+  if (NS_WARN_IF(!mEditorBase)) {
+    return NS_ERROR_NOT_INITIALIZED;
+  }
+
+  RefPtr<Selection> selection = mEditorBase->GetSelection();
   NS_ENSURE_TRUE(selection, NS_ERROR_NULL_POINTER);
   mEndSel.SaveSelection(selection);
   return NS_OK;
 }
 
 } // namespace mozilla
--- a/editor/libeditor/PlaceholderTransaction.h
+++ b/editor/libeditor/PlaceholderTransaction.h
@@ -77,14 +77,14 @@ protected:
   // at the end.  This is so that UndoTransaction() and RedoTransaction() can
   // restore the selection properly.
 
   // Use a pointer because this is constructed before we exist.
   UniquePtr<SelectionState> mStartSel;
   SelectionState mEndSel;
 
   // The editor for this transaction.
-  EditorBase& mEditorBase;
+  RefPtr<EditorBase> mEditorBase;
 };
 
 } // namespace mozilla
 
 #endif // #ifndef PlaceholderTransaction_h
--- a/editor/libeditor/SplitNodeTransaction.cpp
+++ b/editor/libeditor/SplitNodeTransaction.cpp
@@ -14,78 +14,90 @@
 
 namespace mozilla {
 
 using namespace dom;
 
 SplitNodeTransaction::SplitNodeTransaction(EditorBase& aEditorBase,
                                            nsIContent& aNode,
                                            int32_t aOffset)
-  : mEditorBase(aEditorBase)
+  : mEditorBase(&aEditorBase)
   , mExistingRightNode(&aNode)
   , mOffset(aOffset)
 {
 }
 
 SplitNodeTransaction::~SplitNodeTransaction()
 {
 }
 
 NS_IMPL_CYCLE_COLLECTION_INHERITED(SplitNodeTransaction, EditTransactionBase,
+                                   mEditorBase,
                                    mParent,
                                    mNewLeftNode)
 
 NS_IMPL_ADDREF_INHERITED(SplitNodeTransaction, EditTransactionBase)
 NS_IMPL_RELEASE_INHERITED(SplitNodeTransaction, EditTransactionBase)
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(SplitNodeTransaction)
 NS_INTERFACE_MAP_END_INHERITING(EditTransactionBase)
 
 NS_IMETHODIMP
 SplitNodeTransaction::DoTransaction()
 {
+  if (NS_WARN_IF(!mEditorBase)) {
+    return NS_ERROR_NOT_INITIALIZED;
+  }
+
   // Create a new node
   ErrorResult rv;
   // Don't use .downcast directly because AsContent has an assertion we want
   nsCOMPtr<nsINode> clone = mExistingRightNode->CloneNode(false, rv);
   NS_ASSERTION(!rv.Failed() && clone, "Could not create clone");
   NS_ENSURE_TRUE(!rv.Failed() && clone, rv.StealNSResult());
   mNewLeftNode = dont_AddRef(clone.forget().take()->AsContent());
-  mEditorBase.MarkNodeDirty(mExistingRightNode->AsDOMNode());
+  mEditorBase->MarkNodeDirty(mExistingRightNode->AsDOMNode());
 
   // Get the parent node
   mParent = mExistingRightNode->GetParentNode();
   NS_ENSURE_TRUE(mParent, NS_ERROR_NULL_POINTER);
 
   // Insert the new node
-  rv = mEditorBase.SplitNodeImpl(*mExistingRightNode, mOffset, *mNewLeftNode);
-  if (mEditorBase.GetShouldTxnSetSelection()) {
-    RefPtr<Selection> selection = mEditorBase.GetSelection();
+  rv = mEditorBase->SplitNodeImpl(*mExistingRightNode, mOffset, *mNewLeftNode);
+  if (mEditorBase->GetShouldTxnSetSelection()) {
+    RefPtr<Selection> selection = mEditorBase->GetSelection();
     NS_ENSURE_TRUE(selection, NS_ERROR_NULL_POINTER);
     rv = selection->Collapse(mNewLeftNode, mOffset);
   }
   return rv.StealNSResult();
 }
 
 NS_IMETHODIMP
 SplitNodeTransaction::UndoTransaction()
 {
-  MOZ_ASSERT(mNewLeftNode && mParent);
+  if (NS_WARN_IF(!mEditorBase) ||
+      NS_WARN_IF(!mNewLeftNode) ||
+      NS_WARN_IF(!mParent)) {
+    return NS_ERROR_NOT_INITIALIZED;
+  }
 
   // This assumes Do inserted the new node in front of the prior existing node
-  return mEditorBase.JoinNodesImpl(mExistingRightNode, mNewLeftNode, mParent);
+  return mEditorBase->JoinNodesImpl(mExistingRightNode, mNewLeftNode, mParent);
 }
 
 /* Redo cannot simply resplit the right node, because subsequent transactions
  * on the redo stack may depend on the left node existing in its previous
  * state.
  */
 NS_IMETHODIMP
 SplitNodeTransaction::RedoTransaction()
 {
-  MOZ_ASSERT(mNewLeftNode && mParent);
+  if (NS_WARN_IF(!mNewLeftNode) ||
+      NS_WARN_IF(!mParent)) {
+    return NS_ERROR_NOT_INITIALIZED;
+  }
 
   ErrorResult rv;
   // First, massage the existing node so it is in its post-split state
   if (mExistingRightNode->GetAsText()) {
     rv = mExistingRightNode->GetAsText()->DeleteData(0, mOffset);
     NS_ENSURE_TRUE(!rv.Failed(), rv.StealNSResult());
   } else {
     nsCOMPtr<nsIContent> child = mExistingRightNode->GetFirstChild();
--- a/editor/libeditor/SplitNodeTransaction.h
+++ b/editor/libeditor/SplitNodeTransaction.h
@@ -44,17 +44,17 @@ public:
 
   NS_IMETHOD RedoTransaction() override;
 
   nsIContent* GetNewNode();
 
 protected:
   virtual ~SplitNodeTransaction();
 
-  EditorBase& mEditorBase;
+  RefPtr<EditorBase> mEditorBase;
 
   // The node to operate upon.
   nsCOMPtr<nsIContent> mExistingRightNode;
 
   // The offset into mExistingRightNode where its children are split.  mOffset
   // is the index of the first child in the right node.  -1 means the new node
   // gets no children.
   int32_t mOffset;
--- a/editor/libeditor/StyleSheetTransactions.cpp
+++ b/editor/libeditor/StyleSheetTransactions.cpp
@@ -39,90 +39,97 @@ RemoveStyleSheet(EditorBase& aEditor, St
     doc->EndUpdate(UPDATE_STYLE);
   }
 }
 
 /******************************************************************************
  * AddStyleSheetTransaction
  ******************************************************************************/
 
-AddStyleSheetTransaction::AddStyleSheetTransaction(EditorBase& aEditor,
+AddStyleSheetTransaction::AddStyleSheetTransaction(EditorBase& aEditorBase,
                                                    StyleSheet* aSheet)
-  : mEditor(aEditor)
+  : mEditorBase(&aEditorBase)
   , mSheet(aSheet)
 {
   MOZ_ASSERT(aSheet);
 }
 
 NS_IMPL_CYCLE_COLLECTION_INHERITED(AddStyleSheetTransaction,
                                    EditTransactionBase,
+                                   mEditorBase,
                                    mSheet)
 
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(AddStyleSheetTransaction)
 NS_INTERFACE_MAP_END_INHERITING(EditTransactionBase)
 
 NS_IMETHODIMP
 AddStyleSheetTransaction::DoTransaction()
 {
-  NS_ENSURE_TRUE(mSheet, NS_ERROR_NOT_INITIALIZED);
-
-  AddStyleSheet(mEditor, mSheet);
+  if (NS_WARN_IF(!mEditorBase) || NS_WARN_IF(!mSheet)) {
+    return NS_ERROR_NOT_INITIALIZED;
+  }
+  AddStyleSheet(*mEditorBase, mSheet);
   return NS_OK;
 }
 
 NS_IMETHODIMP
 AddStyleSheetTransaction::UndoTransaction()
 {
-  NS_ENSURE_TRUE(mSheet, NS_ERROR_NOT_INITIALIZED);
-
-  RemoveStyleSheet(mEditor, mSheet);
+  if (NS_WARN_IF(!mEditorBase) || NS_WARN_IF(!mSheet)) {
+    return NS_ERROR_NOT_INITIALIZED;
+  }
+  RemoveStyleSheet(*mEditorBase, mSheet);
   return NS_OK;
 }
 
 NS_IMETHODIMP
 AddStyleSheetTransaction::GetTxnDescription(nsAString& aString)
 {
   aString.AssignLiteral("AddStyleSheetTransaction");
   return NS_OK;
 }
 
 /******************************************************************************
  * RemoveStyleSheetTransaction
  ******************************************************************************/
 
-RemoveStyleSheetTransaction::RemoveStyleSheetTransaction(EditorBase& aEditor,
-                                                         StyleSheet* aSheet)
-  : mEditor(aEditor)
+RemoveStyleSheetTransaction::RemoveStyleSheetTransaction(
+                               EditorBase& aEditorBase,
+                               StyleSheet* aSheet)
+  : mEditorBase(&aEditorBase)
   , mSheet(aSheet)
 {
   MOZ_ASSERT(aSheet);
 }
 
 NS_IMPL_CYCLE_COLLECTION_INHERITED(RemoveStyleSheetTransaction,
                                    EditTransactionBase,
+                                   mEditorBase,
                                    mSheet)
 
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(RemoveStyleSheetTransaction)
 NS_INTERFACE_MAP_END_INHERITING(EditTransactionBase)
 
 NS_IMETHODIMP
 RemoveStyleSheetTransaction::DoTransaction()
 {
-  NS_ENSURE_TRUE(mSheet, NS_ERROR_NOT_INITIALIZED);
-
-  RemoveStyleSheet(mEditor, mSheet);
+  if (NS_WARN_IF(!mEditorBase) || NS_WARN_IF(!mSheet)) {
+    return NS_ERROR_NOT_INITIALIZED;
+  }
+  RemoveStyleSheet(*mEditorBase, mSheet);
   return NS_OK;
 }
 
 NS_IMETHODIMP
 RemoveStyleSheetTransaction::UndoTransaction()
 {
-  NS_ENSURE_TRUE(mSheet, NS_ERROR_NOT_INITIALIZED);
-
-  AddStyleSheet(mEditor, mSheet);
+  if (NS_WARN_IF(!mEditorBase) || NS_WARN_IF(!mSheet)) {
+    return NS_ERROR_NOT_INITIALIZED;
+  }
+  AddStyleSheet(*mEditorBase, mSheet);
   return NS_OK;
 }
 
 NS_IMETHODIMP
 RemoveStyleSheetTransaction::GetTxnDescription(nsAString& aString)
 {
   aString.AssignLiteral("RemoveStyleSheetTransaction");
   return NS_OK;
--- a/editor/libeditor/StyleSheetTransactions.h
+++ b/editor/libeditor/StyleSheetTransactions.h
@@ -27,17 +27,17 @@ public:
   NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(AddStyleSheetTransaction,
                                            EditTransactionBase)
   NS_IMETHOD QueryInterface(REFNSIID aIID, void** aInstancePtr) override;
 
   NS_DECL_EDITTRANSACTIONBASE
 
 protected:
   // The editor that created this transaction.
-  EditorBase& mEditor;
+  RefPtr<EditorBase> mEditorBase;
   // The style sheet to add.
   RefPtr<mozilla::StyleSheet> mSheet;
 };
 
 
 class RemoveStyleSheetTransaction final : public EditTransactionBase
 {
 public:
@@ -50,17 +50,17 @@ public:
   NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(RemoveStyleSheetTransaction,
                                            EditTransactionBase)
   NS_IMETHOD QueryInterface(REFNSIID aIID, void** aInstancePtr) override;
 
   NS_DECL_EDITTRANSACTIONBASE
 
 protected:
   // The editor that created this transaction.
-  EditorBase& mEditor;
+  RefPtr<EditorBase> mEditorBase;
   // The style sheet to remove.
   RefPtr<StyleSheet> mSheet;
 
 };
 
 } // namespace mozilla
 
 #endif // #ifndef StylesheetTransactions_h
new file mode 100644
--- /dev/null
+++ b/editor/libeditor/crashtests/1348851.html
@@ -0,0 +1,19 @@
+<!DOCTYPE>
+<html>
+<head>
+<meta charset="UTF-8">
+<script>
+function boom(){
+  document.designMode = "on";
+  document.execCommand("insertlinebreak");
+  document.designMode = "off";
+  document.designMode = "on";
+  document.execCommand("insertunorderedlist");
+}
+addEventListener("DOMContentLoaded", boom);
+</script>
+</head>
+<body style="display:flex;">
+<!--comment-->
+</body>
+</html>
--- a/editor/libeditor/crashtests/crashtests.list
+++ b/editor/libeditor/crashtests/crashtests.list
@@ -67,8 +67,9 @@ load 1134545.html
 load 1158452.html
 load 1158651.html
 load 1244894.xhtml
 load 1264921.html
 load 1272490.html
 load 1317704.html
 load 1317718.html
 load 1324505.html
+load 1348851.html
--- a/image/FrameAnimator.cpp
+++ b/image/FrameAnimator.cpp
@@ -137,20 +137,22 @@ AnimationState::LoopLength() const
 }
 
 
 ///////////////////////////////////////////////////////////////////////////////
 // FrameAnimator implementation.
 ///////////////////////////////////////////////////////////////////////////////
 
 Maybe<TimeStamp>
-FrameAnimator::GetCurrentImgFrameEndTime(AnimationState& aState) const
+FrameAnimator::GetCurrentImgFrameEndTime(AnimationState& aState,
+                                         DrawableSurface& aFrames) const
 {
   TimeStamp currentFrameTime = aState.mCurrentAnimationFrameTime;
-  Maybe<FrameTimeout> timeout = GetTimeoutForFrame(aState, aState.mCurrentAnimationFrameIndex);
+  Maybe<FrameTimeout> timeout =
+    GetTimeoutForFrame(aState, aFrames, aState.mCurrentAnimationFrameIndex);
 
   if (timeout.isNothing()) {
     MOZ_ASSERT(aState.GetHasBeenDecoded() && !aState.GetIsCurrentlyDecoded());
     return Nothing();
   }
 
   if (*timeout == FrameTimeout::Forever()) {
     // We need to return a sentinel value in this case, because our logic
@@ -166,17 +168,19 @@ FrameAnimator::GetCurrentImgFrameEndTime
   TimeDuration durationOfTimeout =
     TimeDuration::FromMilliseconds(double(timeout->AsMilliseconds()));
   TimeStamp currentFrameEndTime = currentFrameTime + durationOfTimeout;
 
   return Some(currentFrameEndTime);
 }
 
 RefreshResult
-FrameAnimator::AdvanceFrame(AnimationState& aState, TimeStamp aTime)
+FrameAnimator::AdvanceFrame(AnimationState& aState,
+                            DrawableSurface& aFrames,
+                            TimeStamp aTime)
 {
   NS_ASSERTION(aTime <= TimeStamp::Now(),
                "Given time appears to be in the future");
   PROFILER_LABEL_FUNC(js::ProfileEntry::Category::GRAPHICS);
 
   RefreshResult ret;
 
   // Determine what the next frame is, taking into account looping.
@@ -226,58 +230,58 @@ FrameAnimator::AdvanceFrame(AnimationSta
   }
 
   // There can be frames in the surface cache with index >= KnownFrameCount()
   // which GetRawFrame() can access because an async decoder has decoded them,
   // but which AnimationState doesn't know about yet because we haven't received
   // the appropriate notification on the main thread. Make sure we stay in sync
   // with AnimationState.
   MOZ_ASSERT(nextFrameIndex < aState.KnownFrameCount());
-  RawAccessFrameRef nextFrame = GetRawFrame(nextFrameIndex);
+  RawAccessFrameRef nextFrame = GetRawFrame(aFrames, nextFrameIndex);
 
   // We should always check to see if we have the next frame even if we have
   // previously finished decoding. If we needed to redecode (e.g. due to a draw
   // failure) we would have discarded all the old frames and may not yet have
   // the new ones.
   if (!nextFrame || !nextFrame->IsFinished()) {
     // Uh oh, the frame we want to show is currently being decoded (partial)
     // Wait until the next refresh driver tick and try again
     return ret;
   }
 
-  Maybe<FrameTimeout> nextFrameTimeout = GetTimeoutForFrame(aState, nextFrameIndex);
+  Maybe<FrameTimeout> nextFrameTimeout = GetTimeoutForFrame(aState, aFrames, nextFrameIndex);
   // GetTimeoutForFrame can only return none if frame doesn't exist,
   // but we just got it above.
   MOZ_ASSERT(nextFrameTimeout.isSome());
   if (*nextFrameTimeout == FrameTimeout::Forever()) {
     ret.mAnimationFinished = true;
   }
 
   if (nextFrameIndex == 0) {
     ret.mDirtyRect = aState.FirstFrameRefreshArea();
   } else {
     MOZ_ASSERT(nextFrameIndex == currentFrameIndex + 1);
 
     // Change frame
-    if (!DoBlend(&ret.mDirtyRect, currentFrameIndex, nextFrameIndex)) {
+    if (!DoBlend(aFrames, &ret.mDirtyRect, currentFrameIndex, nextFrameIndex)) {
       // something went wrong, move on to next
       NS_WARNING("FrameAnimator::AdvanceFrame(): Compositing of frame failed");
       nextFrame->SetCompositingFailed(true);
-      Maybe<TimeStamp> currentFrameEndTime = GetCurrentImgFrameEndTime(aState);
+      Maybe<TimeStamp> currentFrameEndTime = GetCurrentImgFrameEndTime(aState, aFrames);
       MOZ_ASSERT(currentFrameEndTime.isSome());
       aState.mCurrentAnimationFrameTime = *currentFrameEndTime;
       aState.mCurrentAnimationFrameIndex = nextFrameIndex;
 
       return ret;
     }
 
     nextFrame->SetCompositingFailed(false);
   }
 
-  Maybe<TimeStamp> currentFrameEndTime = GetCurrentImgFrameEndTime(aState);
+  Maybe<TimeStamp> currentFrameEndTime = GetCurrentImgFrameEndTime(aState, aFrames);
   MOZ_ASSERT(currentFrameEndTime.isSome());
   aState.mCurrentAnimationFrameTime = *currentFrameEndTime;
 
   // If we can get closer to the current time by a multiple of the image's loop
   // time, we should. We can only do this if we're done decoding; otherwise, we
   // don't know the full loop length, and LoopLength() will have to return
   // FrameTimeout::Forever().
   FrameTimeout loopTime = aState.LoopLength();
@@ -307,37 +311,57 @@ FrameAnimator::RequestRefresh(AnimationS
 {
   // By default, an empty RefreshResult.
   RefreshResult ret;
 
   if (aState.IsDiscarded()) {
     return ret;
   }
 
+  // Get the animation frames once now, and pass them down to callees because
+  // the surface could be discarded at anytime on a different thread. This is
+  // must easier to reason about then trying to write code that is safe to
+  // having the surface disappear at anytime.
+  LookupResult result =
+    SurfaceCache::Lookup(ImageKey(mImage),
+                         RasterSurfaceKey(mSize,
+                                          DefaultSurfaceFlags(),
+                                          PlaybackType::eAnimated));
+
+  if (!result) {
+    if (result.Type() == MatchType::NOT_FOUND) {
+      // No surface, and nothing pending, must have been discarded but
+      // we haven't been notified yet.
+      aState.SetDiscarded(true);
+    }
+    return ret;
+  }
+
   // only advance the frame if the current time is greater than or
   // equal to the current frame's end time.
-  Maybe<TimeStamp> currentFrameEndTime = GetCurrentImgFrameEndTime(aState);
+  Maybe<TimeStamp> currentFrameEndTime =
+    GetCurrentImgFrameEndTime(aState, result.Surface());
   if (currentFrameEndTime.isNothing()) {
     MOZ_ASSERT(gfxPrefs::ImageMemAnimatedDiscardable());
     MOZ_ASSERT(aState.GetHasBeenDecoded() && !aState.GetIsCurrentlyDecoded());
     MOZ_ASSERT(aState.mCompositedFrameInvalid);
     // Nothin